@pithy-sh/cloudflare 0.1.1 → 0.1.3

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 (139) hide show
  1. package/dist/ai/aiManager.d.ts +99 -0
  2. package/dist/ai/aiManager.d.ts.map +1 -0
  3. package/dist/ai/aiManager.js +148 -0
  4. package/dist/ai/vectorizeManager.d.ts +67 -0
  5. package/dist/ai/vectorizeManager.d.ts.map +1 -0
  6. package/dist/ai/vectorizeManager.js +101 -0
  7. package/dist/ai/vectorizeProvisioner.d.ts +123 -0
  8. package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
  9. package/dist/ai/vectorizeProvisioner.js +195 -0
  10. package/dist/client/accounts.d.ts +48 -0
  11. package/dist/client/accounts.d.ts.map +1 -0
  12. package/dist/client/accounts.js +55 -0
  13. package/dist/client/clients.d.ts +122 -0
  14. package/dist/client/clients.d.ts.map +1 -0
  15. package/dist/client/clients.js +216 -0
  16. package/dist/client/errors.d.ts +73 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/errors.js +98 -0
  19. package/dist/client/manager.d.ts +45 -0
  20. package/dist/client/manager.d.ts.map +1 -0
  21. package/dist/client/manager.js +51 -0
  22. package/dist/d1/d1Manager.d.ts +74 -0
  23. package/dist/d1/d1Manager.d.ts.map +1 -0
  24. package/dist/d1/d1Manager.js +128 -0
  25. package/dist/d1/d1PreparedStatement.d.ts +24 -0
  26. package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
  27. package/dist/d1/d1PreparedStatement.js +91 -0
  28. package/dist/d1/d1Provisioner.d.ts +28 -0
  29. package/dist/d1/d1Provisioner.d.ts.map +1 -0
  30. package/dist/d1/d1Provisioner.js +67 -0
  31. package/dist/email/emailRoutingManager.d.ts +36 -0
  32. package/dist/email/emailRoutingManager.d.ts.map +1 -0
  33. package/dist/email/emailRoutingManager.js +124 -0
  34. package/dist/email/emailSendManager.d.ts +45 -0
  35. package/dist/email/emailSendManager.d.ts.map +1 -0
  36. package/dist/email/emailSendManager.js +52 -0
  37. package/dist/env/devVars.d.ts +65 -0
  38. package/dist/env/devVars.d.ts.map +1 -0
  39. package/dist/env/devVars.js +84 -0
  40. package/dist/hostnames/customHostnamesManager.d.ts +51 -0
  41. package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
  42. package/dist/hostnames/customHostnamesManager.js +84 -0
  43. package/dist/kv/kvManager.d.ts +76 -0
  44. package/dist/kv/kvManager.d.ts.map +1 -0
  45. package/dist/kv/kvManager.js +135 -0
  46. package/dist/kv/kvProvisioner.d.ts +28 -0
  47. package/dist/kv/kvProvisioner.d.ts.map +1 -0
  48. package/dist/kv/kvProvisioner.js +71 -0
  49. package/dist/media/assetSeeder.d.ts +31 -0
  50. package/dist/media/assetSeeder.d.ts.map +1 -0
  51. package/dist/media/assetSeeder.js +70 -0
  52. package/dist/media/imageManager.d.ts +34 -0
  53. package/dist/media/imageManager.d.ts.map +1 -0
  54. package/dist/media/imageManager.js +103 -0
  55. package/dist/media/ownership.d.ts +40 -0
  56. package/dist/media/ownership.d.ts.map +1 -0
  57. package/dist/media/ownership.js +46 -0
  58. package/dist/media/streamManager.d.ts +83 -0
  59. package/dist/media/streamManager.d.ts.map +1 -0
  60. package/dist/media/streamManager.js +145 -0
  61. package/dist/queue/queueManager.d.ts +84 -0
  62. package/dist/queue/queueManager.d.ts.map +1 -0
  63. package/dist/queue/queueManager.js +114 -0
  64. package/dist/r2/r2Credentials.d.ts +12 -0
  65. package/dist/r2/r2Credentials.d.ts.map +1 -0
  66. package/dist/r2/r2Credentials.js +13 -0
  67. package/dist/r2/r2Manager.d.ts +226 -0
  68. package/dist/r2/r2Manager.d.ts.map +1 -0
  69. package/dist/r2/r2Manager.js +405 -0
  70. package/dist/r2/r2Provisioner.d.ts +39 -0
  71. package/dist/r2/r2Provisioner.d.ts.map +1 -0
  72. package/dist/r2/r2Provisioner.js +86 -0
  73. package/dist/secrets/secretsStoreManager.d.ts +82 -0
  74. package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
  75. package/dist/secrets/secretsStoreManager.js +147 -0
  76. package/dist/secrets/secretsStores.d.ts +47 -0
  77. package/dist/secrets/secretsStores.d.ts.map +1 -0
  78. package/dist/secrets/secretsStores.js +65 -0
  79. package/dist/test-utils/emailRoutingRules.d.ts +39 -0
  80. package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
  81. package/dist/test-utils/emailRoutingRules.js +89 -0
  82. package/dist/test-utils/fixtureReportSetup.d.ts +25 -0
  83. package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
  84. package/dist/test-utils/fixtureReportSetup.js +30 -0
  85. package/dist/test-utils/fixtures.d.ts +224 -0
  86. package/dist/test-utils/fixtures.d.ts.map +1 -0
  87. package/dist/test-utils/fixtures.js +277 -0
  88. package/dist/test-utils/harness.d.ts +192 -0
  89. package/dist/test-utils/harness.d.ts.map +1 -0
  90. package/dist/test-utils/harness.js +351 -0
  91. package/dist/test-utils/inboundRecorder.d.ts +91 -0
  92. package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
  93. package/dist/test-utils/inboundRecorder.js +141 -0
  94. package/dist/test-utils/integrationSetup.d.ts +22 -0
  95. package/dist/test-utils/integrationSetup.d.ts.map +1 -0
  96. package/dist/test-utils/integrationSetup.js +37 -0
  97. package/dist/test-utils/reap.d.ts +112 -0
  98. package/dist/test-utils/reap.d.ts.map +1 -0
  99. package/dist/test-utils/reap.js +185 -0
  100. package/dist/tokens/accountTokensManager.d.ts +157 -0
  101. package/dist/tokens/accountTokensManager.d.ts.map +1 -0
  102. package/dist/tokens/accountTokensManager.js +280 -0
  103. package/dist/tokens/permissions.d.ts +36 -0
  104. package/dist/tokens/permissions.d.ts.map +1 -0
  105. package/dist/tokens/permissions.js +50 -0
  106. package/dist/tokens/profiles.d.ts +102 -0
  107. package/dist/tokens/profiles.d.ts.map +1 -0
  108. package/dist/tokens/profiles.js +137 -0
  109. package/dist/turnstile/turnstileManager.d.ts +75 -0
  110. package/dist/turnstile/turnstileManager.d.ts.map +1 -0
  111. package/dist/turnstile/turnstileManager.js +138 -0
  112. package/dist/user/userManager.d.ts +36 -0
  113. package/dist/user/userManager.d.ts.map +1 -0
  114. package/dist/user/userManager.js +58 -0
  115. package/dist/workers/buildsManager.d.ts +116 -0
  116. package/dist/workers/buildsManager.d.ts.map +1 -0
  117. package/dist/workers/buildsManager.js +237 -0
  118. package/dist/workers/buildsTypes.d.ts +102 -0
  119. package/dist/workers/buildsTypes.d.ts.map +1 -0
  120. package/dist/workers/buildsTypes.js +87 -0
  121. package/dist/workers/workersBuildEvent.d.ts +64 -0
  122. package/dist/workers/workersBuildEvent.d.ts.map +1 -0
  123. package/dist/workers/workersBuildEvent.js +47 -0
  124. package/dist/workers/workersManager.d.ts +136 -0
  125. package/dist/workers/workersManager.d.ts.map +1 -0
  126. package/dist/workers/workersManager.js +312 -0
  127. package/dist/workers/workersProvisioner.d.ts +80 -0
  128. package/dist/workers/workersProvisioner.d.ts.map +1 -0
  129. package/dist/workers/workersProvisioner.js +106 -0
  130. package/dist/workflows/stepFailure.d.ts +180 -0
  131. package/dist/workflows/stepFailure.d.ts.map +1 -0
  132. package/dist/workflows/stepFailure.js +227 -0
  133. package/dist/workflows/workflowsClient.d.ts +87 -0
  134. package/dist/workflows/workflowsClient.d.ts.map +1 -0
  135. package/dist/workflows/workflowsClient.js +156 -0
  136. package/dist/zones/zonesManager.d.ts +50 -0
  137. package/dist/zones/zonesManager.d.ts.map +1 -0
  138. package/dist/zones/zonesManager.js +74 -0
  139. package/package.json +18 -9
