alchemy 0.1.13 → 0.1.14
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/README.md +12 -2
- package/lib/aws/account-id.d.ts +5 -0
- package/lib/aws/account-id.d.ts.map +1 -0
- package/lib/aws/account-id.js +10 -0
- package/lib/aws/account-id.js.map +1 -0
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.js +22 -0
- package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
- package/lib/aws/oidc/index.d.ts +3 -0
- package/lib/aws/oidc/index.d.ts.map +1 -0
- package/lib/aws/oidc/index.js +3 -0
- package/lib/aws/oidc/index.js.map +1 -0
- package/lib/aws/oidc/oidc-provider.d.ts +60 -0
- package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/oidc-provider.js +140 -0
- package/lib/aws/oidc/oidc-provider.js.map +1 -0
- package/lib/aws/queue.d.ts +24 -0
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +6 -1
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +49 -1
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +20 -28
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +95 -73
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +7 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +2 -5
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js +1 -6
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +3 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +4 -0
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/static-site.d.ts.map +1 -1
- package/lib/cloudflare/static-site.js +12 -30
- package/lib/cloudflare/static-site.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +6 -3
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +41 -15
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/github/client.d.ts +34 -0
- package/lib/github/client.d.ts.map +1 -0
- package/lib/github/client.js +91 -0
- package/lib/github/client.js.map +1 -0
- package/lib/github/index.d.ts +2 -0
- package/lib/github/index.d.ts.map +1 -0
- package/lib/github/index.js +2 -0
- package/lib/github/index.js.map +1 -0
- package/lib/github/secret.d.ts +46 -0
- package/lib/github/secret.d.ts.map +1 -0
- package/lib/github/secret.js +103 -0
- package/lib/github/secret.js.map +1 -0
- package/lib/utils/retry.d.ts +13 -0
- package/lib/utils/retry.d.ts.map +1 -0
- package/lib/utils/retry.js +31 -0
- package/lib/utils/retry.js.map +1 -0
- package/package.json +10 -2
- package/src/aws/account-id.ts +11 -0
- package/src/aws/index.ts +1 -0
- package/src/aws/oidc/github-oidc-provider.ts +34 -0
- package/src/aws/oidc/index.ts +2 -0
- package/src/aws/oidc/oidc-provider.ts +253 -0
- package/src/aws/queue.ts +23 -1
- package/src/aws/role.ts +69 -0
- package/src/aws/ses.ts +27 -31
- package/src/aws/table.ts +113 -82
- package/src/cloudflare/bindings.ts +8 -2
- package/src/cloudflare/durable-object-namespace.ts +4 -10
- package/src/cloudflare/kv-namespace.ts +11 -0
- package/src/cloudflare/static-site.ts +13 -32
- package/src/cloudflare/worker.ts +52 -22
- package/src/github/client.ts +115 -0
- package/src/github/index.ts +1 -0
- package/src/github/secret.ts +172 -0
- package/src/utils/retry.ts +36 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
|
|
5
|
+
// Convert exec to promise-based
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Try to get a GitHub token from the GitHub CLI
|
|
10
|
+
*/
|
|
11
|
+
export async function getGitHubTokenFromCLI(): Promise<string | null> {
|
|
12
|
+
try {
|
|
13
|
+
// Check if GitHub CLI is installed
|
|
14
|
+
const { stdout: ghVersion } = await execAsync("gh --version");
|
|
15
|
+
if (!ghVersion) return null;
|
|
16
|
+
|
|
17
|
+
// Get the auth token
|
|
18
|
+
const { stdout: token } = await execAsync("gh auth token");
|
|
19
|
+
return token?.trim() || null;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get GitHub authentication token with the following priority:
|
|
27
|
+
* 1. Explicit token provided in props
|
|
28
|
+
* 2. GITHUB_ACCESS_TOKEN environment variable (for actions with admin permissions)
|
|
29
|
+
* 3. GITHUB_TOKEN environment variable
|
|
30
|
+
* 4. GitHub CLI token (if gh is installed and authenticated)
|
|
31
|
+
*
|
|
32
|
+
* @param token Optional token to use
|
|
33
|
+
* @returns The resolved token or null if not available
|
|
34
|
+
*/
|
|
35
|
+
export async function getGitHubToken(token?: string): Promise<string | null> {
|
|
36
|
+
return (
|
|
37
|
+
token ||
|
|
38
|
+
process.env.GITHUB_ACCESS_TOKEN ||
|
|
39
|
+
process.env.GITHUB_TOKEN ||
|
|
40
|
+
(await getGitHubTokenFromCLI())
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create an authenticated Octokit client
|
|
46
|
+
*
|
|
47
|
+
* @param options Options for creating the client
|
|
48
|
+
* @returns An authenticated Octokit client
|
|
49
|
+
*/
|
|
50
|
+
export async function createGitHubClient(
|
|
51
|
+
options: { token?: string } = {},
|
|
52
|
+
): Promise<Octokit> {
|
|
53
|
+
const token = await getGitHubToken(options.token);
|
|
54
|
+
|
|
55
|
+
return new Octokit({
|
|
56
|
+
auth: token,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Verifies GitHub authentication and provides helpful error messages
|
|
62
|
+
*
|
|
63
|
+
* @param octokit Octokit client
|
|
64
|
+
* @param owner Repository owner
|
|
65
|
+
* @param repo Repository name
|
|
66
|
+
*/
|
|
67
|
+
export async function verifyGitHubAuth(
|
|
68
|
+
octokit: Octokit,
|
|
69
|
+
owner: string,
|
|
70
|
+
repo: string,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
// Make a test request to check authentication
|
|
74
|
+
await octokit.rest.repos.get({
|
|
75
|
+
owner,
|
|
76
|
+
repo,
|
|
77
|
+
});
|
|
78
|
+
} catch (error: any) {
|
|
79
|
+
if (error.status === 401) {
|
|
80
|
+
console.error(
|
|
81
|
+
"\n⚠️ GitHub authentication failed. Please try one of the following:",
|
|
82
|
+
);
|
|
83
|
+
console.error(
|
|
84
|
+
"1. Run 'gh auth login' to authenticate with the GitHub CLI",
|
|
85
|
+
);
|
|
86
|
+
console.error(
|
|
87
|
+
"2. Set the GITHUB_TOKEN environment variable with a personal access token",
|
|
88
|
+
);
|
|
89
|
+
console.error("3. Pass a token directly to the constructor");
|
|
90
|
+
console.error(
|
|
91
|
+
"\nTo create a token, visit: https://github.com/settings/tokens",
|
|
92
|
+
);
|
|
93
|
+
console.error(
|
|
94
|
+
"Required scopes: 'repo' for private repos or 'public_repo' for public repos\n",
|
|
95
|
+
);
|
|
96
|
+
throw new Error("GitHub authentication failed");
|
|
97
|
+
} else if (error.status === 403) {
|
|
98
|
+
console.error(
|
|
99
|
+
"\n⚠️ Insufficient permissions. You need admin access to the repository.",
|
|
100
|
+
);
|
|
101
|
+
console.error(
|
|
102
|
+
"Make sure your token has the 'repo' scope for private repos or 'public_repo' for public repos\n",
|
|
103
|
+
);
|
|
104
|
+
throw new Error("Insufficient GitHub permissions");
|
|
105
|
+
} else if (error.status === 404) {
|
|
106
|
+
console.error(`\n⚠️ Repository not found: ${owner}/${repo}`);
|
|
107
|
+
console.error(
|
|
108
|
+
"Make sure the repository exists and you have access to it\n",
|
|
109
|
+
);
|
|
110
|
+
throw new Error("GitHub repository not found");
|
|
111
|
+
} else {
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./secret";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import sodium from "libsodium-wrappers";
|
|
2
|
+
import { type Context, Resource } from "../resource";
|
|
3
|
+
import { createGitHubClient, verifyGitHubAuth } from "./client";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for creating or updating a GitHub Secret
|
|
7
|
+
*/
|
|
8
|
+
export interface GitHubSecretProps {
|
|
9
|
+
/**
|
|
10
|
+
* Repository owner (user or organization)
|
|
11
|
+
*/
|
|
12
|
+
owner: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Repository name
|
|
16
|
+
*/
|
|
17
|
+
repository: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Secret name
|
|
21
|
+
*/
|
|
22
|
+
name: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Secret value (will be stored securely on GitHub)
|
|
26
|
+
*/
|
|
27
|
+
value: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Optional GitHub API token (overrides environment variable)
|
|
31
|
+
*/
|
|
32
|
+
token?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Output returned after Secret creation/update
|
|
37
|
+
*/
|
|
38
|
+
export interface GitHubSecretOutput extends Omit<GitHubSecretProps, "value"> {
|
|
39
|
+
/**
|
|
40
|
+
* The ID of the resource
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Time at which the object was created/updated
|
|
46
|
+
*/
|
|
47
|
+
updatedAt: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Encrypt a value for GitHub using libsodium
|
|
52
|
+
* Based on GitHub's documentation for encrypting secrets
|
|
53
|
+
*
|
|
54
|
+
* @param value - The secret value to encrypt
|
|
55
|
+
* @param publicKey - The base64-encoded public key from GitHub
|
|
56
|
+
* @returns The base64-encoded encrypted value
|
|
57
|
+
*/
|
|
58
|
+
async function encryptValue(value: string, publicKey: string): Promise<string> {
|
|
59
|
+
// Initialize libsodium
|
|
60
|
+
await sodium.ready;
|
|
61
|
+
|
|
62
|
+
// Convert the public key from base64 to binary
|
|
63
|
+
const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
|
|
64
|
+
|
|
65
|
+
// Convert the message to a Uint8Array
|
|
66
|
+
const binMessage = sodium.from_string(value);
|
|
67
|
+
|
|
68
|
+
// Encrypt the message with the public key using libsodium's sealed box
|
|
69
|
+
const encryptedBin = sodium.crypto_box_seal(binMessage, binKey);
|
|
70
|
+
|
|
71
|
+
// Convert the encrypted message to base64
|
|
72
|
+
return sodium.to_base64(encryptedBin, sodium.base64_variants.ORIGINAL);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resource for managing GitHub repository secrets
|
|
77
|
+
*/
|
|
78
|
+
export class GitHubSecret extends Resource(
|
|
79
|
+
"github::Secret",
|
|
80
|
+
async (
|
|
81
|
+
ctx: Context<GitHubSecretOutput>,
|
|
82
|
+
props: GitHubSecretProps,
|
|
83
|
+
): Promise<GitHubSecretOutput | void> => {
|
|
84
|
+
// Create authenticated Octokit client - will automatically handle token resolution
|
|
85
|
+
const octokit = await createGitHubClient({ token: props.token });
|
|
86
|
+
|
|
87
|
+
// Verify authentication and permissions
|
|
88
|
+
if (!ctx.quiet) {
|
|
89
|
+
await verifyGitHubAuth(octokit, props.owner, props.repository);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (ctx.event === "delete") {
|
|
93
|
+
if (ctx.output?.id) {
|
|
94
|
+
try {
|
|
95
|
+
// Delete the secret
|
|
96
|
+
await octokit.rest.actions.deleteRepoSecret({
|
|
97
|
+
owner: props.owner,
|
|
98
|
+
repo: props.repository,
|
|
99
|
+
secret_name: props.name,
|
|
100
|
+
});
|
|
101
|
+
} catch (error: any) {
|
|
102
|
+
// Ignore 404 errors (secret already deleted)
|
|
103
|
+
if (error.status === 404) {
|
|
104
|
+
console.log("Secret doesn't exist, ignoring");
|
|
105
|
+
} else {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Return void (a deleted resource has no content)
|
|
112
|
+
return undefined;
|
|
113
|
+
} else {
|
|
114
|
+
try {
|
|
115
|
+
// Get the repository's public key for encrypting secrets
|
|
116
|
+
const { data: publicKey } = await octokit.rest.actions.getRepoPublicKey(
|
|
117
|
+
{
|
|
118
|
+
owner: props.owner,
|
|
119
|
+
repo: props.repository,
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Encrypt the secret value using libsodium
|
|
124
|
+
const encryptedValue = await encryptValue(props.value, publicKey.key);
|
|
125
|
+
|
|
126
|
+
// Create or update the secret with the encrypted value and key_id
|
|
127
|
+
const response = await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
128
|
+
owner: props.owner,
|
|
129
|
+
repo: props.repository,
|
|
130
|
+
secret_name: props.name,
|
|
131
|
+
encrypted_value: encryptedValue,
|
|
132
|
+
key_id: publicKey.key_id,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Log success
|
|
136
|
+
if (!ctx.quiet) {
|
|
137
|
+
console.log(`Successfully created/updated secret: ${props.name}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// GitHub doesn't return the secret details on create/update, so we need to construct it
|
|
141
|
+
const output: GitHubSecretOutput = {
|
|
142
|
+
id: `${props.owner}/${props.repository}/${props.name}`,
|
|
143
|
+
owner: props.owner,
|
|
144
|
+
repository: props.repository,
|
|
145
|
+
name: props.name,
|
|
146
|
+
token: props.token,
|
|
147
|
+
updatedAt: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return output;
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
if (
|
|
153
|
+
error.status === 403 &&
|
|
154
|
+
error.message?.includes("Must have admin rights")
|
|
155
|
+
) {
|
|
156
|
+
console.error(
|
|
157
|
+
"\n⚠️ Error creating/updating GitHub secret: You must have admin rights to the repository.",
|
|
158
|
+
);
|
|
159
|
+
console.error(
|
|
160
|
+
"Make sure your GitHub token has the required permissions (repo scope for private repos).\n",
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
console.error(
|
|
164
|
+
"Error creating/updating GitHub secret:",
|
|
165
|
+
error.message,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
) {}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility function for exponential backoff retry
|
|
3
|
+
* Retries an operation with exponential backoff when a retryable error occurs
|
|
4
|
+
*
|
|
5
|
+
* @param operation The async operation to execute and potentially retry
|
|
6
|
+
* @param isRetryable Function to determine if an error should trigger a retry
|
|
7
|
+
* @param maxAttempts Maximum number of attempts before giving up
|
|
8
|
+
* @param initialDelayMs Initial delay in milliseconds before first retry
|
|
9
|
+
* @returns Result of the operation
|
|
10
|
+
* @throws The last error encountered if all retries fail
|
|
11
|
+
*/
|
|
12
|
+
export async function withExponentialBackoff<T>(
|
|
13
|
+
operation: () => Promise<T>,
|
|
14
|
+
isRetryable: (error: any) => boolean,
|
|
15
|
+
maxAttempts = 5,
|
|
16
|
+
initialDelayMs = 100,
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
let attempt = 0;
|
|
19
|
+
let delay = initialDelayMs;
|
|
20
|
+
|
|
21
|
+
while (true) {
|
|
22
|
+
try {
|
|
23
|
+
return await operation();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
attempt++;
|
|
26
|
+
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Exponential backoff with jitter
|
|
31
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
33
|
+
delay *= 2; // Double the delay for next attempt
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|