dd-trace 5.93.0 → 5.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +46 -44
- package/index.d.ts +182 -13
- package/package.json +14 -10
- package/packages/datadog-instrumentations/src/anthropic.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/{orchestrion/compiler.js → compiler.js} +4 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +16 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/{orchestrion/transforms.js → transforms.js} +3 -89
- package/packages/datadog-instrumentations/src/jest.js +118 -32
- package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
- package/packages/datadog-instrumentations/src/playwright.js +10 -0
- package/packages/datadog-instrumentations/src/vitest.js +119 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -4
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +11 -0
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-vitest/src/index.js +9 -0
- package/packages/datadog-webpack/index.js +187 -0
- package/packages/datadog-webpack/src/loader.js +27 -0
- package/packages/datadog-webpack/src/log.js +32 -0
- package/packages/dd-trace/src/azure_metadata.js +15 -15
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +176 -33
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +76 -1
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +259 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +56 -0
- package/packages/dd-trace/src/config/config-base.d.ts +7 -0
- package/packages/dd-trace/src/config/config-base.js +5 -0
- package/packages/dd-trace/src/config/config-types.d.ts +78 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +582 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/constants/text.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic.js +11 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +4 -1
- package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
- package/packages/dd-trace/src/opentracing/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/util/test.js +76 -0
- package/packages/dd-trace/src/priority_sampler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
- package/packages/dd-trace/src/rate_limiter.js +2 -1
- package/packages/dd-trace/src/tagger.js +31 -35
- package/vendor/dist/@apm-js-collab/code-transformer/LICENSE +28 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +133 -0
- package/vendor/dist/@opentelemetry/core/index.js +1 -1
- package/vendor/dist/@opentelemetry/resources/index.js +1 -1
- package/vendor/dist/esquery/index.js +1 -1
- package/vendor/dist/meriyah/index.js +1 -1
- package/webpack.js +3 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +0 -43
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +0 -49
- package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +0 -121
- package/vendor/dist/astring/LICENSE +0 -19
- package/vendor/dist/astring/index.js +0 -1
|
@@ -35,6 +35,7 @@ const integrationCounters = {
|
|
|
35
35
|
|
|
36
36
|
const startCh = channel('dd-trace:span:start')
|
|
37
37
|
const finishCh = channel('dd-trace:span:finish')
|
|
38
|
+
const tagsUpdateCh = channel('dd-trace:span:tags:update')
|
|
38
39
|
|
|
39
40
|
function getIntegrationCounter (event, integration) {
|
|
40
41
|
const counters = integrationCounters[event]
|
|
@@ -399,6 +400,10 @@ class DatadogSpan {
|
|
|
399
400
|
tagger.add(this._spanContext._tags, keyValuePairs)
|
|
400
401
|
|
|
401
402
|
this._prioritySampler.sample(this, false)
|
|
403
|
+
|
|
404
|
+
if (tagsUpdateCh.hasSubscribers) {
|
|
405
|
+
tagsUpdateCh.publish(this)
|
|
406
|
+
}
|
|
402
407
|
}
|
|
403
408
|
}
|
|
404
409
|
|
|
@@ -18,13 +18,6 @@ const TEST_OPTIMIZATION_PLUGINS = new Set([
|
|
|
18
18
|
|
|
19
19
|
const loadChannel = channel('dd-trace:instrumentation:load')
|
|
20
20
|
|
|
21
|
-
// instrument everything that needs Plugin System V2 instrumentation
|
|
22
|
-
require('../../datadog-instrumentations')
|
|
23
|
-
if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined) {
|
|
24
|
-
// instrument lambda environment
|
|
25
|
-
require('./lambda')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
const DD_TRACE_DISABLED_PLUGINS = getValueFromEnvSources('DD_TRACE_DISABLED_PLUGINS')
|
|
29
22
|
|
|
30
23
|
const disabledPlugins = new Set(
|
|
@@ -35,10 +28,20 @@ const disabledPlugins = new Set(
|
|
|
35
28
|
|
|
36
29
|
const pluginClasses = {}
|
|
37
30
|
|
|
31
|
+
// Subscribe before requiring instrumentations so that loadChannel events fired
|
|
32
|
+
// during instrumentation initialization (e.g. re-requires in bundler contexts)
|
|
33
|
+
// are captured and populate pluginClasses correctly.
|
|
38
34
|
loadChannel.subscribe(({ name }) => {
|
|
39
35
|
maybeEnable(plugins[name])
|
|
40
36
|
})
|
|
41
37
|
|
|
38
|
+
// instrument everything that needs Plugin System V2 instrumentation
|
|
39
|
+
require('../../datadog-instrumentations')
|
|
40
|
+
if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined) {
|
|
41
|
+
// instrument lambda environment
|
|
42
|
+
require('./lambda')
|
|
43
|
+
}
|
|
44
|
+
|
|
42
45
|
function maybeEnable (Plugin) {
|
|
43
46
|
if (!Plugin || typeof Plugin !== 'function') return
|
|
44
47
|
if (!pluginClasses[Plugin.id]) {
|
|
@@ -88,8 +88,24 @@ const TEST_EARLY_FLAKE_ABORT_REASON = 'test.early_flake.abort_reason'
|
|
|
88
88
|
const TEST_RETRY_REASON = 'test.retry_reason'
|
|
89
89
|
const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
|
|
90
90
|
const TEST_IS_MODIFIED = 'test.is_modified'
|
|
91
|
+
const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
|
|
91
92
|
const CI_APP_ORIGIN = 'ciapp-test'
|
|
92
93
|
|
|
94
|
+
// Matches patterns that are almost certainly runtime-generated values in test names:
|
|
95
|
+
// - Unix timestamps in ms (13 digits, years ~2020-2090) or s (10 digits)
|
|
96
|
+
// - UUIDs (8-4-4-4-12 hex)
|
|
97
|
+
// - ISO 8601 dates (2024-03-23) or date-times (2024-03-23T14:30)
|
|
98
|
+
// - Random ports on localhost, 127.0.0.1, or 0.0.0.0
|
|
99
|
+
// - Math.random() float values (10+ decimal digits after 0.)
|
|
100
|
+
const DYNAMIC_NAME_RE = new RegExp(
|
|
101
|
+
String.raw`\b1[6-9]\d{8,11}\b|` +
|
|
102
|
+
String.raw`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|` +
|
|
103
|
+
String.raw`\b\d{4}-\d{2}-\d{2}|` +
|
|
104
|
+
String.raw`(?:localhost|127\.0\.0\.1|0\.0\.0\.0):\d{4,5}\b|` +
|
|
105
|
+
String.raw`\b0\.\d{10,}`,
|
|
106
|
+
'i'
|
|
107
|
+
)
|
|
108
|
+
|
|
93
109
|
const JEST_TEST_RUNNER = 'test.jest.test_runner'
|
|
94
110
|
const JEST_DISPLAY_NAME = 'test.jest.display_name'
|
|
95
111
|
|
|
@@ -260,6 +276,7 @@ module.exports = {
|
|
|
260
276
|
TEST_RETRY_REASON,
|
|
261
277
|
TEST_HAS_FAILED_ALL_RETRIES,
|
|
262
278
|
TEST_IS_MODIFIED,
|
|
279
|
+
TEST_HAS_DYNAMIC_NAME,
|
|
263
280
|
getTestEnvironmentMetadata,
|
|
264
281
|
getTestParametersString,
|
|
265
282
|
finishAllTraceSpans,
|
|
@@ -337,6 +354,9 @@ module.exports = {
|
|
|
337
354
|
POSSIBLE_BASE_BRANCHES,
|
|
338
355
|
GIT_COMMIT_SHA,
|
|
339
356
|
GIT_REPOSITORY_URL,
|
|
357
|
+
DYNAMIC_NAME_RE,
|
|
358
|
+
collectDynamicNamesFromTraces,
|
|
359
|
+
logDynamicNamesWarning,
|
|
340
360
|
}
|
|
341
361
|
|
|
342
362
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -1178,6 +1198,62 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1178
1198
|
return result
|
|
1179
1199
|
}
|
|
1180
1200
|
|
|
1201
|
+
/**
|
|
1202
|
+
* Scans serialized worker trace payloads for tests tagged with TEST_HAS_DYNAMIC_NAME
|
|
1203
|
+
* and populates the provided Set. Silently ignores parse errors.
|
|
1204
|
+
*
|
|
1205
|
+
* @param {string} data - JSON-serialized traces from a worker
|
|
1206
|
+
* @param {Set<string>} newTestsWithDynamicNames - Set to populate with "suite › name" strings
|
|
1207
|
+
*/
|
|
1208
|
+
function collectDynamicNamesFromTraces (data, newTestsWithDynamicNames) {
|
|
1209
|
+
try {
|
|
1210
|
+
const traces = JSON.parse(data)
|
|
1211
|
+
for (const trace of traces) {
|
|
1212
|
+
for (const span of trace) {
|
|
1213
|
+
if (span.meta?.[TEST_HAS_DYNAMIC_NAME] === 'true') {
|
|
1214
|
+
const suite = span.meta[TEST_SUITE]
|
|
1215
|
+
const name = span.meta[TEST_NAME]
|
|
1216
|
+
if (suite && name) {
|
|
1217
|
+
newTestsWithDynamicNames.add(`${suite} › ${name}`)
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} catch {
|
|
1223
|
+
// ignore parse errors
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Logs a "Datadog Test Optimization" warning about new tests with dynamic names.
|
|
1229
|
+
* Clears the Set after logging. No-op if the Set is empty.
|
|
1230
|
+
*
|
|
1231
|
+
* @param {Set<string>} newTestsWithDynamicNames
|
|
1232
|
+
*/
|
|
1233
|
+
function logDynamicNamesWarning (newTestsWithDynamicNames) {
|
|
1234
|
+
if (newTestsWithDynamicNames.size === 0) return
|
|
1235
|
+
|
|
1236
|
+
const MAX_SHOWN = 10
|
|
1237
|
+
const names = [...newTestsWithDynamicNames]
|
|
1238
|
+
const shown = names.slice(0, MAX_SHOWN)
|
|
1239
|
+
const more = names.length - shown.length
|
|
1240
|
+
const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
|
|
1241
|
+
const nameList = shown.map(n => ` • ${n}`).join('\n') + moreSuffix
|
|
1242
|
+
|
|
1243
|
+
const line = '-'.repeat(50)
|
|
1244
|
+
// eslint-disable-next-line no-console -- Intentional user-facing session summary
|
|
1245
|
+
console.warn(
|
|
1246
|
+
`\n${line}\nDatadog Test Optimization\n${line}\n` +
|
|
1247
|
+
`${newTestsWithDynamicNames.size} test(s) detected as new but their names contain ` +
|
|
1248
|
+
'dynamic data (timestamps, UUIDs, etc.).\n' +
|
|
1249
|
+
'Tests with changing names are always treated as new on every run, ' +
|
|
1250
|
+
'causing unnecessary Early Flake Detection retries and preventing correct new test detection.\n' +
|
|
1251
|
+
'Consider using stable, deterministic test names.\n\n' +
|
|
1252
|
+
`${nameList}\n`
|
|
1253
|
+
)
|
|
1254
|
+
newTestsWithDynamicNames.clear()
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1181
1257
|
function isModifiedTest (testPath, testStartLine, testEndLine, modifiedFiles, testFramework) {
|
|
1182
1258
|
if (modifiedFiles === undefined) {
|
|
1183
1259
|
return false
|
|
@@ -249,7 +249,7 @@ class PrioritySampler {
|
|
|
249
249
|
}
|
|
250
250
|
const rawPriority = tags[SAMPLING_PRIORITY]
|
|
251
251
|
if (rawPriority !== undefined) {
|
|
252
|
-
const priority =
|
|
252
|
+
const priority = Math.trunc(rawPriority)
|
|
253
253
|
|
|
254
254
|
if (priority === 1 || priority === 2) {
|
|
255
255
|
return USER_KEEP
|
|
@@ -20,6 +20,7 @@ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
|
|
|
20
20
|
let beforeCh
|
|
21
21
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
22
22
|
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
23
|
+
const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
|
|
23
24
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
24
25
|
|
|
25
26
|
const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')
|
|
@@ -58,33 +59,30 @@ function ensureChannelsActivated (asyncContextFrameEnabled) {
|
|
|
58
59
|
if (channelsActivated) return
|
|
59
60
|
|
|
60
61
|
const shimmer = require('../../../../datadog-shimmer')
|
|
61
|
-
const asyncHooks = require('async_hooks')
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
const { createHook } = asyncHooks
|
|
67
|
-
beforeCh = dc.channel('dd-trace:storage:before')
|
|
68
|
-
createHook({ before: () => beforeCh.publish() }).enable()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { AsyncLocalStorage } = asyncHooks
|
|
72
|
-
|
|
73
|
-
// We need to instrument AsyncLocalStorage.enterWith() both with and without AsyncContextFrame.
|
|
63
|
+
// We need to instrument enterWith() on the legacy storage — that's the storage
|
|
64
|
+
// carrying span data and the only one the profiler cares about.
|
|
65
|
+
const legacyStorage = storage('legacy')
|
|
74
66
|
let inRun = false
|
|
75
|
-
shimmer.wrap(
|
|
76
|
-
return function (
|
|
77
|
-
const retVal = original.
|
|
67
|
+
shimmer.wrap(legacyStorage, 'enterWith', function (original) {
|
|
68
|
+
return function (store) {
|
|
69
|
+
const retVal = original.call(this, store)
|
|
78
70
|
if (!inRun) enterCh.publish()
|
|
79
71
|
return retVal
|
|
80
72
|
}
|
|
81
73
|
})
|
|
82
74
|
|
|
83
|
-
//
|
|
84
|
-
// AsyncContextFrame-based implementation of AsyncLocalStorage.run() delegates
|
|
85
|
-
// to AsyncLocalStorage.enterWith() so it doesn't need to be separately instrumented.
|
|
75
|
+
// When not using AsyncContextFrame, we need additional instrumentation.
|
|
86
76
|
if (!asyncContextFrameEnabled) {
|
|
87
|
-
|
|
77
|
+
// We need async_hooks.createHook to create a "before" callback.
|
|
78
|
+
const { createHook } = require('async_hooks')
|
|
79
|
+
beforeCh = dc.channel('dd-trace:storage:before')
|
|
80
|
+
createHook({ before: () => beforeCh.publish() }).enable()
|
|
81
|
+
|
|
82
|
+
// In ACF-based implementation run() delegates to enterWith() so it doesn't
|
|
83
|
+
// need to be separately instrumented. in non-ACF implementation run()
|
|
84
|
+
// doesn't delegate to enterWith(), so separate instrumentation is necessary.
|
|
85
|
+
shimmer.wrap(legacyStorage, 'run', function (original) {
|
|
88
86
|
return function (store, callback, ...args) {
|
|
89
87
|
const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
|
|
90
88
|
inRun = false
|
|
@@ -125,6 +123,7 @@ class NativeWallProfiler {
|
|
|
125
123
|
// Bind these to this so they can be used as callbacks
|
|
126
124
|
#boundEnter = this.#enter.bind(this)
|
|
127
125
|
#boundSpanFinished = this.#spanFinished.bind(this)
|
|
126
|
+
#boundSpanTagsUpdated = this.#spanTagsUpdated.bind(this)
|
|
128
127
|
#boundGenerateLabels = this._generateLabels.bind(this)
|
|
129
128
|
|
|
130
129
|
get type () { return 'wall' }
|
|
@@ -204,6 +203,9 @@ class NativeWallProfiler {
|
|
|
204
203
|
}
|
|
205
204
|
enterCh.subscribe(this.#boundEnter)
|
|
206
205
|
spanFinishCh.subscribe(this.#boundSpanFinished)
|
|
206
|
+
if (this.#endpointCollectionEnabled) {
|
|
207
|
+
tagsUpdateCh.subscribe(this.#boundSpanTagsUpdated)
|
|
208
|
+
}
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
@@ -290,15 +292,7 @@ class NativeWallProfiler {
|
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
profilingContext = { spanId, rootSpanId, webTags }
|
|
293
|
-
|
|
294
|
-
// the span's type hasn't been set yet. TracingPlugin.startSpan() calls
|
|
295
|
-
// enterWith() before the plugin sets span.type='web' via addRequestTags(),
|
|
296
|
-
// so the first enterCh event fires before the type is known. Without this
|
|
297
|
-
// guard we'd cache webTags=undefined and then serve that stale value on the
|
|
298
|
-
// subsequent activation (when span.type='web' is already set).
|
|
299
|
-
if (!this.#endpointCollectionEnabled || webTags !== undefined || context._tags['span.type']) {
|
|
300
|
-
span[ProfilingContext] = profilingContext
|
|
301
|
-
}
|
|
295
|
+
span[ProfilingContext] = profilingContext
|
|
302
296
|
}
|
|
303
297
|
return profilingContext
|
|
304
298
|
}
|
|
@@ -317,6 +311,16 @@ class NativeWallProfiler {
|
|
|
317
311
|
}
|
|
318
312
|
}
|
|
319
313
|
|
|
314
|
+
#spanTagsUpdated (span) {
|
|
315
|
+
if (!this.#started) return
|
|
316
|
+
const profilingContext = span[ProfilingContext]
|
|
317
|
+
if (profilingContext === undefined || profilingContext.webTags !== undefined) return
|
|
318
|
+
const tags = span.context()._tags
|
|
319
|
+
if (isWebServerSpan(tags)) {
|
|
320
|
+
profilingContext.webTags = tags
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
320
324
|
#reportV8bug (maybeBug) {
|
|
321
325
|
const tag = `v8_profiler_bug_workaround_enabled:${this.#v8ProfilerBugWorkaroundEnabled}`
|
|
322
326
|
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
@@ -355,6 +359,9 @@ class NativeWallProfiler {
|
|
|
355
359
|
}
|
|
356
360
|
enterCh.unsubscribe(this.#boundEnter)
|
|
357
361
|
spanFinishCh.unsubscribe(this.#boundSpanFinished)
|
|
362
|
+
if (this.#endpointCollectionEnabled) {
|
|
363
|
+
tagsUpdateCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
364
|
+
}
|
|
358
365
|
this._profilerState = undefined
|
|
359
366
|
}
|
|
360
367
|
this.#started = false
|
|
@@ -8,7 +8,8 @@ class RateLimiter {
|
|
|
8
8
|
* @param {'second'|'minute'|'hour'|'day'} [interval='second'] - Time window for the limiter.
|
|
9
9
|
*/
|
|
10
10
|
constructor (rateLimit, interval = 'second') {
|
|
11
|
-
|
|
11
|
+
// TODO: Change rateLimit to integers. Right now these are sometimes strings, sometimes numbers.
|
|
12
|
+
this._rateLimit = Math.trunc(rateLimit)
|
|
12
13
|
// The limiter constructor accepts a token count number and an interval string
|
|
13
14
|
this._limiter = new limiter.RateLimiter(this._rateLimit, interval)
|
|
14
15
|
this._tokensRequested = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// TODO: Rename and move file. This is a general purpose helper for adding tags to a carrier.
|
|
4
4
|
|
|
5
5
|
function addNonEmpty (carrier, key, value) {
|
|
6
6
|
if (key !== '') {
|
|
@@ -11,47 +11,43 @@ function addNonEmpty (carrier, key, value) {
|
|
|
11
11
|
function add (carrier, keyValuePairs) {
|
|
12
12
|
if (!carrier) return
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let keyStart = 0
|
|
18
|
-
|
|
19
|
-
for (let i = 0; i < keyValuePairs.length; i++) {
|
|
20
|
-
const char = keyValuePairs[i]
|
|
21
|
-
|
|
22
|
-
if (char === ':') {
|
|
23
|
-
if (valueStart === 0) {
|
|
24
|
-
valueStart = i
|
|
25
|
-
}
|
|
26
|
-
} else if (char === ',') {
|
|
27
|
-
valueStart ||= i
|
|
28
|
-
addNonEmpty(
|
|
29
|
-
carrier,
|
|
30
|
-
keyValuePairs.slice(keyStart, valueStart).trim(),
|
|
31
|
-
keyValuePairs.slice(valueStart + 1, i).trim()
|
|
32
|
-
)
|
|
33
|
-
keyStart = i + 1
|
|
34
|
-
valueStart = 0
|
|
35
|
-
}
|
|
36
|
-
}
|
|
14
|
+
if (typeof keyValuePairs === 'string') {
|
|
15
|
+
let valueStart = 0
|
|
16
|
+
let keyStart = 0
|
|
37
17
|
|
|
38
|
-
|
|
39
|
-
|
|
18
|
+
for (let i = 0; i < keyValuePairs.length; i++) {
|
|
19
|
+
const char = keyValuePairs[i]
|
|
20
|
+
|
|
21
|
+
if (char === ':') {
|
|
22
|
+
if (valueStart === 0) {
|
|
23
|
+
valueStart = i
|
|
24
|
+
}
|
|
25
|
+
} else if (char === ',') {
|
|
26
|
+
valueStart ||= i
|
|
40
27
|
addNonEmpty(
|
|
41
28
|
carrier,
|
|
42
29
|
keyValuePairs.slice(keyStart, valueStart).trim(),
|
|
43
|
-
keyValuePairs.slice(valueStart + 1).trim()
|
|
30
|
+
keyValuePairs.slice(valueStart + 1, i).trim()
|
|
44
31
|
)
|
|
32
|
+
keyStart = i + 1
|
|
33
|
+
valueStart = 0
|
|
45
34
|
}
|
|
46
|
-
} else if (Array.isArray(keyValuePairs)) {
|
|
47
|
-
for (const tags of keyValuePairs) {
|
|
48
|
-
add(carrier, tags)
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
Object.assign(carrier, keyValuePairs)
|
|
52
35
|
}
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
|
|
37
|
+
if (keyValuePairs.at(-1) !== ',') {
|
|
38
|
+
valueStart ||= keyValuePairs.length
|
|
39
|
+
addNonEmpty(
|
|
40
|
+
carrier,
|
|
41
|
+
keyValuePairs.slice(keyStart, valueStart).trim(),
|
|
42
|
+
keyValuePairs.slice(valueStart + 1).trim()
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
} else if (Array.isArray(keyValuePairs)) {
|
|
46
|
+
for (const tags of keyValuePairs) {
|
|
47
|
+
add(carrier, tags)
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
Object.assign(carrier, keyValuePairs)
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
* Neither the names of the Mozilla Foundation nor the names of project
|
|
16
|
+
contributors may be used to endorse or promote products derived from this
|
|
17
|
+
software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
20
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|