@glasstrace/sdk 0.14.1 → 0.15.1

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.
Files changed (40) hide show
  1. package/dist/{chunk-ERGEG4ZQ.js → chunk-2LDBR3F3.js} +16 -3
  2. package/dist/chunk-2LDBR3F3.js.map +1 -0
  3. package/dist/chunk-A2AZL6MZ.js +309 -0
  4. package/dist/chunk-A2AZL6MZ.js.map +1 -0
  5. package/dist/{chunk-ARAOZCZT.js → chunk-ROFOJQWN.js} +118 -16
  6. package/dist/chunk-ROFOJQWN.js.map +1 -0
  7. package/dist/{chunk-WV3NIPWJ.js → chunk-ZNOD6FC7.js} +18 -276
  8. package/dist/chunk-ZNOD6FC7.js.map +1 -0
  9. package/dist/cli/init.cjs +458 -115
  10. package/dist/cli/init.cjs.map +1 -1
  11. package/dist/cli/init.d.cts +33 -1
  12. package/dist/cli/init.d.ts +33 -1
  13. package/dist/cli/init.js +144 -42
  14. package/dist/cli/init.js.map +1 -1
  15. package/dist/cli/mcp-add.cjs.map +1 -1
  16. package/dist/cli/mcp-add.js +4 -2
  17. package/dist/cli/mcp-add.js.map +1 -1
  18. package/dist/cli/uninit.cjs +181 -60
  19. package/dist/cli/uninit.cjs.map +1 -1
  20. package/dist/cli/uninit.d.cts +38 -8
  21. package/dist/cli/uninit.d.ts +38 -8
  22. package/dist/cli/uninit.js +6 -3
  23. package/dist/cli/validate.cjs +135 -0
  24. package/dist/cli/validate.cjs.map +1 -0
  25. package/dist/cli/validate.d.cts +60 -0
  26. package/dist/cli/validate.d.ts +60 -0
  27. package/dist/cli/validate.js +103 -0
  28. package/dist/cli/validate.js.map +1 -0
  29. package/dist/index.cjs +123 -47
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +45 -5
  32. package/dist/index.d.ts +45 -5
  33. package/dist/index.js +109 -46
  34. package/dist/index.js.map +1 -1
  35. package/dist/{source-map-uploader-W6VPGY26.js → source-map-uploader-3GWUQDTS.js} +6 -2
  36. package/package.json +6 -4
  37. package/dist/chunk-ARAOZCZT.js.map +0 -1
  38. package/dist/chunk-ERGEG4ZQ.js.map +0 -1
  39. package/dist/chunk-WV3NIPWJ.js.map +0 -1
  40. /package/dist/{source-map-uploader-W6VPGY26.js.map → source-map-uploader-3GWUQDTS.js.map} +0 -0
@@ -335,10 +335,21 @@ async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
335
335
  const json = await response.json();
336
336
  return PresignedUploadResponseSchema.parse(json);
337
337
  }
338
+ async function defaultBlobClientLoader() {
339
+ const dynamicImport = Function("id", "return import(id)");
340
+ return dynamicImport("@vercel/blob/client");
341
+ }
342
+ var _blobClientLoader = defaultBlobClientLoader;
343
+ function _setBlobClientLoaderForTesting(loader) {
344
+ _blobClientLoader = loader;
345
+ }
346
+ function _resetBlobClientLoaderForTesting() {
347
+ _blobClientLoader = defaultBlobClientLoader;
348
+ }
338
349
  async function uploadToBlob(clientToken, pathname, content) {
339
350
  let mod;
340
351
  try {
341
- mod = await import("@vercel/blob/client");
352
+ mod = await _blobClientLoader();
342
353
  } catch (err) {
343
354
  const code = err.code;
344
355
  if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
@@ -451,7 +462,7 @@ async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options)
451
462
  }
