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.
Files changed (86) hide show
  1. package/README.md +12 -2
  2. package/lib/aws/account-id.d.ts +5 -0
  3. package/lib/aws/account-id.d.ts.map +1 -0
  4. package/lib/aws/account-id.js +10 -0
  5. package/lib/aws/account-id.js.map +1 -0
  6. package/lib/aws/index.d.ts +1 -0
  7. package/lib/aws/index.d.ts.map +1 -1
  8. package/lib/aws/index.js +1 -0
  9. package/lib/aws/index.js.map +1 -1
  10. package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
  11. package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
  12. package/lib/aws/oidc/github-oidc-provider.js +22 -0
  13. package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
  14. package/lib/aws/oidc/index.d.ts +3 -0
  15. package/lib/aws/oidc/index.d.ts.map +1 -0
  16. package/lib/aws/oidc/index.js +3 -0
  17. package/lib/aws/oidc/index.js.map +1 -0
  18. package/lib/aws/oidc/oidc-provider.d.ts +60 -0
  19. package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
  20. package/lib/aws/oidc/oidc-provider.js +140 -0
  21. package/lib/aws/oidc/oidc-provider.js.map +1 -0
  22. package/lib/aws/queue.d.ts +24 -0
  23. package/lib/aws/queue.d.ts.map +1 -1
  24. package/lib/aws/queue.js +6 -1
  25. package/lib/aws/queue.js.map +1 -1
  26. package/lib/aws/role.d.ts.map +1 -1
  27. package/lib/aws/role.js +49 -1
  28. package/lib/aws/role.js.map +1 -1
  29. package/lib/aws/ses.d.ts.map +1 -1
  30. package/lib/aws/ses.js +20 -28
  31. package/lib/aws/ses.js.map +1 -1
  32. package/lib/aws/table.d.ts.map +1 -1
  33. package/lib/aws/table.js +95 -73
  34. package/lib/aws/table.js.map +1 -1
  35. package/lib/cloudflare/bindings.d.ts +7 -2
  36. package/lib/cloudflare/bindings.d.ts.map +1 -1
  37. package/lib/cloudflare/durable-object-namespace.d.ts +2 -5
  38. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  39. package/lib/cloudflare/durable-object-namespace.js +1 -6
  40. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  41. package/lib/cloudflare/kv-namespace.d.ts +3 -0
  42. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  43. package/lib/cloudflare/kv-namespace.js +4 -0
  44. package/lib/cloudflare/kv-namespace.js.map +1 -1
  45. package/lib/cloudflare/static-site.d.ts.map +1 -1
  46. package/lib/cloudflare/static-site.js +12 -30
  47. package/lib/cloudflare/static-site.js.map +1 -1
  48. package/lib/cloudflare/worker.d.ts +6 -3
  49. package/lib/cloudflare/worker.d.ts.map +1 -1
  50. package/lib/cloudflare/worker.js +41 -15
  51. package/lib/cloudflare/worker.js.map +1 -1
  52. package/lib/github/client.d.ts +34 -0
  53. package/lib/github/client.d.ts.map +1 -0
  54. package/lib/github/client.js +91 -0
  55. package/lib/github/client.js.map +1 -0
  56. package/lib/github/index.d.ts +2 -0
  57. package/lib/github/index.d.ts.map +1 -0
  58. package/lib/github/index.js +2 -0
  59. package/lib/github/index.js.map +1 -0
  60. package/lib/github/secret.d.ts +46 -0
  61. package/lib/github/secret.d.ts.map +1 -0
  62. package/lib/github/secret.js +103 -0
  63. package/lib/github/secret.js.map +1 -0
  64. package/lib/utils/retry.d.ts +13 -0
  65. package/lib/utils/retry.d.ts.map +1 -0
  66. package/lib/utils/retry.js +31 -0
  67. package/lib/utils/retry.js.map +1 -0
  68. package/package.json +10 -2
  69. package/src/aws/account-id.ts +11 -0
  70. package/src/aws/index.ts +1 -0
  71. package/src/aws/oidc/github-oidc-provider.ts +34 -0
  72. package/src/aws/oidc/index.ts +2 -0
  73. package/src/aws/oidc/oidc-provider.ts +253 -0
  74. package/src/aws/queue.ts +23 -1
  75. package/src/aws/role.ts +69 -0
  76. package/src/aws/ses.ts +27 -31
  77. package/src/aws/table.ts +113 -82
  78. package/src/cloudflare/bindings.ts +8 -2
  79. package/src/cloudflare/durable-object-namespace.ts +4 -10
  80. package/src/cloudflare/kv-namespace.ts +11 -0
  81. package/src/cloudflare/static-site.ts +13 -32
  82. package/src/cloudflare/worker.ts +52 -22
  83. package/src/github/client.ts +115 -0
  84. package/src/github/index.ts +1 -0
  85. package/src/github/secret.ts +172 -0
  86. 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
+ }