@atlaspack/core 2.30.2 → 2.31.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 (41) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/Atlaspack.js +2 -0
  3. package/dist/Transformation.js +24 -1
  4. package/dist/atlaspack-v3/AtlaspackV3.js +3 -0
  5. package/dist/atlaspack-v3/NapiWorkerPool.js +10 -0
  6. package/dist/atlaspack-v3/worker/compat/plugin-config.js +10 -22
  7. package/dist/atlaspack-v3/worker/compat/plugin-options.js +15 -0
  8. package/dist/atlaspack-v3/worker/side-effect-detector.js +243 -0
  9. package/dist/atlaspack-v3/worker/worker.js +140 -66
  10. package/dist/requests/ConfigRequest.js +24 -0
  11. package/lib/Atlaspack.js +5 -1
  12. package/lib/Transformation.js +31 -1
  13. package/lib/atlaspack-v3/AtlaspackV3.js +3 -0
  14. package/lib/atlaspack-v3/NapiWorkerPool.js +10 -0
  15. package/lib/atlaspack-v3/worker/compat/plugin-config.js +8 -27
  16. package/lib/atlaspack-v3/worker/compat/plugin-options.js +15 -0
  17. package/lib/atlaspack-v3/worker/side-effect-detector.js +215 -0
  18. package/lib/atlaspack-v3/worker/worker.js +152 -72
  19. package/lib/requests/ConfigRequest.js +25 -0
  20. package/lib/types/InternalConfig.d.ts +1 -2
  21. package/lib/types/atlaspack-v3/AtlaspackV3.d.ts +2 -1
  22. package/lib/types/atlaspack-v3/NapiWorkerPool.d.ts +1 -0
  23. package/lib/types/atlaspack-v3/index.d.ts +1 -0
  24. package/lib/types/atlaspack-v3/worker/compat/plugin-config.d.ts +3 -11
  25. package/lib/types/atlaspack-v3/worker/compat/plugin-options.d.ts +1 -0
  26. package/lib/types/atlaspack-v3/worker/side-effect-detector.d.ts +76 -0
  27. package/lib/types/atlaspack-v3/worker/worker.d.ts +26 -6
  28. package/lib/types/requests/ConfigRequest.d.ts +9 -1
  29. package/package.json +14 -14
  30. package/src/Atlaspack.ts +2 -0
  31. package/src/InternalConfig.ts +1 -1
  32. package/src/Transformation.ts +37 -2
  33. package/src/atlaspack-v3/AtlaspackV3.ts +8 -0
  34. package/src/atlaspack-v3/NapiWorkerPool.ts +17 -0
  35. package/src/atlaspack-v3/index.ts +1 -0
  36. package/src/atlaspack-v3/worker/compat/plugin-config.ts +8 -40
  37. package/src/atlaspack-v3/worker/compat/plugin-options.ts +15 -0
  38. package/src/atlaspack-v3/worker/side-effect-detector.ts +298 -0
  39. package/src/atlaspack-v3/worker/worker.ts +288 -172
  40. package/src/requests/ConfigRequest.ts +39 -0
  41. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @atlaspack/core
2
2
 
