alchemy 2.0.0-beta.2 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +171 -171
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.d.ts.map +1 -1
  5. package/lib/cli/index.js +167 -167
  6. package/lib/cli/index.js.map +1 -1
  7. package/package.json +54 -45
  8. package/src/AWS/AGENTS.md +10 -10
  9. package/src/AWS/Assets.ts +1 -1
  10. package/src/AWS/AuthProvider.ts +392 -0
  11. package/src/AWS/Credentials.ts +0 -1
  12. package/src/AWS/DynamoDB/Table.ts +63 -10
  13. package/src/AWS/EC2/VpcEndpoint.ts +4 -4
  14. package/src/AWS/EC2/hosted.ts +1 -1
  15. package/src/AWS/ECS/Task.ts +1 -1
  16. package/src/AWS/Kinesis/Stream.ts +44 -8
  17. package/src/AWS/Lambda/Function.ts +218 -10
  18. package/src/AWS/S3/Bucket.ts +26 -19
  19. package/src/AWS/S3/BucketNotifications.ts +1 -1
  20. package/src/AWS/SNS/Topic.ts +47 -10
  21. package/src/AWS/SQS/Queue.ts +66 -0
  22. package/src/Auth/AuthProvider.ts +33 -0
  23. package/src/Auth/Credentials.ts +56 -0
  24. package/src/Auth/Env.ts +45 -0
  25. package/src/Auth/Profile.ts +72 -0
  26. package/src/Auth/index.ts +2 -0
  27. package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
  28. package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
  29. package/src/Cloudflare/Container/Container.ts +122 -0
  30. package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
  31. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  32. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  33. package/src/Cloudflare/D1/D1Database.ts +23 -4
  34. package/src/Cloudflare/KV/KVNamespace.ts +25 -0
  35. package/src/Cloudflare/Providers.ts +1 -6
  36. package/src/Cloudflare/R2/R2Bucket.ts +45 -2
  37. package/src/Cloudflare/Website/StaticSite.ts +101 -15
  38. package/src/Cloudflare/Website/Vite.ts +86 -20
  39. package/src/Cloudflare/Workers/Assets.ts +170 -207
  40. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
  41. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  42. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  43. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
  44. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  45. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  46. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  47. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  48. package/src/Cloudflare/Workers/Worker.ts +572 -67
  49. package/src/Cloudflare/Workers/Workflow.ts +53 -11
  50. package/src/Cloudflare/Workers/index.ts +4 -1
  51. package/src/Construct.ts +2 -2
  52. package/src/GitHub/Comment.ts +224 -0
  53. package/src/GitHub/Secret.ts +257 -0
  54. package/src/GitHub/Variable.ts +166 -0
  55. package/src/GitHub/index.ts +3 -0
  56. package/src/Kubernetes/client.ts +2 -2
  57. package/src/Output.ts +1 -1
  58. package/src/Platform.ts +10 -5
  59. package/src/Provider.ts +1 -1
  60. package/src/Resource.ts +4 -4
  61. package/src/Test/Vitest.ts +4 -3
  62. package/src/Util/Clank.ts +77 -0
  63. package/src/Util/PlatformServices.ts +21 -0
  64. package/src/Util/dedent.ts +59 -0
  65. package/src/Util/index.ts +1 -0
  66. package/bin/alchemy-effect.js.map +0 -1
  67. package/src/Cloudflare/Workers/DurableObject.ts +0 -527
  68. package/src/Daemon/Client.ts +0 -116
  69. package/src/Daemon/Config.ts +0 -28
  70. package/src/Daemon/Errors.ts +0 -48
  71. package/src/Daemon/Lock.ts +0 -162
  72. package/src/Daemon/ProcessRegistry.ts +0 -284
  73. package/src/Daemon/RpcSchema.ts +0 -43
  74. package/src/Daemon/RpcServer.ts +0 -231
  75. package/src/Daemon/index.ts +0 -27
  76. package/src/Spawn.ts +0 -23
  77. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -186,30 +186,72 @@ export class WorkflowScope extends Context.Service<
186
186
  >()("Cloudflare.Workflow") {}
187
187
 
