alchemy 0.9.1 → 0.10.0

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.
@@ -27,6 +27,12 @@ export interface GitHubSecretProps {
27
27
  */
28
28
  value: Secret;
29
29
 
30
+ /**
31
+ * Optional environment name to create an environment secret
32
+ * If set, the secret will be created in the specified environment instead of at the repository level
33
+ */
34
+ environment?: string;
35
+
30
36
  /**
31
37
  * Optional GitHub API token (overrides environment variable)
32
38
  * If not provided, will use GITHUB_TOKEN environment variable
@@ -54,7 +60,7 @@ export interface GitHubSecretOutput
54
60
  }
55
61
 
56
62
  /**
57
- * Resource for managing GitHub repository secrets
63
+ * Resource for managing GitHub repository and environment secrets
58
64
  *
59
65
  * Authentication is handled in the following order:
60
66
  * 1. `token` parameter in the resource props (if provided)
@@ -65,7 +71,7 @@ export interface GitHubSecretOutput
65
71
  * - 'public_repo' scope for public repositories
66
72
  *
67
73
  * @example
68
- * // Create a secret using GITHUB_TOKEN environment variable:
74
+ * // Create a repository secret using GITHUB_TOKEN environment variable:
69
75
  * const secret = await GitHubSecret("my-secret", {
70
76
  * owner: "my-github-username",
71
77
  * repository: "my-repo",
@@ -74,6 +80,16 @@ export interface GitHubSecretOutput
74
80
  * });
75
81
  *
76
82
  * @example
83
+ * // Create an environment secret:
84
+ * const secret = await GitHubSecret("my-env-secret", {
85
+ * owner: "my-github-username",
86
+ * repository: "my-repo",
87
+ * environmentName: "production",
88
+ * name: "DEPLOY_KEY",
89
+ * value: alchemy.secret("my-secret-deploy-key")
90
+ * });
91
+ *
92
+ * @example
77
93
  * // Create a secret with a custom GitHub token:
78
94
  * const secret = await GitHubSecret("my-secret", {
79
95
  * owner: "my-github-username",
@@ -119,7 +135,7 @@ export const GitHubSecret = Resource(
119
135
  async function (
120
136
  this: Context<GitHubSecretOutput>,
121
137
  id: string,
122
- props: GitHubSecretProps,
138
+ props: GitHubSecretProps
123
139
  ): Promise<GitHubSecretOutput> {
124
140
  // Create authenticated Octokit client - will automatically handle token resolution
125
141
  /// TODO: use fetch
@@ -132,15 +148,27 @@ export const GitHubSecret = Resource(
132
148
  await verifyGitHubAuth(octokit, props.owner, props.repository);
133
149
  }
134
150
 
151
+ // Determine if we're working with an environment secret or repository secret
152
+ const isEnvironmentSecret = !!props.environment;
153
+
135
154
  if (this.phase === "delete") {
136
155
  if (this.output?.id) {
137
156
  try {
138
157
  // Delete the secret
139
- await octokit.rest.actions.deleteRepoSecret({
140
- owner: props.owner,
141
- repo: props.repository,
142
- secret_name: props.name,
143
- });
158
+ if (isEnvironmentSecret) {
159
+ await octokit.rest.actions.deleteEnvironmentSecret({
160
+ owner: props.owner,
161
+ repo: props.repository,
162
+ environment_name: props.environment!,
163
+ secret_name: props.name,
164
+ });
165
+ } else {
166
+ await octokit.rest.actions.deleteRepoSecret({
167
+ owner: props.owner,
168
+ repo: props.repository,
169
+ secret_name: props.name,
170
+ });
171
+ }
144
172
  } catch (error: any) {
145
173
  // Ignore 404 errors (secret already deleted)
146
174
  if (error.status === 404) {
@@ -155,35 +183,107 @@ export const GitHubSecret = Resource(
155
183
  return this.destroy();
156
184
  } else {
157
185
  try {
158
- // Get the repository's public key for encrypting secrets
159
- const { data: publicKey } = await octokit.rest.actions.getRepoPublicKey(
160
- {
186
+ // Check if we're transitioning between secret types (repo <-> environment)
187
+ if (this.phase === "update") {
188
+ const wasEnvironmentSecret = !!this.output.environment;
189
+ const secretTypeChanged =
190
+ isEnvironmentSecret !== wasEnvironmentSecret;
191
+
192
+ // If secret type changed, we need to delete the old one first
193
+ if (secretTypeChanged) {
194
+ console.log(
195
+ `Secret type changed from ${wasEnvironmentSecret ? "environment" : "repository"} to ${isEnvironmentSecret ? "environment" : "repository"} secret. Deleting the old secret first.`
196
+ );
197
+
198
+ try {
199
+ if (wasEnvironmentSecret) {
200
+ // Delete the old environment secret
201
+ await octokit.rest.actions.deleteEnvironmentSecret({
202
+ owner: this.output.owner,
203
+ repo: this.output.repository,
204
+ environment_name: this.output.environment!,
205
+ secret_name: this.output.name,
206
+ });
207
+ } else {
208
+ // Delete the old repository secret
209
+ await octokit.rest.actions.deleteRepoSecret({
210
+ owner: this.output.owner,
211
+ repo: this.output.repository,
212
+ secret_name: this.output.name,
213
+ });
214
+ }
215
+ } catch (error: any) {
216
+ // Log but don't fail if the old secret doesn't exist or can't be deleted
217
+ if (error.status === 404) {
218
+ console.log(
219
+ "Old secret not found, continuing with creation of new secret"
220
+ );
221
+ } else {
222
+ throw error;
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ let publicKey;
229
+
230
+ // Get the appropriate public key for encrypting secrets
231
+ if (isEnvironmentSecret) {
232
+ // Get the environment's public key
233
+ const { data } = await octokit.rest.actions.getEnvironmentPublicKey({
161
234
  owner: props.owner,
162
235
  repo: props.repository,
163
- },
164
- );
236
+ environment_name: props.environment!,
237
+ });
238
+ publicKey = data;
239
+ } else {
240
+ // Get the repository's public key
241
+ const { data } = await octokit.rest.actions.getRepoPublicKey({
242
+ owner: props.owner,
243
+ repo: props.repository,
244
+ });
245
+ publicKey = data;
246
+ }
165
247
 
166
248
  // Encrypt the secret value using libsodium
167
249
  const encryptedValue = await encryptString(
168
250
  props.value.unencrypted,
169
- publicKey.key,
251
+ publicKey.key
170
252
  );
171
253
 
172
254
  // Create or update the secret with the encrypted value and key_id
173
- await octokit.rest.actions.createOrUpdateRepoSecret({
174
- owner: props.owner,
175
- repo: props.repository,
176
- secret_name: props.name,
177
- encrypted_value: encryptedValue,
178
- key_id: publicKey.key_id,
179
- });
255
+ if (isEnvironmentSecret) {
256
+ await octokit.rest.actions.createOrUpdateEnvironmentSecret({
257
+ owner: props.owner,
258
+ repo: props.repository,
259
+ environment_name: props.environment!,
260
+ secret_name: props.name,
261
+ encrypted_value: encryptedValue,
262
+ key_id: publicKey.key_id,
263
+ });
264
+ } else {
265
+ await octokit.rest.actions.createOrUpdateRepoSecret({
266
+ owner: props.owner,
267
+ repo: props.repository,
268
+ secret_name: props.name,
269
+ encrypted_value: encryptedValue,
270
+ key_id: publicKey.key_id,
271
+ });
272
+ }
180
273
 
181
274
  // GitHub doesn't return the secret details on create/update, so we need to construct it
275
+ const idParts = [props.owner, props.repository];
276
+ if (isEnvironmentSecret) {
277
+ idParts.push(props.environment!);
278
+ }
279
+ idParts.push(props.name);
280
+
182
281
  return this({
183
- id: `${props.owner}/${props.repository}/${props.name}`,
282
+ id: idParts.join("/"),
184
283
  owner: props.owner,
185
284
  repository: props.repository,
186
285
  name: props.name,
286
+ environment: props.environment,
187
287
  token: props.token,
188
288
  updatedAt: new Date().toISOString(),
189
289
  });
@@ -193,21 +293,21 @@ export const GitHubSecret = Resource(
193
293
  error.message?.includes("Must have admin rights")
194
294
  ) {
195
295
  console.error(
196
- "\n⚠️ Error creating/updating GitHub secret: You must have admin rights to the repository.",
296
+ "\n⚠️ Error creating/updating GitHub secret: You must have admin rights to the repository."
197
297
  );
198
298
  console.error(
199
- "Make sure your GitHub token has the required permissions (repo scope for private repos).\n",
299
+ "Make sure your GitHub token has the required permissions (repo scope for private repos).\n"
200
300
  );
201
301
  } else {
202
302
  console.error(
203
303
  "Error creating/updating GitHub secret:",
204
- error.message,
304
+ error.message
205
305
  );
206
306
  }
207
307
  throw error;
208
308
  }
209
309
  }
210
- },
310
+ }
211
311
  );
212
312
 
213
313
  /**
@@ -220,7 +320,7 @@ export const GitHubSecret = Resource(
220
320
  */
221
321
  async function encryptString(
222
322
  value: string,
223
- publicKey: string,
323
+ publicKey: string
224
324
  ): Promise<string> {
225
325
  // Initialize libsodium
226
326
  await sodium.ready;