Skip to content

Commit

Permalink
feat: support outputting ND-JSON files via an environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
petebacondarwin committed Jul 31, 2024
1 parent 135a0b4 commit 0163cb9
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-rats-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

feat: support outputting ND-JSON files via an environment variable
229 changes: 229 additions & 0 deletions packages/wrangler/src/__tests__/output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { clearOutputFilePath, writeOutput } from "../output";
import { runInTempDir } from "./helpers/run-in-tmp";
import type { OutputEntry } from "../output";

const originalProcessEnv = process.env;
const {
WRANGLER_OUTPUT_FILE_DIRECTORY: _,
WRANGLER_OUTPUT_FILE_PATH: __,
...processEnvNoVars
} = originalProcessEnv;

describe("writeOutput()", () => {
runInTempDir({ homedir: "home" });
afterEach(clearOutputFilePath);

it("should do nothing with no env vars set", () => {
try {
process.env = processEnvNoVars;
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});
// No files written
expect(readdirSync(".")).toEqual(["home"]);
} finally {
process.env = originalProcessEnv;
}
});

it("should write to the file given by WRANGLER_OUTPUT_FILE_PATH", () => {
try {
const WRANGLER_OUTPUT_FILE_PATH = "output.json";
process.env = { ...processEnvNoVars, WRANGLER_OUTPUT_FILE_PATH };
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});
const outputFile = readFileSync(WRANGLER_OUTPUT_FILE_PATH, "utf8");
expect(outputFile).toContainEntries([
{
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
},
]);
} finally {
process.env = originalProcessEnv;
}
});

it("should write to the file given by WRANGLER_OUTPUT_FILE_PATH, ignoring WRANGLER_OUTPUT_FILE_DIRECTORY", () => {
try {
const WRANGLER_OUTPUT_FILE_PATH = "output.json";
process.env = {
...processEnvNoVars,
WRANGLER_OUTPUT_FILE_PATH,
WRANGLER_OUTPUT_FILE_DIRECTORY: ".",
};
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});
const outputFile = readFileSync(WRANGLER_OUTPUT_FILE_PATH, "utf8");
expect(outputFile).toContainEntries([
{
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
},
]);
} finally {
process.env = originalProcessEnv;
}
});

it("should write multiple entries to the file given by WRANGLER_OUTPUT_FILE_PATH", () => {
try {
const WRANGLER_OUTPUT_FILE_PATH = "output.json";
process.env = { ...processEnvNoVars, WRANGLER_OUTPUT_FILE_PATH };
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});
writeOutput({
type: "deployment",
version: 1,
worker_id: "Worker",
deployment_id: "1234",
});

const outputFile = readFileSync(WRANGLER_OUTPUT_FILE_PATH, "utf8");
expect(outputFile).toContainEntries([
{
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
},
{
type: "deployment",
version: 1,
worker_id: "Worker",
deployment_id: "1234",
},
]);
} finally {
process.env = originalProcessEnv;
}
});

it("should write to a random file in WRANGLER_OUTPUT_FILE_DIRECTORY", () => {
try {
process.env = {
...processEnvNoVars,
WRANGLER_OUTPUT_FILE_DIRECTORY: "output",
};
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});

const outputFilePaths = readdirSync("output");
expect(outputFilePaths.length).toEqual(1);
expect(outputFilePaths[0]).toMatch(/wrangler-output-.+\.json/);
const outputFile = readFileSync(
join("output", outputFilePaths[0]),
"utf8"
);
expect(outputFile).toContainEntries([
{
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
},
]);
} finally {
process.env = originalProcessEnv;
}
});

it("should write multiple entries to the same random file in WRANGLER_OUTPUT_FILE_DIRECTORY", () => {
try {
process.env = {
...processEnvNoVars,
WRANGLER_OUTPUT_FILE_DIRECTORY: "output",
};
writeOutput({
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
});
writeOutput({
type: "deployment",
version: 1,
worker_id: "Worker",
deployment_id: "1234",
});

const outputFilePaths = readdirSync("output");
expect(outputFilePaths.length).toEqual(1);
expect(outputFilePaths[0]).toMatch(/wrangler-output-.+\.json/);
const outputFile = readFileSync(
join("output", outputFilePaths[0]),
"utf8"
);
expect(outputFile).toContainEntries([
{
type: "wrangler-session",
version: 1,
wrangler_version: "0.0.0.0",
command_line_args: "--help",
},
{
type: "deployment",
version: 1,
worker_id: "Worker",
deployment_id: "1234",
},
]);
} finally {
process.env = originalProcessEnv;
}
});
});

expect.extend({
toContainEntries(received: string, expected: OutputEntry[]) {
const outputEntries = received
.trim()
.split("\n")
.map((line) => JSON.parse(line))
.map(({ timestamp, ...entry }) => {
expect(typeof timestamp).toBe("string");
console.log(entry);
return entry;
});
return {
pass: this.equals(outputEntries, expected),
message: () => `Entries are${this.isNot ? " not " : ""} as expected.`,
received,
expected,
};
},
});

interface CustomMatchers {
toContainEntries: (expected: OutputEntry[]) => unknown;
}

