From 15b2a7a234fc385137a7a99b8cf1ec4a9e09fc48 Mon Sep 17 00:00:00 2001 From: Arshadul Monir Date: Sun, 26 Jul 2026 15:01:07 -0400 Subject: [PATCH 1/3] CI: simplified logic in build-image script checkstyle for build-image --- .github/scripts/src/build-image/index.ts | 119 ++++++++++------------- 1 file changed, 50 insertions(+), 69 deletions(-) diff --git a/.github/scripts/src/build-image/index.ts b/.github/scripts/src/build-image/index.ts index c3884c1a..e14feb28 100644 --- a/.github/scripts/src/build-image/index.ts +++ b/.github/scripts/src/build-image/index.ts @@ -1,26 +1,39 @@ -import type { Environment } from "@tahminator/pipeline"; - +import { + DockerClient, + GitHubClient, + type Environment, +} from "@tahminator/pipeline"; import { $ } from "bun"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; process.env.TZ = "America/New_York"; -const { environment, githubOutputFile } = await yargs(hideBin(process.argv)) - .option("environment", { - type: "string", - choices: ["staging", "production"] satisfies Environment[], - demandOption: true, - }) - .option("githubOutputFile", { - type: "string", - describe: "Path to GITHUB_OUTPUT (passed in automatically in CI)", - default: process.env.GITHUB_OUTPUT, - }) - .parseAsync(); - -const tagPrefix = environment === "staging" ? "staging-" : ""; -const serverProfiles = environment === "staging" ? "stg" : "prod"; +const { environment, getGhaOutput, githubOutputFile, dockerFileName } = + await yargs(hideBin(process.argv)) + .option("environment", { + choices: ["staging", "production"] satisfies Environment[], + describe: "Deployment environment (staging or production)", + demandOption: true, + }) + .option("getGhaOutput", { + type: "boolean", + describe: + "Enable GitHub Actions output to receive latest built tag version", + default: false, + }) + .option("githubOutputFile", { + type: "string", + describe: + "Path to GITHUB_OUTPUT (this will be passed in automatically in CI)", + default: process.env.GITHUB_OUTPUT, + }) + .option("dockerFileName", { + type: "string", + default: "Dockerfile.web", + }) + .strict() + .parse(); async function main() { const DOCKER_HUB_PAT = process.env.DOCKER_HUB_PAT; @@ -29,6 +42,14 @@ async function main() { throw new Error("Missing DOCKER_HUB_PAT from platform-infra patchats.yaml"); } + const ghClient = await GitHubClient.createWithDefaultCiToken(); + await using dockerClient = await DockerClient.create( + "patinanetwork", + DOCKER_HUB_PAT, + ); + + const tagPrefix = environment === "staging" ? "staging-" : ""; + const timestamp = new Date() .toLocaleString("en-US", { timeZone: process.env.TZ, @@ -44,59 +65,19 @@ async function main() { const gitSha = (await $`git rev-parse --short HEAD`.text()).trim(); - const tags = [ - `patinanetwork/patchats:${tagPrefix}latest`, - `patinanetwork/patchats:${tagPrefix}${timestamp}`, - `patinanetwork/patchats:${tagPrefix}${gitSha}`, - ]; - - console.log("Building image with following tags:"); - tags.forEach((tag) => console.log(tag)); - - const proc = Bun.spawn( - ["docker", "login", "-u", "patinanetwork", "--password-stdin"], - { - stdin: new TextEncoder().encode(DOCKER_HUB_PAT), - stdout: "inherit", - stderr: "inherit", - }, - ); - - const exitCode = await proc.exited; - - if (exitCode !== 0) { - throw new Error("Docker login failed"); - } - try { - await $`docker buildx create --use --name patchats-builder`; - } catch { - await $`docker buildx use patchats-builder`; - } - - const viteStagingArg = - serverProfiles === "stg" ? ["--build-arg", "VITE_STAGING=true"] : []; - - const tagArgs = tags.flatMap((tag) => ["--tag", tag]); - - await $`docker buildx build --push \ - --platform linux/amd64 \ - --file infra/Dockerfile \ - --build-arg SERVER_PROFILES=${serverProfiles} \ - --build-arg COMMIT_SHA=${gitSha} \ - --cache-from=type=gha \ - --cache-to=type=gha,mode=max \ - ${viteStagingArg} \ - ${tagArgs} \ - .`; - - console.log("Image pushed successfully."); + await dockerClient.buildImage({ + dockerRepository: `patchats`, + dockerFileLocation: `infra/${dockerFileName}`, + tags: [`${tagPrefix}${timestamp}`, `${tagPrefix}${gitSha}`], + }); - if (githubOutputFile) { - console.log("Outputting image tag..."); - const w = Bun.file(githubOutputFile).writer(); - await w.write(`tag< Date: Sun, 26 Jul 2026 15:53:21 -0400 Subject: [PATCH 2/3] CI: simplified logic for run-local-db --- .github/scripts/src/db/run-local-db/index.ts | 165 ++----------------- 1 file changed, 17 insertions(+), 148 deletions(-) diff --git a/.github/scripts/src/db/run-local-db/index.ts b/.github/scripts/src/db/run-local-db/index.ts index 8cdd6105..c22b96da 100644 --- a/.github/scripts/src/db/run-local-db/index.ts +++ b/.github/scripts/src/db/run-local-db/index.ts @@ -1,152 +1,21 @@ +import { LocalPostgresClient } from "@tahminator/pipeline"; import { $ } from "bun"; -const CONTAINER_NAME = "patchats-db"; - -const DB_USER = "postgres"; -const DB_PASSWORD = "postgres"; -const DB_NAME = "patchats"; -const DB_PORT = "5440"; - -const MAX_ATTEMPTS = 30; -const RETRY_DELAY_MS = 2000; - -type DbEnv = { - DATABASE_HOST: string; - DATABASE_PORT: string; - DATABASE_NAME: string; - DATABASE_USER: string; - DATABASE_PASSWORD: string; -}; - -async function start(): Promise { - const env: DbEnv = { - DATABASE_HOST: "localhost", - DATABASE_PORT: DB_PORT, - DATABASE_NAME: DB_NAME, - DATABASE_USER: DB_USER, - DATABASE_PASSWORD: DB_PASSWORD, - }; - - try { - console.log("Starting Postgres container..."); - - // Remove a leftover container from a previous run. - await $`docker rm -f ${CONTAINER_NAME}`.quiet().nothrow(); - - await $`docker run -d \ - --name ${CONTAINER_NAME} \ - -e POSTGRES_USER=${DB_USER} \ - -e POSTGRES_PASSWORD=${DB_PASSWORD} \ - -e POSTGRES_DB=${DB_NAME} \ - -p ${DB_PORT}:5432 \ - mirror.gcr.io/library/postgres:16-alpine`; - - const ready = await waitForPostgres(); - - if (!ready) { - throw new Error("Postgres failed to start in time."); - } - - console.log("Postgres started. Running migrations..."); - - await $.env({ - ...process.env, - ...env, - })`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db`; - - console.log("Postgres is ready."); - - return env; - } catch (error) { - console.error("Failed to start Postgres:", error); - - await end(); - - throw error; - } -} - -async function waitForPostgres(): Promise { - console.log("Waiting for Postgres to become ready..."); - - for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { - const running = - await $`docker inspect -f "{{.State.Running}}" ${CONTAINER_NAME}` - .quiet() - .nothrow(); - - if (running.exitCode !== 0 || running.stdout.toString().trim() !== "true") { - console.error("Postgres container exited before becoming ready."); - return false; - } - - const check = - await $`docker exec ${CONTAINER_NAME} pg_isready -U ${DB_USER} -d ${DB_NAME}` - .quiet() - .nothrow(); - - if (check.exitCode === 0) { - console.log("Postgres is accepting connections."); - return true; - } - - console.log(`Waiting for Postgres... (${attempt}/${MAX_ATTEMPTS})`); - await Bun.sleep(RETRY_DELAY_MS); - } - - return false; -} - -async function end(): Promise { - console.log("Stopping and removing Postgres container..."); - - try { - await printLogs(); - } finally { - const stopResult = await $`docker stop ${CONTAINER_NAME}`.quiet().nothrow(); - - if (stopResult.exitCode !== 0) { - console.log("Postgres container was not running."); - } - - await $`docker rm -f ${CONTAINER_NAME}`.quiet().nothrow(); - - delete process.env.DATABASE_HOST; - delete process.env.DATABASE_PORT; - delete process.env.DATABASE_NAME; - delete process.env.DATABASE_USER; - delete process.env.DATABASE_PASSWORD; - } -} - -async function printLogs(): Promise { - console.log("=== DB LOGS ==="); - - const logs = await $`docker logs ${CONTAINER_NAME}`.quiet().nothrow(); - - if (logs.exitCode === 0) { - const stdout = logs.stdout.toString().trim(); - const stderr = logs.stderr.toString().trim(); - - if (stdout) { - console.log(stdout); - } - - if (stderr) { - console.error(stderr); - } - - if (!stdout && !stderr) { - console.log("DB logs are empty."); - } - } else { - console.log("No DB logs found."); - } - - console.log("=== DB LOGS END ==="); +async function main() { + await using pgClient = await LocalPostgresClient.create({ + database: "patchats", + }); + + const { database, host, port, password, user } = pgClient.state; + + await $.env({ + ...process.env, + DATABASE_HOST: host, + DATABASE_PORT: String(port), + DATABASE_NAME: database, + DATABASE_USER: user, + DATABASE_PASSWORD: password, + })`just migrate`; } -export const db = { - start, - end, -}; +await main(); From 2bc8a8d847c1884a5aecb0fa8d8b54bcdd11d69b Mon Sep 17 00:00:00 2001 From: Arshadul Monir Date: Sun, 26 Jul 2026 16:03:50 -0400 Subject: [PATCH 3/3] fixed dockerfile name --- .github/scripts/src/build-image/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/src/build-image/index.ts b/.github/scripts/src/build-image/index.ts index e14feb28..d6071f9f 100644 --- a/.github/scripts/src/build-image/index.ts +++ b/.github/scripts/src/build-image/index.ts @@ -30,7 +30,7 @@ const { environment, getGhaOutput, githubOutputFile, dockerFileName } = }) .option("dockerFileName", { type: "string", - default: "Dockerfile.web", + default: "Dockerfile", }) .strict() .parse();