alchemy 2.0.0-beta.15 → 2.0.0-beta.17

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 (104) hide show
  1. package/bin/alchemy.js +1661 -710
  2. package/bin/alchemy.js.map +1 -1
  3. package/bin/alchemy.ts +2 -0
  4. package/bin/cli.js +9 -7
  5. package/bin/commands/deploy.ts +11 -2
  6. package/bin/commands/login.ts +6 -3
  7. package/bin/commands/profile.ts +106 -0
  8. package/bin/commands/state.ts +91 -1
  9. package/lib/Auth/Credentials.d.ts +6 -0
  10. package/lib/Auth/Credentials.d.ts.map +1 -1
  11. package/lib/Auth/Credentials.js +12 -1
  12. package/lib/Auth/Credentials.js.map +1 -1
  13. package/lib/Auth/Profile.d.ts +5 -0
  14. package/lib/Auth/Profile.d.ts.map +1 -1
  15. package/lib/Auth/Profile.js +11 -0
  16. package/lib/Auth/Profile.js.map +1 -1
  17. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +4 -4
  18. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
  19. package/lib/Cloudflare/ApiToken/AccountApiToken.js +16 -11
  20. package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
  21. package/lib/Cloudflare/ApiToken/Common.d.ts +11 -12
  22. package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -1
  23. package/lib/Cloudflare/ApiToken/Common.js +4 -14
  24. package/lib/Cloudflare/ApiToken/Common.js.map +1 -1
  25. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +2 -3
  26. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
  27. package/lib/Cloudflare/ApiToken/UserApiToken.js +4 -4
  28. package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -1
  29. package/lib/Cloudflare/Artifacts/Artifacts.d.ts +101 -0
  30. package/lib/Cloudflare/Artifacts/Artifacts.d.ts.map +1 -0
  31. package/lib/Cloudflare/Artifacts/Artifacts.js +92 -0
  32. package/lib/Cloudflare/Artifacts/Artifacts.js.map +1 -0
  33. package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts +83 -0
  34. package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts.map +1 -0
  35. package/lib/Cloudflare/Artifacts/ArtifactsBinding.js +65 -0
  36. package/lib/Cloudflare/Artifacts/ArtifactsBinding.js.map +1 -0
  37. package/lib/Cloudflare/Artifacts/index.d.ts +3 -0
  38. package/lib/Cloudflare/Artifacts/index.d.ts.map +1 -0
  39. package/lib/Cloudflare/Artifacts/index.js +3 -0
  40. package/lib/Cloudflare/Artifacts/index.js.map +1 -0
  41. package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
  42. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  43. package/lib/Cloudflare/Providers.js +3 -1
  44. package/lib/Cloudflare/Providers.js.map +1 -1
  45. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  46. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  47. package/lib/Cloudflare/Workers/InferEnv.js +0 -1
  48. package/lib/Cloudflare/Workers/InferEnv.js.map +1 -1
  49. package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
  50. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  51. package/lib/Cloudflare/Workers/Worker.js +25 -18
  52. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  53. package/lib/Cloudflare/index.d.ts +1 -0
  54. package/lib/Cloudflare/index.d.ts.map +1 -1
  55. package/lib/Cloudflare/index.js +1 -0
  56. package/lib/Cloudflare/index.js.map +1 -1
  57. package/lib/GitHub/AuthProvider.d.ts +37 -0
  58. package/lib/GitHub/AuthProvider.d.ts.map +1 -0
  59. package/lib/GitHub/AuthProvider.js +154 -0
  60. package/lib/GitHub/AuthProvider.js.map +1 -0
  61. package/lib/GitHub/Comment.d.ts +2 -1
  62. package/lib/GitHub/Comment.d.ts.map +1 -1
  63. package/lib/GitHub/Comment.js +1 -0
  64. package/lib/GitHub/Comment.js.map +1 -1
  65. package/lib/GitHub/Credentials.d.ts +29 -0
  66. package/lib/GitHub/Credentials.d.ts.map +1 -0
  67. package/lib/GitHub/Credentials.js +54 -0
  68. package/lib/GitHub/Credentials.js.map +1 -0
  69. package/lib/GitHub/Providers.d.ts +12 -1
  70. package/lib/GitHub/Providers.d.ts.map +1 -1
  71. package/lib/GitHub/Providers.js +14 -3
  72. package/lib/GitHub/Providers.js.map +1 -1
  73. package/lib/GitHub/Secret.d.ts +8 -10
  74. package/lib/GitHub/Secret.d.ts.map +1 -1
  75. package/lib/GitHub/Secret.js +16 -14
  76. package/lib/GitHub/Secret.js.map +1 -1
  77. package/lib/GitHub/Variable.d.ts +8 -10
  78. package/lib/GitHub/Variable.d.ts.map +1 -1
  79. package/lib/GitHub/Variable.js +12 -13
  80. package/lib/GitHub/Variable.js.map +1 -1
  81. package/lib/GitHub/index.d.ts +2 -0
  82. package/lib/GitHub/index.d.ts.map +1 -1
  83. package/lib/GitHub/index.js +2 -0
  84. package/lib/GitHub/index.js.map +1 -1
  85. package/package.json +4 -4
  86. package/src/Auth/Credentials.ts +17 -1
  87. package/src/Auth/Profile.ts +15 -0
  88. package/src/Cloudflare/ApiToken/AccountApiToken.ts +25 -14
  89. package/src/Cloudflare/ApiToken/Common.ts +12 -19
  90. package/src/Cloudflare/ApiToken/UserApiToken.ts +6 -8
  91. package/src/Cloudflare/Artifacts/Artifacts.ts +139 -0
  92. package/src/Cloudflare/Artifacts/ArtifactsBinding.ts +188 -0
  93. package/src/Cloudflare/Artifacts/index.ts +2 -0
  94. package/src/Cloudflare/Providers.ts +3 -0
  95. package/src/Cloudflare/Workers/InferEnv.ts +5 -4
  96. package/src/Cloudflare/Workers/Worker.ts +12 -1
  97. package/src/Cloudflare/index.ts +1 -0
  98. package/src/GitHub/AuthProvider.ts +309 -0
  99. package/src/GitHub/Comment.ts +4 -1
  100. package/src/GitHub/Credentials.ts +99 -0
  101. package/src/GitHub/Providers.ts +29 -3
  102. package/src/GitHub/Secret.ts +22 -24
  103. package/src/GitHub/Variable.ts +16 -23
  104. package/src/GitHub/index.ts +2 -0
