alchemy 0.46.0 → 0.47.0
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.mjs +713 -255
- package/bin/commands/login.ts +2 -5
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/api.d.ts +9 -5
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +6 -46
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/auth.d.ts +16 -35
- package/lib/cloudflare/auth.d.ts.map +1 -1
- package/lib/cloudflare/auth.js +54 -24
- package/lib/cloudflare/auth.js.map +1 -1
- package/lib/cloudflare/{auth-wrangler.d.ts → oauth.d.ts} +11 -14
- package/lib/cloudflare/oauth.d.ts.map +1 -0
- package/lib/cloudflare/{auth-wrangler.js → oauth.js} +101 -111
- package/lib/cloudflare/oauth.js.map +1 -0
- package/lib/cloudflare/user.d.ts +1 -1
- package/lib/cloudflare/user.d.ts.map +1 -1
- package/lib/cloudflare/user.js +20 -29
- package/lib/cloudflare/user.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +6 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +27 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +8 -3
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/zone.d.ts +1 -1
- package/lib/cloudflare/zone.d.ts.map +1 -1
- package/lib/cloudflare/zone.js +38 -56
- package/lib/cloudflare/zone.js.map +1 -1
- package/lib/util/memoize.d.ts +0 -1
- package/lib/util/memoize.d.ts.map +1 -1
- package/lib/util/memoize.js +0 -13
- package/lib/util/memoize.js.map +1 -1
- package/lib/util/neverthrow.d.ts +7 -0
- package/lib/util/neverthrow.d.ts.map +1 -1
- package/lib/util/neverthrow.js +17 -0
- package/lib/util/neverthrow.js.map +1 -1
- package/lib/util/telemetry/constants.js +2 -2
- package/lib/util/telemetry/constants.js.map +1 -1
- package/package.json +2 -1
- package/src/build-date.ts +1 -1
- package/src/cloudflare/api.ts +18 -57
- package/src/cloudflare/auth.ts +84 -68
- package/src/cloudflare/{auth-wrangler.ts → oauth.ts} +156 -169
- package/src/cloudflare/user.ts +33 -51
- package/src/cloudflare/worker-metadata.ts +7 -0
- package/src/cloudflare/worker.ts +40 -4
- package/src/cloudflare/zone.ts +50 -70
- package/src/util/memoize.ts +0 -17
- package/src/util/neverthrow.ts +28 -0
- package/src/util/telemetry/constants.ts +2 -2
- package/lib/cloudflare/auth-wrangler.d.ts.map +0 -1
- package/lib/cloudflare/auth-wrangler.js.map +0 -1
- package/lib/util/xdg-paths.d.ts +0 -9
- package/lib/util/xdg-paths.d.ts.map +0 -1
- package/lib/util/xdg-paths.js +0 -41
- package/lib/util/xdg-paths.js.map +0 -1
- package/src/util/xdg-paths.ts +0 -48
package/src/cloudflare/api.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { alchemy } from "../alchemy.ts";
|
|
2
1
|
import type { Secret } from "../secret.ts";
|
|
3
2
|
import { withExponentialBackoff } from "../util/retry.ts";
|
|
4
3
|
import { safeFetch } from "../util/safe-fetch.ts";
|
|
5
4
|
import {
|
|
6
5
|
getCloudflareAuthHeaders,
|
|
6
|
+
normalizeAuthOptions,
|
|
7
7
|
type CloudflareAuthOptions,
|
|
8
8
|
} from "./auth.ts";
|
|
9
|
-
import {
|
|
9
|
+
import { getCloudflareAccountId } from "./user.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Options for Cloudflare API requests
|
|
@@ -42,6 +42,12 @@ export interface CloudflareApiOptions {
|
|
|
42
42
|
email?: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** Used to propagate normalized auth options from a parent resource to `createCloudflareApi` in a child resource */
|
|
46
|
+
export type InternalCloudflareApiOptions = CloudflareAuthOptions & {
|
|
47
|
+
baseUrl?: string;
|
|
48
|
+
accountId: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
45
51
|
/**
|
|
46
52
|
* Creates a CloudflareApi instance with automatic account ID discovery if not provided
|
|
47
53
|
*
|
|
@@ -49,44 +55,18 @@ export interface CloudflareApiOptions {
|
|
|
49
55
|
* @returns Promise resolving to a CloudflareApi instance
|
|
50
56
|
*/
|
|
51
57
|
export async function createCloudflareApi(
|
|
52
|
-
options: Partial<CloudflareApiOptions> = {},
|
|
58
|
+
options: Partial<CloudflareApiOptions> | InternalCloudflareApiOptions = {},
|
|
53
59
|
): Promise<CloudflareApi> {
|
|
54
|
-
const
|
|
55
|
-
options.apiKey ??
|
|
56
|
-
(process.env.CLOUDFLARE_API_KEY
|
|
57
|
-
? alchemy.secret(process.env.CLOUDFLARE_API_KEY)
|
|
58
|
-
: undefined);
|
|
59
|
-
const apiToken =
|
|
60
|
-
options.apiToken ??
|
|
61
|
-
(process.env.CLOUDFLARE_API_TOKEN
|
|
62
|
-
? alchemy.secret(process.env.CLOUDFLARE_API_TOKEN)
|
|
63
|
-
: undefined);
|
|
64
|
-
let email = options.email ?? process.env.CLOUDFLARE_EMAIL;
|
|
65
|
-
if (apiKey && !email) {
|
|
66
|
-
email = await getUserEmailFromApiKey(apiKey.unencrypted);
|
|
67
|
-
}
|
|
60
|
+
const authOptions = await normalizeAuthOptions(options);
|
|
68
61
|
const accountId =
|
|
69
62
|
options.accountId ??
|
|
70
63
|
process.env.CLOUDFLARE_ACCOUNT_ID ??
|
|
71
64
|
process.env.CF_ACCOUNT_ID ??
|
|
72
|
-
(
|
|
73
|
-
await getCloudflareAccounts({
|
|
74
|
-
apiKey,
|
|
75
|
-
apiToken,
|
|
76
|
-
email,
|
|
77
|
-
} as CloudflareAuthOptions)
|
|
78
|
-
)[0]?.id;
|
|
79
|
-
if (accountId === undefined) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"Either accountId or CLOUDFLARE_ACCOUNT_ID must be provided",
|
|
82
|
-
);
|
|
83
|
-
}
|
|
65
|
+
(await getCloudflareAccountId(authOptions));
|
|
84
66
|
return new CloudflareApi({
|
|
85
67
|
baseUrl: options.baseUrl,
|
|
86
68
|
accountId,
|
|
87
|
-
|
|
88
|
-
apiKey,
|
|
89
|
-
apiToken,
|
|
69
|
+
authOptions,
|
|
90
70
|
});
|
|
91
71
|
}
|
|
92
72
|
|
|
@@ -96,9 +76,6 @@ export async function createCloudflareApi(
|
|
|
96
76
|
export class CloudflareApi {
|
|
97
77
|
public readonly accountId: string;
|
|
98
78
|
public readonly baseUrl: string;
|
|
99
|
-
public readonly apiKey: Secret | undefined;
|
|
100
|
-
public readonly apiToken: Secret | undefined;
|
|
101
|
-
public readonly email: string | undefined;
|
|
102
79
|
public readonly authOptions: CloudflareAuthOptions;
|
|
103
80
|
|
|
104
81
|
/**
|
|
@@ -108,30 +85,14 @@ export class CloudflareApi {
|
|
|
108
85
|
*
|
|
109
86
|
* @param options API options
|
|
110
87
|
*/
|
|
111
|
-
constructor(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
) {
|
|
88
|
+
constructor(options: {
|
|
89
|
+
baseUrl?: string;
|
|
90
|
+
accountId: string;
|
|
91
|
+
authOptions: CloudflareAuthOptions;
|
|
92
|
+
}) {
|
|
116
93
|
this.accountId = options.accountId;
|
|
117
94
|
this.baseUrl = options.baseUrl ?? "https://api.cloudflare.com/client/v4";
|
|
118
|
-
this.
|
|
119
|
-
this.apiToken = options.apiToken;
|
|
120
|
-
this.email = options.email;
|
|
121
|
-
|
|
122
|
-
if (this.apiKey && this.apiToken) {
|
|
123
|
-
throw new Error("'apiKey' and 'apiToken' cannot both be provided");
|
|
124
|
-
} else if (this.apiKey && !this.email) {
|
|
125
|
-
throw new Error("'email' must be provided if 'apiKey' is provided");
|
|
126
|
-
}
|
|
127
|
-
this.authOptions = this.apiKey
|
|
128
|
-
? {
|
|
129
|
-
apiKey: this.apiKey,
|
|
130
|
-
email: this.email!,
|
|
131
|
-
}
|
|
132
|
-
: {
|
|
133
|
-
apiToken: this.apiToken,
|
|
134
|
-
};
|
|
95
|
+
this.authOptions = options.authOptions;
|
|
135
96
|
}
|
|
136
97
|
|
|
137
98
|
/**
|
package/src/cloudflare/auth.ts
CHANGED
|
@@ -1,85 +1,101 @@
|
|
|
1
|
+
import { alchemy } from "../alchemy.ts";
|
|
1
2
|
import type { Secret } from "../secret.ts";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export type CloudflareAuthOptions =
|
|
7
|
-
| CloudflareApiKeyAuthOptions
|
|
8
|
-
| CloudflareApiTokenAuthOptions;
|
|
9
|
-
|
|
10
|
-
export type CloudflareApiKeyAuthOptions = {
|
|
11
|
-
/**
|
|
12
|
-
* API Key to use with API Key
|
|
13
|
-
*/
|
|
14
|
-
apiKey: Secret;
|
|
15
|
-
/**
|
|
16
|
-
* Email to use with API Key
|
|
17
|
-
* If not provided, will attempt to discover from Cloudflare API
|
|
18
|
-
*/
|
|
19
|
-
email: string;
|
|
3
|
+
import type { CloudflareApiOptions } from "./api.ts";
|
|
4
|
+
import { getRefreshedWranglerConfig } from "./oauth.ts";
|
|
5
|
+
import { getUserEmailFromApiKey } from "./user.ts";
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
7
|
+
export interface CloudflareApiTokenAuthOptions {
|
|
8
|
+
type: "api-token";
|
|
9
|
+
apiToken: Secret<string>;
|
|
10
|
+
}
|
|
26
11
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
export interface CloudflareApiKeyAuthOptions {
|
|
13
|
+
type: "api-key";
|
|
14
|
+
apiKey: Secret;
|
|
15
|
+
apiEmail: string;
|
|
31
16
|
}
|
|
32
17
|
|
|
33
|
-
export
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*/
|
|
37
|
-
apiToken?: Secret;
|
|
18
|
+
export interface CloudflareWranglerAuthOptions {
|
|
19
|
+
type: "wrangler";
|
|
20
|
+
}
|
|
38
21
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
export type CloudflareAuthOptions =
|
|
23
|
+
| CloudflareApiTokenAuthOptions
|
|
24
|
+
| CloudflareApiKeyAuthOptions
|
|
25
|
+
| CloudflareWranglerAuthOptions;
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
export const isCloudflareAuthOptions = (
|
|
28
|
+
input?: CloudflareApiOptions | CloudflareAuthOptions,
|
|
29
|
+
): input is CloudflareAuthOptions => {
|
|
30
|
+
return (
|
|
31
|
+
input !== undefined &&
|
|
32
|
+
"type" in input &&
|
|
33
|
+
(input.type === "api-token" ||
|
|
34
|
+
input.type === "api-key" ||
|
|
35
|
+
input.type === "wrangler")
|
|
36
|
+
);
|
|
48
37
|
};
|
|
49
38
|
|
|
50
|
-
export function
|
|
51
|
-
|
|
52
|
-
):
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
export async function normalizeAuthOptions(
|
|
40
|
+
input?: CloudflareApiOptions | CloudflareAuthOptions,
|
|
41
|
+
): Promise<CloudflareAuthOptions> {
|
|
42
|
+
if (isCloudflareAuthOptions(input)) {
|
|
43
|
+
return input;
|
|
44
|
+
}
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"X-Auth-Key": options.apiKey.unencrypted,
|
|
63
|
-
"X-Auth-Email": options.email,
|
|
64
|
-
};
|
|
65
|
-
} else if (isCloudflareApiTokenAuthOptions(options)) {
|
|
66
|
-
// API Token
|
|
46
|
+
const email = async (apiKey: Secret<string>) =>
|
|
47
|
+
input?.email ??
|
|
48
|
+
process.env.CLOUDFLARE_EMAIL ??
|
|
49
|
+
(await getUserEmailFromApiKey(apiKey.unencrypted));
|
|
50
|
+
|
|
51
|
+
if (input?.apiKey) {
|
|
67
52
|
return {
|
|
68
|
-
|
|
53
|
+
type: "api-key",
|
|
54
|
+
apiKey: input.apiKey,
|
|
55
|
+
apiEmail: await email(input.apiKey),
|
|
69
56
|
};
|
|
70
57
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
if (input?.apiToken) {
|
|
59
|
+
return { type: "api-token", apiToken: input.apiToken };
|
|
60
|
+
}
|
|
61
|
+
if (process.env.CLOUDFLARE_API_KEY) {
|
|
62
|
+
const key = alchemy.secret(process.env.CLOUDFLARE_API_KEY);
|
|
63
|
+
return { type: "api-key", apiKey: key, apiEmail: await email(key) };
|
|
64
|
+
}
|
|
65
|
+
if (process.env.CLOUDFLARE_API_TOKEN) {
|
|
74
66
|
return {
|
|
75
|
-
|
|
67
|
+
type: "api-token",
|
|
68
|
+
apiToken: alchemy.secret(process.env.CLOUDFLARE_API_TOKEN),
|
|
76
69
|
};
|
|
77
|
-
} else if (wranglerConfig.isErr()) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`Cloudflare authentication failed: ${wranglerConfig.error.message}`,
|
|
80
|
-
);
|
|
81
70
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
return { type: "wrangler" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function getCloudflareAuthHeaders(
|
|
75
|
+
authOptions: CloudflareAuthOptions,
|
|
76
|
+
): Promise<Record<string, string>> {
|
|
77
|
+
switch (authOptions.type) {
|
|
78
|
+
case "api-token":
|
|
79
|
+
return { Authorization: `Bearer ${authOptions.apiToken.unencrypted}` };
|
|
80
|
+
case "api-key":
|
|
81
|
+
return {
|
|
82
|
+
"X-Auth-Key": authOptions.apiKey.unencrypted,
|
|
83
|
+
"X-Auth-Email": authOptions.apiEmail,
|
|
84
|
+
};
|
|
85
|
+
case "wrangler": {
|
|
86
|
+
const wranglerConfig = await getRefreshedWranglerConfig();
|
|
87
|
+
if (wranglerConfig.isErr()) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
[
|
|
90
|
+
wranglerConfig.error.message,
|
|
91
|
+
"Please run `alchemy login`, or set either CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY in your environment.",
|
|
92
|
+
"Learn more: https://alchemy.run/guides/cloudflare/",
|
|
93
|
+
].join("\n"),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
Authorization: `Bearer ${wranglerConfig.value.oauth_token}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
85
101
|
}
|