@zuplo/cli 7.7.2 → 7.7.4
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/node_modules/@posthog/core/LICENSE +417 -0
- package/node_modules/@posthog/core/dist/error-tracking/parsers/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/error-tracking/parsers/index.js +87 -2
- package/node_modules/@posthog/core/dist/error-tracking/parsers/index.mjs +87 -2
- package/node_modules/@posthog/core/dist/utils/index.d.ts +2 -0
- package/node_modules/@posthog/core/dist/utils/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/utils/index.js +36 -20
- package/node_modules/@posthog/core/dist/utils/index.mjs +5 -1
- package/node_modules/@posthog/core/package.json +2 -2
- package/node_modules/@posthog/core/src/error-tracking/error-properties-builder.parse.spec.ts +3 -1
- package/node_modules/@posthog/core/src/error-tracking/parsers/index.spec.ts +270 -0
- package/node_modules/@posthog/core/src/error-tracking/parsers/index.ts +197 -2
- package/node_modules/@posthog/core/src/utils/index.ts +6 -0
- package/node_modules/@posthog/types/LICENSE +64 -0
- package/node_modules/@posthog/types/dist/posthog-config.d.ts +6 -0
- package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
- package/node_modules/@posthog/types/package.json +1 -1
- package/node_modules/@posthog/types/src/posthog-config.ts +6 -0
- package/node_modules/@zuplo/core/customer.cli.minified.js +185 -185
- package/node_modules/@zuplo/core/index.minified.js +190 -190
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +2 -2
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-BXPXENPX.js → chunk-Z6XBRCGQ.js} +80 -80
- package/node_modules/@zuplo/runtime/out/esm/chunk-Z6XBRCGQ.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
- package/node_modules/@zuplo/runtime/package.json +1 -1
- package/node_modules/js-yaml/dist/js-yaml.js +16 -5
- package/node_modules/js-yaml/dist/js-yaml.js.map +1 -1
- package/node_modules/js-yaml/dist/js-yaml.min.js +1 -1
- package/node_modules/js-yaml/dist/js-yaml.min.js.map +1 -1
- package/node_modules/js-yaml/dist/js-yaml.mjs +14 -6
- package/node_modules/js-yaml/dist/js-yaml.mjs.map +1 -1
- package/node_modules/js-yaml/lib/loader.js +16 -3
- package/node_modules/js-yaml/lib/type/omap.js +3 -3
- package/node_modules/js-yaml/package.json +1 -1
- package/package.json +12 -12
- package/node_modules/@zuplo/runtime/out/esm/chunk-BXPXENPX.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-BXPXENPX.js.LEGAL.txt → chunk-Z6XBRCGQ.js.LEGAL.txt} +0 -0
|
@@ -81,9 +81,10 @@ function __webpack_require__(moduleId) {
|
|
|
81
81
|
var __webpack_exports__ = {};
|
|
82
82
|
(()=>{
|
|
83
83
|
__webpack_require__.r(__webpack_exports__);
|
|
84
|
-
var
|
|
84
|
+
var _type_utils__rspack_import_0 = __webpack_require__("./type-utils");
|
|
85
|
+
var _bot_detection__rspack_import_1 = __webpack_require__("./bot-detection");
|
|
85
86
|
var __rspack_reexport = {};
|
|
86
|
-
for(const __rspack_import_key in
|
|
87
|
+
for(const __rspack_import_key in _bot_detection__rspack_import_1)if ([
|
|
87
88
|
"UUID_REGEX",
|
|
88
89
|
"currentTimestamp",
|
|
89
90
|
"getEventUuid",
|
|
@@ -101,12 +102,13 @@ var __webpack_exports__ = {};
|
|
|
101
102
|
"retriable",
|
|
102
103
|
"isPromise",
|
|
103
104
|
"getFetch",
|
|
105
|
+
"isUrl",
|
|
104
106
|
"toJsonSafeValue"
|
|
105
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
107
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_bot_detection__rspack_import_1[__rspack_import_key];
|
|
106
108
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
107
|
-
var
|
|
109
|
+
var _browser_utils__rspack_import_2 = __webpack_require__("./browser-utils");
|
|
108
110
|
var __rspack_reexport = {};
|
|
109
|
-
for(const __rspack_import_key in
|
|
111
|
+
for(const __rspack_import_key in _browser_utils__rspack_import_2)if ([
|
|
110
112
|
"UUID_REGEX",
|
|
111
113
|
"currentTimestamp",
|
|
112
114
|
"getEventUuid",
|
|
@@ -124,12 +126,13 @@ var __webpack_exports__ = {};
|
|
|
124
126
|
"retriable",
|
|
125
127
|
"isPromise",
|
|
126
128
|
"getFetch",
|
|
129
|
+
"isUrl",
|
|
127
130
|
"toJsonSafeValue"
|
|
128
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
131
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_browser_utils__rspack_import_2[__rspack_import_key];
|
|
129
132
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
130
|
-
var
|
|
133
|
+
var _bucketed_rate_limiter__rspack_import_3 = __webpack_require__("./bucketed-rate-limiter");
|
|
131
134
|
var __rspack_reexport = {};
|
|
132
|
-
for(const __rspack_import_key in
|
|
135
|
+
for(const __rspack_import_key in _bucketed_rate_limiter__rspack_import_3)if ([
|
|
133
136
|
"UUID_REGEX",
|
|
134
137
|
"currentTimestamp",
|
|
135
138
|
"getEventUuid",
|
|
@@ -147,13 +150,14 @@ var __webpack_exports__ = {};
|
|
|
147
150
|
"retriable",
|
|
148
151
|
"isPromise",
|
|
149
152
|
"getFetch",
|
|
153
|
+
"isUrl",
|
|
150
154
|
"toJsonSafeValue"
|
|
151
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
155
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_bucketed_rate_limiter__rspack_import_3[__rspack_import_key];
|
|
152
156
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
153
|
-
var
|
|
154
|
-
var
|
|
157
|
+
var _json_utils__rspack_import_4 = __webpack_require__("./json-utils");
|
|
158
|
+
var _number_utils__rspack_import_5 = __webpack_require__("./number-utils");
|
|
155
159
|
var __rspack_reexport = {};
|
|
156
|
-
for(const __rspack_import_key in
|
|
160
|
+
for(const __rspack_import_key in _number_utils__rspack_import_5)if ([
|
|
157
161
|
"UUID_REGEX",
|
|
158
162
|
"currentTimestamp",
|
|
159
163
|
"getEventUuid",
|
|
@@ -171,12 +175,13 @@ var __webpack_exports__ = {};
|
|
|
171
175
|
"retriable",
|
|
172
176
|
"isPromise",
|
|
173
177
|
"getFetch",
|
|
178
|
+
"isUrl",
|
|
174
179
|
"toJsonSafeValue"
|
|
175
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
180
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_number_utils__rspack_import_5[__rspack_import_key];
|
|
176
181
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
177
|
-
var
|
|
182
|
+
var _string_utils__rspack_import_6 = __webpack_require__("./string-utils");
|
|
178
183
|
var __rspack_reexport = {};
|
|
179
|
-
for(const __rspack_import_key in
|
|
184
|
+
for(const __rspack_import_key in _string_utils__rspack_import_6)if ([
|
|
180
185
|
"UUID_REGEX",
|
|
181
186
|
"currentTimestamp",
|
|
182
187
|
"getEventUuid",
|
|
@@ -194,12 +199,12 @@ var __webpack_exports__ = {};
|
|
|
194
199
|
"retriable",
|
|
195
200
|
"isPromise",
|
|
196
201
|
"getFetch",
|
|
202
|
+
"isUrl",
|
|
197
203
|
"toJsonSafeValue"
|
|
198
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
204
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_string_utils__rspack_import_6[__rspack_import_key];
|
|
199
205
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
200
|
-
var _type_utils__rspack_import_6 = __webpack_require__("./type-utils");
|
|
201
206
|
var __rspack_reexport = {};
|
|
202
|
-
for(const __rspack_import_key in
|
|
207
|
+
for(const __rspack_import_key in _type_utils__rspack_import_0)if ([
|
|
203
208
|
"UUID_REGEX",
|
|
204
209
|
"currentTimestamp",
|
|
205
210
|
"getEventUuid",
|
|
@@ -217,8 +222,9 @@ var __webpack_exports__ = {};
|
|
|
217
222
|
"retriable",
|
|
218
223
|
"isPromise",
|
|
219
224
|
"getFetch",
|
|
225
|
+
"isUrl",
|
|
220
226
|
"toJsonSafeValue"
|
|
221
|
-
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>
|
|
227
|
+
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_type_utils__rspack_import_0[__rspack_import_key];
|
|
222
228
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
223
229
|
var _promise_queue__rspack_import_7 = __webpack_require__("./promise-queue");
|
|
224
230
|
var __rspack_reexport = {};
|
|
@@ -240,6 +246,7 @@ var __webpack_exports__ = {};
|
|
|
240
246
|
"retriable",
|
|
241
247
|
"isPromise",
|
|
242
248
|
"getFetch",
|
|
249
|
+
"isUrl",
|
|
243
250
|
"toJsonSafeValue"
|
|
244
251
|
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_promise_queue__rspack_import_7[__rspack_import_key];
|
|
245
252
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
@@ -263,6 +270,7 @@ var __webpack_exports__ = {};
|
|
|
263
270
|
"retriable",
|
|
264
271
|
"isPromise",
|
|
265
272
|
"getFetch",
|
|
273
|
+
"isUrl",
|
|
266
274
|
"toJsonSafeValue"
|
|
267
275
|
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_logger__rspack_import_8[__rspack_import_key];
|
|
268
276
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
@@ -286,6 +294,7 @@ var __webpack_exports__ = {};
|
|
|
286
294
|
"retriable",
|
|
287
295
|
"isPromise",
|
|
288
296
|
"getFetch",
|
|
297
|
+
"isUrl",
|
|
289
298
|
"toJsonSafeValue"
|
|
290
299
|
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_user_agent_utils__rspack_import_9[__rspack_import_key];
|
|
291
300
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
@@ -309,6 +318,7 @@ var __webpack_exports__ = {};
|
|
|
309
318
|
"retriable",
|
|
310
319
|
"isPromise",
|
|
311
320
|
"getFetch",
|
|
321
|
+
"isUrl",
|
|
312
322
|
"toJsonSafeValue"
|
|
313
323
|
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_webview_app_utils__rspack_import_10[__rspack_import_key];
|
|
314
324
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
@@ -339,6 +349,9 @@ var __webpack_exports__ = {};
|
|
|
339
349
|
if (0 === truthyValue.trim().length) return true;
|
|
340
350
|
return false;
|
|
341
351
|
}
|
|
352
|
+
function isUrl(value) {
|
|
353
|
+
return (0, _type_utils__rspack_import_0.isString)(value) && /^(https?:\/\/|\/|\.\.?\/)/i.test(value.trim());
|
|
354
|
+
}
|
|
342
355
|
function removeTrailingSlash(url) {
|
|
343
356
|
return url?.replace(/\/+$/, '');
|
|
344
357
|
}
|
|
@@ -412,13 +425,14 @@ var __webpack_exports__ = {};
|
|
|
412
425
|
currentTimestamp: ()=>currentTimestamp,
|
|
413
426
|
getEventUuid: ()=>getEventUuid,
|
|
414
427
|
getFetch: ()=>getFetch,
|
|
428
|
+
isUrl: ()=>isUrl,
|
|
415
429
|
isValidUUID: ()=>isValidUUID,
|
|
416
430
|
raceWithTimeout: ()=>raceWithTimeout,
|
|
417
431
|
removeTrailingSlash: ()=>removeTrailingSlash,
|
|
418
432
|
retriable: ()=>retriable,
|
|
419
433
|
safeSetTimeout: ()=>safeSetTimeout,
|
|
420
434
|
stripUrlHash: ()=>stripUrlHash,
|
|
421
|
-
toJsonSafeValue: ()=>
|
|
435
|
+
toJsonSafeValue: ()=>_json_utils__rspack_import_4.toJsonSafeValue
|
|
422
436
|
}, {
|
|
423
437
|
STRING_FORMAT: STRING_FORMAT,
|
|
424
438
|
UUID_REGEX: UUID_REGEX,
|
|
@@ -435,6 +449,7 @@ exports.currentTimestamp = __webpack_exports__.currentTimestamp;
|
|
|
435
449
|
exports.getEventUuid = __webpack_exports__.getEventUuid;
|
|
436
450
|
exports.getFetch = __webpack_exports__.getFetch;
|
|
437
451
|
exports.isPromise = __webpack_exports__.isPromise;
|
|
452
|
+
exports.isUrl = __webpack_exports__.isUrl;
|
|
438
453
|
exports.isValidUUID = __webpack_exports__.isValidUUID;
|
|
439
454
|
exports.raceWithTimeout = __webpack_exports__.raceWithTimeout;
|
|
440
455
|
exports.removeTrailingSlash = __webpack_exports__.removeTrailingSlash;
|
|
@@ -453,6 +468,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
453
468
|
"getEventUuid",
|
|
454
469
|
"getFetch",
|
|
455
470
|
"isPromise",
|
|
471
|
+
"isUrl",
|
|
456
472
|
"isValidUUID",
|
|
457
473
|
"raceWithTimeout",
|
|
458
474
|
"removeTrailingSlash",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isString } from "./type-utils.mjs";
|
|
1
2
|
export * from "./bot-detection.mjs";
|
|
2
3
|
export * from "./browser-utils.mjs";
|
|
3
4
|
export * from "./bucketed-rate-limiter.mjs";
|
|
@@ -35,6 +36,9 @@ function isEmpty(truthyValue) {
|
|
|
35
36
|
if (0 === truthyValue.trim().length) return true;
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
39
|
+
function isUrl(value) {
|
|
40
|
+
return isString(value) && /^(https?:\/\/|\/|\.\.?\/)/i.test(value.trim());
|
|
41
|
+
}
|
|
38
42
|
function removeTrailingSlash(url) {
|
|
39
43
|
return url?.replace(/\/+$/, '');
|
|
40
44
|
}
|
|
@@ -101,4 +105,4 @@ function allSettled(promises) {
|
|
|
101
105
|
}))));
|
|
102
106
|
}
|
|
103
107
|
export { toJsonSafeValue } from "./json-utils.mjs";
|
|
104
|
-
export { STRING_FORMAT, UUID_REGEX, allSettled, assert, createNamedError, currentISOTime, currentTimestamp, getEventUuid, getFetch, isPromise, isValidUUID, raceWithTimeout, removeTrailingSlash, retriable, safeSetTimeout, stripUrlHash };
|
|
108
|
+
export { STRING_FORMAT, UUID_REGEX, allSettled, assert, createNamedError, currentISOTime, currentTimestamp, getEventUuid, getFetch, isPromise, isUrl, isValidUUID, raceWithTimeout, removeTrailingSlash, retriable, safeSetTimeout, stripUrlHash };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.52.0",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/PostHog/posthog-js/issues"
|
|
6
6
|
},
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@posthog/types": "^1.409.
|
|
73
|
+
"@posthog/types": "^1.409.4"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@rslib/core": "0.23.2",
|
package/node_modules/@posthog/core/src/error-tracking/error-properties-builder.parse.spec.ts
CHANGED
|
@@ -29,7 +29,9 @@ describe('ErrorPropertiesBuilder', () => {
|
|
|
29
29
|
const syntheticError = new Error()
|
|
30
30
|
const frames = parseStack(syntheticError)
|
|
31
31
|
expect(frames).toBeDefined()
|
|
32
|
-
|
|
32
|
+
// The test runner calls itself once per nested describe block, and the parser keeps one copy
|
|
33
|
+
// of a repeated frame cycle, so those repeats count as a single frame here.
|
|
34
|
+
expect(frames).toHaveLength(13)
|
|
33
35
|
})
|
|
34
36
|
})
|
|
35
37
|
})
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { createDefaultStackParser } from './index'
|
|
2
|
+
|
|
3
|
+
describe('createDefaultStackParser repeated cycle collapsing', () => {
|
|
4
|
+
const parse = createDefaultStackParser()
|
|
5
|
+
|
|
6
|
+
const OVERFLOW = 'RangeError: Maximum call stack size exceeded'
|
|
7
|
+
|
|
8
|
+
const frame = (func: string, line: number, column: number = 10): string =>
|
|
9
|
+
` at ${func} (https://posthog.com/app.js:${line}:${column})`
|
|
10
|
+
|
|
11
|
+
function recursion(cycle: string[], repeats: number): string[] {
|
|
12
|
+
const frames: string[] = []
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < repeats; i++) {
|
|
15
|
+
cycle.forEach((func, index) => frames.push(frame(func, index + 1)))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return frames
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The runtime lists the innermost frame first and cuts the stack wherever the depth ran out, so
|
|
22
|
+
// `dropInnermost` is how many frames of the cycle that cut took off the innermost end.
|
|
23
|
+
function overflowStack(cycle: string[], repeats: number, dropInnermost: number = 0): string {
|
|
24
|
+
return [
|
|
25
|
+
OVERFLOW,
|
|
26
|
+
...recursion(cycle, repeats).slice(dropInnermost),
|
|
27
|
+
frame('handleClick', 90),
|
|
28
|
+
frame('main', 91),
|
|
29
|
+
].join('\n')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function names(stack: string): (string | undefined)[] {
|
|
33
|
+
return parse(stack).map((f) => f.function)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('keeps one copy of a recursion cycle', () => {
|
|
37
|
+
expect(names(overflowStack(['a', 'b'], 30))).toEqual(['main', 'handleClick', 'b', 'a'])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('keeps one copy when a single function calls itself', () => {
|
|
41
|
+
expect(names(overflowStack(['a'], 60))).toEqual(['main', 'handleClick', 'a'])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('keeps the outer frames a recursion used to push past the frame limit', () => {
|
|
45
|
+
// Without collapsing, the frame limit is spent on the cycle and the frames that name the
|
|
46
|
+
// caller never reach the event.
|
|
47
|
+
const frames = names(overflowStack(['a', 'b'], 400))
|
|
48
|
+
expect(frames).toContain('main')
|
|
49
|
+
expect(frames).toContain('handleClick')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('gives the same frames wherever the runtime cut the recursion', () => {
|
|
53
|
+
// The overflow can happen in either function of the pair, which used to change the frames and
|
|
54
|
+
// so open a new issue for every throw.
|
|
55
|
+
const pair = names(overflowStack(['a', 'b'], 30))
|
|
56
|
+
expect(names(overflowStack(['a', 'b'], 30, 1))).toEqual(pair)
|
|
57
|
+
|
|
58
|
+
const triple = names(overflowStack(['a', 'b', 'c'], 30))
|
|
59
|
+
expect(names(overflowStack(['a', 'b', 'c'], 30, 1))).toEqual(triple)
|
|
60
|
+
expect(names(overflowStack(['a', 'b', 'c'], 30, 2))).toEqual(triple)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('gives the same frames when the runtime cut everything outside the recursion', () => {
|
|
64
|
+
// The runtime keeps the innermost frames, so a deep recursion can push every other frame out of
|
|
65
|
+
// the stack. Nothing then says which function of the cycle ran out of stack first.
|
|
66
|
+
const recursionOnly = (dropInnermost: number): (string | undefined)[] =>
|
|
67
|
+
names([OVERFLOW, ...recursion(['a', 'b'], 40).slice(dropInnermost)].join('\n'))
|
|
68
|
+
|
|
69
|
+
expect(recursionOnly(1)).toEqual(recursionOnly(0))
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('gives the same frames when the stack holds a second repeated section', () => {
|
|
73
|
+
// Stacks repeat outside the recursion too, such as a test runner that calls itself once per
|
|
74
|
+
// nested block. The frames of that section have to keep the recursion's own partial copy.
|
|
75
|
+
const withOuterRepeat = (dropInnermost: number): (string | undefined)[] =>
|
|
76
|
+
names(
|
|
77
|
+
[
|
|
78
|
+
OVERFLOW,
|
|
79
|
+
...recursion(['a', 'b'], 30).slice(dropInnermost),
|
|
80
|
+
frame('handleClick', 90),
|
|
81
|
+
frame('walk', 91),
|
|
82
|
+
frame('walk', 91),
|
|
83
|
+
frame('main', 92),
|
|
84
|
+
].join('\n')
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
expect(withOuterRepeat(0)).toEqual(['main', 'walk', 'handleClick', 'b', 'a'])
|
|
88
|
+
expect(withOuterRepeat(1)).toEqual(withOuterRepeat(0))
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('gives the same frames when a cycle that repeats a call site is all that is left', () => {
|
|
92
|
+
// One copy of a cycle can hold the same call site twice, and the innermost frame carries a
|
|
93
|
+
// column of its own, so a single frame does not say which rotation of the copy this throw is.
|
|
94
|
+
const cycle = [frame('a', 1, 10), frame('b', 2), frame('a', 1, 20), frame('c', 4)]
|
|
95
|
+
const lines: string[] = []
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < 30; i++) {
|
|
98
|
+
lines.push(...cycle)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fromCut = (dropInnermost: number): (string | undefined)[] =>
|
|
102
|
+
names([OVERFLOW, ...lines.slice(dropInnermost)].join('\n'))
|
|
103
|
+
|
|
104
|
+
expect(fromCut(0)).toEqual(['c', 'a', 'b', 'a'])
|
|
105
|
+
expect(fromCut(1)).toEqual(fromCut(0))
|
|
106
|
+
expect(fromCut(2)).toEqual(fromCut(0))
|
|
107
|
+
expect(fromCut(3)).toEqual(fromCut(0))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('reads the outer frames a leftover copy would have pushed past the frame limit', () => {
|
|
111
|
+
// The leftover copy is dropped once the whole stack is read, so it must not hold a slot of the
|
|
112
|
+
// frame limit while the parser is still reading the frames outside the recursion.
|
|
113
|
+
const callers: string[] = []
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < 48; i++) {
|
|
116
|
+
callers.push(frame(`c${i}`, i + 10))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const withCallers = (dropInnermost: number): (string | undefined)[] =>
|
|
120
|
+
names([OVERFLOW, ...recursion(['a', 'b'], 30).slice(dropInnermost), ...callers].join('\n'))
|
|
121
|
+
|
|
122
|
+
expect(withCallers(0)).toHaveLength(50)
|
|
123
|
+
expect(withCallers(1)).toEqual(withCallers(0))
|
|
124
|
+
|
|
125
|
+
// A distinct throw location is not a partial copy and must use a slot of the frame budget.
|
|
126
|
+
const withThrowColumn = [OVERFLOW, frame('a', 1, 4), ...recursion(['a', 'b'], 30).slice(1), ...callers].join('\n')
|
|
127
|
+
const frames = parse(withThrowColumn)
|
|
128
|
+
expect(frames).toHaveLength(50)
|
|
129
|
+
expect(frames.map((f) => f.function)).toEqual([...withCallers(0).slice(1), 'a'])
|
|
130
|
+
expect(frames[frames.length - 1]?.colno).toBe(4)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('gives the same frames when the recursion path calls one function twice in a row', () => {
|
|
134
|
+
// A minified bundle holds one line number per function, so the two calls to `b` are told apart
|
|
135
|
+
// by their column alone. The kept copy holds both of them, and every cut agrees on it.
|
|
136
|
+
const cycle = [frame('a', 1), frame('b', 2, 10), frame('b', 2, 20)]
|
|
137
|
+
const lines: string[] = []
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < 30; i++) {
|
|
140
|
+
lines.push(...cycle)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const fromCut = (dropInnermost: number): (string | undefined)[] =>
|
|
144
|
+
names([OVERFLOW, ...lines.slice(dropInnermost), frame('handleClick', 90), frame('main', 91)].join('\n'))
|
|
145
|
+
|
|
146
|
+
expect(fromCut(0)).toEqual(['main', 'handleClick', 'b', 'b', 'a'])
|
|
147
|
+
expect(fromCut(1)).toEqual(fromCut(0))
|
|
148
|
+
expect(fromCut(2)).toEqual(fromCut(0))
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('keeps minified frames that share a name and a line but not a column', () => {
|
|
152
|
+
// A minified bundle puts every function on one line and hands out the same short names, so the
|
|
153
|
+
// column is all that tells two of them apart. Nothing repeats here, so no frame may be dropped.
|
|
154
|
+
const stack = [
|
|
155
|
+
'Error: boom',
|
|
156
|
+
frame('i', 1, 310),
|
|
157
|
+
frame('t', 1, 4821),
|
|
158
|
+
frame('t', 1, 9137),
|
|
159
|
+
frame('r', 1, 2210),
|
|
160
|
+
frame('onClick', 1, 640),
|
|
161
|
+
].join('\n')
|
|
162
|
+
|
|
163
|
+
expect(parse(stack).map((f) => `${f.function}:${f.colno}`)).toEqual([
|
|
164
|
+
'onClick:640',
|
|
165
|
+
'r:2210',
|
|
166
|
+
't:9137',
|
|
167
|
+
't:4821',
|
|
168
|
+
'i:310',
|
|
169
|
+
])
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it.each(['Error: boom', OVERFLOW])('preserves the unnamed throw location for %s', (header) => {
|
|
173
|
+
// Even a confirmed recursion cannot establish that a different innermost column is redundant.
|
|
174
|
+
// Keep that location for both ordinary errors and overflow stacks.
|
|
175
|
+
const anonymous = (column: number): string => ` at https://posthog.com/app.js:1:${column}`
|
|
176
|
+
|
|
177
|
+
const withThrowColumn = (column: number): string[] => {
|
|
178
|
+
const lines = [anonymous(column)]
|
|
179
|
+
|
|
180
|
+
for (let i = 1; i < 40; i++) {
|
|
181
|
+
lines.push(anonymous(10))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return parse([header, ...lines, frame('handleClick', 90), frame('main', 91)].join('\n')).map(
|
|
185
|
+
(f) => `${f.function}:${f.colno}`
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(withThrowColumn(4)).toEqual(['main:10', 'handleClick:10', '?:10', '?:4'])
|
|
190
|
+
expect(withThrowColumn(5)).toEqual(['main:10', 'handleClick:10', '?:10', '?:5'])
|
|
191
|
+
expect(withThrowColumn(10)).toEqual(['main:10', 'handleClick:10', '?:10'])
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('preserves the column of the call that ran out of stack', () => {
|
|
195
|
+
// Do not guess whether a different column is a redundant overflow location or a distinct throw.
|
|
196
|
+
const stack = [OVERFLOW, frame('a', 1, 4), ...recursion(['a', 'b'], 3).slice(1), frame('handleClick', 90)].join(
|
|
197
|
+
'\n'
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
expect(parse(stack).map((f) => `${f.function}:${f.colno}`)).toEqual(['handleClick:10', 'b:10', 'a:10', 'a:4'])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('keeps frames that repeat without a complete second cycle', () => {
|
|
204
|
+
const stack = ['Error: boom', frame('a', 1), frame('b', 2), frame('a', 1), frame('main', 91)].join('\n')
|
|
205
|
+
expect(names(stack)).toEqual(['main', 'a', 'b', 'a'])
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('invalidates a cycle record subsumed by a later collapse', () => {
|
|
209
|
+
const stack = ['Error: boom', frame('a', 1), frame('b', 2), frame('a', 1), frame('a', 1), frame('b', 2)].join('\n')
|
|
210
|
+
|
|
211
|
+
expect(names(stack)).toEqual(['b', 'a'])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('keeps disjoint cycle records when a later collapse subsumes another record', () => {
|
|
215
|
+
const stack = [
|
|
216
|
+
'Error: boom',
|
|
217
|
+
frame('inner', 90),
|
|
218
|
+
frame('inner', 90),
|
|
219
|
+
frame('a', 1),
|
|
220
|
+
frame('b', 2),
|
|
221
|
+
frame('a', 1),
|
|
222
|
+
frame('a', 1),
|
|
223
|
+
frame('b', 2),
|
|
224
|
+
frame('outer', 91),
|
|
225
|
+
frame('outer', 91),
|
|
226
|
+
].join('\n')
|
|
227
|
+
|
|
228
|
+
expect(names(stack)).toEqual(['outer', 'b', 'a', 'inner'])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it.each([4, 5])('preserves the throw column %i of an ordinary recursive error', (column) => {
|
|
232
|
+
const stack = [
|
|
233
|
+
'Error: boom',
|
|
234
|
+
frame('recurse', 1, column),
|
|
235
|
+
frame('recurse', 1, 10),
|
|
236
|
+
frame('recurse', 1, 10),
|
|
237
|
+
frame('main', 91),
|
|
238
|
+
].join('\n')
|
|
239
|
+
|
|
240
|
+
expect(parse(stack).map((f) => `${f.function}:${f.colno}`)).toEqual(['main:10', 'recurse:10', `recurse:${column}`])
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('keeps distinct innermost minified call sites for an ordinary error', () => {
|
|
244
|
+
const stack = ['Error: boom', frame('t', 1, 100), frame('t', 1, 200), frame('main', 91)].join('\n')
|
|
245
|
+
|
|
246
|
+
expect(parse(stack).map((f) => `${f.function}:${f.colno}`)).toEqual(['main:10', 't:200', 't:100'])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('canonicalizes recursion-only cycles whose rotations differ only by columns', () => {
|
|
250
|
+
const cycle = [frame('a', 1, 10), frame('b', 1, 20), frame('a', 1, 30), frame('b', 1, 40)]
|
|
251
|
+
const lines: string[] = []
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < 30; i++) {
|
|
254
|
+
lines.push(...cycle)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const fromCut = (dropInnermost: number): string[] =>
|
|
258
|
+
parse([OVERFLOW, ...lines.slice(dropInnermost)].join('\n')).map((f) => `${f.function}:${f.colno}`)
|
|
259
|
+
|
|
260
|
+
expect(fromCut(0)).toEqual(['b:40', 'a:30', 'b:20', 'a:10'])
|
|
261
|
+
expect(fromCut(1)).toEqual(fromCut(0))
|
|
262
|
+
expect(fromCut(2)).toEqual(fromCut(0))
|
|
263
|
+
expect(fromCut(3)).toEqual(fromCut(0))
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('keeps calls to one function from different call sites', () => {
|
|
267
|
+
const stack = ['Error: boom', frame('a', 1), frame('a', 2), frame('a', 3), frame('main', 91)].join('\n')
|
|
268
|
+
expect(names(stack)).toEqual(['main', 'a', 'a', 'a'])
|
|
269
|
+
})
|
|
270
|
+
})
|