@posthog/plugin-utils 1.1.1 → 1.1.3

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 CHANGED
@@ -219,8 +219,8 @@ limitations under the License.
219
219
 
220
220
  ---
221
221
 
222
- Some files in this codebase contain code from getsentry/sentry-javascript or getsentry/sentry-react-native by Software, Inc. dba Sentry.
223
- In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
222
+ Some files in this codebase contain code from getsentry/sentry-javascript.
223
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
224
224
 
225
225
  MIT License
226
226
 
@@ -246,6 +246,33 @@ SOFTWARE.
246
246
 
247
247
  ---
248
248
 
249
+ Some files in this codebase contain code from getsentry/sentry-react-native.
250
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
251
+
252
+ The MIT License (MIT)
253
+
254
+ Copyright (c) 2017 Sentry
255
+
256
+ Permission is hereby granted, free of charge, to any person obtaining a copy
257
+ of this software and associated documentation files (the "Software"), to deal
258
+ in the Software without restriction, including without limitation the rights
259
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
260
+ copies of the Software, and to permit persons to whom the Software is
261
+ furnished to do so, subject to the following conditions:
262
+
263
+ The above copyright notice and this permission notice shall be included in all
264
+ copies or substantial portions of the Software.
265
+
266
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
267
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
268
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
269
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
270
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
271
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
272
+ SOFTWARE.
273
+
274
+ ---
275
+
249
276
  Some files in this codebase contain code from facebook/metro by Meta Platforms, Inc. and affiliates.
250
277
  In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
251
278
 
@@ -297,3 +324,30 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
297
324
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
298
325
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
299
326
  SOFTWARE.
