alchemy 0.1.16 → 0.1.18
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/lib/$.d.ts +12 -0
- package/lib/$.d.ts.map +1 -0
- package/lib/$.js +42 -0
- package/lib/$.js.map +1 -0
- package/lib/alchemize.d.ts +47 -0
- package/lib/alchemize.d.ts.map +1 -0
- package/lib/alchemize.js +116 -0
- package/lib/alchemize.js.map +1 -0
- package/lib/apply.d.ts +23 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +4 -0
- package/lib/apply.js.map +1 -1
- package/lib/aws/account-id.d.ts +5 -0
- package/lib/aws/account-id.d.ts.map +1 -0
- package/lib/aws/account-id.js +10 -0
- package/lib/aws/account-id.js.map +1 -0
- package/lib/aws/bucket.d.ts +21 -0
- package/lib/aws/bucket.d.ts.map +1 -0
- package/lib/aws/bucket.js +114 -0
- package/lib/aws/bucket.js.map +1 -0
- package/lib/aws/function.d.ts +53 -0
- package/lib/aws/function.d.ts.map +1 -0
- package/lib/aws/function.js +195 -0
- package/lib/aws/function.js.map +1 -0
- package/lib/aws/index.d.ts +9 -0
- package/lib/aws/index.d.ts.map +1 -0
- package/lib/aws/index.js +9 -0
- package/lib/aws/index.js.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.js +22 -0
- package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
- package/lib/aws/oidc/index.d.ts +3 -0
- package/lib/aws/oidc/index.d.ts.map +1 -0
- package/lib/aws/oidc/index.js +3 -0
- package/lib/aws/oidc/index.js.map +1 -0
- package/lib/aws/oidc/oidc-provider.d.ts +60 -0
- package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/oidc-provider.js +140 -0
- package/lib/aws/oidc/oidc-provider.js.map +1 -0
- package/lib/aws/policy.d.ts +46 -0
- package/lib/aws/policy.d.ts.map +1 -0
- package/lib/aws/policy.js +139 -0
- package/lib/aws/policy.js.map +1 -0
- package/lib/aws/queue.d.ts +62 -0
- package/lib/aws/queue.d.ts.map +1 -0
- package/lib/aws/queue.js +167 -0
- package/lib/aws/queue.js.map +1 -0
- package/lib/aws/role.d.ts +27 -0
- package/lib/aws/role.d.ts.map +1 -0
- package/lib/aws/role.js +197 -0
- package/lib/aws/role.js.map +1 -0
- package/lib/aws/ses.d.ts +75 -0
- package/lib/aws/ses.d.ts.map +1 -0
- package/lib/aws/ses.js +189 -0
- package/lib/aws/ses.js.map +1 -0
- package/lib/aws/table.d.ts +26 -0
- package/lib/aws/table.d.ts.map +1 -0
- package/lib/aws/table.js +154 -0
- package/lib/aws/table.js.map +1 -0
- package/lib/cloudflare/api.d.ts +88 -0
- package/lib/cloudflare/api.d.ts.map +1 -0
- package/lib/cloudflare/api.js +184 -0
- package/lib/cloudflare/api.js.map +1 -0
- package/lib/cloudflare/asset-manifest.d.ts +15 -0
- package/lib/cloudflare/asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/asset-manifest.js +2 -0
- package/lib/cloudflare/asset-manifest.js.map +1 -0
- package/lib/cloudflare/auth.d.ts +52 -0
- package/lib/cloudflare/auth.d.ts.map +1 -0
- package/lib/cloudflare/auth.js +114 -0
- package/lib/cloudflare/auth.js.map +1 -0
- package/lib/cloudflare/bindings.d.ts +261 -0
- package/lib/cloudflare/bindings.d.ts.map +1 -0
- package/lib/cloudflare/bindings.js +2 -0
- package/lib/cloudflare/bindings.js.map +1 -0
- package/lib/cloudflare/bound.d.ts +8 -0
- package/lib/cloudflare/bound.d.ts.map +1 -0
- package/lib/cloudflare/bound.js +2 -0
- package/lib/cloudflare/bound.js.map +1 -0
- package/lib/cloudflare/bucket.d.ts +54 -0
- package/lib/cloudflare/bucket.d.ts.map +1 -0
- package/lib/cloudflare/bucket.js +120 -0
- package/lib/cloudflare/bucket.js.map +1 -0
- package/lib/cloudflare/durable-object-namespace.d.ts +20 -0
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -0
- package/lib/cloudflare/durable-object-namespace.js +21 -0
- package/lib/cloudflare/durable-object-namespace.js.map +1 -0
- package/lib/cloudflare/generate-asset-manifest.d.ts +3 -0
- package/lib/cloudflare/generate-asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/generate-asset-manifest.js +69 -0
- package/lib/cloudflare/generate-asset-manifest.js.map +1 -0
- package/lib/cloudflare/index.d.ts +8 -0
- package/lib/cloudflare/index.d.ts.map +1 -0
- package/lib/cloudflare/index.js +8 -0
- package/lib/cloudflare/index.js.map +1 -0
- package/lib/cloudflare/kv-namespace.d.ts +64 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -0
- package/lib/cloudflare/kv-namespace.js +114 -0
- package/lib/cloudflare/kv-namespace.js.map +1 -0
- package/lib/cloudflare/state.d.ts +83 -0
- package/lib/cloudflare/state.d.ts.map +1 -0
- package/lib/cloudflare/state.js +109 -0
- package/lib/cloudflare/state.js.map +1 -0
- package/lib/cloudflare/static-site-router.d.ts +19 -0
- package/lib/cloudflare/static-site-router.d.ts.map +1 -0
- package/lib/cloudflare/static-site-router.js +110 -0
- package/lib/cloudflare/static-site-router.js.map +1 -0
- package/lib/cloudflare/static-site.d.ts +166 -0
- package/lib/cloudflare/static-site.d.ts.map +1 -0
- package/lib/cloudflare/static-site.js +161 -0
- package/lib/cloudflare/static-site.js.map +1 -0
- package/lib/cloudflare/types.d.ts +52 -0
- package/lib/cloudflare/types.d.ts.map +1 -0
- package/lib/cloudflare/types.js +21 -0
- package/lib/cloudflare/types.js.map +1 -0
- package/lib/cloudflare/upload-asset-manifest.d.ts +12 -0
- package/lib/cloudflare/upload-asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/upload-asset-manifest.js +55 -0
- package/lib/cloudflare/upload-asset-manifest.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts +130 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -0
- package/lib/cloudflare/worker-metadata.js +2 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -0
- package/lib/cloudflare/worker-migration.d.ts +110 -0
- package/lib/cloudflare/worker-migration.d.ts.map +1 -0
- package/lib/cloudflare/worker-migration.js +11 -0
- package/lib/cloudflare/worker-migration.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +114 -0
- package/lib/cloudflare/worker.d.ts.map +1 -0
- package/lib/cloudflare/worker.js +419 -0
- package/lib/cloudflare/worker.js.map +1 -0
- package/lib/destroy.d.ts +19 -0
- package/lib/encrypt.d.ts +17 -0
- package/lib/encrypt.d.ts.map +1 -0
- package/lib/encrypt.js +45 -0
- package/lib/encrypt.js.map +1 -0
- package/lib/error.d.ts +2 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +13 -0
- package/lib/error.js.map +1 -0
- package/lib/esbuild.d.ts +152 -0
- package/lib/esbuild.d.ts.map +1 -0
- package/lib/esbuild.js +67 -0
- package/lib/esbuild.js.map +1 -0
- package/lib/fs.d.ts +12 -0
- package/lib/fs.d.ts.map +1 -0
- package/lib/fs.js +41 -0
- package/lib/fs.js.map +1 -0
- package/lib/github/client.d.ts +34 -0
- package/lib/github/client.d.ts.map +1 -0
- package/lib/github/client.js +91 -0
- package/lib/github/client.js.map +1 -0
- package/lib/github/index.d.ts +2 -0
- package/lib/github/index.d.ts.map +1 -0
- package/lib/github/index.js +2 -0
- package/lib/github/index.js.map +1 -0
- package/lib/github/secret.d.ts +47 -0
- package/lib/github/secret.d.ts.map +1 -0
- package/lib/github/secret.js +99 -0
- package/lib/github/secret.js.map +1 -0
- package/lib/global.d.ts +16 -0
- package/lib/global.d.ts.map +1 -0
- package/lib/global.js +42 -0
- package/lib/global.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +10 -0
- package/lib/index.js.map +1 -0
- package/lib/input.d.ts +7 -0
- package/lib/input.d.ts.map +1 -0
- package/lib/input.js +2 -0
- package/lib/input.js.map +1 -0
- package/lib/main.d.ts +3 -0
- package/lib/main.d.ts.map +1 -0
- package/lib/main.js +32 -0
- package/lib/main.js.map +1 -0
- package/lib/output.d.ts +25 -0
- package/lib/output.d.ts.map +1 -0
- package/lib/output.js +23 -0
- package/lib/output.js.map +1 -0
- package/lib/print.d.ts +7 -0
- package/lib/print.d.ts.map +1 -0
- package/lib/print.js +10 -0
- package/lib/print.js.map +1 -0
- package/lib/resource.d.ts +71 -0
- package/lib/resource.d.ts.map +1 -0
- package/lib/resource.js +276 -0
- package/lib/resource.js.map +1 -0
- package/lib/scope.d.ts +18 -0
- package/lib/scope.d.ts.map +1 -0
- package/lib/scope.js +30 -0
- package/lib/scope.js.map +1 -0
- package/lib/secret.d.ts +12 -0
- package/lib/secret.d.ts.map +1 -0
- package/lib/secret.js +30 -0
- package/lib/secret.js.map +1 -0
- package/lib/serde.d.ts +3 -0
- package/lib/serde.d.ts.map +1 -0
- package/lib/serde.js +37 -0
- package/lib/serde.js.map +1 -0
- package/lib/slug.d.ts +2 -0
- package/lib/slug.d.ts.map +1 -0
- package/lib/slug.js +4 -0
- package/lib/slug.js.map +1 -0
- package/lib/state.d.ts +40 -0
- package/lib/state.d.ts.map +1 -0
- package/lib/state.js +74 -0
- package/lib/state.js.map +1 -0
- package/lib/stripe/index.d.ts +4 -0
- package/lib/stripe/index.d.ts.map +1 -0
- package/lib/stripe/index.js +4 -0
- package/lib/stripe/index.js.map +1 -0
- package/lib/stripe/price.d.ts +109 -0
- package/lib/stripe/price.d.ts.map +1 -0
- package/lib/stripe/price.js +112 -0
- package/lib/stripe/price.js.map +1 -0
- package/lib/stripe/product.d.ts +86 -0
- package/lib/stripe/product.d.ts.map +1 -0
- package/lib/stripe/product.js +90 -0
- package/lib/stripe/product.js.map +1 -0
- package/lib/stripe/webhook.d.ts +73 -0
- package/lib/stripe/webhook.d.ts.map +1 -0
- package/lib/stripe/webhook.js +94 -0
- package/lib/stripe/webhook.js.map +1 -0
- package/lib/utils/content-type.d.ts +8 -0
- package/lib/utils/content-type.d.ts.map +1 -0
- package/lib/utils/content-type.js +41 -0
- package/lib/utils/content-type.js.map +1 -0
- package/lib/utils/retry.d.ts +13 -0
- package/lib/utils/retry.d.ts.map +1 -0
- package/lib/utils/retry.js +32 -0
- package/lib/utils/retry.js.map +1 -0
- package/package.json +3 -6
- package/src/alchemize.ts +9 -0
- package/src/apply.ts +3 -0
- package/src/cloudflare/bindings.ts +9 -1
- package/src/cloudflare/bound.ts +18 -0
- package/src/cloudflare/bucket.ts +240 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/static-site.ts +14 -2
- package/src/cloudflare/worker.ts +84 -59
- package/src/encrypt.ts +79 -0
- package/src/github/secret.ts +34 -33
- package/src/index.ts +1 -0
- package/src/input.ts +13 -10
- package/src/output.ts +13 -9
- package/src/secret.ts +41 -0
- package/src/serde.ts +46 -0
- package/src/state.ts +3 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { Resolved } from "../output";
|
|
2
|
+
import { type Context, Resource } from "../resource";
|
|
3
|
+
import { createCloudflareApi } from "./api";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for creating or updating an R2 Bucket
|
|
7
|
+
*/
|
|
8
|
+
export interface BucketProps {
|
|
9
|
+
/**
|
|
10
|
+
* Name of the bucket
|
|
11
|
+
* Names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)
|
|
12
|
+
* Cannot begin or end with a hyphen
|
|
13
|
+
*/
|
|
14
|
+
name?: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Optional location hint for the bucket
|
|
18
|
+
* Indicates the primary geographical location data will be accessed from
|
|
19
|
+
*/
|
|
20
|
+
locationHint?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Optional jurisdiction for the bucket
|
|
24
|
+
* Determines the regulatory jurisdiction the bucket data falls under
|
|
25
|
+
*/
|
|
26
|
+
jurisdiction?: "default" | "eu" | "fedramp";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether to allow public access through the r2.dev subdomain
|
|
30
|
+
* Only for development purposes - use custom domains for production
|
|
31
|
+
*/
|
|
32
|
+
allowPublicAccess?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Output returned after R2 Bucket creation/update
|
|
37
|
+
*/
|
|
38
|
+
export interface BucketOutput extends BucketProps {
|
|
39
|
+
/**
|
|
40
|
+
* The ID of the bucket (same as name)
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Location of the bucket
|
|
46
|
+
*/
|
|
47
|
+
location: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Time at which the bucket was created (Unix timestamp in seconds)
|
|
51
|
+
*/
|
|
52
|
+
creationDate: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resource type identifier
|
|
56
|
+
*/
|
|
57
|
+
type: "r2_bucket";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Cloudflare R2 bucket API response
|
|
62
|
+
*/
|
|
63
|
+
interface CloudflareBucketResponse {
|
|
64
|
+
result: {
|
|
65
|
+
name: string;
|
|
66
|
+
location?: string;
|
|
67
|
+
creation_date: string;
|
|
68
|
+
};
|
|
69
|
+
success: boolean;
|
|
70
|
+
errors: Array<{ code: number; message: string }>;
|
|
71
|
+
messages: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isBucket(resource: any): resource is Resolved<R2Bucket> {
|
|
75
|
+
return (
|
|
76
|
+
resource && typeof resource === "object" && resource.id && resource.location
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class R2Bucket extends Resource(
|
|
81
|
+
"cloudflare::R2Bucket",
|
|
82
|
+
async (ctx: Context<BucketOutput>, props: BucketProps) => {
|
|
83
|
+
// Create Cloudflare API client with automatic account discovery
|
|
84
|
+
const api = await createCloudflareApi();
|
|
85
|
+
|
|
86
|
+
// Resource ID defaults to bucket name if provided
|
|
87
|
+
const bucketName = props.name || ctx.resourceID;
|
|
88
|
+
|
|
89
|
+
if (ctx.event === "delete") {
|
|
90
|
+
if (ctx.output?.id) {
|
|
91
|
+
// Delete R2 bucket
|
|
92
|
+
const headers: Record<string, string> = {};
|
|
93
|
+
if (props.jurisdiction && props.jurisdiction !== "default") {
|
|
94
|
+
headers["cf-r2-jurisdiction"] = props.jurisdiction;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const deleteResponse = await api.delete(
|
|
98
|
+
`/accounts/${api.accountId}/r2/buckets/${ctx.output.id}`,
|
|
99
|
+
{ headers },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
103
|
+
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
104
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
105
|
+
}));
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Error deleting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Return void (a deleted bucket has no content)
|
|
113
|
+
return;
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
let bucketOutput: BucketOutput;
|
|
117
|
+
|
|
118
|
+
if (ctx.event === "update" && ctx.output?.id) {
|
|
119
|
+
// Get bucket details to verify it exists
|
|
120
|
+
const headers: Record<string, string> = {};
|
|
121
|
+
if (props.jurisdiction && props.jurisdiction !== "default") {
|
|
122
|
+
headers["cf-r2-jurisdiction"] = props.jurisdiction;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const getResponse = await api.get(
|
|
126
|
+
`/accounts/${api.accountId}/r2/buckets/${bucketName}`,
|
|
127
|
+
{ headers },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (!getResponse.ok) {
|
|
131
|
+
const errorData: any = await getResponse.json().catch(() => ({
|
|
132
|
+
errors: [{ message: getResponse.statusText }],
|
|
133
|
+
}));
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Error getting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || getResponse.statusText}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = (await getResponse.json()) as CloudflareBucketResponse;
|
|
140
|
+
bucketOutput = {
|
|
141
|
+
id: result.result.name,
|
|
142
|
+
name: result.result.name,
|
|
143
|
+
location: result.result.location || "default",
|
|
144
|
+
creationDate: Math.floor(
|
|
145
|
+
new Date(result.result.creation_date).getTime() / 1000,
|
|
146
|
+
),
|
|
147
|
+
jurisdiction: props.jurisdiction || "default",
|
|
148
|
+
type: "r2_bucket",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Update public access setting if it has changed
|
|
152
|
+
if (props.allowPublicAccess !== undefined) {
|
|
153
|
+
await updatePublicAccess(api, bucketName, props.allowPublicAccess);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Create new R2 bucket
|
|
157
|
+
const createPayload: any = {
|
|
158
|
+
name: bucketName,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (props.locationHint) {
|
|
162
|
+
createPayload.location_hint = props.locationHint;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const headers: Record<string, string> = {};
|
|
166
|
+
if (props.jurisdiction && props.jurisdiction !== "default") {
|
|
167
|
+
headers["cf-r2-jurisdiction"] = props.jurisdiction;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const createResponse = await api.post(
|
|
171
|
+
`/accounts/${api.accountId}/r2/buckets`,
|
|
172
|
+
createPayload,
|
|
173
|
+
{ headers },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!createResponse.ok) {
|
|
177
|
+
const errorData: any = await createResponse.json().catch(() => ({
|
|
178
|
+
errors: [{ message: createResponse.statusText }],
|
|
179
|
+
}));
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Error creating R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || createResponse.statusText}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result =
|
|
186
|
+
(await createResponse.json()) as CloudflareBucketResponse;
|
|
187
|
+
bucketOutput = {
|
|
188
|
+
id: result.result.name,
|
|
189
|
+
name: result.result.name,
|
|
190
|
+
location: result.result.location || "default",
|
|
191
|
+
creationDate: Math.floor(
|
|
192
|
+
new Date(result.result.creation_date).getTime() / 1000,
|
|
193
|
+
),
|
|
194
|
+
jurisdiction: props.jurisdiction || "default",
|
|
195
|
+
type: "r2_bucket",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Set public access if requested
|
|
199
|
+
if (props.allowPublicAccess) {
|
|
200
|
+
await updatePublicAccess(api, bucketName, true);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return bucketOutput;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("Error creating/updating R2 bucket:", error);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
) {}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Helper function to update the public access setting for a bucket
|
|
215
|
+
*/
|
|
216
|
+
async function updatePublicAccess(
|
|
217
|
+
api: any,
|
|
218
|
+
bucketName: string,
|
|
219
|
+
allowPublicAccess: boolean,
|
|
220
|
+
): Promise<void> {
|
|
221
|
+
try {
|
|
222
|
+
const response = await api.put(
|
|
223
|
+
`/accounts/${api.accountId}/r2/buckets/${bucketName}/domains/managed`,
|
|
224
|
+
{
|
|
225
|
+
enabled: allowPublicAccess,
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const errorData: any = await response.json().catch(() => ({
|
|
231
|
+
errors: [{ message: response.statusText }],
|
|
232
|
+
}));
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Error updating R2 bucket public access: ${errorData.errors?.[0]?.message || response.statusText}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.warn("Could not set public access:", error);
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -323,6 +323,18 @@ export class StaticSite extends Resource(
|
|
|
323
323
|
},
|
|
324
324
|
},
|
|
325
325
|
};
|
|
326
|
+
|
|
327
|
+
// Determine the entrypoint file - check for .ts first, fallback to .js
|
|
328
|
+
let entrypointFile = "static-site-router.js";
|
|
329
|
+
try {
|
|
330
|
+
const tsFile = path.resolve(__dirname, "static-site-router.ts");
|
|
331
|
+
await fs.access(tsFile);
|
|
332
|
+
// If we reach here, the TypeScript file exists
|
|
333
|
+
entrypointFile = "static-site-router.ts";
|
|
334
|
+
} catch (error) {
|
|
335
|
+
// TypeScript file doesn't exist, use JavaScript (default)
|
|
336
|
+
}
|
|
337
|
+
|
|
326
338
|
const worker = new Worker(
|
|
327
339
|
"worker",
|
|
328
340
|
{
|
|
@@ -333,7 +345,7 @@ export class StaticSite extends Resource(
|
|
|
333
345
|
...(props.workerScript
|
|
334
346
|
? { script: props.workerScript }
|
|
335
347
|
: {
|
|
336
|
-
entrypoint: path.resolve(__dirname,
|
|
348
|
+
entrypoint: path.resolve(__dirname, entrypointFile),
|
|
337
349
|
bundle: bundleOptions,
|
|
338
350
|
}),
|
|
339
351
|
bindings,
|
|
@@ -367,7 +379,7 @@ export class StaticSite extends Resource(
|
|
|
367
379
|
updatedAt: now,
|
|
368
380
|
domain: props.domain,
|
|
369
381
|
production: props.production !== false,
|
|
370
|
-
url: worker.url
|
|
382
|
+
url: worker.url as Input<string | undefined>,
|
|
371
383
|
routes,
|
|
372
384
|
};
|
|
373
385
|
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import { apply } from "../apply";
|
|
3
3
|
import { Bundle, type BundleProps } from "../esbuild";
|
|
4
|
+
import type { Input } from "../input";
|
|
4
5
|
import type { Resolved } from "../output";
|
|
5
6
|
import { type Context, Resource } from "../resource";
|
|
7
|
+
import { Secret } from "../secret";
|
|
6
8
|
import { withExponentialBackoff } from "../utils/retry";
|
|
7
9
|
import { type CloudflareApi, createCloudflareApi } from "./api";
|
|
8
10
|
import type { Bindings, WorkerBindingSpec } from "./bindings";
|
|
11
|
+
import type { Bound } from "./bound";
|
|
12
|
+
import { isBucket } from "./bucket";
|
|
9
13
|
import type { DurableObjectNamespace } from "./durable-object-namespace";
|
|
10
14
|
import { isDurableObjectNamespace } from "./durable-object-namespace";
|
|
11
15
|
import { isKVNamespace } from "./kv-namespace";
|
|
@@ -14,7 +18,7 @@ import type { SingleStepMigration } from "./worker-migration";
|
|
|
14
18
|
/**
|
|
15
19
|
* Properties for creating or updating a Worker
|
|
16
20
|
*/
|
|
17
|
-
export interface WorkerProps {
|
|
21
|
+
export interface WorkerProps<B extends Bindings = Bindings> {
|
|
18
22
|
/**
|
|
19
23
|
* The worker script content (JavaScript or WASM)
|
|
20
24
|
* One of script, entryPoint, or bundle must be provided
|
|
@@ -57,7 +61,7 @@ export interface WorkerProps {
|
|
|
57
61
|
/**
|
|
58
62
|
* Bindings to attach to the worker
|
|
59
63
|
*/
|
|
60
|
-
bindings?:
|
|
64
|
+
bindings?: B;
|
|
61
65
|
|
|
62
66
|
/**
|
|
63
67
|
* Environment variables to attach to the worker
|
|
@@ -96,7 +100,8 @@ export interface WorkerProps {
|
|
|
96
100
|
/**
|
|
97
101
|
* Output returned after Worker creation/update
|
|
98
102
|
*/
|
|
99
|
-
export interface WorkerOutput extends
|
|
103
|
+
export interface WorkerOutput<B extends Bindings = Bindings>
|
|
104
|
+
extends Omit<WorkerProps<B>, "url"> {
|
|
100
105
|
type: "service";
|
|
101
106
|
|
|
102
107
|
/**
|
|
@@ -123,83 +128,91 @@ export interface WorkerOutput extends Omit<WorkerProps, "url"> {
|
|
|
123
128
|
/**
|
|
124
129
|
* The bindings that were created
|
|
125
130
|
*/
|
|
126
|
-
bindings:
|
|
131
|
+
bindings: B;
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
export function isWorker(resource: any): resource is Resolved<Worker> {
|
|
130
135
|
return resource && typeof resource === "object" && resource.type === "worker";
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
export class Worker extends Resource(
|
|
138
|
+
export class Worker<B extends Bindings = Bindings> extends Resource(
|
|
134
139
|
"cloudflare::Worker",
|
|
135
140
|
{
|
|
136
141
|
alwaysUpdate: true,
|
|
137
142
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
applyWorker,
|
|
144
|
+
) {
|
|
145
|
+
public declare readonly Env: {
|
|
146
|
+
[bindingName in keyof B]: Bound<B[bindingName]>;
|
|
147
|
+
};
|
|
148
|
+
constructor(id: string, input: Input<WorkerProps<B>>, ...inputs: any[]) {
|
|
149
|
+
super(id, input as any, ...inputs);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
141
152
|
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
async function applyWorker(ctx: Context<WorkerOutput>, props: WorkerProps) {
|
|
154
|
+
// Create Cloudflare API client with automatic account discovery
|
|
155
|
+
const api = await createCloudflareApi();
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
throw new Error("One of script or entryPoint must be provided");
|
|
148
|
-
}
|
|
157
|
+
// Use the provided name
|
|
158
|
+
const workerName = props.name;
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
160
|
+
// Validate input - we need either script, entryPoint, or bundle
|
|
161
|
+
if (!props.script && !props.entrypoint) {
|
|
162
|
+
throw new Error("One of script or entryPoint must be provided");
|
|
163
|
+
}
|
|
155
164
|
|
|
156
|
-
|
|
165
|
+
if (ctx.event === "delete") {
|
|
166
|
+
return deleteWorker(ctx, api, workerName);
|
|
167
|
+
} else if (ctx.event === "create") {
|
|
168
|
+
await assertWorkerDoesNotExist(ctx, api, workerName);
|
|
169
|
+
}
|
|
157
170
|
|
|
158
|
-
|
|
171
|
+
const oldBindings = await ctx.get<Bindings>("bindings");
|
|
159
172
|
|
|
160
|
-
|
|
161
|
-
const scriptContent =
|
|
162
|
-
props.script ?? (await bundleWorkerScript(ctx, props));
|
|
173
|
+
const scriptMetadata = await prepareWorkerMetadata(ctx, oldBindings, props);
|
|
163
174
|
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
// Get the script content - either from props.script, or by bundling
|
|
176
|
+
const scriptContent = props.script ?? (await bundleWorkerScript(ctx, props));
|
|
166
177
|
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
// Upload the worker script
|
|
179
|
+
await putWorker(ctx, api, workerName, scriptContent, scriptMetadata);
|
|
169
180
|
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
182
|
+
await ctx.set("bindings", props.bindings);
|
|
172
183
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
ctx,
|
|
176
|
-
api,
|
|
177
|
-
workerName,
|
|
178
|
-
props.url ?? false,
|
|
179
|
-
);
|
|
184
|
+
// Handle routes if requested
|
|
185
|
+
await setupRoutes(api, workerName, props.routes || []);
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
187
|
+
// Handle worker URL if requested
|
|
188
|
+
const workerUrl = await configureURL(
|
|
189
|
+
ctx,
|
|
190
|
+
api,
|
|
191
|
+
workerName,
|
|
192
|
+
props.url ?? false,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Get current timestamp
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
|
|
198
|
+
// Construct the output
|
|
199
|
+
const output: WorkerOutput = {
|
|
200
|
+
type: "service",
|
|
201
|
+
id: workerName,
|
|
202
|
+
name: workerName,
|
|
203
|
+
script: scriptContent,
|
|
204
|
+
format: props.format || "esm", // Include format in the output
|
|
205
|
+
routes: props.routes || [],
|
|
206
|
+
bindings: props.bindings ?? {},
|
|
207
|
+
env: props.env,
|
|
208
|
+
observability: scriptMetadata.observability,
|
|
209
|
+
createdAt: now,
|
|
210
|
+
updatedAt: now,
|
|
211
|
+
url: workerUrl,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return output;
|
|
215
|
+
}
|
|
203
216
|
|
|
204
217
|
async function deleteWorker(
|
|
205
218
|
ctx: Context<WorkerOutput>,
|
|
@@ -438,6 +451,18 @@ async function prepareWorkerMetadata(
|
|
|
438
451
|
to: className,
|
|
439
452
|
});
|
|
440
453
|
}
|
|
454
|
+
} else if (isBucket(binding)) {
|
|
455
|
+
meta.bindings.push({
|
|
456
|
+
type: "r2_bucket",
|
|
457
|
+
name: bindingName,
|
|
458
|
+
bucket_name: binding.name || binding.id,
|
|
459
|
+
});
|
|
460
|
+
} else if (binding instanceof Secret) {
|
|
461
|
+
meta.bindings.push({
|
|
462
|
+
type: "secret_text",
|
|
463
|
+
name: bindingName,
|
|
464
|
+
text: binding.unencrypted,
|
|
465
|
+
});
|
|
441
466
|
} else {
|
|
442
467
|
// @ts-expect-error - we should never reach here
|
|
443
468
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
package/src/encrypt.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import sodium from "libsodium-wrappers";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encrypt a value with a symmetric key using libsodium
|
|
5
|
+
*
|
|
6
|
+
* @param value - The value to encrypt
|
|
7
|
+
* @param key - The encryption key
|
|
8
|
+
* @returns The base64-encoded encrypted value with nonce
|
|
9
|
+
*/
|
|
10
|
+
export async function encryptWithKey(
|
|
11
|
+
value: string,
|
|
12
|
+
key: string,
|
|
13
|
+
): Promise<string> {
|
|
14
|
+
// Initialize libsodium
|
|
15
|
+
await sodium.ready;
|
|
16
|
+
|
|
17
|
+
// Derive a key from the passphrase
|
|
18
|
+
const cryptoKey = sodium.crypto_generichash(
|
|
19
|
+
sodium.crypto_secretbox_KEYBYTES,
|
|
20
|
+
sodium.from_string(key),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Generate a random nonce
|
|
24
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
|
|
25
|
+
|
|
26
|
+
// Encrypt the message
|
|
27
|
+
const encryptedBin = sodium.crypto_secretbox_easy(
|
|
28
|
+
sodium.from_string(value),
|
|
29
|
+
nonce,
|
|
30
|
+
cryptoKey,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Combine nonce and ciphertext, then encode to base64
|
|
34
|
+
const combined = new Uint8Array(nonce.length + encryptedBin.length);
|
|
35
|
+
combined.set(nonce);
|
|
36
|
+
combined.set(encryptedBin, nonce.length);
|
|
37
|
+
|
|
38
|
+
return sodium.to_base64(combined, sodium.base64_variants.ORIGINAL);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Decrypt a value encrypted with a symmetric key
|
|
43
|
+
*
|
|
44
|
+
* @param encryptedValue - The base64-encoded encrypted value with nonce
|
|
45
|
+
* @param key - The decryption key
|
|
46
|
+
* @returns The decrypted string
|
|
47
|
+
*/
|
|
48
|
+
export async function decryptWithKey(
|
|
49
|
+
encryptedValue: string,
|
|
50
|
+
key: string,
|
|
51
|
+
): Promise<string> {
|
|
52
|
+
// Initialize libsodium
|
|
53
|
+
await sodium.ready;
|
|
54
|
+
|
|
55
|
+
// Derive a key from the passphrase
|
|
56
|
+
const cryptoKey = sodium.crypto_generichash(
|
|
57
|
+
sodium.crypto_secretbox_KEYBYTES,
|
|
58
|
+
sodium.from_string(key),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Decode the base64 combined value
|
|
62
|
+
const combined = sodium.from_base64(
|
|
63
|
+
encryptedValue,
|
|
64
|
+
sodium.base64_variants.ORIGINAL,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Extract nonce and ciphertext
|
|
68
|
+
const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES);
|
|
69
|
+
const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES);
|
|
70
|
+
|
|
71
|
+
// Decrypt the message
|
|
72
|
+
const decryptedBin = sodium.crypto_secretbox_open_easy(
|
|
73
|
+
ciphertext,
|
|
74
|
+
nonce,
|
|
75
|
+
cryptoKey,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return sodium.to_string(decryptedBin);
|
|
79
|
+
}
|
package/src/github/secret.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sodium from "libsodium-wrappers";
|
|
2
2
|
import { type Context, Resource } from "../resource";
|
|
3
|
+
import type { Secret } from "../secret";
|
|
3
4
|
import { createGitHubClient, verifyGitHubAuth } from "./client";
|
|
4
|
-
|
|
5
5
|
/**
|
|
6
6
|
* Properties for creating or updating a GitHub Secret
|
|
7
7
|
*/
|
|
@@ -24,7 +24,7 @@ export interface GitHubSecretProps {
|
|
|
24
24
|
/**
|
|
25
25
|
* Secret value (will be stored securely on GitHub)
|
|
26
26
|
*/
|
|
27
|
-
value:
|
|
27
|
+
value: Secret;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Optional GitHub API token (overrides environment variable)
|
|
@@ -47,31 +47,6 @@ export interface GitHubSecretOutput extends Omit<GitHubSecretProps, "value"> {
|
|
|
47
47
|
updatedAt: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Encrypt a value for GitHub using libsodium
|
|
52
|
-
* Based on GitHub's documentation for encrypting secrets
|
|
53
|
-
*
|
|
54
|
-
* @param value - The secret value to encrypt
|
|
55
|
-
* @param publicKey - The base64-encoded public key from GitHub
|
|
56
|
-
* @returns The base64-encoded encrypted value
|
|
57
|
-
*/
|
|
58
|
-
async function encryptValue(value: string, publicKey: string): Promise<string> {
|
|
59
|
-
// Initialize libsodium
|
|
60
|
-
await sodium.ready;
|
|
61
|
-
|
|
62
|
-
// Convert the public key from base64 to binary
|
|
63
|
-
const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
|
|
64
|
-
|
|
65
|
-
// Convert the message to a Uint8Array
|
|
66
|
-
const binMessage = sodium.from_string(value);
|
|
67
|
-
|
|
68
|
-
// Encrypt the message with the public key using libsodium's sealed box
|
|
69
|
-
const encryptedBin = sodium.crypto_box_seal(binMessage, binKey);
|
|
70
|
-
|
|
71
|
-
// Convert the encrypted message to base64
|
|
72
|
-
return sodium.to_base64(encryptedBin, sodium.base64_variants.ORIGINAL);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
50
|
/**
|
|
76
51
|
* Resource for managing GitHub repository secrets
|
|
77
52
|
*/
|
|
@@ -121,7 +96,10 @@ export class GitHubSecret extends Resource(
|
|
|
121
96
|
);
|
|
122
97
|
|
|
123
98
|
// Encrypt the secret value using libsodium
|
|
124
|
-
const encryptedValue = await
|
|
99
|
+
const encryptedValue = await encryptString(
|
|
100
|
+
props.value.unencrypted,
|
|
101
|
+
publicKey.key,
|
|
102
|
+
);
|
|
125
103
|
|
|
126
104
|
// Create or update the secret with the encrypted value and key_id
|
|
127
105
|
const response = await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
@@ -132,11 +110,6 @@ export class GitHubSecret extends Resource(
|
|
|
132
110
|
key_id: publicKey.key_id,
|
|
133
111
|
});
|
|
134
112
|
|
|
135
|
-
// Log success
|
|
136
|
-
if (!ctx.quiet) {
|
|
137
|
-
console.log(`Successfully created/updated secret: ${props.name}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
113
|
// GitHub doesn't return the secret details on create/update, so we need to construct it
|
|
141
114
|
const output: GitHubSecretOutput = {
|
|
142
115
|
id: `${props.owner}/${props.repository}/${props.name}`,
|
|
@@ -170,3 +143,31 @@ export class GitHubSecret extends Resource(
|
|
|
170
143
|
}
|
|
171
144
|
},
|
|
172
145
|
) {}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Encrypt a value for GitHub using libsodium
|
|
149
|
+
* Based on GitHub's documentation for encrypting secrets
|
|
150
|
+
*
|
|
151
|
+
* @param value - The secret value to encrypt
|
|
152
|
+
* @param publicKey - The base64-encoded public key from GitHub
|
|
153
|
+
* @returns The base64-encoded encrypted value
|
|
154
|
+
*/
|
|
155
|
+
async function encryptString(
|
|
156
|
+
value: string,
|
|
157
|
+
publicKey: string,
|
|
158
|
+
): Promise<string> {
|
|
159
|
+
// Initialize libsodium
|
|
160
|
+
await sodium.ready;
|
|
161
|
+
|
|
162
|
+
// Convert the public key from base64 to binary
|
|
163
|
+
const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
|
|
164
|
+
|
|
165
|
+
// Convert the message to a Uint8Array
|
|
166
|
+
const binMessage = sodium.from_string(value);
|
|
167
|
+
|
|
168
|
+
// Encrypt the message with the public key using libsodium's sealed box
|
|
169
|
+
const encryptedBin = sodium.crypto_box_seal(binMessage, binKey);
|
|
170
|
+
|
|
171
|
+
// Convert the encrypted message to base64
|
|
172
|
+
return sodium.to_base64(encryptedBin, sodium.base64_variants.ORIGINAL);
|
|
173
|
+
}
|