crumbtrail-install-shared 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +227 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +191 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crumbtrail
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BACKEND_JS_STACKS: () => BACKEND_JS_STACKS,
|
|
24
|
+
INFRA_STACKS: () => INFRA_STACKS,
|
|
25
|
+
JS_STACKS: () => JS_STACKS,
|
|
26
|
+
OTLP_CAPABILITY_FACTS: () => OTLP_CAPABILITY_FACTS,
|
|
27
|
+
OTLP_STACKS: () => OTLP_STACKS,
|
|
28
|
+
allStackInstalls: () => allStackInstalls,
|
|
29
|
+
buildAgentPrompt: () => buildAgentPrompt,
|
|
30
|
+
buildBackendJsNote: () => buildBackendJsNote,
|
|
31
|
+
buildOtlpSnippets: () => buildOtlpSnippets,
|
|
32
|
+
getInstallVariant: () => getInstallVariant,
|
|
33
|
+
otlpAuthHeaderValue: () => otlpAuthHeaderValue,
|
|
34
|
+
otlpBearerHeaderValue: () => otlpBearerHeaderValue
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_crumbtrail_core = require("crumbtrail-core");
|
|
38
|
+
var JS_STACKS = [
|
|
39
|
+
"nextjs",
|
|
40
|
+
"react",
|
|
41
|
+
"vue",
|
|
42
|
+
"svelte",
|
|
43
|
+
"vite",
|
|
44
|
+
"express",
|
|
45
|
+
"hono",
|
|
46
|
+
"node"
|
|
47
|
+
];
|
|
48
|
+
var BACKEND_JS_STACKS = ["express", "hono", "node"];
|
|
49
|
+
var OTLP_STACKS = [
|
|
50
|
+
"django",
|
|
51
|
+
"flask",
|
|
52
|
+
"fastapi",
|
|
53
|
+
"dotnet",
|
|
54
|
+
"go",
|
|
55
|
+
"rails"
|
|
56
|
+
];
|
|
57
|
+
var INFRA_STACKS = [
|
|
58
|
+
"postgres",
|
|
59
|
+
"grafana",
|
|
60
|
+
"loki",
|
|
61
|
+
"docker"
|
|
62
|
+
];
|
|
63
|
+
function getInstallVariant(stack) {
|
|
64
|
+
const backendJs = BACKEND_JS_STACKS.includes(stack);
|
|
65
|
+
if (JS_STACKS.includes(stack)) {
|
|
66
|
+
return { stack, kind: "js", backendJs, comingSoon: false };
|
|
67
|
+
}
|
|
68
|
+
if (OTLP_STACKS.includes(stack)) {
|
|
69
|
+
return { stack, kind: "otlp", backendJs: false, comingSoon: false };
|
|
70
|
+
}
|
|
71
|
+
return { stack, kind: "infra", backendJs: false, comingSoon: true };
|
|
72
|
+
}
|
|
73
|
+
function allStackInstalls() {
|
|
74
|
+
return import_crumbtrail_core.STACK_IDS.map(getInstallVariant);
|
|
75
|
+
}
|
|
76
|
+
function buildBackendJsNote(stack) {
|
|
77
|
+
if (stack === "express") {
|
|
78
|
+
return [
|
|
79
|
+
"// Backend (Express) \u2014 also capture server-side errors and requests.",
|
|
80
|
+
"// Reuse the same ingest endpoint + key you set on the SDK above.",
|
|
81
|
+
"import {",
|
|
82
|
+
" createCrumbtrailExpressMiddleware,",
|
|
83
|
+
" createCrumbtrailExpressErrorMiddleware,",
|
|
84
|
+
'} from "crumbtrail-node";',
|
|
85
|
+
"",
|
|
86
|
+
"const crumbtrailOptions = { endpoint: CRUMBTRAIL_ENDPOINT, authToken: CRUMBTRAIL_KEY };",
|
|
87
|
+
"app.use(createCrumbtrailExpressMiddleware(crumbtrailOptions)); // before your routes",
|
|
88
|
+
"app.use(createCrumbtrailExpressErrorMiddleware(crumbtrailOptions)); // after your routes",
|
|
89
|
+
"",
|
|
90
|
+
"// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
"// Backend (Hono / Node) \u2014 crumbtrail-node ships no framework middleware here.",
|
|
95
|
+
"// Open a headless session and record server-side events against it:",
|
|
96
|
+
'import { startHeadlessSession } from "crumbtrail-node";',
|
|
97
|
+
"",
|
|
98
|
+
"const session = await startHeadlessSession({",
|
|
99
|
+
" endpoint: CRUMBTRAIL_ENDPOINT,",
|
|
100
|
+
" authToken: CRUMBTRAIL_KEY,",
|
|
101
|
+
' sessionId: "<your-session-id>",',
|
|
102
|
+
"});",
|
|
103
|
+
"// session.record(event) to attach server events; session.end() when done.",
|
|
104
|
+
"",
|
|
105
|
+
"// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
var OTLP_CAPABILITY_FACTS = {
|
|
109
|
+
paths: ["/v1/traces", "/v1/logs"],
|
|
110
|
+
protocols: ["http/protobuf", "http/json"],
|
|
111
|
+
authHeaders: ["X-Crumbtrail-Auth", "Authorization: Bearer"],
|
|
112
|
+
compression: { recommended: "none", accepted: ["none", "gzip"] },
|
|
113
|
+
sessionAttribute: "crumbtrail.session.id"
|
|
114
|
+
};
|
|
115
|
+
function otlpAuthHeaderValue(apiKey) {
|
|
116
|
+
return `X-Crumbtrail-Auth=${apiKey}`;
|
|
117
|
+
}
|
|
118
|
+
function otlpBearerHeaderValue(apiKey) {
|
|
119
|
+
return `Authorization=Bearer%20${apiKey}`;
|
|
120
|
+
}
|
|
121
|
+
function buildOtlpSnippets({
|
|
122
|
+
endpoint,
|
|
123
|
+
apiKey
|
|
124
|
+
}) {
|
|
125
|
+
const [protobuf, jsonProtocol] = OTLP_CAPABILITY_FACTS.protocols;
|
|
126
|
+
return {
|
|
127
|
+
env: [
|
|
128
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint}`,
|
|
129
|
+
`OTEL_EXPORTER_OTLP_PROTOCOL=${protobuf} # or ${jsonProtocol}`,
|
|
130
|
+
`OTEL_EXPORTER_OTLP_COMPRESSION=${OTLP_CAPABILITY_FACTS.compression.recommended} # recommended; gzip is also accepted`
|
|
131
|
+
].join("\n"),
|
|
132
|
+
authHeader: [
|
|
133
|
+
`OTEL_EXPORTER_OTLP_HEADERS=${otlpAuthHeaderValue(apiKey)}`,
|
|
134
|
+
`# Or, if your exporter sends a Bearer token (note the %20-escaped space):`,
|
|
135
|
+
`# OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeaderValue(apiKey)}`
|
|
136
|
+
].join("\n"),
|
|
137
|
+
sessionAttr: `OTEL_RESOURCE_ATTRIBUTES=${OTLP_CAPABILITY_FACTS.sessionAttribute}=<your-session-id>`,
|
|
138
|
+
note: `Crumbtrail's OTLP receiver appends ${OTLP_CAPABILITY_FACTS.paths.join(" and ")} to the endpoint above \u2014 don't include those paths yourself. Set the ${OTLP_CAPABILITY_FACTS.sessionAttribute} resource attribute to join backend spans to the frontend session.`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function buildAgentPrompt(stack, keys) {
|
|
142
|
+
const { kind, backendJs } = getInstallVariant(stack);
|
|
143
|
+
const { endpoint, apiKey } = keys;
|
|
144
|
+
const header = [
|
|
145
|
+
"You are setting up Crumbtrail in this project. Make ONLY the changes below,",
|
|
146
|
+
"do not refactor or touch anything else, then verify the build still passes.",
|
|
147
|
+
"",
|
|
148
|
+
`Ingest endpoint: ${endpoint}`,
|
|
149
|
+
`Ingest key: ${apiKey}`,
|
|
150
|
+
""
|
|
151
|
+
];
|
|
152
|
+
if (kind === "otlp") {
|
|
153
|
+
return [
|
|
154
|
+
...header,
|
|
155
|
+
"This is a non-JS backend that already uses OpenTelemetry. Do NOT install a",
|
|
156
|
+
"second SDK. Instead, add Crumbtrail as an additional OTLP/HTTP exporter:",
|
|
157
|
+
` 1. Set OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint} (the exporter appends`,
|
|
158
|
+
" /v1/traces and /v1/logs \u2014 do not add those paths).",
|
|
159
|
+
` 2. Send the auth header X-Crumbtrail-Auth: ${apiKey} on every export`,
|
|
160
|
+
" (or Authorization: Bearer <key> if your exporter prefers that).",
|
|
161
|
+
" 3. Stamp the resource attribute crumbtrail.session.id so spans/logs join",
|
|
162
|
+
" the right session.",
|
|
163
|
+
" 4. Keep your existing exporter \u2014 add Crumbtrail alongside it.",
|
|
164
|
+
" 5. Verify the app still builds and starts."
|
|
165
|
+
].join("\n");
|
|
166
|
+
}
|
|
167
|
+
const jsLines = [
|
|
168
|
+
...header,
|
|
169
|
+
"This is a JavaScript/TypeScript project. Do the following:",
|
|
170
|
+
" 1. Install the SDK: npm install crumbtrail-core",
|
|
171
|
+
' 2. Import the SDK: import { Crumbtrail, PRESET_PASSIVE } from "crumbtrail-core";',
|
|
172
|
+
" 3. Initialize once at the app entry point with PRESET_PASSIVE:",
|
|
173
|
+
" Crumbtrail.init({",
|
|
174
|
+
" ...PRESET_PASSIVE,",
|
|
175
|
+
` httpEndpoint: "${endpoint}",`,
|
|
176
|
+
` httpAuthToken: "${apiKey}",`,
|
|
177
|
+
" });"
|
|
178
|
+
];
|
|
179
|
+
if (backendJs && stack === "express") {
|
|
180
|
+
jsLines.push(
|
|
181
|
+
" 4. This is an Express backend \u2014 also install crumbtrail-node and capture",
|
|
182
|
+
" server errors and requests with its Express middleware:",
|
|
183
|
+
" import {",
|
|
184
|
+
" createCrumbtrailExpressMiddleware,",
|
|
185
|
+
" createCrumbtrailExpressErrorMiddleware,",
|
|
186
|
+
' } from "crumbtrail-node";',
|
|
187
|
+
` const opts = { endpoint: "${endpoint}", authToken: "${apiKey}" };`,
|
|
188
|
+
" app.use(createCrumbtrailExpressMiddleware(opts)); // before your routes",
|
|
189
|
+
" app.use(createCrumbtrailExpressErrorMiddleware(opts)); // after your routes",
|
|
190
|
+
" 5. Change nothing else, then verify the build still passes."
|
|
191
|
+
);
|
|
192
|
+
} else if (backendJs) {
|
|
193
|
+
jsLines.push(
|
|
194
|
+
" 4. This is also a backend. Install crumbtrail-node; it ships no framework",
|
|
195
|
+
" middleware for this stack, so open a headless session and record",
|
|
196
|
+
" server-side events against it:",
|
|
197
|
+
' import { startHeadlessSession } from "crumbtrail-node";',
|
|
198
|
+
" const session = await startHeadlessSession({",
|
|
199
|
+
` endpoint: "${endpoint}",`,
|
|
200
|
+
` authToken: "${apiKey}",`,
|
|
201
|
+
' sessionId: "<your-session-id>",',
|
|
202
|
+
" });",
|
|
203
|
+
" // session.record(event) for server events; session.end() when the request ends.",
|
|
204
|
+
" 5. Change nothing else, then verify the build still passes."
|
|
205
|
+
);
|
|
206
|
+
} else {
|
|
207
|
+
jsLines.push(
|
|
208
|
+
" 4. Change nothing else, then verify the build still passes."
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return jsLines.join("\n");
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
BACKEND_JS_STACKS,
|
|
216
|
+
INFRA_STACKS,
|
|
217
|
+
JS_STACKS,
|
|
218
|
+
OTLP_CAPABILITY_FACTS,
|
|
219
|
+
OTLP_STACKS,
|
|
220
|
+
allStackInstalls,
|
|
221
|
+
buildAgentPrompt,
|
|
222
|
+
buildBackendJsNote,
|
|
223
|
+
buildOtlpSnippets,
|
|
224
|
+
getInstallVariant,
|
|
225
|
+
otlpAuthHeaderValue,
|
|
226
|
+
otlpBearerHeaderValue
|
|
227
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Stack } from 'crumbtrail-core';
|
|
2
|
+
|
|
3
|
+
type InstallVariantKind = "js" | "otlp" | "infra";
|
|
4
|
+
/** JS/TS stacks that install crumbtrail-core directly. */
|
|
5
|
+
declare const JS_STACKS: readonly Stack[];
|
|
6
|
+
/** JS backends that additionally wire the crumbtrail-node middleware. */
|
|
7
|
+
declare const BACKEND_JS_STACKS: readonly Stack[];
|
|
8
|
+
/** Non-JS backends wired via their existing OpenTelemetry exporter. */
|
|
9
|
+
declare const OTLP_STACKS: readonly Stack[];
|
|
10
|
+
/** Evidence sources that are not yet a first-class ingest target. */
|
|
11
|
+
declare const INFRA_STACKS: readonly Stack[];
|
|
12
|
+
interface StackInstall {
|
|
13
|
+
stack: Stack;
|
|
14
|
+
kind: InstallVariantKind;
|
|
15
|
+
/** True for express/hono/node — they also need the backend middleware note. */
|
|
16
|
+
backendJs: boolean;
|
|
17
|
+
/** True for infra evidence sources — surfaced as "coming soon". */
|
|
18
|
+
comingSoon: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Classify a single stack into its install variant. Total over all 18 stacks. */
|
|
21
|
+
declare function getInstallVariant(stack: Stack): StackInstall;
|
|
22
|
+
/** The classification table for every supported stack (handy for tests/UI). */
|
|
23
|
+
declare function allStackInstalls(): StackInstall[];
|
|
24
|
+
interface EndpointKey {
|
|
25
|
+
/** Live ingest endpoint (the cloud origin / dashboard origin). */
|
|
26
|
+
endpoint: string;
|
|
27
|
+
/** Live ingest key used as the auth token. */
|
|
28
|
+
apiKey: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The crumbtrail-node backend note shown under <InstallSteps/> for the backend-JS
|
|
32
|
+
* stacks. Uses ONLY the real crumbtrail-node exports — no invented names:
|
|
33
|
+
* • Express is the only stack with framework middleware
|
|
34
|
+
* (createCrumbtrailExpressMiddleware / createCrumbtrailExpressErrorMiddleware).
|
|
35
|
+
* • Hono / Node ship no framework middleware, so they open a headless session
|
|
36
|
+
* (startHeadlessSession) and record server-side events against it.
|
|
37
|
+
*/
|
|
38
|
+
declare function buildBackendJsNote(stack: Stack): string;
|
|
39
|
+
/**
|
|
40
|
+
* Single source of truth for what Crumbtrail's OTLP/HTTP receiver accepts. Both
|
|
41
|
+
* `buildOtlpSnippets` (the wizard guidance) and the collector recipes in
|
|
42
|
+
* `packages/node/src/provider-recipes.json` must agree with these facts — a
|
|
43
|
+
* consistency test asserts the two never drift (compression, endpoint path
|
|
44
|
+
* suffix, auth header names).
|
|
45
|
+
*
|
|
46
|
+
* Nothing here is invented: the paths, protocols, auth headers, and session
|
|
47
|
+
* attribute all match the live ingest routes served by packages/node/src/server.ts
|
|
48
|
+
* and by the hosted Crumbtrail cloud.
|
|
49
|
+
*/
|
|
50
|
+
interface OtlpCapabilityFacts {
|
|
51
|
+
/** Signal paths the receiver serves; exporters append these to the endpoint. */
|
|
52
|
+
readonly paths: readonly ["/v1/traces", "/v1/logs"];
|
|
53
|
+
/** OTLP/HTTP wire protocols accepted (both, as of the protobuf+gzip parity CP). */
|
|
54
|
+
readonly protocols: readonly ["http/protobuf", "http/json"];
|
|
55
|
+
/** Auth header names honored equivalently by the receiver. */
|
|
56
|
+
readonly authHeaders: readonly ["X-Crumbtrail-Auth", "Authorization: Bearer"];
|
|
57
|
+
/** Content-Encoding posture: "none" recommended for collectors; gzip accepted. */
|
|
58
|
+
readonly compression: {
|
|
59
|
+
readonly recommended: "none";
|
|
60
|
+
readonly accepted: readonly ["none", "gzip"];
|
|
61
|
+
};
|
|
62
|
+
/** Resource/span attribute that files spans/logs into a Crumbtrail session. */
|
|
63
|
+
readonly sessionAttribute: "crumbtrail.session.id";
|
|
64
|
+
}
|
|
65
|
+
declare const OTLP_CAPABILITY_FACTS: OtlpCapabilityFacts;
|
|
66
|
+
/**
|
|
67
|
+
* The `X-Crumbtrail-Auth=<key>` value for `OTEL_EXPORTER_OTLP_HEADERS`.
|
|
68
|
+
* OTEL parses that env var as comma-separated `name=value` pairs; the value is
|
|
69
|
+
* used verbatim, so a plain key needs no escaping here.
|
|
70
|
+
*/
|
|
71
|
+
declare function otlpAuthHeaderValue(apiKey: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* The Bearer form for `OTEL_EXPORTER_OTLP_HEADERS`. The space between `Bearer`
|
|
74
|
+
* and the token MUST be percent-encoded (`%20`) — an unescaped space breaks
|
|
75
|
+
* OTEL's `name=value` header parsing and silently drops auth. This is the fix
|
|
76
|
+
* for the previously wrong `Authorization=Bearer <key>` guidance.
|
|
77
|
+
*/
|
|
78
|
+
declare function otlpBearerHeaderValue(apiKey: string): string;
|
|
79
|
+
interface OtlpSnippets {
|
|
80
|
+
/** OTLP endpoint + protocol + compression env vars pointed at the cloud origin. */
|
|
81
|
+
env: string;
|
|
82
|
+
/** Auth header carried by the exporter (X-Crumbtrail-Auth or Bearer). */
|
|
83
|
+
authHeader: string;
|
|
84
|
+
/** Resource attribute that files spans/logs into a Crumbtrail session. */
|
|
85
|
+
sessionAttr: string;
|
|
86
|
+
/** Human note about the appended /v1/traces + /v1/logs paths. */
|
|
87
|
+
note: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the OTLP setup snippets for a non-JS backend. Uses ONLY the real,
|
|
91
|
+
* documented names — no invented env vars or headers, and everything is derived
|
|
92
|
+
* from OTLP_CAPABILITY_FACTS so it can never drift from the collector recipes:
|
|
93
|
+
* • OTEL_EXPORTER_OTLP_ENDPOINT → the cloud origin (exporter appends paths)
|
|
94
|
+
* • OTEL_EXPORTER_OTLP_PROTOCOL → http/protobuf or http/json
|
|
95
|
+
* • OTEL_EXPORTER_OTLP_HEADERS → X-Crumbtrail-Auth=<key> (or Bearer%20<key>)
|
|
96
|
+
* • OTEL_EXPORTER_OTLP_COMPRESSION→ none (recommended) — gzip is accepted too
|
|
97
|
+
* • OTEL_RESOURCE_ATTRIBUTES → crumbtrail.session.id=<id>
|
|
98
|
+
* Verified against docs/integrations/* and the ingest routes.
|
|
99
|
+
*/
|
|
100
|
+
declare function buildOtlpSnippets({ endpoint, apiKey, }: EndpointKey): OtlpSnippets;
|
|
101
|
+
/**
|
|
102
|
+
* Build the "Install via AI" agent prompt — a copyable block with the real
|
|
103
|
+
* endpoint + key baked in. It instructs a coding agent to run the correct setup
|
|
104
|
+
* for the stack, initialize with PRESET_PASSIVE (JS), wire backend middleware
|
|
105
|
+
* when applicable, change nothing else, and verify the build.
|
|
106
|
+
*/
|
|
107
|
+
declare function buildAgentPrompt(stack: Stack, keys: EndpointKey): string;
|
|
108
|
+
|
|
109
|
+
export { BACKEND_JS_STACKS, type EndpointKey, INFRA_STACKS, type InstallVariantKind, JS_STACKS, OTLP_CAPABILITY_FACTS, OTLP_STACKS, type OtlpCapabilityFacts, type OtlpSnippets, type StackInstall, allStackInstalls, buildAgentPrompt, buildBackendJsNote, buildOtlpSnippets, getInstallVariant, otlpAuthHeaderValue, otlpBearerHeaderValue };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Stack } from 'crumbtrail-core';
|
|
2
|
+
|
|
3
|
+
type InstallVariantKind = "js" | "otlp" | "infra";
|
|
4
|
+
/** JS/TS stacks that install crumbtrail-core directly. */
|
|
5
|
+
declare const JS_STACKS: readonly Stack[];
|
|
6
|
+
/** JS backends that additionally wire the crumbtrail-node middleware. */
|
|
7
|
+
declare const BACKEND_JS_STACKS: readonly Stack[];
|
|
8
|
+
/** Non-JS backends wired via their existing OpenTelemetry exporter. */
|
|
9
|
+
declare const OTLP_STACKS: readonly Stack[];
|
|
10
|
+
/** Evidence sources that are not yet a first-class ingest target. */
|
|
11
|
+
declare const INFRA_STACKS: readonly Stack[];
|
|
12
|
+
interface StackInstall {
|
|
13
|
+
stack: Stack;
|
|
14
|
+
kind: InstallVariantKind;
|
|
15
|
+
/** True for express/hono/node — they also need the backend middleware note. */
|
|
16
|
+
backendJs: boolean;
|
|
17
|
+
/** True for infra evidence sources — surfaced as "coming soon". */
|
|
18
|
+
comingSoon: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Classify a single stack into its install variant. Total over all 18 stacks. */
|
|
21
|
+
declare function getInstallVariant(stack: Stack): StackInstall;
|
|
22
|
+
/** The classification table for every supported stack (handy for tests/UI). */
|
|
23
|
+
declare function allStackInstalls(): StackInstall[];
|
|
24
|
+
interface EndpointKey {
|
|
25
|
+
/** Live ingest endpoint (the cloud origin / dashboard origin). */
|
|
26
|
+
endpoint: string;
|
|
27
|
+
/** Live ingest key used as the auth token. */
|
|
28
|
+
apiKey: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The crumbtrail-node backend note shown under <InstallSteps/> for the backend-JS
|
|
32
|
+
* stacks. Uses ONLY the real crumbtrail-node exports — no invented names:
|
|
33
|
+
* • Express is the only stack with framework middleware
|
|
34
|
+
* (createCrumbtrailExpressMiddleware / createCrumbtrailExpressErrorMiddleware).
|
|
35
|
+
* • Hono / Node ship no framework middleware, so they open a headless session
|
|
36
|
+
* (startHeadlessSession) and record server-side events against it.
|
|
37
|
+
*/
|
|
38
|
+
declare function buildBackendJsNote(stack: Stack): string;
|
|
39
|
+
/**
|
|
40
|
+
* Single source of truth for what Crumbtrail's OTLP/HTTP receiver accepts. Both
|
|
41
|
+
* `buildOtlpSnippets` (the wizard guidance) and the collector recipes in
|
|
42
|
+
* `packages/node/src/provider-recipes.json` must agree with these facts — a
|
|
43
|
+
* consistency test asserts the two never drift (compression, endpoint path
|
|
44
|
+
* suffix, auth header names).
|
|
45
|
+
*
|
|
46
|
+
* Nothing here is invented: the paths, protocols, auth headers, and session
|
|
47
|
+
* attribute all match the live ingest routes served by packages/node/src/server.ts
|
|
48
|
+
* and by the hosted Crumbtrail cloud.
|
|
49
|
+
*/
|
|
50
|
+
interface OtlpCapabilityFacts {
|
|
51
|
+
/** Signal paths the receiver serves; exporters append these to the endpoint. */
|
|
52
|
+
readonly paths: readonly ["/v1/traces", "/v1/logs"];
|
|
53
|
+
/** OTLP/HTTP wire protocols accepted (both, as of the protobuf+gzip parity CP). */
|
|
54
|
+
readonly protocols: readonly ["http/protobuf", "http/json"];
|
|
55
|
+
/** Auth header names honored equivalently by the receiver. */
|
|
56
|
+
readonly authHeaders: readonly ["X-Crumbtrail-Auth", "Authorization: Bearer"];
|
|
57
|
+
/** Content-Encoding posture: "none" recommended for collectors; gzip accepted. */
|
|
58
|
+
readonly compression: {
|
|
59
|
+
readonly recommended: "none";
|
|
60
|
+
readonly accepted: readonly ["none", "gzip"];
|
|
61
|
+
};
|
|
62
|
+
/** Resource/span attribute that files spans/logs into a Crumbtrail session. */
|
|
63
|
+
readonly sessionAttribute: "crumbtrail.session.id";
|
|
64
|
+
}
|
|
65
|
+
declare const OTLP_CAPABILITY_FACTS: OtlpCapabilityFacts;
|
|
66
|
+
/**
|
|
67
|
+
* The `X-Crumbtrail-Auth=<key>` value for `OTEL_EXPORTER_OTLP_HEADERS`.
|
|
68
|
+
* OTEL parses that env var as comma-separated `name=value` pairs; the value is
|
|
69
|
+
* used verbatim, so a plain key needs no escaping here.
|
|
70
|
+
*/
|
|
71
|
+
declare function otlpAuthHeaderValue(apiKey: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* The Bearer form for `OTEL_EXPORTER_OTLP_HEADERS`. The space between `Bearer`
|
|
74
|
+
* and the token MUST be percent-encoded (`%20`) — an unescaped space breaks
|
|
75
|
+
* OTEL's `name=value` header parsing and silently drops auth. This is the fix
|
|
76
|
+
* for the previously wrong `Authorization=Bearer <key>` guidance.
|
|
77
|
+
*/
|
|
78
|
+
declare function otlpBearerHeaderValue(apiKey: string): string;
|
|
79
|
+
interface OtlpSnippets {
|
|
80
|
+
/** OTLP endpoint + protocol + compression env vars pointed at the cloud origin. */
|
|
81
|
+
env: string;
|
|
82
|
+
/** Auth header carried by the exporter (X-Crumbtrail-Auth or Bearer). */
|
|
83
|
+
authHeader: string;
|
|
84
|
+
/** Resource attribute that files spans/logs into a Crumbtrail session. */
|
|
85
|
+
sessionAttr: string;
|
|
86
|
+
/** Human note about the appended /v1/traces + /v1/logs paths. */
|
|
87
|
+
note: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the OTLP setup snippets for a non-JS backend. Uses ONLY the real,
|
|
91
|
+
* documented names — no invented env vars or headers, and everything is derived
|
|
92
|
+
* from OTLP_CAPABILITY_FACTS so it can never drift from the collector recipes:
|
|
93
|
+
* • OTEL_EXPORTER_OTLP_ENDPOINT → the cloud origin (exporter appends paths)
|
|
94
|
+
* • OTEL_EXPORTER_OTLP_PROTOCOL → http/protobuf or http/json
|
|
95
|
+
* • OTEL_EXPORTER_OTLP_HEADERS → X-Crumbtrail-Auth=<key> (or Bearer%20<key>)
|
|
96
|
+
* • OTEL_EXPORTER_OTLP_COMPRESSION→ none (recommended) — gzip is accepted too
|
|
97
|
+
* • OTEL_RESOURCE_ATTRIBUTES → crumbtrail.session.id=<id>
|
|
98
|
+
* Verified against docs/integrations/* and the ingest routes.
|
|
99
|
+
*/
|
|
100
|
+
declare function buildOtlpSnippets({ endpoint, apiKey, }: EndpointKey): OtlpSnippets;
|
|
101
|
+
/**
|
|
102
|
+
* Build the "Install via AI" agent prompt — a copyable block with the real
|
|
103
|
+
* endpoint + key baked in. It instructs a coding agent to run the correct setup
|
|
104
|
+
* for the stack, initialize with PRESET_PASSIVE (JS), wire backend middleware
|
|
105
|
+
* when applicable, change nothing else, and verify the build.
|
|
106
|
+
*/
|
|
107
|
+
declare function buildAgentPrompt(stack: Stack, keys: EndpointKey): string;
|
|
108
|
+
|
|
109
|
+
export { BACKEND_JS_STACKS, type EndpointKey, INFRA_STACKS, type InstallVariantKind, JS_STACKS, OTLP_CAPABILITY_FACTS, OTLP_STACKS, type OtlpCapabilityFacts, type OtlpSnippets, type StackInstall, allStackInstalls, buildAgentPrompt, buildBackendJsNote, buildOtlpSnippets, getInstallVariant, otlpAuthHeaderValue, otlpBearerHeaderValue };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { STACK_IDS } from "crumbtrail-core";
|
|
3
|
+
var JS_STACKS = [
|
|
4
|
+
"nextjs",
|
|
5
|
+
"react",
|
|
6
|
+
"vue",
|
|
7
|
+
"svelte",
|
|
8
|
+
"vite",
|
|
9
|
+
"express",
|
|
10
|
+
"hono",
|
|
11
|
+
"node"
|
|
12
|
+
];
|
|
13
|
+
var BACKEND_JS_STACKS = ["express", "hono", "node"];
|
|
14
|
+
var OTLP_STACKS = [
|
|
15
|
+
"django",
|
|
16
|
+
"flask",
|
|
17
|
+
"fastapi",
|
|
18
|
+
"dotnet",
|
|
19
|
+
"go",
|
|
20
|
+
"rails"
|
|
21
|
+
];
|
|
22
|
+
var INFRA_STACKS = [
|
|
23
|
+
"postgres",
|
|
24
|
+
"grafana",
|
|
25
|
+
"loki",
|
|
26
|
+
"docker"
|
|
27
|
+
];
|
|
28
|
+
function getInstallVariant(stack) {
|
|
29
|
+
const backendJs = BACKEND_JS_STACKS.includes(stack);
|
|
30
|
+
if (JS_STACKS.includes(stack)) {
|
|
31
|
+
return { stack, kind: "js", backendJs, comingSoon: false };
|
|
32
|
+
}
|
|
33
|
+
if (OTLP_STACKS.includes(stack)) {
|
|
34
|
+
return { stack, kind: "otlp", backendJs: false, comingSoon: false };
|
|
35
|
+
}
|
|
36
|
+
return { stack, kind: "infra", backendJs: false, comingSoon: true };
|
|
37
|
+
}
|
|
38
|
+
function allStackInstalls() {
|
|
39
|
+
return STACK_IDS.map(getInstallVariant);
|
|
40
|
+
}
|
|
41
|
+
function buildBackendJsNote(stack) {
|
|
42
|
+
if (stack === "express") {
|
|
43
|
+
return [
|
|
44
|
+
"// Backend (Express) \u2014 also capture server-side errors and requests.",
|
|
45
|
+
"// Reuse the same ingest endpoint + key you set on the SDK above.",
|
|
46
|
+
"import {",
|
|
47
|
+
" createCrumbtrailExpressMiddleware,",
|
|
48
|
+
" createCrumbtrailExpressErrorMiddleware,",
|
|
49
|
+
'} from "crumbtrail-node";',
|
|
50
|
+
"",
|
|
51
|
+
"const crumbtrailOptions = { endpoint: CRUMBTRAIL_ENDPOINT, authToken: CRUMBTRAIL_KEY };",
|
|
52
|
+
"app.use(createCrumbtrailExpressMiddleware(crumbtrailOptions)); // before your routes",
|
|
53
|
+
"app.use(createCrumbtrailExpressErrorMiddleware(crumbtrailOptions)); // after your routes",
|
|
54
|
+
"",
|
|
55
|
+
"// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
"// Backend (Hono / Node) \u2014 crumbtrail-node ships no framework middleware here.",
|
|
60
|
+
"// Open a headless session and record server-side events against it:",
|
|
61
|
+
'import { startHeadlessSession } from "crumbtrail-node";',
|
|
62
|
+
"",
|
|
63
|
+
"const session = await startHeadlessSession({",
|
|
64
|
+
" endpoint: CRUMBTRAIL_ENDPOINT,",
|
|
65
|
+
" authToken: CRUMBTRAIL_KEY,",
|
|
66
|
+
' sessionId: "<your-session-id>",',
|
|
67
|
+
"});",
|
|
68
|
+
"// session.record(event) to attach server events; session.end() when done.",
|
|
69
|
+
"",
|
|
70
|
+
"// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
73
|
+
var OTLP_CAPABILITY_FACTS = {
|
|
74
|
+
paths: ["/v1/traces", "/v1/logs"],
|
|
75
|
+
protocols: ["http/protobuf", "http/json"],
|
|
76
|
+
authHeaders: ["X-Crumbtrail-Auth", "Authorization: Bearer"],
|
|
77
|
+
compression: { recommended: "none", accepted: ["none", "gzip"] },
|
|
78
|
+
sessionAttribute: "crumbtrail.session.id"
|
|
79
|
+
};
|
|
80
|
+
function otlpAuthHeaderValue(apiKey) {
|
|
81
|
+
return `X-Crumbtrail-Auth=${apiKey}`;
|
|
82
|
+
}
|
|
83
|
+
function otlpBearerHeaderValue(apiKey) {
|
|
84
|
+
return `Authorization=Bearer%20${apiKey}`;
|
|
85
|
+
}
|
|
86
|
+
function buildOtlpSnippets({
|
|
87
|
+
endpoint,
|
|
88
|
+
apiKey
|
|
89
|
+
}) {
|
|
90
|
+
const [protobuf, jsonProtocol] = OTLP_CAPABILITY_FACTS.protocols;
|
|
91
|
+
return {
|
|
92
|
+
env: [
|
|
93
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint}`,
|
|
94
|
+
`OTEL_EXPORTER_OTLP_PROTOCOL=${protobuf} # or ${jsonProtocol}`,
|
|
95
|
+
`OTEL_EXPORTER_OTLP_COMPRESSION=${OTLP_CAPABILITY_FACTS.compression.recommended} # recommended; gzip is also accepted`
|
|
96
|
+
].join("\n"),
|
|
97
|
+
authHeader: [
|
|
98
|
+
`OTEL_EXPORTER_OTLP_HEADERS=${otlpAuthHeaderValue(apiKey)}`,
|
|
99
|
+
`# Or, if your exporter sends a Bearer token (note the %20-escaped space):`,
|
|
100
|
+
`# OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeaderValue(apiKey)}`
|
|
101
|
+
].join("\n"),
|
|
102
|
+
sessionAttr: `OTEL_RESOURCE_ATTRIBUTES=${OTLP_CAPABILITY_FACTS.sessionAttribute}=<your-session-id>`,
|
|
103
|
+
note: `Crumbtrail's OTLP receiver appends ${OTLP_CAPABILITY_FACTS.paths.join(" and ")} to the endpoint above \u2014 don't include those paths yourself. Set the ${OTLP_CAPABILITY_FACTS.sessionAttribute} resource attribute to join backend spans to the frontend session.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function buildAgentPrompt(stack, keys) {
|
|
107
|
+
const { kind, backendJs } = getInstallVariant(stack);
|
|
108
|
+
const { endpoint, apiKey } = keys;
|
|
109
|
+
const header = [
|
|
110
|
+
"You are setting up Crumbtrail in this project. Make ONLY the changes below,",
|
|
111
|
+
"do not refactor or touch anything else, then verify the build still passes.",
|
|
112
|
+
"",
|
|
113
|
+
`Ingest endpoint: ${endpoint}`,
|
|
114
|
+
`Ingest key: ${apiKey}`,
|
|
115
|
+
""
|
|
116
|
+
];
|
|
117
|
+
if (kind === "otlp") {
|
|
118
|
+
return [
|
|
119
|
+
...header,
|
|
120
|
+
"This is a non-JS backend that already uses OpenTelemetry. Do NOT install a",
|
|
121
|
+
"second SDK. Instead, add Crumbtrail as an additional OTLP/HTTP exporter:",
|
|
122
|
+
` 1. Set OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint} (the exporter appends`,
|
|
123
|
+
" /v1/traces and /v1/logs \u2014 do not add those paths).",
|
|
124
|
+
` 2. Send the auth header X-Crumbtrail-Auth: ${apiKey} on every export`,
|
|
125
|
+
" (or Authorization: Bearer <key> if your exporter prefers that).",
|
|
126
|
+
" 3. Stamp the resource attribute crumbtrail.session.id so spans/logs join",
|
|
127
|
+
" the right session.",
|
|
128
|
+
" 4. Keep your existing exporter \u2014 add Crumbtrail alongside it.",
|
|
129
|
+
" 5. Verify the app still builds and starts."
|
|
130
|
+
].join("\n");
|
|
131
|
+
}
|
|
132
|
+
const jsLines = [
|
|
133
|
+
...header,
|
|
134
|
+
"This is a JavaScript/TypeScript project. Do the following:",
|
|
135
|
+
" 1. Install the SDK: npm install crumbtrail-core",
|
|
136
|
+
' 2. Import the SDK: import { Crumbtrail, PRESET_PASSIVE } from "crumbtrail-core";',
|
|
137
|
+
" 3. Initialize once at the app entry point with PRESET_PASSIVE:",
|
|
138
|
+
" Crumbtrail.init({",
|
|
139
|
+
" ...PRESET_PASSIVE,",
|
|
140
|
+
` httpEndpoint: "${endpoint}",`,
|
|
141
|
+
` httpAuthToken: "${apiKey}",`,
|
|
142
|
+
" });"
|
|
143
|
+
];
|
|
144
|
+
if (backendJs && stack === "express") {
|
|
145
|
+
jsLines.push(
|
|
146
|
+
" 4. This is an Express backend \u2014 also install crumbtrail-node and capture",
|
|
147
|
+
" server errors and requests with its Express middleware:",
|
|
148
|
+
" import {",
|
|
149
|
+
" createCrumbtrailExpressMiddleware,",
|
|
150
|
+
" createCrumbtrailExpressErrorMiddleware,",
|
|
151
|
+
' } from "crumbtrail-node";',
|
|
152
|
+
` const opts = { endpoint: "${endpoint}", authToken: "${apiKey}" };`,
|
|
153
|
+
" app.use(createCrumbtrailExpressMiddleware(opts)); // before your routes",
|
|
154
|
+
" app.use(createCrumbtrailExpressErrorMiddleware(opts)); // after your routes",
|
|
155
|
+
" 5. Change nothing else, then verify the build still passes."
|
|
156
|
+
);
|
|
157
|
+
} else if (backendJs) {
|
|
158
|
+
jsLines.push(
|
|
159
|
+
" 4. This is also a backend. Install crumbtrail-node; it ships no framework",
|
|
160
|
+
" middleware for this stack, so open a headless session and record",
|
|
161
|
+
" server-side events against it:",
|
|
162
|
+
' import { startHeadlessSession } from "crumbtrail-node";',
|
|
163
|
+
" const session = await startHeadlessSession({",
|
|
164
|
+
` endpoint: "${endpoint}",`,
|
|
165
|
+
` authToken: "${apiKey}",`,
|
|
166
|
+
' sessionId: "<your-session-id>",',
|
|
167
|
+
" });",
|
|
168
|
+
" // session.record(event) for server events; session.end() when the request ends.",
|
|
169
|
+
" 5. Change nothing else, then verify the build still passes."
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
jsLines.push(
|
|
173
|
+
" 4. Change nothing else, then verify the build still passes."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return jsLines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
BACKEND_JS_STACKS,
|
|
180
|
+
INFRA_STACKS,
|
|
181
|
+
JS_STACKS,
|
|
182
|
+
OTLP_CAPABILITY_FACTS,
|
|
183
|
+
OTLP_STACKS,
|
|
184
|
+
allStackInstalls,
|
|
185
|
+
buildAgentPrompt,
|
|
186
|
+
buildBackendJsNote,
|
|
187
|
+
buildOtlpSnippets,
|
|
188
|
+
getInstallVariant,
|
|
189
|
+
otlpAuthHeaderValue,
|
|
190
|
+
otlpBearerHeaderValue
|
|
191
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crumbtrail-install-shared",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"crumbtrail-core": "^0.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^19.0.0",
|
|
23
|
+
"react": "^19.0.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"homepage": "https://github.com/crumbtrail-dev/crumbtrail-js/tree/main/packages/install-shared#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/crumbtrail-dev/crumbtrail-js/issues"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/crumbtrail-dev/crumbtrail-js.git",
|
|
33
|
+
"directory": "packages/install-shared"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
44
|
+
"clean": "rm -rf dist"
|
|
45
|
+
}
|
|
46
|
+
}
|