wrangler
Advanced tools
Comparing version 0.0.0-6370075 to 0.0.0-00e51cd
{ | ||
"name": "wrangler", | ||
"version": "0.0.0-6370075", | ||
"version": "0.0.0-00e51cd", | ||
"author": "wrangler@cloudflare.com", | ||
@@ -39,9 +39,6 @@ "description": "Command-line interface for all things Cloudflare Workers", | ||
"dependencies": { | ||
"@cloudflare/pages-functions-compiler": "0.3.7", | ||
"esbuild": "0.14.1", | ||
"miniflare": "2.0.0-rc.3", | ||
"semiver": "^1.1.0", | ||
"serve": "^13.0.2", | ||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1", | ||
"@esbuild-plugins/node-modules-polyfill": "^0.1.2" | ||
"miniflare": "2.1.0", | ||
"path-to-regexp": "^6.2.0", | ||
"semiver": "^1.1.0" | ||
}, | ||
@@ -52,29 +49,36 @@ "optionalDependencies": { | ||
"devDependencies": { | ||
"@babel/types": "^7.16.0", | ||
"@iarna/toml": "^2.2.5", | ||
"@types/cloudflare": "^2.7.6", | ||
"@types/express": "^4.17.13", | ||
"@types/estree": "^0.0.50", | ||
"@types/mime": "^2.0.3", | ||
"@types/react": "^17.0.37", | ||
"@types/serve-static": "^1.13.10", | ||
"@types/signal-exit": "^3.0.1", | ||
"@types/ws": "^8.2.1", | ||
"@types/yargs": "^17.0.7", | ||
"acorn": "^8.6.0", | ||
"acorn-walk": "^8.2.0", | ||
"chokidar": "^3.5.2", | ||
"clipboardy": "^3.0.0", | ||
"command-exists": "^1.2.9", | ||
"devtools-protocol": "^0.0.955664", | ||
"execa": "^6.0.0", | ||
"express": "^4.17.1", | ||
"faye-websocket": "^0.11.4", | ||
"finalhandler": "^1.1.2", | ||
"find-up": "^6.2.0", | ||
"formdata-node": "^4.3.1", | ||
"http-proxy": "^1.18.1", | ||
"http-proxy-middleware": "^2.0.1", | ||
"ink": "^3.2.0", | ||
"ink-select-input": "^4.2.1", | ||
"ink-table": "^3.0.0", | ||
"ink-testing-library": "^2.1.0", | ||
"ink-text-input": "^4.0.2", | ||
"mime": "^3.0.0", | ||
"node-fetch": "^3.1.0", | ||
"open": "^8.4.0", | ||
"path-to-regexp": "^6.2.0", | ||
"react": "^17.0.2", | ||
"react-error-boundary": "^3.1.4", | ||
"serve-static": "^1.14.1", | ||
"signal-exit": "^3.0.6", | ||
"tmp-promise": "^3.0.3", | ||
"undici": "^4.11.1", | ||
"ws": "^8.3.0", | ||
@@ -86,2 +90,3 @@ "yargs": "^17.3.0" | ||
"bin", | ||
"pages", | ||
"miniflare-config-stubs", | ||
@@ -104,2 +109,3 @@ "wrangler-dist", | ||
"jest": { | ||
"restoreMocks": true, | ||
"testRegex": ".*.(test|spec)\\.[jt]sx?$", | ||
@@ -119,4 +125,7 @@ "transformIgnorePatterns": [ | ||
] | ||
} | ||
}, | ||
"setupFilesAfterEnv": [ | ||
"<rootDir>/src/__tests__/jest.setup.ts" | ||
] | ||
} | ||
} | ||
} |
@@ -1,88 +0,116 @@ | ||
import * as fs from "node:fs"; | ||
import * as fsp from "node:fs/promises"; | ||
import * as path from "node:path"; | ||
import * as TOML from "@iarna/toml"; | ||
import { main } from "../index"; | ||
// @ts-expect-error we're mocking cfetch, so of course setMock isn't a thing | ||
import { setMock, unsetAllMocks } from "../cfetch"; | ||
import { mockConfirm } from "./mock-dialogs"; | ||
import { runWrangler } from "./run-wrangler"; | ||
import { runInTempDir } from "./run-in-tmp"; | ||
import * as fs from "node:fs"; | ||
jest.mock("../cfetch", () => { | ||
return jest.requireActual("./mock-cfetch"); | ||
}); | ||
describe("wrangler", () => { | ||
runInTempDir(); | ||
async function w(cmd: void | string, options?: { tap: boolean }) { | ||
const tapped = options?.tap ? tap() : undefined; | ||
await main([...(cmd ? cmd.split(" ") : [])]); | ||
tapped?.off(); | ||
return { stdout: tapped?.out, stderr: tapped?.err }; | ||
} | ||
describe("no command", () => { | ||
it("should display a list of available commands", async () => { | ||
const { stdout, stderr } = await runWrangler(); | ||
function tap() { | ||
const oldLog = console.log; | ||
const oldError = console.error; | ||
expect(stdout).toMatchInlineSnapshot(` | ||
"wrangler | ||
const toReturn = { | ||
off: () => { | ||
console.log = oldLog; | ||
console.error = oldError; | ||
}, | ||
out: "", | ||
err: "", | ||
}; | ||
Commands: | ||
wrangler init [name] 📥 Create a wrangler.toml configuration file | ||
wrangler dev <filename> 👂 Start a local server for developing your worker | ||
wrangler publish [script] 🆙 Publish your Worker to Cloudflare. | ||
wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker. | ||
wrangler secret 🤫 Generate a secret that can be referenced in the worker script | ||
wrangler kv:namespace 🗂️ Interact with your Workers KV Namespaces | ||
wrangler kv:key 🔑 Individually manage Workers KV key-value pairs | ||
wrangler kv:bulk 💪 Interact with multiple Workers KV key-value pairs at once | ||
wrangler pages ⚡️ Configure Cloudflare Pages | ||
console.log = (...args) => { | ||
toReturn.out += args.join(""); | ||
oldLog.apply(console, args); | ||
// console.trace(...args); // use this if you want to find the true source of your console.log | ||
}; | ||
console.error = (...args) => { | ||
toReturn.err += args.join(""); | ||
oldError.apply(console, args); | ||
}; | ||
Flags: | ||
-c, --config Path to .toml configuration file [string] | ||
-h, --help Show help [boolean] | ||
-v, --version Show version number [boolean] | ||
return toReturn; | ||
} | ||
Options: | ||
-l, --local Run on my machine [boolean] [default: false]" | ||
`); | ||
describe("wrangler", () => { | ||
it("should run", async () => { | ||
const { stdout } = await w(undefined, { tap: true }); | ||
expect(stderr).toMatchInlineSnapshot(`""`); | ||
}); | ||
}); | ||
expect(stdout).toMatchInlineSnapshot(` | ||
"wrangler | ||
describe("invalid command", () => { | ||
it("should display an error", async () => { | ||
const { error, stdout, stderr } = await runWrangler("invalid-command"); | ||
Commands: | ||
wrangler init [name] 📥 Create a wrangler.toml configuration file | ||
wrangler dev <filename> 👂 Start a local server for developing your worker | ||
wrangler publish [script] 🆙 Publish your Worker to Cloudflare. | ||
wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker. | ||
wrangler secret 🤫 Generate a secret that can be referenced in the worker script | ||
wrangler kv:namespace 🗂️ Interact with your Workers KV Namespaces | ||
wrangler kv:key 🔑 Individually manage Workers KV key-value pairs | ||
wrangler kv:bulk 💪 Interact with multiple Workers KV key-value pairs at once | ||
wrangler pages ⚡️ Configure Cloudflare Pages | ||
expect(stdout).toMatchInlineSnapshot(`""`); | ||
expect(stderr).toMatchInlineSnapshot(` | ||
"wrangler | ||
Flags: | ||
--config Path to .toml configuration file [string] | ||
--help Show help [boolean] | ||
--version Show version number [boolean] | ||
Commands: | ||
wrangler init [name] 📥 Create a wrangler.toml configuration file | ||
wrangler dev <filename> 👂 Start a local server for developing your worker | ||
wrangler publish [script] 🆙 Publish your Worker to Cloudflare. | ||
wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker. | ||
wrangler secret 🤫 Generate a secret that can be referenced in the worker script | ||
wrangler kv:namespace 🗂️ Interact with your Workers KV Namespaces | ||
wrangler kv:key 🔑 Individually manage Workers KV key-value pairs | ||
wrangler kv:bulk 💪 Interact with multiple Workers KV key-value pairs at once | ||
wrangler pages ⚡️ Configure Cloudflare Pages | ||
Options: | ||
--local Run on my machine [boolean] [default: false]" | ||
`); | ||
Flags: | ||
-c, --config Path to .toml configuration file [string] | ||
-h, --help Show help [boolean] | ||
-v, --version Show version number [boolean] | ||
Options: | ||
-l, --local Run on my machine [boolean] [default: false] | ||
Unknown command: invalid-command." | ||
`); | ||
expect(error).toMatchInlineSnapshot( | ||
`[Error: Unknown command: invalid-command.]` | ||
); | ||
}); | ||
}); | ||
describe("init", () => { | ||
const ogcwd = process.cwd(); | ||
beforeEach(() => { | ||
process.chdir(path.join(__dirname, "fixtures", "init")); | ||
it("should create a wrangler.toml", async () => { | ||
mockConfirm({ | ||
text: "No package.json found. Would you like to create one?", | ||
result: false, | ||
}); | ||
await runWrangler("init"); | ||
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8")); | ||
expect(typeof parsed.compatibility_date).toBe("string"); | ||
expect(fs.existsSync("./package.json")).toBe(false); | ||
expect(fs.existsSync("./tsconfig.json")).toBe(false); | ||
}); | ||
afterEach(async () => { | ||
await fsp.rm("./wrangler.toml"); | ||
process.chdir(ogcwd); | ||
it("should display warning when wrangler.toml already exists, and exit if user does not want to carry on", async () => { | ||
fs.writeFileSync("./wrangler.toml", "", "utf-8"); | ||
mockConfirm({ | ||
text: "Do you want to continue initializing this project?", | ||
result: false, | ||
}); | ||
const { warnings } = await runWrangler("init"); | ||
expect(warnings).toContain("wrangler.toml file already exists!"); | ||
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8")); | ||
expect(typeof parsed.compatibility_date).toBe("undefined"); | ||
}); | ||
it("should create a wrangler.toml", async () => { | ||
await w("init"); | ||
it("should display warning when wrangler.toml already exists, but continue if user does want to carry on", async () => { | ||
fs.writeFileSync("./wrangler.toml", "", "utf-8"); | ||
mockConfirm( | ||
{ | ||
text: "Do you want to continue initializing this project?", | ||
result: true, | ||
}, | ||
{ | ||
text: "No package.json found. Would you like to create one?", | ||
result: false, | ||
} | ||
); | ||
const { warnings } = await runWrangler("init"); | ||
expect(warnings).toContain("wrangler.toml file already exists!"); | ||
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8")); | ||
@@ -92,63 +120,169 @@ expect(typeof parsed.compatibility_date).toBe("string"); | ||
it("should error when wrangler.toml already exists", async () => { | ||
fs.closeSync(fs.openSync("./wrangler.toml", "w")); | ||
const { stderr } = await w("init", { tap: true }); | ||
expect(stderr.endsWith("wrangler.toml already exists.")).toBe(true); | ||
it("should create a package.json if none is found and user confirms", async () => { | ||
mockConfirm( | ||
{ | ||
text: "No package.json found. Would you like to create one?", | ||
result: true, | ||
}, | ||
{ | ||
text: "Would you like to use typescript?", | ||
result: false, | ||
} | ||
); | ||
await runWrangler("init"); | ||
expect(fs.existsSync("./package.json")).toBe(true); | ||
const packageJson = JSON.parse( | ||
fs.readFileSync("./package.json", "utf-8") | ||
); | ||
expect(packageJson.name).toEqual("worker"); // TODO: should we infer the name from the directory? | ||
expect(packageJson.version).toEqual("0.0.1"); | ||
expect(fs.existsSync("./tsconfig.json")).toBe(false); | ||
}); | ||
}); | ||
describe("kv:namespace", () => { | ||
afterAll(() => { | ||
unsetAllMocks(); | ||
it("should not touch an existing package.json in the same directory", async () => { | ||
mockConfirm({ | ||
text: "Would you like to use typescript?", | ||
result: false, | ||
}); | ||
fs.writeFileSync( | ||
"./package.json", | ||
JSON.stringify({ name: "test", version: "1.0.0" }), | ||
"utf-8" | ||
); | ||
await runWrangler("init"); | ||
const packageJson = JSON.parse( | ||
fs.readFileSync("./package.json", "utf-8") | ||
); | ||
expect(packageJson.name).toEqual("test"); | ||
expect(packageJson.version).toEqual("1.0.0"); | ||
}); | ||
let KVNamespaces: { title: string; id: string }[] = []; | ||
it("can create a namespace", async () => { | ||
setMock("/accounts/:accountId/storage/kv/namespaces", (uri, init) => { | ||
expect(init.method === "POST"); | ||
const body = JSON.parse(init.body); | ||
expect(body.title).toBe("worker-UnitTestNamespace"); | ||
KVNamespaces.push({ title: body.title, id: "some-namespace-id" }); | ||
return { id: "some-namespace-id" }; | ||
it("should not touch an existing package.json in an ancestor directory", async () => { | ||
mockConfirm({ | ||
text: "Would you like to use typescript?", | ||
result: false, | ||
}); | ||
await w("kv:namespace create UnitTestNamespace"); | ||
expect( | ||
KVNamespaces.find((ns) => ns.title === `worker-UnitTestNamespace`) | ||
).toBeTruthy(); | ||
fs.writeFileSync( | ||
"./package.json", | ||
JSON.stringify({ name: "test", version: "1.0.0" }), | ||
"utf-8" | ||
); | ||
fs.mkdirSync("./sub-1/sub-2", { recursive: true }); | ||
process.chdir("./sub-1/sub-2"); | ||
await runWrangler("init"); | ||
expect(fs.existsSync("./package.json")).toBe(false); | ||
expect(fs.existsSync("../../package.json")).toBe(true); | ||
const packageJson = JSON.parse( | ||
fs.readFileSync("../../package.json", "utf-8") | ||
); | ||
expect(packageJson.name).toEqual("test"); | ||
expect(packageJson.version).toEqual("1.0.0"); | ||
}); | ||
let createdNamespace: { id: string; title: string }; | ||
it("can list namespaces", async () => { | ||
setMock( | ||
"/accounts/:accountId/storage/kv/namespaces\\?:qs", | ||
(uri, init) => { | ||
expect(init).toBe(undefined); | ||
return KVNamespaces; | ||
it("should create a tsconfig.json and install `workers-types` if none is found and user confirms", async () => { | ||
mockConfirm( | ||
{ | ||
text: "No package.json found. Would you like to create one?", | ||
result: true, | ||
}, | ||
{ | ||
text: "Would you like to use typescript?", | ||
result: true, | ||
} | ||
); | ||
const { stdout } = await w("kv:namespace list", { tap: true }); | ||
const namespaces = JSON.parse(stdout); | ||
createdNamespace = namespaces.find( | ||
(ns) => ns.title === "worker-UnitTestNamespace" | ||
await runWrangler("init"); | ||
expect(fs.existsSync("./tsconfig.json")).toBe(true); | ||
const tsconfigJson = JSON.parse( | ||
fs.readFileSync("./tsconfig.json", "utf-8") | ||
); | ||
expect(createdNamespace.title).toBe("worker-UnitTestNamespace"); | ||
expect(tsconfigJson.compilerOptions.types).toEqual([ | ||
"@cloudflare/workers-types", | ||
]); | ||
const packageJson = JSON.parse( | ||
fs.readFileSync("./package.json", "utf-8") | ||
); | ||
expect(packageJson.devDependencies).toEqual({ | ||
"@cloudflare/workers-types": expect.any(String), | ||
}); | ||
}); | ||
it("can delete a namespace", async () => { | ||
const namespaceIdToDelete = createdNamespace.id; | ||
setMock( | ||
"/accounts/:accountId/storage/kv/namespaces/:namespaceId", | ||
(uri, init) => { | ||
expect(init.method).toBe("DELETE"); | ||
KVNamespaces = KVNamespaces.filter( | ||
(ns) => ns.id !== namespaceIdToDelete | ||
); | ||
} | ||
it("should not touch an existing tsconfig.json in the same directory", async () => { | ||
fs.writeFileSync( | ||
"./package.json", | ||
JSON.stringify({ name: "test", version: "1.0.0" }), | ||
"utf-8" | ||
); | ||
await w(`kv:namespace delete --namespace-id ${namespaceIdToDelete}`); | ||
expect(KVNamespaces.find((ns) => ns.id === namespaceIdToDelete)).toBe( | ||
undefined | ||
fs.writeFileSync( | ||
"./tsconfig.json", | ||
JSON.stringify({ compilerOptions: {} }), | ||
"utf-8" | ||
); | ||
await runWrangler("init"); | ||
const tsconfigJson = JSON.parse( | ||
fs.readFileSync("./tsconfig.json", "utf-8") | ||
); | ||
expect(tsconfigJson.compilerOptions).toEqual({}); | ||
}); | ||
it("should not touch an existing package.json in an ancestor directory", async () => { | ||
fs.writeFileSync( | ||
"./package.json", | ||
JSON.stringify({ name: "test", version: "1.0.0" }), | ||
"utf-8" | ||
); | ||
fs.writeFileSync( | ||
"./tsconfig.json", | ||
JSON.stringify({ compilerOptions: {} }), | ||
"utf-8" | ||
); | ||
fs.mkdirSync("./sub-1/sub-2", { recursive: true }); | ||
process.chdir("./sub-1/sub-2"); | ||
await runWrangler("init"); | ||
expect(fs.existsSync("./tsconfig.json")).toBe(false); | ||
expect(fs.existsSync("../../tsconfig.json")).toBe(true); | ||
const tsconfigJson = JSON.parse( | ||
fs.readFileSync("../../tsconfig.json", "utf-8") | ||
); | ||
expect(tsconfigJson.compilerOptions).toEqual({}); | ||
}); | ||
it("should error if `--type` is used", async () => { | ||
const { error } = await runWrangler("init --type"); | ||
expect(error).toMatchInlineSnapshot( | ||
`[Error: The --type option is no longer supported.]` | ||
); | ||
}); | ||
it("should error if `--type javascript` is used", async () => { | ||
const { error } = await runWrangler("init --type javascript"); | ||
expect(error).toMatchInlineSnapshot( | ||
`[Error: The --type option is no longer supported.]` | ||
); | ||
}); | ||
it("should error if `--type rust` is used", async () => { | ||
const { error } = await runWrangler("init --type rust"); | ||
expect(error).toMatchInlineSnapshot( | ||
`[Error: The --type option is no longer supported.]` | ||
); | ||
}); | ||
it("should error if `--type webpack` is used", async () => { | ||
const { error } = await runWrangler("init --type webpack"); | ||
expect(error).toMatchInlineSnapshot(` | ||
[Error: The --type option is no longer supported. | ||
If you wish to use webpack then you will need to create a custom build.] | ||
`); | ||
}); | ||
}); | ||
}); |
import type { | ||
CfWorkerInit, | ||
CfModuleType, | ||
CfVariable, | ||
CfModule, | ||
CfDurableObjectMigrations, | ||
} from "./worker.js"; | ||
import { FormData, Blob } from "formdata-node"; | ||
// Credit: https://stackoverflow.com/a/9458996 | ||
function toBase64(source: BufferSource): string { | ||
let result = ""; | ||
const buffer = source instanceof ArrayBuffer ? source : source.buffer; | ||
const bytes = new Uint8Array(buffer); | ||
for (let i = 0; i < bytes.byteLength; i++) { | ||
result += String.fromCharCode(bytes[i]); | ||
} | ||
return btoa(result); | ||
} | ||
function toBinding( | ||
name: string, | ||
variable: CfVariable | ||
): Record<string, unknown> { | ||
if (typeof variable === "string") { | ||
return { name, type: "plain_text", text: variable }; | ||
} | ||
if ("namespaceId" in variable) { | ||
return { | ||
name, | ||
type: "kv_namespace", | ||
namespace_id: variable.namespaceId, | ||
}; | ||
} | ||
if ("class_name" in variable) { | ||
return { | ||
name, | ||
type: "durable_object_namespace", | ||
class_name: variable.class_name, | ||
...(variable.script_name && { | ||
script_name: variable.script_name, | ||
}), | ||
}; | ||
} | ||
const { format, algorithm, usages, data } = variable; | ||
if (format) { | ||
let key_base64; | ||
let key_jwk; | ||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { | ||
key_base64 = toBase64(data); | ||
} else { | ||
key_jwk = data; | ||
} | ||
return { | ||
name, | ||
type: "secret_key", | ||
format, | ||
algorithm, | ||
usages, | ||
key_base64, | ||
key_jwk, | ||
}; | ||
} | ||
throw new TypeError("Unsupported variable: " + variable); | ||
} | ||
export function toMimeType(type: CfModuleType): string { | ||
@@ -87,3 +26,3 @@ switch (type) { | ||
function toModule(module: CfModule, entryType?: CfModuleType): Blob { | ||
function toModule(module: CfModule, entryType: CfModuleType): Blob { | ||
const { type: moduleType, content } = module; | ||
@@ -95,2 +34,25 @@ const type = toMimeType(moduleType ?? entryType); | ||
interface WorkerMetadata { | ||
compatibility_date?: string; | ||
compatibility_flags?: string[]; | ||
usage_model?: "bundled" | "unbound"; | ||
migrations?: CfDurableObjectMigrations; | ||
bindings: ( | ||
| { type: "kv_namespace"; name: string; namespace_id: string } | ||
| { type: "plain_text"; name: string; text: string } | ||
| { | ||
type: "durable_object_namespace"; | ||
name: string; | ||
class_name: string; | ||
script_name?: string; | ||
} | ||
| { | ||
type: "service"; | ||
name: string; | ||
service: string; | ||
environment: string; | ||
} | ||
)[]; | ||
} | ||
/** | ||
@@ -104,3 +66,3 @@ * Creates a `FormData` upload from a `CfWorkerInit`. | ||
modules, | ||
variables, | ||
bindings, | ||
migrations, | ||
@@ -111,40 +73,51 @@ usage_model, | ||
} = worker; | ||
const { name, type: mainType } = main; | ||
const bindings = []; | ||
for (const [name, variable] of Object.entries(variables ?? {})) { | ||
const binding = toBinding(name, variable); | ||
bindings.push(binding); | ||
} | ||
const metadataBindings: WorkerMetadata["bindings"] = []; | ||
const metadata = | ||
mainType !== "commonjs" | ||
? { | ||
main_module: name, | ||
bindings, | ||
} | ||
: { | ||
body_part: name, | ||
bindings, | ||
}; | ||
if (compatibility_date) { | ||
// @ts-expect-error - we should type metadata | ||
metadata.compatibility_date = compatibility_date; | ||
} | ||
if (compatibility_flags) { | ||
// @ts-expect-error - we should type metadata | ||
metadata.compatibility_flags = compatibility_flags; | ||
} | ||
if (usage_model) { | ||
// @ts-expect-error - we should type metadata | ||
metadata.usage_model = usage_model; | ||
} | ||
if (migrations) { | ||
// @ts-expect-error - we should type metadata | ||
metadata.migrations = migrations; | ||
} | ||
bindings.kv_namespaces?.forEach(({ id, binding }) => { | ||
metadataBindings.push({ | ||
name: binding, | ||
type: "kv_namespace", | ||
namespace_id: id, | ||
}); | ||
}); | ||
bindings.durable_objects?.bindings.forEach( | ||
({ name, class_name, script_name }) => { | ||
metadataBindings.push({ | ||
name, | ||
type: "durable_object_namespace", | ||
class_name: class_name, | ||
...(script_name && { script_name }), | ||
}); | ||
} | ||
); | ||
Object.entries(bindings.vars || {})?.forEach(([key, value]) => { | ||
metadataBindings.push({ name: key, type: "plain_text", text: value }); | ||
}); | ||
bindings.services?.forEach(({ name, service, environment }) => { | ||
metadataBindings.push({ | ||
name, | ||
type: "service", | ||
service, | ||
environment, | ||
}); | ||
}); | ||
const metadata: WorkerMetadata = { | ||
...(main.type !== "commonjs" | ||
? { main_module: main.name } | ||
: { body_part: main.name }), | ||
bindings: metadataBindings, | ||
...(compatibility_date && { compatibility_date }), | ||
...(compatibility_flags && { compatibility_flags }), | ||
...(usage_model && { usage_model }), | ||
...(migrations && { migrations }), | ||
}; | ||
formData.set("metadata", JSON.stringify(metadata)); | ||
if (mainType === "commonjs" && modules && modules.length > 0) { | ||
if (main.type === "commonjs" && modules && modules.length > 0) { | ||
throw new TypeError( | ||
@@ -157,3 +130,3 @@ "More than one module can only be specified when type = 'esm'" | ||
const { name } = module; | ||
const blob = toModule(module, mainType ?? "esm"); | ||
const blob = toModule(module, main.type ?? "esm"); | ||
formData.set(name, blob, name); | ||
@@ -160,0 +133,0 @@ } |
@@ -1,3 +0,3 @@ | ||
import cfetch from "../cfetch"; | ||
import { fetchJson } from "../util/fetch"; | ||
import fetch from "node-fetch"; | ||
import { fetchResult } from "../cfetch"; | ||
import { toFormData } from "./form_data"; | ||
@@ -12,3 +12,3 @@ import type { CfAccount, CfWorkerInit } from "./worker"; | ||
*/ | ||
export type CfPreviewMode = { workers_dev: boolean } | { routes: string[] }; | ||
type CfPreviewMode = { workers_dev: boolean } | { routes: string[] }; | ||
@@ -64,7 +64,7 @@ /** | ||
const { exchange_url: tokenUrl } = await cfetch<{ exchange_url: string }>( | ||
initUrl | ||
); | ||
const { inspector_websocket: url, token } = await fetchJson()(tokenUrl); | ||
const { host } = new URL(url); | ||
const { exchange_url } = await fetchResult<{ exchange_url: string }>(initUrl); | ||
const { inspector_websocket, token } = (await ( | ||
await fetch(exchange_url) | ||
).json()) as { inspector_websocket: string; token: string }; | ||
const { host } = new URL(inspector_websocket); | ||
const query = `cf_workers_preview_token=${token}`; | ||
@@ -75,3 +75,3 @@ | ||
host, | ||
inspectorUrl: new URL(`${url}?${query}`), | ||
inspectorUrl: new URL(`${inspector_websocket}?${query}`), | ||
prewarmUrl: new URL( | ||
@@ -102,23 +102,28 @@ `https://${host}/cdn-cgi/workers/preview/prewarm?${query}` | ||
const { accountId, zoneId } = account; | ||
const scriptId = zoneId ? randomId() : host.split(".")[0]; | ||
const scriptId = zoneId ? randomId() : worker.name || host.split(".")[0]; | ||
const url = `/accounts/${accountId}/workers/scripts/${scriptId}/edge-preview`; | ||
const mode = zoneId ? { routes: ["*/*"] } : { workers_dev: true }; | ||
const mode: CfPreviewMode = zoneId | ||
? { routes: ["*/*"] } | ||
: { workers_dev: true }; | ||
const formData = toFormData(worker); | ||
formData.set("wrangler-session-config", JSON.stringify(mode)); | ||
const { preview_token: token } = await cfetch<{ preview_token: string }>( | ||
url, | ||
{ | ||
method: "POST", | ||
// @ts-expect-error TODO: fix this | ||
body: formData, | ||
headers: { | ||
"cf-preview-upload-config-token": value, | ||
}, | ||
} | ||
); | ||
const { preview_token } = await fetchResult<{ preview_token: string }>(url, { | ||
method: "POST", | ||
// @ts-expect-error TODO: fix this | ||
body: formData, | ||
headers: { | ||
"cf-preview-upload-config-token": value, | ||
}, | ||
}); | ||
return { | ||
value: token, | ||
host, | ||
value: preview_token, | ||
// TODO: verify this works with zoned workers | ||
host: | ||
worker.name && !zoneId | ||
? `${worker.name}.${host.split(".").slice(1).join(".")}` | ||
: host, | ||
inspectorUrl, | ||
@@ -125,0 +130,0 @@ prewarmUrl, |
@@ -66,12 +66,21 @@ import type { CfPreviewToken } from "./preview"; | ||
/** | ||
* A map of variable names to values. | ||
*/ | ||
interface CfVars { | ||
[key: string]: string; | ||
} | ||
/** | ||
* A KV namespace. | ||
*/ | ||
export interface CfKvNamespace { | ||
/** | ||
* The namespace ID. | ||
*/ | ||
namespaceId: string; | ||
interface CfKvNamespace { | ||
binding: string; | ||
id: string; | ||
} | ||
export interface CfDurableObject { | ||
/** | ||
* A Durable Object. | ||
*/ | ||
interface CfDurableObject { | ||
name: string; | ||
class_name: string; | ||
@@ -81,3 +90,12 @@ script_name?: string; | ||
interface CfDOMigrations { | ||
/** | ||
* A Service. | ||
*/ | ||
interface CfService { | ||
name: string; | ||
service: string; | ||
environment: string; | ||
} | ||
export interface CfDurableObjectMigrations { | ||
old_tag?: string; | ||
@@ -93,31 +111,2 @@ new_tag: string; | ||
/** | ||
* A `WebCrypto` key. | ||
* | ||
* @link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey | ||
*/ | ||
export interface CfCryptoKey { | ||
/** | ||
* The format. | ||
*/ | ||
format: string; | ||
/** | ||
* The algorithm. | ||
*/ | ||
algorithm: string; | ||
/** | ||
* The usages. | ||
*/ | ||
usages: string[]; | ||
/** | ||
* The data. | ||
*/ | ||
data: BufferSource | JsonWebKey; | ||
} | ||
/** | ||
* A variable (aka. environment variable). | ||
*/ | ||
export type CfVariable = string | CfKvNamespace | CfCryptoKey | CfDurableObject; | ||
/** | ||
* Options for creating a `CfWorker`. | ||
@@ -129,3 +118,3 @@ */ | ||
*/ | ||
name: string; | ||
name: string | undefined; | ||
/** | ||
@@ -138,11 +127,16 @@ * The entrypoint module. | ||
*/ | ||
modules: void | CfModule[]; | ||
modules: undefined | CfModule[]; | ||
/** | ||
* The map of names to variables. (aka. environment variables) | ||
* All the bindings | ||
*/ | ||
variables?: { [name: string]: CfVariable }; | ||
migrations: void | CfDOMigrations; | ||
compatibility_date: string | void; | ||
compatibility_flags: void | string[]; | ||
usage_model: void | "bundled" | "unbound"; | ||
bindings: { | ||
kv_namespaces?: CfKvNamespace[]; | ||
durable_objects?: { bindings: CfDurableObject[] }; | ||
vars?: CfVars; | ||
services?: CfService[]; | ||
}; | ||
migrations: undefined | CfDurableObjectMigrations; | ||
compatibility_date: string | undefined; | ||
compatibility_flags: undefined | string[]; | ||
usage_model: undefined | "bundled" | "unbound"; | ||
} | ||
@@ -149,0 +143,0 @@ |
@@ -0,10 +1,9 @@ | ||
import { hideBin } from "yargs/helpers"; | ||
import { main } from "."; | ||
main(process.argv.slice(2)).catch((cause) => { | ||
const { name, message } = cause; | ||
if (name === "CloudflareError") { | ||
console.error("\x1b[31m", message); | ||
return; | ||
} | ||
throw cause; | ||
main(hideBin(process.argv)).catch(() => { | ||
// The logging of any error that was thrown from `main()` is handled in the `yargs.fail()` handler. | ||
// Here we just want to ensure that the process exits with a non-zero code. | ||
// We don't want to do this inside the `main()` function, since that would kill the process when running our tests. | ||
process.exit(1); | ||
}); |
@@ -1,124 +0,487 @@ | ||
// we're going to manually write both the type definition AND | ||
// the validator for the config, so that we can give better error messages | ||
/** | ||
* This is the static type definition for the configuration object. | ||
* It reflects the configuration that you can write in wrangler.toml, | ||
* and optionally augment with arguments passed directly to wrangler. | ||
* The type definition doesn't fully reflect the constraints applied | ||
* to the configuration, but it is a good starting point. Later, we | ||
* also defined a validator function that will validate the configuration | ||
* with the same rules as the type definition, as well as the extra | ||
* constraints. The type definition is good for asserting correctness | ||
* in the wrangler codebase, whereas the validator function is useful | ||
* for signalling errors in the configuration to a user of wrangler. | ||
* | ||
* For more information about the configuration object, see the | ||
* documentation at https://developers.cloudflare.com/workers/cli-wrangler/configuration | ||
* | ||
* Legend for the annotations: | ||
* | ||
* *:optional means providing a value isn't mandatory | ||
* *:deprecated means the field itself isn't necessary anymore in wrangler.toml | ||
* *:breaking means the deprecation/optionality is a breaking change from wrangler 1 | ||
* *:todo means there's more work to be done (with details attached) | ||
* *:inherited means the field is copied to all environments | ||
*/ | ||
export type Config = { | ||
/** | ||
* The name of your worker. Alphanumeric + dashes only. | ||
* | ||
* @optional | ||
* @inherited | ||
*/ | ||
name?: string; | ||
type DOMigration = { | ||
tag: string; | ||
new_classes?: string[]; | ||
renamed_classes?: string[]; | ||
deleted_classes?: string[]; | ||
}; | ||
/** | ||
* The entrypoint/path to the JavaScript file that will be executed. | ||
* | ||
* @optional | ||
* @inherited | ||
* @todo this needs to be implemented! | ||
*/ | ||
entry?: string; | ||
type Project = "webpack" | "javascript" | "rust"; | ||
/** | ||
* This is the ID of the account associated with your zone. | ||
* You might have more than one account, so make sure to use | ||
* the ID of the account associated with the zone/route you | ||
* provide, if you provide one. It can also be specified through | ||
* the CF_ACCOUNT_ID environment variable. | ||
* | ||
* @optional | ||
* @inherited | ||
*/ | ||
account_id?: string; | ||
type Site = { | ||
// inherited | ||
bucket: string; | ||
"entry-point": string; | ||
include?: string[]; | ||
exclude?: string[]; | ||
}; | ||
/** | ||
* The project "type". A holdover from wrangler 1.x. | ||
* Valid values were "webpack", "javascript", and "rust". | ||
* | ||
* @deprecated DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build. | ||
* @optional | ||
* @inherited | ||
* @breaking | ||
*/ | ||
type?: "webpack" | "javascript" | "rust"; | ||
type Dev = { | ||
ip?: string; | ||
port?: number; | ||
local_protocol?: string; | ||
upstream_protocol?: string; | ||
}; | ||
/** | ||
* A date in the form yyyy-mm-dd, which will be used to determine | ||
* which version of the Workers runtime is used. More details at | ||
* https://developers.cloudflare.com/workers/platform/compatibility-dates | ||
* @optional true for `dev`, false for `publish` | ||
* @inherited | ||
*/ | ||
compatibility_date?: string; | ||
type Vars = { [key: string]: string }; | ||
/** | ||
* A list of flags that enable features from upcoming features of | ||
* the Workers runtime, usually used together with compatibility_flags. | ||
* More details at | ||
* https://developers.cloudflare.com/workers/platform/compatibility-dates | ||
* | ||
* @optional | ||
* @inherited | ||
* @todo This could be an enum! | ||
*/ | ||
compatibility_flags?: string[]; | ||
type Cron = string; // TODO: we should be able to parse a cron pattern with ts | ||
/** | ||
* Whether we use <name>.<subdomain>.workers.dev to | ||
* test and deploy your worker. | ||
* | ||
* @default `true` (This is a breaking change from wrangler 1) | ||
* @optional | ||
* @inherited | ||
* @breaking | ||
*/ | ||
workers_dev?: boolean; | ||
type KVNamespace = { | ||
binding?: string; | ||
preview_id: string; | ||
id: string; | ||
}; | ||
/** | ||
* The zone ID of the zone you want to deploy to. You can find this | ||
* in your domain page on the dashboard. | ||
* | ||
* @deprecated This is unnecessary since we can deduce this from routes directly. | ||
* @optional | ||
* @inherited | ||
*/ | ||
zone_id?: string; | ||
type DurableObject = { | ||
name: string; | ||
class_name: string; | ||
script_name?: string; | ||
}; | ||
/** | ||
* A list of routes that your worker should be deployed to. | ||
* Only one of `routes` or `route` is required. | ||
* | ||
* @optional false only when workers_dev is false, and there's no scheduled worker | ||
* @inherited | ||
*/ | ||
routes?: string[]; | ||
type Build = { | ||
command?: string; | ||
cwd?: string; | ||
watch_dir?: string; | ||
} & ( | ||
| { | ||
upload?: { | ||
format: "service-worker"; | ||
main: string; | ||
}; | ||
} | ||
| { | ||
upload?: { | ||
format: "modules"; | ||
dir?: string; | ||
main?: string; | ||
rules?: { | ||
type: "ESModule" | "CommonJS" | "Text" | "Data" | "CompiledWasm"; | ||
globs: string[]; // can we use typescript for these patterns? | ||
fallthrough?: boolean; | ||
/** | ||
* A route that your worker should be deployed to. Literally | ||
* the same as routes, but only one. | ||
* Only one of `routes` or `route` is required. | ||
* | ||
* @optional false only when workers_dev is false, and there's no scheduled worker | ||
* @inherited | ||
*/ | ||
route?: string; | ||
/** | ||
* Path to the webpack config to use when building your worker. | ||
* A holdover from wrangler 1.x, used with `type: "webpack"`. | ||
* | ||
* @deprecated DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build. | ||
* @inherited | ||
* @breaking | ||
*/ | ||
webpack_config?: string; | ||
/** | ||
* The function to use to replace jsx syntax. | ||
* | ||
* @default `"React.createElement"` | ||
* @optional | ||
* @inherited | ||
*/ | ||
jsx_factory?: string; | ||
/** | ||
* The function to use to replace jsx fragment syntax. | ||
* | ||
* @default `"React.Fragment"` | ||
* @optional | ||
* @inherited | ||
*/ | ||
jsx_fragment?: string; | ||
/** | ||
* A map of environment variables to set when deploying your worker. | ||
* Of note, they can only be strings. Which is unfortunate, really. | ||
* (TODO: verify that they can only be strings?) | ||
* NB: these are not inherited, and HAVE to be duplicated across all environments. | ||
* | ||
* @default `{}` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
vars?: { [key: string]: string }; | ||
/** | ||
* A list of durable objects that your worker should be bound to. | ||
* For more information about Durable Objects, see the documentation at | ||
* https://developers.cloudflare.com/workers/learning/using-durable-objects | ||
* NB: these are not inherited, and HAVE to be duplicated across all environments. | ||
* | ||
* @default `{ bindings: [] }` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
durable_objects?: { | ||
bindings: { | ||
/** The name of the binding used to refer to the Durable Object */ | ||
name: string; | ||
/** The exported class name of the Durable Object */ | ||
class_name: string; | ||
/** The script where the Durable Object is defined (if it's external to this worker) */ | ||
script_name?: string; | ||
}[]; | ||
}; | ||
/** | ||
* These specify any Workers KV Namespaces you want to | ||
* access from inside your Worker. To learn more about KV Namespaces, | ||
* see the documentation at https://developers.cloudflare.com/workers/learning/how-kv-works | ||
* NB: these are not inherited, and HAVE to be duplicated across all environments. | ||
* | ||
* @default `[]` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
kv_namespaces?: { | ||
/** The binding name used to refer to the KV Namespace */ | ||
binding: string; | ||
/** The ID of the KV namespace */ | ||
id: string; | ||
/** The ID of the KV namespace used during `wrangler dev` */ | ||
preview_id?: string; | ||
}[]; | ||
/** | ||
* A list of services that your worker should be bound to. | ||
* NB: these are not inherited, and HAVE to be duplicated across all environments. | ||
* | ||
* @default `[]` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
experimental_services?: { | ||
/** The binding name used to refer to the Service */ | ||
name: string; | ||
/** The name of the Service being bound */ | ||
service: string; | ||
/** The Service's environment */ | ||
environment: string; | ||
}[]; | ||
/** | ||
* A list of migrations that should be uploaded with your Worker. | ||
* These define changes in your Durable Object declarations. | ||
* More details at https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-classes-with-migrations | ||
* NB: these ARE inherited, and SHOULD NOT be duplicated across all environments. | ||
* | ||
* @default `[]` | ||
* @optional | ||
* @inherited true | ||
*/ | ||
migrations?: { | ||
/** A unique identifier for this migration. */ | ||
tag: string; | ||
/** The new Durable Objects being defined. */ | ||
new_classes?: string[]; | ||
/** The Durable Objects being renamed. */ | ||
renamed_classes?: { | ||
from: string; | ||
to: string; | ||
}[]; | ||
/** The Durable Objects being removed. */ | ||
deleted_classes?: string[]; | ||
}[]; | ||
/** | ||
* The definition of a Worker Site, a feature that lets you upload | ||
* static assets with your Worker. | ||
* More details at https://developers.cloudflare.com/workers/platform/sites | ||
* NB: This IS inherited, and SHOULD NOT be duplicated across all environments. | ||
* | ||
* @default `undefined` | ||
* @optional | ||
* @inherited true | ||
*/ | ||
site?: { | ||
/** | ||
* The directory containing your static assets. It must be | ||
* a path relative to your wrangler.toml file. | ||
* Example: bucket = "./public" | ||
* | ||
* optional false | ||
*/ | ||
bucket: string; | ||
/** | ||
* The location of your Worker script. | ||
* | ||
* @deprecated DO NOT use this (it's a holdover from wrangler 1.x). Either use the top level `entry` field, or pass the path to your entry file as a command line argument. | ||
* @todo we should use a top level "entry" property instead | ||
* @breaking | ||
*/ | ||
"entry-point": string; | ||
/** | ||
* An exclusive list of .gitignore-style patterns that match file | ||
* or directory names from your bucket location. Only matched | ||
* items will be uploaded. Example: include = ["upload_dir"] | ||
* | ||
* @optional | ||
* @default `[]` | ||
* @todo this needs to be implemented! | ||
*/ | ||
include?: string[]; | ||
/** | ||
* A list of .gitignore-style patterns that match files or | ||
* directories in your bucket that should be excluded from | ||
* uploads. Example: exclude = ["ignore_dir"] | ||
* | ||
* @optional | ||
* @default `[]` | ||
* @todo this needs to be implemented! | ||
*/ | ||
exclude?: string[]; | ||
}; | ||
/** | ||
* "Cron" definitions to trigger a worker's "scheduled" function. | ||
* Lets you call workers periodically, much like a cron job. | ||
* More details here https://developers.cloudflare.com/workers/platform/cron-triggers | ||
* | ||
* @inherited | ||
* @default `{ crons: [] }` | ||
* @optional | ||
* @todo can we use typescript for cron patterns? | ||
*/ | ||
triggers?: { crons: string[] }; | ||
/** | ||
* Options to configure the development server that your worker will use. | ||
* NB: This is NOT inherited, and SHOULD NOT be duplicated across all environments. | ||
* | ||
* @default `{}` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
dev?: { | ||
/** | ||
* IP address for the local dev server to listen on, | ||
* | ||
* @default `127.0.0.1` | ||
* @todo this needs to be implemented | ||
*/ | ||
ip?: string; | ||
/** | ||
* Port for the local dev server to listen on | ||
* | ||
* @default `8787` | ||
*/ | ||
port?: number; | ||
/** | ||
* Protocol that local wrangler dev server listens to requests on. | ||
* | ||
* @default `http` | ||
* @todo this needs to be implemented | ||
*/ | ||
local_protocol?: string; | ||
/** | ||
* Protocol that wrangler dev forwards requests on | ||
* | ||
* @default `https` | ||
* @todo this needs to be implemented | ||
*/ | ||
upstream_protocol?: string; | ||
}; | ||
/** | ||
* Specifies the Usage Model for your Worker. There are two options - | ||
* [bundled](https://developers.cloudflare.com/workers/platform/limits#bundled-usage-model) and | ||
* [unbound](https://developers.cloudflare.com/workers/platform/limits#unbound-usage-model). | ||
* For newly created Workers, if the Usage Model is omitted | ||
* it will be set to the [default Usage Model set on the account](https://dash.cloudflare.com/?account=workers/default-usage-model). | ||
* For existing Workers, if the Usage Model is omitted, it will be | ||
* set to the Usage Model configured in the dashboard for that Worker. | ||
*/ | ||
usage_model?: undefined | "bundled" | "unbound"; | ||
/** | ||
* Configures a custom build step to be run by Wrangler when | ||
* building your Worker. Refer to the [custom builds documentation](https://developers.cloudflare.com/workers/cli-wrangler/configuration#build) | ||
* for more details. | ||
* | ||
* @default `undefined` | ||
* @optional | ||
* @inherited false | ||
*/ | ||
build?: { | ||
/** The command used to build your Worker. On Linux and macOS, the command is executed in the `sh` shell and the `cmd` shell for Windows. The `&&` and `||` shell operators may be used. */ | ||
command?: string; | ||
/** The directory in which the command is executed. */ | ||
cwd?: string; | ||
/** The directory to watch for changes while using wrangler dev, defaults to the current working directory */ | ||
watch_dir?: string; | ||
} & /** | ||
* Much of the rest of this configuration isn't necessary anymore | ||
* in wrangler2. We infer the format automatically, and we can pass | ||
* the path to the script either in the CLI (or, @todo, as the top level | ||
* `entry` property). | ||
*/ ( | ||
| { | ||
upload?: { | ||
/** | ||
* The format of the Worker script, must be "service-worker". | ||
* | ||
* @deprecated We infer the format automatically now. | ||
*/ | ||
format: "service-worker"; | ||
/** | ||
* The path to the Worker script. This should be replaced | ||
* by the top level `entry' property. | ||
* | ||
* @deprecated This will be replaced by the top level `entry' property. | ||
*/ | ||
main: string; | ||
}; | ||
}; | ||
} | ||
); | ||
} | ||
| { | ||
/** | ||
* When we use the module format, we only really | ||
* need to specify the entry point. The format is deduced | ||
* automatically in wrangler2. | ||
*/ | ||
upload?: { | ||
/** | ||
* The format of the Worker script, must be "modules". | ||
* | ||
* @deprecated We infer the format automatically now. | ||
*/ | ||
format: "modules"; | ||
type UsageModel = "bundled" | "unbound"; | ||
/** | ||
* The directory you wish to upload your modules from, | ||
* defaults to the dist relative to the project root directory. | ||
* | ||
* @deprecated | ||
* @breaking | ||
*/ | ||
dir?: string; | ||
type Env = { | ||
name?: string; // inherited | ||
account_id?: string; // inherited | ||
workers_dev?: boolean; // inherited | ||
compatibility_date?: string; // inherited | ||
compatibility_flags?: string[]; // inherited | ||
zone_id?: string; // inherited | ||
routes?: string[]; // inherited | ||
route?: string; // inherited | ||
webpack_config?: string; // inherited | ||
site?: Site; | ||
jsx_factory?: string; // inherited | ||
jsx_fragment?: string; // inherited | ||
polyfill_node?: boolean; //inherited | ||
// we should use typescript to parse cron patterns | ||
triggers?: { crons: Cron[] }; // inherited | ||
vars?: Vars; | ||
durable_objects?: { bindings: DurableObject[] }; | ||
kv_namespaces?: KVNamespace[]; | ||
usage_model?: UsageModel; // inherited | ||
/** | ||
* The path to the Worker script. This should be replaced | ||
* by the top level `entry' property. | ||
* | ||
* @deprecated This will be replaced by the top level `entry' property. | ||
*/ | ||
main?: string; | ||
/** | ||
* An ordered list of rules that define which modules to import, | ||
* and what type to import them as. You will need to specify rules | ||
* to use Text, Data, and CompiledWasm modules, or when you wish to | ||
* have a .js file be treated as an ESModule instead of CommonJS. | ||
* | ||
* @deprecated These are now inferred automatically for major file types, but you can still specify them manually. | ||
* @todo this needs to be implemented! | ||
* @breaking | ||
*/ | ||
rules?: { | ||
type: "ESModule" | "CommonJS" | "Text" | "Data" | "CompiledWasm"; | ||
globs: string[]; | ||
fallthrough?: boolean; | ||
}; | ||
}; | ||
} | ||
); | ||
/** | ||
* The `env` section defines overrides for the configuration for | ||
* different environments. Most fields can be overridden, while | ||
* some have to be specifically duplicated in every environment. | ||
* For more information, see the documentation at https://developers.cloudflare.com/workers/cli-wrangler/configuration#environments | ||
*/ | ||
env?: { | ||
[envName: string]: | ||
| undefined | ||
| Omit<Config, "env" | "migrations" | "site" | "dev">; | ||
}; | ||
}; | ||
export type Config = { | ||
name?: string; // inherited | ||
account_id?: string; // inherited | ||
// @deprecated Don't use this | ||
type?: Project; // top level | ||
compatibility_date?: string; // inherited | ||
compatibility_flags?: string[]; // inherited | ||
// -- there's some mutually exclusive logic for this next block, | ||
// but I didn't bother for now | ||
workers_dev?: boolean; // inherited | ||
zone_id?: string; // inherited | ||
routes?: string[]; // inherited | ||
route?: string; // inherited | ||
// -- end mutually exclusive stuff | ||
// @deprecated Don't use this | ||
webpack_config?: string; // inherited | ||
jsx_factory?: string; // inherited | ||
jsx_fragment?: string; // inherited | ||
polyfill_node?: boolean; //inherited | ||
vars?: Vars; | ||
migrations?: DOMigration[]; | ||
durable_objects?: { bindings: DurableObject[] }; | ||
kv_namespaces?: KVNamespace[]; | ||
site?: Site; // inherited | ||
// we should use typescript to parse cron patterns | ||
triggers?: { crons: Cron[] }; // inherited | ||
dev?: Dev; | ||
usage_model?: UsageModel; // inherited | ||
// top level | ||
build?: Build; | ||
env?: { [envName: string]: void | Env }; | ||
}; | ||
type ValidationResults = ( | ||
| { key: string; info: string } | ||
| { key: string; error: string } | ||
| { key: string; warning: string } | ||
)[]; | ||
/** | ||
* We also define a validation function that manually validates | ||
* every field in the configuration as per the type definitions, | ||
* as well as extra constraints we apply to some fields, as well | ||
* as some constraints on combinations of fields. This is useful for | ||
* presenting errors and messages to the user. Eventually, we will | ||
* combine this with some automatic config rewriting tools. | ||
* | ||
*/ | ||
export async function validateConfig( | ||
_config: Partial<Config> | ||
): Promise<ValidationResults> { | ||
const results: ValidationResults = []; | ||
return results; | ||
} |
@@ -0,15 +1,15 @@ | ||
import assert from "node:assert"; | ||
import path from "node:path"; | ||
import { readFile } from "node:fs/promises"; | ||
import esbuild from "esbuild"; | ||
import { execa } from "execa"; | ||
import tmp from "tmp-promise"; | ||
import type { CfWorkerInit } from "./api/worker"; | ||
import { toFormData } from "./api/form_data"; | ||
import esbuild from "esbuild"; | ||
import tmp from "tmp-promise"; | ||
import { fetchResult } from "./cfetch"; | ||
import type { Config } from "./config"; | ||
import path from "path"; | ||
import { readFile } from "fs/promises"; | ||
import cfetch from "./cfetch"; | ||
import assert from "node:assert"; | ||
import makeModuleCollector from "./module-collection"; | ||
import { syncAssets } from "./sites"; | ||
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill"; | ||
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill"; | ||
type CfScriptFormat = void | "modules" | "service-worker"; | ||
type CfScriptFormat = undefined | "modules" | "service-worker"; | ||
@@ -22,2 +22,4 @@ type Props = { | ||
env?: string; | ||
compatibilityDate?: string; | ||
compatibilityFlags?: string[]; | ||
public?: string; | ||
@@ -28,5 +30,4 @@ site?: string; | ||
legacyEnv?: boolean; | ||
jsxFactory: void | string; | ||
jsxFragment: void | string; | ||
polyfillNode: void | boolean; | ||
jsxFactory: undefined | string; | ||
jsxFragment: undefined | string; | ||
}; | ||
@@ -54,2 +55,9 @@ | ||
const envRootObj = props.env ? config.env[props.env] || {} : config; | ||
assert( | ||
envRootObj.compatibility_date || props["compatibility-date"], | ||
"A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." | ||
); | ||
const triggers = props.triggers || config.triggers?.crons; | ||
@@ -84,2 +92,14 @@ const routes = props.routes || config.routes; | ||
if (props.config.build?.command) { | ||
// TODO: add a deprecation message here? | ||
console.log("running:", props.config.build.command); | ||
const buildCommandPieces = props.config.build.command.split(" "); | ||
await execa(buildCommandPieces[0], buildCommandPieces.slice(1), { | ||
stdout: "inherit", | ||
stderr: "inherit", | ||
...(props.config.build?.cwd && { cwd: props.config.build.cwd }), | ||
}); | ||
} | ||
const moduleCollector = makeModuleCollector(); | ||
const result = await esbuild.build({ | ||
@@ -101,7 +121,2 @@ ...(props.public | ||
bundle: true, | ||
define: { | ||
...((props.polyfillNode ?? config.polyfill_node) && { | ||
global: "globalThis", | ||
}), | ||
}, | ||
nodePaths: props.public ? [path.join(__dirname, "../vendor")] : undefined, | ||
@@ -111,11 +126,9 @@ outdir: destination.path, | ||
format: "esm", | ||
plugins: | ||
props.polyfillNode ?? config.polyfill_node | ||
? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()] | ||
: undefined, | ||
sourcemap: true, | ||
metafile: true, | ||
conditions: ["worker", "browser"], | ||
loader: { | ||
".js": "jsx", | ||
}, | ||
plugins: [moduleCollector.plugin], | ||
...(jsxFactory && { jsxFactory }), | ||
@@ -130,3 +143,3 @@ ...(jsxFragment && { jsxFragment }), | ||
? path.join(path.dirname(file), "static-asset-facade.js") | ||
: file) | ||
: Object.keys(result.metafile.inputs)[0]) | ||
); | ||
@@ -155,3 +168,3 @@ | ||
const content = await readFile(chunks[0], { encoding: "utf-8" }); | ||
destination.cleanup(); | ||
await destination.cleanup(); | ||
@@ -162,6 +175,6 @@ // if config.migrations | ||
if ("migrations" in config) { | ||
const scripts = await cfetch<{ id: string; migration_tag: string }[]>( | ||
const scripts = await fetchResult<{ id: string; migration_tag: string }[]>( | ||
`/accounts/${accountId}/workers/scripts` | ||
); | ||
const script = scripts.find((script) => script.id === scriptName); | ||
const script = scripts.find(({ id }) => id === scriptName); | ||
if (script?.migration_tag) { | ||
@@ -174,3 +187,3 @@ // was already published once | ||
console.warn( | ||
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already delated it. Applying all available migrations to the script...` | ||
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...` | ||
); | ||
@@ -209,3 +222,12 @@ migrations = { | ||
const envRootObj = props.env ? config.env[props.env] || {} : config; | ||
const bindings: CfWorkerInit["bindings"] = { | ||
kv_namespaces: envRootObj.kv_namespaces?.concat( | ||
assets.namespace | ||
? { binding: "__STATIC_CONTENT", id: assets.namespace } | ||
: [] | ||
), | ||
vars: envRootObj.vars, | ||
durable_objects: envRootObj.durable_objects, | ||
services: envRootObj.experimental_services, | ||
}; | ||
@@ -219,31 +241,13 @@ const worker: CfWorkerInit = { | ||
}, | ||
variables: { | ||
...(envRootObj?.vars || {}), | ||
...(envRootObj?.kv_namespaces || []).reduce( | ||
(obj, { binding, preview_id: _preview_id, id }) => { | ||
return { ...obj, [binding]: { namespaceId: id } }; | ||
}, | ||
{} | ||
), | ||
...(envRootObj?.durable_objects?.bindings || []).reduce( | ||
(obj, { name, class_name, script_name }) => { | ||
return { | ||
...obj, | ||
[name]: { class_name, ...(script_name && { script_name }) }, | ||
}; | ||
}, | ||
{} | ||
), | ||
...(assets.namespace | ||
? { __STATIC_CONTENT: { namespaceId: assets.namespace } } | ||
: {}), | ||
}, | ||
bindings, | ||
...(migrations && { migrations }), | ||
modules: assets.manifest | ||
? [].concat({ | ||
name: "__STATIC_CONTENT_MANIFEST", | ||
content: JSON.stringify(assets.manifest), | ||
type: "text", | ||
}) | ||
: [], | ||
modules: moduleCollector.modules.concat( | ||
assets.manifest | ||
? { | ||
name: "__STATIC_CONTENT_MANIFEST", | ||
content: JSON.stringify(assets.manifest), | ||
type: "text", | ||
} | ||
: [] | ||
), | ||
compatibility_date: config.compatibility_date, | ||
@@ -265,4 +269,4 @@ compatibility_flags: config.compatibility_flags, | ||
// Upload the script so it has time to propogate. | ||
const { available_on_subdomain } = await cfetch( | ||
// Upload the script so it has time to propagate. | ||
const { available_on_subdomain } = await fetchResult( | ||
`${workerUrl}?available_on_subdomain=true`, | ||
@@ -281,3 +285,3 @@ { | ||
const userSubdomain = ( | ||
await cfetch<{ subdomain: string }>( | ||
await fetchResult<{ subdomain: string }>( | ||
`/accounts/${accountId}/workers/subdomain` | ||
@@ -296,3 +300,3 @@ ) | ||
deployments.push( | ||
cfetch(`${workerUrl}/subdomain`, { | ||
fetchResult(`${workerUrl}/subdomain`, { | ||
method: "POST", | ||
@@ -309,4 +313,4 @@ body: JSON.stringify({ enabled: true }), | ||
// This is a temporary measure until we fix this on the edge. | ||
.then((url) => { | ||
sleep(3000); | ||
.then(async (url) => { | ||
await sleep(3000); | ||
return url; | ||
@@ -322,3 +326,3 @@ }) | ||
deployments.push( | ||
cfetch(`${workerUrl}/routes`, { | ||
fetchResult(`${workerUrl}/routes`, { | ||
// TODO: PATCH will not delete previous routes on this script, | ||
@@ -348,3 +352,3 @@ // whereas PUT will. We need to decide on the default behaviour | ||
deployments.push( | ||
cfetch(`${workerUrl}/schedules`, { | ||
fetchResult(`${workerUrl}/schedules`, { | ||
// TODO: Unlike routes, this endpoint does not support PATCH. | ||
@@ -351,0 +355,0 @@ // So technically, this will override any previous schedules. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
5
77
12418
1
12512266
36
2
+ Addedpath-to-regexp@^6.2.0
+ Added@miniflare/cache@2.1.0(transitive)
+ Added@miniflare/cli-parser@2.1.0(transitive)
+ Added@miniflare/core@2.1.0(transitive)
+ Added@miniflare/durable-objects@2.1.0(transitive)
+ Added@miniflare/html-rewriter@2.1.0(transitive)
+ Added@miniflare/http-server@2.1.0(transitive)
+ Added@miniflare/kv@2.1.0(transitive)
+ Added@miniflare/runner-vm@2.1.0(transitive)
+ Added@miniflare/scheduler@2.1.0(transitive)
+ Added@miniflare/shared@2.1.0(transitive)
+ Added@miniflare/sites@2.1.0(transitive)
+ Added@miniflare/storage-file@2.1.0(transitive)
+ Added@miniflare/storage-memory@2.1.0(transitive)
+ Added@miniflare/watcher@2.1.0(transitive)
+ Added@miniflare/web-sockets@2.1.0(transitive)
+ Added@types/node@22.13.5(transitive)
+ Added@types/node-forge@1.3.11(transitive)
+ Addedminiflare@2.1.0(transitive)
+ Addednode-forge@1.3.1(transitive)
+ Addedselfsigned@2.4.1(transitive)
+ Addedundici@4.12.1(transitive)
+ Addedundici-types@6.20.0(transitive)
- Removedserve@^13.0.2
- Removed@cloudflare/pages-functions-compiler@0.3.7(transitive)
- Removed@esbuild-plugins/node-globals-polyfill@0.1.1(transitive)
- Removed@esbuild-plugins/node-modules-polyfill@0.1.4(transitive)
- Removed@miniflare/cache@2.0.0-rc.3(transitive)
- Removed@miniflare/cli-parser@2.0.0-rc.3(transitive)
- Removed@miniflare/core@2.0.0-rc.3(transitive)
- Removed@miniflare/durable-objects@2.0.0-rc.3(transitive)
- Removed@miniflare/html-rewriter@2.0.0-rc.3(transitive)
- Removed@miniflare/http-server@2.0.0-rc.3(transitive)
- Removed@miniflare/kv@2.0.0-rc.3(transitive)
- Removed@miniflare/runner-vm@2.0.0-rc.3(transitive)
- Removed@miniflare/scheduler@2.0.0-rc.3(transitive)
- Removed@miniflare/shared@2.0.0-rc.3(transitive)
- Removed@miniflare/sites@2.0.0-rc.3(transitive)
- Removed@miniflare/storage-file@2.0.0-rc.3(transitive)
- Removed@miniflare/storage-memory@2.0.0-rc.3(transitive)
- Removed@miniflare/watcher@2.0.0-rc.3(transitive)
- Removed@miniflare/web-sockets@2.0.0-rc.3(transitive)
- Removed@zeit/schemas@2.6.0(transitive)
- Removedaccepts@1.3.8(transitive)
- Removedacorn@8.14.0(transitive)
- Removedacorn-walk@8.3.4(transitive)
- Removedajv@6.12.6(transitive)
- Removedansi-align@3.0.1(transitive)
- Removedansi-regex@5.0.1(transitive)
- Removedansi-styles@3.2.14.3.0(transitive)
- Removedarch@2.2.0(transitive)
- Removedarg@2.0.0(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedboxen@5.1.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedbytes@3.0.0(transitive)
- Removedcamelcase@6.3.0(transitive)
- Removedchalk@2.4.14.1.2(transitive)
- Removedcli-boxes@2.2.1(transitive)
- Removedclipboardy@2.3.0(transitive)
- Removedcolor-convert@1.9.32.0.1(transitive)
- Removedcolor-name@1.1.31.1.4(transitive)
- Removedcommander@8.3.0(transitive)
- Removedcompressible@2.0.18(transitive)
- Removedcompression@1.7.3(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedcontent-disposition@0.5.2(transitive)
- Removedcross-spawn@6.0.6(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddeep-extend@0.6.0(transitive)
- Removedemoji-regex@8.0.0(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removedescape-string-regexp@1.0.54.0.0(transitive)
- Removedestree-walker@0.6.1(transitive)
- Removedexeca@1.0.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfast-url-parser@1.1.3(transitive)
- Removedget-stream@4.1.0(transitive)
- Removedhas-flag@3.0.04.0.0(transitive)
- Removedini@1.3.8(transitive)
- Removedis-docker@2.2.1(transitive)
- Removedis-fullwidth-code-point@3.0.0(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedis-wsl@2.2.0(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedmagic-string@0.25.9(transitive)
- Removedmime-db@1.33.01.52.01.53.0(transitive)
- Removedmime-types@2.1.182.1.35(transitive)
- Removedminiflare@2.0.0-rc.3(transitive)
- Removedminimatch@3.0.4(transitive)
- Removedminimist@1.2.8(transitive)
- Removedms@2.0.0(transitive)
- Removednegotiator@0.6.3(transitive)
- Removednice-try@1.0.5(transitive)
- Removednode-forge@0.10.0(transitive)
- Removednpm-run-path@2.0.2(transitive)
- Removedon-headers@1.0.2(transitive)
- Removedonce@1.4.0(transitive)
- Removedp-finally@1.0.0(transitive)
- Removedpath-is-inside@1.0.2(transitive)
- Removedpath-key@2.0.1(transitive)
- Removedpath-to-regexp@2.2.1(transitive)
- Removedpump@3.0.2(transitive)
- Removedpunycode@1.4.12.3.1(transitive)
- Removedrange-parser@1.2.0(transitive)
- Removedrc@1.2.8(transitive)
- Removedregistry-auth-token@3.3.2(transitive)
- Removedregistry-url@3.1.0(transitive)
- Removedrollup-plugin-inject@3.0.2(transitive)
- Removedrollup-plugin-node-polyfills@0.2.1(transitive)
- Removedrollup-pluginutils@2.8.2(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedselfsigned@1.10.14(transitive)
- Removedsemver@5.7.2(transitive)
- Removedserve@13.0.4(transitive)
- Removedserve-handler@6.1.3(transitive)
- Removedshebang-command@1.2.0(transitive)
- Removedshebang-regex@1.0.0(transitive)
- Removedsignal-exit@3.0.7(transitive)
- Removedsourcemap-codec@1.4.8(transitive)
- Removedstring-width@4.2.3(transitive)
- Removedstrip-ansi@6.0.1(transitive)
- Removedstrip-eof@1.0.0(transitive)
- Removedstrip-json-comments@2.0.1(transitive)
- Removedsupports-color@5.5.07.2.0(transitive)
- Removedtype-fest@0.20.2(transitive)
- Removedundici@4.16.0(transitive)
- Removedupdate-check@1.5.2(transitive)
- Removeduri-js@4.4.1(transitive)
- Removedvary@1.1.2(transitive)
- Removedwhich@1.3.1(transitive)
- Removedwidest-line@3.1.0(transitive)
- Removedwrap-ansi@7.0.0(transitive)
- Removedwrappy@1.0.2(transitive)
Updatedminiflare@2.1.0