Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 251 additions & 82 deletions lib/distribute.js

Large diffs are not rendered by default.

323 changes: 243 additions & 80 deletions lib/download.js

Large diffs are not rendered by default.

323 changes: 243 additions & 80 deletions lib/go-publish.js

Large diffs are not rendered by default.

316 changes: 237 additions & 79 deletions lib/index.js

Large diffs are not rendered by default.

316 changes: 237 additions & 79 deletions lib/post.js

Large diffs are not rendered by default.

323 changes: 243 additions & 80 deletions lib/upload.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions src/distribute-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import * as core from "@actions/core";
import { HttpCodes, Headers, MediaTypes } from "@actions/http-client";
import { createHttpClient, truncate } from "./utils";
import { DistributeRequest, DistributeResponse } from "./types";
import {
DistributeRequest,
DistributeResponse,
DistributeSummaryEntry,
} from "./types";

const REQUEST_TIMEOUT_MS = 30000;

Expand Down Expand Up @@ -90,7 +94,7 @@ export async function distributeArtifact(
* stay consistent with the step log line (no drift between sources).
*/
export function buildDockerPullCommand(
response: DistributeResponse,
response: DistributeSummaryEntry,
): string | null {
if (response.package_type !== "docker") {
return null;
Expand Down
28 changes: 21 additions & 7 deletions src/distribute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ vi.mock("./utils", () => ({
}));

import * as core from "@actions/core";
import { runDistribute } from "./distribute";
import { runDistribute, toSummaryEntry } from "./distribute";
import {
ENV_FLY_URL_RUNTIME,
ENV_FLY_ACCESS_TOKEN_RUNTIME,
ENV_FLY_DISTRIBUTE_RESULTS,
} from "./constants";
import type { DistributeResponse } from "./types";
import type { DistributeResponse, DistributeSummaryEntry } from "./types";

const MOCK_RESPONSE: DistributeResponse = {
package_name: "my-app",
Expand Down Expand Up @@ -78,13 +78,15 @@ describe("runDistribute", () => {
await runDistribute();

expect(core.setFailed).not.toHaveBeenCalled();
// Step output keeps the full response (not subject to the env-var limit).
expect(core.setOutput).toHaveBeenCalledWith(
"results",
JSON.stringify([MOCK_RESPONSE]),
);
// Env var persists only the slim summary projection.
expect(core.exportVariable).toHaveBeenCalledWith(
ENV_FLY_DISTRIBUTE_RESULTS,
JSON.stringify([MOCK_RESPONSE]),
JSON.stringify([toSummaryEntry(MOCK_RESPONSE)]),
);
expect(mockDispose).toHaveBeenCalled();
});
Expand Down Expand Up @@ -168,6 +170,16 @@ describe("runDistribute", () => {
expect(core.info).toHaveBeenCalledWith(
" Pull: docker pull flyjfrog.jfrog.io/docker-public/myorg/my-image:1.0.0",
);
// Regression for issue #69: the unbounded `files[]` breakdown must NOT be
// persisted to the env var, otherwise FLY_DISTRIBUTE_RESULTS grows past the
// 128 KB single-env-var limit and breaks every later step.
const exported = vi
.mocked(core.exportVariable)
.mock.calls.find((c) => c[0] === ENV_FLY_DISTRIBUTE_RESULTS);
expect(exported).toBeDefined();
expect(exported![1]).not.toContain("files");
expect(exported![1]).not.toContain("manifest.json");
expect(exported![1]).toBe(JSON.stringify([toSummaryEntry(dockerResponse)]));
});

it("does not log a pull command for non-docker distributions", async () => {
Expand Down Expand Up @@ -217,7 +229,9 @@ describe("runDistribute", () => {
const firstExport = vi.mocked(core.exportVariable).mock
.calls[0] as unknown as [string, string];
expect(firstExport[0]).toBe(ENV_FLY_DISTRIBUTE_RESULTS);
expect(firstExport[1]).toBe(JSON.stringify([MOCK_RESPONSE]));
expect(firstExport[1]).toBe(
JSON.stringify([toSummaryEntry(MOCK_RESPONSE)]),
);
process.env[ENV_FLY_DISTRIBUTE_RESULTS] = firstExport[1];

// Second invocation — env var already has the first line; appendDistributeResults
Expand All @@ -227,15 +241,15 @@ describe("runDistribute", () => {
const secondExport = vi.mocked(core.exportVariable).mock
.calls[1] as unknown as [string, string];
expect(secondExport[0]).toBe(ENV_FLY_DISTRIBUTE_RESULTS);
const expected = `${JSON.stringify([MOCK_RESPONSE])}\n${JSON.stringify([response2])}`;
const expected = `${JSON.stringify([toSummaryEntry(MOCK_RESPONSE)])}\n${JSON.stringify([toSummaryEntry(response2)])}`;
expect(secondExport[1]).toBe(expected);

// Verify the post-step parser (same logic used by createJobSummary) handles
// the accumulated newline-separated JSON arrays.
const parsed: DistributeResponse[] = secondExport[1]
const parsed: DistributeSummaryEntry[] = secondExport[1]
.split("\n")
.filter((line) => line.trim().length > 0)
.flatMap((line) => JSON.parse(line) as DistributeResponse[]);
.flatMap((line) => JSON.parse(line) as DistributeSummaryEntry[]);
expect(parsed).toHaveLength(2);
expect(parsed[0].package_name).toBe("my-app");
expect(parsed[1].package_name).toBe("my-lib");
Expand Down
21 changes: 16 additions & 5 deletions src/distribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ENV_FLY_DISTRIBUTE_RESULTS,
} from "./constants";
import { getErrorMessage } from "./utils";
import { DistributeResponse } from "./types";
import { DistributeResponse, DistributeSummaryEntry } from "./types";

export async function runDistribute(): Promise<void> {
try {
Expand Down Expand Up @@ -49,13 +49,24 @@ export async function runDistribute(): Promise<void> {
}
}

/** Slim projection for the env var — see {@link DistributeSummaryEntry} / #69. */
export function toSummaryEntry(r: DistributeResponse): DistributeSummaryEntry {
return {
package_name: r.package_name,
package_version: r.package_version,
package_type: r.package_type,
public_url: r.public_url,
download_url: r.download_url,
};
}

/**
* Appends distribute results to the FLY_DISTRIBUTE_RESULTS env var as a
* JSON line. The post step reads all accumulated lines to render the
* distributed artifacts table in the job summary.
* Appends the slim distribute results to FLY_DISTRIBUTE_RESULTS as a JSON line;
* the post step reads all accumulated lines for the job-summary table. The full
* response is still exposed verbatim via the step `results` output.
*/
function appendDistributeResults(results: DistributeResponse[]): void {
const line = JSON.stringify(results);
const line = JSON.stringify(results.map(toSummaryEntry));
Comment thread
sverdlov93 marked this conversation as resolved.
const existing = process.env[ENV_FLY_DISTRIBUTE_RESULTS] || "";
const updated = existing ? `${existing}\n${line}` : line;
core.exportVariable(ENV_FLY_DISTRIBUTE_RESULTS, updated);
Expand Down
39 changes: 37 additions & 2 deletions src/job-summary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe("createJobSummary", () => {
version: "1.0.0",
results: [
{ name: "app.zip", status: "success" },
{ name: "app.tar.gz", status: "error", message: "checksum" },
{ name: "app.tar.gz", status: "error" },
],
};
process.env[ENV_FLY_TRANSFER_RESULTS] = JSON.stringify(entry);
Expand Down Expand Up @@ -237,6 +237,41 @@ describe("createJobSummary", () => {
expect(markdownContent).not.toContain("Distributed Artifacts");
});

// Issue #69: the env var now carries only the slim summary projection
// (no `download_count`, no `files[]`). Verify the post step still renders a
// complete table — including the docker pull command — from that shape alone.
it("renders the distributed table from the slim env projection (no download_count/files)", async () => {
const slim = [
{
package_name: "my-app",
package_version: "1.0.0",
package_type: "generic",
public_url:
"https://fly.example.com/public/generic/tenant/my-app/1.0.0",
download_url:
"https://fly.example.com/public/generic/tenant/my-app/1.0.0/my-app.tar.gz",
},
{
package_name: "myorg/my-image",
package_version: "2.0.0",
package_type: "docker",
public_url: "https://flyjfrog.jfrog.io/v2/docker-public/myorg/my-image",
download_url:
"https://flyjfrog.jfrog.io/v2/docker-public/myorg/my-image/manifests/2.0.0",
},
];
process.env[ENV_FLY_DISTRIBUTE_RESULTS] = JSON.stringify(slim);

await createJobSummary();

const markdownContent = mockSummary.addRaw.mock.calls[0][0] as string;
expect(markdownContent).toContain("### 🌐 Distributed Artifacts");
expect(markdownContent).toContain("my-app.tar.gz");
expect(markdownContent).toContain(
"docker pull flyjfrog.jfrog.io/docker-public/myorg/my-image:2.0.0",
);
});

it("should render all rows when env var contains multiple newline-separated JSON arrays (multi-step accumulation)", async () => {
const step1: DistributeResponse[] = [
{
Expand Down Expand Up @@ -370,7 +405,7 @@ describe("buildTransfersTable", () => {
version: "1.0",
results: [
{ name: "ok.zip", status: "success" },
{ name: "fail.zip", status: "error", message: "oops" },
{ name: "fail.zip", status: "error" },
{ name: "other.zip", status: "configured" },
],
},
Expand Down
12 changes: 7 additions & 5 deletions src/job-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as fs from "fs";
import * as path from "path";
import {
CollectedArtifact,
DistributeResponse,
DistributeSummaryEntry,
TransferSummaryEntry,
} from "./types";
import {
Expand Down Expand Up @@ -103,7 +103,9 @@ export function parseTransferResults(raw: string): TransferSummaryEntry[] {
.map((line) => JSON.parse(line) as TransferSummaryEntry);
}

export function buildDistributedTable(results: DistributeResponse[]): string {
export function buildDistributedTable(
results: DistributeSummaryEntry[],
): string {
if (results.length === 0) return "";

const header = "| Package | Version | Download URL |\n| --- | --- | --- |";
Expand All @@ -121,7 +123,7 @@ export function buildDistributedTable(results: DistributeResponse[]): string {
* OCI manifest JSON, which is useless to skim in a job summary — consumers
* actually need a paste-ready pull command.
*/
function renderDistributedDownloadCell(r: DistributeResponse): string {
function renderDistributedDownloadCell(r: DistributeSummaryEntry): string {
const pullCommand = buildDockerPullCommand(r);
if (pullCommand) {
return `\`${escPipe(pullCommand)}\``;
Expand Down Expand Up @@ -213,10 +215,10 @@ export async function createJobSummary(
try {
const distributedRaw = process.env[ENV_FLY_DISTRIBUTE_RESULTS] || "";
if (distributedRaw.trim()) {
const allResults: DistributeResponse[] = distributedRaw
const allResults: DistributeSummaryEntry[] = distributedRaw
.split("\n")
.filter((line) => line.trim().length > 0)
.flatMap((line) => JSON.parse(line) as DistributeResponse[]);
.flatMap((line) => JSON.parse(line) as DistributeSummaryEntry[]);
distributedTable = buildDistributedTable(allResults);
}
} catch (err) {
Expand Down
13 changes: 12 additions & 1 deletion src/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,25 @@ export function resolveVersion(
* Appends an upload/download result entry to the FLY_TRANSFER_RESULTS env var
* as a JSON line. The post step reads all accumulated lines to render the
* job summary. Each call adds one line for one sub-action invocation.
*
* Only the rendered fields (name + status) are persisted — see
* {@link TransferSummaryResult} / #69. `results[]` scales with file count, so
* dropping the unused `message` keeps the accumulated env var from drifting
* toward the 128 KB E2BIG wall. The full results (with `message`) are still
* exposed verbatim via the step `results` output.
*/
export function appendTransferResults(
type: "upload" | "download",
name: string,
version: string,
results: FlyClientResult[],
): void {
const entry: TransferSummaryEntry = { type, name, version, results };
const entry: TransferSummaryEntry = {
type,
name,
version,
results: results.map((r) => ({ name: r.name, status: r.status })),
};
const existing = process.env[ENV_FLY_TRANSFER_RESULTS] || "";
const line = JSON.stringify(entry);
const updated = existing ? `${existing}\n${line}` : line;
Expand Down
34 changes: 33 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ export interface DistributeResponse {
files?: DistributeResponseFile[];
}

/**
* Slim projection of {@link DistributeResponse} containing only the fields the
Comment thread
sverdlov93 marked this conversation as resolved.
* job-summary "Distributed Artifacts" table renders. This is what gets
* persisted to the FLY_DISTRIBUTE_RESULTS env var across distribute steps.
*
* The full response carries an unbounded per-file `files[]` breakdown (plus
* `download_count`s) that the summary never reads. Persisting the full object
* grows the env var with every distribute call and can push it past the Linux
* single-env-var limit (MAX_ARG_STRLEN, 128 KB), which makes the kernel reject
* `execve` for every later step and post-cleanup with E2BIG. Keeping only the
* rendered fields bounds the per-entry size. See issue #69.
*/
export interface DistributeSummaryEntry {
package_name: string;
package_version: string;
package_type: string;
public_url: string;
download_url: string;
}

/**
* JSON envelope written to stdout by every fly CLI command.
* Mirrors Go type: client/internal/output/response.go → Response
Expand All @@ -101,6 +121,18 @@ export interface FlyClientResult {
message?: string;
}

/**
* Slim per-file projection persisted in ENV_FLY_TRANSFER_RESULTS — only the
* fields the transfers table renders (name + status). `message` is dropped on
* purpose: the summary never shows it, and persisting it would grow the env var
* with every transferred file across steps, risking the same E2BIG failure as
* the distribute path (#69 / {@link DistributeSummaryEntry}).
*/
export interface TransferSummaryResult {
name: string;
status: FlyClientResult["status"];
}

/**
* One upload or download invocation's results, accumulated in
* ENV_FLY_TRANSFER_RESULTS as JSON-lines so the post step can
Expand All @@ -110,5 +142,5 @@ export interface TransferSummaryEntry {
type: "upload" | "download";
name: string;
version: string;
results: FlyClientResult[];
results: TransferSummaryResult[];
}
Loading