wrangler 0.0.0-e95bea2 → 0.0.0-ea8e701
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/README.md +3 -3
- package/bin/wrangler.js +10 -1
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +7 -6
- package/pages/functions/filepath-routing.test.ts +125 -25
- package/pages/functions/filepath-routing.ts +6 -3
- package/src/__tests__/configuration.test.ts +826 -223
- package/src/__tests__/dev.test.tsx +145 -9
- package/src/__tests__/guess-worker-format.test.ts +26 -1
- package/src/__tests__/helpers/mock-account-id.ts +4 -0
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-oauth-flow.ts +178 -0
- package/src/__tests__/helpers/run-in-tmp.ts +3 -0
- package/src/__tests__/index.test.ts +9 -13
- package/src/__tests__/jest.setup.ts +5 -0
- package/src/__tests__/kv.test.ts +43 -71
- package/src/__tests__/pages.test.ts +48 -10
- package/src/__tests__/publish.test.ts +1456 -327
- package/src/__tests__/r2.test.ts +4 -4
- package/src/__tests__/tail.test.ts +78 -4
- package/src/__tests__/user.test.ts +83 -0
- package/src/__tests__/whoami.test.tsx +16 -7
- package/src/bundle.ts +15 -2
- package/src/cfetch/internal.ts +9 -5
- package/src/config/README.md +6 -6
- package/src/config/config.ts +17 -78
- package/src/config/environment.ts +67 -1
- package/src/config/index.ts +13 -3
- package/src/config/validation-helpers.ts +177 -11
- package/src/config/validation.ts +305 -100
- package/src/dev/dev.tsx +76 -41
- package/src/dev/local.tsx +105 -114
- package/src/dev/remote.tsx +2 -0
- package/src/dev/use-esbuild.ts +4 -0
- package/src/entry.ts +103 -6
- package/src/index.tsx +251 -189
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv/index.tsx → kv.ts} +8 -28
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +58 -53
- package/src/pages.tsx +26 -10
- package/src/proxy.ts +5 -3
- package/src/publish.ts +17 -21
- package/src/sites.tsx +67 -25
- package/src/tail/index.ts +1 -1
- package/src/tail/printing.ts +2 -2
- package/src/user.tsx +161 -81
- package/wrangler-dist/cli.js +3418 -1437
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/dev2.test.tsx +0 -152
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/__tests__/logout.test.ts +0 -50
package/src/dev/dev.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { watch } from "chokidar";
|
|
3
3
|
import clipboardy from "clipboardy";
|
|
4
4
|
import commandExists from "command-exists";
|
|
5
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
5
|
+
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
|
6
6
|
import React, { useState, useEffect, useRef } from "react";
|
|
7
7
|
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
8
8
|
import onExit from "signal-exit";
|
|
@@ -19,17 +19,20 @@ import type { Config } from "../config";
|
|
|
19
19
|
import type { Entry } from "../entry";
|
|
20
20
|
import type { AssetPaths } from "../sites";
|
|
21
21
|
import type { CfWorkerInit } from "../worker";
|
|
22
|
+
import type { EsbuildBundle } from "./use-esbuild";
|
|
22
23
|
|
|
23
24
|
export type DevProps = {
|
|
24
25
|
name?: string;
|
|
25
26
|
entry: Entry;
|
|
26
|
-
port
|
|
27
|
+
port: number;
|
|
28
|
+
ip: string;
|
|
27
29
|
inspectorPort: number;
|
|
28
30
|
rules: Config["rules"];
|
|
29
31
|
accountId: undefined | string;
|
|
30
32
|
initialMode: "local" | "remote";
|
|
31
33
|
jsxFactory: undefined | string;
|
|
32
34
|
jsxFragment: undefined | string;
|
|
35
|
+
tsconfig: string | undefined;
|
|
33
36
|
upstreamProtocol: "https" | "http";
|
|
34
37
|
localProtocol: "https" | "http";
|
|
35
38
|
enableLocalPersistence: boolean;
|
|
@@ -39,7 +42,7 @@ export type DevProps = {
|
|
|
39
42
|
compatibilityDate: undefined | string;
|
|
40
43
|
compatibilityFlags: undefined | string[];
|
|
41
44
|
usageModel: undefined | "bundled" | "unbound";
|
|
42
|
-
|
|
45
|
+
build: {
|
|
43
46
|
command?: undefined | string;
|
|
44
47
|
cwd?: undefined | string;
|
|
45
48
|
watch_dir?: undefined | string;
|
|
@@ -55,11 +58,10 @@ export type DevProps = {
|
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
export function DevImplementation(props: DevProps): JSX.Element {
|
|
58
|
-
const port = props.port ?? 8787;
|
|
59
61
|
const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
|
|
60
62
|
const directory = useTmpDir();
|
|
61
63
|
|
|
62
|
-
useCustomBuild(props.entry, props.
|
|
64
|
+
useCustomBuild(props.entry, props.build);
|
|
63
65
|
|
|
64
66
|
if (props.public && props.entry.format === "service-worker") {
|
|
65
67
|
throw new Error(
|
|
@@ -87,14 +89,36 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
87
89
|
rules: props.rules,
|
|
88
90
|
jsxFragment: props.jsxFragment,
|
|
89
91
|
serveAssetsFromWorker: !!props.public,
|
|
92
|
+
tsconfig: props.tsconfig,
|
|
90
93
|
});
|
|
91
94
|
|
|
95
|
+
// only load the UI if we're running in a supported environment
|
|
96
|
+
const { isRawModeSupported } = useStdin();
|
|
97
|
+
return isRawModeSupported ? (
|
|
98
|
+
<InteractiveDevSession {...props} bundle={bundle} apiToken={apiToken} />
|
|
99
|
+
) : (
|
|
100
|
+
<DevSession
|
|
101
|
+
{...props}
|
|
102
|
+
bundle={bundle}
|
|
103
|
+
apiToken={apiToken}
|
|
104
|
+
local={props.initialMode === "local"}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type InteractiveDevSessionProps = DevProps & {
|
|
110
|
+
apiToken: string | undefined;
|
|
111
|
+
bundle: EsbuildBundle | undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function InteractiveDevSession(props: InteractiveDevSessionProps) {
|
|
92
115
|
const toggles = useHotkeys(
|
|
93
116
|
{
|
|
94
117
|
local: props.initialMode === "local",
|
|
95
118
|
tunnel: false,
|
|
96
119
|
},
|
|
97
|
-
port,
|
|
120
|
+
props.port,
|
|
121
|
+
props.ip,
|
|
98
122
|
props.inspectorPort,
|
|
99
123
|
props.localProtocol
|
|
100
124
|
);
|
|
@@ -103,40 +127,7 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
103
127
|
|
|
104
128
|
return (
|
|
105
129
|
<>
|
|
106
|
-
{toggles.local
|
|
107
|
-
<Local
|
|
108
|
-
name={props.name}
|
|
109
|
-
bundle={bundle}
|
|
110
|
-
format={props.entry.format}
|
|
111
|
-
bindings={props.bindings}
|
|
112
|
-
assetPaths={props.assetPaths}
|
|
113
|
-
public={props.public}
|
|
114
|
-
port={port}
|
|
115
|
-
rules={props.rules}
|
|
116
|
-
inspectorPort={props.inspectorPort}
|
|
117
|
-
enableLocalPersistence={props.enableLocalPersistence}
|
|
118
|
-
/>
|
|
119
|
-
) : (
|
|
120
|
-
<Remote
|
|
121
|
-
name={props.name}
|
|
122
|
-
bundle={bundle}
|
|
123
|
-
format={props.entry.format}
|
|
124
|
-
accountId={props.accountId}
|
|
125
|
-
apiToken={apiToken}
|
|
126
|
-
bindings={props.bindings}
|
|
127
|
-
assetPaths={props.assetPaths}
|
|
128
|
-
public={props.public}
|
|
129
|
-
port={port}
|
|
130
|
-
localProtocol={props.localProtocol}
|
|
131
|
-
inspectorPort={props.inspectorPort}
|
|
132
|
-
compatibilityDate={props.compatibilityDate}
|
|
133
|
-
compatibilityFlags={props.compatibilityFlags}
|
|
134
|
-
usageModel={props.usageModel}
|
|
135
|
-
env={props.env}
|
|
136
|
-
legacyEnv={props.legacyEnv}
|
|
137
|
-
zone={props.zone}
|
|
138
|
-
/>
|
|
139
|
-
)}
|
|
130
|
+
<DevSession {...props} local={toggles.local} />
|
|
140
131
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
141
132
|
<Text>
|
|
142
133
|
{`B to open a browser, D to open Devtools, L to ${
|
|
@@ -148,6 +139,49 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
148
139
|
);
|
|
149
140
|
}
|
|
150
141
|
|
|
142
|
+
type DevSessionProps = InteractiveDevSessionProps & { local: boolean };
|
|
143
|
+
|
|
144
|
+
function DevSession(props: DevSessionProps) {
|
|
145
|
+
return props.local ? (
|
|
146
|
+
<Local
|
|
147
|
+
name={props.name}
|
|
148
|
+
bundle={props.bundle}
|
|
149
|
+
format={props.entry.format}
|
|
150
|
+
compatibilityDate={props.compatibilityDate}
|
|
151
|
+
compatibilityFlags={props.compatibilityFlags}
|
|
152
|
+
bindings={props.bindings}
|
|
153
|
+
assetPaths={props.assetPaths}
|
|
154
|
+
public={props.public}
|
|
155
|
+
port={props.port}
|
|
156
|
+
ip={props.ip}
|
|
157
|
+
rules={props.rules}
|
|
158
|
+
inspectorPort={props.inspectorPort}
|
|
159
|
+
enableLocalPersistence={props.enableLocalPersistence}
|
|
160
|
+
/>
|
|
161
|
+
) : (
|
|
162
|
+
<Remote
|
|
163
|
+
name={props.name}
|
|
164
|
+
bundle={props.bundle}
|
|
165
|
+
format={props.entry.format}
|
|
166
|
+
accountId={props.accountId}
|
|
167
|
+
apiToken={props.apiToken}
|
|
168
|
+
bindings={props.bindings}
|
|
169
|
+
assetPaths={props.assetPaths}
|
|
170
|
+
public={props.public}
|
|
171
|
+
port={props.port}
|
|
172
|
+
ip={props.ip}
|
|
173
|
+
localProtocol={props.localProtocol}
|
|
174
|
+
inspectorPort={props.inspectorPort}
|
|
175
|
+
compatibilityDate={props.compatibilityDate}
|
|
176
|
+
compatibilityFlags={props.compatibilityFlags}
|
|
177
|
+
usageModel={props.usageModel}
|
|
178
|
+
env={props.env}
|
|
179
|
+
legacyEnv={props.legacyEnv}
|
|
180
|
+
zone={props.zone}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
151
185
|
export interface DirectorySyncResult {
|
|
152
186
|
name: string;
|
|
153
187
|
removeCallback: () => void;
|
|
@@ -295,6 +329,7 @@ type useHotkeysInitialState = {
|
|
|
295
329
|
function useHotkeys(
|
|
296
330
|
initial: useHotkeysInitialState,
|
|
297
331
|
port: number,
|
|
332
|
+
ip: string,
|
|
298
333
|
inspectorPort: number,
|
|
299
334
|
localProtocol: "http" | "https"
|
|
300
335
|
) {
|
|
@@ -309,7 +344,7 @@ function useHotkeys(
|
|
|
309
344
|
switch (input.toLowerCase()) {
|
|
310
345
|
// open browser
|
|
311
346
|
case "b": {
|
|
312
|
-
await openInBrowser(`${localProtocol}
|
|
347
|
+
await openInBrowser(`${localProtocol}://${ip}:${port}`);
|
|
313
348
|
break;
|
|
314
349
|
}
|
|
315
350
|
// toggle inspector
|
package/src/dev/local.tsx
CHANGED
|
@@ -11,30 +11,26 @@ import type { Config } from "../config";
|
|
|
11
11
|
import type { AssetPaths } from "../sites";
|
|
12
12
|
import type { CfWorkerInit, CfScriptFormat } from "../worker";
|
|
13
13
|
import type { EsbuildBundle } from "./use-esbuild";
|
|
14
|
+
import type { MiniflareOptions } from "miniflare";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
interface LocalProps {
|
|
16
17
|
name: undefined | string;
|
|
17
18
|
bundle: EsbuildBundle | undefined;
|
|
18
19
|
format: CfScriptFormat | undefined;
|
|
20
|
+
compatibilityDate: string | undefined;
|
|
21
|
+
compatibilityFlags: undefined | string[];
|
|
19
22
|
bindings: CfWorkerInit["bindings"];
|
|
20
23
|
assetPaths: undefined | AssetPaths;
|
|
21
24
|
public: undefined | string;
|
|
22
25
|
port: number;
|
|
26
|
+
ip: string;
|
|
23
27
|
rules: Config["rules"];
|
|
24
28
|
inspectorPort: number;
|
|
25
29
|
enableLocalPersistence: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
format: props.format,
|
|
31
|
-
bindings: props.bindings,
|
|
32
|
-
assetPaths: props.assetPaths,
|
|
33
|
-
public: props.public,
|
|
34
|
-
port: props.port,
|
|
35
|
-
rules: props.rules,
|
|
36
|
-
enableLocalPersistence: props.enableLocalPersistence,
|
|
37
|
-
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Local(props: LocalProps) {
|
|
33
|
+
const { inspectorUrl } = useLocalWorker(props);
|
|
38
34
|
useInspector({
|
|
39
35
|
inspectorUrl,
|
|
40
36
|
port: props.inspectorPort,
|
|
@@ -43,19 +39,21 @@ export function Local(props: {
|
|
|
43
39
|
return null;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
function useLocalWorker(
|
|
47
|
-
name:
|
|
48
|
-
bundle
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
function useLocalWorker({
|
|
43
|
+
name: workerName,
|
|
44
|
+
bundle,
|
|
45
|
+
format,
|
|
46
|
+
compatibilityDate,
|
|
47
|
+
compatibilityFlags,
|
|
48
|
+
bindings,
|
|
49
|
+
assetPaths,
|
|
50
|
+
public: publicDirectory,
|
|
51
|
+
port,
|
|
52
|
+
rules,
|
|
53
|
+
enableLocalPersistence,
|
|
54
|
+
ip,
|
|
55
|
+
}: LocalProps) {
|
|
57
56
|
// TODO: pass vars via command line
|
|
58
|
-
const { bundle, format, bindings, port, assetPaths } = props;
|
|
59
57
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
60
58
|
const removeSignalExitListener = useRef<() => void>();
|
|
61
59
|
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
@@ -66,7 +64,7 @@ function useLocalWorker(props: {
|
|
|
66
64
|
// port for the worker
|
|
67
65
|
await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
|
|
68
66
|
|
|
69
|
-
if (
|
|
67
|
+
if (publicDirectory) {
|
|
70
68
|
throw new Error(
|
|
71
69
|
'⎔ A "public" folder is not yet supported in local mode.'
|
|
72
70
|
);
|
|
@@ -81,100 +79,89 @@ function useLocalWorker(props: {
|
|
|
81
79
|
);
|
|
82
80
|
}
|
|
83
81
|
|
|
82
|
+
const scriptPath = realpathSync(bundle.path);
|
|
83
|
+
|
|
84
|
+
const wasmBindings = { ...bindings.wasm_modules };
|
|
85
|
+
if (format === "service-worker") {
|
|
86
|
+
for (const { type, name } of bundle.modules) {
|
|
87
|
+
if (type === "compiled-wasm") {
|
|
88
|
+
// In service-worker format, .wasm modules are referenced by global identifiers,
|
|
89
|
+
// so we convert it here.
|
|
90
|
+
// This identifier has to be a valid JS identifier, so we replace all non alphanumeric
|
|
91
|
+
// characters with an underscore.
|
|
92
|
+
const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
93
|
+
wasmBindings[identifier] = name;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const options: MiniflareOptions = {
|
|
99
|
+
name: workerName,
|
|
100
|
+
port,
|
|
101
|
+
scriptPath,
|
|
102
|
+
host: ip,
|
|
103
|
+
modules: format === "modules",
|
|
104
|
+
modulesRules: (rules || [])
|
|
105
|
+
.concat(DEFAULT_MODULE_RULES)
|
|
106
|
+
.map(({ type, globs: include, fallthrough }) => ({
|
|
107
|
+
type,
|
|
108
|
+
include,
|
|
109
|
+
fallthrough,
|
|
110
|
+
})),
|
|
111
|
+
compatibilityDate,
|
|
112
|
+
compatibilityFlags,
|
|
113
|
+
kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
|
|
114
|
+
kvPersist: enableLocalPersistence,
|
|
115
|
+
durableObjects: Object.fromEntries(
|
|
116
|
+
(bindings.durable_objects?.bindings ?? []).map<[string, string]>(
|
|
117
|
+
(value) => [value.name, value.class_name]
|
|
118
|
+
)
|
|
119
|
+
),
|
|
120
|
+
durableObjectsPersist: enableLocalPersistence,
|
|
121
|
+
cachePersist: enableLocalPersistence,
|
|
122
|
+
sitePath: assetPaths?.assetDirectory
|
|
123
|
+
? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
|
|
124
|
+
: undefined,
|
|
125
|
+
siteInclude: assetPaths?.includePatterns.length
|
|
126
|
+
? assetPaths?.includePatterns
|
|
127
|
+
: undefined,
|
|
128
|
+
siteExclude: assetPaths?.excludePatterns.length
|
|
129
|
+
? assetPaths.excludePatterns
|
|
130
|
+
: undefined,
|
|
131
|
+
bindings: bindings.vars,
|
|
132
|
+
wasmBindings,
|
|
133
|
+
sourceMap: true,
|
|
134
|
+
logUnhandledRejections: true,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// The path to the Miniflare CLI assumes that this file is being run from
|
|
138
|
+
// `wrangler-dist` and that the CLI is found in `miniflare-dist`.
|
|
139
|
+
// If either of those paths change this line needs updating.
|
|
140
|
+
const miniflareCLIPath = path.resolve(
|
|
141
|
+
__dirname,
|
|
142
|
+
"../miniflare-dist/index.mjs"
|
|
143
|
+
);
|
|
144
|
+
const optionsArg = JSON.stringify(options, null);
|
|
145
|
+
|
|
84
146
|
console.log("⎔ Starting a local server...");
|
|
85
|
-
// TODO: just use execa for this
|
|
86
147
|
local.current = spawn(
|
|
87
148
|
"node",
|
|
88
149
|
[
|
|
89
|
-
"--experimental-vm-modules",
|
|
90
|
-
"--
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"--
|
|
95
|
-
path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
|
|
96
|
-
"--env",
|
|
97
|
-
path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
|
|
98
|
-
"--package",
|
|
99
|
-
path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
|
|
100
|
-
"--port",
|
|
101
|
-
port.toString(),
|
|
102
|
-
...(assetPaths
|
|
103
|
-
? [
|
|
104
|
-
"--site",
|
|
105
|
-
path.join(process.cwd(), assetPaths.baseDirectory),
|
|
106
|
-
...assetPaths.includePatterns.map((pattern) => [
|
|
107
|
-
"--site-include",
|
|
108
|
-
pattern,
|
|
109
|
-
]),
|
|
110
|
-
...assetPaths.excludePatterns.map((pattern) => [
|
|
111
|
-
"--site-exclude",
|
|
112
|
-
pattern,
|
|
113
|
-
]),
|
|
114
|
-
].flatMap((x) => x)
|
|
115
|
-
: []),
|
|
116
|
-
...(props.enableLocalPersistence
|
|
117
|
-
? ["--kv-persist", "--cache-persist", "--do-persist"]
|
|
118
|
-
: []),
|
|
119
|
-
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
120
|
-
return ["--binding", `${key}=${value}`];
|
|
121
|
-
}),
|
|
122
|
-
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
123
|
-
return ["--kv", binding];
|
|
124
|
-
}),
|
|
125
|
-
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
126
|
-
({ name, class_name }) => {
|
|
127
|
-
return ["--do", `${name}=${class_name}`];
|
|
128
|
-
}
|
|
129
|
-
),
|
|
130
|
-
...Object.entries(bindings.wasm_modules || {}).flatMap(
|
|
131
|
-
([name, filePath]) => {
|
|
132
|
-
return [
|
|
133
|
-
"--wasm",
|
|
134
|
-
`${name}=${path.join(process.cwd(), filePath)}`,
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
),
|
|
138
|
-
...bundle.modules.reduce<string[]>((cmd, { name, type }) => {
|
|
139
|
-
if (format === "service-worker") {
|
|
140
|
-
if (type === "compiled-wasm") {
|
|
141
|
-
// In service-worker format, .wasm modules are referenced
|
|
142
|
-
// by global identifiers, so we convert it here.
|
|
143
|
-
// This identifier has to be a valid JS identifier,
|
|
144
|
-
// so we replace all non alphanumeric characters
|
|
145
|
-
// with an underscore.
|
|
146
|
-
const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
147
|
-
return cmd.concat([`--wasm`, `${identifier}=${name}`]);
|
|
148
|
-
} else {
|
|
149
|
-
// TODO: we should actually support this
|
|
150
|
-
throw new Error(
|
|
151
|
-
`⎔ Unsupported module type ${type} for file ${name} in service-worker format`
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return cmd;
|
|
156
|
-
}, []),
|
|
157
|
-
"--modules",
|
|
158
|
-
String(format === "modules"),
|
|
159
|
-
...(props.rules || [])
|
|
160
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
161
|
-
.concat(DEFAULT_MODULE_RULES!)
|
|
162
|
-
.flatMap((rule) =>
|
|
163
|
-
rule.globs.flatMap((glob) => [
|
|
164
|
-
"--modules-rule",
|
|
165
|
-
`${rule.type}=${glob}`,
|
|
166
|
-
])
|
|
167
|
-
),
|
|
150
|
+
"--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
|
|
151
|
+
"--no-warnings", // hide annoying Node warnings
|
|
152
|
+
"--inspect", // start Miniflare listening for a debugger to attach
|
|
153
|
+
miniflareCLIPath,
|
|
154
|
+
optionsArg,
|
|
155
|
+
// "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
|
|
168
156
|
],
|
|
169
157
|
{
|
|
170
|
-
cwd: path.dirname(
|
|
158
|
+
cwd: path.dirname(scriptPath),
|
|
171
159
|
}
|
|
172
160
|
);
|
|
173
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
174
161
|
|
|
175
162
|
local.current.on("close", (code) => {
|
|
176
163
|
if (code) {
|
|
177
|
-
console.log(`
|
|
164
|
+
console.log(`Miniflare process exited with code ${code}`);
|
|
178
165
|
}
|
|
179
166
|
});
|
|
180
167
|
|
|
@@ -195,12 +182,12 @@ function useLocalWorker(props: {
|
|
|
195
182
|
|
|
196
183
|
local.current.on("exit", (code) => {
|
|
197
184
|
if (code) {
|
|
198
|
-
console.error(`
|
|
185
|
+
console.error(`Miniflare process exited with code ${code}`);
|
|
199
186
|
}
|
|
200
187
|
});
|
|
201
188
|
|
|
202
189
|
local.current.on("error", (error: Error) => {
|
|
203
|
-
console.error(`
|
|
190
|
+
console.error(`Miniflare process failed to spawn`);
|
|
204
191
|
console.error(error);
|
|
205
192
|
});
|
|
206
193
|
|
|
@@ -226,15 +213,19 @@ function useLocalWorker(props: {
|
|
|
226
213
|
};
|
|
227
214
|
}, [
|
|
228
215
|
bundle,
|
|
216
|
+
workerName,
|
|
229
217
|
format,
|
|
230
218
|
port,
|
|
219
|
+
ip,
|
|
231
220
|
bindings.durable_objects?.bindings,
|
|
232
221
|
bindings.kv_namespaces,
|
|
233
222
|
bindings.vars,
|
|
234
|
-
|
|
223
|
+
compatibilityDate,
|
|
224
|
+
compatibilityFlags,
|
|
225
|
+
enableLocalPersistence,
|
|
235
226
|
assetPaths,
|
|
236
|
-
|
|
237
|
-
|
|
227
|
+
publicDirectory,
|
|
228
|
+
rules,
|
|
238
229
|
bindings.wasm_modules,
|
|
239
230
|
]);
|
|
240
231
|
return { inspectorUrl };
|
package/src/dev/remote.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export function Remote(props: {
|
|
|
18
18
|
public: undefined | string;
|
|
19
19
|
assetPaths: undefined | AssetPaths;
|
|
20
20
|
port: number;
|
|
21
|
+
ip: string;
|
|
21
22
|
localProtocol: "https" | "http";
|
|
22
23
|
inspectorPort: number;
|
|
23
24
|
accountId: undefined | string;
|
|
@@ -55,6 +56,7 @@ export function Remote(props: {
|
|
|
55
56
|
publicRoot: props.public,
|
|
56
57
|
localProtocol: props.localProtocol,
|
|
57
58
|
localPort: props.port,
|
|
59
|
+
ip: props.ip,
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
useInspector({
|
package/src/dev/use-esbuild.ts
CHANGED
|
@@ -23,6 +23,7 @@ export function useEsbuild({
|
|
|
23
23
|
jsxFragment,
|
|
24
24
|
rules,
|
|
25
25
|
serveAssetsFromWorker,
|
|
26
|
+
tsconfig,
|
|
26
27
|
}: {
|
|
27
28
|
entry: Entry;
|
|
28
29
|
destination: string | undefined;
|
|
@@ -31,6 +32,7 @@ export function useEsbuild({
|
|
|
31
32
|
jsxFragment: string | undefined;
|
|
32
33
|
rules: Config["rules"];
|
|
33
34
|
serveAssetsFromWorker: boolean;
|
|
35
|
+
tsconfig: string | undefined;
|
|
34
36
|
}): EsbuildBundle | undefined {
|
|
35
37
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
36
38
|
useEffect(() => {
|
|
@@ -64,6 +66,7 @@ export function useEsbuild({
|
|
|
64
66
|
jsxFragment,
|
|
65
67
|
rules,
|
|
66
68
|
watch: watchMode,
|
|
69
|
+
tsconfig,
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
// Capture the `stop()` method to use as the `useEffect()` destructor.
|
|
@@ -95,6 +98,7 @@ export function useEsbuild({
|
|
|
95
98
|
jsxFragment,
|
|
96
99
|
serveAssetsFromWorker,
|
|
97
100
|
rules,
|
|
101
|
+
tsconfig,
|
|
98
102
|
]);
|
|
99
103
|
return bundle;
|
|
100
104
|
}
|
package/src/entry.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type Entry = { file: string; directory: string; format: CfScriptFormat };
|
|
|
20
20
|
export async function getEntry(
|
|
21
21
|
args: { script?: string; format?: CfScriptFormat | undefined },
|
|
22
22
|
config: Config,
|
|
23
|
-
command:
|
|
23
|
+
command: "dev" | "publish"
|
|
24
24
|
): Promise<Entry> {
|
|
25
25
|
let file: string;
|
|
26
26
|
let directory = process.cwd();
|
|
@@ -46,8 +46,37 @@ export async function getEntry(
|
|
|
46
46
|
const format = await guessWorkerFormat(
|
|
47
47
|
file,
|
|
48
48
|
directory,
|
|
49
|
-
args.format ?? config.build?.upload?.format
|
|
49
|
+
args.format ?? config.build?.upload?.format,
|
|
50
|
+
config.tsconfig
|
|
50
51
|
);
|
|
52
|
+
|
|
53
|
+
const { localBindings, remoteBindings } =
|
|
54
|
+
partitionDurableObjectBindings(config);
|
|
55
|
+
|
|
56
|
+
if (command === "dev" && remoteBindings.length > 0) {
|
|
57
|
+
console.warn(
|
|
58
|
+
"WARNING: You have Durable Object bindings, which are not defined locally in the worker being developed.\n" +
|
|
59
|
+
"Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.\n" +
|
|
60
|
+
"Remote Durable Objects that are affected:\n" +
|
|
61
|
+
remoteBindings.map((b) => `- ${JSON.stringify(b)}`).join("\n")
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (format === "service-worker" && localBindings.length > 0) {
|
|
66
|
+
const errorMessage =
|
|
67
|
+
"You seem to be trying to use Durable Objects in a Worker written with Service Worker syntax.";
|
|
68
|
+
const addScriptName =
|
|
69
|
+
"You can use Durable Objects defined in other Workers by specifying a `script_name` in your wrangler.toml, where `script_name` is the name of the Worker that implements that Durable Object. For example:";
|
|
70
|
+
const addScriptNameExamples = generateAddScriptNameExamples(localBindings);
|
|
71
|
+
const migrateText =
|
|
72
|
+
"Alternatively, migrate your worker to ES Module syntax to implement a Durable Object in this Worker:";
|
|
73
|
+
const migrateUrl =
|
|
74
|
+
"https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/";
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${errorMessage}\n${addScriptName}\n${addScriptNameExamples}\n${migrateText}\n${migrateUrl}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
51
80
|
return { file, directory, format };
|
|
52
81
|
}
|
|
53
82
|
|
|
@@ -88,7 +117,8 @@ export async function runCustomBuild(
|
|
|
88
117
|
export default async function guessWorkerFormat(
|
|
89
118
|
entryFile: string,
|
|
90
119
|
entryWorkingDirectory: string,
|
|
91
|
-
hint: CfScriptFormat | undefined
|
|
120
|
+
hint: CfScriptFormat | undefined,
|
|
121
|
+
tsconfig?: string | undefined
|
|
92
122
|
): Promise<CfScriptFormat> {
|
|
93
123
|
const result = await esbuild.build({
|
|
94
124
|
entryPoints: [entryFile],
|
|
@@ -97,6 +127,12 @@ export default async function guessWorkerFormat(
|
|
|
97
127
|
bundle: false,
|
|
98
128
|
format: "esm",
|
|
99
129
|
write: false,
|
|
130
|
+
loader: {
|
|
131
|
+
".js": "jsx",
|
|
132
|
+
".mjs": "jsx",
|
|
133
|
+
".cjs": "jsx",
|
|
134
|
+
},
|
|
135
|
+
...(tsconfig && { tsconfig }),
|
|
100
136
|
});
|
|
101
137
|
// result.metafile is defined because of the `metafile: true` option above.
|
|
102
138
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -114,14 +150,30 @@ export default async function guessWorkerFormat(
|
|
|
114
150
|
"More than one entry-point found for generated bundle." +
|
|
115
151
|
listEntryPoints(entryPoints)
|
|
116
152
|
);
|
|
117
|
-
|
|
118
|
-
|
|
153
|
+
|
|
154
|
+
let guessedWorkerFormat: CfScriptFormat;
|
|
155
|
+
const scriptExports = entryPoints[0][1].exports;
|
|
156
|
+
if (scriptExports.length > 0) {
|
|
157
|
+
if (scriptExports.includes("default")) {
|
|
158
|
+
guessedWorkerFormat = "modules";
|
|
159
|
+
} else {
|
|
160
|
+
console.warn(
|
|
161
|
+
`The entrypoint ${path.relative(
|
|
162
|
+
process.cwd(),
|
|
163
|
+
entryFile
|
|
164
|
+
)} has exports like an ES Module, but hasn't defined a default export like a module worker normally would. Building the worker using "service-worker" format...`
|
|
165
|
+
);
|
|
166
|
+
guessedWorkerFormat = "service-worker";
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
guessedWorkerFormat = "service-worker";
|
|
170
|
+
}
|
|
119
171
|
|
|
120
172
|
if (hint) {
|
|
121
173
|
if (hint !== guessedWorkerFormat) {
|
|
122
174
|
if (hint === "service-worker") {
|
|
123
175
|
throw new Error(
|
|
124
|
-
"You configured this worker to be a 'service-worker', but the file you are trying to build appears to have
|
|
176
|
+
"You configured this worker to be a 'service-worker', but the file you are trying to build appears to have a `default` export like a module worker. Please pass `--format modules`, or simply remove the configuration."
|
|
125
177
|
);
|
|
126
178
|
} else {
|
|
127
179
|
throw new Error(
|
|
@@ -157,3 +209,48 @@ export function fileExists(filePath: string): boolean {
|
|
|
157
209
|
}
|
|
158
210
|
return false;
|
|
159
211
|
}
|
|
212
|
+
|
|
213
|
+
type DurableObjectBindings = Config["durable_objects"]["bindings"];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Groups the durable object bindings into two lists:
|
|
217
|
+
* those that are defined locally and those that refer to a durable object defined in another script.
|
|
218
|
+
*/
|
|
219
|
+
function partitionDurableObjectBindings(config: Config): {
|
|
220
|
+
localBindings: DurableObjectBindings;
|
|
221
|
+
remoteBindings: DurableObjectBindings;
|
|
222
|
+
} {
|
|
223
|
+
const localBindings: DurableObjectBindings = [];
|
|
224
|
+
const remoteBindings: DurableObjectBindings = [];
|
|
225
|
+
for (const binding of config.durable_objects.bindings) {
|
|
226
|
+
if (binding.script_name === undefined) {
|
|
227
|
+
localBindings.push(binding);
|
|
228
|
+
} else {
|
|
229
|
+
remoteBindings.push(binding);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { localBindings, remoteBindings };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generates some help text based on the Durable Object bindings in a given
|
|
237
|
+
* config indicating how the user can add a `script_name` field to bind an
|
|
238
|
+
* externally defined Durable Object.
|
|
239
|
+
*/
|
|
240
|
+
function generateAddScriptNameExamples(
|
|
241
|
+
localBindings: DurableObjectBindings
|
|
242
|
+
): string {
|
|
243
|
+
function exampleScriptName(binding_name: string): string {
|
|
244
|
+
return `${binding_name.toLowerCase().replaceAll("_", "-")}-worker`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return localBindings
|
|
248
|
+
.map(({ name, class_name }) => {
|
|
249
|
+
const script_name = exampleScriptName(name);
|
|
250
|
+
const currentBinding = `{ name = ${name}, class_name = ${class_name} }`;
|
|
251
|
+
const fixedBinding = `{ name = ${name}, class_name = ${class_name}, script_name = ${script_name} }`;
|
|
252
|
+
|
|
253
|
+
return `${currentBinding} ==> ${fixedBinding}`;
|
|
254
|
+
})
|
|
255
|
+
.join("\n");
|
|
256
|
+
}
|