declare module "vitest" {
interface Assertion extends CustomMatchers {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
}
14 changes: 7 additions & 7 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ Update them to point to this script instead?`;

export default async function deploy(
props: Props
): Promise<{ sourceMapSize?: number }> {
): Promise<{ sourceMapSize?: number; deploymentId: string | null }> {
// TODO: warn if git/hg has uncommitted changes
const { config, accountId, name } = props;
if (!props.dispatchNamespace && accountId && name) {
Expand All @@ -327,14 +327,14 @@ export default async function deploy(
`You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
);
if (!(await confirm("Would you like to continue?"))) {
return {};
return { deploymentId: null };
}
} else if (default_environment.script.last_deployed_from === "api") {
logger.warn(
`You are about to publish a Workers Service that was last updated via the script API.\nEdits that have been made via the script API will be overridden by your local code and config.`
);
if (!(await confirm("Would you like to continue?"))) {
return {};
return { deploymentId: null };
}
}
} catch (e) {
Expand Down Expand Up @@ -444,7 +444,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
const yes = await confirmLatestDeploymentOverwrite(accountId, scriptName);
if (!yes) {
cancel("Aborting deploy...");
return {};
return { deploymentId: null };
}
}

Expand Down Expand Up @@ -828,7 +828,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m

if (props.dryRun) {
logger.log(`--dry-run: exiting now.`);
return {};
return { deploymentId: null };
}
assert(accountId, "Missing accountId");

Expand All @@ -839,7 +839,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
// Early exit for WfP since it doesn't need the below code
if (props.dispatchNamespace !== undefined) {
deployWfpUserWorker(props.dispatchNamespace, deploymentId);
return {};
return { deploymentId };
}

// deploy triggers
Expand All @@ -850,7 +850,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m

logVersionIdChange();

return { sourceMapSize };
return { sourceMapSize, deploymentId };
}

function deployWfpUserWorker(
Expand Down
15 changes: 13 additions & 2 deletions packages/wrangler/src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "../index";
import { logger } from "../logger";
import * as metrics from "../metrics";
import { writeOutput } from "../output";
import { getLegacyAssetPaths, getSiteAssetPaths } from "../sites";
import { requireAuth } from "../user";
import { collectKeyValues } from "../utils/collectKeyValues";
Expand Down Expand Up @@ -350,10 +351,11 @@ export async function deployHandler(
}

const beforeUpload = Date.now();
const { sourceMapSize } = await deploy({
const name = getScriptName(args, config);
const { sourceMapSize, deploymentId } = await deploy({
config,
accountId,
name: getScriptName(args, config),
name,
rules: getRules(config),
entry,
env: args.env,
Expand Down Expand Up @@ -387,6 +389,15 @@ export async function deployHandler(
experimentalVersions: args.experimentalVersions,
});

if (deploymentId) {
writeOutput({
type: "deployment",
version: 1,
worker_id: name,
deployment_id: deploymentId,
});
}

await metrics.sendMetricsEvent(
"deploy worker script",
{
Expand Down
4 changes: 3 additions & 1 deletion packages/wrangler/src/environment-variables/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ type VariableNames =
| "WRANGLER_LOG_SANITIZE"
| "WRANGLER_REVOKE_URL"
| "WRANGLER_SEND_METRICS"
| "WRANGLER_TOKEN_URL";
| "WRANGLER_TOKEN_URL"
| "WRANGLER_OUTPUT_FILE_DIRECTORY"
| "WRANGLER_OUTPUT_FILE_PATH";

type DeprecatedNames =
| "CF_ACCOUNT_ID"
Expand Down
26 changes: 25 additions & 1 deletion packages/wrangler/src/environment-variables/misc-variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,34 @@ export const getCloudflareApiBaseUrl = getEnvironmentVariableFactory({
: "https://api.cloudflare.com/client/v4",
});

// Should we sanitize debug logs? By default we do, since debug logs could be added to GitHub issues and shouldn't include sensitive information
/**
* `WRANGLER_LOG_SANITIZE` specifies whether we sanitize debug logs.
*
* By default we do, since debug logs could be added to GitHub issues and shouldn't include sensitive information.
*/
export const getSanitizeLogs = getEnvironmentVariableFactory({
variableName: "WRANGLER_LOG_SANITIZE",
defaultValue() {
return "true";
},
});

/**
* `WRANGLER_OUTPUT_FILE_DIRECTORY` specifies a directory where we should write a file containing output data in ND-JSON format.
*
* If this is set a random file will be created in this directory, and certain Wrangler commands will write entries to this file.
* This is overridden by the `WRANGLER_OUTPUT_FILE_PATH` environment variable.
*/
export const getOutputFileDirectoryFromEnv = getEnvironmentVariableFactory({
variableName: "WRANGLER_OUTPUT_FILE_DIRECTORY",
});

/**
* `WRANGLER_OUTPUT_FILE_PATH` specifies a path to a file where we should write output data in ND-JSON format.
*
* If this is set certain Wrangler commands will write entries to this file.
* This overrides the `WRANGLER_OUTPUT_FILE_DIRECTORY` environment variable.
*/
export const getOutputFilePathFromEnv = getEnvironmentVariableFactory({
variableName: "WRANGLER_OUTPUT_FILE_PATH",
});
Loading

0 comments on commit 0163cb9

Please sign in to comment.