brave-mcp 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,62 @@
1
1
  # Brave DevTools for agents
2
2
 
3
- [![npm brave-mcp package](https://img.shields.io/npm/v/brave-mcp.svg)](https://npmjs.org/package/brave-mcp)
3
+ **Full Chrome DevTools MCP parity, rebuilt for Brave.**
4
4
 
5
- Brave DevTools for agents (`brave-mcp`) lets your coding agent (such as Antigravity, Claude, Cursor or Copilot)
6
- control and inspect a live Brave browser. It acts as a Model-Context-Protocol
7
- (MCP) server, giving your AI coding assistant access to the full power of
8
- Brave DevTools for reliable automation, in-depth debugging, and performance analysis.
9
- A [CLI](docs/cli.md) is also provided for use without MCP.
5
+ [![CI](https://github.com/triuzzi/brave-devtools-mcp/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://github.com/triuzzi/brave-devtools-mcp/actions/workflows/run-tests.yml)
6
+ [![npm](https://img.shields.io/npm/v/brave-mcp.svg?logo=npm)](https://www.npmjs.com/package/brave-mcp)
7
+ [![npm downloads](https://img.shields.io/npm/dm/brave-mcp.svg?logo=npm)](https://www.npmjs.com/package/brave-mcp)
8
+ [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-active-5B5BD6)](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.triuzzi%2Fbrave-mcp)
9
+ [![npm provenance](https://img.shields.io/badge/npm-provenance-2E7D32)](https://registry.npmjs.org/-/npm/v1/attestations/brave-mcp@1.7.1)
10
+ [![upstream](https://img.shields.io/badge/upstream-0%20commits%20behind-brightgreen)](https://github.com/triuzzi/brave-devtools-mcp/compare/ChromeDevTools:main...main)
11
+ [![license](https://img.shields.io/github/license/triuzzi/brave-devtools-mcp)](./LICENSE)
10
12
 
11
- This Brave port tracks [ChromeDevTools/chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) and preserves its complete tool surface.
13
+ ![Brave DevTools MCP: full Chrome DevTools MCP parity, rebuilt for Brave](./docs/assets/social-preview.png)
12
14
 
13
- ## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md)
15
+ `brave-mcp` gives Claude Code, Codex, Cursor, OpenCode, and other MCP clients direct access to Brave for browser automation, network and console debugging, performance analysis, screenshots, accessibility inspection, and memory profiling. A standalone [`brave-devtools`](docs/cli.md) CLI is included too.
16
+
17
+ ## Install in one command
18
+
19
+ ### Claude Code
20
+
21
+ ```bash
22
+ claude mcp add brave-devtools --scope user -- npx -y brave-mcp@latest
23
+ ```
24
+
25
+ ### Cursor
26
+
27
+ ```bash
28
+ cursor --add-mcp '{"name":"brave-devtools","command":"npx","args":["-y","brave-mcp@latest"]}'
29
+ ```
30
+
31
+ ### Codex
32
+
33
+ ```bash
34
+ codex mcp add brave-devtools -- npx -y brave-mcp@latest
35
+ ```
36
+
37
+ ### OpenCode
38
+
39
+ ```bash
40
+ opencode mcp add brave-devtools -- npx -y brave-mcp@latest
41
+ ```
42
+
43
+ Restart your client, then try this prompt:
44
+
45
+ > Open my app in Brave. Find console errors and failed network requests, inspect the accessibility tree, run Lighthouse, and explain the highest-impact issue.
46
+
47
+ ## Why use this instead of Chrome DevTools MCP?
48
+
49
+ | Capability | `brave-mcp` | `chrome-devtools-mcp` |
50
+ | ---------------------------------------- | --------------------------------------- | ------------------------------------ |
51
+ | Browser launched and discovered natively | Brave Release, Beta, and Nightly | Chrome Stable, Beta, Dev, and Canary |
52
+ | Attach to an existing browser | Brave profiles, HTTP, or WebSocket | Chrome profiles, HTTP, or WebSocket |
53
+ | Upstream DevTools features | Full parity; currently 0 commits behind | Source implementation |
54
+ | Usage telemetry | Disabled by default | Enabled by default |
55
+ | Standalone CLI | `brave-devtools` | `chrome-devtools` |
56
+
57
+ Use [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) when your target browser is Chrome. Use `brave-mcp` when your target browser is Brave and you want Brave-native discovery, profiles, channels, naming, and privacy defaults without giving up upstream features.
58
+
59
+ ## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design principles](./docs/design-principles.md)
14
60
 
15
61
  ## Key features
16
62
 
@@ -71,7 +71,7 @@ export class WaitForHelper {
71
71
  this.#stableDomTimeout = 3000 * cpuTimeoutMultiplier;
72
72
  this.#stableDomFor = 100 * cpuTimeoutMultiplier;
73
73
  this.#expectNavigationIn = 100 * cpuTimeoutMultiplier;
74
- this.#navigationTimeout = 3000 * networkTimeoutMultiplier;
74
+ this.#navigationTimeout = Math.max(page.getDefaultNavigationTimeout(), 3000 * networkTimeoutMultiplier);
75
75
  this.#page = page;
76
76
  this.#initialUrl = page.url();
77
77
  }
@@ -3,8 +3,9 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { toSnakeCase } from '../utils/string.js';
6
+ import { DevTools } from '../third_party/index.js';
7
7
  import { stripUnderscoreBeforeNumber } from './transformation.js';
8
+ const { StringUtilities } = DevTools.Platform;
8
9
  /**
9
10
  * For enums, log the value as uppercase.
10
11
  * We're going to have an enum for such flags with choices represented
@@ -30,7 +31,7 @@ export function computeFlagUsage(args, options) {
30
31
  const usage = {};
31
32
  for (const [flagName, config] of Object.entries(options)) {
32
33
  const value = args[flagName];
33
- const snakeCaseName = stripUnderscoreBeforeNumber(toSnakeCase(flagName));
34
+ const snakeCaseName = stripUnderscoreBeforeNumber(StringUtilities.toSnakeCase(flagName));
34
35
  // If there isn't a default value provided for the flag,
35
36
  // we're going to log whether it's present on the args user
36
37
  // provided or not. If there is a default value, we only log presence
@@ -59,7 +60,7 @@ export function computeFlagUsage(args, options) {
59
60
  export function getPossibleFlagMetrics(options) {
60
61
  const metrics = [];
61
62
  for (const [flagName, config] of Object.entries(options)) {
62
- const snakeCaseName = stripUnderscoreBeforeNumber(toSnakeCase(flagName));
63
+ const snakeCaseName = stripUnderscoreBeforeNumber(StringUtilities.toSnakeCase(flagName));
63
64
  // _present is always a possible metric
64
65
  metrics.push({
65
66
  name: `${snakeCaseName}_present`,
@@ -854,7 +854,7 @@ License: MIT
854
854
 
855
855
  Name: devtools-frontend
856
856
  URL: https://github.com/ChromeDevTools/devtools-frontend
857
- Version: b89284cbca3d602273b868f7183ddfc0e1e7c983
857
+ Version: 7da4b7eeaea404a21118cd937b23a5c57c367229
858
858
  License: Apache-2.0
859
859
 
860
860
  // Copyright 2014 The Chromium Authors
@@ -6,5 +6,5 @@
6
6
  "semver": "7.8.5",
7
7
  "yargs": "18.1.0",
8
8
  "puppeteer-core": "25.5.0",
9
- "https://github.com/ChromeDevTools/devtools-frontend": "b89284cbca3d602273b868f7183ddfc0e1e7c983"
9
+ "https://github.com/ChromeDevTools/devtools-frontend": "7da4b7eeaea404a21118cd937b23a5c57c367229"
10
10
  }
@@ -82989,11 +82989,6 @@ const UIStrings$1h = {
82989
82989
  };
82990
82990
  const str_$1g = registerUIStrings('core/sdk/sdk-meta.ts', UIStrings$1h);
82991
82991
  const i18nLazyString$j = getLazilyComputedLocalizedString.bind(undefined, str_$1g);
82992
- registerSettingExtension({
82993
- settingName: 'pause-on-uncaught-exception',
82994
- settingType: "boolean" ,
82995
- defaultValue: false,
82996
- });
82997
82992
  registerSettingExtension({
82998
82993
  category: "DEBUGGER" ,
82999
82994
  title: i18nLazyString$j(UIStrings$1h.disableJavascript),
@@ -88522,6 +88517,8 @@ function registerCommands(inspectorBackend) {
88522
88517
  inspectorBackend.registerCommand("Page.setTouchEmulationEnabled", [{ "name": "enabled", "type": "boolean", "optional": false, "description": "Whether the touch event emulation should be enabled.", "typeRef": null }, { "name": "configuration", "type": "string", "optional": true, "description": "Touch/gesture events configuration. Default: current platform.", "typeRef": "Page.SetTouchEmulationEnabledRequestConfiguration" }], [], "Toggles mouse event-based touch event emulation.");
88523
88518
  inspectorBackend.registerEnum("Page.StartScreencastRequestFormat", { Jpeg: "jpeg", Png: "png" });
88524
88519
  inspectorBackend.registerCommand("Page.startScreencast", [{ "name": "format", "type": "string", "optional": true, "description": "Image compression format.", "typeRef": "Page.StartScreencastRequestFormat" }, { "name": "quality", "type": "number", "optional": true, "description": "Compression quality from range [0..100].", "typeRef": null }, { "name": "maxWidth", "type": "number", "optional": true, "description": "Maximum screenshot width.", "typeRef": null }, { "name": "maxHeight", "type": "number", "optional": true, "description": "Maximum screenshot height.", "typeRef": null }, { "name": "everyNthFrame", "type": "number", "optional": true, "description": "Send every n-th frame.", "typeRef": null }], [], "Starts sending each frame using the `screencastFrame` event.");
88520
+ inspectorBackend.registerCommand("Page.startScreenRecording", [{ "name": "audio", "type": "boolean", "optional": true, "description": "", "typeRef": null }, { "name": "maxWidth", "type": "number", "optional": true, "description": "Maximum frame width in pixels.", "typeRef": null }, { "name": "maxHeight", "type": "number", "optional": true, "description": "Maximum frame height in pixels.", "typeRef": null }, { "name": "frameRate", "type": "number", "optional": true, "description": "Maximum frame rate in frames per second.", "typeRef": null }], ["stream"], "Starts screencast video recording.");
88521
+ inspectorBackend.registerCommand("Page.stopScreenRecording", [], ["stream"], "Stops screencast video recording.");
88525
88522
  inspectorBackend.registerCommand("Page.stopLoading", [], [], "Force the page stop all navigations and pending resource fetches.");
88526
88523
  inspectorBackend.registerCommand("Page.crash", [], [], "Crashes renderer on the IO thread, generates minidumps.");
88527
88524
  inspectorBackend.registerCommand("Page.close", [], [], "Tries to close page, running its beforeunload hooks, if any.");
@@ -88824,7 +88821,7 @@ function registerCommands(inspectorBackend) {
88824
88821
  inspectorBackend.registerType("WebAudio.AudioParam", [{ "name": "paramId", "type": "string", "optional": false, "description": "", "typeRef": "WebAudio.GraphObjectId" }, { "name": "nodeId", "type": "string", "optional": false, "description": "", "typeRef": "WebAudio.GraphObjectId" }, { "name": "contextId", "type": "string", "optional": false, "description": "", "typeRef": "WebAudio.GraphObjectId" }, { "name": "paramType", "type": "string", "optional": false, "description": "", "typeRef": "WebAudio.ParamType" }, { "name": "rate", "type": "string", "optional": false, "description": "", "typeRef": "WebAudio.AutomationRate" }, { "name": "defaultValue", "type": "number", "optional": false, "description": "", "typeRef": null }, { "name": "minValue", "type": "number", "optional": false, "description": "", "typeRef": null }, { "name": "maxValue", "type": "number", "optional": false, "description": "", "typeRef": null }]);
88825
88822
  inspectorBackend.registerEnum("WebAuthn.AuthenticatorProtocol", { U2f: "u2f", Ctap2: "ctap2" });
88826
88823
  inspectorBackend.registerEnum("WebAuthn.Ctap2Version", { Ctap2_0: "ctap2_0", Ctap2_1: "ctap2_1", Ctap2_2: "ctap2_2" });
88827
- inspectorBackend.registerEnum("WebAuthn.AuthenticatorTransport", { Usb: "usb", Nfc: "nfc", Ble: "ble", Cable: "cable", Internal: "internal" });
88824
+ inspectorBackend.registerEnum("WebAuthn.AuthenticatorTransport", { Usb: "usb", Nfc: "nfc", Ble: "ble", Cable: "cable", Hybrid: "hybrid", SmartCard: "smart-card", Internal: "internal" });
88828
88825
  inspectorBackend.registerEvent("WebAuthn.credentialAdded", ["authenticatorId", "credential"]);
88829
88826
  inspectorBackend.registerEvent("WebAuthn.credentialDeleted", ["authenticatorId", "credentialId"]);
88830
88827
  inspectorBackend.registerEvent("WebAuthn.credentialUpdated", ["authenticatorId", "credential"]);
@@ -92330,6 +92327,11 @@ const generatedProperties = [
92330
92327
  "runtime_flag_status": "stable"
92331
92328
  },
92332
92329
  {
92330
+ "devtools_keywords": [
92331
+ "none",
92332
+ "move",
92333
+ "no-drag"
92334
+ ],
92333
92335
  "name": "app-region"
92334
92336
  },
92335
92337
  {
@@ -96467,7 +96469,8 @@ const generatedProperties = [
96467
96469
  "inline",
96468
96470
  "both",
96469
96471
  "mandatory",
96470
- "proximity"
96472
+ "proximity",
96473
+ "pair"
96471
96474
  ],
96472
96475
  "name": "scroll-snap-type"
96473
96476
  },
@@ -97382,6 +97385,10 @@ const generatedProperties = [
97382
97385
  "name": "will-change"
97383
97386
  },
97384
97387
  {
97388
+ "devtools_keywords": [
97389
+ "none",
97390
+ "move"
97391
+ ],
97385
97392
  "inherited": true,
97386
97393
  "keywords": [
97387
97394
  "none",
@@ -97719,6 +97726,13 @@ const generatedPropertyValues = {
97719
97726
  "none"
97720
97727
  ]
97721
97728
  },
97729
+ "app-region": {
97730
+ "values": [
97731
+ "none",
97732
+ "move",
97733
+ "no-drag"
97734
+ ]
97735
+ },
97722
97736
  "appearance": {
97723
97737
  "values": [
97724
97738
  "auto",
@@ -99998,7 +100012,8 @@ const generatedPropertyValues = {
99998
100012
  "inline",
99999
100013
  "both",
100000
100014
  "mandatory",
100001
- "proximity"
100015
+ "proximity",
100016
+ "pair"
100002
100017
  ]
100003
100018
  },
100004
100019
  "scroll-target-group": {
@@ -100512,8 +100527,7 @@ const generatedPropertyValues = {
100512
100527
  "window-drag": {
100513
100528
  "values": [
100514
100529
  "none",
100515
- "move",
100516
- "no-drag"
100530
+ "move"
100517
100531
  ]
100518
100532
  },
100519
100533
  "word-break": {
@@ -101050,9 +101064,6 @@ class CSSMetadata {
101050
101064
  if (Boolean(runtimeFlagStatus) && runtimeFlagStatus !== 'stable') {
101051
101065
  continue;
101052
101066
  }
101053
- if (!CSS.supports(propertyName, 'initial')) {
101054
- continue;
101055
- }
101056
101067
  this.#values.push(propertyName);
101057
101068
  if (property.inherited) {
101058
101069
  this.#inherited.add(propertyName);
@@ -101091,7 +101102,7 @@ class CSSMetadata {
101091
101102
  }
101092
101103
  const commonKeywordSet = new Set(CommonKeywords);
101093
101104
  for (const propertyName of this.#longhands.keys()) {
101094
- if (propertyName === 'all' || propertyValueSets.has(propertyName)) {
101105
+ if (propertyName === 'all' || propertyName in generatedPropertyValues) {
101095
101106
  continue;
101096
101107
  }
101097
101108
  const longhands = this.#longhands.get(propertyName);
@@ -101099,6 +101110,10 @@ class CSSMetadata {
101099
101110
  continue;
101100
101111
  }
101101
101112
  const values = new Array();
101113
+ const propertyValueSet = propertyValueSets.get(propertyName);
101114
+ if (propertyValueSet) {
101115
+ values.push(...propertyValueSet);
101116
+ }
101102
101117
  for (const longhand of longhands) {
101103
101118
  const longhandValues = propertyValueSets.get(longhand);
101104
101119
  if (!longhandValues) {
@@ -101119,11 +101134,6 @@ class CSSMetadata {
101119
101134
  }
101120
101135
  }
101121
101136
  }
101122
- for (const commonKeyword of CommonKeywords) {
101123
- if (!values.has(commonKeyword) && CSS.supports(propertyName, commonKeyword)) {
101124
- values.add(commonKeyword);
101125
- }
101126
- }
101127
101137
  this.#propertyValues.set(propertyName, [...values]);
101128
101138
  }
101129
101139
  for (const name of this.#valuesSet) {
@@ -107348,6 +107358,11 @@ const pauseOnCaughtExceptionSettingDescriptor = {
107348
107358
  type: "boolean" ,
107349
107359
  defaultValue: false,
107350
107360
  };
107361
+ const pauseOnUncaughtExceptionSettingDescriptor = {
107362
+ name: 'pause-on-uncaught-exception',
107363
+ type: "boolean" ,
107364
+ defaultValue: false,
107365
+ };
107351
107366
 
107352
107367
  // Copyright 2020 The Chromium Authors
107353
107368
  class IOModel extends SDKModel {
@@ -121307,7 +121322,8 @@ class DebuggerModel extends SDKModel {
121307
121322
  settings.resolve(pauseOnCaughtExceptionSettingDescriptor)
121308
121323
  .addChangeListener(this.pauseOnExceptionStateChanged, this);
121309
121324
  this.#skipAllPausesSetting.addChangeListener(this.skipAllPausesChanged, this);
121310
- settings.moduleSetting('pause-on-uncaught-exception').addChangeListener(this.pauseOnExceptionStateChanged, this);
121325
+ settings.resolve(pauseOnUncaughtExceptionSettingDescriptor)
121326
+ .addChangeListener(this.pauseOnExceptionStateChanged, this);
121311
121327
  settings.moduleSetting('disable-async-stack-traces').addChangeListener(this.asyncStackTracesStateChanged, this);
121312
121328
  settings.moduleSetting('breakpoints-active').addChangeListener(this.breakpointsActiveChanged, this);
121313
121329
  if (!target.suspended()) {
@@ -121463,7 +121479,7 @@ class DebuggerModel extends SDKModel {
121463
121479
  const settings = this.target().targetManager().settings;
121464
121480
  const pauseOnCaughtEnabled = settings.resolve(pauseOnCaughtExceptionSettingDescriptor).get();
121465
121481
  let state;
121466
- const pauseOnUncaughtEnabled = settings.moduleSetting('pause-on-uncaught-exception').get();
121482
+ const pauseOnUncaughtEnabled = settings.resolve(pauseOnUncaughtExceptionSettingDescriptor).get();
121467
121483
  if (pauseOnCaughtEnabled && pauseOnUncaughtEnabled) {
121468
121484
  state = "all" ;
121469
121485
  }
@@ -121673,6 +121689,10 @@ class DebuggerModel extends SDKModel {
121673
121689
  this.resume();
121674
121690
  return;
121675
121691
  }
121692
+ if (this.#skipAllPausesSetting.get()) {
121693
+ this.resume();
121694
+ return;
121695
+ }
121676
121696
  const pausedDetails = new DebuggerPausedDetails(this, callFrames, reason, auxData, breakpointIds, asyncStackTrace, asyncStackTraceId);
121677
121697
  if (this.continueToLocationCallback) {
121678
121698
  const callback = this.continueToLocationCallback;
@@ -121876,6 +121896,8 @@ class DebuggerModel extends SDKModel {
121876
121896
  settings.resolve(pauseOnCaughtExceptionSettingDescriptor)
121877
121897
  .removeChangeListener(this.pauseOnExceptionStateChanged, this);
121878
121898
  this.#skipAllPausesSetting.removeChangeListener(this.skipAllPausesChanged, this);
121899
+ settings.resolve(pauseOnUncaughtExceptionSettingDescriptor)
121900
+ .removeChangeListener(this.pauseOnExceptionStateChanged, this);
121879
121901
  settings.moduleSetting('disable-async-stack-traces').removeChangeListener(this.asyncStackTracesStateChanged, this);
121880
121902
  }
121881
121903
  async suspendModel() {
@@ -168000,7 +168022,7 @@ const NativeFunctions = [
168000
168022
  },
168001
168023
  {
168002
168024
  name: "importNode",
168003
- signatures: [["node", "?options"], ["node", "?deep"]]
168025
+ signatures: [["node", "?options"]]
168004
168026
  },
168005
168027
  {
168006
168028
  name: "queryCommandEnabled",
@@ -98,6 +98,10 @@ export const launchPwa = defineTool({
98
98
  handler: async (request, response, context) => {
99
99
  const { manifestId, url } = request.params;
100
100
  const page = await context.launchPWA({ manifestId, url });
101
+ if (url) {
102
+ const expectedUrl = new URL(url).href;
103
+ await page.waitForFunction(requestedUrl => globalThis.location.href === requestedUrl, { timeout: 30_000 }, expectedUrl);
104
+ }
101
105
  response.appendResponseLine(`Launched PWA with manifest ID: ${manifestId} (${page.url()})`);
102
106
  response.setIncludePages(true);
103
107
  },
@@ -5,6 +5,6 @@
5
5
  */
6
6
  // If moved update release-please config
7
7
  // x-release-please-start-version
8
- export const VERSION = '1.7.0';
8
+ export const VERSION = '1.7.1';
9
9
  // x-release-please-end
10
10
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-mcp",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "MCP server and CLI for Brave DevTools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,37 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- /**
7
- * Converts a given string to snake_case.
8
- * This function handles camelCase, PascalCase, and acronyms, including transitions between letters and numbers.
9
- * It uses Unicode-aware regular expressions (`\p{L}`, `\p{N}`, `\p{Lu}`, `\p{Ll}` with the `u` flag)
10
- * to correctly process letters and numbers from various languages.
11
- *
12
- * @param text The input string to convert to snake_case.
13
- * @returns The snake_case version of the input string.
14
- */
15
- export function toSnakeCase(text) {
16
- if (!text) {
17
- return '';
18
- }
19
- // First, handle case-based transformations to insert underscores correctly.
20
- // 1. Add underscore between a letter and a number.
21
- // e.g., "version2" -> "version_2"
22
- // 2. Add underscore between an uppercase letter sequence and a following uppercase+lowercase sequence.
23
- // e.g., "APIFlags" -> "API_Flags"
24
- // 3. Add underscore between a lowercase/number and an uppercase letter.
25
- // e.g., "lastName" -> "last_Name", "version_2Update" -> "version_2_Update"
26
- // 4. Replace sequences of non-alphanumeric with a single underscore
27
- // 5. Remove any leading or trailing underscores.
28
- const result = text
29
- .replace(/(\p{L})(\p{N})/gu, '$1_$2') // 1
30
- .replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1_$2') // 2
31
- .replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, '$1_$2') // 3
32
- .toLowerCase()
33
- .replace(/[^\p{L}\p{N}]+/gu, '_') // 4
34
- .replace(/^_|_$/g, ''); // 5
35
- return result;
36
- }
37
- //# sourceMappingURL=string.js.map