452
463
  const checkAvailable = options?.checkBlobAvailable ?? (async () => {
453
464
  try {
454
- await import("@vercel/blob/client");
465
+ await _blobClientLoader();
455
466
  return true;
456
467
  } catch {
457
468
  return false;
@@ -488,9 +499,11 @@ export {
488
499
  uploadSourceMaps,
489
500
  PRESIGNED_THRESHOLD_BYTES,
490
501
  requestPresignedTokens,
502
+ _setBlobClientLoaderForTesting,
503
+ _resetBlobClientLoaderForTesting,
491
504
  uploadToBlob,
492
505
  submitManifest,
493
506
  uploadSourceMapsPresigned,
494
507
  uploadSourceMapsAuto
495
508
  };
496
- //# sourceMappingURL=chunk-ERGEG4ZQ.js.map
509
+ //# sourceMappingURL=chunk-2LDBR3F3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/source-map-uploader.ts","../src/console-capture.ts","../src/nudge/error-nudge.ts","../src/env-detection.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { sdkLog } from \"./console-capture.js\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n PresignedUploadResponseSchema,\n type PresignedUploadResponse,\n SourceMapManifestResponseSchema,\n type SourceMapManifestResponse,\n} from \"@glasstrace/protocol\";\n\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Metadata for a discovered source map file, without its content loaded.\n * Used by the streaming upload flow to defer file reads until upload time.\n */\nexport interface SourceMapFileInfo {\n /** Relative path to the compiled JS file (`.map` extension stripped). */\n filePath: string;\n /** Absolute path on disk for reading the file content on demand. */\n absolutePath: string;\n /** File size in bytes. */\n sizeBytes: number;\n}\n\n/** Threshold (50 MB) above which a single source map triggers a warning. */\nconst LARGE_FILE_WARNING_BYTES = 50 * 1024 * 1024;\n\n/**\n * Recursively discovers all `.map` files in the given build directory.\n * Returns metadata only — file content is NOT read into memory.\n */\nexport async function discoverSourceMapFiles(\n buildDir: string,\n): Promise<SourceMapFileInfo[]> {\n // Resolve to absolute so absolutePath in results is always absolute,\n // even when buildDir is relative (e.g. \".next\").\n const resolvedDir = path.resolve(buildDir);\n const results: SourceMapFileInfo[] = [];\n\n try {\n await walkDirMetadata(resolvedDir, resolvedDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n // Warn about oversized source map files\n for (const file of results) {\n if (file.sizeBytes >= LARGE_FILE_WARNING_BYTES) {\n const sizeMB = (file.sizeBytes / (1024 * 1024)).toFixed(1);\n sdkLog(\n \"warn\",\n `[glasstrace] Large source map detected: ${file.filePath} (${sizeMB}MB). Consider enabling source map compression.`,\n );\n }\n }\n\n return results;\n}\n\nasync function walkDirMetadata(\n baseDir: string,\n currentDir: string,\n results: SourceMapFileInfo[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDirMetadata(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const stat = await fs.stat(fullPath);\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({\n filePath: compiledPath,\n absolutePath: fullPath,\n sizeBytes: stat.size,\n });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Reads the content of a single source map file from disk.\n */\nasync function readSourceMapContent(absolutePath: string): Promise<string> {\n return fs.readFile(absolutePath, \"utf-8\");\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n *\n * @deprecated Prefer {@link discoverSourceMapFiles} to avoid loading all\n * source maps into memory simultaneously.\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const fileInfos = await discoverSourceMapFiles(buildDir);\n const results: SourceMapEntry[] = [];\n\n for (const info of fileInfos) {\n try {\n const content = await readSourceMapContent(info.absolutePath);\n results.push({ filePath: info.filePath, content });\n } catch {\n // Skip unreadable files — consistent with previous behavior\n }\n }\n\n return results;\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execFileSync(\"git\", [\"rev-parse\", \"HEAD\"], { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hash = crypto.createHash(\"sha256\");\n\n for (const m of sortedMaps) {\n let content: string;\n if (\"content\" in m) {\n content = m.content;\n } else {\n try {\n content = await readSourceMapContent(m.absolutePath);\n } catch {\n // Skip unreadable files — consistent with collectSourceMaps behavior\n continue;\n }\n }\n hash.update(`${m.filePath}\\n${content.length}\\n${content}`);\n }\n\n return hash.digest(\"hex\");\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (deferred reads). With `SourceMapFileInfo[]`,\n * file content is read at upload time rather than at discovery time.\n * Note: the legacy endpoint sends all files in a single JSON body, so\n * peak memory is similar — the benefit is deferring reads past discovery.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<SourceMapUploadResponse> {\n // Read files on demand — for SourceMapEntry[], content is already available;\n // for SourceMapFileInfo[], each file is read individually to limit memory.\n // Individual reads are guarded so one transient failure (deleted file,\n // permission change) does not abort the entire upload.\n const files: Array<{ filePath: string; sourceMap: string }> = [];\n for (const m of maps) {\n try {\n const content = \"content\" in m\n ? m.content\n : await readSourceMapContent(m.absolutePath);\n files.push({ filePath: m.filePath, sourceMap: content });\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${m.filePath}`);\n }\n }\n\n const body = {\n buildHash,\n files,\n };\n\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n\n// ---------------------------------------------------------------------------\n// Presigned source map upload (3-phase flow for large builds)\n// ---------------------------------------------------------------------------\n\n/** Builds at or above this byte size route to the presigned upload flow. */\nexport const PRESIGNED_THRESHOLD_BYTES = 4_500_000;\n\n/** Signature for the blob upload function, injectable for testing. */\nexport type BlobUploader = (\n clientToken: string,\n pathname: string,\n content: string,\n) => Promise<{ url: string; size: number }>;\n\n/**\n * Strips trailing slashes from a URL string.\n * Uses an iterative approach to avoid regex (CodeQL js/polynomial-redos).\n */\nfunction stripTrailingSlashes(url: string): string {\n let result = url;\n while (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n return result;\n}\n\n/**\n * Phase 1: Request presigned upload tokens from the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps/presign` with the build hash and\n * file metadata. Returns presigned tokens for each file that the client\n * uses to upload directly to blob storage.\n */\nexport async function requestPresignedTokens(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number }>,\n): Promise<PresignedUploadResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Presigned token request failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return PresignedUploadResponseSchema.parse(json);\n}\n\n/** Shape of the subset of `@vercel/blob/client` the SDK consumes. */\ntype BlobClientModule = {\n put: (\n pathname: string,\n body: Blob,\n options: { access: string; token: string },\n ) => Promise<{ url: string }>;\n};\n\n/**\n * Loads `@vercel/blob/client` at runtime using the `Function()`-based\n * dynamic-import evasion trick.\n *\n * The indirection hides the specifier from static bundler analysis — webpack,\n * tsup, esbuild, and rollup all resolve literal `await import(\"...\")` targets\n * at build time and would raise `Module not found` for consumers without\n * `@vercel/blob` installed (it is an optional peer dependency). See DISC-1255.\n *\n * **CSP note:** `Function()` is semantically equivalent to `eval()` and will\n * trigger `unsafe-eval` CSP violations in restricted environments. Source-map\n * upload only runs in Node.js at build time, so CSP does not apply here. The\n * same caveat documented on `tryImport` in `otel-config.ts` applies if this\n * code is ever re-used in a browser-equivalent runtime.\n *\n * Exported for internal test injection via {@link _setBlobClientLoaderForTesting}\n * only; not part of the public API.\n */\nasync function defaultBlobClientLoader(): Promise<BlobClientModule> {\n const dynamicImport = Function(\"id\", \"return import(id)\") as (\n id: string,\n ) => Promise<BlobClientModule>;\n return dynamicImport(\"@vercel/blob/client\");\n}\n\nlet _blobClientLoader: () => Promise<BlobClientModule> = defaultBlobClientLoader;\n\n/**\n * Replaces the blob client loader. For test use only.\n *\n * The production loader uses a `Function()`-based dynamic import to evade\n * static bundler analysis (DISC-1255), which in turn bypasses Vitest's\n * module-mock interceptor. Tests that need to stub `@vercel/blob/client`\n * call this helper to install a fake loader, then restore the default\n * with {@link _resetBlobClientLoaderForTesting}.\n *\n * @internal\n */\nexport function _setBlobClientLoaderForTesting(\n loader: () => Promise<BlobClientModule>,\n): void {\n _blobClientLoader = loader;\n}\n\n/**\n * Restores the default `@vercel/blob/client` loader. For test use only.\n *\n * @internal\n */\nexport function _resetBlobClientLoaderForTesting(): void {\n _blobClientLoader = defaultBlobClientLoader;\n}\n\n/**\n * Phase 2: Upload a single source map to blob storage using a presigned token.\n *\n * Dynamically imports `@vercel/blob/client` via {@link defaultBlobClientLoader}\n * to avoid bundling the optional peer dependency. Throws a descriptive error\n * if the package is not installed.\n */\nexport async function uploadToBlob(\n clientToken: string,\n pathname: string,\n content: string,\n): Promise<{ url: string; size: number }> {\n let mod: BlobClientModule;\n try {\n mod = await _blobClientLoader();\n } catch (err) {\n // Distinguish \"not installed\" from other import errors\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") {\n throw new Error(\n \"Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob\",\n );\n }\n throw err;\n }\n\n const result = await mod.put(pathname, new Blob([content]), {\n access: \"public\",\n token: clientToken,\n });\n\n return { url: result.url, size: Buffer.byteLength(content, \"utf-8\") };\n}\n\n/**\n * Phase 3: Submit the upload manifest to finalize a presigned upload.\n *\n * POSTs to `{endpoint}/v1/source-maps/manifest` with the upload ID,\n * build hash, and blob URLs for each uploaded file. The backend activates\n * the source maps for stack trace resolution.\n */\nexport async function submitManifest(\n apiKey: string,\n endpoint: string,\n uploadId: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number; blobUrl: string }>,\n): Promise<SourceMapManifestResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ uploadId, buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapManifestResponseSchema.parse(json);\n}\n\n/**\n * Orchestrates the 3-phase presigned upload flow.\n *\n * 1. Requests presigned tokens for all source map files\n * 2. Uploads each file to blob storage with a concurrency limit of 5,\n * reading file content from disk just before each upload\n * 3. Submits the manifest to finalize the upload\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n *\n * Accepts an optional `blobUploader` for test injection; defaults to\n * {@link uploadToBlob}.\n */\nexport async function uploadSourceMapsPresigned(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n blobUploader: BlobUploader = uploadToBlob,\n): Promise<SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n // Determine file metadata for the presign request\n const fileMetadata = maps.map((m) => ({\n filePath: m.filePath,\n sizeBytes: \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes,\n }));\n\n // Phase 1: request presigned tokens\n const presigned = await requestPresignedTokens(\n apiKey, endpoint, buildHash, fileMetadata,\n );\n\n // Build a lookup map for O(1) access by filePath\n const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));\n\n if (mapsByPath.size !== maps.length) {\n throw new Error(\"Duplicate filePath entries in source maps\");\n }\n\n // Validate all tokens have matching entries before starting any uploads.\n for (const token of presigned.files) {\n if (!mapsByPath.has(token.filePath)) {\n throw new Error(\n `Presigned token for \"${token.filePath}\" has no matching source map entry`,\n );\n }\n }\n\n // Phase 2: upload to blob storage in chunks of CONCURRENCY.\n // File content is read from disk just before each upload to avoid\n // holding all source maps in memory simultaneously.\n const CONCURRENCY = 5;\n const uploadResults: Array<{ filePath: string; sizeBytes: number; blobUrl: string }> = [];\n\n for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {\n const chunk = presigned.files.slice(i, i + CONCURRENCY);\n const chunkResults = await Promise.all(\n chunk.map(async (token) => {\n const entry = mapsByPath.get(token.filePath)!;\n let content: string;\n if (\"content\" in entry) {\n content = entry.content;\n } else {\n try {\n content = await readSourceMapContent(entry.absolutePath);\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${token.filePath}`);\n return null;\n }\n }\n const result = await blobUploader(token.clientToken, token.pathname, content);\n return {\n filePath: token.filePath,\n sizeBytes: result.size,\n blobUrl: result.url,\n };\n }),\n );\n for (const r of chunkResults) {\n if (r !== null) {\n uploadResults.push(r);\n }\n }\n }\n\n // Phase 3: submit manifest\n return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);\n}\n\n/**\n * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.\n */\nexport interface AutoUploadOptions {\n /** Override blob availability check (for testing). */\n checkBlobAvailable?: () => Promise<boolean>;\n /** Override blob uploader (for testing). */\n blobUploader?: BlobUploader;\n}\n\n/**\n * Automatically routes source map uploads based on total build size.\n *\n * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request\n * {@link uploadSourceMaps} endpoint.\n * - At or above the threshold: checks if `@vercel/blob` is available and\n * uses the presigned 3-phase flow. Falls back to legacy with a warning\n * if the package is not installed.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n */\nexport async function uploadSourceMapsAuto(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n options?: AutoUploadOptions,\n): Promise<SourceMapUploadResponse | SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n const totalBytes = maps.reduce(\n (sum, m) => {\n const bytes = \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes;\n return sum + bytes;\n },\n 0,\n );\n\n if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n }\n\n // Check if @vercel/blob is available. Uses the shared blob client loader\n // (which goes through the `Function()` evasion trick) so webpack / tsup /\n // esbuild do not resolve `@vercel/blob/client` at build time (DISC-1255).\n const checkAvailable = options?.checkBlobAvailable ?? (async () => {\n try {\n await _blobClientLoader();\n return true;\n } catch {\n return false;\n }\n });\n\n const blobAvailable = await checkAvailable();\n\n if (blobAvailable) {\n return uploadSourceMapsPresigned(\n apiKey, endpoint, buildHash, maps, options?.blobUploader,\n );\n }\n\n // Fall back to legacy upload with a warning\n sdkLog(\"warn\",\n `[glasstrace] Build exceeds 4.5MB (${String(totalBytes)} bytes). Install @vercel/blob for ` +\n `presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`\n );\n\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n}\n","/**\n * Console error/warn capture module.\n *\n * When enabled, monkey-patches `console.error` and `console.warn` to record\n * their output as OTel span events on the currently active span. SDK-internal\n * log messages (prefixed with \"[glasstrace]\") are never captured.\n */\n\nimport { maybeShowMcpNudge } from \"./nudge/error-nudge.js\";\n\n/**\n * Module-level flag to suppress capture of SDK-internal log messages.\n * Set to `true` before calling `console.warn`/`console.error` from SDK code,\n * then reset to `false` immediately after.\n */\nexport let isGlasstraceLog = false;\n\n/** Saved reference to the original `console.error`. */\nlet originalError: typeof console.error | null = null;\n\n/** Saved reference to the original `console.warn`. */\nlet originalWarn: typeof console.warn | null = null;\n\n/** Whether the console capture is currently installed. */\nlet installed = false;\n\n/** Cached OTel API module reference, resolved at install time. */\nlet otelApi: typeof import(\"@opentelemetry/api\") | null = null;\n\n/**\n * Formats console arguments into a single string for span event attributes.\n * Uses best-effort serialization: strings pass through, Errors preserve their\n * stack trace, and other values are JSON-stringified with a String() fallback.\n */\nfunction formatArgs(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.stack ?? arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n })\n .join(\" \");\n}\n\n/**\n * Returns `true` if the first argument is a string starting with \"[glasstrace]\".\n * Used to skip capture of SDK-internal log messages without requiring every\n * call site to set the `isGlasstraceLog` flag.\n */\nfunction isSdkMessage(args: unknown[]): boolean {\n return typeof args[0] === \"string\" && args[0].startsWith(\"[glasstrace]\");\n}\n\n/**\n * Installs console capture by replacing `console.error` and `console.warn`\n * with wrappers that record span events on the active OTel span.\n *\n * Must be called after OTel is configured so the API module is available.\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport async function installConsoleCapture(): Promise<void> {\n if (installed) return;\n\n // Resolve OTel API at install time via dynamic import so that:\n // 1. tsup inlines @opentelemetry/api into the bundle (it's in noExternal)\n // 2. vitest's vi.doMock can intercept this import for testing\n try {\n otelApi = await import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n\n originalError = console.error;\n originalWarn = console.warn;\n installed = true;\n\n console.error = (...args: unknown[]) => {\n // Always call the original first to preserve developer experience\n originalError!.apply(console, args);\n\n // Skip SDK-internal messages and flagged messages\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n const formattedMessage = formatArgs(args);\n span.addEvent(\"console.error\", {\n \"console.message\": formattedMessage,\n });\n // Show one-time MCP connection nudge on first captured error\n try {\n maybeShowMcpNudge(formattedMessage);\n } catch {\n // Never allow the monkey-patched console.error wrapper to throw\n }\n }\n }\n };\n\n console.warn = (...args: unknown[]) => {\n originalWarn!.apply(console, args);\n\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n span.addEvent(\"console.warn\", {\n \"console.message\": formatArgs(args),\n });\n }\n }\n };\n}\n\n/**\n * Restores the original `console.error` and `console.warn` methods.\n * Primarily intended for use in tests.\n */\nexport function uninstallConsoleCapture(): void {\n if (!installed) return;\n\n if (originalError) console.error = originalError;\n if (originalWarn) console.warn = originalWarn;\n\n originalError = null;\n originalWarn = null;\n otelApi = null;\n installed = false;\n}\n\n/**\n * Logs a message from SDK-internal code without triggering console capture.\n *\n * Use this helper in new SDK code instead of bare `console.warn(...)` calls\n * to prevent SDK log messages from being recorded as user-facing span events.\n *\n * @param level - The console log level to use.\n * @param message - The message to log.\n */\nexport function sdkLog(level: \"warn\" | \"info\" | \"error\", message: string): void {\n isGlasstraceLog = true;\n try {\n console[level](message);\n } finally {\n isGlasstraceLog = false;\n }\n}\n","import { resolveConfig, isProductionDisabled } from \"../env-detection.js\";\n\n/**\n * Module-level flag ensuring the nudge fires at most once per process.\n */\nlet hasFired = false;\n\n/**\n * Strips control characters (except space) from a string to prevent\n * terminal escape sequence injection via error summaries written to stderr.\n */\nfunction sanitize(input: string): string {\n // eslint-disable-next-line no-control-regex\n return input.replace(/[\\x00-\\x1f\\x7f]/g, \"\");\n}\n\n/**\n * Checks whether the MCP marker file exists using synchronous filesystem\n * APIs. Returns `false` when `node:fs` or `node:path` cannot be resolved\n * (non-Node environments) or on any I/O error.\n */\nfunction markerFileExists(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const path = require(\"node:path\") as typeof import(\"node:path\");\n const markerPath = path.join(process.cwd(), \".glasstrace\", \"mcp-connected\");\n return fs.existsSync(markerPath);\n } catch {\n // node:fs/node:path unavailable, permission denied, ENOENT from\n // cwd(), or other error — treat as not connected\n return false;\n }\n}\n\n/**\n * Shows a one-time stderr nudge when the SDK captures its first error\n * and the MCP connection marker file is absent.\n *\n * The nudge is suppressed when:\n * - It has already fired in this process\n * - The `.glasstrace/mcp-connected` marker file exists at the project root\n * - The environment is detected as production (and force-enable is off)\n *\n * Uses `process.stderr.write()` instead of `console.error()` to avoid\n * being captured by OpenTelemetry console instrumentation.\n */\nexport function maybeShowMcpNudge(errorSummary: string): void {\n if (hasFired) {\n return;\n }\n\n // Production check — suppress silently, but remember the decision\n // so subsequent calls fast-exit via hasFired without re-running I/O.\n const config = resolveConfig();\n if (isProductionDisabled(config)) {\n hasFired = true;\n return;\n }\n\n // Check for MCP connection marker file.\n if (markerFileExists()) {\n hasFired = true;\n return;\n }\n\n // Fire the nudge exactly once\n hasFired = true;\n\n const safe = sanitize(errorSummary);\n process.stderr.write(\n `[glasstrace] Error captured: ${safe}\\n` +\n ` Debug with AI: ask your agent \"What's the latest Glasstrace error?\"\\n` +\n ` Not connected? Run: npx glasstrace mcp add\\n`,\n );\n}\n","import type { GlasstraceEnvVars, GlasstraceOptions } from \"@glasstrace/protocol\";\n\n/**\n * Resolved configuration after merging explicit options with environment variables.\n */\nexport interface ResolvedConfig {\n apiKey: string | undefined;\n endpoint: string;\n forceEnable: boolean;\n verbose: boolean;\n environment: string | undefined;\n coverageMapEnabled: boolean;\n nodeEnv: string | undefined;\n vercelEnv: string | undefined;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.glasstrace.dev\";\n\n/**\n * Reads all recognized Glasstrace environment variables from process.env.\n * Returns undefined for any variable not set. Never throws.\n */\nexport function readEnvVars(): GlasstraceEnvVars {\n return {\n GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || undefined,\n GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,\n GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,\n GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,\n NODE_ENV: process.env.NODE_ENV,\n VERCEL_ENV: process.env.VERCEL_ENV,\n };\n}\n\n/**\n * Merges explicit GlasstraceOptions with environment variables.\n * Explicit options take precedence over environment variables.\n */\nexport function resolveConfig(options?: GlasstraceOptions): ResolvedConfig {\n const env = readEnvVars();\n\n return {\n apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,\n endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,\n forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === \"true\",\n verbose: options?.verbose ?? false,\n environment: env.GLASSTRACE_ENV,\n coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === \"true\",\n nodeEnv: env.NODE_ENV,\n vercelEnv: env.VERCEL_ENV,\n };\n}\n\n/**\n * Returns true when the SDK should be inactive (production detected without force-enable).\n * Logic order:\n * 1. forceEnable === true → return false (override)\n * 2. NODE_ENV === 'production' → return true\n * 3. VERCEL_ENV === 'production' → return true\n * 4. Otherwise → return false\n */\nexport function isProductionDisabled(config: ResolvedConfig): boolean {\n if (config.forceEnable) {\n return false;\n }\n if (config.nodeEnv === \"production\") {\n return true;\n }\n if (config.vercelEnv === \"production\") {\n return true;\n }\n return false;\n}\n\n/**\n * Returns true when no API key is configured (anonymous mode).\n * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.\n */\nexport function isAnonymousMode(config: ResolvedConfig): boolean {\n if (config.apiKey === undefined) {\n return true;\n }\n if (config.apiKey.trim() === \"\") {\n return true;\n }\n if (config.apiKey.startsWith(\"gt_anon_\")) {\n return true;\n }\n return false;\n}\n"],"mappings":"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","names":["fs","path","stat"]}
@@ -0,0 +1,309 @@
1
+ import {
2
+ NEXT_CONFIG_NAMES
3
+ } from "./chunk-BL3YDC6V.js";
4
+ import {
5
+ init_esm_shims
6
+ } from "./chunk-BGZ7J74D.js";
7
+
8
+ // src/cli/scaffolder.ts
9
+ init_esm_shims();
10
+ import { createHash } from "crypto";
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ function identityFingerprint(token) {
14
+ return `sha256:${createHash("sha256").update(token).digest("hex")}`;
15
+ }
16
+ function hasRegisterGlasstraceCall(content) {
17
+ return content.split("\n").some((line) => {
18
+ const uncommented = line.replace(/\/\/.*$/, "");
19
+ return /\bregisterGlasstrace\s*\(/.test(uncommented);
20
+ });
21
+ }
22
+ function injectRegisterGlasstrace(content) {
23
+ if (hasRegisterGlasstraceCall(content)) {
24
+ return { injected: false, content };
25
+ }
26
+ const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
27
+ const match = registerFnRegex.exec(content);
28
+ if (!match) {
29
+ return { injected: false, content };
30
+ }
31
+ const afterBrace = content.slice(match.index + match[0].length);
32
+ const indentMatch = /\n([ \t]+)/.exec(afterBrace);
33
+ const indent = indentMatch ? indentMatch[1] : " ";
34
+ const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
35
+ const hasGlasstraceImport = content.includes("@glasstrace/sdk");
36
+ const insertPoint = match.index + match[0].length;
37
+ const callInjection = `
38
+ ${indent}// Glasstrace must be registered before other instrumentation
39
+ ${indent}registerGlasstrace();
40
+ `;
41
+ let modified;
42
+ if (hasGlasstraceImport) {
43
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
44
+ const importMatch = importRegex.exec(content);
45
+ if (importMatch) {
46
+ const specifiers = importMatch[1];
47
+ const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
48
+ if (alreadyImported) {
49
+ modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
50
+ } else {
51
+ const existingImports = specifiers.trimEnd();
52
+ const separator = existingImports.endsWith(",") ? " " : ", ";
53
+ const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
54
+ modified = content.replace(importMatch[0], updatedImport);
55
+ const newMatch = registerFnRegex.exec(modified);
56
+ if (newMatch) {
57
+ const newInsertPoint = newMatch.index + newMatch[0].length;
58
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
59
+ }
60
+ }
61
+ } else {
62
+ modified = importLine + content;
63
+ const newMatch = registerFnRegex.exec(modified);
64
+ if (newMatch) {
65
+ const newInsertPoint = newMatch.index + newMatch[0].length;
66
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
67
+ }
68
+ }
69
+ } else {
70
+ modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
71
+ }
72
+ return { injected: true, content: modified };
73
+ }
74
+ async function scaffoldInstrumentation(projectRoot) {
75
+ const filePath = path.join(projectRoot, "instrumentation.ts");
76
+ if (!fs.existsSync(filePath)) {
77
+ const content = `import { registerGlasstrace } from "@glasstrace/sdk";
78
+
79
+ export async function register() {
80
+ // Glasstrace must be registered before Prisma instrumentation
81
+ // to ensure all ORM spans are captured correctly.
82
+ // If you use @prisma/instrumentation, import it after this call.
83
+ registerGlasstrace();
84
+ }
85
+ `;
86
+ fs.writeFileSync(filePath, content, "utf-8");
87
+ return { action: "created" };
88
+ }
89
+ const existing = fs.readFileSync(filePath, "utf-8");
90
+ if (hasRegisterGlasstraceCall(existing)) {
91
+ return { action: "already-registered" };
92
+ }
93
+ const result = injectRegisterGlasstrace(existing);
94
+ if (result.injected) {
95
+ fs.writeFileSync(filePath, result.content, "utf-8");
96
+ return { action: "injected" };
97
+ }
98
+ return { action: "unrecognized" };
99
+ }
100
+ async function scaffoldNextConfig(projectRoot) {
101
+ let configPath;
102
+ let configName;
103
+ for (const name of NEXT_CONFIG_NAMES) {
104
+ const candidate = path.join(projectRoot, name);
105
+ if (fs.existsSync(candidate)) {
106
+ configPath = candidate;
107
+ configName = name;
108
+ break;
109
+ }
110
+ }
111
+ if (configPath === void 0 || configName === void 0) {
112
+ return null;
113
+ }
114
+ const existing = fs.readFileSync(configPath, "utf-8");
115
+ if (existing.trim().length === 0) {
116
+ return { modified: false, reason: "empty-file" };
117
+ }
118
+ if (existing.includes("withGlasstraceConfig")) {
119
+ return { modified: false, reason: "already-wrapped" };
120
+ }
121
+ const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
122
+ if (isESM) {
123
+ const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
124
+ const wrapResult2 = wrapExport(existing);
125
+ if (!wrapResult2.wrapped) {
126
+ return { modified: false, reason: "no-export" };
127
+ }
128
+ const modified2 = importLine + "\n" + wrapResult2.content;
129
+ fs.writeFileSync(configPath, modified2, "utf-8");
130
+ return { modified: true };
131
+ }
132
+ const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
133
+ const wrapResult = wrapCJSExport(existing);
134
+ if (!wrapResult.wrapped) {
135
+ return { modified: false, reason: "no-export" };
136
+ }
137
+ const modified = requireLine + "\n" + wrapResult.content;
138
+ fs.writeFileSync(configPath, modified, "utf-8");
139
+ return { modified: true };
140
+ }
141
+ function wrapExport(content) {
142
+ const marker = "export default";
143
+ const idx = content.lastIndexOf(marker);
144
+ if (idx === -1) {
145
+ return { content, wrapped: false };
146
+ }
147
+ const preamble = content.slice(0, idx);
148
+ const exprRaw = content.slice(idx + marker.length);
149
+ const expr = exprRaw.trim().replace(/;?\s*$/, "");
150
+ if (expr.length === 0) {
151
+ return { content, wrapped: false };
152
+ }
153
+ return {
154
+ content: preamble + `export default withGlasstraceConfig(${expr});
155
+ `,
156
+ wrapped: true
157
+ };
158
+ }
159
+ function wrapCJSExport(content) {
160
+ const cjsMarker = "module.exports";
161
+ const cjsIdx = content.lastIndexOf(cjsMarker);
162
+ if (cjsIdx === -1) {
163
+ return { content, wrapped: false };
164
+ }
165
+ const preamble = content.slice(0, cjsIdx);
166
+ const afterMarker = content.slice(cjsIdx + cjsMarker.length);
167
+ const eqMatch = /^\s*=\s*/.exec(afterMarker);
168
+ if (!eqMatch) {
169
+ return { content, wrapped: false };
170
+ }
171
+ const exprRaw = afterMarker.slice(eqMatch[0].length);
172
+ const expr = exprRaw.trim().replace(/;?\s*$/, "");
173
+ if (expr.length === 0) {
174
+ return { content, wrapped: false };
175
+ }
176
+ return {
177
+ content: preamble + `module.exports = withGlasstraceConfig(${expr});
178
+ `,
179
+ wrapped: true
180
+ };
181
+ }
182
+ function readEnvLocalApiKey(content) {
183
+ let last = null;
184
+ const regex = /^\s*GLASSTRACE_API_KEY\s*=\s*(.*)$/gm;
185
+ let match;
186
+ while ((match = regex.exec(content)) !== null) {
187
+ const raw = match[1].trim();
188
+ if (raw === "") continue;
189
+ const unquoted = raw.replace(/^(['"])(.*)\1$/, "$2");
190
+ if (unquoted === "" || unquoted === "your_key_here") continue;
191
+ last = unquoted;
192
+ }
193
+ return last;
194
+ }
195
+ function isDevApiKey(value) {
196
+ if (value === null || value === void 0) return false;
197
+ return value.trim().startsWith("gt_dev_");
198
+ }
199
+ async function scaffoldEnvLocal(projectRoot) {
200
+ const filePath = path.join(projectRoot, ".env.local");
201
+ if (fs.existsSync(filePath)) {
202
+ const existing = fs.readFileSync(filePath, "utf-8");
203
+ if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
204
+ return false;
205
+ }
206
+ const separator = existing.endsWith("\n") ? "" : "\n";
207
+ fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
208
+ return true;
209
+ }
210
+ fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
211
+ return true;
212
+ }
213
+ async function addCoverageMapEnv(projectRoot) {
214
+ const filePath = path.join(projectRoot, ".env.local");
215
+ if (!fs.existsSync(filePath)) {
216
+ fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
217
+ return true;
218
+ }
219
+ const existing = fs.readFileSync(filePath, "utf-8");
220
+ const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
221
+ const keyMatch = keyRegex.exec(existing);
222
+ if (keyMatch) {
223
+ const currentValue = keyMatch[2].trim();
224
+ if (currentValue === "true") {
225
+ return false;
226
+ }
227
+ const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
228
+ fs.writeFileSync(filePath, updated, "utf-8");
229
+ return true;
230
+ }
231
+ const separator = existing.endsWith("\n") ? "" : "\n";
232
+ fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
233
+ return true;
234
+ }
235
+ async function scaffoldGitignore(projectRoot) {
236
+ const filePath = path.join(projectRoot, ".gitignore");
237
+ if (fs.existsSync(filePath)) {
238
+ const existing = fs.readFileSync(filePath, "utf-8");
239
+ const lines = existing.split("\n").map((l) => l.trim());
240
+ if (lines.includes(".glasstrace/")) {
241
+ return false;
242
+ }
243
+ const separator = existing.endsWith("\n") ? "" : "\n";
244
+ fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
245
+ return true;
246
+ }
247
+ fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
248
+ return true;
249
+ }
250
+ function mcpConfigMatches(existingContent, expectedContent) {
251
+ const trimmedExpected = expectedContent.trim();
252
+ try {
253
+ const existingParsed = JSON.parse(existingContent);
254
+ const expectedParsed = JSON.parse(trimmedExpected);
255
+ return JSON.stringify(canonicalize(existingParsed)) === JSON.stringify(canonicalize(expectedParsed));
256
+ } catch {
257
+ }
258
+ return existingContent.trim() === trimmedExpected;
259
+ }
260
+ function canonicalize(value) {
261
+ if (Array.isArray(value)) {
262
+ return value.map(canonicalize);
263
+ }
264
+ if (value !== null && typeof value === "object") {
265
+ const obj = value;
266
+ const sorted = {};
267
+ for (const key of Object.keys(obj).sort()) {
268
+ sorted[key] = canonicalize(obj[key]);
269
+ }
270
+ return sorted;
271
+ }
272
+ return value;
273
+ }
274
+ async function scaffoldMcpMarker(projectRoot, anonKey) {
275
+ const dirPath = path.join(projectRoot, ".glasstrace");
276
+ const markerPath = path.join(dirPath, "mcp-connected");
277
+ const keyHash = identityFingerprint(anonKey);
278
+ if (fs.existsSync(markerPath)) {
279
+ try {
280
+ const existing = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
281
+ if (existing.keyHash === keyHash) {
282
+ return false;
283
+ }
284
+ } catch {
285
+ }
286
+ }
287
+ fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
288
+ const marker = JSON.stringify(
289
+ { keyHash, configuredAt: (/* @__PURE__ */ new Date()).toISOString() },
290
+ null,
291
+ 2
292
+ );
293
+ fs.writeFileSync(markerPath, marker, { mode: 384 });
294
+ fs.chmodSync(markerPath, 384);
295
+ return true;
296
+ }
297
+
298
+ export {
299
+ scaffoldInstrumentation,
300
+ scaffoldNextConfig,
301
+ readEnvLocalApiKey,
302
+ isDevApiKey,
303
+ scaffoldEnvLocal,
304
+ addCoverageMapEnv,
305
+ scaffoldGitignore,
306
+ mcpConfigMatches,
307
+ scaffoldMcpMarker
308
+ };
309
+ //# sourceMappingURL=chunk-A2AZL6MZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/scaffolder.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { NEXT_CONFIG_NAMES } from \"./constants.js\";\n\n/**\n * Computes a stable identity fingerprint for deduplication purposes.\n * This is NOT password hashing — the input is an opaque token used\n * as a marker identity, not a credential stored for authentication.\n *\n * @internal Exported for unit testing only.\n */\nexport function identityFingerprint(token: string): string {\n return `sha256:${createHash(\"sha256\").update(token).digest(\"hex\")}`;\n}\n\n/**\n * Checks whether `content` contains a real (non-commented) `registerGlasstrace()` call.\n *\n * Strips single-line `// ...` comments before matching so that\n * `// registerGlasstrace()` is not treated as a real invocation.\n * Block comments are not stripped — block-commenting a function call\n * while keeping it syntactically valid is extremely unlikely in practice.\n *\n * @internal Exported for unit testing only.\n */\nexport function hasRegisterGlasstraceCall(content: string): boolean {\n return content.split(\"\\n\").some((line) => {\n const uncommented = line.replace(/\\/\\/.*$/, \"\");\n return /\\bregisterGlasstrace\\s*\\(/.test(uncommented);\n });\n}\n\n/** Result of attempting to inject registerGlasstrace into an existing file. */\nexport interface InjectResult {\n content: string;\n injected: boolean;\n}\n\n/** Result of the instrumentation.ts scaffolding step. */\nexport type InstrumentationAction = \"created\" | \"injected\" | \"already-registered\" | \"unrecognized\";\n\n/** Structured result from scaffoldInstrumentation. */\nexport interface ScaffoldInstrumentationResult {\n action: InstrumentationAction;\n}\n\n/** Result of attempting to wrap next.config with withGlasstraceConfig. */\nexport interface ScaffoldNextConfigResult {\n modified: boolean;\n reason?: \"already-wrapped\" | \"empty-file\" | \"no-export\";\n}\n\n/**\n * Injects `registerGlasstrace()` into an existing instrumentation.ts file.\n *\n * Strategy:\n * 1. If the file already contains a real `registerGlasstrace()` call — no-op\n * (commented-out calls are ignored)\n * 2. Find `export [async] function register()` pattern\n * 3. Add `import { registerGlasstrace } from \"@glasstrace/sdk\"` at top\n * (or extend existing `@glasstrace/sdk` import, skipping if already imported)\n * 4. Insert `registerGlasstrace()` as the first statement in the function body\n *\n * @param content - The existing file content\n * @returns The modified content if injection succeeded, or the original content\n * with `injected: false` if the pattern was not recognized\n */\nexport function injectRegisterGlasstrace(content: string): InjectResult {\n // Already has a registerGlasstrace() call — no-op.\n // Uses a helper that strips single-line comments before matching\n // so that `// registerGlasstrace()` is not treated as a real call.\n if (hasRegisterGlasstraceCall(content)) {\n return { injected: false, content };\n }\n\n // Find the register() function: export [async] function register(...) {\n const registerFnRegex = /export\\s+(?:async\\s+)?function\\s+register\\s*\\([^)]*\\)\\s*\\{/;\n const match = registerFnRegex.exec(content);\n\n if (!match) {\n return { injected: false, content };\n }\n\n // Determine indentation from the function body by looking at the first\n // indented line after the opening brace. Only capture spaces and tabs\n // (not newlines) to avoid blank lines corrupting the detected indent.\n // Default to 2-space indent (matches the scaffolded template).\n const afterBrace = content.slice(match.index + match[0].length);\n const indentMatch = /\\n([ \\t]+)/.exec(afterBrace);\n const indent = indentMatch ? indentMatch[1] : \" \";\n\n // Build the import line\n const importLine = 'import { registerGlasstrace } from \"@glasstrace/sdk\";\\n';\n\n // Check if the file already imports from @glasstrace/sdk\n const hasGlasstraceImport = content.includes(\"@glasstrace/sdk\");\n\n // Insert registerGlasstrace() as the first statement in the function body\n const insertPoint = match.index + match[0].length;\n const callInjection = `\\n${indent}// Glasstrace must be registered before other instrumentation\\n${indent}registerGlasstrace();\\n`;\n\n let modified: string;\n if (hasGlasstraceImport) {\n // File already imports from @glasstrace/sdk — check whether registerGlasstrace\n // is already among the specifiers to avoid producing a duplicate like\n // `import { registerGlasstrace, registerGlasstrace }`.\n const importRegex = /import\\s*\\{([^}]+)\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']/;\n const importMatch = importRegex.exec(content);\n if (importMatch) {\n const specifiers = importMatch[1];\n const alreadyImported = specifiers\n .split(\",\")\n .some((s) => s.trim() === \"registerGlasstrace\");\n\n if (alreadyImported) {\n // Import already has registerGlasstrace — only inject the call\n modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);\n } else {\n // Add registerGlasstrace to existing import specifiers\n const existingImports = specifiers.trimEnd();\n const separator = existingImports.endsWith(\",\") ? \" \" : \", \";\n const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from \"@glasstrace/sdk\"`;\n modified = content.replace(importMatch[0], updatedImport);\n // Re-find the function in the shifted content and inject the call\n const newMatch = registerFnRegex.exec(modified);\n if (newMatch) {\n const newInsertPoint = newMatch.index + newMatch[0].length;\n modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);\n }\n }\n } else {\n // Non-destructured import (e.g., import * as sdk) — add a separate import\n modified = importLine + content;\n // Re-find the function in the shifted content and inject the call\n const newMatch = registerFnRegex.exec(modified);\n if (newMatch) {\n const newInsertPoint = newMatch.index + newMatch[0].length;\n modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);\n }\n }\n } else {\n // Add import at the top of the file and the call in the function body\n modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);\n }\n\n return { injected: true, content: modified };\n}\n\n/**\n * Ensures `instrumentation.ts` exists and contains a `registerGlasstrace()` call.\n *\n * - If the file does not exist, creates it with the standard template.\n * - If the file exists and already contains `registerGlasstrace`, skips it.\n * - If the file exists without `registerGlasstrace`, attempts to inject the\n * call into the existing `register()` function.\n * - If injection fails (no recognizable `register()` function), returns\n * `\"unrecognized\"` so the caller can display manual instructions.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns A structured result describing what action was taken.\n */\nexport async function scaffoldInstrumentation(\n projectRoot: string,\n): Promise<ScaffoldInstrumentationResult> {\n const filePath = path.join(projectRoot, \"instrumentation.ts\");\n\n if (!fs.existsSync(filePath)) {\n const content = `import { registerGlasstrace } from \"@glasstrace/sdk\";\n\nexport async function register() {\n // Glasstrace must be registered before Prisma instrumentation\n // to ensure all ORM spans are captured correctly.\n // If you use @prisma/instrumentation, import it after this call.\n registerGlasstrace();\n}\n`;\n fs.writeFileSync(filePath, content, \"utf-8\");\n return { action: \"created\" };\n }\n\n // File exists — check whether registerGlasstrace() is already called.\n // Uses a helper that strips single-line comments before matching\n // so that `// registerGlasstrace()` is not treated as a real call.\n const existing = fs.readFileSync(filePath, \"utf-8\");\n\n if (hasRegisterGlasstraceCall(existing)) {\n return { action: \"already-registered\" };\n }\n\n // Attempt injection into the existing file\n const result = injectRegisterGlasstrace(existing);\n\n if (result.injected) {\n fs.writeFileSync(filePath, result.content, \"utf-8\");\n return { action: \"injected\" };\n }\n\n return { action: \"unrecognized\" };\n}\n\n/**\n * Detects `next.config.js`, `next.config.ts`, or `next.config.mjs` and wraps\n * with `withGlasstraceConfig()`. If the config already contains\n * `withGlasstraceConfig`, the file is not modified.\n *\n * For CJS `.js` configs, adds a `require()` call and wraps `module.exports`.\n * The SDK ships dual ESM/CJS builds via tsup + conditional exports, so\n * `require(\"@glasstrace/sdk\")` resolves to the CJS entrypoint natively.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns A result object describing what happened, or `null` if no config\n * file was found at all.\n */\nexport async function scaffoldNextConfig(\n projectRoot: string,\n): Promise<ScaffoldNextConfigResult | null> {\n let configPath: string | undefined;\n let configName: string | undefined;\n\n for (const name of NEXT_CONFIG_NAMES) {\n const candidate = path.join(projectRoot, name);\n if (fs.existsSync(candidate)) {\n configPath = candidate;\n configName = name;\n break;\n }\n }\n\n if (configPath === undefined || configName === undefined) {\n return null;\n }\n\n const existing = fs.readFileSync(configPath, \"utf-8\");\n\n // Guard: empty or whitespace-only files have no export to wrap\n if (existing.trim().length === 0) {\n return { modified: false, reason: \"empty-file\" };\n }\n\n // Already wrapped — skip even in force mode to avoid double-wrapping\n if (existing.includes(\"withGlasstraceConfig\")) {\n return { modified: false, reason: \"already-wrapped\" };\n }\n\n const isESM = configName.endsWith(\".ts\") || configName.endsWith(\".mjs\");\n\n if (isESM) {\n // ESM: static import at top of file, wrap the export\n const importLine = 'import { withGlasstraceConfig } from \"@glasstrace/sdk\";\\n';\n const wrapResult = wrapExport(existing);\n if (!wrapResult.wrapped) {\n return { modified: false, reason: \"no-export\" };\n }\n const modified = importLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return { modified: true };\n }\n\n // CJS (.js): require() the SDK (resolves to the CJS dist build) and\n // wrap the module.exports expression in place — no file renaming needed.\n const requireLine = 'const { withGlasstraceConfig } = require(\"@glasstrace/sdk\");\\n';\n const wrapResult = wrapCJSExport(existing);\n if (!wrapResult.wrapped) {\n return { modified: false, reason: \"no-export\" };\n }\n const modified = requireLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return { modified: true };\n}\n\n/** @internal Exported for unit testing only. */\nexport interface WrapResult {\n content: string;\n wrapped: boolean;\n}\n\n/**\n * Wraps an ESM `export default` expression with `withGlasstraceConfig()`.\n *\n * Strategy: find the last `export default` in the file. Everything from\n * that statement to EOF is the exported expression. Strip optional trailing\n * semicolons/whitespace and wrap with `withGlasstraceConfig(...)`.\n *\n * @param content - The full file content containing an ESM default export.\n * @returns `{ wrapped: true, content }` on success, or `{ wrapped: false }` if\n * no recognizable export pattern was found (content returned unchanged).\n * @internal Exported for unit testing only.\n */\nexport function wrapExport(content: string): WrapResult {\n // Find the last `export default` — use lastIndexOf for robustness\n const marker = \"export default\";\n const idx = content.lastIndexOf(marker);\n if (idx === -1) {\n return { content, wrapped: false };\n }\n\n const preamble = content.slice(0, idx);\n const exprRaw = content.slice(idx + marker.length);\n // Trim leading whitespace; strip trailing semicolon + whitespace\n const expr = exprRaw.trim().replace(/;?\\s*$/, \"\");\n if (expr.length === 0) {\n return { content, wrapped: false };\n }\n\n return {\n content: preamble + `export default withGlasstraceConfig(${expr});\\n`,\n wrapped: true,\n };\n}\n\n/**\n * Wraps a CJS `module.exports = expr` with `withGlasstraceConfig()`.\n *\n * Strategy: find the last `module.exports =` in the file. Everything from\n * that statement to EOF is the exported expression. Strip optional trailing\n * semicolons/whitespace and wrap with `module.exports = withGlasstraceConfig(...)`.\n *\n * @param content - The full CJS file content containing `module.exports = ...`.\n * @returns `{ wrapped: true, content }` on success, or `{ wrapped: false }` if\n * no recognizable `module.exports` pattern was found (content returned unchanged).\n * @internal Exported for unit testing only.\n */\nexport function wrapCJSExport(content: string): WrapResult {\n const cjsMarker = \"module.exports\";\n const cjsIdx = content.lastIndexOf(cjsMarker);\n if (cjsIdx === -1) {\n return { content, wrapped: false };\n }\n\n const preamble = content.slice(0, cjsIdx);\n const afterMarker = content.slice(cjsIdx + cjsMarker.length);\n const eqMatch = /^\\s*=\\s*/.exec(afterMarker);\n if (!eqMatch) {\n return { content, wrapped: false };\n }\n\n const exprRaw = afterMarker.slice(eqMatch[0].length);\n const expr = exprRaw.trim().replace(/;?\\s*$/, \"\");\n if (expr.length === 0) {\n return { content, wrapped: false };\n }\n\n return {\n content: preamble + `module.exports = withGlasstraceConfig(${expr});\\n`,\n wrapped: true,\n };\n}\n\n/**\n * Extracts the value of `GLASSTRACE_API_KEY` from a `.env.local`-style\n * string. Returns the raw (unquoted) value, or `null` if the key is\n * absent, commented out, or empty.\n *\n * Only uncommented assignments are considered — a `# GLASSTRACE_API_KEY=...`\n * placeholder is treated as if the key is not set.\n *\n * When multiple uncommented assignments are present, the **last**\n * effective value wins — matching typical `.env` override semantics\n * (later lines override earlier ones when loaded by dotenv-style\n * loaders). Placeholder values (empty or `your_key_here`) are skipped\n * so a trailing placeholder does not mask a real earlier value.\n *\n * @internal Exported for unit testing only.\n */\nexport function readEnvLocalApiKey(content: string): string | null {\n let last: string | null = null;\n const regex = /^\\s*GLASSTRACE_API_KEY\\s*=\\s*(.*)$/gm;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(content)) !== null) {\n const raw = match[1].trim();\n if (raw === \"\") continue;\n const unquoted = raw.replace(/^(['\"])(.*)\\1$/, \"$2\");\n if (unquoted === \"\" || unquoted === \"your_key_here\") continue;\n last = unquoted;\n }\n return last;\n}\n\n/**\n * Returns true when the given API key value is a claimed developer key\n * (prefix `gt_dev_`). Defensive against leading/trailing whitespace.\n *\n * @internal Exported for unit testing only.\n */\nexport function isDevApiKey(value: string | null | undefined): boolean {\n if (value === null || value === undefined) return false;\n return value.trim().startsWith(\"gt_dev_\");\n}\n\n/**\n * Creates `.env.local` with `GLASSTRACE_API_KEY=` placeholder, or appends\n * to an existing file if it does not already contain `GLASSTRACE_API_KEY`.\n *\n * Preservation behavior (DISC-1247 Scenario 6): if an existing `.env.local`\n * already defines a developer key (`gt_dev_*`), the file is left untouched\n * so re-running `init` after an account claim does not overwrite the\n * claimed dev key.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already configured.\n */\nexport async function scaffoldEnvLocal(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".env.local\");\n\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, \"utf-8\");\n if (/^\\s*#?\\s*GLASSTRACE_API_KEY\\s*=/m.test(existing)) {\n return false;\n }\n // Append with a newline separator if needed\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \"# GLASSTRACE_API_KEY=your_key_here\\n\", \"utf-8\");\n return true;\n }\n\n fs.writeFileSync(filePath, \"# GLASSTRACE_API_KEY=your_key_here\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Adds `GLASSTRACE_COVERAGE_MAP=true` to `.env.local`.\n * Creates the file if it does not exist. If the key is already present\n * with a value other than `true`, it is updated in place.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already set to `true`.\n */\nexport async function addCoverageMapEnv(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".env.local\");\n\n if (!fs.existsSync(filePath)) {\n fs.writeFileSync(filePath, \"GLASSTRACE_COVERAGE_MAP=true\\n\", \"utf-8\");\n return true;\n }\n\n const existing = fs.readFileSync(filePath, \"utf-8\");\n const keyRegex = /^(\\s*GLASSTRACE_COVERAGE_MAP\\s*=\\s*)(.*)$/m;\n const keyMatch = keyRegex.exec(existing);\n\n if (keyMatch) {\n const currentValue = keyMatch[2].trim();\n if (currentValue === \"true\") {\n // Already set to true — nothing to do\n return false;\n }\n // Key exists but is not `true` — update in place\n const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);\n fs.writeFileSync(filePath, updated, \"utf-8\");\n return true;\n }\n\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \"GLASSTRACE_COVERAGE_MAP=true\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Adds `.glasstrace/` to `.gitignore`, or creates `.gitignore` if missing.\n * Does not add duplicate entries.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already configured.\n */\nexport async function scaffoldGitignore(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".gitignore\");\n\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, \"utf-8\");\n // Check line-by-line to avoid false positive partial matches\n const lines = existing.split(\"\\n\").map((l) => l.trim());\n if (lines.includes(\".glasstrace/\")) {\n return false;\n }\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \".glasstrace/\\n\", \"utf-8\");\n return true;\n }\n\n fs.writeFileSync(filePath, \".glasstrace/\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Compares an existing MCP config file against the content init would\n * write. Returns `true` when they are semantically equal (JSON configs\n * are parsed and compared deeply; TOML configs use trimmed string\n * comparison). Returns `false` on parse errors or mismatch.\n *\n * Used by `init` to detect manually-edited MCP configs before\n * overwriting them (DISC-1247 Scenario 2c).\n *\n * @internal Exported for unit testing only.\n */\nexport function mcpConfigMatches(\n existingContent: string,\n expectedContent: string,\n): boolean {\n const trimmedExpected = expectedContent.trim();\n\n // Attempt JSON comparison first — init writes JSON for most agents.\n try {\n const existingParsed: unknown = JSON.parse(existingContent);\n const expectedParsed: unknown = JSON.parse(trimmedExpected);\n return JSON.stringify(canonicalize(existingParsed)) === JSON.stringify(canonicalize(expectedParsed));\n } catch {\n // Fall through to text comparison for TOML and other non-JSON formats.\n }\n\n return existingContent.trim() === trimmedExpected;\n}\n\n/**\n * Sorts object keys recursively to produce a canonical form suitable\n * for structural equality comparison via JSON.stringify.\n */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(canonicalize);\n }\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n\n/**\n * Creates the `.glasstrace/mcp-connected` marker file, or overwrites it\n * if the key has changed (key rotation).\n *\n * The marker file records a SHA-256 fingerprint of the anonymous key and\n * the ISO 8601 timestamp when it was written. It is used by the nudge\n * system to suppress \"MCP not configured\" prompts.\n *\n * If the marker already exists with the same key fingerprint, this is a\n * no-op (the timestamp is NOT refreshed).\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @param anonKey - The anonymous API key to fingerprint.\n * @returns True if the marker was created or updated, false if it already\n * exists with the same key fingerprint.\n */\nexport async function scaffoldMcpMarker(\n projectRoot: string,\n anonKey: string,\n): Promise<boolean> {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n const markerPath = path.join(dirPath, \"mcp-connected\");\n const keyHash = identityFingerprint(anonKey);\n\n // Check if marker already exists with the same key hash\n if (fs.existsSync(markerPath)) {\n try {\n const existing = JSON.parse(fs.readFileSync(markerPath, \"utf-8\")) as {\n keyHash?: string;\n };\n if (existing.keyHash === keyHash) {\n return false;\n }\n } catch {\n // Corrupted marker — overwrite\n }\n }\n\n // Create directory with restricted permissions\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });\n\n const marker = JSON.stringify(\n { keyHash, configuredAt: new Date().toISOString() },\n null,\n 2,\n );\n\n fs.writeFileSync(markerPath, marker, { mode: 0o600 });\n\n // Ensure permissions even if file pre-existed (writeFile mode only\n // applies on creation on some platforms)\n fs.chmodSync(markerPath, 0o600);\n\n return true;\n}\n"],"mappings":";;;;;;;;AAAA;AAAA,SAAS,kBAAkB;AAC3B,YAAY,QAAQ;AACpB,YAAY,UAAU;AAUf,SAAS,oBAAoB,OAAuB;AACzD,SAAO,UAAU,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AACnE;AAYO,SAAS,0BAA0B,SAA0B;AAClE,SAAO,QAAQ,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS;AACxC,UAAM,cAAc,KAAK,QAAQ,WAAW,EAAE;AAC9C,WAAO,4BAA4B,KAAK,WAAW;AAAA,EACrD,CAAC;AACH;AAqCO,SAAS,yBAAyB,SAA+B;AAItE,MAAI,0BAA0B,OAAO,GAAG;AACtC,WAAO,EAAE,UAAU,OAAO,QAAQ;AAAA,EACpC;AAGA,QAAM,kBAAkB;AACxB,QAAM,QAAQ,gBAAgB,KAAK,OAAO;AAE1C,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,UAAU,OAAO,QAAQ;AAAA,EACpC;AAMA,QAAM,aAAa,QAAQ,MAAM,MAAM,QAAQ,MAAM,CAAC,EAAE,MAAM;AAC9D,QAAM,cAAc,aAAa,KAAK,UAAU;AAChD,QAAM,SAAS,cAAc,YAAY,CAAC,IAAI;AAG9C,QAAM,aAAa;AAGnB,QAAM,sBAAsB,QAAQ,SAAS,iBAAiB;AAG9D,QAAM,cAAc,MAAM,QAAQ,MAAM,CAAC,EAAE;AAC3C,QAAM,gBAAgB;AAAA,EAAK,MAAM;AAAA,EAAkE,MAAM;AAAA;AAEzG,MAAI;AACJ,MAAI,qBAAqB;AAIvB,UAAM,cAAc;AACpB,UAAM,cAAc,YAAY,KAAK,OAAO;AAC5C,QAAI,aAAa;AACf,YAAM,aAAa,YAAY,CAAC;AAChC,YAAM,kBAAkB,WACrB,MAAM,GAAG,EACT,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,oBAAoB;AAEhD,UAAI,iBAAiB;AAEnB,mBAAW,QAAQ,MAAM,GAAG,WAAW,IAAI,gBAAgB,QAAQ,MAAM,WAAW;AAAA,MACtF,OAAO;AAEL,cAAM,kBAAkB,WAAW,QAAQ;AAC3C,cAAM,YAAY,gBAAgB,SAAS,GAAG,IAAI,MAAM;AACxD,cAAM,gBAAgB,YAAY,gBAAgB,KAAK,CAAC,GAAG,SAAS;AACpE,mBAAW,QAAQ,QAAQ,YAAY,CAAC,GAAG,aAAa;AAExD,cAAM,WAAW,gBAAgB,KAAK,QAAQ;AAC9C,YAAI,UAAU;AACZ,gBAAM,iBAAiB,SAAS,QAAQ,SAAS,CAAC,EAAE;AACpD,qBAAW,SAAS,MAAM,GAAG,cAAc,IAAI,gBAAgB,SAAS,MAAM,cAAc;AAAA,QAC9F;AAAA,MACF;AAAA,IACF,OAAO;AAEL,iBAAW,aAAa;AAExB,YAAM,WAAW,gBAAgB,KAAK,QAAQ;AAC9C,UAAI,UAAU;AACZ,cAAM,iBAAiB,SAAS,QAAQ,SAAS,CAAC,EAAE;AACpD,mBAAW,SAAS,MAAM,GAAG,cAAc,IAAI,gBAAgB,SAAS,MAAM,cAAc;AAAA,MAC9F;AAAA,IACF;AAAA,EACF,OAAO;AAEL,eAAW,aAAa,QAAQ,MAAM,GAAG,WAAW,IAAI,gBAAgB,QAAQ,MAAM,WAAW;AAAA,EACnG;AAEA,SAAO,EAAE,UAAU,MAAM,SAAS,SAAS;AAC7C;AAeA,eAAsB,wBACpB,aACwC;AACxC,QAAM,WAAgB,UAAK,aAAa,oBAAoB;AAE5D,MAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAShB,IAAG,iBAAc,UAAU,SAAS,OAAO;AAC3C,WAAO,EAAE,QAAQ,UAAU;AAAA,EAC7B;AAKA,QAAM,WAAc,gBAAa,UAAU,OAAO;AAElD,MAAI,0BAA0B,QAAQ,GAAG;AACvC,WAAO,EAAE,QAAQ,qBAAqB;AAAA,EACxC;AAGA,QAAM,SAAS,yBAAyB,QAAQ;AAEhD,MAAI,OAAO,UAAU;AACnB,IAAG,iBAAc,UAAU,OAAO,SAAS,OAAO;AAClD,WAAO,EAAE,QAAQ,WAAW;AAAA,EAC9B;AAEA,SAAO,EAAE,QAAQ,eAAe;AAClC;AAeA,eAAsB,mBACpB,aAC0C;AAC1C,MAAI;AACJ,MAAI;AAEJ,aAAW,QAAQ,mBAAmB;AACpC,UAAM,YAAiB,UAAK,aAAa,IAAI;AAC7C,QAAO,cAAW,SAAS,GAAG;AAC5B,mBAAa;AACb,mBAAa;AACb;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe,UAAa,eAAe,QAAW;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,WAAc,gBAAa,YAAY,OAAO;AAGpD,MAAI,SAAS,KAAK,EAAE,WAAW,GAAG;AAChC,WAAO,EAAE,UAAU,OAAO,QAAQ,aAAa;AAAA,EACjD;AAGA,MAAI,SAAS,SAAS,sBAAsB,GAAG;AAC7C,WAAO,EAAE,UAAU,OAAO,QAAQ,kBAAkB;AAAA,EACtD;AAEA,QAAM,QAAQ,WAAW,SAAS,KAAK,KAAK,WAAW,SAAS,MAAM;AAEtE,MAAI,OAAO;AAET,UAAM,aAAa;AACnB,UAAMA,cAAa,WAAW,QAAQ;AACtC,QAAI,CAACA,YAAW,SAAS;AACvB,aAAO,EAAE,UAAU,OAAO,QAAQ,YAAY;AAAA,IAChD;AACA,UAAMC,YAAW,aAAa,OAAOD,YAAW;AAChD,IAAG,iBAAc,YAAYC,WAAU,OAAO;AAC9C,WAAO,EAAE,UAAU,KAAK;AAAA,EAC1B;AAIA,QAAM,cAAc;AACpB,QAAM,aAAa,cAAc,QAAQ;AACzC,MAAI,CAAC,WAAW,SAAS;AACvB,WAAO,EAAE,UAAU,OAAO,QAAQ,YAAY;AAAA,EAChD;AACA,QAAM,WAAW,cAAc,OAAO,WAAW;AACjD,EAAG,iBAAc,YAAY,UAAU,OAAO;AAC9C,SAAO,EAAE,UAAU,KAAK;AAC1B;AAoBO,SAAS,WAAW,SAA6B;AAEtD,QAAM,SAAS;AACf,QAAM,MAAM,QAAQ,YAAY,MAAM;AACtC,MAAI,QAAQ,IAAI;AACd,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC;AAEA,QAAM,WAAW,QAAQ,MAAM,GAAG,GAAG;AACrC,QAAM,UAAU,QAAQ,MAAM,MAAM,OAAO,MAAM;AAEjD,QAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,UAAU,EAAE;AAChD,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,SAAS,WAAW,uCAAuC,IAAI;AAAA;AAAA,IAC/D,SAAS;AAAA,EACX;AACF;AAcO,SAAS,cAAc,SAA6B;AACzD,QAAM,YAAY;AAClB,QAAM,SAAS,QAAQ,YAAY,SAAS;AAC5C,MAAI,WAAW,IAAI;AACjB,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC;AAEA,QAAM,WAAW,QAAQ,MAAM,GAAG,MAAM;AACxC,QAAM,cAAc,QAAQ,MAAM,SAAS,UAAU,MAAM;AAC3D,QAAM,UAAU,WAAW,KAAK,WAAW;AAC3C,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC;AAEA,QAAM,UAAU,YAAY,MAAM,QAAQ,CAAC,EAAE,MAAM;AACnD,QAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,UAAU,EAAE;AAChD,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,EAAE,SAAS,SAAS,MAAM;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,SAAS,WAAW,yCAAyC,IAAI;AAAA;AAAA,IACjE,SAAS;AAAA,EACX;AACF;AAkBO,SAAS,mBAAmB,SAAgC;AACjE,MAAI,OAAsB;AAC1B,QAAM,QAAQ;AACd,MAAI;AACJ,UAAQ,QAAQ,MAAM,KAAK,OAAO,OAAO,MAAM;AAC7C,UAAM,MAAM,MAAM,CAAC,EAAE,KAAK;AAC1B,QAAI,QAAQ,GAAI;AAChB,UAAM,WAAW,IAAI,QAAQ,kBAAkB,IAAI;AACnD,QAAI,aAAa,MAAM,aAAa,gBAAiB;AACrD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,YAAY,OAA2C;AACrE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,SAAO,MAAM,KAAK,EAAE,WAAW,SAAS;AAC1C;AAcA,eAAsB,iBAAiB,aAAuC;AAC5E,QAAM,WAAgB,UAAK,aAAa,YAAY;AAEpD,MAAO,cAAW,QAAQ,GAAG;AAC3B,UAAM,WAAc,gBAAa,UAAU,OAAO;AAClD,QAAI,mCAAmC,KAAK,QAAQ,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,SAAS,SAAS,IAAI,IAAI,KAAK;AACjD,IAAG,iBAAc,UAAU,WAAW,YAAY,wCAAwC,OAAO;AACjG,WAAO;AAAA,EACT;AAEA,EAAG,iBAAc,UAAU,wCAAwC,OAAO;AAC1E,SAAO;AACT;AAUA,eAAsB,kBAAkB,aAAuC;AAC7E,QAAM,WAAgB,UAAK,aAAa,YAAY;AAEpD,MAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,IAAG,iBAAc,UAAU,kCAAkC,OAAO;AACpE,WAAO;AAAA,EACT;AAEA,QAAM,WAAc,gBAAa,UAAU,OAAO;AAClD,QAAM,WAAW;AACjB,QAAM,WAAW,SAAS,KAAK,QAAQ;AAEvC,MAAI,UAAU;AACZ,UAAM,eAAe,SAAS,CAAC,EAAE,KAAK;AACtC,QAAI,iBAAiB,QAAQ;AAE3B,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,SAAS,QAAQ,UAAU,GAAG,SAAS,CAAC,CAAC,MAAM;AAC/D,IAAG,iBAAc,UAAU,SAAS,OAAO;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,SAAS,SAAS,IAAI,IAAI,KAAK;AACjD,EAAG,iBAAc,UAAU,WAAW,YAAY,kCAAkC,OAAO;AAC3F,SAAO;AACT;AASA,eAAsB,kBAAkB,aAAuC;AAC7E,QAAM,WAAgB,UAAK,aAAa,YAAY;AAEpD,MAAO,cAAW,QAAQ,GAAG;AAC3B,UAAM,WAAc,gBAAa,UAAU,OAAO;AAElD,UAAM,QAAQ,SAAS,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACtD,QAAI,MAAM,SAAS,cAAc,GAAG;AAClC,aAAO;AAAA,IACT;AACA,UAAM,YAAY,SAAS,SAAS,IAAI,IAAI,KAAK;AACjD,IAAG,iBAAc,UAAU,WAAW,YAAY,kBAAkB,OAAO;AAC3E,WAAO;AAAA,EACT;AAEA,EAAG,iBAAc,UAAU,kBAAkB,OAAO;AACpD,SAAO;AACT;AAaO,SAAS,iBACd,iBACA,iBACS;AACT,QAAM,kBAAkB,gBAAgB,KAAK;AAG7C,MAAI;AACF,UAAM,iBAA0B,KAAK,MAAM,eAAe;AAC1D,UAAM,iBAA0B,KAAK,MAAM,eAAe;AAC1D,WAAO,KAAK,UAAU,aAAa,cAAc,CAAC,MAAM,KAAK,UAAU,aAAa,cAAc,CAAC;AAAA,EACrG,QAAQ;AAAA,EAER;AAEA,SAAO,gBAAgB,KAAK,MAAM;AACpC;AAMA,SAAS,aAAa,OAAyB;AAC7C,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,YAAY;AAAA,EAC/B;AACA,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AACzC,aAAO,GAAG,IAAI,aAAa,IAAI,GAAG,CAAC;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAkBA,eAAsB,kBACpB,aACA,SACkB;AAClB,QAAM,UAAe,UAAK,aAAa,aAAa;AACpD,QAAM,aAAkB,UAAK,SAAS,eAAe;AACrD,QAAM,UAAU,oBAAoB,OAAO;AAG3C,MAAO,cAAW,UAAU,GAAG;AAC7B,QAAI;AACF,YAAM,WAAW,KAAK,MAAS,gBAAa,YAAY,OAAO,CAAC;AAGhE,UAAI,SAAS,YAAY,SAAS;AAChC,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,EAAG,aAAU,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAEtD,QAAM,SAAS,KAAK;AAAA,IAClB,EAAE,SAAS,eAAc,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,IAClD;AAAA,IACA;AAAA,EACF;AAEA,EAAG,iBAAc,YAAY,QAAQ,EAAE,MAAM,IAAM,CAAC;AAIpD,EAAG,aAAU,YAAY,GAAK;AAE9B,SAAO;AACT;","names":["wrapResult","modified"]}