@avi2dg/checks 0.50.1 → 0.52.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/.bun-version +1 -0
- package/CHANGELOG.md +27 -0
- package/README.md +24 -249
- package/commitlint.config.ts +0 -1
- package/dist/mutation/unit-test-order.js +1 -1
- package/dist/presets/knip.d.ts +1 -1
- package/dist/presets/knip.js +1 -1
- package/dist/templates/how-to.md +1 -1
- package/dist/templates/readme.md +1 -1
- package/dist/templates/tutorial.md +1 -1
- package/docs/bins.md +67 -0
- package/docs/gates/checks-advisories.md +2 -2
- package/docs/gates/checks-browser.md +5 -1
- package/docs/gates/checks-ci-wiring.md +18 -9
- package/docs/gates/checks-commit-identity.md +6 -3
- package/docs/gates/checks-copy.md +4 -0
- package/docs/gates/checks-docs.md +24 -3
- package/docs/gates/checks-exports.md +1 -1
- package/docs/gates/checks-frontend-syntax.md +5 -1
- package/docs/gates/checks-lint.md +2 -2
- package/docs/gates/checks-mutation-baseline.md +36 -7
- package/docs/gates/checks-mutation-compare.md +3 -3
- package/docs/gates/checks-mutation-scope.md +1 -1
- package/docs/gates/checks-mutation.md +26 -9
- package/docs/gates/checks-night-run.md +1 -1
- package/docs/gates/checks-secrets.md +2 -2
- package/docs/gates/checks-unused.md +1 -1
- package/docs/how-to/set-up-a-repository.md +142 -0
- package/docs/how-to/turn-on-the-css-families.md +35 -0
- package/docs/how-to/upgrade-the-kit.md +42 -0
- package/docs/shipped-files.md +36 -0
- package/package.json +13 -1
- package/src/delivery/bun-pin.ts +19 -5
- package/src/delivery/ci-wiring.ts +11 -77
- package/src/delivery/commit-identity.ts +14 -7
- package/src/delivery/runner-policy.ts +385 -0
- package/src/delivery/workflow-references.ts +1 -1
- package/src/docs/doc-templates.ts +1 -1
- package/src/docs/doc-versions.ts +137 -0
- package/src/docs/docs.ts +7 -0
- package/src/testing/mutation-baseline-full.ts +68 -0
- package/src/testing/mutation-baseline-saved.ts +52 -0
- package/src/testing/mutation-baseline.ts +27 -23
- package/src/testing/mutation-scope-graph.ts +89 -0
- package/src/testing/mutation-scope.ts +48 -93
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "Deterministic checks shared across a set of TypeScript repositories",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "avi2d",
|
|
7
|
+
"email": "avi2dg@gmail.com"
|
|
8
|
+
},
|
|
5
9
|
"license": "MIT",
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
@@ -13,10 +17,14 @@
|
|
|
13
17
|
"type": "module",
|
|
14
18
|
"files": [
|
|
15
19
|
"CHANGELOG.md",
|
|
20
|
+
"docs/bins.md",
|
|
16
21
|
"docs/configs/",
|
|
17
22
|
"docs/design.md",
|
|
18
23
|
"docs/gates/",
|
|
24
|
+
"docs/how-to/",
|
|
25
|
+
"docs/shipped-files.md",
|
|
19
26
|
"bunfig.toml",
|
|
27
|
+
".bun-version",
|
|
20
28
|
"commitlint.config.ts",
|
|
21
29
|
"dependency-cruiser.config.js",
|
|
22
30
|
"knip-base.json",
|
|
@@ -48,7 +56,9 @@
|
|
|
48
56
|
"src/testing/mutation-refusal.ts",
|
|
49
57
|
"src/testing/mutation-compare.ts",
|
|
50
58
|
"src/testing/mutation-scope.ts",
|
|
59
|
+
"src/testing/mutation-scope-graph.ts",
|
|
51
60
|
"src/testing/mutation-baseline.ts",
|
|
61
|
+
"src/testing/mutation-baseline-full.ts",
|
|
52
62
|
"src/testing/mutation-baseline-saved.ts",
|
|
53
63
|
"src/testing/subsumed-tests.ts",
|
|
54
64
|
"src/testing/night-run.ts",
|
|
@@ -58,6 +68,7 @@
|
|
|
58
68
|
"src/delivery/workflow-references.ts",
|
|
59
69
|
"src/delivery/bun-pin.ts",
|
|
60
70
|
"src/delivery/mutation-job.ts",
|
|
71
|
+
"src/delivery/runner-policy.ts",
|
|
61
72
|
"src/delivery/secrets.ts",
|
|
62
73
|
"src/delivery/gitleaks.ts",
|
|
63
74
|
"src/delivery/gitleaks.toml",
|
|
@@ -93,6 +104,7 @@
|
|
|
93
104
|
"src/docs/doc-snapshot.ts",
|
|
94
105
|
"src/docs/doc-templates.ts",
|
|
95
106
|
"src/docs/doc-names.ts",
|
|
107
|
+
"src/docs/doc-versions.ts",
|
|
96
108
|
"dist/templates/",
|
|
97
109
|
"src/quality/presets/effect.language-service.json",
|
|
98
110
|
"src/quality/presets/oxlint.ts",
|
package/src/delivery/bun-pin.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { Effect, FileSystem, Path, Schema } from "effect";
|
|
|
2
2
|
import { within } from "./mutation-job.ts";
|
|
3
3
|
import { isRecord, locate, workflowJobs, type Workflow } from "./workflow-references.ts";
|
|
4
4
|
|
|
5
|
-
export const FLEET_BUN_VERSION = "1.4.3";
|
|
6
5
|
const ROOT_MANIFEST = "package.json";
|
|
7
6
|
|
|
8
7
|
export type BunPinPolicy = {
|
|
9
8
|
readonly files: ReadonlyMap<string, string>;
|
|
10
9
|
readonly manifests: ReadonlyMap<string, unknown>;
|
|
10
|
+
readonly fleet: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export type BunPinFault = {
|
|
@@ -94,12 +94,12 @@ function bunPinRequest(policy: BunPinPolicy, step: Readonly<Record<string, unkno
|
|
|
94
94
|
function bunPinFault(policy: BunPinPolicy, step: Readonly<Record<string, unknown>>): string | undefined {
|
|
95
95
|
const request = bunPinRequest(policy, step);
|
|
96
96
|
if (request.expression !== undefined) {
|
|
97
|
-
return `requests bun ${request.expression} from ${request.source}, which cannot be shown equal to ${
|
|
97
|
+
return `requests bun ${request.expression} from ${request.source}, which cannot be shown equal to ${policy.fleet} before the run`;
|
|
98
98
|
}
|
|
99
99
|
if (request.version === undefined) {
|
|
100
|
-
return `pins no bun version from ${request.source}, so setup-bun installs latest, want ${
|
|
100
|
+
return `pins no bun version from ${request.source}, so setup-bun installs latest, want ${policy.fleet}`;
|
|
101
101
|
}
|
|
102
|
-
return request.version ===
|
|
102
|
+
return request.version === policy.fleet ? undefined : `requests bun ${request.version} from ${request.source}, want ${policy.fleet}`;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function unknownRefFault(ref: string): string {
|
|
@@ -156,6 +156,20 @@ const readJsonFile = Effect.fn("readJsonFile")(function* (file: string) {
|
|
|
156
156
|
);
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
export class BunPinError extends Schema.TaggedError<BunPinError>()("BunPinError", {
|
|
160
|
+
message: Schema.String,
|
|
161
|
+
}) {}
|
|
162
|
+
|
|
163
|
+
// The kit ships its own .bun-version, so the check reads the fleet pin from that file rather than a constant.
|
|
164
|
+
const kitBunVersion = Effect.gen(function* () {
|
|
165
|
+
const file = (yield* Path.Path).join(import.meta.dir, "..", "..", ".bun-version");
|
|
166
|
+
const version = (yield* (yield* FileSystem.FileSystem).readFileString(file).pipe(
|
|
167
|
+
Effect.mapError((cause) => new BunPinError({ message: `cannot read the kit .bun-version at ${file}: ${cause.message}` })),
|
|
168
|
+
)).trim();
|
|
169
|
+
if (version === "") return yield* new BunPinError({ message: `the kit .bun-version at ${file} pins no version` });
|
|
170
|
+
return version;
|
|
171
|
+
});
|
|
172
|
+
|
|
159
173
|
export const readBunPinPolicy = Effect.fn("readBunPinPolicy")(function* (root: string, workflows: readonly Workflow[]) {
|
|
160
174
|
const path = yield* Path.Path;
|
|
161
175
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -170,5 +184,5 @@ export const readBunPinPolicy = Effect.fn("readBunPinPolicy")(function* (root: s
|
|
|
170
184
|
if (text !== undefined) files.set(name, text);
|
|
171
185
|
}
|
|
172
186
|
}
|
|
173
|
-
return { files, manifests };
|
|
187
|
+
return { files, manifests, fleet: yield* kitBunVersion };
|
|
174
188
|
});
|
|
@@ -4,11 +4,13 @@ import { defaultBranch, git } from "../core/git.ts";
|
|
|
4
4
|
import { runMain } from "../core/main.ts";
|
|
5
5
|
import { ENTRY_POINT, KIT_GATES, type KitGate } from "../core/gates.ts";
|
|
6
6
|
import { findBunPinFaults, formatBunPinReport, readBunPinPolicy } from "./bun-pin.ts";
|
|
7
|
-
import { readShellFiles
|
|
7
|
+
import { readShellFiles } from "./mutation-job.ts";
|
|
8
|
+
import { findRunnerFaults, formatRunnerReport, jobRuns, readRunnerConfig } from "./runner-policy.ts";
|
|
8
9
|
import { invokes, mentions, plainCommand, type Command } from "./shell-command.ts";
|
|
9
10
|
import { findMissingWorkflows, formatMissingReport, isRecord, names, workflowJobs, type Workflow } from "./workflow-references.ts";
|
|
10
11
|
|
|
11
|
-
export {
|
|
12
|
+
export { BunPinError, findBunPinFaults, formatBunPinReport } from "./bun-pin.ts";
|
|
13
|
+
export { RunnerPolicyError } from "./runner-policy.ts";
|
|
12
14
|
export type { BunPinFault, BunPinPolicy } from "./bun-pin.ts";
|
|
13
15
|
|
|
14
16
|
export type { Command };
|
|
@@ -39,17 +41,6 @@ export type Gap = {
|
|
|
39
41
|
|
|
40
42
|
export type Visibility = "private" | "public" | "unknown";
|
|
41
43
|
|
|
42
|
-
export type RunnerPolicy = {
|
|
43
|
-
readonly visibility: Visibility;
|
|
44
|
-
readonly scripts: ReadonlyMap<string, string>;
|
|
45
|
-
readonly files?: ReadonlyMap<string, string>;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type RunnerFault = {
|
|
49
|
-
readonly location: string;
|
|
50
|
-
readonly fault: string;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
44
|
type RunStep = {
|
|
54
45
|
readonly location: string;
|
|
55
46
|
readonly blocker: string | undefined;
|
|
@@ -71,7 +62,9 @@ const TITLE_LINT = "bunx checks-commit-title";
|
|
|
71
62
|
// The gate holds until every consumer migrates, so the direct commitlint call still counts.
|
|
72
63
|
// The kit calls its own file through bun, since bunx never resolves the working tree.
|
|
73
64
|
const TITLE_CALLS = ["bun src/delivery/commit-title.ts", "./node_modules/.bin/commitlint"];
|
|
74
|
-
const
|
|
65
|
+
const ScriptsManifest = Schema.Struct({
|
|
66
|
+
scripts: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
67
|
+
});
|
|
75
68
|
// Without these a pull_request workflow never sees the commits a pull request pushes.
|
|
76
69
|
const GATING_TYPES = ["opened", "synchronize"];
|
|
77
70
|
const CONSTANTS = new Map([
|
|
@@ -79,13 +72,6 @@ const CONSTANTS = new Map([
|
|
|
79
72
|
["false", false],
|
|
80
73
|
]);
|
|
81
74
|
const STATUS_OVERRIDE = /\b(?:always|failure|cancelled)\s*\(/;
|
|
82
|
-
// GitHub expressions match context and property names without regard to case.
|
|
83
|
-
const OVERRIDE_ACCESS = String.raw`vars\s*(?:\.\s*CI_RUNS_ON\b|\[\s*'CI_RUNS_ON'\s*\])`;
|
|
84
|
-
const RUNNER_OVERRIDE = new RegExp(String.raw`\b${OVERRIDE_ACCESS}`, "i");
|
|
85
|
-
const HOSTED_OVERRIDE = new RegExp(String.raw`^\$\{\{\s*${OVERRIDE_ACCESS}\s*\|\|\s*'ubuntu-latest'\s*\}\}$`, "i");
|
|
86
|
-
const MUTATION_RUNNER = ["self-hosted", "Linux", "X64", "winbox"];
|
|
87
|
-
const DEVICE_RUNNER = ["self-hosted", "macOS", "iphone"];
|
|
88
|
-
const HOSTED_DEFAULT = "${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}";
|
|
89
75
|
const EventRepository = Schema.fromJsonString(Schema.Struct({ repository: Schema.Struct({ private: Schema.Boolean }) }));
|
|
90
76
|
|
|
91
77
|
function constant(value: unknown): boolean | undefined {
|
|
@@ -278,59 +264,6 @@ export function declarationFor(scripts: readonly string[], branch: string): Decl
|
|
|
278
264
|
};
|
|
279
265
|
}
|
|
280
266
|
|
|
281
|
-
function sameLabels(runsOn: unknown, labels: readonly string[]): boolean {
|
|
282
|
-
const named = Array.isArray(runsOn) ? names(runsOn) : undefined;
|
|
283
|
-
return named?.length === labels.length && labels.every((label) => named.includes(label));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function readsOverride(runsOn: unknown): boolean {
|
|
287
|
-
if (typeof runsOn === "string") return RUNNER_OVERRIDE.test(runsOn);
|
|
288
|
-
const values = isRecord(runsOn) ? Object.values(runsOn) : Array.isArray(runsOn) ? runsOn : [];
|
|
289
|
-
return values.some(readsOverride);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function runnerFault(runsOn: unknown, mutation: boolean, visibility: Visibility): string | undefined {
|
|
293
|
-
const overridden = readsOverride(runsOn);
|
|
294
|
-
const pin = `set runs-on: [${MUTATION_RUNNER.join(", ")}]`;
|
|
295
|
-
if (mutation && overridden) return `a mutation job reads CI_RUNS_ON, so an override moves its mutation runs off winbox; ${pin}`;
|
|
296
|
-
if (mutation) return visibility === "private" && !sameLabels(runsOn, MUTATION_RUNNER) ? `a mutation job in a private repository runs off winbox; ${pin}` : undefined;
|
|
297
|
-
const hosted = typeof runsOn === "string" && HOSTED_OVERRIDE.test(runsOn.trim());
|
|
298
|
-
if (hosted || sameLabels(runsOn, DEVICE_RUNNER) || (visibility !== "private" && !overridden)) return undefined;
|
|
299
|
-
return `the job is not on a hosted runner CI_RUNS_ON can override; set runs-on: ${HOSTED_DEFAULT}`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function stepDirectory(value: unknown, inherited: string | undefined): string | undefined {
|
|
303
|
-
if (value === undefined) return inherited;
|
|
304
|
-
return typeof value === "string" ? within("", value) : undefined;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function defaultDirectory(node: unknown, inherited: string | undefined): string | undefined {
|
|
308
|
-
const defaults = isRecord(node) ? node["defaults"] : undefined;
|
|
309
|
-
const run = isRecord(defaults) ? defaults["run"] : undefined;
|
|
310
|
-
return stepDirectory(isRecord(run) ? run["working-directory"] : undefined, inherited);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function jobRuns(workflow: Workflow, job: Readonly<Record<string, unknown>>): readonly Run[] {
|
|
314
|
-
const dir = defaultDirectory(job, defaultDirectory(workflow.document, ""));
|
|
315
|
-
const steps = Array.isArray(job["steps"]) ? job["steps"] : [];
|
|
316
|
-
return steps.flatMap((step: unknown) => (isRecord(step) && typeof step["run"] === "string" ? [{ script: step["run"], dir: stepDirectory(step["working-directory"], dir) }] : []));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function findRunnerFaults(policy: RunnerPolicy, workflows: readonly Workflow[]): readonly RunnerFault[] {
|
|
320
|
-
return workflows.flatMap((workflow) =>
|
|
321
|
-
workflowJobs(workflow).flatMap(([id, job]) => {
|
|
322
|
-
if (job["runs-on"] === undefined) return [];
|
|
323
|
-
const mutation = jobRuns(workflow, job).some((run) => runsMutation(run, policy.scripts, (file) => policy.files?.get(file)));
|
|
324
|
-
const fault = runnerFault(job["runs-on"], mutation, policy.visibility);
|
|
325
|
-
return fault === undefined ? [] : [{ location: `${workflow.path} job ${id}`, fault }];
|
|
326
|
-
}),
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function formatRunnerReport(faults: readonly RunnerFault[]): string {
|
|
331
|
-
return [`ci-wiring: ${faults.length} job(s) run on the wrong runner:`, ...faults.map(({ location, fault }) => ` ${location}: ${fault}`)].join("\n");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
267
|
export const parseWorkflow = (path: string, text: string): Effect.Effect<Workflow, WiringError> =>
|
|
335
268
|
Effect.try({
|
|
336
269
|
try: () => ({ path, document: Bun.YAML.parse(text), text }),
|
|
@@ -342,10 +275,10 @@ const readScripts = Effect.fn("readScripts")(function* (root: string) {
|
|
|
342
275
|
const file = (yield* Path.Path).join(root, "package.json");
|
|
343
276
|
const { scripts = {} } = (yield* fs.exists(file))
|
|
344
277
|
? yield* fs.readFileString(file).pipe(
|
|
345
|
-
Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(
|
|
278
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(ScriptsManifest))),
|
|
346
279
|
Effect.mapError((cause) => new WiringError({ message: `cannot read ${file}: ${cause.message}` })),
|
|
347
280
|
)
|
|
348
|
-
:
|
|
281
|
+
: ScriptsManifest.make({});
|
|
349
282
|
return new Map(Object.entries(scripts));
|
|
350
283
|
});
|
|
351
284
|
|
|
@@ -390,7 +323,8 @@ const wiring = Effect.gen(function* () {
|
|
|
390
323
|
if (missing.length > 0) yield* Console.error(formatMissingReport(missing));
|
|
391
324
|
const scripts = yield* readScripts(root);
|
|
392
325
|
const runs = workflows.flatMap((workflow) => workflowJobs(workflow).flatMap(([, job]) => jobRuns(workflow, job)));
|
|
393
|
-
const
|
|
326
|
+
const runners = yield* readRunnerConfig(root);
|
|
327
|
+
const faults = findRunnerFaults({ visibility: yield* readVisibility, scripts, files: yield* readShellFiles(root, scripts, runs), runners }, workflows);
|
|
394
328
|
if (faults.length > 0) yield* Console.error(formatRunnerReport(faults));
|
|
395
329
|
const bunFaults = findBunPinFaults(yield* readBunPinPolicy(root, workflows), workflows);
|
|
396
330
|
if (bunFaults.length > 0) yield* Console.error(formatBunPinReport(bunFaults));
|
|
@@ -16,8 +16,6 @@ type Commit = {
|
|
|
16
16
|
|
|
17
17
|
type Offence = { readonly commit: Commit; readonly reasons: readonly string[] };
|
|
18
18
|
|
|
19
|
-
const DEFAULT_AUTHORS: readonly Identity[] = [{ name: "avi2d", email: "avi2dg@gmail.com" }];
|
|
20
|
-
|
|
21
19
|
// GitHub writes the squash commit, so it commits what the owner authored and never authors.
|
|
22
20
|
const SQUASH_COMMITTER: Identity = { name: "GitHub", email: "noreply@github.com" };
|
|
23
21
|
|
|
@@ -74,13 +72,12 @@ const allowedAuthors = Effect.gen(function* () {
|
|
|
74
72
|
const root = (yield* git(["rev-parse", "--show-toplevel"])).trim();
|
|
75
73
|
const fs = yield* FileSystem.FileSystem;
|
|
76
74
|
const file = (yield* Path.Path).join(root, "package.json");
|
|
77
|
-
if (!(yield* fs.exists(file))) return
|
|
75
|
+
if (!(yield* fs.exists(file))) return [];
|
|
78
76
|
const manifest = yield* fs.readFileString(file).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Authors))));
|
|
79
|
-
|
|
77
|
+
return [manifest.author, ...(manifest.contributors ?? [])]
|
|
80
78
|
.filter((entry) => entry !== undefined)
|
|
81
79
|
.map(identityOf)
|
|
82
80
|
.filter((entry) => entry !== undefined);
|
|
83
|
-
return listed.length === 0 ? DEFAULT_AUTHORS : listed;
|
|
84
81
|
});
|
|
85
82
|
|
|
86
83
|
const readCommits = Effect.fn("readCommits")(function* (revisions: readonly string[]) {
|
|
@@ -123,11 +120,17 @@ function allows(allowed: readonly Identity[], identity: Identity): boolean {
|
|
|
123
120
|
);
|
|
124
121
|
}
|
|
125
122
|
|
|
123
|
+
// GitHub authors a web merge under the account's display name, which the owner's git name need not match.
|
|
124
|
+
function allowsEmail(allowed: readonly Identity[], identity: Identity): boolean {
|
|
125
|
+
return allowed.some((entry) => entry.email.toLowerCase() === identity.email.toLowerCase());
|
|
126
|
+
}
|
|
127
|
+
|
|
126
128
|
function inspect(commit: Commit, allowed: readonly Identity[]): Offence | undefined {
|
|
127
129
|
const reasons: string[] = [];
|
|
128
130
|
const release = releasedVersionOf(commit.subject) !== undefined;
|
|
129
131
|
const authors = release ? [...allowed, RELEASE_AUTHOR] : allowed;
|
|
130
|
-
|
|
132
|
+
const madeByGitHub = allows([SQUASH_COMMITTER], commit.committer);
|
|
133
|
+
if (!(madeByGitHub ? allowsEmail(authors, commit.author) : allows(authors, commit.author))) {
|
|
131
134
|
reasons.push(`author ${render(commit.author)}`);
|
|
132
135
|
}
|
|
133
136
|
if (!allows([...allowed, SQUASH_COMMITTER], commit.committer)) {
|
|
@@ -146,6 +149,10 @@ const check = Effect.gen(function* () {
|
|
|
146
149
|
const revisions = second === undefined ? ["-1", range] : [range];
|
|
147
150
|
|
|
148
151
|
const allowed = yield* allowedAuthors;
|
|
152
|
+
if (allowed.length === 0) {
|
|
153
|
+
yield* Console.error("commit-identity: package.json declares no author or contributors, so no identity is allowed; declare the owners there");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
149
156
|
const commits = yield* readCommits(revisions);
|
|
150
157
|
const offences = commits
|
|
151
158
|
.map((commit) => inspect(commit, allowed))
|
|
@@ -158,7 +165,7 @@ const check = Effect.gen(function* () {
|
|
|
158
165
|
for (const reason of reasons) lines.push(` ${reason}`);
|
|
159
166
|
}
|
|
160
167
|
lines.push(` allowed: ${allowed.map(render).join(", ")}`);
|
|
161
|
-
lines.push(` allowed as committer only: ${render(SQUASH_COMMITTER)}`);
|
|
168
|
+
lines.push(` allowed as committer only: ${render(SQUASH_COMMITTER)}, which also lets an allowed email author under any name`);
|
|
162
169
|
lines.push(` allowed as release author only: ${render(RELEASE_AUTHOR)}`);
|
|
163
170
|
yield* Console.error(lines.join("\n"));
|
|
164
171
|
return false;
|