Last active
September 1, 2022 14:13
-
-
Save lesleh/7a9f9c24e01689fa2a44b30447250c70 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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