3
+ ## 2.31.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#934](https://github.com/atlassian-labs/atlaspack/pull/934) [`02cc8b3`](https://github.com/atlassian-labs/atlaspack/commit/02cc8b32c06ca6b51806b33f6f707ca06e55e957) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Add experimental native persistent cache for Atlaspack V3.
8
+
9
+ - [#934](https://github.com/atlassian-labs/atlaspack/pull/934) [`02cc8b3`](https://github.com/atlassian-labs/atlaspack/commit/02cc8b32c06ca6b51806b33f6f707ca06e55e957) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Add new Transformer `setup` method and deprecate `loadConfig`.
10
+
11
+ Atlaspack is moving to a pure Transformer model to improve caching performance and consistency.
12
+ The old `loadConfig` method which ran once per Asset goes against this behaviour is now deprecated.
13
+ The new `setup` method runs once per Transformer instance, allowing for better caching and performance optimizations.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`515149d`](https://github.com/atlassian-labs/atlaspack/commit/515149d0a0767d844af803efdc611646780ad0fe), [`02cc8b3`](https://github.com/atlassian-labs/atlaspack/commit/02cc8b32c06ca6b51806b33f6f707ca06e55e957), [`02cc8b3`](https://github.com/atlassian-labs/atlaspack/commit/02cc8b32c06ca6b51806b33f6f707ca06e55e957)]:
18
+ - @atlaspack/rust@3.16.0
19
+ - @atlaspack/feature-flags@2.27.5
20
+ - @atlaspack/cache@3.2.41
21
+ - @atlaspack/fs@2.15.41
22
+ - @atlaspack/logger@2.14.38
23
+ - @atlaspack/source-map@3.2.1
24
+ - @atlaspack/utils@3.2.7
25
+ - @atlaspack/graph@3.6.8
26
+ - @atlaspack/plugin@2.14.46
27
+ - @atlaspack/profiler@2.15.7
28
+ - @atlaspack/types@2.15.36
29
+ - @atlaspack/workers@2.14.46
30
+ - @atlaspack/package-manager@2.14.46
31
+
3
32
  ## 2.30.2
4
33
 
5
34
  ### Patch Changes
package/dist/Atlaspack.js CHANGED
@@ -430,6 +430,7 @@ class Atlaspack {
430
430
  return result;
431
431
  },
432
432
  unstable_requestStats: __classPrivateFieldGet(this, _Atlaspack_requestTracker, "f").flushStats(),
433
+ nativeCacheStats: await this.rustAtlaspack?.completeCacheSession(),
433
434
  scopeHoistingStats,
434
435
  };
435
436
  // @ts-expect-error TS2345
@@ -452,6 +453,7 @@ class Atlaspack {
452
453
  type: 'buildFailure',
453
454
  diagnostics: Array.isArray(diagnostic) ? diagnostic : [diagnostic],
454
455
  unstable_requestStats: __classPrivateFieldGet(this, _Atlaspack_requestTracker, "f").flushStats(),
456
+ nativeCacheStats: await this.rustAtlaspack?.completeCacheSession(),
455
457
  };
456
458
  // @ts-expect-error TS2345
457
459
  await __classPrivateFieldGet(this, _Atlaspack_reporterRunner, "f").report(event);
@@ -60,6 +60,10 @@ const assert_1 = __importDefault(require("assert"));
60
60
  const profiler_1 = require("@atlaspack/profiler");
61
61
  const source_map_1 = __importDefault(require("@atlaspack/source-map"));
62
62
  const feature_flags_1 = require("@atlaspack/feature-flags");
