@logbrew/sdk 0.1.18 → 0.1.20
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/README.md +3 -3
- package/core.cjs +7 -1
- package/index.d.ts +10 -0
- package/issue-stack.cjs +28 -1
- package/package.json +1 -1
- package/vite-release-artifacts.cjs +25 -4
- package/vite-release-artifacts.d.cts +1 -1
- package/vite-release-artifacts.d.ts +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ node node_modules/@logbrew/sdk/examples/index.mjs agent-timeline
|
|
|
25
25
|
npm --prefix node_modules/@logbrew/sdk/examples run agent-timeline
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
For Vite apps, add the build-time release-artifact plugin to `vite.config.js`. It enables hidden source maps when your config has not
|
|
28
|
+
For Vite apps, add the build-time release-artifact plugin to `vite.config.js`. It enables hidden source maps and function-name preservation when your config has not made either choice, injects matching Debug IDs after the build, strips embedded source text and local source prefixes, writes a privacy-bounded manifest, and can upload the prepared artifacts before the build completes:
|
|
29
29
|
|
|
30
30
|
```js
|
|
31
31
|
import { createLogBrewViteReleaseArtifactsPlugin } from "@logbrew/sdk/vite-release-artifacts";
|
|
@@ -48,7 +48,7 @@ export default {
|
|
|
48
48
|
};
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
Set `LOGBREW_RELEASE_ARTIFACT_TOKEN` in the build environment to a dedicated release-artifact token. Use `tokenEnv` when your CI uses a different environment variable name, or `dryRun: true` to prepare the complete build output without a network request. The plugin runs only during Vite builds, keeps upload disabled when `upload` is omitted, and fails the build when preparation or upload cannot complete safely. It never uses normal SDK ingest keys or account/session API values.
|
|
51
|
+
Set `LOGBREW_RELEASE_ARTIFACT_TOKEN` in the build environment to a dedicated release-artifact token. Use `tokenEnv` when your CI uses a different environment variable name, or `dryRun: true` to prepare the complete build output without a network request. The plugin runs only during Vite builds, keeps upload disabled when `upload` is omitted, and fails the build when preparation or upload cannot complete safely. It never uses normal SDK ingest keys or account/session API values. When your app chooses `build.minify` or multiple Rolldown outputs explicitly, configure `keepNames` for that build; otherwise captured stacks retain shortened function labels.
|
|
52
52
|
|
|
53
53
|
The package also ships the dependency-free `logbrew-release-artifacts` command for JavaScript source-map preparation and upload. Use it after your frontend build to inject matching Debug IDs, strip embedded source text by default, and create a privacy-bounded manifest that can be inspected before upload:
|
|
54
54
|
|
|
@@ -102,7 +102,7 @@ npx logbrew-release-artifacts upload-js \
|
|
|
102
102
|
|
|
103
103
|
Non-loopback endpoints require `--allow-hosted`, a UUID `projectId` created by `manifest-js --project-id`, HTTPS, and no embedded auth values, query strings, or fragments. Local loopback preparation remains valid without a project ID. The upload command never uses normal SDK ingest keys or account/session API auth values. Full backend-symbolicated issue support is separate from artifact upload until your project has completed hosted symbolication for its release.
|
|
104
104
|
|
|
105
|
-
When you capture a JavaScript error, use `createIssueAttributesFromError()` to keep error metadata structured and source-map-friendly without sending raw stack text by default. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../docs/exception-chain-evidence.md).
|
|
105
|
+
When you capture a JavaScript error, use `createIssueAttributesFromError()` to keep error metadata structured and source-map-friendly without sending raw stack text by default. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../docs/exception-chain-evidence.md). A bundle prepared by the Vite plugin registers its Debug ID automatically; use `debugIdMap` only for a different app-owned build flow:
|
|
106
106
|
|
|
107
107
|
```js
|
|
108
108
|
import { createIssueAttributesFromError, LogBrewClient } from "@logbrew/sdk";
|
package/core.cjs
CHANGED
|
@@ -133,7 +133,8 @@ const {
|
|
|
133
133
|
|
|
134
134
|
const {
|
|
135
135
|
javascriptStackEvidence,
|
|
136
|
-
validateIssueStackFrames
|
|
136
|
+
validateIssueStackFrames,
|
|
137
|
+
validateNativeStackFrames
|
|
137
138
|
} = buildIssueStackHelpers({ SdkError });
|
|
138
139
|
const {
|
|
139
140
|
MAX_ISSUE_BREADCRUMBS,
|
|
@@ -2256,6 +2257,9 @@ function cloneSpanLinks(links) {
|
|
|
2256
2257
|
|
|
2257
2258
|
function cloneEvent(event) {
|
|
2258
2259
|
const attributes = { ...event.attributes, ...cloneIssueDiagnostics(event.attributes) };
|
|
2260
|
+
if (Array.isArray(event.attributes.nativeStackFrames)) {
|
|
2261
|
+
attributes.nativeStackFrames = event.attributes.nativeStackFrames.map((frame) => ({ ...frame }));
|
|
2262
|
+
}
|
|
2259
2263
|
if (event.attributes.metadata !== undefined) {
|
|
2260
2264
|
attributes.metadata = { ...event.attributes.metadata };
|
|
2261
2265
|
}
|
|
@@ -2295,11 +2299,13 @@ function validateIssue(attributes) {
|
|
|
2295
2299
|
requireNonEmpty("issue title", attributes.title);
|
|
2296
2300
|
const level = normalizeSeverity("issue level", attributes.level);
|
|
2297
2301
|
const stackFrames = validateIssueStackFrames(attributes.stackFrames);
|
|
2302
|
+
const nativeStackFrames = validateNativeStackFrames(attributes.nativeStackFrames);
|
|
2298
2303
|
return withMetadata({
|
|
2299
2304
|
title: attributes.title,
|
|
2300
2305
|
level,
|
|
2301
2306
|
...(attributes.message !== undefined ? { message: attributes.message } : {}),
|
|
2302
2307
|
...(stackFrames !== undefined ? { stackFrames } : {}),
|
|
2308
|
+
...(nativeStackFrames !== undefined ? { nativeStackFrames } : {}),
|
|
2303
2309
|
...validateIssueDiagnostics(attributes)
|
|
2304
2310
|
}, attributes.metadata, attributes.context);
|
|
2305
2311
|
}
|
package/index.d.ts
CHANGED
|
@@ -389,6 +389,14 @@ export type IssueDiagnosticEvidence = {
|
|
|
389
389
|
truncatedFields?: string[];
|
|
390
390
|
};
|
|
391
391
|
|
|
392
|
+
export type NativeStackArchitecture = "arm" | "arm64" | "arm64e" | "x86" | "x86_64";
|
|
393
|
+
|
|
394
|
+
export type NativeStackFrame = {
|
|
395
|
+
imageUuid: string;
|
|
396
|
+
architecture: NativeStackArchitecture;
|
|
397
|
+
instructionOffset: string;
|
|
398
|
+
};
|
|
399
|
+
|
|
392
400
|
/** Public issue event attributes. */
|
|
393
401
|
export type IssueAttributes = {
|
|
394
402
|
title: string;
|
|
@@ -399,6 +407,8 @@ export type IssueAttributes = {
|
|
|
399
407
|
exceptionChain?: IssueExceptionChain;
|
|
400
408
|
/** Ordered privacy-bounded generated frames, capped at 32. */
|
|
401
409
|
stackFrames?: IssueStackFrame[];
|
|
410
|
+
/** Ordered native image-relative frames for exact release symbolication, capped at 32. */
|
|
411
|
+
nativeStackFrames?: NativeStackFrame[];
|
|
402
412
|
/** Oldest-to-newest issue history, capped at the most recent 64 entries. */
|
|
403
413
|
breadcrumbs?: IssueBreadcrumb[];
|
|
404
414
|
/** True when older or invalid history was omitted before capture. */
|
package/issue-stack.cjs
CHANGED
|
@@ -4,6 +4,9 @@ const MAX_ISSUE_STACK_FRAMES = 32;
|
|
|
4
4
|
const MAX_ISSUE_STACK_FUNCTION_LENGTH = 256;
|
|
5
5
|
const MAX_ISSUE_STACK_MODULE_LENGTH = 512;
|
|
6
6
|
const SAFE_DEBUG_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
7
|
+
const NATIVE_IMAGE_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
8
|
+
const NATIVE_OFFSET_PATTERN = /^[0-9a-f]{16}$/u;
|
|
9
|
+
const NATIVE_ARCHITECTURES = new Set(["arm", "arm64", "arm64e", "x86", "x86_64"]);
|
|
7
10
|
const LOCAL_ABSOLUTE_PATH_PATTERN = /(?:^|\s)(?:\/(?:Users|home|private|tmp|var|Volumes)\/|[A-Za-z]:[\\/])/u;
|
|
8
11
|
const DEBUG_ID_REGISTRY = Symbol.for("logbrew.release-artifact.debug-ids");
|
|
9
12
|
|
|
@@ -87,7 +90,31 @@ function buildIssueStackHelpers({ SdkError }) {
|
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
function validateNativeStackFrames(frames) {
|
|
94
|
+
if (frames === undefined) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(frames) || frames.length === 0 || frames.length > MAX_ISSUE_STACK_FRAMES) {
|
|
98
|
+
throw new SdkError("validation_error", `issue nativeStackFrames must contain 1-${MAX_ISSUE_STACK_FRAMES} frames`);
|
|
99
|
+
}
|
|
100
|
+
return frames.map((frame) => {
|
|
101
|
+
const keys = frame && !Array.isArray(frame) && typeof frame === "object" ? Object.keys(frame) : [];
|
|
102
|
+
if (keys.length !== 3
|
|
103
|
+
|| !keys.every((key) => ["imageUuid", "architecture", "instructionOffset"].includes(key))
|
|
104
|
+
|| typeof frame.imageUuid !== "string" || !NATIVE_IMAGE_UUID_PATTERN.test(frame.imageUuid)
|
|
105
|
+
|| !NATIVE_ARCHITECTURES.has(frame.architecture)
|
|
106
|
+
|| typeof frame.instructionOffset !== "string" || !NATIVE_OFFSET_PATTERN.test(frame.instructionOffset)) {
|
|
107
|
+
throw new SdkError("validation_error", "issue native stack frame is invalid");
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
imageUuid: frame.imageUuid,
|
|
111
|
+
architecture: frame.architecture,
|
|
112
|
+
instructionOffset: frame.instructionOffset
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { javascriptStackEvidence, validateIssueStackFrames, validateNativeStackFrames };
|
|
91
118
|
}
|
|
92
119
|
|
|
93
120
|
function parseJavaScriptStackFrame(rawLine) {
|
package/package.json
CHANGED
|
@@ -55,6 +55,16 @@ function resolveBuildDir(root, outDir, explicitBuildDir) {
|
|
|
55
55
|
return resolvePathFromRoot(root, outDir || "dist");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function viteMajorVersion(root) {
|
|
59
|
+
try {
|
|
60
|
+
const manifestPath = require.resolve("vite/package.json", { paths: [path.resolve(root || process.cwd())] });
|
|
61
|
+
const version = JSON.parse(fs.readFileSync(manifestPath, "utf8")).version;
|
|
62
|
+
return Number.parseInt(version, 10) || 0;
|
|
63
|
+
} catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
function createLogBrewViteReleaseArtifactsPlugin(options) {
|
|
59
69
|
const release = requiredString(options, "release");
|
|
60
70
|
const environment = requiredString(options, "environment");
|
|
@@ -78,10 +88,21 @@ function createLogBrewViteReleaseArtifactsPlugin(options) {
|
|
|
78
88
|
apply: "build",
|
|
79
89
|
enforce: "post",
|
|
80
90
|
config(config = {}) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
const sourceMap = enableSourceMaps && config.build?.sourcemap === undefined;
|
|
92
|
+
const keepNames = config.build?.minify === undefined;
|
|
93
|
+
const output = config.build?.rolldownOptions?.output;
|
|
94
|
+
const viteMajor = viteMajorVersion(config.root);
|
|
95
|
+
const modernNames = keepNames && viteMajor >= 8
|
|
96
|
+
&& !Array.isArray(output) && output?.keepNames === undefined;
|
|
97
|
+
const legacyNames = keepNames && viteMajor < 8
|
|
98
|
+
&& config.esbuild !== false && config.esbuild?.keepNames === undefined;
|
|
99
|
+
return sourceMap || modernNames || legacyNames ? {
|
|
100
|
+
...(sourceMap || modernNames ? { build: {
|
|
101
|
+
...(sourceMap ? { sourcemap: "hidden" } : {}),
|
|
102
|
+
...(modernNames ? { rolldownOptions: { output: { keepNames: true } } } : {})
|
|
103
|
+
} } : {}),
|
|
104
|
+
...(legacyNames ? { esbuild: { keepNames: true } } : {})
|
|
105
|
+
} : null;
|
|
85
106
|
},
|
|
86
107
|
configResolved(config) {
|
|
87
108
|
viteRoot = config?.root ? path.resolve(config.root) : process.cwd();
|
|
@@ -28,7 +28,7 @@ export interface LogBrewViteReleaseArtifactsPlugin {
|
|
|
28
28
|
name: "logbrew-vite-release-artifacts";
|
|
29
29
|
apply: "build";
|
|
30
30
|
enforce: "post";
|
|
31
|
-
config(config?: { build?: { sourcemap?: unknown } }): null | { build
|
|
31
|
+
config(config?: { root?: string; build?: { sourcemap?: unknown; minify?: unknown; rolldownOptions?: { output?: { keepNames?: boolean } | Array<{ keepNames?: boolean }> } }; esbuild?: false | { keepNames?: boolean } }): null | { build?: { sourcemap?: "hidden"; rolldownOptions?: { output: { keepNames: true } } }; esbuild?: { keepNames: true } };
|
|
32
32
|
configResolved(config: {
|
|
33
33
|
root?: string;
|
|
34
34
|
build?: { outDir?: string };
|
|
@@ -28,7 +28,7 @@ export interface LogBrewViteReleaseArtifactsPlugin {
|
|
|
28
28
|
name: "logbrew-vite-release-artifacts";
|
|
29
29
|
apply: "build";
|
|
30
30
|
enforce: "post";
|
|
31
|
-
config(config?: { build?: { sourcemap?: unknown } }): null | { build
|
|
31
|
+
config(config?: { root?: string; build?: { sourcemap?: unknown; minify?: unknown; rolldownOptions?: { output?: { keepNames?: boolean } | Array<{ keepNames?: boolean }> } }; esbuild?: false | { keepNames?: boolean } }): null | { build?: { sourcemap?: "hidden"; rolldownOptions?: { output: { keepNames: true } } }; esbuild?: { keepNames: true } };
|
|
32
32
|
configResolved(config: {
|
|
33
33
|
root?: string;
|
|
34
34
|
build?: { outDir?: string };
|