alchemy 2.0.0-beta.15 → 2.0.0-beta.17
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/bin/alchemy.js +1661 -710
- package/bin/alchemy.js.map +1 -1
- package/bin/alchemy.ts +2 -0
- package/bin/cli.js +9 -7
- package/bin/commands/deploy.ts +11 -2
- package/bin/commands/login.ts +6 -3
- package/bin/commands/profile.ts +106 -0
- package/bin/commands/state.ts +91 -1
- package/lib/Auth/Credentials.d.ts +6 -0
- package/lib/Auth/Credentials.d.ts.map +1 -1
- package/lib/Auth/Credentials.js +12 -1
- package/lib/Auth/Credentials.js.map +1 -1
- package/lib/Auth/Profile.d.ts +5 -0
- package/lib/Auth/Profile.d.ts.map +1 -1
- package/lib/Auth/Profile.js +11 -0
- package/lib/Auth/Profile.js.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +4 -4
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.js +16 -11
- package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
- package/lib/Cloudflare/ApiToken/Common.d.ts +11 -12
- package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/Common.js +4 -14
- package/lib/Cloudflare/ApiToken/Common.js.map +1 -1
- package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +2 -3
- package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/UserApiToken.js +4 -4
- package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -1
- package/lib/Cloudflare/Artifacts/Artifacts.d.ts +101 -0
- package/lib/Cloudflare/Artifacts/Artifacts.d.ts.map +1 -0
- package/lib/Cloudflare/Artifacts/Artifacts.js +92 -0
- package/lib/Cloudflare/Artifacts/Artifacts.js.map +1 -0
- package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts +83 -0
- package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Artifacts/ArtifactsBinding.js +65 -0
- package/lib/Cloudflare/Artifacts/ArtifactsBinding.js.map +1 -0
- package/lib/Cloudflare/Artifacts/index.d.ts +3 -0
- package/lib/Cloudflare/Artifacts/index.d.ts.map +1 -0
- package/lib/Cloudflare/Artifacts/index.js +3 -0
- package/lib/Cloudflare/Artifacts/index.js.map +1 -0
- package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +3 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.js +0 -1
- package/lib/Cloudflare/Workers/InferEnv.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +25 -18
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +1 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +1 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/GitHub/AuthProvider.d.ts +37 -0
- package/lib/GitHub/AuthProvider.d.ts.map +1 -0
- package/lib/GitHub/AuthProvider.js +154 -0
- package/lib/GitHub/AuthProvider.js.map +1 -0
- package/lib/GitHub/Comment.d.ts +2 -1
- package/lib/GitHub/Comment.d.ts.map +1 -1
- package/lib/GitHub/Comment.js +1 -0
- package/lib/GitHub/Comment.js.map +1 -1
- package/lib/GitHub/Credentials.d.ts +29 -0
- package/lib/GitHub/Credentials.d.ts.map +1 -0
- package/lib/GitHub/Credentials.js +54 -0
- package/lib/GitHub/Credentials.js.map +1 -0
- package/lib/GitHub/Providers.d.ts +12 -1
- package/lib/GitHub/Providers.d.ts.map +1 -1
- package/lib/GitHub/Providers.js +14 -3
- package/lib/GitHub/Providers.js.map +1 -1
- package/lib/GitHub/Secret.d.ts +8 -10
- package/lib/GitHub/Secret.d.ts.map +1 -1
- package/lib/GitHub/Secret.js +16 -14
- package/lib/GitHub/Secret.js.map +1 -1
- package/lib/GitHub/Variable.d.ts +8 -10
- package/lib/GitHub/Variable.d.ts.map +1 -1
- package/lib/GitHub/Variable.js +12 -13
- package/lib/GitHub/Variable.js.map +1 -1
- package/lib/GitHub/index.d.ts +2 -0
- package/lib/GitHub/index.d.ts.map +1 -1
- package/lib/GitHub/index.js +2 -0
- package/lib/GitHub/index.js.map +1 -1
- package/package.json +4 -4
- package/src/Auth/Credentials.ts +17 -1
- package/src/Auth/Profile.ts +15 -0
- package/src/Cloudflare/ApiToken/AccountApiToken.ts +25 -14
- package/src/Cloudflare/ApiToken/Common.ts +12 -19
- package/src/Cloudflare/ApiToken/UserApiToken.ts +6 -8
- package/src/Cloudflare/Artifacts/Artifacts.ts +139 -0
- package/src/Cloudflare/Artifacts/ArtifactsBinding.ts +188 -0
- package/src/Cloudflare/Artifacts/index.ts +2 -0
- package/src/Cloudflare/Providers.ts +3 -0
- package/src/Cloudflare/Workers/InferEnv.ts +5 -4
- package/src/Cloudflare/Workers/Worker.ts +12 -1
- package/src/Cloudflare/index.ts +1 -0
- package/src/GitHub/AuthProvider.ts +309 -0
- package/src/GitHub/Comment.ts +4 -1
- package/src/GitHub/Credentials.ts +99 -0
- package/src/GitHub/Providers.ts +29 -3
- package/src/GitHub/Secret.ts +22 -24
- package/src/GitHub/Variable.ts +16 -23
- package/src/GitHub/index.ts +2 -0
|
@@ -12,14 +12,12 @@ import {
|
|
|
12
12
|
conditionFingerprint,
|
|
13
13
|
policyFingerprint,
|
|
14
14
|
resolvePolicies,
|
|
15
|
-
type
|
|
15
|
+
type ApiTokenProps,
|
|
16
16
|
} from "./Common.ts";
|
|
17
17
|
|
|
18
|
-
export type AccountApiTokenProps = BaseApiTokenProps;
|
|
19
|
-
|
|
20
18
|
export type AccountApiToken = Resource<
|
|
21
19
|
"Cloudflare.AccountApiToken",
|
|
22
|
-
|
|
20
|
+
ApiTokenProps,
|
|
23
21
|
{
|
|
24
22
|
tokenId: string;
|
|
25
23
|
name: string;
|
|
@@ -52,6 +50,7 @@ export type AccountApiToken = Resource<
|
|
|
52
50
|
* ```typescript
|
|
53
51
|
* const token = yield* Cloudflare.AccountApiToken("ci-token", {
|
|
54
52
|
* name: "my-ci-token",
|
|
53
|
+
* accountId,
|
|
55
54
|
* policies: [
|
|
56
55
|
* {
|
|
57
56
|
* effect: "allow",
|
|
@@ -59,7 +58,7 @@ export type AccountApiToken = Resource<
|
|
|
59
58
|
* "Workers Scripts Write",
|
|
60
59
|
* "Workers KV Storage Write",
|
|
61
60
|
* ],
|
|
62
|
-
* resources: {
|
|
61
|
+
* resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
|
|
63
62
|
* },
|
|
64
63
|
* ],
|
|
65
64
|
* });
|
|
@@ -87,7 +86,7 @@ export const AccountApiTokenProvider = () =>
|
|
|
87
86
|
Provider.effect(
|
|
88
87
|
AccountApiToken,
|
|
89
88
|
Effect.gen(function* () {
|
|
90
|
-
const { accountId } = yield* CloudflareEnvironment;
|
|
89
|
+
const { accountId: defaultAccountId } = yield* CloudflareEnvironment;
|
|
91
90
|
const createToken = yield* accounts.createToken;
|
|
92
91
|
const updateToken = yield* accounts.updateToken;
|
|
93
92
|
const deleteToken = yield* accounts.deleteToken;
|
|
@@ -100,6 +99,7 @@ export const AccountApiTokenProvider = () =>
|
|
|
100
99
|
status?: "active" | "disabled" | "expired" | null;
|
|
101
100
|
},
|
|
102
101
|
value: Redacted.Redacted<string>,
|
|
102
|
+
accountId: string,
|
|
103
103
|
): AccountApiTokenAttributes => ({
|
|
104
104
|
tokenId: tokenData.id ?? "",
|
|
105
105
|
name: tokenData.name ?? "",
|
|
@@ -112,16 +112,19 @@ export const AccountApiTokenProvider = () =>
|
|
|
112
112
|
stables: ["tokenId", "accountId"],
|
|
113
113
|
diff: Effect.fn(function* ({ id, olds, news, output }) {
|
|
114
114
|
if (!isResolved(news)) return undefined;
|
|
115
|
-
|
|
115
|
+
const newAccountId = news.accountId ?? defaultAccountId;
|
|
116
|
+
const oldAccountId =
|
|
117
|
+
output?.accountId ?? olds?.accountId ?? defaultAccountId;
|
|
118
|
+
if (oldAccountId !== newAccountId) {
|
|
116
119
|
return { action: "replace" } as const;
|
|
117
120
|
}
|
|
118
121
|
const oldName = output?.name ?? (yield* resolveName(id, olds?.name));
|
|
119
122
|
const newName = yield* resolveName(id, news.name);
|
|
120
123
|
const oldPolicyFp = policyFingerprint(
|
|
121
|
-
resolvePolicies(olds?.policies ?? []
|
|
124
|
+
resolvePolicies(olds?.policies ?? []),
|
|
122
125
|
);
|
|
123
126
|
const newPolicyFp = policyFingerprint(
|
|
124
|
-
resolvePolicies(news.policies
|
|
127
|
+
resolvePolicies(news.policies),
|
|
125
128
|
);
|
|
126
129
|
const oldCondFp = conditionFingerprint(olds?.condition);
|
|
127
130
|
const newCondFp = conditionFingerprint(news.condition);
|
|
@@ -136,8 +139,9 @@ export const AccountApiTokenProvider = () =>
|
|
|
136
139
|
}
|
|
137
140
|
}),
|
|
138
141
|
create: Effect.fn(function* ({ id, news }) {
|
|
142
|
+
const accountId = news.accountId ?? defaultAccountId;
|
|
139
143
|
const name = yield* resolveName(id, news.name);
|
|
140
|
-
const policies = resolvePolicies(news.policies
|
|
144
|
+
const policies = resolvePolicies(news.policies);
|
|
141
145
|
const result = yield* createToken({
|
|
142
146
|
accountId,
|
|
143
147
|
name,
|
|
@@ -151,11 +155,16 @@ export const AccountApiTokenProvider = () =>
|
|
|
151
155
|
`Cloudflare did not return a value for token "${name}".`,
|
|
152
156
|
);
|
|
153
157
|
}
|
|
154
|
-
return buildAttributes(
|
|
158
|
+
return buildAttributes(
|
|
159
|
+
result,
|
|
160
|
+
Redacted.make(result.value),
|
|
161
|
+
accountId,
|
|
162
|
+
);
|
|
155
163
|
}),
|
|
156
164
|
update: Effect.fn(function* ({ id, news, output }) {
|
|
165
|
+
const accountId = news.accountId ?? defaultAccountId;
|
|
157
166
|
const name = yield* resolveName(id, news.name);
|
|
158
|
-
const policies = resolvePolicies(news.policies
|
|
167
|
+
const policies = resolvePolicies(news.policies);
|
|
159
168
|
const result = yield* updateToken({
|
|
160
169
|
accountId,
|
|
161
170
|
tokenId: output.tokenId,
|
|
@@ -167,7 +176,7 @@ export const AccountApiTokenProvider = () =>
|
|
|
167
176
|
});
|
|
168
177
|
// Cloudflare doesn't return the value on update; preserve the
|
|
169
178
|
// value captured at creation.
|
|
170
|
-
return buildAttributes(result, output.value);
|
|
179
|
+
return buildAttributes(result, output.value, accountId);
|
|
171
180
|
}),
|
|
172
181
|
delete: Effect.fn(function* ({ output }) {
|
|
173
182
|
yield* deleteToken({
|
|
@@ -187,7 +196,9 @@ export const AccountApiTokenProvider = () =>
|
|
|
187
196
|
accountId: output.accountId,
|
|
188
197
|
tokenId: output.tokenId,
|
|
189
198
|
}).pipe(
|
|
190
|
-
Effect.map((token) =>
|
|
199
|
+
Effect.map((token) =>
|
|
200
|
+
buildAttributes(token, output.value, output.accountId),
|
|
201
|
+
),
|
|
191
202
|
Effect.catchTag("InvalidRoute", () => Effect.succeed(undefined)),
|
|
192
203
|
Effect.catchTag("TokenNotFound", () => Effect.succeed(undefined)),
|
|
193
204
|
);
|
|
@@ -6,14 +6,13 @@ import {
|
|
|
6
6
|
/**
|
|
7
7
|
* Resource keys recognized by Cloudflare API token policies.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Cloudflare requires the account ID to be embedded directly in the resource
|
|
10
|
+
* key (e.g. `com.cloudflare.api.account.<accountId>`); pass the fully-qualified
|
|
11
|
+
* key — no rewriting is performed.
|
|
12
12
|
*
|
|
13
13
|
* @see https://developers.cloudflare.com/fundamentals/api/reference/permissions/
|
|
14
14
|
*/
|
|
15
15
|
export type ApiTokenResourceKey =
|
|
16
|
-
| "com.cloudflare.api.account"
|
|
17
16
|
| `com.cloudflare.api.account.${string}`
|
|
18
17
|
| `com.cloudflare.api.account.zone.${string}`
|
|
19
18
|
| `com.cloudflare.edge.r2.bucket.${string}`
|
|
@@ -41,12 +40,17 @@ export interface ApiTokenCondition {
|
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
export type
|
|
43
|
+
export type ApiTokenProps = {
|
|
45
44
|
/**
|
|
46
45
|
* Token name. Defaults to a generated physical name based on the
|
|
47
46
|
* resource's logical id, app name, and stage.
|
|
48
47
|
*/
|
|
49
48
|
name?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The Cloudflare account ID that owns this token. Defaults to the
|
|
51
|
+
* account ID resolved from the ambient {@link CloudflareEnvironment}.
|
|
52
|
+
*/
|
|
53
|
+
accountId?: string;
|
|
50
54
|
/**
|
|
51
55
|
* Access policies attached to the token. At least one policy is required
|
|
52
56
|
* by Cloudflare.
|
|
@@ -81,35 +85,24 @@ export const resolvePermissionGroup = (ref: ApiTokenPermissionGroupRef) => {
|
|
|
81
85
|
return ref.meta ? { id: ref.id, meta: ref.meta } : { id: ref.id };
|
|
82
86
|
};
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
* Rewrite the bare `"com.cloudflare.api.account"` key to the
|
|
86
|
-
* account-scoped form. Pass `accountId: undefined` to leave keys unchanged
|
|
87
|
-
* (e.g. for user-owned tokens whose policies don't carry an account scope).
|
|
88
|
-
*/
|
|
89
|
-
export const resolveResources = (
|
|
88
|
+
const resolveResources = (
|
|
90
89
|
resources: ApiTokenPolicy["resources"],
|
|
91
|
-
accountId: string | undefined,
|
|
92
90
|
): Record<string, string> => {
|
|
93
91
|
const out: Record<string, string> = {};
|
|
94
92
|
for (const [key, value] of Object.entries(resources)) {
|
|
95
93
|
if (value === undefined) continue;
|
|
96
|
-
|
|
97
|
-
out[`com.cloudflare.api.account.${accountId}`] = value;
|
|
98
|
-
} else {
|
|
99
|
-
out[key] = value;
|
|
100
|
-
}
|
|
94
|
+
out[key] = value;
|
|
101
95
|
}
|
|
102
96
|
return out;
|
|
103
97
|
};
|
|
104
98
|
|
|
105
99
|
export const resolvePolicies = (
|
|
106
100
|
policies: ApiTokenPolicy[],
|
|
107
|
-
accountId: string | undefined,
|
|
108
101
|
): ResolvedPolicy[] =>
|
|
109
102
|
policies.map((policy) => ({
|
|
110
103
|
effect: policy.effect,
|
|
111
104
|
permissionGroups: policy.permissionGroups.map(resolvePermissionGroup),
|
|
112
|
-
resources: resolveResources(policy.resources
|
|
105
|
+
resources: resolveResources(policy.resources),
|
|
113
106
|
}));
|
|
114
107
|
|
|
115
108
|
export const policyFingerprint = (policies: ResolvedPolicy[]): string =>
|
|
@@ -11,14 +11,12 @@ import {
|
|
|
11
11
|
conditionFingerprint,
|
|
12
12
|
policyFingerprint,
|
|
13
13
|
resolvePolicies,
|
|
14
|
-
type
|
|
14
|
+
type ApiTokenProps,
|
|
15
15
|
} from "./Common.ts";
|
|
16
16
|
|
|
17
|
-
export type UserApiTokenProps = BaseApiTokenProps;
|
|
18
|
-
|
|
19
17
|
export type UserApiToken = Resource<
|
|
20
18
|
"Cloudflare.UserApiToken",
|
|
21
|
-
|
|
19
|
+
ApiTokenProps,
|
|
22
20
|
{
|
|
23
21
|
tokenId: string;
|
|
24
22
|
name: string;
|
|
@@ -102,10 +100,10 @@ export const UserApiTokenProvider = () =>
|
|
|
102
100
|
const oldName = output?.name ?? (yield* resolveName(id, olds?.name));
|
|
103
101
|
const newName = yield* resolveName(id, news.name);
|
|
104
102
|
const oldPolicyFp = policyFingerprint(
|
|
105
|
-
resolvePolicies(olds?.policies ?? []
|
|
103
|
+
resolvePolicies(olds?.policies ?? []),
|
|
106
104
|
);
|
|
107
105
|
const newPolicyFp = policyFingerprint(
|
|
108
|
-
resolvePolicies(news.policies
|
|
106
|
+
resolvePolicies(news.policies),
|
|
109
107
|
);
|
|
110
108
|
const oldCondFp = conditionFingerprint(olds?.condition);
|
|
111
109
|
const newCondFp = conditionFingerprint(news.condition);
|
|
@@ -121,7 +119,7 @@ export const UserApiTokenProvider = () =>
|
|
|
121
119
|
}),
|
|
122
120
|
create: Effect.fn(function* ({ id, news }) {
|
|
123
121
|
const name = yield* resolveName(id, news.name);
|
|
124
|
-
const policies = resolvePolicies(news.policies
|
|
122
|
+
const policies = resolvePolicies(news.policies);
|
|
125
123
|
const result = yield* createToken({
|
|
126
124
|
name,
|
|
127
125
|
policies,
|
|
@@ -138,7 +136,7 @@ export const UserApiTokenProvider = () =>
|
|
|
138
136
|
}),
|
|
139
137
|
update: Effect.fn(function* ({ id, news, output }) {
|
|
140
138
|
const name = yield* resolveName(id, news.name);
|
|
141
|
-
const policies = resolvePolicies(news.policies
|
|
139
|
+
const policies = resolvePolicies(news.policies);
|
|
142
140
|
const result = yield* updateToken({
|
|
143
141
|
tokenId: output.tokenId,
|
|
144
142
|
name,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Stack } from "../../Stack.ts";
|
|
3
|
+
import { Stage } from "../../Stage.ts";
|
|
4
|
+
import { ArtifactsBinding } from "./ArtifactsBinding.ts";
|
|
5
|
+
|
|
6
|
+
type ArtifactsTypeId = typeof ArtifactsTypeId;
|
|
7
|
+
const ArtifactsTypeId = "Cloudflare.Artifacts" as const;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare validation: 3–63 chars, lowercase alphanumeric and hyphens, must
|
|
11
|
+
* start and end with a lowercase alphanumeric character.
|
|
12
|
+
*/
|
|
13
|
+
const ARTIFACTS_NAMESPACE_REGEX = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])?$/;
|
|
14
|
+
|
|
15
|
+
export class InvalidArtifactsNamespaceError extends Error {
|
|
16
|
+
readonly _tag = "InvalidArtifactsNamespaceError" as const;
|
|
17
|
+
constructor(public readonly namespace: string) {
|
|
18
|
+
super(
|
|
19
|
+
`Invalid artifacts namespace name '${namespace}'. Must be 3-63 characters, start and end with a lowercase alphanumeric character, and contain only lowercase alphanumeric characters and hyphens.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ArtifactsProps = {
|
|
25
|
+
/**
|
|
26
|
+
* Cloudflare namespace name. Namespaces are implicit on Cloudflare — the
|
|
27
|
+
* first repo created against this name conjures the namespace.
|
|
28
|
+
*
|
|
29
|
+
* Must be 3–63 lowercase alphanumeric characters or hyphens, and must start
|
|
30
|
+
* and end with a lowercase alphanumeric character. If omitted, a unique
|
|
31
|
+
* physical name is generated from the resource's logical id, app, and stage.
|
|
32
|
+
*/
|
|
33
|
+
namespace?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Marker for a Cloudflare Artifacts namespace binding.
|
|
38
|
+
*
|
|
39
|
+
* Artifacts namespaces are implicit (created on first repo write) and require
|
|
40
|
+
* no deploy-time provisioning, so this is a pure binding marker rather than
|
|
41
|
+
* a full Resource. The Worker provider sees this object in `bindings: { ... }`
|
|
42
|
+
* and emits the corresponding `{ type: "artifacts", name, namespace }` binding
|
|
43
|
+
* to the script.
|
|
44
|
+
*/
|
|
45
|
+
export type Artifacts = {
|
|
46
|
+
kind: ArtifactsTypeId;
|
|
47
|
+
name: string;
|
|
48
|
+
namespace: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const isArtifacts = (value: unknown): value is Artifacts =>
|
|
52
|
+
typeof value === "object" &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
"kind" in value &&
|
|
55
|
+
(value as Artifacts).kind === ArtifactsTypeId;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A Cloudflare Artifacts namespace — the top-level container for Git-compatible
|
|
59
|
+
* versioned repositories. See the
|
|
60
|
+
* {@link https://blog.cloudflare.com/artifacts-git-for-agents-beta/ | Artifacts launch post}
|
|
61
|
+
* and {@link https://developers.cloudflare.com/artifacts/concepts/namespaces/ | Namespaces docs}.
|
|
62
|
+
*
|
|
63
|
+
* Namespaces on Cloudflare are **implicit**: there is no `POST /namespaces`
|
|
64
|
+
* endpoint. The namespace is conjured the first time a repo is created against
|
|
65
|
+
* it (either via the REST API or the Worker binding). Because of that, the
|
|
66
|
+
* Alchemy "resource" is a thin binding marker — there is nothing to provision
|
|
67
|
+
* at deploy time. Repos themselves are typically created at runtime through
|
|
68
|
+
* the bound `Artifacts` API.
|
|
69
|
+
*
|
|
70
|
+
* @section Declaring a Namespace
|
|
71
|
+
* @example Default namespace (a unique physical name is generated)
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const Repos = Cloudflare.Artifacts("Repos");
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Override the namespace name (must be lowercase, 3–63 chars)
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const Repos = Cloudflare.Artifacts("Repos", { namespace: "starter-repos" });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @section Binding to a Worker
|
|
82
|
+
* @example Wiring it into a Worker
|
|
83
|
+
* ```typescript
|
|
84
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
85
|
+
* main: "./src/worker.ts",
|
|
86
|
+
* bindings: { Repos },
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
|
|
90
|
+
* // { Repos: Artifacts }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example Async-style worker
|
|
94
|
+
* ```typescript
|
|
95
|
+
* export default {
|
|
96
|
+
* async fetch(request: Request, env: WorkerEnv) {
|
|
97
|
+
* const repo = await env.Repos.create("starter-repo");
|
|
98
|
+
* return Response.json({ remote: repo.remote, token: repo.token });
|
|
99
|
+
* },
|
|
100
|
+
* };
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example Effect-style worker
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const artifacts = yield* Cloudflare.Artifacts.bind(Repos);
|
|
106
|
+
* const repo = yield* artifacts.create("starter-repo", {
|
|
107
|
+
* setDefaultBranch: "main",
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export const Artifacts: {
|
|
112
|
+
(
|
|
113
|
+
name: string,
|
|
114
|
+
props?: ArtifactsProps,
|
|
115
|
+
): Effect.Effect<Artifacts, never, Stack | Stage>;
|
|
116
|
+
/**
|
|
117
|
+
* Bind a Cloudflare Artifacts namespace to the surrounding Worker, returning
|
|
118
|
+
* an Effect-native client. See {@link ArtifactsBinding}.
|
|
119
|
+
*/
|
|
120
|
+
bind: typeof ArtifactsBinding.bind;
|
|
121
|
+
} = Object.assign(
|
|
122
|
+
Effect.fn(function* (name: string, props?: ArtifactsProps) {
|
|
123
|
+
const namespace = props?.namespace
|
|
124
|
+
? props.namespace
|
|
125
|
+
: name.toLocaleLowerCase();
|
|
126
|
+
if (!ARTIFACTS_NAMESPACE_REGEX.test(namespace)) {
|
|
127
|
+
return yield* Effect.die(new InvalidArtifactsNamespaceError(namespace));
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
kind: ArtifactsTypeId,
|
|
131
|
+
name,
|
|
132
|
+
namespace,
|
|
133
|
+
} satisfies Artifacts;
|
|
134
|
+
}),
|
|
135
|
+
{
|
|
136
|
+
bind: (...args: Parameters<typeof ArtifactsBinding.bind>) =>
|
|
137
|
+
ArtifactsBinding.bind(...args),
|
|
138
|
+
},
|
|
139
|
+
);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Binding from "../../Binding.ts";
|
|
5
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
6
|
+
import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
|
|
7
|
+
import { type Artifacts as ArtifactsLike } from "./Artifacts.ts";
|
|
8
|
+
|
|
9
|
+
export class ArtifactsError extends Data.TaggedError("ArtifactsError")<{
|
|
10
|
+
message: string;
|
|
11
|
+
cause: Error;
|
|
12
|
+
}> {}
|
|
13
|
+
|
|
14
|
+
export type Scope = "read" | "write";
|
|
15
|
+
|
|
16
|
+
export type ArtifactsCreateOptions = {
|
|
17
|
+
readOnly?: boolean;
|
|
18
|
+
description?: string;
|
|
19
|
+
setDefaultBranch?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ArtifactsImportOptions = {
|
|
23
|
+
source: { url: string; branch?: string; depth?: number };
|
|
24
|
+
target: {
|
|
25
|
+
name: string;
|
|
26
|
+
opts?: { description?: string; readOnly?: boolean };
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ArtifactsListOptions = {
|
|
31
|
+
limit?: number;
|
|
32
|
+
cursor?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ArtifactsForkOptions = {
|
|
36
|
+
description?: string;
|
|
37
|
+
readOnly?: boolean;
|
|
38
|
+
defaultBranchOnly?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Effect-native handle to a single Artifacts repo. Wraps the runtime
|
|
43
|
+
* {@link ArtifactsRepo} so each method returns an Effect.
|
|
44
|
+
*/
|
|
45
|
+
export interface ArtifactsRepoClient {
|
|
46
|
+
/** Underlying Cloudflare runtime handle. */
|
|
47
|
+
raw: ArtifactsRepo;
|
|
48
|
+
createToken(
|
|
49
|
+
scope?: Scope,
|
|
50
|
+
ttl?: number,
|
|
51
|
+
): Effect.Effect<ArtifactsCreateTokenResult, ArtifactsError>;
|
|
52
|
+
listTokens(): Effect.Effect<ArtifactsTokenListResult, ArtifactsError>;
|
|
53
|
+
revokeToken(tokenOrId: string): Effect.Effect<boolean, ArtifactsError>;
|
|
54
|
+
fork(
|
|
55
|
+
name: string,
|
|
56
|
+
opts?: ArtifactsForkOptions,
|
|
57
|
+
): Effect.Effect<ArtifactsCreateRepoResult, ArtifactsError>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Effect-native client for a Cloudflare Artifacts namespace binding.
|
|
62
|
+
*
|
|
63
|
+
* Wraps the runtime {@link Artifacts} binding so each method returns an
|
|
64
|
+
* Effect tagged with {@link ArtifactsError}. Use
|
|
65
|
+
* `Cloudflare.ArtifactsBinding.bind(Repos)` inside a Worker's init phase.
|
|
66
|
+
*/
|
|
67
|
+
export interface ArtifactsClient {
|
|
68
|
+
/** Effect resolving to the raw Cloudflare runtime binding. */
|
|
69
|
+
raw: Effect.Effect<Artifacts, never, WorkerEnvironment>;
|
|
70
|
+
create(
|
|
71
|
+
name: string,
|
|
72
|
+
opts?: ArtifactsCreateOptions,
|
|
73
|
+
): Effect.Effect<
|
|
74
|
+
ArtifactsCreateRepoResult,
|
|
75
|
+
ArtifactsError,
|
|
76
|
+
WorkerEnvironment
|
|
77
|
+
>;
|
|
78
|
+
/** Look up an existing repo by name. Fails with `ArtifactsError` if missing. */
|
|
79
|
+
get(
|
|
80
|
+
name: string,
|
|
81
|
+
): Effect.Effect<ArtifactsRepoClient, ArtifactsError, WorkerEnvironment>;
|
|
82
|
+
list(
|
|
83
|
+
opts?: ArtifactsListOptions,
|
|
84
|
+
): Effect.Effect<ArtifactsRepoListResult, ArtifactsError, WorkerEnvironment>;
|
|
85
|
+
delete(
|
|
86
|
+
name: string,
|
|
87
|
+
): Effect.Effect<boolean, ArtifactsError, WorkerEnvironment>;
|
|
88
|
+
import(
|
|
89
|
+
opts: ArtifactsImportOptions,
|
|
90
|
+
): Effect.Effect<
|
|
91
|
+
ArtifactsCreateRepoResult,
|
|
92
|
+
ArtifactsError,
|
|
93
|
+
WorkerEnvironment
|
|
94
|
+
>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class ArtifactsBinding extends Binding.Service<
|
|
98
|
+
ArtifactsBinding,
|
|
99
|
+
(artifacts: ArtifactsLike) => Effect.Effect<ArtifactsClient>
|
|
100
|
+
>()("Cloudflare.Artifacts.Binding") {}
|
|
101
|
+
|
|
102
|
+
export const ArtifactsBindingLive = Layer.effect(
|
|
103
|
+
ArtifactsBinding,
|
|
104
|
+
Effect.gen(function* () {
|
|
105
|
+
const Policy = yield* ArtifactsBindingPolicy;
|
|
106
|
+
|
|
107
|
+
return Effect.fn(function* (artifacts: ArtifactsLike) {
|
|
108
|
+
yield* Policy(artifacts);
|
|
109
|
+
const env = WorkerEnvironment.asEffect();
|
|
110
|
+
const raw = env.pipe(
|
|
111
|
+
Effect.map(
|
|
112
|
+
(env) => (env as Record<string, Artifacts>)[artifacts.name]!,
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const use = <T>(
|
|
117
|
+
fn: (raw: Artifacts) => Promise<T>,
|
|
118
|
+
): Effect.Effect<T, ArtifactsError, WorkerEnvironment> =>
|
|
119
|
+
raw.pipe(Effect.flatMap((raw) => tryPromise(() => fn(raw))));
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
raw,
|
|
123
|
+
create: (name, opts) => use((raw) => raw.create(name, opts)),
|
|
124
|
+
get: (name) =>
|
|
125
|
+
use((raw) => raw.get(name)).pipe(
|
|
126
|
+
Effect.flatMap((repo) =>
|
|
127
|
+
repo == null
|
|
128
|
+
? Effect.fail(
|
|
129
|
+
new ArtifactsError({
|
|
130
|
+
message: `Artifacts repo '${name}' not found`,
|
|
131
|
+
cause: new Error("not_found"),
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
: Effect.succeed(wrapRepo(repo as ArtifactsRepo)),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
list: (opts) => use((raw) => raw.list(opts)),
|
|
138
|
+
delete: (name) => use((raw) => raw.delete(name)),
|
|
139
|
+
import: (opts) => use((raw) => raw.import(opts)),
|
|
140
|
+
} satisfies ArtifactsClient;
|
|
141
|
+
});
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
export class ArtifactsBindingPolicy extends Binding.Policy<
|
|
146
|
+
ArtifactsBindingPolicy,
|
|
147
|
+
(artifacts: ArtifactsLike) => Effect.Effect<void>
|
|
148
|
+
>()("Cloudflare.Artifacts.Binding") {}
|
|
149
|
+
|
|
150
|
+
export const ArtifactsBindingPolicyLive = ArtifactsBindingPolicy.layer.succeed(
|
|
151
|
+
Effect.fn(function* (host: ResourceLike, artifacts: ArtifactsLike) {
|
|
152
|
+
if (isWorker(host)) {
|
|
153
|
+
yield* host.bind(artifacts.name, {
|
|
154
|
+
bindings: [
|
|
155
|
+
{
|
|
156
|
+
type: "artifacts",
|
|
157
|
+
name: artifacts.name,
|
|
158
|
+
namespace: artifacts.namespace,
|
|
159
|
+
} as any,
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
return yield* Effect.die(
|
|
164
|
+
new Error(`ArtifactsBinding does not support runtime '${host.Type}'`),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const tryPromise = <T>(
|
|
171
|
+
fn: () => Promise<T>,
|
|
172
|
+
): Effect.Effect<T, ArtifactsError> =>
|
|
173
|
+
Effect.tryPromise({
|
|
174
|
+
try: fn,
|
|
175
|
+
catch: (error: any) =>
|
|
176
|
+
new ArtifactsError({
|
|
177
|
+
message: error?.message ?? "Unknown error",
|
|
178
|
+
cause: error,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const wrapRepo = (raw: ArtifactsRepo): ArtifactsRepoClient => ({
|
|
183
|
+
raw,
|
|
184
|
+
createToken: (scope, ttl) => tryPromise(() => raw.createToken(scope, ttl)),
|
|
185
|
+
listTokens: () => tryPromise(() => raw.listTokens()),
|
|
186
|
+
revokeToken: (tokenOrId) => tryPromise(() => raw.revokeToken(tokenOrId)),
|
|
187
|
+
fork: (name, opts) => tryPromise(() => raw.fork(name, opts)),
|
|
188
|
+
});
|
|
@@ -4,6 +4,7 @@ import * as Build from "../Build/index.ts";
|
|
|
4
4
|
import * as Provider from "../Provider.ts";
|
|
5
5
|
import { Random, RandomProvider } from "../Random.ts";
|
|
6
6
|
import * as ApiToken from "./ApiToken/index.ts";
|
|
7
|
+
import * as Artifacts from "./Artifacts/index.ts";
|
|
7
8
|
import { CloudflareAuth } from "./Auth/AuthProvider.ts";
|
|
8
9
|
import * as CloudflareEnvironment from "./CloudflareEnvironment.ts";
|
|
9
10
|
import * as Containers from "./Container/index.ts";
|
|
@@ -33,6 +34,7 @@ export const providers = () =>
|
|
|
33
34
|
Provider.collection([
|
|
34
35
|
ApiToken.AccountApiToken,
|
|
35
36
|
ApiToken.UserApiToken,
|
|
37
|
+
Artifacts.ArtifactsBindingPolicy,
|
|
36
38
|
Command,
|
|
37
39
|
Containers.Container,
|
|
38
40
|
D1.D1ConnectionPolicy,
|
|
@@ -58,6 +60,7 @@ export const providers = () =>
|
|
|
58
60
|
Layer.mergeAll(
|
|
59
61
|
ApiToken.AccountApiTokenProvider(),
|
|
60
62
|
ApiToken.UserApiTokenProvider(),
|
|
63
|
+
Artifacts.ArtifactsBindingPolicyLive,
|
|
61
64
|
Containers.ContainerProvider(),
|
|
62
65
|
D1.D1ConnectionPolicyLive,
|
|
63
66
|
D1.DatabaseProvider(),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
-
// import type * as cf from "@cloudflare/workers-types";
|
|
3
2
|
|
|
4
3
|
import type * as Effect from "effect/Effect";
|
|
5
4
|
import type { UnwrapEffect } from "../../Util/effect.ts";
|
|
@@ -24,6 +23,8 @@ type GetBindingType<T> = T extends Cloudflare.Assets
|
|
|
24
23
|
? KVNamespace
|
|
25
24
|
: T extends Cloudflare.Queue
|
|
26
25
|
? Queue<unknown>
|
|
27
|
-
: T extends Cloudflare.
|
|
28
|
-
?
|
|
29
|
-
:
|
|
26
|
+
: T extends Cloudflare.Artifacts
|
|
27
|
+
? Artifacts
|
|
28
|
+
: T extends Cloudflare.DurableObjectNamespaceLike
|
|
29
|
+
? DurableObjectNamespace<Exclude<T["Shape"], undefined>>
|
|
30
|
+
: never;
|
|
@@ -24,6 +24,10 @@ import Sonda from "sonda/rolldown";
|
|
|
24
24
|
import type * as vite from "vite";
|
|
25
25
|
import * as Artifacts from "../../Artifacts.ts";
|
|
26
26
|
import * as Binding from "../../Binding.ts";
|
|
27
|
+
import {
|
|
28
|
+
isArtifacts as isArtifactsBinding,
|
|
29
|
+
type Artifacts as ArtifactsBinding,
|
|
30
|
+
} from "../Artifacts/Artifacts.ts";
|
|
27
31
|
import { hashDirectory, type MemoOptions } from "../../Build/Memo.ts";
|
|
28
32
|
import * as Bundle from "../../Bundle/Bundle.ts";
|
|
29
33
|
import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
|
|
@@ -183,6 +187,7 @@ export type WorkerBindingResource =
|
|
|
183
187
|
| D1Database
|
|
184
188
|
| KVNamespace
|
|
185
189
|
| CloudflareQueue
|
|
190
|
+
| ArtifactsBinding
|
|
186
191
|
| DurableObjectNamespaceLike<any>;
|
|
187
192
|
|
|
188
193
|
export type WorkerBindings = {
|
|
@@ -667,7 +672,13 @@ export const Worker: Platform<
|
|
|
667
672
|
type: "assets",
|
|
668
673
|
name: bindingName,
|
|
669
674
|
}
|
|
670
|
-
:
|
|
675
|
+
: isArtifactsBinding(binding)
|
|
676
|
+
? ({
|
|
677
|
+
type: "artifacts",
|
|
678
|
+
name: bindingName,
|
|
679
|
+
namespace: binding.namespace,
|
|
680
|
+
} as any)
|
|
681
|
+
: isDurableObjectNamespaceLike(binding)
|
|
671
682
|
? {
|
|
672
683
|
type: "durable_object_namespace",
|
|
673
684
|
name: bindingName,
|