@@ -0,0 +1,309 @@
1
+ import * as Console from "effect/Console";
2
+ import * as Effect from "effect/Effect";
3
+ import * as FileSystem from "effect/FileSystem";
4
+ import * as Match from "effect/Match";
5
+ import * as Redacted from "effect/Redacted";
6
+ import * as Stream from "effect/Stream";
7
+ import * as ChildProcess from "effect/unstable/process/ChildProcess";
8
+ import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
9
+ import {
10
+ AuthError,
11
+ AuthProviderLayer,
12
+ type ConfigureContext,
13
+ } from "../Auth/AuthProvider.ts";
14
+ import {
15
+ deleteCredentials,
16
+ displayRedacted,
17
+ readCredentials,
18
+ writeCredentials,
19
+ } from "../Auth/Credentials.ts";
20
+ import { getEnvRedacted, retryOnce } from "../Auth/Env.ts";
21
+ import * as Clank from "../Util/Clank.ts";
22
+
23
+ const options: Array<{
24
+ value: GitHubAuthConfig["method"];
25
+ label: string;
26
+ hint?: string;
27
+ }> = [
28
+ {
29
+ value: "gh-cli",
30
+ label: "GitHub CLI",
31
+ hint: "delegate to `gh auth token` (run `gh auth login` first)",
32
+ },
33
+ {
34
+ value: "env",
35
+ label: "Environment Variables",
36
+ hint: "GITHUB_ACCESS_TOKEN or GITHUB_TOKEN",
37
+ },
38
+ {
39
+ value: "stored",
40
+ label: "Personal Access Token",
41
+ hint: "enter PAT interactively, stored in ~/.alchemy/credentials",
42
+ },
43
+ ];
44
+
45
+ export type GitHubAuthConfig =
46
+ | { method: "env" }
47
+ | { method: "stored" }
48
+ | { method: "gh-cli" };
49
+
50
+ export interface GitHubStoredCredentials {
51
+ type: "pat";
52
+ token: string;
53
+ }
54
+
55
+ export interface GitHubResolvedCredentials {
56
+ type: "token";
57
+ token: Redacted.Redacted<string>;
58
+ source: { type: GitHubAuthConfig["method"]; details?: string };
59
+ }
60
+
61
+ export const GITHUB_AUTH_PROVIDER_NAME = "GitHub";
62
+
63
+ /**
64
+ * Layer that registers the GitHub {@link AuthProvider} into the
65
+ * {@link AuthProviders} registry when built. Include this in the GitHub
66
+ * `providers()` layer so `alchemy login` can discover it.
67
+ *
68
+ * Supported methods:
69
+ * - `gh-cli`: shells out to `gh auth token` (recommended).
70
+ * - `env`: reads `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN`.
71
+ * - `stored`: prompts for a PAT and writes it to `~/.alchemy/credentials`.
72
+ *
73
+ * Browser/device OAuth is intentionally not implemented: GitHub's
74
+ * OAuth App flow requires a `client_secret` we cannot ship, and
75
+ * device flow is exactly what `gh auth login` already does.
76
+ */
77
+ export const GitHubAuth = AuthProviderLayer<
78
+ GitHubAuthConfig,
79
+ GitHubResolvedCredentials
80
+ >()(GITHUB_AUTH_PROVIDER_NAME, {
81
+ configure: (profileName, ctx) => configureCredentials(profileName, ctx),
82
+ logout: (profileName, config) => logout(profileName, config),
83
+ login: (profileName, config) => login(profileName, config),
84
+ prettyPrint: (profileName, config) => prettyPrint(profileName, config),
85
+ read: (profileName, config) => resolveCredentials(profileName, config),
86
+ });
87
+
88
+ const resolveCredentials = (
89
+ profileName: string,
90
+ config: GitHubAuthConfig,
91
+ ): Effect.Effect<
92
+ GitHubResolvedCredentials,
93
+ AuthError,
94
+ FileSystem.FileSystem | ChildProcessSpawner
95
+ > =>
96
+ Match.value(config).pipe(
97
+ Match.when(
98
+ { method: "env" },
99
+ Effect.fnUntraced(function* () {
100
+ const access = yield* getEnvRedacted("GITHUB_ACCESS_TOKEN");
101
+ if (access) {
102
+ return {
103
+ type: "token" as const,
104
+ token: access,
105
+ source: {
106
+ type: "env" as const,
107
+ details: "GITHUB_ACCESS_TOKEN",
108
+ },
109
+ };
110
+ }
111
+ const token = yield* getEnvRedacted("GITHUB_TOKEN");
112
+ if (token) {
113
+ return {
114
+ type: "token" as const,
115
+ token,
116
+ source: { type: "env" as const, details: "GITHUB_TOKEN" },
117
+ };
118
+ }
119
+ return yield* new AuthError({
120
+ message:
121
+ "GitHub env credentials not found. Set GITHUB_ACCESS_TOKEN or GITHUB_TOKEN.",
122
+ });
123
+ }),
124
+ ),
125
+ Match.when({ method: "stored" }, () =>
126
+ readCredentials<GitHubStoredCredentials>(profileName, "gh-stored").pipe(
127
+ Effect.flatMap((creds) =>
128
+ creds == null
129
+ ? Effect.fail(
130
+ new AuthError({
131
+ message:
132
+ "GitHub stored credentials not found. Run: alchemy login --configure",
133
+ }),
134
+ )
135
+ : Effect.succeed({
136
+ type: "token" as const,
137
+ token: Redacted.make(creds.token),
138
+ source: { type: "stored" as const },
139
+ }),
140
+ ),
141
+ ),
142
+ ),
143
+ Match.when({ method: "gh-cli" }, () =>
144
+ ghCliToken().pipe(
145
+ Effect.map((token) => ({
146
+ type: "token" as const,
147
+ token: Redacted.make(token),
148
+ source: { type: "gh-cli" as const },
149
+ })),
150
+ ),
151
+ ),
152
+ Match.exhaustive,
153
+ );
154
+
155
+ const logout = (profileName: string, config: GitHubAuthConfig) =>
156
+ Match.value(config).pipe(
157
+ Match.when({ method: "env" }, () => Effect.void),
158
+ Match.when({ method: "gh-cli" }, () => Effect.void),
159
+ Match.when({ method: "stored" }, () =>
160
+ deleteCredentials(profileName, "gh-stored").pipe(
161
+ Effect.andThen(Clank.success("GitHub: stored credentials removed")),
162
+ ),
163
+ ),
164
+ Match.exhaustive,
165
+ );
166
+
167
+ const login = (profileName: string, config: GitHubAuthConfig) =>
168
+ Match.value(config)
169
+ .pipe(
170
+ Match.when({ method: "env" }, () => Effect.void),
171
+ Match.when({ method: "gh-cli" }, () =>
172
+ ghCliToken().pipe(
173
+ Effect.tap(() =>
174
+ Clank.success("GitHub: gh CLI authentication available."),
175
+ ),
176
+ Effect.asVoid,
177
+ ),
178
+ ),
179
+ Match.when({ method: "stored" }, () =>
180
+ readCredentials<GitHubStoredCredentials>(profileName, "gh-stored").pipe(
181
+ Effect.flatMap((creds) =>
182
+ creds == null ? loginStored(profileName) : Effect.void,
183
+ ),
184
+ ),
185
+ ),
186
+ Match.exhaustive,
187
+ )
188
+ .pipe(
189
+ Effect.mapError(
190
+ (e) => new AuthError({ message: "login failed", cause: e }),
191
+ ),
192
+ );
193
+
194
+ const configureCredentials = (profileName: string, ctx: ConfigureContext) =>
195
+ Effect.gen(function* () {
196
+ if (ctx.ci) {
197
+ return { method: "env" as const };
198
+ }
199
+ return yield* configureInteractive(profileName);
200
+ }).pipe(
201
+ Effect.mapError(
202
+ (e) =>
203
+ new AuthError({
204
+ message: "failed to configure credentials",
205
+ cause: e,
206
+ }),
207
+ ),
208
+ );
209
+
210
+ const configureInteractive = (profileName: string) =>
211
+ Clank.select({
212
+ message: "GitHub authentication method",
213
+ options,
214
+ }).pipe(
215
+ Effect.flatMap((method) =>
216
+ Match.value(method).pipe(
217
+ Match.when("env", () => Effect.succeed({ method: "env" as const })),
218
+ Match.when("gh-cli", () =>
219
+ ghCliToken().pipe(
220
+ Effect.as({ method: "gh-cli" as const }),
221
+ Effect.mapError(
222
+ (e) =>
223
+ new AuthError({
224
+ message: `gh CLI not available: ${e.message}`,
225
+ cause: e,
226
+ }),
227
+ ),
228
+ ),
229
+ ),
230
+ Match.when("stored", () => loginStored(profileName)),
231
+ Match.exhaustive,
232
+ ),
233
+ ),
234
+ );
235
+
236
+ const loginStored = Effect.fnUntraced(function* (profileName: string) {
237
+ const token = yield* Clank.password({
238
+ message:
239
+ "GitHub Personal Access Token (needs `repo` scope; `workflow` for Actions)",
240
+ validate: (v) => (v.length === 0 ? "Required" : undefined),
241
+ }).pipe(retryOnce);
242
+
243
+ yield* writeCredentials<GitHubStoredCredentials>(profileName, "gh-stored", {
244
+ type: "pat",
245
+ token,
246
+ });
247
+ yield* Clank.success("GitHub: credentials saved.");
248
+ return { method: "stored" as const };
249
+ });
250
+
251
+ class GhCliError extends Error {
252
+ readonly _tag = "GhCliError";
253
+ }
254
+
255
+ const ghCliToken = () =>
256
+ Effect.gen(function* () {
257
+ const handle = yield* ChildProcess.make("gh", ["auth", "token"], {
258
+ shell: false,
259
+ });
260
+ const [exitCode, stdout, stderr] = yield* Effect.all(
261
+ [
262
+ handle.exitCode,
263
+ Stream.mkString(Stream.decodeText(handle.stdout)),
264
+ Stream.mkString(Stream.decodeText(handle.stderr)),
265
+ ],
266
+ { concurrency: 3 },
267
+ );
268
+ if (exitCode !== 0) {
269
+ return yield* Effect.fail(
270
+ new GhCliError(
271
+ `gh auth token exited with ${exitCode}: ${stderr.trim() || stdout.trim()}`,
272
+ ),
273
+ );
274
+ }
275
+ const token = stdout.trim();
276
+ if (!token) {
277
+ return yield* Effect.fail(
278
+ new GhCliError("gh auth token returned empty output"),
279
+ );
280
+ }
281
+ return token;
282
+ }).pipe(
283
+ Effect.scoped,
284
+ Effect.mapError((e) =>
285
+ e instanceof GhCliError
286
+ ? new AuthError({ message: e.message, cause: e })
287
+ : new AuthError({
288
+ message:
289
+ "Could not invoke `gh`. Install GitHub CLI from https://cli.github.com/ and run `gh auth login`.",
290
+ cause: e,
291
+ }),
292
+ ),
293
+ );
294
+
295
+ const prettyPrint = (profileName: string, config: GitHubAuthConfig) =>
296
+ resolveCredentials(profileName, config).pipe(
297
+ Effect.tap((creds) => {
298
+ const sourceStr = creds.source.details
299
+ ? `${creds.source.type} - ${creds.source.details}`
300
+ : creds.source.type;
301
+ return Effect.all([
302
+ Console.log(` token: ${displayRedacted(creds.token, 6)}`),
303
+ Console.log(` source: ${sourceStr}`),
304
+ ]);
305
+ }),
306
+ Effect.catch((e) =>
307
+ Console.error(` Failed to retrieve credentials: ${e}`),
308
+ ),
309
+ );
@@ -3,6 +3,7 @@ import * as Effect from "effect/Effect";
3
3
  import * as Provider from "../Provider.ts";
