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/esbuild/bundle.d.ts
CHANGED
|
@@ -95,7 +95,8 @@ export interface Bundle extends Resource<"esbuild::Bundle"> {
|
|
|
95
95
|
export declare const Bundle: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Bundle>, id: string, props: BundleProps) => Promise<Bundle>);
|
|
96
96
|
export declare function bundle(props: BundleProps): Promise<esbuild.BuildResult<{
|
|
97
97
|
entryPoints: string[];
|
|
98
|
-
|
|
98
|
+
outdir: string | undefined;
|
|
99
|
+
outfile: string | undefined;
|
|
99
100
|
bundle: true;
|
|
100
101
|
format: "iife" | "cjs" | "esm" | undefined;
|
|
101
102
|
target: string | string[] | undefined;
|
|
@@ -107,7 +108,6 @@ export declare function bundle(props: BundleProps): Promise<esbuild.BuildResult<
|
|
|
107
108
|
write: true;
|
|
108
109
|
splitting?: boolean;
|
|
109
110
|
preserveSymlinks?: boolean;
|
|
110
|
-
outdir?: string;
|
|
111
111
|
outbase?: string;
|
|
112
112
|
packages?: "bundle" | "external";
|
|
113
113
|
alias?: Record<string, string>;
|
package/lib/esbuild/bundle.js
CHANGED
|
@@ -23,34 +23,51 @@ export const Bundle = Resource("esbuild::Bundle", {
|
|
|
23
23
|
alwaysUpdate: true,
|
|
24
24
|
}, async function (id, props) {
|
|
25
25
|
// Determine output path
|
|
26
|
-
const
|
|
26
|
+
const outDirPath = props.outfile != null ? path.dirname(props.outfile) : props.outdir;
|
|
27
|
+
if (outDirPath == null) {
|
|
28
|
+
throw new Error(`You need to specify either outfile or outdir in your bundle configuration ${JSON.stringify(props)}`);
|
|
29
|
+
}
|
|
27
30
|
// Ensure output directory exists
|
|
28
|
-
await fs.promises.mkdir(path.dirname(
|
|
31
|
+
await fs.promises.mkdir(path.dirname(outDirPath), { recursive: true });
|
|
29
32
|
if (this.phase === "delete") {
|
|
30
|
-
await fs.promises.
|
|
31
|
-
// Also clean up sourcemap if it exists
|
|
32
|
-
await fs.promises.unlink(outputPath + ".map").catch(() => { });
|
|
33
|
-
return this.destroy();
|
|
33
|
+
await fs.promises.rm(outDirPath, { recursive: true });
|
|
34
34
|
}
|
|
35
35
|
const result = await bundle(props);
|
|
36
|
+
// Check that bundle created output an file for the given entrypoint
|
|
37
|
+
// Use bundle metada data to retrieve its content
|
|
38
|
+
const bundleOutputPath = Object.entries(result.metafile.outputs).find(([file, output]) => {
|
|
39
|
+
if (output.entryPoint === undefined) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// resolve to absolute and then relative to ensure consistent result (e.g. ./src/handler.ts instead of src/handler.ts)
|
|
43
|
+
const relativeOutput = path.relative(process.cwd(), path.resolve(output.entryPoint));
|
|
44
|
+
return (relativeOutput ===
|
|
45
|
+
path.relative(process.cwd(), path.resolve(process.cwd(), props.entryPoint)));
|
|
46
|
+
})?.[0];
|
|
47
|
+
if (!bundleOutputPath) {
|
|
48
|
+
throw new Error(`Unable to find a compiled file`);
|
|
49
|
+
}
|
|
36
50
|
// Calculate hash of the output
|
|
37
|
-
const
|
|
38
|
-
const hash = crypto
|
|
51
|
+
const bundleOutputContent = await fs.promises.readFile(bundleOutputPath);
|
|
52
|
+
const hash = crypto
|
|
53
|
+
.createHash("sha256")
|
|
54
|
+
.update(bundleOutputContent)
|
|
55
|
+
.digest("hex");
|
|
39
56
|
// Store metadata in context
|
|
40
57
|
await this.set("metafile", result.metafile);
|
|
41
58
|
await this.set("hash", hash);
|
|
42
59
|
// Return output info
|
|
43
60
|
return this({
|
|
44
|
-
path:
|
|
61
|
+
path: bundleOutputPath,
|
|
45
62
|
hash,
|
|
46
63
|
});
|
|
47
64
|
});
|
|
48
65
|
export async function bundle(props) {
|
|
49
|
-
const outputPath = getOutputPath(props);
|
|
50
66
|
return await esbuild.build({
|
|
51
67
|
...props.options,
|
|
52
68
|
entryPoints: [props.entryPoint],
|
|
53
|
-
|
|
69
|
+
outdir: props.outdir,
|
|
70
|
+
outfile: props.outfile,
|
|
54
71
|
bundle: true,
|
|
55
72
|
format: props.format,
|
|
56
73
|
target: props.target,
|
|
@@ -66,7 +83,3 @@ export async function bundle(props) {
|
|
|
66
83
|
write: true,
|
|
67
84
|
});
|
|
68
85
|
}
|
|
69
|
-
function getOutputPath(props) {
|
|
70
|
-
return (props.outfile ||
|
|
71
|
-
path.join(props.outdir || "dist", path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js"));
|
|
72
|
-
}
|
package/lib/github/index.d.ts
CHANGED
package/lib/github/index.js
CHANGED
|
@@ -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
|
+
});
|
package/lib/github/secret.d.ts
CHANGED
|
@@ -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",
|