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.
- package/lib/aws/function.d.ts +4 -0
- package/lib/aws/function.js +203 -1
- package/lib/cloudflare/api-error.js +8 -1
- package/lib/github/index.d.ts +1 -0
- package/lib/github/index.js +1 -0
- package/lib/github/repository-environment.d.ts +153 -0
- package/lib/github/repository-environment.js +295 -0
- package/lib/github/secret.d.ts +17 -2
- package/lib/github/secret.js +111 -20
- package/package.json +1 -1
- package/src/aws/function.ts +247 -14
- package/src/cloudflare/api-error.ts +7 -2
- package/src/github/index.ts +1 -0
- package/src/github/repository-environment.ts +469 -0
- package/src/github/secret.ts +127 -27
package/src/github/secret.ts
CHANGED
|
@@ -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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
//
|
|
159
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
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;
|