Skip to content

Commit

Permalink
Add parity with pages-action for pages deploy outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
courtney-sims committed Oct 21, 2024
1 parent cd8a317 commit 1d56fe8
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-experts-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler-action": minor
---

Support id, environment, url, and alias outputs for Pages deploys.
15 changes: 13 additions & 2 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1"
"@actions/exec": "^1.1.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.8",
Expand All @@ -39,6 +40,7 @@
"@types/node": "^20.10.4",
"@vercel/ncc": "^0.38.1",
"prettier": "^3.1.0",
"mock-fs": "^5.4.0",
"semver": "^7.5.4",
"typescript": "^5.3.3",
"vitest": "^1.0.3"
Expand Down
41 changes: 30 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import {
debug,
getBooleanInput,
getInput,
getMultilineInput,
endGroup as originalEndGroup,
error as originalError,
info as originalInfo,
debug,
startGroup as originalStartGroup,
setFailed,
setOutput,
} from "@actions/core";
import { getExecOutput } from "@actions/exec";
import semverEq from "semver/functions/eq";
import { exec, execShell } from "./exec";
import { checkWorkingDirectory, semverCompare } from "./utils";
import { getPackageManager } from "./packageManagers";
import { checkWorkingDirectory, semverCompare } from "./utils";
import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager";

const DEFAULT_WRANGLER_VERSION = "3.78.10";
const DEFAULT_WRANGLER_VERSION = "3.81.0";

