@interfere/next 0.0.13 → 0.0.15-alpha.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 +1 -6
- package/dist/build/env-config.d.mts +7 -0
- package/dist/build/env-config.d.mts.map +1 -0
- package/dist/build/env-config.mjs +17 -0
- package/dist/build/env-config.mjs.map +1 -0
- package/dist/build/logger.d.mts +11 -0
- package/dist/build/logger.d.mts.map +1 -0
- package/dist/build/logger.mjs +155 -0
- package/dist/build/logger.mjs.map +1 -0
- package/dist/build/release-program.d.mts +19 -0
- package/dist/build/release-program.d.mts.map +1 -0
- package/dist/build/release-program.mjs +92 -0
- package/dist/build/release-program.mjs.map +1 -0
- package/dist/build/secret-key.d.mts +10 -0
- package/dist/build/secret-key.d.mts.map +1 -0
- package/dist/build/secret-key.mjs +16 -0
- package/dist/build/secret-key.mjs.map +1 -0
- package/dist/build/services/config.service.d.mts +9 -0
- package/dist/build/services/config.service.d.mts.map +1 -0
- package/dist/build/services/config.service.mjs +8 -0
- package/dist/build/services/config.service.mjs.map +1 -0
- package/dist/build/services/preflight.service.d.mts +19 -0
- package/dist/build/services/preflight.service.d.mts.map +1 -0
- package/dist/build/services/preflight.service.mjs +76 -0
- package/dist/build/services/preflight.service.mjs.map +1 -0
- package/dist/build/services/release-identity.service.d.mts +22 -0
- package/dist/build/services/release-identity.service.d.mts.map +1 -0
- package/dist/build/services/release-identity.service.mjs +48 -0
- package/dist/build/services/release-identity.service.mjs.map +1 -0
- package/dist/build/services/source-map.service.d.mts +24 -0
- package/dist/build/services/source-map.service.d.mts.map +1 -0
- package/dist/build/services/source-map.service.mjs +58 -0
- package/dist/build/services/source-map.service.mjs.map +1 -0
- package/dist/build/source-maps/api.d.mts +35 -0
- package/dist/build/source-maps/api.d.mts.map +1 -0
- package/dist/build/source-maps/api.mjs +61 -0
- package/dist/build/source-maps/api.mjs.map +1 -0
- package/dist/build/source-maps/client.d.mts +73 -0
- package/dist/build/source-maps/client.d.mts.map +1 -0
- package/dist/build/source-maps/client.mjs +228 -0
- package/dist/build/source-maps/client.mjs.map +1 -0
- package/dist/build/source-maps/errors.d.mts +109 -0
- package/dist/build/source-maps/errors.d.mts.map +1 -0
- package/dist/build/source-maps/errors.mjs +22 -0
- package/dist/build/source-maps/errors.mjs.map +1 -0
- package/dist/build/source-maps/files.d.mts +35 -0
- package/dist/build/source-maps/files.d.mts.map +1 -0
- package/dist/build/source-maps/files.mjs +222 -0
- package/dist/build/source-maps/files.mjs.map +1 -0
- package/dist/build/source-maps/providers/deployment/detector.d.mts +26 -0
- package/dist/build/source-maps/providers/deployment/detector.d.mts.map +1 -0
- package/dist/build/source-maps/providers/deployment/detector.mjs +22 -0
- package/dist/build/source-maps/providers/deployment/detector.mjs.map +1 -0
- package/dist/build/source-maps/providers/deployment/types.d.mts +12 -0
- package/dist/build/source-maps/providers/deployment/types.d.mts.map +1 -0
- package/dist/build/source-maps/providers/deployment/types.mjs +3 -0
- package/dist/build/source-maps/providers/deployment/vercel.d.mts +6 -0
- package/dist/build/source-maps/providers/deployment/vercel.d.mts.map +1 -0
- package/dist/build/source-maps/providers/deployment/vercel.mjs +44 -0
- package/dist/build/source-maps/providers/deployment/vercel.mjs.map +1 -0
- package/dist/build/source-maps/providers/source-control/detector.d.mts +15 -0
- package/dist/build/source-maps/providers/source-control/detector.d.mts.map +1 -0
- package/dist/build/source-maps/providers/source-control/detector.mjs +22 -0
- package/dist/build/source-maps/providers/source-control/detector.mjs.map +1 -0
- package/dist/build/source-maps/providers/source-control/git.d.mts +6 -0
- package/dist/build/source-maps/providers/source-control/git.d.mts.map +1 -0
- package/dist/build/source-maps/providers/source-control/git.mjs +50 -0
- package/dist/build/source-maps/providers/source-control/git.mjs.map +1 -0
- package/dist/build/source-maps/providers/source-control/types.d.mts +12 -0
- package/dist/build/source-maps/providers/source-control/types.d.mts.map +1 -0
- package/dist/build/source-maps/providers/source-control/types.mjs +3 -0
- package/dist/build/with-interfere.d.mts +49 -0
- package/dist/build/with-interfere.d.mts.map +1 -0
- package/dist/build/with-interfere.mjs +75 -0
- package/dist/build/with-interfere.mjs.map +1 -0
- package/dist/client/client.d.mts +3 -0
- package/dist/client/client.mjs +5 -0
- package/dist/client/provider.d.mts +22 -0
- package/dist/client/provider.d.mts.map +1 -0
- package/dist/client/provider.mjs +33 -0
- package/dist/client/provider.mjs.map +1 -0
- package/dist/lib/env.d.mts +12 -0
- package/dist/lib/env.d.mts.map +1 -0
- package/dist/lib/env.mjs +17 -0
- package/dist/lib/env.mjs.map +1 -0
- package/dist/lib/test-utils/make-next-request.d.mts +6 -0
- package/dist/lib/test-utils/make-next-request.d.mts.map +1 -0
- package/dist/lib/test-utils/make-next-request.mjs +12 -0
- package/dist/lib/test-utils/make-next-request.mjs.map +1 -0
- package/dist/lib/types.d.mts +22 -0
- package/dist/lib/types.d.mts.map +1 -0
- package/dist/lib/types.mjs +7 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/server/middleware.d.mts +11 -0
- package/dist/server/middleware.d.mts.map +1 -0
- package/dist/server/middleware.mjs +84 -0
- package/dist/server/middleware.mjs.map +1 -0
- package/dist/server/proxy.d.mts +6 -0
- package/dist/server/proxy.d.mts.map +1 -0
- package/dist/server/proxy.mjs +29 -0
- package/dist/server/proxy.mjs.map +1 -0
- package/dist/server/route-handler.d.mts +9 -0
- package/dist/server/route-handler.d.mts.map +1 -0
- package/dist/server/route-handler.mjs +172 -0
- package/dist/server/route-handler.mjs.map +1 -0
- package/dist/server/services/config.service.d.mts +21 -0
- package/dist/server/services/config.service.d.mts.map +1 -0
- package/dist/server/services/config.service.mjs +43 -0
- package/dist/server/services/config.service.mjs.map +1 -0
- package/dist/server/services/error-tracking.service.d.mts +19 -0
- package/dist/server/services/error-tracking.service.d.mts.map +1 -0
- package/dist/server/services/error-tracking.service.mjs +31 -0
- package/dist/server/services/error-tracking.service.mjs.map +1 -0
- package/package.json +67 -36
- package/dist/__tests__/build/with-interfere-coverage.test.d.ts +0 -2
- package/dist/__tests__/build/with-interfere-coverage.test.d.ts.map +0 -1
- package/dist/__tests__/build/with-interfere-coverage.test.js +0 -325
- package/dist/__tests__/build/with-interfere-coverage.test.js.map +0 -1
- package/dist/__tests__/build/with-interfere.test.d.ts +0 -2
- package/dist/__tests__/build/with-interfere.test.d.ts.map +0 -1
- package/dist/__tests__/build/with-interfere.test.js +0 -424
- package/dist/__tests__/build/with-interfere.test.js.map +0 -1
- package/dist/__tests__/core/client.test.d.ts +0 -2
- package/dist/__tests__/core/client.test.d.ts.map +0 -1
- package/dist/__tests__/core/client.test.js +0 -373
- package/dist/__tests__/core/client.test.js.map +0 -1
- package/dist/__tests__/core/encoders.test.d.ts +0 -2
- package/dist/__tests__/core/encoders.test.d.ts.map +0 -1
- package/dist/__tests__/core/encoders.test.js +0 -56
- package/dist/__tests__/core/encoders.test.js.map +0 -1
- package/dist/__tests__/core/rage-click.test.d.ts +0 -2
- package/dist/__tests__/core/rage-click.test.d.ts.map +0 -1
- package/dist/__tests__/core/rage-click.test.js +0 -121
- package/dist/__tests__/core/rage-click.test.js.map +0 -1
- package/dist/__tests__/core/session-manager.test.d.ts +0 -2
- package/dist/__tests__/core/session-manager.test.d.ts.map +0 -1
- package/dist/__tests__/core/session-manager.test.js +0 -1168
- package/dist/__tests__/core/session-manager.test.js.map +0 -1
- package/dist/__tests__/integration/release-upload.test.d.ts +0 -2
- package/dist/__tests__/integration/release-upload.test.d.ts.map +0 -1
- package/dist/__tests__/integration/release-upload.test.js +0 -167
- package/dist/__tests__/integration/release-upload.test.js.map +0 -1
- package/dist/__tests__/session/persistence.test.d.ts +0 -2
- package/dist/__tests__/session/persistence.test.d.ts.map +0 -1
- package/dist/__tests__/session/persistence.test.js +0 -129
- package/dist/__tests__/session/persistence.test.js.map +0 -1
- package/dist/__tests__/session/session-summary.test.d.ts +0 -2
- package/dist/__tests__/session/session-summary.test.d.ts.map +0 -1
- package/dist/__tests__/session/session-summary.test.js +0 -763
- package/dist/__tests__/session/session-summary.test.js.map +0 -1
- package/dist/client.d.ts +0 -75
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -123
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -61
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -315
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.jsx +0 -50
- package/dist/index.jsx.map +0 -1
- package/dist/lib/core/client-core.d.ts +0 -27
- package/dist/lib/core/client-core.d.ts.map +0 -1
- package/dist/lib/core/client-core.js +0 -152
- package/dist/lib/core/client-core.js.map +0 -1
- package/dist/lib/core/constants.d.ts +0 -12
- package/dist/lib/core/constants.d.ts.map +0 -1
- package/dist/lib/core/constants.js +0 -17
- package/dist/lib/core/constants.js.map +0 -1
- package/dist/lib/core/debug.d.ts +0 -47
- package/dist/lib/core/debug.d.ts.map +0 -1
- package/dist/lib/core/debug.js +0 -79
- package/dist/lib/core/debug.js.map +0 -1
- package/dist/lib/core/encoders.d.ts +0 -3
- package/dist/lib/core/encoders.d.ts.map +0 -1
- package/dist/lib/core/encoders.js +0 -5
- package/dist/lib/core/encoders.js.map +0 -1
- package/dist/lib/core/error-handlers.d.ts +0 -14
- package/dist/lib/core/error-handlers.d.ts.map +0 -1
- package/dist/lib/core/error-handlers.js +0 -191
- package/dist/lib/core/error-handlers.js.map +0 -1
- package/dist/lib/core/runtime.d.ts +0 -7
- package/dist/lib/core/runtime.d.ts.map +0 -1
- package/dist/lib/core/runtime.js +0 -16
- package/dist/lib/core/runtime.js.map +0 -1
- package/dist/lib/persistence/storage.d.ts +0 -5
- package/dist/lib/persistence/storage.d.ts.map +0 -1
- package/dist/lib/persistence/storage.js +0 -67
- package/dist/lib/persistence/storage.js.map +0 -1
- package/dist/lib/session/constants.d.ts +0 -19
- package/dist/lib/session/constants.d.ts.map +0 -1
- package/dist/lib/session/constants.js +0 -34
- package/dist/lib/session/constants.js.map +0 -1
- package/dist/lib/session/persistence.d.ts +0 -58
- package/dist/lib/session/persistence.d.ts.map +0 -1
- package/dist/lib/session/persistence.js +0 -179
- package/dist/lib/session/persistence.js.map +0 -1
- package/dist/lib/session/rage-click.d.ts +0 -17
- package/dist/lib/session/rage-click.d.ts.map +0 -1
- package/dist/lib/session/rage-click.js +0 -104
- package/dist/lib/session/rage-click.js.map +0 -1
- package/dist/lib/session/replay.d.ts +0 -3
- package/dist/lib/session/replay.d.ts.map +0 -1
- package/dist/lib/session/replay.js +0 -109
- package/dist/lib/session/replay.js.map +0 -1
- package/dist/lib/session/session-manager.d.ts +0 -126
- package/dist/lib/session/session-manager.d.ts.map +0 -1
- package/dist/lib/session/session-manager.js +0 -635
- package/dist/lib/session/session-manager.js.map +0 -1
- package/dist/lib/session/session-summary.d.ts +0 -3
- package/dist/lib/session/session-summary.d.ts.map +0 -1
- package/dist/lib/session/session-summary.js +0 -214
- package/dist/lib/session/session-summary.js.map +0 -1
- package/dist/middleware.d.ts +0 -8
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js +0 -139
- package/dist/middleware.js.map +0 -1
- package/dist/types/storage.d.ts +0 -7
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -2
- package/dist/types/storage.js.map +0 -1
- package/dist/types.d.ts +0 -6
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -4
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getEnvBuildId, getEnvReleaseId } from "../../lib/env.mjs";
|
|
2
|
+
import { MissingBuildIdError, MissingReleaseIdError } from "../source-maps/errors.mjs";
|
|
3
|
+
import { detectDeployment } from "../source-maps/providers/deployment/detector.mjs";
|
|
4
|
+
import { detectSourceControl } from "../source-maps/providers/source-control/detector.mjs";
|
|
5
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/build/services/release-identity.service.ts
|
|
8
|
+
var ReleaseIdentityService = class extends Context.Tag("ReleaseIdentityService")() {};
|
|
9
|
+
const makeReleaseIdentity = Effect.gen(function* () {
|
|
10
|
+
const envBuildId = getEnvBuildId();
|
|
11
|
+
const envReleaseId = getEnvReleaseId();
|
|
12
|
+
const deploymentOpt = yield* detectDeployment().pipe(Effect.option);
|
|
13
|
+
const sourceControlOpt = yield* detectSourceControl().pipe(Effect.option);
|
|
14
|
+
const deployment = Option.getOrUndefined(deploymentOpt);
|
|
15
|
+
const sourceControl = Option.getOrUndefined(sourceControlOpt);
|
|
16
|
+
let buildId = envBuildId ?? void 0;
|
|
17
|
+
let releaseId = envReleaseId ?? void 0;
|
|
18
|
+
if (!buildId && deployment && "deployment" in deployment.metadata) {
|
|
19
|
+
const deploymentId = deployment.metadata.deployment?.id ?? void 0;
|
|
20
|
+
if (deploymentId) buildId = deploymentId;
|
|
21
|
+
}
|
|
22
|
+
if (!buildId && sourceControl && "gitCommitSha" in sourceControl.metadata) {
|
|
23
|
+
const sha = sourceControl.metadata.gitCommitSha;
|
|
24
|
+
if (sha) buildId = sha;
|
|
25
|
+
}
|
|
26
|
+
if (!releaseId && sourceControl && "gitCommitSha" in sourceControl.metadata) {
|
|
27
|
+
const sha = sourceControl.metadata.gitCommitSha;
|
|
28
|
+
if (sha) releaseId = sha;
|
|
29
|
+
}
|
|
30
|
+
if (!releaseId && deployment && "deployment" in deployment.metadata) {
|
|
31
|
+
const deploymentId = deployment.metadata.deployment?.id ?? void 0;
|
|
32
|
+
if (deploymentId) releaseId = deploymentId;
|
|
33
|
+
}
|
|
34
|
+
if (!releaseId && buildId) releaseId = buildId;
|
|
35
|
+
if (!buildId && !releaseId) return yield* Effect.fail(new MissingBuildIdError({ message: "Couldn't determine build ID (no deployment provider, git, or env vars found)" }));
|
|
36
|
+
if (!buildId) return yield* Effect.fail(new MissingBuildIdError({ message: "Couldn't determine build ID (tried NEXT_PUBLIC_INTERFERE_BUILD_ID, NEXT_BUILD_ID, deployment metadata, and git)" }));
|
|
37
|
+
if (!releaseId) return yield* Effect.fail(new MissingReleaseIdError({ message: "Couldn't determine release ID (tried NEXT_PUBLIC_INTERFERE_RELEASE_ID, deployment metadata, and git)" }));
|
|
38
|
+
return {
|
|
39
|
+
buildId,
|
|
40
|
+
releaseId,
|
|
41
|
+
sourceControl,
|
|
42
|
+
deployment
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const ReleaseIdentityServiceLive = Layer.succeed(ReleaseIdentityService, makeReleaseIdentity);
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ReleaseIdentityService, ReleaseIdentityServiceLive };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-identity.service.mjs","names":[],"sources":["../../../src/build/services/release-identity.service.ts"],"sourcesContent":["import type { ReleaseDestinationMetadata, ReleaseDestinationType, ReleaseSourceMetadata, ReleaseSourceType } from \"@interfere/types/releases/definition\";\nimport { Context, Effect, Layer, Option } from \"effect\";\nimport { getEnvBuildId, getEnvReleaseId } from \"../../lib/env.js\";\nimport {\n MissingBuildIdError,\n MissingReleaseIdError,\n} from \"../source-maps/errors.js\";\nimport { detectDeployment } from \"../source-maps/providers/deployment/detector.js\";\nimport { detectSourceControl } from \"../source-maps/providers/source-control/detector.js\";\n\nexport type ReleaseIdentity = {\n buildId: string;\n releaseId: string;\n sourceControl?: {\n type: ReleaseSourceType;\n metadata: ReleaseSourceMetadata;\n };\n deployment?: {\n type: ReleaseDestinationType;\n metadata: ReleaseDestinationMetadata;\n };\n};\n\nexport class ReleaseIdentityService extends Context.Tag(\"ReleaseIdentityService\")<\n ReleaseIdentityService,\n Effect.Effect<ReleaseIdentity, MissingBuildIdError | MissingReleaseIdError>\n>() {}\n\nconst makeReleaseIdentity = Effect.gen(function* () {\n // Read explicitly configured IDs if present\n const envBuildId = getEnvBuildId();\n const envReleaseId = getEnvReleaseId();\n\n // Detect deployment and source control once and reuse\n const deploymentOpt = yield* detectDeployment().pipe(Effect.option);\n const sourceControlOpt = yield* detectSourceControl().pipe(Effect.option);\n\n const deployment = Option.getOrUndefined(deploymentOpt);\n const sourceControl = Option.getOrUndefined(sourceControlOpt);\n\n // Start with env-provided IDs (if any), then fill gaps from metadata\n let buildId = envBuildId ?? undefined;\n let releaseId = envReleaseId ?? undefined;\n\n // Try to get IDs from deployment metadata (Vercel, etc.)\n if (!buildId && deployment && \"deployment\" in deployment.metadata) {\n const deploymentId = deployment.metadata.deployment?.id ?? undefined;\n\n if (deploymentId) {\n buildId = deploymentId;\n }\n }\n\n // Try to get IDs from source control metadata (git)\n if (!buildId && sourceControl && \"gitCommitSha\" in sourceControl.metadata) {\n const sha = sourceControl.metadata.gitCommitSha;\n\n if (sha) {\n buildId = sha;\n }\n }\n\n // Prefer git SHA for releaseId if available\n if (!releaseId && sourceControl && \"gitCommitSha\" in sourceControl.metadata) {\n const sha = sourceControl.metadata.gitCommitSha;\n\n if (sha) {\n releaseId = sha;\n }\n }\n\n // Otherwise fall back to deployment id\n if (!releaseId && deployment && \"deployment\" in deployment.metadata) {\n const deploymentId = deployment.metadata.deployment?.id ?? undefined;\n\n if (deploymentId) {\n releaseId = deploymentId;\n }\n }\n\n // Fall back to buildId for releaseId if we still don't have one\n if (!releaseId && buildId) {\n releaseId = buildId;\n }\n\n // Error if we couldn't determine both IDs\n if (!buildId && !releaseId) {\n return yield* Effect.fail(\n new MissingBuildIdError({\n message:\n \"Couldn't determine build ID (no deployment provider, git, or env vars found)\",\n })\n );\n }\n\n if (!buildId) {\n return yield* Effect.fail(\n new MissingBuildIdError({\n message:\n \"Couldn't determine build ID (tried NEXT_PUBLIC_INTERFERE_BUILD_ID, NEXT_BUILD_ID, deployment metadata, and git)\",\n })\n );\n }\n\n if (!releaseId) {\n return yield* Effect.fail(\n new MissingReleaseIdError({\n message:\n \"Couldn't determine release ID (tried NEXT_PUBLIC_INTERFERE_RELEASE_ID, deployment metadata, and git)\",\n })\n );\n }\n\n const result: ReleaseIdentity = {\n buildId,\n releaseId,\n sourceControl,\n deployment,\n };\n\n return result;\n});\n\nexport const ReleaseIdentityServiceLive = Layer.succeed(\n ReleaseIdentityService,\n makeReleaseIdentity\n);"],"mappings":";;;;;;;AAuBA,IAAa,yBAAb,cAA4C,QAAQ,IAAI,yBAAyB,EAG9E,CAAC;AAEJ,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAElD,MAAM,aAAa,eAAe;CAClC,MAAM,eAAe,iBAAiB;CAGtC,MAAM,gBAAgB,OAAO,kBAAkB,CAAC,KAAK,OAAO,OAAO;CACnE,MAAM,mBAAmB,OAAO,qBAAqB,CAAC,KAAK,OAAO,OAAO;CAEzE,MAAM,aAAa,OAAO,eAAe,cAAc;CACvD,MAAM,gBAAgB,OAAO,eAAe,iBAAiB;CAG7D,IAAI,UAAU,cAAc;CAC5B,IAAI,YAAY,gBAAgB;AAGhC,KAAI,CAAC,WAAW,cAAc,gBAAgB,WAAW,UAAU;EACjE,MAAM,eAAe,WAAW,SAAS,YAAY,MAAM;AAE3D,MAAI,aACF,WAAU;;AAKd,KAAI,CAAC,WAAW,iBAAiB,kBAAkB,cAAc,UAAU;EACzE,MAAM,MAAM,cAAc,SAAS;AAEnC,MAAI,IACF,WAAU;;AAKd,KAAI,CAAC,aAAa,iBAAiB,kBAAkB,cAAc,UAAU;EAC3E,MAAM,MAAM,cAAc,SAAS;AAEnC,MAAI,IACF,aAAY;;AAKhB,KAAI,CAAC,aAAa,cAAc,gBAAgB,WAAW,UAAU;EACnE,MAAM,eAAe,WAAW,SAAS,YAAY,MAAM;AAE3D,MAAI,aACF,aAAY;;AAKhB,KAAI,CAAC,aAAa,QAChB,aAAY;AAId,KAAI,CAAC,WAAW,CAAC,UACf,QAAO,OAAO,OAAO,KACnB,IAAI,oBAAoB,EACtB,SACE,gFACH,CAAC,CACH;AAGH,KAAI,CAAC,QACH,QAAO,OAAO,OAAO,KACnB,IAAI,oBAAoB,EACtB,SACE,mHACH,CAAC,CACH;AAGH,KAAI,CAAC,UACH,QAAO,OAAO,OAAO,KACnB,IAAI,sBAAsB,EACxB,SACE,wGACH,CAAC,CACH;AAUH,QAPgC;EAC9B;EACA;EACA;EACA;EACD;EAGD;AAEF,MAAa,6BAA6B,MAAM,QAC9C,wBACA,oBACD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ConfigService } from "./config.service.mjs";
|
|
2
|
+
import { FileDeleteError, FileGlobError, FileHashError, FileReadError, InvalidApiUrlError, UploadError } from "../source-maps/errors.mjs";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
4
|
+
import { UploadedSourceMap } from "@interfere/types/data/source-maps";
|
|
5
|
+
|
|
6
|
+
//#region src/build/services/source-map.service.d.ts
|
|
7
|
+
type UploadMeta = {
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
orgId: string;
|
|
10
|
+
sourceId: string;
|
|
11
|
+
surfaceId: number;
|
|
12
|
+
environment: string;
|
|
13
|
+
releaseSlug: string;
|
|
14
|
+
};
|
|
15
|
+
declare const SourceMapService_base: Context.TagClass<SourceMapService, "SourceMapService", {
|
|
16
|
+
readonly find: Effect.Effect<string[], FileGlobError>;
|
|
17
|
+
readonly read: (files: string[]) => Effect.Effect<UploadedSourceMap[], FileReadError | FileHashError>;
|
|
18
|
+
readonly upload: (sourceMaps: UploadedSourceMap[], meta: UploadMeta) => Effect.Effect<void, UploadError | InvalidApiUrlError>;
|
|
19
|
+
readonly cleanup: (files: string[], debug: boolean) => Effect.Effect<void, FileDeleteError>;
|
|
20
|
+
}>;
|
|
21
|
+
declare class SourceMapService extends SourceMapService_base {}
|
|
22
|
+
declare const SourceMapServiceLive: Layer.Layer<SourceMapService, never, ConfigService>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SourceMapService, SourceMapServiceLive, UploadMeta };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-map.service.d.mts","names":[],"sources":["../../../src/build/services/source-map.service.ts"],"sourcesContent":[],"mappings":";;;;;;KAuBY,UAAA;;EAAA,KAAA,EAAA,MAAU;EAOpB,QAAA,EAAA,MAAA;;EAMyC,WAAA,EAAA,MAAA;EAAxB,WAAO,EAAA,MAAA;CAIH;cAVrB,qBAU0C,kBAAA,iBAAA,EAAA,kBAAA,EAAA;EAAgB,SAAA,IAAA,EAJzC,MAAA,CAAO,MAIkC,CAAA,MAAA,EAAA,EAJjB,aAIiB,CAAA;EAAnD,SAAO,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,GAAP,MAAA,CAAO,MAAA,CAAO,iBAAP,EAAA,EAA4B,aAA5B,GAA4C,aAA5C,CAAA;EAGE,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,iBAAA,EAAA,EAAA,IAAA,EACN,UADM,EAAA,GAET,MAAA,CAAO,MAFE,CAAA,IAAA,EAEW,WAFX,GAEyB,kBAFzB,CAAA;EACN,SAAA,OAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,OAAA,EAAA,GAMH,MAAA,CAAO,MANJ,CAAA,IAAA,EAMiB,eANjB,CAAA;CACiB,CAAA;AAAc,cAZ9B,gBAAA,SAAyB,qBAAA,CAYK;AAKd,cAIhB,oBAJgB,EAII,KAAA,CAAA,KAJJ,CAII,gBAJJ,EAAA,KAAA,EAII,aAJJ,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { appendBuildLogLine, logBuildResult } from "../logger.mjs";
|
|
2
|
+
import { ConfigService } from "./config.service.mjs";
|
|
3
|
+
import { UploadError } from "../source-maps/errors.mjs";
|
|
4
|
+
import { uploadSourceMaps } from "../source-maps/api.mjs";
|
|
5
|
+
import { buildSourceMapToGeneratedMapping, cleanupSourceMaps, findGeneratedJsFiles, findSourceMapFiles, readSourceMapsFromFiles } from "../source-maps/files.mjs";
|
|
6
|
+
import { Context, Effect, Layer } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/build/services/source-map.service.ts
|
|
9
|
+
var SourceMapService = class extends Context.Tag("SourceMapService")() {};
|
|
10
|
+
const SourceMapServiceLive = Layer.effect(SourceMapService, Effect.gen(function* () {
|
|
11
|
+
const config = yield* ConfigService;
|
|
12
|
+
return {
|
|
13
|
+
find: Effect.gen(function* () {
|
|
14
|
+
return yield* findSourceMapFiles();
|
|
15
|
+
}),
|
|
16
|
+
read: (files) => readSourceMapsFromFiles(files),
|
|
17
|
+
upload: (sourceMaps, meta) => Effect.gen(function* () {
|
|
18
|
+
const sourceMapToGenerated = yield* buildSourceMapToGeneratedMapping(yield* findGeneratedJsFiles().pipe(Effect.mapError((error) => {
|
|
19
|
+
return new UploadError({
|
|
20
|
+
message: `Failed to find generated JS files: ${error.message}`,
|
|
21
|
+
file: "js-files",
|
|
22
|
+
cause: error
|
|
23
|
+
});
|
|
24
|
+
}))).pipe(Effect.mapError((error) => {
|
|
25
|
+
if (error._tag === "FileReadError") return new UploadError({
|
|
26
|
+
message: `Failed to read JS file: ${error.message}`,
|
|
27
|
+
file: error.path,
|
|
28
|
+
cause: error
|
|
29
|
+
});
|
|
30
|
+
return new UploadError({
|
|
31
|
+
message: `Failed to read JS files: ${String(error)}`,
|
|
32
|
+
file: "js-files",
|
|
33
|
+
cause: error
|
|
34
|
+
});
|
|
35
|
+
}));
|
|
36
|
+
const totalMb = (sourceMaps.reduce((sum, sm) => sum + sm.content.length, 0) / 1024 / 1024).toFixed(2);
|
|
37
|
+
yield* appendBuildLogLine(`Uploading ${sourceMaps.length} source map(s) in parallel (${totalMb} MB total)`);
|
|
38
|
+
const result = yield* uploadSourceMaps(sourceMaps, sourceMapToGenerated, config, meta).pipe(Effect.mapError((error) => {
|
|
39
|
+
if (error._tag === "TokenRequestError") return new UploadError({
|
|
40
|
+
message: "Failed to request upload token",
|
|
41
|
+
file: "batch",
|
|
42
|
+
cause: error
|
|
43
|
+
});
|
|
44
|
+
if (error._tag === "NotificationError") return new UploadError({
|
|
45
|
+
message: "Failed to notify upload completion",
|
|
46
|
+
file: "batch",
|
|
47
|
+
cause: error
|
|
48
|
+
});
|
|
49
|
+
return error;
|
|
50
|
+
}));
|
|
51
|
+
yield* logBuildResult("Info", "Source map upload complete", [`${result.uploaded} source map${result.uploaded === 1 ? "" : "s"} uploaded successfully`]);
|
|
52
|
+
}),
|
|
53
|
+
cleanup: (files, debug) => config.cleanupSourceMaps !== false ? cleanupSourceMaps(files, debug) : Effect.void
|
|
54
|
+
};
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { SourceMapService, SourceMapServiceLive };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-map.service.mjs","names":[],"sources":["../../../src/build/services/source-map.service.ts"],"sourcesContent":["import type { UploadedSourceMap } from \"@interfere/types/data/source-maps\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { appendBuildLogLine, logBuildResult } from \"../logger.js\";\nimport {\n uploadSourceMaps,\n} from \"../source-maps/api.js\";\nimport {\n FileDeleteError,\n FileGlobError,\n FileHashError,\n FileReadError,\n InvalidApiUrlError,\n UploadError,\n} from \"../source-maps/errors.js\";\nimport {\n buildSourceMapToGeneratedMapping,\n cleanupSourceMaps,\n findGeneratedJsFiles,\n findSourceMapFiles,\n readSourceMapsFromFiles,\n} from \"../source-maps/files.js\";\nimport { ConfigService } from \"./config.service.js\";\n\nexport type UploadMeta = {\n apiUrl: string;\n orgId: string;\n sourceId: string;\n surfaceId: number;\n environment: string;\n releaseSlug: string;\n};\n\n\nexport class SourceMapService extends Context.Tag(\"SourceMapService\")<\n SourceMapService,\n {\n readonly find: Effect.Effect<string[], FileGlobError>;\n\n readonly read: (\n files: string[]\n ) => Effect.Effect<UploadedSourceMap[], FileReadError | FileHashError>;\n\n readonly upload: (\n sourceMaps: UploadedSourceMap[],\n meta: UploadMeta\n ) => Effect.Effect<void, UploadError | InvalidApiUrlError>;\n\n readonly cleanup: (\n files: string[],\n debug: boolean\n ) => Effect.Effect<void, FileDeleteError>;\n }\n>() {}\n\nexport const SourceMapServiceLive = Layer.effect(\n SourceMapService,\n Effect.gen(function* () {\n const config = yield* ConfigService;\n\n return {\n // Lazily evaluate file discovery so that simply constructing the layer\n // (e.g. in tests or early‑exit paths) does not trigger any filesystem work.\n find: Effect.gen(function* () {\n return yield* findSourceMapFiles();\n }),\n \n read: (files: string[]) => readSourceMapsFromFiles(files),\n \n upload: (\n sourceMaps: UploadedSourceMap[],\n meta: UploadMeta\n ) =>\n Effect.gen(function* () {\n // Find and read generated JS files to extract sourceMappingURL mapping\n const jsFilePaths = yield* findGeneratedJsFiles().pipe(\n Effect.mapError((error) => {\n return new UploadError({\n message: `Failed to find generated JS files: ${error.message}`,\n file: \"js-files\",\n cause: error,\n });\n })\n );\n\n // Build mapping from source map filename to generated JS file path\n const sourceMapToGenerated = yield* buildSourceMapToGeneratedMapping(jsFilePaths).pipe(\n Effect.mapError((error) => {\n if (error._tag === \"FileReadError\") {\n return new UploadError({\n message: `Failed to read JS file: ${error.message}`,\n file: error.path,\n cause: error,\n });\n }\n return new UploadError({\n message: `Failed to read JS files: ${String(error)}`,\n file: \"js-files\",\n cause: error,\n });\n })\n );\n\n // Calculate total bytes for logging (only source maps, not JS files)\n const sourceMapBytes = sourceMaps.reduce(\n (sum, sm) => sum + sm.content.length,\n 0\n );\n const totalMb = (sourceMapBytes / 1024 / 1024).toFixed(2);\n\n yield* appendBuildLogLine(\n `Uploading ${sourceMaps.length} source map(s) in parallel (${totalMb} MB total)`\n );\n\n const result = yield* uploadSourceMaps(\n sourceMaps,\n sourceMapToGenerated,\n config,\n meta\n ).pipe(\n Effect.mapError((error) => {\n if (error._tag === \"TokenRequestError\") {\n return new UploadError({\n message: \"Failed to request upload token\",\n file: \"batch\",\n cause: error,\n });\n }\n\n if (error._tag === \"NotificationError\") {\n return new UploadError({\n message: \"Failed to notify upload completion\",\n file: \"batch\",\n cause: error,\n });\n }\n\n return error;\n })\n );\n\n yield* logBuildResult(\"Info\", \"Source map upload complete\", [\n `${result.uploaded} source map${\n result.uploaded === 1 ? \"\" : \"s\"\n } uploaded successfully`,\n ]);\n }),\n \n cleanup: (files: string[], debug: boolean) =>\n config.cleanupSourceMaps !== false\n ? cleanupSourceMaps(files, debug)\n : Effect.void,\n };\n })\n);"],"mappings":";;;;;;;;AAiCA,IAAa,mBAAb,cAAsC,QAAQ,IAAI,mBAAmB,EAmBlE,CAAC;AAEJ,MAAa,uBAAuB,MAAM,OACxC,kBACA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO;AAEtB,QAAO;EAGL,MAAM,OAAO,IAAI,aAAa;AAC5B,UAAO,OAAO,oBAAoB;IAClC;EAEF,OAAO,UAAoB,wBAAwB,MAAM;EAEzD,SACE,YACA,SAEA,OAAO,IAAI,aAAa;GAatB,MAAM,uBAAuB,OAAO,iCAXhB,OAAO,sBAAsB,CAAC,KAChD,OAAO,UAAU,UAAU;AACzB,WAAO,IAAI,YAAY;KACrB,SAAS,sCAAsC,MAAM;KACrD,MAAM;KACN,OAAO;KACR,CAAC;KACF,CACH,CAGgF,CAAC,KAChF,OAAO,UAAU,UAAU;AACzB,QAAI,MAAM,SAAS,gBACjB,QAAO,IAAI,YAAY;KACrB,SAAS,2BAA2B,MAAM;KAC1C,MAAM,MAAM;KACZ,OAAO;KACR,CAAC;AAEJ,WAAO,IAAI,YAAY;KACrB,SAAS,4BAA4B,OAAO,MAAM;KAClD,MAAM;KACN,OAAO;KACR,CAAC;KACF,CACH;GAOD,MAAM,WAJiB,WAAW,QAC/B,KAAK,OAAO,MAAM,GAAG,QAAQ,QAC9B,EACD,GACiC,OAAO,MAAM,QAAQ,EAAE;AAEzD,UAAO,mBACL,aAAa,WAAW,OAAO,8BAA8B,QAAQ,YACtE;GAED,MAAM,SAAS,OAAO,iBACpB,YACA,sBACA,QACA,KACD,CAAC,KACA,OAAO,UAAU,UAAU;AACzB,QAAI,MAAM,SAAS,oBACjB,QAAO,IAAI,YAAY;KACrB,SAAS;KACT,MAAM;KACN,OAAO;KACR,CAAC;AAGJ,QAAI,MAAM,SAAS,oBACjB,QAAO,IAAI,YAAY;KACrB,SAAS;KACT,MAAM;KACN,OAAO;KACR,CAAC;AAGJ,WAAO;KACP,CACH;AAED,UAAO,eAAe,QAAQ,8BAA8B,CAC1D,GAAG,OAAO,SAAS,aACjB,OAAO,aAAa,IAAI,KAAK,IAC9B,wBACF,CAAC;IACF;EAEJ,UAAU,OAAiB,UACzB,OAAO,sBAAsB,QACzB,kBAAkB,OAAO,MAAM,GAC/B,OAAO;EACd;EACD,CACH"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PreflightConfig } from "../../lib/types.mjs";
|
|
2
|
+
import { InvalidApiUrlError, InvalidSecretKeyError, NotificationError, TokenRequestError, UploadError } from "./errors.mjs";
|
|
3
|
+
import { UploadMeta } from "../services/source-map.service.mjs";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { ReleaseDestinationMetadata, ReleaseDestinationType, ReleaseSourceMetadata, ReleaseSourceType } from "@interfere/types/releases/definition";
|
|
6
|
+
import { UploadedSourceMap } from "@interfere/types/data/source-maps";
|
|
7
|
+
|
|
8
|
+
//#region src/build/source-maps/api.d.ts
|
|
9
|
+
type ReleaseInfo = {
|
|
10
|
+
environment: string;
|
|
11
|
+
sourceId: string;
|
|
12
|
+
sourceType: ReleaseSourceType;
|
|
13
|
+
sourceMetadata: ReleaseSourceMetadata;
|
|
14
|
+
destinationType: ReleaseDestinationType;
|
|
15
|
+
destinationMetadata: ReleaseDestinationMetadata;
|
|
16
|
+
};
|
|
17
|
+
declare function createRelease(config: PreflightConfig, releaseInfo: ReleaseInfo): Effect.Effect<{
|
|
18
|
+
orgId: string;
|
|
19
|
+
surfaceId: number;
|
|
20
|
+
surfaceSlug: string;
|
|
21
|
+
sourceId: string;
|
|
22
|
+
environment: string;
|
|
23
|
+
releaseId: number;
|
|
24
|
+
releaseSlug: string;
|
|
25
|
+
}, UploadError | InvalidApiUrlError | InvalidSecretKeyError, never>;
|
|
26
|
+
/**
|
|
27
|
+
* Upload source maps using the new v2 client
|
|
28
|
+
* @param sourceMapToGenerated - Mapping from source map filename to generated JS file path
|
|
29
|
+
*/
|
|
30
|
+
declare function uploadSourceMaps(sourceMaps: UploadedSourceMap[], sourceMapToGenerated: Record<string, string>, config: PreflightConfig, meta: UploadMeta): Effect.Effect<{
|
|
31
|
+
uploaded: number;
|
|
32
|
+
processed: any;
|
|
33
|
+
}, UploadError | TokenRequestError | NotificationError, never>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { createRelease, uploadSourceMaps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.mts","names":[],"sources":["../../../src/build/source-maps/api.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAyBK,WAAA;;EAAA,QAAA,EAAA,MAAW;EAGF,UAAA,EAAA,iBAAA;EACI,cAAA,EAAA,qBAAA;EACC,eAAA,EAAA,sBAAA;EACI,mBAAA,EAAA,0BAAA;CAA0B;AA4FjC,iBAAA,aAAA,CAAa,MAAA,EACnB,eADmB,EAAA,WAAA,EAEd,WAFc,CAAA,EAEH,MAAA,CAAA,MAFG,CAAA;EACnB,KAAA,EAAA,MAAA;EACK,SAAA,EAAA,MAAA;;;;EAAW,SAAA,EAAA,MAAA;EAAA,WAAA,EAAA,MAAA;AAmB1B,CAAA,aAAgB,qBAAgB,wBAAA,EAAA,KAAA,CAAA;;;;;iBAAhB,gBAAA,aACF,2CACU,gCACd,uBACF,aAAU,MAAA,CAAA;;;CAAA,aAAA,wCAAA,EAAA,KAAA,CAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { resolveApiUrl } from "../env-config.mjs";
|
|
2
|
+
import { InvalidApiUrlError, InvalidSecretKeyError, UploadError } from "./errors.mjs";
|
|
3
|
+
import { SourceMapClient } from "./client.mjs";
|
|
4
|
+
import { Duration, Effect, pipe } from "effect";
|
|
5
|
+
import { createReleaseResponseSchema } from "@interfere/types/releases/definition";
|
|
6
|
+
|
|
7
|
+
//#region src/build/source-maps/api.ts
|
|
8
|
+
const API_TIMEOUT = Duration.seconds(30);
|
|
9
|
+
const validateConfig = (config) => config.enabled ? Effect.succeed(config) : Effect.fail(new InvalidSecretKeyError({ message: "Interfere is disabled - secret key or surface id missing" }));
|
|
10
|
+
const createReleaseEffect = (config, body) => pipe(Effect.tryPromise({
|
|
11
|
+
try: () => fetch(`${resolveApiUrl()}/releases`, {
|
|
12
|
+
method: "PUT",
|
|
13
|
+
body: JSON.stringify(body),
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
16
|
+
"Content-Type": "application/json"
|
|
17
|
+
}
|
|
18
|
+
}).then((response) => ({
|
|
19
|
+
config,
|
|
20
|
+
response
|
|
21
|
+
})),
|
|
22
|
+
catch: (error) => new InvalidApiUrlError({ message: `Failed to reach Interfere API: ${String(error)}` })
|
|
23
|
+
}), Effect.timeoutFail({
|
|
24
|
+
duration: API_TIMEOUT,
|
|
25
|
+
onTimeout: () => new InvalidApiUrlError({ message: "Timed out while connecting to the Interfere API. Check INTERFERE_API_URL." })
|
|
26
|
+
}));
|
|
27
|
+
const handleResponse = ({ config, response }) => {
|
|
28
|
+
if (response.status === 401) return Effect.fail(new InvalidSecretKeyError({ message: `INTERFERE_SECRET_KEY ending in '${config.secretKey.slice(-4)}' is invalid. Check the environment variable in your build environment.` }));
|
|
29
|
+
if (!response.ok) return pipe(Effect.promise(() => response.text().catch(() => "Unknown error")), Effect.flatMap((errorText) => Effect.fail(new UploadError({ message: `Failed to create release: ${response.status} ${errorText}` }))));
|
|
30
|
+
return Effect.succeed(response);
|
|
31
|
+
};
|
|
32
|
+
const parseReleaseResponse = (response) => pipe(Effect.promise(() => response.json()), Effect.flatMap((json) => {
|
|
33
|
+
const parseResult = createReleaseResponseSchema.safeParse(json);
|
|
34
|
+
return parseResult.success ? Effect.succeed(parseResult.data) : Effect.fail(new UploadError({ message: `Invalid release response: ${parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}` }));
|
|
35
|
+
}));
|
|
36
|
+
function createRelease(config, releaseInfo) {
|
|
37
|
+
return pipe(validateConfig(config), Effect.flatMap((validConfig) => pipe(createReleaseEffect(validConfig, releaseInfo), Effect.flatMap(handleResponse), Effect.flatMap(parseReleaseResponse))));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Upload source maps using the new v2 client
|
|
41
|
+
* @param sourceMapToGenerated - Mapping from source map filename to generated JS file path
|
|
42
|
+
*/
|
|
43
|
+
function uploadSourceMaps(sourceMaps, sourceMapToGenerated, config, meta) {
|
|
44
|
+
if (!config.enabled || sourceMaps.length === 0) return Effect.succeed({
|
|
45
|
+
uploaded: 0,
|
|
46
|
+
processed: 0
|
|
47
|
+
});
|
|
48
|
+
const sourceMapFiles = sourceMaps.map((sm) => ({
|
|
49
|
+
relativePath: sm.relativePath,
|
|
50
|
+
content: sm.content,
|
|
51
|
+
hash: sm.hash,
|
|
52
|
+
size: Buffer.byteLength(sm.content, "utf8")
|
|
53
|
+
}));
|
|
54
|
+
return new SourceMapClient(config, meta.apiUrl, {
|
|
55
|
+
concurrency: 8,
|
|
56
|
+
uploadTimeoutMs: Duration.toMillis(API_TIMEOUT)
|
|
57
|
+
}).uploadSourceMaps(meta.releaseSlug, sourceMapFiles, sourceMapToGenerated);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createRelease, uploadSourceMaps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../../../src/build/source-maps/api.ts"],"sourcesContent":["import type {\n UploadedJsFile,\n UploadedSourceMap,\n} from \"@interfere/types/data/source-maps\";\nimport {\n type CreateRelease,\n createReleaseResponseSchema,\n type ReleaseDestinationMetadata,\n type ReleaseDestinationType,\n type ReleaseSourceMetadata,\n type ReleaseSourceType,\n} from \"@interfere/types/releases/definition\";\nimport { Duration, Effect, pipe } from \"effect\";\nimport type { PreflightConfig, PreflightEnabled } from \"../../lib/types.js\";\nimport { resolveApiUrl } from \"../env-config.js\";\nimport type { UploadMeta } from \"../services/source-map.service.js\";\nimport { SourceMapClient } from \"./client.js\";\nimport {\n InvalidApiUrlError,\n InvalidSecretKeyError,\n UploadError,\n} from \"./errors.js\";\n\nconst API_TIMEOUT = Duration.seconds(30);\n\ntype ReleaseInfo = {\n environment: string;\n sourceId: string;\n sourceType: ReleaseSourceType;\n sourceMetadata: ReleaseSourceMetadata;\n destinationType: ReleaseDestinationType;\n destinationMetadata: ReleaseDestinationMetadata;\n};\n\nconst validateConfig = (\n config: PreflightConfig\n): Effect.Effect<PreflightEnabled, InvalidSecretKeyError> =>\n config.enabled\n ? Effect.succeed(config as PreflightEnabled)\n : Effect.fail(\n new InvalidSecretKeyError({\n message: \"Interfere is disabled - secret key or surface id missing\"\n })\n );\n\nconst createReleaseEffect = (config: PreflightEnabled, body: CreateRelease) =>\n pipe(\n Effect.tryPromise({\n try: () =>\n fetch(`${resolveApiUrl()}/releases`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n headers: {\n Authorization: `Bearer ${config.secretKey}`,\n \"Content-Type\": \"application/json\",\n },\n }).then((response) => ({\n config,\n response,\n })),\n catch: (error) =>\n new InvalidApiUrlError({\n message: `Failed to reach Interfere API: ${String(error)}`\n }),\n }),\n Effect.timeoutFail({\n duration: API_TIMEOUT,\n onTimeout: () =>\n new InvalidApiUrlError({\n message: \"Timed out while connecting to the Interfere API. Check INTERFERE_API_URL.\"\n }),\n })\n );\n\nconst handleResponse = (\n {config, response}: {config: PreflightEnabled, response: Response}\n): Effect.Effect<Response, InvalidSecretKeyError | UploadError> => {\n // Handle 401 specifically - invalid secret key\n if (response.status === 401) {\n return Effect.fail(\n new InvalidSecretKeyError({\n message: \n `INTERFERE_SECRET_KEY ending in '${config.secretKey.slice(-4)}' is invalid. Check the environment variable in your build environment.`,\n })\n );\n }\n \n // Handle other non-ok responses\n if (!response.ok) {\n return pipe(\n Effect.promise(() => response.text().catch(() => \"Unknown error\")),\n Effect.flatMap((errorText) =>\n Effect.fail(\n new UploadError({\n message: `Failed to create release: ${response.status} ${errorText}`,\n })\n )\n )\n );\n }\n \n // Success\n return Effect.succeed(response);\n};\n\nconst parseReleaseResponse = (response: Response) =>\n pipe(\n Effect.promise(() => response.json()),\n Effect.flatMap((json) => {\n const parseResult = createReleaseResponseSchema.safeParse(json);\n \n return parseResult.success\n ? Effect.succeed(parseResult.data)\n : Effect.fail(\n new UploadError({\n message: `Invalid release response: ${parseResult.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\", \")}`\n })\n );\n })\n );\n\nexport function createRelease(\n config: PreflightConfig,\n releaseInfo: ReleaseInfo\n ) {\n return pipe(\n validateConfig(config),\n Effect.flatMap((validConfig) =>\n pipe(\n createReleaseEffect(validConfig, releaseInfo),\n Effect.flatMap(handleResponse),\n Effect.flatMap(parseReleaseResponse)\n )\n )\n );\n}\n\n\n/**\n * Upload source maps using the new v2 client\n * @param sourceMapToGenerated - Mapping from source map filename to generated JS file path\n */\nexport function uploadSourceMaps(\n sourceMaps: UploadedSourceMap[],\n sourceMapToGenerated: Record<string, string>,\n config: PreflightConfig,\n meta: UploadMeta\n) {\n // Early return for disabled or no source maps\n if (!config.enabled || sourceMaps.length === 0) {\n return Effect.succeed({ uploaded: 0, processed: 0 });\n }\n\n // Convert source maps to client format\n const sourceMapFiles = sourceMaps.map((sm) => ({\n relativePath: sm.relativePath,\n content: sm.content,\n hash: sm.hash,\n size: Buffer.byteLength(sm.content, \"utf8\"),\n }));\n\n // Create client and upload with higher concurrency for faster uploads\n const client = new SourceMapClient(config, meta.apiUrl, {\n concurrency: 8,\n uploadTimeoutMs: Duration.toMillis(API_TIMEOUT),\n });\n\n return client.uploadSourceMaps(\n meta.releaseSlug,\n sourceMapFiles,\n sourceMapToGenerated\n );\n}"],"mappings":";;;;;;;AAuBA,MAAM,cAAc,SAAS,QAAQ,GAAG;AAWxC,MAAM,kBACJ,WAEA,OAAO,UACH,OAAO,QAAQ,OAA2B,GAC1C,OAAO,KACL,IAAI,sBAAsB,EACxB,SAAS,4DACV,CAAC,CACH;AAEP,MAAM,uBAAuB,QAA0B,SACrD,KACE,OAAO,WAAW;CAChB,WACE,MAAM,GAAG,eAAe,CAAC,YAAY;EACnC,QAAQ;EACR,MAAM,KAAK,UAAU,KAAK;EAC1B,SAAS;GACP,eAAe,UAAU,OAAO;GAChC,gBAAgB;GACjB;EACF,CAAC,CAAC,MAAM,cAAc;EACrB;EACA;EACD,EAAE;CACL,QAAQ,UACN,IAAI,mBAAmB,EACrB,SAAS,kCAAkC,OAAO,MAAM,IACzD,CAAC;CACL,CAAC,EACF,OAAO,YAAY;CACjB,UAAU;CACV,iBACE,IAAI,mBAAmB,EACrB,SAAS,6EACV,CAAC;CACL,CAAC,CACH;AAEH,MAAM,kBACJ,EAAC,QAAQ,eACwD;AAEjE,KAAI,SAAS,WAAW,IACtB,QAAO,OAAO,KACV,IAAI,sBAAsB,EACxB,SACA,mCAAmC,OAAO,UAAU,MAAM,GAAG,CAAC,0EAC3D,CAAC,CACT;AAIH,KAAI,CAAC,SAAS,GACZ,QAAO,KACL,OAAO,cAAc,SAAS,MAAM,CAAC,YAAY,gBAAgB,CAAC,EAC5D,OAAO,SAAS,cACd,OAAO,KACL,IAAI,YAAY,EACpB,SAAS,6BAA6B,SAAS,OAAO,GAAG,aACpD,CAAC,CACP,CACN,CACF;AAID,QAAO,OAAO,QAAQ,SAAS;;AAGjC,MAAM,wBAAwB,aAC5B,KACE,OAAO,cAAc,SAAS,MAAM,CAAC,EACrC,OAAO,SAAS,SAAS;CACvB,MAAM,cAAc,4BAA4B,UAAU,KAAK;AAE/D,QAAO,YAAY,UACf,OAAO,QAAQ,YAAY,KAAK,GAChC,OAAO,KACL,IAAI,YAAY,EACd,SAAS,6BAA6B,YAAY,MAAM,OACrD,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,UAAU,CAC/C,KAAK,KAAK,IACd,CAAC,CACH;EACL,CACH;AAEH,SAAgB,cACd,QACA,aACM;AACN,QAAO,KACL,eAAe,OAAO,EACtB,OAAO,SAAS,gBACd,KACE,oBAAoB,aAAa,YAAY,EAC7C,OAAO,QAAQ,eAAe,EAC9B,OAAO,QAAQ,qBAAqB,CACrC,CACF,CACF;;;;;;AAQH,SAAgB,iBACd,YACA,sBACA,QACA,MACA;AAEA,KAAI,CAAC,OAAO,WAAW,WAAW,WAAW,EAC3C,QAAO,OAAO,QAAQ;EAAE,UAAU;EAAG,WAAW;EAAG,CAAC;CAItD,MAAM,iBAAiB,WAAW,KAAK,QAAQ;EAC7C,cAAc,GAAG;EACjB,SAAS,GAAG;EACZ,MAAM,GAAG;EACT,MAAM,OAAO,WAAW,GAAG,SAAS,OAAO;EAC5C,EAAE;AAQH,QALe,IAAI,gBAAgB,QAAQ,KAAK,QAAQ;EACtD,aAAa;EACb,iBAAiB,SAAS,SAAS,YAAY;EAChD,CAAC,CAEY,iBACZ,KAAK,aACL,gBACA,qBACD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PreflightConfig } from "../../lib/types.mjs";
|
|
2
|
+
import { NotificationError, TokenRequestError, UploadError } from "./errors.mjs";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { BatchUploadPaths, SourceMapConfig, SourceMapFile, UploadedBlob } from "@interfere/types/data/source-maps";
|
|
5
|
+
|
|
6
|
+
//#region src/build/source-maps/client.d.ts
|
|
7
|
+
type SourceMapFileWithContent = SourceMapFile & {
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
declare class SourceMapClient {
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly apiUrl;
|
|
13
|
+
private readonly secretKey;
|
|
14
|
+
constructor(preflightConfig: PreflightConfig, apiUrl: string, options?: Partial<SourceMapConfig>);
|
|
15
|
+
/**
|
|
16
|
+
* Request upload paths for source maps (paths only, no tokens).
|
|
17
|
+
* Tokens are generated per-file via the /upload endpoint.
|
|
18
|
+
*/
|
|
19
|
+
requestUploadPaths(releaseSlug: string, request: {
|
|
20
|
+
files: SourceMapFile[];
|
|
21
|
+
folderPrefix?: string;
|
|
22
|
+
}): Effect.Effect<{
|
|
23
|
+
uploadPaths: Record<string, {
|
|
24
|
+
uploadPath: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
}>;
|
|
27
|
+
expiresAt: string;
|
|
28
|
+
}, TokenRequestError, never>;
|
|
29
|
+
/**
|
|
30
|
+
* Upload a source map file using Vercel Blob's secure client upload.
|
|
31
|
+
* The server validates auth and issues a path-scoped token via /upload endpoint.
|
|
32
|
+
*/
|
|
33
|
+
uploadFile(releaseSlug: string, file: SourceMapFileWithContent, uploadInfo: {
|
|
34
|
+
uploadPath: string;
|
|
35
|
+
contentType: string;
|
|
36
|
+
}): Effect.Effect<{
|
|
37
|
+
relativePath: string;
|
|
38
|
+
hash: string;
|
|
39
|
+
blobUrl: string;
|
|
40
|
+
}, UploadError, never>;
|
|
41
|
+
/**
|
|
42
|
+
* Upload multiple source maps in parallel using secure client uploads.
|
|
43
|
+
* Each upload gets a path-scoped token from the server.
|
|
44
|
+
*/
|
|
45
|
+
uploadBatch(releaseSlug: string, files: SourceMapFileWithContent[], batchPaths: BatchUploadPaths): Effect.Effect<{
|
|
46
|
+
relativePath: string;
|
|
47
|
+
hash: string;
|
|
48
|
+
blobUrl: string;
|
|
49
|
+
}[], UploadError, never>;
|
|
50
|
+
/**
|
|
51
|
+
* Notify the server that uploads are complete
|
|
52
|
+
*/
|
|
53
|
+
notifyCompletion(releaseSlug: string, notification: {
|
|
54
|
+
uploadedFiles: UploadedBlob[];
|
|
55
|
+
sourceMapToGenerated?: Record<string, string>;
|
|
56
|
+
}): Effect.Effect<any, NotificationError, never>;
|
|
57
|
+
/**
|
|
58
|
+
* Complete workflow: request paths, upload files with secure tokens, notify completion
|
|
59
|
+
*
|
|
60
|
+
* Flow:
|
|
61
|
+
* 1. Request upload paths from API (paths only, no tokens)
|
|
62
|
+
* 2. Upload files using secure client uploads (server issues path-scoped tokens)
|
|
63
|
+
* 3. Notify API that uploads are complete for processing
|
|
64
|
+
*
|
|
65
|
+
* @param sourceMapToGenerated - Mapping from source map filename to generated JS file path
|
|
66
|
+
*/
|
|
67
|
+
uploadSourceMaps(releaseSlug: string, sourceMapFiles: SourceMapFileWithContent[], sourceMapToGenerated: Record<string, string>): Effect.Effect<{
|
|
68
|
+
uploaded: number;
|
|
69
|
+
processed: any;
|
|
70
|
+
}, UploadError | TokenRequestError | NotificationError, never>;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { SourceMapClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../../../src/build/source-maps/client.ts"],"sourcesContent":[],"mappings":";;;;;;KAaK,wBAAA,GAA2B;;AAFgD,CAAA;AAMnE,cAAA,eAAA,CAAe;EAMP,iBAAA,MAAA;EAEC,iBAAA,MAAA;EAAR,iBAAA,SAAA;EAqBQ,WAAA,CAAA,eAAA,EAvBD,eAuBC,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EArBR,OAqBQ,CArBA,eAqBA,CAAA;;;;;;IA+DqC,KAAA,EA/DrC,aA+DqC,EAAA;IAyDhD,YAAA,CAAA,EAAA,MAAA;EACK,CAAA,CAAA,EAzH8C,MAAA,CAAA,MAyH9C,CAAA;;MAAgB,UAAA,EAAA,MAAA;MAwCX,WAAA,EAAA,MAAA;IACQ,CAAA,CAAA;IACxB,SAAA,EAAA,MAAA;EAAA,CAAA,mBAAA,EAAA,KAAA,CAAA;EAiGe;;;;wCAtMV;IAuMsC,UAAA,EAAA,MAAA;IAAA,WAAA,EAAA,MAAA;MAtMW,MAAA,CAAA;;;;;;;;;0CAyDhD,wCACK,mBAAgB,MAAA,CAAA;;;;;;;;;mBAwCX;2BACQ;MACxB,MAAA,CAAA,YAAA;;;;;;;;;;;wDAiGe,kDACM,yBAAsB,MAAA,CAAA"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { NotificationError, TokenRequestError, UploadError } from "./errors.mjs";
|
|
2
|
+
import { Effect, pipe } from "effect";
|
|
3
|
+
import { batchUploadPathsSchema, sourceMapConfigSchema } from "@interfere/types/data/source-maps";
|
|
4
|
+
|
|
5
|
+
//#region src/build/source-maps/client.ts
|
|
6
|
+
var SourceMapClient = class {
|
|
7
|
+
config;
|
|
8
|
+
apiUrl;
|
|
9
|
+
secretKey;
|
|
10
|
+
constructor(preflightConfig, apiUrl, options) {
|
|
11
|
+
if (!preflightConfig.enabled) throw new Error("Source maps are disabled");
|
|
12
|
+
this.config = sourceMapConfigSchema.parse({
|
|
13
|
+
...options,
|
|
14
|
+
...preflightConfig
|
|
15
|
+
});
|
|
16
|
+
this.apiUrl = apiUrl;
|
|
17
|
+
this.secretKey = preflightConfig.secretKey;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Request upload paths for source maps (paths only, no tokens).
|
|
21
|
+
* Tokens are generated per-file via the /upload endpoint.
|
|
22
|
+
*/
|
|
23
|
+
requestUploadPaths(releaseSlug, request) {
|
|
24
|
+
const { apiUrl, secretKey, config } = this;
|
|
25
|
+
return Effect.gen(function* () {
|
|
26
|
+
const url = `${apiUrl}/releases/${releaseSlug}/source-maps/tokens`;
|
|
27
|
+
yield* Effect.logDebug("Requesting upload paths", {
|
|
28
|
+
releaseSlug,
|
|
29
|
+
fileCount: request.files.length
|
|
30
|
+
});
|
|
31
|
+
const response = yield* Effect.tryPromise({
|
|
32
|
+
try: async () => {
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${secretKey}`,
|
|
37
|
+
"Content-Type": "application/json"
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify(request),
|
|
40
|
+
signal: AbortSignal.timeout(config.uploadTimeoutMs)
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const error = await res.text().catch((e) => e);
|
|
44
|
+
throw new TokenRequestError({ message: `POST ${url} failed: ${res.status} ${res.statusText} - ${error.slice(0, 100)}...` });
|
|
45
|
+
}
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
return batchUploadPathsSchema.parse(data);
|
|
48
|
+
},
|
|
49
|
+
catch: (error) => {
|
|
50
|
+
if (error instanceof TokenRequestError) return error;
|
|
51
|
+
return new TokenRequestError({ message: String(error) });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
yield* Effect.logDebug("Received upload paths", { pathCount: Object.keys(response.uploadPaths).length });
|
|
55
|
+
return response;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Upload a source map file using Vercel Blob's secure client upload.
|
|
60
|
+
* The server validates auth and issues a path-scoped token via /upload endpoint.
|
|
61
|
+
*/
|
|
62
|
+
uploadFile(releaseSlug, file, uploadInfo) {
|
|
63
|
+
const { apiUrl, secretKey } = this;
|
|
64
|
+
return Effect.gen(function* () {
|
|
65
|
+
yield* Effect.logDebug("Uploading source map", {
|
|
66
|
+
path: file.relativePath,
|
|
67
|
+
size: file.size
|
|
68
|
+
});
|
|
69
|
+
const blobUrl = yield* Effect.tryPromise({
|
|
70
|
+
try: async () => {
|
|
71
|
+
if (!file.content) throw new Error("File content is required for upload");
|
|
72
|
+
const { upload } = await import("@vercel/blob/client");
|
|
73
|
+
return (await upload(uploadInfo.uploadPath, file.content, {
|
|
74
|
+
access: "public",
|
|
75
|
+
handleUploadUrl: `${apiUrl}/releases/${releaseSlug}/source-maps/upload`,
|
|
76
|
+
clientPayload: JSON.stringify({ secretKey }),
|
|
77
|
+
contentType: uploadInfo.contentType
|
|
78
|
+
})).url;
|
|
79
|
+
},
|
|
80
|
+
catch: (cause) => {
|
|
81
|
+
return new UploadError({
|
|
82
|
+
message: `Upload failed: ${String(cause)}`,
|
|
83
|
+
file: file.relativePath,
|
|
84
|
+
cause: String(cause)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
yield* Effect.logDebug("Upload complete", {
|
|
89
|
+
path: file.relativePath,
|
|
90
|
+
url: blobUrl
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
relativePath: file.relativePath,
|
|
94
|
+
hash: file.hash,
|
|
95
|
+
blobUrl
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Upload multiple source maps in parallel using secure client uploads.
|
|
101
|
+
* Each upload gets a path-scoped token from the server.
|
|
102
|
+
*/
|
|
103
|
+
uploadBatch(releaseSlug, files, batchPaths) {
|
|
104
|
+
const { config } = this;
|
|
105
|
+
const uploadFile = this.uploadFile.bind(this);
|
|
106
|
+
return Effect.gen(function* () {
|
|
107
|
+
const results = yield* Effect.forEach(files, (file) => {
|
|
108
|
+
const uploadInfo = batchPaths.uploadPaths[file.relativePath];
|
|
109
|
+
if (!uploadInfo) return Effect.fail(new UploadError({
|
|
110
|
+
file: file.relativePath,
|
|
111
|
+
message: "No upload path found for file"
|
|
112
|
+
}));
|
|
113
|
+
return uploadFile(releaseSlug, file, uploadInfo);
|
|
114
|
+
}, { concurrency: config.concurrency });
|
|
115
|
+
yield* Effect.logDebug("Batch upload complete", {
|
|
116
|
+
uploaded: results.length,
|
|
117
|
+
total: files.length
|
|
118
|
+
});
|
|
119
|
+
return results;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Notify the server that uploads are complete
|
|
124
|
+
*/
|
|
125
|
+
notifyCompletion(releaseSlug, notification) {
|
|
126
|
+
const { apiUrl, secretKey, config } = this;
|
|
127
|
+
return Effect.gen(function* () {
|
|
128
|
+
const url = `${apiUrl}/releases/${releaseSlug}/source-maps/process`;
|
|
129
|
+
yield* Effect.logDebug("Notifying upload completion", {
|
|
130
|
+
releaseSlug,
|
|
131
|
+
fileCount: notification.uploadedFiles.length
|
|
132
|
+
});
|
|
133
|
+
const notifyStart = Date.now();
|
|
134
|
+
const response = yield* Effect.tryPromise({
|
|
135
|
+
try: async () => {
|
|
136
|
+
const processingTimeout = Math.max(config.uploadTimeoutMs * 2, 6e4);
|
|
137
|
+
const requestStart = Date.now();
|
|
138
|
+
const res = await fetch(url, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: {
|
|
141
|
+
Authorization: `Bearer ${secretKey}`,
|
|
142
|
+
"Content-Type": "application/json"
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify(notification),
|
|
145
|
+
signal: AbortSignal.timeout(processingTimeout)
|
|
146
|
+
});
|
|
147
|
+
const requestTime = Date.now() - requestStart;
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
const error = await res.text().catch(() => "Unknown error");
|
|
150
|
+
throw new NotificationError({
|
|
151
|
+
status: res.status,
|
|
152
|
+
message: error
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const parseStart = Date.now();
|
|
156
|
+
const json = await res.json();
|
|
157
|
+
const parseTime = Date.now() - parseStart;
|
|
158
|
+
if (typeof json !== "object" || json === null) throw new NotificationError({
|
|
159
|
+
status: res.status,
|
|
160
|
+
message: "Invalid response format"
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
json,
|
|
164
|
+
requestTime,
|
|
165
|
+
parseTime,
|
|
166
|
+
status: res.status
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
catch: (error) => {
|
|
170
|
+
if (error instanceof NotificationError) return error;
|
|
171
|
+
if (error instanceof Error && error.name === "AbortError") return new NotificationError({ message: `Request timed out after ${config.uploadTimeoutMs * 2}ms` });
|
|
172
|
+
return new NotificationError({ message: String(error) });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
yield* Effect.logDebug(`Notification request completed in ${response.requestTime}ms`, { status: response.status });
|
|
176
|
+
yield* Effect.logDebug(`Response parsed in ${response.parseTime}ms`);
|
|
177
|
+
const notifyTime = Date.now() - notifyStart;
|
|
178
|
+
yield* Effect.logDebug(`Server notified of upload completion in ${notifyTime}ms`, {
|
|
179
|
+
fileCount: notification.uploadedFiles.length,
|
|
180
|
+
processed: response.json.processed ?? "unknown"
|
|
181
|
+
});
|
|
182
|
+
return response.json;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Complete workflow: request paths, upload files with secure tokens, notify completion
|
|
187
|
+
*
|
|
188
|
+
* Flow:
|
|
189
|
+
* 1. Request upload paths from API (paths only, no tokens)
|
|
190
|
+
* 2. Upload files using secure client uploads (server issues path-scoped tokens)
|
|
191
|
+
* 3. Notify API that uploads are complete for processing
|
|
192
|
+
*
|
|
193
|
+
* @param sourceMapToGenerated - Mapping from source map filename to generated JS file path
|
|
194
|
+
*/
|
|
195
|
+
uploadSourceMaps(releaseSlug, sourceMapFiles, sourceMapToGenerated) {
|
|
196
|
+
const requestUploadPaths = this.requestUploadPaths.bind(this);
|
|
197
|
+
const uploadBatch = this.uploadBatch.bind(this);
|
|
198
|
+
const notifyCompletion = this.notifyCompletion.bind(this);
|
|
199
|
+
return pipe(Effect.gen(function* () {
|
|
200
|
+
if (sourceMapFiles.length === 0) {
|
|
201
|
+
yield* Effect.logInfo("No source maps to upload");
|
|
202
|
+
return {
|
|
203
|
+
uploaded: 0,
|
|
204
|
+
processed: 0
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const uploadedSourceMaps = yield* uploadBatch(releaseSlug, sourceMapFiles, yield* requestUploadPaths(releaseSlug, {
|
|
208
|
+
files: sourceMapFiles.map(({ content, ...file }) => file),
|
|
209
|
+
folderPrefix: "source-maps"
|
|
210
|
+
}));
|
|
211
|
+
const result = yield* notifyCompletion(releaseSlug, {
|
|
212
|
+
uploadedFiles: uploadedSourceMaps,
|
|
213
|
+
sourceMapToGenerated
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
uploaded: uploadedSourceMaps.length,
|
|
217
|
+
processed: result.processed
|
|
218
|
+
};
|
|
219
|
+
}), Effect.withSpan("source-maps.upload", { attributes: {
|
|
220
|
+
releaseSlug,
|
|
221
|
+
sourceMapCount: sourceMapFiles.length,
|
|
222
|
+
mappingCount: Object.keys(sourceMapToGenerated).length
|
|
223
|
+
} }), Effect.tap((result) => Effect.logDebug("Source map upload succeeded", result)));
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
export { SourceMapClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../../src/build/source-maps/client.ts"],"sourcesContent":["import {\n type BatchUploadPaths,\n batchUploadPathsSchema,\n type SourceMapConfig,\n type SourceMapFile,\n sourceMapConfigSchema,\n type UploadedBlob,\n} from \"@interfere/types/data/source-maps\";\n\nimport { Effect, pipe } from \"effect\";\nimport type { PreflightConfig } from \"../../lib/types.js\";\nimport { NotificationError, TokenRequestError, UploadError } from \"./errors.js\";\n\ntype SourceMapFileWithContent = SourceMapFile & {\n content: string;\n};\n\nexport class SourceMapClient {\n private readonly config: SourceMapConfig;\n private readonly apiUrl: string;\n private readonly secretKey: string;\n\n constructor(\n preflightConfig: PreflightConfig,\n apiUrl: string,\n options?: Partial<SourceMapConfig>\n ) {\n if (!preflightConfig.enabled) {\n throw new Error(\"Source maps are disabled\");\n }\n\n this.config = sourceMapConfigSchema.parse({\n ...options,\n ...preflightConfig,\n });\n\n this.apiUrl = apiUrl;\n this.secretKey = preflightConfig.secretKey;\n }\n\n /**\n * Request upload paths for source maps (paths only, no tokens).\n * Tokens are generated per-file via the /upload endpoint.\n */\n requestUploadPaths(\n releaseSlug: string,\n request: { files: SourceMapFile[]; folderPrefix?: string }\n ) {\n const { apiUrl, secretKey, config } = this;\n\n return Effect.gen(function* () {\n const url = `${apiUrl}/releases/${releaseSlug}/source-maps/tokens`;\n\n yield* Effect.logDebug(\"Requesting upload paths\", {\n releaseSlug,\n fileCount: request.files.length,\n });\n\n const response = yield* Effect.tryPromise({\n try: async () => {\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n signal: AbortSignal.timeout(config.uploadTimeoutMs),\n });\n\n if (!res.ok) {\n const error = await res.text().catch((e) => e);\n\n throw new TokenRequestError({\n message: `POST ${url} failed: ${res.status} ${res.statusText} - ${error.slice(0, 100)}...`,\n });\n }\n\n const data = await res.json();\n const parsed = batchUploadPathsSchema.parse(data);\n\n return parsed;\n },\n catch: (error) => {\n if (error instanceof TokenRequestError) {\n return error;\n }\n\n return new TokenRequestError({\n message: String(error),\n });\n },\n });\n\n yield* Effect.logDebug(\"Received upload paths\", {\n pathCount: Object.keys(response.uploadPaths).length,\n });\n\n return response;\n });\n }\n\n /**\n * Upload a source map file using Vercel Blob's secure client upload.\n * The server validates auth and issues a path-scoped token via /upload endpoint.\n */\n uploadFile(\n releaseSlug: string,\n file: SourceMapFileWithContent,\n uploadInfo: { uploadPath: string; contentType: string }\n ) {\n const { apiUrl, secretKey } = this;\n\n return Effect.gen(function* () {\n yield* Effect.logDebug(\"Uploading source map\", {\n path: file.relativePath,\n size: file.size,\n });\n\n const blobUrl = yield* Effect.tryPromise({\n try: async () => {\n if (!file.content) {\n throw new Error(\"File content is required for upload\");\n }\n\n // Use Vercel Blob client SDK for secure uploads\n // The server validates auth and returns a path-scoped token\n const { upload } = await import(\"@vercel/blob/client\");\n \n const result = await upload(uploadInfo.uploadPath, file.content, {\n access: \"public\",\n handleUploadUrl: `${apiUrl}/releases/${releaseSlug}/source-maps/upload`,\n clientPayload: JSON.stringify({ secretKey }),\n contentType: uploadInfo.contentType,\n });\n\n return result.url;\n },\n catch: (cause) => {\n return new UploadError({\n message: `Upload failed: ${String(cause)}`,\n file: file.relativePath,\n cause: String(cause),\n });\n },\n });\n\n yield* Effect.logDebug(\"Upload complete\", {\n path: file.relativePath,\n url: blobUrl,\n });\n\n return {\n relativePath: file.relativePath,\n hash: file.hash,\n blobUrl,\n };\n });\n }\n\n /**\n * Upload multiple source maps in parallel using secure client uploads.\n * Each upload gets a path-scoped token from the server.\n */\n uploadBatch(\n releaseSlug: string,\n files: SourceMapFileWithContent[],\n batchPaths: BatchUploadPaths\n ) {\n const { config } = this;\n const uploadFile = this.uploadFile.bind(this);\n\n return Effect.gen(function* () {\n // Upload files in parallel with concurrency limit\n const results = yield* Effect.forEach(\n files,\n (file) => {\n const uploadInfo = batchPaths.uploadPaths[file.relativePath];\n if (!uploadInfo) {\n return Effect.fail(\n new UploadError({\n file: file.relativePath,\n message: \"No upload path found for file\",\n })\n );\n }\n return uploadFile(releaseSlug, file, uploadInfo);\n },\n { concurrency: config.concurrency }\n );\n\n // Internal debug – the user‑visible status comes from the build logger\n yield* Effect.logDebug(\"Batch upload complete\", {\n uploaded: results.length,\n total: files.length,\n });\n\n return results;\n });\n }\n\n /**\n * Notify the server that uploads are complete\n */\n notifyCompletion(\n releaseSlug: string,\n notification: {\n uploadedFiles: UploadedBlob[];\n sourceMapToGenerated?: Record<string, string>;\n }\n ) {\n const { apiUrl, secretKey, config } = this;\n\n return Effect.gen(function* () {\n const url = `${apiUrl}/releases/${releaseSlug}/source-maps/process`;\n\n yield* Effect.logDebug(\"Notifying upload completion\", {\n releaseSlug,\n fileCount: notification.uploadedFiles.length,\n });\n\n const notifyStart = Date.now();\n const response = yield* Effect.tryPromise({\n try: async () => {\n // Use a longer timeout for processing notification since it can take time\n const processingTimeout = Math.max(config.uploadTimeoutMs * 2, 60_000);\n \n const requestStart = Date.now();\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${secretKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(notification),\n signal: AbortSignal.timeout(processingTimeout),\n });\n const requestTime = Date.now() - requestStart;\n\n if (!res.ok) {\n const error = await res.text().catch(() => \"Unknown error\");\n throw new NotificationError({\n status: res.status,\n message: error,\n });\n }\n\n const parseStart = Date.now();\n const json = await res.json();\n const parseTime = Date.now() - parseStart;\n \n // Validate response has expected shape\n if (typeof json !== \"object\" || json === null) {\n throw new NotificationError({\n status: res.status,\n message: \"Invalid response format\",\n });\n }\n\n return { json, requestTime, parseTime, status: res.status };\n },\n catch: (error) => {\n if (error instanceof NotificationError) {\n return error;\n }\n \n // Handle timeout/abort errors\n if (error instanceof Error && error.name === \"AbortError\") {\n return new NotificationError({\n message: `Request timed out after ${config.uploadTimeoutMs * 2}ms`,\n });\n }\n \n return new NotificationError({\n message: String(error),\n });\n },\n });\n\n yield* Effect.logDebug(`Notification request completed in ${response.requestTime}ms`, {\n status: response.status,\n });\n yield* Effect.logDebug(`Response parsed in ${response.parseTime}ms`);\n\n const notifyTime = Date.now() - notifyStart;\n yield* Effect.logDebug(`Server notified of upload completion in ${notifyTime}ms`, {\n fileCount: notification.uploadedFiles.length,\n processed: response.json.processed ?? \"unknown\",\n });\n\n return response.json;\n });\n }\n\n /**\n * Complete workflow: request paths, upload files with secure tokens, notify completion\n *\n * Flow:\n * 1. Request upload paths from API (paths only, no tokens)\n * 2. Upload files using secure client uploads (server issues path-scoped tokens)\n * 3. Notify API that uploads are complete for processing\n * \n * @param sourceMapToGenerated - Mapping from source map filename to generated JS file path\n */\n uploadSourceMaps(\n releaseSlug: string,\n sourceMapFiles: SourceMapFileWithContent[],\n sourceMapToGenerated: Record<string, string>\n ) {\n const requestUploadPaths = this.requestUploadPaths.bind(this);\n const uploadBatch = this.uploadBatch.bind(this);\n const notifyCompletion = this.notifyCompletion.bind(this);\n\n return pipe(\n Effect.gen(function* () {\n if (sourceMapFiles.length === 0) {\n yield* Effect.logInfo(\"No source maps to upload\");\n return { uploaded: 0, processed: 0 };\n }\n\n // Step 1: Request upload paths for source maps (only send metadata, not content)\n const sourceMapPaths = yield* requestUploadPaths(releaseSlug, {\n files: sourceMapFiles.map(({ content, ...file }) => file),\n folderPrefix: \"source-maps\",\n });\n\n // Step 2: Upload source maps in parallel with secure client uploads\n // Each upload gets a path-scoped token from the /upload endpoint\n const uploadedSourceMaps = yield* uploadBatch(\n releaseSlug,\n sourceMapFiles,\n sourceMapPaths\n );\n\n // Step 3: Notify completion with source map to generated file mapping\n // (JS files are not uploaded, but we send the mapping for processing)\n const result = yield* notifyCompletion(releaseSlug, {\n uploadedFiles: uploadedSourceMaps,\n sourceMapToGenerated,\n });\n\n return {\n uploaded: uploadedSourceMaps.length,\n processed: result.processed,\n };\n }),\n Effect.withSpan(\"source-maps.upload\", {\n attributes: {\n releaseSlug,\n sourceMapCount: sourceMapFiles.length,\n mappingCount: Object.keys(sourceMapToGenerated).length,\n },\n }),\n // Let the outer releaseProgram own failure logging so we don't\n // print a separate top-level error line before the buffered\n // \"Failed to upload source maps\" block.\n Effect.tap((result) =>\n Effect.logDebug(\"Source map upload succeeded\", result)\n )\n );\n }\n}\n"],"mappings":"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"}
|