diff --git a/scripts/push-charts.test.ts b/scripts/push-charts.test.ts index faf0603..778e0d6 100644 --- a/scripts/push-charts.test.ts +++ b/scripts/push-charts.test.ts @@ -31,21 +31,26 @@ describe("push-charts", () => { describe("main", () => { test("should push single chart", async () => { fs.readdir.mockResolvedValue([myChartDirentMock]); - await main(["node", "push-chart.ts", "my-chart"]); - expect(child_process.exec).toHaveBeenCalledWith( + // mock `git tag` to return empty string + child_process.execSync.mockReturnValue(Buffer.from("")); + await main(["node", "push-chart.ts", "charts/my-chart"]); + expect(child_process.execSync).toHaveBeenNthCalledWith( + 2, `helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, ); }); test("should push multiple charts", async () => { fs.readdir.mockResolvedValue([myChartDirentMock, fooChartDirentMock]); - await main(["node", "push-chart.ts", "my-chart,foo"]); - expect(child_process.exec).toHaveBeenNthCalledWith( - 1, + child_process.execSync.mockReturnValue(Buffer.from("")); + + await main(["node", "push-chart.ts", "charts/my-chart,charts/foo"]); + expect(child_process.execSync).toHaveBeenNthCalledWith( + 2, `helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, ); - expect(child_process.exec).toHaveBeenNthCalledWith( - 2, + expect(child_process.execSync).toHaveBeenNthCalledWith( + 3, `helm push ".cr-release-packages/foo-2.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, ); }); @@ -54,24 +59,56 @@ describe("push-charts", () => { describe("getChangedCharts", () => { test("should return changed chart if only one exists", async () => { fs.readdir.mockResolvedValue([myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); await expect(getChangedChartsArchives(["my-chart"])).resolves.toEqual([ - ".cr-release-packages/my-chart-1.0.0.tgz", + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, ]); }); - test("should return changed chart if multiple exists", async () => { + test("should return multiple", async () => { fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); await expect(getChangedChartsArchives(["foo"])).resolves.toEqual([ - ".cr-release-packages/foo-2.0.0.tgz", + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, ]); }); test("should return multiple changed charts if multiple exists and changed", async () => { fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual( [ - ".cr-release-packages/my-chart-1.0.0.tgz", - ".cr-release-packages/foo-2.0.0.tgz", + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, + ], + ); + }); + + test("should skip already existing image", async () => { + fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("my-chart-1.0.0\n")); + await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual( + [ + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, ], ); }); diff --git a/scripts/push-charts.ts b/scripts/push-charts.ts index 4d5b238..5df72fe 100644 --- a/scripts/push-charts.ts +++ b/scripts/push-charts.ts @@ -1,73 +1,73 @@ import * as fs from "node:fs/promises"; import * as process from "node:process"; import * as path from "node:path"; -import { exec } from "node:child_process"; +import { execSync } from "node:child_process"; const CR_RELEASE_PACKAGE_PATH = ".cr-release-packages"; +interface Archive { + path: string; + fileName: string; +} + export async function main(rawArgs: string[]) { // remove file path node and script name const args = rawArgs.slice(2); const archives = await getChangedChartsArchives(args); - console.log("Pushing charts:"); + console.log("Pushing charts"); for (const archive of archives) { - console.log(`- ${archive}`); - exec( - `helm push "${archive}" "oci://ghcr.io/${process.env.GITHUB_REPOSITORY}"`, - ); + const cmd = `helm push "${archive.path}" "oci://ghcr.io/${process.env.GITHUB_REPOSITORY}"`; + console.log(cmd); + console.log(execSync(cmd).toString()); } } export async function getChangedChartsArchives( args: string[], -): Promise { - if (args.length === 0) { - console.log("Usage: push-charts "); - process.exit(1); - } - - const changedCharts = parseChartList(args[0]); +): Promise { const chartArchives = await getChartArchives(); + const tags = getGitTags(); // translate chart names to chart archives - const changedChartArchives: string[] = []; - for (const chart of changedCharts) { - if (chartArchives[chart]) { - const archive = chartArchives[chart]; - changedChartArchives.push(archive); + const changedChartArchives: Archive[] = []; + for (const [, value] of Object.entries(chartArchives)) { + if (tags.includes(value.fileName)) { + // the chart has already pushed + console.log( + `Skipping ${value.path}. Tag ${value.fileName} already exists`, + ); + continue; } + changedChartArchives.push(value); } return changedChartArchives; } -function parseChartList(chartListArg: string): string[] { - const parsed: string[] = []; - for (const chartPath of chartListArg.split(",")) { - const name = path.parse(chartPath).name; - parsed.push(name); - } - return parsed; -} - -async function getChartArchives(): Promise> { +async function getChartArchives(): Promise> { const archiveList = await fs.readdir(CR_RELEASE_PACKAGE_PATH, { withFileTypes: true, }); - const chartNames: Record = {}; + const chartNames: Record = {}; for (const dirent of archiveList) { if (dirent.isFile() && dirent.name.endsWith(".tgz")) { const parsed = /^(?[\w-]+?)-\d/.exec(dirent.name); // immich --> .cr-release-packages/immich-1.0.0.tgz - chartNames[parsed.groups.name] = path.join( - CR_RELEASE_PACKAGE_PATH, - dirent.name, - ); + chartNames[parsed.groups.name] = { + path: path.join(CR_RELEASE_PACKAGE_PATH, dirent.name), + fileName: path.parse(dirent.name).name, + }; } } return chartNames; } +export function getGitTags(): string[] { + const result = execSync("git tag"); + const tagString = result.toString(); + return tagString.split("\n"); +} + // do not run main if this script is being tested if (!process.env.VITEST_WORKER_ID) { main(process.argv);