@r_masseater/msch 0.5.1 → 0.6.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 +4 -2
- package/dist/{cli-contract-CkYucdU4.mjs → cli-contract-DpQurUwC.mjs} +11 -5
- package/dist/cli-contract-DpQurUwC.mjs.map +1 -0
- package/dist/cli.mjs +453 -36
- package/dist/cli.mjs.map +1 -1
- package/dist/oxlint.mjs +1 -1
- package/package.json +11 -8
- package/src/vite.d.ts +8 -2
- package/dist/cli-contract-CkYucdU4.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
3
|
-
import { Array, Cause, Effect, FileSystem, Function, Layer, Number as Number$1, Option, Order, Path, Record, Schema, Stdio, Stream, String as String$1, pipe } from "effect";
|
|
4
|
-
import { layer } from "@effect/platform-node/NodeServices";
|
|
2
|
+
import { $ as isObjectLiteralExpression, A as nodeServicesLayer, B as isBinaryExpression, C as MANIFEST_FILE, D as workspaceDependencies, E as readManifest, F as getJSDocTags, G as isExportDeclaration, H as isClassDeclaration, I as getLeadingCommentRanges, J as isInterfaceDeclaration, K as isIdentifier, L as getNameOfDeclaration, M as IndexKind, N as InternalSymbolName, O as RepositoryFiles, P as SyntaxKind, Q as isNumericLiteral, R as getTextOfJSDocComment, S as uniqueByReference, T as dependencyEntries, U as isElementAccessExpression, V as isCallExpression, W as isEnumDeclaration, X as isModuleDeclaration, Y as isModuleBlock, Z as isNewExpression, _ as lineOf, a as problemAt, at as isSpreadElement, b as resolvedSymbol, c as childrenOf$1, ct as isTypeLiteralNode, d as declarationsOf, dt as isVariableDeclaration, et as isPropertyAccessExpression, f as descendantsOf, ft as isVariableDeclarationList, g as isTypeDeclaration, h as isGenericDeclaration, i as formatProblem, it as isSourceFile, j as API, k as nodeRepositoryFiles, l as columnOf, lt as isTypeReferenceNode, m as isExternalDeclaration, n as ProblemList, nt as isSatisfiesExpression, o as sortProblems, ot as isStringLiteral, p as importedModulesOf, pt as isVariableStatement, q as isImportDeclaration, rt as isSignatureDeclaration, s as NODE_MODULES_SEGMENT, st as isTypeAliasDeclaration, tt as isPropertyAssignment, u as contains, ut as isUnionTypeNode, v as propertyNamesOf, w as NODE_MODULES, x as symbolAt, y as referencedSymbols, z as isArrayLiteralExpression } from "./cli-contract-DpQurUwC.mjs";
|
|
3
|
+
import { Array, Cause, Effect, FileSystem, Function, HashMap, HashSet, Layer, Number as Number$1, Option, Order, Path, Predicate, Record, Result, Schema, Stdio, Stream, String as String$1, pipe } from "effect";
|
|
5
4
|
import { ChildProcess } from "effect/process";
|
|
6
5
|
import { resolveConfigFromFile } from "vite-plus";
|
|
7
6
|
import { LineCounter, isMap, isScalar, parseAllDocuments, parseDocument } from "yaml";
|
|
8
|
-
import { runMain } from "@effect/platform-node/NodeRuntime";
|
|
7
|
+
import { runMain } from "@effect/platform-node-shared/NodeRuntime";
|
|
9
8
|
import { FetchHttpClient } from "effect/http";
|
|
10
9
|
import { Otlp, OtlpSerialization } from "effect/observability";
|
|
11
10
|
// src/slots.ts
|
|
@@ -87,7 +86,7 @@ const valueText = (value) => {
|
|
|
87
86
|
const formatValues = (values) => pipe(values, Array.map(valueText), Array.join(" | "));
|
|
88
87
|
// src/flows.ts
|
|
89
88
|
const SCOPE_PREFIX$1 = "@";
|
|
90
|
-
const SCOPED_SEGMENTS = 2;
|
|
89
|
+
const SCOPED_SEGMENTS$1 = 2;
|
|
91
90
|
const UNSCOPED_SEGMENTS = 1;
|
|
92
91
|
const MODULE_PREFIX = /^".*?"\./u;
|
|
93
92
|
const packageOf$1 = (fileName) => {
|
|
@@ -96,7 +95,7 @@ const packageOf$1 = (fileName) => {
|
|
|
96
95
|
onSome: (index) => String$1.slice(index + NODE_MODULES_SEGMENT.length)(fileName)
|
|
97
96
|
}));
|
|
98
97
|
const segments = String$1.split("/")(afterModules);
|
|
99
|
-
if (String$1.startsWith(SCOPE_PREFIX$1)(afterModules)) return pipe(Array.take(segments, SCOPED_SEGMENTS), Array.join("/"));
|
|
98
|
+
if (String$1.startsWith(SCOPE_PREFIX$1)(afterModules)) return pipe(Array.take(segments, SCOPED_SEGMENTS$1), Array.join("/"));
|
|
100
99
|
return pipe(Array.take(segments, UNSCOPED_SEGMENTS), Array.join("/"));
|
|
101
100
|
};
|
|
102
101
|
const externalDeclarationOf = (context) => (symbol) => Array.findFirst(declarationsOf(symbol), (declaration) => isExternalDeclaration(context.program)(declaration));
|
|
@@ -457,7 +456,7 @@ const derivationScopeOf = (node) => {
|
|
|
457
456
|
};
|
|
458
457
|
const sourcesOf = (checker) => (node) => pipe(referencedSymbols(checker)(derivationScopeOf(node)), Array.filter((symbol) => Option.isSome(namedValuesOf(checker)(symbol))));
|
|
459
458
|
const sitesUnder = (checker) => (node) => {
|
|
460
|
-
const nested = pipe(childrenOf(node), Array.flatMap((child) => sitesUnder(checker)(child)));
|
|
459
|
+
const nested = pipe(childrenOf$1(node), Array.flatMap((child) => sitesUnder(checker)(child)));
|
|
461
460
|
if (Array.isReadonlyArrayNonEmpty(nested)) return nested;
|
|
462
461
|
return siteValuesOf(checker)(node).pipe(Option.map((values) => ({
|
|
463
462
|
node,
|
|
@@ -796,10 +795,10 @@ const checkRepository = Effect.fn("checkRepository")(function* checkRepository(r
|
|
|
796
795
|
]);
|
|
797
796
|
}, Effect.scoped);
|
|
798
797
|
// src/text-position.ts
|
|
799
|
-
const START$
|
|
798
|
+
const START$2 = 0;
|
|
800
799
|
const FIRST_COLUMN$1 = 1;
|
|
801
800
|
const positionOf = (source) => (index) => {
|
|
802
|
-
const before = pipe(source, String$1.slice(START$
|
|
801
|
+
const before = pipe(source, String$1.slice(START$2, index), String$1.split("\n"));
|
|
803
802
|
return {
|
|
804
803
|
line: before.length,
|
|
805
804
|
column: pipe(Array.last(before), Option.match({
|
|
@@ -811,17 +810,17 @@ const positionOf = (source) => (index) => {
|
|
|
811
810
|
// src/hidden-unicode.ts
|
|
812
811
|
const HIDDEN = /[\u00AD\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]|[\u{E0000}-\u{E007F}]/gu;
|
|
813
812
|
const NUL = "\0";
|
|
814
|
-
const START = 0;
|
|
813
|
+
const START$1 = 0;
|
|
815
814
|
const HEX = 16;
|
|
816
815
|
const CODE_POINT_WIDTH = 4;
|
|
817
|
-
const READ_CONCURRENCY$
|
|
816
|
+
const READ_CONCURRENCY$3 = 64;
|
|
818
817
|
const RULE$1 = "hidden-unicode";
|
|
819
|
-
const hexOf = (character) => pipe(String$1.codePointAt(START)(character), Option.getOrElse(() => START), (code) => code.toString(HEX), String$1.toUpperCase, String$1.padStart(CODE_POINT_WIDTH, "0"));
|
|
818
|
+
const hexOf = (character) => pipe(String$1.codePointAt(START$1)(character), Option.getOrElse(() => START$1), (code) => code.toString(HEX), String$1.toUpperCase, String$1.padStart(CODE_POINT_WIDTH, "0"));
|
|
820
819
|
const problemOf = (file, source) => (found) => {
|
|
821
|
-
const hex = hexOf(found[START]);
|
|
820
|
+
const hex = hexOf(found[START$1]);
|
|
822
821
|
return {
|
|
823
822
|
file,
|
|
824
|
-
...positionOf(source)(found.index ?? START),
|
|
823
|
+
...positionOf(source)(found.index ?? START$1),
|
|
825
824
|
rule: RULE$1,
|
|
826
825
|
message: `U+${hex} is invisible in editors and reviews and can change what the text means. Remove it, or write it as the escape \\u${hex}.`
|
|
827
826
|
};
|
|
@@ -840,32 +839,447 @@ const checkHiddenUnicode = Effect.fn("checkHiddenUnicode")(function* checkHidden
|
|
|
840
839
|
onNone: () => [],
|
|
841
840
|
onSome: (source) => problemsIn(file, source)
|
|
842
841
|
})));
|
|
843
|
-
}, { concurrency: READ_CONCURRENCY$
|
|
842
|
+
}, { concurrency: READ_CONCURRENCY$3 });
|
|
844
843
|
return Array.flatten(found);
|
|
845
844
|
});
|
|
846
|
-
// src/
|
|
847
|
-
const
|
|
848
|
-
const
|
|
845
|
+
// src/import-graph.ts
|
|
846
|
+
const RELATIVE = /^\.\.?(?:\/|$)/u;
|
|
847
|
+
const SUBPATH_IMPORT = "#";
|
|
848
|
+
const WILDCARD = "*";
|
|
849
|
+
const SCOPE = "@";
|
|
850
|
+
const SEPARATOR$3 = "/";
|
|
851
|
+
const SOURCE_SUFFIXES = [
|
|
852
|
+
"",
|
|
853
|
+
".ts",
|
|
854
|
+
".tsx",
|
|
855
|
+
"/index.ts"
|
|
856
|
+
];
|
|
857
|
+
const SCOPED_SEGMENTS = 2;
|
|
858
|
+
const PLAIN_SEGMENTS = 1;
|
|
859
|
+
const ImportsField = Schema.Struct({ imports: Schema.optional(Schema.Record(Schema.String, Schema.String)) });
|
|
860
|
+
const decodeImports = Schema.decodeOption(Schema.fromJsonString(ImportsField));
|
|
861
|
+
const packageNameOf = (specifier) => {
|
|
862
|
+
const segments = String$1.split(specifier, SEPARATOR$3);
|
|
863
|
+
if (String$1.startsWith(SCOPE)(specifier)) return pipe(segments, Array.take(SCOPED_SEGMENTS), Array.join(SEPARATOR$3));
|
|
864
|
+
return pipe(segments, Array.take(PLAIN_SEGMENTS), Array.join(SEPARATOR$3));
|
|
865
|
+
};
|
|
866
|
+
const literalText = (specifier) => pipe(Option.liftPredicate(specifier, isStringLiteral), Option.map((literal) => literal.text));
|
|
867
|
+
const runtimeSpecifierOf = (statement) => {
|
|
868
|
+
if (isImportDeclaration(statement)) {
|
|
869
|
+
const typeOnly = pipe(Option.fromUndefinedOr(statement.importClause), Option.exists((clause) => clause.phaseModifier === SyntaxKind.TypeKeyword));
|
|
870
|
+
return Option.filter(literalText(statement.moduleSpecifier), () => !typeOnly);
|
|
871
|
+
}
|
|
872
|
+
if (isExportDeclaration(statement) && !statement.isTypeOnly) return Option.flatMap(Option.fromUndefinedOr(statement.moduleSpecifier), literalText);
|
|
873
|
+
return Option.none();
|
|
874
|
+
};
|
|
875
|
+
const specifiersIn = (api, file, text) => {
|
|
876
|
+
const retained = api.createSourceFile(file, text);
|
|
877
|
+
const found = pipe(retained.sourceFile.statements, Array.fromIterable, Array.map(runtimeSpecifierOf));
|
|
878
|
+
retained.dispose();
|
|
879
|
+
return Array.getSomes(found);
|
|
880
|
+
};
|
|
881
|
+
const subpathTarget = (imports, specifier) => pipe(Record.toEntries(imports), Array.findFirst(([pattern, target]) => {
|
|
882
|
+
if (!String$1.endsWith(WILDCARD)(pattern)) return Option.liftPredicate(target, () => pattern === specifier);
|
|
883
|
+
const prefix = String$1.replace(WILDCARD, "")(pattern);
|
|
884
|
+
return Option.liftPredicate(String$1.replace(WILDCARD, String$1.replace(prefix, "")(specifier))(target), () => String$1.startsWith(prefix)(specifier));
|
|
885
|
+
}));
|
|
886
|
+
const existingSource = Effect.fn("existingSource")(function* existingSource(base) {
|
|
887
|
+
const files = yield* RepositoryFiles;
|
|
888
|
+
const found = yield* pipe(SOURCE_SUFFIXES, Effect.forEach((suffix) => {
|
|
889
|
+
const candidate = `${base}${suffix}`;
|
|
890
|
+
return files.isDirectory(candidate).pipe(Effect.map((directory) => Option.liftPredicate(candidate, () => Option.exists(directory, (isDirectory) => !isDirectory))));
|
|
891
|
+
}));
|
|
892
|
+
return Array.head(Array.getSomes(found));
|
|
893
|
+
});
|
|
894
|
+
const localFile = Effect.fn("localFile")(function* localFile({ directory, imports }, from, specifier) {
|
|
895
|
+
const files = yield* RepositoryFiles;
|
|
896
|
+
if (RELATIVE.test(specifier)) return yield* existingSource(files.resolve(files.dirname(from), specifier));
|
|
897
|
+
const target = subpathTarget(imports, specifier);
|
|
898
|
+
if (Option.isNone(target)) return Option.none();
|
|
899
|
+
return yield* existingSource(files.resolve(directory, target.value));
|
|
900
|
+
});
|
|
901
|
+
const referenceOf = (graph, from, specifier) => {
|
|
902
|
+
if (RELATIVE.test(specifier) || String$1.startsWith(SUBPATH_IMPORT)(specifier)) return localFile(graph, from, specifier).pipe(Effect.map(Option.map(Result.succeed)));
|
|
903
|
+
const name = packageNameOf(specifier);
|
|
904
|
+
return Effect.succeedSome(Result.fail(name));
|
|
905
|
+
};
|
|
906
|
+
const referencesIn = Effect.fn("referencesIn")(function* referencesIn(graph, file) {
|
|
907
|
+
const text = yield* (yield* RepositoryFiles).readFile(file);
|
|
908
|
+
const specifiers = Option.match(text, {
|
|
909
|
+
onNone: () => [],
|
|
910
|
+
onSome: (source) => specifiersIn(graph.api, file, source)
|
|
911
|
+
});
|
|
912
|
+
const references = yield* pipe(specifiers, Effect.forEach((specifier) => referenceOf(graph, file, specifier)));
|
|
913
|
+
const found = Array.getSomes(references);
|
|
914
|
+
return {
|
|
915
|
+
packages: Array.getFailures(found),
|
|
916
|
+
sources: Array.getSuccesses(found)
|
|
917
|
+
};
|
|
918
|
+
});
|
|
919
|
+
const packagesFrom = (graph, walk) => Array.match(walk.pending, {
|
|
920
|
+
onEmpty: () => Effect.succeed(walk.packages),
|
|
921
|
+
onNonEmpty: ([next, ...rest]) => {
|
|
922
|
+
if (HashSet.has(walk.visited, next)) return packagesFrom(graph, {
|
|
923
|
+
...walk,
|
|
924
|
+
pending: rest
|
|
925
|
+
});
|
|
926
|
+
return referencesIn(graph, next).pipe(Effect.flatMap(({ packages, sources }) => packagesFrom(graph, {
|
|
927
|
+
pending: [...rest, ...sources],
|
|
928
|
+
visited: HashSet.add(walk.visited, next),
|
|
929
|
+
packages: HashSet.union(walk.packages, HashSet.fromIterable(packages))
|
|
930
|
+
})));
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
/** @internal */
|
|
934
|
+
const packagesByEntry = Effect.fn("packagesByEntry")(function* packagesByEntry(api, directory, entries) {
|
|
935
|
+
const files = yield* RepositoryFiles;
|
|
936
|
+
const manifest = yield* files.readFile(files.join(directory, MANIFEST_FILE));
|
|
937
|
+
const graph = {
|
|
938
|
+
api,
|
|
939
|
+
directory,
|
|
940
|
+
imports: pipe(manifest, Option.flatMap(decodeImports), Option.flatMap((field) => Option.fromUndefinedOr(field.imports)), Option.getOrElse(() => ({})))
|
|
941
|
+
};
|
|
942
|
+
return yield* pipe(entries, Effect.forEach((entry) => packagesFrom(graph, {
|
|
943
|
+
pending: [entry],
|
|
944
|
+
visited: HashSet.empty(),
|
|
945
|
+
packages: HashSet.empty()
|
|
946
|
+
}).pipe(Effect.map((packages) => [entry, packages]))));
|
|
947
|
+
});
|
|
948
|
+
// src/installed-closure.ts
|
|
949
|
+
const EMPTY = 0;
|
|
950
|
+
const DependencyMap = Schema.optional(Schema.Record(Schema.String, Schema.String));
|
|
951
|
+
const PeerMetaEntry = Schema.Struct({ optional: Schema.optional(Schema.Boolean) });
|
|
952
|
+
const PeerMeta = Schema.optional(Schema.Record(Schema.String, PeerMetaEntry));
|
|
953
|
+
const InstalledManifest = Schema.Struct({
|
|
954
|
+
dependencies: DependencyMap,
|
|
955
|
+
optionalDependencies: DependencyMap,
|
|
956
|
+
peerDependencies: DependencyMap,
|
|
957
|
+
peerDependenciesMeta: PeerMeta
|
|
958
|
+
});
|
|
959
|
+
const decodeManifest = Schema.decodeOption(Schema.fromJsonString(InstalledManifest));
|
|
960
|
+
const keysOf$1 = (map) => pipe(Option.fromUndefinedOr(map), Option.map(Record.keys), Option.getOrElse(() => []));
|
|
961
|
+
const isOptionalPeer = (manifest, name) => pipe(Option.fromUndefinedOr(manifest.peerDependenciesMeta), Option.flatMap(Record.get(name)), Option.exists((meta) => meta.optional === true));
|
|
962
|
+
const installedNames = (manifest) => [
|
|
963
|
+
...keysOf$1(manifest.dependencies),
|
|
964
|
+
...keysOf$1(manifest.optionalDependencies),
|
|
965
|
+
...pipe(keysOf$1(manifest.peerDependencies), Array.filter((name) => !isOptionalPeer(manifest, name)))
|
|
966
|
+
];
|
|
967
|
+
const childrenOf = Effect.fn("childrenOf")(function* childrenOf(directory) {
|
|
968
|
+
const files = yield* RepositoryFiles;
|
|
969
|
+
const text = yield* files.readFile(files.join(directory, MANIFEST_FILE));
|
|
970
|
+
return pipe(text, Option.flatMap(decodeManifest), Option.map(installedNames), Option.getOrElse(() => []), Array.map((child) => [directory, child]));
|
|
971
|
+
});
|
|
972
|
+
const resolveFrom = (from, name) => Effect.gen(function* resolvePackage() {
|
|
973
|
+
const files = yield* RepositoryFiles;
|
|
974
|
+
const candidate = files.join(from, NODE_MODULES, name);
|
|
975
|
+
if (yield* files.exists(files.join(candidate, "package.json"))) {
|
|
976
|
+
const real = yield* files.realPath(candidate);
|
|
977
|
+
return Option.some(real);
|
|
978
|
+
}
|
|
979
|
+
const parent = files.dirname(from);
|
|
980
|
+
if (parent === from) return Option.none();
|
|
981
|
+
return yield* resolveFrom(parent, name);
|
|
982
|
+
});
|
|
983
|
+
const unseen = (from, name, seen) => resolveFrom(from, name).pipe(Effect.map(Option.filter((directory) => !HashSet.has(seen, directory))));
|
|
984
|
+
const closureOf = (provided, collect) => Array.match(collect.pending, {
|
|
985
|
+
onEmpty: () => Effect.succeed(collect.seen),
|
|
986
|
+
onNonEmpty: ([[from, name], ...rest]) => {
|
|
987
|
+
if (HashSet.has(provided, name)) return closureOf(provided, {
|
|
988
|
+
...collect,
|
|
989
|
+
pending: rest
|
|
990
|
+
});
|
|
991
|
+
return unseen(from, name, collect.seen).pipe(Effect.flatMap((resolved) => {
|
|
992
|
+
if (Option.isNone(resolved)) return closureOf(provided, {
|
|
993
|
+
...collect,
|
|
994
|
+
pending: rest
|
|
995
|
+
});
|
|
996
|
+
return childrenOf(resolved.value).pipe(Effect.flatMap((children) => closureOf(provided, {
|
|
997
|
+
pending: [...rest, ...children],
|
|
998
|
+
seen: HashSet.add(collect.seen, resolved.value)
|
|
999
|
+
})));
|
|
1000
|
+
}));
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
const directorySize = (directory) => Effect.gen(function* sizeOfDirectory() {
|
|
1004
|
+
const files = yield* RepositoryFiles;
|
|
1005
|
+
const names = yield* files.readDirectory(directory);
|
|
1006
|
+
const entries = pipe(names, Array.filter((name) => name !== NODE_MODULES), Array.map((name) => files.join(directory, name)));
|
|
1007
|
+
const sizes = yield* pipe(entries, Effect.forEach((entry) => files.isDirectory(entry).pipe(Effect.flatMap((kind) => {
|
|
1008
|
+
if (Option.isNone(kind)) return Effect.succeed(EMPTY);
|
|
1009
|
+
if (kind.value) return directorySize(entry);
|
|
1010
|
+
return files.fileSize(entry);
|
|
1011
|
+
}))));
|
|
1012
|
+
return Number$1.sumAll(sizes);
|
|
1013
|
+
});
|
|
1014
|
+
const installedClosure = ({ provided, from, name }) => closureOf(provided, {
|
|
1015
|
+
pending: [[from, name]],
|
|
1016
|
+
seen: HashSet.empty()
|
|
1017
|
+
});
|
|
1018
|
+
const installedSize = (directory) => {
|
|
1019
|
+
if (String$1.includes(`/node_modules/`)(directory)) return directorySize(directory);
|
|
1020
|
+
return Effect.succeed(EMPTY);
|
|
1021
|
+
};
|
|
1022
|
+
// src/public-entries.ts
|
|
1023
|
+
const SOURCE = /\.[cm]?tsx?$/u;
|
|
1024
|
+
const BUILT = /\.(?:d\.)?[cm]?[jt]s$/u;
|
|
1025
|
+
const DECLARATION = ".d.";
|
|
1026
|
+
const SEPARATOR$2 = "/";
|
|
1027
|
+
const ROOT_SUBPATH = ".";
|
|
1028
|
+
const BIN_LABEL = "bin";
|
|
1029
|
+
const targetsIn = (held) => {
|
|
1030
|
+
if (Predicate.isString(held)) return [held];
|
|
1031
|
+
if (!Predicate.isObject(held)) return [];
|
|
1032
|
+
return pipe(Record.values(held), Array.flatMap(targetsIn));
|
|
1033
|
+
};
|
|
1034
|
+
const subpathsOf = (exportsField) => {
|
|
1035
|
+
if (!Predicate.isObject(exportsField)) return [[ROOT_SUBPATH, exportsField]];
|
|
1036
|
+
const entries = Record.toEntries(exportsField);
|
|
1037
|
+
if (pipe(entries, Array.some(([key]) => String$1.startsWith(ROOT_SUBPATH)(key)))) return entries;
|
|
1038
|
+
return [[ROOT_SUBPATH, exportsField]];
|
|
1039
|
+
};
|
|
1040
|
+
const binsOf = (manifest) => {
|
|
1041
|
+
if (Predicate.isString(manifest.bin)) {
|
|
1042
|
+
const name = Option.getOrElse(Option.fromUndefinedOr(manifest.name), () => "");
|
|
1043
|
+
return [[`${BIN_LABEL} ${name}`, manifest.bin]];
|
|
1044
|
+
}
|
|
1045
|
+
if (!Predicate.isObject(manifest.bin)) return [];
|
|
1046
|
+
return pipe(Record.toEntries(manifest.bin), Array.map(([name, target]) => [`${BIN_LABEL} ${name}`, target]));
|
|
1047
|
+
};
|
|
1048
|
+
const baseNameOf = (target) => pipe(String$1.split(target, SEPARATOR$2), Array.last, Option.getOrElse(() => target), String$1.replace(BUILT, ""));
|
|
1049
|
+
const sourceOf = ({ join, packEntries }) => (target) => {
|
|
1050
|
+
if (SOURCE.test(target) && !String$1.includes(DECLARATION)(target)) return Option.some(join(target));
|
|
1051
|
+
const name = baseNameOf(target);
|
|
1052
|
+
return pipe(packEntries, Array.findFirst((entry) => entry.name === name), Option.map((entry) => entry.source));
|
|
1053
|
+
};
|
|
1054
|
+
const publicEntriesOf = (lookup) => pipe([...subpathsOf(lookup.manifest.exports), ...binsOf(lookup.manifest)], Array.flatMap(([label, held]) => pipe(targetsIn(held), Array.map(sourceOf(lookup)), Array.getSomes, Array.map((source) => ({
|
|
1055
|
+
label,
|
|
1056
|
+
source
|
|
1057
|
+
})))), Array.dedupeWith((left, right) => left.label === right.label && left.source === right.source));
|
|
1058
|
+
// src/published.ts
|
|
849
1059
|
const CONFIG_FILE = "vite.config.ts";
|
|
850
|
-
const
|
|
851
|
-
const READ_CONCURRENCY$
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
1060
|
+
const SEPARATOR$1 = "/";
|
|
1061
|
+
const READ_CONCURRENCY$2 = 16;
|
|
1062
|
+
const SOURCE_EXTENSION = /\.[cm]?tsx?$/u;
|
|
1063
|
+
const UNIT_BYTES = [
|
|
1064
|
+
["B", 1],
|
|
1065
|
+
["kB", 1e3],
|
|
1066
|
+
["MB", 1e6]
|
|
1067
|
+
];
|
|
1068
|
+
const SizeText = Schema.TemplateLiteralParser([
|
|
1069
|
+
Schema.Finite,
|
|
1070
|
+
" ",
|
|
1071
|
+
Schema.Literals([
|
|
1072
|
+
"B",
|
|
1073
|
+
"kB",
|
|
1074
|
+
"MB"
|
|
1075
|
+
])
|
|
1076
|
+
]);
|
|
1077
|
+
const PackEntry = Schema.Union([
|
|
1078
|
+
Schema.String,
|
|
1079
|
+
Schema.Array(Schema.String),
|
|
1080
|
+
Schema.Record(Schema.String, Schema.String)
|
|
1081
|
+
]);
|
|
1082
|
+
const PackSettings = Schema.Struct({ entry: Schema.optional(PackEntry) });
|
|
1083
|
+
const PackList = Schema.Union([PackSettings, Schema.Array(PackSettings)]);
|
|
1084
|
+
const isEntryList = Schema.is(Schema.Array(Schema.String));
|
|
1085
|
+
const WorkspaceConfig = Schema.Struct({
|
|
1086
|
+
msch: Schema.Struct({ publish: Schema.Struct({ installSize: SizeText }) }),
|
|
1087
|
+
pack: Schema.optional(PackList)
|
|
1088
|
+
});
|
|
1089
|
+
const PublicManifest = Schema.Struct({ private: Schema.optional(Schema.Literal(false)) });
|
|
856
1090
|
const taggedError$2 = Schema.TaggedError;
|
|
857
1091
|
var WorkspaceConfigUnreadable = class extends taggedError$2()("WorkspaceConfigUnreadable", { file: Schema.String }) {};
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1092
|
+
const bytesPer = (unit) => pipe(UNIT_BYTES, Array.findFirst(([name]) => name === unit), Option.map(([, bytes]) => bytes), Option.getOrElse(() => NaN));
|
|
1093
|
+
const nameOf = (source) => pipe(String$1.split(source, SEPARATOR$1), Array.last, Option.getOrElse(() => source), String$1.replace(SOURCE_EXTENSION, ""));
|
|
1094
|
+
const entriesOf = (entry) => {
|
|
1095
|
+
if (Predicate.isString(entry)) return [{
|
|
1096
|
+
name: nameOf(entry),
|
|
1097
|
+
source: entry
|
|
1098
|
+
}];
|
|
1099
|
+
if (isEntryList(entry)) return pipe(entry, Array.map((source) => ({
|
|
1100
|
+
name: nameOf(source),
|
|
1101
|
+
source
|
|
1102
|
+
})));
|
|
1103
|
+
return pipe(Record.toEntries(entry), Array.map(([name, source]) => ({
|
|
1104
|
+
name,
|
|
1105
|
+
source
|
|
1106
|
+
})));
|
|
1107
|
+
};
|
|
1108
|
+
const packEntriesOf = (pack) => pipe(Option.fromUndefinedOr(pack), Option.map(Array.ensure), Option.getOrElse(() => []), Array.flatMap((setting) => pipe(Option.fromUndefinedOr(setting.entry), Option.match({
|
|
1109
|
+
onNone: () => [],
|
|
1110
|
+
onSome: entriesOf
|
|
1111
|
+
}))));
|
|
1112
|
+
const settingsIn = Effect.fn("settingsIn")(function* settingsIn(file) {
|
|
863
1113
|
const config = yield* Effect.tryPromise({
|
|
864
1114
|
try: () => resolveConfigFromFile(file),
|
|
865
1115
|
catch: () => WorkspaceConfigUnreadable.make({ file })
|
|
866
1116
|
}).pipe(Effect.option);
|
|
867
|
-
return pipe(config, Option.flatMap(Schema.decodeUnknownOption(WorkspaceConfig))
|
|
1117
|
+
return pipe(config, Option.flatMap(Schema.decodeUnknownOption(WorkspaceConfig)));
|
|
1118
|
+
});
|
|
1119
|
+
const isPublicPackage = Effect.fn("isPublicPackage")(function* isPublicPackage(directory) {
|
|
1120
|
+
const files = yield* RepositoryFiles;
|
|
1121
|
+
const text = yield* files.readFile(files.join(directory, MANIFEST_FILE));
|
|
1122
|
+
return Option.exists(text, (found) => Option.isSome(Schema.decodeOption(Schema.fromJsonString(PublicManifest))(found)));
|
|
1123
|
+
});
|
|
1124
|
+
const workspaceOf = (directory, configFile, join) => ({ msch, pack }) => {
|
|
1125
|
+
const [amount, , unit] = msch.publish.installSize;
|
|
1126
|
+
return {
|
|
1127
|
+
directory,
|
|
1128
|
+
configFile,
|
|
1129
|
+
installSize: `${amount} ${unit}`,
|
|
1130
|
+
installBudget: amount * bytesPer(unit),
|
|
1131
|
+
packEntries: pipe(packEntriesOf(pack), Array.map((entry) => ({
|
|
1132
|
+
name: entry.name,
|
|
1133
|
+
source: join(entry.source)
|
|
1134
|
+
})))
|
|
1135
|
+
};
|
|
1136
|
+
};
|
|
1137
|
+
const publishedIn = Effect.fn("publishedIn")(function* publishedIn(root, configName) {
|
|
1138
|
+
const files = yield* RepositoryFiles;
|
|
1139
|
+
const configFile = files.join(root, configName);
|
|
1140
|
+
const directory = files.dirname(configFile);
|
|
1141
|
+
if (!(yield* isPublicPackage(directory))) return Option.none();
|
|
1142
|
+
const settings = yield* settingsIn(configFile);
|
|
1143
|
+
return pipe(settings, Option.map(workspaceOf(directory, configFile, (source) => files.join(directory, source))));
|
|
1144
|
+
});
|
|
1145
|
+
/** @internal */
|
|
1146
|
+
const publishedWorkspaces = Effect.fn("publishedWorkspaces")(function* publishedWorkspaces(root) {
|
|
1147
|
+
const files = yield* RepositoryFiles;
|
|
1148
|
+
const names = yield* files.listedFiles(root);
|
|
1149
|
+
const configs = pipe(names, Array.filter((name) => files.basename(name) === CONFIG_FILE && String$1.includes(SEPARATOR$1)(name)));
|
|
1150
|
+
const found = yield* Effect.forEach(configs, (name) => publishedIn(root, name), { concurrency: READ_CONCURRENCY$2 });
|
|
1151
|
+
return Array.getSomes(found);
|
|
1152
|
+
});
|
|
1153
|
+
// src/install-size.ts
|
|
1154
|
+
const INSTALL_SIZE = "install-size";
|
|
1155
|
+
const STACK_DEPENDENCY = "stack-dependency";
|
|
1156
|
+
const STACK_CATALOG = "catalog:stack";
|
|
1157
|
+
const BUDGET_KEY = "installSize";
|
|
1158
|
+
const DEPENDENCIES_KEY = "\"dependencies\"";
|
|
1159
|
+
const LISTED_LARGEST = 5;
|
|
1160
|
+
const SIZE_CONCURRENCY = 32;
|
|
1161
|
+
const MEGABYTE = 1e6;
|
|
1162
|
+
const MEGABYTE_DIGITS = 1;
|
|
1163
|
+
const START = 0;
|
|
1164
|
+
const SINGLE_ENTRY = 1;
|
|
1165
|
+
const megabytes = (bytes) => `${(bytes / MEGABYTE).toFixed(MEGABYTE_DIGITS)} MB`;
|
|
1166
|
+
const usageHint = (dependency, entryCount) => {
|
|
1167
|
+
if (!(entryCount > SINGLE_ENTRY && dependency.entries.length > START && dependency.entries.length < entryCount)) return "";
|
|
1168
|
+
return ` (imported only from ${Array.join(dependency.entries, ", ")})`;
|
|
1169
|
+
};
|
|
1170
|
+
const listed = ({ largest, entryCount }) => pipe(largest, Array.map((dependency) => `${dependency.name} ${megabytes(dependency.size)}${usageHint(dependency, entryCount)}`), Array.join(", "));
|
|
1171
|
+
const budgetProblem = Effect.fn("budgetProblem")(function* budgetProblem(overrun) {
|
|
1172
|
+
const files = yield* RepositoryFiles;
|
|
1173
|
+
const { workspace, total } = overrun;
|
|
1174
|
+
const config = yield* files.readFile(workspace.configFile);
|
|
1175
|
+
const source = Option.getOrElse(config, () => "");
|
|
1176
|
+
const index = Option.getOrElse(String$1.indexOf(BUDGET_KEY)(source), () => START);
|
|
1177
|
+
return {
|
|
1178
|
+
file: workspace.configFile,
|
|
1179
|
+
...positionOf(source)(index),
|
|
1180
|
+
rule: INSTALL_SIZE,
|
|
1181
|
+
message: `installing this package adds ${megabytes(total)} of dependencies besides its peers, over the ${workspace.installSize} in msch.publish.installSize. Largest: ${listed(overrun)}. Make a dependency imported only from some entries an optional peer, move one that the installing project already has to peerDependencies, or replace a heavy one. Raise installSize only when none of these applies.`
|
|
1182
|
+
};
|
|
1183
|
+
});
|
|
1184
|
+
const measure = Effect.fn("measure")(function* measure({ workspace, provided }, direct) {
|
|
1185
|
+
const closures = yield* pipe(direct, Effect.forEach((name) => installedClosure({
|
|
1186
|
+
provided,
|
|
1187
|
+
from: workspace.directory,
|
|
1188
|
+
name
|
|
1189
|
+
}).pipe(Effect.map((packages) => [name, packages]))));
|
|
1190
|
+
const everything = pipe(closures, Array.flatMap(([, packages]) => Array.fromIterable(packages)), HashSet.fromIterable);
|
|
1191
|
+
const measured = yield* Effect.forEach(everything, (found) => installedSize(found).pipe(Effect.map((size) => [found, size])), { concurrency: SIZE_CONCURRENCY });
|
|
1192
|
+
const sizes = HashMap.fromIterable(measured);
|
|
1193
|
+
const sizeOf = (packages) => Number$1.sumAll(pipe(Array.fromIterable(packages), Array.map((found) => Option.getOrElse(HashMap.get(sizes, found), () => START))));
|
|
1194
|
+
return {
|
|
1195
|
+
closures,
|
|
1196
|
+
sizeOf,
|
|
1197
|
+
total: sizeOf(everything)
|
|
1198
|
+
};
|
|
1199
|
+
});
|
|
1200
|
+
const importersOf = Effect.fn("importersOf")(function* importersOf({ workspace, entries }) {
|
|
1201
|
+
const compiler = yield* typeScriptCompiler;
|
|
1202
|
+
const sources = pipe(entries, Array.map((entry) => entry.source), Array.dedupe);
|
|
1203
|
+
const byEntry = yield* packagesByEntry(compiler.api, workspace.directory, sources);
|
|
1204
|
+
const reached = HashMap.fromIterable(byEntry);
|
|
1205
|
+
return (name) => pipe(entries, Array.filter((entry) => pipe(HashMap.get(reached, entry.source), Option.exists(HashSet.has(name)))), Array.map((entry) => entry.label), Array.dedupe);
|
|
1206
|
+
}, Effect.scoped);
|
|
1207
|
+
const bySizeDescending = Order.mapInput(Order.flip(Order.Number), (dependency) => dependency.size);
|
|
1208
|
+
const sizeProblems = Effect.fn("sizeProblems")(function* sizeProblems(published, direct) {
|
|
1209
|
+
const { closures, sizeOf, total } = yield* measure(published, direct);
|
|
1210
|
+
if (total <= published.workspace.installBudget) return [];
|
|
1211
|
+
const importers = yield* importersOf(published);
|
|
1212
|
+
const largest = pipe(closures, Array.map(([name, packages]) => ({
|
|
1213
|
+
name,
|
|
1214
|
+
size: sizeOf(packages),
|
|
1215
|
+
entries: importers(name)
|
|
1216
|
+
})), Array.sortBy(bySizeDescending), Array.take(LISTED_LARGEST));
|
|
1217
|
+
const entryCount = pipe(published.entries, Array.map((entry) => entry.label), Array.dedupe).length;
|
|
1218
|
+
return [yield* budgetProblem({
|
|
1219
|
+
workspace: published.workspace,
|
|
1220
|
+
total,
|
|
1221
|
+
largest,
|
|
1222
|
+
entryCount
|
|
1223
|
+
})];
|
|
1224
|
+
});
|
|
1225
|
+
const stackProblems = (file, source, dependencies) => {
|
|
1226
|
+
const section = Option.getOrElse(String$1.indexOf(DEPENDENCIES_KEY)(source), () => START);
|
|
1227
|
+
const rest = String$1.slice(section)(source);
|
|
1228
|
+
return pipe(Record.toEntries(dependencies), Array.filter(([, specifier]) => specifier === STACK_CATALOG), Array.map(([name]) => ({
|
|
1229
|
+
file,
|
|
1230
|
+
...positionOf(source)(section + Option.getOrElse(String$1.indexOf(`"${name}"`)(rest), () => START)),
|
|
1231
|
+
rule: STACK_DEPENDENCY,
|
|
1232
|
+
message: `${name} comes from the stack catalog, so every project that installs this package already has it. Move it to peerDependencies and keep it in devDependencies, so the installing project shares one copy.`
|
|
1233
|
+
})));
|
|
1234
|
+
};
|
|
1235
|
+
const PublishedManifest = Schema.Struct({
|
|
1236
|
+
name: Schema.optional(Schema.String),
|
|
1237
|
+
dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
1238
|
+
peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
1239
|
+
exports: Schema.optional(Schema.Unknown),
|
|
1240
|
+
bin: Schema.optional(Schema.Unknown)
|
|
868
1241
|
});
|
|
1242
|
+
const problemsOf = Effect.fn("problemsOf")(function* problemsOf(workspace, file, source) {
|
|
1243
|
+
const files = yield* RepositoryFiles;
|
|
1244
|
+
const manifest = Schema.decodeOption(Schema.fromJsonString(PublishedManifest))(source);
|
|
1245
|
+
if (Option.isNone(manifest)) return [];
|
|
1246
|
+
const dependencies = Option.getOrElse(Option.fromUndefinedOr(manifest.value.dependencies), () => ({}));
|
|
1247
|
+
const entries = publicEntriesOf({
|
|
1248
|
+
manifest: manifest.value,
|
|
1249
|
+
join: (target) => files.join(workspace.directory, target),
|
|
1250
|
+
packEntries: workspace.packEntries
|
|
1251
|
+
});
|
|
1252
|
+
const provided = HashSet.fromIterable(keysOf$1(manifest.value.peerDependencies));
|
|
1253
|
+
const sized = yield* sizeProblems({
|
|
1254
|
+
workspace,
|
|
1255
|
+
entries,
|
|
1256
|
+
provided
|
|
1257
|
+
}, Record.keys(dependencies));
|
|
1258
|
+
return [...stackProblems(file, source, dependencies), ...sized];
|
|
1259
|
+
});
|
|
1260
|
+
const workspaceProblems = Effect.fn("workspaceProblems")(function* workspaceProblems(workspace) {
|
|
1261
|
+
const files = yield* RepositoryFiles;
|
|
1262
|
+
const file = files.join(workspace.directory, MANIFEST_FILE);
|
|
1263
|
+
const source = yield* files.readFile(file);
|
|
1264
|
+
return yield* Option.match(source, {
|
|
1265
|
+
onNone: () => Effect.succeed([]),
|
|
1266
|
+
onSome: (text) => problemsOf(workspace, file, text)
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
/** @internal */
|
|
1270
|
+
const checkPublishedPackages = Effect.fn("checkPublishedPackages")(function* checkPublishedPackages(root) {
|
|
1271
|
+
const workspaces = yield* publishedWorkspaces(root);
|
|
1272
|
+
const found = yield* pipe(workspaces, Effect.forEach(workspaceProblems));
|
|
1273
|
+
return Array.flatten(found);
|
|
1274
|
+
});
|
|
1275
|
+
// src/nested-docs.ts
|
|
1276
|
+
const RULE = "nested-docs";
|
|
1277
|
+
const SEPARATOR = "/";
|
|
1278
|
+
const FIRST = 1;
|
|
1279
|
+
const READ_CONCURRENCY$1 = 64;
|
|
1280
|
+
const AGENT_DOCS = /^(?:AGENTS|CLAUDE)(?:\.[^/]+)?\.md$/iu;
|
|
1281
|
+
const README = /^README(?:\.[^/]+)?$/iu;
|
|
1282
|
+
const isNested = (name) => String$1.includes(SEPARATOR)(name);
|
|
869
1283
|
const problemFor = (file, message) => ({
|
|
870
1284
|
file,
|
|
871
1285
|
line: FIRST,
|
|
@@ -874,21 +1288,23 @@ const problemFor = (file, message) => ({
|
|
|
874
1288
|
message
|
|
875
1289
|
});
|
|
876
1290
|
const AGENT_DOCS_MESSAGE = "belongs only at the repository root. Move what agents need into the root AGENTS.md and delete this file.";
|
|
877
|
-
const README_MESSAGE = "is allowed below the root only in a workspace published to npm, marked by msch: { publish:
|
|
878
|
-
const problemIn = Effect.fn("problemIn")(function* problemIn(root, name) {
|
|
1291
|
+
const README_MESSAGE = "is allowed below the root only in a workspace published to npm, marked by msch: { publish: { installSize } } in its vite.config.ts. Set it there if the workspace is published, or move the content into the root README.md and delete this file.";
|
|
1292
|
+
const problemIn = Effect.fn("problemIn")(function* problemIn(root, published, name) {
|
|
879
1293
|
const files = yield* RepositoryFiles;
|
|
880
1294
|
const base = files.basename(name);
|
|
881
1295
|
const file = files.join(root, name);
|
|
882
1296
|
if (AGENT_DOCS.test(base)) return Option.some(problemFor(file, `${base} ${AGENT_DOCS_MESSAGE}`));
|
|
883
1297
|
if (!README.test(base)) return Option.none();
|
|
884
|
-
if (
|
|
1298
|
+
if (HashSet.has(published, files.dirname(file))) return Option.none();
|
|
885
1299
|
return Option.some(problemFor(file, `${base} ${README_MESSAGE}`));
|
|
886
1300
|
});
|
|
887
1301
|
/** @internal */
|
|
888
1302
|
const checkNestedDocs = Effect.fn("checkNestedDocs")(function* checkNestedDocs(root) {
|
|
889
1303
|
const names = yield* (yield* RepositoryFiles).listedFiles(root);
|
|
890
1304
|
const nested = pipe(names, Array.filter(isNested));
|
|
891
|
-
const
|
|
1305
|
+
const workspaces = yield* publishedWorkspaces(root);
|
|
1306
|
+
const published = pipe(workspaces, Array.map((workspace) => workspace.directory), HashSet.fromIterable);
|
|
1307
|
+
const found = yield* Effect.forEach(nested, (name) => problemIn(root, published, name), { concurrency: READ_CONCURRENCY$1 });
|
|
892
1308
|
return Array.getSomes(found);
|
|
893
1309
|
});
|
|
894
1310
|
// src/override-findings.ts
|
|
@@ -1251,7 +1667,8 @@ const checkAll = Effect.fn("checkAll")(function* checkAll(root) {
|
|
|
1251
1667
|
checkOverrides(root),
|
|
1252
1668
|
checkHiddenUnicode(root),
|
|
1253
1669
|
checkPython(root),
|
|
1254
|
-
checkNestedDocs(root)
|
|
1670
|
+
checkNestedDocs(root),
|
|
1671
|
+
checkPublishedPackages(root)
|
|
1255
1672
|
], { concurrency: "unbounded" });
|
|
1256
1673
|
return Array.flatten(found);
|
|
1257
1674
|
});
|
|
@@ -1271,7 +1688,7 @@ const reportFailure = (cause) => Effect.gen(function* report() {
|
|
|
1271
1688
|
});
|
|
1272
1689
|
const tracing = Otlp.layerFromConfig({ resource: { serviceName: "msch" } }).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
|
|
1273
1690
|
const runCli = (program) => {
|
|
1274
|
-
const services = Layer.mergeAll(nodeRepositoryFiles, tracing).pipe(Layer.provideMerge(
|
|
1691
|
+
const services = Layer.mergeAll(nodeRepositoryFiles, tracing).pipe(Layer.provideMerge(nodeServicesLayer));
|
|
1275
1692
|
const main = Layer.build(services).pipe(Effect.flatMap((context) => Effect.provideContext(program.pipe(Effect.tapCause(reportFailure)), context)), Effect.scoped);
|
|
1276
1693
|
runMain(main, { disableErrorReporting: true });
|
|
1277
1694
|
};
|