alchemy 0.9.2 → 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.
@@ -1 +1,2 @@
1
+ export * from "./repository-environment";
1
2
  export * from "./secret";
@@ -1 +1,2 @@
1
+ export * from "./repository-environment";
1
2
  export * from "./secret";
@@ -0,0 +1,153 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ /**
4
+ * Properties for creating or updating a GitHub Repository Environment
5
+ */
6
+ export interface RepositoryEnvironmentProps {
7
+ /**
8
+ * Repository owner (user or organization)
9
+ */
10
+ owner: string;
11
+ /**
12
+ * Repository name
13
+ */
14
+ repository: string;
15
+ /**
16
+ * Environment name
17
+ */
18
+ name: string;
19
+ /**
20
+ * Wait timer before allowing deployments to proceed (in minutes)
21
+ * Must be between 0 and 43200 (30 days)
22
+ * @default 0
23
+ */
24
+ waitTimer?: number;
25
+ /**
26
+ * Determine whether to prevent self-reviews on pull requests
27
+ * Note: Requires at least one reviewer when enabled
28
+ * @default false
29
+ */
30
+ preventSelfReview?: boolean;
31
+ /**
32
+ * Determine whether administrators can bypass deployment protection rules
33
+ * @default true
34
+ */
35
+ adminBypass?: boolean;
36
+ /**
37
+ * Required reviewers for deployments to this environment
38
+ */
39
+ reviewers?: {
40
+ /**
41
+ * GitHub usernames or user IDs that can approve deployments
42
+ * Can be numeric IDs or string usernames (which will be resolved to IDs)
43
+ */
44
+ users?: Array<number | string>;
45
+ /**
46
+ * GitHub team names or team IDs that can approve deployments
47
+ * Can be numeric IDs or string team names (which will be resolved to IDs)
48
+ * Note: For team names, should be in the format "org/team-name"
49
+ */
50
+ teams?: Array<number | string>;
51
+ };
52
+ /**
53
+ * Deployment branch policy for the environment
54
+ */
55
+ deploymentBranchPolicy?: {
56
+ /**
57
+ * Whether to restrict deployments to protected branches
58
+ * @default false
59
+ */
60
+ protectedBranches?: boolean;
61
+ /**
62
+ * Whether to allow custom branch policies
63
+ * When true, specific branch patterns can be specified using createDeploymentBranchPolicy
64
+ * @default false
65
+ */
66
+ customBranchPolicies?: boolean;
67
+ };
68
+ /**
69
+ * Branch patterns for deployment when customBranchPolicies is true
70
+ * For example: ["main", "releases/*"]
71
+ * @default []
72
+ */
73
+ branchPatterns?: string[];
74
+ /**
75
+ * Optional GitHub API token (overrides environment variable)
76
+ * If not provided, will use GITHUB_TOKEN environment variable
77
+ * @default process.env.GITHUB_TOKEN
78
+ */
79
+ token?: string;
80
+ }
81
+ /**
82
+ * Output returned after Repository Environment creation/update
83
+ */
84
+ export interface RepositoryEnvironment extends Resource<"github::RepositoryEnvironment">, RepositoryEnvironmentProps {
85
+ /**
86
+ * The ID of the resource
87
+ */
88
+ id: string;
89
+ /**
90
+ * The numeric ID of the environment in GitHub
91
+ */
92
+ environmentId: number;
93
+ /**
94
+ * Time at which the object was created/updated
95
+ */
96
+ updatedAt: string;
97
+ }
98
+ /**
99
+ * Resource for managing GitHub repository environments
100
+ *
101
+ * Note: If preventSelfReview is true, at least one reviewer must be specified.
102
+ *
103
+ * Branch policies are efficiently managed with proper diffing:
104
+ * - Only manages branch patterns configured through this resource
105
+ * - Preserves any manually added branch patterns outside this resource
106
+ * - When updating, only modifies patterns that have changed from previous state
107
+ * - Compares against previous resource state, not current environment state
108
+ * - Safely handles policy type changes
109
+ *
110
+ * @example
111
+ * // Create a basic environment with no protection rules
112
+ * const devEnv = await RepositoryEnvironment("dev-environment", {
113
+ * owner: "my-org",
114
+ * repository: "my-repo",
115
+ * name: "development"
116
+ * });
117
+ *
118
+ * @example
119
+ * // Create a production environment with approval requirements
120
+ * const prodEnv = await RepositoryEnvironment("prod-environment", {
121
+ * owner: "my-org",
122
+ * repository: "my-repo",
123
+ * name: "production",
124
+ * waitTimer: 10, // 10 minute delay
125
+ * preventSelfReview: true,
126
+ * reviewers: {
127
+ * teams: ["platform-team"], // team name
128
+ * users: ["security-admin"] // username
129
+ * },
130
+ * deploymentBranchPolicy: {
131
+ * protectedBranches: true,
132
+ * customBranchPolicies: false
133
+ * }
134
+ * });
135
+ *
136
+ * @example
137
+ * // Create an environment with reviewer IDs
138
+ * const stagingEnv = await RepositoryEnvironment("staging-environment", {
139
+ * owner: "my-org",
140
+ * repository: "my-repo",
141
+ * name: "staging",
142
+ * reviewers: {
143
+ * teams: [1234567], // team ID
144
+ * users: [7654321] // user ID
145
+ * },
146
+ * deploymentBranchPolicy: {
147
+ * protectedBranches: false,
148
+ * customBranchPolicies: true
149
+ * },
150
+ * branchPatterns: ["main", "release/*"]
151
+ * });
152
+ */
153
+ export declare const RepositoryEnvironment: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<RepositoryEnvironment>, id: string, props: RepositoryEnvironmentProps) => Promise<RepositoryEnvironment>);
@@ -0,0 +1,295 @@
1
+ import { Resource } from "../resource";
2
+ import { createGitHubClient, verifyGitHubAuth } from "./client";
3
+ /**
4
+ * Resource for managing GitHub repository environments
5
+ *
6
+ * Note: If preventSelfReview is true, at least one reviewer must be specified.
7
+ *
8
+ * Branch policies are efficiently managed with proper diffing:
9
+ * - Only manages branch patterns configured through this resource
10
+ * - Preserves any manually added branch patterns outside this resource
11
+ * - When updating, only modifies patterns that have changed from previous state
12
+ * - Compares against previous resource state, not current environment state
13
+ * - Safely handles policy type changes
14
+ *
15
+ * @example
16
+ * // Create a basic environment with no protection rules
17
+ * const devEnv = await RepositoryEnvironment("dev-environment", {
18
+ * owner: "my-org",
19
+ * repository: "my-repo",
20
+ * name: "development"
21
+ * });
22
+ *
23
+ * @example
24
+ * // Create a production environment with approval requirements
25
+ * const prodEnv = await RepositoryEnvironment("prod-environment", {
26
+ * owner: "my-org",
27
+ * repository: "my-repo",
28
+ * name: "production",
29
+ * waitTimer: 10, // 10 minute delay
30
+ * preventSelfReview: true,
31
+ * reviewers: {
32
+ * teams: ["platform-team"], // team name
33
+ * users: ["security-admin"] // username
34
+ * },
35
+ * deploymentBranchPolicy: {
36
+ * protectedBranches: true,
37
+ * customBranchPolicies: false
38
+ * }
39
+ * });
40
+ *
41
+ * @example
42
+ * // Create an environment with reviewer IDs
43
+ * const stagingEnv = await RepositoryEnvironment("staging-environment", {
44
+ * owner: "my-org",
45
+ * repository: "my-repo",
46
+ * name: "staging",
47
+ * reviewers: {
48
+ * teams: [1234567], // team ID
49
+ * users: [7654321] // user ID
50
+ * },
51
+ * deploymentBranchPolicy: {
52
+ * protectedBranches: false,
53
+ * customBranchPolicies: true
54
+ * },
55
+ * branchPatterns: ["main", "release/*"]
56
+ * });
57
+ */
58
+ export const RepositoryEnvironment = Resource("github::RepositoryEnvironment", async function (id, props) {
59
+ // Create authenticated Octokit client
60
+ const octokit = await createGitHubClient({
61
+ token: props.token,
62
+ });
63
+ // Verify authentication and permissions
64
+ if (!this.quiet) {
65
+ await verifyGitHubAuth(octokit, props.owner, props.repository);
66
+ }
67
+ if (this.phase === "delete") {
68
+ if (this.output?.id) {
69
+ try {
70
+ // Delete the environment
71
+ await octokit.rest.repos.deleteAnEnvironment({
72
+ owner: props.owner,
73
+ repo: props.repository,
74
+ environment_name: props.name,
75
+ });
76
+ }
77
+ catch (error) {
78
+ // Ignore 404 errors (environment already deleted)
79
+ if (error.status === 404) {
80
+ console.log("Environment doesn't exist, ignoring");
81
+ }
82
+ else {
83
+ throw error;
84
+ }
85
+ }
86
+ }
87
+ // Return void (a deleted resource has no content)
88
+ return this.destroy();
89
+ }
90
+ else {
91
+ try {
92
+ // Check if the environment already exists
93
+ let environmentId = undefined; // Use undefined instead of 0
94
+ try {
95
+ const { data: environments } = await octokit.rest.repos.getAllEnvironments({
96
+ owner: props.owner,
97
+ repo: props.repository,
98
+ });
99
+ const existingEnv = environments.environments?.find((env) => env.name.toLowerCase() === props.name.toLowerCase());
100
+ if (existingEnv?.id) {
101
+ environmentId = existingEnv.id;
102
+ }
103
+ }
104
+ catch (error) {
105
+ // If it's a 404, the environment doesn't exist, which is fine
106
+ if (error.status !== 404) {
107
+ throw error;
108
+ }
109
+ }
110
+ // Convert reviewers to API format
111
+ const reviewers = [];
112
+ // Process user reviewers
113
+ if (props.reviewers?.users && props.reviewers.users.length > 0) {
114
+ for (const user of props.reviewers.users) {
115
+ if (typeof user === "number") {
116
+ // If a numeric ID is provided, use it directly
117
+ reviewers.push({
118
+ type: "User",
119
+ id: user,
120
+ });
121
+ }
122
+ else {
123
+ // If a username string is provided, look up the ID
124
+ const { data: userData } = await octokit.rest.users.getByUsername({
125
+ username: user,
126
+ });
127
+ reviewers.push({
128
+ type: "User",
129
+ id: userData.id,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ // Process team reviewers
135
+ if (props.reviewers?.teams && props.reviewers.teams.length > 0) {
136
+ for (const team of props.reviewers.teams) {
137
+ if (typeof team === "number") {
138
+ // If a numeric ID is provided, use it directly
139
+ reviewers.push({
140
+ type: "Team",
141
+ id: team,
142
+ });
143
+ }
144
+ else {
145
+ // If a team name string is provided, look up the ID
146
+ // Check if team name is in the format 'org/team-name'
147
+ const teamParts = team.includes("/")
148
+ ? team.split("/")
149
+ : [props.owner, team];
150
+ if (teamParts.length !== 2) {
151
+ throw new Error(`Invalid team format: ${team}. Expected format: "org/team-slug" or just "team-slug"`);
152
+ }
153
+ const [org, teamSlug] = teamParts;
154
+ const { data: teamData } = await octokit.rest.teams.getByName({
155
+ org,
156
+ team_slug: teamSlug,
157
+ });
158
+ reviewers.push({
159
+ type: "Team",
160
+ id: teamData.id,
161
+ });
162
+ }
163
+ }
164
+ }
165
+ // Create or update the environment
166
+ if (environmentId === undefined) {
167
+ // Create the environment
168
+ const { data: createdEnv } = await octokit.rest.repos.createOrUpdateEnvironment({
169
+ owner: props.owner,
170
+ repo: props.repository,
171
+ environment_name: props.name,
172
+ wait_timer: props.waitTimer,
173
+ prevent_self_review: props.preventSelfReview,
174
+ reviewers: reviewers.length > 0 ? reviewers : undefined,
175
+ deployment_branch_policy: props.deploymentBranchPolicy
176
+ ? {
177
+ protected_branches: props.deploymentBranchPolicy.protectedBranches ?? false,
178
+ custom_branch_policies: props.deploymentBranchPolicy.customBranchPolicies ??
179
+ false,
180
+ }
181
+ : undefined,
182
+ });
183
+ environmentId = createdEnv.id;
184
+ // If there are specific branch patterns, set them
185
+ if (props.deploymentBranchPolicy?.customBranchPolicies === true &&
186
+ props.branchPatterns &&
187
+ props.branchPatterns.length > 0) {
188
+ // Add branch patterns one by one
189
+ for (const pattern of props.branchPatterns) {
190
+ await octokit.rest.repos.createDeploymentBranchPolicy({
191
+ owner: props.owner,
192
+ repo: props.repository,
193
+ environment_name: props.name,
194
+ name: pattern,
195
+ });
196
+ }
197
+ }
198
+ }
199
+ else {
200
+ // Update the environment
201
+ await octokit.rest.repos.createOrUpdateEnvironment({
202
+ owner: props.owner,
203
+ repo: props.repository,
204
+ environment_name: props.name,
205
+ wait_timer: props.waitTimer,
206
+ prevent_self_review: props.preventSelfReview,
207
+ reviewers: reviewers.length > 0 ? reviewers : undefined,
208
+ deployment_branch_policy: props.deploymentBranchPolicy
209
+ ? {
210
+ protected_branches: props.deploymentBranchPolicy.protectedBranches ?? false,
211
+ custom_branch_policies: props.deploymentBranchPolicy.customBranchPolicies ?? false,
212
+ }
213
+ : undefined,
214
+ });
215
+ // If there are specific branch patterns, update them
216
+ if (props.deploymentBranchPolicy?.customBranchPolicies === true) {
217
+ // Get existing branch policies to understand what's currently configured
218
+ const { data: existingPolicies } = await octokit.rest.repos.listDeploymentBranchPolicies({
219
+ owner: props.owner,
220
+ repo: props.repository,
221
+ environment_name: props.name,
222
+ });
223
+ const existingPatterns = existingPolicies.branch_policies.map((policy) => policy.name);
224
+ const newPatterns = props.branchPatterns || [];
225
+ // For updates, keep track of which branch policies we should manage
226
+ // This set identifies patterns that were previously managed by this resource
227
+ // If we don't have previous props, use an empty set
228
+ const previouslyManagedPatterns = new Set(this.phase === "update" &&
229
+ this.props?.deploymentBranchPolicy?.customBranchPolicies === true
230
+ ? this.props.branchPatterns || []
231
+ : []);
232
+ // Patterns to add (in new config but not in existing)
233
+ const patternsToAdd = newPatterns.filter((pattern) => !existingPatterns.includes(pattern));
234
+ // Patterns to delete (were managed by us previously but not in new config)
235
+ const patternsToDelete = existingPatterns.filter((pattern) => previouslyManagedPatterns.has(pattern) &&
236
+ !newPatterns.includes(pattern));
237
+ // Delete policies that are no longer needed
238
+ for (const patternToDelete of patternsToDelete) {
239
+ const policy = existingPolicies.branch_policies.find((p) => p.name === patternToDelete);
240
+ if (policy?.id) {
241
+ await octokit.rest.repos.deleteDeploymentBranchPolicy({
242
+ owner: props.owner,
243
+ repo: props.repository,
244
+ environment_name: props.name,
245
+ branch_policy_id: policy.id,
246
+ });
247
+ }
248
+ }
249
+ // Add new branch patterns
250
+ for (const pattern of patternsToAdd) {
251
+ await octokit.rest.repos.createDeploymentBranchPolicy({
252
+ owner: props.owner,
253
+ repo: props.repository,
254
+ environment_name: props.name,
255
+ name: pattern,
256
+ });
257
+ }
258
+ }
259
+ }
260
+ // Get the updated environment details
261
+ const { data: env } = await octokit.rest.repos.getEnvironment({
262
+ owner: props.owner,
263
+ repo: props.repository,
264
+ environment_name: props.name,
265
+ });
266
+ // Return environment details
267
+ return this({
268
+ id: `${props.owner}/${props.repository}/${props.name}`,
269
+ environmentId: environmentId || env.id,
270
+ owner: props.owner,
271
+ repository: props.repository,
272
+ name: props.name,
273
+ waitTimer: props.waitTimer,
274
+ preventSelfReview: props.preventSelfReview,
275
+ adminBypass: props.adminBypass,
276
+ reviewers: props.reviewers,
277
+ deploymentBranchPolicy: props.deploymentBranchPolicy,
278
+ branchPatterns: props.branchPatterns,
279
+ token: props.token,
280
+ updatedAt: new Date().toISOString(),
281
+ });
282
+ }
283
+ catch (error) {
284
+ if (error.status === 403 &&
285
+ error.message?.includes("Must have admin rights")) {
286
+ console.error("\n⚠️ Error creating/updating GitHub environment: You must have admin rights to the repository.");
287
+ console.error("Make sure your GitHub token has the required permissions (repo scope for private repos).\n");
288
+ }
289
+ else {
290
+ console.error("Error creating/updating GitHub environment:", error.message);
291
+ }
292
+ throw error;
293
+ }
294
+ }
295
+ });
@@ -21,6 +21,11 @@ export interface GitHubSecretProps {
21
21
  * Secret value (will be stored securely on GitHub)
22
22
  */
23
23
  value: Secret;
24
+ /**
25
+ * Optional environment name to create an environment secret
26
+ * If set, the secret will be created in the specified environment instead of at the repository level
27
+ */
28
+ environment?: string;
24
29
  /**
25
30
  * Optional GitHub API token (overrides environment variable)
26
31
  * If not provided, will use GITHUB_TOKEN environment variable
@@ -43,7 +48,7 @@ export interface GitHubSecretOutput extends Resource<"github::Secret">, Omit<Git
43
48
  updatedAt: string;
44
49
  }
45
50
  /**
46
- * Resource for managing GitHub repository secrets
51
+ * Resource for managing GitHub repository and environment secrets
47
52
  *
48
53
  * Authentication is handled in the following order:
49
54
  * 1. `token` parameter in the resource props (if provided)
@@ -54,7 +59,7 @@ export interface GitHubSecretOutput extends Resource<"github::Secret">, Omit<Git
54
59
  * - 'public_repo' scope for public repositories
55
60
  *
56
61
  * @example
57
- * // Create a secret using GITHUB_TOKEN environment variable:
62
+ * // Create a repository secret using GITHUB_TOKEN environment variable:
58
63
  * const secret = await GitHubSecret("my-secret", {
59
64
  * owner: "my-github-username",
60
65
  * repository: "my-repo",
@@ -63,6 +68,16 @@ export interface GitHubSecretOutput extends Resource<"github::Secret">, Omit<Git
63
68
  * });
64
69
  *
65
70
  * @example
71
+ * // Create an environment secret:
72
+ * const secret = await GitHubSecret("my-env-secret", {
73
+ * owner: "my-github-username",
74
+ * repository: "my-repo",
75
+ * environmentName: "production",
76
+ * name: "DEPLOY_KEY",
77
+ * value: alchemy.secret("my-secret-deploy-key")
78
+ * });
79
+ *
80
+ * @example
66
81
  * // Create a secret with a custom GitHub token:
67
82
  * const secret = await GitHubSecret("my-secret", {
68
83
  * owner: "my-github-username",
@@ -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
  });