wrangler 0.0.0-e9a1820 → 0.0.0-e9ab55a
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/package.json +12 -5
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +228 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +136 -0
- package/src/__tests__/dev.test.tsx +67 -0
- package/src/__tests__/jest.setup.ts +5 -1
- package/src/__tests__/kv.test.ts +849 -45
- package/src/__tests__/mock-cfetch.ts +137 -29
- package/src/__tests__/publish.test.ts +102 -0
- package/src/api/form_data.ts +1 -1
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +6 -6
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +69 -0
- package/src/config.ts +633 -118
- package/src/dev.tsx +114 -209
- package/src/index.tsx +265 -239
- package/src/inspect.ts +543 -0
- package/src/kv.tsx +49 -30
- package/src/pages.tsx +431 -242
- package/src/proxy.ts +275 -76
- package/src/publish.ts +79 -47
- package/src/sites.tsx +10 -7
- package/src/tail.tsx +5 -3
- package/src/user.tsx +22 -17
- package/wrangler-dist/cli.js +42319 -36190
- package/wrangler-dist/cli.js.map +3 -3
- package/src/api/inspect.ts +0 -430
- package/src/cfetch.ts +0 -72
|
@@ -1,47 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { RequestInit } from "node-fetch";
|
|
2
|
+
import { URLSearchParams } from "node:url";
|
|
3
3
|
import { pathToRegexp } from "path-to-regexp";
|
|
4
|
+
import { CF_API_BASE_URL } from "../cfetch";
|
|
5
|
+
import type { FetchResult } from "../cfetch";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The signature of the function that will handle a mock request.
|
|
9
|
+
*/
|
|
10
|
+
export type MockHandler<ResponseType> = (
|
|
11
|
+
uri: RegExpExecArray,
|
|
12
|
+
init: RequestInit,
|
|
13
|
+
queryParams: URLSearchParams
|
|
14
|
+
) => ResponseType;
|
|
4
15
|
|
|
5
|
-
// Sadly we cannot give use the correct `RequestInit` type from node-fetch.
|
|
6
|
-
// Jest needs to transform the code as part of the module mocking, and it doesn't know how to cope with such types.
|
|
7
|
-
type RequestInit = { method: string; body: string };
|
|
8
|
-
type MockHandler = (uri: RegExpExecArray, init?: RequestInit) => unknown;
|
|
9
|
-
type MockFetch = {
|
|
10
|
-
regexp: RegExp;
|
|
11
|
-
method: string | undefined;
|
|
12
|
-
handler: MockHandler;
|
|
13
|
-
};
|
|
14
16
|
type RemoveMockFn = () => void;
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
interface MockFetch<ResponseType> {
|
|
19
|
+
regexp: RegExp;
|
|
20
|
+
method: string | undefined;
|
|
21
|
+
handler: MockHandler<ResponseType>;
|
|
22
|
+
}
|
|
23
|
+
const mocks: MockFetch<unknown>[] = [];
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The mock implementation of `cfApi.fetch()`.
|
|
27
|
+
*
|
|
28
|
+
* This function will attempt to match the given request to one of the mock handlers configured by calls to `setMock`.
|
|
29
|
+
*
|
|
30
|
+
* Once found the handler will be used to generate a mock response.
|
|
31
|
+
*/
|
|
32
|
+
export async function mockFetchInternal(
|
|
19
33
|
resource: string,
|
|
20
|
-
init: RequestInit
|
|
21
|
-
|
|
34
|
+
init: RequestInit = {},
|
|
35
|
+
queryParams: URLSearchParams = new URLSearchParams()
|
|
36
|
+
) {
|
|
22
37
|
for (const { regexp, method, handler } of mocks) {
|
|
23
|
-
const
|
|
38
|
+
const resourcePath = new URL(resource, CF_API_BASE_URL).pathname;
|
|
39
|
+
const uri = regexp.exec(resourcePath);
|
|
24
40
|
// Do the resource path and (if specified) the HTTP method match?
|
|
25
41
|
if (uri !== null && (!method || method === init.method)) {
|
|
26
42
|
// The `resource` regular expression will extract the labelled groups from the URL.
|
|
27
43
|
// These are passed through to the `handler` call, to allow it to do additional checks or behaviour.
|
|
28
|
-
return handler(uri, init); // TODO: should we have some kind of fallthrough system? we'll see.
|
|
44
|
+
return handler(uri, init, queryParams); // TODO: should we have some kind of fallthrough system? we'll see.
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
|
-
throw new Error(`no mocks found for ${resource}`);
|
|
47
|
+
throw new Error(`no mocks found for ${init.method}: ${resource}`);
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
/**
|
|
35
|
-
* Specify an expected resource path that is to be handled.
|
|
51
|
+
* Specify an expected resource path that is to be handled, resulting in a raw JSON response.
|
|
52
|
+
*
|
|
53
|
+
* @param resource The path of the resource to be matched.
|
|
54
|
+
* This can include wildcards whose value will be passed to the `handler`.
|
|
55
|
+
* @param handler The function that will generate the mock response for this request.
|
|
36
56
|
*/
|
|
37
|
-
export function
|
|
57
|
+
export function setMockRawResponse<ResponseType>(
|
|
58
|
+
resource: string,
|
|
59
|
+
handler: MockHandler<ResponseType>
|
|
60
|
+
): RemoveMockFn;
|
|
61
|
+
/**
|
|
62
|
+
* Specify an expected resource path that is to be handled, resulting in a raw JSON response.
|
|
63
|
+
*
|
|
64
|
+
* @param resource The path of the resource to be matched.
|
|
65
|
+
* This can include wildcards whose value will be passed to the `handler`.
|
|
66
|
+
* @param method The HTTP method (e.g. GET, POST, etc) that the request must have to match this mock handler.
|
|
67
|
+
* @param handler The function that will generate the mock response for this request.
|
|
68
|
+
*/
|
|
69
|
+
export function setMockRawResponse<ResponseType>(
|
|
38
70
|
resource: string,
|
|
39
71
|
method: string,
|
|
40
|
-
handler: MockHandler
|
|
72
|
+
handler: MockHandler<ResponseType>
|
|
41
73
|
): RemoveMockFn;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Specify an expected resource path that is to be handled, resulting in a raw JSON response.
|
|
76
|
+
*/
|
|
77
|
+
export function setMockRawResponse<ResponseType>(
|
|
78
|
+
resource: string,
|
|
79
|
+
...args: [string, MockHandler<ResponseType>] | [MockHandler<ResponseType>]
|
|
80
|
+
): RemoveMockFn {
|
|
81
|
+
const handler = args.pop() as MockHandler<ResponseType>;
|
|
45
82
|
const method = args.pop() as string;
|
|
46
83
|
const mock = {
|
|
47
84
|
resource,
|
|
@@ -51,13 +88,84 @@ export function setMock(resource: string, ...args: unknown[]): RemoveMockFn {
|
|
|
51
88
|
};
|
|
52
89
|
mocks.push(mock);
|
|
53
90
|
return () => {
|
|
54
|
-
|
|
91
|
+
const mockIndex = mocks.indexOf(mock);
|
|
92
|
+
if (mockIndex !== -1) {
|
|
93
|
+
mocks.splice(mockIndex, 1);
|
|
94
|
+
}
|
|
55
95
|
};
|
|
56
96
|
}
|
|
57
97
|
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Specify an expected resource path that is to be handled, resulting in a `FetchRequest`.
|
|
100
|
+
*
|
|
101
|
+
* The mock `handler` should return the `result`, which will then be wrapped in a `FetchRequest` object.
|
|
102
|
+
*
|
|
103
|
+
* @param resource The path of the resource to be matched.
|
|
104
|
+
* This can include wildcards whose value will be passed to the `handler`.
|
|
105
|
+
* @param handler The function that will generate the mock response for this request.
|
|
106
|
+
*/
|
|
107
|
+
export function setMockResponse<ResponseType>(
|
|
108
|
+
resource: string,
|
|
109
|
+
handler: MockHandler<ResponseType>
|
|
110
|
+
): RemoveMockFn;
|
|
111
|
+
/**
|
|
112
|
+
* Specify an expected resource path that is to be handled, resulting in a FetchRequest..
|
|
113
|
+
*
|
|
114
|
+
* @param resource The path of the resource to be matched.
|
|
115
|
+
* This can include wildcards whose value will be passed to the `handler`.
|
|
116
|
+
* @param method The HTTP method (e.g. GET, POST, etc) that the request must have to match this mock handler.
|
|
117
|
+
* @param handler The function that will generate the mock response for this request.
|
|
118
|
+
*/
|
|
119
|
+
export function setMockResponse<ResponseType>(
|
|
120
|
+
resource: string,
|
|
121
|
+
method: string,
|
|
122
|
+
handler: MockHandler<ResponseType>
|
|
123
|
+
): RemoveMockFn;
|
|
124
|
+
/**
|
|
125
|
+
* Specify an expected resource path that is to be handled, resulting in a FetchRequest.
|
|
126
|
+
*/
|
|
127
|
+
export function setMockResponse<ResponseType>(
|
|
128
|
+
resource: string,
|
|
129
|
+
...args: [string, MockHandler<ResponseType>] | [MockHandler<ResponseType>]
|
|
130
|
+
): RemoveMockFn {
|
|
131
|
+
const handler = args.pop() as MockHandler<ResponseType>;
|
|
132
|
+
const method = args.pop() as string;
|
|
133
|
+
return setMockRawResponse(resource, method, (...handlerArgs) =>
|
|
134
|
+
createFetchResult(handler(...handlerArgs))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A helper to make it easier to create `FetchResult` objects in tests.
|
|
140
|
+
*/
|
|
141
|
+
export function createFetchResult<ResponseType>(
|
|
142
|
+
result: ResponseType,
|
|
143
|
+
success = true,
|
|
144
|
+
errors = [],
|
|
145
|
+
messages = [],
|
|
146
|
+
result_info?: unknown
|
|
147
|
+
): FetchResult<ResponseType> {
|
|
148
|
+
return result_info
|
|
149
|
+
? {
|
|
150
|
+
result,
|
|
151
|
+
success,
|
|
152
|
+
errors,
|
|
153
|
+
messages,
|
|
154
|
+
result_info,
|
|
155
|
+
}
|
|
156
|
+
: {
|
|
157
|
+
result,
|
|
158
|
+
success,
|
|
159
|
+
errors,
|
|
160
|
+
messages,
|
|
161
|
+
};
|
|
60
162
|
}
|
|
61
163
|
|
|
62
|
-
|
|
63
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Remove all the configured mock handlers.
|
|
166
|
+
*
|
|
167
|
+
* This should be called in an `afterEach()` block to ensure that mock handlers do not leak between tests.
|
|
168
|
+
*/
|
|
169
|
+
export function unsetAllMocks() {
|
|
170
|
+
mocks.length = 0;
|
|
171
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { setMockResponse } from "./mock-cfetch";
|
|
3
|
+
import { runInTempDir } from "./run-in-tmp";
|
|
4
|
+
import { runWrangler } from "./run-wrangler";
|
|
5
|
+
|
|
6
|
+
describe("publish", () => {
|
|
7
|
+
runInTempDir();
|
|
8
|
+
|
|
9
|
+
it("should be able to use `index` with no extension as the entry-point", async () => {
|
|
10
|
+
writeWranglerToml();
|
|
11
|
+
writeEsmWorkerSource();
|
|
12
|
+
mockUploadWorkerRequest();
|
|
13
|
+
mockSubDomainRequest();
|
|
14
|
+
|
|
15
|
+
const { stdout, stderr, error } = await runWrangler("publish ./index");
|
|
16
|
+
|
|
17
|
+
expect(stdout).toMatchInlineSnapshot(`
|
|
18
|
+
"Uploaded
|
|
19
|
+
test-name
|
|
20
|
+
(0.00 sec)
|
|
21
|
+
Deployed
|
|
22
|
+
test-name
|
|
23
|
+
(0.00 sec)
|
|
24
|
+
|
|
25
|
+
test-name.test-sub-domain.workers.dev"
|
|
26
|
+
`);
|
|
27
|
+
expect(stderr).toMatchInlineSnapshot(`""`);
|
|
28
|
+
expect(error).toMatchInlineSnapshot(`undefined`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should be able to use the `build.upload.main` config as the entry-point for ESM sources", async () => {
|
|
32
|
+
writeWranglerToml("./index.js");
|
|
33
|
+
writeEsmWorkerSource();
|
|
34
|
+
mockUploadWorkerRequest();
|
|
35
|
+
mockSubDomainRequest();
|
|
36
|
+
|
|
37
|
+
const { stdout, stderr, error } = await runWrangler("publish");
|
|
38
|
+
|
|
39
|
+
expect(stdout).toMatchInlineSnapshot(`
|
|
40
|
+
"Uploaded
|
|
41
|
+
test-name
|
|
42
|
+
(0.00 sec)
|
|
43
|
+
Deployed
|
|
44
|
+
test-name
|
|
45
|
+
(0.00 sec)
|
|
46
|
+
|
|
47
|
+
test-name.test-sub-domain.workers.dev"
|
|
48
|
+
`);
|
|
49
|
+
expect(stderr).toMatchInlineSnapshot(`""`);
|
|
50
|
+
expect(error).toMatchInlineSnapshot(`undefined`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/** Write a mock wrangler.toml file to disk. */
|
|
55
|
+
function writeWranglerToml(main?: string) {
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
"./wrangler.toml",
|
|
58
|
+
[
|
|
59
|
+
`compatibility_date = "2022-01-12"`,
|
|
60
|
+
`name = "test-name"`,
|
|
61
|
+
main !== undefined ? `[build.upload]\nmain = "${main}"` : "",
|
|
62
|
+
].join("\n"),
|
|
63
|
+
"utf-8"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Write a mock Worker script to disk. */
|
|
68
|
+
function writeEsmWorkerSource() {
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
"index.js",
|
|
71
|
+
[
|
|
72
|
+
`import { foo } from "./another";`,
|
|
73
|
+
`export default {`,
|
|
74
|
+
` async fetch(request) {`,
|
|
75
|
+
` return new Response('Hello' + foo);`,
|
|
76
|
+
` },`,
|
|
77
|
+
`};`,
|
|
78
|
+
].join("\n")
|
|
79
|
+
);
|
|
80
|
+
fs.writeFileSync("another.js", `export const foo = 100;`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Create a mock handler for the request to upload a worker script. */
|
|
84
|
+
function mockUploadWorkerRequest(available_on_subdomain = true) {
|
|
85
|
+
setMockResponse(
|
|
86
|
+
"/accounts/:accountId/workers/scripts/:scriptName",
|
|
87
|
+
"PUT",
|
|
88
|
+
([_url, accountId, scriptName], _init, queryParams) => {
|
|
89
|
+
expect(accountId).toEqual("some-account-id");
|
|
90
|
+
expect(scriptName).toEqual("test-name");
|
|
91
|
+
expect(queryParams.get("available_on_subdomains")).toEqual("true");
|
|
92
|
+
return { available_on_subdomain };
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Create a mock handler the request for the account's subdomain. */
|
|
98
|
+
function mockSubDomainRequest(subdomain = "test-sub-domain") {
|
|
99
|
+
setMockResponse("/accounts/:accountId/workers/subdomain", () => {
|
|
100
|
+
return { subdomain };
|
|
101
|
+
});
|
|
102
|
+
}
|
package/src/api/form_data.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function toMimeType(type: CfModuleType): string {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function toModule(module: CfModule, entryType
|
|
26
|
+
function toModule(module: CfModule, entryType: CfModuleType): Blob {
|
|
27
27
|
const { type: moduleType, content } = module;
|
|
28
28
|
const type = toMimeType(moduleType ?? entryType);
|
|
29
29
|
|
package/src/api/preview.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import { fetchResult } from "../cfetch";
|
|
2
3
|
import { toFormData } from "./form_data";
|
|
3
4
|
import type { CfAccount, CfWorkerInit } from "./worker";
|
|
4
|
-
import fetch from "node-fetch";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A preview mode.
|
|
@@ -60,7 +60,7 @@ async function sessionToken(account: CfAccount): Promise<CfPreviewToken> {
|
|
|
60
60
|
? `/zones/${zoneId}/workers/edge-preview`
|
|
61
61
|
: `/accounts/${accountId}/workers/subdomain/edge-preview`;
|
|
62
62
|
|
|
63
|
-
const { exchange_url } = await
|
|
63
|
+
const { exchange_url } = await fetchResult<{ exchange_url: string }>(initUrl);
|
|
64
64
|
const { inspector_websocket, token } = (await (
|
|
65
65
|
await fetch(exchange_url)
|
|
66
66
|
).json()) as { inspector_websocket: string; token: string };
|
|
@@ -106,7 +106,7 @@ export async function previewToken(
|
|
|
106
106
|
const formData = toFormData(worker);
|
|
107
107
|
formData.set("wrangler-session-config", JSON.stringify(mode));
|
|
108
108
|
|
|
109
|
-
const { preview_token } = await
|
|
109
|
+
const { preview_token } = await fetchResult<{ preview_token: string }>(url, {
|
|
110
110
|
method: "POST",
|
|
111
111
|
// @ts-expect-error TODO: fix this
|
|
112
112
|
body: formData,
|
package/src/api/worker.ts
CHANGED
|
@@ -112,7 +112,7 @@ export interface CfWorkerInit {
|
|
|
112
112
|
/**
|
|
113
113
|
* The name of the worker.
|
|
114
114
|
*/
|
|
115
|
-
name: string |
|
|
115
|
+
name: string | undefined;
|
|
116
116
|
/**
|
|
117
117
|
* The entrypoint module.
|
|
118
118
|
*/
|
|
@@ -120,7 +120,7 @@ export interface CfWorkerInit {
|
|
|
120
120
|
/**
|
|
121
121
|
* The list of additional modules.
|
|
122
122
|
*/
|
|
123
|
-
modules:
|
|
123
|
+
modules: undefined | CfModule[];
|
|
124
124
|
/**
|
|
125
125
|
* All the bindings
|
|
126
126
|
*/
|
|
@@ -130,10 +130,10 @@ export interface CfWorkerInit {
|
|
|
130
130
|
vars?: CfVars;
|
|
131
131
|
services?: CfService[];
|
|
132
132
|
};
|
|
133
|
-
migrations:
|
|
134
|
-
compatibility_date: string |
|
|
135
|
-
compatibility_flags:
|
|
136
|
-
usage_model:
|
|
133
|
+
migrations: undefined | CfDurableObjectMigrations;
|
|
134
|
+
compatibility_date: string | undefined;
|
|
135
|
+
compatibility_flags: undefined | string[];
|
|
136
|
+
usage_model: undefined | "bundled" | "unbound";
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { RequestInit } from "node-fetch";
|
|
2
|
+
import { URLSearchParams } from "url";
|
|
3
|
+
import { fetchInternal } from "./internal";
|
|
4
|
+
|
|
5
|
+
// Check out https://api.cloudflare.com/ for API docs.
|
|
6
|
+
|
|
7
|
+
export { CF_API_BASE_URL } from "./internal";
|
|
8
|
+
|
|
9
|
+
export interface FetchError {
|
|
10
|
+
code: number;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FetchResult<ResponseType = unknown> {
|
|
14
|
+
success: boolean;
|
|
15
|
+
result: ResponseType;
|
|
16
|
+
errors: FetchError[];
|
|
17
|
+
messages: string[];
|
|
18
|
+
result_info?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make a fetch request for a raw JSON value.
|
|
23
|
+
*/
|
|
24
|
+
export async function fetchRaw<ResponseType>(
|
|
25
|
+
resource: string,
|
|
26
|
+
init: RequestInit = {},
|
|
27
|
+
queryParams?: URLSearchParams
|
|
28
|
+
): Promise<ResponseType> {
|
|
29
|
+
return fetchInternal<ResponseType>(resource, init, queryParams);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Make a fetch request, and extract the `result` from the JSON response.
|
|
34
|
+
*/
|
|
35
|
+
export async function fetchResult<ResponseType>(
|
|
36
|
+
resource: string,
|
|
37
|
+
init: RequestInit = {},
|
|
38
|
+
queryParams?: URLSearchParams
|
|
39
|
+
): Promise<ResponseType> {
|
|
40
|
+
const json = await fetchInternal<FetchResult<ResponseType>>(
|
|
41
|
+
resource,
|
|
42
|
+
init,
|
|
43
|
+
queryParams
|
|
44
|
+
);
|
|
45
|
+
if (json.success) {
|
|
46
|
+
return json.result;
|
|
47
|
+
} else {
|
|
48
|
+
throwFetchError(resource, json);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Make a fetch request for a list of values,
|
|
54
|
+
* extracting the `result` from the JSON response,
|
|
55
|
+
* and repeating the request if the results are paginated.
|
|
56
|
+
*/
|
|
57
|
+
export async function fetchListResult<ResponseType>(
|
|
58
|
+
resource: string,
|
|
59
|
+
init: RequestInit = {},
|
|
60
|
+
queryParams?: URLSearchParams
|
|
61
|
+
): Promise<ResponseType[]> {
|
|
62
|
+
const results: ResponseType[] = [];
|
|
63
|
+
let getMoreResults = true;
|
|
64
|
+
let cursor: string | undefined;
|
|
65
|
+
while (getMoreResults) {
|
|
66
|
+
if (cursor) {
|
|
67
|
+
queryParams = new URLSearchParams(queryParams);
|
|
68
|
+
queryParams.set("cursor", cursor);
|
|
69
|
+
}
|
|
70
|
+
const json = await fetchInternal<FetchResult<ResponseType[]>>(
|
|
71
|
+
resource,
|
|
72
|
+
init,
|
|
73
|
+
queryParams
|
|
74
|
+
);
|
|
75
|
+
if (json.success) {
|
|
76
|
+
results.push(...json.result);
|
|
77
|
+
if (hasCursor(json.result_info)) {
|
|
78
|
+
cursor = json.result_info?.cursor;
|
|
79
|
+
} else {
|
|
80
|
+
getMoreResults = false;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
throwFetchError(resource, json);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function throwFetchError(
|
|
90
|
+
resource: string,
|
|
91
|
+
response: FetchResult<unknown>
|
|
92
|
+
): never {
|
|
93
|
+
response.messages.forEach((message) => console.warn(message));
|
|
94
|
+
const errors = response.errors
|
|
95
|
+
.map((error) => `${error.code}: ${error.message}`)
|
|
96
|
+
.join("\n");
|
|
97
|
+
throw new Error(`Failed to fetch ${resource} - ${errors}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hasCursor(result_info: unknown): result_info is { cursor: string } {
|
|
101
|
+
return (result_info as { cursor } | undefined)?.cursor !== undefined;
|
|
102
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import type { RequestInit, HeadersInit } from "node-fetch";
|
|
3
|
+
import { getAPIToken, loginOrRefreshIfRequired } from "../user";
|
|
4
|
+
|
|
5
|
+
export const CF_API_BASE_URL =
|
|
6
|
+
process.env.CF_API_BASE_URL || "https://api.cloudflare.com/client/v4";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Make a fetch request to the Cloudflare API.
|
|
10
|
+
*
|
|
11
|
+
* This function handles acquiring the API token and logging the caller in, as necessary.
|
|
12
|
+
*
|
|
13
|
+
* Check out https://api.cloudflare.com/ for API docs.
|
|
14
|
+
*
|
|
15
|
+
* This function should not be used directly, instead use the functions in `cfetch/index.ts`.
|
|
16
|
+
*/
|
|
17
|
+
export async function fetchInternal<ResponseType>(
|
|
18
|
+
resource: string,
|
|
19
|
+
init: RequestInit = {},
|
|
20
|
+
queryParams?: URLSearchParams
|
|
21
|
+
): Promise<ResponseType> {
|
|
22
|
+
await requireLoggedIn();
|
|
23
|
+
const apiToken = requireApiToken();
|
|
24
|
+
const headers = cloneHeaders(init.headers);
|
|
25
|
+
addAuthorizationHeader(headers, apiToken);
|
|
26
|
+
|
|
27
|
+
const queryString = queryParams ? `?${queryParams.toString()}` : "";
|
|
28
|
+
const response = await fetch(`${CF_API_BASE_URL}${resource}${queryString}`, {
|
|
29
|
+
method: "GET",
|
|
30
|
+
...init,
|
|
31
|
+
headers,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (response.ok) {
|
|
35
|
+
return (await response.json()) as ResponseType;
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Failed to fetch ${resource} - ${response.status}: ${response.statusText}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cloneHeaders(headers: HeadersInit | undefined): HeadersInit {
|
|
44
|
+
return { ...headers };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function requireLoggedIn(): Promise<void> {
|
|
48
|
+
const loggedIn = await loginOrRefreshIfRequired();
|
|
49
|
+
if (!loggedIn) {
|
|
50
|
+
throw new Error("Not logged in.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireApiToken(): string {
|
|
55
|
+
const apiToken = getAPIToken();
|
|
56
|
+
if (!apiToken) {
|
|
57
|
+
throw new Error("No API token found.");
|
|
58
|
+
}
|
|
59
|
+
return apiToken;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function addAuthorizationHeader(headers: HeadersInit, apiToken: string): void {
|
|
63
|
+
if (headers["Authorization"]) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"The request already specifies an authorisation header - cannot add a new one."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
headers["Authorization"] = `Bearer ${apiToken}`;
|
|
69
|
+
}
|