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.
@@ -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
- await octokit.rest.actions.deleteRepoSecret({
80
- owner: props.owner,
81
- repo: props.repository,
82
- secret_name: props.name,
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
- // Get the repository's public key for encrypting secrets
101
- const { data: publicKey } = await octokit.rest.actions.getRepoPublicKey({
102
- owner: props.owner,
103
- repo: props.repository,
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
- await octokit.rest.actions.createOrUpdateRepoSecret({
109
- owner: props.owner,
110
- repo: props.repository,
111
- secret_name: props.name,
112
- encrypted_value: encryptedValue,
113
- key_id: publicKey.key_id,
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: `${props.owner}/${props.repository}/${props.name}`,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -119,22 +119,54 @@ export const Bundle = Resource(
119
119
  props: BundleProps
120
120
  ): Promise<Bundle> {
121
121
  // Determine output path
122
- const outputPath = getOutputPath(props);
122
+ const outDirPath =
123
+ props.outfile != null ? path.dirname(props.outfile) : props.outdir;
123
124
 
124
- // Ensure output directory exists
125
- await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
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.unlink(outputPath).catch(() => {});
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 contents = await fs.promises.readFile(outputPath);
137
- const hash = crypto.createHash("sha256").update(contents).digest("hex");
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: outputPath,
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
- outfile: outputPath,
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
- }
@@ -1 +1,2 @@
1
+ export * from "./repository-environment";
1
2
  export * from "./secret";