better-ship 0.25.0 → 0.27.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.
Files changed (73) hide show
  1. package/dist/auth-secondary-storage-b9ptwPSB.d.ts +37 -0
  2. package/dist/auth-secondary-storage-b9ptwPSB.d.ts.map +1 -0
  3. package/dist/better-auth/secondary-storage.d.ts +1 -1
  4. package/dist/bin/notify-ci.d.ts +1 -0
  5. package/dist/bin/notify-ci.js +24 -0
  6. package/dist/bin/notify-ci.js.map +1 -0
  7. package/dist/client-Dv1Ch7yY.js +20 -0
  8. package/dist/client-Dv1Ch7yY.js.map +1 -0
  9. package/dist/cloudflare/auth-secondary-storage.d.ts +2 -36
  10. package/dist/cloudflare/durable-object-client.js +1 -1
  11. package/dist/cloudflare/durable-object-errors.js +2 -47
  12. package/dist/cloudflare/kv-client.js +1 -1
  13. package/dist/cloudflare/kv-errors.js +2 -143
  14. package/dist/cloudflare/r2-client.js +1 -1
  15. package/dist/cloudflare/r2-errors.js +2 -89
  16. package/dist/cloudflare/turnstile.d.ts +57 -0
  17. package/dist/cloudflare/turnstile.d.ts.map +1 -0
  18. package/dist/cloudflare/turnstile.js +191 -0
  19. package/dist/cloudflare/turnstile.js.map +1 -0
  20. package/dist/durable-object-errors-DkMFXQnK.js +48 -0
  21. package/dist/durable-object-errors-DkMFXQnK.js.map +1 -0
  22. package/dist/kv-errors-BUTpFy0t.js +144 -0
  23. package/dist/kv-errors-BUTpFy0t.js.map +1 -0
  24. package/dist/metrics-CWv37yf9.js +30 -0
  25. package/dist/metrics-CWv37yf9.js.map +1 -0
  26. package/dist/metrics-cv9P5nvk.d.ts +19 -0
  27. package/dist/metrics-cv9P5nvk.d.ts.map +1 -0
  28. package/dist/payment-service-DUYd3Itt.d.ts +52 -0
  29. package/dist/payment-service-DUYd3Itt.d.ts.map +1 -0
  30. package/dist/payment-service.error-CqF7GkNp.js +30 -0
  31. package/dist/payment-service.error-CqF7GkNp.js.map +1 -0
  32. package/dist/posthog/metrics.d.ts +2 -18
  33. package/dist/posthog/metrics.js +2 -29
  34. package/dist/posthog/server.d.ts +1 -1
  35. package/dist/posthog/server.js +1 -1
  36. package/dist/r2-errors-rk7n7rvk.js +90 -0
  37. package/dist/r2-errors-rk7n7rvk.js.map +1 -0
  38. package/dist/request-4vJEWxyZ.js +21 -0
  39. package/dist/request-4vJEWxyZ.js.map +1 -0
  40. package/dist/slack-webhook-j1FCPeu8.js +270 -0
  41. package/dist/slack-webhook-j1FCPeu8.js.map +1 -0
  42. package/dist/slack.d.ts +102 -0
  43. package/dist/slack.d.ts.map +1 -0
  44. package/dist/slack.js +3 -0
  45. package/dist/stripe/client.js +2 -19
  46. package/dist/stripe/payment-service.d.ts +2 -51
  47. package/dist/stripe/payment-service.error.js +2 -29
  48. package/dist/stripe/payment-service.js +1 -1
  49. package/dist/stripe/request.js +2 -20
  50. package/dist/stripe-fixture-yFUknTJO.js +33 -0
  51. package/dist/stripe-fixture-yFUknTJO.js.map +1 -0
  52. package/dist/testing/payment-service.d.ts +1 -1
  53. package/dist/testing/payment-service.js +2 -2
  54. package/dist/testing/stripe.js +2 -32
  55. package/package.json +7 -1
  56. package/src/infrastructure/cloudflare/turnstile/index.ts +7 -0
  57. package/src/infrastructure/cloudflare/turnstile/turnstile.error.ts +21 -0
  58. package/src/infrastructure/cloudflare/turnstile/turnstile.ts +261 -0
  59. package/src/infrastructure/slack/ci-message.ts +276 -0
  60. package/src/infrastructure/slack/index.ts +10 -0
  61. package/src/infrastructure/slack/notify-ci.ts +22 -0
  62. package/src/infrastructure/slack/slack-webhook.ts +36 -0
  63. package/dist/cloudflare/auth-secondary-storage.d.ts.map +0 -1
  64. package/dist/cloudflare/durable-object-errors.js.map +0 -1
  65. package/dist/cloudflare/kv-errors.js.map +0 -1
  66. package/dist/cloudflare/r2-errors.js.map +0 -1
  67. package/dist/posthog/metrics.d.ts.map +0 -1
  68. package/dist/posthog/metrics.js.map +0 -1
  69. package/dist/stripe/client.js.map +0 -1
  70. package/dist/stripe/payment-service.d.ts.map +0 -1
  71. package/dist/stripe/payment-service.error.js.map +0 -1
  72. package/dist/stripe/request.js.map +0 -1
  73. package/dist/testing/stripe.js.map +0 -1
@@ -1,30 +1,3 @@
1
- //#region src/infrastructure/posthog/metrics.ts
2
- /** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
3
- var PostHogMetricsService = class {
4
- client;
5
- waitUntil;
6
- /** Use the analytics client's metrics; the runtime owns each flush promise. */
7
- constructor(client, waitUntil) {
8
- this.client = client;
9
- this.waitUntil = waitUntil;
10
- }
11
- /** Record a total and schedule its flush. */
12
- count(name, value = 1, options) {
13
- this.client.metrics.count(name, value, options);
14
- this.waitUntil(this.client.metrics.flush());
15
- }
16
- /** Record a current value and schedule its flush. */
17
- gauge(name, value, options) {
18
- this.client.metrics.gauge(name, value, options);
19
- this.waitUntil(this.client.metrics.flush());
20
- }
21
- /** Record an observation and schedule its flush. */
22
- histogram(name, value, options) {
23
- this.client.metrics.histogram(name, value, options);
24
- this.waitUntil(this.client.metrics.flush());
25
- }
26
- };
1
+ import { t as PostHogMetricsService } from "../metrics-CWv37yf9.js";
27
2
 