4
4
  import { Resource } from "../Resource.ts";
5
5
  import { dedent } from "../Util/dedent.ts";
6
+ import * as GitHub from "./Providers.ts";
6
7
 
7
8
  export interface CommentProps {
8
9
  /**
@@ -62,7 +63,9 @@ export interface Comment extends Resource<
62
63
  * ISO-8601 timestamp of the last update.
63
64
  */
64
65
  updatedAt: string;
65
- }
66
+ },
67
+ never,
68
+ GitHub.Providers
66
69
  > {}
67
70
 
68
71
  /**
@@ -0,0 +1,99 @@
1
+ import { Octokit } from "@octokit/rest";
2
+ import * as Config from "effect/Config";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Redacted from "effect/Redacted";
7
+ import { AuthError, getAuthProvider } from "../Auth/AuthProvider.ts";
8
+ import { ALCHEMY_PROFILE, loadOrConfigure } from "../Auth/Profile.ts";
9
+ import {
10
+ GITHUB_AUTH_PROVIDER_NAME,
11
+ type GitHubAuthConfig,
12
+ type GitHubResolvedCredentials,
13
+ } from "./AuthProvider.ts";
14
+
15
+ export interface GitHubCredentialsService {
16
+ readonly token: Redacted.Redacted<string>;
17
+ readonly octokit: () => Octokit;
18
+ }
19
+
20
+ export class GitHubCredentials extends Context.Service<
21
+ GitHubCredentials,
22
+ GitHubCredentialsService
23
+ >()("GitHub::Credentials") {}
24
+
25
+ const make = (token: Redacted.Redacted<string>): GitHubCredentialsService => ({
26
+ token,
27
+ octokit: () => new Octokit({ auth: Redacted.value(token) }),
28
+ });
29
+
30
+ /**
31
+ * Build a `GitHubCredentials` layer from a literal token. Useful for
32
+ * tests or when callers already have a PAT in hand.
33
+ */
34
+ export const fromToken = (token: string | Redacted.Redacted<string>) =>
35
+ Layer.succeed(
36
+ GitHubCredentials,
37
+ make(typeof token === "string" ? Redacted.make(token) : token),
38
+ );
39
+
40
+ /**
41
+ * Build a `GitHubCredentials` layer that reads the token from
42
+ * `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` at layer build time.
43
+ */
44
+ export const fromEnv = () =>
45
+ Layer.effect(
46
+ GitHubCredentials,
47
+ Effect.gen(function* () {
48
+ const access = yield* Config.redacted("GITHUB_ACCESS_TOKEN").pipe(
49
+ Config.option,
50
+ );
51
+ const token = yield* Config.redacted("GITHUB_TOKEN").pipe(Config.option);
52
+ const value =
53
+ access._tag === "Some"
54
+ ? access.value
55
+ : token._tag === "Some"
56
+ ? token.value
57
+ : undefined;
58
+ if (value == null) {
59
+ return yield* new AuthError({
60
+ message:
61
+ "GitHub credentials not found. Set GITHUB_ACCESS_TOKEN or GITHUB_TOKEN.",
62
+ });
63
+ }
64
+ return make(value);
65
+ }).pipe(Effect.orDie),
66
+ );
67
+
68
+ /**
69
+ * Build a `GitHubCredentials` layer that resolves a token via the
70
+ * Alchemy AuthProvider for the configured profile (defaults to
71
+ * `default`, overridable with `ALCHEMY_PROFILE`).
72
+ */
73
+ export const fromAuthProvider = () =>
74
+ Layer.effect(
75
+ GitHubCredentials,
76
+ Effect.gen(function* () {
77
+ const auth = yield* getAuthProvider<
78
+ GitHubAuthConfig,
79
+ GitHubResolvedCredentials
80
+ >(GITHUB_AUTH_PROVIDER_NAME);
81
+ const profileName = yield* ALCHEMY_PROFILE;
82
+ const ci = yield* Config.boolean("CI").pipe(Config.withDefault(false));
83
+ const ctx = yield* Effect.context<never>();
84
+
85
+ return yield* loadOrConfigure(auth, profileName, { ci }).pipe(
86
+ Effect.flatMap((config) =>
87
+ auth.read(profileName, config as GitHubAuthConfig),
88
+ ),
89
+ Effect.map((creds) => make(creds.token)),
90
+ Effect.mapError(
91
+ (e) =>
92
+ new AuthError({
93
+ message: `Failed to resolve GitHub credentials for profile '${profileName}': ${(e as { message?: string }).message ?? String(e)}`,
94
+ }),
95
+ ),
96
+ Effect.provide(ctx),
97
+ );
98
+ }).pipe(Effect.orDie),
99
+ );
@@ -1,6 +1,32 @@
1
1
  import * as Layer from "effect/Layer";
