| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5d90e48 commit 445bcce
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -236,6 +236,10 @@ debug> | |||
| 236 | 236 | added: | |
| 237 | 237 | - v24.16.0 | |
| 238 | 238 | changes: | |
| 239 | + - version: REPLACEME | ||
| 240 | + pr-url: https://github.com/nodejs/node/pull/64328 | ||
| 241 | + description: Add per-probe `--cond <expr>` option to only record a hit when the | ||
| 242 | + condition is truthy at the probe location. | ||
| 239 | 243 | - version: v24.19.0 | |
| 240 | 244 | pr-url: https://github.com/nodejs/node/pull/63704 | |
| 241 | 245 | description: Add per-probe `--max-hit <n>` option to limit evaluated hits and finish | |
@@ -268,8 +272,8 @@ printf-style debugging without having to modify the application code and | |||
| 268 | 272 | clean up afterwards. It also supports structured JSON output for tool use. | |
| 269 | 273 | ||
| 270 | 274 | ```console | |
| 271 | - $ node inspect --probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] | ||
| 272 | - [--probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] ...] | ||
| 275 | + $ node inspect --probe <file>:<line>[:<col>] --expr <expr> [--cond <expr>] [--max-hit <n>] | ||
| 276 | + [--probe <file>:<line>[:<col>] --expr <expr> [--cond <expr>] [--max-hit <n>] ...] | ||
| 273 | 277 | [--json] [--preview] [--timeout=<ms>] [--port=<port>] | |
| 274 | 278 | [--] [<node-option> ...] <script> [<script-args> ...] | |
| 275 | 279 | ``` | |
@@ -282,6 +286,9 @@ $ node inspect --probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] | |||
| 282 | 286 | * `--expr <expr>`: JavaScript expression to evaluate whenever execution reaches | |
| 283 | 287 | the location specified by the preceding `--probe`. | |
| 284 | 288 | Must immediately follow the `--probe` it belongs to. | |
| 289 | + * `--cond <expr>`: An optional condition for the probe location. The probe only | ||
| 290 | + records a hit when `<expr>` is truthy at the location. A condition that throws | ||
| 291 | + is treated as false. | ||
| 285 | 292 | * `--max-hit <n>`: An optional per-probe limit on the number of times the probe | |
| 286 | 293 | can be hit. When not specified, there's no hit limit. When any probe reaches | |
| 287 | 294 | its hit limit, the probing process will detach and report the results. The process | |
@@ -297,14 +304,17 @@ $ node inspect --probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] | |||
| 297 | 304 | will listen. Defaults to `0`, which requests a random port. | |
| 298 | 305 | * `--` is optional unless the child needs its own Node.js flags. | |
| 299 | 306 | ||
| 300 | - Additional rules about the `--probe` and `--expr` arguments: | ||
| 307 | + Additional rules about the composition of the options: | ||
| 301 | 308 | ||
| 302 | 309 | * `--probe <file>:<line>[:<col>]` and `--expr <expr>` are strict pairs. Each | |
| 303 | 310 | `--probe` must be followed immediately by exactly one `--expr`. | |
| 304 | - * `--max-hit <n>` is an optional per-probe option that applies to the most recent | ||
| 305 | - `--probe`/`--expr` pair. It may not appear before the first `--probe` or | ||
| 306 | - between a `--probe` and its matching `--expr`, and may be given at most once | ||
| 307 | - per probe. | ||
| 311 | + * `--cond <expr>` and `--max-hit <n>` are optional modifiers written _after_ the | ||
| 312 | + `--probe`/`--expr` pair they apply to, each at most once per pair. They may not | ||
| 313 | + appear before the first `--probe` or between a `--probe` and its matching | ||
| 314 | + `--expr`. | ||
| 315 | + * `--max-hit` scopes to the `--probe`/`--expr` pair it follows, so pairs | ||
| 316 | + sharing a location may set different limits. `--cond` scopes to the whole | ||
| 317 | + location, probes sharing a location must share one condition (or none). | ||
| 308 | 318 | * `--timeout`, `--json`, `--preview`, and `--port` are global probe options | |
| 309 | 319 | for the whole probe session. They may appear before or between probe pairs, | |
| 310 | 320 | but not between a `--probe` and its matching `--expr`. | |
@@ -379,6 +389,7 @@ $ node inspect --json --probe cli.js:5 --expr 'rss' cli.js | |||
| 379 | 389 | "suffix": "cli.js", | |
| 380 | 390 | "line": 5 | |
| 381 | 391 | } | |
| 392 | + // `condition` is present only when the probe was given a --cond expression. | ||
| 382 | 393 | // `maxHit` is present only when the probe was given a --max-hit limit. | |
| 383 | 394 | } | |
| 384 | 395 | ], | |
@@ -480,6 +491,10 @@ When multiple `--probe`/`--expr` pairs share the same `--probe`, the | |||
| 480 | 491 | expressions will be evaluated on the same pause in the order they appear | |
| 481 | 492 | on the command line. | |
| 482 | 493 | ||
| 494 | + For each location, there can only be at most one `--cond` (or none). | ||
| 495 | + Multiple `--probe`/`--expr` pairs with conflicting conditions | ||
| 496 | + at the same location will be rejected at launch time. | ||
| 497 | + | ||
| 483 | 498 | ```js | |
| 484 | 499 | // app.js | |
| 485 | 500 | const x = { x: 42 }; // line 2 | |
@@ -563,6 +578,37 @@ that only matches the intended file: | |||
| 563 | 578 | $ node inspect --probe src/utils.js:10 --expr 'x' main.js # matches only src/utils.js | |
| 564 | 579 | ``` | |
| 565 | 580 | ||
| 581 | + ### Probe examples | ||
| 582 | + | ||
| 583 | + #### Probing a variable conditionally | ||
| 584 | + | ||
| 585 | + ```js | ||
| 586 | + // app.js | ||
| 587 | + let total = 0; | ||
| 588 | + for (let i = 0; i < 10; i++) { | ||
| 589 | + total += i; // line 4 | ||
| 590 | + } | ||
| 591 | + ``` | ||
| 592 | + | ||
| 593 | + ```console | ||
| 594 | + $ out/Release/node inspect --probe app.js:4 --expr 'total' \ | ||
| 595 | + --cond 'i % 3 === 0' app.js | ||
| 596 | + ``` | ||
| 597 | + | ||
| 598 | + ```text | ||
| 599 | + Hit 1 at file:///path/to/app.js:3:3 | ||
| 600 | + total = 0 | ||
| 601 | + Hit 2 at file:///path/to/app.js:3:3 | ||
| 602 | + total = 3 | ||
| 603 | + Hit 3 at file:///path/to/app.js:3:3 | ||
| 604 | + total = 15 | ||
| 605 | + Hit 4 at file:///path/to/app.js:3:3 | ||
| 606 | + total = 36 | ||
| 607 | + Completed | ||
| 608 | + ``` | ||
| 609 | + | ||
| 610 | + <!-- TODO(joyeecheung): add more examples for different options --> | ||
| 611 | + | ||
| 566 | 612 | ## Advanced usage | |
| 567 | 613 | ||
| 568 | 614 | ### V8 inspector integration for Node.js | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -268,6 +268,7 @@ function parseInteractiveArgs(args) { | |||
| 268 | 268 | ||
| 269 | 269 | const kInspectArgOptions = { | |
| 270 | 270 | '__proto__': null, | |
| 271 | + 'cond': { type: 'string' }, | ||
| 271 | 272 | 'expr': { type: 'string' }, | |
| 272 | 273 | 'help': { type: 'boolean', short: 'h' }, | |
| 273 | 274 | 'json': { type: 'boolean' }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -69,8 +69,8 @@ function writeInspectUsageAndExit(invokedAs, message, exitCode) { | |||
| 69 | 69 | } | |
| 70 | 70 | out.write(`Usage: ${invokedAs} [--port=<port>] [<node-option> ...] | |
| 71 | 71 | [<script> [<script-args>] | <host>:<port> | -p <pid>] | |
| 72 | - ${invokedAs} --probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] | ||
| 73 | - [--probe <file>:<line>[:<col>] --expr <expr> [--max-hit <n>] ...] | ||
| 72 | + ${invokedAs} --probe <file>:<line>[:<col>] --expr <expr> [--cond <expr>] [--max-hit <n>] | ||
| 73 | + [--probe <file>:<line>[:<col>] --expr <expr> [--cond <expr>] [--max-hit <n>] ...] | ||
| 74 | 74 | [--json] [--preview] [--timeout=<ms>] [--port=<port>] | |
| 75 | 75 | [--] [<node-option> ...] <script> [<script-args> ...] | |
| 76 | 76 | ||
@@ -109,6 +109,9 @@ Options: | |||
| 109 | 109 | preceding --probe each time execution reaches it. | |
| 110 | 110 | Avoid probing let/const-bound variables at their | |
| 111 | 111 | declaration site or a ReferenceError may be thrown. | |
| 112 | + --cond <expr> Optional condition for the probe location. The probe only | ||
| 113 | + records a hit when <expr> is truthy at the location. A | ||
| 114 | + condition that throws is treated as false. | ||
| 112 | 115 | --max-hit <n> Per-probe limit on evaluated hits. When not specified, | |
| 113 | 116 | there's no hit limit. When any probe reaches its hit LIMIT, | |
| 114 | 117 | the probing process will detach and report the results. | |
@@ -121,6 +124,9 @@ Options: | |||
| 121 | 124 | Semantics: | |
| 122 | 125 | * Multiple --probe/--expr pairs are allowed. Same-location --probes share | |
| 123 | 126 | a pause and scope, their --exprs are evaluated in command-line order. | |
| 127 | + * --max-hit scopes to one --probe/--expr pair, so same-location pairs may set | ||
| 128 | + different limits. --cond scopes to the location, probes sharing a location | ||
| 129 | + must all share one condition (or none). | ||
| 124 | 130 | * --probe utils.js:<line>[:<col>] matches every loaded utils.js. Pass a | |
| 125 | 131 | fuller path e.g. src/utils.js to narrow the match. | |
| 126 | 132 | * Use -- before any Node.js flags intended for the child process. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,6 +21,7 @@ const { | |||
| 21 | 21 | StringPrototypeIncludes, | |
| 22 | 22 | StringPrototypeSlice, | |
| 23 | 23 | StringPrototypeStartsWith, | |
| 24 | + StringPrototypeTrim, | ||
| 24 | 25 | Symbol, | |
| 25 | 26 | } = primordials; | |
| 26 | 27 | ||
@@ -87,6 +88,8 @@ const kInspectPortRegex = /^--inspect-port=(\d+)$/; | |||
| 87 | 88 | * @typedef {object} Probe | |
| 88 | 89 | * @property {string} expr Expression to evaluate on hit. | |
| 89 | 90 | * @property {ProbeTarget} target User's original --probe request shape. | |
| 91 | + * @property {string} [condition] Condition from --cond. V8 only breaks when it is truthy. | ||
| 92 | + * Scoped to the location, so probes sharing a location all carry the same value. | ||
| 90 | 93 | * @property {number} maxHit Per-probe hit limit from --max-hit. Infinity when unlimited. | |
| 91 | 94 | * @property {number} hits Count of hits observed. | |
| 92 | 95 | */ | |
@@ -130,6 +133,12 @@ function formatTargetText(target) { | |||
| 130 | 133 | return column === undefined ? `${suffix}:${line}` : `${suffix}:${line}:${column}`; | |
| 131 | 134 | } | |
| 132 | 135 | ||
| 136 | + // Identity of a probe location. Probes sharing a key share one V8 breakpoint, | ||
| 137 | + // so this must stay in sync between condition validation and breakpoint setup. | ||
| 138 | + function locationKey(target) { | ||
| 139 | + return `${target.suffix}\n${target.line}\n${target.column ?? ''}`; | ||
| 140 | + } | ||
| 141 | + | ||
| 133 | 142 | function formatPendingProbeLocations(probes, pending) { | |
| 134 | 143 | const seen = new SafeSet(); | |
| 135 | 144 | for (const probeIndex of pending) { | |
@@ -391,6 +400,22 @@ function parseProbeTokens(tokens, args) { | |||
| 391 | 400 | probe.maxHit = parseUnsignedInteger(token.value, 'max-hit'); | |
| 392 | 401 | break; | |
| 393 | 402 | } | |
| 403 | + case 'cond': { | ||
| 404 | + if (probes.length === 0) { | ||
| 405 | + throw new ERR_DEBUGGER_STARTUP_ERROR('Unexpected --cond before --probe'); | ||
| 406 | + } | ||
| 407 | + // A blank condition does not act as a real predicate in V8 (an empty | ||
| 408 | + // string always breaks), so reject it rather than silently mislead. | ||
| 409 | + if (token.value === undefined || StringPrototypeTrim(token.value) === '') { | ||
| 410 | + throw new ERR_DEBUGGER_STARTUP_ERROR(`Missing value for ${token.rawName}`); | ||
| 411 | + } | ||
| 412 | + const probe = probes[probes.length - 1]; | ||
| 413 | + if (probe.condition !== undefined) { | ||
| 414 | + throw new ERR_DEBUGGER_STARTUP_ERROR('A --probe can have at most one --cond'); | ||
| 415 | + } | ||
| 416 | + probe.condition = token.value; | ||
| 417 | + break; | ||
| 418 | + } | ||
| 394 | 419 | default: | |
| 395 | 420 | if (probes.length > 0) { | |
| 396 | 421 | throw new ERR_DEBUGGER_STARTUP_ERROR( | |
@@ -410,6 +435,24 @@ function parseProbeTokens(tokens, args) { | |||
| 410 | 435 | 'Probe mode requires at least one --probe <loc> --expr <expr> group'); | |
| 411 | 436 | } | |
| 412 | 437 | ||
| 438 | + // V8 allows only one breakpoint per location, so probes sharing a location | ||
| 439 | + // cannot carry different conditions. | ||
| 440 | + const conditionByLocation = new SafeMap(); | ||
| 441 | + for (const { target, condition } of probes) { | ||
| 442 | + const key = locationKey(target); | ||
| 443 | + // All probes at the same location must share one condition. We split the | ||
| 444 | + // existence check since if one probe does not have a condition (= undefined), | ||
| 445 | + // then all probes at the location must also omit it. | ||
| 446 | + if (conditionByLocation.has(key)) { | ||
| 447 | + if (conditionByLocation.get(key) !== condition) { | ||
| 448 | + throw new ERR_DEBUGGER_STARTUP_ERROR( | ||
| 449 | + `Probes at ${formatTargetText(target)} must use the same --cond (or none)`); | ||
| 450 | + } | ||
| 451 | + } else { | ||
| 452 | + conditionByLocation.set(key, condition); | ||
| 453 | + } | ||
| 454 | + } | ||
| 455 | + | ||
| 413 | 456 | const childArgv = ArrayPrototypeSlice(args, childStartIndex); | |
| 414 | 457 | if (childArgv.length === 0) { | |
| 415 | 458 | throw new ERR_DEBUGGER_STARTUP_ERROR('Probe mode requires a child script'); | |
@@ -479,8 +522,8 @@ class ProbeInspectorSession { | |||
| 479 | 522 | this.resolveCompletion = resolve; | |
| 480 | 523 | /** @type {Probe[]} */ | |
| 481 | 524 | this.probes = ArrayPrototypeMap(options.probes, | |
| 482 | - ({ expr, target, maxHit }) => | ||
| 483 | - ({ expr, target, maxHit: maxHit ?? Infinity, hits: 0 })); | ||
| 525 | + ({ expr, target, maxHit, condition }) => | ||
| 526 | + ({ expr, target, condition, maxHit: maxHit ?? Infinity, hits: 0 })); | ||
| 484 | 527 | this.onChildOutput = FunctionPrototypeBind(this.onChildOutput, this); | |
| 485 | 528 | this.onChildExit = FunctionPrototypeBind(this.onChildExit, this); | |
| 486 | 529 | this.onClientClose = FunctionPrototypeBind(this.onClientClose, this); | |
@@ -887,17 +930,19 @@ class ProbeInspectorSession { | |||
| 887 | 930 | const uniqueTargets = new SafeMap(); | |
| 888 | 931 | ||
| 889 | 932 | for (let probeIndex = 0; probeIndex < this.probes.length; probeIndex++) { | |
| 890 | - const { target } = this.probes[probeIndex]; | ||
| 891 | - const key = `${target.suffix}\n${target.line}\n${target.column ?? ''}`; | ||
| 933 | + const { target, condition } = this.probes[probeIndex]; | ||
| 934 | + // Probes at the same location share one V8 breakpoint. parseProbeTokens has | ||
| 935 | + // already ensured they carry the same condition. | ||
| 936 | + const key = locationKey(target); | ||
| 892 | 937 | let entry = uniqueTargets.get(key); | |
| 893 | 938 | if (entry === undefined) { | |
| 894 | - entry = { target, probeIndices: [] }; | ||
| 939 | + entry = { target, condition, probeIndices: [] }; | ||
| 895 | 940 | uniqueTargets.set(key, entry); | |
| 896 | 941 | } | |
| 897 | 942 | ArrayPrototypePush(entry.probeIndices, probeIndex); | |
| 898 | 943 | } | |
| 899 | 944 | ||
| 900 | - for (const { target, probeIndices } of uniqueTargets.values()) { | ||
| 945 | + for (const { target, condition, probeIndices } of uniqueTargets.values()) { | ||
| 901 | 946 | // On Windows, normalize backslashes to forward slashes so the regex matches | |
| 902 | 947 | // V8 script URLs which always use forward slashes. | |
| 903 | 948 | const normalizedFile = process.platform === 'win32' ? | |
@@ -918,6 +963,9 @@ class ProbeInspectorSession { | |||
| 918 | 963 | // the inspector bind to the first executable column. | |
| 919 | 964 | params.columnNumber = target.column - 1; | |
| 920 | 965 | } | |
| 966 | + if (condition !== undefined) { | ||
| 967 | + params.condition = condition; | ||
| 968 | + } | ||
| 921 | 969 | ||
| 922 | 970 | const result = await this.callCdp('Debugger.setBreakpointByUrl', params); | |
| 923 | 971 | debug('breakpoint set: id=%s urlRegex=%s locations=%j', | |
@@ -943,9 +991,10 @@ class ProbeInspectorSession { | |||
| 943 | 991 | code: exitCode, | |
| 944 | 992 | report: { | |
| 945 | 993 | v: kProbeVersion, | |
| 946 | - probes: ArrayPrototypeMap(this.probes, ({ expr, target, maxHit }) => { | ||
| 947 | - // Omit an unlimited maxHit, as Infinity would serialize to null in JSON. | ||
| 994 | + probes: ArrayPrototypeMap(this.probes, ({ expr, target, maxHit, condition }) => { | ||
| 948 | 995 | const probe = { expr, target }; | |
| 996 | + if (condition !== undefined) { probe.condition = condition; } | ||
| 997 | + // Omit an unlimited maxHit, as Infinity would serialize to null in JSON. | ||
| 949 | 998 | if (maxHit !== Infinity) { probe.maxHit = maxHit; } | |
| 950 | 999 | return probe; | |
| 951 | 1000 | }), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,40 @@ | |||
| 1 | + // This tests that probe mode rejects malformed --cond usage. | ||
| 2 | + 'use strict'; | ||
| 3 | + | ||
| 4 | + const common = require('../common'); | ||
| 5 | + common.skipIfInspectorDisabled(); | ||
| 6 | + | ||
| 7 | + const fixtures = require('../common/fixtures'); | ||
| 8 | + const { assertProbeCliError } = require('../common/debugger-probe'); | ||
| 9 | + | ||
| 10 | + const cwd = fixtures.path('debugger'); | ||
| 11 | + | ||
| 12 | + assertProbeCliError( | ||
| 13 | + ['--cond', 'x', '--probe', 'probe.js:12', '--expr', 'finalValue', 'probe.js'], | ||
| 14 | + /Unexpected --cond before --probe/, { cwd }); | ||
| 15 | + | ||
| 16 | + assertProbeCliError( | ||
| 17 | + ['--probe', 'probe.js:12', '--cond', 'x', '--expr', 'finalValue', 'probe.js'], | ||
| 18 | + /Each --probe must be followed immediately by --expr/, { cwd }); | ||
| 19 | + | ||
| 20 | + assertProbeCliError( | ||
| 21 | + ['--probe', 'probe.js:12', '--expr', 'finalValue', '--cond', 'x', '--cond', 'y', 'probe.js'], | ||
| 22 | + /A --probe can have at most one --cond/, { cwd }); | ||
| 23 | + | ||
| 24 | + assertProbeCliError( | ||
| 25 | + ['--probe', 'probe.js:12', '--expr', 'finalValue', '--cond', ' ', 'probe.js'], | ||
| 26 | + /Missing value for --cond/, { cwd }); | ||
| 27 | + | ||
| 28 | + assertProbeCliError( | ||
| 29 | + ['--probe', 'probe.js:12', '--expr', 'finalValue', '--cond'], | ||
| 30 | + /Missing value for --cond/, { cwd }); | ||
| 31 | + | ||
| 32 | + assertProbeCliError( | ||
| 33 | + ['--probe', 'probe.js:12', '--expr', 'a', '--cond', 'x', | ||
| 34 | + '--probe', 'probe.js:12', '--expr', 'b', '--cond', 'y', 'probe.js'], | ||
| 35 | + /Probes at probe\.js:12 must use the same --cond \(or none\)/, { cwd }); | ||
| 36 | + | ||
| 37 | + assertProbeCliError( | ||
| 38 | + ['--probe', 'probe.js:12', '--expr', 'a', '--cond', 'x', | ||
| 39 | + '--probe', 'probe.js:12', '--expr', 'b', 'probe.js'], | ||
| 40 | + /Probes at probe\.js:12 must use the same --cond \(or none\)/, { cwd }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,47 @@ | |||
| 1 | + // This tests that --cond and --max-hit work together. | ||
| 2 | + 'use strict'; | ||
| 3 | + | ||
| 4 | + const common = require('../common'); | ||
| 5 | + common.skipIfInspectorDisabled(); | ||
| 6 | + | ||
| 7 | + const fixtures = require('../common/fixtures'); | ||
| 8 | + const { spawnSyncAndAssert } = require('../common/child_process'); | ||
| 9 | + const { assertProbeJson } = require('../common/debugger-probe'); | ||
| 10 | + | ||
| 11 | + const cwd = fixtures.path('debugger'); | ||
| 12 | + const probeUrl = fixtures.fileURL('debugger', 'probe-max-hit.js').href; | ||
| 13 | + | ||
| 14 | + // --max-hit written before --cond. The condition still filters index !== 1, so | ||
| 15 | + // the only recorded hit carries value 1 rather than the loop's first iteration. | ||
| 16 | + spawnSyncAndAssert(process.execPath, [ | ||
| 17 | + 'inspect', | ||
| 18 | + '--json', | ||
| 19 | + '--probe', 'probe-max-hit.js:5', | ||
| 20 | + '--expr', 'index', | ||
| 21 | + '--max-hit', '5', | ||
| 22 | + '--cond', 'index === 1', | ||
| 23 | + 'probe-max-hit.js', | ||
| 24 | + ], { cwd }, { | ||
| 25 | + stdout(output) { | ||
| 26 | + assertProbeJson(output, { | ||
| 27 | + v: 2, | ||
| 28 | + probes: [{ | ||
| 29 | + expr: 'index', | ||
| 30 | + condition: 'index === 1', | ||
| 31 | + maxHit: 5, | ||
| 32 | + target: { suffix: 'probe-max-hit.js', line: 5 }, | ||
| 33 | + }], | ||
| 34 | + results: [ | ||
| 35 | + { | ||
| 36 | + probe: 0, | ||
| 37 | + event: 'hit', | ||
| 38 | + hit: 1, | ||
| 39 | + location: { url: probeUrl, line: 5, column: 3 }, | ||
| 40 | + result: { type: 'number', value: 1, description: '1' }, | ||
| 41 | + }, | ||
| 42 | + { event: 'completed' }, | ||
| 43 | + ], | ||
| 44 | + }); | ||
| 45 | + }, | ||
| 46 | + trim: true, | ||
| 47 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments