Skip to content

Instantly share code, notes, and snippets.

@lesleh
Last active September 1, 2022 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lesleh/7a9f9c24e01689fa2a44b30447250c70 to your computer and use it in GitHub Desktop.
Save lesleh/7a9f9c24e01689fa2a44b30447250c70 to your computer and use it in GitHub Desktop.
/* eslint-disable no-restricted-syntax, no-console, no-await-in-loop */
const { promises: fs } = require("fs");
const { exec } = require("child_process");
const TEST_COMMANDS = [
"yarn check-types",
"yarn lint",
"yarn test",
"yarn build",
];
const IGNORED_PACKAGES = [
"@react-aria/button",
"@testing-library/react",
"@testing-library/user-event",
"@types/react",
"@types/react-dom",
"react",
"react-dom",
"webpack",
];
function execPromise(cmd, { ignoreErrors = false } = {}) {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
if (error && !ignoreErrors) reject(error);
resolve({ error, stdout, stderr });
});
});
}
function arrayToObject(arr, keyNames) {
if (arr.length !== keyNames.length)
throw new RangeError("Arrays do not match");
const output = {};
for (let i = 0; i < arr.length; i += 1) {
output[keyNames[i]] = arr[i];
}
return output;
}
function toArray(str) {
return str.split(/\s+/).filter(Boolean);
}
async function gitChangedFiles() {
const { stdout } = await execPromise("git diff --name-only");
return toArray(stdout);
}
async function didPackageChange() {
const changedFiles = await gitChangedFiles();
return (
changedFiles.includes("package.json") && changedFiles.includes("yarn.lock")
);
}
function toLower(str) {
return str.toLowerCase();
}
function camelize(str) {
return str.replace(/[-\s]([a-z])/g, (g) => g[1].toUpperCase());
}
async function getPackageUpdates() {
const { stdout } = await execPromise("yarn outdated --json", {
ignoreErrors: true,
});
const data = stdout.split("\n")[1];
const json = JSON.parse(data).data;
const head = json.head.map(toLower).map(camelize);
const updates = json.body.map((packageArray) =>
arrayToObject(packageArray, head)
);
return updates
.filter((x) => !IGNORED_PACKAGES.includes(x.package))
.filter((x) => x.current !== x.wanted);
}
async function updatePackageInPackageJson(packageJson, name, type, version) {
const contents = await fs.readFile(packageJson, "utf-8");
const parsed = JSON.parse(contents);
parsed[type][name] = `^${version}`;
const newContents = `${JSON.stringify(parsed, null, 2)}\n`;
await fs.writeFile(packageJson, newContents);
}
async function commitPackageChange(packageJson, name, version) {
console.log("Committing change");
await execPromise(`git add ${packageJson} yarn.lock`);
await execPromise(`git commit -m "Update ${name} to ${version}"`);
}
async function isWorkspace() {
const { stdout } = await execPromise("yarn workspaces info");
return stdout.includes("workspace");
}
async function getWorkspacesInfo() {
const { stdout } = await execPromise("yarn workspaces --silent info --json");
return JSON.parse(stdout);
}
const getWorkspaceDetails = async (workspaceName) => {
const packageUpdates = (await getPackageUpdates()).filter(
(x) => x.workspace === workspaceName
);
if (workspaceName) {
const workspacesInfo = await getWorkspacesInfo();
const workspaceInfo = workspacesInfo[workspaceName];
const packageJson = `${workspaceInfo.location}/package.json`;
return {
packageJson,
packageUpdates,
};
} else {
return {
packageJson: "package.json",
packageUpdates,
};
}
};
async function processWorkspace(workspaceName = "") {
const { packageJson, packageUpdates } = await getWorkspaceDetails(
workspaceName
);
for (const packageUpdate of packageUpdates) {
try {
const { package, current, wanted, packageType } = packageUpdate;
try {
console.group(`Updating ${package} from ${current} to ${wanted}`);
await updatePackageInPackageJson(
packageJson,
package,
packageType,
wanted
);
console.log("Running yarn install");
await execPromise("yarn install");
if (!didPackageChange()) {
console.log("Package didn't change, continuing");
continue;
}
try {
console.group("Running tests");
for (const command of TEST_COMMANDS) {
console.log("Running", command);
await execPromise(command);
}
} finally {
console.groupEnd();
}
await commitPackageChange(packageJson, package, wanted);
} finally {
console.groupEnd();
}
} catch (error) {
console.error(error);
// Clean up
await execPromise("git checkout packages package.json yarn.lock");
await execPromise("yarn install");
}
}
}
async function processWorkspaces() {
const workspacesInfo = await getWorkspacesInfo();
const workspaceNames = [...Object.keys(workspacesInfo), ""];
for (const workspaceName of workspaceNames) {
console.group(`Processing ${workspaceName || "root"} workspace`);
await processWorkspace(workspaceName);
console.groupEnd();
}
}
(async () => {
await processWorkspaces();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment