flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/web/app/lib/secrets.server.ts

93 lines3,821 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Secrets and variables: one list, rows per environment, for workflows and deployments1import { redirect } from "react-router";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs2
Secrets and variables: one list, rows per environment, for workflows and deployments3import type { Setting, SettingKind, SettingReader, SettingsOwner, User } from "@g1t/contracts";
4
Projects: what a workspace builds and runs, first on every page5import { actions, projects } from "./services.server";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs6
7export type SecretsData = {
Secrets and variables: one list, rows per environment, for workflows and deployments8 rows: Setting[];
Projects: what a workspace builds and runs, first on every page9 /** For a workspace: its projects' slugs, to link rows to. */
10 projects: string[];
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs11 error: string | null;
12};
13
Secrets and variables: one list, rows per environment, for workflows and deployments14/** A repository's or a workspace's secrets and variables, as one list. */
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs15export async function loadSecrets(owner: SettingsOwner, actor: User): Promise<SecretsData> {
Secrets and variables: one list, rows per environment, for workflows and deployments16 const [rows, list] = await Promise.all([
17 actions.settings(actor, owner, "all"),
Projects: what a workspace builds and runs, first on every page18 "workspace" in owner ? projects.list(owner.workspace, actor) : Promise.resolve(null),
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs19 ]);
20 return {
Secrets and variables: one list, rows per environment, for workflows and deployments21 rows: rows.ok ? rows.value : [],
Projects: what a workspace builds and runs, first on every page22 projects: list?.ok ? list.value.map((project) => project.slug) : [],
Secrets and variables: one list, rows per environment, for workflows and deployments23 error: rows.ok ? null : rows.error.message,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs24 };
25}
26
Secrets and variables: one list, rows per environment, for workflows and deployments27export type SecretsAction = { error?: string };
28
29/** `KEY=value` lines, as a .env file has them; quotes around a value are dropped. */
30function parseDotenv(text: string): [string, string][] {
31 const pairs: [string, string][] = [];
32 for (const raw of text.split(/\r?\n/)) {
33 const line = raw.replace(/^\s*export\s+/, "").trim();
34 if (!line || line.startsWith("#")) continue;
35 const at = line.indexOf("=");
36 if (at <= 0) continue;
37 let value = line.slice(at + 1).trim();
38 if (/^(["']).*\1$/.test(value)) value = value.slice(1, -1);
39 pairs.push([line.slice(0, at).trim(), value]);
40 }
41 return pairs;
42}
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs43
Secrets and variables: one list, rows per environment, for workflows and deployments44/**
45 * Saves the side panel's form (one row, or many pasted as a .env file), or
46 * removes a row, then returns to the list.
47 */
48export async function actOnSecrets(owner: SettingsOwner, actor: User, form: FormData, page: string): Promise<SecretsAction> {
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs49 const intent = String(form.get("intent") ?? "");
Secrets and variables: one list, rows per environment, for workflows and deployments50 const id = String(form.get("id") ?? "") || undefined;
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs51 if (intent === "delete") {
Secrets and variables: one list, rows per environment, for workflows and deployments52 const removed = await actions.deleteSetting(actor, owner, "all", String(form.get("name") ?? ""), id);
53 if (!removed.ok) return { error: removed.error.message };
54 throw redirect(page);
55 }
56 const kind: SettingKind = form.get("type") === "config" ? "variable" : "secret";
57 const environments =
58 form.get("scope") === "some"
59 ? [
60 ...form.getAll("env").map(String),
61 ...String(form.get("envCustom") ?? "")
62 .split(",")
63 .map((name) => name.trim())
64 .filter(Boolean),
65 ]
66 : [];
67 if (form.get("scope") === "some" && environments.length === 0) {
68 return { error: "Choose at least one environment, or All environments." };
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs69 }
Secrets and variables: one list, rows per environment, for workflows and deployments70 const availableTo = form.getAll("availableTo").map(String) as SettingReader[];
71 if (availableTo.length === 0) return { error: "Choose who reads it: Workflows, Deployments, or both." };
Projects: what a workspace builds and runs, first on every page72 const linked =
73 "workspace" in owner && form.get("reach") === "some" ? form.getAll("project").map(String) : [];
Secrets and variables: one list, rows per environment, for workflows and deployments74 const note = String(form.get("note") ?? "");
75 const key = String(form.get("key") ?? "").trim();
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs76 const value = String(form.get("value") ?? "");
Secrets and variables: one list, rows per environment, for workflows and deployments77 // A pasted .env file adds a row for each line.
78 const pasted = !id && key.includes("=") ? parseDotenv(key) : [];
79 const entries: [string, string | null][] = pasted.length > 0 ? pasted : [[key, value === "" && id ? null : value]];
80 for (const [name, entryValue] of entries) {
81 if (!name) return { error: "Give it a key." };
82 if (entryValue === "" && !id) return { error: `Give ${name} a value.` };
83 const saved = await actions.setSetting(actor, owner, kind, name, entryValue, {
84 id,
85 availableTo,
86 environments,
Projects: what a workspace builds and runs, first on every page87 projects: "workspace" in owner ? linked : undefined,
Secrets and variables: one list, rows per environment, for workflows and deployments88 note,
89 });
90 if (!saved.ok) return { error: pasted.length > 0 ? `${name}: ${saved.error.message}` : saved.error.message };
91 }
92 throw redirect(page);
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs93}