alchemy 2.0.0-beta.2 → 2.0.0-beta.test-export-fix-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/{alchemy-effect.js → alchemy.js} +173 -173
- package/bin/alchemy.js.map +1 -0
- package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
- package/lib/cli/index.js +169 -169
- package/lib/cli/index.js.map +1 -1
- package/package.json +75 -67
- package/src/AWS/AGENTS.md +10 -10
- package/src/AWS/Assets.ts +1 -1
- package/src/AWS/Credentials.ts +0 -1
- package/src/AWS/DynamoDB/Table.ts +63 -10
- package/src/AWS/EC2/hosted.ts +1 -1
- package/src/AWS/ECS/Task.ts +1 -1
- package/src/AWS/Kinesis/Stream.ts +44 -8
- package/src/AWS/Lambda/Function.ts +218 -10
- package/src/AWS/S3/Bucket.ts +26 -19
- package/src/AWS/S3/BucketNotifications.ts +1 -1
- package/src/AWS/SNS/Topic.ts +47 -10
- package/src/AWS/SQS/Queue.ts +66 -0
- package/src/Cloudflare/Container/Container.ts +122 -0
- package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
- package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
- package/src/Cloudflare/Container/StartContainer.ts +1 -1
- package/src/Cloudflare/D1/D1Database.ts +23 -4
- package/src/Cloudflare/KV/KVNamespace.ts +25 -0
- package/src/Cloudflare/Providers.ts +1 -6
- package/src/Cloudflare/R2/R2Bucket.ts +45 -2
- package/src/Cloudflare/Website/StaticSite.ts +79 -6
- package/src/Cloudflare/Website/Vite.ts +67 -13
- package/src/Cloudflare/Workers/Assets.ts +170 -207
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
- package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
- package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
- package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
- package/src/Cloudflare/Workers/InferEnv.ts +11 -7
- package/src/Cloudflare/Workers/Rpc.ts +13 -2
- package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
- package/src/Cloudflare/Workers/WebSocket.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +406 -49
- package/src/Cloudflare/Workers/Workflow.ts +53 -11
- package/src/Cloudflare/Workers/index.ts +4 -1
- package/src/Construct.ts +2 -2
- package/src/GitHub/Comment.ts +224 -0
- package/src/GitHub/Secret.ts +257 -0
- package/src/GitHub/Variable.ts +166 -0
- package/src/GitHub/index.ts +3 -0
- package/src/Kubernetes/client.ts +2 -2
- package/src/Output.ts +1 -1
- package/src/Platform.ts +1 -1
- package/src/Provider.ts +1 -1
- package/src/Test/Vitest.ts +4 -3
- package/src/Util/PlatformServices.ts +21 -0
- package/src/Util/dedent.ts +59 -0
- package/src/Util/index.ts +1 -0
- package/bin/alchemy-effect.js.map +0 -1
- package/src/Cloudflare/Workers/DurableObject.ts +0 -527
- package/src/Daemon/Client.ts +0 -116
- package/src/Daemon/Config.ts +0 -28
- package/src/Daemon/Errors.ts +0 -48
- package/src/Daemon/Lock.ts +0 -162
- package/src/Daemon/ProcessRegistry.ts +0 -284
- package/src/Daemon/RpcSchema.ts +0 -43
- package/src/Daemon/RpcServer.ts +0 -231
- package/src/Daemon/index.ts +0 -27
- package/src/Spawn.ts +0 -23
- /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
package/src/Construct.ts
CHANGED
|
@@ -12,8 +12,8 @@ import * as Namespace from "./Namespace.ts";
|
|
|
12
12
|
* @section Creating Constructs
|
|
13
13
|
* @example Simple Reusable Construct
|
|
14
14
|
* ```typescript
|
|
15
|
-
* import * as Construct from "alchemy
|
|
16
|
-
* import { Bucket } from "alchemy
|
|
15
|
+
* import * as Construct from "alchemy/Construct";
|
|
16
|
+
* import { Bucket } from "alchemy/AWS/S3";
|
|
17
17
|
*
|
|
18
18
|
* export const Logs = Construct.fn(function* (
|
|
19
19
|
* id: string,
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Provider from "../Provider.ts";
|
|
4
|
+
import { Resource } from "../Resource.ts";
|
|
5
|
+
import { dedent } from "../Util/dedent.ts";
|
|
6
|
+
|
|
7
|
+
export interface CommentProps {
|
|
8
|
+
/**
|
|
9
|
+
* Repository owner (user or organization).
|
|
10
|
+
*/
|
|
11
|
+
owner: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Repository name.
|
|
15
|
+
*/
|
|
16
|
+
repository: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Issue or Pull Request number to comment on.
|
|
20
|
+
*/
|
|
21
|
+
issueNumber: number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Comment body (supports GitHub Markdown).
|
|
25
|
+
*
|
|
26
|
+
* The body is automatically dedented, so you can use indented template
|
|
27
|
+
* literals without worrying about leading whitespace. Accepts
|
|
28
|
+
* `Output<string>` at the call site via `Output.interpolate` to embed
|
|
29
|
+
* resource attributes that are not yet resolved.
|
|
30
|
+
*/
|
|
31
|
+
body: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether to allow deletion of the comment when the resource is destroyed.
|
|
35
|
+
* By default, comments are never deleted to preserve discussion history.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
allowDelete?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* GitHub API token. If not provided, falls back to
|
|
42
|
+
* `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
|
|
43
|
+
*/
|
|
44
|
+
token?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Comment extends Resource<
|
|
48
|
+
"GitHub.Comment",
|
|
49
|
+
CommentProps,
|
|
50
|
+
{
|
|
51
|
+
/**
|
|
52
|
+
* The numeric ID of the comment in GitHub.
|
|
53
|
+
*/
|
|
54
|
+
commentId: number;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* URL to view the comment in a browser.
|
|
58
|
+
*/
|
|
59
|
+
htmlUrl: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ISO-8601 timestamp of the last update.
|
|
63
|
+
*/
|
|
64
|
+
updatedAt: string;
|
|
65
|
+
}
|
|
66
|
+
> {}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A GitHub issue or pull request comment.
|
|
70
|
+
*
|
|
71
|
+
* `Comment` manages the lifecycle of a single comment on an issue or pull
|
|
72
|
+
* request. Comments are created on the first deploy and updated in place on
|
|
73
|
+
* subsequent deploys when the `body` changes. By default, comments are never
|
|
74
|
+
* deleted to preserve discussion history — set `allowDelete: true` to opt in.
|
|
75
|
+
*
|
|
76
|
+
* Authentication is resolved in order: explicit `token` prop,
|
|
77
|
+
* `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
|
|
78
|
+
* `repo` scope for private repositories or `public_repo` for public ones.
|
|
79
|
+
*
|
|
80
|
+
* @section Creating Comments
|
|
81
|
+
* @example Comment on an Issue
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const comment = yield* GitHub.Comment("issue-comment", {
|
|
84
|
+
* owner: "my-org",
|
|
85
|
+
* repository: "my-repo",
|
|
86
|
+
* issueNumber: 123,
|
|
87
|
+
* body: "This is a comment created by Alchemy!",
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example Comment on a Pull Request
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const prComment = yield* GitHub.Comment("pr-comment", {
|
|
94
|
+
* owner: "my-org",
|
|
95
|
+
* repository: "my-repo",
|
|
96
|
+
* issueNumber: 456,
|
|
97
|
+
* body: "## Deployment Status\n\nSuccessfully deployed to staging!",
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @section Updating Comments
|
|
102
|
+
* Deploy with the same logical ID and a different `body` to update the
|
|
103
|
+
* existing comment in place rather than creating a new one.
|
|
104
|
+
*
|
|
105
|
+
* @example Update Comment Content
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const comment = yield* GitHub.Comment("status-comment", {
|
|
108
|
+
* owner: "my-org",
|
|
109
|
+
* repository: "my-repo",
|
|
110
|
+
* issueNumber: 789,
|
|
111
|
+
* body: "Deployment completed successfully!",
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @section Deleting Comments
|
|
116
|
+
* @example Allow Comment Deletion
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const comment = yield* GitHub.Comment("temp-comment", {
|
|
119
|
+
* owner: "my-org",
|
|
120
|
+
* repository: "my-repo",
|
|
121
|
+
* issueNumber: 123,
|
|
122
|
+
* body: "This comment can be deleted",
|
|
123
|
+
* allowDelete: true,
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @section CI Preview Comments
|
|
128
|
+
* A common pattern is posting a preview-deployment URL on every pull request.
|
|
129
|
+
* The comment auto-updates on each push because the logical ID stays the same.
|
|
130
|
+
*
|
|
131
|
+
* @example PR Preview Comment
|
|
132
|
+
* ```typescript
|
|
133
|
+
* if (process.env.PULL_REQUEST) {
|
|
134
|
+
* yield* GitHub.Comment("preview-comment", {
|
|
135
|
+
* owner: "my-org",
|
|
136
|
+
* repository: "my-repo",
|
|
137
|
+
* issueNumber: Number(process.env.PULL_REQUEST),
|
|
138
|
+
* body: Output.interpolate`
|
|
139
|
+
* ## Preview Deployed
|
|
140
|
+
*
|
|
141
|
+
* **URL:** ${website.url}
|
|
142
|
+
* `,
|
|
143
|
+
* });
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export const Comment = Resource<Comment>("GitHub.Comment");
|
|
148
|
+
|
|
149
|
+
function resolveToken(props: CommentProps): string | undefined {
|
|
150
|
+
return (
|
|
151
|
+
props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createClient(props: CommentProps): Octokit {
|
|
156
|
+
return new Octokit({ auth: resolveToken(props) });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const CommentProvider = () =>
|
|
160
|
+
Provider.succeed(Comment, {
|
|
161
|
+
stables: ["commentId"],
|
|
162
|
+
|
|
163
|
+
create: Effect.fn(function* ({ news }) {
|
|
164
|
+
const octokit = createClient(news);
|
|
165
|
+
const body = dedent(news.body);
|
|
166
|
+
|
|
167
|
+
const { data } = yield* Effect.tryPromise(() =>
|
|
168
|
+
octokit.rest.issues.createComment({
|
|
169
|
+
owner: news.owner,
|
|
170
|
+
repo: news.repository,
|
|
171
|
+
issue_number: news.issueNumber,
|
|
172
|
+
body,
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
commentId: data.id,
|
|
178
|
+
htmlUrl: data.html_url,
|
|
179
|
+
updatedAt: data.updated_at,
|
|
180
|
+
};
|
|
181
|
+
}),
|
|
182
|
+
|
|
183
|
+
update: Effect.fn(function* ({ news, output }) {
|
|
184
|
+
const octokit = createClient(news);
|
|
185
|
+
const body = dedent(news.body);
|
|
186
|
+
|
|
187
|
+
const { data } = yield* Effect.tryPromise(() =>
|
|
188
|
+
octokit.rest.issues.updateComment({
|
|
189
|
+
owner: news.owner,
|
|
190
|
+
repo: news.repository,
|
|
191
|
+
comment_id: output.commentId,
|
|
192
|
+
body,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
commentId: data.id,
|
|
198
|
+
htmlUrl: data.html_url,
|
|
199
|
+
updatedAt: data.updated_at,
|
|
200
|
+
};
|
|
201
|
+
}),
|
|
202
|
+
|
|
203
|
+
delete: Effect.fn(function* ({ olds, output }) {
|
|
204
|
+
if (!olds.allowDelete) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const octokit = createClient(olds);
|
|
209
|
+
|
|
210
|
+
yield* Effect.tryPromise(async () => {
|
|
211
|
+
try {
|
|
212
|
+
await octokit.rest.issues.deleteComment({
|
|
213
|
+
owner: olds.owner,
|
|
214
|
+
repo: olds.repository,
|
|
215
|
+
comment_id: output.commentId,
|
|
216
|
+
});
|
|
217
|
+
} catch (error: any) {
|
|
218
|
+
if (error.status !== 404) {
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Redacted from "effect/Redacted";
|
|
4
|
+
import * as Provider from "../Provider.ts";
|
|
5
|
+
import { Resource } from "../Resource.ts";
|
|
6
|
+
|
|
7
|
+
export interface SecretProps {
|
|
8
|
+
/**
|
|
9
|
+
* Repository owner (user or organization).
|
|
10
|
+
*/
|
|
11
|
+
owner: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Repository name.
|
|
15
|
+
*/
|
|
16
|
+
repository: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Secret name (e.g. `AWS_ROLE_ARN`).
|
|
20
|
+
*/
|
|
21
|
+
name: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Secret value. Wrap with `Redacted.make` to prevent the value from
|
|
25
|
+
* appearing in logs or state.
|
|
26
|
+
*/
|
|
27
|
+
value: Redacted.Redacted;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Optional environment name. When set the secret is scoped to that
|
|
31
|
+
* GitHub Actions environment instead of the whole repository.
|
|
32
|
+
*/
|
|
33
|
+
environment?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* GitHub API token. If not provided, falls back to
|
|
37
|
+
* `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
|
|
38
|
+
*/
|
|
39
|
+
token?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Secret extends Resource<
|
|
43
|
+
"GitHub.Secret",
|
|
44
|
+
SecretProps,
|
|
45
|
+
{
|
|
46
|
+
/**
|
|
47
|
+
* ISO-8601 timestamp of the last update.
|
|
48
|
+
*/
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
}
|
|
51
|
+
> {}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A GitHub Actions repository or environment secret.
|
|
55
|
+
*
|
|
56
|
+
* `Secret` manages the lifecycle of an encrypted secret in GitHub Actions.
|
|
57
|
+
* Secrets are encrypted using the repository's (or environment's) public
|
|
58
|
+
* key via `libsodium` before being stored. The resource is idempotent —
|
|
59
|
+
* calling it with the same name will update the secret value in place.
|
|
60
|
+
*
|
|
61
|
+
* Authentication is resolved in order: explicit `token` prop,
|
|
62
|
+
* `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
|
|
63
|
+
* `repo` scope for private repositories or `public_repo` for public ones.
|
|
64
|
+
*
|
|
65
|
+
* @section Repository Secrets
|
|
66
|
+
* Store secrets accessible to all GitHub Actions workflows in the
|
|
67
|
+
* repository.
|
|
68
|
+
*
|
|
69
|
+
* @example Create a Repository Secret
|
|
70
|
+
* ```typescript
|
|
71
|
+
* yield* GitHub.Secret("aws-role", {
|
|
72
|
+
* owner: "my-org",
|
|
73
|
+
* repository: "my-repo",
|
|
74
|
+
* name: "AWS_ROLE_ARN",
|
|
75
|
+
* value: Redacted.make(role.roleArn),
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @section Environment Secrets
|
|
80
|
+
* Scope a secret to a specific GitHub Actions environment (e.g.
|
|
81
|
+
* `production`, `staging`). Environment secrets require environment
|
|
82
|
+
* protection rules to be satisfied before workflows can access them.
|
|
83
|
+
*
|
|
84
|
+
* @example Create an Environment Secret
|
|
85
|
+
* ```typescript
|
|
86
|
+
* yield* GitHub.Secret("deploy-key", {
|
|
87
|
+
* owner: "my-org",
|
|
88
|
+
* repository: "my-repo",
|
|
89
|
+
* environment: "production",
|
|
90
|
+
* name: "DEPLOY_KEY",
|
|
91
|
+
* value: Redacted.make("my-secret-value"),
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @section Wiring with Other Resources
|
|
96
|
+
* A common pattern is wiring the output of another resource — like an
|
|
97
|
+
* IAM role ARN or a database URL — directly into a GitHub secret so
|
|
98
|
+
* that CI workflows can use it.
|
|
99
|
+
*
|
|
100
|
+
* @example Store an IAM Role ARN for CI
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const role = yield* AWS.IAM.Role("ci-role", { ... });
|
|
103
|
+
*
|
|
104
|
+
* yield* GitHub.Secret("ci-role-arn", {
|
|
105
|
+
* owner: "my-org",
|
|
106
|
+
* repository: "my-repo",
|
|
107
|
+
* name: "AWS_ROLE_ARN",
|
|
108
|
+
* value: Redacted.make(role.roleArn),
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @example Store Multiple Secrets
|
|
113
|
+
* ```typescript
|
|
114
|
+
* yield* GitHub.Secret("db-url", {
|
|
115
|
+
* owner: "my-org",
|
|
116
|
+
* repository: "my-repo",
|
|
117
|
+
* environment: "production",
|
|
118
|
+
* name: "DATABASE_URL",
|
|
119
|
+
* value: Redacted.make(database.connectionString),
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* yield* GitHub.Secret("api-key", {
|
|
123
|
+
* owner: "my-org",
|
|
124
|
+
* repository: "my-repo",
|
|
125
|
+
* environment: "production",
|
|
126
|
+
* name: "API_KEY",
|
|
127
|
+
* value: Redacted.make(apiKey),
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export const Secret = Resource<Secret>("GitHub.Secret");
|
|
132
|
+
|
|
133
|
+
function resolveToken(props: SecretProps): string | undefined {
|
|
134
|
+
return (
|
|
135
|
+
props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createClient(props: SecretProps): Octokit {
|
|
140
|
+
return new Octokit({ auth: resolveToken(props) });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function encryptValue(
|
|
144
|
+
plaintext: string,
|
|
145
|
+
publicKey: string,
|
|
146
|
+
): Promise<string> {
|
|
147
|
+
const sodium = await import("libsodium-wrappers");
|
|
148
|
+
await sodium.ready;
|
|
149
|
+
const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
|
|
150
|
+
const binMessage = sodium.from_string(plaintext);
|
|
151
|
+
const encrypted = sodium.crypto_box_seal(binMessage, binKey);
|
|
152
|
+
return sodium.to_base64(encrypted, sodium.base64_variants.ORIGINAL);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const SecretProvider = () =>
|
|
156
|
+
Provider.succeed(Secret, {
|
|
157
|
+
create: Effect.fn(function* ({ news }) {
|
|
158
|
+
const octokit = createClient(news);
|
|
159
|
+
yield* upsertSecret(octokit, news);
|
|
160
|
+
return { updatedAt: new Date().toISOString() };
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
update: Effect.fn(function* ({ news, olds }) {
|
|
164
|
+
const octokit = createClient(news);
|
|
165
|
+
|
|
166
|
+
const wasEnv = !!olds.environment;
|
|
167
|
+
const isEnv = !!news.environment;
|
|
168
|
+
if (wasEnv !== isEnv || olds.environment !== news.environment) {
|
|
169
|
+
yield* deleteSecret(octokit, olds);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
yield* upsertSecret(octokit, news);
|
|
173
|
+
return { updatedAt: new Date().toISOString() };
|
|
174
|
+
}),
|
|
175
|
+
|
|
176
|
+
delete: Effect.fn(function* ({ olds }) {
|
|
177
|
+
const octokit = createClient(olds);
|
|
178
|
+
yield* deleteSecret(octokit, olds);
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const upsertSecret = Effect.fn(function* (
|
|
183
|
+
octokit: Octokit,
|
|
184
|
+
props: SecretProps,
|
|
185
|
+
) {
|
|
186
|
+
const plaintext = Redacted.value(props.value);
|
|
187
|
+
const isEnv = !!props.environment;
|
|
188
|
+
|
|
189
|
+
const publicKey = yield* Effect.tryPromise(async () => {
|
|
190
|
+
if (isEnv) {
|
|
191
|
+
const { data } = await octokit.rest.actions.getEnvironmentPublicKey({
|
|
192
|
+
owner: props.owner,
|
|
193
|
+
repo: props.repository,
|
|
194
|
+
environment_name: props.environment!,
|
|
195
|
+
});
|
|
196
|
+
return data;
|
|
197
|
+
}
|
|
198
|
+
const { data } = await octokit.rest.actions.getRepoPublicKey({
|
|
199
|
+
owner: props.owner,
|
|
200
|
+
repo: props.repository,
|
|
201
|
+
});
|
|
202
|
+
return data;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const encrypted = yield* Effect.tryPromise(() =>
|
|
206
|
+
encryptValue(plaintext, publicKey.key),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
yield* Effect.tryPromise(async () => {
|
|
210
|
+
if (isEnv) {
|
|
211
|
+
await octokit.rest.actions.createOrUpdateEnvironmentSecret({
|
|
212
|
+
owner: props.owner,
|
|
213
|
+
repo: props.repository,
|
|
214
|
+
environment_name: props.environment!,
|
|
215
|
+
secret_name: props.name,
|
|
216
|
+
encrypted_value: encrypted,
|
|
217
|
+
key_id: publicKey.key_id,
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
221
|
+
owner: props.owner,
|
|
222
|
+
repo: props.repository,
|
|
223
|
+
secret_name: props.name,
|
|
224
|
+
encrypted_value: encrypted,
|
|
225
|
+
key_id: publicKey.key_id,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const deleteSecret = Effect.fn(function* (
|
|
232
|
+
octokit: Octokit,
|
|
233
|
+
props: SecretProps,
|
|
234
|
+
) {
|
|
235
|
+
yield* Effect.tryPromise(async () => {
|
|
236
|
+
try {
|
|
237
|
+
if (props.environment) {
|
|
238
|
+
await octokit.rest.actions.deleteEnvironmentSecret({
|
|
239
|
+
owner: props.owner,
|
|
240
|
+
repo: props.repository,
|
|
241
|
+
environment_name: props.environment,
|
|
242
|
+
secret_name: props.name,
|
|
243
|
+
});
|
|
244
|
+
} else {
|
|
245
|
+
await octokit.rest.actions.deleteRepoSecret({
|
|
246
|
+
owner: props.owner,
|
|
247
|
+
repo: props.repository,
|
|
248
|
+
secret_name: props.name,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
} catch (error: any) {
|
|
252
|
+
if (error.status !== 404) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Provider from "../Provider.ts";
|
|
4
|
+
import { Resource } from "../Resource.ts";
|
|
5
|
+
|
|
6
|
+
export interface VariableProps {
|
|
7
|
+
/**
|
|
8
|
+
* Repository owner (user or organization).
|
|
9
|
+
*/
|
|
10
|
+
owner: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Repository name.
|
|
14
|
+
*/
|
|
15
|
+
repository: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Variable name (e.g. `AWS_ROLE_ARN`).
|
|
19
|
+
*/
|
|
20
|
+
name: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Variable value.
|
|
24
|
+
*/
|
|
25
|
+
value: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* GitHub API token. If not provided, falls back to
|
|
29
|
+
* `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
|
|
30
|
+
*/
|
|
31
|
+
token?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Variable extends Resource<
|
|
35
|
+
"GitHub.Variable",
|
|
36
|
+
VariableProps,
|
|
37
|
+
{
|
|
38
|
+
/**
|
|
39
|
+
* ISO-8601 timestamp of the last update.
|
|
40
|
+
*/
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
}
|
|
43
|
+
> {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A GitHub Actions repository variable.
|
|
47
|
+
*
|
|
48
|
+
* `Variable` manages the lifecycle of a plain-text configuration variable
|
|
49
|
+
* in GitHub Actions. Variables are visible in workflow logs and are
|
|
50
|
+
* suitable for non-sensitive configuration like region names, environment
|
|
51
|
+
* labels, or feature flags. For sensitive values, use `GitHub.Secret`
|
|
52
|
+
* instead.
|
|
53
|
+
*
|
|
54
|
+
* Authentication is resolved in order: explicit `token` prop,
|
|
55
|
+
* `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
|
|
56
|
+
* `repo` scope for private repositories or `public_repo` for public ones.
|
|
57
|
+
*
|
|
58
|
+
* @section Repository Variables
|
|
59
|
+
* Store variables accessible to all GitHub Actions workflows in the
|
|
60
|
+
* repository.
|
|
61
|
+
*
|
|
62
|
+
* @example Create a Repository Variable
|
|
63
|
+
* ```typescript
|
|
64
|
+
* yield* GitHub.Variable("aws-region", {
|
|
65
|
+
* owner: "my-org",
|
|
66
|
+
* repository: "my-repo",
|
|
67
|
+
* name: "AWS_REGION",
|
|
68
|
+
* value: "us-east-1",
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @section Wiring with Other Resources
|
|
73
|
+
* Pass output attributes from other resources into GitHub variables so
|
|
74
|
+
* that CI workflows can reference them.
|
|
75
|
+
*
|
|
76
|
+
* @example Store a Worker URL for CI
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const worker = yield* Cloudflare.Worker("Api", { ... });
|
|
79
|
+
*
|
|
80
|
+
* yield* GitHub.Variable("api-url", {
|
|
81
|
+
* owner: "my-org",
|
|
82
|
+
* repository: "my-repo",
|
|
83
|
+
* name: "API_URL",
|
|
84
|
+
* value: worker.url!,
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @example Multiple Variables
|
|
89
|
+
* ```typescript
|
|
90
|
+
* yield* GitHub.Variable("region", {
|
|
91
|
+
* owner: "my-org",
|
|
92
|
+
* repository: "my-repo",
|
|
93
|
+
* name: "AWS_REGION",
|
|
94
|
+
* value: "us-east-1",
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* yield* GitHub.Variable("stage", {
|
|
98
|
+
* owner: "my-org",
|
|
99
|
+
* repository: "my-repo",
|
|
100
|
+
* name: "DEPLOY_STAGE",
|
|
101
|
+
* value: "production",
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export const Variable = Resource<Variable>("GitHub.Variable");
|
|
106
|
+
|
|
107
|
+
function resolveToken(props: VariableProps): string | undefined {
|
|
108
|
+
return (
|
|
109
|
+
props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createClient(props: VariableProps): Octokit {
|
|
114
|
+
return new Octokit({ auth: resolveToken(props) });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const VariableProvider = () =>
|
|
118
|
+
Provider.succeed(Variable, {
|
|
119
|
+
create: Effect.fn(function* ({ news }) {
|
|
120
|
+
const octokit = createClient(news);
|
|
121
|
+
|
|
122
|
+
yield* Effect.tryPromise(() =>
|
|
123
|
+
octokit.rest.actions.createRepoVariable({
|
|
124
|
+
owner: news.owner,
|
|
125
|
+
repo: news.repository,
|
|
126
|
+
name: news.name,
|
|
127
|
+
value: news.value,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return { updatedAt: new Date().toISOString() };
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
update: Effect.fn(function* ({ news }) {
|
|
135
|
+
const octokit = createClient(news);
|
|
136
|
+
|
|
137
|
+
yield* Effect.tryPromise(() =>
|
|
138
|
+
octokit.rest.actions.updateRepoVariable({
|
|
139
|
+
owner: news.owner,
|
|
140
|
+
repo: news.repository,
|
|
141
|
+
name: news.name,
|
|
142
|
+
value: news.value,
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return { updatedAt: new Date().toISOString() };
|
|
147
|
+
}),
|
|
148
|
+
|
|
149
|
+
delete: Effect.fn(function* ({ olds }) {
|
|
150
|
+
const octokit = createClient(olds);
|
|
151
|
+
|
|
152
|
+
yield* Effect.tryPromise(async () => {
|
|
153
|
+
try {
|
|
154
|
+
await octokit.rest.actions.deleteRepoVariable({
|
|
155
|
+
owner: olds.owner,
|
|
156
|
+
repo: olds.repository,
|
|
157
|
+
name: olds.name,
|
|
158
|
+
});
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
if (error.status !== 404) {
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}),
|
|
166
|
+
});
|
package/src/Kubernetes/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AwsClient } from "aws4fetch";
|
|
2
1
|
import { Credentials } from "@distilled.cloud/aws/Credentials";
|
|
3
2
|
import { Region } from "@distilled.cloud/aws/Region";
|
|
3
|
+
import { AwsClient } from "aws4fetch";
|
|
4
4
|
import * as Data from "effect/Data";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
6
6
|
import * as Redacted from "effect/Redacted";
|
|
@@ -28,7 +28,7 @@ export interface KubernetesClusterConnection {
|
|
|
28
28
|
certificateAuthorityData: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const fieldManager = "alchemy
|
|
31
|
+
const fieldManager = "alchemy";
|
|
32
32
|
|
|
33
33
|
const createBearerToken = Effect.fn(function* (clusterName: string) {
|
|
34
34
|
const credentials = yield* yield* Credentials;
|
package/src/Output.ts
CHANGED
|
@@ -90,7 +90,7 @@ export abstract class BaseExpr<A = any, Req = any> implements Output<A, Req> {
|
|
|
90
90
|
declare readonly A: A;
|
|
91
91
|
declare readonly src: ResourceLike;
|
|
92
92
|
declare readonly req: Req;
|
|
93
|
-
// we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy
|
|
93
|
+
// we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy module imports
|
|
94
94
|
constructor() {}
|
|
95
95
|
as<T>(): Output<T, Req> {
|
|
96
96
|
return this as any;
|
package/src/Platform.ts
CHANGED
package/src/Provider.ts
CHANGED
|
@@ -369,7 +369,7 @@ export const tryFindProviderByType: {
|
|
|
369
369
|
const Tag = Provider<R>(resourceType) as Context.Service<Provider<R>, any>;
|
|
370
370
|
const direct = yield* Effect.serviceOption(Tag);
|
|
371
371
|
if (Option.isSome(direct)) {
|
|
372
|
-
return direct
|
|
372
|
+
return direct;
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
const context = yield* Effect.context<never>();
|