@contrast/esm-hooks 1.22.1 → 2.0.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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -12,31 +12,117 @@
12
12
  * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
13
  * way not consistent with the End User License Agreement.
14
14
  */
15
-
15
+ import fs from 'node:fs';
16
+ import os from 'node:os';
16
17
  import path from 'path';
17
- import parent from 'parent-package-json';
18
+ import M from 'module';
18
19
 
19
- export default function getType(filename) {
20
+ const isBuiltin = M.isBuiltin || function(pathname) {
21
+ if (pathname.startsWith('node:')) {
22
+ pathname = pathname.slice(5);
23
+ }
24
+ return M.builtinModules.includes(pathname);
25
+ };
20
26
 
21
- let pathname = filename.pathname || filename;
27
+ /**
28
+ *
29
+ * @param {string} path
30
+ * @returns {string}
31
+ */
32
+ export function fixPath(p) {
33
+ if (p.match(/^(\\|\/)[A-Za-z]:/)) {
34
+ p = p.slice(1);
35
+ }
36
+ return p;
37
+ }
22
38
 
23
- if (pathname.startsWith('node:')) {
39
+ export function getFileType(filename) {
40
+ let pathname = filename.pathname || filename;
41
+ // presumes 16.17+
42
+ if (isBuiltin(pathname)) {
24
43
  return 'builtin';
25
44
  }
45
+
26
46
  pathname = pathname.replace('file://', '');
27
47
 
48
+ // if the file extension specifies the type, there's no need to do extra
49
+ // IO.
50
+ const ext = path.extname(pathname);
51
+ if (ext === '.mjs') {
52
+ return 'module';
53
+ } else if (ext === '.cjs') {
54
+ return 'commonjs';
55
+ }
56
+
57
+ // Node assumes `commonjs ` if there's no `type` set in package.json
28
58
  let parentType = 'commonjs';
29
59
  try {
30
- parentType = parent(pathname).parse().type;
60
+ let pkg = parent(pathname);
61
+ if (pkg) {
62
+ pkg = pkg.parse();
63
+ if (pkg.type) {
64
+ parentType = pkg.type;
65
+ }
66
+ }
31
67
  } catch (err) {
32
- // Node assumes `commonjs ` if there's no `type` set in package.json
68
+ // not sure what errors can occur here. should consider logging.
33
69
  }
34
70
 
35
- const ext = path.extname(pathname);
36
- if (ext === '.mjs' || (ext === '.js' && parentType === 'module')) {
37
- return 'module';
38
- } else if (ext === '.cjs' || (ext === '.js' && parentType !== 'module')) {
39
- return 'commonjs';
71
+ if (ext === '.js') {
72
+ return parentType;
73
+ }
74
+
75
+ // should this assume commonjs?
76
+ return null;
77
+ }
78
+
79
+ function parent(startPath, ignore) {
80
+ startPath = path.resolve(fixPath(startPath) || process.cwd());
81
+ ignore = ignore || 0;
82
+
83
+ let searchPath = path.dirname(startPath);
84
+
85
+ let root = '/';
86
+ if (os.platform() === 'win32') {
87
+ root = path.parse(startPath).root;
40
88
  }
41
- return 'unknown';
89
+
90
+ let packageDotJsonContents;
91
+
92
+ // put a limit on this though i think that for(;;) will work fine.
93
+ for (let i = 100; i > 0; i -= 1) {
94
+ try {
95
+ const possible = path.join(searchPath, 'package.json');
96
+ packageDotJsonContents = fs.readFileSync(possible, 'utf8');
97
+ if (ignore > 0) {
98
+ ignore--;
99
+ } else {
100
+ break;
101
+ }
102
+ } catch (err) {
103
+ if (err.code !== 'ENOENT') {
104
+ // log?
105
+ }
106
+ }
107
+
108
+ // if root there is nothing more to check
109
+ if (searchPath === root) {
110
+ break;
111
+ }
112
+ searchPath = path.dirname(searchPath);
113
+ }
114
+
115
+ if (packageDotJsonContents) {
116
+ return {
117
+ read() {
118
+ return packageDotJsonContents;
119
+ },
120
+ parse() {
121
+ return JSON.parse(packageDotJsonContents);
122
+ },
123
+ path: searchPath,
124
+ };
125
+ }
126
+
127
+ return null;
42
128
  }
package/lib/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -13,75 +13,121 @@
13
13
  * way not consistent with the End User License Agreement.
14
14
  */
15
15
 
16
- import { promises as fs } from 'fs';
17
- import { fileURLToPath } from 'url';
18
- import getType from './get-file-type.mjs';
16
+ //
17
+ // this file supports agent/lib/esm-hooks.mjs.
18
+ //
19
19
 
20
- export default function (core) {
21
- core.esmHooks = {
22
- async getSource(url, context, defaultGetSource) {
23
- const filename = fileURLToPath(url);
24
- core.logger.trace('getSource %s', filename);
25
- return defaultGetSource(url, context, defaultGetSource);
26
- },
20
+ import { readdir } from 'node:fs/promises';
21
+ import path from 'node:path';
27
22
 
28
- async transformSource(source, context, defaultTransformSource) {
29
- let result;
30
- const filename = fileURLToPath(context.url);
31
- core.logger.trace('transformSource %s', filename);
32
- try {
33
- result = core.rewriter.rewrite(source.toString(), {
34
- filename,
35
- isModule: true,
36
- inject: true,
37
- wrap: false,
38
- });
39
- return { source: result.code };
40
- } catch (err) {
41
- core.logger.error(
42
- { err, result },
43
- 'Failed to compile rewritten code for %s. compiling original code.',
44
- filename,
45
- );
46
- return defaultTransformSource(source, context, defaultTransformSource);
47
- }
48
- },
23
+ const redirectPath = './redirects';
49
24
 
50
- async load(url, context, defaultLoad) {
51
- const type = getType(url);
25
+ // why do we need to fix paths? Because windows, node, and URLs don't get
26
+ // along very well.
27
+ //
28
+ // the load() hook receives an URL. When the URL is converted into an URL object,
29
+ // the pathname property will be '/C:/Users/.../redirects' on windows. And that
30
+ // doesn't start with a drive letter, so when fsp.readdir() gets it, node decides
31
+ // to add a drive letter: 'C:/C:/Users/.../redirects'. So we have to strip off the
32
+ // the leading / so the drive letter is recognized and another isn't added. it seems
33
+ // like a node bug (or possibly windows bug) but i don't have time to research the
34
+ // standards to figure out what URL.pathname should be in a windows file: context.
35
+ //
36
+ // maybe this should be in the load() hook?
37
+ //
52
38
 
53
- if (type === 'builtin' || type === 'unknown') {
54
- core.logger.debug(
55
- 'Skipping rewrite for %s module %s, loading original code',
56
- type,
57
- url
58
- );
59
- return defaultLoad(url, context, defaultLoad);
60
- }
39
+ import { fixPath, getFileType } from './get-file-type.mjs';
40
+
41
+ // useful for tests
42
+ export { makeMappings };
43
+
44
+ export default async function init(core) {
45
+ return {
46
+ mappings: await makeMappings(),
47
+ fixPath,
48
+ getFileType,
49
+ };
50
+ }
61
51
 
62
- const filename = fileURLToPath(url);
63
- core.logger.trace('load %s', filename);
52
+ async function makeMappings() {
53
+ /**
54
+ * @typedef { 'builtin' | 'commonjs' | 'module' } TargetType
55
+ * @typedef {string} RequireSpecifier
56
+ */
57
+ /**
58
+ * @type {Record<RequireSpecifier, {url: URL, target: TargetType}>}
59
+ */
60
+ const mappings = Object.create(null);
64
61
 
65
- let result;
66
- try {
67
- const source = await fs.readFile(filename, 'utf8');
68
- result = core.rewriter.rewrite(source, {
69
- filename,
70
- isModule: type !== 'commonjs',
71
- inject: true,
72
- wrap: false,
73
- });
74
- return { format: type, source: result.code, shortCircuit: true };
75
- } catch (err) {
76
- core.logger.error(
77
- { err, result },
78
- 'Failed to load rewritten code for %s. loading original code.',
79
- filename,
80
- );
81
- return defaultLoad(url, context, defaultLoad);
62
+ // the name of the directory is the format of the target being loaded. so "cjs"
63
+ // means the target is a commonjs module that will be loaded via require(),
64
+ // "builtin" means the target is a builtin, and "esm" (when it's added) will
65
+ // mean that the target is a native esm module.
66
+ //
67
+ // at this time, all "builtin" modules are "cjs" modules and can be required.
68
+ // and all "cjs" modules are commonjs modules that can be required. The reason
69
+ // they need to be here is that they might be loaded by the ESM loader in the
70
+ // background thread, so we need to redirect them to the commonjs loader.
71
+ //
72
+ // all files in the "redirects" directory are .mjs files.
73
+ //
74
+ // 'esm' does not currently have any redirect files; there is one example file
75
+ // for node-fetch, but it does not have the extension .mjs, so it won't be used.
76
+ // 'esm' will be needed when we have to patch an esm-native file.
77
+ for (const dir of ['builtin', 'cjs', 'esm']) {
78
+ // keep track of recursive calls to handle nested directories, e.g., fs/promises
79
+ const pathStack = [];
80
+
81
+ // get an absolute path because reading the redirect file is going to be executed
82
+ // in another context.
83
+ const p = path.join(redirectPath, dir);
84
+ const redirectDir = new URL(p, import.meta.url);
85
+ const dirpath = fixPath(redirectDir.pathname);
86
+
87
+ await recursiveReaddir(dirpath);
88
+
89
+ // eslint-disable-next-line no-inner-declarations
90
+ async function recursiveReaddir(dirpath) {
91
+ const dirents = await readdir(dirpath, { withFileTypes: true });
92
+ for (const dirent of dirents) {
93
+ if (dirent.isDirectory()) {
94
+ const { name: subdir } = dirent;
95
+ const subp = path.join(dirpath, subdir);
96
+ pathStack.push(subdir);
97
+ await recursiveReaddir(subp);
98
+ pathStack.pop();
99
+ continue;
100
+ }
101
+ if (!dirent.name.endsWith('.mjs')) {
102
+ continue;
103
+ }
104
+ // it a file that ends with .mjs, so it's a redirect file.
105
+ const redirectURL = new URL(path.join(p, ...pathStack, dirent.name), import.meta.url);
106
+
107
+ // all redirects point to .mjs files; the target property specifies the type of the
108
+ // the file that will be loaded by the .mjs. target. target is builtin, commonjs, or
109
+ // module.
110
+ // e.g., //'node-fetch': {url: new URL(`${p}/node-fetch.mjs`, import.meta.url), target: 'module'},
111
+ //
112
+ // there is a separate builtin directory because putting a colon in a filename doesn't work on windows. so
113
+ // that's why the mapping below adds the `node:` prefix.
114
+ let name = path.basename(dirent.name, '.mjs');
115
+ if (pathStack.length) {
116
+ name = `${pathStack.join('/')}/${name}`;
117
+ }
118
+ if (dir === 'builtin') {
119
+ mappings[name] = { url: redirectURL, target: 'builtin' };
120
+ mappings[`node:${name}`] = { url: redirectURL, target: 'builtin' };
121
+ } else if (dir === 'cjs') {
122
+ mappings[name] = { url: redirectURL, target: 'commonjs' };
123
+ } else if (dir === 'esm') {
124
+ mappings[name] = { url: redirectURL, target: 'module' };
125
+ } else {
126
+ throw new Error(`target type ${dir} not yet implemented`);
127
+ }
82
128
  }
83
129
  }
84
- };
130
+ }
85
131
 
86
- return core.esmHooks;
132
+ return mappings;
87
133
  }
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const cp = require('node:child_process');
18
+
19
+ export default cp;
20
+
21
+ export const {
22
+ _forkChild,
23
+ ChildProcess,
24
+ exec,
25
+ execFile,
26
+ execFileSync,
27
+ execSync,
28
+ fork,
29
+ spawn,
30
+ spawnSync,
31
+ } = cp;
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const promises = require('node:fs/promises');
18
+
19
+ export default promises;
20
+
21
+ export const {
22
+ access,
23
+ copyFile,
24
+ cp,
25
+ open,
26
+ opendir,
27
+ rename,
28
+ truncate,
29
+ rm,
30
+ rmdir,
31
+ mkdir,
32
+ readdir,
33
+ readlink,
34
+ symlink,
35
+ lstat,
36
+ stat,
37
+ statfs,
38
+ link,
39
+ unlink,
40
+ chmod,
41
+ lchmod,
42
+ lchown,
43
+ chown,
44
+ utimes,
45
+ lutimes,
46
+ realpath,
47
+ mkdtemp,
48
+ writeFile,
49
+ appendFile,
50
+ readFile,
51
+ watch,
52
+ constants,
53
+ } = promises;
@@ -0,0 +1,126 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const fs = require('node:fs');
18
+
19
+ export default fs;
20
+
21
+ export const {
22
+ appendFile,
23
+ appendFileSync,
24
+ access,
25
+ accessSync,
26
+ chown,
27
+ chownSync,
28
+ chmod,
29
+ chmodSync,
30
+ close,
31
+ closeSync,
32
+ copyFile,
33
+ copyFileSync,
34
+ cp,
35
+ cpSync,
36
+ createReadStream,
37
+ createWriteStream,
38
+ exists,
39
+ existsSync,
40
+ fchown,
41
+ fchownSync,
42
+ fchmod,
43
+ fchmodSync,
44
+ fdatasync,
45
+ fdatasyncSync,
46
+ fstat,
47
+ fstatSync,
48
+ fsync,
49
+ fsyncSync,
50
+ ftruncate,
51
+ ftruncateSync,
52
+ futimes,
53
+ futimesSync,
54
+ lchown,
55
+ lchownSync,
56
+ lchmod,
57
+ lchmodSync,
58
+ link,
59
+ linkSync,
60
+ lstat,
61
+ lstatSync,
62
+ lutimes,
63
+ lutimesSync,
64
+ mkdir,
65
+ mkdirSync,
66
+ mkdtemp,
67
+ mkdtempSync,
68
+ open,
69
+ openSync,
70
+ openAsBlob,
71
+ readdir,
72
+ readdirSync,
73
+ read,
74
+ readSync,
75
+ readv,
76
+ readvSync,
77
+ readFile,
78
+ readFileSync,
79
+ readlink,
80
+ readlinkSync,
81
+ realpath,
82
+ realpathSync,
83
+ rename,
84
+ renameSync,
85
+ rm,
86
+ rmSync,
87
+ rmdir,
88
+ rmdirSync,
89
+ stat,
90
+ statfs,
91
+ statSync,
92
+ statfsSync,
93
+ symlink,
94
+ symlinkSync,
95
+ truncate,
96
+ truncateSync,
97
+ unwatchFile,
98
+ unlink,
99
+ unlinkSync,
100
+ utimes,
101
+ utimesSync,
102
+ watch,
103
+ watchFile,
104
+ writeFile,
105
+ writeFileSync,
106
+ write,
107
+ writeSync,
108
+ writev,
109
+ writevSync,
110
+ Dirent,
111
+ Stats,
112
+ ReadStream,
113
+ WriteStream,
114
+ FileReadStream,
115
+ FileWriteStream,
116
+ _toUnixTimestamp,
117
+ Dir,
118
+ opendir,
119
+ opendirSync,
120
+ F_OK,
121
+ R_OK,
122
+ W_OK,
123
+ X_OK,
124
+ constants,
125
+ promises,
126
+ } = fs;
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const http = require('node:http');
18
+
19
+ export default http;
20
+
21
+ export const {
22
+ _connectionListener,
23
+ METHODS,
24
+ STATUS_CODES,
25
+ Agent,
26
+ ClientRequest,
27
+ IncomingMessage,
28
+ OutgoingMessage,
29
+ Server,
30
+ ServerResponse,
31
+ createServer,
32
+ validateHeaderName,
33
+ validateHeaderValue,
34
+ get,
35
+ request,
36
+ setMaxIdleHTTPParsers,
37
+ maxHeaderSize,
38
+ globalAgent,
39
+ } = http;
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright: 2024 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
+ const m = await import('node:module');
16
+ const require = m.createRequire(import.meta.url);
17
+ // does this get patched because it's being required?
18
+ const http2 = require('http2');
19
+
20
+ export default http2;
21
+
22
+ export const {
23
+ connect,
24
+ constants,
25
+ createServer,
26
+ createSecureServer,
27
+ getDefaultSettings,
28
+ getPackedSettings,
29
+ getUnpackedSettings,
30
+ sensitiveHeaders,
31
+ Http2ServerRequest,
32
+ Http2ServerResponse,
33
+ } = http2;
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright: 2024 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
+ const m = await import('node:module');
16
+ const require = m.createRequire(import.meta.url);
17
+ // does this get patched because it's being required?
18
+ const https = require('https');
19
+
20
+ export default https;
21
+
22
+ export const {
23
+ Agent,
24
+ globalAgent,
25
+ Server,
26
+ createServer,
27
+ get,
28
+ request,
29
+ } = https;
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const path = require('node:path/posix');
18
+
19
+ export default path;
20
+
21
+ export const {
22
+ resolve,
23
+ normalize,
24
+ isAbsolute,
25
+ join,
26
+ relative,
27
+ toNamespacedPath,
28
+ dirname,
29
+ basename,
30
+ extname,
31
+ format,
32
+ parse,
33
+ sep,
34
+ delimiter,
35
+ win32,
36
+ posix,
37
+ _makeLong,
38
+ } = path;