[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[v8] Update for ESM monkeypatch + ESM file structure #10046

Description

@AbhiPrasad

We’re going to be adding proper support for ESM in the Sentry repo.

Requirement:

  • There is no require in ESM bundles at all
  • ESM files have .mjs file extension
  • CJS files have .cjs file extension
### Tasks
- [ ] https://github.com/getsentry/sentry-javascript/pull/10069
- [x] (pre-v8) Figure out how to use `dynamicRequire` with ESM
- [x] (pre-v8) Remove usage of `require.main.filename` for module name resolution #10061
- [x] (v8) Vendor `https-proxy-agent` which is cjs only #10088
- [x] (v8) Change build to output `mjs`/`cjs` for esm/cjs
- [x] (v8) Remove conditional import of `worker_threads` #10791
- [x] (v8) Update `inspector.d.ts` to work with esm and cjs
- [x] (v8) Remove require call in `require('inspector')` in `LocalVariables` integration
- [x] (v8) Update Rollup
- [ ] https://github.com/getsentry/sentry-javascript/pull/10928
- [ ] https://github.com/getsentry/sentry-javascript/issues/11066
- [ ] https://github.com/getsentry/sentry-javascript/issues/11067

Read below for some justification and information

Module Customization Hooks for Monkey Patching

For ESM bundles on server-side, we’ll require a minimum Node version of 18.6.0 or 20.6.0 or higher. Otherwise we’ll support CJS bundles for Node 14+ generally. This is because we want access to Node customization hooks, which allows us to add monkeypatching for esm modules programatically (doesn't require command-line loader).

We can take some inspiration here from https://github.com/DataDog/import-in-the-middle

import{register}from'node:module';register('./sentry-patch.mjs',import.meta.url);

Registering hooks to affect an import still needs to happen before the import is resolved, so there are ordering issues, but I think we can work around this by recommending that Sentry.init is called as soon as possible, and encouraging Sentry.addIntegration patterns for integrations that require objects from different libraries (like express with app).

The modules we need register via register API:

  • Node node:http and node:https (diagnostics channels only works well Node 16+, so we need to maintain this for Node 14
  • Remix @remix-run/server-runtime and react-router-dom - monkeypatch
  • OTEL esm instrumentation
  • GoogleCloudGrpc integration require('google-gax')
  • GoogleCloudHttp integration with require('@google-cloud/common')
  • AWSServices integration with require('aws-sdk/global')

We need to re-evaluate if we need monkeypatching for the database integrations now that we have OpenTelemetry.

Change File Structure

We’ll need to move our file structure to the following:

{// at build time we strip `build/X`"main": "build/cjs/index.cjs","module": "build/esm/index.mjs","types": "build/types/index.d.cts",// note: this is an opportunity for us to explore multiple subpaths, like// exporting from `@sentry/browser/utils`"exports": {"./package.json": "./package.json",".": {// esm"import": {// path must be relative"types": "./build/types/index.d.mts","default": "./build/esm/index.mjs"},// cjs"require": {// path must be relative"types": "./build/types/index.d.cts","default": "./build/esm/index.mjs"}// we might need to expose an ESM only export for customization hooks// to use via node --loader=... API}},"typesVersions": {"<4.9": {"build/types/index.d.cts": ["build/types-ts3.8/index.d.cts"],"build/types/index.d.mts": ["build/types-ts3.8/index.d.mts"]}},"files": ["cjs","esm","types","types-ts3.8"]}

Shameless promo: Watch my talk if you want more details about this.

We also should change all node standard library imports to import from node:X.

Make dynamicRequire/loadModule work with ESM import

This exists to trick webpack, but we use it all over the codebase. We need to add an esm compatible way.

One thing we can do is introduce an async dynamicRequire that simulates await import, and then build time replace the functionality of dynamicRequire to use import or require under the hood?

  • packages/node/src/integrations/anr/index.ts - to conditionally import worker_threads. This is just because of Node 12 support, we’ll remove this import entirely
  • packages/node/src/integrations/local-variables/local-variables-async.ts - to conditionally import node:inspector/promises.
  • All of our database integrations - these are all going away
  • packages/utils/src/time.ts - this needs to be refactored entirely. We still want an isomorphic solution because we need date-time helpers that work across core/browser/node and all other packages, but what should happen is that we look for the globalThis.performance instead of relying on perf_hooks. This means that if the global performance API is not available we will fall back to plain [Date.now](http://Date.now) and similar helpers (basically only Node 14 for us). We also need to add a smarter timeOrigin calculation that reset’s itself to help alleviate issues with drifting monotonic clock. See Provide a supported way to get anchored clock times open-telemetry/opentelemetry-js#3279 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions