@pithy-sh/cloudflare 0.1.6 → 0.1.7
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/dist/client/errors.d.ts +144 -2
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +416 -7
- package/dist/media/assetSeeder.d.ts.map +1 -1
- package/dist/media/assetSeeder.js +19 -5
- package/dist/test-utils/emailRoutingRules.d.ts.map +1 -1
- package/dist/test-utils/emailRoutingRules.js +19 -6
- package/dist/test-utils/inboundRecorder.d.ts.map +1 -1
- package/dist/test-utils/inboundRecorder.js +19 -6
- package/dist/tokens/accountTokensManager.d.ts.map +1 -1
- package/dist/tokens/accountTokensManager.js +40 -17
- package/dist/workers/buildsManager.d.ts.map +1 -1
- package/dist/workers/buildsManager.js +9 -5
- package/dist/workflows/stepFailure.d.ts +28 -0
- package/dist/workflows/stepFailure.d.ts.map +1 -1
- package/dist/workflows/stepFailure.js +44 -2
- package/package.json +2 -2
- package/src/client/errors.ts +497 -8
- package/src/media/assetSeeder.ts +22 -4
- package/src/test-utils/emailRoutingRules.ts +19 -5
- package/src/test-utils/inboundRecorder.ts +19 -5
- package/src/tokens/accountTokensManager.ts +118 -54
- package/src/workers/buildsManager.ts +21 -6
- package/src/workflows/stepFailure.ts +44 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetSeeder.d.ts","sourceRoot":"","sources":["../../src/media/assetSeeder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAyB/D;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC7D,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAMzB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1F,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"assetSeeder.d.ts","sourceRoot":"","sources":["../../src/media/assetSeeder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAyB/D;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC7D,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAMzB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1F,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAiC1B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import {
|
|
3
|
+
import { cloudflareApiErrors, cloudflareRefusal, cloudflareRequest, decodeResponse } from "../client/errors.js";
|
|
4
4
|
import { withAssetOwnership } from "./ownership.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/media/assetSeeder.ts
|
|
@@ -61,12 +61,26 @@ async function uploadStreamBytes(manager, bytes, opts) {
|
|
|
61
61
|
method: "POST",
|
|
62
62
|
body: form
|
|
63
63
|
});
|
|
64
|
-
if (!response.ok)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const body = await response.text();
|
|
66
|
+
throw cloudflareRefusal({
|
|
67
|
+
problem: "The Stream direct upload failed.",
|
|
68
|
+
apiErrors: cloudflareApiErrors(parseEnvelope(body)),
|
|
69
|
+
status: response.status,
|
|
70
|
+
permission: "Stream",
|
|
71
|
+
detail: `Stream direct upload returned ${response.status}: ${body}`.slice(0, 2e3)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
68
74
|
});
|
|
69
75
|
return { uid: mint.uid };
|
|
70
76
|
}
|
|
77
|
+
/** A CF response body as an object, or `undefined` when it is not JSON — `cloudflareApiErrors` reads either. */
|
|
78
|
+
function parseEnvelope(body) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(body);
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
71
85
|
//#endregion
|
|
72
86
|
export { uploadImageBytes, uploadStreamBytes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailRoutingRules.d.ts","sourceRoot":"","sources":["../../src/test-utils/emailRoutingRules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkClD,iEAAiE;AACjE,eAAO,MAAM,mBAAmB;;;;iBAMoB,CAAC;AAErD,yEAAyE;AACzE,eAAO,MAAM,kBAAkB;;;iBAKoB,CAAC;AAEpD,qDAAqD;AACrD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;iBAYsC,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAiBjE,+DAA+D;AAC/D,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"emailRoutingRules.d.ts","sourceRoot":"","sources":["../../src/test-utils/emailRoutingRules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkClD,iEAAiE;AACjE,eAAO,MAAM,mBAAmB;;;;iBAMoB,CAAC;AAErD,yEAAyE;AACzE,eAAO,MAAM,kBAAkB;;;iBAKoB,CAAC;AAEpD,qDAAqD;AACrD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;iBAYsC,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAiBjE,+DAA+D;AAC/D,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA4BhH;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAEjF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import {
|
|
3
|
+
import { cloudflareApiErrors, cloudflareRefusal } from "../client/errors.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/test-utils/emailRoutingRules.ts
|
|
6
6
|
/**
|
|
@@ -67,11 +67,16 @@ async function listEmailRoutingRules(creds, zoneId) {
|
|
|
67
67
|
for (let page = 1; page <= MAX_RULE_PAGES; page += 1) {
|
|
68
68
|
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/email/routing/rules?page=${page}&per_page=${RULES_PER_PAGE}`;
|
|
69
69
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${creds.apiToken}` } });
|
|
70
|
-
if (!response.ok)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const body = await response.text();
|
|
72
|
+
throw cloudflareRefusal({
|
|
73
|
+
problem: "Could not read the zone's Email Routing rules.",
|
|
74
|
+
apiErrors: cloudflareApiErrors(parseEnvelope(body)),
|
|
75
|
+
status: response.status,
|
|
76
|
+
permission: "Email Routing Rules",
|
|
77
|
+
detail: `Email Routing rule list returned ${response.status}: ${body}`.slice(0, 2e3)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
75
80
|
const envelope = RuleListEnvelope.parse(await response.json());
|
|
76
81
|
const batch = envelope.result ?? [];
|
|
77
82
|
rules.push(...batch);
|
|
@@ -87,5 +92,13 @@ async function listEmailRoutingRules(creds, zoneId) {
|
|
|
87
92
|
function namedRules(rules) {
|
|
88
93
|
return rules.filter((rule) => rule.name !== "");
|
|
89
94
|
}
|
|
95
|
+
/** A CF response body as an object, or `undefined` when it is not JSON — `cloudflareApiErrors` reads either. */
|
|
96
|
+
function parseEnvelope(body) {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(body);
|
|
99
|
+
} catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
90
103
|
//#endregion
|
|
91
104
|
export { EmailRoutingAction, EmailRoutingMatcher, EmailRoutingRule, listEmailRoutingRules, namedRules };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inboundRecorder.d.ts","sourceRoot":"","sources":["../../src/test-utils/inboundRecorder.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAapD;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,myCAmCnC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;;iBAgBqD,CAAC;AAElF,6BAA6B;AAC7B,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,8FAA8F;AAC9F,eAAO,MAAM,0BAA0B,2CAA2C,CAAC;AAEnF,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,wBAAwB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,2GAA2G;IAC3G,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"inboundRecorder.d.ts","sourceRoot":"","sources":["../../src/test-utils/inboundRecorder.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAapD;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,myCAmCnC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;;iBAgBqD,CAAC;AAElF,6BAA6B;AAC7B,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,8FAA8F;AAC9F,eAAO,MAAM,0BAA0B,2CAA2C,CAAC;AAEnF,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,wBAAwB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,2GAA2G;IAC3G,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4BlC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { CloudflareInvalidResponseError,
|
|
3
|
+
import { CloudflareInvalidResponseError, cloudflareApiErrors, cloudflareRefusal } from "../client/errors.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/test-utils/inboundRecorder.ts
|
|
6
6
|
/**
|
|
@@ -127,11 +127,16 @@ async function readObservedInbound(options) {
|
|
|
127
127
|
const url = `https://api.cloudflare.com/client/v4/accounts/${creds.accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(nonce)}`;
|
|
128
128
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${creds.apiToken}` } });
|
|
129
129
|
if (response.status === 404) return null;
|
|
130
|
-
if (!response.ok)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const failure = await response.text();
|
|
132
|
+
throw cloudflareRefusal({
|
|
133
|
+
problem: "Could not read the recorded inbound message.",
|
|
134
|
+
apiErrors: cloudflareApiErrors(parseEnvelope(failure)),
|
|
135
|
+
status: response.status,
|
|
136
|
+
permission: "Workers KV Storage",
|
|
137
|
+
detail: `KV read for the inbound record returned ${response.status}: ${failure}`.slice(0, 2e3)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
135
140
|
const body = JSON.parse(await response.text());
|
|
136
141
|
if (typeof body === "object" && body !== null && "failure" in body) throw new CloudflareInvalidResponseError({
|
|
137
142
|
message: "The inbound recorder could not read the message it was handed.",
|
|
@@ -139,5 +144,13 @@ async function readObservedInbound(options) {
|
|
|
139
144
|
});
|
|
140
145
|
return ObservedInbound.parse(body);
|
|
141
146
|
}
|
|
147
|
+
/** A CF response body as an object, or `undefined` when it is not JSON — `cloudflareApiErrors` reads either. */
|
|
148
|
+
function parseEnvelope(body) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(body);
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
142
155
|
//#endregion
|
|
143
156
|
export { INBOUND_MODULE_NAME, INBOUND_NONCE_HEADER, INBOUND_RECORDER_BINDING, INBOUND_RECORDER_MODULE, ObservedInbound, PLACEHOLDER_INBOUND_MODULE, deployInboundRecorder, readObservedInbound };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountTokensManager.d.ts","sourceRoot":"","sources":["../../src/tokens/accountTokensManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"accountTokensManager.d.ts","sourceRoot":"","sources":["../../src/tokens/accountTokensManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB1D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kHAAkH;IAClH,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kGAAkG;IAClG,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzE;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;iBAK8E,CAAC;AAC7G,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;iBAO6D,CAAC;AAC7F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;iBAMuD,CAAC;AACxF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,SAAQ,iBAAiB;IACnE,cAAc,IAAI,MAAM,CAEvB;IAED,qGAAqG;IAC/F,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED;;;;;;;OAOG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAKhD;IAED;;;;;;;;;;;;OAYG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe1D;IAED,qGAAqG;IAC/F,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAezD;IAED;;;;;;OAMG;IACG,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAE7E;IAED;;;;;;;OAOG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAgDzF;IAED;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAqB3B,2GAA2G;IACrG,UAAU,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAajD;IAED,oGAAoG;IAC9F,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAYvE;IAED,qCAAqC;IAC/B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhD;IAED;;;;OAIG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBtD;IAED;;;;;OAKG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrD;IAED;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAKzF;CACF"}
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { CloudflareNotConfiguredError,
|
|
3
|
+
import { CloudflareNotConfiguredError, cloudflareApiErrors, cloudflareRefusal, cloudflareRequest, cloudflareSaid, decodeResponse, isAuthorizationError, messageOf, statusOf } from "../client/errors.js";
|
|
4
4
|
import { CloudflareManager } from "../client/manager.js";
|
|
5
5
|
import { CfTokenVerification } from "../user/userManager.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/tokens/accountTokensManager.ts
|
|
8
8
|
/**
|
|
9
|
+
* The account permission group behind every endpoint in this file, handed to `cloudflareRequest` so an
|
|
10
|
+
* auth-class refusal names it.
|
|
11
|
+
*
|
|
12
|
+
* It has to be stated rather than derived, because Cloudflare's `documentation_url` for
|
|
13
|
+
* `accounts/tokens` yields `accounts` — a segment `ACCOUNT_PERMISSIONS` deliberately does not map, since
|
|
14
|
+
* account-scoped endpoints span every product. And it has to be on **every** call here rather than only
|
|
15
|
+
* on the mint: `pithy token mint` is `rollToken → findTokenByName → listTokens`, then
|
|
16
|
+
* `listPermissionGroups`, and every one of those runs before `tokens.create` on the same credential. A
|
|
17
|
+
* hint reachable only from the mint is a hint an under-scoped bootstrap token never reaches (#534).
|
|
18
|
+
*
|
|
19
|
+
* `verifyToken` is the deliberate omission: any account token may call `/tokens/verify` with no
|
|
20
|
+
* permission at all, so a refusal there is a dead token or the wrong account, never a missing grant.
|
|
21
|
+
*/
|
|
22
|
+
const API_TOKENS_ENDPOINT = { permission: "API Tokens" };
|
|
23
|
+
/**
|
|
9
24
|
* The whole-account resource scope for a token policy: `{ "com.cloudflare.api.account.<id>": "*" }`.
|
|
10
25
|
* Account-level permission groups (Secrets Store, D1, Workers, …) scope to this. One helper so the
|
|
11
26
|
* exact resource key format lives in a single place instead of being hand-spelled per use case.
|
|
@@ -106,7 +121,7 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
106
121
|
if (isAuthorizationError(error)) return null;
|
|
107
122
|
throw error;
|
|
108
123
|
}
|
|
109
|
-
});
|
|
124
|
+
}, API_TOKENS_ENDPOINT);
|
|
110
125
|
const parsed = TokenName.safeParse(raw);
|
|
111
126
|
return parsed.success ? parsed.data.name : null;
|
|
112
127
|
}
|
|
@@ -119,7 +134,7 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
119
134
|
if (parsed.success) out.push(parsed.data);
|
|
120
135
|
}
|
|
121
136
|
return out;
|
|
122
|
-
});
|
|
137
|
+
}, API_TOKENS_ENDPOINT);
|
|
123
138
|
}
|
|
124
139
|
/**
|
|
125
140
|
* Resolve permission-group names to their `{ id }` references against the account's live group list.
|
|
@@ -154,15 +169,23 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
154
169
|
policies
|
|
155
170
|
});
|
|
156
171
|
} catch (error) {
|
|
157
|
-
if (isAuthorizationError(error))
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
172
|
+
if (isAuthorizationError(error)) {
|
|
173
|
+
const said = cloudflareSaid("The Cloudflare API token is not allowed to create account tokens.", cloudflareApiErrors(error));
|
|
174
|
+
throw new CloudflareNotConfiguredError({
|
|
175
|
+
message: said.message,
|
|
176
|
+
action: "Grant it 'Account API Tokens Write' (Account → API Tokens → Edit), then re-run.",
|
|
177
|
+
params: said.params,
|
|
178
|
+
detail: `mint account token '${name}': ${messageOf(error)}`
|
|
179
|
+
}, { cause: error });
|
|
180
|
+
}
|
|
181
|
+
throw cloudflareRefusal({
|
|
182
|
+
problem: `Failed to mint account token '${name}'.`,
|
|
183
|
+
apiErrors: cloudflareApiErrors(error),
|
|
184
|
+
status: statusOf(error),
|
|
185
|
+
permission: "API Tokens",
|
|
186
|
+
detail: messageOf(error),
|
|
187
|
+
cause: error
|
|
188
|
+
});
|
|
166
189
|
}
|
|
167
190
|
return decodeResponse(MintedAccountToken, raw, "account token create");
|
|
168
191
|
}
|
|
@@ -196,7 +219,7 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
196
219
|
if (parsed.success) out.push(parsed.data);
|
|
197
220
|
}
|
|
198
221
|
return out;
|
|
199
|
-
});
|
|
222
|
+
}, API_TOKENS_ENDPOINT);
|
|
200
223
|
}
|
|
201
224
|
/** Find an account token by exact name, or `null` — for idempotent re-mint (SDK auto-paginates). */
|
|
202
225
|
async findTokenByName(name) {
|
|
@@ -206,11 +229,11 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
206
229
|
if (parsed.success && parsed.data.name === name) return parsed.data;
|
|
207
230
|
}
|
|
208
231
|
return null;
|
|
209
|
-
});
|
|
232
|
+
}, API_TOKENS_ENDPOINT);
|
|
210
233
|
}
|
|
211
234
|
/** Delete an account token by id. */
|
|
212
235
|
async deleteToken(tokenId) {
|
|
213
|
-
await cloudflareRequest(`delete account token ${tokenId}`, () => this.getClient().accounts.tokens.delete(tokenId, { account_id: this.accountId }));
|
|
236
|
+
await cloudflareRequest(`delete account token ${tokenId}`, () => this.getClient().accounts.tokens.delete(tokenId, { account_id: this.accountId }), API_TOKENS_ENDPOINT);
|
|
214
237
|
}
|
|
215
238
|
/**
|
|
216
239
|
* Delete every account token with the given name; returns how many were removed. Names are not
|
|
@@ -226,7 +249,7 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
226
249
|
}
|
|
227
250
|
for (const id of ids) await this.getClient().accounts.tokens.delete(id, { account_id: this.accountId });
|
|
228
251
|
return ids.length;
|
|
229
|
-
});
|
|
252
|
+
}, API_TOKENS_ENDPOINT);
|
|
230
253
|
}
|
|
231
254
|
/**
|
|
232
255
|
* Roll an existing token's secret: regenerate its value **in place**, keeping the same token id,
|
|
@@ -238,7 +261,7 @@ var CloudflareAccountTokensManager = class extends CloudflareManager {
|
|
|
238
261
|
const raw = await cloudflareRequest(`roll account token value ${tokenId}`, () => this.getClient().accounts.tokens.value.update(tokenId, {
|
|
239
262
|
account_id: this.accountId,
|
|
240
263
|
body: {}
|
|
241
|
-
}));
|
|
264
|
+
}), API_TOKENS_ENDPOINT);
|
|
242
265
|
return decodeResponse(RolledTokenValue, raw, "account token value roll");
|
|
243
266
|
}
|
|
244
267
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildsManager.d.ts","sourceRoot":"","sources":["../../src/workers/buildsManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"buildsManager.d.ts","sourceRoot":"","sources":["../../src/workers/buildsManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { CloudflareInvalidResponseError, CloudflareRequestError, cloudflareRequest, decodeResponse, messageOf } from "../client/errors.js";
|
|
3
|
+
import { CloudflareInvalidResponseError, CloudflareRequestError, cloudflareApiErrors, cloudflareRefusal, cloudflareRequest, decodeResponse, messageOf } from "../client/errors.js";
|
|
4
4
|
import { CloudflareManager } from "../client/manager.js";
|
|
5
5
|
import { CfBuild, CfBuildLog, CfBuildToken, CfBuildTrigger, CfRepoConnection } from "./buildsTypes.js";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -17,7 +17,8 @@ const CfEnvelope = z.object({
|
|
|
17
17
|
success: z.boolean().optional(),
|
|
18
18
|
errors: z.array(z.object({
|
|
19
19
|
code: z.number().optional(),
|
|
20
|
-
message: z.string().optional()
|
|
20
|
+
message: z.string().optional(),
|
|
21
|
+
documentation_url: z.string().optional()
|
|
21
22
|
}).loose()).optional()
|
|
22
23
|
});
|
|
23
24
|
/**
|
|
@@ -61,10 +62,13 @@ var CloudflareBuildsManager = class extends CloudflareManager {
|
|
|
61
62
|
const envelope = CfEnvelope.safeParse(parsed);
|
|
62
63
|
const apiReportedFailure = envelope.success && envelope.data.success === false;
|
|
63
64
|
if (!response.ok || apiReportedFailure) {
|
|
64
|
-
const
|
|
65
|
+
const errors = envelope.success ? envelope.data.errors ?? [] : [];
|
|
66
|
+
const codes = errors.map((e) => e.code).filter((c) => c !== void 0);
|
|
65
67
|
const marker = codes.length > 0 ? `[cf-codes:${codes.join(",")}] ` : "";
|
|
66
|
-
throw
|
|
67
|
-
|
|
68
|
+
throw cloudflareRefusal({
|
|
69
|
+
problem: `Cloudflare Builds returned ${response.status}.`,
|
|
70
|
+
apiErrors: cloudflareApiErrors(errors),
|
|
71
|
+
status: response.status,
|
|
68
72
|
detail: `${marker}${method} ${path} → ${response.status}: ${text}`.slice(0, 2e3)
|
|
69
73
|
});
|
|
70
74
|
}
|
|
@@ -170,6 +170,34 @@ export declare const WORKFLOW_FAILED_STATUS: 500;
|
|
|
170
170
|
* the boundary — and nothing derived from the step's own `detail` is in it, because the step's
|
|
171
171
|
* `detail` never left the step. The only fields promoted from the far side are `message` and `action`,
|
|
172
172
|
* both already public, both already proved kit-authored by `kitSentence`.
|
|
173
|
+
*
|
|
174
|
+
* ## `params` did not cross either, and a recovered code still has to keep their promise
|
|
175
|
+
*
|
|
176
|
+
* `core`'s `GUARANTEED_ERROR_PARAMS` states the names a code's throw sites pass on **every** path, and
|
|
177
|
+
* a locale writes `{apiAnswer}` into its sentence against that statement. Re-raising a recovered code
|
|
178
|
+
* here is a throw site like any other — it just happens to be one whose inputs came over a wire that
|
|
179
|
+
* carries three fields. So every guaranteed name is supplied, as the empty string: `interpolate` leaves
|
|
180
|
+
* an unsupplied placeholder written out, and `{apiAnswer}` on a Spanish reader's screen is the exact
|
|
181
|
+
* defect the declaration exists to prevent. Empty is also the honest value — it is what the catalogs
|
|
182
|
+
* define as *no answer to quote*, and the answer genuinely did not cross.
|
|
183
|
+
*
|
|
184
|
+
* **One reader does lose something by it, and it is worth naming rather than glossing.** An earlier
|
|
185
|
+
* version of this comment said nothing was lost, on the grounds that the API's words are already inside
|
|
186
|
+
* the promoted English `message`. That is true of the operator's terminal, the `--json` line and any
|
|
187
|
+
* client with no catalog — but the contract is `t.maybe(payload.code, payload.params) ?? payload.message`
|
|
188
|
+
* (`core/src/i18n/translator.ts`), and `maybe` returns null only on a catalog **miss**. `es` has an entry
|
|
189
|
+
* for this code, so a Spanish reader gets the translated sentence with an empty `{apiAnswer}` rather
|
|
190
|
+
* than falling through to the English one that carries the answer:
|
|
191
|
+
*
|
|
192
|
+
* ```
|
|
193
|
+
* at the CLI No se ha podido completar la llamada a Cloudflare: 10000 Authentication error.
|
|
194
|
+
* across a step No se ha podido completar la llamada a Cloudflare.
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* The step channel carries three fields and `params` is not one of them, so closing this means widening
|
|
198
|
+
* the channel or re-parsing the promoted sentence — the first is a change to a durable format, the
|
|
199
|
+
* second reconstructs data from prose. Neither belongs in a re-raise, so the gap stands and is written
|
|
200
|
+
* down here instead of being claimed away.
|
|
173
201
|
*/
|
|
174
202
|
export declare function terminalWorkflowError(args: {
|
|
175
203
|
/** What the instance's steps said, or `null` when no step reported a failure. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stepFailure.d.ts","sourceRoot":"","sources":["../../src/workflows/stepFailure.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stepFailure.d.ts","sourceRoot":"","sources":["../../src/workflows/stepFailure.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AAOjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,gHAAgH;AAChH,eAAO,MAAM,qBAAqB;;;iBAQwD,CAAC;AAC3F,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E,qGAAqG;AACrG,eAAO,MAAM,mBAAmB;;;;;;iBAK0D,CAAC;AAC3F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAQ+D,CAAC;AAC/F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE,gGAAgG;AAChG,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yGAAyG;IACzG,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAmBD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqBnG;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAAG,mBAAmB,GAAG,IAAI,CAoB7F;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAC;AAEpE,oGAAoG;AACpG,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,iFAAiF;IACjF,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACpC,8FAA8F;IAC9F,eAAe,EAAE,MAAM,CAAC;IACxB,uGAAuG;IACvG,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,UAAU,CAmBb"}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { PithyError } from "@pithy-sh/core/src/error/pithyError";
|
|
5
|
-
import { ErrorPayload, kitErrorStatus } from "@pithy-sh/core/src/error/payload";
|
|
6
5
|
import { MAX_WORKFLOW_STEP_TEXT, decodeWorkflowStepMessage, splitWorkflowStepCode } from "@pithy-sh/core/src/workflow/stepMessage";
|
|
6
|
+
import { guaranteedErrorParams } from "@pithy-sh/core/src/error/messageParams";
|
|
7
|
+
import { ErrorPayload, kitErrorStatus } from "@pithy-sh/core/src/error/payload";
|
|
7
8
|
//#region src/workflows/stepFailure.ts
|
|
8
9
|
/**
|
|
9
10
|
* **The step's sentence, recovered from an instance the engine has already written its own over.**
|
|
@@ -200,6 +201,34 @@ const WORKFLOW_FAILED_STATUS = 500;
|
|
|
200
201
|
* the boundary — and nothing derived from the step's own `detail` is in it, because the step's
|
|
201
202
|
* `detail` never left the step. The only fields promoted from the far side are `message` and `action`,
|
|
202
203
|
* both already public, both already proved kit-authored by `kitSentence`.
|
|
204
|
+
*
|
|
205
|
+
* ## `params` did not cross either, and a recovered code still has to keep their promise
|
|
206
|
+
*
|
|
207
|
+
* `core`'s `GUARANTEED_ERROR_PARAMS` states the names a code's throw sites pass on **every** path, and
|
|
208
|
+
* a locale writes `{apiAnswer}` into its sentence against that statement. Re-raising a recovered code
|
|
209
|
+
* here is a throw site like any other — it just happens to be one whose inputs came over a wire that
|
|
210
|
+
* carries three fields. So every guaranteed name is supplied, as the empty string: `interpolate` leaves
|
|
211
|
+
* an unsupplied placeholder written out, and `{apiAnswer}` on a Spanish reader's screen is the exact
|
|
212
|
+
* defect the declaration exists to prevent. Empty is also the honest value — it is what the catalogs
|
|
213
|
+
* define as *no answer to quote*, and the answer genuinely did not cross.
|
|
214
|
+
*
|
|
215
|
+
* **One reader does lose something by it, and it is worth naming rather than glossing.** An earlier
|
|
216
|
+
* version of this comment said nothing was lost, on the grounds that the API's words are already inside
|
|
217
|
+
* the promoted English `message`. That is true of the operator's terminal, the `--json` line and any
|
|
218
|
+
* client with no catalog — but the contract is `t.maybe(payload.code, payload.params) ?? payload.message`
|
|
219
|
+
* (`core/src/i18n/translator.ts`), and `maybe` returns null only on a catalog **miss**. `es` has an entry
|
|
220
|
+
* for this code, so a Spanish reader gets the translated sentence with an empty `{apiAnswer}` rather
|
|
221
|
+
* than falling through to the English one that carries the answer:
|
|
222
|
+
*
|
|
223
|
+
* ```
|
|
224
|
+
* at the CLI No se ha podido completar la llamada a Cloudflare: 10000 Authentication error.
|
|
225
|
+
* across a step No se ha podido completar la llamada a Cloudflare.
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* The step channel carries three fields and `params` is not one of them, so closing this means widening
|
|
229
|
+
* the channel or re-parsing the promoted sentence — the first is a change to a durable format, the
|
|
230
|
+
* second reconstructs data from prose. Neither belongs in a re-raise, so the gap stands and is written
|
|
231
|
+
* down here instead of being claimed away.
|
|
203
232
|
*/
|
|
204
233
|
function terminalWorkflowError(args) {
|
|
205
234
|
const { failure, fallbackMessage, detail } = args;
|
|
@@ -212,7 +241,8 @@ function terminalWorkflowError(args) {
|
|
|
212
241
|
status,
|
|
213
242
|
message,
|
|
214
243
|
action,
|
|
215
|
-
detail
|
|
244
|
+
detail,
|
|
245
|
+
params: crossedParams(failure.code)
|
|
216
246
|
};
|
|
217
247
|
const parsed = ErrorPayload.safeParse(candidate);
|
|
218
248
|
if (parsed.success) return new PithyError(parsed.data);
|
|
@@ -225,5 +255,17 @@ function terminalWorkflowError(args) {
|
|
|
225
255
|
detail
|
|
226
256
|
});
|
|
227
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* The `params` a re-raised code owes a translating client: every name its throw sites guarantee, empty.
|
|
260
|
+
*
|
|
261
|
+
* `undefined` when the code guarantees none, so a payload for the other 60-odd codes is byte-identical
|
|
262
|
+
* to what it was — an empty `params` object on every recovered error would be a new field on the wire
|
|
263
|
+
* for no reader.
|
|
264
|
+
*/
|
|
265
|
+
function crossedParams(code) {
|
|
266
|
+
const names = guaranteedErrorParams(code);
|
|
267
|
+
if (names.length === 0) return void 0;
|
|
268
|
+
return Object.fromEntries(names.map((name) => [name, ""]));
|
|
269
|
+
}
|
|
228
270
|
//#endregion
|
|
229
271
|
export { WORKFLOW_FAILED_CODE, WORKFLOW_FAILED_STATUS, WorkflowFailureDetail, WorkflowStepAttempt, WorkflowStepRecord, kitSentence, stepFailure, terminalWorkflowError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pithy-sh/cloudflare",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@aws-sdk/client-s3": "^3.1111.0",
|
|
37
37
|
"@aws-sdk/s3-request-presigner": "^3.1111.0",
|
|
38
38
|
"@cloudflare/workers-types": "^5.20260729.1",
|
|
39
|
-
"@pithy-sh/core": "^0.3.
|
|
39
|
+
"@pithy-sh/core": "^0.3.1",
|
|
40
40
|
"cloudflare": "^7.0.0",
|
|
41
41
|
"js-base64": "^3.9.2"
|
|
42
42
|
},
|