@avi2dg/checks 0.20.0 → 0.21.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/CHANGELOG.md +8 -0
- package/dist/feature-rules.js +5 -0
- package/docs/configs/quality-file.md +2 -0
- package/docs/gates/checks-quality.md +2 -0
- package/package.json +1 -1
- package/quality.schema.json +18 -0
- package/scripts/quality-file.ts +6 -0
- package/scripts/quality.ts +28 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.21.0
|
|
6
|
+
|
|
7
|
+
Released 2026-09-26.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **scripts:** let quality.json set the runs-on of generated workflows (#66)
|
|
12
|
+
|
|
5
13
|
## 0.20.0
|
|
6
14
|
|
|
7
15
|
Released 2026-09-26.
|
package/dist/feature-rules.js
CHANGED
|
@@ -153,6 +153,10 @@ var Gates = Schema3.Struct({
|
|
|
153
153
|
scheduled: Schema3.optionalKey(Schema3.Array(Command).annotate({ description: "The commands a cron-scheduled workflow runs" })),
|
|
154
154
|
lint: Schema3.optionalKey(LintGates)
|
|
155
155
|
});
|
|
156
|
+
var RunsOn = Schema3.NonEmptyArray(Schema3.NonEmptyString).annotate({
|
|
157
|
+
identifier: "RunsOn",
|
|
158
|
+
description: "The runner labels every job the kit generates runs on; ubuntu-latest when absent"
|
|
159
|
+
});
|
|
156
160
|
var EffectSources = Schema3.Struct({
|
|
157
161
|
paths: Schema3.NonEmptyArray(PathGlob).annotate({
|
|
158
162
|
description: "Where source is written in Effect, held to the Effect rules of oxlint and the language service"
|
|
@@ -233,6 +237,7 @@ var Quality = Schema3.Struct({
|
|
|
233
237
|
$schema: Schema3.optionalKey(Schema3.String),
|
|
234
238
|
defaultBranch: Schema3.optionalKey(Schema3.NonEmptyString.annotate({ description: "The branch pull requests merge into; main when absent" })),
|
|
235
239
|
gates: Schema3.optionalKey(Gates),
|
|
240
|
+
runsOn: Schema3.optionalKey(RunsOn),
|
|
236
241
|
commitIdentity: Schema3.optionalKey(CommitIdentity),
|
|
237
242
|
sources: Schema3.optionalKey(Sources),
|
|
238
243
|
size: Schema3.optionalKey(Size),
|
|
@@ -14,6 +14,7 @@ The kit's bins find the file at the git root and read it there:
|
|
|
14
14
|
"ci": ["bun run lint", "bun run typecheck", "bun run test"],
|
|
15
15
|
"scheduled": ["bunx checks-flake --runs 10 --report flake-report.json"]
|
|
16
16
|
},
|
|
17
|
+
"runsOn": ["self-hosted", "Linux", "X64", "winbox"],
|
|
17
18
|
"commitIdentity": { "authors": [{ "name": "avi2d", "email": "avi2dg@gmail.com" }] },
|
|
18
19
|
"sources": {
|
|
19
20
|
"production": ["src/**/*.ts"],
|
|
@@ -43,6 +44,7 @@ The kit's bins find the file at the git root and read it there:
|
|
|
43
44
|
| `gates.ci` | `checks-ci-wiring`, `checks-quality` | the commands CI runs on every pull request, as [checks-ci-wiring](../gates/checks-ci-wiring.md) says |
|
|
44
45
|
| `gates.scheduled` | `checks-ci-wiring` | the commands a schedule runs |
|
|
45
46
|
| `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply, as [Gate selection](../gates/checks-lint.md#gate-selection) says |
|
|
47
|
+
| `runsOn` | `checks-quality` | the runner labels every job the ci and commitlint workflows run on, `ubuntu-latest` when absent |
|
|
46
48
|
| `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit, as [checks-commit-identity](../gates/checks-commit-identity.md) says |
|
|
47
49
|
| `sources.production` | `checks-size-budget`, `checks-repetition`, `checks-quality` | the source the repository ships, as [checks-size-budget](../gates/checks-size-budget.md) and [checks-repetition](../gates/checks-repetition.md) say |
|
|
48
50
|
| `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not, as [The Effect rules](effect-rules.md) says |
|
|
@@ -32,6 +32,8 @@ A rule only this repository needs stays in its own `.oxlintrc.json`, whose overr
|
|
|
32
32
|
GitHub Actions reads its own YAML and nothing else, so `checks-quality generate` also writes the kit recipe workflows whole.
|
|
33
33
|
`.github/workflows/ci.yml` runs every `gates.ci` command but the title lint as its own step after a frozen install.
|
|
34
34
|
`.github/workflows/commitlint.yml` lints the pull request title with the installed kit config.
|
|
35
|
+
Both jobs run on the runner labels `quality.json` `runsOn` lists, or on `ubuntu-latest` when the key is absent.
|
|
36
|
+
A self-hosted runner in a public repository runs the code of any pull request from a fork.
|
|
35
37
|
A step one repository alone needs lives in another workflow file, never in the recipe.
|
|
36
38
|
All generated workflows are committed.
|
|
37
39
|
|
package/package.json
CHANGED
package/quality.schema.json
CHANGED
|
@@ -88,6 +88,9 @@
|
|
|
88
88
|
},
|
|
89
89
|
"additionalProperties": false
|
|
90
90
|
},
|
|
91
|
+
"runsOn": {
|
|
92
|
+
"$ref": "#/$defs/RunsOn"
|
|
93
|
+
},
|
|
91
94
|
"commitIdentity": {
|
|
92
95
|
"type": "object",
|
|
93
96
|
"properties": {
|
|
@@ -406,6 +409,21 @@
|
|
|
406
409
|
"type": "string",
|
|
407
410
|
"minLength": 1
|
|
408
411
|
},
|
|
412
|
+
"RunsOn": {
|
|
413
|
+
"type": "array",
|
|
414
|
+
"prefixItems": [
|
|
415
|
+
{
|
|
416
|
+
"type": "string",
|
|
417
|
+
"minLength": 1
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
"minItems": 1,
|
|
421
|
+
"items": {
|
|
422
|
+
"type": "string",
|
|
423
|
+
"minLength": 1
|
|
424
|
+
},
|
|
425
|
+
"description": "The runner labels every job the kit generates runs on; ubuntu-latest when absent"
|
|
426
|
+
},
|
|
409
427
|
"Identity": {
|
|
410
428
|
"type": "object",
|
|
411
429
|
"properties": {
|
package/scripts/quality-file.ts
CHANGED
|
@@ -82,6 +82,11 @@ const Gates = Schema.Struct({
|
|
|
82
82
|
lint: Schema.optionalKey(LintGates),
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
const RunsOn = Schema.NonEmptyArray(Schema.NonEmptyString).annotate({
|
|
86
|
+
identifier: "RunsOn",
|
|
87
|
+
description: "The runner labels every job the kit generates runs on; ubuntu-latest when absent",
|
|
88
|
+
});
|
|
89
|
+
|
|
85
90
|
const EffectSources = Schema.Struct({
|
|
86
91
|
paths: Schema.NonEmptyArray(PathGlob).annotate({
|
|
87
92
|
description: "Where source is written in Effect, held to the Effect rules of oxlint and the language service",
|
|
@@ -205,6 +210,7 @@ export const Quality = Schema.Struct({
|
|
|
205
210
|
Schema.NonEmptyString.annotate({ description: "The branch pull requests merge into; main when absent" }),
|
|
206
211
|
),
|
|
207
212
|
gates: Schema.optionalKey(Gates),
|
|
213
|
+
runsOn: Schema.optionalKey(RunsOn),
|
|
208
214
|
commitIdentity: Schema.optionalKey(CommitIdentity),
|
|
209
215
|
sources: Schema.optionalKey(Sources),
|
|
210
216
|
size: Schema.optionalKey(Size),
|
package/scripts/quality.ts
CHANGED
|
@@ -105,11 +105,26 @@ function runsInTitleLint(gate: string, commitlintConfig: string): boolean {
|
|
|
105
105
|
return words !== undefined && invokes(plainCommand(titleLint(commitlintConfig)), words);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// YAML 1.2's core schema resolves these plain scalars to a null, a boolean or a number, and YAML 1.1 parsers also read yes, no, on and off as booleans.
|
|
109
|
+
const NON_STRING_SCALAR =
|
|
110
|
+
/^(?:~|null|Null|NULL|true|True|TRUE|false|False|FALSE|y|Y|yes|Yes|YES|n|N|no|No|NO|on|On|ON|off|Off|OFF|[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?|0o[0-7]+|0x[0-9a-fA-F]+|[-+]?\.(?:inf|Inf|INF)|\.(?:nan|NaN|NAN))$/;
|
|
111
|
+
|
|
108
112
|
function runScalar(command: string): string {
|
|
109
|
-
return /(^[\s#&*!|>@`'"%{[-])|:\s|\s#|:$|[\n\\]/.test(command)
|
|
113
|
+
return /(^[\s#&*!|>@`'"%{[-])|:\s|\s#|:$|[\n\\]/.test(command) || NON_STRING_SCALAR.test(command)
|
|
114
|
+
? JSON.stringify(command)
|
|
115
|
+
: command;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function flowScalar(label: string): string {
|
|
119
|
+
return /[,[\]{}]/.test(label) ? JSON.stringify(label) : runScalar(label);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function runsOnLine(runsOn: Quality["runsOn"]): string {
|
|
123
|
+
if (runsOn === undefined) return "ubuntu-latest";
|
|
124
|
+
return `[${runsOn.map(flowScalar).join(", ")}]`;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
export function commitlintWorkflow(commitlintConfig: string): string {
|
|
127
|
+
export function commitlintWorkflow(commitlintConfig: string, runsOn: Quality["runsOn"]): string {
|
|
113
128
|
return `permissions:
|
|
114
129
|
contents: read
|
|
115
130
|
on:
|
|
@@ -117,7 +132,7 @@ on:
|
|
|
117
132
|
types: [opened, edited, synchronize, reopened]
|
|
118
133
|
jobs:
|
|
119
134
|
commitlint:
|
|
120
|
-
runs-on:
|
|
135
|
+
runs-on: ${runsOnLine(runsOn)}
|
|
121
136
|
steps:
|
|
122
137
|
- uses: actions/checkout@v5
|
|
123
138
|
- uses: oven-sh/setup-bun@v2
|
|
@@ -135,7 +150,13 @@ jobs:
|
|
|
135
150
|
`;
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
export function suiteWorkflow(
|
|
153
|
+
export function suiteWorkflow(
|
|
154
|
+
defaultBranch: string,
|
|
155
|
+
gates: readonly string[],
|
|
156
|
+
bunVersionFile: boolean,
|
|
157
|
+
nodeVersionFile: boolean,
|
|
158
|
+
runsOn: Quality["runsOn"],
|
|
159
|
+
): string {
|
|
139
160
|
const node = nodeVersionFile
|
|
140
161
|
? " - uses: actions/setup-node@v5\n with:\n node-version-file: .node-version\n"
|
|
141
162
|
: "";
|
|
@@ -143,20 +164,20 @@ export function suiteWorkflow(defaultBranch: string, gates: readonly string[], b
|
|
|
143
164
|
? " - uses: oven-sh/setup-bun@v2\n with:\n bun-version-file: .bun-version\n"
|
|
144
165
|
: " - uses: oven-sh/setup-bun@v2\n";
|
|
145
166
|
const steps = gates.map((gate) => ` - run: ${runScalar(gate)}\n`).join("");
|
|
146
|
-
return `name: ci\non:\n push:\n branches: [${defaultBranch}]\n pull_request:\n types: [opened, edited, synchronize, reopened]\njobs:\n checks:\n runs-on:
|
|
167
|
+
return `name: ci\non:\n push:\n branches: [${defaultBranch}]\n pull_request:\n types: [opened, edited, synchronize, reopened]\njobs:\n checks:\n runs-on: ${runsOnLine(runsOn)}\n steps:\n # The head, not GitHub's merge ref, so the tree the gates read is the commit the range ends at.\n # The whole history, since the range starts where the head branched from the base branch.\n - uses: actions/checkout@v5\n with:\n ref: \${{ github.event.pull_request.head.sha || github.sha }}\n fetch-depth: 0\n${node}${setup} - run: bun install --frozen-lockfile\n${steps}`;
|
|
147
168
|
}
|
|
148
169
|
|
|
149
170
|
export function workflowsFor(quality: Quality, recipe: WorkflowRecipe): readonly GeneratedWorkflow[] {
|
|
150
171
|
const commitlint: GeneratedWorkflow = {
|
|
151
172
|
file: COMMITLINT_WORKFLOW,
|
|
152
|
-
content: commitlintWorkflow(recipe.commitlintConfig),
|
|
173
|
+
content: commitlintWorkflow(recipe.commitlintConfig, quality.runsOn),
|
|
153
174
|
};
|
|
154
175
|
if (quality.gates?.ci === undefined) return [commitlint];
|
|
155
176
|
const suite = quality.gates.ci.filter((gate) => !runsInTitleLint(gate, recipe.commitlintConfig));
|
|
156
177
|
return [
|
|
157
178
|
{
|
|
158
179
|
file: SUITE_WORKFLOW,
|
|
159
|
-
content: suiteWorkflow(quality.defaultBranch ?? DEFAULT_BRANCH, suite, recipe.bunVersionFile, recipe.nodeVersionFile),
|
|
180
|
+
content: suiteWorkflow(quality.defaultBranch ?? DEFAULT_BRANCH, suite, recipe.bunVersionFile, recipe.nodeVersionFile, quality.runsOn),
|
|
160
181
|
},
|
|
161
182
|
commitlint,
|
|
162
183
|
];
|