@contrast/rewriter 1.37.0 → 1.37.2

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright: 2025 Contrast Security, Inc
1
+ Copyright: 2026 Contrast Security, Inc
2
2
  Contact: support@contrastsecurity.com
3
3
  License: Commercial
4
4
 
package/lib/cache.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2025 Contrast Security, Inc
2
+ * Copyright: 2026 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -142,7 +142,7 @@ module.exports.Cache = class Cache {
142
142
  }
143
143
 
144
144
  /**
145
- * Looks up and returns the source map for a previously rewritten file.
145
+ * Asynchronously looks up and returns the source map for a previously rewritten file.
146
146
  * @param {string} filename
147
147
  * @returns {Promise<string | undefined>}
148
148
  */
@@ -164,6 +164,29 @@ module.exports.Cache = class Cache {
164
164
  }
165
165
  }
166
166
 
167
+
168
+ /**
169
+ * Synchronously looks up and returns the source map for a previously rewritten file.
170
+ * @param {string} filename
171
+ * @returns {string | undefined}
172
+ */
173
+ readMapSync(filename) {
174
+ const filenameCached = this.getCachedFilename(filename);
175
+ const sourceMap = `${filenameCached}.map`;
176
+ try {
177
+ return fs.readFileSync(sourceMap, 'utf8');
178
+ } catch (err) {
179
+ // @ts-expect-error ts treats errors poorly.
180
+ if (err.code !== 'ENOENT') {
181
+ this.logger.warn(
182
+ { err, filename, filenameCached, sourceMap },
183
+ 'An unexpected error occurred finding source map.'
184
+ );
185
+ }
186
+ return undefined;
187
+ }
188
+ }
189
+
167
190
  /**
168
191
  * Looks up and returns the string content of a previously rewritten file
169
192
  * synchronously. Used when we need to block on cache lookups.
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2025 Contrast Security, Inc
2
+ * Copyright: 2026 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -14,12 +14,18 @@
14
14
  */
15
15
  // @ts-check
16
16
  'use strict';
17
-
18
17
  const { arch, platform } = require('node:os');
19
- const path = require('node:path');
18
+ // @ts-expect-error
19
+ const { SourceMapConsumer: SourceMapConsumerSync } = require('@contrast/synchronous-source-maps');
20
20
  const { parseSync, transform, transformSync } = require('@swc/core');
21
+ // @ts-expect-error `@contrast/agent-swc-plugin` .d.ts file doesn't exist.
22
+ const { defaultRewriter, defaultUnwriter } = require('@contrast/agent-swc-plugin');
23
+ const { funcInfo } = require('@contrast/fn-inspect');
24
+ const { primordials } = require('@contrast/common');
21
25
  const { Cache } = require('./cache');
22
26
 
27
+ const MAGIC_COMMENT_PREFIX = '//# sourceMappingURL=';
28
+
23
29
  /**
24
30
  * @typedef {Object} Core
25
31
  * @prop {import('@contrast/common').AppInfo} appInfo
@@ -35,10 +41,6 @@ const { Cache } = require('./cache');
35
41
  * @prop {boolean=} inject if true, injects ContrastMethods on the global object
36
42
  * @prop {boolean=} minify if true, minifies the output when source maps are enabled
37
43
  */
