@posthog/plugin-utils 1.1.3 → 2.0.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/dist/chunk-ids.d.ts +22 -6
- package/dist/chunk-ids.d.ts.map +1 -1
- package/dist/chunk-ids.js +16 -2
- package/dist/chunk-ids.mjs +15 -4
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -5
- package/dist/cli.mjs +30 -6
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -1
- package/dist/config.mjs +11 -1
- package/dist/spawn-local.d.ts +14 -0
- package/dist/spawn-local.d.ts.map +1 -1
- package/dist/spawn-local.js +33 -1
- package/dist/spawn-local.mjs +30 -1
- package/package.json +3 -3
- package/src/chunk-ids.ts +43 -7
- package/src/cli.ts +63 -10
- package/src/config.ts +30 -0
- package/src/spawn-local.ts +40 -0
package/dist/chunk-ids.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns the minified chunk-id IIFE the SDK reads at runtime to map an
|
|
3
|
-
* error's stack back to a chunk id.
|
|
3
|
+
* error's stack back to a chunk id. With `releaseId`, the snippet also pins
|
|
4
|
+
* `_posthogReleaseId` on the global so the SDK emits the release on every
|
|
5
|
+
* exception (first write wins, so the first loaded chunk decides).
|
|
4
6
|
*
|
|
5
|
-
* Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE`
|
|
6
|
-
* cli/src/sourcemaps/constant.rs): the
|
|
7
|
-
* injected snippets by exact substring
|
|
8
|
-
*
|
|
7
|
+
* Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE` and
|
|
8
|
+
* `CODE_SNIPPET_WITH_RELEASE_TEMPLATE` in cli/src/sourcemaps/constant.rs): the
|
|
9
|
+
* CLI recognizes and removes previously injected snippets by exact substring
|
|
10
|
+
* match, so any change here must ship in the CLI constants too. Ids are
|
|
11
|
+
* JSON-encoded for the same reason the CLI encodes them: an id carrying a quote
|
|
12
|
+
* would otherwise break out of the string literal. Covered by a parity test in
|
|
13
|
+
* chunk-ids.spec.ts.
|
|
9
14
|
*/
|
|
10
|
-
export declare function createChunkIdSnippet(chunkId: string): string;
|
|
15
|
+
export declare function createChunkIdSnippet(chunkId: string, releaseId?: string): string;
|
|
11
16
|
/**
|
|
12
17
|
* Returns the comment posthog-cli discovers chunk ids from at upload time
|
|
13
18
|
* (`CHUNKID_COMMENT_PREFIX` in cli/src/sourcemaps/constant.rs), including the
|
|
@@ -18,6 +23,17 @@ export declare function createChunkIdComment(chunkId: string): string;
|
|
|
18
23
|
* Mints a fresh random chunk id: a new id per chunk, per build.
|
|
19
24
|
*/
|
|
20
25
|
export declare function createChunkId(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Derives a chunk id from the chunk's own bytes (UUIDv5), so identical code keeps its id across
|
|
28
|
+
* rebuilds and re-uploads dedupe against the symbol set already stored. Used in event release
|
|
29
|
+
* mode, where the release travels inside the chunk instead of being bound to the symbol set, so
|
|
30
|
+
* a per-build random id would create a fresh symbol set on every build.
|
|
31
|
+
*
|
|
32
|
+
* The id is content-addressed over what `renderChunk` sees. posthog-cli derives its own ids from
|
|
33
|
+
* the file on disk, which by then also carries the trailing `sourceMappingURL` comment, so the
|
|
34
|
+
* same chunk gets a different (but equally stable) id depending on which tool injected it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createStableChunkId(code: string): string;
|
|
21
37
|
/**
|
|
22
38
|
* Extracts the chunk id from source that already carries one, using the same
|
|
23
39
|
* detection the CLI uses: a `//# chunkId=` comment at the start of a line.
|
package/dist/chunk-ids.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-ids.d.ts","sourceRoot":"","sources":["../src/chunk-ids.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"chunk-ids.d.ts","sourceRoot":"","sources":["../src/chunk-ids.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAMhF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAOD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUxD;AASD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa3E"}
|
package/dist/chunk-ids.js
CHANGED
|
@@ -24,14 +24,17 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
createStableChunkId: ()=>createStableChunkId,
|
|
27
28
|
createChunkIdSnippet: ()=>createChunkIdSnippet,
|
|
28
29
|
createChunkId: ()=>createChunkId,
|
|
29
30
|
createChunkIdComment: ()=>createChunkIdComment,
|
|
30
31
|
determineChunkIdFromSource: ()=>determineChunkIdFromSource
|
|
31
32
|
});
|
|
32
33
|
const external_crypto_namespaceObject = require("crypto");
|
|
33
|
-
function createChunkIdSnippet(chunkId) {
|
|
34
|
-
|
|
34
|
+
function createChunkIdSnippet(chunkId, releaseId) {
|
|
35
|
+
const chunk = JSON.stringify(chunkId);
|
|
36
|
+
if (void 0 === releaseId) 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]=${chunk})}catch(e){}}();`;
|
|
37
|
+
return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e._posthogReleaseId=e._posthogReleaseId||${JSON.stringify(releaseId)};var n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]=${chunk})}catch(e){}}();`;
|
|
35
38
|
}
|
|
36
39
|
function createChunkIdComment(chunkId) {
|
|
37
40
|
return `\n//# chunkId=${chunkId}`;
|
|
@@ -39,6 +42,15 @@ function createChunkIdComment(chunkId) {
|
|
|
39
42
|
function createChunkId() {
|
|
40
43
|
return external_crypto_namespaceObject.randomUUID();
|
|
41
44
|
}
|
|
45
|
+
const CHUNK_ID_NAMESPACE = '0e9b3c7a-5d1f-42a8-b6c4-e2d0f8a17593';
|
|
46
|
+
function createStableChunkId(code) {
|
|
47
|
+
const namespace = Buffer.from(CHUNK_ID_NAMESPACE.replace(/-/g, ''), 'hex');
|
|
48
|
+
const hash = external_crypto_namespaceObject.createHash('sha1').update(namespace).update(code, 'utf8').digest();
|
|
49
|
+
hash[6] = 0x0f & hash[6] | 0x50;
|
|
50
|
+
hash[8] = 0x3f & hash[8] | 0x80;
|
|
51
|
+
const hex = hash.subarray(0, 16).toString('hex');
|
|
52
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
53
|
+
}
|
|
42
54
|
const COMMENT_REGEX = /^\/\/# chunkId=(\S{1,128})/;
|
|
43
55
|
const COMMENT_MARKER = '//# chunkId=';
|
|
44
56
|
const WINDOW = 256;
|
|
@@ -52,11 +64,13 @@ function determineChunkIdFromSource(code) {
|
|
|
52
64
|
exports.createChunkId = __webpack_exports__.createChunkId;
|
|
53
65
|
exports.createChunkIdComment = __webpack_exports__.createChunkIdComment;
|
|
54
66
|
exports.createChunkIdSnippet = __webpack_exports__.createChunkIdSnippet;
|
|
67
|
+
exports.createStableChunkId = __webpack_exports__.createStableChunkId;
|
|
55
68
|
exports.determineChunkIdFromSource = __webpack_exports__.determineChunkIdFromSource;
|
|
56
69
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
57
70
|
"createChunkId",
|
|
58
71
|
"createChunkIdComment",
|
|
59
72
|
"createChunkIdSnippet",
|
|
73
|
+
"createStableChunkId",
|
|
60
74
|
"determineChunkIdFromSource"
|
|
61
75
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
62
76
|
Object.defineProperty(exports, '__esModule', {
|
package/dist/chunk-ids.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
function createChunkIdSnippet(chunkId) {
|
|
3
|
-
|
|
1
|
+
import { createHash, randomUUID } from "crypto";
|
|
2
|
+
function createChunkIdSnippet(chunkId, releaseId) {
|
|
3
|
+
const chunk = JSON.stringify(chunkId);
|
|
4
|
+
if (void 0 === releaseId) 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]=${chunk})}catch(e){}}();`;
|
|
5
|
+
return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e._posthogReleaseId=e._posthogReleaseId||${JSON.stringify(releaseId)};var n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]=${chunk})}catch(e){}}();`;
|
|
4
6
|
}
|
|
5
7
|
function createChunkIdComment(chunkId) {
|
|
6
8
|
return `\n//# chunkId=${chunkId}`;
|
|
@@ -8,6 +10,15 @@ function createChunkIdComment(chunkId) {
|
|
|
8
10
|
function createChunkId() {
|
|
9
11
|
return randomUUID();
|
|
10
12
|
}
|
|
13
|
+
const CHUNK_ID_NAMESPACE = '0e9b3c7a-5d1f-42a8-b6c4-e2d0f8a17593';
|
|
14
|
+
function createStableChunkId(code) {
|
|
15
|
+
const namespace = Buffer.from(CHUNK_ID_NAMESPACE.replace(/-/g, ''), 'hex');
|
|
16
|
+
const hash = createHash('sha1').update(namespace).update(code, 'utf8').digest();
|
|
17
|
+
hash[6] = 0x0f & hash[6] | 0x50;
|
|
18
|
+
hash[8] = 0x3f & hash[8] | 0x80;
|
|
19
|
+
const hex = hash.subarray(0, 16).toString('hex');
|
|
20
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
21
|
+
}
|
|
11
22
|
const COMMENT_REGEX = /^\/\/# chunkId=(\S{1,128})/;
|
|
12
23
|
const COMMENT_MARKER = '//# chunkId=';
|
|
13
24
|
const WINDOW = 256;
|
|
@@ -18,4 +29,4 @@ function determineChunkIdFromSource(code) {
|
|
|
18
29
|
if (match) return match[1];
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
|
-
export { createChunkId, createChunkIdComment, createChunkIdSnippet, determineChunkIdFromSource };
|
|
32
|
+
export { createChunkId, createChunkIdComment, createChunkIdSnippet, createStableChunkId, determineChunkIdFromSource };
|
package/dist/cli.d.ts
CHANGED
|
@@ -28,4 +28,12 @@ export declare function runSourcemapCli(config: ResolvedPluginConfig, options: (
|
|
|
28
28
|
}) & {
|
|
29
29
|
command?: SourcemapCliCommand;
|
|
30
30
|
}): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the release this build belongs to, creating it if it doesn't exist yet, and returns its
|
|
33
|
+
* id for injection into the chunks. Returns undefined when nothing identifies a release: the CLI
|
|
34
|
+
* prints nothing and exits zero for a build with no release name/version and no git or CI
|
|
35
|
+
* metadata to derive them from, which is a build that ships without a release rather than a
|
|
36
|
+
* failure.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveReleaseId(config: ResolvedPluginConfig): Promise<string | undefined>;
|
|
31
39
|
//# 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;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAA;
|
|
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;AAwBtD;;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,CA8BV;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC,UAAU,CAW3E;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;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuBhG"}
|
package/dist/cli.js
CHANGED
|
@@ -24,11 +24,19 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
-
|
|
27
|
+
resolveReleaseId: ()=>resolveReleaseId,
|
|
28
28
|
buildCliEnv: ()=>buildCliEnv,
|
|
29
|
+
runSourcemapCli: ()=>runSourcemapCli,
|
|
29
30
|
buildSourcemapCliArgs: ()=>buildSourcemapCliArgs
|
|
30
31
|
});
|
|
31
32
|
const external_spawn_local_js_namespaceObject = require("./spawn-local.js");
|
|
33
|
+
function buildReleaseArgs(config) {
|
|
34
|
+
const args = [];
|
|
35
|
+
if (config.sourcemaps.releaseName) args.push('--release-name', config.sourcemaps.releaseName);
|
|
36
|
+
if (config.sourcemaps.releaseVersion) args.push('--release-version', config.sourcemaps.releaseVersion);
|
|
37
|
+
if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
32
40
|
function buildSourcemapCliArgs(config, mode, command = 'process') {
|
|
33
41
|
const args = [
|
|
34
42
|
'sourcemap',
|
|
@@ -36,9 +44,8 @@ function buildSourcemapCliArgs(config, mode, command = 'process') {
|
|
|
36
44
|
];
|
|
37
45
|
if ('stdin' in mode) args.push('--stdin');
|
|
38
46
|
else args.push('--directory', mode.directory);
|
|
39
|
-
|
|
40
|
-
if (config.sourcemaps.
|
|
41
|
-
if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
|
|
47
|
+
args.push(...buildReleaseArgs(config));
|
|
48
|
+
if ('event' === config.sourcemaps.releaseMode) args.push('--release-mode', 'event');
|
|
42
49
|
if (config.sourcemaps.deleteAfterUpload && 'process' === command) args.push('--delete-after');
|
|
43
50
|
if (config.sourcemaps.batchSize) args.push('--batch-size', config.sourcemaps.batchSize.toString());
|
|
44
51
|
return args;
|
|
@@ -49,7 +56,8 @@ function buildCliEnv(config) {
|
|
|
49
56
|
RUST_LOG: `posthog_cli=${config.logLevel}`,
|
|
50
57
|
POSTHOG_CLI_HOST: config.host,
|
|
51
58
|
POSTHOG_CLI_API_KEY: config.personalApiKey,
|
|
52
|
-
POSTHOG_CLI_PROJECT_ID: config.projectId
|
|
59
|
+
POSTHOG_CLI_PROJECT_ID: config.projectId,
|
|
60
|
+
POSTHOG_RELEASE_MODE: config.sourcemaps.releaseMode
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
async function runSourcemapCli(config, options) {
|
|
@@ -68,12 +76,31 @@ async function runSourcemapCli(config, options) {
|
|
|
68
76
|
if ('filePaths' in options) spawnOptions.stdin = options.filePaths.join('\n') + '\n';
|
|
69
77
|
await (0, external_spawn_local_js_namespaceObject.spawnLocal)(config.cliBinaryPath, args, spawnOptions);
|
|
70
78
|
}
|
|
79
|
+
async function resolveReleaseId(config) {
|
|
80
|
+
const args = [
|
|
81
|
+
'release',
|
|
82
|
+
'resolve',
|
|
83
|
+
...buildReleaseArgs(config)
|
|
84
|
+
];
|
|
85
|
+
const { code, stdout, stderr } = await (0, external_spawn_local_js_namespaceObject.spawnLocalCapture)(config.cliBinaryPath, args, {
|
|
86
|
+
cwd: process.cwd(),
|
|
87
|
+
env: buildCliEnv(config)
|
|
88
|
+
});
|
|
89
|
+
if (0 !== code) {
|
|
90
|
+
const hint = /unrecognized subcommand|unexpected argument/.test(stderr) ? ` Event release mode needs a posthog-cli with 'release resolve' (${config.cliBinaryPath}).` : '';
|
|
91
|
+
throw new Error(`posthog-cli release resolve failed with code ${code}.${hint}\n${stderr.trim()}`);
|
|
92
|
+
}
|
|
93
|
+
if (stderr.trim()) process.stderr.write(stderr);
|
|
94
|
+
return stdout.trim() || void 0;
|
|
95
|
+
}
|
|
71
96
|
exports.buildCliEnv = __webpack_exports__.buildCliEnv;
|
|
72
97
|
exports.buildSourcemapCliArgs = __webpack_exports__.buildSourcemapCliArgs;
|
|
98
|
+
exports.resolveReleaseId = __webpack_exports__.resolveReleaseId;
|
|
73
99
|
exports.runSourcemapCli = __webpack_exports__.runSourcemapCli;
|
|
74
100
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
75
101
|
"buildCliEnv",
|
|
76
102
|
"buildSourcemapCliArgs",
|
|
103
|
+
"resolveReleaseId",
|
|
77
104
|
"runSourcemapCli"
|
|
78
105
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
79
106
|
Object.defineProperty(exports, '__esModule', {
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { spawnLocal } from "./spawn-local.mjs";
|
|
1
|
+
import { spawnLocal, spawnLocalCapture } from "./spawn-local.mjs";
|
|
2
|
+
function buildReleaseArgs(config) {
|
|
3
|
+
const args = [];
|
|
4
|
+
if (config.sourcemaps.releaseName) args.push('--release-name', config.sourcemaps.releaseName);
|
|
5
|
+
if (config.sourcemaps.releaseVersion) args.push('--release-version', config.sourcemaps.releaseVersion);
|
|
6
|
+
if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
|
|
7
|
+
return args;
|
|
8
|
+
}
|
|
2
9
|
function buildSourcemapCliArgs(config, mode, command = 'process') {
|
|
3
10
|
const args = [
|
|
4
11
|
'sourcemap',
|
|
@@ -6,9 +13,8 @@ function buildSourcemapCliArgs(config, mode, command = 'process') {
|
|
|
6
13
|
];
|
|
7
14
|
if ('stdin' in mode) args.push('--stdin');
|
|
8
15
|
else args.push('--directory', mode.directory);
|
|
9
|
-
|
|
10
|
-
if (config.sourcemaps.
|
|
11
|
-
if (void 0 !== config.sourcemaps.build && '' !== config.sourcemaps.build) args.push('--build', config.sourcemaps.build);
|
|
16
|
+
args.push(...buildReleaseArgs(config));
|
|
17
|
+
if ('event' === config.sourcemaps.releaseMode) args.push('--release-mode', 'event');
|
|
12
18
|
if (config.sourcemaps.deleteAfterUpload && 'process' === command) args.push('--delete-after');
|
|
13
19
|
if (config.sourcemaps.batchSize) args.push('--batch-size', config.sourcemaps.batchSize.toString());
|
|
14
20
|
return args;
|
|
@@ -19,7 +25,8 @@ function buildCliEnv(config) {
|
|
|
19
25
|
RUST_LOG: `posthog_cli=${config.logLevel}`,
|
|
20
26
|
POSTHOG_CLI_HOST: config.host,
|
|
21
27
|
POSTHOG_CLI_API_KEY: config.personalApiKey,
|
|
22
|
-
POSTHOG_CLI_PROJECT_ID: config.projectId
|
|
28
|
+
POSTHOG_CLI_PROJECT_ID: config.projectId,
|
|
29
|
+
POSTHOG_RELEASE_MODE: config.sourcemaps.releaseMode
|
|
23
30
|
};
|
|
24
31
|
}
|
|
25
32
|
async function runSourcemapCli(config, options) {
|
|
@@ -38,4 +45,21 @@ async function runSourcemapCli(config, options) {
|
|
|
38
45
|
if ('filePaths' in options) spawnOptions.stdin = options.filePaths.join('\n') + '\n';
|
|
39
46
|
await spawnLocal(config.cliBinaryPath, args, spawnOptions);
|
|
40
47
|
}
|
|
41
|
-
|
|
48
|
+
async function resolveReleaseId(config) {
|
|
49
|
+
const args = [
|
|
50
|
+
'release',
|
|
51
|
+
'resolve',
|
|
52
|
+
...buildReleaseArgs(config)
|
|
53
|
+
];
|
|
54
|
+
const { code, stdout, stderr } = await spawnLocalCapture(config.cliBinaryPath, args, {
|
|
55
|
+
cwd: process.cwd(),
|
|
56
|
+
env: buildCliEnv(config)
|
|
57
|
+
});
|
|
58
|
+
if (0 !== code) {
|
|
59
|
+
const hint = /unrecognized subcommand|unexpected argument/.test(stderr) ? ` Event release mode needs a posthog-cli with 'release resolve' (${config.cliBinaryPath}).` : '';
|
|
60
|
+
throw new Error(`posthog-cli release resolve failed with code ${code}.${hint}\n${stderr.trim()}`);
|
|
61
|
+
}
|
|
62
|
+
if (stderr.trim()) process.stderr.write(stderr);
|
|
63
|
+
return stdout.trim() || void 0;
|
|
64
|
+
}
|
|
65
|
+
export { buildCliEnv, buildSourcemapCliArgs, resolveReleaseId, runSourcemapCli };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
2
|
+
/**
|
|
3
|
+
* How an exception gets associated with a release, mirroring posthog-cli's `--release-mode`.
|
|
4
|
+
*
|
|
5
|
+
* `event`, the default, leaves the uploaded symbol sets unbound and injects the release id into
|
|
6
|
+
* each chunk, so the release is resolved per exception. `symbol-set` binds the uploaded symbol
|
|
7
|
+
* sets to a release instead.
|
|
8
|
+
*/
|
|
9
|
+
export type ReleaseMode = 'symbol-set' | 'event';
|
|
2
10
|
export interface PluginConfig {
|
|
3
11
|
personalApiKey: string;
|
|
4
12
|
/** @deprecated Use projectId instead */
|
|
@@ -18,6 +26,13 @@ export interface PluginConfig {
|
|
|
18
26
|
build?: string | number;
|
|
19
27
|
deleteAfterUpload?: boolean;
|
|
20
28
|
batchSize?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Defaults to the `POSTHOG_RELEASE_MODE` env var, the same one posthog-cli reads, then to
|
|
31
|
+
* `event`. Event mode needs a posthog-cli that supports `release resolve` and
|
|
32
|
+
* `--release-mode`. Set `symbol-set` to bind the uploaded symbol sets to a release
|
|
33
|
+
* instead.
|
|
34
|
+
*/
|
|
35
|
+
releaseMode?: ReleaseMode;
|
|
21
36
|
};
|
|
22
37
|
}
|
|
23
38
|
export interface ResolvedPluginConfig extends Omit<PluginConfig, 'envId' | 'projectId'> {
|
|
@@ -32,6 +47,7 @@ export interface ResolvedPluginConfig extends Omit<PluginConfig, 'envId' | 'proj
|
|
|
32
47
|
build?: string;
|
|
33
48
|
deleteAfterUpload: boolean;
|
|
34
49
|
batchSize?: number;
|
|
50
|
+
releaseMode: ReleaseMode;
|
|
35
51
|
};
|
|
36
52
|
}
|
|
37
53
|
export interface ResolveConfigOptions {
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErE,MAAM,WAAW,YAAY;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,0CAA0C;QAC1C,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,6CAA6C;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAA;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AAchD,MAAM,WAAW,YAAY;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,0CAA0C;QAC1C,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,6CAA6C;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAA;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB;;;;;WAKG;QACH,WAAW,CAAC,EAAE,WAAW,CAAA;KAC5B,CAAA;CACJ;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,WAAW,CAAC;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE;QACR,OAAO,EAAE,OAAO,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,iBAAiB,EAAE,OAAO,CAAA;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,WAAW,EAAE,WAAW,CAAA;KAC3B,CAAA;CACJ;AAED,MAAM,WAAW,oBAAoB;IACjC,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CA0ChH"}
|
package/dist/config.js
CHANGED
|
@@ -35,6 +35,15 @@ function normalizeHost(value) {
|
|
|
35
35
|
const normalizedHost = 'string' == typeof value ? value.trim() : '';
|
|
36
36
|
return normalizedHost || DEFAULT_PLUGIN_HOST;
|
|
37
37
|
}
|
|
38
|
+
const RELEASE_MODES = [
|
|
39
|
+
'symbol-set',
|
|
40
|
+
'event'
|
|
41
|
+
];
|
|
42
|
+
function normalizeReleaseMode(value) {
|
|
43
|
+
if (void 0 === value || '' === value) return 'event';
|
|
44
|
+
if (!RELEASE_MODES.includes(value)) throw new Error(`sourcemaps.releaseMode must be one of ${RELEASE_MODES.join(', ')}, got '${value}'`);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
38
47
|
function resolveConfig(options, resolveOptions) {
|
|
39
48
|
const projectId = options.projectId ?? options.envId;
|
|
40
49
|
const personalApiKey = normalizeApiKey(options.personalApiKey);
|
|
@@ -64,7 +73,8 @@ function resolveConfig(options, resolveOptions) {
|
|
|
64
73
|
releaseVersion: userSourcemaps.releaseVersion ?? userSourcemaps.version,
|
|
65
74
|
build: void 0 !== userSourcemaps.build ? String(userSourcemaps.build) : void 0,
|
|
66
75
|
deleteAfterUpload: userSourcemaps.deleteAfterUpload ?? true,
|
|
67
|
-
batchSize: userSourcemaps.batchSize
|
|
76
|
+
batchSize: userSourcemaps.batchSize,
|
|
77
|
+
releaseMode: normalizeReleaseMode(userSourcemaps.releaseMode ?? process.env.POSTHOG_RELEASE_MODE)
|
|
68
78
|
}
|
|
69
79
|
};
|
|
70
80
|
}
|
package/dist/config.mjs
CHANGED
|
@@ -7,6 +7,15 @@ function normalizeHost(value) {
|
|
|
7
7
|
const normalizedHost = 'string' == typeof value ? value.trim() : '';
|
|
8
8
|
return normalizedHost || DEFAULT_PLUGIN_HOST;
|
|
9
9
|
}
|
|
10
|
+
const RELEASE_MODES = [
|
|
11
|
+
'symbol-set',
|
|
12
|
+
'event'
|
|
13
|
+
];
|
|
14
|
+
function normalizeReleaseMode(value) {
|
|
15
|
+
if (void 0 === value || '' === value) return 'event';
|
|
16
|
+
if (!RELEASE_MODES.includes(value)) throw new Error(`sourcemaps.releaseMode must be one of ${RELEASE_MODES.join(', ')}, got '${value}'`);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
10
19
|
function resolveConfig(options, resolveOptions) {
|
|
11
20
|
const projectId = options.projectId ?? options.envId;
|
|
12
21
|
const personalApiKey = normalizeApiKey(options.personalApiKey);
|
|
@@ -36,7 +45,8 @@ function resolveConfig(options, resolveOptions) {
|
|
|
36
45
|
releaseVersion: userSourcemaps.releaseVersion ?? userSourcemaps.version,
|
|
37
46
|
build: void 0 !== userSourcemaps.build ? String(userSourcemaps.build) : void 0,
|
|
38
47
|
deleteAfterUpload: userSourcemaps.deleteAfterUpload ?? true,
|
|
39
|
-
batchSize: userSourcemaps.batchSize
|
|
48
|
+
batchSize: userSourcemaps.batchSize,
|
|
49
|
+
releaseMode: normalizeReleaseMode(userSourcemaps.releaseMode ?? process.env.POSTHOG_RELEASE_MODE)
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
package/dist/spawn-local.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export interface CapturedRun {
|
|
2
|
+
code: number | null;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Runs a command and captures both streams instead of inheriting them, for commands whose stdout
|
|
8
|
+
* is a value the caller needs rather than build output. Resolves with a non-zero `code` rather
|
|
9
|
+
* than rejecting, so the caller can shape the error from what the command wrote to stderr.
|
|
10
|
+
*/
|
|
11
|
+
export declare function spawnLocalCapture(executable: string, args: string[], options: {
|
|
12
|
+
env: NodeJS.ProcessEnv;
|
|
13
|
+
cwd: string;
|
|
14
|
+
}): Promise<CapturedRun>;
|
|
1
15
|
export declare function spawnLocal(executable: string, args: string[], options: {
|
|
2
16
|
env: NodeJS.ProcessEnv;
|
|
3
17
|
stdio: 'inherit' | 'ignore';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-local.d.ts","sourceRoot":"","sources":["../src/spawn-local.ts"],"names":[],"mappings":"AAEA,wBAAsB,UAAU,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACL,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB,GACF,OAAO,CAAC,IAAI,CAAC,CAiCf"}
|
|
1
|
+
{"version":3,"file":"spawn-local.d.ts","sourceRoot":"","sources":["../src/spawn-local.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACL,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,GAAG,EAAE,MAAM,CAAA;CACd,GACF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,wBAAsB,UAAU,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACL,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB,GACF,OAAO,CAAC,IAAI,CAAC,CAiCf"}
|
package/dist/spawn-local.js
CHANGED
|
@@ -24,9 +24,39 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
spawnLocalCapture: ()=>spawnLocalCapture,
|
|
27
28
|
spawnLocal: ()=>spawnLocal
|
|
28
29
|
});
|
|
29
30
|
const external_cross_spawn_namespaceObject = require("cross-spawn");
|
|
31
|
+
async function spawnLocalCapture(executable, args, options) {
|
|
32
|
+
const child = (0, external_cross_spawn_namespaceObject.spawn)(executable, [
|
|
33
|
+
...args
|
|
34
|
+
], {
|
|
35
|
+
stdio: [
|
|
36
|
+
'ignore',
|
|
37
|
+
'pipe',
|
|
38
|
+
'pipe'
|
|
39
|
+
],
|
|
40
|
+
env: options.env,
|
|
41
|
+
cwd: options.cwd
|
|
42
|
+
});
|
|
43
|
+
let stdout = '';
|
|
44
|
+
let stderr = '';
|
|
45
|
+
child.stdout?.on('data', (chunk)=>{
|
|
46
|
+
stdout += chunk.toString();
|
|
47
|
+
});
|
|
48
|
+
child.stderr?.on('data', (chunk)=>{
|
|
49
|
+
stderr += chunk.toString();
|
|
50
|
+
});
|
|
51
|
+
return await new Promise((resolve, reject)=>{
|
|
52
|
+
child.on('close', (code)=>resolve({
|
|
53
|
+
code,
|
|
54
|
+
stdout,
|
|
55
|
+
stderr
|
|
56
|
+
}));
|
|
57
|
+
child.on('error', (error)=>reject(error));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
30
60
|
async function spawnLocal(executable, args, options) {
|
|
31
61
|
const stdioOption = void 0 !== options.stdin ? [
|
|
32
62
|
'pipe',
|
|
@@ -58,8 +88,10 @@ async function spawnLocal(executable, args, options) {
|
|
|
58
88
|
});
|
|
59
89
|
}
|
|
60
90
|
exports.spawnLocal = __webpack_exports__.spawnLocal;
|
|
91
|
+
exports.spawnLocalCapture = __webpack_exports__.spawnLocalCapture;
|
|
61
92
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
62
|
-
"spawnLocal"
|
|
93
|
+
"spawnLocal",
|
|
94
|
+
"spawnLocalCapture"
|
|
63
95
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
64
96
|
Object.defineProperty(exports, '__esModule', {
|
|
65
97
|
value: true
|
package/dist/spawn-local.mjs
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { spawn } from "cross-spawn";
|
|
2
|
+
async function spawnLocalCapture(executable, args, options) {
|
|
3
|
+
const child = spawn(executable, [
|
|
4
|
+
...args
|
|
5
|
+
], {
|
|
6
|
+
stdio: [
|
|
7
|
+
'ignore',
|
|
8
|
+
'pipe',
|
|
9
|
+
'pipe'
|
|
10
|
+
],
|
|
11
|
+
env: options.env,
|
|
12
|
+
cwd: options.cwd
|
|
13
|
+
});
|
|
14
|
+
let stdout = '';
|
|
15
|
+
let stderr = '';
|
|
16
|
+
child.stdout?.on('data', (chunk)=>{
|
|
17
|
+
stdout += chunk.toString();
|
|
18
|
+
});
|
|
19
|
+
child.stderr?.on('data', (chunk)=>{
|
|
20
|
+
stderr += chunk.toString();
|
|
21
|
+
});
|
|
22
|
+
return await new Promise((resolve, reject)=>{
|
|
23
|
+
child.on('close', (code)=>resolve({
|
|
24
|
+
code,
|
|
25
|
+
stdout,
|
|
26
|
+
stderr
|
|
27
|
+
}));
|
|
28
|
+
child.on('error', (error)=>reject(error));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
2
31
|
async function spawnLocal(executable, args, options) {
|
|
3
32
|
const stdioOption = void 0 !== options.stdin ? [
|
|
4
33
|
'pipe',
|
|
@@ -29,4 +58,4 @@ async function spawnLocal(executable, args, options) {
|
|
|
29
58
|
});
|
|
30
59
|
});
|
|
31
60
|
}
|
|
32
|
-
export { spawnLocal };
|
|
61
|
+
export { spawnLocal, spawnLocalCapture };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/plugin-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/PostHog/posthog-js/issues"
|
|
6
6
|
},
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"clean": "rimraf dist",
|
|
42
|
-
"lint": "
|
|
43
|
-
"lint:fix": "
|
|
42
|
+
"lint": "oxlint --report-unused-disable-directives-severity error src",
|
|
43
|
+
"lint:fix": "oxlint --report-unused-disable-directives-severity error src --fix",
|
|
44
44
|
"build": "rslib build",
|
|
45
45
|
"dev": "rslib build -w",
|
|
46
46
|
"test:unit": "jest",
|
package/src/chunk-ids.ts
CHANGED
|
@@ -2,15 +2,24 @@ import * as crypto from 'crypto'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns the minified chunk-id IIFE the SDK reads at runtime to map an
|
|
5
|
-
* error's stack back to a chunk id.
|
|
5
|
+
* error's stack back to a chunk id. With `releaseId`, the snippet also pins
|
|
6
|
+
* `_posthogReleaseId` on the global so the SDK emits the release on every
|
|
7
|
+
* exception (first write wins, so the first loaded chunk decides).
|
|
6
8
|
*
|
|
7
|
-
* Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE`
|
|
8
|
-
* cli/src/sourcemaps/constant.rs): the
|
|
9
|
-
* injected snippets by exact substring
|
|
10
|
-
*
|
|
9
|
+
* Byte-for-byte contract with posthog-cli (`CODE_SNIPPET_TEMPLATE` and
|
|
10
|
+
* `CODE_SNIPPET_WITH_RELEASE_TEMPLATE` in cli/src/sourcemaps/constant.rs): the
|
|
11
|
+
* CLI recognizes and removes previously injected snippets by exact substring
|
|
12
|
+
* match, so any change here must ship in the CLI constants too. Ids are
|
|
13
|
+
* JSON-encoded for the same reason the CLI encodes them: an id carrying a quote
|
|
14
|
+
* would otherwise break out of the string literal. Covered by a parity test in
|
|
15
|
+
* chunk-ids.spec.ts.
|
|
11
16
|
*/
|
|
12
|
-
export function createChunkIdSnippet(chunkId: string): string {
|
|
13
|
-
|
|
17
|
+
export function createChunkIdSnippet(chunkId: string, releaseId?: string): string {
|
|
18
|
+
const chunk = JSON.stringify(chunkId)
|
|
19
|
+
if (releaseId === undefined) {
|
|
20
|
+
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]=${chunk})}catch(e){}}();`
|
|
21
|
+
}
|
|
22
|
+
return `!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e._posthogReleaseId=e._posthogReleaseId||${JSON.stringify(releaseId)};var n=(new e.Error).stack;n&&(e._posthogChunkIds=e._posthogChunkIds||{},e._posthogChunkIds[n]=${chunk})}catch(e){}}();`
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
/**
|
|
@@ -29,6 +38,33 @@ export function createChunkId(): string {
|
|
|
29
38
|
return crypto.randomUUID()
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
// Fixed namespace for content-addressed chunk ids, matching `CHUNK_ID_NAMESPACE` in
|
|
42
|
+
// cli/src/sourcemaps/constant.rs. Both sides must derive ids from the same namespace, or a
|
|
43
|
+
// dist processed by one tool and re-processed by the other would upload the same code twice.
|
|
44
|
+
const CHUNK_ID_NAMESPACE = '0e9b3c7a-5d1f-42a8-b6c4-e2d0f8a17593'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Derives a chunk id from the chunk's own bytes (UUIDv5), so identical code keeps its id across
|
|
48
|
+
* rebuilds and re-uploads dedupe against the symbol set already stored. Used in event release
|
|
49
|
+
* mode, where the release travels inside the chunk instead of being bound to the symbol set, so
|
|
50
|
+
* a per-build random id would create a fresh symbol set on every build.
|
|
51
|
+
*
|
|
52
|
+
* The id is content-addressed over what `renderChunk` sees. posthog-cli derives its own ids from
|
|
53
|
+
* the file on disk, which by then also carries the trailing `sourceMappingURL` comment, so the
|
|
54
|
+
* same chunk gets a different (but equally stable) id depending on which tool injected it.
|
|
55
|
+
*/
|
|
56
|
+
export function createStableChunkId(code: string): string {
|
|
57
|
+
const namespace = Buffer.from(CHUNK_ID_NAMESPACE.replace(/-/g, ''), 'hex')
|
|
58
|
+
const hash = crypto.createHash('sha1').update(namespace).update(code, 'utf8').digest()
|
|
59
|
+
|
|
60
|
+
// RFC 4122: version in the high nibble of byte 6, variant in the top two bits of byte 8.
|
|
61
|
+
hash[6] = (hash[6] & 0x0f) | 0x50
|
|
62
|
+
hash[8] = (hash[8] & 0x3f) | 0x80
|
|
63
|
+
|
|
64
|
+
const hex = hash.subarray(0, 16).toString('hex')
|
|
65
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
66
|
+
}
|
|
67
|
+
|
|
32
68
|
// Anchored with a bounded quantifier and only ever run against a fixed-size
|
|
33
69
|
// window: an unbounded regex over the whole bundle is quadratic on adversarial
|
|
34
70
|
// input (a match attempt per marker occurrence, each scanning ahead).
|
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResolvedPluginConfig } from './config'
|
|
2
|
-
import { spawnLocal } from './spawn-local'
|
|
2
|
+
import { spawnLocal, spawnLocalCapture } from './spawn-local'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `process` injects chunk ids into the built files on disk and uploads them.
|
|
@@ -8,6 +8,28 @@ import { spawnLocal } from './spawn-local'
|
|
|
8
8
|
*/
|
|
9
9
|
export type SourcemapCliCommand = 'process' | 'upload'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The flags identifying which release this build belongs to. Shared by every command that
|
|
13
|
+
* resolves one, so `release resolve` lands on the same release row the upload would have.
|
|
14
|
+
*/
|
|
15
|
+
function buildReleaseArgs(config: ResolvedPluginConfig): string[] {
|
|
16
|
+
const args: string[] = []
|
|
17
|
+
|
|
18
|
+
if (config.sourcemaps.releaseName) {
|
|
19
|
+
args.push('--release-name', config.sourcemaps.releaseName)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (config.sourcemaps.releaseVersion) {
|
|
23
|
+
args.push('--release-version', config.sourcemaps.releaseVersion)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (config.sourcemaps.build !== undefined && config.sourcemaps.build !== '') {
|
|
27
|
+
args.push('--build', config.sourcemaps.build)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return args
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
/**
|
|
12
34
|
* Build CLI arguments for `posthog-cli sourcemap <command>`.
|
|
13
35
|
*/
|
|
@@ -24,16 +46,12 @@ export function buildSourcemapCliArgs(
|
|
|
24
46
|
args.push('--directory', mode.directory)
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
args.push('--release-name', config.sourcemaps.releaseName)
|
|
29
|
-
}
|
|
49
|
+
args.push(...buildReleaseArgs(config))
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (config.sourcemaps.build !== undefined && config.sourcemaps.build !== '') {
|
|
36
|
-
args.push('--build', config.sourcemaps.build)
|
|
51
|
+
// Only passed in event mode, so a symbol-set build keeps working against a posthog-cli
|
|
52
|
+
// predating the flag.
|
|
53
|
+
if (config.sourcemaps.releaseMode === 'event') {
|
|
54
|
+
args.push('--release-mode', 'event')
|
|
37
55
|
}
|
|
38
56
|
|
|
39
57
|
// On `upload` the caller owns map deletion: `--delete-after` also rewrites
|
|
@@ -62,6 +80,9 @@ export function buildCliEnv(config: ResolvedPluginConfig): NodeJS.ProcessEnv {
|
|
|
62
80
|
POSTHOG_CLI_HOST: config.host,
|
|
63
81
|
POSTHOG_CLI_API_KEY: config.personalApiKey,
|
|
64
82
|
POSTHOG_CLI_PROJECT_ID: config.projectId,
|
|
83
|
+
// The CLI reads this variable itself, so an inherited value would decide the release mode
|
|
84
|
+
// of the spawned command while the plugin injected chunks for the mode it resolved.
|
|
85
|
+
POSTHOG_RELEASE_MODE: config.sourcemaps.releaseMode,
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
88
|
|
|
@@ -88,3 +109,35 @@ export async function runSourcemapCli(
|
|
|
88
109
|
|
|
89
110
|
await spawnLocal(config.cliBinaryPath, args, spawnOptions)
|
|
90
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolves the release this build belongs to, creating it if it doesn't exist yet, and returns its
|
|
115
|
+
* id for injection into the chunks. Returns undefined when nothing identifies a release: the CLI
|
|
116
|
+
* prints nothing and exits zero for a build with no release name/version and no git or CI
|
|
117
|
+
* metadata to derive them from, which is a build that ships without a release rather than a
|
|
118
|
+
* failure.
|
|
119
|
+
*/
|
|
120
|
+
export async function resolveReleaseId(config: ResolvedPluginConfig): Promise<string | undefined> {
|
|
121
|
+
const args = ['release', 'resolve', ...buildReleaseArgs(config)]
|
|
122
|
+
const { code, stdout, stderr } = await spawnLocalCapture(config.cliBinaryPath, args, {
|
|
123
|
+
cwd: process.cwd(),
|
|
124
|
+
env: buildCliEnv(config),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
if (code !== 0) {
|
|
128
|
+
// `release resolve` shipped after `sourcemap upload`, so an older binary on the PATH
|
|
129
|
+
// fails here rather than at upload time, where the message would make more sense.
|
|
130
|
+
const hint = /unrecognized subcommand|unexpected argument/.test(stderr)
|
|
131
|
+
? ` Event release mode needs a posthog-cli with 'release resolve' (${config.cliBinaryPath}).`
|
|
132
|
+
: ''
|
|
133
|
+
throw new Error(`posthog-cli release resolve failed with code ${code}.${hint}\n${stderr.trim()}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The CLI logs to stderr and prints only the id to stdout, so forward its warnings rather
|
|
137
|
+
// than swallowing them with the captured stream.
|
|
138
|
+
if (stderr.trim()) {
|
|
139
|
+
process.stderr.write(stderr)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return stdout.trim() || undefined
|
|
143
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -13,6 +13,27 @@ function normalizeHost(value?: unknown): string {
|
|
|
13
13
|
|
|
14
14
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* How an exception gets associated with a release, mirroring posthog-cli's `--release-mode`.
|
|
18
|
+
*
|
|
19
|
+
* `event`, the default, leaves the uploaded symbol sets unbound and injects the release id into
|
|
20
|
+
* each chunk, so the release is resolved per exception. `symbol-set` binds the uploaded symbol
|
|
21
|
+
* sets to a release instead.
|
|
22
|
+
*/
|
|
23
|
+
export type ReleaseMode = 'symbol-set' | 'event'
|
|
24
|
+
|
|
25
|
+
const RELEASE_MODES: ReleaseMode[] = ['symbol-set', 'event']
|
|
26
|
+
|
|
27
|
+
function normalizeReleaseMode(value: string | undefined): ReleaseMode {
|
|
28
|
+
if (value === undefined || value === '') {
|
|
29
|
+
return 'event'
|
|
30
|
+
}
|
|
31
|
+
if (!(RELEASE_MODES as string[]).includes(value)) {
|
|
32
|
+
throw new Error(`sourcemaps.releaseMode must be one of ${RELEASE_MODES.join(', ')}, got '${value}'`)
|
|
33
|
+
}
|
|
34
|
+
return value as ReleaseMode
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
export interface PluginConfig {
|
|
17
38
|
personalApiKey: string
|
|
18
39
|
/** @deprecated Use projectId instead */
|
|
@@ -32,6 +53,13 @@ export interface PluginConfig {
|
|
|
32
53
|
build?: string | number
|
|
33
54
|
deleteAfterUpload?: boolean
|
|
34
55
|
batchSize?: number
|
|
56
|
+
/**
|
|
57
|
+
* Defaults to the `POSTHOG_RELEASE_MODE` env var, the same one posthog-cli reads, then to
|
|
58
|
+
* `event`. Event mode needs a posthog-cli that supports `release resolve` and
|
|
59
|
+
* `--release-mode`. Set `symbol-set` to bind the uploaded symbol sets to a release
|
|
60
|
+
* instead.
|
|
61
|
+
*/
|
|
62
|
+
releaseMode?: ReleaseMode
|
|
35
63
|
}
|
|
36
64
|
}
|
|
37
65
|
|
|
@@ -47,6 +75,7 @@ export interface ResolvedPluginConfig extends Omit<PluginConfig, 'envId' | 'proj
|
|
|
47
75
|
build?: string
|
|
48
76
|
deleteAfterUpload: boolean
|
|
49
77
|
batchSize?: number
|
|
78
|
+
releaseMode: ReleaseMode
|
|
50
79
|
}
|
|
51
80
|
}
|
|
52
81
|
|
|
@@ -96,6 +125,7 @@ export function resolveConfig(options: PluginConfig, resolveOptions?: ResolveCon
|
|
|
96
125
|
build: userSourcemaps.build !== undefined ? String(userSourcemaps.build) : undefined,
|
|
97
126
|
deleteAfterUpload: userSourcemaps.deleteAfterUpload ?? true,
|
|
98
127
|
batchSize: userSourcemaps.batchSize,
|
|
128
|
+
releaseMode: normalizeReleaseMode(userSourcemaps.releaseMode ?? process.env.POSTHOG_RELEASE_MODE),
|
|
99
129
|
},
|
|
100
130
|
}
|
|
101
131
|
}
|
package/src/spawn-local.ts
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { spawn } from 'cross-spawn'
|
|
2
2
|
|
|
3
|
+
export interface CapturedRun {
|
|
4
|
+
code: number | null
|
|
5
|
+
stdout: string
|
|
6
|
+
stderr: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs a command and captures both streams instead of inheriting them, for commands whose stdout
|
|
11
|
+
* is a value the caller needs rather than build output. Resolves with a non-zero `code` rather
|
|
12
|
+
* than rejecting, so the caller can shape the error from what the command wrote to stderr.
|
|
13
|
+
*/
|
|
14
|
+
export async function spawnLocalCapture(
|
|
15
|
+
executable: string,
|
|
16
|
+
args: string[],
|
|
17
|
+
options: {
|
|
18
|
+
env: NodeJS.ProcessEnv
|
|
19
|
+
cwd: string
|
|
20
|
+
}
|
|
21
|
+
): Promise<CapturedRun> {
|
|
22
|
+
const child = spawn(executable, [...args], {
|
|
23
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
24
|
+
env: options.env,
|
|
25
|
+
cwd: options.cwd,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
let stdout = ''
|
|
29
|
+
let stderr = ''
|
|
30
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
31
|
+
stdout += chunk.toString()
|
|
32
|
+
})
|
|
33
|
+
child.stderr?.on('data', (chunk: Buffer) => {
|
|
34
|
+
stderr += chunk.toString()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return await new Promise<CapturedRun>((resolve, reject) => {
|
|
38
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }))
|
|
39
|
+
child.on('error', (error) => reject(error))
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
3
43
|
export async function spawnLocal(
|
|
4
44
|
executable: string,
|
|
5
45
|
args: string[],
|