/** * Per-repository secrets, read from the file `PDSJS_CI_SECRETS` names. * * A step sees a secret only when both sides name it: the file grants it to * the repository, and the workflow declares it in `secrets`. The names are * public in the workflow file; the values exist only on the runner host. */ /** Where the file format is described, for error messages. */ const FILE_LABEL = 'secrets file'; /** * A grant maps `/` to the secrets that repository may * request. The authority resolves to a DID the same way a watch spec does, * and the name to the record key a run names the repository by. * @typedef {Record>} SecretsGrants */ /** * What one run receives: the secrets this repository is granted and its * workflow declares. * @typedef {(did: string, repoRkey: string, names: string[]) => Record} SecretsResolver */ /** * @param {string} source - file contents * @returns {SecretsGrants} */ export function parseSecretsFile(source) { /** @type {unknown} */ let parsed; try { parsed = JSON.parse(source); } catch (err) { throw new Error( `${FILE_LABEL}: invalid JSON (${err instanceof Error ? err.message : err})`, ); } if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(`${FILE_LABEL}: must be a JSON object keyed by repository`); } /** @type {SecretsGrants} */ const grants = {}; for (const [repo, entry] of Object.entries(parsed)) { if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { throw new Error(`${FILE_LABEL}: ${repo} must be an object of secrets`); } /** @type {Record} */ const values = {}; for (const [name, value] of Object.entries(entry)) { if (typeof value !== 'string') { throw new Error(`${FILE_LABEL}: ${repo}.${name} must be a string`); } values[name] = value; } grants[repo] = values; } return grants; } /** * The intersection of what the repository is granted and what the workflow * declares. A declared name with no grant is absent, and the workflow's own * guard reports it; the run does not fail here. * @param {Record|undefined} granted - this repository's grants * @param {string[]} names - what the workflow declares * @returns {Record} */ export function grantedSecrets(granted, names) { /** @type {Record} */ const env = {}; if (!granted) return env; for (const name of names) { if (name in granted) env[name] = granted[name]; } return env; }