workspace-conformance 2.0.1 → 3.0.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 +12 -11
- package/dist/cli.js +327 -76
- package/dist/index.cjs +365 -84
- package/dist/index.d.cts +51 -12
- package/dist/index.d.ts +51 -12
- package/dist/index.js +365 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ pnpm exec workspace-conformance check
|
|
|
55
55
|
|
|
56
56
|
The tool reads two sections through `@exadev/config`, each from `exadev.config.ts` under its key or from a standalone `exadev.<section>.config.ts` in the working directory, never both. See that package for `extends`, presets and how a section is found.
|
|
57
57
|
|
|
58
|
-
- **`layout`** is the shared `layoutSection`. The import checks read it: `groups` (with `path`, `rank`, `slice`), `nameRanks`, `defaultRank`, `rankSkip`, `isolatedGroups`, and where the packages are (`root`, `packages`, or the `packages` list of `pnpm-workspace.yaml`). Checks that do not read the layout need no `layout` section.
|
|
58
|
+
- **`layout`** is the shared `layoutSection`. The import checks read it: `groups` (with `path`, `rank`, `slice`), `nameRanks`, `defaultRank`, `rankSkip`, `isolatedGroups`, `dependencyFields`, and where the packages are (`root`, `packages`, or the `packages` list of `pnpm-workspace.yaml`). Checks that do not read the layout need no `layout` section.
|
|
59
59
|
- **`conformance`** is `conformanceSection`. Its `checks` map decides what runs: a check whose name is present with an options object runs (`{}` for the defaults), and `false` switches it off, which lets a config that extends another turn one of its checks off. A key that is not a check name, or an option a check does not have, fails when the section loads.
|
|
60
60
|
|
|
61
61
|
`runChecks` and the command line fail with exit status 2 when no check is enabled, so an empty section never reads as a pass.
|
|
@@ -88,7 +88,8 @@ How an import is attributed to a package, and the limits of that:
|
|
|
88
88
|
- An import of a workspace package by its name (or a subpath of it) that resolves to no file, because the workspace is not installed or the package's entry point is not built, is attributed to the package by name. An import that neither resolves nor names a workspace package, such as an alias without `tsConfig` that is not a package name, is not seen, so a check can look cleaner than the workspace is; give `tsConfig` when aliases are used.
|
|
89
89
|
- `import-cycles` follows files, so it cannot follow an import that resolves to no file. It treats packages that import each other by such names as a cycle between the packages, and finds it only when every import in the ring is of that kind; a ring that mixes such imports with imports that resolve to files is not seen until the workspace is installed and built.
|
|
90
90
|
- Type-only imports and dynamic `import()` count, because a type dependency is still a dependency.
|
|
91
|
-
-
|
|
91
|
+
- An import of a package that the importer declares only in `package.json` fields the layout's `dependencyFields` does not read (`dependencies` alone by default, so a `devDependencies` entry by default) is not judged by `import-uphill`, `import-rank-skip`, `import-cross-slice` or `import-isolated-groups`, whether it is written as the package name, a relative path or an alias. The ESLint workspace rules read the same `dependencyFields` and never see such a declaration, so a test that imports a package declared for tests is exempt from both, and widening `dependencyFields` to `['dependencies', 'devDependencies']` brings it into both. A package declared in a field that is read, in any field alongside, or not declared at all is judged, since an undeclared import is what this tool exists to catch. `import-cycles` does not apply this: a ring of imports between files is a cycle whatever the manifests declare, and a file-level cycle that runs through a test file is one only when another file imports that test file.
|
|
92
|
+
- Test and tooling files inside a package are otherwise part of the package. Use `exclude` for paths that may cross layers.
|
|
92
93
|
- dependency-cruiser supports TypeScript below 7. With a TypeScript it cannot load, or none, every import check fails with a configuration error and exit status 2 instead of reporting a clean workspace.
|
|
93
94
|
|
|
94
95
|
| Check | Verifies |
|
|
@@ -229,24 +230,24 @@ Every workflow check takes `workflows` (globs of the files to read, `.github/wor
|
|
|
229
230
|
| `workflow-skippable-jobs` | A path filter does not leave a required check pending or skip a job that no junction job reports for. | `trigger-path-filter`, `job-gated-by-path-filter` |
|
|
230
231
|
| `workflow-runner-resolution` | A custom or self-hosted runner label is not named literally in several jobs; a resolver job has a `timeout-minutes` and its output a literal hosted fallback. | `repeated-label`, `resolver-no-timeout`, `resolver-no-fallback`, `resolver-fallback-not-hosted` |
|
|
231
232
|
| `workflow-version-single-source` | A setup step does not hold a literal runtime version when `.nvmrc` or `.tool-versions` already does. | `literal-version` |
|
|
232
|
-
| `workflow-credentials` | Attestation steps have `id-token: write` and `attestations: write`; a publish that passes no token blanks the token variables, or at least requests provenance so that a fallback publish is traceable. | `attestation-permissions`, `tokenless-publish-unprotected` |
|
|
233
|
+
| `workflow-credentials` | Attestation steps have `id-token: write` and `attestations: write`; a publish that passes no token blanks the token variables it reads, or at least requests provenance so that a fallback publish is traceable. | `attestation-permissions`, `tokenless-publish-unprotected` |
|
|
233
234
|
| `workflow-release-job` | The release job is the only job granted `id-token: write`, runs in a deployment environment and checks out with `persist-credentials: false`; no job holding `id-token: write` passes an npm token from a secret. | `id-token-workflow-level`, `id-token-outside-release`, `missing-environment`, `persisted-credentials`, `token-beside-oidc` |
|
|
234
235
|
| `workflow-repository-dispatch` | A `repository_dispatch` handler does not push to the default branch, auto-merge without a required-checks rule, push or open pull requests as `GITHUB_TOKEN`, or dispatch to another repository with it. | `pushes-default-branch`, `auto-merge-without-required-checks`, `default-token-triggers-nothing`, `dispatch-other-repository-with-default-token` |
|
|
235
236
|
| `workflow-update-bot-cooldown` | Dependabot has a `cooldown` in every update entry, and Renovate sets `minimumReleaseAge`. | `dependabot-no-cooldown`, `renovate-no-minimum-release-age` |
|
|
236
|
-
| `workflow-merge-group` | A workflow that runs for `pull_request` also has a `merge_group` trigger. | `missing-trigger` |
|
|
237
|
+
| `workflow-merge-group` | A workflow that runs for `pull_request` also has a `merge_group` trigger, when the branch has a merge queue (offline: always). | `missing-trigger` |
|
|
237
238
|
| `workflow-action-pinning` | Every `uses` follows the pinning policy. | `not-pinned`, `docker-not-pinned`, `missing-ref` |
|
|
238
239
|
|
|
239
240
|
Codes are `<check>/<code>`, so `workflow-job-ordering/release-before-deploy`. The options and the limits of each check:
|
|
240
241
|
|
|
241
|
-
**`workflow-job-ordering`** takes `releaseJobs` (`release`), `deployJobs` (`deploy`), `docsDeployJobs` (`docs-deploy`), `junctionJobs` (`required-checks`), `junctionExempt` (jobs the junction job need not wait for) and `defaultBranch` (`main`). A role with no job in a workflow is not judged there. Ordering is judged through the transitive `needs` graph, but the junction job must list each job directly, because `needs.*.result` covers only direct needs; a job that itself needs the junction job is never required of it, nor is a job that never runs for a pull request (an `if` that compares `github.ref`, `github.ref_name` or `github.event_name`, mentions neither `pull_request` nor `merge_group`; in an `&&` one such operand is enough, in an `||` every operand must be one, since the merge queue is where the required check decides the merge and a disjunction can hold for a pull request through another operand), a job that needs such a job, or one named in `junctionExempt`. A documentation job's checkout is correct when its `ref` is `${{ github.event.repository.default_branch }}` or the default branch written literally; no `ref` means the triggering commit, which predates the release commit. The junction job's pass condition is read as text: it must read the results of its needs (`needs.*.result`, `needs.<job>.result` or `toJSON(needs)`, including a `join(needs.*.result, ' ')` looped over in a script) and name both `'failure'` and `'cancelled'`, or use the `re-actors/alls-green` action
|
|
242
|
+
**`workflow-job-ordering`** takes `releaseJobs` (`release`), `deployJobs` (`deploy`), `docsDeployJobs` (`docs-deploy`), `junctionJobs` (`required-checks`), `junctionExempt` (jobs the junction job need not wait for) and `defaultBranch` (`main`). A role with no job in a workflow is not judged there. Ordering is judged through the transitive `needs` graph, but the junction job must list each job directly, because `needs.*.result` covers only direct needs; a job that itself needs the junction job is never required of it, nor is a job that never runs for a pull request (an `if` that compares `github.ref`, `github.ref_name` or `github.event_name`, mentions neither `pull_request` nor `merge_group`; in an `&&` one such operand is enough, in an `||` every operand must be one, since the merge queue is where the required check decides the merge and a disjunction can hold for a pull request through another operand), a job that needs such a job, or one named in `junctionExempt`. A documentation job's checkout is correct when its `ref` is `${{ github.event.repository.default_branch }}` or the default branch written literally; no `ref` means the triggering commit, which predates the release commit. The junction job's pass condition is read as text, with each environment variable its scripts (`$NAME`, `${NAME}`) and conditions (`env.NAME`) read replaced by the expression that sets it, so `RESULT: ${{ needs.test.result }}` tested as `"$RESULT" != "success"` reads as the test of `needs.test.result` it is. It must read the results of its needs (`needs.*.result`, `needs.<job>.result` or `toJSON(needs)`, including a `join(needs.*.result, ' ')` looped over in a script) and either name both `'failure'` and `'cancelled'` or test a result for `!= 'success'`, which fails on both, or use the `re-actors/alls-green` action. A test that names `'skipped'` is reported as failing on a skip, because a skipped job is how a path filter or an `if` keeps a job out, and so is a `!= 'success'` test of a job that an `if` other than `always()`, its own or that of a job it needs, can skip unseen: unseen unless that `if` reads job outputs and the junction reads every one of them too, as a junction that tests a `has-packages` output before the result of the job skipped on it does. A job no such `if` can skip is skipped only when a job it needs fails, which the junction rightly fails on. A decision the text does not show is reported as incomplete.
|
|
242
243
|
|
|
243
244
|
**`workflow-skippable-jobs`** takes `junctionJobs` and `pathFilterActions` (`dorny/paths-filter`, `tj-actions/changed-files`, `step-security/changed-files`). A `paths` or `paths-ignore` filter on `pull_request` or `pull_request_target` skips the whole workflow, so a required check from it stays pending; it is reported when the workflow has a junction job, the sign that it produces a required check. A job whose own `if` reads the outputs of a job that runs a path filter action is skipped; it is reported unless a junction job lists it in `needs`, and the fix is to put the condition on its steps, so the job still runs and reports. Limits: a workflow without a junction job is not judged for its trigger filter; a script that computes changed paths itself is not recognised as a path filter; only the filters of the `pull_request` and `pull_request_target` triggers are read, so a `paths` filter on `push` is not reported; only `needs.<job>.outputs` in a job's own `if` is read.
|
|
244
245
|
|
|
245
|
-
**`workflow-runner-resolution`** takes `hostedLabels`, regular expressions for the labels
|
|
246
|
+
**`workflow-runner-resolution`** takes `hostedLabels`, regular expressions for the labels of hosted runners: by default GitHub's standard `ubuntu`, `windows` and `macos` images and their `-latest`, versioned, `-arm`, `-intel`, `-large`, `-xlarge` and `-slim` forms, exported as `DEFAULT_HOSTED_LABELS`. Setting it replaces the default, as every list option here does, which also lets a repository drop an image it never uses; to add a third-party hosted runner such as Blacksmith to GitHub's own, extend the default: `hostedLabels: [...DEFAULT_HOSTED_LABELS, '^blacksmith-']`. A label that matches none, or a runner group, named literally in more than one job of a workflow is reported at each such job: resolve it once in a resolver job and read it with `runs-on: ${{ fromJson(needs.<resolver>.outputs.<name>) }}`. Each resolver (the job whose output a `fromJson` in a `runs-on` reads) must have `timeout-minutes`, so a runner that never starts does not hold the jobs waiting for it for the default six hours, and its output must carry a literal fallback (`|| '["<label>"]'`) whose labels all match `hostedLabels`, since a fallback to another self-hosted label does not help when the fleet is down; the violation names each fallback label that matches no pattern. A resolver that is a call of a local reusable workflow is followed through `on.workflow_call.outputs` to the job that sets the output, and the findings are reported there; one in another repository cannot be read and is not judged. Limits: labels are compared within one file, not across files; a larger runner is named by a custom label, so it counts as one; an output's fallback is the first operand after the first of its expression's top-level `||` chain whose value is known before the run and not empty: a string literal, or `format()` of literals and of inputs of the reusable workflow the resolver is in, each read from the literal its caller passes in `with` or else from the input's `default` (so `format('["{0}"]', inputs.fallback-label)` with a literal default counts); an input passed as an expression, or with no default, is not known, and an output with no such operand has no fallback. The fallback is read as JSON; one that is not valid JSON counts as not hosted.
|
|
246
247
|
|
|
247
248
|
**`workflow-version-single-source`** recognises the setup actions for Node, Python, Go, Java, .NET, Ruby, Bun, Deno, pnpm and Terraform. A step is reported when it gives the version input a literal while `.nvmrc` (for Node) or `.tool-versions` (for any tool it lists, by its asdf name) exists in the working directory; the message names the input that reads the file. A version containing an expression, such as a matrix value, is not a literal. An alias such as `lts/*` is. Limits: only the actions listed are recognised, only the root files are looked for, and composite actions are not read.
|
|
248
249
|
|
|
249
|
-
**`workflow-credentials`**: an attestation step (`actions/attest`, `actions/attest-build-provenance`, `actions/attest-sbom`) needs `id-token: write` and `attestations: write` in the job's effective permissions. Permissions declared nowhere are the repository's default, which the file cannot show and which does not grant `id-token`, so they are reported. A job with `id-token: write` that runs a publish command (`npm`, `pnpm` or `yarn npm publish`, with options such as `-r` or `--filter <package>` before `publish`, `semantic-release`, `changeset publish`, `lerna publish`, bare or behind `pnpm`, `pnpm exec`, `yarn` or `npx`) and passes no token must set
|
|
250
|
+
**`workflow-credentials`**: an attestation step (`actions/attest`, `actions/attest-build-provenance`, `actions/attest-sbom`) needs `id-token: write` and `attestations: write` in the job's effective permissions. Permissions declared nowhere are the repository's default, which the file cannot show and which does not grant `id-token`, so they are reported. A job with `id-token: write` that runs a publish command (`npm`, `pnpm` or `yarn npm publish`, with options such as `-r` or `--filter <package>` before `publish`, `semantic-release`, `changeset publish`, `lerna publish`, bare or behind `pnpm`, `pnpm exec`, `yarn` or `npx`) and passes no token must set every token variable the publish reads to the empty string (at step, job or workflow level) or request provenance (`--provenance`, `NPM_CONFIG_PROVENANCE: true`, or `publishConfig.provenance: true` in the `package.json` of the working directory); provenance does not stop an inherited token being used, it makes that publish traceable. npm reads no token variable by name: a variable reaches it only through `${NAME}` in the value of an `.npmrc` setting, and otherwise only variables named `npm_config_*` are configuration ([npmrc](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc), [archived](https://web.archive.org/web/20260719085027/https://docs.npmjs.com/cli/v11/configuring-npm/npmrc/); [config](https://docs.npmjs.com/cli/v11/using-npm/config), [archived](https://web.archive.org/web/20260923175926/https://docs.npmjs.com/cli/v11/using-npm/config/)). So a variable is required to be blanked only where the check sees something read it, and the violation names each such variable and its reader: `NODE_AUTH_TOKEN` after an `actions/setup-node` step with `registry-url`, whose `.npmrc` refers to it; `NPM_TOKEN` for `semantic-release`, whose npm plugin (`lib/set-npmrc-auth.js` of `@semantic-release/npm`) reads it when its own exchange fails; and every variable the auth settings (`_authToken`, `_auth`, `_password`) of the `.npmrc` in the working directory refer to. A tokenless publish that nothing visible reads a token for, such as `pnpm publish` with no `registry-url` and no `.npmrc` reference, is not reported. Limits: an `.npmrc` the workflow does not show (one in the runner's home directory, or written by a script), a Yarn `.yarnrc.yml` and a token set through an `npm_config_` variable are not read; commands are read as text where a command starts, so a script file or composite action is not seen; provenance set in another `package.json` is not read; a job is tokenless only if no token variable has a non-empty value anywhere in it; a job that calls a reusable workflow is not read. `registry-url` is deliberately not reported, see below.
|
|
250
251
|
|
|
251
252
|
**`workflow-release-job`** takes `releaseJobs` (`release`, the same default as `workflow-job-ordering`). npm binds a trusted publisher to the repository and the workflow file, not to a branch ([trusted publishers](https://docs.npmjs.com/trusted-publishers), [archived](https://web.archive.org/web/20260921070919/https://docs.npmjs.com/trusted-publishers/)), so anyone who can push a branch can edit that branch's copy of the workflow, remove the `if` that keeps publishing to the default branch, and publish. The protection has to live outside the file: a deployment environment whose deployment branch policy allows only the default branch, named as the environment of the trusted publisher on npm, so an OIDC token minted anywhere else is refused. In a workflow with a release job, `id-token-workflow-level` reports workflow-level `permissions` that grant `id-token: write` (directly or through `write-all`), since every job that declares none inherits it, and `id-token-outside-release` reports any other job that declares it, a job that calls a reusable workflow included, because the OIDC identity is the publishing credential and only the release job should be able to mint it. `missing-environment` reports a release job with no `environment` (a name, or a mapping with a `name`). `persisted-credentials` reports a release job's `actions/checkout` that does not set `persist-credentials: false` literally, since the job token, a `token` input or an `ssh-key` would otherwise stay in git config for every later step, the dependency install and third-party actions included. There is no exception for a release that pushes: semantic-release pushes to its `repositoryUrl` as written and, only when that fails, to the same URL with `GITHUB_TOKEN` or `GH_TOKEN` from the environment embedded (`lib/get-git-auth-url.js` of `semantic-release`), so it never needs the checkout's credentials. A deploy-key release job, the shape ExaDev's own release jobs use, checks out with `persist-credentials: false`, writes the key to a file in the step before the release, and hands it to git only through the `GIT_SSH_COMMAND` in the `env` of the steps that push; it is accepted as written, and the clean fixture holds one. In every workflow, `token-beside-oidc` reports a job whose effective permissions grant `id-token: write` and whose workflow, job or step `env` sets `NODE_AUTH_TOKEN` or `NPM_TOKEN` from the `secrets` context: a long-lived token beside the OIDC identity is a second way to publish that no environment protection or trusted publisher setting covers. Limits: the environment's protection rules and the npm trusted publisher settings cannot be seen in the file and are not read (`missing-environment` checks only that an environment is named); a release job that calls a reusable workflow is judged only for `id-token`, since `environment` and the steps belong to the called workflow; a token passed through `with`, written to an `.npmrc` by a script, or set inside a composite action is not seen.
|
|
252
253
|
|
|
@@ -256,7 +257,7 @@ What zizmor already covers, and what this check adds, as observed with zizmor 1.
|
|
|
256
257
|
|
|
257
258
|
**`workflow-update-bot-cooldown`** takes `dependabot` (`.github/dependabot.yml`, else `.yaml`) and `renovate` (`renovate.json`, `.renovaterc.json`, `.renovaterc` and `.github/renovate.json`, the first that exists). Every Dependabot `updates` entry needs a `cooldown` with `default-days` or one of the `semver-*-days` above zero. Renovate needs `minimumReleaseAge` at the top level or in a `packageRules` entry, as a duration above zero (`0 days` and `null` wait for nothing). Limits: Dependabot's cooldown does not apply to security updates; a Renovate preset is not resolved (an `extends` entry counts only when its name contains `minimumReleaseAge`); a Renovate config is read as JSON with comments and trailing commas, so other JSON5 syntax and a `renovate` key in `package.json` are not read. A repository with neither bot has nothing to report; a file named in the options that does not exist fails the run.
|
|
258
259
|
|
|
259
|
-
**`workflow-merge-group`** reports a workflow with a `pull_request` trigger and no `merge_group` trigger: GitHub starts no run of it for a queued pull request, so its required checks never report. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too; narrow it with `workflows` and `exclude`. `pull_request_target` is left out, since it is not a place for required checks, and a workflow with no `pull_request` trigger is not judged. Limits: a `merge_group` trigger takes only `branches` filters (`paths` is a filter of `push`, `pull_request` and `pull_request_target`), so a workflow path-filtered for pull requests still runs in the queue once it has the trigger, and the `branches` filter of a `merge_group` trigger is not read ([workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)).
|
|
260
|
+
**`workflow-merge-group`** reports a workflow with a `pull_request` trigger and no `merge_group` trigger: GitHub starts no run of it for a queued pull request, so its required checks never report. Whether the branch has a merge queue at all is a repository setting. When the run is given a GitHub client (`runChecks({ github })`, `--settings`), the check reads the rules of the branch, as the `settings-*` checks do and with the same `repository` and `branch` options, once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies; without a queue no `merge_group` event is sent, so the trigger is not needed. Offline it cannot tell, and it reports every such workflow, as it always has. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too; narrow it with `workflows` and `exclude`. `pull_request_target` is left out, since it is not a place for required checks, and a workflow with no `pull_request` trigger is not judged. Limits: a `merge_group` trigger takes only `branches` filters (`paths` is a filter of `push`, `pull_request` and `pull_request_target`), so a workflow path-filtered for pull requests still runs in the queue once it has the trigger, and the `branches` filter of a `merge_group` trigger is not read ([workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)); with a client only rulesets are read, so a merge queue configured through classic branch protection is not seen and nothing is reported.
|
|
260
261
|
|
|
261
262
|
**`workflow-action-pinning`** encodes a pinning policy, and does not choose a side silently: pinning to a commit SHA protects against a moved tag or branch, and a ref lets the owner of a shared workflow roll out a fix without every caller changing. The default follows the reasoning that you do not control a third party but do control your own organisation: an action or reusable workflow from outside `organisations` must be pinned by full 40-character commit SHA (`thirdParty: 'sha'`), a Docker image by digest, and one owned by a listed organisation may use any ref (`sameOrganisation: 'ref'`). With no `organisations` configured, nothing is relaxed. `thirdParty` and `sameOrganisation` each take `sha` or `ref`, so either side of the policy can be flipped, `organisations` lists the owners that count as the same organisation, and `allow` exempts `owner/repository` or `owner/*` (for example `actions/*`). Local actions and workflows (`./...`) are not judged. Limits: it checks the form of the ref, not that the SHA belongs to the repository or to a release; composite actions are not read.
|
|
262
263
|
|
|
@@ -268,13 +269,13 @@ What zizmor already covers, and what this check adds, as observed with zizmor 1.
|
|
|
268
269
|
|
|
269
270
|
### Repository settings
|
|
270
271
|
|
|
271
|
-
The `settings-*` checks read repository settings through the GitHub API, so they need a token and are kept apart from the file checks. They run only when the run is given a GitHub client: `runChecks({ github })` in the library, `--settings` on the command line (which reads the token from `GITHUB_TOKEN` or `GH_TOKEN`). Without one they are left out, even when enabled, so the same configuration runs offline; naming one with `--check` or `checks` without a client, or enabling only settings checks and running offline, fails the run instead of reporting a pass. The client is an interface (`GitHubClient`, with `repository` and `branchRules`); `createGitHubClient({ token })` is the implementation over the REST API (GitHub reports the merge-method settings only to a token with write access to the repository, so `repository` fails with a `ConformanceError` for a read-only token), and a test or another host can supply its own. A violation's `file` is the repository, `owner/name`.
|
|
272
|
+
The `settings-*` checks read repository settings through the GitHub API, so they need a token and are kept apart from the file checks. They run only when the run is given a GitHub client: `runChecks({ github })` in the library, `--settings` on the command line (which reads the token from `GITHUB_TOKEN` or `GH_TOKEN`). Without one they are left out, even when enabled, so the same configuration runs offline; naming one with `--check` or `checks` without a client, or enabling only settings checks and running offline, fails the run instead of reporting a pass. The client is an interface (`GitHubClient`, with `repository` and `branchRules`, whose `BranchRules` result also says whether a `required_linear_history` and a `merge_queue` rule apply); `workflow-merge-group` reads it too when it is given, and runs without it; `createGitHubClient({ token })` is the implementation over the REST API (GitHub reports the merge-method settings only to a token with write access to the repository, so `repository` fails with a `ConformanceError` for a read-only token), and a test or another host can supply its own. A violation's `file` is the repository, `owner/name`.
|
|
272
273
|
|
|
273
274
|
All three take `repository` (`owner/name`; the `origin` remote of the working directory, as configured before any `insteadOf` rewriting, when omitted) and `branch` (the repository's default branch). Rules are the effective rules of the branch from `GET /repos/{owner}/{repo}/rules/branches/{branch}` ([docs](https://docs.github.com/en/rest/repos/rules), [archived](https://web.archive.org/web/20260924024404/https://docs.github.com/en/rest/repos/rules)), which covers rulesets and not classic branch protection.
|
|
274
275
|
|
|
275
276
|
| Check | Verifies | Codes |
|
|
276
277
|
|---|---|---|
|
|
277
|
-
| `settings-merge-methods` | Only one merge method (`allowed`, `rebase` by default) is available: the repository allows it
|
|
278
|
+
| `settings-merge-methods` | Only one merge method (`allowed`, `rebase` by default) is available: the repository allows it, no `pull_request` rule of the branch leaves it out and, for merge commits, no `required_linear_history` rule of the branch blocks them. | `method-enabled`, `method-unavailable` |
|
|
278
279
|
| `settings-required-checks` | A `required_status_checks` rule requires the check of each junction job (`junctionJobs`, found in the workflows selected by `workflows` and `exclude`; the check name is the job's `name`, else its id) and some rule sets strict mode. | `no-junction-job`, `no-required-checks`, `required-check-missing`, `not-strict`, `rules-unavailable` |
|
|
279
280
|
| `settings-review-thread-resolution` | A `pull_request` rule requires review conversations to be resolved. | `not-required`, `rules-unavailable` |
|
|
280
281
|
|
|
@@ -327,7 +328,7 @@ it('conforms to the workspace layout', async () => {
|
|
|
327
328
|
});
|
|
328
329
|
```
|
|
329
330
|
|
|
330
|
-
Every check is also exported as a function taking `{ cwd, options }` (and `layout` for the import checks): `aggregateMappers`, `codecPairs`, `commandTypes`, `derivedTypes`, `importUphill`, `importRankSkip`, `importCrossSlice`, `importIsolatedGroups`, `importCycles`, `instructionSymlinks`, `singleStorybook`, `commitTypes`, `enginesFloor`, `dockerfilePackageManager`, `migrationsDirectory`, `eslint`, the workflow checks (`workflowJobOrdering`, `workflowSkippableJobs`, `workflowRunnerResolution`, `workflowVersionSingleSource`, `workflowCredentials`, `workflowReleaseJob`, `workflowRepositoryDispatch`, `workflowUpdateBotCooldown`, `workflowMergeGroup` and `workflowActionPinning`) and, taking `{ cwd, options, github }`, the settings checks (`settingsMergeMethods`, `settingsRequiredChecks` and `settingsReviewThreadResolution`). These take options and a layout as already validated and do not check them again; go through `runChecks` for validation. `parseWorkflow` and `loadWorkflows` expose the workflow model the workflow checks read. A check is a function that returns `Promise<readonly Violation[]>`, where a `Violation` is `{ code, message, file, location? }`.
|
|
331
|
+
Every check is also exported as a function taking `{ cwd, options }` (and `layout` for the import checks): `aggregateMappers`, `codecPairs`, `commandTypes`, `derivedTypes`, `importUphill`, `importRankSkip`, `importCrossSlice`, `importIsolatedGroups`, `importCycles`, `instructionSymlinks`, `singleStorybook`, `commitTypes`, `enginesFloor`, `dockerfilePackageManager`, `migrationsDirectory`, `eslint`, the workflow checks (`workflowJobOrdering`, `workflowSkippableJobs`, `workflowRunnerResolution`, `workflowVersionSingleSource`, `workflowCredentials`, `workflowReleaseJob`, `workflowRepositoryDispatch`, `workflowUpdateBotCooldown`, `workflowMergeGroup` and `workflowActionPinning`) and, taking `{ cwd, options, github }`, the settings checks (`settingsMergeMethods`, `settingsRequiredChecks` and `settingsReviewThreadResolution`); `workflowMergeGroup` takes `github` too, optionally. These take options and a layout as already validated and do not check them again; go through `runChecks` for validation. `parseWorkflow` and `loadWorkflows` expose the workflow model the workflow checks read. A check is a function that returns `Promise<readonly Violation[]>`, where a `Violation` is `{ code, message, file, location? }`.
|
|
331
332
|
|
|
332
333
|
### Proving a handler map is exhaustive
|
|
333
334
|
|