Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codemod should update package versions #1227

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 49 additions & 43 deletions packages/codemod/__tests__/package-json-update.spec.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,106 @@
import * as fs from 'fs/promises';
import * as glob from 'glob';
import { findAndUpdatePackageJson } from '../scripts/package-json-update';
import * as fs from "fs/promises";
import * as glob from "glob";
import { findAndUpdatePackageJson } from "../scripts/package-json-update";

jest.mock('fs/promises');
jest.mock('glob');
jest.mock("fs/promises");
jest.mock("glob");

describe('Package JSON Update Script', () => {
describe("Package JSON Update Script", () => {
const mockFs = fs as jest.Mocked<typeof fs>;
const mockGlob = glob as jest.Mocked<typeof glob>;

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation();
jest.spyOn(console, "log").mockImplementation();
jest.clearAllMocks();
});

it('should update dependencies and scripts in package.json', async () => {
it("should update dependencies and scripts in package.json", async () => {
const mockPackageJson = {
dependencies: {
'edgedb': '^1.0.0',
'@edgedb/generate': '^2.0.0',
'other-pkg': '^3.0.0'
edgedb: "^1.0.0",
"@edgedb/auth-nextjs": "^0.4.0",
"other-pkg": "^3.0.0",
},
devDependencies: {
'@edgedb/auth': '^1.0.0'
"@edgedb/generate": "^2.0.0",
},
scripts: {
generate: 'npx @edgedb/generate edgeql',
other: 'npx do-something'
}
generate: "npx @edgedb/generate edgeql && npx @edgedb/generate queries",
other: "npx do-something",
},
};

mockGlob.sync.mockReturnValue(['/path/to/package.json']);
mockGlob.sync.mockReturnValue(["/path/to/package.json"]);
mockFs.readFile.mockResolvedValue(JSON.stringify(mockPackageJson));
mockFs.writeFile.mockResolvedValue();

await findAndUpdatePackageJson('/root');
await findAndUpdatePackageJson("/root");

expect(mockGlob.sync).toHaveBeenCalledWith('**/package.json', {
cwd: '/root',
ignore: ['**/node_modules/**'],
absolute: true
expect(mockGlob.sync).toHaveBeenCalledWith("**/package.json", {
cwd: "/root",
ignore: ["**/node_modules/**"],
absolute: true,
});

expect(mockFs.readFile).toHaveBeenCalledWith('/path/to/package.json', 'utf8');
expect(mockFs.readFile).toHaveBeenCalledWith(
"/path/to/package.json",
"utf8",
);

const expectedPackageJson = {
dependencies: {
'other-pkg': '^3.0.0',
'gel': '^1.0.0',
'@gel/generate': '^2.0.0'
"other-pkg": "^3.0.0",
gel: "^2.0.1",
"@gel/auth-nextjs": "^0.4.0",
},
devDependencies: {
'@gel/auth': '^1.0.0'
"@gel/generate": "^0.6.2",
},
scripts: {
generate: 'npx @gel/generate edgeql',
other: 'npx do-something'
}
generate: "npx @gel/generate edgeql && npx @gel/generate queries",
other: "npx do-something",
},
};

expect(mockFs.writeFile).toHaveBeenCalledWith(
'/path/to/package.json',
JSON.stringify(expectedPackageJson, null, 2) + '\n'
"/path/to/package.json",
JSON.stringify(expectedPackageJson, null, 2) + "\n",
);
});

it('should handle package.json without dependencies or scripts', async () => {
it("should handle package.json without dependencies or scripts", async () => {
const mockPackageJson = {
name: 'test-package'
name: "test-package",
};

mockGlob.sync.mockReturnValue(['/path/to/package.json']);
mockGlob.sync.mockReturnValue(["/path/to/package.json"]);
mockFs.readFile.mockResolvedValue(JSON.stringify(mockPackageJson));
mockFs.writeFile.mockResolvedValue();

await findAndUpdatePackageJson('/root');
await findAndUpdatePackageJson("/root");

expect(mockFs.readFile).toHaveBeenCalled();
expect(mockFs.writeFile).not.toHaveBeenCalled();
});

it('should handle file read errors', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
it("should handle file read errors", async () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation();

mockGlob.sync.mockReturnValue(['/path/to/package.json']);
mockFs.readFile.mockRejectedValue(new Error('File read error'));
mockGlob.sync.mockReturnValue(["/path/to/package.json"]);
mockFs.readFile.mockRejectedValue(new Error("File read error"));

await findAndUpdatePackageJson('/root');
await findAndUpdatePackageJson("/root");

expect(consoleSpy).toHaveBeenCalledWith('Error:', 'Error processing /path/to/package.json: File read error');
expect(consoleSpy).toHaveBeenCalledWith(
"Error:",
"Error processing /path/to/package.json: File read error",
);
});

it('should handle no package.json files found', async () => {
it("should handle no package.json files found", async () => {
mockGlob.sync.mockReturnValue([]);

await findAndUpdatePackageJson('/root');
await findAndUpdatePackageJson("/root");

expect(mockFs.readFile).not.toHaveBeenCalled();
expect(mockFs.writeFile).not.toHaveBeenCalled();
Expand Down
2 changes: 2 additions & 0 deletions packages/codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"devDependencies": {
"@types/debug": "^4.1.12",
"@types/node": "^20.12.13",
"jest": "^29.7.0",
"ts-jest": "^29.2.6",
"tsx": "^4.11.0",
"typescript": "^5.5.2"
},
Expand Down
70 changes: 41 additions & 29 deletions packages/codemod/scripts/package-json-update.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
import * as fs from 'fs/promises';
import * as glob from 'glob';
import * as fs from "fs/promises";
import * as glob from "glob";

const dependenciesMap = {
'edgedb': 'gel',
'@edgedb/generate': '@gel/generate',
'@edgedb/auth': '@gel/auth',
'@edgedb/auth-core': '@gel/auth-core',
'@edgedb/auth-nextjs': '@gel/auth-nextjs',
'@edgedb/ai': '@gel/ai',
'@edgedb/auth-express': '@gel/auth-express',
'@edgedb/auth-sveltekit': '@gel/auth-sveltekit',
'@edgedb/auth-remix': '@gel/auth-remix',
edgedb: ["gel", "^2.0.1"],
"@edgedb/generate": ["@gel/generate", "^0.6.2"],
"@edgedb/ai": ["@gel/ai", "^0.1.0"],
"@edgedb/auth-core": ["@gel/auth-core", "^0.3.0"],
"@edgedb/auth-nextjs": ["@gel/auth-nextjs", "^0.4.0"],
"@edgedb/auth-express": ["@gel/auth-express", "^0.3.0"],
"@edgedb/auth-sveltekit": ["@gel/auth-sveltekit", "^0.3.0"],
"@edgedb/auth-remix": ["@gel/auth-remix", "^0.3.0"],
};

async function updatePackageJson(filePath: string): Promise<string[]> {
const changes: string[] = [];

try {
const content = await fs.readFile(filePath, 'utf8');
const content = await fs.readFile(filePath, "utf8");
const pkg = JSON.parse(content);
let modified = false;

for (const [oldPkg, newPkg] of Object.entries(dependenciesMap)) {
for (const [oldPkg, [newPkg, version]] of Object.entries(dependenciesMap)) {
if (pkg.dependencies?.[oldPkg]) {
pkg.dependencies[newPkg] = pkg.dependencies[oldPkg];
pkg.dependencies[newPkg] = version;
delete pkg.dependencies[oldPkg];
changes.push(`Replaced ${oldPkg} with ${newPkg} in dependencies`);
changes.push(
`Replaced ${oldPkg} with ${newPkg}@${version} in dependencies`,
);
modified = true;
}

if (pkg.devDependencies?.[oldPkg]) {
pkg.devDependencies[newPkg] = pkg.devDependencies[oldPkg];
pkg.devDependencies[newPkg] = version;
delete pkg.devDependencies[oldPkg];
changes.push(`Replaced ${oldPkg} with ${newPkg} in devDependencies`);
changes.push(
`Replaced ${oldPkg} with ${newPkg}@${version} in devDependencies`,
);
modified = true;
}
}
Expand All @@ -41,24 +44,31 @@ async function updatePackageJson(filePath: string): Promise<string[]> {
const updatedScripts: Record<string, string> = {};

for (const [scriptName, scriptValue] of Object.entries(pkg.scripts)) {
const updatedValue = (scriptValue as string)
.replace(/@edgedb\/generate/g, '@gel/generate');
const updatedValue = (scriptValue as string).replace(
/@edgedb\/generate/g,
"@gel/generate",
);

updatedScripts[scriptName] = updatedValue;

if (updatedValue !== scriptValue) {
modified = true;
changes.push(`Updated script "${scriptName}": ${scriptValue} -> ${updatedValue}`);
updatedScripts[scriptName] = updatedValue;
changes.push(
`Updated script "${scriptName}": ${scriptValue} -> ${updatedValue}`,
);
}
}

if (Object.keys(updatedScripts).length > 0 && Object.keys(updatedScripts).length === Object.keys(pkg.scripts).length) {
if (
Object.keys(updatedScripts).length > 0 &&
Object.keys(updatedScripts).length === Object.keys(pkg.scripts).length
) {
pkg.scripts = updatedScripts;
}
}

if (modified) {
await fs.writeFile(filePath, JSON.stringify(pkg, null, 2) + '\n');
await fs.writeFile(filePath, JSON.stringify(pkg, null, 2) + "\n");
}

return changes;
Expand All @@ -69,26 +79,28 @@ async function updatePackageJson(filePath: string): Promise<string[]> {

export async function findAndUpdatePackageJson(rootDir: string) {
try {
const files = glob.sync('**/package.json', {
const files = glob.sync("**/package.json", {
cwd: rootDir,
ignore: ['**/node_modules/**'],
absolute: true
ignore: ["**/node_modules/**"],
absolute: true,
});

console.log(`Found ${files.length} package.json ${files.length === 1 ? 'file' : 'files'}`);
console.log(
`Found ${files.length} package.json ${files.length === 1 ? "file" : "files"}`,
);

for (const file of files) {
console.log(`Processing ${file}...`);
const changes = await updatePackageJson(file);

if (changes.length > 0) {
console.log(`Changes in ${file}:`);
changes.forEach(change => console.log(` - ${change}`));
changes.forEach((change) => console.log(` - ${change}`));
} else {
console.log(` No changes needed`);
}
}
} catch (error: any) {
console.error('Error:', error.message);
console.error("Error:", error.message);
}
}
8 changes: 7 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@
]
},
"ci:test": {
"dependsOn": ["build", "gel#test", "@gel/auth-core#test"],
"dependsOn": [
"build",
"gel#test",
"@gel/generate#test",
"@gel/auth-core#test",
"@gel/codemod#test"
],
"env": ["CI", "GEL_SERVER_BIN"]
},
"ci:integration-test": {
Expand Down
Loading
Loading