/**
* A configuration object that contains all the inputs & immutable state for the action.
Expand Down Expand Up @@ -313,6 +314,9 @@ async function wranglerCommands() {
let stdErr = "";

// Construct the options for the exec command
const wranglerOutputDir = "/opt/wranglerArtifacts";
process.env.WRANGLER_OUTPUT_FILE_DIRECTORY = wranglerOutputDir;

const options = {
cwd: config["workingDirectory"],
silent: config["QUIET_MODE"],
Expand All @@ -333,14 +337,9 @@ async function wranglerCommands() {
setOutput("command-output", stdOut);
setOutput("command-stderr", stdErr);

// Check if this command is a workers or pages deployment
if (
command.startsWith("deploy") ||
command.startsWith("publish") ||
command.startsWith("pages publish") ||
command.startsWith("pages deploy")
) {
// If this is a workers or pages deployment, try to extract the deployment URL
// Check if this command is a workers deployment
if (command.startsWith("deploy") || command.startsWith("publish")) {
// Try to extract the deployment URL
let deploymentUrl = "";
const deploymentUrlMatch = stdOut.match(/https?:\/\/[a-zA-Z0-9-./]+/);
if (deploymentUrlMatch && deploymentUrlMatch[0]) {
Expand All @@ -357,6 +356,26 @@ async function wranglerCommands() {
setOutput("deployment-alias-url", aliasUrl);
}
}
// Check if this command is a pages deployment
if (
command.startsWith("pages publish") ||
command.startsWith("pages deploy")
) {
const pagesArtifactFields =
await getDetailedPagesDeployOutput(wranglerOutputDir);

if (pagesArtifactFields) {
setOutput("id", pagesArtifactFields.deployment_id);
setOutput("url", pagesArtifactFields.url);
// To ensure parity with pages-action, display url for alias if there is no alias
setOutput("alias", pagesArtifactFields.alias);
setOutput("environment", pagesArtifactFields.environment);
} else {
info(
"No fields available for output. Have you updated wrangler to version >=3.81.0?",
);
}
}
}
} finally {
endGroup();
Expand Down
78 changes: 78 additions & 0 deletions src/wranglerArtifactManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, expect, it } from "vitest";
import {
getDetailedPagesDeployOutput,
getWranglerArtifacts,
} from "./wranglerArtifactManager";

describe("wranglerArtifactsManager", () => {
const mock = require("mock-fs");

describe("getWranglerArtifacts()", async () => {
it("Returns only wrangler output files from a given directory", async () => {
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
{"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"}
{"version": 1, "type":"pages-deploy-detailed", "environment":"production", "alias":"test.com", "deployment_id": "123", "url":"url.com"}`,
"not-wrangler-output.json": "test",
},
});

const artifacts = await getWranglerArtifacts("./testOutputDir");

expect(artifacts).toEqual([
"./testOutputDir/wrangler-output-2024-10-17_18-48-40_463-2e6e83.json",
]);
mock.restore();
});
});

describe("getDetailedPagesDeployOutput()", async () => {
it("Returns only detailed pages deploy output from wrangler artifacts", async () => {
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
{"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"}
{"version": 1, "type":"pages-deploy-detailed", "pages_project": "project", "environment":"production", "alias":"test.com", "deployment_id": "123", "url":"url.com"}`,
"not-wrangler-output.json": "test",
},
});

const artifacts = await getDetailedPagesDeployOutput("./testOutputDir");

expect(artifacts).toEqual({
version: 1,
pages_project: "project",
type: "pages-deploy-detailed",
url: "url.com",
environment: "production",
deployment_id: "123",
alias: "test.com",
});
mock.restore();
}),
it("Skips artifact entries that are not parseable", async () => {
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
this line is invalid json.
{"version": 1, "type":"pages-deploy-detailed", "pages_project": "project", "environment":"production", "alias":"test.com", "deployment_id": "123", "url":"url.com"}`,
"not-wrangler-output.json": "test",
},
});

const artifacts = await getDetailedPagesDeployOutput("./testOutputDir");

expect(artifacts).toEqual({
version: 1,
type: "pages-deploy-detailed",
pages_project: "project",
url: "url.com",
environment: "production",
deployment_id: "123",
alias: "test.com",
});
mock.restore();
});
});
});
80 changes: 80 additions & 0 deletions src/wranglerArtifactManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { open, readdir } from "fs/promises";
import { z } from "zod";

const OutputEntryBase = z.object({
version: z.literal(1),
type: z.string(),
});

const OutputEntryPagesDeployment = OutputEntryBase.merge(
z.object({
type: z.literal("pages-deploy-detailed"),
pages_project: z.string().nullable(),
deployment_id: z.string().nullable(),
url: z.string().optional(),
alias: z.string().optional(),
environment: z.enum(["production", "preview"]),
}),
);

type OutputEntryPagesDeployment = z.infer<typeof OutputEntryPagesDeployment>;

/**
* Parses file names in a directory to find wrangler artifact files
*
* @param artifactDirectory
* @returns All artifact files from the directory
*/
export async function getWranglerArtifacts(
artifactDirectory: string,
): Promise<string[]> {
// read files in asset directory
const dirent = await readdir(artifactDirectory, {
withFileTypes: true,
recursive: false,
});

// Match files to wrangler-output-<timestamp>-xxxxxx.json
const regex = new RegExp(
/^wrangler-output-[\d]{4}-[\d]{2}-[\d]{2}_[\d]{2}-[\d]{2}-[\d]{2}_[\d]{3}-[A-Fa-f0-9]{6}\.json$/,
);
const artifactFilePaths = dirent
.filter((d) => d.name.match(regex))
.map((d) => `${artifactDirectory}/${d.name}`);

return artifactFilePaths;
}

/**
* Searches for detailed wrangler output from a pages deploy
*
* @param artifactDirectory
* @returns The first pages-output-detailed found within a wrangler artifact directory
*/
export async function getDetailedPagesDeployOutput(
artifactDirectory: string,
): Promise<OutputEntryPagesDeployment | null> {
const artifactFilePaths = await getWranglerArtifacts(artifactDirectory);

for (let i = 0; i < artifactFilePaths.length; i++) {
const file = await open(artifactFilePaths[i]);

for await (const line of file.readLines()) {
try {
const output = JSON.parse(line);
const parsedOutput = OutputEntryPagesDeployment.parse(output);
if (parsedOutput.type === "pages-deploy-detailed") {
// Assume, in the context of the action, the first detailed deploy instance seen will suffice
return parsedOutput;
}
} catch (err) {
// If the line can't be parsed, skip it
continue;
}
}

await file.close();
}

return null;
}

0 comments on commit 1d56fe8

Please sign in to comment.