@@ -275,6 +275,8 @@ function diff(org, desired, live, opts = {}) {
275275 const entries = [];
276276 diffSettings(desired.settings, live.settings, entries);
277277 diffRulesets("", "org-ruleset", desired.rulesets, live.rulesets ?? [], opts, entries);
278+ diffSecrets("", "org-secret", desired.secrets, live.secrets ?? [], opts, entries);
279+ diffVariables("", "org-variable", desired.variables, live.variables ?? [], opts, entries);
278280 diffTeams(desired.teams, live.teams ?? {}, opts, entries);
279281 diffMembers(desired.members, live.members ?? [], opts, entries);
280282 diffRepos(desired.repos, live.repos ?? {}, opts, entries);
@@ -489,6 +491,8 @@ function diffRepos(desired, live, opts, out) {
489491 diffRulesets(`${name}/`, "repo-ruleset", dr.rulesets, lr.rulesets ?? [], opts, out);
490492 diffRepoSecurity(name, dr.security, lr.security, out);
491493 diffEnvironments(name, dr.environments, lr.environments ?? [], opts, out);
494+ diffSecrets(`${name}/`, "repo-secret", dr.secrets, lr.secrets ?? [], opts, out);
495+ diffVariables(`${name}/`, "repo-variable", dr.variables, lr.variables ?? [], opts, out);
492496 }
493497 for (const name of Object.keys(live)) {
494498 if (!Object.prototype.hasOwnProperty.call(desired, name)) {
@@ -633,6 +637,55 @@ function diffEnvironments(repoName, desired, live, opts, out) {
633637 }
634638 }
635639}
640+ function diffSecrets(keyPrefix, resourceType, desired, live, opts, out) {
641+ if (desired === void 0) return;
642+ const desiredByName = new Map(desired.map((s) => [s.name, s]));
643+ const liveByName = new Map(live.map((s) => [s.name, s]));
644+ for (const [name, ds] of desiredByName) {
645+ if (!liveByName.has(name)) {
646+ out.push({ kind: "create", resourceType, key: `${keyPrefix}${name}`, after: ds });
647+ }
648+ }
649+ for (const [name, ls] of liveByName) {
650+ if (!desiredByName.has(name)) {
651+ const key = `${keyPrefix}${name}`;
652+ if (opts.isOwned?.(resourceType, key)) {
653+ out.push({ kind: "delete", resourceType, key, before: ls });
654+ }
655+ }
656+ }
657+ }
658+ function diffVariables(keyPrefix, resourceType, desired, live, opts, out) {
659+ if (desired === void 0) return;
660+ const desiredByName = new Map(desired.map((v) => [v.name, v]));
661+ const liveByName = new Map(live.map((v) => [v.name, v]));
662+ for (const [name, dv] of desiredByName) {
663+ const lv = liveByName.get(name);
664+ const key = `${keyPrefix}${name}`;
665+ if (!lv) {
666+ out.push({ kind: "create", resourceType, key, after: dv });
667+ continue;
668+ }
669+ if (dv.value !== void 0 && dv.value !== lv.value) {
670+ out.push({
671+ kind: "update",
672+ resourceType,
673+ key,
674+ before: lv,
675+ after: dv,
676+ fields: [{ field: "value", before: lv.value, after: dv.value }]
677+ });
678+ }
679+ }
680+ for (const [name, lv] of liveByName) {
681+ if (!desiredByName.has(name)) {
682+ const key = `${keyPrefix}${name}`;
683+ if (opts.isOwned?.(resourceType, key)) {
684+ out.push({ kind: "delete", resourceType, key, before: lv });
685+ }
686+ }
687+ }
688+ }
636689function diffObject(desired, live) {
637690 const fields = [];
638691 for (const key of Object.keys(desired)) {
@@ -703,6 +756,8 @@ var init_diff = __esm({
703756 RESOURCE_TYPE_ORDER = [
704757 "org-settings",
705758 "org-ruleset",
759+ "org-secret",
760+ "org-variable",
706761 "team",
707762 "team-member",
708763 "team-repo",
@@ -711,7 +766,9 @@ var init_diff = __esm({
711766 "repo-security",
712767 "environment",
713768 "branch-protection",
714- "repo-ruleset"
769+ "repo-ruleset",
770+ "repo-secret",
771+ "repo-variable"
715772 ];
716773 RULESET_FIELDS = ["target", "enforcement", "bypassActors", "conditions", "rules"];
717774 ENVIRONMENT_FIELDS = [
@@ -230240,6 +230297,172 @@ var environmentsCycle = {
230240230297 }
230241230298};
230242230299
230300+ // src/cycles/secrets-variables.ts
230301+ var PER_PAGE4 = 100;
230302+ async function listWrapped(client, makePath, field, budget) {
230303+ const out = [];
230304+ let page = 1;
230305+ for (; ; ) {
230306+ if (budget.exhausted) break;
230307+ budget.use(1);
230308+ const data = await client.request("GET", makePath(page));
230309+ const batch = data?.[field] ?? [];
230310+ if (!Array.isArray(batch) || batch.length === 0) break;
230311+ out.push(...batch);
230312+ if (batch.length < PER_PAGE4) break;
230313+ page++;
230314+ }
230315+ return out;
230316+ }
230317+ async function fetchSecrets(client, basePath, budget) {
230318+ try {
230319+ const raw = await listWrapped(
230320+ client,
230321+ (page) => `${basePath}?per_page=${PER_PAGE4}&page=${page}`,
230322+ "secrets",
230323+ budget
230324+ );
230325+ return raw.filter((s) => typeof s.name === "string").map((s) => ({ name: s.name }));
230326+ } catch (err) {
230327+ if (err instanceof Error && (err.message.includes("404") || err.message.includes("403"))) return [];
230328+ throw err;
230329+ }
230330+ }
230331+ async function fetchVariables(client, basePath, budget) {
230332+ try {
230333+ const raw = await listWrapped(
230334+ client,
230335+ (page) => `${basePath}?per_page=${PER_PAGE4}&page=${page}`,
230336+ "variables",
230337+ budget
230338+ );
230339+ return raw.filter((v) => typeof v.name === "string").map((v) => ({ name: v.name, value: v.value }));
230340+ } catch (err) {
230341+ if (err instanceof Error && (err.message.includes("404") || err.message.includes("403"))) return [];
230342+ throw err;
230343+ }
230344+ }
230345+ function splitRepoKey(key, resourceType) {
230346+ const idx = key.indexOf("/");
230347+ if (idx === -1) {
230348+ throw new Error(`secrets-variables: malformed ${resourceType} key "${key}" \u2014 expected "<repo>/<name>"`);
230349+ }
230350+ return [key.slice(0, idx), key.slice(idx + 1)];
230351+ }
230352+ async function applySecret(client, entry, org, budget) {
230353+ const isRepo = entry.resourceType === "repo-secret";
230354+ let basePath;
230355+ let name;
230356+ if (isRepo) {
230357+ const [repo, secretName] = splitRepoKey(entry.key, "repo-secret");
230358+ basePath = `/repos/${org}/${repo}/actions/secrets`;
230359+ name = secretName;
230360+ } else {
230361+ basePath = `/orgs/${org}/actions/secrets`;
230362+ name = entry.key;
230363+ }
230364+ if (entry.kind === "delete") {
230365+ budget.use(1);
230366+ await client.request("DELETE", `${basePath}/${encodeURIComponent(name)}`);
230367+ return;
230368+ }
230369+ throw new Error(
230370+ `secret "${name}" is declared but absent \u2014 provision its value out-of-band; warden never reads or writes secret values`
230371+ );
230372+ }
230373+ async function applyVariable(client, entry, org, budget) {
230374+ const isRepo = entry.resourceType === "repo-variable";
230375+ let basePath;
230376+ let name;
230377+ if (isRepo) {
230378+ const [repo, varName] = splitRepoKey(entry.key, "repo-variable");
230379+ basePath = `/repos/${org}/${repo}/actions/variables`;
230380+ name = varName;
230381+ } else {
230382+ basePath = `/orgs/${org}/actions/variables`;
230383+ name = entry.key;
230384+ }
230385+ if (entry.kind === "delete") {
230386+ budget.use(1);
230387+ await client.request("DELETE", `${basePath}/${encodeURIComponent(name)}`);
230388+ return;
230389+ }
230390+ const desired = entry.after;
230391+ if (entry.kind === "create") {
230392+ if (desired.value === void 0) {
230393+ throw new Error(`variable "${name}" is declared without a value \u2014 set a value to create it`);
230394+ }
230395+ const body = { name, value: desired.value };
230396+ if (!isRepo) body.visibility = desired.visibility ?? "all";
230397+ budget.use(1);
230398+ await client.request("POST", basePath, body);
230399+ return;
230400+ }
230401+ if (desired.value === void 0) return;
230402+ budget.use(1);
230403+ await client.request("PATCH", `${basePath}/${encodeURIComponent(name)}`, { value: desired.value });
230404+ }
230405+ var secretsVariablesCycle = {
230406+ name: "secrets-variables",
230407+ // ── Part 2: fetchLive ──────────────────────────────────────────────────────
230408+ async fetchLive(client, orgLogin, scope, budget) {
230409+ if (budget.exhausted) {
230410+ const { BudgetExhaustedError: BudgetExhaustedError2 } = await Promise.resolve().then(() => (init_runner(), runner_exports));
230411+ throw new BudgetExhaustedError2();
230412+ }
230413+ const secrets = await fetchSecrets(client, `/orgs/${orgLogin}/actions/secrets`, budget);
230414+ const variables = budget.exhausted ? [] : await fetchVariables(client, `/orgs/${orgLogin}/actions/variables`, budget);
230415+ const repos = {};
230416+ for (const [name, repoConfig] of Object.entries(scope?.repos ?? {})) {
230417+ const wantsSecrets = repoConfig.secrets !== void 0;
230418+ const wantsVariables = repoConfig.variables !== void 0;
230419+ if (!wantsSecrets && !wantsVariables) continue;
230420+ if (budget.exhausted) break;
230421+ const repoLive = {};
230422+ if (wantsSecrets) {
230423+ repoLive.secrets = await fetchSecrets(client, `/repos/${orgLogin}/${name}/actions/secrets`, budget);
230424+ }
230425+ if (wantsVariables && !budget.exhausted) {
230426+ repoLive.variables = await fetchVariables(client, `/repos/${orgLogin}/${name}/actions/variables`, budget);
230427+ }
230428+ repos[name] = repoLive;
230429+ }
230430+ return { secrets, variables, repos };
230431+ },
230432+ // ── Part 3: buildDesired ───────────────────────────────────────────────────
230433+ buildDesired(orgConfig, _orgLogin, _scope) {
230434+ const out = {};
230435+ if (orgConfig.secrets) out.secrets = orgConfig.secrets;
230436+ if (orgConfig.variables) out.variables = orgConfig.variables;
230437+ if (orgConfig.repos) {
230438+ const repos = {};
230439+ for (const [name, repoConfig] of Object.entries(orgConfig.repos)) {
230440+ const stripped = {};
230441+ if (repoConfig.secrets !== void 0) stripped.secrets = repoConfig.secrets;
230442+ if (repoConfig.variables !== void 0) stripped.variables = repoConfig.variables;
230443+ if (stripped.secrets !== void 0 || stripped.variables !== void 0) {
230444+ repos[name] = stripped;
230445+ }
230446+ }
230447+ out.repos = repos;
230448+ }
230449+ return out;
230450+ },
230451+ // ── Part 4: apply ──────────────────────────────────────────────────────────
230452+ async apply(client, entry, orgLogin, _scope, budget) {
230453+ switch (entry.resourceType) {
230454+ case "org-secret":
230455+ case "repo-secret":
230456+ return applySecret(client, entry, orgLogin, budget);
230457+ case "org-variable":
230458+ case "repo-variable":
230459+ return applyVariable(client, entry, orgLogin, budget);
230460+ default:
230461+ return;
230462+ }
230463+ }
230464+ };
230465+
230243230466// src/cli/registry.ts
230244230467var CYCLE_REGISTRY = {
230245230468 [branchProtectionCycle.name]: branchProtectionCycle,
@@ -230249,7 +230472,8 @@ var CYCLE_REGISTRY = {
230249230472 [teamsCycle.name]: teamsCycle,
230250230473 [rulesetsCycle.name]: rulesetsCycle,
230251230474 [securityFeaturesCycle.name]: securityFeaturesCycle,
230252- [environmentsCycle.name]: environmentsCycle
230475+ [environmentsCycle.name]: environmentsCycle,
230476+ [secretsVariablesCycle.name]: secretsVariablesCycle
230253230477};
230254230478
230255230479// node_modules/@intentius/chant/src/audit/fetch.ts
0 commit comments