2
- import { SecretProvider } from "./Secret.ts";
3
- import { VariableProvider } from "./Variable.ts";
2
+ import * as Provider from "../Provider.ts";
3
+ import { GitHubAuth } from "./AuthProvider.ts";
4
+ import { Comment, CommentProvider } from "./Comment.ts";
5
+ import * as Credentials from "./Credentials.ts";
6
+ import { Secret, SecretProvider } from "./Secret.ts";
7
+ import { Variable, VariableProvider } from "./Variable.ts";
4
8
 
9
+ export { GitHubCredentials } from "./Credentials.ts";
10
+
11
+ export class Providers extends Provider.ProviderCollection<Providers>()(
12
+ "GitHub",
13
+ ) {}
14
+
15
+ export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
16
+
17
+ /**
18
+ * GitHub providers (Secret, Variable) plus the GitHub AuthProvider that
19
+ * `alchemy login` discovers.
20
+ */
5
21
  export const providers = () =>
6
- Layer.mergeAll(SecretProvider(), VariableProvider());
22
+ Layer.effect(
23
+ Providers,
24
+ Provider.collection([Comment, Secret, Variable]),
25
+ ).pipe(
26
+ Layer.provide(
27
+ Layer.mergeAll(CommentProvider(), SecretProvider(), VariableProvider()),
28
+ ),
29
+ Layer.provideMerge(Credentials.fromAuthProvider()),
30
+ Layer.provideMerge(GitHubAuth),
31
+ Layer.orDie,
32
+ );
@@ -1,8 +1,10 @@
1
- import { Octokit } from "@octokit/rest";
1
+ import type { Octokit } from "@octokit/rest";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Redacted from "effect/Redacted";
4
4
  import * as Provider from "../Provider.ts";