188
188
  /**
189
- * Declare a Cloudflare Workflow inside a Worker program.
189
+ * A Cloudflare Workflow that orchestrates durable, multi-step tasks with
190
+ * automatic retries and at-least-once delivery.
190
191
  *
191
- * The outer Effect resolves infrastructure dependencies (Durable Objects,
192
- * etc.) and returns the workflow body -- an Effect that uses `WorkflowEvent`
193
- * and step primitives (`task`, `sleep`, `sleepUntil`).
192
+ * A Workflow follows the same two-phase pattern as Workers and Durable
193
+ * Objects. The outer `Effect.gen` resolves shared dependencies. The inner
194
+ * `Effect.gen` is the workflow body — it reads the triggering event and
195
+ * runs steps using `task`, `sleep`, and `sleepUntil`.
194
196
  *
195
- * Internally this creates a `WorkflowResource` that manages the Cloudflare
196
- * Workflows API lifecycle (PUT / DELETE), similar to how `bindContainer`
197
- * creates a `ContainerApplication`.
197
+ * ```typescript
198
+ * Effect.gen(function* () {
199
+ * // Phase 1: resolve dependencies
200
+ * const notifier = yield* NotificationService;
201
+ *
202
+ * return Effect.gen(function* () {
203
+ * // Phase 2: workflow body (durable steps)
204
+ * const event = yield* Cloudflare.WorkflowEvent;
205
+ * const result = yield* Cloudflare.task("process", doWork(event.payload));
206
+ * yield* Cloudflare.sleep("cooldown", "10 seconds");
207
+ * return result;
208
+ * });
209
+ * })
210
+ * ```
211
+ *
212
+ * @resource
198
213
  *
199
- * @example
214
+ * @section Defining a Workflow
215
+ * @example Minimal workflow
200
216
  * ```typescript
201
217
  * export default class MyWorkflow extends Cloudflare.Workflow<MyWorkflow>()(
202
218
  * "MyWorkflow",
203
219
  * Effect.gen(function* () {
204
220
  * return Effect.gen(function* () {
205
221
  * const event = yield* Cloudflare.WorkflowEvent;
206
- * const data = yield* Cloudflare.task("fetch-data", Effect.succeed({ ok: true }));
207
- * yield* Cloudflare.sleep("pause", "5 seconds");
208
- * return data;
222
+ * return { received: event.payload };
209
223
  * });
210
224
  * }),
211
225
  * ) {}
212
226
  * ```
227
+ *
228
+ * @section Step Primitives
229
+ * @example Running a named task
230
+ * ```typescript
231
+ * const result = yield* Cloudflare.task(
232
+ * "process-order",
233
+ * Effect.succeed({ orderId: "abc", total: 42 }),
234
+ * );
235
+ * ```
236
+ *
237
+ * @example Sleeping between steps
238
+ * ```typescript
239
+ * yield* Cloudflare.sleep("cooldown", "30 seconds");
240
+ * ```
241
+ *
242
+ * @section Starting and Monitoring Instances
243
+ * @example Creating an instance from a Worker
244
+ * ```typescript
245
+ * const workflow = yield* MyWorkflow;
246
+ * const instance = yield* workflow.create({ orderId: "abc" });
247
+ * ```
248
+ *
249
+ * @example Checking instance status
250
+ * ```typescript
251
+ * const workflow = yield* MyWorkflow;
252
+ * const handle = yield* workflow.get(instanceId);
253
+ * const status = yield* handle.status();
254
+ * ```
213
255
  */
214
256
  export const Workflow: WorkflowClass = taggedFunction(WorkflowScope, ((
215
257
  ...args: [] | [name: string, impl: Effect.Effect<WorkflowBody>]
@@ -1,12 +1,15 @@
1
1
  export * from "./Assets.ts";
2
2
  export * from "./ConfigProvider.ts";
3
- export * from "./DurableObject.ts";
3
+ export * from "./DurableObjectNamespace.ts";
4
+ export * from "./DurableObjectState.ts";
5
+ export * from "./DurableObjectStorage.ts";
4
6
  export * from "./DynamicWorkerLoader.ts";
5
7
  export * from "./Fetch.ts";
6
8
  export * from "./HttpServer.ts";
7
9
  export * from "./InferEnv.ts";
8
10
  export * from "./Request.ts";
9
11
  export * from "./Rpc.ts";
12
+ export * from "./ScheduledEvents.ts";
10
13
  export * from "./WebSocket.ts";
11
14
  export * from "./Worker.ts";
12
15
  export * from "./Workflow.ts";
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-effect/Construct";
16
- * import { Bucket } from "alchemy-effect/AWS/S3";
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
+ });