chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:11 +08:00
commit 70bf21e064
5213 changed files with 731895 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
dev-snippets-outputs
+9
View File
@@ -0,0 +1,9 @@
# @cloudflare/codemod
## 1.1.0
### Minor Changes
- [#13102](https://github.com/cloudflare/workers-sdk/pull/13102) [`e200179`](https://github.com/cloudflare/workers-sdk/commit/e200179d8dbdb36d716e5df9e981b3219c93d974) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - First release of new package
Add `@cloudflare/codemod` package with internal AST-based codemod utilities (`parseJs`, `parseTs`, `parseFile`, `transformFile`, `mergeObjectProperties`) built on `recast`.
+63
View File
@@ -0,0 +1,63 @@
# @cloudflare/codemod
Internal codemod utilities for the Cloudflare Workers SDK.
## Developing Codemods
Writing codemods often requires trial and error. The package ships a dedicated dev workflow so you can iterate on transforms in isolation without writing throw-away scripts.
### Commands
| Command | Description |
| --------------- | ---------------------------------------------------------- |
| `pnpm dev` | Run `src/dev.ts` in watch mode (hot-reloads on every save) |
| `pnpm dev:once` | Run `src/dev.ts` once without watching |
### Workflow
1. **Edit `dev-snippets/test.ts`** — put whatever source code you want to transform into this file. It is the default input used by the dev script. You can add more files under `dev-snippets/` and reference them from `src/dev.ts`.
2. **Edit `testCodemod()` in `src/dev.ts`** — this is your sandbox. Call `testTransform()` with the path to your snippet and a [`recast` visitor](https://github.com/benjamn/recast) that describes the transform:
```ts
const testCodemod = () => {
testTransform("../dev-snippets/test.ts", {
visitIdentifier(n) {
n.node.name = "MyNewName";
return false;
},
});
};
```
3. **Run `pnpm dev`** — the transformed code is printed to the console and written to `dev-snippets-outputs/test.ts` (gitignored). Inspect the output file to verify the transform behaves as expected.
### Key Files
| File | Purpose |
| ----------------------- | -------------------------------------------------------------------------------------------- |
| `src/dev.ts` | Dev entry point; exports `testTransform()` and contains the editable `testCodemod()` sandbox |
| `dev-snippets/test.ts` | Default sample input — replace its contents freely |
| `dev-snippets-outputs/` | Auto-generated transform output, gitignored — safe to inspect, never committed |
### `testTransform(filePath, methods)`
Mirrors the production `transformFile()` API but instead of silently writing in place it:
- Prints the transformed source to the console
- Writes the result to the corresponding path under `dev-snippets-outputs/`
The `filePath` argument must point to a file inside `dev-snippets/`; paths outside that directory are rejected.
### Inspecting the AST
`src/dev.ts` includes a commented-out `_printSnippet()` helper. Uncomment its call at the bottom of the file to log the AST of an arbitrary snippet to the console — useful when you need to know the exact node shape to target in a visitor:
```ts
const _printSnippet = () => {
const snippet = `if (true) { console.log("potato"); }`;
const program = parseTs(snippet).program;
console.log(program.body[0]);
};
_printSnippet(); // uncomment to run
```
+5
View File
@@ -0,0 +1,5 @@
// This file is used by `pnpm dev` as a sample input for testing codemods.
// Feel free to replace its contents with whatever code you want to transform.
const greeting = "hello";
console.log(greeting);
+55
View File
@@ -0,0 +1,55 @@
{
"name": "@cloudflare/codemod",
"version": "1.1.0",
"private": true,
"description": "Internal codemod utilities",
"homepage": "https://github.com/cloudflare/workers-sdk#readme",
"bugs": {
"url": "https://github.com/cloudflare/workers-sdk/issues"
},
"license": "MIT OR Apache-2.0",
"author": "workers-devprod@cloudflare.com",
"repository": {
"type": "git",
"url": "https://github.com/cloudflare/workers-sdk.git",
"directory": "packages/codemod"
},
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.mjs",
"types": "./dist/index.d.mts"
}
},
"scripts": {
"build": "tsup",
"check:type": "tsc -p ./tsconfig.json",
"deploy": "echo 'no deploy'",
"dev": "tsx watch src/dev.ts",
"dev:once": "tsx src/dev.ts",
"test": "vitest",
"test:ci": "vitest run",
"type:tests": "tsc -p ./tests/tsconfig.json"
},
"devDependencies": {
"@cloudflare/workers-tsconfig": "workspace:*",
"@types/esprima": "^4.0.3",
"@types/node": "catalog:default",
"@vitest/ui": "catalog:default",
"concurrently": "^8.2.2",
"recast": "^0.23.11",
"tsup": "8.3.0",
"tsx": "^3.12.8",
"typescript": "catalog:default",
"vitest": "catalog:default"
},
"peerDependencies": {
"recast": "^0.23.11"
},
"volta": {
"extends": "../../package.json"
},
"workers-sdk": {}
}
+75
View File
@@ -0,0 +1,75 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import * as recast from "recast";
import { parseFile, parseTs } from "./index";
/**
* Writing code-mods often requires some trial and error.
* Often manual testing can become a hassle. This script was meant
* to help test and develop transforms in isolation without having to write a throw-away script.
*
* Replace your codemod below and run the script with `pnpm run dev`
*/
/**
* This function mocks the `transformFile` API but outputs it to the console and writes it
* to a dedicated output file for easier testing.
*/
export const testTransform = (
filePath: string,
methods: recast.types.Visitor
) => {
const devSnippetsDir = resolve(__dirname, "../dev-snippets");
const resolvedInput = resolve(__dirname, filePath);
const relativeInput = relative(devSnippetsDir, resolvedInput);
if (
relativeInput.startsWith("..") ||
resolve(relativeInput) === relativeInput
) {
throw new Error(`Input file must be under dev-snippets/. Got: ${filePath}`);
}
const ast = parseFile(resolvedInput);
if (ast) {
recast.visit(ast, methods);
const code = recast.print(ast).code;
console.log(code);
const outputPath = join(
__dirname,
"../dev-snippets-outputs",
relativeInput
);
mkdirSync(dirname(outputPath), {
recursive: true,
});
writeFileSync(outputPath, code);
}
};
// Use this function to experiment with a codemod in isolation
const testCodemod = () => {
testTransform("../dev-snippets/test.ts", {
visitIdentifier(n) {
n.node.name = "Potato";
return false;
},
});
};
testCodemod();
// This function can be used to inspect the AST of a particular snippet
const _printSnippet = () => {
const snippet = `
if(true) {
console.log("potato");
}
`;
const program = parseTs(snippet).program;
console.log(program.body[0]);
};
// _printSnippet();
+124
View File
@@ -0,0 +1,124 @@
import { readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import * as recast from "recast";
import * as esprimaParser from "recast/parsers/esprima";
import * as typescriptParser from "recast/parsers/typescript";
import type { Program } from "esprima";
/*
CODEMOD TIPS & TRICKS
=====================
More info about parsing and transforming can be found in the `recast` docs:
https://github.com/benjamn/recast
`recast` uses the `ast-types` library under the hood for basic AST operations
and defining node types. If you need to manipulate or manually construct AST nodes as
part of a code mod operation, be sure to check the `ast-types` documentation:
https://github.com/benjamn/ast-types
Last but not least, AST viewers can be extremely helpful when trying to write
a transformer:
- https://astexplorer.net/
- https://ts-ast-viewer.com/#
*/
// Parse an input string as javascript and return an ast
export function parseJs(src: string) {
src = src.trim();
try {
return recast.parse(src, { parser: esprimaParser });
} catch {
throw new Error("Error parsing js template.");
}
}
// Parse an input string as typescript and return an ast
export function parseTs(src: string) {
src = src.trim();
try {
return recast.parse(src, { parser: typescriptParser });
} catch {
throw new Error("Error parsing ts template.");
}
}
// Parse a provided file with recast and return an ast
// Selects the correct parser based on the file extension
export function parseFile(filePath: string) {
const lang = path.extname(filePath).slice(1);
const parser = lang === "js" ? esprimaParser : typescriptParser;
try {
const fileContents = readFileSync(path.resolve(filePath), "utf-8");
if (fileContents) {
return recast.parse(fileContents, { parser }).program as Program;
}
} catch {
throw new Error(`Error parsing file: ${filePath}`);
}
return null;
}
// Transform a file with the provided transformer methods and write it back to disk
export function transformFile(filePath: string, methods: recast.types.Visitor) {
const ast = parseFile(filePath);
if (ast) {
recast.visit(ast, methods);
writeFileSync(filePath, recast.print(ast).code);
}
}
/**
* merges provided properties into a given object (updating the object itself), deeply merging them in case
* some properties are object themselves
*
* @param sourceObject the object into which merge the new properties
* @param newProperties the new properties to add/merge
*/
export function mergeObjectProperties(
sourceObject: recast.types.namedTypes.ObjectExpression,
newProperties: recast.types.namedTypes.ObjectProperty[]
): void {
newProperties.forEach((newProp) => {
const newPropName = getPropertyName(newProp);
if (!newPropName) {
return false;
}
const indexOfExisting = sourceObject.properties.findIndex(
(p) => p.type === "ObjectProperty" && getPropertyName(p) === newPropName
);
const existing = sourceObject.properties[indexOfExisting];
if (!existing) {
sourceObject.properties.push(newProp);
return;
}
if (
existing.type === "ObjectProperty" &&
existing.value.type === "ObjectExpression" &&
newProp.value.type === "ObjectExpression"
) {
mergeObjectProperties(
existing.value,
newProp.value.properties as recast.types.namedTypes.ObjectProperty[]
);
return;
}
sourceObject.properties[indexOfExisting] = newProp;
});
}
function getPropertyName(newProp: recast.types.namedTypes.ObjectProperty) {
return newProp.key.type === "Identifier"
? newProp.key.name
: newProp.key.type === "StringLiteral"
? newProp.key.value
: null;
}
+129
View File
@@ -0,0 +1,129 @@
import * as recast from "recast";
import parser from "recast/parsers/babel";
import { describe, test } from "vitest";
import { mergeObjectProperties } from "../src/index";
describe("mergeObjectProperties", () => {
const tests = [
{
testName: "merges simple objects",
sourcePropertiesObject: {
propA: "_A_",
},
newPropertiesObject: {
propB: "_B_",
},
expectedPropertiesObject: {
propA: "_A_",
propB: "_B_",
},
},
{
testName: "overrides existing non-object properties",
sourcePropertiesObject: {
__Prop0: true,
propA: "_A_",
propB: false,
propC: 123,
},
newPropertiesObject: {
propA: "_a_",
propB: true,
propC: 456,
},
expectedPropertiesObject: {
__Prop0: true,
propA: "_a_",
propB: true,
propC: 456,
},
},
{
testName: "deep merges object properties",
sourcePropertiesObject: {
propA: {
propAA: "a",
propAB: "b",
propAC: {
propAA: {
propAAA: "this is quite nested 1",
propAAC: {
propAAAA: "this is even more nested",
},
},
},
},
},
newPropertiesObject: {
propA: {
propAB: "B",
propAC: {
propAA: {
propAAB: "this is quite nested 2",
propAAC: {
propAAAA: "this is even more nested 1",
propAAAB: "this is even more nested 2",
},
},
},
},
},
expectedPropertiesObject: {
propA: {
propAA: "a",
propAB: "B",
propAC: {
propAA: {
propAAA: "this is quite nested 1",
propAAC: {
propAAAA: "this is even more nested 1",
propAAAB: "this is even more nested 2",
},
propAAB: "this is quite nested 2",
},
},
},
},
},
] satisfies {
testName: string;
sourcePropertiesObject: Record<string, unknown>;
newPropertiesObject: Record<string, unknown>;
expectedPropertiesObject: Record<string, unknown>;
}[];
tests.forEach(({ testName, ...testObjects }) =>
test(`${testName}`, ({ expect }) => {
const {
sourcePropertiesObject,
newPropertiesObject,
expectedPropertiesObject,
} = testObjects;
const sourceObj = createObjectExpression(sourcePropertiesObject);
const newProperties = createObjectExpression(newPropertiesObject)
.properties as recast.types.namedTypes.ObjectProperty[];
const expectedObj = createObjectExpression(expectedPropertiesObject);
mergeObjectProperties(sourceObj, newProperties);
expect(recast.prettyPrint(sourceObj, { parser }).code).toEqual(
recast.prettyPrint(expectedObj, { parser }).code
);
})
);
});
const createObjectExpression = (
sourceObj: Record<string, unknown>
): recast.types.namedTypes.ObjectExpression => {
return (
(
recast.parse(
`const obj = {${Object.entries(sourceObj)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(",\n")}}`,
{ parser }
).program.body[0] as recast.types.namedTypes.VariableDeclaration
).declarations[0] as recast.types.namedTypes.VariableDeclarator
).init as recast.types.namedTypes.ObjectExpression;
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["node"],
"tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["**/*.ts"],
"exclude": []
}
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "@cloudflare/workers-tsconfig/tsconfig.json",
"compilerOptions": {
"module": "esnext",
"types": ["node"],
"tsBuildInfoFile": ".tsbuildinfo"
},
"include": ["**/*.ts", "**/*.js"],
"exclude": [
"dist",
"node_modules",
"tests/**/*.test.ts",
"dev-snippets",
"dev-snippets-outputs"
]
}
+29
View File
@@ -0,0 +1,29 @@
import { defineConfig } from "tsup";
export default defineConfig(() => [
{
treeshake: true,
keepNames: true,
entry: ["src/index.ts"],
platform: "node",
format: "esm",
dts: true,
outDir: "dist",
tsconfig: "tsconfig.json",
metafile: true,
sourcemap: process.env.SOURCEMAPS !== "false",
define: {
"process.env.NODE_ENV": `'${"production"}'`,
},
external: [
"@cloudflare/*",
"vitest",
// Note: recast is external and a peer dependency of the package because `recast` does generally need
// to be used directly by this package's consumers, and if it were bundled in this package that
// would mean that consumers of this package would get the `recast` code twice significantly
"recast",
"recast/parsers/esprima",
"recast/parsers/typescript",
],
},
]);
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "http://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "!**/__tests__/**"],
"outputs": ["dist/**"],
"env": ["SOURCEMAPS"],
"passThroughEnv": ["PWD"]
},
"test:ci": {
"dependsOn": ["build"],
"env": ["LC_ALL", "TZ"]
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
testTimeout: 15_000,
pool: "forks",
include: ["**/tests/**/*.test.ts"],
reporters: ["default"],
unstubEnvs: true,
mockReset: true,
},
});