wrangler 0.0.0-e22f9d7 → 0.0.0-e2a26a0
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/wrangler.js +2 -2
- package/package.json +15 -10
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +21 -13
- package/pages/functions/filepath-routing.ts +38 -28
- package/pages/functions/routes.ts +8 -6
- package/pages/functions/template-worker.ts +16 -24
- package/src/__tests__/dev.test.tsx +7 -3
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +33 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +49 -13
- package/src/__tests__/helpers/mock-console.ts +56 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/run-in-tmp.ts +29 -0
- package/src/__tests__/helpers/run-wrangler.ts +14 -0
- package/src/__tests__/index.test.ts +312 -56
- package/src/__tests__/jest.setup.ts +17 -2
- package/src/__tests__/kv.test.ts +344 -316
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1222 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/whoami.test.tsx +86 -0
- package/src/api/form_data.ts +10 -7
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +3 -3
- package/src/cfetch/index.ts +14 -14
- package/src/cfetch/internal.ts +40 -8
- package/src/config.ts +162 -8
- package/src/dev.tsx +103 -53
- package/src/dialogs.tsx +2 -2
- package/src/index.tsx +438 -311
- package/src/inspect.ts +80 -23
- package/src/kv.tsx +9 -1
- package/src/module-collection.ts +5 -6
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +41 -26
- package/src/paths.ts +26 -0
- package/src/proxy.ts +78 -10
- package/src/publish.ts +98 -52
- package/src/sites.tsx +175 -53
- package/src/user.tsx +51 -59
- package/src/whoami.tsx +69 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/wrangler-dist/cli.js +111479 -110187
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/run-in-tmp.ts +0 -19
- package/src/__tests__/run-wrangler.ts +0 -32
package/src/inspect.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import assert from "assert";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { createServer } from "http";
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
|
|
6
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
7
7
|
import { version } from "../package.json";
|
|
8
|
-
|
|
9
8
|
import type Protocol from "devtools-protocol";
|
|
9
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
10
|
+
import type { MessageEvent } from "ws";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* `useInspector` is a hook for debugging Workers applications
|
|
@@ -315,9 +316,14 @@ export default function useInspector(props: InspectorProps) {
|
|
|
315
316
|
};
|
|
316
317
|
}, [
|
|
317
318
|
props.inspectorUrl,
|
|
318
|
-
retryRemoteWebSocketConnectionSigil,
|
|
319
319
|
props.logToTerminal,
|
|
320
320
|
wsServer,
|
|
321
|
+
// We use a state value as a sigil to trigger a retry of the
|
|
322
|
+
// remote websocket connection. It's not used inside the effect,
|
|
323
|
+
// so react-hooks/exhaustive-deps doesn't complain if it's not
|
|
324
|
+
// included in the dependency array. But its presence is critical,
|
|
325
|
+
// so do NOT remove it from the dependency list.
|
|
326
|
+
retryRemoteWebSocketConnectionSigil,
|
|
321
327
|
]);
|
|
322
328
|
|
|
323
329
|
/**
|
|
@@ -432,6 +438,33 @@ function randomId(): string {
|
|
|
432
438
|
* we're just doing a little bit of the work of the devtools console,
|
|
433
439
|
* directly in the terminal.
|
|
434
440
|
*/
|
|
441
|
+
|
|
442
|
+
export const mapConsoleAPIMessageTypeToConsoleMethod: {
|
|
443
|
+
[key in Protocol.Runtime.ConsoleAPICalledEvent["type"]]: Exclude<
|
|
444
|
+
keyof Console,
|
|
445
|
+
"Console"
|
|
446
|
+
>;
|
|
447
|
+
} = {
|
|
448
|
+
log: "log",
|
|
449
|
+
debug: "debug",
|
|
450
|
+
info: "info",
|
|
451
|
+
warning: "warn",
|
|
452
|
+
error: "error",
|
|
453
|
+
dir: "dir",
|
|
454
|
+
dirxml: "dirxml",
|
|
455
|
+
table: "table",
|
|
456
|
+
trace: "trace",
|
|
457
|
+
clear: "clear",
|
|
458
|
+
count: "count",
|
|
459
|
+
assert: "assert",
|
|
460
|
+
profile: "profile",
|
|
461
|
+
profileEnd: "profileEnd",
|
|
462
|
+
timeEnd: "timeEnd",
|
|
463
|
+
startGroup: "group",
|
|
464
|
+
startGroupCollapsed: "groupCollapsed",
|
|
465
|
+
endGroup: "groupEnd",
|
|
466
|
+
};
|
|
467
|
+
|
|
435
468
|
function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
436
469
|
const args: string[] = [];
|
|
437
470
|
for (const ro of evt.args) {
|
|
@@ -449,7 +482,11 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
449
482
|
break;
|
|
450
483
|
case "object":
|
|
451
484
|
if (!ro.preview) {
|
|
452
|
-
args.push(
|
|
485
|
+
args.push(
|
|
486
|
+
ro.subtype === "null"
|
|
487
|
+
? "null"
|
|
488
|
+
: ro.description ?? "<no-description>"
|
|
489
|
+
);
|
|
453
490
|
} else {
|
|
454
491
|
args.push(ro.preview.description ?? "<no-description>");
|
|
455
492
|
|
|
@@ -467,11 +504,14 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
467
504
|
);
|
|
468
505
|
|
|
469
506
|
break;
|
|
507
|
+
case "weakmap":
|
|
470
508
|
case "map":
|
|
471
509
|
args.push(
|
|
472
510
|
"{\n" +
|
|
473
|
-
|
|
474
|
-
|
|
511
|
+
// Maps always have entries
|
|
512
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
513
|
+
ro.preview
|
|
514
|
+
.entries!.map(({ key, value }) => {
|
|
475
515
|
return ` ${key?.description ?? "<unknown>"} => ${
|
|
476
516
|
value.description
|
|
477
517
|
}`;
|
|
@@ -482,38 +522,47 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
482
522
|
);
|
|
483
523
|
|
|
484
524
|
break;
|
|
525
|
+
case "weakset":
|
|
485
526
|
case "set":
|
|
486
527
|
args.push(
|
|
487
528
|
"{ " +
|
|
488
|
-
|
|
489
|
-
|
|
529
|
+
// Sets always have entries
|
|
530
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
531
|
+
ro.preview
|
|
532
|
+
.entries!.map(({ value }) => {
|
|
490
533
|
return `${value.description}`;
|
|
491
534
|
})
|
|
492
535
|
.join(", ") +
|
|
493
536
|
(ro.preview.overflow ? ", ..." : "") +
|
|
494
537
|
" }"
|
|
495
538
|
);
|
|
496
|
-
|
|
497
539
|
break;
|
|
498
|
-
case "null":
|
|
499
|
-
args.push("null");
|
|
500
|
-
break;
|
|
501
|
-
case "node":
|
|
502
540
|
case "regexp":
|
|
541
|
+
break;
|
|
503
542
|
case "date":
|
|
504
|
-
|
|
505
|
-
case "weakset":
|
|
506
|
-
case "iterator":
|
|
543
|
+
break;
|
|
507
544
|
case "generator":
|
|
508
|
-
|
|
509
|
-
|
|
545
|
+
args.push(ro.preview.properties[0].value || "");
|
|
546
|
+
break;
|
|
510
547
|
case "promise":
|
|
548
|
+
if (ro.preview.properties[0].value === "pending") {
|
|
549
|
+
args.push(`{<${ro.preview.properties[0].value}>}`);
|
|
550
|
+
} else {
|
|
551
|
+
args.push(
|
|
552
|
+
`{<${ro.preview.properties[0].value}>: ${ro.preview.properties[1].value}}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
case "node":
|
|
557
|
+
case "iterator":
|
|
558
|
+
case "proxy":
|
|
511
559
|
case "typedarray":
|
|
512
560
|
case "arraybuffer":
|
|
513
561
|
case "dataview":
|
|
514
562
|
case "webassemblymemory":
|
|
515
563
|
case "wasmvalue":
|
|
516
564
|
break;
|
|
565
|
+
case "error":
|
|
517
566
|
default:
|
|
518
567
|
// just a pojo
|
|
519
568
|
args.push(
|
|
@@ -535,5 +584,13 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
535
584
|
}
|
|
536
585
|
}
|
|
537
586
|
|
|
538
|
-
|
|
587
|
+
const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
|
|
588
|
+
|
|
589
|
+
if (method in console) {
|
|
590
|
+
// eslint-disable-next-line prefer-spread
|
|
591
|
+
console[method].apply(console, args);
|
|
592
|
+
} else {
|
|
593
|
+
console.warn(`Unsupported console method: ${method}`);
|
|
594
|
+
console.log("console event:", evt);
|
|
595
|
+
}
|
|
539
596
|
}
|
package/src/kv.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { URLSearchParams } from "node:url";
|
|
2
|
+
import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
|
|
2
3
|
import type { Config } from "./config";
|
|
3
|
-
import { fetchListResult, fetchResult } from "./cfetch";
|
|
4
4
|
|
|
5
5
|
type KvArgs = {
|
|
6
6
|
binding?: string;
|
|
@@ -115,6 +115,14 @@ export async function putKeyValue(
|
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
export async function getKeyValue(
|
|
119
|
+
accountId: string,
|
|
120
|
+
namespaceId: string,
|
|
121
|
+
key: string
|
|
122
|
+
): Promise<string> {
|
|
123
|
+
return await fetchKVGetValue(accountId, namespaceId, key);
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
export async function putBulkKeyValue(
|
|
119
127
|
accountId: string,
|
|
120
128
|
namespaceId: string,
|
package/src/module-collection.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import type { CfModule } from "./api/worker";
|
|
2
5
|
import type esbuild from "esbuild";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
-
import crypto from "node:crypto";
|
|
6
6
|
|
|
7
7
|
// This is a combination of an esbuild plugin and a mutable array
|
|
8
8
|
// that we use to collect module references from source code.
|
|
@@ -30,11 +30,10 @@ export default function makeModuleCollector(): {
|
|
|
30
30
|
build.onResolve(
|
|
31
31
|
// filter on "known" file types,
|
|
32
32
|
// we can expand this list later
|
|
33
|
-
{ filter: /.*\.(
|
|
33
|
+
{ filter: /.*\.(wasm)$/ },
|
|
34
34
|
async (args: esbuild.OnResolveArgs) => {
|
|
35
35
|
// take the file and massage it to a
|
|
36
36
|
// transportable/manageable format
|
|
37
|
-
const fileExt = path.extname(args.path);
|
|
38
37
|
const filePath = path.join(args.resolveDir, args.path);
|
|
39
38
|
const fileContent = await readFile(filePath);
|
|
40
39
|
const fileHash = crypto
|
|
@@ -47,7 +46,7 @@ export default function makeModuleCollector(): {
|
|
|
47
46
|
modules.push({
|
|
48
47
|
name: fileName,
|
|
49
48
|
content: fileContent,
|
|
50
|
-
type:
|
|
49
|
+
type: "compiled-wasm",
|
|
51
50
|
});
|
|
52
51
|
|
|
53
52
|
return {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execa, execaCommandSync } from "execa";
|
|
4
|
+
|
|
5
|
+
export interface PackageManager {
|
|
6
|
+
addDevDeps(...packages: string[]): Promise<void>;
|
|
7
|
+
install(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getPackageManager(root: string): Promise<PackageManager> {
|
|
11
|
+
const [hasYarn, hasNpm] = await Promise.all([supportsYarn(), supportsNpm()]);
|
|
12
|
+
const hasYarnLock = existsSync(join(root, "yarn.lock"));
|
|
13
|
+
const hasNpmLock = existsSync(join(root, "package-lock.json"));
|
|
14
|
+
|
|
15
|
+
if (hasNpmLock) {
|
|
16
|
+
if (hasNpm) {
|
|
17
|
+
console.log(
|
|
18
|
+
"Using npm as package manager, as there is already a package-lock.json file."
|
|
19
|
+
);
|
|
20
|
+
return NpmPackageManager;
|
|
21
|
+
} else if (hasYarn) {
|
|
22
|
+
console.log("Using yarn as package manager.");
|
|
23
|
+
console.warn(
|
|
24
|
+
"There is already a package-lock.json file but could not find npm on the PATH."
|
|
25
|
+
);
|
|
26
|
+
return YarnPackageManager;
|
|
27
|
+
}
|
|
28
|
+
} else if (hasYarnLock) {
|
|
29
|
+
if (hasYarn) {
|
|
30
|
+
console.log(
|
|
31
|
+
"Using yarn as package manager, as there is already a yarn.lock file."
|
|
32
|
+
);
|
|
33
|
+
return YarnPackageManager;
|
|
34
|
+
} else if (hasNpm) {
|
|
35
|
+
console.log("Using npm as package manager.");
|
|
36
|
+
console.warn(
|
|
37
|
+
"There is already a yarn.lock file but could not find yarn on the PATH."
|
|
38
|
+
);
|
|
39
|
+
return NpmPackageManager;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (hasNpm) {
|
|
44
|
+
console.log("Using npm as package manager.");
|
|
45
|
+
return NpmPackageManager;
|
|
46
|
+
} else if (hasYarn) {
|
|
47
|
+
console.log("Using yarn as package manager.");
|
|
48
|
+
return YarnPackageManager;
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"Unable to find a package manager. Supported managers are: npm and yarn."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the name of the given `packageManager`.
|
|
58
|
+
*/
|
|
59
|
+
export function getPackageManagerName(packageManager: unknown): string {
|
|
60
|
+
return packageManager === NpmPackageManager
|
|
61
|
+
? "npm"
|
|
62
|
+
: packageManager === YarnPackageManager
|
|
63
|
+
? "yarn"
|
|
64
|
+
: "unknown";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Manage packages using npm
|
|
69
|
+
*/
|
|
70
|
+
const NpmPackageManager: PackageManager = {
|
|
71
|
+
/** Add and install a new devDependency into the local package.json. */
|
|
72
|
+
async addDevDeps(...packages: string[]): Promise<void> {
|
|
73
|
+
await execa("npm", ["install", ...packages, "--save-dev"], {
|
|
74
|
+
stdio: "inherit",
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/** Install all the dependencies in the local package.json. */
|
|
79
|
+
async install(): Promise<void> {
|
|
80
|
+
await execa("npm", ["install"], {
|
|
81
|
+
stdio: "inherit",
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Manage packages using yarn
|
|
88
|
+
*/
|
|
89
|
+
const YarnPackageManager: PackageManager = {
|
|
90
|
+
/** Add and install a new devDependency into the local package.json. */
|
|
91
|
+
async addDevDeps(...packages: string[]): Promise<void> {
|
|
92
|
+
await execa("yarn", ["add", ...packages, "--dev"], {
|
|
93
|
+
stdio: "inherit",
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/** Install all the dependencies in the local package.json. */
|
|
98
|
+
async install(): Promise<void> {
|
|
99
|
+
await execa("yarn", ["install"], {
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
async function supports(name: string): Promise<boolean> {
|
|
106
|
+
try {
|
|
107
|
+
execaCommandSync(`${name} --version`, { stdio: "ignore" });
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function supportsYarn(): Promise<boolean> {
|
|
115
|
+
return supports("yarn");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function supportsNpm(): Promise<boolean> {
|
|
119
|
+
return supports("npm");
|
|
120
|
+
}
|
package/src/pages.tsx
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/* eslint-disable no-shadow */
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { URL } from "url";
|
|
3
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { URL } from "node:url";
|
|
8
|
+
import { watch } from "chokidar";
|
|
10
9
|
import { getType } from "mime";
|
|
11
10
|
import open from "open";
|
|
12
|
-
import { watch } from "chokidar";
|
|
13
|
-
import type { BuildResult } from "esbuild";
|
|
14
11
|
import { buildWorker } from "../pages/functions/buildWorker";
|
|
15
|
-
import type { Config } from "../pages/functions/routes";
|
|
16
|
-
import { writeRoutesModule } from "../pages/functions/routes";
|
|
17
12
|
import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
|
|
13
|
+
import { writeRoutesModule } from "../pages/functions/routes";
|
|
14
|
+
import { toUrlPath } from "./paths";
|
|
15
|
+
import type { Config } from "../pages/functions/routes";
|
|
16
|
+
import type { Headers, Request, fetch } from "@miniflare/core";
|
|
17
|
+
import type { BuildResult } from "esbuild";
|
|
18
|
+
import type { MiniflareOptions } from "miniflare";
|
|
19
|
+
import type { BuilderCallback } from "yargs";
|
|
18
20
|
|
|
19
21
|
// Defer importing miniflare until we really need it. This takes ~0.5s
|
|
20
22
|
// and also modifies some `stream/web` and `undici` prototypes, so we
|
|
21
23
|
// don't want to do this if pages commands aren't being called.
|
|
22
|
-
import type { Headers, Request, fetch } from "@miniflare/core";
|
|
23
|
-
import type { MiniflareOptions } from "miniflare";
|
|
24
24
|
|
|
25
|
-
const EXIT_CALLBACKS = [];
|
|
25
|
+
const EXIT_CALLBACKS: (() => void)[] = [];
|
|
26
26
|
const EXIT = (message?: string, code?: number) => {
|
|
27
27
|
if (message) console.log(message);
|
|
28
28
|
if (code) process.exitCode = code;
|
|
@@ -122,7 +122,9 @@ async function spawnProxyProcess({
|
|
|
122
122
|
},
|
|
123
123
|
}
|
|
124
124
|
);
|
|
125
|
-
EXIT_CALLBACKS.push(() =>
|
|
125
|
+
EXIT_CALLBACKS.push(() => {
|
|
126
|
+
proxy.kill();
|
|
127
|
+
});
|
|
126
128
|
|
|
127
129
|
proxy.stdout.on("data", (data) => {
|
|
128
130
|
console.log(`[proxy]: ${data}`);
|
|
@@ -664,16 +666,17 @@ async function buildFunctions({
|
|
|
664
666
|
);
|
|
665
667
|
|
|
666
668
|
const routesModule = join(tmpdir(), "./functionsRoutes.mjs");
|
|
669
|
+
const baseURL = toUrlPath("/");
|
|
667
670
|
|
|
668
671
|
const config: Config = await generateConfigFromFileTree({
|
|
669
672
|
baseDir: functionsDirectory,
|
|
670
|
-
baseURL
|
|
673
|
+
baseURL,
|
|
671
674
|
});
|
|
672
675
|
|
|
673
676
|
if (outputConfigPath) {
|
|
674
677
|
writeFileSync(
|
|
675
678
|
outputConfigPath,
|
|
676
|
-
JSON.stringify({ ...config, baseURL
|
|
679
|
+
JSON.stringify({ ...config, baseURL }, null, 2)
|
|
677
680
|
);
|
|
678
681
|
}
|
|
679
682
|
|
|
@@ -827,6 +830,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
827
830
|
scriptPath,
|
|
828
831
|
};
|
|
829
832
|
} else {
|
|
833
|
+
scriptReadyResolve();
|
|
834
|
+
|
|
830
835
|
const scriptPath =
|
|
831
836
|
directory !== undefined
|
|
832
837
|
? join(directory, singleWorkerScriptPath)
|
|
@@ -862,11 +867,13 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
862
867
|
// internally just waits for that promise to resolve.
|
|
863
868
|
await scriptReadyPromise;
|
|
864
869
|
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
const assetsFetch =
|
|
868
|
-
|
|
869
|
-
|
|
870
|
+
// `assetsFetch()` will only be called if there is `proxyPort` defined.
|
|
871
|
+
// We only define `proxyPort`, above, when there is no `directory` defined.
|
|
872
|
+
const assetsFetch =
|
|
873
|
+
directory !== undefined
|
|
874
|
+
? await generateAssetsFetch(directory)
|
|
875
|
+
: invalidAssetsFetch;
|
|
876
|
+
|
|
870
877
|
const miniflare = new Miniflare({
|
|
871
878
|
port,
|
|
872
879
|
watch: true,
|
|
@@ -897,7 +904,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
897
904
|
if (proxyPort) {
|
|
898
905
|
try {
|
|
899
906
|
const url = new URL(request.url);
|
|
900
|
-
url.host = `
|
|
907
|
+
url.host = `localhost:${proxyPort}`;
|
|
901
908
|
return await fetch(url, request);
|
|
902
909
|
} catch (thrown) {
|
|
903
910
|
console.error(`Could not proxy request: ${thrown}`);
|
|
@@ -935,10 +942,10 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
935
942
|
try {
|
|
936
943
|
// `startServer` might throw if user code contains errors
|
|
937
944
|
const server = await miniflare.startServer();
|
|
938
|
-
console.log(`Serving at http://
|
|
945
|
+
console.log(`Serving at http://localhost:${port}/`);
|
|
939
946
|
|
|
940
947
|
if (process.env.BROWSER !== "none") {
|
|
941
|
-
const childProcess = await open(`http://
|
|
948
|
+
const childProcess = await open(`http://localhost:${port}/`);
|
|
942
949
|
// fail silently if the open command doesn't work (e.g. in GitHub Codespaces)
|
|
943
950
|
childProcess.on("error", (_err) => {});
|
|
944
951
|
}
|
|
@@ -962,7 +969,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
962
969
|
}
|
|
963
970
|
}
|
|
964
971
|
)
|
|
965
|
-
.command("functions",
|
|
972
|
+
.command("functions", false, (yargs) =>
|
|
973
|
+
// we hide this command from help output because
|
|
974
|
+
// it's not meant to be used directly right now
|
|
966
975
|
yargs.command(
|
|
967
976
|
"build [directory]",
|
|
968
977
|
"Compile a folder of Cloudflare Pages Functions into a single Worker",
|
|
@@ -1029,3 +1038,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1029
1038
|
)
|
|
1030
1039
|
);
|
|
1031
1040
|
};
|
|
1041
|
+
|
|
1042
|
+
const invalidAssetsFetch: typeof fetch = () => {
|
|
1043
|
+
throw new Error(
|
|
1044
|
+
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
|
|
1045
|
+
);
|
|
1046
|
+
};
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { assert } from "console";
|
|
2
|
+
|
|
3
|
+
type DiscriminatedPath<Discriminator extends string> = string & {
|
|
4
|
+
_discriminator: Discriminator;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A branded string that expects to be URL compatible.
|
|
9
|
+
*
|
|
10
|
+
* Require this type when you want callers to ensure that they have converted file-path strings into URL-safe paths.
|
|
11
|
+
*/
|
|
12
|
+
export type UrlPath = DiscriminatedPath<"UrlPath">;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a file-path string to a URL-path string.
|
|
16
|
+
*
|
|
17
|
+
* Use this helper to convert a `string` to a `UrlPath` when it is not clear whether the string needs normalizing.
|
|
18
|
+
* Replaces all back-slashes with forward-slashes, and throws an error if the path contains a drive letter (e.g. `C:`).
|
|
19
|
+
*/
|
|
20
|
+
export function toUrlPath(path: string): UrlPath {
|
|
21
|
+
assert(
|
|
22
|
+
!/^[a-z]:/i.test(path),
|
|
23
|
+
"Tried to convert a Windows file path with a drive to a URL path."
|
|
24
|
+
);
|
|
25
|
+
return path.replace(/\\/g, "/") as UrlPath;
|
|
26
|
+
}
|
package/src/proxy.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { connect } from "node:http2";
|
|
2
|
-
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
3
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { connect } from "node:http2";
|
|
3
|
+
import WebSocket from "faye-websocket";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import serveStatic from "serve-static";
|
|
6
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
4
7
|
import type {
|
|
5
8
|
IncomingHttpHeaders,
|
|
6
9
|
RequestListener,
|
|
@@ -8,10 +11,7 @@ import type {
|
|
|
8
11
|
ServerResponse,
|
|
9
12
|
Server,
|
|
10
13
|
} from "node:http";
|
|
11
|
-
import
|
|
12
|
-
import serveStatic from "serve-static";
|
|
13
|
-
import type { CfPreviewToken } from "./api/preview";
|
|
14
|
-
import { useEffect, useRef } from "react";
|
|
14
|
+
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* `usePreviewServer` is a React hook that creates a local development
|
|
@@ -80,6 +80,17 @@ export function usePreviewServer({
|
|
|
80
80
|
{ request: IncomingMessage; response: ServerResponse }[]
|
|
81
81
|
>([]);
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The session doesn't last forever, and will evetually drop
|
|
85
|
+
* (usually within 5-15 minutes). When that happens, we simply
|
|
86
|
+
* restart the effect, effectively restarting the server. We use
|
|
87
|
+
* a state sigil as an effect dependency to do so.
|
|
88
|
+
*/
|
|
89
|
+
const [retryServerSetupSigil, setRetryServerSetupSigil] = useState<number>(0);
|
|
90
|
+
function retryServerSetup() {
|
|
91
|
+
setRetryServerSetupSigil((x) => x + 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
useEffect(() => {
|
|
84
95
|
// If we don't have a token, that means either we're just starting up,
|
|
85
96
|
// or we're refreshing the token.
|
|
@@ -119,6 +130,11 @@ export function usePreviewServer({
|
|
|
119
130
|
const remote = connect(`https://${previewToken.host}`);
|
|
120
131
|
cleanupListeners.push(() => remote.destroy());
|
|
121
132
|
|
|
133
|
+
// As mentioned above, the session may die at any point,
|
|
134
|
+
// so we need to restart the effect.
|
|
135
|
+
remote.on("close", retryServerSetup);
|
|
136
|
+
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
137
|
+
|
|
122
138
|
/** HTTP/2 -> HTTP/2 */
|
|
123
139
|
const handleStream = createStreamHandler(previewToken, remote, port);
|
|
124
140
|
proxy.on("stream", handleStream);
|
|
@@ -209,15 +225,31 @@ export function usePreviewServer({
|
|
|
209
225
|
cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
|
|
210
226
|
|
|
211
227
|
return () => {
|
|
212
|
-
cleanupListeners.forEach((
|
|
228
|
+
cleanupListeners.forEach((cleanup) => cleanup());
|
|
213
229
|
};
|
|
214
|
-
}, [
|
|
230
|
+
}, [
|
|
231
|
+
previewToken,
|
|
232
|
+
publicRoot,
|
|
233
|
+
port,
|
|
234
|
+
proxy,
|
|
235
|
+
// We use a state value as a sigil to trigger reconnecting the server.
|
|
236
|
+
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
237
|
+
// doesn't complain if it's not included in the dependency array.
|
|
238
|
+
// But its presence is critical, so Do NOT remove it from the dependency list.
|
|
239
|
+
retryServerSetupSigil,
|
|
240
|
+
]);
|
|
215
241
|
|
|
216
242
|
// Start/stop the server whenever the
|
|
217
243
|
// containing component is mounted/unmounted.
|
|
218
244
|
useEffect(() => {
|
|
219
|
-
|
|
220
|
-
|
|
245
|
+
waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
|
|
246
|
+
.then(() => {
|
|
247
|
+
proxy.listen(port);
|
|
248
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
249
|
+
})
|
|
250
|
+
.catch((err) => {
|
|
251
|
+
console.error(`⬣ Failed to start server: ${err}`);
|
|
252
|
+
});
|
|
221
253
|
|
|
222
254
|
return () => {
|
|
223
255
|
proxy.close();
|
|
@@ -300,3 +332,39 @@ function createStreamHandler(
|
|
|
300
332
|
});
|
|
301
333
|
};
|
|
302
334
|
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* A helper function that waits for a port to be available.
|
|
338
|
+
*/
|
|
339
|
+
export async function waitForPortToBeAvailable(
|
|
340
|
+
port: number,
|
|
341
|
+
options: { retryPeriod: number; timeout: number }
|
|
342
|
+
): Promise<void> {
|
|
343
|
+
return new Promise((resolve, reject) => {
|
|
344
|
+
const timeout = setTimeout(() => {
|
|
345
|
+
reject(new Error(`Timed out waiting for port ${port}`));
|
|
346
|
+
}, options.timeout);
|
|
347
|
+
|
|
348
|
+
function checkPort() {
|
|
349
|
+
// Testing whether a port is 'available' involves simply
|
|
350
|
+
// trying to make a server listen on that port, and retrying
|
|
351
|
+
// until it succeeds.
|
|
352
|
+
const server = createServer();
|
|
353
|
+
server.on("error", (err) => {
|
|
354
|
+
// @ts-expect-error non standard property on Error
|
|
355
|
+
if (err.code === "EADDRINUSE") {
|
|
356
|
+
setTimeout(checkPort, options.retryPeriod);
|
|
357
|
+
} else {
|
|
358
|
+
reject(err);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
server.listen(port, () => {
|
|
362
|
+
server.close();
|
|
363
|
+
clearTimeout(timeout);
|
|
364
|
+
resolve();
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
checkPort();
|
|
369
|
+
});
|
|
370
|
+
}
|