63
+ const build_cache_1 = require("@atlaspack/build-cache");
64
+ // Global setup config are not file-specific, so we only need to
65
+ // load them once per build.
66
+ const setupConfig = (0, build_cache_1.createBuildCache)();
63
67
  class Transformation {
64
68
  constructor({ request, options, config, workerApi }) {
65
69
  this.configs = new Map();
@@ -357,11 +361,30 @@ class Transformation {
357
361
  return nextPipeline;
358
362
  }
359
363
  async loadTransformerConfig(transformer, isSource) {
364
+ // Only load setup config for a transformer once per build.
365
+ let config = setupConfig.get(transformer.name);
366
+ if (config == null && transformer.plugin.setup != null) {
367
+ config = (0, InternalConfig_1.createConfig)({
368
+ plugin: transformer.name,
369
+ searchPath: (0, projectPath_1.toProjectPathUnsafe)('index'),
370
+ // Consider project setup config as source
371
+ isSource: true,
372
+ });
373
+ await (0, ConfigRequest_1.loadPluginSetup)(transformer.name, transformer.plugin.setup, config, this.options);
374
+ setupConfig.set(transformer.name, config);
375
+ }
376
+ if (config != null) {
377
+ for (let devDep of config.devDeps) {
378
+ await this.addDevDependency(devDep);
379
+ }
380
+ // `loadConfig` is not called for setup configs
381
+ return config;
382
+ }
360
383
  let loadConfig = transformer.plugin.loadConfig;
361
384
  if (!loadConfig) {
362
385
  return;
363
386
  }
364
- let config = (0, InternalConfig_1.createConfig)({
387
+ config = (0, InternalConfig_1.createConfig)({
365
388
  plugin: transformer.name,
366
389
  isSource,
367
390
  // @ts-expect-error TS2322
@@ -59,5 +59,8 @@ class AtlaspackV3 {
59
59
  }
60
60
  return needsRebuild;
61
61
  }
62
+ async completeCacheSession() {
63
+ return (await (0, rust_1.atlaspackNapiCompleteSession)(this._atlaspack_napi));
64
+ }
62
65
  }
63
66
  exports.AtlaspackV3 = AtlaspackV3;
@@ -45,6 +45,16 @@ class NapiWorkerPool {
45
45
  __classPrivateFieldGet(this, _NapiWorkerPool_napiWorkers, "f").push(new Promise((res) => worker.once('message', res)));
46
46
  }
47
47
  }
48
+ clearAllWorkerState() {
49
+ return Promise.all(__classPrivateFieldGet(this, _NapiWorkerPool_workers, "f").map((worker) => new Promise((res) => {
50
+ worker.postMessage('clearState');
51
+ worker.once('message', (message) => {
52
+ if (message == 'stateCleared') {
53
+ res();
54
+ }
55
+ });
56
+ })));
57
+ }
48
58
  workerCount() {
49
59
  return __classPrivateFieldGet(this, _NapiWorkerPool_workerCount, "f");
50
60
  }
@@ -13,32 +13,20 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- var _PluginConfig_projectRoot, _PluginConfig_inner;
16
+ var _PluginConfig_inner;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.PluginConfig = void 0;
19
19
  const Config_1 = __importDefault(require("../../../public/Config"));
20
+ const InternalConfig_1 = require("../../../InternalConfig");
20
21
  class PluginConfig {
21
- constructor({ env, isSource, searchPath, projectRoot, fs, packageManager, }) {
22
- // @ts-expect-error TS2564
23
- _PluginConfig_projectRoot.set(this, void 0);
22
+ constructor(configOpts, options) {
24
23
  _PluginConfig_inner.set(this, void 0);
25
- this.env = env;
26
- this.isSource = isSource;
27
- this.searchPath = searchPath;
28
- __classPrivateFieldSet(this, _PluginConfig_inner, new Config_1.default(
29
- // @ts-expect-error TS2345
30
- {
31
- invalidateOnConfigKeyChange: [],
32
- invalidateOnFileCreate: [],
33
- invalidateOnFileChange: new Set(),
34
- devDeps: [],
35
- searchPath: searchPath.replace(projectRoot + '/', ''),
36
- }, {
37
- projectRoot,
38
- inputFS: fs,
39
- outputFS: fs,
40
- packageManager,
41
- }), "f");
24
+ let internalConfig = (0, InternalConfig_1.createConfig)(configOpts);
25
+ this.isSource = internalConfig.isSource;
26
+ this.searchPath = internalConfig.searchPath;
27
+ // @ts-expect-error TS2564
28
+ this.env = internalConfig.env;
29
+ __classPrivateFieldSet(this, _PluginConfig_inner, new Config_1.default(internalConfig, options), "f");
42
30
  }
43
31
  // eslint-disable-next-line no-unused-vars
44
32
  invalidateOnFileChange(filePath) { }
@@ -73,4 +61,4 @@ class PluginConfig {
73
61
  }
74
62
  }
75
63
  exports.PluginConfig = PluginConfig;
76
- _PluginConfig_projectRoot = new WeakMap(), _PluginConfig_inner = new WeakMap();
64
+ _PluginConfig_inner = new WeakMap();
@@ -25,18 +25,21 @@ class PluginOptions {
25
25
  if (!('projectRoot' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
26
26
  throw new Error('PluginOptions.projectRoot');
27
27
  }
28
+ this.used = true;
28
29
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").projectRoot;
29
30
  }
30
31
  get packageManager() {
31
32
  if (!('packageManager' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
32
33
  throw new Error('PluginOptions.packageManager');
33
34
  }
35
+ this.used = true;
34
36
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").packageManager;
35
37
  }
36
38
  get mode() {
37
39
  if (!('mode' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
38
40
  throw new Error('PluginOptions.mode');
39
41
  }
42
+ this.used = true;
40
43
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").mode;
41
44
  }
42
45
  get parcelVersion() {
@@ -44,30 +47,35 @@ class PluginOptions {
44
47
  return 'UNKNOWN VERSION';
45
48
  // throw new Error('PluginOptions.parcelVersion');
46
49
  }
50
+ this.used = true;
47
51
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").parcelVersion;
48
52
  }
49
53
  get hmrOptions() {
50
54
  if (!('hmrOptions' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
51
55
  throw new Error('PluginOptions.hmrOptions');
52
56
  }
57
+ this.used = true;
53
58
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").hmrOptions;
54
59
  }
55
60
  get serveOptions() {
56
61
  if (!('serveOptions' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
57
62
  throw new Error('PluginOptions.serveOptions');
58
63
  }
64
+ this.used = true;
59
65
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").serveOptions;
60
66
  }
61
67
  get shouldBuildLazily() {
62
68
  if (!('shouldBuildLazily' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
63
69
  throw new Error('PluginOptions.shouldBuildLazily');
64
70
  }
71
+ this.used = true;
65
72
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").shouldBuildLazily;
66
73
  }
67
74
  get shouldAutoInstall() {
68
75
  if (!('shouldAutoInstall' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
69
76
  throw new Error('PluginOptions.shouldAutoInstall');
70
77
  }
78
+ this.used = true;
71
79
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").shouldAutoInstall;
72
80
  }
73
81
  get logLevel() {
@@ -80,40 +88,47 @@ class PluginOptions {
80
88
  if (!('cacheDir' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
81
89
  throw new Error('PluginOptions.cacheDir');
82
90
  }
91
+ this.used = true;
83
92
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").cacheDir;
84
93
  }
85
94
  get inputFS() {
86
95
  if (!('inputFS' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
87
96
  throw new Error('PluginOptions.inputFS');
88
97
  }
98
+ this.used = true;
89
99
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").inputFS;
90
100
  }
91
101
  get outputFS() {
92
102
  if (!('outputFS' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
93
103
  throw new Error('PluginOptions.outputFS');
94
104
  }
105
+ this.used = true;
95
106
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").outputFS;
96
107
  }
97
108
  get instanceId() {
98
109
  if (!('instanceId' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
99
110
  throw new Error('PluginOptions.instanceId');
100
111
  }
112
+ this.used = true;
101
113
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").instanceId;
102
114
  }
103
115
  get detailedReport() {
104
116
  if (!('detailedReport' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
105
117
  throw new Error('PluginOptions.detailedReport');
106
118
  }
119
+ this.used = true;
107
120
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").detailedReport;
108
121
  }
109
122
  get featureFlags() {
110
123
  if (!('featureFlags' in __classPrivateFieldGet(this, _PluginOptions_options, "f"))) {
111
124
  throw new Error('PluginOptions.featureFlags');
112
125
  }
126
+ this.used = true;
113
127
  return __classPrivateFieldGet(this, _PluginOptions_options, "f").featureFlags;
114
128
  }
115
129
  constructor(options) {
116
130
  _PluginOptions_options.set(this, void 0);
131
+ this.used = false;
117
132
  // @ts-expect-error TS2322
118
133
  __classPrivateFieldSet(this, _PluginOptions_options, options, "f");
119
134
  }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultDetector = exports.SideEffectDetector = void 0;
4
+ const async_hooks_1 = require("async_hooks");
5
+ /**
6
+ * Side effect detector using AsyncLocalStorage to track filesystem and environment variable
7
+ * access across concurrent async operations in a Node.js worker thread.
8
+ *
9
+ * Usage:
10
+ * const detector = new SideEffectDetector();
11
+ * detector.install();
12
+ *
13
+ * const [result, sideEffects] = await detector.monitorSideEffects(async () => {
14
+ * return await someOperation();
15
+ * });
16
+ *
17
+ * console.log(sideEffects.fsUsage); // Array of filesystem accesses
18
+ * console.log(sideEffects.envUsage); // Array of environment variable accesses
19
+ */
20
+ class SideEffectDetector {
21
+ constructor() {
22
+ this.asyncStorage = new async_hooks_1.AsyncLocalStorage();
23
+ this.patchesInstalled = false;
24
+ this.originalMethods = {};
25
+ }
26
+ /**
27
+ * Install global patches for filesystem and environment variable monitoring.
28
+ * This should be called once when the worker starts up.
29
+ */
30
+ install() {
31
+ if (this.patchesInstalled) {
32
+ return;
33
+ }
34
+ this._patchFilesystem();
35
+ this._patchProcessEnv();
36
+ this.patchesInstalled = true;
37
+ }
38
+ /**
39
+ * Monitor side effects for an async operation.
40
+ *
41
+ * @param {Function} fn - Async function to monitor
42
+ * @param {Object} options - Optional configuration
43
+ * @param {string} options.label - Optional label for debugging
44
+ * @returns {Promise<[any, SideEffects]>} Tuple of [result, sideEffects]
45
+ */
46
+ monitorSideEffects(packageName, fn) {
47
+ if (!this.patchesInstalled) {
48
+ throw new Error('SideEffectDetector: install() must be called before monitorSideEffects()');
49
+ }
50
+ const context = {
51
+ fsUsage: [],
52
+ envUsage: {
53
+ vars: new Set(),
54
+ didEnumerate: false,
55
+ },
56
+ packageName: packageName,
57
+ };
58
+ return this.asyncStorage.run(context, async () => {
59
+ const result = await fn();
60
+ return [result, context];
61
+ });
62
+ }
63
+ /**
64
+ * Get the current monitoring context, if any.
65
+ * Useful for debugging or custom instrumentation.
66
+ *
67
+ * @returns {Object|null} Current context or null if not monitoring
68
+ */
69
+ getCurrentContext() {
70
+ return this.asyncStorage.getStore() || null;
71
+ }
72
+ /**
73
+ * Check if currently monitoring side effects.
74
+ *
75
+ * @returns {boolean}
76
+ */
77
+ isMonitoring() {
78
+ return this.asyncStorage.getStore() !== undefined;
79
+ }
80
+ /**
81
+ * Patch filesystem methods to record access.
82
+ * @private
83
+ */
84
+ _patchFilesystem() {
85
+ // Inline require this to avoid babel transformer issue
86
+ const fs = require('fs');
87
+ const methodsToPatch = [
88
+ // Sync methods
89
+ 'readFileSync',
90
+ 'writeFileSync',
91
+ 'appendFileSync',
92
+ 'existsSync',
93
+ 'statSync',
94
+ 'lstatSync',
95
+ 'readdirSync',
96
+ 'mkdirSync',
97
+ 'rmdirSync',
98
+ 'unlinkSync',
99
+ 'copyFileSync',
100
+ 'renameSync',
101
+ 'chmodSync',
102
+ 'chownSync',
103
+ // Async methods
104
+ 'readFile',
105
+ 'writeFile',
106
+ 'appendFile',
107
+ 'stat',
108
+ 'lstat',
109
+ 'readdir',
110
+ 'mkdir',
111
+ 'rmdir',
112
+ 'unlink',
113
+ 'copyFile',
114
+ 'rename',
115
+ 'chmod',
116
+ 'chown',
117
+ ];
118
+ methodsToPatch.forEach((method) => {
119
+ if (typeof fs[method] === 'function') {
120
+ this.originalMethods[method] = fs[method];
121
+ const self = this;
122
+ // @ts-expect-error Dynamic method patching
123
+ fs[method] = function (path, ...args) {
124
+ // Record filesystem access in current context
125
+ const context = self.asyncStorage.getStore();
126
+ if (context) {
127
+ context.fsUsage.push({
128
+ method,
129
+ path: typeof path === 'string' ? path : path?.toString(),
130
+ });
131
+ }
132
+ return self.originalMethods[method].call(this, path, ...args);
133
+ };
134
+ }
135
+ });
136
+ // Handle fs.promises methods
137
+ if (fs.promises) {
138
+ const promiseMethodsToPatch = [
139
+ 'readFile',
140
+ 'writeFile',
141
+ 'appendFile',
142
+ 'stat',
143
+ 'lstat',
144
+ 'readdir',
145
+ 'mkdir',
146
+ 'rmdir',
147
+ 'unlink',
148
+ 'copyFile',
149
+ 'rename',
150
+ 'chmod',
151
+ 'chown',
152
+ ];
153
+ const promises = fs.promises;
154
+ promiseMethodsToPatch.forEach((method) => {
155
+ if (typeof promises[method] === 'function') {
156
+ const originalKey = `promises_${method}`;
157
+ this.originalMethods[originalKey] = promises[method];
158
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
159
+ const self = this;
160
+ promises[method] = function (path, ...args) {
161
+ const context = self.asyncStorage.getStore();
162
+ if (context) {
163
+ context.fsUsage.push({
164
+ method: `promises.${method}`,
165
+ path: typeof path === 'string' ? path : String(path),
166
+ });
167
+ }
168
+ return self.originalMethods[originalKey].call(this, path, ...args);
169
+ };
170
+ }
171
+ });
172
+ }
173
+ }
174
+ /**
175
+ * Patch process.env to record environment variable access.
176
+ * @private
177
+ */
178
+ _patchProcessEnv() {
179
+ if (this.originalMethods.processEnv) {
180
+ return; // Already patched
181
+ }
182
+ this.originalMethods.processEnv = process.env;
183
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
184
+ const self = this;
185
+ // The following environment variables are allowed to be accessed by transformers
186
+ const allowedVars = new Set([
187
+ 'ATLASPACK_ENABLE_SENTRY',
188
+ // TODO we should also add the other atlaspack env vars here
189
+ 'NODE_V8_COVERAGE',
190
+ 'VSCODE_INSPECTOR_OPTIONS',
191
+ 'NODE_INSPECTOR_IPC',
192
+ 'FORCE_COLOR',
193
+ 'NO_COLOR',
194
+ 'TTY',
195
+ ]);
196
+ // Create a proxy that intercepts property access
197
+ process.env = new Proxy(this.originalMethods.processEnv, {
198
+ get(target, property) {
199
+ const context = self.asyncStorage.getStore();
200
+ if (context && typeof property === 'string') {
201
+ // Only record if this is a real environment variable access
202
+ // (not internal properties like 'constructor', 'valueOf', etc.)
203
+ if (!allowedVars.has(property) &&
204
+ (property in target || !property.startsWith('_'))) {
205
+ context.envUsage.vars.add(property);
206
+ }
207
+ }
208
+ return target[property];
209
+ },
210
+ set(target, property, value) {
211
+ const context = self.asyncStorage.getStore();
212
+ if (context && typeof property === 'string') {
213
+ if (!allowedVars.has(property) && property in target) {
214
+ context.envUsage.vars.add(property);
215
+ }
216
+ }
217
+ target[property] = value;
218
+ return true;
219
+ },
220
+ has(target, property) {
221
+ const context = self.asyncStorage.getStore();
222
+ if (context && typeof property === 'string') {
223
+ if (!allowedVars.has(property) && property in target) {
224
+ context.envUsage.vars.add(property);
225
+ }
226
+ }
227
+ return property in target;
228
+ },
229
+ ownKeys(target) {
230
+ const context = self.asyncStorage.getStore();
231
+ if (context) {
232
+ context.envUsage.didEnumerate = true;
233
+ }
234
+ return Object.keys(target);
235
+ },
236
+ });
237
+ }
238
+ }
239
+ exports.SideEffectDetector = SideEffectDetector;
240
+ /**
241
+ * Default instance for convenience. Most workers will only need one detector.
242
+ */
243
+ exports.defaultDetector = new SideEffectDetector();