28
- //#endregion
29
- export { PostHogMetricsService };
30
- //# sourceMappingURL=metrics.js.map
3
+ export { PostHogMetricsService };
@@ -1,6 +1,6 @@
1
1
  import { Gt as AnalyticsEventIdentity, Kt as AnalyticsEventInput, qt as AnalyticsProperties } from "../index-DUFS0SJ_.js";
2
2
  import { $ as AnalyticsService } from "../index-lS_g1olK.js";
3
- import { PostHogMetricsService } from "./metrics.js";
3
+ import { t as PostHogMetricsService } from "../metrics-cv9P5nvk.js";
4
4
  import { PostHog, PostHogOptions } from "posthog-node";
5
5
  //#region src/infrastructure/posthog/server.d.ts
6
6
  /** Deployment values and SDK options; the runtime owns delivery promises. */
@@ -1,5 +1,5 @@
1
1
  import { tt as MissingConfigurationError } from "../core-BiN6S_cm.js";
2
- import { PostHogMetricsService } from "./metrics.js";
2
+ import { t as PostHogMetricsService } from "../metrics-CWv37yf9.js";
3
3
  import { PostHog } from "posthog-node";
4
4
 
5
5
  //#region src/infrastructure/posthog/server.ts
@@ -0,0 +1,90 @@
1
+ import { nt as AppError, u as classifyBoundaryError, v as isAbortError } from "./core-BiN6S_cm.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/infrastructure/cloudflare/r2-errors.ts
5
+ const R2FailureSchema = z.object({
6
+ code: z.number().int().positive(),
7
+ action: z.string().optional()
8
+ });
9
+ const classifications = /* @__PURE__ */ new Map([
10
+ [10001, "transient"],
11
+ [10043, "transient"],
12
+ [10054, "transient"],
13
+ [10058, "transient"],
14
+ [10013, "transient"],
15
+ [10002, "terminal"],
16
+ [10003, "terminal"],
17
+ [10018, "terminal"],
18
+ [10035, "terminal"],
19
+ [10042, "terminal"],
20
+ [10005, "terminal"],
21
+ [10006, "terminal"],
22
+ [10008, "terminal"],
23
+ [10009, "terminal"],
24
+ [10073, "terminal"],
25
+ [10007, "terminal"],
26
+ [10020, "terminal"],
27
+ [100100, "terminal"],
28
+ [10012, "terminal"],
29
+ [10069, "terminal"],
30
+ [10033, "terminal"],
31
+ [10014, "terminal"],
32
+ [10037, "terminal"],
33
+ [10039, "terminal"],
34
+ [10031, "terminal"],
35
+ [10011, "terminal"],
36
+ [10024, "terminal"],
37
+ [10025, "terminal"],
38
+ [10048, "terminal"]
39
+ ]);
40
+ function extractR2Failure(cause) {
41
+ if (!(cause instanceof Error)) return void 0;
42
+ const parsed = R2FailureSchema.safeParse(cause);
43
+ if (parsed.success) return parsed.data;
44
+ const match = /\((\d{5,6})\)\s*$/.exec(cause.message);
45
+ return match?.[1] === void 0 ? void 0 : { code: Number(match[1]) };
46
+ }
47
+ function classifyR2Code(cause) {
48
+ const failure = extractR2Failure(cause);
49
+ return failure === void 0 ? void 0 : classifications.get(failure.code) ?? "unknown";
50
+ }
51
+ function classifyR2Error(cause) {
52
+ if (isAbortError(cause)) return "terminal";
53
+ if (cause instanceof AppError) return cause.classification;
54
+ return classifyBoundaryError({
55
+ cause,
56
+ classifyBoundary: classifyR2Code,
57
+ includeCloudflare: true
58
+ });
59
+ }
60
+ /** A raw binding failure after one attempt. Classification does not establish whether a mutation committed. */
61
+ var R2Error = class extends AppError {
62
+ _tag = "R2Error";
63
+ classification;
64
+ operation;
65
+ r2;
66
+ cause;
67
+ constructor(input) {
68
+ super(`R2 ${input.operation} failed`, { cause: input.cause });
69
+ this.operation = input.operation;
70
+ this.cause = input.cause;
71
+ this.r2 = extractR2Failure(input.cause);
72
+ this.classification = classifyR2Error(input.cause);
73
+ }
74
+ };
75
+ /** JSON serialization or syntax failed. Binding and body-read failures use R2Error. */
76
+ var R2JsonError = class extends AppError {
77
+ _tag = "R2JsonError";
78
+ classification = "terminal";
79
+ operation;
80
+ cause;
81
+ constructor(input) {
82
+ super(`R2 JSON ${input.operation} failed`, { cause: input.cause });
83
+ this.operation = input.operation;
84
+ this.cause = input.cause;
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ export { R2JsonError as n, R2Error as t };
90
+ //# sourceMappingURL=r2-errors-rk7n7rvk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-errors-rk7n7rvk.js","names":[],"sources":["../src/infrastructure/cloudflare/r2-errors.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { AppError, classifyBoundaryError, isAbortError, type FailureClassification } from '@/core'\n\n/** The storage operation that failed. */\nexport type R2Operation =\n | 'head'\n | 'get'\n | 'getJson'\n | 'put'\n | 'delete'\n | 'list'\n | 'createMultipartUpload'\n | 'uploadPart'\n | 'complete'\n | 'abort'\n\n/** Provider diagnostics from the outer failure, without request bodies or credentials. */\nexport type R2Failure = {\n readonly code: number\n readonly action?: string | undefined\n}\n\nconst R2FailureSchema = z.object({\n code: z.number().int().positive(),\n action: z.string().optional(),\n})\n\n// R2 codes describe failures; the caller owns retry decisions and repeat safety.\nconst classifications: ReadonlyMap<number, FailureClassification> = new Map([\n [10001, 'transient'],\n [10043, 'transient'],\n [10054, 'transient'],\n [10058, 'transient'],\n [10013, 'transient'],\n [10002, 'terminal'],\n [10003, 'terminal'],\n [10018, 'terminal'],\n [10035, 'terminal'],\n [10042, 'terminal'],\n [10005, 'terminal'],\n [10006, 'terminal'],\n [10008, 'terminal'],\n [10009, 'terminal'],\n [10073, 'terminal'],\n [10007, 'terminal'],\n [10020, 'terminal'],\n [100100, 'terminal'],\n [10012, 'terminal'],\n [10069, 'terminal'],\n [10033, 'terminal'],\n [10014, 'terminal'],\n [10037, 'terminal'],\n [10039, 'terminal'],\n [10031, 'terminal'],\n [10011, 'terminal'],\n [10024, 'terminal'],\n [10025, 'terminal'],\n [10048, 'terminal'],\n])\n\nfunction extractR2Failure(cause: unknown): R2Failure | undefined {\n if (!(cause instanceof Error)) return undefined\n const parsed = R2FailureSchema.safeParse(cause)\n if (parsed.success) return parsed.data\n // Cloudflare documents the numeric suffix for bindings that omit structured fields.\n const match = /\\((\\d{5,6})\\)\\s*$/.exec(cause.message)\n return match?.[1] === undefined ? undefined : { code: Number(match[1]) }\n}\n\nfunction classifyR2Code(cause: unknown): FailureClassification | undefined {\n const failure = extractR2Failure(cause)\n return failure === undefined ? undefined : (classifications.get(failure.code) ?? 'unknown')\n}\n\nfunction classifyR2Error(cause: unknown): FailureClassification {\n if (isAbortError(cause)) return 'terminal'\n if (cause instanceof AppError) return cause.classification\n return classifyBoundaryError({ cause, classifyBoundary: classifyR2Code, includeCloudflare: true })\n}\n\n/** A raw binding failure after one attempt. Classification does not establish whether a mutation committed. */\nexport class R2Error extends AppError {\n readonly _tag = 'R2Error'\n readonly classification: FailureClassification\n readonly operation: R2Operation\n readonly r2?: R2Failure | undefined\n override readonly cause: unknown\n\n constructor(input: { operation: R2Operation; cause: unknown }) {\n super(`R2 ${input.operation} failed`, { cause: input.cause })\n this.operation = input.operation\n this.cause = input.cause\n this.r2 = extractR2Failure(input.cause)\n this.classification = classifyR2Error(input.cause)\n }\n}\n\n/** JSON serialization or syntax failed. Binding and body-read failures use R2Error. */\nexport class R2JsonError extends AppError {\n readonly _tag = 'R2JsonError'\n readonly classification = 'terminal'\n readonly operation: 'encode' | 'decode'\n override readonly cause: unknown\n\n constructor(input: { operation: 'encode' | 'decode'; cause: unknown }) {\n super(`R2 JSON ${input.operation} failed`, { cause: input.cause })\n this.operation = input.operation\n this.cause = input.cause\n }\n}\n"],"mappings":";;;;AAuBA,MAAM,kBAAkB,EAAE,OAAO;CAC/B,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAChC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;AAC9B,CAAC;AAGD,MAAM,kCAA8D,IAAI,IAAI;CAC1E,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,QAAQ,UAAU;CACnB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;AACpB,CAAC;AAED,SAAS,iBAAiB,OAAuC;CAC/D,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,MAAM,SAAS,gBAAgB,UAAU,KAAK;CAC9C,IAAI,OAAO,SAAS,OAAO,OAAO;CAElC,MAAM,QAAQ,oBAAoB,KAAK,MAAM,OAAO;CACpD,OAAO,QAAQ,OAAO,SAAY,SAAY,EAAE,MAAM,OAAO,MAAM,EAAE,EAAE;AACzE;AAEA,SAAS,eAAe,OAAmD;CACzE,MAAM,UAAU,iBAAiB,KAAK;CACtC,OAAO,YAAY,SAAY,SAAa,gBAAgB,IAAI,QAAQ,IAAI,KAAK;AACnF;AAEA,SAAS,gBAAgB,OAAuC;CAC9D,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,IAAI,iBAAiB,UAAU,OAAO,MAAM;CAC5C,OAAO,sBAAsB;EAAE;EAAO,kBAAkB;EAAgB,mBAAmB;CAAK,CAAC;AACnG;;AAGA,IAAa,UAAb,cAA6B,SAAS;CACpC,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CACT,AAAS;CACT,AAAkB;CAElB,YAAY,OAAmD;EAC7D,MAAM,MAAM,MAAM,UAAU,UAAU,EAAE,OAAO,MAAM,MAAM,CAAC;EAC5D,KAAK,YAAY,MAAM;EACvB,KAAK,QAAQ,MAAM;EACnB,KAAK,KAAK,iBAAiB,MAAM,KAAK;EACtC,KAAK,iBAAiB,gBAAgB,MAAM,KAAK;CACnD;AACF;;AAGA,IAAa,cAAb,cAAiC,SAAS;CACxC,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAC1B,AAAS;CACT,AAAkB;CAElB,YAAY,OAA2D;EACrE,MAAM,WAAW,MAAM,UAAU,UAAU,EAAE,OAAO,MAAM,MAAM,CAAC;EACjE,KAAK,YAAY,MAAM;EACvB,KAAK,QAAQ,MAAM;CACrB;AACF"}
@@ -0,0 +1,21 @@
1
+ import { t as PaymentServiceError } from "./payment-service.error-CqF7GkNp.js";
2
+ import Stripe from "stripe";
3
+
4
+ //#region src/infrastructure/stripe/request.ts
5
+ /**
6
+ * Invoke one SDK operation; the SDK owns retries configured on its client or request.
7
+ * Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.
8
+ * Non-Stripe failures propagate unchanged.
9
+ */
10
+ async function stripeRequest(operation, work) {
11
+ try {
12
+ return await work();
13
+ } catch (cause) {
14
+ if (!(cause instanceof Stripe.errors.StripeError)) throw cause;
15
+ throw new PaymentServiceError(operation, cause);
16
+ }
17
+ }
18
+
19
+ //#endregion
20
+ export { stripeRequest as t };
21
+ //# sourceMappingURL=request-4vJEWxyZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-4vJEWxyZ.js","names":[],"sources":["../src/infrastructure/stripe/request.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { PaymentServiceError } from './payment-service.error.ts'\n\n/**\n * Invoke one SDK operation; the SDK owns retries configured on its client or request.\n * Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.\n * Non-Stripe failures propagate unchanged.\n */\nexport async function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n if (!(cause instanceof Stripe.errors.StripeError)) throw cause\n throw new PaymentServiceError(operation, cause)\n }\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,cAAiB,WAAmB,MAAoC;CAC5F,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,OAAO,OAAO,cAAc,MAAM;EACzD,MAAM,IAAI,oBAAoB,WAAW,KAAK;CAChD;AACF"}
@@ -0,0 +1,270 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/infrastructure/slack/ci-message.ts
4
+ /** The CI environment could not be parsed. `issues` names each invalid variable. */
5
+ var CiRunParseError = class extends Error {
6
+ issues;
7
+ cause;
8
+ _tag = "CiRunParseError";
9
+ constructor(issues, cause) {
10
+ super(`Invalid CI environment: ${issues.join(", ")}`);
11
+ this.issues = issues;
12
+ this.cause = cause;
13
+ }
14
+ };
15
+ const OutcomeSchema = z.enum([
16
+ "success",
17
+ "failure",
18
+ "cancelled",
19
+ "skipped"
20
+ ]);
21
+ const jsonRecord = (value) => z.string().transform((text, context) => {
22
+ try {
23
+ return JSON.parse(text);
24
+ } catch {
25
+ context.addIssue({
26
+ code: "custom",
27
+ message: "Invalid JSON"
28
+ });
29
+ return z.NEVER;
30
+ }
31
+ }).pipe(z.record(z.string(), value));
32
+ const CiEnvSchema = z.object({
33
+ REPO: z.string().regex(/^[\w.-]+\/[\w.-]+$/),
34
+ COMMIT_SHA: z.string().regex(/^[0-9a-f]{40}$/),
35
+ BRANCH: z.string().min(1),
36
+ RUN_URL: z.httpUrl(),
37
+ JOBS_JSON: jsonRecord(z.object({ result: OutcomeSchema })).optional(),
38
+ STEPS_JSON: jsonRecord(z.object({ outcome: OutcomeSchema })).optional(),
39
+ PR_NUMBER: z.coerce.number().int().positive().optional(),
40
+ PR_URL: z.httpUrl().optional(),
41
+ PR_TITLE: z.string().min(1).optional(),
42
+ ACTION: z.enum([
43
+ "check",
44
+ "deploy",
45
+ "publish"
46
+ ]).default("check"),
47
+ CLOUDFLARE_ACCOUNT_ID: z.string().min(1).optional(),
48
+ CLOUDFLARE_WORKER: z.string().min(1).optional(),
49
+ SITE_URL: z.httpUrl().optional(),
50
+ DEPLOY_VERSION_ID: z.string().min(1).optional(),
51
+ NPM_PACKAGE: z.string().min(1).optional(),
52
+ NPM_VERSION: z.string().min(1).optional()
53
+ });
54
+ /**
55
+ * Parse the variables that a workflow passes to the notifier. GitHub expressions render a
56
+ * missing value as an empty string, so an empty variable counts as unset.
57
+ * @throws {CiRunParseError} when a variable is missing or invalid.
58
+ */
59
+ function parseCiRun(env) {
60
+ const present = Object.fromEntries(Object.entries(env).filter(([, value]) => value !== ""));
61
+ const parsed = CiEnvSchema.safeParse(present);
62
+ if (!parsed.success) {
63
+ const issues = parsed.error.issues.map((issue) => issue.path.join("."));
64
+ throw new CiRunParseError(issues, parsed.error);
65
+ }
66
+ return toCiRun(parsed.data);
67
+ }
68
+ function toCiRun(env) {
69
+ const ref = env.PR_NUMBER !== void 0 && env.PR_URL !== void 0 && env.PR_TITLE !== void 0 ? {
70
+ kind: "pullRequest",
71
+ number: env.PR_NUMBER,
72
+ url: env.PR_URL,
73
+ title: env.PR_TITLE
74
+ } : {
75
+ kind: "branch",
76
+ name: env.BRANCH
77
+ };
78
+ return {
79
+ repository: env.REPO,
80
+ commitSha: env.COMMIT_SHA,
81
+ ref,
82
+ runUrl: env.RUN_URL,
83
+ jobs: Object.entries(env.JOBS_JSON ?? {}).map(([id, job]) => ({
84
+ name: displayName(id),
85
+ outcome: job.result
86
+ })),
87
+ steps: Object.entries(env.STEPS_JSON ?? {}).map(([id, step]) => ({
88
+ name: displayName(id),
89
+ outcome: step.outcome
90
+ })),
91
+ target: toTarget(env)
92
+ };
93
+ }
94
+ function toTarget(env) {
95
+ switch (env.ACTION) {
96
+ case "check": return { kind: "check" };
97
+ case "deploy": {
98
+ const { CLOUDFLARE_ACCOUNT_ID: accountId, CLOUDFLARE_WORKER: worker, SITE_URL: siteUrl } = env;
99
+ if (accountId === void 0 || worker === void 0 || siteUrl === void 0) throw new CiRunParseError([
100
+ "CLOUDFLARE_ACCOUNT_ID",
101
+ "CLOUDFLARE_WORKER",
102
+ "SITE_URL"
103
+ ], void 0);
104
+ return {
105
+ kind: "deploy",
106
+ accountId,
107
+ worker,
108
+ siteUrl,
109
+ versionId: env.DEPLOY_VERSION_ID
110
+ };
111
+ }
112
+ case "publish":
113
+ if (env.NPM_PACKAGE === void 0) throw new CiRunParseError(["NPM_PACKAGE"], void 0);
114
+ return {
115
+ kind: "publish",
116
+ packageName: env.NPM_PACKAGE,
117
+ version: env.NPM_VERSION
118
+ };
119
+ }
120
+ }
121
+ function displayName(id) {
122
+ const words = id.replaceAll(/[-_]/g, " ");
123
+ return words.charAt(0).toUpperCase() + words.slice(1);
124
+ }
125
+ const logos = {
126
+ depot: "https://avatars.githubusercontent.com/u/68166601?s=64",
127
+ github: "https://avatars.githubusercontent.com/u/9919?s=64",
128
+ cloudflare: "https://avatars.githubusercontent.com/u/314135?s=64",
129
+ npm: "https://avatars.githubusercontent.com/u/6078720?s=64"
130
+ };
131
+ /** Build the four-line CI message: result, change, links, and identity. */
132
+ function buildCiMessage(run, commit) {
133
+ const headline = headlineFor(run);
134
+ const change = run.ref.kind === "pullRequest" ? run.ref.title : commit.subject;
135
+ return {
136
+ text: `${headline} — ${change}`,
137
+ blocks: [
138
+ {
139
+ type: "section",
140
+ text: {
141
+ type: "mrkdwn",
142
+ text: `*${headline}*\n${escape(change)}`
143
+ }
144
+ },
145
+ {
146
+ type: "context",
147
+ elements: linkElements(run)
148
+ },
149
+ {
150
+ type: "context",
151
+ elements: [{
152
+ type: "mrkdwn",
153
+ text: identityLine(run, commit)
154
+ }]
155
+ }
156
+ ]
157
+ };
158
+ }
159
+ function headlineFor(run) {
160
+ const failedJobs = namesWith(run.jobs, "failure");
161
+ if (failedJobs) return `:x: CI failed: ${failedJobs}`;
162
+ const cancelledJobs = namesWith(run.jobs, "cancelled");
163
+ if (cancelledJobs) return `:white_circle: CI cancelled: ${cancelledJobs}`;
164
+ const failedSteps = namesWith(run.steps, "failure");
165
+ const { target } = run;
166
+ switch (target.kind) {
167
+ case "check": return failedSteps ? `:x: CI failed: ${failedSteps}` : ":white_check_mark: CI passed";
168
+ case "deploy":
169
+ if (failedSteps) return `:x: Deploy failed: ${failedSteps}`;
170
+ return target.versionId === void 0 ? ":white_check_mark: CI passed" : ":white_check_mark: Deployed to production";
171
+ case "publish":
172
+ if (failedSteps) return `:x: Publish failed: ${failedSteps}`;
173
+ return target.version === void 0 ? ":white_check_mark: CI passed, nothing to publish" : `:white_check_mark: Published ${target.packageName} ${target.version}`;
174
+ }
175
+ }
176
+ function namesWith(results, outcome) {
177
+ return results.filter((result) => result.outcome === outcome).map((result) => result.name).join(", ");
178
+ }
179
+ function linkElements(run) {
180
+ const runHost = new URL(run.runUrl).hostname;
181
+ const elements = [{
182
+ type: "image",
183
+ image_url: runHost.endsWith("github.com") ? logos.github : logos.depot,
184
+ alt_text: runHost
185
+ }, {
186
+ type: "mrkdwn",
187
+ text: `<${run.runUrl}|CI run>`
188
+ }];
189
+ const { target } = run;
190
+ if (target.kind === "deploy" && target.versionId !== void 0) {
191
+ const dashboard = `https://dash.cloudflare.com/${target.accountId}/workers/services/view/${target.worker}/production/deployments`;
192
+ const site = new URL(target.siteUrl);
193
+ elements.push({
194
+ type: "mrkdwn",
195
+ text: "·"
196
+ }, {
197
+ type: "image",
198
+ image_url: logos.cloudflare,
199
+ alt_text: "Cloudflare"
200
+ }, {
201
+ type: "mrkdwn",
202
+ text: `<${dashboard}|Version \`${target.versionId.slice(0, 8)}\`>`
203
+ }, {
204
+ type: "mrkdwn",
205
+ text: `· :globe_with_meridians: <${site.origin}|${site.host}>`
206
+ });
207
+ }
208
+ if (target.kind === "publish" && target.version !== void 0) {
209
+ const npm = `https://www.npmjs.com/package/${target.packageName}/v/${target.version}`;
210
+ elements.push({
211
+ type: "mrkdwn",
212
+ text: "·"
213
+ }, {
214
+ type: "image",
215
+ image_url: logos.npm,
216
+ alt_text: "npm"
217
+ }, {
218
+ type: "mrkdwn",
219
+ text: `<${npm}|${target.packageName}@${target.version}>`
220
+ });
221
+ }
222
+ return elements;
223
+ }
224
+ function identityLine(run, commit) {
225
+ return [
226
+ run.repository.split("/").at(-1) ?? run.repository,
227
+ run.ref.kind === "pullRequest" ? `<${run.ref.url}|PR #${run.ref.number}>` : run.ref.name,
228
+ `<${`https://github.com/${run.repository}/commit/${run.commitSha}`}|\`${run.commitSha.slice(0, 7)}\`>`,
229
+ escape(commit.author)
230
+ ].join(" · ");
231
+ }
232
+ function escape(text) {
233
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/infrastructure/slack/slack-webhook.ts
238
+ /** Slack refused the message or could not be reached. */
239
+ var SlackWebhookError = class extends Error {
240
+ status;
241
+ cause;
242
+ _tag = "SlackWebhookError";
243
+ constructor(status, cause) {
244
+ super(status === void 0 ? "Slack webhook unreachable" : `Slack webhook returned ${status}`);
245
+ this.status = status;
246
+ this.cause = cause;
247
+ }
248
+ };
249
+ /**
250
+ * Post one message to a Slack incoming webhook.
251
+ * @throws {SlackWebhookError} when the request fails or Slack answers with an error status.
252
+ */
253
+ async function postSlackMessage(webhookUrl, message, fetcher = fetch) {
254
+ let response;
255
+ try {
256
+ response = await fetcher(webhookUrl, {
257
+ method: "POST",
258
+ headers: { "Content-Type": "application/json" },
259
+ body: JSON.stringify(message),
260
+ signal: AbortSignal.timeout(3e4)
261
+ });
262
+ } catch (cause) {
263
+ throw new SlackWebhookError(void 0, cause);
264
+ }
265
+ if (!response.ok) throw new SlackWebhookError(response.status, await response.text());
266
+ }
267
+
268
+ //#endregion
269
+ export { parseCiRun as a, buildCiMessage as i, postSlackMessage as n, CiRunParseError as r, SlackWebhookError as t };
270
+ //# sourceMappingURL=slack-webhook-j1FCPeu8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-webhook-j1FCPeu8.js","names":[],"sources":["../src/infrastructure/slack/ci-message.ts","../src/infrastructure/slack/slack-webhook.ts"],"sourcesContent":["import { z } from 'zod'\n\n/** The result of one CI job or step, in GitHub Actions terms. */\nexport type CiOutcome = 'success' | 'failure' | 'cancelled' | 'skipped'\n\n/** What the run delivers after its checks pass. */\nexport type CiTarget =\n | { readonly kind: 'check' }\n | {\n readonly kind: 'deploy'\n readonly accountId: string\n readonly worker: string\n readonly siteUrl: string\n /** Absent when the run reached no deploy, such as a pull request. */\n readonly versionId: string | undefined\n }\n | {\n readonly kind: 'publish'\n readonly packageName: string\n /** Absent when npm already has the package version. */\n readonly version: string | undefined\n }\n\n/** One CI run, parsed from the environment that the workflow sets. */\nexport interface CiRun {\n readonly repository: string\n readonly commitSha: string\n readonly ref:\n | { readonly kind: 'branch'; readonly name: string }\n | {\n readonly kind: 'pullRequest'\n readonly number: number\n readonly url: string\n readonly title: string\n }\n readonly runUrl: string\n readonly jobs: ReadonlyArray<CiStepResult>\n readonly steps: ReadonlyArray<CiStepResult>\n readonly target: CiTarget\n}\n\n/** A named job or step and its outcome. */\nexport interface CiStepResult {\n readonly name: string\n readonly outcome: CiOutcome\n}\n\n/** The commit facts that only git knows. */\nexport interface CiCommit {\n readonly subject: string\n readonly author: string\n}\n\n/** A Slack incoming-webhook payload. */\nexport interface SlackMessage {\n readonly text: string\n readonly blocks: ReadonlyArray<SlackBlock>\n}\n\ntype SlackElement =\n | { readonly type: 'mrkdwn'; readonly text: string }\n | { readonly type: 'image'; readonly image_url: string; readonly alt_text: string }\n\ntype SlackBlock =\n | { readonly type: 'section'; readonly text: { readonly type: 'mrkdwn'; readonly text: string } }\n | { readonly type: 'context'; readonly elements: ReadonlyArray<SlackElement> }\n\n/** The CI environment could not be parsed. `issues` names each invalid variable. */\nexport class CiRunParseError extends Error {\n readonly _tag = 'CiRunParseError' as const\n\n constructor(\n readonly issues: ReadonlyArray<string>,\n override readonly cause: unknown,\n ) {\n super(`Invalid CI environment: ${issues.join(', ')}`)\n }\n}\n\nconst OutcomeSchema = z.enum(['success', 'failure', 'cancelled', 'skipped'])\n\nconst jsonRecord = <T extends z.ZodType>(value: T) =>\n z\n .string()\n .transform((text, context) => {\n try {\n const value: unknown = JSON.parse(text)\n return value\n } catch {\n context.addIssue({ code: 'custom', message: 'Invalid JSON' })\n return z.NEVER\n }\n })\n .pipe(z.record(z.string(), value))\n\nconst CiEnvSchema = z.object({\n REPO: z.string().regex(/^[\\w.-]+\\/[\\w.-]+$/),\n COMMIT_SHA: z.string().regex(/^[0-9a-f]{40}$/),\n BRANCH: z.string().min(1),\n RUN_URL: z.httpUrl(),\n JOBS_JSON: jsonRecord(z.object({ result: OutcomeSchema })).optional(),\n STEPS_JSON: jsonRecord(z.object({ outcome: OutcomeSchema })).optional(),\n PR_NUMBER: z.coerce.number().int().positive().optional(),\n PR_URL: z.httpUrl().optional(),\n PR_TITLE: z.string().min(1).optional(),\n ACTION: z.enum(['check', 'deploy', 'publish']).default('check'),\n CLOUDFLARE_ACCOUNT_ID: z.string().min(1).optional(),\n CLOUDFLARE_WORKER: z.string().min(1).optional(),\n SITE_URL: z.httpUrl().optional(),\n DEPLOY_VERSION_ID: z.string().min(1).optional(),\n NPM_PACKAGE: z.string().min(1).optional(),\n NPM_VERSION: z.string().min(1).optional(),\n})\n\ntype CiEnv = z.infer<typeof CiEnvSchema>\n\n/**\n * Parse the variables that a workflow passes to the notifier. GitHub expressions render a\n * missing value as an empty string, so an empty variable counts as unset.\n * @throws {CiRunParseError} when a variable is missing or invalid.\n */\nexport function parseCiRun(env: Readonly<Record<string, string | undefined>>): CiRun {\n const present = Object.fromEntries(Object.entries(env).filter(([, value]) => value !== ''))\n const parsed = CiEnvSchema.safeParse(present)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((issue) => issue.path.join('.'))\n throw new CiRunParseError(issues, parsed.error)\n }\n return toCiRun(parsed.data)\n}\n\nfunction toCiRun(env: CiEnv): CiRun {\n const ref: CiRun['ref'] =\n env.PR_NUMBER !== undefined && env.PR_URL !== undefined && env.PR_TITLE !== undefined\n ? { kind: 'pullRequest', number: env.PR_NUMBER, url: env.PR_URL, title: env.PR_TITLE }\n : { kind: 'branch', name: env.BRANCH }\n return {\n repository: env.REPO,\n commitSha: env.COMMIT_SHA,\n ref,\n runUrl: env.RUN_URL,\n jobs: Object.entries(env.JOBS_JSON ?? {}).map(([id, job]) => ({\n name: displayName(id),\n outcome: job.result,\n })),\n steps: Object.entries(env.STEPS_JSON ?? {}).map(([id, step]) => ({\n name: displayName(id),\n outcome: step.outcome,\n })),\n target: toTarget(env),\n }\n}\n\nfunction toTarget(env: CiEnv): CiTarget {\n switch (env.ACTION) {\n case 'check':\n return { kind: 'check' }\n case 'deploy': {\n const { CLOUDFLARE_ACCOUNT_ID: accountId, CLOUDFLARE_WORKER: worker, SITE_URL: siteUrl } = env\n if (accountId === undefined || worker === undefined || siteUrl === undefined) {\n throw new CiRunParseError(\n ['CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_WORKER', 'SITE_URL'],\n undefined,\n )\n }\n return { kind: 'deploy', accountId, worker, siteUrl, versionId: env.DEPLOY_VERSION_ID }\n }\n case 'publish':\n if (env.NPM_PACKAGE === undefined) throw new CiRunParseError(['NPM_PACKAGE'], undefined)\n return { kind: 'publish', packageName: env.NPM_PACKAGE, version: env.NPM_VERSION }\n }\n}\n\n// A job or step id such as `check-and-build` reads as \"Check and build\".\nfunction displayName(id: string): string {\n const words = id.replaceAll(/[-_]/g, ' ')\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\nconst logos = {\n depot: 'https://avatars.githubusercontent.com/u/68166601?s=64',\n github: 'https://avatars.githubusercontent.com/u/9919?s=64',\n cloudflare: 'https://avatars.githubusercontent.com/u/314135?s=64',\n npm: 'https://avatars.githubusercontent.com/u/6078720?s=64',\n} as const\n\n/** Build the four-line CI message: result, change, links, and identity. */\nexport function buildCiMessage(run: CiRun, commit: CiCommit): SlackMessage {\n const headline = headlineFor(run)\n const change = run.ref.kind === 'pullRequest' ? run.ref.title : commit.subject\n return {\n text: `${headline} — ${change}`,\n blocks: [\n { type: 'section', text: { type: 'mrkdwn', text: `*${headline}*\\n${escape(change)}` } },\n { type: 'context', elements: linkElements(run) },\n { type: 'context', elements: [{ type: 'mrkdwn', text: identityLine(run, commit) }] },\n ],\n }\n}\n\nfunction headlineFor(run: CiRun): string {\n const failedJobs = namesWith(run.jobs, 'failure')\n if (failedJobs) return `:x: CI failed: ${failedJobs}`\n const cancelledJobs = namesWith(run.jobs, 'cancelled')\n if (cancelledJobs) return `:white_circle: CI cancelled: ${cancelledJobs}`\n const failedSteps = namesWith(run.steps, 'failure')\n const { target } = run\n switch (target.kind) {\n case 'check':\n return failedSteps ? `:x: CI failed: ${failedSteps}` : ':white_check_mark: CI passed'\n case 'deploy':\n if (failedSteps) return `:x: Deploy failed: ${failedSteps}`\n return target.versionId === undefined\n ? ':white_check_mark: CI passed'\n : ':white_check_mark: Deployed to production'\n case 'publish':\n if (failedSteps) return `:x: Publish failed: ${failedSteps}`\n return target.version === undefined\n ? ':white_check_mark: CI passed, nothing to publish'\n : `:white_check_mark: Published ${target.packageName} ${target.version}`\n }\n}\n\nfunction namesWith(results: ReadonlyArray<CiStepResult>, outcome: CiOutcome): string {\n return results\n .filter((result) => result.outcome === outcome)\n .map((result) => result.name)\n .join(', ')\n}\n\nfunction linkElements(run: CiRun): ReadonlyArray<SlackElement> {\n const runHost = new URL(run.runUrl).hostname\n const runLogo = runHost.endsWith('github.com') ? logos.github : logos.depot\n const elements: SlackElement[] = [\n { type: 'image', image_url: runLogo, alt_text: runHost },\n { type: 'mrkdwn', text: `<${run.runUrl}|CI run>` },\n ]\n const { target } = run\n if (target.kind === 'deploy' && target.versionId !== undefined) {\n const dashboard = `https://dash.cloudflare.com/${target.accountId}/workers/services/view/${target.worker}/production/deployments`\n const site = new URL(target.siteUrl)\n elements.push(\n { type: 'mrkdwn', text: '·' },\n { type: 'image', image_url: logos.cloudflare, alt_text: 'Cloudflare' },\n { type: 'mrkdwn', text: `<${dashboard}|Version \\`${target.versionId.slice(0, 8)}\\`>` },\n { type: 'mrkdwn', text: `· :globe_with_meridians: <${site.origin}|${site.host}>` },\n )\n }\n if (target.kind === 'publish' && target.version !== undefined) {\n const npm = `https://www.npmjs.com/package/${target.packageName}/v/${target.version}`\n elements.push(\n { type: 'mrkdwn', text: '·' },\n { type: 'image', image_url: logos.npm, alt_text: 'npm' },\n { type: 'mrkdwn', text: `<${npm}|${target.packageName}@${target.version}>` },\n )\n }\n return elements\n}\n\nfunction identityLine(run: CiRun, commit: CiCommit): string {\n const repository = run.repository.split('/').at(-1) ?? run.repository\n const ref =\n run.ref.kind === 'pullRequest' ? `<${run.ref.url}|PR #${run.ref.number}>` : run.ref.name\n const commitUrl = `https://github.com/${run.repository}/commit/${run.commitSha}`\n return [\n repository,\n ref,\n `<${commitUrl}|\\`${run.commitSha.slice(0, 7)}\\`>`,\n escape(commit.author),\n ].join(' · ')\n}\n\n// Slack mrkdwn treats these three characters as markup.\nfunction escape(text: string): string {\n return text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')\n}\n","import type { SlackMessage } from './ci-message.ts'\n\n/** Slack refused the message or could not be reached. */\nexport class SlackWebhookError extends Error {\n readonly _tag = 'SlackWebhookError' as const\n\n constructor(\n readonly status: number | undefined,\n override readonly cause: unknown,\n ) {\n super(status === undefined ? 'Slack webhook unreachable' : `Slack webhook returned ${status}`)\n }\n}\n\n/**\n * Post one message to a Slack incoming webhook.\n * @throws {SlackWebhookError} when the request fails or Slack answers with an error status.\n */\nexport async function postSlackMessage(\n webhookUrl: string,\n message: SlackMessage,\n fetcher: typeof fetch = fetch,\n): Promise<void> {\n let response: Response\n try {\n response = await fetcher(webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(message),\n signal: AbortSignal.timeout(30_000),\n })\n } catch (cause) {\n throw new SlackWebhookError(undefined, cause)\n }\n if (!response.ok) throw new SlackWebhookError(response.status, await response.text())\n}\n"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ //#region src/infrastructure/slack/ci-message.d.ts
2
+ /** The result of one CI job or step, in GitHub Actions terms. */
3
+ type CiOutcome = 'success' | 'failure' | 'cancelled' | 'skipped';
4
+ /** What the run delivers after its checks pass. */
5
+ type CiTarget = {
6
+ readonly kind: 'check';
7
+ } | {
8
+ readonly kind: 'deploy';
9
+ readonly accountId: string;
10
+ readonly worker: string;
11
+ readonly siteUrl: string;
12
+ /** Absent when the run reached no deploy, such as a pull request. */
13
+ readonly versionId: string | undefined;
14
+ } | {
15
+ readonly kind: 'publish';
16
+ readonly packageName: string;
17
+ /** Absent when npm already has the package version. */
18
+ readonly version: string | undefined;
19
+ };
20
+ /** One CI run, parsed from the environment that the workflow sets. */
21
+ interface CiRun {
22
+ readonly repository: string;
23
+ readonly commitSha: string;
24
+ readonly ref: {
25
+ readonly kind: 'branch';
26
+ readonly name: string;
27
+ } | {
28
+ readonly kind: 'pullRequest';
29
+ readonly number: number;
30
+ readonly url: string;
31
+ readonly title: string;
32
+ };
33
+ readonly runUrl: string;
34
+ readonly jobs: ReadonlyArray<CiStepResult>;
35
+ readonly steps: ReadonlyArray<CiStepResult>;
36
+ readonly target: CiTarget;
37
+ }
38
+ /** A named job or step and its outcome. */
39
+ interface CiStepResult {
40
+ readonly name: string;
41
+ readonly outcome: CiOutcome;
42
+ }
43
+ /** The commit facts that only git knows. */
44
+ interface CiCommit {
45
+ readonly subject: string;
46
+ readonly author: string;
47
+ }
48
+ /** A Slack incoming-webhook payload. */
49
+ interface SlackMessage {
50
+ readonly text: string;
51
+ readonly blocks: ReadonlyArray<SlackBlock>;
52
+ }
53
+ type SlackElement = {
54
+ readonly type: 'mrkdwn';
55
+ readonly text: string;
56
+ } | {
57
+ readonly type: 'image';
58
+ readonly image_url: string;
59
+ readonly alt_text: string;
60
+ };
61
+ type SlackBlock = {
62
+ readonly type: 'section';
63
+ readonly text: {
64
+ readonly type: 'mrkdwn';
65
+ readonly text: string;
66
+ };
67
+ } | {
68
+ readonly type: 'context';
69
+ readonly elements: ReadonlyArray<SlackElement>;
70
+ };
71
+ /** The CI environment could not be parsed. `issues` names each invalid variable. */
72
+ export declare class CiRunParseError extends Error {
73
+ readonly issues: ReadonlyArray<string>;
74
+ readonly cause: unknown;
75
+ readonly _tag: 'CiRunParseError';
76
+ constructor(issues: ReadonlyArray<string>, cause: unknown);
77
+ }
78
+ /**
79
+ * Parse the variables that a workflow passes to the notifier. GitHub expressions render a
80
+ * missing value as an empty string, so an empty variable counts as unset.
81
+ * @throws {CiRunParseError} when a variable is missing or invalid.
82
+ */
83
+ export declare function parseCiRun(env: Readonly<Record<string, string | undefined>>): CiRun;
84
+ /** Build the four-line CI message: result, change, links, and identity. */
85
+ export declare function buildCiMessage(run: CiRun, commit: CiCommit): SlackMessage;
86
+ //#endregion
87
+ //#region src/infrastructure/slack/slack-webhook.d.ts
88
+ /** Slack refused the message or could not be reached. */
89
+ export declare class SlackWebhookError extends Error {
90
+ readonly status: number | undefined;
91
+ readonly cause: unknown;
92
+ readonly _tag: 'SlackWebhookError';
93
+ constructor(status: number | undefined, cause: unknown);
94
+ }
95
+ /**
96
+ * Post one message to a Slack incoming webhook.
97
+ * @throws {SlackWebhookError} when the request fails or Slack answers with an error status.
98
+ */
99
+ export declare function postSlackMessage(webhookUrl: string, message: SlackMessage, fetcher?: typeof fetch): Promise<void>;
100
+ //#endregion
101
+ export type { CiCommit, CiOutcome, CiRun, CiStepResult, CiTarget, SlackMessage };
102
+ //# sourceMappingURL=slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.d.ts","names":[],"sources":["../src/infrastructure/slack/ci-message.ts","../src/infrastructure/slack/slack-webhook.ts"],"mappings":";;KAGY;;KAGA;WACG;;WAEA;WACA;WACA;WACA;;WAEA;;WAGA;WACA;;WAEA;;;UAIE;WACN;WACA;WACA;aACM;aAAyB;;aAEzB;aACA;aACA;aACA;;WAEN;WACA,MAAM,cAAc;WACpB,OAAO,cAAc;WACrB,QAAQ;;;UAIF;WACN;WACA,SAAS;;;UAIH;WACN;WACA;;;UAIM;WACN;WACA,QAAQ,cAAc;;KAG5B;WACU;WAAyB;;WACzB;WAAwB;WAA4B;;KAE9D;WACU;WAA0B;aAAiB;aAAyB;;;WACpE;WAA0B,UAAU,cAAc;;;qBAGpD,wBAAwB;WAIxB,QAAQ;WACC;WAJX;EAET,YACW,QAAQ,uBACC;;;;;;;wBAgDN,WAAW,KAAK,SAAS,sCAAsC;;wBAkE/D,eAAe,KAAK,OAAO,QAAQ,WAAW;;;;qBCxLjD,0BAA0B;WAI1B;WACS;WAJX;EAET,YACW,4BACS;;;;;;wBAUA,iBACpB,oBACA,SAAS,cACT,iBAAgB,QACf"}
package/dist/slack.js ADDED
@@ -0,0 +1,3 @@
1
+ import { a as parseCiRun, i as buildCiMessage, n as postSlackMessage, r as CiRunParseError, t as SlackWebhookError } from "./slack-webhook-j1FCPeu8.js";
2
+
3
+ export { CiRunParseError, SlackWebhookError, buildCiMessage, parseCiRun, postSlackMessage };
@@ -1,20 +1,3 @@
1
- import Stripe from "stripe";
1
+ import { n as createStripeClient, t as STRIPE_REQUEST_OPTIONS } from "../client-Dv1Ch7yY.js";
2
2
 
3
- //#region src/infrastructure/stripe/client.ts
4
- /** Default SDK retries; client configuration and individual requests can override this value. */
5
- const STRIPE_REQUEST_OPTIONS = Object.freeze({ maxNetworkRetries: 2 });
6
- /**
7
- * Construct a native Stripe client with fetch transport and two SDK retries by default.
8
- * Native configuration overrides these defaults. Inject the client into application services.
9
- */
10
- function createStripeClient(secretKey, options = {}) {
11
- return new Stripe(secretKey, {
12
- httpClient: Stripe.createFetchHttpClient(),
13
- ...STRIPE_REQUEST_OPTIONS,
14
- ...options
15
- });
16
- }
17
-
18
- //#endregion
19
- export { STRIPE_REQUEST_OPTIONS, createStripeClient };
20
- //# sourceMappingURL=client.js.map
3
+ export { STRIPE_REQUEST_OPTIONS, createStripeClient };