@@ -0,0 +1,30 @@
1
+ import { reportFixtureEstate } from "./fixtures.js";
2
+ //#region src/test-utils/fixtureReportSetup.ts
3
+ /**
4
+ * The `globalSetup` for an integration config whose suites **create nothing** — the fixture report, and
5
+ * no debris sweep.
6
+ *
7
+ * `integrationSetup.ts` beside this does both, and it is the right entry point for a package that mints
8
+ * a Worker, a database or a bucket. A package whose live suites only *read* a third party — a token
9
+ * endpoint, a siteverify — has nothing to reclaim, and pointing it at the sweeping setup would mean
10
+ * every run of it deleted stale resources across the whole account on behalf of suites it does not run.
11
+ * That is somebody else's housekeeping, done at a surprising moment, and it costs a run half a minute
12
+ * of REST calls that answer nothing about the code under test.
13
+ *
14
+ * The report half is not optional either way, which is the whole reason this file exists rather than
15
+ * the config simply declaring no `globalSetup`. Vitest runs no hooks inside a `describe.skipIf(true)`,
16
+ * so a suite that skips for want of a fixture is exactly the suite that cannot say which fixture — see
17
+ * the long note in `fixtures.ts`. A config with no `globalSetup` gets a silent skip, and a silent skip
18
+ * is the failure mode #106 exists to remove.
19
+ *
20
+ * **Switch a config to `integrationSetup` the moment one of its suites creates a Cloudflare resource.**
21
+ * A suite that mints and a run that never sweeps is how debris becomes permanent.
22
+ *
23
+ * Never throws, for the same reason its neighbor does not: a report that fails the run it was meant to
24
+ * explain is worse than no report.
25
+ */
26
+ function setup() {
27
+ reportFixtureEstate();
28
+ }
29
+ //#endregion
30
+ export { setup as default };
@@ -0,0 +1,224 @@
1
+ /**
2
+ * The live test fixture estate: which real-world things a live suite needs, whether this run has them,
3
+ * and what a run says when it does not.
4
+ *
5
+ * ## Absent means skip, never fail
6
+ *
7
+ * A contributor with no Cloudflare account, no Turnstile widget and no zone must be able to run the
8
+ * whole suite and get green. That is the rule, and both ways of getting it subtly wrong are worse than
9
+ * having no helper at all. A gate that **throws** on a missing fixture turns "you have no credentials"
10
+ * into "the kit is broken", and every one of those costs somebody an afternoon proving it is not. A gate
11
+ * that **silently passes** turns "nothing ran" into "everything is fine", which is how a release gate
12
+ * comes to certify a suite that has never executed.
13
+ *
14
+ * So the skip is loud. {@link reportFixtureEstate} runs from `globalSetup` — once per run, before a
15
+ * single suite is collected — and prints a line per fixture naming what is missing, what will therefore
16
+ * skip, and the document that creates it. The `describe.skipIf` in a suite is only the mechanism; this
17
+ * report is the sentence a human reads.
18
+ *
19
+ * **From `globalSetup` for the same reason the debris sweep is** (see `integrationSetup.ts`): Vitest runs
20
+ * no hooks inside a `describe.skipIf(true)`, so anything living in a suite hook is gated on exactly the
21
+ * condition it exists to report. A report that goes quiet precisely when there is something to say is
22
+ * not a report.
23
+ *
24
+ * ## A value never appears
25
+ *
26
+ * These names resolve API tokens, OAuth client secrets and Turnstile secret keys. A line here names the
27
+ * **variable** and never its contents, in every outcome — including the malformed ones, where the
28
+ * temptation to print what was found is strongest. `fixtures.test.ts` plants a distinctive value and
29
+ * asserts no report line contains it.
30
+ */
31
+ /**
32
+ * How a fixture's value is judged.
33
+ *
34
+ * `credential` — any usable text is the fixture. An account id, a token, a zone id, a sitekey.
35
+ *
36
+ * `switch` — a deliberate opt-in, where the *word* is the whole meaning. `PITHY_LIVE_DEPLOY` is the one
37
+ * that matters: it arms a suite that deploys real Workers and deletes them again, so `0` has to mean no
38
+ * rather than "a non-empty string, therefore yes". Judging it as a credential is not a style choice —
39
+ * it is the difference between an opt-out and a deploy.
40
+ */
41
+ export type FixtureShape = "credential" | "switch";
42
+ /** One live fixture: what it is, where its value comes from, what skips without it, and how to make it. */
43
+ export interface LiveFixture {
44
+ /** The fixture's name — what a skip line says, and the anchor its documentation lives under. */
45
+ name: string;
46
+ /** Every environment variable that must resolve before the fixture is usable. All of them, not any. */
47
+ keys: readonly string[];
48
+ /** How the value is judged. See {@link FixtureShape}. */
49
+ shape: FixtureShape;
50
+ /**
51
+ * What a run loses without it, as a whole sentence — "Turnstile sign-in gating (#84) skips."
52
+ *
53
+ * A sentence rather than a noun phrase, because the report is read by somebody deciding whether the
54
+ * green they just got means anything, and "the Custom Hostnames lifecycle" does not answer that.
55
+ */
56
+ consequence: string;
57
+ /** The document section that creates it. Must exist; `fixtures.test.ts` proves every one of them does. */
58
+ doc: string;
59
+ }
60
+ /**
61
+ * What this run has, per fixture.
62
+ *
63
+ * Four outcomes rather than a boolean, because three different things skip a suite and only one of them
64
+ * is fine. **`absent`** is the contributor with no credentials, and is the normal case. **`declined`** is
65
+ * a switch deliberately turned off. **`malformed`** is somebody who tried to set the fixture and failed —
66
+ * a CI export of an unset secret, a shell interpolation that produced the literal text `undefined`.
67
+ *
68
+ * All three skip. Only the reporting differs, and that is the whole point: #323 landed the same
69
+ * distinction one layer down — *"'not recorded' is a claim about the file, and it is now made only when
70
+ * the file makes no claim"* — after two investigations died inside a "missing" that meant "malformed".
71
+ * A fixture blanked by a broken export must not read as a fixture nobody configured.
72
+ */
73
+ export type FixtureOutcome = "present" | "absent" | "malformed" | "declined";
74
+ /** A fixture, resolved against one environment. */
75
+ export interface FixtureResolution {
76
+ /** The fixture that was resolved. */
77
+ fixture: LiveFixture;
78
+ /** The fixture's outcome — the worst of its keys'. */
79
+ outcome: FixtureOutcome;
80
+ /** True only for `present`. The boolean a `describe.skipIf` negates. */
81
+ ready: boolean;
82
+ /** One sentence naming the offending variables and their state. Never a value. */
83
+ reason: string;
84
+ }
85
+ /**
86
+ * The estate, declared once.
87
+ *
88
+ * Every live fixture the repository knows about, whether or not a suite reads it yet — a fixture that
89
+ * exists only in the head of whoever wrote the suite is a fixture nobody can be told to create. The
90
+ * `consequence` line names the issue, so the report is a to-do list for anyone who wants a red X to become
91
+ * a run.
92
+ *
93
+ * Alphabetical, as the reap plan is, so the set reads as a list rather than as an accident of the order
94
+ * somebody happened to add things in.
95
+ */
96
+ export declare const LIVE_FIXTURES: {
97
+ readonly "cloudflare-account": {
98
+ readonly name: "cloudflare-account";
99
+ readonly keys: readonly ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"];
100
+ readonly shape: "credential";
101
+ readonly consequence: "Every live suite skips, and the run sweeps no debris.";
102
+ readonly doc: "docs/FIXTURES.md#cloudflare-account";
103
+ };
104
+ readonly "custom-hostname-zones": {
105
+ readonly name: "custom-hostname-zones";
106
+ readonly keys: readonly ["CUSTOM_HOSTNAME_ZONE_ID", "CUSTOM_HOSTNAME_CUSTOMER_ZONE_ID"];
107
+ readonly shape: "credential";
108
+ readonly consequence: "The Custom Hostnames lifecycle (#41) skips.";
109
+ readonly doc: "docs/FIXTURES.md#custom-hostname-zones";
110
+ };
111
+ readonly "email-routing": {
112
+ readonly name: "email-routing";
113
+ readonly keys: readonly ["EMAIL_ROUTING_ZONE_ID", "EMAIL_ROUTING_ADDRESS"];
114
+ readonly shape: "credential";
115
+ readonly consequence: "The inbound Email Routing rule (#47) skips.";
116
+ readonly doc: "docs/FIXTURES.md#email-routing";
117
+ };
118
+ readonly "email-sending": {
119
+ readonly name: "email-sending";
120
+ readonly keys: readonly ["EMAIL_SENDING_FROM"];
121
+ readonly shape: "credential";
122
+ readonly consequence: "The inbound delivery round trip (#47) skips: nothing can post a message to the routed address.";
123
+ readonly doc: "docs/FIXTURES.md#email-sending";
124
+ };
125
+ readonly "google-oauth": {
126
+ readonly name: "google-oauth";
127
+ readonly keys: readonly ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"];
128
+ readonly shape: "credential";
129
+ readonly consequence: "The Google provider suite, on localhost (#84), skips.";
130
+ readonly doc: "docs/FIXTURES.md#google-oauth";
131
+ };
132
+ readonly "live-deploy": {
133
+ readonly name: "live-deploy";
134
+ readonly keys: readonly ["PITHY_LIVE_DEPLOY"];
135
+ readonly shape: "switch";
136
+ readonly consequence: "The secrets provision, write, rotate and teardown round trip skips.";
137
+ readonly doc: "docs/FIXTURES.md#live-deploy";
138
+ };
139
+ readonly "r2-s3-keys": {
140
+ readonly name: "r2-s3-keys";
141
+ readonly keys: readonly ["R2_CREDENTIALS"];
142
+ readonly shape: "credential";
143
+ readonly consequence: "The R2 presigned-URL suite skips, and stale buckets are not reclaimed.";
144
+ readonly doc: "docs/FIXTURES.md#r2-s3-keys";
145
+ };
146
+ readonly "secrets-store": {
147
+ readonly name: "secrets-store";
148
+ readonly keys: readonly ["SECRETS_STORE_ID"];
149
+ readonly shape: "credential";
150
+ readonly consequence: "The Secrets Store suites skip, and stale entries are not reclaimed.";
151
+ readonly doc: "docs/FIXTURES.md#secrets-store";
152
+ };
153
+ readonly "turnstile-widget": {
154
+ readonly name: "turnstile-widget";
155
+ readonly keys: readonly ["TURNSTILE_SITE_KEY", "TURNSTILE_SECRET_KEY"];
156
+ readonly shape: "credential";
157
+ readonly consequence: "Turnstile sign-in gating on a workers.dev hostname (#84) skips.";
158
+ readonly doc: "docs/FIXTURES.md#turnstile-widget";
159
+ };
160
+ };
161
+ /** Every declared fixture name. A typo is a type error rather than a silently skipped suite. */
162
+ export type FixtureName = keyof typeof LIVE_FIXTURES;
163
+ /** How a fixture is resolved: from an injected environment, or from the run's own. */
164
+ export interface FixtureOptions {
165
+ /**
166
+ * The environment to read. Defaults to {@link fixtureEnv}.
167
+ *
168
+ * A test passes its own, so a maintainer's real `.dev.vars` can never decide whether a unit test
169
+ * passes — the failure mode `vitest.setup.ts` exists to prevent, one level up.
170
+ */
171
+ env?: Record<string, string | undefined>;
172
+ }
173
+ /**
174
+ * The environment a fixture resolves against: `packages/cloudflare/.dev.vars` first, then `process.env`.
175
+ *
176
+ * The same pair {@link loadIntegrationEnv} already reads for credentials, so a fixture and a credential
177
+ * cannot disagree about which file they came from. `.dev.vars` wins where both state a key, because a
178
+ * file somebody wrote on purpose beats a variable their shell exported hours ago.
179
+ */
180
+ export declare function fixtureEnv(): Record<string, string | undefined>;
181
+ /**
182
+ * Resolve one fixture against an environment.
183
+ *
184
+ * Never throws and never reaches the network. It answers what this run has; deciding what to do about it
185
+ * is {@link fixtureReady}'s job, and saying so out loud is {@link reportFixtureEstate}'s.
186
+ */
187
+ export declare function resolveFixture(name: FixtureName, options?: FixtureOptions): FixtureResolution;
188
+ /**
189
+ * Whether a suite gated on this fixture can run — the boolean a `describe.skipIf` negates:
190
+ *
191
+ * ```ts
192
+ * describe.skipIf(!fixtureReady("turnstile-widget"))("turnstile — LIVE", () => { … });
193
+ * ```
194
+ *
195
+ * False for every outcome that is not `present`, malformed included. The distinction between a fixture
196
+ * nobody configured and one somebody configured wrongly belongs in the report, not in whether the suite
197
+ * runs: neither can be tested against, and a run that failed on the second would fail a contributor
198
+ * whose CI template exports blanks — the same "the kit is broken" this file exists to prevent.
199
+ */
200
+ export declare function fixtureReady(name: FixtureName, options?: FixtureOptions): boolean;
201
+ /**
202
+ * One of a ready fixture's values.
203
+ *
204
+ * Throws when the fixture is not ready, and that is not a contradiction of the rule above: reaching here
205
+ * means a suite read a value it never gated on, which is a defect in the suite rather than a missing
206
+ * credential. The error names the key and never carries a value — `detail` reaches a log.
207
+ */
208
+ export declare function fixtureValue(name: FixtureName, key: string, options?: FixtureOptions): string;
209
+ /**
210
+ * The lines a run prints about its fixtures — pure, so a test reads exactly what a human would.
211
+ *
212
+ * One line per fixture, then a count. A present fixture still gets a line: a report that only speaks when
213
+ * something is wrong cannot tell "the estate is complete" from "the report never ran", and that is the
214
+ * same indistinguishability this whole file is about.
215
+ */
216
+ export declare function fixtureReportLines(resolutions: readonly FixtureResolution[]): string[];
217
+ /**
218
+ * Resolve every fixture, say what the run has, and hand back the resolutions.
219
+ *
220
+ * The `globalSetup` entry point. Never throws — a report that fails the run it was meant to explain is
221
+ * worse than no report, exactly as the debris sweep beside it is.
222
+ */
223
+ export declare function reportFixtureEstate(options?: FixtureOptions): FixtureResolution[];
224
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/test-utils/fixtures.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEnD,2GAA2G;AAC3G,MAAM,WAAW,WAAW;IAC1B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,yDAAyD;IACzD,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0GAA0G;IAC1G,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AAY7E,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,sDAAsD;IACtD,OAAO,EAAE,cAAc,CAAC;IACxB,wEAAwE;IACxE,KAAK,EAAE,OAAO,CAAC;IACf,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;CAChB;AA0BD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa;;iBAEtB,IAAI,EAAE,oBAAoB;iBAC1B,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,uDAAuD;iBACpE,GAAG,EAAE,qCAAqC;;;iBAG1C,IAAI,EAAE,uBAAuB;iBAC7B,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,6CAA6C;iBAC1D,GAAG,EAAE,wCAAwC;;;iBAG7C,IAAI,EAAE,eAAe;iBACrB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,6CAA6C;iBAC1D,GAAG,EAAE,gCAAgC;;;iBAGrC,IAAI,EAAE,eAAe;iBACrB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,gGAAgG;iBAC7G,GAAG,EAAE,gCAAgC;;;iBAGrC,IAAI,EAAE,cAAc;iBACpB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,uDAAuD;iBACpE,GAAG,EAAE,+BAA+B;;;iBAGpC,IAAI,EAAE,aAAa;iBACnB,IAAI;iBACJ,KAAK,EAAE,QAAQ;iBACf,WAAW,EAAE,qEAAqE;iBAClF,GAAG,EAAE,8BAA8B;;;iBAGnC,IAAI,EAAE,YAAY;iBAClB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,wEAAwE;iBACrF,GAAG,EAAE,6BAA6B;;;iBAGlC,IAAI,EAAE,eAAe;iBACrB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,qEAAqE;iBAClF,GAAG,EAAE,gCAAgC;;;iBAGrC,IAAI,EAAE,kBAAkB;iBACxB,IAAI;iBACJ,KAAK,EAAE,YAAY;iBACnB,WAAW,EAAE,iEAAiE;iBAC9E,GAAG,EAAE,mCAAmC;;CAEI,CAAC;AAEjD,gGAAgG;AAChG,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,aAAa,CAAC;AAErD,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAE/D;AAyCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAMjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAErF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAmBjG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,GAAG,MAAM,EAAE,CAkBtF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,EAAE,CAUrF"}
@@ -0,0 +1,277 @@
1
+ import { loadIntegrationEnv } from "./harness.js";
2
+ import { ValidationError } from "@pithy-sh/core/src/error/pithyError";
3
+ //#region src/test-utils/fixtures.ts
4
+ /** Words a switch reads as on. Case-insensitive. */
5
+ const SWITCH_ON = /* @__PURE__ */ new Set([
6
+ "1",
7
+ "true",
8
+ "yes",
9
+ "on"
10
+ ]);
11
+ /** Words a switch reads as off — a deliberate decline, not a misconfiguration. */
12
+ const SWITCH_OFF = /* @__PURE__ */ new Set([
13
+ "0",
14
+ "false",
15
+ "no",
16
+ "off"
17
+ ]);
18
+ /**
19
+ * Text a value must never be, whatever its shape.
20
+ *
21
+ * These are not values. They are what a templating layer writes when it had nothing: `export
22
+ * KEY=$MISSING` under some shells, a JSON `null` stringified, a GitHub Actions expression that resolved
23
+ * to nothing and got quoted anyway. Each is a non-empty string, so every `Boolean(value)` check in the
24
+ * world reads it as present and hands it to Cloudflare, which answers 401 three frames later.
25
+ */
26
+ const PLACEHOLDER_TEXT = /* @__PURE__ */ new Set(["undefined", "null"]);
27
+ /** Worst-first, so folding a fixture's keys is a `Math.min` over this order. */
28
+ const OUTCOME_RANK = {
29
+ malformed: 0,
30
+ absent: 1,
31
+ declined: 2,
32
+ present: 3
33
+ };
34
+ /**
35
+ * The estate, declared once.
36
+ *
37
+ * Every live fixture the repository knows about, whether or not a suite reads it yet — a fixture that
38
+ * exists only in the head of whoever wrote the suite is a fixture nobody can be told to create. The
39
+ * `consequence` line names the issue, so the report is a to-do list for anyone who wants a red X to become
40
+ * a run.
41
+ *
42
+ * Alphabetical, as the reap plan is, so the set reads as a list rather than as an accident of the order
43
+ * somebody happened to add things in.
44
+ */
45
+ const LIVE_FIXTURES = {
46
+ "cloudflare-account": {
47
+ name: "cloudflare-account",
48
+ keys: ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"],
49
+ shape: "credential",
50
+ consequence: "Every live suite skips, and the run sweeps no debris.",
51
+ doc: "docs/FIXTURES.md#cloudflare-account"
52
+ },
53
+ "custom-hostname-zones": {
54
+ name: "custom-hostname-zones",
55
+ keys: ["CUSTOM_HOSTNAME_ZONE_ID", "CUSTOM_HOSTNAME_CUSTOMER_ZONE_ID"],
56
+ shape: "credential",
57
+ consequence: "The Custom Hostnames lifecycle (#41) skips.",
58
+ doc: "docs/FIXTURES.md#custom-hostname-zones"
59
+ },
60
+ "email-routing": {
61
+ name: "email-routing",
62
+ keys: ["EMAIL_ROUTING_ZONE_ID", "EMAIL_ROUTING_ADDRESS"],
63
+ shape: "credential",
64
+ consequence: "The inbound Email Routing rule (#47) skips.",
65
+ doc: "docs/FIXTURES.md#email-routing"
66
+ },
67
+ "email-sending": {
68
+ name: "email-sending",
69
+ keys: ["EMAIL_SENDING_FROM"],
70
+ shape: "credential",
71
+ consequence: "The inbound delivery round trip (#47) skips: nothing can post a message to the routed address.",
72
+ doc: "docs/FIXTURES.md#email-sending"
73
+ },
74
+ "google-oauth": {
75
+ name: "google-oauth",
76
+ keys: ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"],
77
+ shape: "credential",
78
+ consequence: "The Google provider suite, on localhost (#84), skips.",
79
+ doc: "docs/FIXTURES.md#google-oauth"
80
+ },
81
+ "live-deploy": {
82
+ name: "live-deploy",
83
+ keys: ["PITHY_LIVE_DEPLOY"],
84
+ shape: "switch",
85
+ consequence: "The secrets provision, write, rotate and teardown round trip skips.",
86
+ doc: "docs/FIXTURES.md#live-deploy"
87
+ },
88
+ "r2-s3-keys": {
89
+ name: "r2-s3-keys",
90
+ keys: ["R2_CREDENTIALS"],
91
+ shape: "credential",
92
+ consequence: "The R2 presigned-URL suite skips, and stale buckets are not reclaimed.",
93
+ doc: "docs/FIXTURES.md#r2-s3-keys"
94
+ },
95
+ "secrets-store": {
96
+ name: "secrets-store",
97
+ keys: ["SECRETS_STORE_ID"],
98
+ shape: "credential",
99
+ consequence: "The Secrets Store suites skip, and stale entries are not reclaimed.",
100
+ doc: "docs/FIXTURES.md#secrets-store"
101
+ },
102
+ "turnstile-widget": {
103
+ name: "turnstile-widget",
104
+ keys: ["TURNSTILE_SITE_KEY", "TURNSTILE_SECRET_KEY"],
105
+ shape: "credential",
106
+ consequence: "Turnstile sign-in gating on a workers.dev hostname (#84) skips.",
107
+ doc: "docs/FIXTURES.md#turnstile-widget"
108
+ }
109
+ };
110
+ /**
111
+ * The environment a fixture resolves against: `packages/cloudflare/.dev.vars` first, then `process.env`.
112
+ *
113
+ * The same pair {@link loadIntegrationEnv} already reads for credentials, so a fixture and a credential
114
+ * cannot disagree about which file they came from. `.dev.vars` wins where both state a key, because a
115
+ * file somebody wrote on purpose beats a variable their shell exported hours ago.
116
+ */
117
+ function fixtureEnv() {
118
+ return {
119
+ ...process.env,
120
+ ...loadIntegrationEnv()
121
+ };
122
+ }
123
+ /** Judge one variable, with no knowledge of the fixture it belongs to. */
124
+ function verdictFor(key, shape, raw) {
125
+ if (raw === void 0) return {
126
+ key,
127
+ outcome: "absent",
128
+ note: "is not set"
129
+ };
130
+ if (raw === "") return {
131
+ key,
132
+ outcome: "malformed",
133
+ note: "is set to an empty value"
134
+ };
135
+ const value = raw.trim();
136
+ if (value === "") return {
137
+ key,
138
+ outcome: "malformed",
139
+ note: "is set to whitespace"
140
+ };
141
+ if (PLACEHOLDER_TEXT.has(value.toLowerCase())) return {
142
+ key,
143
+ outcome: "malformed",
144
+ note: "is set to placeholder text, not a value"
145
+ };
146
+ if (shape === "switch") {
147
+ const word = value.toLowerCase();
148
+ if (SWITCH_ON.has(word)) return {
149
+ key,
150
+ outcome: "present",
151
+ note: "is on"
152
+ };
153
+ if (SWITCH_OFF.has(word)) return {
154
+ key,
155
+ outcome: "declined",
156
+ note: "is off"
157
+ };
158
+ return {
159
+ key,
160
+ outcome: "malformed",
161
+ note: "is set to a word that is neither on nor off"
162
+ };
163
+ }
164
+ return {
165
+ key,
166
+ outcome: "present",
167
+ note: "is set"
168
+ };
169
+ }
170
+ /** Fold a fixture's keys into one outcome: the worst wins, so one broken key is never averaged away. */
171
+ function foldOutcome(verdicts) {
172
+ let worst = "present";
173
+ for (const verdict of verdicts) if (OUTCOME_RANK[verdict.outcome] < OUTCOME_RANK[worst]) worst = verdict.outcome;
174
+ return worst;
175
+ }
176
+ /** The sentence for a fixture's outcome: every key in the offending state, and what state that is. */
177
+ function reasonFor(outcome, verdicts) {
178
+ if (outcome === "present") return "Ready.";
179
+ const offenders = verdicts.filter((verdict) => verdict.outcome === outcome);
180
+ const note = offenders[0]?.note ?? "is not set";
181
+ return `${offenders.map((verdict) => verdict.key).join(", ")} ${offenders.length === 1 ? note : note.replace(/^is /, "are ")}.`;
182
+ }
183
+ /**
184
+ * Resolve one fixture against an environment.
185
+ *
186
+ * Never throws and never reaches the network. It answers what this run has; deciding what to do about it
187
+ * is {@link fixtureReady}'s job, and saying so out loud is {@link reportFixtureEstate}'s.
188
+ */
189
+ function resolveFixture(name, options = {}) {
190
+ const fixture = LIVE_FIXTURES[name];
191
+ const env = options.env ?? fixtureEnv();
192
+ const verdicts = fixture.keys.map((key) => verdictFor(key, fixture.shape, env[key]));
193
+ const outcome = foldOutcome(verdicts);
194
+ return {
195
+ fixture,
196
+ outcome,
197
+ ready: outcome === "present",
198
+ reason: reasonFor(outcome, verdicts)
199
+ };
200
+ }
201
+ /**
202
+ * Whether a suite gated on this fixture can run — the boolean a `describe.skipIf` negates:
203
+ *
204
+ * ```ts
205
+ * describe.skipIf(!fixtureReady("turnstile-widget"))("turnstile — LIVE", () => { … });
206
+ * ```
207
+ *
208
+ * False for every outcome that is not `present`, malformed included. The distinction between a fixture
209
+ * nobody configured and one somebody configured wrongly belongs in the report, not in whether the suite
210
+ * runs: neither can be tested against, and a run that failed on the second would fail a contributor
211
+ * whose CI template exports blanks — the same "the kit is broken" this file exists to prevent.
212
+ */
213
+ function fixtureReady(name, options = {}) {
214
+ return resolveFixture(name, options).ready;
215
+ }
216
+ /**
217
+ * One of a ready fixture's values.
218
+ *
219
+ * Throws when the fixture is not ready, and that is not a contradiction of the rule above: reaching here
220
+ * means a suite read a value it never gated on, which is a defect in the suite rather than a missing
221
+ * credential. The error names the key and never carries a value — `detail` reaches a log.
222
+ */
223
+ function fixtureValue(name, key, options = {}) {
224
+ const fixture = LIVE_FIXTURES[name];
225
+ if (!fixture.keys.includes(key)) throw new ValidationError({
226
+ message: `Fixture ${name} declares no ${key}.`,
227
+ action: `Read one of: ${fixture.keys.join(", ")}.`,
228
+ detail: `fixtureValue was asked for ${key}, which is not in the ${name} fixture's keys.`
229
+ });
230
+ const resolution = resolveFixture(name, options);
231
+ if (!resolution.ready) throw new ValidationError({
232
+ message: `Fixture ${name} is ${resolution.outcome}.`,
233
+ action: `Gate the suite with fixtureReady("${name}"), or create the fixture: ${fixture.doc}.`,
234
+ detail: `${resolution.reason} A suite read a fixture value it had not gated on.`
235
+ });
236
+ return ((options.env ?? fixtureEnv())[key] ?? "").trim();
237
+ }
238
+ /**
239
+ * The lines a run prints about its fixtures — pure, so a test reads exactly what a human would.
240
+ *
241
+ * One line per fixture, then a count. A present fixture still gets a line: a report that only speaks when
242
+ * something is wrong cannot tell "the estate is complete" from "the report never ran", and that is the
243
+ * same indistinguishability this whole file is about.
244
+ */
245
+ function fixtureReportLines(resolutions) {
246
+ const lines = resolutions.map((resolution) => {
247
+ if (resolution.ready) return `fixture present: ${resolution.fixture.name}.`;
248
+ return [
249
+ `fixture ${resolution.outcome}: ${resolution.fixture.name}.`,
250
+ resolution.reason,
251
+ resolution.fixture.consequence,
252
+ `See ${resolution.fixture.doc}.`
253
+ ].join(" ");
254
+ });
255
+ const counted = (outcome) => resolutions.filter((entry) => entry.outcome === outcome).length;
256
+ lines.push(`fixtures: ${counted("present")} present, ${counted("absent")} absent, ${counted("malformed")} malformed, ${counted("declined")} declined. A skipped suite is not a passing one.`);
257
+ return lines;
258
+ }
259
+ /**
260
+ * Resolve every fixture, say what the run has, and hand back the resolutions.
261
+ *
262
+ * The `globalSetup` entry point. Never throws — a report that fails the run it was meant to explain is
263
+ * worse than no report, exactly as the debris sweep beside it is.
264
+ */
265
+ function reportFixtureEstate(options = {}) {
266
+ let resolutions = [];
267
+ try {
268
+ const env = options.env ?? fixtureEnv();
269
+ resolutions = Object.keys(LIVE_FIXTURES).map((name) => resolveFixture(name, { env }));
270
+ for (const line of fixtureReportLines(resolutions)) console.warn(line);
271
+ } catch (error) {
272
+ console.warn(`the fixture estate could not be reported: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ return resolutions;
275
+ }
276
+ //#endregion
277
+ export { LIVE_FIXTURES, fixtureEnv, fixtureReady, fixtureReportLines, fixtureValue, reportFixtureEstate, resolveFixture };