workspace-conformance 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +304 -140
- package/dist/index.cjs +305 -140
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +304 -140
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -5,8 +5,9 @@ let tinyglobby = require("tinyglobby");
|
|
|
5
5
|
let ts_morph = require("ts-morph");
|
|
6
6
|
let cosmiconfig_extends = require("cosmiconfig-extends");
|
|
7
7
|
let node_fs_promises = require("node:fs/promises");
|
|
8
|
-
let
|
|
8
|
+
let semver = require("semver");
|
|
9
9
|
let yaml = require("yaml");
|
|
10
|
+
let node_module = require("node:module");
|
|
10
11
|
let node_child_process = require("node:child_process");
|
|
11
12
|
let jsonc_parser = require("jsonc-parser");
|
|
12
13
|
let smol_toml = require("smol-toml");
|
|
@@ -782,6 +783,301 @@ const dockerfilePackageManager = async ({ cwd, options }) => {
|
|
|
782
783
|
return violations;
|
|
783
784
|
};
|
|
784
785
|
//#endregion
|
|
786
|
+
//#region src/workspace/packages.ts
|
|
787
|
+
function segmentsOf(path) {
|
|
788
|
+
return path.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
789
|
+
}
|
|
790
|
+
function groupRoot(group) {
|
|
791
|
+
return segmentsOf(group.path ?? group.name);
|
|
792
|
+
}
|
|
793
|
+
function startsWithSegments(segments, prefix) {
|
|
794
|
+
return prefix.every((segment, index) => segments[index] === segment);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* The group whose root is the longest prefix of `dir`; the first declared wins a tie. `undefined` when no group owns the directory.
|
|
798
|
+
*/
|
|
799
|
+
function owningGroup(dir, groups) {
|
|
800
|
+
const segments = segmentsOf(dir);
|
|
801
|
+
let best;
|
|
802
|
+
let bestLength = -1;
|
|
803
|
+
for (const group of groups) {
|
|
804
|
+
const root = groupRoot(group);
|
|
805
|
+
if (root.length > bestLength && startsWithSegments(segments, root)) {
|
|
806
|
+
best = group;
|
|
807
|
+
bestLength = root.length;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return best;
|
|
811
|
+
}
|
|
812
|
+
function rankOf(name, group, layout) {
|
|
813
|
+
if (name !== void 0) {
|
|
814
|
+
const rule = layout.nameRanks?.find((candidate) => new RegExp(candidate.pattern, "u").test(name));
|
|
815
|
+
if (rule !== void 0) return rule.rank;
|
|
816
|
+
}
|
|
817
|
+
return group.rank ?? layout.defaultRank;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* The name without its npm scope: `@scope/store-cli` gives `store-cli`.
|
|
821
|
+
*/
|
|
822
|
+
function unscoped(name) {
|
|
823
|
+
return name.replace(/^@[^/]+\//u, "");
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* The longest slice value that is the whole unscoped name or its prefix up to a `-`.
|
|
827
|
+
*/
|
|
828
|
+
function sliceByNamePrefix(name, known) {
|
|
829
|
+
const bare = unscoped(name);
|
|
830
|
+
return [...known].filter((candidate) => bare === candidate || bare.startsWith(`${candidate}-`)).sort((a, b) => b.length - a.length)[0];
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Classify discovered packages by the layout: group, rank and slice. Pure.
|
|
834
|
+
*
|
|
835
|
+
* A package no group owns is a configuration error, because every check would otherwise skip it without saying so. Nested package directories are one too: the checks attribute a file to the package whose directory is its prefix, which is ambiguous for a package inside another. A `namePrefix` slice takes the longest slice value that a `segment` group produced and that prefixes the package's unscoped name; a package without a name has no such slice.
|
|
836
|
+
*/
|
|
837
|
+
function classifyPackages(discovered, layout) {
|
|
838
|
+
const owned = discovered.map((found) => {
|
|
839
|
+
const group = owningGroup(found.dir, layout.groups);
|
|
840
|
+
if (group === void 0) throw new ConformanceError(`the package directory '${found.dir}' is not under any layout group's path; add a group whose path prefixes it, or narrow the workspace package globs`);
|
|
841
|
+
return {
|
|
842
|
+
found,
|
|
843
|
+
group
|
|
844
|
+
};
|
|
845
|
+
});
|
|
846
|
+
const knownSlices = /* @__PURE__ */ new Set();
|
|
847
|
+
for (const { found, group } of owned) if (group.slice !== void 0 && "segment" in group.slice) {
|
|
848
|
+
const value = segmentsOf(found.dir).slice(groupRoot(group).length)[group.slice.segment];
|
|
849
|
+
if (value !== void 0) knownSlices.add(value);
|
|
850
|
+
}
|
|
851
|
+
const packages = owned.map(({ found, group }) => {
|
|
852
|
+
let slice;
|
|
853
|
+
if (group.slice !== void 0 && "segment" in group.slice) slice = segmentsOf(found.dir).slice(groupRoot(group).length)[group.slice.segment];
|
|
854
|
+
else if (group.slice !== void 0 && found.name !== void 0) slice = sliceByNamePrefix(found.name, knownSlices);
|
|
855
|
+
return {
|
|
856
|
+
...found,
|
|
857
|
+
group: group.name,
|
|
858
|
+
rank: rankOf(found.name, group, layout),
|
|
859
|
+
slice
|
|
860
|
+
};
|
|
861
|
+
});
|
|
862
|
+
for (const outer of packages) {
|
|
863
|
+
const nested = packages.find((inner) => inner.dir !== outer.dir && inner.dir.startsWith(`${outer.dir}/`));
|
|
864
|
+
if (nested !== void 0) throw new ConformanceError(`the package directory '${nested.dir}' is inside the package directory '${outer.dir}'; workspace packages must not nest`);
|
|
865
|
+
}
|
|
866
|
+
return packages;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* The workspace root: `layout.root` resolved against `cwd`, or `cwd` itself.
|
|
870
|
+
*/
|
|
871
|
+
function workspaceRoot(cwd, layout) {
|
|
872
|
+
return layout.root === void 0 ? cwd : (0, node_path.resolve)(cwd, layout.root);
|
|
873
|
+
}
|
|
874
|
+
const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
875
|
+
/**
|
|
876
|
+
* The `packages` list of `pnpm-workspace.yaml` in `root`, or `undefined` when there is no such file. A file that exists but cannot be read, or has no list of strings under `packages`, is a configuration error.
|
|
877
|
+
*/
|
|
878
|
+
async function pnpmWorkspacePatterns(root) {
|
|
879
|
+
const file = (0, node_path.join)(root, PNPM_WORKSPACE_FILE);
|
|
880
|
+
if (!(0, node_fs.existsSync)(file)) return;
|
|
881
|
+
let source;
|
|
882
|
+
try {
|
|
883
|
+
source = await (0, node_fs_promises.readFile)(file, "utf8");
|
|
884
|
+
} catch (error) {
|
|
885
|
+
throw new ConformanceError(`${file} cannot be read`, { cause: error });
|
|
886
|
+
}
|
|
887
|
+
const document = (0, yaml.parse)(source);
|
|
888
|
+
if (typeof document !== "object" || document === null || !("packages" in document) || !Array.isArray(document.packages)) throw new ConformanceError(`${file} has no 'packages' list`);
|
|
889
|
+
const patterns = document.packages;
|
|
890
|
+
if (!patterns.every((pattern) => typeof pattern === "string")) throw new ConformanceError(`${file}: every entry of 'packages' must be a string`);
|
|
891
|
+
return patterns;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* The package globs: `layout.packages`, else the `packages` list of `pnpm-workspace.yaml` in `root`.
|
|
895
|
+
*/
|
|
896
|
+
async function workspacePatterns(root, layout) {
|
|
897
|
+
if (layout.packages !== void 0) return layout.packages;
|
|
898
|
+
const patterns = await pnpmWorkspacePatterns(root);
|
|
899
|
+
if (patterns === void 0) throw new ConformanceError(`the layout has no 'packages' and ${(0, node_path.join)(root, PNPM_WORKSPACE_FILE)} does not exist`);
|
|
900
|
+
return patterns;
|
|
901
|
+
}
|
|
902
|
+
async function readName(manifest) {
|
|
903
|
+
let content;
|
|
904
|
+
try {
|
|
905
|
+
content = JSON.parse(await (0, node_fs_promises.readFile)(manifest, "utf8"));
|
|
906
|
+
} catch (error) {
|
|
907
|
+
throw new ConformanceError(`${manifest} cannot be read as JSON`, { cause: error });
|
|
908
|
+
}
|
|
909
|
+
if (typeof content === "object" && content !== null && "name" in content && typeof content.name === "string") return content.name;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* The directories under `root` that the pnpm-style `patterns` select and that hold a `package.json`, sorted. A `!` pattern excludes. The root package itself (`.`) is not a member of any group and is left out.
|
|
913
|
+
*/
|
|
914
|
+
async function discoverPackages(root, patterns) {
|
|
915
|
+
const manifests = patterns.map((pattern) => pattern.startsWith("!") ? `!${pattern.slice(1)}/**` : `${pattern}/package.json`);
|
|
916
|
+
const dirs = (await (0, tinyglobby.glob)(manifests, {
|
|
917
|
+
cwd: root,
|
|
918
|
+
dot: true,
|
|
919
|
+
ignore: [INSTALLED_DEPENDENCIES_GLOB],
|
|
920
|
+
onlyFiles: true
|
|
921
|
+
})).map((manifest) => manifest.replace(/\/?package\.json$/u, "")).filter((dir) => dir !== "");
|
|
922
|
+
return Promise.all([...new Set(dirs)].sort().map(async (dir) => ({
|
|
923
|
+
dir,
|
|
924
|
+
name: await readName((0, node_path.join)(root, dir, "package.json"))
|
|
925
|
+
})));
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* The layout's packages found under the workspace root.
|
|
929
|
+
*/
|
|
930
|
+
async function readWorkspacePackages(cwd, layout) {
|
|
931
|
+
const root = workspaceRoot(cwd, layout);
|
|
932
|
+
return classifyPackages(await discoverPackages(root, await workspacePatterns(root, layout)), layout);
|
|
933
|
+
}
|
|
934
|
+
//#endregion
|
|
935
|
+
//#region src/checks/engines-floor.ts
|
|
936
|
+
/**
|
|
937
|
+
* The pattern that selects the root package, the `package.json` in the directory the checks run in.
|
|
938
|
+
*/
|
|
939
|
+
const ROOT_PACKAGE = ".";
|
|
940
|
+
const MANIFEST = "package.json";
|
|
941
|
+
const ANY_INSTALLED_COPY = {};
|
|
942
|
+
const DEPENDENCY_FIELDS = [
|
|
943
|
+
{
|
|
944
|
+
field: "dependencies",
|
|
945
|
+
noun: "dependency",
|
|
946
|
+
mayBeAbsent: () => false,
|
|
947
|
+
judgedCopy: () => ANY_INSTALLED_COPY
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
field: "optionalDependencies",
|
|
951
|
+
noun: "optional dependency",
|
|
952
|
+
mayBeAbsent: () => true,
|
|
953
|
+
judgedCopy: () => ANY_INSTALLED_COPY
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
field: "peerDependencies",
|
|
957
|
+
noun: "peer dependency",
|
|
958
|
+
mayBeAbsent: (manifest, name) => {
|
|
959
|
+
const meta = manifest["peerDependenciesMeta"];
|
|
960
|
+
const entry = isRecord(meta) ? meta[name] : void 0;
|
|
961
|
+
return isRecord(entry) && entry["optional"] === true;
|
|
962
|
+
},
|
|
963
|
+
judgedCopy: (spec) => {
|
|
964
|
+
const version = onlyAdmittedVersion(spec);
|
|
965
|
+
return version === null ? void 0 : { version };
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
];
|
|
969
|
+
/**
|
|
970
|
+
* The one version `spec` admits, when it is a semver range of a single comparator naming an exact version (`1.2.3` or `=1.2.3`); `null`, as semver gives for no version, for any other range, including one of a single major, and for a spec that is not a semver range (`workspace:^`, a tag or a URL).
|
|
971
|
+
*/
|
|
972
|
+
function onlyAdmittedVersion(spec) {
|
|
973
|
+
if ((0, semver.validRange)(spec) === null) return null;
|
|
974
|
+
const [comparators, ...others] = new semver.Range(spec).set;
|
|
975
|
+
if (comparators === void 0 || others.length > 0 || comparators.length !== 1) return null;
|
|
976
|
+
const [comparator] = comparators;
|
|
977
|
+
if (comparator?.operator !== "") return null;
|
|
978
|
+
return (0, semver.valid)(comparator.value);
|
|
979
|
+
}
|
|
980
|
+
async function readManifest(cwd, file) {
|
|
981
|
+
let content;
|
|
982
|
+
try {
|
|
983
|
+
content = JSON.parse(await (0, node_fs_promises.readFile)((0, node_path.resolve)(cwd, file), "utf8"));
|
|
984
|
+
} catch (error) {
|
|
985
|
+
throw new ConformanceError(`${file} cannot be read as JSON`, { cause: error });
|
|
986
|
+
}
|
|
987
|
+
if (!isRecord(content)) throw new ConformanceError(`${file} is not a JSON object`);
|
|
988
|
+
return content;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The `engines.node` range the manifest at `file` declares, or `undefined` when it declares none. A range semver cannot parse is a configuration error, since nothing can be said about the versions it admits.
|
|
992
|
+
*/
|
|
993
|
+
function nodeRange(manifest, file) {
|
|
994
|
+
const engines = manifest["engines"];
|
|
995
|
+
if (engines === void 0) return;
|
|
996
|
+
if (!isRecord(engines)) throw new ConformanceError(`${file}: engines must be an object`);
|
|
997
|
+
const node = engines["node"];
|
|
998
|
+
if (node === void 0) return;
|
|
999
|
+
if (typeof node !== "string") throw new ConformanceError(`${file}: engines.node must be a string, not ${JSON.stringify(node)}`);
|
|
1000
|
+
if ((0, semver.validRange)(node) === null) throw new ConformanceError(`${file}: engines.node '${node}' is not a valid semver range`);
|
|
1001
|
+
return node;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* The name and version spec of each dependency a dependency field of the manifest at `file` lists, sorted by name; none when the field is absent.
|
|
1005
|
+
*/
|
|
1006
|
+
function dependencyEntries(manifest, field, file) {
|
|
1007
|
+
const listed = manifest[field];
|
|
1008
|
+
if (listed === void 0) return [];
|
|
1009
|
+
if (!isRecord(listed)) throw new ConformanceError(`${file}: ${field} must be an object`);
|
|
1010
|
+
return Object.keys(listed).sort().map((name) => {
|
|
1011
|
+
const spec = listed[name];
|
|
1012
|
+
if (typeof spec !== "string") throw new ConformanceError(`${file}: ${field}.${name} must be a string, not ${JSON.stringify(spec)}`);
|
|
1013
|
+
return [name, spec];
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* The `package.json` of the dependency `name` as installed for the package in `packageDirectory`, found the way Node finds a package directory: in `node_modules` of the package's own directory, then of each directory above it, stopping at the working directory, since a copy above the workspace is not one its install put there. A symbolic link, as pnpm installs every dependency, is followed. `undefined` when no directory has it.
|
|
1018
|
+
*/
|
|
1019
|
+
function installedManifest(cwd, packageDirectory, name) {
|
|
1020
|
+
const top = (0, node_path.resolve)(cwd);
|
|
1021
|
+
for (let directory = (0, node_path.resolve)(cwd, packageDirectory);; directory = (0, node_path.dirname)(directory)) {
|
|
1022
|
+
const candidate = (0, node_path.join)(directory, "node_modules", name, MANIFEST);
|
|
1023
|
+
if ((0, node_fs.existsSync)(candidate)) return relativePosix(cwd, candidate);
|
|
1024
|
+
if (directory === top) return;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* The directories of the packages to judge: those `packages` selects, else the root package and the `packages` of `pnpm-workspace.yaml`. Selecting none is a configuration error, so a pattern that matches nothing never reads as a pass.
|
|
1029
|
+
*/
|
|
1030
|
+
async function packageDirectories(cwd, options) {
|
|
1031
|
+
const patterns = options.packages ?? [ROOT_PACKAGE, ...await pnpmWorkspacePatterns(cwd) ?? []];
|
|
1032
|
+
const members = await discoverPackages(cwd, patterns.filter((pattern) => pattern !== ROOT_PACKAGE));
|
|
1033
|
+
const directories = [...patterns.includes(ROOT_PACKAGE) ? [ROOT_PACKAGE] : [], ...members.map((member) => member.dir)];
|
|
1034
|
+
if (directories.length === 0) throw new ConformanceError(`no package.json is selected by ${patterns.join(", ")}`);
|
|
1035
|
+
return directories;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* The `engines.node` range of each package is a subset of the `engines.node` range of each dependency and optional dependency it lists, and of each peer dependency whose range admits a single version, as installed in `node_modules`, so the package admits no Node version that something it needs at run time rejects. A package that declares no range admits every version, so it is reported for each dependency that declares one. A dependency that declares no range constrains nothing and is ignored.
|
|
1039
|
+
*
|
|
1040
|
+
* The comparison is `semver.subset(package, dependency)`: every version the package's range admits satisfies the dependency's range. A floor alone is not enough, because a range of several lines (`^20.19.0 || ^22.13.0 || >=24`) also rejects the versions between them, which `>=20.19.0` admits. semver compares a range of several comparator sets set by set, so each set of the package's range must lie within one set of the dependency's; a set that only two adjacent dependency sets cover together (`>=22` against `^22.0.0 || >=23`) is reported, and splitting it the same way (`^22.0.0 || >=23`) satisfies the check.
|
|
1041
|
+
*
|
|
1042
|
+
* A peer dependency is judged only when its range admits a single version (`1.2.3` or `=1.2.3`), and then by the copy installed, which must be that version. The consumer chooses which admitted version to install, so the package's range holds when each Node version in it is accepted by some admitted version; one installed copy that rejects a Node version does not show that every admitted version does, and the others are not on disk.
|
|
1043
|
+
*
|
|
1044
|
+
* A dependency that is not installed fails the run: its range cannot be read, and passing it unjudged would read as a pass. An optional dependency and a peer dependency marked optional in `peerDependenciesMeta` may be absent and are then skipped.
|
|
1045
|
+
*/
|
|
1046
|
+
const enginesFloor = async ({ cwd, options }) => {
|
|
1047
|
+
const violations = [];
|
|
1048
|
+
for (const directory of await packageDirectories(cwd, options)) {
|
|
1049
|
+
const file = node_path.posix.join(directory, MANIFEST);
|
|
1050
|
+
const manifest = await readManifest(cwd, file);
|
|
1051
|
+
const own = nodeRange(manifest, file);
|
|
1052
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1053
|
+
for (const { field, noun, mayBeAbsent, judgedCopy } of DEPENDENCY_FIELDS) for (const [name, spec] of dependencyEntries(manifest, field, file).filter(([candidate]) => !seen.has(candidate))) {
|
|
1054
|
+
seen.add(name);
|
|
1055
|
+
const copy = judgedCopy(spec);
|
|
1056
|
+
if (copy === void 0) continue;
|
|
1057
|
+
const installed = installedManifest(cwd, directory, name);
|
|
1058
|
+
if (installed === void 0) {
|
|
1059
|
+
if (mayBeAbsent(manifest, name)) continue;
|
|
1060
|
+
throw new ConformanceError(`${file}: the ${noun} '${name}' is not installed (no node_modules/${name}/package.json in its directory or any directory above it up to the working directory); install the workspace before running engines-floor`);
|
|
1061
|
+
}
|
|
1062
|
+
const dependency = await readManifest(cwd, installed);
|
|
1063
|
+
if (copy.version !== void 0 && dependency["version"] !== copy.version) throw new ConformanceError(`${file}: the ${noun} '${name}' admits only version ${copy.version}, but ${installed} is version ${String(dependency["version"])}; install the workspace before running engines-floor`);
|
|
1064
|
+
const theirs = nodeRange(dependency, installed);
|
|
1065
|
+
if (theirs === void 0) continue;
|
|
1066
|
+
if (own === void 0) violations.push({
|
|
1067
|
+
code: "engines-floor/no-engines",
|
|
1068
|
+
message: `no engines.node is declared, so every Node version is admitted, but the ${noun} '${name}' requires engines.node '${theirs}'; declare a subset of that range`,
|
|
1069
|
+
file
|
|
1070
|
+
});
|
|
1071
|
+
else if (!(0, semver.subset)(own, theirs)) violations.push({
|
|
1072
|
+
code: "engines-floor/wider-than-dependency",
|
|
1073
|
+
message: `engines.node '${own}' admits Node versions that the ${noun} '${name}' rejects with engines.node '${theirs}'; narrow it to a subset of that range`,
|
|
1074
|
+
file
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return violations;
|
|
1079
|
+
};
|
|
1080
|
+
//#endregion
|
|
785
1081
|
//#region src/checks/eslint.ts
|
|
786
1082
|
/**
|
|
787
1083
|
* The patterns the full level lints when the options name none.
|
|
@@ -1110,145 +1406,6 @@ function unresolvedNameImportRule(packages) {
|
|
|
1110
1406
|
};
|
|
1111
1407
|
}
|
|
1112
1408
|
//#endregion
|
|
1113
|
-
//#region src/workspace/packages.ts
|
|
1114
|
-
function segmentsOf(path) {
|
|
1115
|
-
return path.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
1116
|
-
}
|
|
1117
|
-
function groupRoot(group) {
|
|
1118
|
-
return segmentsOf(group.path ?? group.name);
|
|
1119
|
-
}
|
|
1120
|
-
function startsWithSegments(segments, prefix) {
|
|
1121
|
-
return prefix.every((segment, index) => segments[index] === segment);
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* The group whose root is the longest prefix of `dir`; the first declared wins a tie. `undefined` when no group owns the directory.
|
|
1125
|
-
*/
|
|
1126
|
-
function owningGroup(dir, groups) {
|
|
1127
|
-
const segments = segmentsOf(dir);
|
|
1128
|
-
let best;
|
|
1129
|
-
let bestLength = -1;
|
|
1130
|
-
for (const group of groups) {
|
|
1131
|
-
const root = groupRoot(group);
|
|
1132
|
-
if (root.length > bestLength && startsWithSegments(segments, root)) {
|
|
1133
|
-
best = group;
|
|
1134
|
-
bestLength = root.length;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return best;
|
|
1138
|
-
}
|
|
1139
|
-
function rankOf(name, group, layout) {
|
|
1140
|
-
if (name !== void 0) {
|
|
1141
|
-
const rule = layout.nameRanks?.find((candidate) => new RegExp(candidate.pattern, "u").test(name));
|
|
1142
|
-
if (rule !== void 0) return rule.rank;
|
|
1143
|
-
}
|
|
1144
|
-
return group.rank ?? layout.defaultRank;
|
|
1145
|
-
}
|
|
1146
|
-
/**
|
|
1147
|
-
* The name without its npm scope: `@scope/store-cli` gives `store-cli`.
|
|
1148
|
-
*/
|
|
1149
|
-
function unscoped(name) {
|
|
1150
|
-
return name.replace(/^@[^/]+\//u, "");
|
|
1151
|
-
}
|
|
1152
|
-
/**
|
|
1153
|
-
* The longest slice value that is the whole unscoped name or its prefix up to a `-`.
|
|
1154
|
-
*/
|
|
1155
|
-
function sliceByNamePrefix(name, known) {
|
|
1156
|
-
const bare = unscoped(name);
|
|
1157
|
-
return [...known].filter((candidate) => bare === candidate || bare.startsWith(`${candidate}-`)).sort((a, b) => b.length - a.length)[0];
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Classify discovered packages by the layout: group, rank and slice. Pure.
|
|
1161
|
-
*
|
|
1162
|
-
* A package no group owns is a configuration error, because every check would otherwise skip it without saying so. Nested package directories are one too: the checks attribute a file to the package whose directory is its prefix, which is ambiguous for a package inside another. A `namePrefix` slice takes the longest slice value that a `segment` group produced and that prefixes the package's unscoped name; a package without a name has no such slice.
|
|
1163
|
-
*/
|
|
1164
|
-
function classifyPackages(discovered, layout) {
|
|
1165
|
-
const owned = discovered.map((found) => {
|
|
1166
|
-
const group = owningGroup(found.dir, layout.groups);
|
|
1167
|
-
if (group === void 0) throw new ConformanceError(`the package directory '${found.dir}' is not under any layout group's path; add a group whose path prefixes it, or narrow the workspace package globs`);
|
|
1168
|
-
return {
|
|
1169
|
-
found,
|
|
1170
|
-
group
|
|
1171
|
-
};
|
|
1172
|
-
});
|
|
1173
|
-
const knownSlices = /* @__PURE__ */ new Set();
|
|
1174
|
-
for (const { found, group } of owned) if (group.slice !== void 0 && "segment" in group.slice) {
|
|
1175
|
-
const value = segmentsOf(found.dir).slice(groupRoot(group).length)[group.slice.segment];
|
|
1176
|
-
if (value !== void 0) knownSlices.add(value);
|
|
1177
|
-
}
|
|
1178
|
-
const packages = owned.map(({ found, group }) => {
|
|
1179
|
-
let slice;
|
|
1180
|
-
if (group.slice !== void 0 && "segment" in group.slice) slice = segmentsOf(found.dir).slice(groupRoot(group).length)[group.slice.segment];
|
|
1181
|
-
else if (group.slice !== void 0 && found.name !== void 0) slice = sliceByNamePrefix(found.name, knownSlices);
|
|
1182
|
-
return {
|
|
1183
|
-
...found,
|
|
1184
|
-
group: group.name,
|
|
1185
|
-
rank: rankOf(found.name, group, layout),
|
|
1186
|
-
slice
|
|
1187
|
-
};
|
|
1188
|
-
});
|
|
1189
|
-
for (const outer of packages) {
|
|
1190
|
-
const nested = packages.find((inner) => inner.dir !== outer.dir && inner.dir.startsWith(`${outer.dir}/`));
|
|
1191
|
-
if (nested !== void 0) throw new ConformanceError(`the package directory '${nested.dir}' is inside the package directory '${outer.dir}'; workspace packages must not nest`);
|
|
1192
|
-
}
|
|
1193
|
-
return packages;
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* The workspace root: `layout.root` resolved against `cwd`, or `cwd` itself.
|
|
1197
|
-
*/
|
|
1198
|
-
function workspaceRoot(cwd, layout) {
|
|
1199
|
-
return layout.root === void 0 ? cwd : (0, node_path.resolve)(cwd, layout.root);
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* The package globs: `layout.packages`, else the `packages` list of `pnpm-workspace.yaml` in `root`.
|
|
1203
|
-
*/
|
|
1204
|
-
async function workspacePatterns(root, layout) {
|
|
1205
|
-
if (layout.packages !== void 0) return layout.packages;
|
|
1206
|
-
const file = (0, node_path.join)(root, "pnpm-workspace.yaml");
|
|
1207
|
-
let source;
|
|
1208
|
-
try {
|
|
1209
|
-
source = await (0, node_fs_promises.readFile)(file, "utf8");
|
|
1210
|
-
} catch (error) {
|
|
1211
|
-
throw new ConformanceError(`the layout has no 'packages' and ${file} cannot be read`, { cause: error });
|
|
1212
|
-
}
|
|
1213
|
-
const document = (0, yaml.parse)(source);
|
|
1214
|
-
if (typeof document !== "object" || document === null || !("packages" in document) || !Array.isArray(document.packages)) throw new ConformanceError(`${file} has no 'packages' list`);
|
|
1215
|
-
const patterns = document.packages;
|
|
1216
|
-
if (!patterns.every((pattern) => typeof pattern === "string")) throw new ConformanceError(`${file}: every entry of 'packages' must be a string`);
|
|
1217
|
-
return patterns;
|
|
1218
|
-
}
|
|
1219
|
-
async function readName(manifest) {
|
|
1220
|
-
let content;
|
|
1221
|
-
try {
|
|
1222
|
-
content = JSON.parse(await (0, node_fs_promises.readFile)(manifest, "utf8"));
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
throw new ConformanceError(`${manifest} cannot be read as JSON`, { cause: error });
|
|
1225
|
-
}
|
|
1226
|
-
if (typeof content === "object" && content !== null && "name" in content && typeof content.name === "string") return content.name;
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* The directories under `root` that the pnpm-style `patterns` select and that hold a `package.json`, sorted. A `!` pattern excludes. The root package itself (`.`) is not a member of any group and is left out.
|
|
1230
|
-
*/
|
|
1231
|
-
async function discoverPackages(root, patterns) {
|
|
1232
|
-
const manifests = patterns.map((pattern) => pattern.startsWith("!") ? `!${pattern.slice(1)}/**` : `${pattern}/package.json`);
|
|
1233
|
-
const dirs = (await (0, tinyglobby.glob)(manifests, {
|
|
1234
|
-
cwd: root,
|
|
1235
|
-
dot: true,
|
|
1236
|
-
ignore: [INSTALLED_DEPENDENCIES_GLOB],
|
|
1237
|
-
onlyFiles: true
|
|
1238
|
-
})).map((manifest) => manifest.replace(/\/?package\.json$/u, "")).filter((dir) => dir !== "");
|
|
1239
|
-
return Promise.all([...new Set(dirs)].sort().map(async (dir) => ({
|
|
1240
|
-
dir,
|
|
1241
|
-
name: await readName((0, node_path.join)(root, dir, "package.json"))
|
|
1242
|
-
})));
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* The layout's packages found under the workspace root.
|
|
1246
|
-
*/
|
|
1247
|
-
async function readWorkspacePackages(cwd, layout) {
|
|
1248
|
-
const root = workspaceRoot(cwd, layout);
|
|
1249
|
-
return classifyPackages(await discoverPackages(root, await workspacePatterns(root, layout)), layout);
|
|
1250
|
-
}
|
|
1251
|
-
//#endregion
|
|
1252
1409
|
//#region src/checks/import-graph.ts
|
|
1253
1410
|
/**
|
|
1254
1411
|
* Paths left out of the import graph unless the options say otherwise: a package's own installed dependencies.
|
|
@@ -3331,6 +3488,7 @@ const checks = zod.z.strictObject({
|
|
|
3331
3488
|
commitlint: zod.z.exactOptional(path),
|
|
3332
3489
|
release: zod.z.exactOptional(path)
|
|
3333
3490
|
}))),
|
|
3491
|
+
"engines-floor": zod.z.exactOptional(enabled(zod.z.strictObject({ packages: zod.z.exactOptional(paths) }))),
|
|
3334
3492
|
"dockerfile-package-manager": zod.z.exactOptional(enabled(zod.z.strictObject({
|
|
3335
3493
|
dockerfiles: zod.z.exactOptional(paths),
|
|
3336
3494
|
exclude: zod.z.exactOptional(paths),
|
|
@@ -3624,6 +3782,12 @@ const registry = {
|
|
|
3624
3782
|
select: (checks) => enabledOptions(checks["commit-types"]),
|
|
3625
3783
|
check: commitTypes
|
|
3626
3784
|
}),
|
|
3785
|
+
"engines-floor": plain({
|
|
3786
|
+
name: "engines-floor",
|
|
3787
|
+
description: "A package's engines.node range admits no Node version that one of its installed dependencies rejects",
|
|
3788
|
+
select: (checks) => enabledOptions(checks["engines-floor"]),
|
|
3789
|
+
check: enginesFloor
|
|
3790
|
+
}),
|
|
3627
3791
|
"dockerfile-package-manager": plain({
|
|
3628
3792
|
name: "dockerfile-package-manager",
|
|
3629
3793
|
description: "A package manager version pinned in a Dockerfile is the version packageManager names",
|
|
@@ -3953,6 +4117,7 @@ exports.derivedTypes = derivedTypes;
|
|
|
3953
4117
|
exports.dockerfilePackageManager = dockerfilePackageManager;
|
|
3954
4118
|
exports.effectiveEnv = effectiveEnv;
|
|
3955
4119
|
exports.effectivePermissions = effectivePermissions;
|
|
4120
|
+
exports.enginesFloor = enginesFloor;
|
|
3956
4121
|
exports.eslint = eslint;
|
|
3957
4122
|
exports.generators = generators;
|
|
3958
4123
|
exports.handlerCases = handlerCases;
|
package/dist/index.d.cts
CHANGED
|
@@ -524,6 +524,15 @@ interface CommitTypesOptions {
|
|
|
524
524
|
*/
|
|
525
525
|
readonly release?: string;
|
|
526
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Options of the `engines-floor` check: the `engines.node` range of a package is a subset of the `engines.node` range of each of its installed dependencies.
|
|
529
|
+
*/
|
|
530
|
+
interface EnginesFloorOptions {
|
|
531
|
+
/**
|
|
532
|
+
* Globs of the package directories to judge, in the form of the `packages` list of `pnpm-workspace.yaml` (a `!` pattern excludes), relative to the directory the checks run in; `.` is the root package. The root package and the `packages` of `pnpm-workspace.yaml`, when that file exists, when omitted.
|
|
533
|
+
*/
|
|
534
|
+
readonly packages?: readonly string[];
|
|
535
|
+
}
|
|
527
536
|
/**
|
|
528
537
|
* Options of the `dockerfile-package-manager` check: a package manager version pinned in a Dockerfile is the one `packageManager` names.
|
|
529
538
|
*/
|
|
@@ -790,6 +799,7 @@ interface CheckOptionsByName {
|
|
|
790
799
|
readonly "instruction-symlinks": InstructionSymlinksOptions;
|
|
791
800
|
readonly "single-storybook": SingleStorybookOptions;
|
|
792
801
|
readonly "commit-types": CommitTypesOptions;
|
|
802
|
+
readonly "engines-floor": EnginesFloorOptions;
|
|
793
803
|
readonly "dockerfile-package-manager": DockerfilePackageManagerOptions;
|
|
794
804
|
readonly "migrations-directory": MigrationsDirectoryOptions;
|
|
795
805
|
readonly eslint: EslintOptions;
|
|
@@ -872,6 +882,18 @@ export declare const derivedTypes: CheckFunction<DerivedTypesOptions>;
|
|
|
872
882
|
*/
|
|
873
883
|
export declare const dockerfilePackageManager: CheckFunction<DockerfilePackageManagerOptions>;
|
|
874
884
|
//#endregion
|
|
885
|
+
//#region src/checks/engines-floor.d.ts
|
|
886
|
+
/**
|
|
887
|
+
* The `engines.node` range of each package is a subset of the `engines.node` range of each dependency and optional dependency it lists, and of each peer dependency whose range admits a single version, as installed in `node_modules`, so the package admits no Node version that something it needs at run time rejects. A package that declares no range admits every version, so it is reported for each dependency that declares one. A dependency that declares no range constrains nothing and is ignored.
|
|
888
|
+
*
|
|
889
|
+
* The comparison is `semver.subset(package, dependency)`: every version the package's range admits satisfies the dependency's range. A floor alone is not enough, because a range of several lines (`^20.19.0 || ^22.13.0 || >=24`) also rejects the versions between them, which `>=20.19.0` admits. semver compares a range of several comparator sets set by set, so each set of the package's range must lie within one set of the dependency's; a set that only two adjacent dependency sets cover together (`>=22` against `^22.0.0 || >=23`) is reported, and splitting it the same way (`^22.0.0 || >=23`) satisfies the check.
|
|
890
|
+
*
|
|
891
|
+
* A peer dependency is judged only when its range admits a single version (`1.2.3` or `=1.2.3`), and then by the copy installed, which must be that version. The consumer chooses which admitted version to install, so the package's range holds when each Node version in it is accepted by some admitted version; one installed copy that rejects a Node version does not show that every admitted version does, and the others are not on disk.
|
|
892
|
+
*
|
|
893
|
+
* A dependency that is not installed fails the run: its range cannot be read, and passing it unjudged would read as a pass. An optional dependency and a peer dependency marked optional in `peerDependenciesMeta` may be absent and are then skipped.
|
|
894
|
+
*/
|
|
895
|
+
export declare const enginesFloor: CheckFunction<EnginesFloorOptions>;
|
|
896
|
+
//#endregion
|
|
875
897
|
//#region src/checks/eslint.d.ts
|
|
876
898
|
/**
|
|
877
899
|
* Proves the repository's own ESLint is applied, which no check that runs inside ESLint can notice: a config that dropped a shared preset, switched its rules off or narrowed `ignores` until nothing is linted still passes its own lint.
|
|
@@ -1386,4 +1408,4 @@ export declare function proveExhaustive(input: {
|
|
|
1386
1408
|
readonly compilerOptions?: CompilerOptions;
|
|
1387
1409
|
}): ExhaustivenessProof;
|
|
1388
1410
|
//#endregion
|
|
1389
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, 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, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1411
|
+
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, 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
|
@@ -524,6 +524,15 @@ interface CommitTypesOptions {
|
|
|
524
524
|
*/
|
|
525
525
|
readonly release?: string;
|
|
526
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Options of the `engines-floor` check: the `engines.node` range of a package is a subset of the `engines.node` range of each of its installed dependencies.
|
|
529
|
+
*/
|
|
530
|
+
interface EnginesFloorOptions {
|
|
531
|
+
/**
|
|
532
|
+
* Globs of the package directories to judge, in the form of the `packages` list of `pnpm-workspace.yaml` (a `!` pattern excludes), relative to the directory the checks run in; `.` is the root package. The root package and the `packages` of `pnpm-workspace.yaml`, when that file exists, when omitted.
|
|
533
|
+
*/
|
|
534
|
+
readonly packages?: readonly string[];
|
|
535
|
+
}
|
|
527
536
|
/**
|
|
528
537
|
* Options of the `dockerfile-package-manager` check: a package manager version pinned in a Dockerfile is the one `packageManager` names.
|
|
529
538
|
*/
|
|
@@ -790,6 +799,7 @@ interface CheckOptionsByName {
|
|
|
790
799
|
readonly "instruction-symlinks": InstructionSymlinksOptions;
|
|
791
800
|
readonly "single-storybook": SingleStorybookOptions;
|
|
792
801
|
readonly "commit-types": CommitTypesOptions;
|
|
802
|
+
readonly "engines-floor": EnginesFloorOptions;
|
|
793
803
|
readonly "dockerfile-package-manager": DockerfilePackageManagerOptions;
|
|
794
804
|
readonly "migrations-directory": MigrationsDirectoryOptions;
|
|
795
805
|
readonly eslint: EslintOptions;
|
|
@@ -872,6 +882,18 @@ export declare const derivedTypes: CheckFunction<DerivedTypesOptions>;
|
|
|
872
882
|
*/
|
|
873
883
|
export declare const dockerfilePackageManager: CheckFunction<DockerfilePackageManagerOptions>;
|
|
874
884
|
//#endregion
|
|
885
|
+
//#region src/checks/engines-floor.d.ts
|
|
886
|
+
/**
|
|
887
|
+
* The `engines.node` range of each package is a subset of the `engines.node` range of each dependency and optional dependency it lists, and of each peer dependency whose range admits a single version, as installed in `node_modules`, so the package admits no Node version that something it needs at run time rejects. A package that declares no range admits every version, so it is reported for each dependency that declares one. A dependency that declares no range constrains nothing and is ignored.
|
|
888
|
+
*
|
|
889
|
+
* The comparison is `semver.subset(package, dependency)`: every version the package's range admits satisfies the dependency's range. A floor alone is not enough, because a range of several lines (`^20.19.0 || ^22.13.0 || >=24`) also rejects the versions between them, which `>=20.19.0` admits. semver compares a range of several comparator sets set by set, so each set of the package's range must lie within one set of the dependency's; a set that only two adjacent dependency sets cover together (`>=22` against `^22.0.0 || >=23`) is reported, and splitting it the same way (`^22.0.0 || >=23`) satisfies the check.
|
|
890
|
+
*
|
|
891
|
+
* A peer dependency is judged only when its range admits a single version (`1.2.3` or `=1.2.3`), and then by the copy installed, which must be that version. The consumer chooses which admitted version to install, so the package's range holds when each Node version in it is accepted by some admitted version; one installed copy that rejects a Node version does not show that every admitted version does, and the others are not on disk.
|
|
892
|
+
*
|
|
893
|
+
* A dependency that is not installed fails the run: its range cannot be read, and passing it unjudged would read as a pass. An optional dependency and a peer dependency marked optional in `peerDependenciesMeta` may be absent and are then skipped.
|
|
894
|
+
*/
|
|
895
|
+
export declare const enginesFloor: CheckFunction<EnginesFloorOptions>;
|
|
896
|
+
//#endregion
|
|
875
897
|
//#region src/checks/eslint.d.ts
|
|
876
898
|
/**
|
|
877
899
|
* Proves the repository's own ESLint is applied, which no check that runs inside ESLint can notice: a config that dropped a shared preset, switched its rules off or narrowed `ignores` until nothing is linted still passes its own lint.
|
|
@@ -1386,4 +1408,4 @@ export declare function proveExhaustive(input: {
|
|
|
1386
1408
|
readonly compilerOptions?: CompilerOptions;
|
|
1387
1409
|
}): ExhaustivenessProof;
|
|
1388
1410
|
//#endregion
|
|
1389
|
-
export type { AggregateMappersOptions, BranchRules, CaseRemoval, CheckContext, CheckFunction, CheckName, CheckOptionsByName, CheckResult, ChecksConfig, CodecPairsOptions, CommandTypesOptions, CommitTypesOptions, ConformanceConfig, DeployDirectory, DeploySelector, DeployToolAdapter, DeployToolName, DerivedTypesOptions, DerivedTypesPair, DiscoveredPackage, DockerfilePackageManagerOptions, 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, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|
|
1411
|
+
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, SettingsMergeMethodsOptions, SettingsOptions, SettingsRequiredChecksOptions, SettingsReviewThreadResolutionOptions, SingleStorybookOptions, SourceLocation, Step, TypeErrorReport, UsesReference, Violation, Workflow, WorkflowActionPinningOptions, WorkflowCredentialsOptions, WorkflowJobOrderingOptions, WorkflowMergeGroupOptions, WorkflowReleaseJobOptions, WorkflowRepositoryDispatchOptions, WorkflowRunnerResolutionOptions, WorkflowSelection, WorkflowSkippableJobsOptions, WorkflowUpdateBotCooldownOptions, WorkflowVersionSingleSourceOptions, WorkspacePackage };
|