38
-
39
- // @ts-expect-error `@contrast/agent-swc-plugin` .d.ts file doesn't exist.
40
- const { defaultRewriter, defaultUnwriter } = require('@contrast/agent-swc-plugin');
41
-
42
44
  class Rewriter {
43
45
  /**
44
46
  * @param {Core} core
@@ -66,11 +68,10 @@ class Rewriter {
66
68
  * @param {RewriteOpts} opts
67
69
  * @returns {import('@swc/core').Options}
68
70
  */
69
- rewriteConfig(opts) {
71
+ swcOptions(opts) {
70
72
  const nodeCfg = this.core.config.agent.node;
71
73
  const sourceMaps = nodeCfg.source_maps.enable;
72
74
  const minify = opts.minify && nodeCfg.source_maps.enable && nodeCfg.rewrite.minify;
73
-
74
75
  return {
75
76
  swcrc: false,
76
77
  filename: opts.filename,
@@ -85,7 +86,7 @@ class Rewriter {
85
86
  assess: this.modes.has('assess'),
86
87
  inject: opts.inject,
87
88
  }]],
88
- cacheRoot: path.join(nodeCfg.rewrite.cache.path, '.swc'),
89
+ cacheRoot: primordials.PathJoin(nodeCfg.rewrite.cache.path, '.swc'),
89
90
  },
90
91
  parser: {
91
92
  syntax: 'ecmascript',
@@ -101,6 +102,45 @@ class Rewriter {
101
102
  };
102
103
  }
103
104
 
105
+ /**
106
+ * Make all of the source map paths absolute.
107
+ * Mutates Output result:
108
+ * - magic sourceMappingURL comment in result.code
109
+ * - result.map.sources array
110
+ * @param {RewriteOpts} rewriteOpts
111
+ * @param {import('@swc/core').Output} result
112
+ */
113
+ adjustMap(rewriteOpts, result) {
114
+ if (rewriteOpts.filename && result.map) {
115
+ const map = JSON.parse(result.map);
116
+ const cachedFilename = this.cache.getCachedFilename(rewriteOpts.filename);
117
+ const absoluteCachedMapName = `${cachedFilename}.map`;
118
+ const origMapURL = `${primordials.PathBasename(rewriteOpts.filename)}.map`;
119
+ const comment = `${MAGIC_COMMENT_PREFIX}${origMapURL}`;
120
+
121
+ // 1) magic comment
122
+ // https://github.com/nodejs/node/blob/main/lib/internal/source_map/source_map_cache.js#L134-L136
123
+ const i = result.code.lastIndexOf(comment);
124
+ if (i >= 0) {
125
+ let code = primordials.StringPrototypeSubstr.call(result.code, 0, i);
126
+ code += `${MAGIC_COMMENT_PREFIX}${absoluteCachedMapName}`;
127
+ result.code = code;
128
+ }
129
+ // 2) map.sources array
130
+ for (let i = 0; i < map.sources?.length; i++) {
131
+ const resolved = primordials.PathResolve(rewriteOpts.filename, '..', map.sources[i]);
132
+ map.sources[i] = resolved;
133
+ }
134
+ // 3) map.file
135
+ if (map.file) {
136
+ map.file = cachedFilename;
137
+ }
138
+
139
+ // reasign modified map
140
+ result.map = primordials.JSONStringify(map);
141
+ }
142
+ }
143
+
104
144
  /**
105
145
  * Rewrites the provided source code string asynchronously. this is used in an ESM
106
146
  * context. CJS cannot use this because `require` is synchronous.
@@ -111,7 +151,10 @@ class Rewriter {
111
151
  */
112
152
  async rewrite(content, opts = {}) {
113
153
  this.logger.trace({ opts }, 'rewriting %s', opts.filename);
114
- return transform(content, this.rewriteConfig(opts));
154
+ const rewriteOpts = this.swcOptions(opts);
155
+ const ret = await transform(content, rewriteOpts);
156
+ this.adjustMap(rewriteOpts, ret);
157
+ return ret;
115
158
  }
116
159
 
117
160
  /**
@@ -125,7 +168,10 @@ class Rewriter {
125
168
  */
126
169
  rewriteSync(content, opts = {}) {
127
170
  this.logger.trace({ opts }, 'rewriting %s', opts.filename);
128
- return transformSync(content, this.rewriteConfig(opts));
171
+ const rewriteOpts = this.swcOptions(opts);
172
+ const result = transformSync(content, rewriteOpts);
173
+ this.adjustMap(rewriteOpts, result);
174
+ return result;
129
175
  }
130
176
 
131
177
  /**
@@ -149,6 +195,44 @@ class Rewriter {
149
195
  sourceMaps: false,
150
196
  }).code;
151
197
  }
198
+
199
+ /**
200
+ * TODO implement for async scenarios using newer versions of source-map
201
+ * @param {function} func
202
+ */
203
+ async funcInfo(func) {
204
+ throw new Error('funcInfo is not implemented');
205
+ }
206
+
207
+ /**
208
+ * @param {function} func
209
+ */
210
+ funcInfoSync(func) {
211
+ /** @type any */
212
+ const fnInfo = funcInfo(func);
213
+
214
+ if (
215
+ !fnInfo?.file ||
216
+ !Number.isFinite(fnInfo.lineNumber) ||
217
+ !Number.isFinite(fnInfo.column) ||
218
+ !this.core.config.agent.node.source_maps.enable
219
+ ) return fnInfo;
220
+
221
+ const map = this.cache.readMapSync(fnInfo.file);
222
+ if (!map) return fnInfo;
223
+
224
+ const orig = new SourceMapConsumerSync(map).originalPositionFor({
225
+ line: fnInfo.lineNumber + 1,
226
+ column: fnInfo.column,
227
+ });
228
+ if (orig) {
229
+ fnInfo.file = orig.source ? primordials.PathResolve(fnInfo.file, '..', orig.source) : fnInfo.file;
230
+ fnInfo.lineNumber = orig.line;
231
+ fnInfo.column = orig.column;
232
+ }
233
+
234
+ return fnInfo;
235
+ }
152
236
  }
153
237
 
154
238
  /**
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2025 Contrast Security, Inc
2
+ * Copyright: 2026 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/rewriter",
3
- "version": "1.37.0",
3
+ "version": "1.37.2",
4
4
  "description": "A transpilation tool mainly used for instrumentation",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -21,10 +21,12 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@contrast/agent-swc-plugin": "3.2.0",
24
- "@contrast/common": "1.39.0",
25
- "@contrast/config": "1.55.0",
26
- "@contrast/core": "1.60.0",
27
- "@contrast/logger": "1.33.0",
24
+ "@contrast/common": "1.39.1",
25
+ "@contrast/config": "1.55.1",
26
+ "@contrast/core": "1.60.1",
27
+ "@contrast/fn-inspect": "^5.0.2",
28
+ "@contrast/logger": "1.33.1",
29
+ "@contrast/synchronous-source-maps": "^1.1.5",
28
30
  "@swc/core": "1.13.3"
29
31
  }
30
32
  }