5
5
  import { Resource } from "../Resource.ts";
6
+ import { GitHubCredentials } from "./Credentials.ts";
7
+ import type * as GitHub from "./Providers.ts";
6
8
 
7
9
  export interface SecretProps {
8
10
  /**
@@ -31,12 +33,6 @@ export interface SecretProps {
31
33
  * GitHub Actions environment instead of the whole repository.
32
34
  */
33
35
  environment?: string;
34
-
35
- /**
36
- * GitHub API token. If not provided, falls back to
37
- * `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
38
- */
39
- token?: string;
40
36
  }
41
37
 
42
38
  export interface Secret extends Resource<
@@ -47,7 +43,9 @@ export interface Secret extends Resource<
47
43
  * ISO-8601 timestamp of the last update.
48
44
  */
49
45
  updatedAt: string;
50
- }
46
+ },
47
+ never,
48
+ GitHub.Providers
51
49
  > {}
52
50
 
53
51
  /**
@@ -58,9 +56,10 @@ export interface Secret extends Resource<
58
56
  * key via `libsodium` before being stored. The resource is idempotent —
59
57
  * calling it with the same name will update the secret value in place.
60
58
  *
61
- * Authentication is resolved in order: explicit `token` prop,
62
- * `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
63
- * `repo` scope for private repositories or `public_repo` for public ones.
59
+ * Authentication is resolved via the `GitHubCredentials` service supplied
60
+ * by `GitHub.providers()` (which uses the Alchemy AuthProvider — env,
61
+ * stored PAT, `gh` CLI, or OAuth). The token needs `repo` scope for
62
+ * private repositories or `public_repo` for public ones.
64
63
  *
65
64
  * @section Repository Secrets
66
65
  * Store secrets accessible to all GitHub Actions workflows in the
@@ -130,21 +129,20 @@ export interface Secret extends Resource<
130
129
  */
