workspace-conformance 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/cli.js +219 -8
- package/dist/index.cjs +221 -8
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +220 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -100,6 +100,23 @@ function expandTemplate(template, values) {
|
|
|
100
100
|
return value;
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
const NAME_PLACEHOLDER = "{name}";
|
|
104
|
+
/**
|
|
105
|
+
* What a name template must be, as a configuration error says it. It does not quote the template, as no configuration error quotes a value.
|
|
106
|
+
*/
|
|
107
|
+
const NAME_TEMPLATE_REQUIREMENT = "needs {name} exactly once, other text beside it and no other placeholder";
|
|
108
|
+
const ANY_PLACEHOLDER = /\{[a-z]+\}/u;
|
|
109
|
+
/**
|
|
110
|
+
* `template` split at `{name}`, or `undefined` unless it holds `{name}` exactly once, other text beside it and no other placeholder. Without other text every name would match, and with `{name}` twice a name could be split more than one way.
|
|
111
|
+
*/
|
|
112
|
+
function parseNameTemplate(template) {
|
|
113
|
+
const [before, after, ...rest] = template.split(NAME_PLACEHOLDER);
|
|
114
|
+
if (before === void 0 || after === void 0 || rest.length > 0 || before + after === "" || ANY_PLACEHOLDER.test(before + after)) return;
|
|
115
|
+
return {
|
|
116
|
+
before,
|
|
117
|
+
after
|
|
118
|
+
};
|
|
119
|
+
}
|
|
103
120
|
//#endregion
|
|
104
121
|
//#region src/ts-project.ts
|
|
105
122
|
/**
|
|
@@ -213,6 +230,97 @@ const aggregateMappers = async ({ cwd, options }) => {
|
|
|
213
230
|
return violations.sort((a, b) => a.file.localeCompare(b.file) || (a.location?.line ?? 0) - (b.location?.line ?? 0) || a.message.localeCompare(b.message));
|
|
214
231
|
};
|
|
215
232
|
//#endregion
|
|
233
|
+
//#region src/checks/codec-pairs.ts
|
|
234
|
+
function collect(container, prefix, found, entered) {
|
|
235
|
+
for (const [name, declarations] of container.getExportedDeclarations()) {
|
|
236
|
+
const value = declarations.find((declaration) => !ts_morph.Node.isInterfaceDeclaration(declaration) && !ts_morph.Node.isTypeAliasDeclaration(declaration) && !ts_morph.Node.isModuleDeclaration(declaration) && !ts_morph.Node.isSourceFile(declaration));
|
|
237
|
+
if (value !== void 0) found.set(`${prefix}${name}`, value);
|
|
238
|
+
for (const member of declarations) if (ts_morph.Node.isModuleDeclaration(member)) collect(member, `${prefix}${name}.`, found, entered);
|
|
239
|
+
else if (ts_morph.Node.isSourceFile(member) && !entered.has(member)) collect(member, `${prefix}${name}.`, found, /* @__PURE__ */ new Set([...entered, member]));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The values `file` exports, by name, wherever they are declared: a re-export counts. Those inside an exported namespace, or in a module re-exported as a namespace (`export * as ns from './x'`), are included under their qualified name (`Namespace.value`). Interfaces and type aliases are types, not codecs, and a namespace is where codecs live rather than one itself, so all three are left out.
|
|
244
|
+
*/
|
|
245
|
+
function exportedValues(file) {
|
|
246
|
+
const found = /* @__PURE__ */ new Map();
|
|
247
|
+
collect(file, "", found, /* @__PURE__ */ new Set([file]));
|
|
248
|
+
return found;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Where the declaration's name is written, or where the declaration starts when it has no name node.
|
|
252
|
+
*/
|
|
253
|
+
function valueLocation(declaration) {
|
|
254
|
+
const nameNode = ts_morph.Node.hasName(declaration) ? declaration.getNameNode() : void 0;
|
|
255
|
+
const { line, column } = declaration.getSourceFile().getLineAndColumnAtPos((nameNode ?? declaration).getStart());
|
|
256
|
+
return {
|
|
257
|
+
line,
|
|
258
|
+
column
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The template of an option, which the configuration has already validated; a template that does not parse is a configuration error naming the option.
|
|
263
|
+
*/
|
|
264
|
+
function templateOf(options, option) {
|
|
265
|
+
const template = parseNameTemplate(options[option]);
|
|
266
|
+
if (template === void 0) throw new ConformanceError(`checks.codec-pairs.${option}: ${NAME_TEMPLATE_REQUIREMENT}`);
|
|
267
|
+
return template;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The shared name a qualified export name gives under `template`, or `undefined` when its last segment does not fit the template. The namespace part is kept apart so a counterpart is looked for in the same namespace.
|
|
271
|
+
*/
|
|
272
|
+
function sharedName(qualified, template) {
|
|
273
|
+
const namespace = qualified.slice(0, qualified.lastIndexOf(".") + 1);
|
|
274
|
+
const local = qualified.slice(namespace.length);
|
|
275
|
+
if (local.length <= template.before.length + template.after.length || !local.startsWith(template.before) || !local.endsWith(template.after)) return;
|
|
276
|
+
return {
|
|
277
|
+
namespace,
|
|
278
|
+
name: local.slice(template.before.length, local.length - template.after.length)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Every encoder a codec file exports has the decoder its name implies, exported by the same file, and every decoder the encoder. A name is an encoder or a decoder when it fits that template; other exports are not codecs and are not judged. A half re-exported from another file counts, and is reported where it is declared.
|
|
283
|
+
*
|
|
284
|
+
* It checks that the counterpart is exported, not that the two round-trip: that is a property test of the repository's own.
|
|
285
|
+
*/
|
|
286
|
+
const codecPairs = async ({ cwd, options }) => {
|
|
287
|
+
const { project, files } = await createProject(cwd, options.tsConfig, options.codecs, {
|
|
288
|
+
check: "codec-pairs",
|
|
289
|
+
files: "codecs"
|
|
290
|
+
});
|
|
291
|
+
const excluded = new Set(options.exclude);
|
|
292
|
+
const encoder = templateOf(options, "encoder");
|
|
293
|
+
const decoder = templateOf(options, "decoder");
|
|
294
|
+
const halves = [{
|
|
295
|
+
template: encoder,
|
|
296
|
+
counterpart: decoder,
|
|
297
|
+
missing: "decoder"
|
|
298
|
+
}, {
|
|
299
|
+
template: decoder,
|
|
300
|
+
counterpart: encoder,
|
|
301
|
+
missing: "encoder"
|
|
302
|
+
}];
|
|
303
|
+
const violations = [];
|
|
304
|
+
for (const file of files) {
|
|
305
|
+
const values = exportedValues(project.getSourceFileOrThrow((0, node_path.resolve)(cwd, file)));
|
|
306
|
+
for (const [qualified, declaration] of values) {
|
|
307
|
+
if (excluded.has(qualified)) continue;
|
|
308
|
+
for (const { template, counterpart, missing } of halves) {
|
|
309
|
+
const shared = sharedName(qualified, template);
|
|
310
|
+
if (shared === void 0) continue;
|
|
311
|
+
const expected = `${shared.namespace}${counterpart.before}${shared.name}${counterpart.after}`;
|
|
312
|
+
if (!values.has(expected)) violations.push({
|
|
313
|
+
code: `codec-pairs/missing-${missing}`,
|
|
314
|
+
message: `${qualified} has no ${missing}: ${file} does not export ${expected}`,
|
|
315
|
+
file: relativePosix(cwd, declaration.getSourceFile().getFilePath()),
|
|
316
|
+
location: valueLocation(declaration)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || (a.location?.line ?? 0) - (b.location?.line ?? 0) || a.message.localeCompare(b.message));
|
|
322
|
+
};
|
|
323
|
+
//#endregion
|
|
216
324
|
//#region src/checks/type-references.ts
|
|
217
325
|
/**
|
|
218
326
|
* The identifier a reference ends in: `infer` in `z.infer`, the name itself when it is not qualified.
|
|
@@ -2020,6 +2128,9 @@ function runsOnOf(reader, value, what) {
|
|
|
2020
2128
|
group
|
|
2021
2129
|
};
|
|
2022
2130
|
}
|
|
2131
|
+
function environmentOf(reader, value, what) {
|
|
2132
|
+
return isRecord(value) ? reader.text(value["name"], `${what}.name`) : reader.text(value, what);
|
|
2133
|
+
}
|
|
2023
2134
|
function stepOf(reader, value, jobPath, index) {
|
|
2024
2135
|
const path = [
|
|
2025
2136
|
...jobPath,
|
|
@@ -2052,6 +2163,7 @@ function jobOf(reader, id, value) {
|
|
|
2052
2163
|
needs: reader.list(job["needs"], `${what}.needs`),
|
|
2053
2164
|
condition: reader.text(job["if"], `${what}.if`),
|
|
2054
2165
|
permissions: permissionsOf(reader, job["permissions"], `${what}.permissions`),
|
|
2166
|
+
environment: environmentOf(reader, job["environment"], `${what}.environment`),
|
|
2055
2167
|
env: reader.texts(job["env"], `${what}.env`),
|
|
2056
2168
|
runsOn: runsOnOf(reader, job["runs-on"], `${what}.runs-on`),
|
|
2057
2169
|
timeoutMinutes: reader.text(job["timeout-minutes"], `${what}.timeout-minutes`),
|
|
@@ -2087,6 +2199,7 @@ function parseWorkflow(file, source) {
|
|
|
2087
2199
|
triggers,
|
|
2088
2200
|
triggerLocations: triggerLocationsOf(reader, triggers, root["on"]),
|
|
2089
2201
|
permissions: permissionsOf(reader, root["permissions"], "permissions"),
|
|
2202
|
+
permissionsLocation: root["permissions"] === void 0 ? void 0 : reader.location(["permissions"]),
|
|
2090
2203
|
env: reader.texts(root["env"], "env"),
|
|
2091
2204
|
jobs: Object.entries(reader.record(root["jobs"], "jobs")).map(([id, job]) => jobOf(reader, id, job)),
|
|
2092
2205
|
callOutputs: Object.fromEntries(Object.entries(outputs).map(([name, output]) => [name, reader.text(reader.record(output, `on.workflow_call.outputs.${name}`)["value"], `on.workflow_call.outputs.${name}.value`) ?? ""]))
|
|
@@ -2265,6 +2378,14 @@ function callsAlways(condition) {
|
|
|
2265
2378
|
* The default ids of junction jobs, shared by the checks that look for one.
|
|
2266
2379
|
*/
|
|
2267
2380
|
const DEFAULT_JUNCTION_JOBS = ["required-checks"];
|
|
2381
|
+
/**
|
|
2382
|
+
* The default ids of release jobs, shared by the checks that look for one.
|
|
2383
|
+
*/
|
|
2384
|
+
const DEFAULT_RELEASE_JOBS = ["release"];
|
|
2385
|
+
/**
|
|
2386
|
+
* The variables a token for the npm registry is passed in.
|
|
2387
|
+
*/
|
|
2388
|
+
const REGISTRY_TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
|
|
2268
2389
|
//#endregion
|
|
2269
2390
|
//#region src/checks/settings-required-checks.ts
|
|
2270
2391
|
/**
|
|
@@ -2415,10 +2536,6 @@ const PACKAGE_MANAGER_OPTIONS = String.raw`(?:\s+(?:(?:--filter|-F|--workspace|-
|
|
|
2415
2536
|
* Commands that publish a package to a registry.
|
|
2416
2537
|
*/
|
|
2417
2538
|
const PUBLISH_COMMAND = new RegExp(String.raw`^(?:(?:npm|pnpm|yarn)${PACKAGE_MANAGER_OPTIONS}(?:\s+npm)?\s+publish|(?:(?:pnpm|yarn)(?:\s+exec)?\s+|npx\s+)?(?:semantic-release|changeset\s+publish|lerna\s+publish))(?:\s|$)`, "u");
|
|
2418
|
-
/**
|
|
2419
|
-
* The variables a token for the registry is passed in.
|
|
2420
|
-
*/
|
|
2421
|
-
const TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
|
|
2422
2539
|
const PROVENANCE_FLAG = /--provenance(?:=true)?(?:\s|$)/u;
|
|
2423
2540
|
/**
|
|
2424
2541
|
* Whether the `package.json` in the working directory asks for provenance with `publishConfig.provenance: true`, which applies to every publish of that package.
|
|
@@ -2442,7 +2559,7 @@ function attestationViolations(workflow, job) {
|
|
|
2442
2559
|
* Whether the job passes a registry token to a step, which makes its publish not tokenless: a token variable with a value other than the empty string, anywhere in the environment.
|
|
2443
2560
|
*/
|
|
2444
2561
|
function passesToken(workflow, job) {
|
|
2445
|
-
return [void 0, ...job.steps].some((step) =>
|
|
2562
|
+
return [void 0, ...job.steps].some((step) => REGISTRY_TOKEN_VARIABLES.some((variable) => (effectiveEnv(workflow, job, step)[variable] ?? "") !== ""));
|
|
2446
2563
|
}
|
|
2447
2564
|
function tokenlessPublishViolations(workflow, job, provenanceInManifest) {
|
|
2448
2565
|
if (accessOf(effectivePermissions(workflow, job), "id-token") !== "write" || passesToken(workflow, job)) return [];
|
|
@@ -2450,9 +2567,9 @@ function tokenlessPublishViolations(workflow, job, provenanceInManifest) {
|
|
|
2450
2567
|
const publishes = scriptCommands(step.run).filter((line) => PUBLISH_COMMAND.test(line));
|
|
2451
2568
|
if (publishes.length === 0) return [];
|
|
2452
2569
|
const env = effectiveEnv(workflow, job, step);
|
|
2453
|
-
const blanked =
|
|
2570
|
+
const blanked = REGISTRY_TOKEN_VARIABLES.every((variable) => env[variable] === "");
|
|
2454
2571
|
const provenance = provenanceInManifest || publishes.some((line) => PROVENANCE_FLAG.test(line)) || env["NPM_CONFIG_PROVENANCE"] === "true";
|
|
2455
|
-
return blanked || provenance ? [] : [workflowViolation(workflow, "workflow-credentials/tokenless-publish-unprotected", `job '${job.id}' publishes with an OIDC identity and no token, but does not set ${
|
|
2572
|
+
return blanked || provenance ? [] : [workflowViolation(workflow, "workflow-credentials/tokenless-publish-unprotected", `job '${job.id}' publishes with an OIDC identity and no token, but does not set ${REGISTRY_TOKEN_VARIABLES.join(" and ")} to the empty string, so a token inherited from the environment is used when the OIDC exchange fails; set them to the empty string to prevent that, or request provenance so that such a publish at least carries a traceable attestation`, step.location)];
|
|
2456
2573
|
});
|
|
2457
2574
|
}
|
|
2458
2575
|
/**
|
|
@@ -2470,7 +2587,6 @@ const workflowCredentials = async ({ cwd, options }) => {
|
|
|
2470
2587
|
};
|
|
2471
2588
|
//#endregion
|
|
2472
2589
|
//#region src/checks/workflow-job-ordering.ts
|
|
2473
|
-
const DEFAULT_RELEASE_JOBS = ["release"];
|
|
2474
2590
|
const DEFAULT_DEPLOY_JOBS = ["deploy"];
|
|
2475
2591
|
const DEFAULT_DOCS_DEPLOY_JOBS = ["docs-deploy"];
|
|
2476
2592
|
const DEFAULT_BRANCH = "main";
|
|
@@ -2618,6 +2734,71 @@ const workflowMergeGroup = async ({ cwd, options }) => (await loadWorkflows(cwd,
|
|
|
2618
2734
|
return location === void 0 || workflow.triggers.has("merge_group") ? [] : [workflowViolation(workflow, "workflow-merge-group/missing-trigger", "the workflow runs for pull requests but has no merge_group trigger, so its checks do not report for a pull request in the merge queue", location)];
|
|
2619
2735
|
});
|
|
2620
2736
|
//#endregion
|
|
2737
|
+
//#region src/checks/workflow-release-job.ts
|
|
2738
|
+
const CHECKOUT_ACTION$1 = "actions/checkout";
|
|
2739
|
+
/**
|
|
2740
|
+
* A value that reads the `secrets` context inside an expression.
|
|
2741
|
+
*/
|
|
2742
|
+
const FROM_SECRET = /\$\{\{[^}]*\bsecrets\./u;
|
|
2743
|
+
/**
|
|
2744
|
+
* The release jobs named for a message: `release job 'a'` or `release jobs 'a' and 'b'`.
|
|
2745
|
+
*/
|
|
2746
|
+
function releaseJobsText(releases) {
|
|
2747
|
+
const ids = releases.map((job) => `'${job.id}'`);
|
|
2748
|
+
return `release job${ids.length === 1 ? "" : "s"} ${ids.join(" and ")}`;
|
|
2749
|
+
}
|
|
2750
|
+
function grantsIdToken(permissions) {
|
|
2751
|
+
return accessOf(permissions, "id-token") === "write";
|
|
2752
|
+
}
|
|
2753
|
+
function idTokenViolations(workflow, releases) {
|
|
2754
|
+
const releaseIds = new Set(releases.map((job) => job.id));
|
|
2755
|
+
const named = releaseJobsText(releases);
|
|
2756
|
+
const workflowLevel = grantsIdToken(workflow.permissions) && workflow.permissionsLocation !== void 0 ? [workflowViolation(workflow, "workflow-release-job/id-token-workflow-level", `the workflow's permissions grant id-token: write to every job that declares none of its own; grant it on ${named} only`, workflow.permissionsLocation)] : [];
|
|
2757
|
+
const jobLevel = workflow.jobs.filter((job) => !releaseIds.has(job.id) && grantsIdToken(job.permissions)).map((job) => workflowViolation(workflow, "workflow-release-job/id-token-outside-release", `job '${job.id}' is granted id-token: write but is not a release job, so it can mint the OIDC identity npm trusted publishing accepts for this workflow; only ${named} may hold it`, job.location));
|
|
2758
|
+
return [...workflowLevel, ...jobLevel];
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* What a checkout that does not set `persist-credentials: false` leaves in git config.
|
|
2762
|
+
*/
|
|
2763
|
+
function persistedCredential(step) {
|
|
2764
|
+
if (step.with["ssh-key"] !== void 0) return "the SSH key";
|
|
2765
|
+
return step.with["token"] === void 0 ? "the job token" : "the token it was given";
|
|
2766
|
+
}
|
|
2767
|
+
function releaseJobViolations(workflow, job) {
|
|
2768
|
+
if (job.uses !== void 0) return [];
|
|
2769
|
+
const environment = job.environment === void 0 ? [workflowViolation(workflow, "workflow-release-job/missing-environment", `release job '${job.id}' declares no environment, so nothing outside the workflow file stops a branch's copy of it publishing; run it in a protected environment restricted to the default branch`, job.location)] : [];
|
|
2770
|
+
const checkouts = job.steps.filter((step) => stepUses(step, CHECKOUT_ACTION$1) && step.with["persist-credentials"] !== "false").map((step) => workflowViolation(workflow, "workflow-release-job/persisted-credentials", `release job '${job.id}' checks out without persist-credentials: false, which leaves ${persistedCredential(step)} in git config for every later step`, step.location));
|
|
2771
|
+
return [...environment, ...checkouts];
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* The token variables an environment sets from a secret.
|
|
2775
|
+
*/
|
|
2776
|
+
function secretTokens(env) {
|
|
2777
|
+
return REGISTRY_TOKEN_VARIABLES.filter((variable) => FROM_SECRET.test(env[variable] ?? ""));
|
|
2778
|
+
}
|
|
2779
|
+
function tokenBesideOidcViolations(workflow, job) {
|
|
2780
|
+
if (!grantsIdToken(effectivePermissions(workflow, job))) return [];
|
|
2781
|
+
const report = (variables, location) => variables.length === 0 ? [] : [workflowViolation(workflow, "workflow-release-job/token-beside-oidc", `job '${job.id}' holds id-token: write and passes ${variables.join(" and ")} from a secret, a long-lived npm token beside the OIDC identity; publish through trusted publishing and blank the token variables`, location)];
|
|
2782
|
+
return [...report(secretTokens(effectiveEnv(workflow, job, void 0)), job.location), ...job.steps.flatMap((step) => report(secretTokens(step.env), step.location))];
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* npm binds a trusted publisher to the repository and the workflow file, not to a branch, so anyone who can push a branch can edit that branch's copy of the workflow, drop an `if` that keeps publishing to the default branch, and publish. What stops that has to live outside the file: a protected environment that only the default branch may deploy to, named in npm's trusted publisher too. This check holds the release job to the shape that makes that protection hold.
|
|
2786
|
+
*
|
|
2787
|
+
* In a workflow with a release job (by id, `releaseJobs`, `release` by default): `workflow-release-job/id-token-workflow-level`, the workflow's own `permissions` grant `id-token: write` (directly or through `write-all`), which every job that declares none inherits; `id-token-outside-release`, a job that is not a release job declares `id-token: write` itself, a job that calls a reusable workflow included; `missing-environment`, a release job declares no `environment`; `persisted-credentials`, a release job runs `actions/checkout` without `persist-credentials: false` (literally; an expression is unknown and reported), so the job token, a `token` input or an `ssh-key` stays in git config for every later step. A release job that pushes as a deploy key provisions the key in a step and hands it to git through that step's `GIT_SSH_COMMAND`, after a checkout that persists nothing, and is accepted as written; semantic-release authenticates its own push (`repositoryUrl` as written, else the URL with `GITHUB_TOKEN` or `GH_TOKEN` from the environment), so it needs nothing from the checkout.
|
|
2788
|
+
*
|
|
2789
|
+
* In every workflow: `token-beside-oidc`, a job whose effective permissions grant `id-token: write` sets `NODE_AUTH_TOKEN` or `NPM_TOKEN` from the `secrets` context in its workflow, job or step `env`.
|
|
2790
|
+
*
|
|
2791
|
+
* Limits: the environment's protection rules and npm's trusted publisher settings are not visible in the file and are not read; a release job that calls a reusable workflow is judged only for `id-token`, since `environment` and steps belong to the called workflow; a token passed through `with`, written to an `.npmrc` by a script, or inherited through a composite action is not seen.
|
|
2792
|
+
*/
|
|
2793
|
+
const workflowReleaseJob = async ({ cwd, options }) => {
|
|
2794
|
+
return (await loadWorkflows(cwd, options)).flatMap((workflow) => {
|
|
2795
|
+
const releases = jobsNamed(workflow, options.releaseJobs ?? DEFAULT_RELEASE_JOBS);
|
|
2796
|
+
const releaseFindings = releases.length === 0 ? [] : idTokenViolations(workflow, releases);
|
|
2797
|
+
const jobFindings = workflow.jobs.flatMap((job) => [...releases.includes(job) ? releaseJobViolations(workflow, job) : [], ...tokenBesideOidcViolations(workflow, job)]);
|
|
2798
|
+
return [...releaseFindings, ...jobFindings];
|
|
2799
|
+
});
|
|
2800
|
+
};
|
|
2801
|
+
//#endregion
|
|
2621
2802
|
//#region src/checks/workflow-repository-dispatch.ts
|
|
2622
2803
|
const DEFAULT_BRANCHES = ["main", "master"];
|
|
2623
2804
|
const DEFAULT_BRANCH_EXPRESSION = /\$\{\{\s*github\.event\.repository\.default_branch\s*\}\}/u;
|
|
@@ -3071,6 +3252,10 @@ function compiles(source) {
|
|
|
3071
3252
|
*/
|
|
3072
3253
|
const pattern = zod.z.string().min(1).refine(compiles, "not a valid regular expression");
|
|
3073
3254
|
const patterns = zod.z.array(pattern);
|
|
3255
|
+
/**
|
|
3256
|
+
* A name template of `codec-pairs`.
|
|
3257
|
+
*/
|
|
3258
|
+
const nameTemplate = zod.z.string().refine((template) => parseNameTemplate(template) !== void 0, NAME_TEMPLATE_REQUIREMENT);
|
|
3074
3259
|
const severity = zod.z.enum(["warn", "error"]);
|
|
3075
3260
|
const requiredRules = zod.z.record(zod.z.string().min(1), severity);
|
|
3076
3261
|
const selection = {
|
|
@@ -3103,6 +3288,16 @@ const checks = zod.z.strictObject({
|
|
|
3103
3288
|
exclude: zod.z.exactOptional(names),
|
|
3104
3289
|
tsConfig: zod.z.exactOptional(path)
|
|
3105
3290
|
}))),
|
|
3291
|
+
"codec-pairs": zod.z.exactOptional(enabled(zod.z.strictObject({
|
|
3292
|
+
codecs: paths,
|
|
3293
|
+
encoder: nameTemplate,
|
|
3294
|
+
decoder: nameTemplate,
|
|
3295
|
+
exclude: zod.z.exactOptional(names),
|
|
3296
|
+
tsConfig: zod.z.exactOptional(path)
|
|
3297
|
+
}).refine((options) => options.encoder !== options.decoder, {
|
|
3298
|
+
path: ["decoder"],
|
|
3299
|
+
message: "the decoder template is the encoder template, so every name would be its own pair"
|
|
3300
|
+
}))),
|
|
3106
3301
|
"command-types": zod.z.exactOptional(enabled(zod.z.strictObject({
|
|
3107
3302
|
commands: paths,
|
|
3108
3303
|
exclude: zod.z.exactOptional(names),
|
|
@@ -3180,6 +3375,10 @@ const checks = zod.z.strictObject({
|
|
|
3180
3375
|
}))),
|
|
3181
3376
|
"workflow-version-single-source": zod.z.exactOptional(enabled(zod.z.strictObject(selection))),
|
|
3182
3377
|
"workflow-credentials": zod.z.exactOptional(enabled(zod.z.strictObject(selection))),
|
|
3378
|
+
"workflow-release-job": zod.z.exactOptional(enabled(zod.z.strictObject({
|
|
3379
|
+
...selection,
|
|
3380
|
+
releaseJobs: zod.z.exactOptional(names)
|
|
3381
|
+
}))),
|
|
3183
3382
|
"workflow-repository-dispatch": zod.z.exactOptional(enabled(zod.z.strictObject({
|
|
3184
3383
|
...selection,
|
|
3185
3384
|
defaultBranches: zod.z.exactOptional(names),
|
|
@@ -3359,6 +3558,12 @@ const registry = {
|
|
|
3359
3558
|
select: (checks) => enabledOptions(checks["aggregate-mappers"]),
|
|
3360
3559
|
check: aggregateMappers
|
|
3361
3560
|
}),
|
|
3561
|
+
"codec-pairs": plain({
|
|
3562
|
+
name: "codec-pairs",
|
|
3563
|
+
description: "Every exported encoder in a codec file has a decoder under the naming templates, and every decoder an encoder",
|
|
3564
|
+
select: (checks) => enabledOptions(checks["codec-pairs"]),
|
|
3565
|
+
check: codecPairs
|
|
3566
|
+
}),
|
|
3362
3567
|
"command-types": plain({
|
|
3363
3568
|
name: "command-types",
|
|
3364
3569
|
description: "Every exported command type is derived from a schema, not hand-written",
|
|
@@ -3467,6 +3672,12 @@ const registry = {
|
|
|
3467
3672
|
select: (checks) => enabledOptions(checks["workflow-credentials"]),
|
|
3468
3673
|
check: workflowCredentials
|
|
3469
3674
|
}),
|
|
3675
|
+
"workflow-release-job": plain({
|
|
3676
|
+
name: "workflow-release-job",
|
|
3677
|
+
description: "The release job alone holds id-token, runs in an environment and persists no credentials, and no job with id-token passes an npm token secret",
|
|
3678
|
+
select: (checks) => enabledOptions(checks["workflow-release-job"]),
|
|
3679
|
+
check: workflowReleaseJob
|
|
3680
|
+
}),
|
|
3470
3681
|
"workflow-repository-dispatch": plain({
|
|
3471
3682
|
name: "workflow-repository-dispatch",
|
|
3472
3683
|
description: "A repository_dispatch handler does not push to the default branch, auto-merge unguarded, or rely on the default token",
|
|
@@ -3729,6 +3940,7 @@ exports.accessOf = accessOf;
|
|
|
3729
3940
|
exports.aggregateMappers = aggregateMappers;
|
|
3730
3941
|
exports.checkNames = checkNames;
|
|
3731
3942
|
exports.classifyPackages = classifyPackages;
|
|
3943
|
+
exports.codecPairs = codecPairs;
|
|
3732
3944
|
exports.commandTypes = commandTypes;
|
|
3733
3945
|
exports.commitTypes = commitTypes;
|
|
3734
3946
|
exports.conformanceSchema = conformanceSchema;
|
|
@@ -3773,6 +3985,7 @@ exports.workflowActionPinning = workflowActionPinning;
|
|
|
3773
3985
|
exports.workflowCredentials = workflowCredentials;
|
|
3774
3986
|
exports.workflowJobOrdering = workflowJobOrdering;
|
|
3775
3987
|
exports.workflowMergeGroup = workflowMergeGroup;
|
|
3988
|
+
exports.workflowReleaseJob = workflowReleaseJob;
|
|
3776
3989
|
exports.workflowRepositoryDispatch = workflowRepositoryDispatch;
|
|
3777
3990
|
exports.workflowRunnerResolution = workflowRunnerResolution;
|
|
3778
3991
|
exports.workflowSkippableJobs = workflowSkippableJobs;
|
package/dist/index.d.cts
CHANGED
|
@@ -255,6 +255,10 @@ interface Job {
|
|
|
255
255
|
readonly needs: readonly string[];
|
|
256
256
|
readonly condition: string | undefined;
|
|
257
257
|
readonly permissions: Permissions;
|
|
258
|
+
/**
|
|
259
|
+
* The name of the deployment environment the job runs in, written as `environment: <name>` or `environment: { name: <name> }`; `undefined` when it names none.
|
|
260
|
+
*/
|
|
261
|
+
readonly environment: string | undefined;
|
|
258
262
|
readonly env: Readonly<Record<string, string>>;
|
|
259
263
|
readonly runsOn: RunsOn;
|
|
260
264
|
readonly timeoutMinutes: string | undefined;
|
|
@@ -282,6 +286,10 @@ interface Workflow {
|
|
|
282
286
|
*/
|
|
283
287
|
readonly triggerLocations: ReadonlyMap<string, SourceLocation>;
|
|
284
288
|
readonly permissions: Permissions;
|
|
289
|
+
/**
|
|
290
|
+
* Where the workflow's `permissions` is written; `undefined` when it declares none.
|
|
291
|
+
*/
|
|
292
|
+
readonly permissionsLocation: SourceLocation | undefined;
|
|
285
293
|
readonly env: Readonly<Record<string, string>>;
|
|
286
294
|
readonly jobs: readonly Job[];
|
|
287
295
|
/**
|
|
@@ -376,6 +384,31 @@ interface AggregateMappersOptions {
|
|
|
376
384
|
*/
|
|
377
385
|
readonly tsConfig?: string;
|
|
378
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Options of the `codec-pairs` check: every encoder a codec file exports has a decoder it also exports, and every decoder an encoder.
|
|
389
|
+
*/
|
|
390
|
+
interface CodecPairsOptions {
|
|
391
|
+
/**
|
|
392
|
+
* Globs of the codec files, relative to the directory the checks run in. Each file is judged on its own exports, re-exports included.
|
|
393
|
+
*/
|
|
394
|
+
readonly codecs: readonly string[];
|
|
395
|
+
/**
|
|
396
|
+
* The name of an encoder, with `{name}` standing for the name it shares with its decoder, such as `encode{name}` or `{name}Encoder`. `{name}` appears exactly once, beside other text, and is the only placeholder.
|
|
397
|
+
*/
|
|
398
|
+
readonly encoder: string;
|
|
399
|
+
/**
|
|
400
|
+
* The name of a decoder, in the same form as `encoder` and different from it.
|
|
401
|
+
*/
|
|
402
|
+
readonly decoder: string;
|
|
403
|
+
/**
|
|
404
|
+
* Names of exported encoders and decoders that need no counterpart, such as an encoder for a format that is only ever written.
|
|
405
|
+
*/
|
|
406
|
+
readonly exclude?: readonly string[];
|
|
407
|
+
/**
|
|
408
|
+
* The tsconfig that supplies compiler options and module resolution; `tsconfig.json` when omitted.
|
|
409
|
+
*/
|
|
410
|
+
readonly tsConfig?: string;
|
|
411
|
+
}
|
|
379
412
|
/**
|
|
380
413
|
* Options of the `command-types` check: every command type a file exports is derived from a schema.
|
|
381
414
|
*/
|
|
@@ -642,6 +675,15 @@ type WorkflowVersionSingleSourceOptions = WorkflowSelection;
|
|
|
642
675
|
* Options of the `workflow-credentials` check.
|
|
643
676
|
*/
|
|
644
677
|
type WorkflowCredentialsOptions = WorkflowSelection;
|
|
678
|
+
/**
|
|
679
|
+
* Options of the `workflow-release-job` check: the job that publishes is the only one that can mint the OIDC identity, runs in an environment and leaves no credentials behind.
|
|
680
|
+
*/
|
|
681
|
+
interface WorkflowReleaseJobOptions extends WorkflowSelection {
|
|
682
|
+
/**
|
|
683
|
+
* Ids of release jobs: the jobs that publish, and the only ones that may hold `id-token: write`. `release` when omitted.
|
|
684
|
+
*/
|
|
685
|
+
readonly releaseJobs?: readonly string[];
|
|
686
|
+
}
|
|
645
687
|
/**
|
|
646
688
|
* Options of the `workflow-repository-dispatch` check: what a handler of `repository_dispatch` may do.
|
|
647
689
|
*/
|
|
@@ -737,6 +779,7 @@ type SettingsReviewThreadResolutionOptions = SettingsOptions;
|
|
|
737
779
|
*/
|
|
738
780
|
interface CheckOptionsByName {
|
|
739
781
|
readonly "aggregate-mappers": AggregateMappersOptions;
|
|
782
|
+
readonly "codec-pairs": CodecPairsOptions;
|
|
740
783
|
readonly "command-types": CommandTypesOptions;
|
|
741
784
|
readonly "derived-types": DerivedTypesOptions;
|
|
742
785
|
readonly "import-uphill": ImportGraphOptions;
|
|
@@ -755,6 +798,7 @@ interface CheckOptionsByName {
|
|
|
755
798
|
readonly "workflow-runner-resolution": WorkflowRunnerResolutionOptions;
|
|
756
799
|
readonly "workflow-version-single-source": WorkflowVersionSingleSourceOptions;
|
|
757
800
|
readonly "workflow-credentials": WorkflowCredentialsOptions;
|
|
801
|
+
readonly "workflow-release-job": WorkflowReleaseJobOptions;
|
|
758
802
|
readonly "workflow-repository-dispatch": WorkflowRepositoryDispatchOptions;
|
|
759
803
|
readonly "workflow-update-bot-cooldown": WorkflowUpdateBotCooldownOptions;
|
|
760
804
|
readonly "workflow-merge-group": WorkflowMergeGroupOptions;
|
|
@@ -776,6 +820,14 @@ type CheckName = keyof CheckOptionsByName;
|
|
|
776
820
|
*/
|
|
777
821
|
export declare const aggregateMappers: CheckFunction<AggregateMappersOptions>;
|
|
778
822
|
//#endregion
|
|
823
|
+
//#region src/checks/codec-pairs.d.ts
|
|
824
|
+
/**
|
|
825
|
+
* Every encoder a codec file exports has the decoder its name implies, exported by the same file, and every decoder the encoder. A name is an encoder or a decoder when it fits that template; other exports are not codecs and are not judged. A half re-exported from another file counts, and is reported where it is declared.
|
|
826
|
+
*
|
|
827
|
+
* It checks that the counterpart is exported, not that the two round-trip: that is a property test of the repository's own.
|
|
828
|
+
*/
|
|
829
|
+
export declare const codecPairs: CheckFunction<CodecPairsOptions>;
|
|
830
|
+
//#endregion
|
|
779
831
|
//#region src/checks/command-types.d.ts
|
|
780
832
|
/**
|
|
781
833
|
* The last names of the generics that turn a schema into its type, used when the options name none: `z.infer`, `z.input`, `z.output`, `z.TypeOf` and Valibot's `InferInput` and `InferOutput`.
|
|
@@ -1054,6 +1106,18 @@ export declare const workflowJobOrdering: CheckFunction<WorkflowJobOrderingOptio
|
|
|
1054
1106
|
*/
|
|
1055
1107
|
export declare const workflowMergeGroup: CheckFunction<WorkflowMergeGroupOptions>;
|
|
1056
1108
|
//#endregion
|
|
1109
|
+
//#region src/checks/workflow-release-job.d.ts
|
|
1110
|
+
/**
|
|
1111
|
+
* npm binds a trusted publisher to the repository and the workflow file, not to a branch, so anyone who can push a branch can edit that branch's copy of the workflow, drop an `if` that keeps publishing to the default branch, and publish. What stops that has to live outside the file: a protected environment that only the default branch may deploy to, named in npm's trusted publisher too. This check holds the release job to the shape that makes that protection hold.
|
|
1112
|
+
*
|
|
1113
|
+
* In a workflow with a release job (by id, `releaseJobs`, `release` by default): `workflow-release-job/id-token-workflow-level`, the workflow's own `permissions` grant `id-token: write` (directly or through `write-all`), which every job that declares none inherits; `id-token-outside-release`, a job that is not a release job declares `id-token: write` itself, a job that calls a reusable workflow included; `missing-environment`, a release job declares no `environment`; `persisted-credentials`, a release job runs `actions/checkout` without `persist-credentials: false` (literally; an expression is unknown and reported), so the job token, a `token` input or an `ssh-key` stays in git config for every later step. A release job that pushes as a deploy key provisions the key in a step and hands it to git through that step's `GIT_SSH_COMMAND`, after a checkout that persists nothing, and is accepted as written; semantic-release authenticates its own push (`repositoryUrl` as written, else the URL with `GITHUB_TOKEN` or `GH_TOKEN` from the environment), so it needs nothing from the checkout.
|
|
1114
|
+
*
|
|
1115
|
+
* In every workflow: `token-beside-oidc`, a job whose effective permissions grant `id-token: write` sets `NODE_AUTH_TOKEN` or `NPM_TOKEN` from the `secrets` context in its workflow, job or step `env`.
|
|
1116
|
+
*
|
|
1117
|
+
* Limits: the environment's protection rules and npm's trusted publisher settings are not visible in the file and are not read; a release job that calls a reusable workflow is judged only for `id-token`, since `environment` and steps belong to the called workflow; a token passed through `with`, written to an `.npmrc` by a script, or inherited through a composite action is not seen.
|
|
1118
|
+
*/
|
|
1119
|
+
export declare const workflowReleaseJob: CheckFunction<WorkflowReleaseJobOptions>;
|
|
1120
|
+
//#endregion
|
|
1057
1121
|
//#region src/checks/workflow-repository-dispatch.d.ts
|
|
1058
1122
|
/**
|
|
1059
1123
|
* A workflow that handles `repository_dispatch` can be started by anyone with write access through the API, so what it does with the repository is held to a higher bar.
|
|
@@ -1322,4 +1386,4 @@ export declare function proveExhaustive(input: {
|
|
|
1322
1386
|
readonly compilerOptions?: CompilerOptions;
|
|
1323
1387
|
}): ExhaustivenessProof;
|
|
1324
1388
|
//#endregion
|
|
1325
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RunChecksOptions, RunResult, RunsOn, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1389
|
+
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RunChecksOptions, RunResult, RunsOn, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
package/dist/index.d.ts
CHANGED
|
@@ -255,6 +255,10 @@ interface Job {
|
|
|
255
255
|
readonly needs: readonly string[];
|
|
256
256
|
readonly condition: string | undefined;
|
|
257
257
|
readonly permissions: Permissions;
|
|
258
|
+
/**
|
|
259
|
+
* The name of the deployment environment the job runs in, written as `environment: <name>` or `environment: { name: <name> }`; `undefined` when it names none.
|
|
260
|
+
*/
|
|
261
|
+
readonly environment: string | undefined;
|
|
258
262
|
readonly env: Readonly<Record<string, string>>;
|
|
259
263
|
readonly runsOn: RunsOn;
|
|
260
264
|
readonly timeoutMinutes: string | undefined;
|
|
@@ -282,6 +286,10 @@ interface Workflow {
|
|
|
282
286
|
*/
|
|
283
287
|
readonly triggerLocations: ReadonlyMap<string, SourceLocation>;
|
|
284
288
|
readonly permissions: Permissions;
|
|
289
|
+
/**
|
|
290
|
+
* Where the workflow's `permissions` is written; `undefined` when it declares none.
|
|
291
|
+
*/
|
|
292
|
+
readonly permissionsLocation: SourceLocation | undefined;
|
|
285
293
|
readonly env: Readonly<Record<string, string>>;
|
|
286
294
|
readonly jobs: readonly Job[];
|
|
287
295
|
/**
|
|
@@ -376,6 +384,31 @@ interface AggregateMappersOptions {
|
|
|
376
384
|
*/
|
|
377
385
|
readonly tsConfig?: string;
|
|
378
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Options of the `codec-pairs` check: every encoder a codec file exports has a decoder it also exports, and every decoder an encoder.
|
|
389
|
+
*/
|
|
390
|
+
interface CodecPairsOptions {
|
|
391
|
+
/**
|
|
392
|
+
* Globs of the codec files, relative to the directory the checks run in. Each file is judged on its own exports, re-exports included.
|
|
393
|
+
*/
|
|
394
|
+
readonly codecs: readonly string[];
|
|
395
|
+
/**
|
|
396
|
+
* The name of an encoder, with `{name}` standing for the name it shares with its decoder, such as `encode{name}` or `{name}Encoder`. `{name}` appears exactly once, beside other text, and is the only placeholder.
|
|
397
|
+
*/
|
|
398
|
+
readonly encoder: string;
|
|
399
|
+
/**
|
|
400
|
+
* The name of a decoder, in the same form as `encoder` and different from it.
|
|
401
|
+
*/
|
|
402
|
+
readonly decoder: string;
|
|
403
|
+
/**
|
|
404
|
+
* Names of exported encoders and decoders that need no counterpart, such as an encoder for a format that is only ever written.
|
|
405
|
+
*/
|
|
406
|
+
readonly exclude?: readonly string[];
|
|
407
|
+
/**
|
|
408
|
+
* The tsconfig that supplies compiler options and module resolution; `tsconfig.json` when omitted.
|
|
409
|
+
*/
|
|
410
|
+
readonly tsConfig?: string;
|
|
411
|
+
}
|
|
379
412
|
/**
|
|
380
413
|
* Options of the `command-types` check: every command type a file exports is derived from a schema.
|
|
381
414
|
*/
|
|
@@ -642,6 +675,15 @@ type WorkflowVersionSingleSourceOptions = WorkflowSelection;
|
|
|
642
675
|
* Options of the `workflow-credentials` check.
|
|
643
676
|
*/
|
|
644
677
|
type WorkflowCredentialsOptions = WorkflowSelection;
|
|
678
|
+
/**
|
|
679
|
+
* Options of the `workflow-release-job` check: the job that publishes is the only one that can mint the OIDC identity, runs in an environment and leaves no credentials behind.
|
|
680
|
+
*/
|
|
681
|
+
interface WorkflowReleaseJobOptions extends WorkflowSelection {
|
|
682
|
+
/**
|
|
683
|
+
* Ids of release jobs: the jobs that publish, and the only ones that may hold `id-token: write`. `release` when omitted.
|
|
684
|
+
*/
|
|
685
|
+
readonly releaseJobs?: readonly string[];
|
|
686
|
+
}
|
|
645
687
|
/**
|
|
646
688
|
* Options of the `workflow-repository-dispatch` check: what a handler of `repository_dispatch` may do.
|
|
647
689
|
*/
|
|
@@ -737,6 +779,7 @@ type SettingsReviewThreadResolutionOptions = SettingsOptions;
|
|
|
737
779
|
*/
|
|
738
780
|
interface CheckOptionsByName {
|
|
739
781
|
readonly "aggregate-mappers": AggregateMappersOptions;
|
|
782
|
+
readonly "codec-pairs": CodecPairsOptions;
|
|
740
783
|
readonly "command-types": CommandTypesOptions;
|
|
741
784
|
readonly "derived-types": DerivedTypesOptions;
|
|
742
785
|
readonly "import-uphill": ImportGraphOptions;
|
|
@@ -755,6 +798,7 @@ interface CheckOptionsByName {
|
|
|
755
798
|
readonly "workflow-runner-resolution": WorkflowRunnerResolutionOptions;
|
|
756
799
|
readonly "workflow-version-single-source": WorkflowVersionSingleSourceOptions;
|
|
757
800
|
readonly "workflow-credentials": WorkflowCredentialsOptions;
|
|
801
|
+
readonly "workflow-release-job": WorkflowReleaseJobOptions;
|
|
758
802
|
readonly "workflow-repository-dispatch": WorkflowRepositoryDispatchOptions;
|
|
759
803
|
readonly "workflow-update-bot-cooldown": WorkflowUpdateBotCooldownOptions;
|
|
760
804
|
readonly "workflow-merge-group": WorkflowMergeGroupOptions;
|
|
@@ -776,6 +820,14 @@ type CheckName = keyof CheckOptionsByName;
|
|
|
776
820
|
*/
|
|
777
821
|
export declare const aggregateMappers: CheckFunction<AggregateMappersOptions>;
|
|
778
822
|
//#endregion
|
|
823
|
+
//#region src/checks/codec-pairs.d.ts
|
|
824
|
+
/**
|
|
825
|
+
* Every encoder a codec file exports has the decoder its name implies, exported by the same file, and every decoder the encoder. A name is an encoder or a decoder when it fits that template; other exports are not codecs and are not judged. A half re-exported from another file counts, and is reported where it is declared.
|
|
826
|
+
*
|
|
827
|
+
* It checks that the counterpart is exported, not that the two round-trip: that is a property test of the repository's own.
|
|
828
|
+
*/
|
|
829
|
+
export declare const codecPairs: CheckFunction<CodecPairsOptions>;
|
|
830
|
+
//#endregion
|
|
779
831
|
//#region src/checks/command-types.d.ts
|
|
780
832
|
/**
|
|
781
833
|
* The last names of the generics that turn a schema into its type, used when the options name none: `z.infer`, `z.input`, `z.output`, `z.TypeOf` and Valibot's `InferInput` and `InferOutput`.
|
|
@@ -1054,6 +1106,18 @@ export declare const workflowJobOrdering: CheckFunction<WorkflowJobOrderingOptio
|
|
|
1054
1106
|
*/
|
|
1055
1107
|
export declare const workflowMergeGroup: CheckFunction<WorkflowMergeGroupOptions>;
|
|
1056
1108
|
//#endregion
|
|
1109
|
+
//#region src/checks/workflow-release-job.d.ts
|
|
1110
|
+
/**
|
|
1111
|
+
* npm binds a trusted publisher to the repository and the workflow file, not to a branch, so anyone who can push a branch can edit that branch's copy of the workflow, drop an `if` that keeps publishing to the default branch, and publish. What stops that has to live outside the file: a protected environment that only the default branch may deploy to, named in npm's trusted publisher too. This check holds the release job to the shape that makes that protection hold.
|
|
1112
|
+
*
|
|
1113
|
+
* In a workflow with a release job (by id, `releaseJobs`, `release` by default): `workflow-release-job/id-token-workflow-level`, the workflow's own `permissions` grant `id-token: write` (directly or through `write-all`), which every job that declares none inherits; `id-token-outside-release`, a job that is not a release job declares `id-token: write` itself, a job that calls a reusable workflow included; `missing-environment`, a release job declares no `environment`; `persisted-credentials`, a release job runs `actions/checkout` without `persist-credentials: false` (literally; an expression is unknown and reported), so the job token, a `token` input or an `ssh-key` stays in git config for every later step. A release job that pushes as a deploy key provisions the key in a step and hands it to git through that step's `GIT_SSH_COMMAND`, after a checkout that persists nothing, and is accepted as written; semantic-release authenticates its own push (`repositoryUrl` as written, else the URL with `GITHUB_TOKEN` or `GH_TOKEN` from the environment), so it needs nothing from the checkout.
|
|
1114
|
+
*
|
|
1115
|
+
* In every workflow: `token-beside-oidc`, a job whose effective permissions grant `id-token: write` sets `NODE_AUTH_TOKEN` or `NPM_TOKEN` from the `secrets` context in its workflow, job or step `env`.
|
|
1116
|
+
*
|
|
1117
|
+
* Limits: the environment's protection rules and npm's trusted publisher settings are not visible in the file and are not read; a release job that calls a reusable workflow is judged only for `id-token`, since `environment` and steps belong to the called workflow; a token passed through `with`, written to an `.npmrc` by a script, or inherited through a composite action is not seen.
|
|
1118
|
+
*/
|
|
1119
|
+
export declare const workflowReleaseJob: CheckFunction<WorkflowReleaseJobOptions>;
|
|
1120
|
+
//#endregion
|
|
1057
1121
|
//#region src/checks/workflow-repository-dispatch.d.ts
|
|
1058
1122
|
/**
|
|
1059
1123
|
* A workflow that handles `repository_dispatch` can be started by anyone with write access through the API, so what it does with the repository is held to a higher bar.
|
|
@@ -1322,4 +1386,4 @@ export declare function proveExhaustive(input: {
|
|
|
1322
1386
|
readonly compilerOptions?: CompilerOptions;
|
|
1323
1387
|
}): ExhaustivenessProof;
|
|
1324
1388
|
//#endregion
|
|
1325
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RunChecksOptions, RunResult, RunsOn, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1389
|
+
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RunChecksOptions, RunResult, RunsOn, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|