Replace SDK package dependencies #19447

Description

@Lms24

Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

Replace with Custom Code

1. yargs@sentry/remix

Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
wrap-ansi, …)

Where it is used:
packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
args, no nesting.

How to replace:
util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
needs:

// Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

The --usage / --help text can be printed manually on parse error (a plain console.log
string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
not required.


2. glob@sentry/remix

Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
minipass)

Where it is used:
packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
directory and deletes them:

constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

How to replace:
This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
fs.readdirSync walk is all that is needed:

constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

3. glob@sentry/react-router

Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
brace-expansion, balanced-match, @pkgjs/parseargs, …)

Where it is used:
packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
deletes those files:

constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

How to replace:
The default value is always a single **/*.map pattern. The custom
filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
matching glob patterns. A small async recursive walker handles the default case; for user-supplied
patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
virtually all real-world values:

import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


4. minimatch@sentry/node

Transitive dep count: 2 packages (brace-expansion, balanced-match)

Where it is used:
packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
@fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
ignorePaths pattern:

constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

How to replace:
Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
(any number of segments):

functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
upstream where needed), this change is straightforward.


Replace with a Better Alternative

5. recast + @babel/parser@sentry/sveltekit

Transitive dep count: ~8 packages combined

  • recast brings: ast-types, source-map, tslib
  • @babel/parser brings: @babel/types, @babel/helper-string-parser,
    @babel/helper-validator-identifier, to-fast-properties

Where they are used:
packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
ASTs, and checks whether a top-level export const load or export function load declaration
exists. It does not actually mutate the AST — if load is found, it discards the entire
module and returns a freshly generated wrapper string. Because the AST is only read (never
rewritten in place), recast's write-preserving round-trip feature is never exercised, and
@babel/parser is only used as the parse step.

Better alternative: acorn + acorn-typescript

acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
magic-string (already a dependency of @sentry/sveltekit) continues to handle the
append/prepend operations in injectGlobalValues.ts unchanged.

Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
@babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
packages). Adds acorn + acorn-typescript (0 transitive deps each).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Milestone

    No 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

    Replace SDK package dependencies #19447

    Description

    @Lms24

    Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

    I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

    Replace with Custom Code

    1. yargs@sentry/remix

    Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
    require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
    wrap-ansi, …)

    Where it is used:
    packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
    field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
    args, no nesting.

    How to replace:
    util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
    needs:

    // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

    The --usage / --help text can be printed manually on parse error (a plain console.log
    string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
    not required.


    2. glob@sentry/remix

    Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
    minipass)

    Where it is used:
    packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
    directory and deletes them:

    constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

    How to replace:
    This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
    fs.readdirSync walk is all that is needed:

    constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

    3. glob@sentry/react-router

    Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
    brace-expansion, balanced-match, @pkgjs/parseargs, …)

    Where it is used:
    packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
    filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
    deletes those files:

    constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

    How to replace:
    The default value is always a single **/*.map pattern. The custom
    filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
    matching glob patterns. A small async recursive walker handles the default case; for user-supplied
    patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
    virtually all real-world values:

    import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

    Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
    field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


    4. minimatch@sentry/node

    Transitive dep count: 2 packages (brace-expansion, balanced-match)

    Where it is used:
    packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
    @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
    ignorePaths pattern:

    constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

    How to replace:
    Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
    values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
    are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
    (any number of segments):

    functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

    Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
    upstream where needed), this change is straightforward.


    Replace with a Better Alternative

    5. recast + @babel/parser@sentry/sveltekit

    Transitive dep count: ~8 packages combined

    • recast brings: ast-types, source-map, tslib
    • @babel/parser brings: @babel/types, @babel/helper-string-parser,
      @babel/helper-validator-identifier, to-fast-properties

    Where they are used:
    packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
    reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
    ASTs, and checks whether a top-level export const load or export function load declaration
    exists. It does not actually mutate the AST — if load is found, it discards the entire
    module and returns a freshly generated wrapper string. Because the AST is only read (never
    rewritten in place), recast's write-preserving round-trip feature is never exercised, and
    @babel/parser is only used as the parse step.

    Better alternative: acorn + acorn-typescript

    acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
    TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
    to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
    magic-string (already a dependency of @sentry/sveltekit) continues to handle the
    append/prepend operations in injectGlobalValues.ts unchanged.

    Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
    @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
    packages). Adds acorn + acorn-typescript (0 transitive deps each).

    Activity

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

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      No labels
      No labels

      Projects

      Milestone

      No 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

      Replace SDK package dependencies #19447

      Description

      @Lms24

      Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

      I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

      Replace with Custom Code

      1. yargs@sentry/remix

      Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
      require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
      wrap-ansi, …)

      Where it is used:
      packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
      field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
      args, no nesting.

      How to replace:
      util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
      needs:

      // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

      The --usage / --help text can be printed manually on parse error (a plain console.log
      string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
      not required.


      2. glob@sentry/remix

      Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
      minipass)

      Where it is used:
      packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
      directory and deletes them:

      constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

      How to replace:
      This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
      fs.readdirSync walk is all that is needed:

      constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

      3. glob@sentry/react-router

      Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
      brace-expansion, balanced-match, @pkgjs/parseargs, …)

      Where it is used:
      packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
      filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
      deletes those files:

      constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

      How to replace:
      The default value is always a single **/*.map pattern. The custom
      filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
      matching glob patterns. A small async recursive walker handles the default case; for user-supplied
      patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
      virtually all real-world values:

      import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

      Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
      field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


      4. minimatch@sentry/node

      Transitive dep count: 2 packages (brace-expansion, balanced-match)

      Where it is used:
      packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
      @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
      ignorePaths pattern:

      constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

      How to replace:
      Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
      values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
      are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
      (any number of segments):

      functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

      Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
      upstream where needed), this change is straightforward.


      Replace with a Better Alternative

      5. recast + @babel/parser@sentry/sveltekit

      Transitive dep count: ~8 packages combined

      • recast brings: ast-types, source-map, tslib
      • @babel/parser brings: @babel/types, @babel/helper-string-parser,
        @babel/helper-validator-identifier, to-fast-properties

      Where they are used:
      packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
      reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
      ASTs, and checks whether a top-level export const load or export function load declaration
      exists. It does not actually mutate the AST — if load is found, it discards the entire
      module and returns a freshly generated wrapper string. Because the AST is only read (never
      rewritten in place), recast's write-preserving round-trip feature is never exercised, and
      @babel/parser is only used as the parse step.

      Better alternative: acorn + acorn-typescript

      acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
      TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
      to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
      magic-string (already a dependency of @sentry/sveltekit) continues to handle the
      append/prepend operations in injectGlobalValues.ts unchanged.

      Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
      @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
      packages). Adds acorn + acorn-typescript (0 transitive deps each).

      Activity

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

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Projects

        Milestone

        No 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

        Replace SDK package dependencies #19447

        Description

        @Lms24

        Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

        I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

        Replace with Custom Code

        1. yargs@sentry/remix

        Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
        require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
        wrap-ansi, …)

        Where it is used:
        packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
        field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
        args, no nesting.

        How to replace:
        util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
        needs:

        // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

        The --usage / --help text can be printed manually on parse error (a plain console.log
        string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
        not required.


        2. glob@sentry/remix

        Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
        minipass)

        Where it is used:
        packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
        directory and deletes them:

        constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

        How to replace:
        This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
        fs.readdirSync walk is all that is needed:

        constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

        3. glob@sentry/react-router

        Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
        brace-expansion, balanced-match, @pkgjs/parseargs, …)

        Where it is used:
        packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
        filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
        deletes those files:

        constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

        How to replace:
        The default value is always a single **/*.map pattern. The custom
        filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
        matching glob patterns. A small async recursive walker handles the default case; for user-supplied
        patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
        virtually all real-world values:

        import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

        Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
        field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


        4. minimatch@sentry/node

        Transitive dep count: 2 packages (brace-expansion, balanced-match)

        Where it is used:
        packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
        @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
        ignorePaths pattern:

        constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

        How to replace:
        Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
        values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
        are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
        (any number of segments):

        functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

        Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
        upstream where needed), this change is straightforward.


        Replace with a Better Alternative

        5. recast + @babel/parser@sentry/sveltekit

        Transitive dep count: ~8 packages combined

        • recast brings: ast-types, source-map, tslib
        • @babel/parser brings: @babel/types, @babel/helper-string-parser,
          @babel/helper-validator-identifier, to-fast-properties

        Where they are used:
        packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
        reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
        ASTs, and checks whether a top-level export const load or export function load declaration
        exists. It does not actually mutate the AST — if load is found, it discards the entire
        module and returns a freshly generated wrapper string. Because the AST is only read (never
        rewritten in place), recast's write-preserving round-trip feature is never exercised, and
        @babel/parser is only used as the parse step.

        Better alternative: acorn + acorn-typescript

        acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
        TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
        to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
        magic-string (already a dependency of @sentry/sveltekit) continues to handle the
        append/prepend operations in injectGlobalValues.ts unchanged.

        Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
        @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
        packages). Adds acorn + acorn-typescript (0 transitive deps each).

        Activity

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

        Metadata

        Metadata

        Assignees

        No one assigned

          Labels

          No labels
          No labels

          Projects

          Milestone

          No 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

          Replace SDK package dependencies #19447

          Description

          @Lms24

          Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

          I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

          Replace with Custom Code

          1. yargs@sentry/remix

          Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
          require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
          wrap-ansi, …)

          Where it is used:
          packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
          field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
          args, no nesting.

          How to replace:
          util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
          needs:

          // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

          The --usage / --help text can be printed manually on parse error (a plain console.log
          string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
          not required.


          2. glob@sentry/remix

          Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
          minipass)

          Where it is used:
          packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
          directory and deletes them:

          constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

          How to replace:
          This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
          fs.readdirSync walk is all that is needed:

          constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

          3. glob@sentry/react-router

          Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
          brace-expansion, balanced-match, @pkgjs/parseargs, …)

          Where it is used:
          packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
          filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
          deletes those files:

          constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

          How to replace:
          The default value is always a single **/*.map pattern. The custom
          filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
          matching glob patterns. A small async recursive walker handles the default case; for user-supplied
          patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
          virtually all real-world values:

          import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

          Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
          field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


          4. minimatch@sentry/node

          Transitive dep count: 2 packages (brace-expansion, balanced-match)

          Where it is used:
          packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
          @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
          ignorePaths pattern:

          constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

          How to replace:
          Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
          values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
          are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
          (any number of segments):

          functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

          Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
          upstream where needed), this change is straightforward.


          Replace with a Better Alternative

          5. recast + @babel/parser@sentry/sveltekit

          Transitive dep count: ~8 packages combined

          • recast brings: ast-types, source-map, tslib
          • @babel/parser brings: @babel/types, @babel/helper-string-parser,
            @babel/helper-validator-identifier, to-fast-properties

          Where they are used:
          packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
          reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
          ASTs, and checks whether a top-level export const load or export function load declaration
          exists. It does not actually mutate the AST — if load is found, it discards the entire
          module and returns a freshly generated wrapper string. Because the AST is only read (never
          rewritten in place), recast's write-preserving round-trip feature is never exercised, and
          @babel/parser is only used as the parse step.

          Better alternative: acorn + acorn-typescript

          acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
          TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
          to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
          magic-string (already a dependency of @sentry/sveltekit) continues to handle the
          append/prepend operations in injectGlobalValues.ts unchanged.

          Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
          @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
          packages). Adds acorn + acorn-typescript (0 transitive deps each).

          Activity

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

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Projects

            Milestone

            No 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

            Replace SDK package dependencies #19447

            Description

            @Lms24

            Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

            I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

            Replace with Custom Code

            1. yargs@sentry/remix

            Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
            require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
            wrap-ansi, …)

            Where it is used:
            packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
            field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
            args, no nesting.

            How to replace:
            util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
            needs:

            // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

            The --usage / --help text can be printed manually on parse error (a plain console.log
            string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
            not required.


            2. glob@sentry/remix

            Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
            minipass)

            Where it is used:
            packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
            directory and deletes them:

            constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

            How to replace:
            This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
            fs.readdirSync walk is all that is needed:

            constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

            3. glob@sentry/react-router

            Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
            brace-expansion, balanced-match, @pkgjs/parseargs, …)

            Where it is used:
            packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
            filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
            deletes those files:

            constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

            How to replace:
            The default value is always a single **/*.map pattern. The custom
            filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
            matching glob patterns. A small async recursive walker handles the default case; for user-supplied
            patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
            virtually all real-world values:

            import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

            Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
            field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


            4. minimatch@sentry/node

            Transitive dep count: 2 packages (brace-expansion, balanced-match)

            Where it is used:
            packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
            @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
            ignorePaths pattern:

            constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

            How to replace:
            Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
            values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
            are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
            (any number of segments):

            functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

            Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
            upstream where needed), this change is straightforward.


            Replace with a Better Alternative

            5. recast + @babel/parser@sentry/sveltekit

            Transitive dep count: ~8 packages combined

            • recast brings: ast-types, source-map, tslib
            • @babel/parser brings: @babel/types, @babel/helper-string-parser,
              @babel/helper-validator-identifier, to-fast-properties

            Where they are used:
            packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
            reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
            ASTs, and checks whether a top-level export const load or export function load declaration
            exists. It does not actually mutate the AST — if load is found, it discards the entire
            module and returns a freshly generated wrapper string. Because the AST is only read (never
            rewritten in place), recast's write-preserving round-trip feature is never exercised, and
            @babel/parser is only used as the parse step.

            Better alternative: acorn + acorn-typescript

            acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
            TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
            to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
            magic-string (already a dependency of @sentry/sveltekit) continues to handle the
            append/prepend operations in injectGlobalValues.ts unchanged.

            Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
            @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
            packages). Adds acorn + acorn-typescript (0 transitive deps each).

            Activity

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

            Metadata

            Metadata

            Assignees

            No one assigned

              Labels

              No labels
              No labels

              Projects

              Milestone

              No 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

              Replace SDK package dependencies #19447

              Description

              @Lms24

              Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

              I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

              Replace with Custom Code

              1. yargs@sentry/remix

              Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
              require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
              wrap-ansi, …)

              Where it is used:
              packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
              field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
              args, no nesting.

              How to replace:
              util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
              needs:

              // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

              The --usage / --help text can be printed manually on parse error (a plain console.log
              string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
              not required.


              2. glob@sentry/remix

              Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
              minipass)

              Where it is used:
              packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
              directory and deletes them:

              constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

              How to replace:
              This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
              fs.readdirSync walk is all that is needed:

              constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

              3. glob@sentry/react-router

              Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
              brace-expansion, balanced-match, @pkgjs/parseargs, …)

              Where it is used:
              packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
              filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
              deletes those files:

              constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

              How to replace:
              The default value is always a single **/*.map pattern. The custom
              filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
              matching glob patterns. A small async recursive walker handles the default case; for user-supplied
              patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
              virtually all real-world values:

              import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

              Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
              field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


              4. minimatch@sentry/node

              Transitive dep count: 2 packages (brace-expansion, balanced-match)

              Where it is used:
              packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
              @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
              ignorePaths pattern:

              constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

              How to replace:
              Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
              values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
              are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
              (any number of segments):

              functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

              Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
              upstream where needed), this change is straightforward.


              Replace with a Better Alternative

              5. recast + @babel/parser@sentry/sveltekit

              Transitive dep count: ~8 packages combined

              • recast brings: ast-types, source-map, tslib
              • @babel/parser brings: @babel/types, @babel/helper-string-parser,
                @babel/helper-validator-identifier, to-fast-properties

              Where they are used:
              packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
              reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
              ASTs, and checks whether a top-level export const load or export function load declaration
              exists. It does not actually mutate the AST — if load is found, it discards the entire
              module and returns a freshly generated wrapper string. Because the AST is only read (never
              rewritten in place), recast's write-preserving round-trip feature is never exercised, and
              @babel/parser is only used as the parse step.

              Better alternative: acorn + acorn-typescript

              acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
              TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
              to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
              magic-string (already a dependency of @sentry/sveltekit) continues to handle the
              append/prepend operations in injectGlobalValues.ts unchanged.

              Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
              @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
              packages). Adds acorn + acorn-typescript (0 transitive deps each).

              Activity

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

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Projects

                Milestone

                No 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

                Replace SDK package dependencies #19447

                Description

                @Lms24

                Vulnerability alerts and dependency updates haunt us these days, especially when they surface in user-facing dependencies. Obviously, we want to fix these issues as quickly as possible, though with 20+ alerts popping up every week lately (to be clear, the vast majority of these alerts are dev and test dependencies!), the maintenance burden has reached new levels. So we discussed if we can reduce the number of direct and transitive dependencies we ship in our SDK packages. The goal is that we replace dependencies with much smaller, tailored/custom implementations.

                I had cursor run an audit across the dependencies of our SDK packages. It presented a couple of opportunities that, after filtering out a couple of suggestions, I think are worth looking into further:

                Replace with Custom Code

                1. yargs@sentry/remix

                Transitive dep count: ~10 packages (cliui, escalade, get-caller-file,
                require-directory, string-width, y18n, yargs-parser, ansi-regex, strip-ansi,
                wrap-ansi, …)

                Where it is used:
                packages/remix/scripts/sentry-upload-sourcemaps.js — a CLI script that ships via the bin
                field. It parses 8 simple --flag / --flag value options with no subcommands, no positional
                args, no nesting.

                How to replace:
                util.parseArgs has been built into Node.js since v18.3.0 and handles everything this script
                needs:

                // Beforeconstyargs=require('yargs');constargv=yargs(process.argv.slice(2)).option('release',{type: 'string'}).option('org',{type: 'string'}).option('project',{type: 'string'}).option('url',{type: 'string'}).option('urlPrefix',{type: 'string',default: DEFAULT_URL_PREFIX}).option('buildPath',{type: 'string',default: DEFAULT_BUILD_PATH}).option('disableDebugIds',{type: 'boolean',default: false}).option('deleteAfterUpload',{type: 'boolean',default: true}).argv;// Afterconst{ parseArgs }=require('node:util');const{values: argv}=parseArgs({args: process.argv.slice(2),options: {release: {type: 'string'},org: {type: 'string'},project: {type: 'string'},url: {type: 'string'},urlPrefix: {type: 'string',default: DEFAULT_URL_PREFIX},buildPath: {type: 'string',default: DEFAULT_BUILD_PATH},disableDebugIds: {type: 'boolean',default: false},deleteAfterUpload: {type: 'boolean',default: true},},});

                The --usage / --help text can be printed manually on parse error (a plain console.log
                string). The script is a simple one-shot tool, so the ergonomics of yargs' help formatter are
                not required.


                2. glob@sentry/remix

                Transitive dep count: 4 packages (minimatch, brace-expansion, balanced-match,
                minipass)

                Where it is used:
                packages/remix/scripts/deleteSourcemaps.js — finds all **/*.map files under a given build
                directory and deletes them:

                constmapFiles=glob.sync('**/*.map',{cwd: buildPath});

                How to replace:
                This is a fully static pattern (**/*.map) over a known root directory. A simple recursive
                fs.readdirSync walk is all that is needed:

                constfs=require('node:fs');constpath=require('node:path');functionfindMapFiles(dir){constresults=[];for(constentryoffs.readdirSync(dir,{withFileTypes: true})){constfull=path.join(dir,entry.name);if(entry.isDirectory()){results.push(...findMapFiles(full));}elseif(entry.isFile()&&entry.name.endsWith('.map')){results.push(full);}}returnresults;}

                3. glob@sentry/react-router

                Transitive dep count: ~8 packages (jackspeak, minipass, path-scurry, minimatch,
                brace-expansion, balanced-match, @pkgjs/parseargs, …)

                Where it is used:
                packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts — resolves the
                filesToDeleteAfterUpload option (typically ["<buildDir>/**/*.map"]) after a Vite build, then
                deletes those files:

                constfilePathsToDelete=awaitglob(updatedFilesToDeleteAfterUpload,{absolute: true,nodir: true,});

                How to replace:
                The default value is always a single **/*.map pattern. The custom
                filesToDeleteAfterUpload option that users can pass is already typed as string | string[]
                matching glob patterns. A small async recursive walker handles the default case; for user-supplied
                patterns a lightweight inline matcher is sufficient since **/*.ext and dir/**/*.ext cover
                virtually all real-world values:

                import{readdir}from'node:fs/promises';import{join}from'node:path';asyncfunctionfindFiles(dir: string,predicate: (name: string)=>boolean): Promise<string[]>{constresults: string[]=[];for(constentryofawaitreaddir(dir,{withFileTypes: true})){constfull=join(dir,entry.name);if(entry.isDirectory()){results.push(...(awaitfindFiles(full,predicate)));}elseif(entry.isFile()&&predicate(entry.name)){results.push(full);}}returnresults;}

                Note: @sentry/react-router already requires Node ≥ 20 ("node": ">=20" in its engines
                field), so Node 22's native fs.glob could also be used once Node 22 becomes the minimum.


                4. minimatch@sentry/node

                Transitive dep count: 2 packages (brace-expansion, balanced-match)

                Where it is used:
                packages/node/src/integrations/tracing/fastify/fastify-otel/index.js — vendored copy of
                @fastify/otel. It performs a single glob match of a Fastify route URL against a user-supplied
                ignorePaths pattern:

                constglobMatcher=minimatch.minimatch;this[kIgnorePaths]=routeOptions=>globMatcher(routeOptions.url,ignorePaths);

                How to replace:
                Route URLs are simple path strings (e.g. /api/users, /health). Realistic ignorePaths
                values are things like /health, /api/*, /static/**. Full brace-expansion glob semantics
                are not needed. A minimal path glob matcher (~20 lines) handles * (single segment) and **
                (any number of segments):

                functionmatchesGlob(path,pattern){// Escape regex special chars except * which we handle ourselvesconstregexStr=pattern.replace(/[.+^${}()|[\]\\]/g,'\\$&').replace(/\*\*/g,'{{DOUBLE_STAR}}').replace(/\*/g,'[^/]*').replace(/{{DOUBLE_STAR}}/g,'.*');returnnewRegExp(`^${regexStr}$`).test(path);}

                Since fastify-otel/index.js is already a vendored file that we maintain (and diverge from
                upstream where needed), this change is straightforward.


                Replace with a Better Alternative

                5. recast + @babel/parser@sentry/sveltekit

                Transitive dep count: ~8 packages combined

                • recast brings: ast-types, source-map, tslib
                • @babel/parser brings: @babel/types, @babel/helper-string-parser,
                  @babel/helper-validator-identifier, to-fast-properties

                Where they are used:
                packages/sveltekit/src/vite/autoInstrument.ts and recastTypescriptParser.ts. The plugin
                reads SvelteKit +page.ts / +layout.server.ts files at build time, parses them as TypeScript
                ASTs, and checks whether a top-level export const load or export function load declaration
                exists. It does not actually mutate the AST — if load is found, it discards the entire
                module and returns a freshly generated wrapper string. Because the AST is only read (never
                rewritten in place), recast's write-preserving round-trip feature is never exercised, and
                @babel/parser is only used as the parse step.

                Better alternative: acorn + acorn-typescript

                acorn is the parser used by Node.js itself (0 transitive deps). acorn-typescript adds
                TypeScript syntax support as an acorn plugin (0 transitive deps). The existing check only needs
                to walk top-level ExportNamedDeclaration nodes, which maps directly onto acorn's AST output.
                magic-string (already a dependency of @sentry/sveltekit) continues to handle the
                append/prepend operations in injectGlobalValues.ts unchanged.

                Dep reduction: Removes recast, ast-types, source-map, @babel/parser, @babel/types,
                @babel/helper-string-parser, @babel/helper-validator-identifier, to-fast-properties (~8
                packages). Adds acorn + acorn-typescript (0 transitive deps each).

                Activity

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

                Metadata

                Metadata

                Assignees

                No one assigned

                  Labels

                  No labels
                  No labels

                  Projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions