@interfere/next 9.0.2 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -5
- package/dist/config.d.mts +24 -5
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +38 -28
- package/dist/config.mjs.map +1 -1
- package/dist/instrument-client.d.mts +14 -3
- package/dist/instrument-client.d.mts.map +1 -1
- package/dist/instrument-client.mjs +7 -9
- package/dist/instrument-client.mjs.map +1 -1
- package/dist/instrumentation-client.d.mts +1 -0
- package/dist/instrumentation-client.mjs +22 -0
- package/dist/instrumentation-client.mjs.map +1 -0
- package/dist/instrumentation.d.mts +134 -0
- package/dist/instrumentation.d.mts.map +1 -0
- package/dist/instrumentation.edge.d.mts +35 -0
- package/dist/instrumentation.edge.d.mts.map +1 -0
- package/dist/instrumentation.edge.mjs +34 -0
- package/dist/instrumentation.edge.mjs.map +1 -0
- package/dist/instrumentation.mjs +165 -0
- package/dist/instrumentation.mjs.map +1 -0
- package/dist/internal/build/configure-build.d.mts +1 -2
- package/dist/internal/build/configure-build.d.mts.map +1 -1
- package/dist/internal/build/configure-build.mjs +10 -2
- package/dist/internal/build/configure-build.mjs.map +1 -1
- package/dist/internal/build/detect-bundler.d.mts +6 -0
- package/dist/internal/build/detect-bundler.d.mts.map +1 -0
- package/dist/internal/build/detect-bundler.mjs +9 -0
- package/dist/internal/build/detect-bundler.mjs.map +1 -0
- package/dist/internal/build/pipeline.d.mts +14 -1
- package/dist/internal/build/pipeline.d.mts.map +1 -1
- package/dist/internal/build/pipeline.mjs +26 -10
- package/dist/internal/build/pipeline.mjs.map +1 -1
- package/dist/internal/build/release/destinations/index.d.mts +14 -0
- package/dist/internal/build/release/destinations/index.d.mts.map +1 -0
- package/dist/internal/build/release/destinations/index.mjs +13 -0
- package/dist/internal/build/release/destinations/index.mjs.map +1 -0
- package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
- package/dist/internal/build/release/git.d.mts +13 -0
- package/dist/internal/build/release/git.d.mts.map +1 -1
- package/dist/internal/build/release/git.mjs +13 -2
- package/dist/internal/build/release/git.mjs.map +1 -1
- package/dist/internal/build/release/index.d.mts +2 -1
- package/dist/internal/build/release/index.d.mts.map +1 -1
- package/dist/internal/build/release/index.mjs +4 -5
- package/dist/internal/build/release/index.mjs.map +1 -1
- package/dist/internal/build/release/sources/github.mjs.map +1 -1
- package/dist/internal/build/release/sources/index.d.mts +21 -0
- package/dist/internal/build/release/sources/index.d.mts.map +1 -0
- package/dist/internal/build/release/sources/index.mjs +20 -0
- package/dist/internal/build/release/sources/index.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts +32 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs +68 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts +53 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs +112 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover.d.mts +28 -10
- package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover.mjs +22 -83
- package/dist/internal/build/source-maps/discover.mjs.map +1 -1
- package/dist/internal/build/source-maps/index.d.mts +2 -24
- package/dist/internal/build/source-maps/index.d.mts.map +1 -1
- package/dist/internal/build/source-maps/index.mjs +13 -23
- package/dist/internal/build/source-maps/index.mjs.map +1 -1
- package/dist/internal/build/source-maps/paths.d.mts +28 -0
- package/dist/internal/build/source-maps/paths.d.mts.map +1 -0
- package/dist/internal/build/source-maps/paths.mjs +49 -0
- package/dist/internal/build/source-maps/paths.mjs.map +1 -0
- package/dist/internal/build/source-maps/upload.d.mts +46 -0
- package/dist/internal/build/source-maps/upload.d.mts.map +1 -0
- package/dist/internal/build/source-maps/upload.mjs +134 -0
- package/dist/internal/build/source-maps/upload.mjs.map +1 -0
- package/dist/internal/build/value-injection-loader.mjs.map +1 -1
- package/dist/internal/env.d.mts +11 -2
- package/dist/internal/env.d.mts.map +1 -1
- package/dist/internal/env.mjs +12 -3
- package/dist/internal/env.mjs.map +1 -1
- package/dist/internal/logger.d.mts +9 -1
- package/dist/internal/logger.d.mts.map +1 -1
- package/dist/internal/logger.mjs +10 -2
- package/dist/internal/logger.mjs.map +1 -1
- package/dist/internal/release-slug.d.mts +25 -0
- package/dist/internal/release-slug.d.mts.map +1 -0
- package/dist/internal/release-slug.mjs +32 -0
- package/dist/internal/release-slug.mjs.map +1 -0
- package/dist/internal/route/handle-get.d.mts +14 -1
- package/dist/internal/route/handle-get.d.mts.map +1 -1
- package/dist/internal/route/handle-get.mjs +35 -14
- package/dist/internal/route/handle-get.mjs.map +1 -1
- package/dist/internal/route/handle-post.d.mts +11 -0
- package/dist/internal/route/handle-post.d.mts.map +1 -1
- package/dist/internal/route/handle-post.mjs +11 -50
- package/dist/internal/route/handle-post.mjs.map +1 -1
- package/dist/internal/route/proxy.d.mts +21 -1
- package/dist/internal/route/proxy.d.mts.map +1 -1
- package/dist/internal/route/proxy.mjs +61 -16
- package/dist/internal/route/proxy.mjs.map +1 -1
- package/dist/internal/server/capture.d.mts +2 -2
- package/dist/internal/server/capture.d.mts.map +1 -1
- package/dist/internal/server/capture.mjs +71 -37
- package/dist/internal/server/capture.mjs.map +1 -1
- package/dist/internal/server/console-bridge.d.mts +19 -0
- package/dist/internal/server/console-bridge.d.mts.map +1 -0
- package/dist/internal/server/console-bridge.mjs +112 -0
- package/dist/internal/server/console-bridge.mjs.map +1 -0
- package/dist/internal/server/id-generator.d.mts +38 -0
- package/dist/internal/server/id-generator.d.mts.map +1 -0
- package/dist/internal/server/id-generator.mjs +68 -0
- package/dist/internal/server/id-generator.mjs.map +1 -0
- package/dist/internal/server/instrumentation-options.d.mts +86 -0
- package/dist/internal/server/instrumentation-options.d.mts.map +1 -0
- package/dist/internal/server/instrumentation-options.mjs +1 -0
- package/dist/internal/server/remote-config.mjs +2 -2
- package/dist/internal/server/remote-config.mjs.map +1 -1
- package/dist/internal/server/trace-meta.d.mts +34 -0
- package/dist/internal/server/trace-meta.d.mts.map +1 -0
- package/dist/internal/server/trace-meta.mjs +41 -0
- package/dist/internal/server/trace-meta.mjs.map +1 -0
- package/dist/internal/server/traceparent.d.mts +16 -0
- package/dist/internal/server/traceparent.d.mts.map +1 -0
- package/dist/internal/server/traceparent.mjs +26 -0
- package/dist/internal/server/traceparent.mjs.map +1 -0
- package/dist/internal/server/types.d.mts +1 -7
- package/dist/internal/server/types.d.mts.map +1 -1
- package/dist/internal/setup-warnings.d.mts +17 -0
- package/dist/internal/setup-warnings.d.mts.map +1 -0
- package/dist/internal/setup-warnings.mjs +45 -0
- package/dist/internal/setup-warnings.mjs.map +1 -0
- package/dist/package.mjs +1 -1
- package/dist/provider.d.mts +23 -2
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +23 -1
- package/dist/provider.mjs.map +1 -0
- package/dist/route-handler.d.mts +7 -2
- package/dist/route-handler.d.mts.map +1 -1
- package/dist/route-handler.mjs +11 -9
- package/dist/route-handler.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/package.json +73 -20
- package/dist/internal/route/sw-script.d.mts +0 -4
- package/dist/internal/route/sw-script.d.mts.map +0 -1
- package/dist/internal/route/sw-script.mjs +0 -38
- package/dist/internal/route/sw-script.mjs.map +0 -1
- package/dist/internal/server/dedupe.d.mts +0 -5
- package/dist/internal/server/dedupe.d.mts.map +0 -1
- package/dist/internal/server/dedupe.mjs +0 -11
- package/dist/internal/server/dedupe.mjs.map +0 -1
- package/dist/internal/server/envelope.d.mts +0 -14
- package/dist/internal/server/envelope.d.mts.map +0 -1
- package/dist/internal/server/envelope.mjs +0 -59
- package/dist/internal/server/envelope.mjs.map +0 -1
- package/dist/internal/server/normalize-request.d.mts +0 -7
- package/dist/internal/server/normalize-request.d.mts.map +0 -1
- package/dist/internal/server/normalize-request.mjs +0 -50
- package/dist/internal/server/normalize-request.mjs.map +0 -1
- package/dist/internal/server/runtime.d.mts +0 -14
- package/dist/internal/server/runtime.d.mts.map +0 -1
- package/dist/internal/server/runtime.mjs +0 -18
- package/dist/internal/server/runtime.mjs.map +0 -1
- package/dist/internal/server/transport.d.mts +0 -12
- package/dist/internal/server/transport.d.mts.map +0 -1
- package/dist/internal/server/transport.mjs +0 -26
- package/dist/internal/server/transport.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"sourcesContent":["import type { ReleaseSlug } from \"@interfere/types/releases/slug\";\nimport type { ReleasesConfigResponse } from \"@interfere/sdk/models/releases-config-response.js\";\nimport type { CreateReleaseRequest } from \"@interfere/types/releases/definition\";\n\nimport { log } from \"../../logger.js\";\nimport { PRODUCER_VERSION } from \"../../version.js\";\nimport { destinations } from \"./destinations/index.js\";\nimport { sources } from \"./sources/index.js\";\n\nexport function resolveReleaseRequest(\n buildId: string,\n releaseSlug: ReleaseSlug,\n config: ReleasesConfigResponse\n): CreateReleaseRequest {\n if (!config.surface.sourceProvider) {\n return log.fatal(\"Missing version control provider\", [\n \"This surface does not have a version control provider configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n if (!config.surface.destinationProvider) {\n return log.fatal(\"Missing deployment target\", [\n \"This surface does not have a deployment target integration configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n return {\n source: sources[config.surface.sourceProvider](),\n destination: destinations[config.surface.destinationProvider](),\n buildId,\n slug: releaseSlug,\n producerVersion: PRODUCER_VERSION,\n };\n}\n"],"mappings":";;;;;AASA,SAAgB,sBACd,SACA,aACA,QACsB;CACtB,IAAI,CAAC,OAAO,QAAQ,gBAClB,OAAO,IAAI,MAAM,oCAAoC,CACnD,qEACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;CAGJ,IAAI,CAAC,OAAO,QAAQ,qBAClB,OAAO,IAAI,MAAM,6BAA6B,CAC5C,0EACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;CAGJ,OAAO;EACL,QAAQ,QAAQ,OAAO,QAAQ,iBAAiB;EAChD,aAAa,aAAa,OAAO,QAAQ,sBAAsB;EAC/D;EACA,MAAM;EACN,iBAAiB;EAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.mjs","names":[],"sources":["../../../../../src/internal/build/release/sources/github.ts"],"sourcesContent":["import type { ReleaseSourceMetadata } from \"@interfere/types/integrations\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { runGitCommand } from \"../git.js\";\n\nexport function resolve(): ReleaseSourceMetadata {\n return {\n provider: \"github\",\n branch:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_REF\"]) ??\n parseEnvValue(process.env[\"GITHUB_REF_NAME\"]) ??\n parseEnvValue(process.env[\"GITHUB_HEAD_REF\"]) ??\n runGitCommand(\"git rev-parse --abbrev-ref HEAD\") ??\n \"unknown\",\n commitMessage:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_MESSAGE\"]) ??\n runGitCommand(\"git log -1 --pretty=%B\") ??\n \"\",\n commitSha:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_SHA\"]) ??\n parseEnvValue(process.env[\"GITHUB_SHA\"]) ??\n runGitCommand(\"git rev-parse HEAD\"),\n };\n}\n"],"mappings":";;;AAKA,SAAgB,UAAiC;
|
|
1
|
+
{"version":3,"file":"github.mjs","names":[],"sources":["../../../../../src/internal/build/release/sources/github.ts"],"sourcesContent":["import type { ReleaseSourceMetadata } from \"@interfere/types/integrations\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { runGitCommand } from \"../git.js\";\n\nexport function resolve(): ReleaseSourceMetadata {\n return {\n provider: \"github\",\n branch:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_REF\"]) ??\n parseEnvValue(process.env[\"GITHUB_REF_NAME\"]) ??\n parseEnvValue(process.env[\"GITHUB_HEAD_REF\"]) ??\n runGitCommand(\"git rev-parse --abbrev-ref HEAD\") ??\n \"unknown\",\n commitMessage:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_MESSAGE\"]) ??\n runGitCommand(\"git log -1 --pretty=%B\") ??\n \"\",\n commitSha:\n parseEnvValue(process.env[\"VERCEL_GIT_COMMIT_SHA\"]) ??\n parseEnvValue(process.env[\"GITHUB_SHA\"]) ??\n runGitCommand(\"git rev-parse HEAD\"),\n };\n}\n"],"mappings":";;;AAKA,SAAgB,UAAiC;CAC/C,OAAO;EACL,UAAU;EACV,QACE,cAAc,QAAQ,IAAI,yBAAyB,IACnD,cAAc,QAAQ,IAAI,mBAAmB,IAC7C,cAAc,QAAQ,IAAI,mBAAmB,IAC7C,cAAc,kCAAkC,IAChD;EACF,eACE,cAAc,QAAQ,IAAI,6BAA6B,IACvD,cAAc,yBAAyB,IACvC;EACF,WACE,cAAc,QAAQ,IAAI,yBAAyB,IACnD,cAAc,QAAQ,IAAI,cAAc,IACxC,cAAc,qBAAqB;EACtC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReleaseSourceMetadata, SourceProvider } from "@interfere/types/integrations";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/build/release/sources/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Per-provider source metadata resolvers. Each entry knows how to read
|
|
6
|
+
* its platform's env vars (and falls back to local git where applicable)
|
|
7
|
+
* and returns a `ReleaseSourceMetadata` blob keyed to the provider.
|
|
8
|
+
*
|
|
9
|
+
* Adding a new provider (gitlab, bitbucket, …) is one new file under
|
|
10
|
+
* this directory + one entry here. Every consumer that walks this
|
|
11
|
+
* registry — release-create payload assembly in the build pipeline —
|
|
12
|
+
* picks up the new provider for free.
|
|
13
|
+
*
|
|
14
|
+
* Lives under `internal/build/` because resolvers shell out to git via
|
|
15
|
+
* `runGitCommand`, which depends on `node:child_process` (not Edge-safe).
|
|
16
|
+
* Anything imported from `instrumentation.ts` must stay edge-safe; this
|
|
17
|
+
* tree is build-only.
|
|
18
|
+
*/
|
|
19
|
+
declare const sources: Record<SourceProvider, () => ReleaseSourceMetadata>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { sources };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../src/internal/build/release/sources/index.ts"],"mappings":";;;;;AAsBA;;;;;;;;;;;;;cAAa,OAAA,EAAS,MAAA,CAAO,cAAA,QAAsB,qBAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolve } from "./github.mjs";
|
|
2
|
+
//#region src/internal/build/release/sources/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Per-provider source metadata resolvers. Each entry knows how to read
|
|
5
|
+
* its platform's env vars (and falls back to local git where applicable)
|
|
6
|
+
* and returns a `ReleaseSourceMetadata` blob keyed to the provider.
|
|
7
|
+
*
|
|
8
|
+
* Adding a new provider (gitlab, bitbucket, …) is one new file under
|
|
9
|
+
* this directory + one entry here. Every consumer that walks this
|
|
10
|
+
* registry — release-create payload assembly in the build pipeline —
|
|
11
|
+
* picks up the new provider for free.
|
|
12
|
+
*
|
|
13
|
+
* Lives under `internal/build/` because resolvers shell out to git via
|
|
14
|
+
* `runGitCommand`, which depends on `node:child_process` (not Edge-safe).
|
|
15
|
+
* Anything imported from `instrumentation.ts` must stay edge-safe; this
|
|
16
|
+
* tree is build-only.
|
|
17
|
+
*/
|
|
18
|
+
const sources = { github: resolve };
|
|
19
|
+
//#endregion
|
|
20
|
+
export { sources };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["resolveGithub"],"sources":["../../../../../src/internal/build/release/sources/index.ts"],"sourcesContent":["import type {\n ReleaseSourceMetadata,\n SourceProvider,\n} from \"@interfere/types/integrations\";\n\nimport { resolve as resolveGithub } from \"./github.js\";\n\n/**\n * Per-provider source metadata resolvers. Each entry knows how to read\n * its platform's env vars (and falls back to local git where applicable)\n * and returns a `ReleaseSourceMetadata` blob keyed to the provider.\n *\n * Adding a new provider (gitlab, bitbucket, …) is one new file under\n * this directory + one entry here. Every consumer that walks this\n * registry — release-create payload assembly in the build pipeline —\n * picks up the new provider for free.\n *\n * Lives under `internal/build/` because resolvers shell out to git via\n * `runGitCommand`, which depends on `node:child_process` (not Edge-safe).\n * Anything imported from `instrumentation.ts` must stay edge-safe; this\n * tree is build-only.\n */\nexport const sources: Record<SourceProvider, () => ReleaseSourceMetadata> = {\n github: resolveGithub,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,MAAa,UAA+D,EAC1E,QAAQA,SACT"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SourceMapFile } from "./discover.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/build/source-maps/discover-turbopack.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Turbopack-mode discovery.
|
|
6
|
+
*
|
|
7
|
+
* Turbopack hashes the JS chunk and its source map independently, so the
|
|
8
|
+
* `//# sourceMappingURL=…` URL embedded in the chunk never matches the
|
|
9
|
+
* on-disk `.js.map` filename. When `experimental.turbo.debugIds` is on
|
|
10
|
+
* (we set it for the user in `configure-build.ts`), turbopack natively
|
|
11
|
+
* injects matching `debugId` values into both the chunk
|
|
12
|
+
* (`//# debugId=…`) and the source map (`"debugId": "…"`).
|
|
13
|
+
*
|
|
14
|
+
* We pair JS chunks ↔ source maps by `debugId` (turbopack's natural key)
|
|
15
|
+
* and additionally record each chunk's public URL as `chunkUrl` so the
|
|
16
|
+
* server-side suffix-match resolver has a uniform field to look up by
|
|
17
|
+
* `frame.fileName` regardless of bundler. The webpack discoverer
|
|
18
|
+
* populates the same field directly from the chunk's URL; the turbopack
|
|
19
|
+
* discoverer derives it from the JS chunk's on-disk path.
|
|
20
|
+
*
|
|
21
|
+
* Maps without a debugId, or whose debugId never appears in any chunk,
|
|
22
|
+
* are silently skipped — they have no way to resolve at runtime.
|
|
23
|
+
*/
|
|
24
|
+
declare function discoverTurbopack(opts: {
|
|
25
|
+
projectDir: string;
|
|
26
|
+
distDir: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
files: SourceMapFile[];
|
|
29
|
+
sourceFileCount: number;
|
|
30
|
+
}>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { discoverTurbopack };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-turbopack.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/discover-turbopack.ts"],"mappings":";;;;;AA6BA;;;;;;;;;;;;;;;;;;iBAAsB,iBAAA,CAAkB,IAAA;EACtC,UAAA;EACA,OAAA;AAAA,IACE,OAAA;EAAU,KAAA,EAAO,aAAA;EAAiB,eAAA;AAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { resolveDistDir, toPublicPath, walkDistTrees } from "./paths.mjs";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
//#region src/internal/build/source-maps/discover-turbopack.ts
|
|
6
|
+
const DEBUG_ID_RE = /\/\/[#@]\s*debugId=([0-9a-f-]+)\s*$/m;
|
|
7
|
+
/**
|
|
8
|
+
* Turbopack-mode discovery.
|
|
9
|
+
*
|
|
10
|
+
* Turbopack hashes the JS chunk and its source map independently, so the
|
|
11
|
+
* `//# sourceMappingURL=…` URL embedded in the chunk never matches the
|
|
12
|
+
* on-disk `.js.map` filename. When `experimental.turbo.debugIds` is on
|
|
13
|
+
* (we set it for the user in `configure-build.ts`), turbopack natively
|
|
14
|
+
* injects matching `debugId` values into both the chunk
|
|
15
|
+
* (`//# debugId=…`) and the source map (`"debugId": "…"`).
|
|
16
|
+
*
|
|
17
|
+
* We pair JS chunks ↔ source maps by `debugId` (turbopack's natural key)
|
|
18
|
+
* and additionally record each chunk's public URL as `chunkUrl` so the
|
|
19
|
+
* server-side suffix-match resolver has a uniform field to look up by
|
|
20
|
+
* `frame.fileName` regardless of bundler. The webpack discoverer
|
|
21
|
+
* populates the same field directly from the chunk's URL; the turbopack
|
|
22
|
+
* discoverer derives it from the JS chunk's on-disk path.
|
|
23
|
+
*
|
|
24
|
+
* Maps without a debugId, or whose debugId never appears in any chunk,
|
|
25
|
+
* are silently skipped — they have no way to resolve at runtime.
|
|
26
|
+
*/
|
|
27
|
+
async function discoverTurbopack(opts) {
|
|
28
|
+
const absDistDir = resolveDistDir(opts.projectDir, opts.distDir);
|
|
29
|
+
const relDistDir = relative(opts.projectDir, absDistDir);
|
|
30
|
+
const [mapPaths, jsPaths] = await Promise.all([walkDistTrees(absDistDir, ".js.map"), walkDistTrees(absDistDir, ".js")]);
|
|
31
|
+
const debugIdToChunkUrl = /* @__PURE__ */ new Map();
|
|
32
|
+
await Promise.all(jsPaths.map(async (jsAbs) => {
|
|
33
|
+
const content = await readFile(jsAbs, "utf8").catch(() => null);
|
|
34
|
+
if (content === null) return;
|
|
35
|
+
const debugId = DEBUG_ID_RE.exec(content)?.[1];
|
|
36
|
+
if (!debugId) return;
|
|
37
|
+
const chunkUrl = toPublicPath(relative(opts.projectDir, jsAbs), relDistDir);
|
|
38
|
+
if (!debugIdToChunkUrl.has(debugId)) debugIdToChunkUrl.set(debugId, chunkUrl);
|
|
39
|
+
}));
|
|
40
|
+
const loaded = (await Promise.all(mapPaths.map(async (mapAbs) => {
|
|
41
|
+
const content = await readFile(mapAbs, "utf8").catch(() => null);
|
|
42
|
+
if (content === null) return null;
|
|
43
|
+
let debugId;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(content);
|
|
46
|
+
if (typeof parsed.debugId === "string" && parsed.debugId.length > 0) debugId = parsed.debugId;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (!debugId) return null;
|
|
51
|
+
const chunkUrl = debugIdToChunkUrl.get(debugId);
|
|
52
|
+
if (!chunkUrl) return null;
|
|
53
|
+
return {
|
|
54
|
+
absolute: mapAbs,
|
|
55
|
+
path: toPublicPath(relative(opts.projectDir, mapAbs), relDistDir),
|
|
56
|
+
content,
|
|
57
|
+
hash: createHash("sha256").update(content).digest("hex"),
|
|
58
|
+
debugId,
|
|
59
|
+
chunkUrl
|
|
60
|
+
};
|
|
61
|
+
}))).filter((f) => f !== null);
|
|
62
|
+
return {
|
|
63
|
+
files: loaded,
|
|
64
|
+
sourceFileCount: loaded.length
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { discoverTurbopack };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-turbopack.mjs","names":[],"sources":["../../../../src/internal/build/source-maps/discover-turbopack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\n\nimport type { SourceMapFile } from \"./discover.js\";\nimport { resolveDistDir, toPublicPath, walkDistTrees } from \"./paths.js\";\n\nconst DEBUG_ID_RE = /\\/\\/[#@]\\s*debugId=([0-9a-f-]+)\\s*$/m;\n\n/**\n * Turbopack-mode discovery.\n *\n * Turbopack hashes the JS chunk and its source map independently, so the\n * `//# sourceMappingURL=…` URL embedded in the chunk never matches the\n * on-disk `.js.map` filename. When `experimental.turbo.debugIds` is on\n * (we set it for the user in `configure-build.ts`), turbopack natively\n * injects matching `debugId` values into both the chunk\n * (`//# debugId=…`) and the source map (`\"debugId\": \"…\"`).\n *\n * We pair JS chunks ↔ source maps by `debugId` (turbopack's natural key)\n * and additionally record each chunk's public URL as `chunkUrl` so the\n * server-side suffix-match resolver has a uniform field to look up by\n * `frame.fileName` regardless of bundler. The webpack discoverer\n * populates the same field directly from the chunk's URL; the turbopack\n * discoverer derives it from the JS chunk's on-disk path.\n *\n * Maps without a debugId, or whose debugId never appears in any chunk,\n * are silently skipped — they have no way to resolve at runtime.\n */\nexport async function discoverTurbopack(opts: {\n projectDir: string;\n distDir: string;\n}): Promise<{ files: SourceMapFile[]; sourceFileCount: number }> {\n const absDistDir = resolveDistDir(opts.projectDir, opts.distDir);\n const relDistDir = relative(opts.projectDir, absDistDir);\n\n const [mapPaths, jsPaths] = await Promise.all([\n walkDistTrees(absDistDir, \".js.map\"),\n walkDistTrees(absDistDir, \".js\"),\n ]);\n\n // Walk JS chunks once and build `debugId → publicUrl` so a single\n // pass over the maps can attach `chunkUrl` per entry.\n const debugIdToChunkUrl = new Map<string, string>();\n await Promise.all(\n jsPaths.map(async (jsAbs) => {\n const content = await readFile(jsAbs, \"utf8\").catch(() => null);\n if (content === null) {\n return;\n }\n const match = DEBUG_ID_RE.exec(content);\n const debugId = match?.[1];\n if (!debugId) {\n return;\n }\n const chunkUrl = toPublicPath(\n relative(opts.projectDir, jsAbs),\n relDistDir\n );\n // First write wins — collisions across .js shouldn't happen with\n // content-hashed chunk filenames, but if they do, the first walked\n // entry is the deterministic choice.\n if (!debugIdToChunkUrl.has(debugId)) {\n debugIdToChunkUrl.set(debugId, chunkUrl);\n }\n })\n );\n\n const files = await Promise.all(\n mapPaths.map(async (mapAbs): Promise<SourceMapFile | null> => {\n const content = await readFile(mapAbs, \"utf8\").catch(() => null);\n if (content === null) {\n return null;\n }\n\n let debugId: string | undefined;\n try {\n const parsed = JSON.parse(content) as { debugId?: unknown };\n if (typeof parsed.debugId === \"string\" && parsed.debugId.length > 0) {\n debugId = parsed.debugId;\n }\n } catch {\n return null;\n }\n\n if (!debugId) {\n return null;\n }\n\n const chunkUrl = debugIdToChunkUrl.get(debugId);\n if (!chunkUrl) {\n // No JS chunk in the dist tree carries this debugId — the map\n // can't pair to a runtime URL, so it's unresolvable. Drop it\n // rather than emitting an unusable manifest entry.\n return null;\n }\n\n return {\n absolute: mapAbs,\n path: toPublicPath(relative(opts.projectDir, mapAbs), relDistDir),\n content,\n hash: createHash(\"sha256\").update(content).digest(\"hex\"),\n debugId,\n chunkUrl,\n } satisfies SourceMapFile;\n })\n );\n\n const loaded = files.filter((f): f is SourceMapFile => f !== null);\n\n return {\n files: loaded,\n sourceFileCount: loaded.length,\n };\n}\n"],"mappings":";;;;;AAOA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;AAsBpB,eAAsB,kBAAkB,MAGyB;CAC/D,MAAM,aAAa,eAAe,KAAK,YAAY,KAAK,QAAQ;CAChE,MAAM,aAAa,SAAS,KAAK,YAAY,WAAW;CAExD,MAAM,CAAC,UAAU,WAAW,MAAM,QAAQ,IAAI,CAC5C,cAAc,YAAY,UAAU,EACpC,cAAc,YAAY,MAAM,CACjC,CAAC;CAIF,MAAM,oCAAoB,IAAI,KAAqB;CACnD,MAAM,QAAQ,IACZ,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,UAAU,MAAM,SAAS,OAAO,OAAO,CAAC,YAAY,KAAK;EAC/D,IAAI,YAAY,MACd;EAGF,MAAM,UADQ,YAAY,KAAK,QACV,GAAG;EACxB,IAAI,CAAC,SACH;EAEF,MAAM,WAAW,aACf,SAAS,KAAK,YAAY,MAAM,EAChC,WACD;EAID,IAAI,CAAC,kBAAkB,IAAI,QAAQ,EACjC,kBAAkB,IAAI,SAAS,SAAS;GAE1C,CACH;CA0CD,MAAM,UAAS,MAxCK,QAAQ,IAC1B,SAAS,IAAI,OAAO,WAA0C;EAC5D,MAAM,UAAU,MAAM,SAAS,QAAQ,OAAO,CAAC,YAAY,KAAK;EAChE,IAAI,YAAY,MACd,OAAO;EAGT,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,QAAQ;GAClC,IAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,SAAS,GAChE,UAAU,OAAO;UAEb;GACN,OAAO;;EAGT,IAAI,CAAC,SACH,OAAO;EAGT,MAAM,WAAW,kBAAkB,IAAI,QAAQ;EAC/C,IAAI,CAAC,UAIH,OAAO;EAGT,OAAO;GACL,UAAU;GACV,MAAM,aAAa,SAAS,KAAK,YAAY,OAAO,EAAE,WAAW;GACjE;GACA,MAAM,WAAW,SAAS,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM;GACxD;GACA;GACD;GACD,CACH,EAEoB,QAAQ,MAA0B,MAAM,KAAK;CAElE,OAAO;EACL,OAAO;EACP,iBAAiB,OAAO;EACzB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SourceMapFile } from "./discover.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/build/source-maps/discover-webpack.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reads an existing `//# debugId=…` comment from the bundle source.
|
|
6
|
+
* Returns `null` when no debug ID comment is present — the caller is
|
|
7
|
+
* expected to generate and inject one.
|
|
8
|
+
*/
|
|
9
|
+
declare function readDebugIdFromJs(content: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a
|
|
12
|
+
* spec-compliant comment is already present (e.g. webpack 5.104+ with
|
|
13
|
+
* `SourceMapDevToolPlugin({ debugIds: true })`, turbopack with
|
|
14
|
+
* `experimental.turbopack.debugIds`, or any other tool) it's
|
|
15
|
+
* overwritten with the supplied debugId — callers that read the
|
|
16
|
+
* existing id via `readDebugIdFromJs` first get an idempotent no-op.
|
|
17
|
+
*
|
|
18
|
+
* Otherwise the comment is appended just before the existing
|
|
19
|
+
* `//# sourceMappingURL=…` line so the browser still resolves the map.
|
|
20
|
+
*/
|
|
21
|
+
declare function injectDebugIdIntoJs(content: string, debugId: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Adds `debugId` to the source-map JSON. Idempotent: writes both
|
|
24
|
+
* `debugId` (TC39 spec field) and `debug_id` (legacy variant some
|
|
25
|
+
* older Sentry tooling emits) so manifests stay readable across the
|
|
26
|
+
* ecosystem. Re-running with the same id produces identical output.
|
|
27
|
+
*/
|
|
28
|
+
declare function injectDebugIdIntoMap(content: string, debugId: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Webpack-mode discovery: walks the dist tree, parses each `.js`
|
|
31
|
+
* chunk's `//# sourceMappingURL=…` comment to find its paired `.map`,
|
|
32
|
+
* and ensures both carry the same `debugId`.
|
|
33
|
+
*
|
|
34
|
+
* Idempotent w.r.t. debug-id state on disk:
|
|
35
|
+
* - If the chunk already carries a `//# debugId=…` comment (webpack
|
|
36
|
+
* 5.104+ with `SourceMapDevToolPlugin({ debugIds: true })`, or a
|
|
37
|
+
* third-party tool that already injected one) we reuse it.
|
|
38
|
+
* - Otherwise we generate a fresh UUID and inject it into both files.
|
|
39
|
+
*
|
|
40
|
+
* The "reuse if present" branch matches the strategy in Sentry's
|
|
41
|
+
* webpack plugin (`addDebugIdToBundleSource` in
|
|
42
|
+
* `bundler-plugin-core/src/debug-id-upload.ts`) and means our
|
|
43
|
+
* discovery works transparently when bundlers natively emit debugIds.
|
|
44
|
+
*/
|
|
45
|
+
declare function discoverWebpack(opts: {
|
|
46
|
+
projectDir: string;
|
|
47
|
+
distDir: string;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
files: SourceMapFile[];
|
|
50
|
+
sourceFileCount: number;
|
|
51
|
+
}>;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { discoverWebpack, injectDebugIdIntoJs, injectDebugIdIntoMap, readDebugIdFromJs };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-webpack.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/discover-webpack.ts"],"mappings":";;;;;AAwBA;;;iBAAgB,iBAAA,CAAkB,OAAA;;AAgBlC;;;;;AAuBA;;;;;iBAvBgB,mBAAA,CAAoB,OAAA,UAAiB,OAAA;;;;;;;iBAuBrC,oBAAA,CAAqB,OAAA,UAAiB,OAAA;;;;;;;;;;;;;;;;;iBAsBhC,eAAA,CAAgB,IAAA;EACpC,UAAA;EACA,OAAA;AAAA,IACE,OAAA;EAAU,KAAA,EAAO,aAAA;EAAiB,eAAA;AAAA"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { resolveDistDir, toPublicPath, walkDistTrees } from "./paths.mjs";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
//#region src/internal/build/source-maps/discover-webpack.ts
|
|
6
|
+
const SOURCEMAPPING_RE = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
|
|
7
|
+
/**
|
|
8
|
+
* Per the TC39 sourcemap-debug-id proposal, the LAST `//# debugId=…`
|
|
9
|
+
* comment in the bundle is the canonical one. Same regex Sentry uses
|
|
10
|
+
* to detect spec-compliant debug IDs already injected by another tool
|
|
11
|
+
* (webpack 5.104+ `SourceMapDevToolPlugin({ debugIds: true })`,
|
|
12
|
+
* turbopack `experimental.turbopack.debugIds`, rollup, vite, etc.).
|
|
13
|
+
*/
|
|
14
|
+
const SPEC_LAST_DEBUG_ID_RE = /\/\/# debugId=([a-fA-F0-9-]+)(?![\s\S]*\/\/# debugId=)/m;
|
|
15
|
+
/**
|
|
16
|
+
* Reads an existing `//# debugId=…` comment from the bundle source.
|
|
17
|
+
* Returns `null` when no debug ID comment is present — the caller is
|
|
18
|
+
* expected to generate and inject one.
|
|
19
|
+
*/
|
|
20
|
+
function readDebugIdFromJs(content) {
|
|
21
|
+
return SPEC_LAST_DEBUG_ID_RE.exec(content)?.[1] ?? null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a
|
|
25
|
+
* spec-compliant comment is already present (e.g. webpack 5.104+ with
|
|
26
|
+
* `SourceMapDevToolPlugin({ debugIds: true })`, turbopack with
|
|
27
|
+
* `experimental.turbopack.debugIds`, or any other tool) it's
|
|
28
|
+
* overwritten with the supplied debugId — callers that read the
|
|
29
|
+
* existing id via `readDebugIdFromJs` first get an idempotent no-op.
|
|
30
|
+
*
|
|
31
|
+
* Otherwise the comment is appended just before the existing
|
|
32
|
+
* `//# sourceMappingURL=…` line so the browser still resolves the map.
|
|
33
|
+
*/
|
|
34
|
+
function injectDebugIdIntoJs(content, debugId) {
|
|
35
|
+
const debugIdComment = `//# debugId=${debugId}`;
|
|
36
|
+
if (SPEC_LAST_DEBUG_ID_RE.test(content)) return content.replace(SPEC_LAST_DEBUG_ID_RE, debugIdComment);
|
|
37
|
+
const match = SOURCEMAPPING_RE.exec(content);
|
|
38
|
+
if (match) return `${content.slice(0, match.index).trimEnd()}\n${debugIdComment}\n${match[0]}`;
|
|
39
|
+
return `${content}\n${debugIdComment}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Adds `debugId` to the source-map JSON. Idempotent: writes both
|
|
43
|
+
* `debugId` (TC39 spec field) and `debug_id` (legacy variant some
|
|
44
|
+
* older Sentry tooling emits) so manifests stay readable across the
|
|
45
|
+
* ecosystem. Re-running with the same id produces identical output.
|
|
46
|
+
*/
|
|
47
|
+
function injectDebugIdIntoMap(content, debugId) {
|
|
48
|
+
const json = JSON.parse(content);
|
|
49
|
+
json["debugId"] = debugId;
|
|
50
|
+
return JSON.stringify(json);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Webpack-mode discovery: walks the dist tree, parses each `.js`
|
|
54
|
+
* chunk's `//# sourceMappingURL=…` comment to find its paired `.map`,
|
|
55
|
+
* and ensures both carry the same `debugId`.
|
|
56
|
+
*
|
|
57
|
+
* Idempotent w.r.t. debug-id state on disk:
|
|
58
|
+
* - If the chunk already carries a `//# debugId=…` comment (webpack
|
|
59
|
+
* 5.104+ with `SourceMapDevToolPlugin({ debugIds: true })`, or a
|
|
60
|
+
* third-party tool that already injected one) we reuse it.
|
|
61
|
+
* - Otherwise we generate a fresh UUID and inject it into both files.
|
|
62
|
+
*
|
|
63
|
+
* The "reuse if present" branch matches the strategy in Sentry's
|
|
64
|
+
* webpack plugin (`addDebugIdToBundleSource` in
|
|
65
|
+
* `bundler-plugin-core/src/debug-id-upload.ts`) and means our
|
|
66
|
+
* discovery works transparently when bundlers natively emit debugIds.
|
|
67
|
+
*/
|
|
68
|
+
async function discoverWebpack(opts) {
|
|
69
|
+
const absDistDir = resolveDistDir(opts.projectDir, opts.distDir);
|
|
70
|
+
const relDistDir = relative(opts.projectDir, absDistDir);
|
|
71
|
+
const jsPaths = await walkDistTrees(absDistDir, ".js");
|
|
72
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const jsAbs of jsPaths) {
|
|
74
|
+
const content = await readFile(jsAbs, "utf8");
|
|
75
|
+
const match = SOURCEMAPPING_RE.exec(content);
|
|
76
|
+
if (!match?.[1]) continue;
|
|
77
|
+
const mapAbs = `${jsAbs.slice(0, jsAbs.lastIndexOf("/") + 1)}${decodeURIComponent(match[1])}`;
|
|
78
|
+
const jsPublic = toPublicPath(relative(opts.projectDir, jsAbs), relDistDir);
|
|
79
|
+
const mapPublic = toPublicPath(relative(opts.projectDir, mapAbs), relDistDir);
|
|
80
|
+
pairs.set(mapPublic, {
|
|
81
|
+
mapAbs,
|
|
82
|
+
jsAbs,
|
|
83
|
+
jsPublic,
|
|
84
|
+
jsContent: content,
|
|
85
|
+
existingDebugId: readDebugIdFromJs(content)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
files: (await Promise.all(Array.from(pairs.entries(), async ([mapPublic, pair]) => {
|
|
90
|
+
const mapContent = await readFile(pair.mapAbs, "utf8").catch(() => null);
|
|
91
|
+
if (mapContent === null) return null;
|
|
92
|
+
const debugId = pair.existingDebugId ?? randomUUID();
|
|
93
|
+
if (!pair.existingDebugId) {
|
|
94
|
+
const injectedJs = injectDebugIdIntoJs(pair.jsContent, debugId);
|
|
95
|
+
await writeFile(pair.jsAbs, injectedJs, "utf8");
|
|
96
|
+
}
|
|
97
|
+
const finalMap = JSON.parse(mapContent)["debugId"] === debugId ? mapContent : injectDebugIdIntoMap(mapContent, debugId);
|
|
98
|
+
if (finalMap !== mapContent) await writeFile(pair.mapAbs, finalMap, "utf8");
|
|
99
|
+
return {
|
|
100
|
+
absolute: pair.mapAbs,
|
|
101
|
+
path: mapPublic,
|
|
102
|
+
content: finalMap,
|
|
103
|
+
hash: createHash("sha256").update(finalMap).digest("hex"),
|
|
104
|
+
debugId,
|
|
105
|
+
chunkUrl: pair.jsPublic
|
|
106
|
+
};
|
|
107
|
+
}))).filter((f) => f !== null),
|
|
108
|
+
sourceFileCount: pairs.size
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { discoverWebpack, injectDebugIdIntoJs, injectDebugIdIntoMap, readDebugIdFromJs };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-webpack.mjs","names":[],"sources":["../../../../src/internal/build/source-maps/discover-webpack.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\n\nimport type { SourceMapFile } from \"./discover.js\";\nimport { resolveDistDir, toPublicPath, walkDistTrees } from \"./paths.js\";\n\nconst SOURCEMAPPING_RE = /\\/\\/[#@]\\s*sourceMappingURL=(\\S+)\\s*$/;\n\n/**\n * Per the TC39 sourcemap-debug-id proposal, the LAST `//# debugId=…`\n * comment in the bundle is the canonical one. Same regex Sentry uses\n * to detect spec-compliant debug IDs already injected by another tool\n * (webpack 5.104+ `SourceMapDevToolPlugin({ debugIds: true })`,\n * turbopack `experimental.turbopack.debugIds`, rollup, vite, etc.).\n */\nconst SPEC_LAST_DEBUG_ID_RE =\n /\\/\\/# debugId=([a-fA-F0-9-]+)(?![\\s\\S]*\\/\\/# debugId=)/m;\n\n/**\n * Reads an existing `//# debugId=…` comment from the bundle source.\n * Returns `null` when no debug ID comment is present — the caller is\n * expected to generate and inject one.\n */\nexport function readDebugIdFromJs(content: string): string | null {\n const match = SPEC_LAST_DEBUG_ID_RE.exec(content);\n return match?.[1] ?? null;\n}\n\n/**\n * Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a\n * spec-compliant comment is already present (e.g. webpack 5.104+ with\n * `SourceMapDevToolPlugin({ debugIds: true })`, turbopack with\n * `experimental.turbopack.debugIds`, or any other tool) it's\n * overwritten with the supplied debugId — callers that read the\n * existing id via `readDebugIdFromJs` first get an idempotent no-op.\n *\n * Otherwise the comment is appended just before the existing\n * `//# sourceMappingURL=…` line so the browser still resolves the map.\n */\nexport function injectDebugIdIntoJs(content: string, debugId: string): string {\n const debugIdComment = `//# debugId=${debugId}`;\n\n if (SPEC_LAST_DEBUG_ID_RE.test(content)) {\n return content.replace(SPEC_LAST_DEBUG_ID_RE, debugIdComment);\n }\n\n const match = SOURCEMAPPING_RE.exec(content);\n if (match) {\n const before = content.slice(0, match.index).trimEnd();\n const sourceMappingLine = match[0];\n return `${before}\\n${debugIdComment}\\n${sourceMappingLine}`;\n }\n\n return `${content}\\n${debugIdComment}`;\n}\n\n/**\n * Adds `debugId` to the source-map JSON. Idempotent: writes both\n * `debugId` (TC39 spec field) and `debug_id` (legacy variant some\n * older Sentry tooling emits) so manifests stay readable across the\n * ecosystem. Re-running with the same id produces identical output.\n */\nexport function injectDebugIdIntoMap(content: string, debugId: string): string {\n const json = JSON.parse(content) as Record<string, unknown>;\n json[\"debugId\"] = debugId;\n return JSON.stringify(json);\n}\n\n/**\n * Webpack-mode discovery: walks the dist tree, parses each `.js`\n * chunk's `//# sourceMappingURL=…` comment to find its paired `.map`,\n * and ensures both carry the same `debugId`.\n *\n * Idempotent w.r.t. debug-id state on disk:\n * - If the chunk already carries a `//# debugId=…` comment (webpack\n * 5.104+ with `SourceMapDevToolPlugin({ debugIds: true })`, or a\n * third-party tool that already injected one) we reuse it.\n * - Otherwise we generate a fresh UUID and inject it into both files.\n *\n * The \"reuse if present\" branch matches the strategy in Sentry's\n * webpack plugin (`addDebugIdToBundleSource` in\n * `bundler-plugin-core/src/debug-id-upload.ts`) and means our\n * discovery works transparently when bundlers natively emit debugIds.\n */\nexport async function discoverWebpack(opts: {\n projectDir: string;\n distDir: string;\n}): Promise<{ files: SourceMapFile[]; sourceFileCount: number }> {\n const absDistDir = resolveDistDir(opts.projectDir, opts.distDir);\n const relDistDir = relative(opts.projectDir, absDistDir);\n\n const jsPaths = await walkDistTrees(absDistDir, \".js\");\n\n const pairs = new Map<\n string,\n {\n mapAbs: string;\n jsAbs: string;\n jsPublic: string;\n jsContent: string;\n existingDebugId: string | null;\n }\n >();\n\n for (const jsAbs of jsPaths) {\n const content = await readFile(jsAbs, \"utf8\");\n const match = SOURCEMAPPING_RE.exec(content);\n if (!match?.[1]) {\n continue;\n }\n\n const jsDir = jsAbs.slice(0, jsAbs.lastIndexOf(\"/\") + 1);\n const mapRef = decodeURIComponent(match[1]);\n const mapAbs = `${jsDir}${mapRef}`;\n const jsPublic = toPublicPath(relative(opts.projectDir, jsAbs), relDistDir);\n const mapPublic = toPublicPath(\n relative(opts.projectDir, mapAbs),\n relDistDir\n );\n\n pairs.set(mapPublic, {\n mapAbs,\n jsAbs,\n jsPublic,\n jsContent: content,\n existingDebugId: readDebugIdFromJs(content),\n });\n }\n\n const files = await Promise.all(\n Array.from(\n pairs.entries(),\n async ([mapPublic, pair]): Promise<SourceMapFile | null> => {\n const mapContent = await readFile(pair.mapAbs, \"utf8\").catch(\n () => null\n );\n if (mapContent === null) {\n return null;\n }\n\n const debugId = pair.existingDebugId ?? randomUUID();\n\n // Skip the rewrite when the bundler already injected the\n // canonical debug-id comment + source-map field (webpack\n // native debugIds, turbopack, etc.). Re-running with the\n // same id is a no-op anyway, but skipping the I/O is faster\n // and keeps file mtimes stable for incremental builds.\n if (!pair.existingDebugId) {\n const injectedJs = injectDebugIdIntoJs(pair.jsContent, debugId);\n await writeFile(pair.jsAbs, injectedJs, \"utf8\");\n }\n\n const mapJson = JSON.parse(mapContent) as Record<string, unknown>;\n const finalMap =\n mapJson[\"debugId\"] === debugId\n ? mapContent\n : injectDebugIdIntoMap(mapContent, debugId);\n if (finalMap !== mapContent) {\n await writeFile(pair.mapAbs, finalMap, \"utf8\");\n }\n\n return {\n absolute: pair.mapAbs,\n path: mapPublic,\n content: finalMap,\n hash: createHash(\"sha256\").update(finalMap).digest(\"hex\"),\n debugId,\n chunkUrl: pair.jsPublic,\n };\n }\n )\n );\n\n const loaded = files.filter((f): f is SourceMapFile => f !== null);\n\n return {\n files: loaded,\n sourceFileCount: pairs.size,\n };\n}\n"],"mappings":";;;;;AAOA,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,wBACJ;;;;;;AAOF,SAAgB,kBAAkB,SAAgC;CAEhE,OADc,sBAAsB,KAAK,QAC7B,GAAG,MAAM;;;;;;;;;;;;;AAcvB,SAAgB,oBAAoB,SAAiB,SAAyB;CAC5E,MAAM,iBAAiB,eAAe;CAEtC,IAAI,sBAAsB,KAAK,QAAQ,EACrC,OAAO,QAAQ,QAAQ,uBAAuB,eAAe;CAG/D,MAAM,QAAQ,iBAAiB,KAAK,QAAQ;CAC5C,IAAI,OAGF,OAAO,GAFQ,QAAQ,MAAM,GAAG,MAAM,MAAM,CAAC,SAE7B,CAAC,IAAI,eAAe,IADV,MAAM;CAIlC,OAAO,GAAG,QAAQ,IAAI;;;;;;;;AASxB,SAAgB,qBAAqB,SAAiB,SAAyB;CAC7E,MAAM,OAAO,KAAK,MAAM,QAAQ;CAChC,KAAK,aAAa;CAClB,OAAO,KAAK,UAAU,KAAK;;;;;;;;;;;;;;;;;;AAmB7B,eAAsB,gBAAgB,MAG2B;CAC/D,MAAM,aAAa,eAAe,KAAK,YAAY,KAAK,QAAQ;CAChE,MAAM,aAAa,SAAS,KAAK,YAAY,WAAW;CAExD,MAAM,UAAU,MAAM,cAAc,YAAY,MAAM;CAEtD,MAAM,wBAAQ,IAAI,KASf;CAEH,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,UAAU,MAAM,SAAS,OAAO,OAAO;EAC7C,MAAM,QAAQ,iBAAiB,KAAK,QAAQ;EAC5C,IAAI,CAAC,QAAQ,IACX;EAKF,MAAM,SAAS,GAFD,MAAM,MAAM,GAAG,MAAM,YAAY,IAAI,GAAG,EAE/B,GADR,mBAAmB,MAAM,GACR;EAChC,MAAM,WAAW,aAAa,SAAS,KAAK,YAAY,MAAM,EAAE,WAAW;EAC3E,MAAM,YAAY,aAChB,SAAS,KAAK,YAAY,OAAO,EACjC,WACD;EAED,MAAM,IAAI,WAAW;GACnB;GACA;GACA;GACA,WAAW;GACX,iBAAiB,kBAAkB,QAAQ;GAC5C,CAAC;;CAiDJ,OAAO;EACL,QAHa,MA5CK,QAAQ,IAC1B,MAAM,KACJ,MAAM,SAAS,EACf,OAAO,CAAC,WAAW,UAAyC;GAC1D,MAAM,aAAa,MAAM,SAAS,KAAK,QAAQ,OAAO,CAAC,YAC/C,KACP;GACD,IAAI,eAAe,MACjB,OAAO;GAGT,MAAM,UAAU,KAAK,mBAAmB,YAAY;GAOpD,IAAI,CAAC,KAAK,iBAAiB;IACzB,MAAM,aAAa,oBAAoB,KAAK,WAAW,QAAQ;IAC/D,MAAM,UAAU,KAAK,OAAO,YAAY,OAAO;;GAIjD,MAAM,WADU,KAAK,MAAM,WAElB,CAAC,eAAe,UACnB,aACA,qBAAqB,YAAY,QAAQ;GAC/C,IAAI,aAAa,YACf,MAAM,UAAU,KAAK,QAAQ,UAAU,OAAO;GAGhD,OAAO;IACL,UAAU,KAAK;IACf,MAAM;IACN,SAAS;IACT,MAAM,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,MAAM;IACzD;IACA,UAAU,KAAK;IAChB;IAEJ,CACF,EAEoB,QAAQ,MAA0B,MAAM,KAG9C;EACb,iBAAiB,MAAM;EACxB"}
|
|
@@ -1,19 +1,37 @@
|
|
|
1
|
+
import { injectDebugIdIntoJs, injectDebugIdIntoMap } from "./discover-webpack.mjs";
|
|
2
|
+
import { ManifestBundler } from "@interfere/types/data/source-maps";
|
|
3
|
+
|
|
1
4
|
//#region src/internal/build/source-maps/discover.d.ts
|
|
2
5
|
interface SourceMapFile {
|
|
3
6
|
readonly absolute: string;
|
|
7
|
+
/**
|
|
8
|
+
* Public URL the runtime will load this chunk from. ALWAYS populated
|
|
9
|
+
* for both bundlers — turbopack discovery walks JS chunks (in addition
|
|
10
|
+
* to .map files) so it can record this. Lifted onto the manifest as
|
|
11
|
+
* `chunkUrl`; server-side suffix-matching pairs `frame.fileName` to
|
|
12
|
+
* this field by content-hashed filename.
|
|
13
|
+
*/
|
|
14
|
+
readonly chunkUrl: string;
|
|
4
15
|
readonly content: string;
|
|
5
16
|
readonly debugId: string;
|
|
6
17
|
readonly hash: string;
|
|
7
18
|
readonly path: string;
|
|
8
19
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
interface DiscoveryResult {
|
|
21
|
+
readonly files: SourceMapFile[];
|
|
22
|
+
readonly sourceFileCount: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Single entry point for source-map discovery. The pipeline calls this
|
|
26
|
+
* with the active bundler from `detectActiveBundler()`; new bundlers (e.g.
|
|
27
|
+
* rspack) drop in here as additional cases without touching the pipeline.
|
|
28
|
+
* `"tsc"` is excluded — non-bundler producers live outside the Next build
|
|
29
|
+
* pipeline and go through `@interfere/cli` instead.
|
|
30
|
+
*/
|
|
31
|
+
declare function discoverSourceMaps(opts: {
|
|
32
|
+
bundler: Exclude<ManifestBundler, "tsc">;
|
|
33
|
+
projectDir: string;
|
|
34
|
+
distDir: string;
|
|
35
|
+
}): Promise<DiscoveryResult>;
|
|
18
36
|
//#endregion
|
|
19
|
-
export {
|
|
37
|
+
export { DiscoveryResult, SourceMapFile, discoverSourceMaps, injectDebugIdIntoJs, injectDebugIdIntoMap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/discover.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"discover.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/discover.ts"],"mappings":";;;;UAMiB,aAAA;EAAA,SACN,QAAA;;AADX;;;;;;WASW,QAAA;EAAA,SACA,OAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA;AAAA;AAAA,UAGM,eAAA;EAAA,SACN,KAAA,EAAO,aAAA;EAAA,SACP,eAAA;AAAA;;;;;;AAUX;;iBAAsB,kBAAA,CAAmB,IAAA;EACvC,OAAA,EAAS,OAAA,CAAQ,eAAA;EACjB,UAAA;EACA,OAAA;AAAA,IACE,OAAA,CAAQ,eAAA"}
|
|
@@ -1,87 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { normalizeDistDir } from "./paths.mjs";
|
|
2
|
+
import { discoverTurbopack } from "./discover-turbopack.mjs";
|
|
3
|
+
import { discoverWebpack, injectDebugIdIntoJs, injectDebugIdIntoMap } from "./discover-webpack.mjs";
|
|
4
4
|
//#region src/internal/build/source-maps/discover.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const json = JSON.parse(content);
|
|
24
|
-
json["debugId"] = debugId;
|
|
25
|
-
if (typeof json["mappings"] === "string") json["mappings"] = `;${json["mappings"]}`;
|
|
26
|
-
return JSON.stringify(json);
|
|
27
|
-
}
|
|
28
|
-
async function discover(projectDir, distDir) {
|
|
29
|
-
const absDistDir = resolveDistDir(projectDir, distDir);
|
|
30
|
-
const relDistDir = relative(projectDir, absDistDir);
|
|
31
|
-
const [staticFiles, serverFiles] = await Promise.all([findJsFiles(join(absDistDir, "static")), findJsFiles(join(absDistDir, "server"))]);
|
|
32
|
-
const jsPaths = [...staticFiles, ...serverFiles];
|
|
33
|
-
const mapping = {};
|
|
34
|
-
const mapAbsolutes = /* @__PURE__ */ new Map();
|
|
35
|
-
const jsAbsolutes = /* @__PURE__ */ new Map();
|
|
36
|
-
for (const jsAbs of jsPaths) {
|
|
37
|
-
const content = await readFile(jsAbs, "utf8");
|
|
38
|
-
const match = SOURCEMAPPING_RE.exec(content);
|
|
39
|
-
if (!match?.[1]) continue;
|
|
40
|
-
const mapAbs = `${jsAbs.slice(0, jsAbs.lastIndexOf("/") + 1)}${decodeURIComponent(match[1])}`;
|
|
41
|
-
const jsPublic = toPublicPath(relative(projectDir, jsAbs), relDistDir);
|
|
42
|
-
const mapPublic = toPublicPath(relative(projectDir, mapAbs), relDistDir);
|
|
43
|
-
mapping[mapPublic] = jsPublic;
|
|
44
|
-
mapAbsolutes.set(mapPublic, mapAbs);
|
|
45
|
-
jsAbsolutes.set(mapPublic, jsAbs);
|
|
46
|
-
}
|
|
47
|
-
const loaded = (await Promise.all(mapAbsolutes.entries().map(async ([mapPublic, mapAbs]) => {
|
|
48
|
-
const mapContent = await readFile(mapAbs, "utf8").catch(() => null);
|
|
49
|
-
if (mapContent === null) return null;
|
|
50
|
-
const jsAbs = jsAbsolutes.get(mapPublic);
|
|
51
|
-
if (!jsAbs) return null;
|
|
52
|
-
const debugId = randomUUID();
|
|
53
|
-
await writeFile(jsAbs, injectDebugIdIntoJs(await readFile(jsAbs, "utf8"), debugId), "utf8");
|
|
54
|
-
const injectedMap = injectDebugIdIntoMap(mapContent, debugId);
|
|
55
|
-
await writeFile(mapAbs, injectedMap, "utf8");
|
|
56
|
-
return {
|
|
57
|
-
absolute: mapAbs,
|
|
58
|
-
path: mapPublic,
|
|
59
|
-
content: injectedMap,
|
|
60
|
-
hash: createHash("sha256").update(injectedMap).digest("hex"),
|
|
61
|
-
debugId
|
|
62
|
-
};
|
|
63
|
-
}))).filter((f) => f !== null);
|
|
64
|
-
const validMapping = {};
|
|
65
|
-
for (const file of loaded) {
|
|
66
|
-
const js = mapping[file.path];
|
|
67
|
-
if (js) validMapping[file.path] = js;
|
|
5
|
+
/**
|
|
6
|
+
* Single entry point for source-map discovery. The pipeline calls this
|
|
7
|
+
* with the active bundler from `detectActiveBundler()`; new bundlers (e.g.
|
|
8
|
+
* rspack) drop in here as additional cases without touching the pipeline.
|
|
9
|
+
* `"tsc"` is excluded — non-bundler producers live outside the Next build
|
|
10
|
+
* pipeline and go through `@interfere/cli` instead.
|
|
11
|
+
*/
|
|
12
|
+
async function discoverSourceMaps(opts) {
|
|
13
|
+
const distDir = normalizeDistDir(opts.distDir);
|
|
14
|
+
switch (opts.bundler) {
|
|
15
|
+
case "webpack": return discoverWebpack({
|
|
16
|
+
projectDir: opts.projectDir,
|
|
17
|
+
distDir
|
|
18
|
+
});
|
|
19
|
+
case "turbopack": return discoverTurbopack({
|
|
20
|
+
projectDir: opts.projectDir,
|
|
21
|
+
distDir
|
|
22
|
+
});
|
|
68
23
|
}
|
|
69
|
-
return {
|
|
70
|
-
files: loaded,
|
|
71
|
-
mapping: validMapping,
|
|
72
|
-
sourceFileCount: mapAbsolutes.size
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function normalizeDistDir(distDir = ".next") {
|
|
76
|
-
let d = distDir.split("\\").join("/");
|
|
77
|
-
while (d.startsWith("./")) d = d.slice(2);
|
|
78
|
-
return d;
|
|
79
|
-
}
|
|
80
|
-
function toPublicPath(rel, distDir) {
|
|
81
|
-
const p = rel.split("\\").join("/");
|
|
82
|
-
if (p.startsWith(`${distDir}/`)) return `_next/${p.slice(distDir.length + 1)}`;
|
|
83
|
-
if (p.startsWith(".next/")) return `_next/${p.slice(6)}`;
|
|
84
|
-
return p;
|
|
85
24
|
}
|
|
86
25
|
//#endregion
|
|
87
|
-
export {
|
|
26
|
+
export { discoverSourceMaps, injectDebugIdIntoJs, injectDebugIdIntoMap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.mjs","names":[],"sources":["../../../../src/internal/build/source-maps/discover.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"discover.mjs","names":[],"sources":["../../../../src/internal/build/source-maps/discover.ts"],"sourcesContent":["import type { ManifestBundler } from \"@interfere/types/data/source-maps\";\n\nimport { discoverTurbopack } from \"./discover-turbopack.js\";\nimport { discoverWebpack } from \"./discover-webpack.js\";\nimport { normalizeDistDir } from \"./paths.js\";\n\nexport interface SourceMapFile {\n readonly absolute: string;\n /**\n * Public URL the runtime will load this chunk from. ALWAYS populated\n * for both bundlers — turbopack discovery walks JS chunks (in addition\n * to .map files) so it can record this. Lifted onto the manifest as\n * `chunkUrl`; server-side suffix-matching pairs `frame.fileName` to\n * this field by content-hashed filename.\n */\n readonly chunkUrl: string;\n readonly content: string;\n readonly debugId: string;\n readonly hash: string;\n readonly path: string;\n}\n\nexport interface DiscoveryResult {\n readonly files: SourceMapFile[];\n readonly sourceFileCount: number;\n}\n\n/**\n * Single entry point for source-map discovery. The pipeline calls this\n * with the active bundler from `detectActiveBundler()`; new bundlers (e.g.\n * rspack) drop in here as additional cases without touching the pipeline.\n * `\"tsc\"` is excluded — non-bundler producers live outside the Next build\n * pipeline and go through `@interfere/cli` instead.\n */\nexport async function discoverSourceMaps(opts: {\n bundler: Exclude<ManifestBundler, \"tsc\">;\n projectDir: string;\n distDir: string;\n}): Promise<DiscoveryResult> {\n const distDir = normalizeDistDir(opts.distDir);\n switch (opts.bundler) {\n case \"webpack\":\n return discoverWebpack({ projectDir: opts.projectDir, distDir });\n case \"turbopack\":\n return discoverTurbopack({ projectDir: opts.projectDir, distDir });\n }\n}\n\nexport {\n injectDebugIdIntoJs,\n injectDebugIdIntoMap,\n} from \"./discover-webpack.js\";\n"],"mappings":";;;;;;;;;;;AAkCA,eAAsB,mBAAmB,MAIZ;CAC3B,MAAM,UAAU,iBAAiB,KAAK,QAAQ;CAC9C,QAAQ,KAAK,SAAb;EACE,KAAK,WACH,OAAO,gBAAgB;GAAE,YAAY,KAAK;GAAY;GAAS,CAAC;EAClE,KAAK,aACH,OAAO,kBAAkB;GAAE,YAAY,KAAK;GAAY;GAAS,CAAC"}
|
|
@@ -1,29 +1,7 @@
|
|
|
1
1
|
import { SourceMapFile } from "./discover.mjs";
|
|
2
|
+
import { uploadSourceMaps } from "./upload.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/internal/build/source-maps/index.d.ts
|
|
4
|
-
declare function buildUploadBody(discovered: {
|
|
5
|
-
files: SourceMapFile[];
|
|
6
|
-
mapping: Record<string, string>;
|
|
7
|
-
sourceFileCount: number;
|
|
8
|
-
}): {
|
|
9
|
-
body: {
|
|
10
|
-
files: {
|
|
11
|
-
fileName: string;
|
|
12
|
-
content: Blob;
|
|
13
|
-
}[];
|
|
14
|
-
metadata: {
|
|
15
|
-
sourceMapToGenerated: Record<string, string>;
|
|
16
|
-
hashes: {
|
|
17
|
-
[k: string]: string;
|
|
18
|
-
};
|
|
19
|
-
debugIds: {
|
|
20
|
-
[k: string]: string;
|
|
21
|
-
};
|
|
22
|
-
sourceFileCount: number;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
totalBytes: number;
|
|
26
|
-
};
|
|
27
5
|
declare function cleanupSourceMaps(files: SourceMapFile[]): Promise<void>;
|
|
28
6
|
//#endregion
|
|
29
|
-
export {
|
|
7
|
+
export { cleanupSourceMaps, uploadSourceMaps };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/source-maps/index.ts"],"mappings":";;;;iBAKsB,iBAAA,CAAkB,KAAA,EAAO,aAAA,KAAkB,OAAA"}
|