alchemy 0.9.2 → 0.10.1
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/esbuild/bundle.d.ts +2 -2
- package/lib/esbuild/bundle.js +28 -15
- 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/esbuild/bundle.ts +44 -22
- package/src/github/index.ts +1 -0
- package/src/github/repository-environment.ts +469 -0
- package/src/github/secret.ts +127 -27
package/lib/github/secret.js
CHANGED
|
@@ -2,7 +2,7 @@ import sodium from "libsodium-wrappers";
|
|
|
2
2
|
import { Resource } from "../resource";
|
|
3
3
|
import { createGitHubClient, verifyGitHubAuth } from "./client";
|
|
4
4
|
/**
|
|
5
|
-
* Resource for managing GitHub repository secrets
|
|
5
|
+
* Resource for managing GitHub repository and environment secrets
|
|
6
6
|
*
|
|
7
7
|
* Authentication is handled in the following order:
|
|
8
8
|
* 1. `token` parameter in the resource props (if provided)
|
|
@@ -13,7 +13,7 @@ import { createGitHubClient, verifyGitHubAuth } from "./client";
|
|
|
13
13
|
* - 'public_repo' scope for public repositories
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
|
-
* // Create a secret using GITHUB_TOKEN environment variable:
|
|
16
|
+
* // Create a repository secret using GITHUB_TOKEN environment variable:
|
|
17
17
|
* const secret = await GitHubSecret("my-secret", {
|
|
18
18
|
* owner: "my-github-username",
|
|
19
19
|
* repository: "my-repo",
|
|
@@ -22,6 +22,16 @@ import { createGitHubClient, verifyGitHubAuth } from "./client";
|
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
25
|
+
* // Create an environment secret:
|
|
26
|
+
* const secret = await GitHubSecret("my-env-secret", {
|
|
27
|
+
* owner: "my-github-username",
|
|
28
|
+
* repository: "my-repo",
|
|
29
|
+
* environmentName: "production",
|
|
30
|
+
* name: "DEPLOY_KEY",
|
|
31
|
+
* value: alchemy.secret("my-secret-deploy-key")
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
25
35
|
* // Create a secret with a custom GitHub token:
|
|
26
36
|
* const secret = await GitHubSecret("my-secret", {
|
|
27
37
|
* owner: "my-github-username",
|
|
@@ -72,15 +82,27 @@ export const GitHubSecret = Resource("github::Secret", async function (id, props
|
|
|
72
82
|
if (!this.quiet) {
|
|
73
83
|
await verifyGitHubAuth(octokit, props.owner, props.repository);
|
|
74
84
|
}
|
|
85
|
+
// Determine if we're working with an environment secret or repository secret
|
|
86
|
+
const isEnvironmentSecret = !!props.environment;
|
|
75
87
|
if (this.phase === "delete") {
|
|
76
88
|
if (this.output?.id) {
|
|
77
89
|
try {
|
|
78
90
|
// Delete the secret
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
if (isEnvironmentSecret) {
|
|
92
|
+
await octokit.rest.actions.deleteEnvironmentSecret({
|
|
93
|
+
owner: props.owner,
|
|
94
|
+
repo: props.repository,
|
|
95
|
+
environment_name: props.environment,
|
|
96
|
+
secret_name: props.name,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
await octokit.rest.actions.deleteRepoSecret({
|
|
101
|
+
owner: props.owner,
|
|
102
|
+
repo: props.repository,
|
|
103
|
+
secret_name: props.name,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
84
106
|
}
|
|
85
107
|
catch (error) {
|
|
86
108
|
// Ignore 404 errors (secret already deleted)
|
|
@@ -97,27 +119,96 @@ export const GitHubSecret = Resource("github::Secret", async function (id, props
|
|
|
97
119
|
}
|
|
98
120
|
else {
|
|
99
121
|
try {
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
// Check if we're transitioning between secret types (repo <-> environment)
|
|
123
|
+
if (this.phase === "update") {
|
|
124
|
+
const wasEnvironmentSecret = !!this.output.environment;
|
|
125
|
+
const secretTypeChanged = isEnvironmentSecret !== wasEnvironmentSecret;
|
|
126
|
+
// If secret type changed, we need to delete the old one first
|
|
127
|
+
if (secretTypeChanged) {
|
|
128
|
+
console.log(`Secret type changed from ${wasEnvironmentSecret ? "environment" : "repository"} to ${isEnvironmentSecret ? "environment" : "repository"} secret. Deleting the old secret first.`);
|
|
129
|
+
try {
|
|
130
|
+
if (wasEnvironmentSecret) {
|
|
131
|
+
// Delete the old environment secret
|
|
132
|
+
await octokit.rest.actions.deleteEnvironmentSecret({
|
|
133
|
+
owner: this.output.owner,
|
|
134
|
+
repo: this.output.repository,
|
|
135
|
+
environment_name: this.output.environment,
|
|
136
|
+
secret_name: this.output.name,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Delete the old repository secret
|
|
141
|
+
await octokit.rest.actions.deleteRepoSecret({
|
|
142
|
+
owner: this.output.owner,
|
|
143
|
+
repo: this.output.repository,
|
|
144
|
+
secret_name: this.output.name,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Log but don't fail if the old secret doesn't exist or can't be deleted
|
|
150
|
+
if (error.status === 404) {
|
|
151
|
+
console.log("Old secret not found, continuing with creation of new secret");
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let publicKey;
|
|
160
|
+
// Get the appropriate public key for encrypting secrets
|
|
161
|
+
if (isEnvironmentSecret) {
|
|
162
|
+
// Get the environment's public key
|
|
163
|
+
const { data } = await octokit.rest.actions.getEnvironmentPublicKey({
|
|
164
|
+
owner: props.owner,
|
|
165
|
+
repo: props.repository,
|
|
166
|
+
environment_name: props.environment,
|
|
167
|
+
});
|
|
168
|
+
publicKey = data;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Get the repository's public key
|
|
172
|
+
const { data } = await octokit.rest.actions.getRepoPublicKey({
|
|
173
|
+
owner: props.owner,
|
|
174
|
+
repo: props.repository,
|
|
175
|
+
});
|
|
176
|
+
publicKey = data;
|
|
177
|
+
}
|
|
105
178
|
// Encrypt the secret value using libsodium
|
|
106
179
|
const encryptedValue = await encryptString(props.value.unencrypted, publicKey.key);
|
|
107
180
|
// Create or update the secret with the encrypted value and key_id
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
181
|
+
if (isEnvironmentSecret) {
|
|
182
|
+
await octokit.rest.actions.createOrUpdateEnvironmentSecret({
|
|
183
|
+
owner: props.owner,
|
|
184
|
+
repo: props.repository,
|
|
185
|
+
environment_name: props.environment,
|
|
186
|
+
secret_name: props.name,
|
|
187
|
+
encrypted_value: encryptedValue,
|
|
188
|
+
key_id: publicKey.key_id,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
193
|
+
owner: props.owner,
|
|
194
|
+
repo: props.repository,
|
|
195
|
+
secret_name: props.name,
|
|
196
|
+
encrypted_value: encryptedValue,
|
|
197
|
+
key_id: publicKey.key_id,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
115
200
|
// GitHub doesn't return the secret details on create/update, so we need to construct it
|
|
201
|
+
const idParts = [props.owner, props.repository];
|
|
202
|
+
if (isEnvironmentSecret) {
|
|
203
|
+
idParts.push(props.environment);
|
|
204
|
+
}
|
|
205
|
+
idParts.push(props.name);
|
|
116
206
|
return this({
|
|
117
|
-
id:
|
|
207
|
+
id: idParts.join("/"),
|
|
118
208
|
owner: props.owner,
|
|
119
209
|
repository: props.repository,
|
|
120
210
|
name: props.name,
|
|
211
|
+
environment: props.environment,
|
|
121
212
|
token: props.token,
|
|
122
213
|
updatedAt: new Date().toISOString(),
|
|
123
214
|
});
|
package/package.json
CHANGED
package/src/esbuild/bundle.ts
CHANGED
|
@@ -119,22 +119,54 @@ export const Bundle = Resource(
|
|
|
119
119
|
props: BundleProps
|
|
120
120
|
): Promise<Bundle> {
|
|
121
121
|
// Determine output path
|
|
122
|
-
const
|
|
122
|
+
const outDirPath =
|
|
123
|
+
props.outfile != null ? path.dirname(props.outfile) : props.outdir;
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if (outDirPath == null) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`You need to specify either outfile or outdir in your bundle configuration ${JSON.stringify(props)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
126
130
|
|
|
131
|
+
// Ensure output directory exists
|
|
132
|
+
await fs.promises.mkdir(path.dirname(outDirPath), { recursive: true });
|
|
127
133
|
if (this.phase === "delete") {
|
|
128
|
-
await fs.promises.
|
|
129
|
-
// Also clean up sourcemap if it exists
|
|
130
|
-
await fs.promises.unlink(outputPath + ".map").catch(() => {});
|
|
131
|
-
return this.destroy();
|
|
134
|
+
await fs.promises.rm(outDirPath, { recursive: true });
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
const result = await bundle(props);
|
|
138
|
+
// Check that bundle created output an file for the given entrypoint
|
|
139
|
+
// Use bundle metada data to retrieve its content
|
|
140
|
+
const bundleOutputPath = Object.entries(result.metafile.outputs).find(
|
|
141
|
+
([file, output]) => {
|
|
142
|
+
if (output.entryPoint === undefined) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
// resolve to absolute and then relative to ensure consistent result (e.g. ./src/handler.ts instead of src/handler.ts)
|
|
146
|
+
const relativeOutput = path.relative(
|
|
147
|
+
process.cwd(),
|
|
148
|
+
path.resolve(output.entryPoint)
|
|
149
|
+
);
|
|
150
|
+
return (
|
|
151
|
+
relativeOutput ===
|
|
152
|
+
path.relative(
|
|
153
|
+
process.cwd(),
|
|
154
|
+
path.resolve(process.cwd(), props.entryPoint)
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
)?.[0];
|
|
159
|
+
|
|
160
|
+
if (!bundleOutputPath) {
|
|
161
|
+
throw new Error(`Unable to find a compiled file`);
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
// Calculate hash of the output
|
|
136
|
-
const
|
|
137
|
-
const hash = crypto
|
|
165
|
+
const bundleOutputContent = await fs.promises.readFile(bundleOutputPath);
|
|
166
|
+
const hash = crypto
|
|
167
|
+
.createHash("sha256")
|
|
168
|
+
.update(bundleOutputContent)
|
|
169
|
+
.digest("hex");
|
|
138
170
|
|
|
139
171
|
// Store metadata in context
|
|
140
172
|
await this.set("metafile", result.metafile);
|
|
@@ -142,18 +174,18 @@ export const Bundle = Resource(
|
|
|
142
174
|
|
|
143
175
|
// Return output info
|
|
144
176
|
return this({
|
|
145
|
-
path:
|
|
177
|
+
path: bundleOutputPath,
|
|
146
178
|
hash,
|
|
147
179
|
});
|
|
148
180
|
}
|
|
149
181
|
);
|
|
150
182
|
|
|
151
183
|
export async function bundle(props: BundleProps) {
|
|
152
|
-
const outputPath = getOutputPath(props);
|
|
153
184
|
return await esbuild.build({
|
|
154
185
|
...props.options,
|
|
155
186
|
entryPoints: [props.entryPoint],
|
|
156
|
-
|
|
187
|
+
outdir: props.outdir,
|
|
188
|
+
outfile: props.outfile,
|
|
157
189
|
bundle: true,
|
|
158
190
|
format: props.format,
|
|
159
191
|
target: props.target,
|
|
@@ -169,13 +201,3 @@ export async function bundle(props: BundleProps) {
|
|
|
169
201
|
write: true,
|
|
170
202
|
});
|
|
171
203
|
}
|
|
172
|
-
|
|
173
|
-
function getOutputPath(props: BundleProps) {
|
|
174
|
-
return (
|
|
175
|
-
props.outfile ||
|
|
176
|
-
path.join(
|
|
177
|
-
props.outdir || "dist",
|
|
178
|
-
path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js"
|
|
179
|
-
)
|
|
180
|
-
);
|
|
181
|
-
}
|
package/src/github/index.ts
CHANGED