327
+
328
+ ---
329
+
330
+ Some files in this codebase contain code from agentcathq/agentcat-typescript-sdk (formerly MCPCat/mcpcat-typescript-sdk).
331
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
332
+
333
+ MIT License
334
+
335
+ Copyright (c) 2025 AgentCat, Inc. (formerly MCPcat)
336
+
337
+ Permission is hereby granted, free of charge, to any person obtaining a copy
338
+ of this software and associated documentation files (the "Software"), to deal
339
+ in the Software without restriction, including without limitation the rights
340
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
341
+ copies of the Software, and to permit persons to whom the Software is
342
+ furnished to do so, subject to the following conditions:
343
+
344
+ The above copyright notice and this permission notice shall be included in all
345
+ copies or substantial portions of the Software.
346
+
347
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
348
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
349
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
350
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
351
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
352
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
353
+ SOFTWARE.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Returns the minified chunk-id IIFE the SDK reads at runtime to map an
3
+ * error's stack back to a chunk id.
4
+ *
5
+ * Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE` in
6
+ * cli/src/sourcemaps/constant.rs): the CLI recognizes and removes previously
7
+ * injected snippets by exact substring match, so any change here must ship in
8
+ * the CLI constants too. Covered by a parity test in chunk-ids.spec.ts.
9
+ */
10
+ export declare function createChunkIdSnippet(chunkId: string): string;
11
+ /**
12
+ * Returns the comment posthog-cli discovers chunk ids from at upload time
13
+ * (`CHUNKID_COMMENT_PREFIX` in cli/src/sourcemaps/constant.rs), including the
14
+ * leading newline the CLI's exact-match removal expects.
15
+ */
16
+ export declare function createChunkIdComment(chunkId: string): string;
17
+ /**
18
+ * Mints a fresh random chunk id: a new id per chunk, per build.
19
+ */
20
+ export declare function createChunkId(): string;
21
+ /**
22
+ * Extracts the chunk id from source that already carries one, using the same
23
+ * detection the CLI uses: a `//# chunkId=` comment at the start of a line.
24
+ * The injected IIFE is deliberately not matched — its marker text can appear
25
+ * inside string literals of bundled code (docs strings, bundled PostHog
26
+ * tooling), and the CLI only ever reads the line-anchored comment. Keeping the
27
+ * two in sync guarantees this returning an id ⟺ the CLI finding one.
28
+ */
29
+ export declare function determineChunkIdFromSource(code: string): string | undefined;
30
+ //# sourceMappingURL=chunk-ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-ids.d.ts","sourceRoot":"","sources":["../src/chunk-ids.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AASD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa3E"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ createChunkIdSnippet: ()=>createChunkIdSnippet,
28
+ createChunkId: ()=>createChunkId,
29
+ createChunkIdComment: ()=>createChunkIdComment,
30
+ determineChunkIdFromSource: ()=>determineChunkIdFromSource
31
+ });
32
+ const external_crypto_namespaceObject = require("crypto");
33
+ function createChunkIdSnippet(chunkId) {
34
+ return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]="${chunkId}")}catch(e){}}();`;
35
+ }
36
+ function createChunkIdComment(chunkId) {
37
+ return `\n//# chunkId=${chunkId}`;
38
+ }
39
+ function createChunkId() {
40
+ return external_crypto_namespaceObject.randomUUID();
41
+ }
42
+ const COMMENT_REGEX = /^\/\/# chunkId=(\S{1,128})/;
43
+ const COMMENT_MARKER = '//# chunkId=';
44
+ const WINDOW = 256;
45
+ function determineChunkIdFromSource(code) {
46
+ for(let at = code.indexOf(COMMENT_MARKER); -1 !== at; at = code.indexOf(COMMENT_MARKER, at + COMMENT_MARKER.length)){
47
+ if (0 !== at && '\n' !== code[at - 1]) continue;
48
+ const match = COMMENT_REGEX.exec(code.slice(at, at + WINDOW));
49
+ if (match) return match[1];
50
+ }
51
+ }
52
+ exports.createChunkId = __webpack_exports__.createChunkId;
53
+ exports.createChunkIdComment = __webpack_exports__.createChunkIdComment;
54
+ exports.createChunkIdSnippet = __webpack_exports__.createChunkIdSnippet;
55
+ exports.determineChunkIdFromSource = __webpack_exports__.determineChunkIdFromSource;
56
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
57
+ "createChunkId",
58
+ "createChunkIdComment",
59
+ "createChunkIdSnippet",
60
+ "determineChunkIdFromSource"
61
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
62
+ Object.defineProperty(exports, '__esModule', {
63
+ value: true
64
+ });
@@ -0,0 +1,21 @@
1
+ import { randomUUID } from "crypto";
2
+ function createChunkIdSnippet(chunkId) {
3
+ return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]="${chunkId}")}catch(e){}}();`;
4
+ }
5
+ function createChunkIdComment(chunkId) {
6
+ return `\n//# chunkId=${chunkId}`;
7
+ }
8
+ function createChunkId() {
9
+ return randomUUID();
10
+ }
11
+ const COMMENT_REGEX = /^\/\/# chunkId=(\S{1,128})/;
12
+ const COMMENT_MARKER = '//# chunkId=';
13
+ const WINDOW = 256;
14
+ function determineChunkIdFromSource(code) {
15
+ for(let at = code.indexOf(COMMENT_MARKER); -1 !== at; at = code.indexOf(COMMENT_MARKER, at + COMMENT_MARKER.length)){
16
+ if (0 !== at && '\n' !== code[at - 1]) continue;
17
+ const match = COMMENT_REGEX.exec(code.slice(at, at + WINDOW));
18
+ if (match) return match[1];
19
+ }
20
+ }
21
+ export { createChunkId, createChunkIdComment, createChunkIdSnippet, determineChunkIdFromSource };
package/dist/cli.d.ts CHANGED
@@ -1,12 +1,18 @@
1
1
  import { ResolvedPluginConfig } from './config';
2
2
  /**
3
- * Build CLI arguments for `posthog-cli sourcemap process`.
3
+ * `process` injects chunk ids into the built files on disk and uploads them.
4
+ * `upload` only reads the files, expecting chunk ids to already be present
5
+ * (e.g. injected in-memory by a bundler plugin before the files were written).
6
+ */
7
+ export type SourcemapCliCommand = 'process' | 'upload';
8
+ /**
9
+ * Build CLI arguments for `posthog-cli sourcemap <command>`.
4
10
  */
