@zgeoff/chex 0.2.1 → 0.2.3
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
CHANGED
|
@@ -82,8 +82,8 @@ skipped file back:
|
|
|
82
82
|
|
|
83
83
|
`chex init` writes the `extends` line and a
|
|
84
84
|
[`chex-baseline` job](../../docs/architecture/pre-push-baseline.md#a-checkout-without-the-baseline)
|
|
85
|
-
into `lefthook.yml`, and adds a `check:docs` script of `chex prose length links skill` when
|
|
86
|
-
has none, and changes nothing on a second run. `chex ci-plan` plans a run of `bun-checks`.
|
|
85
|
+
into `lefthook.yml`, and adds a `check:docs` script of `chex prose length links readmes skill` when
|
|
86
|
+
the repo has none, and changes nothing on a second run. `chex ci-plan` plans a run of `bun-checks`.
|
|
87
87
|
|
|
88
88
|
## The pre-push baseline
|
|
89
89
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import { isRecord } from '@zgeoff/std';
|
|
|
2
2
|
|
|
3
3
|
// The baseline's jobs run chex, the related tests through rig, and each job under diet's memory cap.
|
|
4
4
|
export const baselinePackages = ['@zgeoff/chex', '@zgeoff/rig', '@zgeoff/diet'] as const;
|
|
5
|
-
export const docsScript = 'chex prose length links skill';
|
|
5
|
+
export const docsScript = 'chex prose length links readmes skill';
|
|
6
6
|
|
|
7
7
|
export type PackageOutcome =
|
|
8
8
|
| { readonly kind: 'missing'; readonly packages: readonly string[] }
|
|
@@ -8,16 +8,31 @@ interface UnneededOverridesInput {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function collectUnneededOverrides(input: UnneededOverridesInput): DependencyGateFinding[] {
|
|
11
|
-
return Object.entries(input.overrides).flatMap(([
|
|
11
|
+
return Object.entries(input.overrides).flatMap(([key, pin]) => {
|
|
12
|
+
const target = parseOverrideKey(key);
|
|
13
|
+
|
|
14
|
+
if (target === null) {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
path: 'package.json',
|
|
18
|
+
entry: key,
|
|
19
|
+
problem: 'bun ignores an override key with more than one parent level',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
const ranges = input.dependents
|
|
13
|
-
.filter(
|
|
14
|
-
|
|
25
|
+
.filter(
|
|
26
|
+
(dependent) =>
|
|
27
|
+
dependent.name === target.name && isDependentOfParent(dependent, target.parent),
|
|
28
|
+
)
|
|
29
|
+
.map((dependent) => resolveCatalogRange(input, target.name, dependent.range));
|
|
15
30
|
|
|
16
31
|
if (ranges.length === 0) {
|
|
17
32
|
return [
|
|
18
33
|
{
|
|
19
34
|
path: 'package.json',
|
|
20
|
-
entry:
|
|
35
|
+
entry: key,
|
|
21
36
|
problem: 'no package in bun.lock depends on the overridden package',
|
|
22
37
|
},
|
|
23
38
|
];
|
|
@@ -27,7 +42,7 @@ export function collectUnneededOverrides(input: UnneededOverridesInput): Depende
|
|
|
27
42
|
(range) => !isSemverRange(range) || !Bun.semver.satisfies(pin, range),
|
|
28
43
|
);
|
|
29
44
|
|
|
30
|
-
const isPinTheCatalogVersion = input.catalog[name] === pin;
|
|
45
|
+
const isPinTheCatalogVersion = input.catalog[target.name] === pin;
|
|
31
46
|
|
|
32
47
|
if (isPinOutsideARange || isPinTheCatalogVersion) {
|
|
33
48
|
return [];
|
|
@@ -36,13 +51,74 @@ export function collectUnneededOverrides(input: UnneededOverridesInput): Depende
|
|
|
36
51
|
return [
|
|
37
52
|
{
|
|
38
53
|
path: 'package.json',
|
|
39
|
-
entry:
|
|
54
|
+
entry: key,
|
|
40
55
|
problem: `every range in bun.lock already admits ${pin}, so the override changes nothing`,
|
|
41
56
|
},
|
|
42
57
|
];
|
|
43
58
|
});
|
|
44
59
|
}
|
|
45
60
|
|
|
61
|
+
interface PackageSpec {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly range: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface OverrideTarget {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly parent: PackageSpec | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Bun splits a key at the first `>` that follows no space, `|`, `@` or `:`, so the `>` of a range
|
|
72
|
+
// such as `left-pad@>=1` stays in the range.
|
|
73
|
+
const parentSeparatorPattern = /[^ |@:]>/u;
|
|
74
|
+
|
|
75
|
+
// A child range, as in `terser@4`, narrows the dependents bun applies the key to. The check keeps
|
|
76
|
+
// every dependent of the name, so a narrower key can only look more needed, never less.
|
|
77
|
+
function parseOverrideKey(key: string): OverrideTarget | null {
|
|
78
|
+
const separator = parentSeparatorPattern.exec(key);
|
|
79
|
+
|
|
80
|
+
if (separator === null) {
|
|
81
|
+
return { name: parsePackageSpec(key).name, parent: null };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const separatorIndex = separator.index + 1;
|
|
85
|
+
const child = key.slice(separatorIndex + 1).trim();
|
|
86
|
+
|
|
87
|
+
if (parentSeparatorPattern.test(child)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
name: parsePackageSpec(child).name,
|
|
93
|
+
parent: parsePackageSpec(key.slice(0, separatorIndex).trim()),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A scoped name starts with `@`, so the range starts at the next `@`.
|
|
98
|
+
function parsePackageSpec(spec: string): PackageSpec {
|
|
99
|
+
const rangeIndex = spec.indexOf('@', 1);
|
|
100
|
+
|
|
101
|
+
if (rangeIndex === -1) {
|
|
102
|
+
return { name: spec, range: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { name: spec.slice(0, rangeIndex), range: spec.slice(rangeIndex + 1) };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isDependentOfParent(dependent: LockfileDependent, parent: PackageSpec | null): boolean {
|
|
109
|
+
if (parent === null) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const id = parsePackageSpec(dependent.dependent);
|
|
114
|
+
|
|
115
|
+
if (id.name !== parent.name) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return parent.range === null || id.range === null || Bun.semver.satisfies(id.range, parent.range);
|
|
120
|
+
}
|
|
121
|
+
|
|
46
122
|
// bun.lock keeps a workspace's `catalog:` reference as it is written in its package.json.
|
|
47
123
|
function resolveCatalogRange(input: UnneededOverridesInput, name: string, range: string): string {
|
|
48
124
|
if (!range.startsWith('catalog:')) {
|
|
@@ -32,11 +32,24 @@ const workspaceFields = [
|
|
|
32
32
|
] as const;
|
|
33
33
|
|
|
34
34
|
function collectWorkspaceDependents(path: string, workspace: unknown): LockfileDependent[] {
|
|
35
|
-
const dependent = path
|
|
35
|
+
const dependent = buildWorkspaceID(path, workspace);
|
|
36
36
|
|
|
37
37
|
return collectDeclaredRanges(dependent, workspace, workspaceFields);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// A nested override key names its parent by package name, whether the parent is a workspace or a
|
|
41
|
+
// registry package, so a workspace with a name takes it in place of its path.
|
|
42
|
+
function buildWorkspaceID(path: string, workspace: unknown): string {
|
|
43
|
+
const name = isRecord(workspace) ? workspace['name'] : undefined;
|
|
44
|
+
const version = isRecord(workspace) ? workspace['version'] : undefined;
|
|
45
|
+
|
|
46
|
+
if (typeof name !== 'string') {
|
|
47
|
+
return path === '' ? 'the root workspace' : path;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return typeof version === 'string' ? `${name}@${version}` : name;
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
function collectDeclaredRanges(
|
|
41
54
|
dependent: string,
|
|
42
55
|
metadata: unknown,
|
|
@@ -21,13 +21,55 @@ export function parseRootManifest(text: string): RootManifest {
|
|
|
21
21
|
const audit = isRecord(scripts) ? scripts['audit'] : undefined;
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
|
-
overrides:
|
|
24
|
+
overrides: parseOverrides(manifest['overrides']),
|
|
25
25
|
catalog: parseVersions(catalog, 'workspaces.catalog'),
|
|
26
26
|
catalogs: parseNamedCatalogs(catalogs),
|
|
27
27
|
auditScript: typeof audit === 'string' ? audit : '',
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function parseOverrides(value: unknown): Record<string, string> {
|
|
32
|
+
if (value === undefined) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isRecord(value)) {
|
|
37
|
+
throw new TypeError('package.json sets overrides to a value that is not an object');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Object.fromEntries(collectOverrideEntries('', value));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Bun reads `{ "a": { ".": "1.0.0", "b": "2.0.0" } }` as the keys `a` and `a>b`.
|
|
44
|
+
function collectOverrideEntries(
|
|
45
|
+
parentKey: string,
|
|
46
|
+
overrides: Record<string, unknown>,
|
|
47
|
+
): [string, string][] {
|
|
48
|
+
return Object.entries(overrides).flatMap(([name, value]): [string, string][] => {
|
|
49
|
+
const key = buildOverrideKey(parentKey, name);
|
|
50
|
+
|
|
51
|
+
if (typeof value === 'string') {
|
|
52
|
+
return [[key, value]];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isRecord(value)) {
|
|
56
|
+
return collectOverrideEntries(key, value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw new TypeError(
|
|
60
|
+
`package.json sets the overrides entry for ${key} to a value that is not a version`,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildOverrideKey(parentKey: string, name: string): string {
|
|
66
|
+
if (name === '.') {
|
|
67
|
+
return parentKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return parentKey === '' ? name : `${parentKey}>${name}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
function parseVersions(value: unknown, field: string): Record<string, string> {
|
|
32
74
|
if (value === undefined) {
|
|
33
75
|
return {};
|