import { isBuiltinAction } from "./builtin-actions";
import { GITHUB_COM_TOKEN_SECRET } from "./actions/compile-action";
import { loadSecretRows } from "./config-values";
import { parseWorkflow, WORKFLOW_DIR } from "./spec";
import type { WorkflowSpec } from "./spec";
import { listRefs, resolveRevision } from "../git/refs";
import { withStore } from "../git/store";
import type { RepoRow } from "~/lib/types";
const TOKEN_EXPRESSION = /\$\{\{\s*(?:github\.token|secrets\.GITHUB_TOKEN)\s*\}\}/;
const MAX_LISTED = 10;
/** Actions whose `with:` passes `github.token` / `secrets.GITHUB_TOKEN`, which gitfor maps to GITHUB_COM_TOKEN. */
export function actionsWantingGithubToken(spec: WorkflowSpec): string[] {
const uses = new Set<string>();
for (const job of Object.values(spec.jobs)) {
for (const step of job.steps) {
if (step.uses === undefined || isBuiltinAction(step)) continue;
if (Object.values(step.with ?? {}).some((value) => TOKEN_EXPRESSION.test(String(value)))) {
uses.add(step.uses.trim());
}
}
}
return [...uses];
}
export type GithubTokenNotice = { secret: string; actions: string[]; workflows: string[] };
/**
* Workflows on the default branch whose actions expect a github.com token, when neither the repository nor its
* owner has a GITHUB_COM_TOKEN secret: those actions run anonymously against api.github.com and hit rate limits
* on shared runner IPs, which otherwise only shows up as a failed step at runtime.
*/
export async function githubTokenNotice(repo: RepoRow): Promise<GithubTokenNotice | null> {
const revision = resolveRevision(await listRefs(repo), repo.default_branch);
if (!revision) return null;
const found = await withStore(repo.artifacts_name, async (store) => {
const commit = await store.commit(revision.sha);
const directory = commit ? await store.entryAtPath(commit.treeSha, WORKFLOW_DIR) : null;
if (!directory || directory.type !== "tree") return { actions: [], workflows: [] };