Skip to content

Commit

Permalink
add a cf field to the getBindingsProxy result (#4926)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: James Culveyhouse <jculveyhouse@cloudflare.com>
  • Loading branch information
dario-piotrowicz and jculvey authored Feb 7, 2024
1 parent 321c7ed commit a14bd1d
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 135 deletions.
16 changes: 16 additions & 0 deletions .changeset/strong-otters-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"wrangler": minor
---

feature: add a `cf` field to the `getBindingsProxy` result

Add a new `cf` field to the `getBindingsProxy` result that people can use to mock the production
`cf` (`IncomingRequestCfProperties`) object.

Example:

```ts
const { cf } = await getBindingsProxy();

console.log(`country = ${cf.country}; colo = ${cf.colo}`); // logs 'country = GB ; colo = LHR'
```
43 changes: 43 additions & 0 deletions fixtures/get-bindings-proxy/tests/get-bindings-proxy.cf.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { getBindingsProxy } from "./shared";

describe("getBindingsProxy - cf", () => {
it("should provide mock data", async () => {
const { cf, dispose } = await getBindingsProxy();
try {
expect(cf).toMatchObject({
colo: "DFW",
city: "Austin",
regionCode: "TX",
});
} finally {
await dispose();
}
});

it("should match the production runtime cf object", async () => {
const { cf, dispose } = await getBindingsProxy();
try {
expect(cf.constructor.name).toBe("Object");

expect(() => {
cf.city = "test city";
}).toThrowError(
"Cannot assign to read only property 'city' of object '#<Object>'"
);
expect(cf.city).not.toBe("test city");

expect(() => {
cf.newField = "test new field";
}).toThrowError("Cannot add property newField, object is not extensible");
expect("newField" in cf).toBe(false);

expect(cf.botManagement).toMatchObject({
score: 99,
});
expect(Object.isFrozen(cf.botManagement)).toBe(true);
} finally {
await dispose();
}
});
});
8 changes: 8 additions & 0 deletions packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@cloudflare/workers-types": "^4.20230914.0"
},
"peerDependenciesMeta": {
"@cloudflare/workers-types": {
"optional": true
}
},
"engines": {
"node": ">=16.17.0"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/wrangler/scripts/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const EXTERNAL_DEPENDENCIES = [
"@esbuild-plugins/node-globals-polyfill",
"@esbuild-plugins/node-modules-polyfill",
"chokidar",
// @cloudflare/workers-types is an optional peer dependency of wrangler, so users can
// get the types by installing the package (to what version they prefer) themselves
"@cloudflare/workers-types",
];

const pathToPackageJson = path.resolve(__dirname, "..", "package.json");
Expand Down
73 changes: 48 additions & 25 deletions packages/wrangler/scripts/emit-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,52 @@ const configObject = ExtractorConfig.loadFile(configObjectFullPath);
// include the dependencies we want to bundle
configObject.bundledPackages = BUNDLED_DEPENDENCIES;

const extractorConfig = ExtractorConfig.prepare({
configObject,
configObjectFullPath,
packageJsonFullPath,
packageJson,
});

// Invoke API Extractor
const extractorResult = Extractor.invoke(extractorConfig, {
// Equivalent to the "--local" command-line parameter
localBuild: true,

// Equivalent to the "--verbose" command-line parameter
showVerboseMessages: true,
});

if (extractorResult.succeeded) {
console.log(`API Extractor completed successfully`);
process.exitCode = 0;
} else {
console.error(
`API Extractor completed with ${extractorResult.errorCount} errors` +
` and ${extractorResult.warningCount} warnings`
);
process.exitCode = 1;
const pkgRoot = path.resolve(__dirname, "..");

// `api-extractor` doesn't know to load `index.ts` instead of `index.d.ts` when
// resolving imported types, so copy `index.ts` to `index.d.ts`, bundle types,
// then restore the original contents. We need the original `index.d.ts` for
// typing the `packages/miniflare/src/workers` directory.
const workersTypesExperimental = path.join(
pkgRoot,
"node_modules",
"@cloudflare",
"workers-types",
"experimental"
);
const indexTsPath = path.join(workersTypesExperimental, "index.ts");
const indexDtsPath = path.join(workersTypesExperimental, "index.d.ts");
const originalDtsContent = fs.readFileSync(indexDtsPath);

fs.copyFileSync(indexTsPath, indexDtsPath);

try {
const extractorConfig = ExtractorConfig.prepare({
configObject,
configObjectFullPath,
packageJsonFullPath,
packageJson,
});

// Invoke API Extractor
const extractorResult = Extractor.invoke(extractorConfig, {
// Equivalent to the "--local" command-line parameter
localBuild: true,

// Equivalent to the "--verbose" command-line parameter
showVerboseMessages: true,
});

if (extractorResult.succeeded) {
console.log(`API Extractor completed successfully`);
process.exitCode = 0;
} else {
console.error(
`API Extractor completed with ${extractorResult.errorCount} errors` +
` and ${extractorResult.warningCount} warnings`
);
process.exitCode = 1;
}
} finally {
fs.writeFileSync(indexDtsPath, originalDtsContent);
}
32 changes: 29 additions & 3 deletions packages/wrangler/src/api/integrations/bindings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CacheStorage } from "./caches";
import { ExecutionContext } from "./executionContext";
import { getServiceBindings } from "./services";
import type { Config } from "../../../config";
import type { IncomingRequestCfProperties } from "@cloudflare/workers-types/experimental";
import type { MiniflareOptions } from "miniflare";

/**
Expand Down Expand Up @@ -36,11 +37,18 @@ export type GetBindingsProxyOptions = {
/**
* Result of the `getBindingsProxy` utility
*/
export type BindingsProxy<Bindings = Record<string, unknown>> = {
export type BindingsProxy<
Bindings = Record<string, unknown>,
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties
> = {
/**
* Object containing the various proxies
*/
bindings: Bindings;
/**
* Mock of the context object that Workers received in their request handler, all the object's methods are no-op
*/
cf: CfProperties;
/**
* Mock of the context object that Workers received in their request handler, all the object's methods are no-op
*/
Expand All @@ -62,9 +70,12 @@ export type BindingsProxy<Bindings = Record<string, unknown>> = {
* @param options The various options that can tweak this function's behavior
* @returns An Object containing the generated proxies alongside other related utilities
*/
export async function getBindingsProxy<Bindings = Record<string, unknown>>(
export async function getBindingsProxy<

Check warning on line 73 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L73

Added line #L73 was not covered by tests
Bindings = Record<string, unknown>,
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties
>(
options: GetBindingsProxyOptions = {}
): Promise<BindingsProxy<Bindings>> {
): Promise<BindingsProxy<Bindings, CfProperties>> {

Check warning on line 78 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L78

Added line #L78 was not covered by tests
const rawConfig = readConfig(options.configPath, {
experimentalJsonConfig: options.experimentalJsonConfig,
});
Expand All @@ -88,11 +99,15 @@ export async function getBindingsProxy<Bindings = Record<string, unknown>>(

const vars = getVarsForDev(rawConfig, env);

const cf = await mf.getCf();
deepFreeze(cf);

Check warning on line 103 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L102-L103

Added lines #L102 - L103 were not covered by tests

return {
bindings: {
...vars,
...bindings,
},
cf: cf as CfProperties,
ctx: new ExecutionContext(),
caches: new CacheStorage(),
dispose: () => mf.dispose(),
Expand Down Expand Up @@ -177,3 +192,14 @@ function getMiniflarePersistOptions(
d1Persist: `${persistPath}/d1`,
};
}

function deepFreeze<T extends Record<string | number | symbol, unknown>>(

Check warning on line 196 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L196

Added line #L196 was not covered by tests
obj: T
): void {
Object.freeze(obj);
Object.entries(obj).forEach(([, prop]) => {

Check warning on line 200 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L198-L200

Added lines #L198 - L200 were not covered by tests
if (prop !== null && typeof prop === "object" && !Object.isFrozen(prop)) {
deepFreeze(prop as Record<string | number | symbol, unknown>);

Check warning on line 202 in packages/wrangler/src/api/integrations/bindings/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/wrangler/src/api/integrations/bindings/index.ts#L202

Added line #L202 was not covered by tests
}
});
}
Loading

0 comments on commit a14bd1d

Please sign in to comment.