131
130
  export const Secret = Resource<Secret>("GitHub.Secret");
132
131
 
133
- function resolveToken(props: SecretProps): string | undefined {
134
- return (
135
- props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
136
- );
137
- }
138
-
139
- function createClient(props: SecretProps): Octokit {
140
- return new Octokit({ auth: resolveToken(props) });
141
- }
132
+ const getOctokit = Effect.gen(function* () {
133
+ const creds = yield* GitHubCredentials;
134
+ return creds.octokit();
135
+ });
142
136
 
143
137
  async function encryptValue(
144
138
  plaintext: string,
145
139
  publicKey: string,
146
140
  ): Promise<string> {
147
- const sodium = await import("libsodium-wrappers");
141
+ const mod = await import("libsodium-wrappers");
142
+ // Bun/ESM interop: the actual sodium API lives on `.default` when the
143
+ // CJS module is wrapped, but is the module itself under other loaders.
144
+ const sodium: typeof import("libsodium-wrappers") =
145
+ (mod as any).default ?? mod;
148
146
  await sodium.ready;
149
147
  const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
150
148
  const binMessage = sodium.from_string(plaintext);
@@ -155,13 +153,13 @@ async function encryptValue(
155
153
  export const SecretProvider = () =>
156
154
  Provider.succeed(Secret, {
157
155
  create: Effect.fn(function* ({ news }) {
158
- const octokit = createClient(news);
156
+ const octokit = yield* getOctokit;
159
157
  yield* upsertSecret(octokit, news);
160
158
  return { updatedAt: new Date().toISOString() };
161
159
  }),
162
160
 
163
161
  update: Effect.fn(function* ({ news, olds }) {
164
- const octokit = createClient(news);
162
+ const octokit = yield* getOctokit;
165
163
 
166
164
  const wasEnv = !!olds.environment;
167
165
  const isEnv = !!news.environment;
@@ -174,7 +172,7 @@ export const SecretProvider = () =>
174
172
  }),
175
173
 
176
174
  delete: Effect.fn(function* ({ olds }) {
177
- const octokit = createClient(olds);
175
+ const octokit = yield* getOctokit;
178
176
  yield* deleteSecret(octokit, olds);
179
177
  }),
180
178
  });
@@ -1,7 +1,8 @@
1
- import { Octokit } from "@octokit/rest";
2
1
  import * as Effect from "effect/Effect";
3
2
  import * as Provider from "../Provider.ts";
4
3
  import { Resource } from "../Resource.ts";
4
+ import { GitHubCredentials } from "./Credentials.ts";
5
+ import type * as GitHub from "./Providers.ts";
5
6
 
6
7
  export interface VariableProps {
7
8
  /**
@@ -23,12 +24,6 @@ export interface VariableProps {
23
24
  * Variable value.
24
25
  */
25
26
  value: string;
26
-
27
- /**
28
- * GitHub API token. If not provided, falls back to
29
- * `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
30
- */
31
- token?: string;
32
27
  }
33
28
 
34
29
  export interface Variable extends Resource<
@@ -39,7 +34,9 @@ export interface Variable extends Resource<
39
34
  * ISO-8601 timestamp of the last update.
40
35
  */
41
36
  updatedAt: string;
42
- }
37
+ },
38
+ never,
39
+ GitHub.Providers
43
40
  > {}
