workspace-conformance 3.0.0 → 4.0.1
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 +9 -7
- package/dist/cli.js +47 -33
- package/dist/index.cjs +27 -27
- package/dist/index.d.cts +31 -10
- package/dist/index.d.ts +31 -10
- package/dist/index.js +27 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# workspace-conformance
|
|
2
2
|
|
|
3
|
-
Conformance checks for a pnpm workspace that ESLint cannot express. Type-graph checks run on [ts-morph](https://ts-morph.com), import-graph checks run on [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) with rules generated from the shared `layout` section of [`@exadev/config`](https://github.com/ExaDev/config), file-tree and cross-config checks compare two files or read git state, workflow checks read GitHub Actions files, and settings checks read the repository's settings through the GitHub API. Each check reports violations with a stable code, a message
|
|
3
|
+
Conformance checks for a pnpm workspace that ESLint cannot express. Type-graph checks run on [ts-morph](https://ts-morph.com), import-graph checks run on [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) with rules generated from the shared `layout` section of [`@exadev/config`](https://github.com/ExaDev/config), file-tree and cross-config checks compare two files or read git state, workflow checks read GitHub Actions files, and settings checks read the repository's settings through the GitHub API. Each check reports violations with a stable code, a message and what the violation is about: a file and, where it has one, a position, or for the settings checks the repository.
|
|
4
4
|
|
|
5
5
|
It complements the workspace architecture rules of [`@exadev/eslint-config`](https://github.com/ExaDev/eslint-config), it does not replace them. Those rules run in the editor and check what `package.json` files declare (`no-uphill-dependency` and `no-dependency-cycle`), incrementally and one file at a time. This tool checks what the code does: the import statements that actually cross packages, whether written as a package name, a relative path or a path alias, and everything that needs two files, the type checker or git. Both read the same `layout` section, so one description of the workspace drives both. Naming (`package-name-mirrors-path`) and per-file lint stay in ESLint.
|
|
6
6
|
|
|
@@ -257,7 +257,7 @@ What zizmor already covers, and what this check adds, as observed with zizmor 1.
|
|
|
257
257
|
|
|
258
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.
|
|
259
259
|
|
|
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
|
+
**`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. When GitHub refuses the rules because the plan of the repository has no rulesets (the refusal the `settings-*` checks report as `rules-unavailable`, and `RulesetsUnavailableError` from a client of your own), nothing is reported either, since the repository cannot have a merge queue: GitHub offers merge queues only in public repositories owned by an organisation and in private ones of organisations on GitHub Enterprise Cloud, which have rulesets ([managing a merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue), [archived](https://web.archive.org/web/20261002170053/https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue)); any other refusal fails the run.
|
|
261
261
|
|
|
262
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.
|
|
263
263
|
|
|
@@ -269,7 +269,7 @@ What zizmor already covers, and what this check adds, as observed with zizmor 1.
|
|
|
269
269
|
|
|
270
270
|
### Repository settings
|
|
271
271
|
|
|
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
|
|
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 settings violation is about the repository, not a file in it, so it has `repository` (`owner/name`) in place of `file` and no `location`.
|
|
273
273
|
|
|
274
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.
|
|
275
275
|
|
|
@@ -293,9 +293,9 @@ workspace-conformance check [--cwd <directory>] [--check <name>]... [--list] [--
|
|
|
293
293
|
|
|
294
294
|
| Format | Output |
|
|
295
295
|
|---|---|
|
|
296
|
-
| `text` | The default. Violations on standard error as `file:line:column: code: message` (`file: code: message` for one without a position), a check's notes on standard output as `check: note`, and on a clean run a line naming the checks that ran. |
|
|
297
|
-
| `json` | One JSON document on standard output: the `RunResult` of `runChecks`, so `results` (each `{ check, violations, notes }`), `violations` (the library's `{ code, message, file, location? }` objects, with `file` relative to `--cwd`) and `exitCode`. |
|
|
298
|
-
| `github` | A GitHub Actions error annotation per violation on standard output, `::error file=<file>,line=<line>,col=<column>,title=<code>::<message>` (no `line` and `col` without a position), and a `::notice` per note. The runner turns each into an annotation on the file ([workflow commands](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message), [archived](https://web.archive.org/web/20260918001346/https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)). `file` is relative to the directory the command runs in, the repository root in a workflow step unless it sets `working-directory`, so `--cwd` may name a subdirectory. Reserved characters are percent-encoded as `@actions/core` encodes them (`%`, carriage return and line feed everywhere, and `:` and `,` in properties; [`command.ts`](https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts), [archived](https://web.archive.org/web/20250901085906/https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts)). A settings violation names the repository rather than a file, so its annotation is attached to no file
|
|
296
|
+
| `text` | The default. Violations on standard error as `file:line:column: code: message` (`file: code: message` for one without a position, `owner/name: code: message` for a settings violation), a check's notes on standard output as `check: note`, and on a clean run a line naming the checks that ran. |
|
|
297
|
+
| `json` | One JSON document on standard output: the `RunResult` of `runChecks`, so `results` (each `{ check, violations, notes }`), `violations` (the library's `{ code, message, file, location? }` objects, with `file` relative to `--cwd`, and `{ code, message, repository }` for a settings violation) and `exitCode`. |
|
|
298
|
+
| `github` | A GitHub Actions error annotation per violation on standard output, `::error file=<file>,line=<line>,col=<column>,title=<code>::<message>` (no `line` and `col` without a position), and a `::notice` per note. The runner turns each into an annotation on the file ([workflow commands](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message), [archived](https://web.archive.org/web/20260918001346/https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)). `file` is relative to the directory the command runs in, the repository root in a workflow step unless it sets `working-directory`, so `--cwd` may name a subdirectory. Reserved characters are percent-encoded as `@actions/core` encodes them (`%`, carriage return and line feed everywhere, and `:` and `,` in properties; [`command.ts`](https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts), [archived](https://web.archive.org/web/20250901085906/https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts)). A settings violation names the repository rather than a file, so its annotation has no `file` and is attached to no file: `::error title=<code>::<message>`. |
|
|
299
299
|
|
|
300
300
|
When the checks cannot run (exit status 2), the reason is printed to standard error as text in every format.
|
|
301
301
|
|
|
@@ -328,7 +328,7 @@ it('conforms to the workspace layout', async () => {
|
|
|
328
328
|
});
|
|
329
329
|
```
|
|
330
330
|
|
|
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
|
+
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 either a `FileViolation`, `{ code, message, file, location? }`, or, from a settings check, a `RepositoryViolation`, `{ code, message, repository }`; exactly one of `file` and `repository` is present, so `violation.file === undefined` tells them apart.
|
|
332
332
|
|
|
333
333
|
### Proving a handler map is exhaustive
|
|
334
334
|
|
|
@@ -379,6 +379,8 @@ Each check has a fixture that violates it and one that does not, under `test/fix
|
|
|
379
379
|
|
|
380
380
|
Every push to `main` runs semantic-release once the commitlint, lint, typecheck and package jobs pass. It derives the next version from the Conventional Commits since the last tag, publishes to npm through trusted publishing with provenance, creates the tag and the GitHub Release, and pushes a `chore(release)` commit that updates `CHANGELOG.md` and `package.json`. `main` is protected by a ruleset that requires the Required Checks status, a branch up to date with `main`, linear history, and forbids deletion and non-fast-forward pushes. Pull requests therefore reach `main` by rebase merge once checks pass, and the release commit and tag are pushed over SSH with the repository deploy key, which the ruleset lets bypass it (as it does organisation admins). The key exists on disk only for the release step.
|
|
381
381
|
|
|
382
|
+
CI also lints the workflows themselves with [actionlint](https://github.com/rhysd/actionlint) (and the shellcheck it runs on every `run:` script) and [zizmor](https://docs.zizmor.sh), each at a pinned version and configured in `.github/actionlint.yaml` and `.github/zizmor.yml`; both are part of the Required Checks status.
|
|
383
|
+
|
|
382
384
|
CI selects its runner with `ExaDev/runner-fallback-action` (self-hosted fleet first, Blacksmith as fallback). The release job stays on a GitHub-hosted runner because npm trusted publishing needs one, and publishes with provenance through OIDC, with no stored token. The repository deploy key (the `RELEASE_DEPLOY_KEY` secret) is a ruleset bypass actor, so it is written to disk by the step immediately before the release step, and only the release step's own environment carries `GIT_SSH_COMMAND` (absolute `/usr/bin/ssh`, no ssh config, a pinned GitHub host key, only the ssh protocol allowed); the key is deleted afterwards. The build and package checks run before the key exists and the release step sets `NPM_CONFIG_IGNORE_SCRIPTS`, so no dev dependency runs beside the key, the job's actions are pinned by commit SHA, and the checkout, the action setup and the dependency install run with no write-capable credential in git config. That limits what code from those steps can read; it is not a sandbox, since a process started earlier in the job still runs as the same user. `release.config.ts` sets an SSH `repositoryUrl`, so semantic-release pushes the release commit and tag over SSH. The default `GITHUB_TOKEN` cannot bypass a branch ruleset on `main`, and semantic-release falls back to pushing as it only when the SSH push fails, so the job dry-run pushes first and fails before publishing if the key does not authenticate, and refuses the https URL outright. Commits follow [Conventional Commits](https://www.conventionalcommits.org); semantic-release derives the version from them.
|
|
383
385
|
|
|
384
386
|
`README.md`, `AGENTS.md` and `CLAUDE.md` are one file: the two instruction files are tracked symlinks to this one.
|
package/dist/cli.js
CHANGED
|
@@ -1323,9 +1323,9 @@ async function branchOf(github, slug, options) {
|
|
|
1323
1323
|
return options.branch ?? (await github.repository(slug)).defaultBranch;
|
|
1324
1324
|
}
|
|
1325
1325
|
/**
|
|
1326
|
-
*
|
|
1326
|
+
* The repository a violation of a settings check names, `owner/name`.
|
|
1327
1327
|
*/
|
|
1328
|
-
function
|
|
1328
|
+
function repositoryName(slug) {
|
|
1329
1329
|
return `${slug.owner}/${slug.name}`;
|
|
1330
1330
|
}
|
|
1331
1331
|
/**
|
|
@@ -1343,8 +1343,8 @@ async function branchRulesOf(github, slug, options) {
|
|
|
1343
1343
|
/**
|
|
1344
1344
|
* The message of a `rules-unavailable` violation: the branch rules of the repository cannot be read or enforced on its plan, so `what` cannot be required.
|
|
1345
1345
|
*/
|
|
1346
|
-
function rulesUnavailableMessage(
|
|
1347
|
-
return `the branch rules of ${
|
|
1346
|
+
function rulesUnavailableMessage(repository, what, githubMessage) {
|
|
1347
|
+
return `the branch rules of ${repository} cannot be read or enforced on its plan, so ${what} cannot be required (GitHub: ${githubMessage})`;
|
|
1348
1348
|
}
|
|
1349
1349
|
//#endregion
|
|
1350
1350
|
//#region src/checks/settings-merge-methods.ts
|
|
@@ -1370,7 +1370,7 @@ const METHOD_NAMES = {
|
|
|
1370
1370
|
*/
|
|
1371
1371
|
const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
1372
1372
|
const slug = await repositoryOf(cwd, options);
|
|
1373
|
-
const
|
|
1373
|
+
const repository = repositoryName(slug);
|
|
1374
1374
|
const settings = await github.repository(slug);
|
|
1375
1375
|
const read = await branchRulesOf(github, slug, options);
|
|
1376
1376
|
const pullRequests = "unavailable" in read ? [] : read.rules.pullRequests;
|
|
@@ -1385,13 +1385,13 @@ const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
|
1385
1385
|
const available = METHODS.filter((method) => repositoryAllows[method] && rulesAllow(method));
|
|
1386
1386
|
const violations = available.filter((method) => method !== wanted).map((method) => ({
|
|
1387
1387
|
code: "settings-merge-methods/method-enabled",
|
|
1388
|
-
message: `${
|
|
1389
|
-
|
|
1388
|
+
message: `${repository} allows ${METHOD_NAMES[method]}; only ${METHOD_NAMES[wanted]} should be allowed`,
|
|
1389
|
+
repository
|
|
1390
1390
|
}));
|
|
1391
1391
|
if (!available.includes(wanted)) violations.push({
|
|
1392
1392
|
code: "settings-merge-methods/method-unavailable",
|
|
1393
|
-
message: `${
|
|
1394
|
-
|
|
1393
|
+
message: `${repository} does not allow ${METHOD_NAMES[wanted]}`,
|
|
1394
|
+
repository
|
|
1395
1395
|
});
|
|
1396
1396
|
return violations;
|
|
1397
1397
|
};
|
|
@@ -1789,12 +1789,12 @@ const REGISTRY_TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
|
|
|
1789
1789
|
*/
|
|
1790
1790
|
const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
1791
1791
|
const slug = await repositoryOf(cwd, options);
|
|
1792
|
-
const
|
|
1792
|
+
const repository = repositoryName(slug);
|
|
1793
1793
|
const read = await branchRulesOf(github, slug, options);
|
|
1794
1794
|
if ("unavailable" in read) return [{
|
|
1795
1795
|
code: "settings-required-checks/rules-unavailable",
|
|
1796
|
-
message: rulesUnavailableMessage(
|
|
1797
|
-
|
|
1796
|
+
message: rulesUnavailableMessage(repository, "a status check", read.unavailable),
|
|
1797
|
+
repository
|
|
1798
1798
|
}];
|
|
1799
1799
|
const { rules } = read;
|
|
1800
1800
|
const junctions = (await loadWorkflows(cwd, options)).flatMap((workflow) => jobsNamed(workflow, options.junctionJobs ?? DEFAULT_JUNCTION_JOBS).map((job) => ({
|
|
@@ -1805,26 +1805,26 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
1805
1805
|
if (junctions.length === 0) violations.push({
|
|
1806
1806
|
code: "settings-required-checks/no-junction-job",
|
|
1807
1807
|
message: "no workflow has a junction job, so there is no check that reports for the whole workflow to require",
|
|
1808
|
-
|
|
1808
|
+
repository
|
|
1809
1809
|
});
|
|
1810
1810
|
if (rules.requiredStatusChecks.length === 0) {
|
|
1811
1811
|
violations.push({
|
|
1812
1812
|
code: "settings-required-checks/no-required-checks",
|
|
1813
|
-
message: `no rule requires status checks on the branch of ${
|
|
1814
|
-
|
|
1813
|
+
message: `no rule requires status checks on the branch of ${repository}`,
|
|
1814
|
+
repository
|
|
1815
1815
|
});
|
|
1816
1816
|
return violations;
|
|
1817
1817
|
}
|
|
1818
1818
|
const required = new Set(rules.requiredStatusChecks.flatMap((rule) => rule.contexts));
|
|
1819
1819
|
for (const { workflow, name } of junctions.filter((junction) => !required.has(junction.name))) violations.push({
|
|
1820
1820
|
code: "settings-required-checks/required-check-missing",
|
|
1821
|
-
message: `${
|
|
1822
|
-
|
|
1821
|
+
message: `${repository} does not require the check '${name}' of ${workflow.file}`,
|
|
1822
|
+
repository
|
|
1823
1823
|
});
|
|
1824
1824
|
if (!rules.requiredStatusChecks.some((rule) => rule.strict)) violations.push({
|
|
1825
1825
|
code: "settings-required-checks/not-strict",
|
|
1826
|
-
message: `${
|
|
1827
|
-
|
|
1826
|
+
message: `${repository} does not require branches to be up to date before merging`,
|
|
1827
|
+
repository
|
|
1828
1828
|
});
|
|
1829
1829
|
return violations;
|
|
1830
1830
|
};
|
|
@@ -1840,16 +1840,16 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
1840
1840
|
const settingsReviewThreadResolution = async ({ cwd, options, github }) => {
|
|
1841
1841
|
const slug = await repositoryOf(cwd, options);
|
|
1842
1842
|
const read = await branchRulesOf(github, slug, options);
|
|
1843
|
-
const
|
|
1843
|
+
const repository = repositoryName(slug);
|
|
1844
1844
|
if ("unavailable" in read) return [{
|
|
1845
1845
|
code: "settings-review-thread-resolution/rules-unavailable",
|
|
1846
|
-
message: rulesUnavailableMessage(
|
|
1847
|
-
|
|
1846
|
+
message: rulesUnavailableMessage(repository, "resolving review conversations", read.unavailable),
|
|
1847
|
+
repository
|
|
1848
1848
|
}];
|
|
1849
1849
|
return read.rules.pullRequests.some((rule) => rule.requiredReviewThreadResolution) ? [] : [{
|
|
1850
1850
|
code: "settings-review-thread-resolution/not-required",
|
|
1851
|
-
message: `no rule requires review conversations to be resolved before merging on the branch of ${
|
|
1852
|
-
|
|
1851
|
+
message: `no rule requires review conversations to be resolved before merging on the branch of ${repository}`,
|
|
1852
|
+
repository
|
|
1853
1853
|
}];
|
|
1854
1854
|
};
|
|
1855
1855
|
//#endregion
|
|
@@ -2277,7 +2277,7 @@ const workflowJobOrdering = async ({ cwd, options }) => (await loadWorkflows(cwd
|
|
|
2277
2277
|
*
|
|
2278
2278
|
* A workflow is taken to produce required checks when it is triggered by `pull_request`; `pull_request_target` is left out, since it runs trusted code against untrusted input and is not a place for required checks. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too: narrow the check with `workflows` and `exclude`. A `merge_group` trigger on a workflow with no `pull_request` trigger is not judged.
|
|
2279
2279
|
*
|
|
2280
|
-
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. Offline it cannot tell, so it reports every such workflow.
|
|
2280
|
+
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. A repository whose plan has no rulesets (the client rejects with `RulesetsUnavailableError`) has no merge queue either, since GitHub offers merge queues only in public repositories of an organisation and in private ones on GitHub Enterprise Cloud, both of which have rulesets, so nothing is reported for it; any other refusal fails the run. Offline it cannot tell, so it reports every such workflow.
|
|
2281
2281
|
*
|
|
2282
2282
|
* 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; the `branches` filter of a `merge_group` trigger is not read; only rulesets are read, so a queue configured through classic branch protection is not seen and, with a client, nothing is reported for it.
|
|
2283
2283
|
*/
|
|
@@ -2287,8 +2287,8 @@ const workflowMergeGroup = async ({ cwd, options, github }) => {
|
|
|
2287
2287
|
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)];
|
|
2288
2288
|
});
|
|
2289
2289
|
if (violations.length === 0 || github === void 0) return violations;
|
|
2290
|
-
const
|
|
2291
|
-
return
|
|
2290
|
+
const branch = await branchRulesOf(github, await repositoryOf(cwd, options), options);
|
|
2291
|
+
return "rules" in branch && branch.rules.mergeQueue ? violations : [];
|
|
2292
2292
|
};
|
|
2293
2293
|
//#endregion
|
|
2294
2294
|
//#region src/checks/workflow-release-job.ts
|
|
@@ -4376,8 +4376,15 @@ Exit status: ${String(EXIT_CODES.clean)} when nothing is found, ${String(EXIT_CO
|
|
|
4376
4376
|
function messageOf(error) {
|
|
4377
4377
|
return error instanceof Error ? error.message : String(error);
|
|
4378
4378
|
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Where a violation is, as text: the repository a violation of the settings is about, else the file and, when the violation has one, its position.
|
|
4381
|
+
*/
|
|
4382
|
+
function placeOf(violation) {
|
|
4383
|
+
if (violation.file === void 0) return violation.repository;
|
|
4384
|
+
return violation.location === void 0 ? violation.file : `${violation.file}:${String(violation.location.line)}:${String(violation.location.column)}`;
|
|
4385
|
+
}
|
|
4379
4386
|
function formatViolation(violation) {
|
|
4380
|
-
return `${
|
|
4387
|
+
return `${placeOf(violation)}: ${violation.code}: ${violation.message}\n`;
|
|
4381
4388
|
}
|
|
4382
4389
|
/**
|
|
4383
4390
|
* A workflow command property value with the characters the runner reserves percent-encoded, as `escapeProperty` of `@actions/core` encodes them.
|
|
@@ -4392,11 +4399,15 @@ function escapeData(value) {
|
|
|
4392
4399
|
return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
4393
4400
|
}
|
|
4394
4401
|
/**
|
|
4395
|
-
* The GitHub Actions `error` workflow command for a violation at `file
|
|
4402
|
+
* The GitHub Actions `error` workflow command for a violation, with its code as the title. A violation about a file is placed at its `file`, which must be relative to the directory the job runs in (that is how the runner places an annotation), and at its position when it has one; a violation about the repository names no file, so its annotation is attached to none.
|
|
4396
4403
|
*/
|
|
4397
|
-
function githubAnnotation(violation
|
|
4398
|
-
const
|
|
4399
|
-
|
|
4404
|
+
function githubAnnotation(violation) {
|
|
4405
|
+
const properties = [`title=${escapeProperty(violation.code)}`];
|
|
4406
|
+
if (violation.file !== void 0) {
|
|
4407
|
+
const position = violation.location === void 0 ? [] : [`line=${String(violation.location.line)}`, `col=${String(violation.location.column)}`];
|
|
4408
|
+
properties.unshift(`file=${escapeProperty(violation.file)}`, ...position);
|
|
4409
|
+
}
|
|
4410
|
+
return `::error ${properties.join(",")}::${escapeData(violation.message)}\n`;
|
|
4400
4411
|
}
|
|
4401
4412
|
function formatOf(value) {
|
|
4402
4413
|
if (value === void 0) return "text";
|
|
@@ -4415,7 +4426,10 @@ function printResult(result, format, cwd, output) {
|
|
|
4415
4426
|
return;
|
|
4416
4427
|
}
|
|
4417
4428
|
for (const entry of result.results) for (const note of entry.notes) output.stdout(format === "github" ? `::notice title=${escapeProperty(entry.check)}::${escapeData(note)}\n` : `${entry.check}: ${note}\n`);
|
|
4418
|
-
for (const violation of result.violations) if (format === "github") output.stdout(githubAnnotation(violation
|
|
4429
|
+
for (const violation of result.violations) if (format === "github") output.stdout(githubAnnotation(violation.file !== void 0 ? {
|
|
4430
|
+
...violation,
|
|
4431
|
+
file: relativePosix(process.cwd(), resolve(cwd, violation.file))
|
|
4432
|
+
} : violation));
|
|
4419
4433
|
else output.stderr(formatViolation(violation));
|
|
4420
4434
|
if (result.violations.length === 0) output.stdout(summaryOf(result));
|
|
4421
4435
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -2320,9 +2320,9 @@ async function branchOf(github, slug, options) {
|
|
|
2320
2320
|
return options.branch ?? (await github.repository(slug)).defaultBranch;
|
|
2321
2321
|
}
|
|
2322
2322
|
/**
|
|
2323
|
-
*
|
|
2323
|
+
* The repository a violation of a settings check names, `owner/name`.
|
|
2324
2324
|
*/
|
|
2325
|
-
function
|
|
2325
|
+
function repositoryName(slug) {
|
|
2326
2326
|
return `${slug.owner}/${slug.name}`;
|
|
2327
2327
|
}
|
|
2328
2328
|
/**
|
|
@@ -2340,8 +2340,8 @@ async function branchRulesOf(github, slug, options) {
|
|
|
2340
2340
|
/**
|
|
2341
2341
|
* The message of a `rules-unavailable` violation: the branch rules of the repository cannot be read or enforced on its plan, so `what` cannot be required.
|
|
2342
2342
|
*/
|
|
2343
|
-
function rulesUnavailableMessage(
|
|
2344
|
-
return `the branch rules of ${
|
|
2343
|
+
function rulesUnavailableMessage(repository, what, githubMessage) {
|
|
2344
|
+
return `the branch rules of ${repository} cannot be read or enforced on its plan, so ${what} cannot be required (GitHub: ${githubMessage})`;
|
|
2345
2345
|
}
|
|
2346
2346
|
//#endregion
|
|
2347
2347
|
//#region src/checks/settings-merge-methods.ts
|
|
@@ -2367,7 +2367,7 @@ const METHOD_NAMES = {
|
|
|
2367
2367
|
*/
|
|
2368
2368
|
const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
2369
2369
|
const slug = await repositoryOf(cwd, options);
|
|
2370
|
-
const
|
|
2370
|
+
const repository = repositoryName(slug);
|
|
2371
2371
|
const settings = await github.repository(slug);
|
|
2372
2372
|
const read = await branchRulesOf(github, slug, options);
|
|
2373
2373
|
const pullRequests = "unavailable" in read ? [] : read.rules.pullRequests;
|
|
@@ -2382,13 +2382,13 @@ const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
|
2382
2382
|
const available = METHODS.filter((method) => repositoryAllows[method] && rulesAllow(method));
|
|
2383
2383
|
const violations = available.filter((method) => method !== wanted).map((method) => ({
|
|
2384
2384
|
code: "settings-merge-methods/method-enabled",
|
|
2385
|
-
message: `${
|
|
2386
|
-
|
|
2385
|
+
message: `${repository} allows ${METHOD_NAMES[method]}; only ${METHOD_NAMES[wanted]} should be allowed`,
|
|
2386
|
+
repository
|
|
2387
2387
|
}));
|
|
2388
2388
|
if (!available.includes(wanted)) violations.push({
|
|
2389
2389
|
code: "settings-merge-methods/method-unavailable",
|
|
2390
|
-
message: `${
|
|
2391
|
-
|
|
2390
|
+
message: `${repository} does not allow ${METHOD_NAMES[wanted]}`,
|
|
2391
|
+
repository
|
|
2392
2392
|
});
|
|
2393
2393
|
return violations;
|
|
2394
2394
|
};
|
|
@@ -2786,12 +2786,12 @@ const REGISTRY_TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
|
|
|
2786
2786
|
*/
|
|
2787
2787
|
const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
2788
2788
|
const slug = await repositoryOf(cwd, options);
|
|
2789
|
-
const
|
|
2789
|
+
const repository = repositoryName(slug);
|
|
2790
2790
|
const read = await branchRulesOf(github, slug, options);
|
|
2791
2791
|
if ("unavailable" in read) return [{
|
|
2792
2792
|
code: "settings-required-checks/rules-unavailable",
|
|
2793
|
-
message: rulesUnavailableMessage(
|
|
2794
|
-
|
|
2793
|
+
message: rulesUnavailableMessage(repository, "a status check", read.unavailable),
|
|
2794
|
+
repository
|
|
2795
2795
|
}];
|
|
2796
2796
|
const { rules } = read;
|
|
2797
2797
|
const junctions = (await loadWorkflows(cwd, options)).flatMap((workflow) => jobsNamed(workflow, options.junctionJobs ?? DEFAULT_JUNCTION_JOBS).map((job) => ({
|
|
@@ -2802,26 +2802,26 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
2802
2802
|
if (junctions.length === 0) violations.push({
|
|
2803
2803
|
code: "settings-required-checks/no-junction-job",
|
|
2804
2804
|
message: "no workflow has a junction job, so there is no check that reports for the whole workflow to require",
|
|
2805
|
-
|
|
2805
|
+
repository
|
|
2806
2806
|
});
|
|
2807
2807
|
if (rules.requiredStatusChecks.length === 0) {
|
|
2808
2808
|
violations.push({
|
|
2809
2809
|
code: "settings-required-checks/no-required-checks",
|
|
2810
|
-
message: `no rule requires status checks on the branch of ${
|
|
2811
|
-
|
|
2810
|
+
message: `no rule requires status checks on the branch of ${repository}`,
|
|
2811
|
+
repository
|
|
2812
2812
|
});
|
|
2813
2813
|
return violations;
|
|
2814
2814
|
}
|
|
2815
2815
|
const required = new Set(rules.requiredStatusChecks.flatMap((rule) => rule.contexts));
|
|
2816
2816
|
for (const { workflow, name } of junctions.filter((junction) => !required.has(junction.name))) violations.push({
|
|
2817
2817
|
code: "settings-required-checks/required-check-missing",
|
|
2818
|
-
message: `${
|
|
2819
|
-
|
|
2818
|
+
message: `${repository} does not require the check '${name}' of ${workflow.file}`,
|
|
2819
|
+
repository
|
|
2820
2820
|
});
|
|
2821
2821
|
if (!rules.requiredStatusChecks.some((rule) => rule.strict)) violations.push({
|
|
2822
2822
|
code: "settings-required-checks/not-strict",
|
|
2823
|
-
message: `${
|
|
2824
|
-
|
|
2823
|
+
message: `${repository} does not require branches to be up to date before merging`,
|
|
2824
|
+
repository
|
|
2825
2825
|
});
|
|
2826
2826
|
return violations;
|
|
2827
2827
|
};
|
|
@@ -2837,16 +2837,16 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
2837
2837
|
const settingsReviewThreadResolution = async ({ cwd, options, github }) => {
|
|
2838
2838
|
const slug = await repositoryOf(cwd, options);
|
|
2839
2839
|
const read = await branchRulesOf(github, slug, options);
|
|
2840
|
-
const
|
|
2840
|
+
const repository = repositoryName(slug);
|
|
2841
2841
|
if ("unavailable" in read) return [{
|
|
2842
2842
|
code: "settings-review-thread-resolution/rules-unavailable",
|
|
2843
|
-
message: rulesUnavailableMessage(
|
|
2844
|
-
|
|
2843
|
+
message: rulesUnavailableMessage(repository, "resolving review conversations", read.unavailable),
|
|
2844
|
+
repository
|
|
2845
2845
|
}];
|
|
2846
2846
|
return read.rules.pullRequests.some((rule) => rule.requiredReviewThreadResolution) ? [] : [{
|
|
2847
2847
|
code: "settings-review-thread-resolution/not-required",
|
|
2848
|
-
message: `no rule requires review conversations to be resolved before merging on the branch of ${
|
|
2849
|
-
|
|
2848
|
+
message: `no rule requires review conversations to be resolved before merging on the branch of ${repository}`,
|
|
2849
|
+
repository
|
|
2850
2850
|
}];
|
|
2851
2851
|
};
|
|
2852
2852
|
//#endregion
|
|
@@ -3289,7 +3289,7 @@ const workflowJobOrdering = async ({ cwd, options }) => (await loadWorkflows(cwd
|
|
|
3289
3289
|
*
|
|
3290
3290
|
* A workflow is taken to produce required checks when it is triggered by `pull_request`; `pull_request_target` is left out, since it runs trusted code against untrusted input and is not a place for required checks. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too: narrow the check with `workflows` and `exclude`. A `merge_group` trigger on a workflow with no `pull_request` trigger is not judged.
|
|
3291
3291
|
*
|
|
3292
|
-
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. Offline it cannot tell, so it reports every such workflow.
|
|
3292
|
+
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. A repository whose plan has no rulesets (the client rejects with `RulesetsUnavailableError`) has no merge queue either, since GitHub offers merge queues only in public repositories of an organisation and in private ones on GitHub Enterprise Cloud, both of which have rulesets, so nothing is reported for it; any other refusal fails the run. Offline it cannot tell, so it reports every such workflow.
|
|
3293
3293
|
*
|
|
3294
3294
|
* 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; the `branches` filter of a `merge_group` trigger is not read; only rulesets are read, so a queue configured through classic branch protection is not seen and, with a client, nothing is reported for it.
|
|
3295
3295
|
*/
|
|
@@ -3299,8 +3299,8 @@ const workflowMergeGroup = async ({ cwd, options, github }) => {
|
|
|
3299
3299
|
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)];
|
|
3300
3300
|
});
|
|
3301
3301
|
if (violations.length === 0 || github === void 0) return violations;
|
|
3302
|
-
const
|
|
3303
|
-
return
|
|
3302
|
+
const branch = await branchRulesOf(github, await repositoryOf(cwd, options), options);
|
|
3303
|
+
return "rules" in branch && branch.rules.mergeQueue ? violations : [];
|
|
3304
3304
|
};
|
|
3305
3305
|
//#endregion
|
|
3306
3306
|
//#region src/checks/workflow-release-job.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -98,9 +98,9 @@ interface SourceLocation {
|
|
|
98
98
|
readonly column: number;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* A way the workspace fails a check that is about a file.
|
|
102
102
|
*/
|
|
103
|
-
interface
|
|
103
|
+
interface FileViolation {
|
|
104
104
|
/**
|
|
105
105
|
* Stable identifier of the rule that was broken, `<check name>/<reason>`. Callers may match on it; the message may change.
|
|
106
106
|
*/
|
|
@@ -111,7 +111,28 @@ interface Violation {
|
|
|
111
111
|
*/
|
|
112
112
|
readonly file: string;
|
|
113
113
|
readonly location?: SourceLocation;
|
|
114
|
+
readonly repository?: never;
|
|
114
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* A way the repository fails a check that reads its settings, which is about the repository and no file in it.
|
|
118
|
+
*/
|
|
119
|
+
interface RepositoryViolation {
|
|
120
|
+
/**
|
|
121
|
+
* Stable identifier of the rule that was broken, `<check name>/<reason>`. Callers may match on it; the message may change.
|
|
122
|
+
*/
|
|
123
|
+
readonly code: string;
|
|
124
|
+
readonly message: string;
|
|
125
|
+
/**
|
|
126
|
+
* The repository the violation is about, `owner/name`.
|
|
127
|
+
*/
|
|
128
|
+
readonly repository: string;
|
|
129
|
+
readonly file?: never;
|
|
130
|
+
readonly location?: never;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* One way the workspace fails a check: about a file ({@link FileViolation}, with `file`) or about the repository's settings ({@link RepositoryViolation}, with `repository`). A violation has exactly one of the two, so `violation.file === undefined` tells them apart.
|
|
134
|
+
*/
|
|
135
|
+
type Violation = FileViolation | RepositoryViolation;
|
|
115
136
|
/**
|
|
116
137
|
* What every check receives.
|
|
117
138
|
*/
|
|
@@ -139,11 +160,11 @@ interface LayoutCheckContext<Options> extends CheckContext<Options> {
|
|
|
139
160
|
/**
|
|
140
161
|
* A check: reads the workspace and returns everything it finds wrong, in a stable order. It throws `ConformanceError` when it cannot run.
|
|
141
162
|
*/
|
|
142
|
-
type CheckFunction<Options> = (context: CheckContext<Options>) => Promise<readonly
|
|
163
|
+
type CheckFunction<Options> = (context: CheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
143
164
|
/**
|
|
144
165
|
* A check that also needs the resolved workspace layout.
|
|
145
166
|
*/
|
|
146
|
-
type LayoutCheckFunction<Options> = (context: LayoutCheckContext<Options>) => Promise<readonly
|
|
167
|
+
type LayoutCheckFunction<Options> = (context: LayoutCheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
147
168
|
/**
|
|
148
169
|
* What a check that reads repository settings through the GitHub API receives.
|
|
149
170
|
*/
|
|
@@ -151,9 +172,9 @@ interface GitHubCheckContext<Options> extends CheckContext<Options> {
|
|
|
151
172
|
readonly github: GitHubClient;
|
|
152
173
|
}
|
|
153
174
|
/**
|
|
154
|
-
* A check that reads repository settings through the GitHub API. Its violations
|
|
175
|
+
* A check that reads repository settings through the GitHub API. Its violations are about the repository, which they name as `owner/name`.
|
|
155
176
|
*/
|
|
156
|
-
type GitHubCheckFunction<Options> = (context: GitHubCheckContext<Options>) => Promise<readonly
|
|
177
|
+
type GitHubCheckFunction<Options> = (context: GitHubCheckContext<Options>) => Promise<readonly RepositoryViolation[]>;
|
|
157
178
|
/**
|
|
158
179
|
* What a check that reads files, and the repository's settings too when it can, receives.
|
|
159
180
|
*/
|
|
@@ -166,7 +187,7 @@ interface SettingsAwareCheckContext<Options> extends CheckContext<Options> {
|
|
|
166
187
|
/**
|
|
167
188
|
* A check that reads files and refines its judgement with the repository's settings when the run is given a GitHub client. It runs offline too, unlike a {@link GitHubCheckFunction}.
|
|
168
189
|
*/
|
|
169
|
-
type SettingsAwareCheckFunction<Options> = (context: SettingsAwareCheckContext<Options>) => Promise<readonly
|
|
190
|
+
type SettingsAwareCheckFunction<Options> = (context: SettingsAwareCheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
170
191
|
//#endregion
|
|
171
192
|
//#region src/migrations/adapters.d.ts
|
|
172
193
|
/**
|
|
@@ -1058,7 +1079,7 @@ interface ImportCheckSpec {
|
|
|
1058
1079
|
/**
|
|
1059
1080
|
* The violations for the findings of this check's rules, in a stable order.
|
|
1060
1081
|
*/
|
|
1061
|
-
readonly report: (findings: readonly IViolation[], scope: ImportScope) => readonly
|
|
1082
|
+
readonly report: (findings: readonly IViolation[], scope: ImportScope) => readonly FileViolation[];
|
|
1062
1083
|
}
|
|
1063
1084
|
//#endregion
|
|
1064
1085
|
//#region src/checks/import-cross-slice.d.ts
|
|
@@ -1179,7 +1200,7 @@ export declare const workflowJobOrdering: CheckFunction<WorkflowJobOrderingOptio
|
|
|
1179
1200
|
*
|
|
1180
1201
|
* A workflow is taken to produce required checks when it is triggered by `pull_request`; `pull_request_target` is left out, since it runs trusted code against untrusted input and is not a place for required checks. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too: narrow the check with `workflows` and `exclude`. A `merge_group` trigger on a workflow with no `pull_request` trigger is not judged.
|
|
1181
1202
|
*
|
|
1182
|
-
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. Offline it cannot tell, so it reports every such workflow.
|
|
1203
|
+
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. A repository whose plan has no rulesets (the client rejects with `RulesetsUnavailableError`) has no merge queue either, since GitHub offers merge queues only in public repositories of an organisation and in private ones on GitHub Enterprise Cloud, both of which have rulesets, so nothing is reported for it; any other refusal fails the run. Offline it cannot tell, so it reports every such workflow.
|
|
1183
1204
|
*
|
|
1184
1205
|
* 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; the `branches` filter of a `merge_group` trigger is not read; only rulesets are read, so a queue configured through classic branch protection is not seen and, with a client, nothing is reported for it.
|
|
1185
1206
|
*/
|
|
@@ -1469,4 +1490,4 @@ export declare function proveExhaustive(input: {
|
|
|
1469
1490
|
readonly compilerOptions?: CompilerOptions;
|
|
1470
1491
|
}): ExhaustivenessProof;
|
|
1471
1492
|
//#endregion
|
|
1472
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EnginesFloorOptions, 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, SettingsAwareCheckContext, SettingsAwareCheckFunction, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1493
|
+
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EnginesFloorOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, FileViolation, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RepositoryViolation, RunChecksOptions, RunResult, RunsOn, SettingsAwareCheckContext, SettingsAwareCheckFunction, 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
|
@@ -98,9 +98,9 @@ interface SourceLocation {
|
|
|
98
98
|
readonly column: number;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* A way the workspace fails a check that is about a file.
|
|
102
102
|
*/
|
|
103
|
-
interface
|
|
103
|
+
interface FileViolation {
|
|
104
104
|
/**
|
|
105
105
|
* Stable identifier of the rule that was broken, `<check name>/<reason>`. Callers may match on it; the message may change.
|
|
106
106
|
*/
|
|
@@ -111,7 +111,28 @@ interface Violation {
|
|
|
111
111
|
*/
|
|
112
112
|
readonly file: string;
|
|
113
113
|
readonly location?: SourceLocation;
|
|
114
|
+
readonly repository?: never;
|
|
114
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* A way the repository fails a check that reads its settings, which is about the repository and no file in it.
|
|
118
|
+
*/
|
|
119
|
+
interface RepositoryViolation {
|
|
120
|
+
/**
|
|
121
|
+
* Stable identifier of the rule that was broken, `<check name>/<reason>`. Callers may match on it; the message may change.
|
|
122
|
+
*/
|
|
123
|
+
readonly code: string;
|
|
124
|
+
readonly message: string;
|
|
125
|
+
/**
|
|
126
|
+
* The repository the violation is about, `owner/name`.
|
|
127
|
+
*/
|
|
128
|
+
readonly repository: string;
|
|
129
|
+
readonly file?: never;
|
|
130
|
+
readonly location?: never;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* One way the workspace fails a check: about a file ({@link FileViolation}, with `file`) or about the repository's settings ({@link RepositoryViolation}, with `repository`). A violation has exactly one of the two, so `violation.file === undefined` tells them apart.
|
|
134
|
+
*/
|
|
135
|
+
type Violation = FileViolation | RepositoryViolation;
|
|
115
136
|
/**
|
|
116
137
|
* What every check receives.
|
|
117
138
|
*/
|
|
@@ -139,11 +160,11 @@ interface LayoutCheckContext<Options> extends CheckContext<Options> {
|
|
|
139
160
|
/**
|
|
140
161
|
* A check: reads the workspace and returns everything it finds wrong, in a stable order. It throws `ConformanceError` when it cannot run.
|
|
141
162
|
*/
|
|
142
|
-
type CheckFunction<Options> = (context: CheckContext<Options>) => Promise<readonly
|
|
163
|
+
type CheckFunction<Options> = (context: CheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
143
164
|
/**
|
|
144
165
|
* A check that also needs the resolved workspace layout.
|
|
145
166
|
*/
|
|
146
|
-
type LayoutCheckFunction<Options> = (context: LayoutCheckContext<Options>) => Promise<readonly
|
|
167
|
+
type LayoutCheckFunction<Options> = (context: LayoutCheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
147
168
|
/**
|
|
148
169
|
* What a check that reads repository settings through the GitHub API receives.
|
|
149
170
|
*/
|
|
@@ -151,9 +172,9 @@ interface GitHubCheckContext<Options> extends CheckContext<Options> {
|
|
|
151
172
|
readonly github: GitHubClient;
|
|
152
173
|
}
|
|
153
174
|
/**
|
|
154
|
-
* A check that reads repository settings through the GitHub API. Its violations
|
|
175
|
+
* A check that reads repository settings through the GitHub API. Its violations are about the repository, which they name as `owner/name`.
|
|
155
176
|
*/
|
|
156
|
-
type GitHubCheckFunction<Options> = (context: GitHubCheckContext<Options>) => Promise<readonly
|
|
177
|
+
type GitHubCheckFunction<Options> = (context: GitHubCheckContext<Options>) => Promise<readonly RepositoryViolation[]>;
|
|
157
178
|
/**
|
|
158
179
|
* What a check that reads files, and the repository's settings too when it can, receives.
|
|
159
180
|
*/
|
|
@@ -166,7 +187,7 @@ interface SettingsAwareCheckContext<Options> extends CheckContext<Options> {
|
|
|
166
187
|
/**
|
|
167
188
|
* A check that reads files and refines its judgement with the repository's settings when the run is given a GitHub client. It runs offline too, unlike a {@link GitHubCheckFunction}.
|
|
168
189
|
*/
|
|
169
|
-
type SettingsAwareCheckFunction<Options> = (context: SettingsAwareCheckContext<Options>) => Promise<readonly
|
|
190
|
+
type SettingsAwareCheckFunction<Options> = (context: SettingsAwareCheckContext<Options>) => Promise<readonly FileViolation[]>;
|
|
170
191
|
//#endregion
|
|
171
192
|
//#region src/migrations/adapters.d.ts
|
|
172
193
|
/**
|
|
@@ -1058,7 +1079,7 @@ interface ImportCheckSpec {
|
|
|
1058
1079
|
/**
|
|
1059
1080
|
* The violations for the findings of this check's rules, in a stable order.
|
|
1060
1081
|
*/
|
|
1061
|
-
readonly report: (findings: readonly IViolation[], scope: ImportScope) => readonly
|
|
1082
|
+
readonly report: (findings: readonly IViolation[], scope: ImportScope) => readonly FileViolation[];
|
|
1062
1083
|
}
|
|
1063
1084
|
//#endregion
|
|
1064
1085
|
//#region src/checks/import-cross-slice.d.ts
|
|
@@ -1179,7 +1200,7 @@ export declare const workflowJobOrdering: CheckFunction<WorkflowJobOrderingOptio
|
|
|
1179
1200
|
*
|
|
1180
1201
|
* A workflow is taken to produce required checks when it is triggered by `pull_request`; `pull_request_target` is left out, since it runs trusted code against untrusted input and is not a place for required checks. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too: narrow the check with `workflows` and `exclude`. A `merge_group` trigger on a workflow with no `pull_request` trigger is not judged.
|
|
1181
1202
|
*
|
|
1182
|
-
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. Offline it cannot tell, so it reports every such workflow.
|
|
1203
|
+
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. A repository whose plan has no rulesets (the client rejects with `RulesetsUnavailableError`) has no merge queue either, since GitHub offers merge queues only in public repositories of an organisation and in private ones on GitHub Enterprise Cloud, both of which have rulesets, so nothing is reported for it; any other refusal fails the run. Offline it cannot tell, so it reports every such workflow.
|
|
1183
1204
|
*
|
|
1184
1205
|
* 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; the `branches` filter of a `merge_group` trigger is not read; only rulesets are read, so a queue configured through classic branch protection is not seen and, with a client, nothing is reported for it.
|
|
1185
1206
|
*/
|
|
@@ -1469,4 +1490,4 @@ export declare function proveExhaustive(input: {
|
|
|
1469
1490
|
readonly compilerOptions?: CompilerOptions;
|
|
1470
1491
|
}): ExhaustivenessProof;
|
|
1471
1492
|
//#endregion
|
|
1472
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EnginesFloorOptions, 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, SettingsAwareCheckContext, SettingsAwareCheckFunction, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1493
|
+
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, EnginesFloorOptions, EslintOptions, EslintRequiredSeverity, EslintSample, ExhaustivenessProof, FileViolation, GeneratorAdapter, GeneratorName, GitHubCheckContext, GitHubCheckFunction, GitHubClient, GitHubClientOptions, ImportGraphOptions, InstructionSymlinksOptions, Job, LayoutCheckContext, LayoutCheckFunction, MigrationsDirectoryOptions, PermissionAccess, Permissions, PinningLevel, RegisteredCheck, RepositorySettings, RepositorySlug, RepositoryViolation, RunChecksOptions, RunResult, RunsOn, SettingsAwareCheckContext, SettingsAwareCheckFunction, 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.js
CHANGED
|
@@ -2319,9 +2319,9 @@ async function branchOf(github, slug, options) {
|
|
|
2319
2319
|
return options.branch ?? (await github.repository(slug)).defaultBranch;
|
|
2320
2320
|
}
|
|
2321
2321
|
/**
|
|
2322
|
-
*
|
|
2322
|
+
* The repository a violation of a settings check names, `owner/name`.
|
|
2323
2323
|
*/
|
|
2324
|
-
function
|
|
2324
|
+
function repositoryName(slug) {
|
|
2325
2325
|
return `${slug.owner}/${slug.name}`;
|
|
2326
2326
|
}
|
|
2327
2327
|
/**
|
|
@@ -2339,8 +2339,8 @@ async function branchRulesOf(github, slug, options) {
|
|
|
2339
2339
|
/**
|
|
2340
2340
|
* The message of a `rules-unavailable` violation: the branch rules of the repository cannot be read or enforced on its plan, so `what` cannot be required.
|
|
2341
2341
|
*/
|
|
2342
|
-
function rulesUnavailableMessage(
|
|
2343
|
-
return `the branch rules of ${
|
|
2342
|
+
function rulesUnavailableMessage(repository, what, githubMessage) {
|
|
2343
|
+
return `the branch rules of ${repository} cannot be read or enforced on its plan, so ${what} cannot be required (GitHub: ${githubMessage})`;
|
|
2344
2344
|
}
|
|
2345
2345
|
//#endregion
|
|
2346
2346
|
//#region src/checks/settings-merge-methods.ts
|
|
@@ -2366,7 +2366,7 @@ const METHOD_NAMES = {
|
|
|
2366
2366
|
*/
|
|
2367
2367
|
const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
2368
2368
|
const slug = await repositoryOf(cwd, options);
|
|
2369
|
-
const
|
|
2369
|
+
const repository = repositoryName(slug);
|
|
2370
2370
|
const settings = await github.repository(slug);
|
|
2371
2371
|
const read = await branchRulesOf(github, slug, options);
|
|
2372
2372
|
const pullRequests = "unavailable" in read ? [] : read.rules.pullRequests;
|
|
@@ -2381,13 +2381,13 @@ const settingsMergeMethods = async ({ cwd, options, github }) => {
|
|
|
2381
2381
|
const available = METHODS.filter((method) => repositoryAllows[method] && rulesAllow(method));
|
|
2382
2382
|
const violations = available.filter((method) => method !== wanted).map((method) => ({
|
|
2383
2383
|
code: "settings-merge-methods/method-enabled",
|
|
2384
|
-
message: `${
|
|
2385
|
-
|
|
2384
|
+
message: `${repository} allows ${METHOD_NAMES[method]}; only ${METHOD_NAMES[wanted]} should be allowed`,
|
|
2385
|
+
repository
|
|
2386
2386
|
}));
|
|
2387
2387
|
if (!available.includes(wanted)) violations.push({
|
|
2388
2388
|
code: "settings-merge-methods/method-unavailable",
|
|
2389
|
-
message: `${
|
|
2390
|
-
|
|
2389
|
+
message: `${repository} does not allow ${METHOD_NAMES[wanted]}`,
|
|
2390
|
+
repository
|
|
2391
2391
|
});
|
|
2392
2392
|
return violations;
|
|
2393
2393
|
};
|
|
@@ -2785,12 +2785,12 @@ const REGISTRY_TOKEN_VARIABLES = ["NODE_AUTH_TOKEN", "NPM_TOKEN"];
|
|
|
2785
2785
|
*/
|
|
2786
2786
|
const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
2787
2787
|
const slug = await repositoryOf(cwd, options);
|
|
2788
|
-
const
|
|
2788
|
+
const repository = repositoryName(slug);
|
|
2789
2789
|
const read = await branchRulesOf(github, slug, options);
|
|
2790
2790
|
if ("unavailable" in read) return [{
|
|
2791
2791
|
code: "settings-required-checks/rules-unavailable",
|
|
2792
|
-
message: rulesUnavailableMessage(
|
|
2793
|
-
|
|
2792
|
+
message: rulesUnavailableMessage(repository, "a status check", read.unavailable),
|
|
2793
|
+
repository
|
|
2794
2794
|
}];
|
|
2795
2795
|
const { rules } = read;
|
|
2796
2796
|
const junctions = (await loadWorkflows(cwd, options)).flatMap((workflow) => jobsNamed(workflow, options.junctionJobs ?? DEFAULT_JUNCTION_JOBS).map((job) => ({
|
|
@@ -2801,26 +2801,26 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
2801
2801
|
if (junctions.length === 0) violations.push({
|
|
2802
2802
|
code: "settings-required-checks/no-junction-job",
|
|
2803
2803
|
message: "no workflow has a junction job, so there is no check that reports for the whole workflow to require",
|
|
2804
|
-
|
|
2804
|
+
repository
|
|
2805
2805
|
});
|
|
2806
2806
|
if (rules.requiredStatusChecks.length === 0) {
|
|
2807
2807
|
violations.push({
|
|
2808
2808
|
code: "settings-required-checks/no-required-checks",
|
|
2809
|
-
message: `no rule requires status checks on the branch of ${
|
|
2810
|
-
|
|
2809
|
+
message: `no rule requires status checks on the branch of ${repository}`,
|
|
2810
|
+
repository
|
|
2811
2811
|
});
|
|
2812
2812
|
return violations;
|
|
2813
2813
|
}
|
|
2814
2814
|
const required = new Set(rules.requiredStatusChecks.flatMap((rule) => rule.contexts));
|
|
2815
2815
|
for (const { workflow, name } of junctions.filter((junction) => !required.has(junction.name))) violations.push({
|
|
2816
2816
|
code: "settings-required-checks/required-check-missing",
|
|
2817
|
-
message: `${
|
|
2818
|
-
|
|
2817
|
+
message: `${repository} does not require the check '${name}' of ${workflow.file}`,
|
|
2818
|
+
repository
|
|
2819
2819
|
});
|
|
2820
2820
|
if (!rules.requiredStatusChecks.some((rule) => rule.strict)) violations.push({
|
|
2821
2821
|
code: "settings-required-checks/not-strict",
|
|
2822
|
-
message: `${
|
|
2823
|
-
|
|
2822
|
+
message: `${repository} does not require branches to be up to date before merging`,
|
|
2823
|
+
repository
|
|
2824
2824
|
});
|
|
2825
2825
|
return violations;
|
|
2826
2826
|
};
|
|
@@ -2836,16 +2836,16 @@ const settingsRequiredChecks = async ({ cwd, options, github }) => {
|
|
|
2836
2836
|
const settingsReviewThreadResolution = async ({ cwd, options, github }) => {
|
|
2837
2837
|
const slug = await repositoryOf(cwd, options);
|
|
2838
2838
|
const read = await branchRulesOf(github, slug, options);
|
|
2839
|
-
const
|
|
2839
|
+
const repository = repositoryName(slug);
|
|
2840
2840
|
if ("unavailable" in read) return [{
|
|
2841
2841
|
code: "settings-review-thread-resolution/rules-unavailable",
|
|
2842
|
-
message: rulesUnavailableMessage(
|
|
2843
|
-
|
|
2842
|
+
message: rulesUnavailableMessage(repository, "resolving review conversations", read.unavailable),
|
|
2843
|
+
repository
|
|
2844
2844
|
}];
|
|
2845
2845
|
return read.rules.pullRequests.some((rule) => rule.requiredReviewThreadResolution) ? [] : [{
|
|
2846
2846
|
code: "settings-review-thread-resolution/not-required",
|
|
2847
|
-
message: `no rule requires review conversations to be resolved before merging on the branch of ${
|
|
2848
|
-
|
|
2847
|
+
message: `no rule requires review conversations to be resolved before merging on the branch of ${repository}`,
|
|
2848
|
+
repository
|
|
2849
2849
|
}];
|
|
2850
2850
|
};
|
|
2851
2851
|
//#endregion
|
|
@@ -3288,7 +3288,7 @@ const workflowJobOrdering = async ({ cwd, options }) => (await loadWorkflows(cwd
|
|
|
3288
3288
|
*
|
|
3289
3289
|
* A workflow is taken to produce required checks when it is triggered by `pull_request`; `pull_request_target` is left out, since it runs trusted code against untrusted input and is not a place for required checks. The file cannot show which checks are required, so a `pull_request` workflow whose checks are not required is reported too: narrow the check with `workflows` and `exclude`. A `merge_group` trigger on a workflow with no `pull_request` trigger is not judged.
|
|
3290
3290
|
*
|
|
3291
|
-
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. Offline it cannot tell, so it reports every such workflow.
|
|
3291
|
+
* Whether the branch has a merge queue is a setting, not something a file shows. Given a GitHub client, the check reads the rules of the branch (`branch`, else the default branch, of `repository`, else the `origin` remote) once it has found a workflow without the trigger, and reports only when a `merge_queue` rule applies, since without a queue no `merge_group` event is ever sent. A repository whose plan has no rulesets (the client rejects with `RulesetsUnavailableError`) has no merge queue either, since GitHub offers merge queues only in public repositories of an organisation and in private ones on GitHub Enterprise Cloud, both of which have rulesets, so nothing is reported for it; any other refusal fails the run. Offline it cannot tell, so it reports every such workflow.
|
|
3292
3292
|
*
|
|
3293
3293
|
* 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; the `branches` filter of a `merge_group` trigger is not read; only rulesets are read, so a queue configured through classic branch protection is not seen and, with a client, nothing is reported for it.
|
|
3294
3294
|
*/
|
|
@@ -3298,8 +3298,8 @@ const workflowMergeGroup = async ({ cwd, options, github }) => {
|
|
|
3298
3298
|
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)];
|
|
3299
3299
|
});
|
|
3300
3300
|
if (violations.length === 0 || github === void 0) return violations;
|
|
3301
|
-
const
|
|
3302
|
-
return
|
|
3301
|
+
const branch = await branchRulesOf(github, await repositoryOf(cwd, options), options);
|
|
3302
|
+
return "rules" in branch && branch.rules.mergeQueue ? violations : [];
|
|
3303
3303
|
};
|
|
3304
3304
|
//#endregion
|
|
3305
3305
|
//#region src/checks/workflow-release-job.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workspace-conformance",
|
|
3
3
|
"description": "Conformance checks for a pnpm workspace that ESLint cannot express: type-graph, import-graph and cross-file checks driven by the shared layout section of @exadev/config",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.1",
|
|
5
5
|
"bin": {
|
|
6
6
|
"workspace-conformance": "./dist/cli.js"
|
|
7
7
|
},
|