alchemy 2.0.0-beta.4 → 2.0.0-beta.test-export-fix-4
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/bin/alchemy.js +73 -73
- package/bin/alchemy.js.map +1 -1
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js +72 -72
- package/lib/cli/index.js.map +1 -1
- package/package.json +4 -5
- package/src/AWS/EC2/VpcEndpoint.ts +4 -4
- package/src/Cloudflare/Website/StaticSite.ts +19 -32
- package/src/Cloudflare/Website/Vite.ts +10 -22
- package/src/Cloudflare/Workers/Worker.ts +19 -167
- package/src/Platform.ts +4 -9
- package/src/Resource.ts +4 -4
- package/src/AWS/AuthProvider.ts +0 -392
- package/src/Auth/AuthProvider.ts +0 -33
- package/src/Auth/Credentials.ts +0 -56
- package/src/Auth/Env.ts +0 -45
- package/src/Auth/Profile.ts +0 -72
- package/src/Auth/index.ts +0 -2
- package/src/Cloudflare/Auth/AuthProvider.ts +0 -670
- package/src/Cloudflare/Auth/OAuthClient.ts +0 -315
- package/src/Util/Clank.ts +0 -77
package/src/AWS/AuthProvider.ts
DELETED
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import * as DistilledAuth from "@distilled.cloud/aws/Auth";
|
|
2
|
-
import * as Console from "effect/Console";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
|
-
import * as FileSystem from "effect/FileSystem";
|
|
5
|
-
import * as Match from "effect/Match";
|
|
6
|
-
import * as Path from "effect/Path";
|
|
7
|
-
import * as Redacted from "effect/Redacted";
|
|
8
|
-
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
9
|
-
import {
|
|
10
|
-
ChildProcess,
|
|
11
|
-
type ChildProcessSpawner,
|
|
12
|
-
} from "effect/unstable/process";
|
|
13
|
-
import * as NodeCrypto from "node:crypto";
|
|
14
|
-
import * as NodeOs from "node:os";
|
|
15
|
-
import { AuthError, type AuthProvider } from "../Auth/AuthProvider.ts";
|
|
16
|
-
import {
|
|
17
|
-
deleteCredentials,
|
|
18
|
-
displayRedacted,
|
|
19
|
-
readCredentials,
|
|
20
|
-
writeCredentials,
|
|
21
|
-
} from "../Auth/Credentials.ts";
|
|
22
|
-
import {
|
|
23
|
-
getEnv,
|
|
24
|
-
getEnvRedacted,
|
|
25
|
-
getEnvRedactedRequired,
|
|
26
|
-
retryOnce,
|
|
27
|
-
} from "../Auth/Env.ts";
|
|
28
|
-
import * as Clank from "../Util/Clank.ts";
|
|
29
|
-
|
|
30
|
-
export type AwsAuthConfig =
|
|
31
|
-
| { method: "sso"; ssoProfile: string }
|
|
32
|
-
| { method: "stored" }
|
|
33
|
-
| { method: "env" };
|
|
34
|
-
|
|
35
|
-
const options: Array<{
|
|
36
|
-
value: AwsAuthConfig["method"];
|
|
37
|
-
label: string;
|
|
38
|
-
hint?: string;
|
|
39
|
-
}> = [
|
|
40
|
-
{
|
|
41
|
-
value: "sso",
|
|
42
|
-
label: "SSO",
|
|
43
|
-
hint: "aws sso login — credentials loaded from AWS SSO cache",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
value: "env",
|
|
47
|
-
label: "Environment Variables",
|
|
48
|
-
hint: "AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
value: "stored",
|
|
52
|
-
label: "Stored",
|
|
53
|
-
hint: "stored in ~/.alchemy/credentials",
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
export interface AwsStoredCredentials {
|
|
58
|
-
accessKeyId: string;
|
|
59
|
-
secretAccessKey: string;
|
|
60
|
-
sessionToken?: string;
|
|
61
|
-
region?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface AwsResolvedCredentials {
|
|
65
|
-
accessKeyId: Redacted.Redacted<string>;
|
|
66
|
-
secretAccessKey: Redacted.Redacted<string>;
|
|
67
|
-
sessionToken?: Redacted.Redacted<string>;
|
|
68
|
-
region?: string;
|
|
69
|
-
source: { type: AwsAuthConfig["method"]; details?: string };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const AwsAuth = Effect.gen(function* () {
|
|
73
|
-
const context = yield* Effect.context<
|
|
74
|
-
| FileSystem.FileSystem
|
|
75
|
-
| Path.Path
|
|
76
|
-
| HttpClient.HttpClient
|
|
77
|
-
| ChildProcessSpawner.ChildProcessSpawner
|
|
78
|
-
>();
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
name: "AWS",
|
|
82
|
-
configure: (profileName: string) =>
|
|
83
|
-
configureCredentials(profileName).pipe(Effect.provide(context)),
|
|
84
|
-
|
|
85
|
-
logout: (profileName, config) =>
|
|
86
|
-
logout(profileName, config).pipe(Effect.provide(context)),
|
|
87
|
-
|
|
88
|
-
login: (profileName, config) =>
|
|
89
|
-
login(profileName, config).pipe(Effect.provide(context)),
|
|
90
|
-
|
|
91
|
-
prettyPrint: (profileName, config) =>
|
|
92
|
-
prettyPrint(profileName, config).pipe(Effect.provide(context)),
|
|
93
|
-
|
|
94
|
-
read: (profileName, config) =>
|
|
95
|
-
resolveCredentials(profileName, config).pipe(Effect.provide(context)),
|
|
96
|
-
} satisfies AuthProvider<AwsAuthConfig, AwsResolvedCredentials>;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const runSsoCommand = (command: "login" | "logout", ssoProfile: string) =>
|
|
100
|
-
Effect.gen(function* () {
|
|
101
|
-
const handle = yield* ChildProcess.make(
|
|
102
|
-
"aws",
|
|
103
|
-
["sso", command, "--profile", ssoProfile],
|
|
104
|
-
{
|
|
105
|
-
shell: false,
|
|
106
|
-
stdin: "inherit",
|
|
107
|
-
stdout: "inherit",
|
|
108
|
-
stderr: "inherit",
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
const exit = yield* handle.exitCode;
|
|
112
|
-
if (exit !== 0) {
|
|
113
|
-
yield* Effect.fail(
|
|
114
|
-
new AuthError({
|
|
115
|
-
message: `aws sso ${command} exited with code ${exit}`,
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}).pipe(Effect.scoped);
|
|
120
|
-
|
|
121
|
-
const resolveCredentials = (profileName: string, config: AwsAuthConfig) =>
|
|
122
|
-
Match.value(config)
|
|
123
|
-
.pipe(
|
|
124
|
-
Match.when(
|
|
125
|
-
{ method: "env" },
|
|
126
|
-
Effect.fnUntraced(function* () {
|
|
127
|
-
const accessKeyId =
|
|
128
|
-
yield* getEnvRedactedRequired("AWS_ACCESS_KEY_ID");
|
|
129
|
-
const secretAccessKey = yield* getEnvRedactedRequired(
|
|
130
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
131
|
-
);
|
|
132
|
-
const sessionToken = yield* getEnvRedacted("AWS_SESSION_TOKEN");
|
|
133
|
-
const region = yield* (
|
|
134
|
-
getEnv("AWS_REGION") ?? getEnv("AWS_DEFAULT_REGION") ?? undefined
|
|
135
|
-
);
|
|
136
|
-
return {
|
|
137
|
-
accessKeyId,
|
|
138
|
-
secretAccessKey,
|
|
139
|
-
sessionToken,
|
|
140
|
-
region,
|
|
141
|
-
source: { type: "env" as const },
|
|
142
|
-
};
|
|
143
|
-
}),
|
|
144
|
-
),
|
|
145
|
-
Match.when({ method: "stored" }, () =>
|
|
146
|
-
readCredentials<AwsStoredCredentials>(profileName, "aws").pipe(
|
|
147
|
-
Effect.flatMap((creds) =>
|
|
148
|
-
creds == null
|
|
149
|
-
? Effect.fail(
|
|
150
|
-
new AuthError({
|
|
151
|
-
message:
|
|
152
|
-
"AWS stored credentials not found. Run: alchemy-effect login --configure",
|
|
153
|
-
}),
|
|
154
|
-
)
|
|
155
|
-
: Effect.succeed({
|
|
156
|
-
accessKeyId: Redacted.make(creds.accessKeyId),
|
|
157
|
-
secretAccessKey: Redacted.make(creds.secretAccessKey),
|
|
158
|
-
sessionToken: creds.sessionToken
|
|
159
|
-
? Redacted.make(creds.sessionToken)
|
|
160
|
-
: undefined,
|
|
161
|
-
region: creds.region,
|
|
162
|
-
source: { type: "stored" as const },
|
|
163
|
-
}),
|
|
164
|
-
),
|
|
165
|
-
),
|
|
166
|
-
),
|
|
167
|
-
Match.when({ method: "sso" }, (config) =>
|
|
168
|
-
Effect.gen(function* () {
|
|
169
|
-
const auth = yield* DistilledAuth.Default;
|
|
170
|
-
const creds = yield* auth
|
|
171
|
-
.loadProfileCredentials(config.ssoProfile)
|
|
172
|
-
.pipe(
|
|
173
|
-
Effect.mapError(
|
|
174
|
-
(e) =>
|
|
175
|
-
new AuthError({
|
|
176
|
-
message: "failed to load credentials",
|
|
177
|
-
cause: e,
|
|
178
|
-
}),
|
|
179
|
-
),
|
|
180
|
-
);
|
|
181
|
-
const profile = yield* auth
|
|
182
|
-
.loadProfile(config.ssoProfile)
|
|
183
|
-
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
184
|
-
return {
|
|
185
|
-
accessKeyId: creds.accessKeyId,
|
|
186
|
-
secretAccessKey: creds.secretAccessKey,
|
|
187
|
-
sessionToken: creds.sessionToken,
|
|
188
|
-
region: profile?.region,
|
|
189
|
-
source: { type: "sso" as const, details: config.ssoProfile },
|
|
190
|
-
};
|
|
191
|
-
}),
|
|
192
|
-
),
|
|
193
|
-
Match.exhaustive,
|
|
194
|
-
)
|
|
195
|
-
.pipe(
|
|
196
|
-
Effect.mapError(
|
|
197
|
-
(e) => new AuthError({ message: "login failed", cause: e }),
|
|
198
|
-
),
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const prettyPrint = (profileName: string, config: AwsAuthConfig) =>
|
|
202
|
-
resolveCredentials(profileName, config).pipe(
|
|
203
|
-
Effect.tap((creds) =>
|
|
204
|
-
Effect.all([
|
|
205
|
-
Console.log(` accessKeyId: ${displayRedacted(creds.accessKeyId)}`),
|
|
206
|
-
Console.log(
|
|
207
|
-
` secretAccessKey: ${displayRedacted(creds.secretAccessKey)}`,
|
|
208
|
-
),
|
|
209
|
-
creds.sessionToken
|
|
210
|
-
? Console.log(
|
|
211
|
-
` sessionToken: ${displayRedacted(creds.sessionToken)}`,
|
|
212
|
-
)
|
|
213
|
-
: Effect.void,
|
|
214
|
-
creds.region
|
|
215
|
-
? Console.log(` region: ${creds.region}`)
|
|
216
|
-
: Effect.void,
|
|
217
|
-
Console.log(
|
|
218
|
-
//@ts-expect-error
|
|
219
|
-
` source: ${creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type}`,
|
|
220
|
-
),
|
|
221
|
-
]),
|
|
222
|
-
),
|
|
223
|
-
Effect.catch((e) =>
|
|
224
|
-
Console.error(` Failed to retrieve credentials: ${e}`),
|
|
225
|
-
),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* `aws sso logout` only clears AWS CLI's own caches — it does not know about the
|
|
230
|
-
* `<sha1(sso_session)>.credentials.json` file that `@distilled.cloud/aws`
|
|
231
|
-
* writes alongside the SSO token. Without this cleanup, `loadProfileCredentials`
|
|
232
|
-
* short-circuits on the stale distilled cache file after logout and appears to
|
|
233
|
-
* stay logged in until the role creds hit their TTL.
|
|
234
|
-
*/
|
|
235
|
-
const clearDistilledSsoCache = (ssoProfile: string) =>
|
|
236
|
-
Effect.gen(function* () {
|
|
237
|
-
const auth = yield* DistilledAuth.Default;
|
|
238
|
-
const profile = yield* auth.loadProfile(ssoProfile);
|
|
239
|
-
const ssoSession = (profile as { sso_session?: string }).sso_session;
|
|
240
|
-
if (!ssoSession) return;
|
|
241
|
-
const fs = yield* FileSystem.FileSystem;
|
|
242
|
-
const path = yield* Path.Path;
|
|
243
|
-
const hash = NodeCrypto.createHash("sha1").update(ssoSession).digest("hex");
|
|
244
|
-
const cacheFile = path.join(
|
|
245
|
-
NodeOs.homedir(),
|
|
246
|
-
".aws",
|
|
247
|
-
"sso",
|
|
248
|
-
"cache",
|
|
249
|
-
`${hash}.credentials.json`,
|
|
250
|
-
);
|
|
251
|
-
yield* fs.remove(cacheFile).pipe(Effect.catch(() => Effect.void));
|
|
252
|
-
}).pipe(Effect.catch(() => Effect.void));
|
|
253
|
-
|
|
254
|
-
const logout = (profileName: string, config: AwsAuthConfig) =>
|
|
255
|
-
Match.value(config).pipe(
|
|
256
|
-
Match.when({ method: "env" }, () => Effect.void),
|
|
257
|
-
Match.when({ method: "sso" }, (config) =>
|
|
258
|
-
Clank.info(
|
|
259
|
-
`AWS: running 'aws sso logout --profile ${config.ssoProfile}'...`,
|
|
260
|
-
).pipe(
|
|
261
|
-
Effect.zip(runSsoCommand("logout", config.ssoProfile)),
|
|
262
|
-
Effect.zip(clearDistilledSsoCache(config.ssoProfile)),
|
|
263
|
-
Effect.match({
|
|
264
|
-
onSuccess: () => Clank.success("AWS: SSO logout complete"),
|
|
265
|
-
onFailure: (e) =>
|
|
266
|
-
Clank.warn(`AWS: SSO logout failed: \`${e.message}\``),
|
|
267
|
-
}),
|
|
268
|
-
),
|
|
269
|
-
),
|
|
270
|
-
Match.when({ method: "stored" }, () =>
|
|
271
|
-
deleteCredentials(profileName, "aws").pipe(
|
|
272
|
-
Effect.andThen(Clank.success("AWS: stored credentials removed")),
|
|
273
|
-
),
|
|
274
|
-
),
|
|
275
|
-
Match.exhaustive,
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
const login = (profileName: string, config: AwsAuthConfig) =>
|
|
279
|
-
Match.value(config)
|
|
280
|
-
.pipe(
|
|
281
|
-
Match.when({ method: "env" }, () => Effect.void),
|
|
282
|
-
Match.when({ method: "sso" }, (config) =>
|
|
283
|
-
DistilledAuth.loadProfileCredentials(config.ssoProfile).pipe(
|
|
284
|
-
Effect.matchEffect({
|
|
285
|
-
onSuccess: () =>
|
|
286
|
-
Clank.info(
|
|
287
|
-
`AWS: SSO profile '${config.ssoProfile}' already has valid credentials`,
|
|
288
|
-
),
|
|
289
|
-
onFailure: () => loginSSO(config),
|
|
290
|
-
}),
|
|
291
|
-
),
|
|
292
|
-
),
|
|
293
|
-
Match.when({ method: "stored" }, () =>
|
|
294
|
-
readCredentials<AwsStoredCredentials>(profileName, "aws").pipe(
|
|
295
|
-
Effect.flatMap((creds) =>
|
|
296
|
-
creds == null ? loginStored(profileName) : Effect.void,
|
|
297
|
-
),
|
|
298
|
-
),
|
|
299
|
-
),
|
|
300
|
-
Match.exhaustive,
|
|
301
|
-
)
|
|
302
|
-
.pipe(
|
|
303
|
-
Effect.mapError(
|
|
304
|
-
(e) => new AuthError({ message: "login failed", cause: e }),
|
|
305
|
-
),
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
const configureCredentials = (profileName: string) =>
|
|
309
|
-
Clank.select({
|
|
310
|
-
message: "AWS authentication method",
|
|
311
|
-
options,
|
|
312
|
-
})
|
|
313
|
-
.pipe(
|
|
314
|
-
Effect.flatMap((method) =>
|
|
315
|
-
Match.value(method).pipe(
|
|
316
|
-
Match.when("env", () => Effect.succeed({ method: "env" as const })),
|
|
317
|
-
Match.when("sso", () =>
|
|
318
|
-
Effect.gen(function* () {
|
|
319
|
-
const ssoProfile = yield* Clank.text({
|
|
320
|
-
message: "AWS profile name (from ~/.aws/config)",
|
|
321
|
-
placeholder: "default",
|
|
322
|
-
defaultValue: "default",
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const config = {
|
|
326
|
-
method: "sso" as const,
|
|
327
|
-
ssoProfile: ssoProfile ?? "default",
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
yield* loginSSO(config);
|
|
331
|
-
|
|
332
|
-
return config;
|
|
333
|
-
}),
|
|
334
|
-
),
|
|
335
|
-
Match.when("stored", () => loginStored(profileName)),
|
|
336
|
-
Match.exhaustive,
|
|
337
|
-
),
|
|
338
|
-
),
|
|
339
|
-
)
|
|
340
|
-
.pipe(
|
|
341
|
-
Effect.mapError(
|
|
342
|
-
(e) =>
|
|
343
|
-
new AuthError({
|
|
344
|
-
message: "failed to configure credentials",
|
|
345
|
-
cause: e,
|
|
346
|
-
}),
|
|
347
|
-
),
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
const loginSSO = (config: Extract<AwsAuthConfig, { method: "sso" }>) =>
|
|
351
|
-
Clank.info(
|
|
352
|
-
`AWS SSO: running 'aws sso login --profile ${config.ssoProfile}'...`,
|
|
353
|
-
).pipe(
|
|
354
|
-
Effect.andThen(runSsoCommand("login", config.ssoProfile)),
|
|
355
|
-
Effect.matchEffect({
|
|
356
|
-
onSuccess: () => Clank.success("AWS SSO: login complete"),
|
|
357
|
-
onFailure: (e) => Clank.warn(`AWS SSO: login faield: \`${e.message}\``),
|
|
358
|
-
}),
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
const loginStored = Effect.fnUntraced(function* (profileName: string) {
|
|
362
|
-
const accessKeyId = yield* Clank.text({
|
|
363
|
-
message: "AWS Access Key ID",
|
|
364
|
-
validate: (v) => (v.length === 0 ? "Required" : undefined),
|
|
365
|
-
}).pipe(retryOnce);
|
|
366
|
-
|
|
367
|
-
const secretAccessKey = yield* Clank.password({
|
|
368
|
-
message: "AWS Secret Access Key",
|
|
369
|
-
validate: (v) => (v.length === 0 ? "Required" : undefined),
|
|
370
|
-
}).pipe(retryOnce);
|
|
371
|
-
|
|
372
|
-
const sessionToken = yield* Clank.text({
|
|
373
|
-
message: "AWS Session Token (optional — press Enter or Esc to skip)",
|
|
374
|
-
placeholder: "(none)",
|
|
375
|
-
}).pipe(Effect.catch(() => Effect.succeed("")));
|
|
376
|
-
|
|
377
|
-
const region = yield* Clank.text({
|
|
378
|
-
message: "AWS Region",
|
|
379
|
-
placeholder: "us-east-1",
|
|
380
|
-
defaultValue: "us-east-1",
|
|
381
|
-
}).pipe(retryOnce);
|
|
382
|
-
|
|
383
|
-
yield* writeCredentials<AwsStoredCredentials>(profileName, "aws", {
|
|
384
|
-
accessKeyId,
|
|
385
|
-
secretAccessKey,
|
|
386
|
-
sessionToken,
|
|
387
|
-
region,
|
|
388
|
-
});
|
|
389
|
-
yield* Clank.success("AWS credentials saved.");
|
|
390
|
-
|
|
391
|
-
return { method: "stored" as const };
|
|
392
|
-
});
|
package/src/Auth/AuthProvider.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type * as Effect from "effect/Effect";
|
|
2
|
-
import * as Schema from "effect/Schema";
|
|
3
|
-
|
|
4
|
-
export class AuthError extends Schema.TaggedErrorClass<AuthError>()(
|
|
5
|
-
"AuthError",
|
|
6
|
-
{
|
|
7
|
-
message: Schema.String,
|
|
8
|
-
cause: Schema.optional(Schema.Defect),
|
|
9
|
-
},
|
|
10
|
-
) {}
|
|
11
|
-
|
|
12
|
-
export interface AuthProvider<Config extends { method: string }, Credentials> {
|
|
13
|
-
readonly name: string;
|
|
14
|
-
|
|
15
|
-
configure(profileName: string): Effect.Effect<Config, AuthError>;
|
|
16
|
-
|
|
17
|
-
login(
|
|
18
|
-
profileName: string,
|
|
19
|
-
config: Config,
|
|
20
|
-
): Effect.Effect<void, AuthError, never>;
|
|
21
|
-
|
|
22
|
-
logout(
|
|
23
|
-
profileName: string,
|
|
24
|
-
config: Config,
|
|
25
|
-
): Effect.Effect<void, AuthError, never>;
|
|
26
|
-
|
|
27
|
-
prettyPrint(profileName: string, config: Config): Effect.Effect<void>;
|
|
28
|
-
|
|
29
|
-
read(
|
|
30
|
-
profileName: string,
|
|
31
|
-
config: Config,
|
|
32
|
-
): Effect.Effect<Credentials, AuthError, never>;
|
|
33
|
-
}
|
package/src/Auth/Credentials.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as FileSystem from "effect/FileSystem";
|
|
3
|
-
import * as Redacted from "effect/Redacted";
|
|
4
|
-
import path from "pathe";
|
|
5
|
-
import { rootDir } from "./Profile.ts";
|
|
6
|
-
|
|
7
|
-
const credentialsDirPath = path.join(rootDir, "credentials");
|
|
8
|
-
|
|
9
|
-
export const credentialsFilePath = (profile: string, provider: string) =>
|
|
10
|
-
path.join(credentialsDirPath, profile, `${provider}.json`);
|
|
11
|
-
|
|
12
|
-
export const readCredentials = Effect.fnUntraced(function* <T>(
|
|
13
|
-
profile: string,
|
|
14
|
-
provider: string,
|
|
15
|
-
) {
|
|
16
|
-
const fs = yield* FileSystem.FileSystem;
|
|
17
|
-
const data = yield* fs
|
|
18
|
-
.readFileString(credentialsFilePath(profile, provider))
|
|
19
|
-
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
20
|
-
if (data === undefined) return undefined as T | undefined;
|
|
21
|
-
try {
|
|
22
|
-
return JSON.parse(data) as T;
|
|
23
|
-
} catch {
|
|
24
|
-
return undefined as T | undefined;
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export const writeCredentials = Effect.fnUntraced(function* <T>(
|
|
29
|
-
profile: string,
|
|
30
|
-
provider: string,
|
|
31
|
-
credentials: T,
|
|
32
|
-
) {
|
|
33
|
-
const fs = yield* FileSystem.FileSystem;
|
|
34
|
-
const filePath = credentialsFilePath(profile, provider);
|
|
35
|
-
yield* fs.makeDirectory(path.dirname(filePath), { recursive: true });
|
|
36
|
-
yield* fs.writeFileString(filePath, JSON.stringify(credentials, null, 2));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const deleteCredentials = Effect.fnUntraced(function* (
|
|
40
|
-
profile: string,
|
|
41
|
-
provider: string,
|
|
42
|
-
) {
|
|
43
|
-
const fs = yield* FileSystem.FileSystem;
|
|
44
|
-
yield* fs
|
|
45
|
-
.remove(credentialsFilePath(profile, provider))
|
|
46
|
-
.pipe(Effect.catch(() => Effect.void));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export function displayRedacted(
|
|
50
|
-
r: Redacted.Redacted<string>,
|
|
51
|
-
visibleChars = 4,
|
|
52
|
-
): string {
|
|
53
|
-
const raw = Redacted.value(r);
|
|
54
|
-
if (raw.length <= visibleChars) return "****";
|
|
55
|
-
return `${raw.slice(0, visibleChars)}****`;
|
|
56
|
-
}
|
package/src/Auth/Env.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import * as Config from "effect/Config";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as Clank from "../Util/Clank.ts";
|
|
4
|
-
import { AuthError } from "./AuthProvider.ts";
|
|
5
|
-
|
|
6
|
-
export const getEnv = (key: string) =>
|
|
7
|
-
Config.string(key)
|
|
8
|
-
.asEffect()
|
|
9
|
-
.pipe(Effect.orElseSucceed(() => undefined));
|
|
10
|
-
|
|
11
|
-
export const getEnvRequired = (key: string) =>
|
|
12
|
-
Config.string(key)
|
|
13
|
-
.asEffect()
|
|
14
|
-
.pipe(
|
|
15
|
-
Effect.catch(() =>
|
|
16
|
-
Effect.fail(new AuthError({ message: `Missing required env: ${key}` })),
|
|
17
|
-
),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
export const getEnvRedacted = (key: string) =>
|
|
21
|
-
Config.redacted(key)
|
|
22
|
-
.asEffect()
|
|
23
|
-
.pipe(Effect.orElseSucceed(() => undefined));
|
|
24
|
-
|
|
25
|
-
export const getEnvRedactedRequired = (key: string) =>
|
|
26
|
-
Config.redacted(key)
|
|
27
|
-
.asEffect()
|
|
28
|
-
.pipe(
|
|
29
|
-
Effect.catch(() =>
|
|
30
|
-
Effect.fail(new AuthError({ message: `Missing required env: ${key}` })),
|
|
31
|
-
),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
export const retryOnce = <A, R>(
|
|
35
|
-
self: Effect.Effect<A, Clank.PromptCancelled, R>,
|
|
36
|
-
) =>
|
|
37
|
-
self.pipe(
|
|
38
|
-
Effect.retry({
|
|
39
|
-
times: 1,
|
|
40
|
-
while: (e) => e instanceof Clank.PromptCancelled,
|
|
41
|
-
}),
|
|
42
|
-
Effect.mapError(
|
|
43
|
-
(e) => new AuthError({ message: "User cancelled prompt", cause: e }),
|
|
44
|
-
),
|
|
45
|
-
);
|
package/src/Auth/Profile.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as FileSystem from "effect/FileSystem";
|
|
3
|
-
import type { PlatformError } from "effect/PlatformError";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "pathe";
|
|
6
|
-
|
|
7
|
-
export const rootDir = path.join(os.homedir(), ".alchemy");
|
|
8
|
-
export const configFilePath = path.join(rootDir, "profiles.json");
|
|
9
|
-
|
|
10
|
-
export const CONFIG_VERSION = 2;
|
|
11
|
-
|
|
12
|
-
export interface AlchemyProfiles {
|
|
13
|
-
version: typeof CONFIG_VERSION;
|
|
14
|
-
profiles: Record<string, AlchemyProfile>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type AlchemyProfile = Record<string, { method: string }>;
|
|
18
|
-
|
|
19
|
-
const emptyConfig = (): AlchemyProfiles => ({
|
|
20
|
-
version: CONFIG_VERSION,
|
|
21
|
-
profiles: {},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export const readConfig: Effect.Effect<
|
|
25
|
-
AlchemyProfiles,
|
|
26
|
-
never,
|
|
27
|
-
FileSystem.FileSystem
|
|
28
|
-
> = Effect.gen(function* () {
|
|
29
|
-
const fs = yield* FileSystem.FileSystem;
|
|
30
|
-
const data = yield* fs
|
|
31
|
-
.readFileString(configFilePath)
|
|
32
|
-
.pipe(Effect.orElseSucceed(() => undefined));
|
|
33
|
-
if (data === undefined) return emptyConfig();
|
|
34
|
-
try {
|
|
35
|
-
const parsed = JSON.parse(data);
|
|
36
|
-
if (parsed?.version !== CONFIG_VERSION) {
|
|
37
|
-
return emptyConfig();
|
|
38
|
-
}
|
|
39
|
-
return parsed as AlchemyProfiles;
|
|
40
|
-
} catch {
|
|
41
|
-
return emptyConfig();
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
export const writeConfig = (
|
|
46
|
-
config: AlchemyProfiles,
|
|
47
|
-
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
|
48
|
-
Effect.gen(function* () {
|
|
49
|
-
const fs = yield* FileSystem.FileSystem;
|
|
50
|
-
yield* fs.makeDirectory(path.dirname(configFilePath), {
|
|
51
|
-
recursive: true,
|
|
52
|
-
});
|
|
53
|
-
yield* fs.writeFileString(configFilePath, JSON.stringify(config, null, 2));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export const getProfile = (
|
|
57
|
-
name: string,
|
|
58
|
-
): Effect.Effect<AlchemyProfile | undefined, never, FileSystem.FileSystem> =>
|
|
59
|
-
Effect.gen(function* () {
|
|
60
|
-
const config = yield* readConfig;
|
|
61
|
-
return config.profiles[name];
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const setProfile = (
|
|
65
|
-
name: string,
|
|
66
|
-
profile: AlchemyProfile,
|
|
67
|
-
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
|
68
|
-
Effect.gen(function* () {
|
|
69
|
-
const config = yield* readConfig;
|
|
70
|
-
config.profiles[name] = profile;
|
|
71
|
-
yield* writeConfig(config);
|
|
72
|
-
});
|
package/src/Auth/index.ts
DELETED