5
11
  export declare function buildSourcemapCliArgs(config: ResolvedPluginConfig, mode: {
6
12
  stdin: true;
7
13
  } | {
8
14
  directory: string;
9
- }): string[];
15
+ }, command?: SourcemapCliCommand): string[];
10
16
  /**
11
17
  * Build environment variables for CLI invocation.
12
18
  * Plugin config values override any existing process.env values.
@@ -15,9 +21,11 @@ export declare function buildCliEnv(config: ResolvedPluginConfig): NodeJS.Proces
15
21
  /**
16
22
  * Spawn the PostHog CLI for sourcemap processing via stdin (file list).
17
23
  */
18
- export declare function runSourcemapCli(config: ResolvedPluginConfig, options: {
24
+ export declare function runSourcemapCli(config: ResolvedPluginConfig, options: ({
19
25
  filePaths: string[];
20
26
  } | {
21
27
  directory: string;
28
+ }) & {
29
+ command?: SourcemapCliCommand;
22
30
  }): Promise<void>;
23
31
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAG/C;;GAEG;AACH,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAC9C,MAAM,EAAE,CA8BV;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC,UAAU,CAQ3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CACjC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,IAAI,CAAC,CAgBf"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAG/C;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEtD;;GAEG;AACH,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7C,OAAO,GAAE,mBAA+B,GACzC,MAAM,EAAE,CAkCV;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC,UAAU,CAQ3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CACjC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAAE,GAC/F,OAAO,CAAC,IAAI,CAAC,CAgBf"}
package/dist/cli.js CHANGED
@@ -29,17 +29,17 @@ __webpack_require__.d(__webpack_exports__, {
29
29
  buildSourcemapCliArgs: ()=>buildSourcemapCliArgs
30
30
  });
31
31
  const external_spawn_local_js_namespaceObject = require("./spawn-local.js");
32
- function buildSourcemapCliArgs(config, mode) {
32
+ function buildSourcemapCliArgs(config, mode, command = 'process') {
33
33
  const args = [
34
34
  'sourcemap',
35
- 'process'
35
+ command
36
36
  ];
37
37
  if ('stdin' in mode) args.push('--stdin');
38
38
  else args.push('--directory', mode.directory);
39
39
  if (config.sourcemaps.releaseName) args.push('--release-name', config.sourcemaps.releaseName);
40
40
  if (config.sourcemaps.releaseVersion) args.push('--release-version', config.sourcemaps.releaseVersion);
41
41
  if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
42
- if (config.sourcemaps.deleteAfterUpload) args.push('--delete-after');
42
+ if (config.sourcemaps.deleteAfterUpload && 'process' === command) args.push('--delete-after');
43
43
  if (config.sourcemaps.batchSize) args.push('--batch-size', config.sourcemaps.batchSize.toString());
44
44
  return args;
45
45
  }
@@ -58,7 +58,7 @@ async function runSourcemapCli(config, options) {
58
58
  } : {
59
59
  directory: options.directory
60
60
  };
61
- const args = buildSourcemapCliArgs(config, mode);
61
+ const args = buildSourcemapCliArgs(config, mode, options.command ?? 'process');
62
62
  const env = buildCliEnv(config);
63
63
  const spawnOptions = {
64
64
  cwd: process.cwd(),
package/dist/cli.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  import { spawnLocal } from "./spawn-local.mjs";
2
- function buildSourcemapCliArgs(config, mode) {
2
+ function buildSourcemapCliArgs(config, mode, command = 'process') {
3
3
  const args = [
4
4
  'sourcemap',
5
- 'process'
5
+ command
6
6
  ];
7
7
  if ('stdin' in mode) args.push('--stdin');
8
8
  else args.push('--directory', mode.directory);
9
9
  if (config.sourcemaps.releaseName) args.push('--release-name', config.sourcemaps.releaseName);
10
10
  if (config.sourcemaps.releaseVersion) args.push('--release-version', config.sourcemaps.releaseVersion);
11
11
  if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
12
- if (config.sourcemaps.deleteAfterUpload) args.push('--delete-after');
12
+ if (config.sourcemaps.deleteAfterUpload && 'process' === command) args.push('--delete-after');
13
13
  if (config.sourcemaps.batchSize) args.push('--batch-size', config.sourcemaps.batchSize.toString());
14
14
  return args;
15
15
  }
@@ -28,7 +28,7 @@ async function runSourcemapCli(config, options) {
28
28
  } : {
29
29
  directory: options.directory
30
30
  };
31
- const args = buildSourcemapCliArgs(config, mode);
31
+ const args = buildSourcemapCliArgs(config, mode, options.command ?? 'process');
32
32
  const env = buildCliEnv(config);
33
33
  const spawnOptions = {
34
34
  cwd: process.cwd(),
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './spawn-local';
2
2
  export { resolveBinaryPath, buildLocalBinaryPaths } from './utils';
3
3
  export * from './config';
4
4
  export * from './cli';
5
+ export * from './chunk-ids';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAClE,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAClE,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  var __webpack_modules__ = {
3
+ "./chunk-ids": function(module) {
4
+ module.exports = require("./chunk-ids.js");
5
+ },
3
6
  "./cli": function(module) {
4
7
  module.exports = require("./cli.js");
5
8
  },
@@ -91,6 +94,16 @@ var __webpack_exports__ = {};
91
94
  return _cli__WEBPACK_IMPORTED_MODULE_3__[key];
92
95
  }).bind(0, __WEBPACK_IMPORT_KEY__);
93
96
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
97
+ var _chunk_ids__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__("./chunk-ids");
98
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
99
+ for(var __WEBPACK_IMPORT_KEY__ in _chunk_ids__WEBPACK_IMPORTED_MODULE_4__)if ([
100
+ "buildLocalBinaryPaths",
101
+ "resolveBinaryPath",
102
+ "default"
103
+ ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
104
+ return _chunk_ids__WEBPACK_IMPORTED_MODULE_4__[key];
105
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
106
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
94
107
  })();
95
108
  exports.buildLocalBinaryPaths = __webpack_exports__.buildLocalBinaryPaths;
96
109
  exports.resolveBinaryPath = __webpack_exports__.resolveBinaryPath;
package/dist/index.mjs CHANGED
@@ -2,4 +2,5 @@ import { buildLocalBinaryPaths, resolveBinaryPath } from "./utils.mjs";
2
2
  export * from "./spawn-local.mjs";
3
3
  export * from "./config.mjs";
4
4
  export * from "./cli.mjs";
5
+ export * from "./chunk-ids.mjs";
5
6
  export { buildLocalBinaryPaths, resolveBinaryPath };
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@posthog/plugin-utils",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
+ "bugs": {
5
+ "url": "https://github.com/PostHog/posthog-js/issues"
6
+ },
4
7
  "description": "Shared CLI and sourcemap utilities for PostHog build plugins",
5
8
  "license": "MIT",
6
9
  "repository": {
@@ -0,0 +1,60 @@
1
+ import * as crypto from 'crypto'
2
+
3
+ /**
4
+ * Returns the minified chunk-id IIFE the SDK reads at runtime to map an
5
+ * error's stack back to a chunk id.
6
+ *
7
+ * Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE` in
8
+ * cli/src/sourcemaps/constant.rs): the CLI recognizes and removes previously
9
+ * injected snippets by exact substring match, so any change here must ship in
10
+ * the CLI constants too. Covered by a parity test in chunk-ids.spec.ts.
11
+ */
12
+ export function createChunkIdSnippet(chunkId: string): string {
13
+ return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]="${chunkId}")}catch(e){}}();`
14
+ }
15
+
16
+ /**
17
+ * Returns the comment posthog-cli discovers chunk ids from at upload time
18
+ * (`CHUNKID_COMMENT_PREFIX` in cli/src/sourcemaps/constant.rs), including the
19
+ * leading newline the CLI's exact-match removal expects.
20
+ */
21
+ export function createChunkIdComment(chunkId: string): string {
22
+ return `\n//# chunkId=${chunkId}`
23
+ }
24
+
25
+ /**
26
+ * Mints a fresh random chunk id: a new id per chunk, per build.
27
+ */
28
+ export function createChunkId(): string {
29
+ return crypto.randomUUID()
30
+ }
31
+
32
+ // Anchored with a bounded quantifier and only ever run against a fixed-size
33
+ // window: an unbounded regex over the whole bundle is quadratic on adversarial
34
+ // input (a match attempt per marker occurrence, each scanning ahead).
35
+ const COMMENT_REGEX = /^\/\/# chunkId=(\S{1,128})/
36
+ const COMMENT_MARKER = '//# chunkId='
37
+ const WINDOW = 256
38
+
39
+ /**
40
+ * Extracts the chunk id from source that already carries one, using the same
41
+ * detection the CLI uses: a `//# chunkId=` comment at the start of a line.
42
+ * The injected IIFE is deliberately not matched — its marker text can appear
43
+ * inside string literals of bundled code (docs strings, bundled PostHog
44
+ * tooling), and the CLI only ever reads the line-anchored comment. Keeping the
45
+ * two in sync guarantees this returning an id ⟺ the CLI finding one.
46
+ */
47
+ export function determineChunkIdFromSource(code: string): string | undefined {
48
+ for (
49
+ let at = code.indexOf(COMMENT_MARKER);
50
+ at !== -1;
51
+ at = code.indexOf(COMMENT_MARKER, at + COMMENT_MARKER.length)
52
+ ) {
53
+ if (at !== 0 && code[at - 1] !== '\n') continue
54
+ const match = COMMENT_REGEX.exec(code.slice(at, at + WINDOW))
55
+ if (match) {
56
+ return match[1]
57
+ }
58
+ }
59
+ return undefined
60
+ }
package/src/cli.ts CHANGED
@@ -2,13 +2,21 @@ import { ResolvedPluginConfig } from './config'
2
2
  import { spawnLocal } from './spawn-local'
3
3
 
4
4
  /**
5
- * Build CLI arguments for `posthog-cli sourcemap process`.
5
+ * `process` injects chunk ids into the built files on disk and uploads them.
6
+ * `upload` only reads the files, expecting chunk ids to already be present
7
+ * (e.g. injected in-memory by a bundler plugin before the files were written).
8
+ */
9
+ export type SourcemapCliCommand = 'process' | 'upload'
10
+
11
+ /**
12
+ * Build CLI arguments for `posthog-cli sourcemap <command>`.
6
13
  */
7
14
  export function buildSourcemapCliArgs(
8
15
  config: ResolvedPluginConfig,
9
- mode: { stdin: true } | { directory: string }
16
+ mode: { stdin: true } | { directory: string },
17
+ command: SourcemapCliCommand = 'process'
10
18
  ): string[] {
11
- const args = ['sourcemap', 'process']
19
+ const args = ['sourcemap', command]
12
20
 
13
21
  if ('stdin' in mode) {
14
22
  args.push('--stdin')
@@ -28,7 +36,11 @@ export function buildSourcemapCliArgs(
28
36
  args.push('--build', config.sourcemaps.build)
29
37
  }
30
38
 
31
- if (config.sourcemaps.deleteAfterUpload) {
39
+ // On `upload` the caller owns map deletion: `--delete-after` also rewrites
40
+ // the .js files (stripping sourcemap references), and callers pick `upload`
41
+ // precisely because the written files must not change — e.g. Subresource
42
+ // Integrity hashes were already computed from them.
43
+ if (config.sourcemaps.deleteAfterUpload && command === 'process') {
32
44
  args.push('--delete-after')
33
45
  }
34
46
 
@@ -58,10 +70,10 @@ export function buildCliEnv(config: ResolvedPluginConfig): NodeJS.ProcessEnv {
58
70
  */
59
71
  export async function runSourcemapCli(
60
72
  config: ResolvedPluginConfig,
61
- options: { filePaths: string[] } | { directory: string }
73
+ options: ({ filePaths: string[] } | { directory: string }) & { command?: SourcemapCliCommand }
62
74
  ): Promise<void> {
63
75
  const mode = 'filePaths' in options ? { stdin: true as const } : { directory: options.directory }
64
- const args = buildSourcemapCliArgs(config, mode)
76
+ const args = buildSourcemapCliArgs(config, mode, options.command ?? 'process')
65
77
  const env = buildCliEnv(config)
66
78
 
67
79
  const spawnOptions: Parameters<typeof spawnLocal>[2] = {
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './spawn-local'
2
2
  export { resolveBinaryPath, buildLocalBinaryPaths } from './utils'
3
3
  export * from './config'
4
4
  export * from './cli'
5
+ export * from './chunk-ids'