44
41
 
45
42
  /**
@@ -51,9 +48,10 @@ export interface Variable extends Resource<
51
48
  * labels, or feature flags. For sensitive values, use `GitHub.Secret`
52
49
  * instead.
53
50
  *
54
- * Authentication is resolved in order: explicit `token` prop,
55
- * `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
56
- * `repo` scope for private repositories or `public_repo` for public ones.
51
+ * Authentication is resolved via the `GitHubCredentials` service supplied
52
+ * by `GitHub.providers()` (which uses the Alchemy AuthProvider — env,
53
+ * stored PAT, `gh` CLI, or OAuth). The token needs `repo` scope for
54
+ * private repositories or `public_repo` for public ones.
57
55
  *
58
56
  * @section Repository Variables
59
57
  * Store variables accessible to all GitHub Actions workflows in the
@@ -104,20 +102,15 @@ export interface Variable extends Resource<
104
102
  */
105
103
  export const Variable = Resource<Variable>("GitHub.Variable");
106
104
 
107
- function resolveToken(props: VariableProps): string | undefined {
108
- return (
109
- props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
110
- );
111
- }
112
-
113
- function createClient(props: VariableProps): Octokit {
114
- return new Octokit({ auth: resolveToken(props) });
115
- }
105
+ const getOctokit = Effect.gen(function* () {
106
+ const creds = yield* GitHubCredentials;
107
+ return creds.octokit();
108
+ });
116
109
 
