adding monkeytype
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled

This commit is contained in:
Benjamin Falch
2026-04-23 13:53:44 +02:00
parent e214a2fd35
commit 2bc741fb78
1930 changed files with 7590652 additions and 0 deletions

376
packages/release/src/index.js Executable file
View File

@@ -0,0 +1,376 @@
import { Octokit } from "@octokit/rest";
import { execSync } from "child_process";
import dotenv from "dotenv";
import fs, { readFileSync } from "fs";
import readlineSync from "readline-sync";
import path, { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config();
const args = new Set(process.argv.slice(2));
const isFrontend = args.has("--fe");
const noDeploy = args.has("--no-deploy");
const isBackend = args.has("--be");
const isDryRun = args.has("--dry");
const noSyncCheck = args.has("--no-sync-check");
const hotfix = args.has("--hotfix");
const previewFe = args.has("--preview-fe");
const PROJECT_ROOT = path.resolve(__dirname, "../../../");
const runCommand = (command, force) => {
if (isDryRun && !force) {
console.log(`[Dry Run] Command: ${command}`);
return "[Dry Run] Command executed.";
} else {
try {
const output = execSync(command, { stdio: "pipe" }).toString();
return output;
} catch (error) {
console.error(`Error executing command ${command}`);
console.error(error.output.toString());
process.exit(1);
}
}
return undefined;
};
const runProjectRootCommand = (command, force) => {
if (isDryRun && !force) {
console.log(`[Dry Run] Command: ${command}`);
return "[Dry Run] Command executed.";
} else {
try {
const output = execSync(`cd ${PROJECT_ROOT} && ${command}`, {
stdio: "pipe",
}).toString();
return output;
} catch (error) {
console.error(`Error executing command ${command}`);
console.error(error);
process.exit(1);
}
}
return undefined;
};
const checkBranchSync = () => {
console.log("Checking if local branch is master...");
const currentBranch = runProjectRootCommand(
"git branch --show-current",
).trim();
if (currentBranch !== "master") {
console.error(
"Local branch is not master. Please checkout the master branch.",
);
process.exit(1);
}
console.log("Checking if local master branch is in sync with origin...");
if (noSyncCheck) {
console.log("Skipping sync check.");
} else if (isDryRun) {
console.log("[Dry Run] Checking sync...");
} else {
try {
// Fetch the latest changes from the remote repository
runProjectRootCommand("git fetch origin");
// Get the commit hashes of the local and remote master branches
const localMaster = runProjectRootCommand("git rev-parse master").trim();
const remoteMaster = runProjectRootCommand(
"git rev-parse origin/master",
).trim();
if (localMaster !== remoteMaster) {
console.error(
"Local master branch is not in sync with origin. Please pull the latest changes before proceeding.",
);
process.exit(1);
}
} catch (error) {
console.error("Error checking branch sync status.");
console.error(error);
process.exit(1);
}
}
};
const getCurrentVersion = () => {
console.log("Getting current version...");
const rootPackageJson = JSON.parse(
readFileSync(`${PROJECT_ROOT}/package.json`, "utf-8"),
);
return rootPackageJson.version;
};
const incrementVersion = (currentVersion) => {
console.log("Incrementing version...");
const now = new Date();
const year = Number(now.getFullYear().toString().slice(-2));
const start = new Date(now.getFullYear(), 0, 1);
const week = Math.ceil(((now - start) / 86400000 + start.getDay() + 1) / 7);
const [prevYear, prevWeek, minor] = currentVersion.split(".").map(Number);
let newMinor = minor + 1;
if (year !== prevYear || week !== prevWeek) {
newMinor = 0;
}
const v = `v${year}.${week}.${newMinor}`;
return v;
};
const updatePackage = (newVersion) => {
console.log("Updating package.json...");
if (isDryRun) {
console.log(`[Dry Run] Updated package.json to version ${newVersion}`);
return;
}
const packagePath = `${PROJECT_ROOT}/package.json`;
// Read the package.json file
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
// Update the version field
packageJson.version = newVersion.replace("v", "");
// Write the updated JSON back to package.json
fs.writeFileSync(
packagePath,
JSON.stringify(packageJson, null, 2) + "\n",
"utf8",
);
console.log(`Updated package.json to version ${newVersion}`);
};
const checkUncommittedChanges = () => {
console.log("Checking uncommitted changes...");
const status = execSync("git status --porcelain").toString().trim();
if (isDryRun) {
console.log("[Dry Run] Checking uncommitted changes...");
} else if (status) {
console.error(
"You have uncommitted changes. Please commit or stash them before proceeding.",
);
process.exit(1);
}
};
const installDependencies = () => {
console.log("Installing dependencies...");
if (isDryRun) {
console.log("[Dry Run] Dependencies would be installed.");
} else {
runProjectRootCommand("pnpm i");
}
};
const buildProject = () => {
console.log("Building project...");
if (isFrontend && !isBackend) {
runProjectRootCommand(
"NODE_ENV=production SENTRY=1 npx turbo lint test check-assets build --filter @monkeytype/frontend --force",
);
} else if (isBackend && !isFrontend) {
runProjectRootCommand(
"NODE_ENV=production SENTRY=1 npx turbo lint test build --filter @monkeytype/backend --force",
);
} else {
runProjectRootCommand(
"NODE_ENV=production SENTRY=1 npx turbo lint test check-assets build --force",
);
}
};
const deployBackend = () => {
console.log("Deploying backend...");
const p = path.resolve(__dirname, "../bin/deployBackend.sh");
runCommand(`sh ${p}`);
};
const deployFrontend = () => {
console.log("Deploying frontend...");
runProjectRootCommand(
"cd frontend && npx firebase deploy -P live --only hosting",
);
};
const purgeCache = () => {
console.log("Purging Cloudflare cache...");
const p = path.resolve(__dirname, "../bin/purgeCfCache.sh");
runCommand(`sh ${p}`);
};
const generateChangelog = async () => {
console.log("Generating changelog...");
const p = path.resolve(__dirname, "./buildChangelog.js");
const changelog = runCommand(`node ${p}`, true);
return changelog;
};
const generateContributors = () => {
console.log("Generating contributors list...");
try {
const p = path.resolve(__dirname, "./buildContributors.js");
let contributors = runCommand(`node ${p}`, true);
contributors = JSON.parse(
contributors.replaceAll("\n", "").replace(/^.*?\[/, "["),
);
fs.writeFileSync(
`${PROJECT_ROOT}/frontend/static/contributors.json`,
JSON.stringify(contributors, null, 2),
"utf8",
);
console.log("Contributors list updated.");
} catch (e) {
console.error("Failed to generate contributors list.");
console.error(e);
}
};
const createCommitAndTag = (version) => {
console.log("Creating commit and tag... Pushing to Github...");
runCommand(`git add .`);
runCommand(`git commit -m "chore: release ${version}" --no-verify`);
runCommand(`git tag ${version}`);
runCommand(`git push origin master --tags --no-verify`);
};
const createGithubRelease = async (version, changelogContent) => {
console.log("Creating GitHub release...");
if (isDryRun) {
console.log(
`[Dry Run] Sent release request to GitHub for version ${version}`,
);
} else {
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { owner, repo } = {
owner: "monkeytypegame",
repo: "monkeytype",
};
await octokit.repos.createRelease({
owner,
repo,
tag_name: version,
name: `${version}`,
body: changelogContent,
});
}
};
const main = async () => {
if (previewFe) {
console.log(`Starting frontend preview deployment process...`);
installDependencies();
runProjectRootCommand(
"NODE_ENV=production npx turbo lint test check-assets build --filter @monkeytype/frontend --force",
);
const name = readlineSync.question(
"Enter preview channel name (default: preview): ",
);
let channelName = name.trim();
if (channelName === "") {
channelName = "preview";
}
const expirationTime = readlineSync.question(
"Enter expiration time (e.g., 2h, default: 1d): ",
);
let expires = expirationTime.trim();
if (expires === "") {
expires = "1d";
}
console.log(
`Deploying frontend preview to channel "${channelName}" with expiration "${expires}"...`,
);
const result = runProjectRootCommand(
`cd frontend && npx firebase hosting:channel:deploy ${channelName} -P live --expires ${expires}`,
);
console.log(result);
console.log("Frontend preview deployed successfully.");
process.exit(0);
}
console.log(`Starting ${hotfix ? "hotfix" : "release"} process...`);
if (!hotfix) checkBranchSync();
checkUncommittedChanges();
installDependencies();
let changelogContent;
let newVersion;
if (!hotfix) {
changelogContent = await generateChangelog();
console.log(changelogContent);
if (!readlineSync.keyInYN("Changelog looks good?")) {
console.log("Exiting.");
process.exit(1);
}
const currentVersion = getCurrentVersion();
newVersion = incrementVersion(currentVersion);
console.log(`New version: ${newVersion}`);
}
buildProject();
if (!hotfix && !readlineSync.keyInYN(`Ready to release ${newVersion}?`)) {
console.log("Exiting.");
process.exit(1);
}
if (!noDeploy && (isBackend || (!isFrontend && !isBackend))) {
deployBackend();
}
if (!noDeploy && (isFrontend || (!isFrontend && !isBackend))) {
deployFrontend();
}
if (!noDeploy) purgeCache();
if (!hotfix) {
generateContributors();
updatePackage(newVersion);
createCommitAndTag(newVersion);
try {
await createGithubRelease(newVersion, changelogContent);
} catch (e) {
console.error(`Failed to create release on GitHub: ${e}`);
console.log("Please create the release manually.");
}
}
if (hotfix) {
console.log("Hotfix completed successfully.");
} else {
console.log(`Release ${newVersion} completed successfully.`);
}
process.exit(0);
};
main();