@r_masseater/msch 0.0.0-stage → 0.2.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/dist/cli.mjs ADDED
@@ -0,0 +1,1229 @@
1
+ #!/usr/bin/env node
2
+ import { $ as isSignatureDeclaration, A as API, B as isCallExpression, C as MANIFEST_FILE, D as workspaceDependencies, E as readManifest, F as getLeadingCommentRanges, G as isInterfaceDeclaration, H as isElementAccessExpression, I as getNameOfDeclaration, J as isNewExpression, K as isModuleBlock, L as getTextOfJSDocComment, M as InternalSymbolName, N as SyntaxKind, O as RepositoryFiles, P as getJSDocTags, Q as isPropertyAssignment, R as isArrayLiteralExpression, S as uniqueByReference, T as dependencyEntries, U as isEnumDeclaration, V as isClassDeclaration, W as isIdentifier, X as isObjectLiteralExpression, Y as isNumericLiteral, Z as isPropertyAccessExpression, _ as lineOf, a as problemAt, at as isUnionTypeNode, b as resolvedSymbol, c as childrenOf, ct as isVariableStatement, d as declarationsOf, et as isSourceFile, f as descendantsOf, g as isTypeDeclaration, h as isGenericDeclaration, i as formatProblem, it as isTypeReferenceNode, j as IndexKind, k as nodeRepositoryFiles, l as columnOf, m as isExternalDeclaration, n as ProblemList, nt as isTypeAliasDeclaration, o as sortProblems, ot as isVariableDeclaration, p as importedModulesOf, q as isModuleDeclaration, rt as isTypeLiteralNode, s as NODE_MODULES_SEGMENT, st as isVariableDeclarationList, tt as isStringLiteral, u as contains, v as propertyNamesOf, w as NODE_MODULES, x as symbolAt, y as referencedSymbols, z as isBinaryExpression } from "./cli-contract-BRpk_UKr.mjs";
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";
5
+ import { ChildProcess } from "effect/process";
6
+ import { LineCounter, isMap, isScalar, parseAllDocuments, parseDocument } from "yaml";
7
+ import { runMain } from "@effect/platform-node/NodeRuntime";
8
+ import { FetchHttpClient } from "effect/http";
9
+ import { Otlp, OtlpSerialization } from "effect/observability";
10
+ // src/slots.ts
11
+ const ANNOTATED_KINDS = /* @__PURE__ */ new Set([
12
+ SyntaxKind.Parameter,
13
+ SyntaxKind.VariableDeclaration,
14
+ SyntaxKind.PropertySignature,
15
+ SyntaxKind.PropertyDeclaration
16
+ ]);
17
+ const isAnnotated = (declaration) => ANNOTATED_KINDS.has(declaration.kind);
18
+ const annotationOf = (declaration) => Option.liftPredicate(declaration, isAnnotated).pipe(Option.flatMap((annotated) => Option.fromUndefinedOr(annotated.type)));
19
+ const argumentSlot = (checker) => (expression) => {
20
+ const call = expression.parent;
21
+ if (!isCallExpression(call) && !isNewExpression(call)) return Option.none();
22
+ const { arguments: listed } = call;
23
+ const index = (listed ?? []).indexOf(expression);
24
+ return Option.fromUndefinedOr(checker.getResolvedSignature(call).declaration).pipe(Option.flatMap((handle) => Option.fromUndefinedOr(handle.resolve())), Option.filter(isSignatureDeclaration), Option.flatMap((declaration) => Array.get(declaration.parameters, index)));
25
+ };
26
+ const propertySlot = (checker) => (expression) => {
27
+ const assignment = expression.parent;
28
+ if (!isPropertyAssignment(assignment) || !isObjectLiteralExpression(assignment.parent)) return Option.none();
29
+ return Option.fromUndefinedOr(checker.getContextualType(assignment.parent)).pipe(Option.flatMap((objectType) => Option.fromUndefinedOr(objectType.getProperty(assignment.name.getText()))), Option.flatMap((property) => Array.head(declarationsOf(property))));
30
+ };
31
+ const variableSlot = (expression) => {
32
+ const declaration = expression.parent;
33
+ if (!isVariableDeclaration(declaration) || declaration.initializer !== expression) return Option.none();
34
+ return Option.some(declaration);
35
+ };
36
+ const slotOf = (checker) => (expression) => argumentSlot(checker)(expression).pipe(Option.orElse(() => propertySlot(checker)(expression)), Option.orElse(() => variableSlot(expression)));
37
+ const contextSymbolsOf = (checker) => (expression) => {
38
+ const alias = Option.fromUndefinedOr(checker.getContextualType(expression)).pipe(Option.flatMap((contextual) => Option.fromUndefinedOr(contextual.getAliasSymbol())));
39
+ if (Option.isSome(alias)) return [alias.value];
40
+ return slotOf(checker)(expression).pipe(Option.flatMap(annotationOf), Option.map((annotation) => referencedSymbols(checker)(annotation)), Option.getOrElse(() => []));
41
+ };
42
+ // src/values.ts
43
+ const SCHEMA_TYPE_PROPERTIES = ["Type", "_output"];
44
+ const MINIMUM_SET_SIZE = 2;
45
+ const DATA_FIRST_ARITY = 2;
46
+ const valueKey = (value) => `${typeof value}:${String(value)}`;
47
+ const literalValueOf = (type) => {
48
+ if (type.isStringLiteralType() || type.isNumberLiteralType()) return Option.some(type.value);
49
+ return Option.none();
50
+ };
51
+ const literalValuesOf = (types) => {
52
+ const values = pipe(types, Array.map((member) => literalValueOf(member)), Array.getSomes, Array.dedupeWith((left, right) => valueKey(left) === valueKey(right)));
53
+ if (values.length !== types.length || values.length < MINIMUM_SET_SIZE) return Option.none();
54
+ return Option.some(values);
55
+ };
56
+ const literalUnionOf = (type) => {
57
+ if (!type.isUnionType()) return Option.none();
58
+ return literalValuesOf(type.getTypes());
59
+ };
60
+ const elementTypeOf = (checker, type) => {
61
+ if (!checker.isArrayLikeType(type)) return Option.none();
62
+ return Option.fromUndefinedOr(checker.getIndexTypeOfType(type, IndexKind.Number));
63
+ };
64
+ const schemaTypeOf = (checker) => (type) => Array.findFirst(SCHEMA_TYPE_PROPERTIES, (name) => Option.fromUndefinedOr(checker.getPropertyOfType(type, name))).pipe(Option.map((property) => checker.getTypeOfSymbol(property)));
65
+ const finiteValuesOf = (checker) => (type) => literalUnionOf(type).pipe(Option.orElse(() => elementTypeOf(checker, type).pipe(Option.flatMap(literalUnionOf))), Option.orElse(() => schemaTypeOf(checker)(type).pipe(Option.flatMap(literalUnionOf))));
66
+ const vocabularyOf = (checker) => (type) => {
67
+ if (!type.isUnionType()) return Option.none();
68
+ const optionalIds = /* @__PURE__ */ new Set([checker.getUndefinedType().id, checker.getNullType().id]);
69
+ const members = pipe(type.getTypes(), Array.filter((member) => !optionalIds.has(member.id)));
70
+ const values = Array.getSomes(pipe(members, Array.map((member) => literalValueOf(member))));
71
+ if (values.length < MINIMUM_SET_SIZE) return Option.none();
72
+ return Option.some({
73
+ values,
74
+ admitsUnnamedValues: values.length !== members.length
75
+ });
76
+ };
77
+ const isSubsetOf = Function.dual(DATA_FIRST_ARITY, (subset, superset) => {
78
+ const held = new Set(pipe(superset, Array.map((value) => valueKey(value))));
79
+ return pipe(subset, Array.every((value) => held.has(valueKey(value))));
80
+ });
81
+ const sameValues = Function.dual(DATA_FIRST_ARITY, (left, right) => left.length === right.length && isSubsetOf(left, right));
82
+ const valueText = (value) => {
83
+ if (typeof value === "string") return `"${value}"`;
84
+ return String(value);
85
+ };
86
+ const formatValues = (values) => pipe(values, Array.map(valueText), Array.join(" | "));
87
+ // src/flows.ts
88
+ const SCOPE_PREFIX$1 = "@";
89
+ const SCOPED_SEGMENTS = 2;
90
+ const UNSCOPED_SEGMENTS = 1;
91
+ const MODULE_PREFIX = /^".*?"\./u;
92
+ const packageOf$1 = (fileName) => {
93
+ const afterModules = pipe(fileName, String$1.lastIndexOf(NODE_MODULES_SEGMENT), Option.match({
94
+ onNone: () => fileName,
95
+ onSome: (index) => String$1.slice(index + NODE_MODULES_SEGMENT.length)(fileName)
96
+ }));
97
+ const segments = String$1.split("/")(afterModules);
98
+ if (String$1.startsWith(SCOPE_PREFIX$1)(afterModules)) return pipe(Array.take(segments, SCOPED_SEGMENTS), Array.join("/"));
99
+ return pipe(Array.take(segments, UNSCOPED_SEGMENTS), Array.join("/"));
100
+ };
101
+ const externalDeclarationOf = (context) => (symbol) => Array.findFirst(declarationsOf(symbol), (declaration) => isExternalDeclaration(context.program)(declaration));
102
+ const aliasTarget = (context) => (type) => Option.fromUndefinedOr(type.getAliasSymbol()).pipe(Option.flatMap((alias) => externalDeclarationOf(context)(alias).pipe(Option.map((declaration) => `${String$1.replace(MODULE_PREFIX, "")(context.checker.getFullyQualifiedName(alias))} from ${packageOf$1(declaration.getSourceFile().fileName)}`))));
103
+ const isNamedContainer = (node) => isSignatureDeclaration(node) || isInterfaceDeclaration(node) || isClassDeclaration(node);
104
+ const slotTarget = (context) => (expression) => slotOf(context.checker)(expression).pipe(Option.filter((slot) => isExternalDeclaration(context.program)(slot)), Option.filter((slot) => Option.isSome(vocabularyOf(context.checker)(context.checker.getTypeAtLocation(slot)))), Option.map((slot) => {
105
+ const owner = Option.liftPredicate(slot.parent, isNamedContainer).pipe(Option.flatMap((container) => Option.fromUndefinedOr(getNameOfDeclaration(container))), Option.map((name) => ` of ${name.getText()}`), Option.getOrElse(() => ""));
106
+ return `${Option.fromUndefinedOr(getNameOfDeclaration(slot)).pipe(Option.map((found) => found.getText()), Option.getOrElse(() => "value"))}${owner} from ${packageOf$1(slot.getSourceFile().fileName)}`;
107
+ }));
108
+ const libraryTargetOf = (context) => (expression, contextual) => aliasTarget(context)(contextual).pipe(Option.orElse(() => slotTarget(context)(expression)));
109
+ const siteWithin = (context) => (node) => Array.findFirst(context.sites, (site) => contains(node)(site.node));
110
+ const aliasSite = (context) => (type) => Option.fromUndefinedOr(type.getAliasSymbol()).pipe(Option.flatMap((alias) => Array.findFirst(declarationsOf(alias), isTypeAliasDeclaration)), Option.flatMap(siteWithin(context)));
111
+ const annotationSite = (context) => (expression) => symbolAt(context.checker)(expression).pipe(Option.flatMap((symbol) => Option.fromUndefinedOr(symbol.valueDeclaration)), Option.flatMap((handle) => Option.fromUndefinedOr(handle.resolve())), Option.flatMap(annotationOf), Option.flatMap((annotation) => siteWithin(context)(annotation).pipe(Option.orElse(() => aliasSite(context)(context.checker.getTypeFromTypeNode(annotation))))));
112
+ const siteBehind = (context) => (expression) => aliasSite(context)(context.checker.getTypeAtLocation(expression)).pipe(Option.orElse(() => annotationSite(context)(expression)));
113
+ const isFlowSource = (node) => isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node);
114
+ const flowOf = (context) => (expression) => Option.fromUndefinedOr(context.checker.getContextualType(expression)).pipe(Option.flatMap((contextual) => Option.all([vocabularyOf(context.checker)(contextual), libraryTargetOf(context)(expression, contextual)])), Option.flatMap(([vocabulary, target]) => siteBehind(context)(expression).pipe(Option.filter((site) => isSubsetOf(site.values, vocabulary.values)), Option.map((site) => ({
115
+ site,
116
+ target
117
+ })))));
118
+ const flowsIn = (context) => (sourceFile) => Array.getSomes(pipe(descendantsOf(sourceFile), Array.filter((node) => isFlowSource(node)), Array.map((node) => flowOf(context)(node))));
119
+ // src/owners.ts
120
+ const OWNER_TAG = "msch";
121
+ const FIRST_OWNER_COUNT = 1;
122
+ const ownerKeyOf = (declaration) => {
123
+ if (!isVariableDeclaration(declaration) || !isIdentifier(declaration.name)) return Option.none();
124
+ return Option.some(`${declaration.getSourceFile().fileName}#${declaration.name.text}`);
125
+ };
126
+ const annotationsOf = (node) => pipe(getJSDocTags(node), Array.filter((tag) => tag.tagName.text === OWNER_TAG), Array.map((tag) => ({
127
+ node,
128
+ conceptId: String$1.trim(getTextOfJSDocComment(tag.comment) ?? "")
129
+ })));
130
+ const ANNOTATABLE_KINDS = /* @__PURE__ */ new Set([
131
+ SyntaxKind.VariableDeclaration,
132
+ SyntaxKind.FunctionDeclaration,
133
+ SyntaxKind.ClassDeclaration,
134
+ SyntaxKind.InterfaceDeclaration,
135
+ SyntaxKind.TypeAliasDeclaration,
136
+ SyntaxKind.EnumDeclaration,
137
+ SyntaxKind.PropertySignature,
138
+ SyntaxKind.PropertyAssignment,
139
+ SyntaxKind.PropertyDeclaration
140
+ ]);
141
+ const isAnnotatable = (node) => ANNOTATABLE_KINDS.has(node.kind);
142
+ const isConstList = (list) => Option.fromUndefinedOr(list.getFirstToken()).pipe(Option.exists((token) => token.kind === SyntaxKind.ConstKeyword));
143
+ const isModuleStatementList = (list) => isVariableStatement(list.parent) && isSourceFile(list.parent.parent);
144
+ const isModuleConstList = (node) => isVariableDeclarationList(node) && isModuleStatementList(node) && isConstList(node);
145
+ const isModuleConst = (node) => isVariableDeclaration(node) && isIdentifier(node.name) && isModuleConstList(node.parent);
146
+ const objectValuesOf = (checker) => (type) => literalValuesOf(pipe(checker.getPropertiesOfType(type), Array.map((property) => checker.getTypeOfSymbol(property))));
147
+ const ownerValuesOf = (checker) => (declaration) => {
148
+ const type = checker.getTypeAtLocation(declaration.name);
149
+ return finiteValuesOf(checker)(type).pipe(Option.orElse(() => objectValuesOf(checker)(type)));
150
+ };
151
+ const annotationProblem = (message) => problemAt({
152
+ rule: "owner-annotation",
153
+ message
154
+ });
155
+ const scanned = (owners, problems) => ({
156
+ owners,
157
+ problems
158
+ });
159
+ const ownerOf = (project) => (annotated) => {
160
+ const { node, conceptId } = annotated;
161
+ if (!isModuleConst(node)) return scanned([], [annotationProblem(`@${OWNER_TAG} registers an owner only on a module-scope const.`)(node)]);
162
+ if (conceptId === "") return scanned([], [annotationProblem(`@${OWNER_TAG} needs a concept id.`)(node)]);
163
+ const values = ownerValuesOf(project.checker)(node);
164
+ return Option.match(Option.all([values, ownerKeyOf(node)]), {
165
+ onNone: () => scanned([], [annotationProblem(`The owner of ${conceptId} holds no finite value set.`)(node)]),
166
+ onSome: ([ownedValues, key]) => scanned([{
167
+ conceptId,
168
+ key,
169
+ name: node.name.getText(),
170
+ file: node.getSourceFile().fileName,
171
+ line: lineOf(node),
172
+ column: columnOf(node),
173
+ packageName: project.packageName,
174
+ values: ownedValues
175
+ }], [])
176
+ });
177
+ };
178
+ const annotatedIn = (sourceFile) => pipe(descendantsOf(sourceFile), Array.filter((node) => isAnnotatable(node)), Array.flatMap((node) => annotationsOf(node)));
179
+ const ownerProblem = (message) => (owner) => ({
180
+ file: owner.file,
181
+ line: owner.line,
182
+ column: owner.column,
183
+ rule: "owner-annotation",
184
+ message
185
+ });
186
+ const conflictProblems = (owners) => pipe(owners, Array.flatMap((owner, index) => {
187
+ const earlier = Array.take(owners, index);
188
+ const sameConcept = pipe(earlier, Array.filter((other) => other.conceptId === owner.conceptId));
189
+ const sameSet = pipe(earlier, Array.filter((other) => other.conceptId !== owner.conceptId && sameValues(other.values, owner.values)));
190
+ return [...pipe(Array.take(sameConcept, FIRST_OWNER_COUNT), Array.map(() => ownerProblem(`The concept ${owner.conceptId} already has an owner.`)(owner))), ...pipe(Array.take(sameSet, FIRST_OWNER_COUNT), Array.map((other) => ownerProblem(`The values ${formatValues(owner.values)} are already owned by ${other.conceptId}.`)(owner)))];
191
+ }));
192
+ const ownersIn = (project) => {
193
+ const results = pipe(project.sourceFiles, Array.flatMap((sourceFile) => pipe(annotatedIn(sourceFile), Array.map((annotated) => ownerOf(project)(annotated)))));
194
+ return scanned(pipe(results, Array.flatMap((result) => result.owners)), pipe(results, Array.flatMap((result) => result.problems)));
195
+ };
196
+ const combineOwnerScans = (scans) => {
197
+ const owners = Array.dedupeWith(pipe(scans, Array.flatMap((scan) => scan.owners)), (left, right) => left.key === right.key);
198
+ return {
199
+ owners,
200
+ problems: [...pipe(scans, Array.flatMap((scan) => scan.problems)), ...conflictProblems(owners)]
201
+ };
202
+ };
203
+ // src/library.ts
204
+ const NAMESPACE_DEPTH_LIMIT = 8;
205
+ const EMPTY_INDEX = {
206
+ vocabularies: [],
207
+ shapes: []
208
+ };
209
+ const isObjectType = (checker) => (type) => !type.isUnionType() && !type.isLiteralType() && checker.getApparentType(type).id === type.id && Array.isReadonlyArrayNonEmpty(checker.getPropertiesOfType(type));
210
+ const describeLibraryType = (type) => `${type.typeName} from ${type.specifier}`;
211
+ const locationOf = (declaration) => ({
212
+ file: declaration.getSourceFile().fileName,
213
+ pos: declaration.pos,
214
+ kind: declaration.kind
215
+ });
216
+ const declarationKey = (location) => `${location.file}#${String(location.pos)}#${String(location.kind)}`;
217
+ const shapeOf = (checker) => (source, declaration, type) => Array.filter([{
218
+ ...source,
219
+ declaration: locationOf(declaration),
220
+ propertyNames: propertyNamesOf(checker)(type)
221
+ }], () => isObjectType(checker)(type));
222
+ const declaredTypeIndex = (walk, exported) => {
223
+ const declaration = Array.findFirst(declarationsOf(exported.symbol), isTypeDeclaration);
224
+ if (Option.isNone(declaration) || isGenericDeclaration(declaration.value)) return EMPTY_INDEX;
225
+ const located = {
226
+ specifier: walk.entry.specifier,
227
+ typeName: exported.typeName
228
+ };
229
+ const type = walk.checker.getDeclaredTypeOfSymbol(exported.symbol);
230
+ return {
231
+ vocabularies: vocabularyOf(walk.checker)(type).pipe(Option.map((vocabulary) => ({
232
+ ...located,
233
+ vocabulary
234
+ })), Option.toArray),
235
+ shapes: shapeOf(walk.checker)({
236
+ ...located,
237
+ form: "declared"
238
+ }, declaration.value, type)
239
+ };
240
+ };
241
+ const schemaTypeIndex = (walk, located) => (declaration, schemaType) => ({
242
+ vocabularies: vocabularyOf(walk.checker)(schemaType).pipe(Option.map((vocabulary) => ({
243
+ ...located,
244
+ vocabulary
245
+ })), Option.toArray),
246
+ shapes: shapeOf(walk.checker)({
247
+ ...located,
248
+ form: "schema"
249
+ }, declaration, schemaType)
250
+ });
251
+ const schemaValueIndex = (walk, exported) => {
252
+ const declaration = Array.findFirst(declarationsOf(exported.symbol), isVariableDeclaration);
253
+ if (Option.isNone(declaration)) return EMPTY_INDEX;
254
+ const valueType = walk.checker.getTypeOfSymbolAtLocation(exported.symbol, declaration.value);
255
+ const located = {
256
+ specifier: walk.entry.specifier,
257
+ typeName: `(typeof ${exported.typeName})["Type"]`
258
+ };
259
+ return Option.match(schemaTypeOf(walk.checker)(valueType), {
260
+ onNone: () => EMPTY_INDEX,
261
+ onSome: (schemaType) => schemaTypeIndex(walk, located)(declaration.value, schemaType)
262
+ });
263
+ };
264
+ const mergeIndexes = (indexes) => ({
265
+ vocabularies: pipe(indexes, Array.flatMap((index) => index.vocabularies)),
266
+ shapes: pipe(indexes, Array.flatMap((index) => index.shapes))
267
+ });
268
+ const isNamespace = (symbol) => pipe(declarationsOf(symbol), Array.some((declaration) => isModuleDeclaration(declaration)));
269
+ const indexUnder = (walk, visit) => {
270
+ const symbol = resolvedSymbol(walk.checker)(visit.exported);
271
+ const named = {
272
+ symbol,
273
+ typeName: `${visit.qualifier}${visit.exported.name}`
274
+ };
275
+ const own = mergeIndexes([declaredTypeIndex(walk, named), schemaValueIndex(walk, named)]);
276
+ if (!isNamespace(symbol) || visit.ancestors.length >= NAMESPACE_DEPTH_LIMIT || visit.ancestors.includes(symbol)) return own;
277
+ const nested = pipe(walk.checker.getExportsOfModule(symbol), Array.map((member) => indexUnder(walk, {
278
+ exported: member,
279
+ qualifier: `${named.typeName}.`,
280
+ ancestors: [...visit.ancestors, symbol]
281
+ })));
282
+ return mergeIndexes([own, ...nested]);
283
+ };
284
+ const exportAssignmentOf = (moduleSymbol) => Option.fromUndefinedOr(moduleSymbol.getExports().get(InternalSymbolName.ExportEquals));
285
+ const indexOfEntry = (checker) => (entry) => {
286
+ const moduleSymbol = Option.fromUndefinedOr(checker.getSymbolOfSourceFile(entry.declarationsPath));
287
+ if (Option.isNone(moduleSymbol)) return EMPTY_INDEX;
288
+ const walk = {
289
+ checker,
290
+ entry
291
+ };
292
+ const roots = Option.match(exportAssignmentOf(moduleSymbol.value), {
293
+ onNone: () => checker.getExportsOfModule(moduleSymbol.value),
294
+ onSome: (assigned) => [resolvedSymbol(checker)(assigned)]
295
+ });
296
+ return mergeIndexes(pipe(roots, Array.map((exported) => indexUnder(walk, {
297
+ exported,
298
+ qualifier: "",
299
+ ancestors: []
300
+ }))));
301
+ };
302
+ const libraryCache = () => {
303
+ const indexes = /* @__PURE__ */ new Map();
304
+ return { indexOf: (entry, compute) => Option.match(Option.fromUndefinedOr(indexes.get(entry.declarationsPath)), {
305
+ onSome: (index) => ({
306
+ index,
307
+ cached: true
308
+ }),
309
+ onNone: () => {
310
+ const index = compute();
311
+ indexes.set(entry.declarationsPath, index);
312
+ return {
313
+ index,
314
+ cached: false
315
+ };
316
+ }
317
+ }) };
318
+ };
319
+ const indexEntry = (cache) => (checker) => (entry) => Effect.sync(() => cache.indexOf(entry, () => indexOfEntry(checker)(entry))).pipe(Effect.tap(({ index, cached }) => Effect.annotateCurrentSpan({
320
+ "msch.library.cached": cached,
321
+ "msch.library.vocabularies": index.vocabularies.length,
322
+ "msch.library.shapes": index.shapes.length
323
+ })), Effect.map(({ index }) => index), Effect.withSpan("indexLibraryEntry", { attributes: { "msch.library.specifier": entry.specifier } }));
324
+ const libraryIndex = (cache) => (checker) => (entries) => pipe(entries, Effect.forEach(indexEntry(cache)(checker)), Effect.map(mergeIndexes), Effect.withSpan("libraryIndex"));
325
+ // src/shapes.ts
326
+ const MINIMUM_SHAPE_PROPERTIES = 3;
327
+ const EXACT = 0;
328
+ const augmentsAModule = (node) => {
329
+ const block = node.parent;
330
+ return isModuleBlock(block) && isModuleDeclaration(block.parent) && isStringLiteral(block.parent.name);
331
+ };
332
+ const isPlainInterface = (node) => isInterfaceDeclaration(node) && !isGenericDeclaration(node) && !augmentsAModule(node);
333
+ const isPlainTypeLiteral = (node) => isTypeAliasDeclaration(node) && !isGenericDeclaration(node) && isTypeLiteralNode(node.type);
334
+ const isDeclaredShape = (node) => isPlainInterface(node) || isPlainTypeLiteral(node);
335
+ const declaredShapeOf = (checker) => (node) => Option.liftPredicate(node, isDeclaredShape).pipe(Option.map((declaration) => checker.getTypeAtLocation(declaration.name)));
336
+ const schemaShapeOf = (checker) => (node) => {
337
+ if (!isCallExpression(node)) return Option.none();
338
+ const { arguments: callArguments } = node;
339
+ if (!pipe(callArguments, Array.some((argument) => isObjectLiteralExpression(argument)))) return Option.none();
340
+ return schemaTypeOf(checker)(checker.getTypeAtLocation(node)).pipe(Option.filter((type) => isObjectType(checker)(type)));
341
+ };
342
+ const shapeSitesIn = (checker) => (sourceFile) => Array.getSomes(pipe(descendantsOf(sourceFile), Array.map((node) => declaredShapeOf(checker)(node).pipe(Option.orElse(() => schemaShapeOf(checker)(node)), Option.map((type) => ({
343
+ node,
344
+ type
345
+ }))))));
346
+ const openTypeIdsOf = (checker) => ({
347
+ any: checker.getAnyType().id,
348
+ unknown: checker.getUnknownType().id
349
+ });
350
+ const isIndexedDeclaration = (node) => isTypeDeclaration(node) || isVariableDeclaration(node);
351
+ const declarationAt = (program) => (location) => Option.fromUndefinedOr(program.getSourceFile(location.file)).pipe(Option.flatMap((sourceFile) => Array.findFirst(descendantsOf(sourceFile), (node) => node.pos === location.pos && node.kind === location.kind && isIndexedDeclaration(node))));
352
+ const symbolOfDeclaration = (checker) => (declaration) => Option.fromUndefinedOr(checker.getSymbolAtLocation(declaration.name));
353
+ const shapeTypeOf = (context) => (shape) => {
354
+ const { checker } = context;
355
+ return declarationAt(context.program)(shape.declaration).pipe(Option.flatMap((declaration) => symbolOfDeclaration(checker)(declaration).pipe(Option.flatMap((symbol) => {
356
+ if (shape.form === "declared") return Option.some(checker.getDeclaredTypeOfSymbol(symbol));
357
+ return schemaTypeOf(checker)(checker.getTypeOfSymbolAtLocation(symbol, declaration));
358
+ }))));
359
+ };
360
+ const hasEveryName = (locals) => (shape) => {
361
+ const names = new Set(shape.propertyNames);
362
+ return pipe(locals, Array.every((local) => names.has(local.name)));
363
+ };
364
+ const sameProperty = (checker, open) => (localType, library) => {
365
+ const libraryType = checker.getTypeOfSymbol(library);
366
+ return libraryType.id !== open.any && libraryType.id !== open.unknown && checker.isTypeAssignableTo(localType, libraryType) && checker.isTypeAssignableTo(libraryType, localType);
367
+ };
368
+ const matchOf = (context, open) => (locals, shape) => {
369
+ const { checker } = context;
370
+ if (!shapeTypeOf(context)(shape).pipe(Option.exists((shapeType) => pipe(locals, Array.every((local) => Option.fromUndefinedOr(checker.getPropertyOfType(shapeType, local.name)).pipe(Option.exists((library) => sameProperty(checker, open)(local.type, library)))))))) return Option.none();
371
+ return Option.some({
372
+ shape,
373
+ extra: shape.propertyNames.length - locals.length
374
+ });
375
+ };
376
+ const localPropertiesOf = (checker) => (symbols) => pipe(Array.zip(symbols, checker.getTypeOfSymbol(symbols)), Array.map(([symbol, type]) => ({
377
+ name: symbol.name,
378
+ type
379
+ })));
380
+ const derivedDeclarationsOf = (checker) => (node) => new Set(pipe(referencedSymbols(checker)(node), Array.flatMap((symbol) => declarationsOf(symbol)), Array.map((declaration) => declarationKey(locationOf(declaration)))));
381
+ const matchOrder = Order.mapInput(Order.Number, (match) => match.extra);
382
+ const shapeProblem = (site, matches) => {
383
+ const shown = pipe(Array.take(matches, 3), Array.map((match) => describeLibraryType(match.shape)), Array.join(", "));
384
+ const advice = Option.match(Array.findFirst(matches, (match) => match.extra === EXACT), {
385
+ onNone: () => "Derive it from the library type, for example with Pick, instead of copying its properties.",
386
+ onSome: () => "Use the library type instead of copying it."
387
+ });
388
+ return problemAt({
389
+ rule: "library-shape",
390
+ message: `This shape copies ${shown}. ${advice}`
391
+ })(site.node);
392
+ };
393
+ const shapeProblemOf = (context, open) => (site) => {
394
+ const symbols = context.checker.getPropertiesOfType(site.type);
395
+ if (symbols.length < MINIMUM_SHAPE_PROPERTIES) return Option.none();
396
+ const locals = localPropertiesOf(context.checker)(symbols);
397
+ const derived = derivedDeclarationsOf(context.checker)(site.node);
398
+ const candidates = pipe(context.shapes, Array.filter(hasEveryName(locals)), Array.filter((shape) => !derived.has(declarationKey(shape.declaration))), Array.map((shape) => matchOf(context, open)(locals, shape)), Array.getSomes);
399
+ const matches = Array.sort(candidates, matchOrder);
400
+ return Option.liftPredicate(matches, Array.isReadonlyArrayNonEmpty).pipe(Option.map((found) => shapeProblem(site, found)));
401
+ };
402
+ const shapeProblemsIn = (context) => (sourceFile) => {
403
+ const sites = shapeSitesIn(context.checker)(sourceFile);
404
+ if (!Array.isReadonlyArrayNonEmpty(sites)) return [];
405
+ const open = openTypeIdsOf(context.checker);
406
+ return Array.getSomes(pipe(sites, Array.map((site) => shapeProblemOf(context, open)(site))));
407
+ };
408
+ // src/sites.ts
409
+ const namedValuesOf = (checker) => (symbol) => {
410
+ if (pipe(declarationsOf(symbol), Array.some((declaration) => isTypeDeclaration(declaration)))) return finiteValuesOf(checker)(checker.getDeclaredTypeOfSymbol(symbol));
411
+ return finiteValuesOf(checker)(checker.getTypeOfSymbol(symbol));
412
+ };
413
+ const hasFiniteContext = (checker) => (node) => Option.fromUndefinedOr(checker.getContextualType(node)).pipe(Option.flatMap(finiteValuesOf(checker)), Option.isSome);
414
+ const isNamedValue = (element) => isIdentifier(element) || isPropertyAccessExpression(element) || isElementAccessExpression(element);
415
+ const namesEveryElement = (array) => pipe(array.elements, Array.every((element) => isNamedValue(element)));
416
+ const expressionValuesOf = (checker) => (node) => {
417
+ if (hasFiniteContext(checker)(node)) return Option.none();
418
+ const type = checker.getTypeAtLocation(node);
419
+ if (isArrayLiteralExpression(node)) return Option.liftPredicate(node, (array) => !namesEveryElement(array)).pipe(Option.flatMap(() => finiteValuesOf(checker)(type)));
420
+ return schemaTypeOf(checker)(type).pipe(Option.flatMap(literalUnionOf));
421
+ };
422
+ const selectsKeys = (checker) => (node, values) => {
423
+ const { parent } = node;
424
+ if (!isTypeReferenceNode(parent)) return false;
425
+ const typeArguments = parent.typeArguments ?? [];
426
+ return pipe(typeArguments, Array.filter((argument) => argument !== node), Array.some((argument) => isSubsetOf(values, propertyNamesOf(checker)(checker.getTypeFromTypeNode(argument)))));
427
+ };
428
+ const unionValuesOf = (checker) => (node) => literalUnionOf(checker.getTypeFromTypeNode(node)).pipe(Option.filter((values) => !selectsKeys(checker)(node, values)));
429
+ const isExpressionSite = (node) => isArrayLiteralExpression(node) || isCallExpression(node);
430
+ const expressionSiteValuesOf = (checker) => (node) => Option.liftPredicate(node, isExpressionSite).pipe(Option.flatMap(expressionValuesOf(checker)));
431
+ const siteValuesOf = (checker) => (node) => {
432
+ if (isUnionTypeNode(node)) return unionValuesOf(checker)(node);
433
+ if (isEnumDeclaration(node)) return literalUnionOf(checker.getTypeAtLocation(node.name));
434
+ return expressionSiteValuesOf(checker)(node);
435
+ };
436
+ const derivationScopeOf = (node) => {
437
+ if (isTypeReferenceNode(node.parent)) return node.parent;
438
+ return node;
439
+ };
440
+ const sourcesOf = (checker) => (node) => pipe(referencedSymbols(checker)(derivationScopeOf(node)), Array.filter((symbol) => Option.isSome(namedValuesOf(checker)(symbol))));
441
+ const sitesUnder = (checker) => (node) => {
442
+ const nested = pipe(childrenOf(node), Array.flatMap((child) => sitesUnder(checker)(child)));
443
+ if (Array.isReadonlyArrayNonEmpty(nested)) return nested;
444
+ return siteValuesOf(checker)(node).pipe(Option.map((values) => ({
445
+ node,
446
+ values,
447
+ sources: sourcesOf(checker)(node)
448
+ })), Option.toArray);
449
+ };
450
+ // src/value-checks.ts
451
+ const OWNER_SEARCH_DEPTH = 4;
452
+ const NOTHING_HIDDEN = 0;
453
+ const directOwners = (context) => (symbol) => {
454
+ const keys = Array.getSomes(pipe(declarationsOf(symbol), Array.map((declaration) => ownerKeyOf(declaration))));
455
+ return pipe(context.owners, Array.filter((owner) => keys.includes(owner.key)));
456
+ };
457
+ const isFollowable = (declaration) => !declaration.getSourceFile().isDeclarationFile && (isTypeAliasDeclaration(declaration) || isVariableDeclaration(declaration));
458
+ const ownersBehind = (context) => (symbol, ancestors) => {
459
+ const direct = directOwners(context)(symbol);
460
+ if (Array.isReadonlyArrayNonEmpty(direct) || ancestors.length >= OWNER_SEARCH_DEPTH) return direct;
461
+ const visited = [...ancestors, symbol];
462
+ return uniqueByReference(pipe(declarationsOf(symbol), Array.filter((declaration) => isFollowable(declaration)), Array.flatMap((declaration) => referencedSymbols(context.checker)(declaration)), Array.filter((referenced) => !visited.includes(referenced)), Array.flatMap((referenced) => ownersBehind(context)(referenced, visited))));
463
+ };
464
+ const describeOwners = (owners) => pipe(owners, Array.map((owner) => `${owner.conceptId} (${owner.name})`), Array.join(", "));
465
+ const verdict = (problems, unowned) => ({
466
+ problems,
467
+ unowned
468
+ });
469
+ const derivedVerdict = (context) => (site) => {
470
+ const owners = uniqueByReference(pipe(site.sources, Array.flatMap((source) => ownersBehind(context)(source, []))));
471
+ const held = pipe(owners, Array.flatMap((owner) => owner.values));
472
+ const added = pipe(site.values, Array.filter((value) => !isSubsetOf([value], held)));
473
+ if (!Array.isReadonlyArrayNonEmpty(owners) || !Array.isReadonlyArrayNonEmpty(added)) return verdict([], []);
474
+ const message = `This set adds ${formatValues(added)} to ${describeOwners(owners)}. Add the values to the owner, or register the wider set as its own concept.`;
475
+ return verdict([problemAt({
476
+ rule: "owner-extension",
477
+ message
478
+ })(site.node)], []);
479
+ };
480
+ const libraryCandidates = (context) => (site) => {
481
+ const flowing = Array.dedupe(pipe(context.flows, Array.filter((flow) => flow.site.node === site.node), Array.map((flow) => flow.target)));
482
+ if (Array.isReadonlyArrayNonEmpty(flowing)) return flowing;
483
+ if (!pipe(site.values, Array.some((value) => typeof value === "string"))) return [];
484
+ const imported = importedModulesOf(site.node.getSourceFile());
485
+ return Array.dedupe(pipe(context.vocabularies, Array.filter((library) => imported.includes(library.specifier)), Array.filter((library) => isSubsetOf(site.values, library.vocabulary.values)), Array.map((library) => describeLibraryType(library))));
486
+ };
487
+ const listCandidates = (candidates) => {
488
+ const shown = pipe(Array.take(candidates, 3), Array.join(", "));
489
+ const hidden = candidates.length - 3;
490
+ if (hidden > NOTHING_HIDDEN) return `${shown} and ${String(hidden)} more`;
491
+ return shown;
492
+ };
493
+ const originalVerdict = (context) => (site) => {
494
+ const owners = pipe(context.owners, Array.filter((owner) => isSubsetOf(site.values, owner.values)));
495
+ if (Array.isReadonlyArrayNonEmpty(owners)) {
496
+ const message = `The values ${formatValues(site.values)} are owned by ${describeOwners(owners)}. Derive this set from the owner instead of restating it.`;
497
+ return verdict([problemAt({
498
+ rule: "owner-value-set",
499
+ message
500
+ })(site.node)], []);
501
+ }
502
+ const candidates = libraryCandidates(context)(site);
503
+ if (Array.isReadonlyArrayNonEmpty(candidates)) {
504
+ const message = `The values ${formatValues(site.values)} restate a dependency type: ${listCandidates(candidates)}. Derive this set from the library type.`;
505
+ return verdict([problemAt({
506
+ rule: "library-value-set",
507
+ message
508
+ })(site.node)], []);
509
+ }
510
+ return verdict([], [site]);
511
+ };
512
+ const siteVerdict = (context) => (site) => {
513
+ if (Array.isReadonlyArrayNonEmpty(site.sources)) return derivedVerdict(context)(site);
514
+ return originalVerdict(context)(site);
515
+ };
516
+ const VALUE_PARENT_KINDS = /* @__PURE__ */ new Set([
517
+ SyntaxKind.ReturnStatement,
518
+ SyntaxKind.ArrayLiteralExpression,
519
+ SyntaxKind.ConditionalExpression,
520
+ SyntaxKind.ParenthesizedExpression,
521
+ SyntaxKind.ArrowFunction,
522
+ SyntaxKind.JsxExpression
523
+ ]);
524
+ const isCallArgument = (node, parent) => (isCallExpression(parent) || isNewExpression(parent)) && parent.expression !== node;
525
+ const isInitializer = (node, parent) => (isPropertyAssignment(parent) || isVariableDeclaration(parent)) && parent.initializer === node;
526
+ const isRightOperand = (node, parent) => isBinaryExpression(parent) && parent.right === node;
527
+ const VALUE_SLOTS = [
528
+ isCallArgument,
529
+ isInitializer,
530
+ isRightOperand
531
+ ];
532
+ const isValuePosition = (node) => {
533
+ const { parent } = node;
534
+ return VALUE_PARENT_KINDS.has(parent.kind) || pipe(VALUE_SLOTS, Array.some((isSlot) => isSlot(node, parent)));
535
+ };
536
+ const ownedLiteralProblem = (context) => (literal) => Option.fromUndefinedOr(context.checker.getContextualType(literal)).pipe(Option.filter((contextual) => vocabularyOf(context.checker)(contextual).pipe(Option.exists((vocabulary) => !vocabulary.admitsUnnamedValues))), Option.map(() => pipe(contextSymbolsOf(context.checker)(literal), Array.flatMap((symbol) => ownersBehind(context)(symbol, [])), uniqueByReference, Array.filter((owner) => isSubsetOf([literal.text], pipe(owner.values, Array.map(String)))))), Option.filter((owners) => Array.isReadonlyArrayNonEmpty(owners)), Option.map((owners) => problemAt({
537
+ rule: "owned-literal",
538
+ message: `${literal.getText()} is a value of ${describeOwners(owners)}. Write it through the owner's binding instead of as a literal.`
539
+ })(literal)));
540
+ const ownedLiteralProblems = (context) => (sourceFile, excluded) => Array.getSomes(pipe(descendantsOf(sourceFile), Array.filter((node) => isStringLiteral(node) || isNumericLiteral(node)), Array.filter((node) => isValuePosition(node)), Array.filter((node) => !pipe(excluded, Array.some((outer) => contains(outer)(node)))), Array.map((node) => ownedLiteralProblem(context)(node))));
541
+ // src/analysis.ts
542
+ const isVisibleTo = (project) => (owner) => pipe(project.sourceFiles, Array.some((sourceFile) => sourceFile.fileName === owner.file)) || Option.exists(owner.packageName, (name) => Option.contains(project.packageName, name) || project.workspaceDependencies.includes(name));
543
+ const ownerDeclarationsIn = (project, owners) => {
544
+ const keys = new Set(pipe(owners, Array.map((owner) => owner.key)));
545
+ return pipe(project.sourceFiles, Array.flatMap((sourceFile) => pipe(descendantsOf(sourceFile), Array.filter((node) => isVariableDeclaration(node)), Array.filter((declaration) => Option.exists(ownerKeyOf(declaration), (key) => keys.has(key))))));
546
+ };
547
+ const isInside = (outers) => (node) => pipe(outers, Array.some((outer) => contains(outer)(node)));
548
+ const sitesOutside = (checker) => (project, outers) => pipe(project.sourceFiles, Array.flatMap((sourceFile) => sitesUnder(checker)(sourceFile)), Array.filter((site) => !isInside(outers)(site.node)));
549
+ const nodesOf = (sites) => pipe(sites, Array.map((site) => site.node));
550
+ const analyzeProject = (owners) => (project) => {
551
+ const { checker } = project;
552
+ const ownerDeclarations = ownerDeclarationsIn(project, owners);
553
+ const sites = sitesOutside(checker)(project, ownerDeclarations);
554
+ const flowContext = {
555
+ checker,
556
+ program: project.program,
557
+ sites
558
+ };
559
+ const context = {
560
+ checker,
561
+ owners: pipe(owners, Array.filter((owner) => isVisibleTo(project)(owner))),
562
+ vocabularies: project.library.vocabularies,
563
+ flows: pipe(project.sourceFiles, Array.flatMap((sourceFile) => flowsIn(flowContext)(sourceFile)))
564
+ };
565
+ const verdicts = pipe(sites, Array.map((site) => siteVerdict(context)(site)));
566
+ const excluded = [...ownerDeclarations, ...nodesOf(sites)];
567
+ const shapeContext = {
568
+ checker,
569
+ program: project.program,
570
+ shapes: project.library.shapes
571
+ };
572
+ return {
573
+ problems: [...pipe(verdicts, Array.flatMap((found) => found.problems)), ...pipe(project.sourceFiles, Array.flatMap((sourceFile) => [...ownedLiteralProblems(context)(sourceFile, excluded), ...shapeProblemsIn(shapeContext)(sourceFile)]))],
574
+ unowned: pipe(verdicts, Array.flatMap((found) => found.unowned))
575
+ };
576
+ };
577
+ // src/duplicates.ts
578
+ const DUPLICATE_THRESHOLD = 1;
579
+ const unownedSiteOf = (scope) => (site) => ({
580
+ scope,
581
+ file: site.node.getSourceFile().fileName,
582
+ start: site.node.getStart(),
583
+ line: lineOf(site.node),
584
+ column: columnOf(site.node),
585
+ values: site.values
586
+ });
587
+ const canDerive = (left, right) => left.scope.name === right.scope.name || left.scope.dependencies.includes(right.scope.name) || right.scope.dependencies.includes(left.scope.name);
588
+ const siteKey = (site) => `${site.file}#${String(site.start)}`;
589
+ const valueSetKey = (site) => {
590
+ const keys = pipe(site.values, Array.map((value) => valueKey(value)));
591
+ return pipe(Array.sort(keys, Order.String), Array.join("\0"));
592
+ };
593
+ const duplicateProblems = (relative) => (sites) => {
594
+ const unique = Array.dedupeWith(sites, (left, right) => siteKey(left) === siteKey(right));
595
+ const groups = Record.values(Array.groupBy(unique, (site) => valueSetKey(site)));
596
+ return pipe(groups, Array.filter((group) => group.length > DUPLICATE_THRESHOLD), Array.flatMap((group) => pipe(group, Array.flatMap((site) => {
597
+ const related = pipe(group, Array.filter((other) => other !== site && canDerive(site, other)));
598
+ if (!Array.isReadonlyArrayNonEmpty(related)) return [];
599
+ const others = pipe(related, Array.map((other) => `${relative(other.file)}:${String(other.line)}`), Array.join(", "));
600
+ return [{
601
+ file: site.file,
602
+ line: site.line,
603
+ column: site.column,
604
+ rule: "duplicate-value-set",
605
+ message: `The values ${formatValues(site.values)} are also declared at ${others}. Register one owner with /** @${OWNER_TAG} <concept> */ and derive the others from it.`
606
+ }];
607
+ }))));
608
+ };
609
+ // src/telemetry.ts
610
+ const RSS_LINE = /^VmRSS:\s+(?<kib>\d+) kB$/mu;
611
+ const BYTES_PER_KIB = 1024;
612
+ const UNKNOWN_RSS = 0;
613
+ const kibibytesIn = (status) => Option.fromNullishOr(RSS_LINE.exec(status)).pipe(Option.flatMap((match) => Option.fromUndefinedOr(match.groups)), Option.flatMap((groups) => Option.fromUndefinedOr(groups["kib"])));
614
+ const residentBytesOf = (pid) => Effect.gen(function* residentBytes() {
615
+ const status = yield* (yield* RepositoryFiles).readFile(`/proc/${pid}/status`);
616
+ return pipe(status, Option.flatMap(kibibytesIn), Option.flatMap(Number$1.parse), Option.map((kib) => kib * BYTES_PER_KIB), Option.getOrElse(() => UNKNOWN_RSS));
617
+ });
618
+ const childPids = Effect.gen(function* childPids() {
619
+ const files = yield* RepositoryFiles;
620
+ const pid = String(process.pid);
621
+ const children = yield* files.readFile(`/proc/${pid}/task/${pid}/children`);
622
+ return pipe(children, Option.map((text) => pipe(String$1.trim(text), String$1.split(" "), Array.filter(String$1.isNonEmpty))), Option.getOrElse(() => []));
623
+ });
624
+ const annotateMemory = Effect.gen(function* annotateMemory() {
625
+ const own = yield* residentBytesOf(String(process.pid));
626
+ const pids = yield* childPids;
627
+ const children = yield* pipe(pids, Effect.forEach((pid) => residentBytesOf(pid)));
628
+ const { heapUsed } = process.memoryUsage();
629
+ yield* Effect.annotateCurrentSpan({
630
+ "process.memory.rss": own,
631
+ "process.memory.heap_used": heapUsed,
632
+ "msch.compiler.memory.rss": Number$1.sumAll(children)
633
+ });
634
+ });
635
+ const annotateResources = (api) => Effect.gen(function* annotateCompiler() {
636
+ yield* annotateMemory;
637
+ const { totals } = api.getTimingInfo();
638
+ yield* Effect.annotateCurrentSpan({
639
+ "msch.compiler.requests": totals.requestCount,
640
+ "msch.compiler.round_trip_ms": totals.roundTripMs,
641
+ "msch.compiler.server_ms": totals.serverTimeMs
642
+ });
643
+ });
644
+ // src/project.ts
645
+ const CONFIG_FILE = "tsconfig.json";
646
+ const HIDDEN_PREFIX = ".";
647
+ const SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
648
+ NODE_MODULES,
649
+ "dist",
650
+ "coverage"
651
+ ]);
652
+ const listConfigFiles = (directory) => Effect.gen(function* configFilesUnder() {
653
+ const files = yield* RepositoryFiles;
654
+ const names = yield* files.readDirectory(directory);
655
+ const visible = pipe(names, Array.filter((name) => !String$1.startsWith(HIDDEN_PREFIX)(name) && !SKIPPED_DIRECTORIES.has(name)), Array.map((name) => files.join(directory, name)));
656
+ const nested = yield* Function.pipe(visible, Effect.forEach((entryPath) => files.isDirectory(entryPath).pipe(Effect.flatMap(Option.match({
657
+ onNone: () => Effect.succeed([]),
658
+ onSome: (directoryEntry) => {
659
+ if (directoryEntry) return listConfigFiles(entryPath);
660
+ return Effect.succeed(Array.filter([entryPath], () => files.basename(entryPath) === CONFIG_FILE));
661
+ }
662
+ })))));
663
+ return Array.sort(nested.flat(), Order.String);
664
+ });
665
+ const parseConfig = (api) => (configPath) => Option.some(api.parseConfigFile(configPath)).pipe(Option.filter((parsed) => Array.isReadonlyArrayNonEmpty(parsed.fileNames)));
666
+ const nearestPackageDirectory = (directory, root) => Effect.gen(function* nearestPackage() {
667
+ const files = yield* RepositoryFiles;
668
+ const hasManifest = yield* files.exists(files.join(directory, MANIFEST_FILE));
669
+ const parent = files.dirname(directory);
670
+ if (hasManifest || directory === root || parent === directory) return directory;
671
+ return yield* nearestPackageDirectory(parent, root);
672
+ });
673
+ const packageInfoOf = Effect.fn("packageInfoOf")(function* packageInfoOf(configPath, root) {
674
+ const files = yield* RepositoryFiles;
675
+ const directory = yield* nearestPackageDirectory(files.dirname(configPath), root);
676
+ const manifest = yield* readManifest(directory);
677
+ return {
678
+ directory,
679
+ name: manifest.pipe(Option.flatMap((found) => Option.fromUndefinedOr(found.name))),
680
+ workspaceDependencies: manifest.pipe(Option.map(workspaceDependencies), Option.getOrElse(() => []))
681
+ };
682
+ });
683
+ const NO_CHECK_PRAGMA = /^\/\/\s*@ts-nocheck/u;
684
+ const FILE_START = 0;
685
+ const skipsTypeChecking = (sourceFile) => pipe(getLeadingCommentRanges(sourceFile.text, FILE_START) ?? [], Array.some((range) => NO_CHECK_PRAGMA.test(String$1.slice(range.pos, range.end)(sourceFile.text))));
686
+ const buildProject = Effect.fn("buildProject")(function* buildProject({ compiler: { api, libraries }, generated }, parsed, info) {
687
+ const entries = yield* dependencyEntries(info.directory);
688
+ const program = yield* Effect.sync(() => api.createProgram([...parsed.fileNames, ...pipe(entries, Array.map((entry) => entry.declarationsPath))], {
689
+ ...parsed.options,
690
+ noEmit: true
691
+ })).pipe(Effect.withSpan("createProgram"));
692
+ const { checker } = program.getProject();
693
+ const library = yield* libraryIndex(libraries)(checker)(entries);
694
+ const projectFiles = new Set(pipe(parsed.fileNames, Array.filter((fileName) => !generated.includes(fileName))));
695
+ return {
696
+ packageName: info.name,
697
+ workspaceDependencies: info.workspaceDependencies,
698
+ program,
699
+ checker,
700
+ sourceFiles: pipe(program.getSourceFileNames(), Array.filter((fileName) => projectFiles.has(fileName)), Array.flatMap((fileName) => Option.toArray(Option.fromUndefinedOr(program.getSourceFile(fileName)))), Array.filter((sourceFile) => !sourceFile.isDeclarationFile && !skipsTypeChecking(sourceFile))),
701
+ library
702
+ };
703
+ });
704
+ const typeScriptCompiler = Effect.acquireRelease(Effect.sync(() => ({
705
+ api: new API({ collectTiming: true }),
706
+ libraries: libraryCache()
707
+ })), ({ api }) => Effect.sync(() => {
708
+ api.close();
709
+ }));
710
+ const loadProject = Effect.fn("loadProject")(function* loadProject(workspace, configPath) {
711
+ const { compiler, root } = workspace;
712
+ const parsed = parseConfig(compiler.api)(configPath);
713
+ if (Option.isNone(parsed)) return Option.none();
714
+ const info = yield* packageInfoOf(configPath, root);
715
+ const project = yield* buildProject(workspace, parsed.value, info);
716
+ yield* Effect.annotateCurrentSpan({
717
+ "msch.project.config": configPath,
718
+ "msch.project.program_files": project.program.getSourceFileNames().length,
719
+ "msch.project.source_files": project.sourceFiles.length
720
+ });
721
+ yield* annotateResources(compiler.api);
722
+ return Option.some(project);
723
+ });
724
+ const releaseProject = (project) => Effect.sync(() => {
725
+ project.program.dispose();
726
+ });
727
+ const withProject = (workspace) => (configPath) => (work) => Effect.acquireUseRelease(loadProject(workspace, configPath), (loaded) => Option.match(loaded, {
728
+ onNone: () => Effect.succeedNone,
729
+ onSome: (project) => work(project).pipe(Effect.asSome)
730
+ }), (loaded) => Option.match(loaded, {
731
+ onNone: () => Effect.void,
732
+ onSome: releaseProject
733
+ }));
734
+ const mentions = (needle) => ({ api }) => (configPath) => Effect.gen(function* mentionsNeedle() {
735
+ const files = yield* RepositoryFiles;
736
+ const fileNames = Option.match(parseConfig(api)(configPath), {
737
+ onNone: () => [],
738
+ onSome: (parsed) => parsed.fileNames
739
+ });
740
+ const texts = yield* pipe(fileNames, Effect.forEach((fileName) => files.readFile(fileName)));
741
+ const includesNeedle = String$1.includes(needle);
742
+ return pipe(texts, Array.some((text) => Option.exists(text, includesNeedle)));
743
+ });
744
+ // src/check.ts
745
+ const scanOwners = Effect.fn("scanOwners")(function* scanOwners(workspace, configPaths) {
746
+ const ownerConfigs = yield* pipe(configPaths, Effect.filter(mentions(`@${OWNER_TAG}`)(workspace.compiler)));
747
+ const scans = yield* pipe(ownerConfigs, Effect.forEach((configPath) => withProject(workspace)(configPath)((project) => Effect.sync(() => ownersIn(project)))));
748
+ return combineOwnerScans(Array.getSomes(scans));
749
+ });
750
+ const scopeOf = (project, configPath) => ({
751
+ name: Option.getOrElse(project.packageName, () => configPath),
752
+ dependencies: project.workspaceDependencies
753
+ });
754
+ const analyzeProjects = (workspace, owners) => (configPaths) => pipe(configPaths, Effect.forEach((configPath) => withProject(workspace)(configPath)((project) => Effect.sync(() => {
755
+ const analysis = analyzeProject(owners)(project);
756
+ const scope = scopeOf(project, configPath);
757
+ return {
758
+ problems: analysis.problems,
759
+ unowned: pipe(analysis.unowned, Array.map(unownedSiteOf(scope)))
760
+ };
761
+ }).pipe(Effect.withSpan("analyzeProject")))), Effect.map(Array.getSomes));
762
+ const checkRepository = Effect.fn("checkRepository")(function* checkRepository(root) {
763
+ const files = yield* RepositoryFiles;
764
+ const workspace = {
765
+ compiler: yield* typeScriptCompiler,
766
+ root,
767
+ generated: yield* files.generatedFiles(root)
768
+ };
769
+ const configPaths = yield* listConfigFiles(root);
770
+ const ownerScan = yield* scanOwners(workspace, configPaths);
771
+ const analyses = yield* analyzeProjects(workspace, ownerScan.owners)(configPaths);
772
+ const relative = (file) => files.relative(root, file);
773
+ const unowned = pipe(analyses, Array.flatMap((analysis) => analysis.unowned));
774
+ return sortProblems([
775
+ ...ownerScan.problems,
776
+ ...pipe(analyses, Array.flatMap((analysis) => analysis.problems)),
777
+ ...duplicateProblems(relative)(unowned)
778
+ ]);
779
+ }, Effect.scoped);
780
+ // src/text-position.ts
781
+ const START$1 = 0;
782
+ const FIRST_COLUMN$1 = 1;
783
+ const positionOf = (source) => (index) => {
784
+ const before = pipe(source, String$1.slice(START$1, index), String$1.split("\n"));
785
+ return {
786
+ line: before.length,
787
+ column: pipe(Array.last(before), Option.match({
788
+ onNone: () => FIRST_COLUMN$1,
789
+ onSome: (last) => last.length + FIRST_COLUMN$1
790
+ }))
791
+ };
792
+ };
793
+ // src/hidden-unicode.ts
794
+ const HIDDEN = /[\u00AD\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]|[\u{E0000}-\u{E007F}]/gu;
795
+ const NUL = "\0";
796
+ const START = 0;
797
+ const HEX = 16;
798
+ const CODE_POINT_WIDTH = 4;
799
+ const READ_CONCURRENCY$1 = 64;
800
+ const RULE = "hidden-unicode";
801
+ 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"));
802
+ const problemOf = (file, source) => (found) => {
803
+ const hex = hexOf(found[START]);
804
+ return {
805
+ file,
806
+ ...positionOf(source)(found.index ?? START),
807
+ rule: RULE,
808
+ 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}.`
809
+ };
810
+ };
811
+ const problemsIn = (file, source) => {
812
+ if (String$1.includes(NUL)(source)) return [];
813
+ return pipe(Array.fromIterable(String$1.matchAll(HIDDEN)(source)), Array.map(problemOf(file, source)));
814
+ };
815
+ /** @internal */
816
+ const checkHiddenUnicode = Effect.fn("checkHiddenUnicode")(function* checkHiddenUnicode(root) {
817
+ const files = yield* RepositoryFiles;
818
+ const names = yield* files.listedFiles(root);
819
+ const found = yield* Effect.forEach(names, (name) => {
820
+ const file = files.join(root, name);
821
+ return files.readFile(file).pipe(Effect.map(Option.match({
822
+ onNone: () => [],
823
+ onSome: (source) => problemsIn(file, source)
824
+ })));
825
+ }, { concurrency: READ_CONCURRENCY$1 });
826
+ return Array.flatten(found);
827
+ });
828
+ // src/output.ts
829
+ const taggedError$1 = Schema.TaggedError;
830
+ var ProblemsFound = class extends taggedError$1()("ProblemsFound", { count: Schema.Finite }) {};
831
+ /** @internal */
832
+ const writeStdout = Effect.fn("writeStdout")(function* writeStdout(text) {
833
+ const stdio = yield* Stdio.Stdio;
834
+ yield* Stream.make(text).pipe(Stream.run(stdio.stdout()), Effect.orDie);
835
+ });
836
+ // src/override-findings.ts
837
+ const describeVersions = (versions) => Option.getOrElse(Option.liftPredicate(Array.join(versions, ","), (joined) => joined !== ""), () => "-");
838
+ const describeChanges = (report) => {
839
+ const versions = pipe(report.changes, Array.map((change) => `${change.name} ${describeVersions(change.before)} -> ${describeVersions(change.after)}`));
840
+ const contexts = Array.filter(["the peer contexts"], () => report.peerContextsChanged);
841
+ return Array.join([...versions, ...contexts], "; ");
842
+ };
843
+ const overrideProblem = (located) => (report) => {
844
+ const at = {
845
+ file: located.file,
846
+ line: located.line,
847
+ column: located.column
848
+ };
849
+ if (report.verdict === "unused") return Option.some({
850
+ ...at,
851
+ rule: "unused-override",
852
+ message: `Every package resolves the same way without the override ${report.key}. Remove it.`
853
+ });
854
+ if (report.verdict === "verify") return Option.some({
855
+ ...at,
856
+ rule: "removable-override",
857
+ message: `Removing the override ${report.key} changes ${describeChanges(report)} without new peer issues or advisories. Remove it and run verify.`
858
+ });
859
+ return Option.none();
860
+ };
861
+ // src/override-resolution.ts
862
+ const VerdictSchema = Schema.Literals([
863
+ "unused",
864
+ "keep",
865
+ "verify"
866
+ ]);
867
+ const PackageChangeSchema = Schema.Struct({
868
+ name: Schema.String,
869
+ before: Schema.Array(Schema.String),
870
+ after: Schema.Array(Schema.String)
871
+ });
872
+ Schema.Struct({
873
+ key: Schema.String,
874
+ verdict: VerdictSchema,
875
+ changes: Schema.Array(PackageChangeSchema),
876
+ peerContextsChanged: Schema.Boolean,
877
+ peerIssues: Schema.Array(Schema.String),
878
+ advisories: Schema.Array(Schema.String)
879
+ });
880
+ const PeerParentSchema = Schema.Struct({
881
+ name: Schema.String,
882
+ version: Schema.String
883
+ });
884
+ const PeerIssueSchema = Schema.Struct({
885
+ parents: Schema.Array(PeerParentSchema),
886
+ wantedRange: Schema.String,
887
+ foundVersion: Schema.optionalKey(Schema.String)
888
+ });
889
+ const ProjectPeerIssuesSchema = Schema.Struct({
890
+ bad: Schema.Record(Schema.String, Schema.Array(PeerIssueSchema)),
891
+ missing: Schema.Record(Schema.String, Schema.Array(PeerIssueSchema)),
892
+ conflicts: Schema.Array(Schema.String)
893
+ });
894
+ const PeerIssuesLog = Schema.fromJsonString(Schema.Struct({
895
+ name: Schema.Literal("pnpm:peer-dependency-issues"),
896
+ issuesByProjects: Schema.Record(Schema.String, ProjectPeerIssuesSchema)
897
+ }));
898
+ const AdvisorySchema = Schema.Struct({
899
+ module_name: Schema.String,
900
+ github_advisory_id: Schema.String
901
+ });
902
+ const AuditReport = Schema.fromJsonString(Schema.Struct({ advisories: Schema.Record(Schema.String, AdvisorySchema) }));
903
+ const SCOPE_PREFIX = "@";
904
+ const NAME_START = 0;
905
+ const SEPARATOR_LENGTH = 1;
906
+ const PEER_CONTEXT_START = "(";
907
+ const sorted = (values) => Array.sort(Array.dedupe(values), Order.String);
908
+ const describeIssue = (project, kind) => ([peer, issues]) => pipe(issues, Array.map((issue) => {
909
+ const dependent = Option.match(Array.last(issue.parents), {
910
+ onNone: () => project,
911
+ onSome: (parent) => `${parent.name}@${parent.version}`
912
+ });
913
+ const found = Option.getOrElse(Option.fromUndefinedOr(issue.foundVersion), () => "none");
914
+ return `${project}: ${kind} peer ${peer}@${issue.wantedRange} of ${dependent}, found ${found}`;
915
+ }));
916
+ const issuesOfProject = ([project, issues]) => [
917
+ ...pipe(Record.toEntries(issues.bad), Array.flatMap(describeIssue(project, "unmet"))),
918
+ ...pipe(Record.toEntries(issues.missing), Array.flatMap(describeIssue(project, "missing"))),
919
+ ...pipe(issues.conflicts, Array.map((peer) => `${project}: conflicting peer ${peer}`))
920
+ ];
921
+ const peerIssuesIn = (output) => sorted(pipe(String$1.split(output, "\n"), Array.map((line) => Schema.decodeOption(PeerIssuesLog)(line)), Array.getSomes, Array.flatMap((log) => pipe(Record.toEntries(log.issuesByProjects), Array.flatMap(issuesOfProject)))));
922
+ const advisoriesIn = (output) => Option.match(Schema.decodeOption(AuditReport)(output), {
923
+ onNone: () => [],
924
+ onSome: (report) => sorted(pipe(Record.values(report.advisories), Array.map((advisory) => `${advisory.module_name} ${advisory.github_advisory_id}`)))
925
+ });
926
+ const separatorOf = (key) => {
927
+ if (!String$1.startsWith(SCOPE_PREFIX)(key)) return String$1.indexOf(SCOPE_PREFIX)(key);
928
+ return pipe(String$1.slice(SEPARATOR_LENGTH)(key), String$1.indexOf(SCOPE_PREFIX), Option.map((index) => index + SEPARATOR_LENGTH));
929
+ };
930
+ const withoutPeerContext = (version) => Option.match(String$1.indexOf(PEER_CONTEXT_START)(version), {
931
+ onNone: () => version,
932
+ onSome: (start) => String$1.slice(NAME_START, start)(version)
933
+ });
934
+ const packageOf = (key) => Option.map(separatorOf(key), (separator) => [String$1.slice(NAME_START, separator)(key), withoutPeerContext(String$1.slice(separator + SEPARATOR_LENGTH)(key))]);
935
+ const keysOf = (node) => {
936
+ if (!isMap(node)) return [];
937
+ return pipe(node.items, Array.flatMap((pair) => {
938
+ if (isScalar(pair.key)) return [String(pair.key.value)];
939
+ return [];
940
+ }));
941
+ };
942
+ const lockfileKeysOf = (lockfile, section) => pipe(parseAllDocuments(lockfile), Array.flatMap((document) => keysOf(document.get(section))));
943
+ const resolutionOf = (findings) => (lockfile) => {
944
+ const snapshots = lockfileKeysOf(lockfile, "snapshots");
945
+ const packages = Array.getSomes(pipe([...lockfileKeysOf(lockfile, "packages"), ...snapshots], Array.map((key) => packageOf(key))));
946
+ const versions = Record.map(Array.groupBy(packages, ([name]) => name), (entries) => sorted(pipe(entries, Array.map(([, version]) => version))));
947
+ return {
948
+ ...findings,
949
+ versions,
950
+ snapshots: sorted(snapshots)
951
+ };
952
+ };
953
+ const changesBetween = (baseline, candidate) => pipe(sorted([...Record.keys(baseline.versions), ...Record.keys(candidate.versions)]), Array.map((name) => ({
954
+ name,
955
+ before: baseline.versions[name] ?? [],
956
+ after: candidate.versions[name] ?? []
957
+ })), Array.filter((change) => Array.join(change.before, " ") !== Array.join(change.after, " ")));
958
+ const growsThePackageSet = (change) => change.after.length > change.before.length;
959
+ const needsTheOverride = (found) => pipe(found.changes, Array.some((change) => growsThePackageSet(change))) || Array.isReadonlyArrayNonEmpty(found.peerIssues) || Array.isReadonlyArrayNonEmpty(found.advisories);
960
+ const judge = (baseline) => (key, candidate) => {
961
+ const changes = changesBetween(baseline, candidate);
962
+ const peerIssues = Array.difference(candidate.peerIssues, baseline.peerIssues);
963
+ const advisories = Array.difference(candidate.advisories, baseline.advisories);
964
+ const peerContextsChanged = Array.join(baseline.snapshots, " ") !== Array.join(candidate.snapshots, " ");
965
+ const found = {
966
+ key,
967
+ changes,
968
+ peerContextsChanged,
969
+ peerIssues,
970
+ advisories
971
+ };
972
+ if (!Array.isReadonlyArrayNonEmpty(changes) && !peerContextsChanged) return {
973
+ ...found,
974
+ verdict: "unused"
975
+ };
976
+ if (needsTheOverride(found)) return {
977
+ ...found,
978
+ verdict: "keep"
979
+ };
980
+ return {
981
+ ...found,
982
+ verdict: "verify"
983
+ };
984
+ };
985
+ const OVERRIDES = "overrides";
986
+ const BASELINE_PROBE = "msch-baseline-probe";
987
+ const REMOVED_DEPENDENCY = "-";
988
+ const overrideKeysOf = (manifest) => {
989
+ const lineCounter = new LineCounter();
990
+ const overrides = parseDocument(manifest, { lineCounter }).get(OVERRIDES);
991
+ if (!isMap(overrides)) return [];
992
+ return pipe(overrides.items, Array.flatMap((pair) => {
993
+ const { key } = pair;
994
+ if (!isScalar(key)) return [];
995
+ return pipe(Option.fromNullishOr(key.range), Option.map(([start]) => {
996
+ const { line, col } = lineCounter.linePos(start);
997
+ return {
998
+ key: String(key.value),
999
+ line,
1000
+ column: col
1001
+ };
1002
+ }), Option.toArray);
1003
+ }));
1004
+ };
1005
+ const withoutOverride = (key) => (manifest) => {
1006
+ const document = parseDocument(manifest);
1007
+ document.deleteIn([OVERRIDES, key]);
1008
+ return document.toString();
1009
+ };
1010
+ const withBaselineProbe = (manifest) => {
1011
+ const document = parseDocument(manifest);
1012
+ document.setIn([OVERRIDES, BASELINE_PROBE], REMOVED_DEPENDENCY);
1013
+ return document.toString();
1014
+ };
1015
+ // src/overrides.ts
1016
+ const WORKSPACE_FILE = "pnpm-workspace.yaml";
1017
+ const LOCKFILE$1 = "pnpm-lock.yaml";
1018
+ const RESOLUTION_FILES = [
1019
+ "*package.json",
1020
+ WORKSPACE_FILE,
1021
+ LOCKFILE$1,
1022
+ ".npmrc",
1023
+ "patches/*"
1024
+ ];
1025
+ const RESOLUTION_CONCURRENCY = 4;
1026
+ const SUCCEEDED = 0;
1027
+ const taggedError = Schema.TaggedError;
1028
+ var OverrideResolutionFailed = class extends taggedError()("OverrideResolutionFailed", {
1029
+ override: Schema.String,
1030
+ output: Schema.String
1031
+ }) {};
1032
+ const textOf = (stream) => stream.pipe(Stream.decodeText(), Stream.mkString);
1033
+ const resolveIn = Effect.fn("resolveIn")(function* resolveIn(directory, override) {
1034
+ const handle = yield* ChildProcess.make("vp", [
1035
+ "install",
1036
+ "--lockfile-only",
1037
+ "--ignore-scripts",
1038
+ "--",
1039
+ "--reporter=ndjson"
1040
+ ], {
1041
+ cwd: directory,
1042
+ env: { CI: "true" },
1043
+ extendEnv: true,
1044
+ stdin: "ignore"
1045
+ });
1046
+ const [output, status] = yield* Effect.all([textOf(handle.all), handle.exitCode], { concurrency: "unbounded" });
1047
+ if (Number(status) !== SUCCEEDED) return yield* OverrideResolutionFailed.make({
1048
+ override,
1049
+ output
1050
+ });
1051
+ return output;
1052
+ });
1053
+ const auditIn = Effect.fn("auditIn")(function* auditIn(directory) {
1054
+ const handle = yield* ChildProcess.make("vp", [
1055
+ "pm",
1056
+ "audit",
1057
+ "--json"
1058
+ ], {
1059
+ cwd: directory,
1060
+ extendEnv: true,
1061
+ stdin: "ignore"
1062
+ });
1063
+ const [report] = yield* Effect.all([textOf(handle.stdout), handle.exitCode], { concurrency: "unbounded" });
1064
+ return advisoriesIn(report);
1065
+ }, Effect.scoped);
1066
+ const trackedResolutionFiles = Effect.fn("trackedResolutionFiles")(function* trackedFiles(root) {
1067
+ const handle = yield* ChildProcess.make("git", [
1068
+ "ls-files",
1069
+ "-z",
1070
+ "--",
1071
+ ...RESOLUTION_FILES
1072
+ ], { cwd: root });
1073
+ const listed = yield* textOf(handle.stdout);
1074
+ return pipe(String$1.split(listed, "\0"), Array.filter(String$1.isNonEmpty));
1075
+ }, Effect.scoped);
1076
+ const copyFiles = Effect.fn("copyFiles")(function* copyFiles(workspace, directory) {
1077
+ const fs = yield* FileSystem.FileSystem;
1078
+ const path = yield* Path.Path;
1079
+ const copyOne = (file) => {
1080
+ const target = path.join(directory, file);
1081
+ const copy = fs.copyFile(path.join(workspace.root, file), target);
1082
+ return fs.makeDirectory(path.dirname(target), { recursive: true }).pipe(Effect.andThen(copy));
1083
+ };
1084
+ yield* Effect.forEach(workspace.files, copyOne, { discard: true });
1085
+ });
1086
+ const resolutionWith = Effect.fn("resolutionWith")(function* resolutionWith(workspace, override, manifest) {
1087
+ const fs = yield* FileSystem.FileSystem;
1088
+ const path = yield* Path.Path;
1089
+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "msch-overrides-" });
1090
+ yield* copyFiles(workspace, directory);
1091
+ yield* fs.writeFileString(path.join(directory, WORKSPACE_FILE), manifest);
1092
+ const output = yield* resolveIn(directory, override);
1093
+ const lockfile = yield* fs.readFileString(path.join(directory, LOCKFILE$1));
1094
+ yield* Effect.annotateCurrentSpan({ "msch.override": override });
1095
+ const advisories = yield* auditIn(directory);
1096
+ return resolutionOf({
1097
+ peerIssues: peerIssuesIn(output),
1098
+ advisories
1099
+ })(lockfile);
1100
+ }, Effect.scoped);
1101
+ const problemsFor = Effect.fn("problemsFor")(function* problemsFor(root, manifest, keys) {
1102
+ const path = yield* Path.Path;
1103
+ const workspace = {
1104
+ root,
1105
+ files: yield* trackedResolutionFiles(root),
1106
+ manifest
1107
+ };
1108
+ const baseline = yield* resolutionWith(workspace, BASELINE_PROBE, withBaselineProbe(manifest));
1109
+ const judgeAgainstBaseline = judge(baseline);
1110
+ const file = path.join(root, WORKSPACE_FILE);
1111
+ const problemOf = (located) => resolutionWith(workspace, located.key, withoutOverride(located.key)(manifest)).pipe(Effect.map((candidate) => overrideProblem({
1112
+ ...located,
1113
+ file
1114
+ })(judgeAgainstBaseline(located.key, candidate))));
1115
+ const problems = yield* Effect.forEach(keys, problemOf, { concurrency: RESOLUTION_CONCURRENCY });
1116
+ return Array.getSomes(problems);
1117
+ });
1118
+ /** @internal */
1119
+ const checkOverrides = Effect.fn("checkOverrides")(function* checkOverrides(root) {
1120
+ const fs = yield* FileSystem.FileSystem;
1121
+ const path = yield* Path.Path;
1122
+ const manifest = yield* fs.readFileString(path.join(root, WORKSPACE_FILE)).pipe(Effect.option);
1123
+ const keys = Option.match(manifest, {
1124
+ onNone: () => [],
1125
+ onSome: overrideKeysOf
1126
+ });
1127
+ if (Option.isNone(manifest) || !Array.isReadonlyArrayNonEmpty(keys)) return [];
1128
+ return yield* problemsFor(root, manifest.value, keys);
1129
+ });
1130
+ // src/python.ts
1131
+ const PYTHON_FILE = /(?:\.(?:py|pyi|pyw|pyx|ipynb)|(?:^|\/)(?:pyproject\.toml|uv\.lock|uv\.toml|poetry\.lock|Pipfile|Pipfile\.lock|setup\.cfg|tox\.ini|pytest\.ini|\.python-version|requirements[^/]*\.txt))$/u;
1132
+ const SCANNED_FILE = /(?:\.(?:[cm]?[jt]sx?|jsonc?|toml|ya?ml|sh|bash|zsh|fish|rs|cs|go|mk)|(?:^|\/)(?:Dockerfile[^/]*|Makefile|justfile|\.tool-versions|[^./][^./]*))$/u;
1133
+ const LOCKFILE = /(?:^|\/)(?:[^/]*\.lock|[^/]*-lock\.(?:json|ya?ml))$/u;
1134
+ const TOOL_VERSIONS = /(?:^|\/)(?:\.?mise(?:\.[^/]+)?\.toml|\.tool-versions)$/u;
1135
+ const COMMAND_PATTERNS = [
1136
+ /(?<![\w./@-])python[\d.]*[ \t]+(?:[-"'$./]|\S+\.py\b)/gu,
1137
+ /(?<![\w./@-])pip3?[ \t]+(?:install|uninstall|download|freeze|wheel|compile|sync)\b/gu,
1138
+ /(?<![\w./@-])pipx[ \t]+(?:run|install|inject|upgrade)\b/gu,
1139
+ /(?<![\w./@-])uvx[ \t]+[^\s=:,;)|&]/gu,
1140
+ /(?<![\w./@-])uv[ \t]+(?:run|sync|lock|pip|tool|venv|add|remove|python|export|build|publish|init|tree|cache|self)\b/gu,
1141
+ /(?<![\w./@-])poetry[ \t]+(?:run|install|add|lock|build|publish|shell|export)\b/gu,
1142
+ /(?:\bcommand["']?\s*[:=]\s*|(?:\bspawn\w*|\bexec\w*|\bexeca|Command::new|ChildProcess\.make|\$)\(\s*)["'`](?:python[\d.]*|pip3?|pipx|uvx?|poetry)["'`]/gu,
1143
+ /\buses:\s*["']?(?:actions\/setup-python|astral-sh\/setup-uv|astral-sh\/ruff-action|pypa\/[\w.-]+|snok\/install-poetry|abatilo\/actions-poetry|conda-incubator\/setup-miniconda|mamba-org\/setup-micromamba|pdm-project\/setup-pdm)\b/gu
1144
+ ];
1145
+ const TOOL_PATTERNS = [/^[ \t]*["']?(?:python|uv|pipx|poetry|ruff|pdm|hatch|pipx:[^"'\s=]+)["']?[ \t]*(?:=|[ \t]+\S)/gmu];
1146
+ const READ_CONCURRENCY = 64;
1147
+ const FIRST_LINE = 1;
1148
+ const FIRST_COLUMN = 1;
1149
+ const WHOLE_MATCH = 0;
1150
+ const FILE_RULE = "python-file";
1151
+ const COMMAND_RULE = "python-command";
1152
+ const fileProblem = ({ file }) => ({
1153
+ file,
1154
+ line: FIRST_LINE,
1155
+ column: FIRST_COLUMN,
1156
+ rule: FILE_RULE,
1157
+ message: "Python is not used in this repository. Replace this file with TypeScript or Rust."
1158
+ });
1159
+ const patternsFor = (name) => {
1160
+ if (TOOL_VERSIONS.test(name)) return [...COMMAND_PATTERNS, ...TOOL_PATTERNS];
1161
+ return COMMAND_PATTERNS;
1162
+ };
1163
+ const commandProblem = ({ file }, source) => (found) => ({
1164
+ file,
1165
+ ...positionOf(source)(found.index ?? WHOLE_MATCH),
1166
+ rule: COMMAND_RULE,
1167
+ message: `Python is not used in this repository. Remove \`${String$1.trim(found[WHOLE_MATCH])}\` and do the work in TypeScript or Rust.`
1168
+ });
1169
+ const commandProblems = (entry, source) => pipe(patternsFor(entry.name), Array.flatMap((pattern) => Array.fromIterable(String$1.matchAll(pattern)(source))), Array.map(commandProblem(entry, source)));
1170
+ const isPython = ({ name }) => PYTHON_FILE.test(name);
1171
+ const isScanned = (entry) => !isPython(entry) && SCANNED_FILE.test(entry.name) && !LOCKFILE.test(entry.name);
1172
+ /** @internal */
1173
+ const checkPython = Effect.fn("checkPython")(function* checkPython(root) {
1174
+ const files = yield* RepositoryFiles;
1175
+ const names = yield* files.listedFiles(root);
1176
+ const listed = pipe(names, Array.map((name) => ({
1177
+ name,
1178
+ file: files.join(root, name)
1179
+ })));
1180
+ const problemsIn = (entry) => files.readFile(entry.file).pipe(Effect.map(Option.match({
1181
+ onNone: () => [],
1182
+ onSome: (source) => commandProblems(entry, source)
1183
+ })));
1184
+ const scanned = yield* Effect.forEach(pipe(listed, Array.filter(isScanned)), problemsIn, { concurrency: READ_CONCURRENCY });
1185
+ const present = yield* Effect.filter(pipe(listed, Array.filter(isPython)), ({ file }) => files.exists(file));
1186
+ return [...pipe(present, Array.map(fileProblem)), ...Array.flatten(scanned)];
1187
+ });
1188
+ // src/runtime.ts
1189
+ const reportFailure = (cause) => Effect.gen(function* report() {
1190
+ const stdio = yield* Stdio.Stdio;
1191
+ if (Cause.findErrorOption(cause).pipe(Option.exists(Schema.is(ProblemsFound)))) return;
1192
+ yield* Stream.make(`${Cause.pretty(cause)}\n`).pipe(Stream.run(stdio.stderr()), Effect.orDie);
1193
+ });
1194
+ const tracing = Otlp.layerFromConfig({ resource: { serviceName: "msch" } }).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
1195
+ const runCli = (program) => {
1196
+ const services = Layer.mergeAll(nodeRepositoryFiles, tracing).pipe(Layer.provideMerge(layer));
1197
+ const main = Layer.build(services).pipe(Effect.flatMap((context) => Effect.provideContext(program.pipe(Effect.tapCause(reportFailure)), context)), Effect.scoped);
1198
+ runMain(main, { disableErrorReporting: true });
1199
+ };
1200
+ // src/cli.ts
1201
+ const NO_PROBLEMS = 0;
1202
+ const reportText = (root, problems) => Effect.gen(function* report() {
1203
+ const files = yield* RepositoryFiles;
1204
+ const format = formatProblem((file) => files.relative(root, file));
1205
+ yield* writeStdout(pipe(problems, Array.map((problem) => `${format(problem)}\n`), Array.join("")));
1206
+ if (problems.length > NO_PROBLEMS) return yield* ProblemsFound.make({ count: problems.length });
1207
+ return yield* Effect.void;
1208
+ });
1209
+ runCli(Effect.gen(function* msch() {
1210
+ const stdio = yield* Stdio.Stdio;
1211
+ const files = yield* RepositoryFiles;
1212
+ const args = yield* stdio.args;
1213
+ const root = files.resolve(".");
1214
+ const found = yield* Effect.all([
1215
+ checkRepository(root),
1216
+ checkOverrides(root),
1217
+ checkHiddenUnicode(root),
1218
+ checkPython(root)
1219
+ ], { concurrency: "unbounded" });
1220
+ const problems = sortProblems(Array.flatten(found));
1221
+ if (args.includes("--json")) {
1222
+ const text = yield* Schema.encodeEffect(Schema.fromJsonString(ProblemList))(problems);
1223
+ return yield* writeStdout(text);
1224
+ }
1225
+ return yield* reportText(root, problems);
1226
+ }));
1227
+ export {};
1228
+
1229
+ //# sourceMappingURL=cli.mjs.map