117
110
  export const VariableProvider = () =>
118
111
  Provider.succeed(Variable, {
119
112
  create: Effect.fn(function* ({ news }) {
120
- const octokit = createClient(news);
113
+ const octokit = yield* getOctokit;
121
114
 
122
115
  yield* Effect.tryPromise(() =>
123
116
  octokit.rest.actions.createRepoVariable({
@@ -132,7 +125,7 @@ export const VariableProvider = () =>
132
125
  }),
133
126
 
134
127
  update: Effect.fn(function* ({ news }) {
135
- const octokit = createClient(news);
128
+ const octokit = yield* getOctokit;
136
129
 
137
130
  yield* Effect.tryPromise(() =>
138
131
  octokit.rest.actions.updateRepoVariable({
@@ -147,7 +140,7 @@ export const VariableProvider = () =>
147
140
  }),
148
141
 
149
142
  delete: Effect.fn(function* ({ olds }) {
150
- const octokit = createClient(olds);
143
+ const octokit = yield* getOctokit;
151
144
 
152
145
  yield* Effect.tryPromise(async () => {
153
146
  try {
@@ -1,4 +1,6 @@
1
+ export * as Auth from "./AuthProvider.ts";
1
2
  export * from "./Comment.ts";
3
+ export { GitHubCredentials, fromEnv, fromToken } from "./Credentials.ts";
2
4
  export * from "./Providers.ts";
3
5
  export * from "./Secret.ts";
4
6
  export * from "./Variable.ts";