// @ts-check import { existsSync } from "node:fs" const SYNC_SOURCE = "master" // SYNC_DEST is defined in code and not in an environment variable to // have this config version controlled. const SYNC_DEST = [ "test", // "stage", // "prod" ] const CLONE_DIR = `${process.env.HOME}/branch-sync-clone` function error(msg) { throw new Error(`[branch-sync] ${msg}`) } function createLogger() { return { debug(msg) { console.log(`[branch-sync] ${msg}`) }, info(msg) { // info logs are grey console.log("\x1b[90m%s\x1b[0m", `[branch-sync] ${msg}`) }, } } export const onPreBuild = async function ({ utils }) { // Only run for branch builds of source branch. if ( process.env.BRANCH === SYNC_SOURCE && process.env.CONTEXT === "branch-deploy" ) { try { const logger = createLogger() if (!process.env.BITBUCKET_USER_EMAIL) { error( `Missing commit user email, set env var 'BITBUCKET_USER_EMAIL'. See README.` ) } if (!process.env.BITBUCKET_ACCESS_TOKEN) { error( `Missing access token for Bitbucket, set env var 'BITBUCKET_ACCESS_TOKEN'. See README.` ) } const { run } = utils await run("git", [ "config", "user.email", process.env.BITBUCKET_USER_EMAIL, ]) logger.debug( `Git user configured with email ${process.env.BITBUCKET_USER_EMAIL}` ) await run("git", ["config", "user.name", "Netlify Branch Sync Bot"]) logger.debug(`Git user configured with name 'Netlify Branch Sync Bot'`) logger.debug(`Clone directory: ${CLONE_DIR}`) if ( await utils.cache.restore(CLONE_DIR, { move: true, }) ) { logger.debug(`Restored cached for ${CLONE_DIR}`) } else { logger.debug(`Nothing to restore from cache for ${CLONE_DIR}`) } if (existsSync(CLONE_DIR)) { try { await run("git", [ "-C", CLONE_DIR, "rev-parse", "--is-bare-repository", ]) logger.debug( `Verified cached clone directory is a valid bare repository.` ) } catch (e) { logger.info( `Cached clone directory is corrupted or invalid. Removing and recloning. Error: ${e.message}` ) await run("rm", ["-rf", CLONE_DIR]) // Remove corrupted cache } } if (!existsSync(CLONE_DIR)) { // Clone if there is no clone. const token = process.env.BITBUCKET_ACCESS_TOKEN ?? "" const cloneURL = `https://x-token-auth:${token}@bitbucket.org/scandic-swap/web.git` logger.debug(`Cloning from ${cloneURL.replace(token, "****")}`) logger.debug(`Cloning to ${CLONE_DIR}`) await run("git", ["clone", "--bare", "--branch", SYNC_SOURCE, cloneURL, CLONE_DIR]) } // Always fetch to ensure FETCH_HEAD is available logger.debug(`Fetching from origin`) await run("git", ["-C", CLONE_DIR, "fetch", "--no-tags", "origin", SYNC_SOURCE]) logger.debug(`Attempting to sync: ${SYNC_DEST.join(", ")}`) for (let i = 0; i < SYNC_DEST.length; ++i) { const branch = SYNC_DEST[i] await run("git", [ "-C", CLONE_DIR, "push", "origin", `FETCH_HEAD:${branch}`, ]) console.log(`Successfully synced '${branch}' with '${SYNC_SOURCE}'`) } utils.status.show({ title: "Branch sync", summary: "All branches are synced! ✅", text: [ `The following branches have been synced with '${SYNC_SOURCE}'@${process.env.COMMIT_REF}:`, "", ] .concat( SYNC_DEST.map((branch) => { return `- ${branch} - [Deployment list](https://app.netlify.com/sites/web-scandic-hotels/deploys?filter=${branch})` }) ) .join("\n"), }) } catch (error) { utils.build.failBuild("Failed to sync branches.", { error }) } } } export const onPostBuild = async function ({ utils }) { // Only run for branch builds of source branch. if ( process.env.BRANCH === SYNC_SOURCE && process.env.CONTEXT === "branch-deploy" ) { try { const logger = createLogger() if ( await utils.cache.save(CLONE_DIR, { move: true, }) ) { logger.debug(`Saved cached for ${CLONE_DIR}`) } } catch (error) { utils.build.failBuild("Failed to sync branches.", { error }) } } }