Skip to content

Commit

Permalink
Fix vercel redeploy (#4119)
Browse files Browse the repository at this point in the history
### What
* Closes #4091

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested [demo.rerun.io](https://demo.rerun.io/pr/4119) (if
applicable)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG

- [PR Build Summary](https://build.rerun.io/pr/4119)
- [Docs
preview](https://rerun.io/preview/2fb8dc91fad2ed71a93686e1e283031d224ef63e/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/2fb8dc91fad2ed71a93686e1e283031d224ef63e/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://ref.rerun.io/dev/bench/)
- [Wasm size tracking](https://ref.rerun.io/dev/sizes/)
  • Loading branch information
jprochazk authored Nov 1, 2023
1 parent 7bf3153 commit 18309aa
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 55 deletions.
54 changes: 32 additions & 22 deletions .github/actions/deploy-vercel/index.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

import { Client } from "./vercel.mjs";
import { assert, find, get, getRequiredInput } from "./util.mjs";
import { assert, getRequiredInput, info } from "./util.mjs";

// These inputs are defined in `action.yml`, and should be kept in sync
const token = getRequiredInput("vercel_token");
Expand All @@ -11,25 +11,35 @@ const releaseCommit = getRequiredInput("release_commit");

const client = new Client(token);

console.log(`Fetching team \`${teamName}\``);
const team = await client.teams().then(find("name", teamName));
assert(team, `failed to get team \`${teamName}\``);

console.log(`Fetching project \`${projectName}\``);
const project = await client.projects(team.id).then(find("name", projectName));
assert(project, `failed to get project \`${projectName}\``);

console.log(`Fetching latest deployment`);
const deployment = await client.deployments(team.id, project.id).then(get(0));
assert(deployment, `failed to get latest deployment`);

console.log(`Fetching \`RELEASE_COMMIT\` env var`);
const env = await client.envs(team.id, project.id).then(find("key", "RELEASE_COMMIT"));
assert(env, `failed to get \`RELEASE_COMMIT\` env var`);

console.log(`Setting \`RELEASE_COMMIT\` env to \`${releaseCommit}\``);
await client.setEnv(team.id, project.id, env.id, { key: "RELEASE_COMMIT", value: releaseCommit });

console.log(`Triggering redeploy`);
await client.redeploy(team.id, deployment.uid, "landing");
info`Fetching team "${teamName}"`;
const availableTeams = await client.teams();
assert(availableTeams, `failed to get team "${teamName}"`);
const team = availableTeams.find((team) => team.name === teamName);
assert(team, `failed to get team "${teamName}"`);

info`Fetching project "${projectName}"`;
const projectsInTeam = await client.projects(team.id);
const project = projectsInTeam.find((project) => project.name === projectName);
assert(project, `failed to get project "${projectName}"`);

info`Fetching latest production deployment`;
const productionDeployments = await client.deployments(team.id, project.id);
const latestProductionDeployment = productionDeployments[0];
assert(latestProductionDeployment, `failed to get latest production deployment`);

const RELEASE_COMMIT_KEY = "RELEASE_COMMIT";

info`Fetching "${RELEASE_COMMIT_KEY}" env var`;
const environment = await client.envs(team.id, project.id);
const releaseCommitEnv = environment.find((env) => env.key === RELEASE_COMMIT_KEY);
assert(releaseCommitEnv, `failed to get "${RELEASE_COMMIT_KEY}" env var`);

info`Setting "${RELEASE_COMMIT_KEY}" env to "${releaseCommit}"`;
await client.setEnv(team.id, project.id, releaseCommitEnv.id, {
key: RELEASE_COMMIT_KEY,
value: releaseCommit,
});

info`Triggering redeploy`;
await client.redeploy(team.id, latestProductionDeployment.uid, "landing");

48 changes: 48 additions & 0 deletions .github/actions/deploy-vercel/manual.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node

// Manually run the deployment:
//
// node manual.mjs \
// --token VERCEL_TOKEN \
// --team rerun \
// --project landing \
// --commit RELEASE_COMMIT
//

import { execSync } from "node:child_process";
import { parseArgs } from "node:util";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { assert } from "./util.mjs";

const dirname = path.dirname(fileURLToPath(import.meta.url));

/** @type {typeof execSync} */
const $ = (cmd, opts) => execSync(cmd, { stdio: "inherit", ...opts });

const { token, team, project, commit } = parseArgs({
options: {
token: { type: "string" },
team: { type: "string" },
project: { type: "string" },
commit: { type: "string" },
},
strict: true,
allowPositionals: false,
}).values;
assert(token, "missing `--token`");
assert(team, "missing `--team`");
assert(project, "missing `--project`");
assert(commit, "missing `--commit`");

$("node index.mjs", {
cwd: dirname,
env: {
...process.env,
INPUT_VERCEL_TOKEN: token,
INPUT_VERCEL_TEAM_NAME: team,
INPUT_VERCEL_PROJECT_NAME: project,
INPUT_RELEASE_COMMIT: commit,
},
});

55 changes: 27 additions & 28 deletions .github/actions/deploy-vercel/util.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
// @ts-check

/**
* Log a message with level `INFO`
*
* @param {TemplateStringsArray} strings
* @param {any[]} values
*/
export function info(strings, ...values) {
let out = "";
for (let i = 0; i < strings.length; i++) {
out += strings[i];
if (i < values.length) {
out += values[i].toString();
}
}
console.info(out);
}

/**
* Return a GitHub Actions input, returning `null` if it was not set.
*
* @param {string} name
* @returns {string | null}
*/
export function getInput(name) {
// @ts-expect-error: `process` is not defined without the right type definitions
return process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ?? null;
}

Expand All @@ -29,37 +45,20 @@ export function getRequiredInput(name) {
* Assert that `value` is truthy, throwing an error if it is not.
*
* @param {any} value
* @param {string} [message]
* @param {string | (() => string)} [message]
* @returns {asserts value}
*/
export function assert(value, message) {
if (!value) {
throw new Error(`assertion failed` + (message ? ` ${message}` : ""));
let error;
if (typeof message === "string") {
error = `assertion failed: ${message}`;
} else if (typeof message === "function") {
error = `assertion failed: ${message()}`;
} else {
error = `assertion failed`;
}
throw new Error(error);
}
}

/**
* Returns a function that attempts to find an object with
* `key` set to `value` in an array of objects with `key` properties.
*
* @template {string} Key
* @template {{ [p in Key]: string }} T
* @param {Key} key
* @param {string} value
* @returns {(a: T[]) => T|null}
*/
export function find(key, value) {
return (a) => a.find((v) => v[key] === value) ?? null;
}

/**
* Returns a function that attempts to retrieve the value at `index` from an array.
*
* @template T
* @param {number} index
* @returns {(a: T[]) => T|null}
*/
export function get(index) {
return (a) => a[index] ?? null;
}

28 changes: 23 additions & 5 deletions .github/actions/deploy-vercel/vercel.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-check
import { assert } from "./util.mjs";

/**
* @typedef {Record<string, string>} Params
Expand Down Expand Up @@ -104,7 +105,9 @@ export class Client {
* @returns {Promise<Team[]>}
*/
async teams() {
return await this.get("v2/teams").then((r) => r.teams);
const response = await this.get("v2/teams");
assert("teams" in response, () => `failed to get teams: ${JSON.stringify(response)}`);
return response.teams;
}

/**
Expand All @@ -115,7 +118,9 @@ export class Client {
* @returns {Promise<Project[]>}
*/
async projects(teamId) {
return await this.get("v9/projects", { teamId }).then((r) => r.projects);
const response = await this.get("v9/projects", { teamId });
assert("projects" in response, () => `failed to get projects: ${JSON.stringify(response)}`);
return response.projects;
}

/**
Expand All @@ -133,9 +138,17 @@ export class Client {
* @returns {Promise<Deployment[]>}
*/
async deployments(teamId, projectId, target = "production") {
return await this.get("v6/deployments", { teamId, projectId, target, sort: "created" }).then(
(r) => r.deployments
const response = await this.get("v6/deployments", {
teamId,
projectId,
target,
sort: "created",
});
assert(
"deployments" in response,
() => `failed to get deployments: ${JSON.stringify(response)}`
);
return response.deployments;
}

/**
Expand All @@ -146,7 +159,12 @@ export class Client {
* @returns {Promise<Env[]>}
*/
async envs(teamId, projectId) {
return await this.get(`v9/projects/${projectId}/env`, { teamId }).then((r) => r.envs);
const response = await this.get(`v9/projects/${projectId}/env`, { teamId });
assert(
"envs" in response,
() => `failed to get environment variables: ${JSON.stringify(response)}`
);
return response.envs;
}

/**
Expand Down

0 comments on commit 18309aa

Please sign in to comment.