@emeryld/manager 1.3.0 → 1.4.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 +96 -0
- package/dist/create-package/cli-args.js +78 -0
- package/dist/create-package/prompts.js +138 -0
- package/dist/create-package/shared/configs.js +309 -0
- package/dist/create-package/shared/constants.js +5 -0
- package/dist/create-package/shared/fs-utils.js +69 -0
- package/dist/create-package/tasks.js +89 -0
- package/dist/create-package/types.js +1 -0
- package/dist/create-package/variant-info.js +67 -0
- package/dist/create-package/variants/client/expo-react-native/lib-files.js +168 -0
- package/dist/create-package/variants/client/expo-react-native/package-files.js +94 -0
- package/dist/create-package/variants/client/expo-react-native/scaffold.js +59 -0
- package/dist/create-package/variants/client/expo-react-native/ui-files.js +215 -0
- package/dist/create-package/variants/client/vite-react/health-page.js +251 -0
- package/dist/create-package/variants/client/vite-react/lib-files.js +176 -0
- package/dist/create-package/variants/client/vite-react/package-files.js +79 -0
- package/dist/create-package/variants/client/vite-react/scaffold.js +68 -0
- package/dist/create-package/variants/client/vite-react/ui-files.js +154 -0
- package/dist/create-package/variants/fullstack/files.js +129 -0
- package/dist/create-package/variants/fullstack/index.js +86 -0
- package/dist/create-package/variants/fullstack/utils.js +241 -0
- package/dist/llm-pack.js +2 -0
- package/dist/robot/cli/prompts.js +84 -27
- package/dist/robot/cli/settings.js +131 -56
- package/dist/robot/config.js +123 -50
- package/dist/robot/coordinator.js +10 -105
- package/dist/robot/extractors/classes.js +14 -13
- package/dist/robot/extractors/components.js +17 -10
- package/dist/robot/extractors/constants.js +9 -6
- package/dist/robot/extractors/functions.js +11 -8
- package/dist/robot/extractors/shared.js +6 -1
- package/dist/robot/extractors/types.js +5 -8
- package/dist/robot/llm-pack.js +1226 -0
- package/dist/robot/pack/builder.js +374 -0
- package/dist/robot/pack/cli.js +65 -0
- package/dist/robot/pack/exemplars.js +573 -0
- package/dist/robot/pack/globs.js +119 -0
- package/dist/robot/pack/selection.js +44 -0
- package/dist/robot/pack/symbols.js +309 -0
- package/dist/robot/pack/type-registry.js +285 -0
- package/dist/robot/pack/types.js +48 -0
- package/dist/robot/pack/utils.js +36 -0
- package/dist/robot/serializer.js +97 -0
- package/dist/robot/v2/cli.js +86 -0
- package/dist/robot/v2/globs.js +103 -0
- package/dist/robot/v2/parser/bundles.js +55 -0
- package/dist/robot/v2/parser/candidates.js +63 -0
- package/dist/robot/v2/parser/exemplars.js +114 -0
- package/dist/robot/v2/parser/exports.js +57 -0
- package/dist/robot/v2/parser/symbols.js +179 -0
- package/dist/robot/v2/parser.js +114 -0
- package/dist/robot/v2/types.js +42 -0
- package/dist/utils/export.js +39 -18
- package/package.json +2 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createGlobMatchers, matchesAnyGlob } from './globs.js';
|
|
3
|
+
import { normalizeRelativePath, isPathInside } from './utils.js';
|
|
4
|
+
export function selectCandidateFiles(sourceFiles, settings) {
|
|
5
|
+
const includeMatchers = createGlobMatchers(settings.includeGlobs);
|
|
6
|
+
const excludeMatchers = createGlobMatchers(settings.excludeGlobs);
|
|
7
|
+
const results = [];
|
|
8
|
+
for (const file of sourceFiles) {
|
|
9
|
+
if (file.isDeclarationFile)
|
|
10
|
+
continue;
|
|
11
|
+
const absolutePath = path.resolve(file.fileName);
|
|
12
|
+
if (!absolutePath.startsWith(settings.rootDir))
|
|
13
|
+
continue;
|
|
14
|
+
if (!isPathInside(absolutePath, settings.scopeDir))
|
|
15
|
+
continue;
|
|
16
|
+
const relative = normalizeRelativePath(path.relative(settings.rootDir, absolutePath));
|
|
17
|
+
if (relative.startsWith('..'))
|
|
18
|
+
continue;
|
|
19
|
+
if (!matchesAnyGlob(relative, includeMatchers))
|
|
20
|
+
continue;
|
|
21
|
+
if (matchesAnyGlob(relative, excludeMatchers))
|
|
22
|
+
continue;
|
|
23
|
+
results.push({ absolutePath, relativePath: relative, sourceFile: file });
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
export function resolveEntrypoints(candidates, settings) {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
const entrypointMatchers = createGlobMatchers(settings.entrypoints);
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
if (settings.exportMode === 'all-files') {
|
|
32
|
+
map.set(candidate.absolutePath, new Set([candidate.relativePath]));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (matchesAnyGlob(candidate.relativePath, entrypointMatchers)) {
|
|
36
|
+
map.set(candidate.absolutePath, new Set([candidate.relativePath]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (map.size === 0 && settings.exportMode === 'entrypoints' && candidates.length > 0) {
|
|
40
|
+
const fallback = candidates.find((entry) => isPathInside(entry.absolutePath, settings.scopeDir)) ?? candidates[0];
|
|
41
|
+
map.set(fallback.absolutePath, new Set([fallback.relativePath]));
|
|
42
|
+
}
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { buildSymbolId, normalizeRelativePath, normalizeTypeText, PRINTER } from './utils.js';
|
|
4
|
+
export function collectPublicSymbols(program, checker, candidates, entrypoints, settings) {
|
|
5
|
+
const compilerOptions = program.getCompilerOptions();
|
|
6
|
+
const candidatesByPath = new Map(candidates.map((entry) => [entry.absolutePath, entry]));
|
|
7
|
+
const queue = [...entrypoints.keys()];
|
|
8
|
+
const moduleEntrypoints = new Map(entrypoints);
|
|
9
|
+
const visited = new Set();
|
|
10
|
+
const symbols = new Map();
|
|
11
|
+
const followReexports = settings.visibility !== 'exported-only';
|
|
12
|
+
while (queue.length > 0) {
|
|
13
|
+
const modulePath = queue.shift();
|
|
14
|
+
if (!modulePath || visited.has(modulePath))
|
|
15
|
+
continue;
|
|
16
|
+
visited.add(modulePath);
|
|
17
|
+
const moduleInfo = candidatesByPath.get(modulePath);
|
|
18
|
+
if (!moduleInfo)
|
|
19
|
+
continue;
|
|
20
|
+
const exposures = moduleEntrypoints.get(modulePath) ?? new Set();
|
|
21
|
+
const moduleSymbol = checker.getSymbolAtLocation(moduleInfo.sourceFile);
|
|
22
|
+
if (!moduleSymbol)
|
|
23
|
+
continue;
|
|
24
|
+
for (const exportSymbol of checker.getExportsOfModule(moduleSymbol)) {
|
|
25
|
+
handleSymbol(symbols, exportSymbol, exposures, moduleInfo, checker, settings, Boolean(exportSymbol.flags & ts.SymbolFlags.Alias));
|
|
26
|
+
}
|
|
27
|
+
if (settings.visibility === 'all') {
|
|
28
|
+
includeLocalDeclarations(symbols, moduleInfo, exposures, checker, settings);
|
|
29
|
+
}
|
|
30
|
+
if (followReexports) {
|
|
31
|
+
for (const stmt of moduleInfo.sourceFile.statements) {
|
|
32
|
+
if (ts.isExportDeclaration(stmt) &&
|
|
33
|
+
stmt.moduleSpecifier &&
|
|
34
|
+
ts.isStringLiteral(stmt.moduleSpecifier)) {
|
|
35
|
+
const resolved = resolveModuleSpecifier(stmt.moduleSpecifier.text, moduleInfo.sourceFile, compilerOptions);
|
|
36
|
+
if (resolved && candidatesByPath.has(resolved)) {
|
|
37
|
+
ensureModuleExposure(moduleEntrypoints, queue, resolved, exposures);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return symbols;
|
|
44
|
+
}
|
|
45
|
+
function includeLocalDeclarations(symbols, moduleInfo, exposures, checker, settings) {
|
|
46
|
+
for (const stmt of moduleInfo.sourceFile.statements) {
|
|
47
|
+
const candidates = [];
|
|
48
|
+
if (ts.isFunctionDeclaration(stmt) ||
|
|
49
|
+
ts.isClassDeclaration(stmt) ||
|
|
50
|
+
ts.isInterfaceDeclaration(stmt) ||
|
|
51
|
+
ts.isTypeAliasDeclaration(stmt) ||
|
|
52
|
+
ts.isEnumDeclaration(stmt)) {
|
|
53
|
+
const symbol = stmt.name ? checker.getSymbolAtLocation(stmt.name) : undefined;
|
|
54
|
+
if (symbol)
|
|
55
|
+
candidates.push(symbol);
|
|
56
|
+
}
|
|
57
|
+
if (ts.isVariableStatement(stmt)) {
|
|
58
|
+
for (const declaration of stmt.declarationList.declarations) {
|
|
59
|
+
const symbol = declaration.name
|
|
60
|
+
? checker.getSymbolAtLocation(declaration.name)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (symbol)
|
|
63
|
+
candidates.push(symbol);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
handleSymbol(symbols, candidate, exposures, moduleInfo, checker, settings, false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function handleSymbol(symbols, sourceSymbol, exposures, moduleInfo, checker, settings, isReexport) {
|
|
72
|
+
const targetSymbol = (sourceSymbol.flags & ts.SymbolFlags.Alias)
|
|
73
|
+
? checker.getAliasedSymbol(sourceSymbol)
|
|
74
|
+
: sourceSymbol;
|
|
75
|
+
if (!targetSymbol)
|
|
76
|
+
return;
|
|
77
|
+
const declaration = targetSymbol.valueDeclaration ?? targetSymbol.declarations?.[0];
|
|
78
|
+
if (!declaration)
|
|
79
|
+
return;
|
|
80
|
+
const kind = determineSymbolKind(targetSymbol, declaration);
|
|
81
|
+
if (!kind || !settings.includeKinds.includes(kind))
|
|
82
|
+
return;
|
|
83
|
+
const name = targetSymbol.getName();
|
|
84
|
+
const sourcePath = normalizeRelativePath(path.relative(settings.rootDir, declaration.getSourceFile().fileName));
|
|
85
|
+
const summary = {
|
|
86
|
+
name,
|
|
87
|
+
kind,
|
|
88
|
+
entrypoints: [],
|
|
89
|
+
modulePath: exposures.values().next().value ?? moduleInfo.relativePath,
|
|
90
|
+
sourcePath,
|
|
91
|
+
stableId: buildStableSymbolId(sourcePath, kind, name),
|
|
92
|
+
signature: buildSymbolSignature(targetSymbol, declaration, checker, kind),
|
|
93
|
+
isReexport,
|
|
94
|
+
docTags: extractDocTags(targetSymbol, settings.keepJSDocTags),
|
|
95
|
+
typeDependencies: [],
|
|
96
|
+
};
|
|
97
|
+
const symbolId = buildSymbolId(targetSymbol, declaration);
|
|
98
|
+
let record = symbols.get(symbolId);
|
|
99
|
+
if (!record) {
|
|
100
|
+
record = {
|
|
101
|
+
symbol: targetSymbol,
|
|
102
|
+
declaration,
|
|
103
|
+
summary,
|
|
104
|
+
entrypoints: new Set(exposures),
|
|
105
|
+
};
|
|
106
|
+
record.summary.entrypoints = Array.from(record.entrypoints).sort();
|
|
107
|
+
symbols.set(symbolId, record);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const entry of exposures) {
|
|
111
|
+
record.entrypoints.add(entry);
|
|
112
|
+
}
|
|
113
|
+
record.summary.entrypoints = Array.from(record.entrypoints).sort();
|
|
114
|
+
record.summary.modulePath = record.summary.entrypoints[0] ?? moduleInfo.relativePath;
|
|
115
|
+
}
|
|
116
|
+
function determineSymbolKind(symbol, declaration) {
|
|
117
|
+
if (ts.isClassLike(declaration))
|
|
118
|
+
return 'class';
|
|
119
|
+
if (ts.isFunctionLike(declaration))
|
|
120
|
+
return 'function';
|
|
121
|
+
if (ts.isInterfaceDeclaration(declaration))
|
|
122
|
+
return 'interface';
|
|
123
|
+
if (ts.isTypeAliasDeclaration(declaration))
|
|
124
|
+
return 'type-alias';
|
|
125
|
+
if (ts.isEnumDeclaration(declaration))
|
|
126
|
+
return 'enum';
|
|
127
|
+
if (ts.isVariableDeclaration(declaration))
|
|
128
|
+
return 'const';
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
function buildSymbolSignature(symbol, declaration, checker, kind) {
|
|
132
|
+
if (kind === 'function') {
|
|
133
|
+
return buildFunctionSignature(symbol, declaration, checker);
|
|
134
|
+
}
|
|
135
|
+
if (kind === 'class') {
|
|
136
|
+
return buildClassSignature(declaration, checker);
|
|
137
|
+
}
|
|
138
|
+
if (kind === 'const') {
|
|
139
|
+
return buildConstSignature(symbol, declaration, checker);
|
|
140
|
+
}
|
|
141
|
+
return buildTypeSignature(declaration);
|
|
142
|
+
}
|
|
143
|
+
function buildFunctionSignature(symbol, declaration, checker) {
|
|
144
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
145
|
+
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
|
|
146
|
+
if (!signatures.length)
|
|
147
|
+
return symbol.getName();
|
|
148
|
+
const formatted = signatures
|
|
149
|
+
.map((signature) => checker.signatureToString(signature))
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
return formatted.join(' | ');
|
|
152
|
+
}
|
|
153
|
+
function buildClassSignature(declaration, checker) {
|
|
154
|
+
if (!ts.isClassLike(declaration))
|
|
155
|
+
return declaration.getText();
|
|
156
|
+
const name = declaration.name?.getText() ?? 'Anonymous';
|
|
157
|
+
const heritage = declaration.heritageClauses
|
|
158
|
+
? declaration.heritageClauses
|
|
159
|
+
.map((clause) => clause.getText())
|
|
160
|
+
.join(' ')
|
|
161
|
+
.trim()
|
|
162
|
+
: '';
|
|
163
|
+
const header = `export declare class ${name}${heritage ? ` ${heritage}` : ''}`;
|
|
164
|
+
const memberParts = [];
|
|
165
|
+
for (const member of declaration.members) {
|
|
166
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
167
|
+
memberParts.push(`constructor(${formatParameters(member.parameters, checker)})`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
|
|
171
|
+
const memberName = member.name?.getText() ?? 'anonymous';
|
|
172
|
+
const signature = checker.getSignatureFromDeclaration(member);
|
|
173
|
+
const text = signature
|
|
174
|
+
? checker.signatureToString(signature)
|
|
175
|
+
: `${memberName}()`;
|
|
176
|
+
memberParts.push(`${memberName}${text.slice(memberName.length)}`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
|
|
180
|
+
const nameText = member.name?.getText() ?? '';
|
|
181
|
+
const type = member.type
|
|
182
|
+
? checker.getTypeAtLocation(member.type)
|
|
183
|
+
: checker.getTypeAtLocation(member);
|
|
184
|
+
const typeText = checker.typeToString(type);
|
|
185
|
+
const optional = member.questionToken ? '?' : '';
|
|
186
|
+
memberParts.push(`${nameText}${optional}: ${typeText}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const body = memberParts.length ? ` { ${memberParts.join('; ')} }` : ' {}';
|
|
190
|
+
return `${header}${body}`;
|
|
191
|
+
}
|
|
192
|
+
export function buildClassMemberSignatures(className, modulePath, declaration, checker) {
|
|
193
|
+
if (!ts.isClassLike(declaration))
|
|
194
|
+
return [];
|
|
195
|
+
const entries = [];
|
|
196
|
+
for (const member of declaration.members) {
|
|
197
|
+
if (!isClassMemberSignature(member))
|
|
198
|
+
continue;
|
|
199
|
+
const memberName = getClassMemberName(member);
|
|
200
|
+
if (!memberName)
|
|
201
|
+
continue;
|
|
202
|
+
const text = buildClassMemberSignatureText(member, checker);
|
|
203
|
+
if (!text)
|
|
204
|
+
continue;
|
|
205
|
+
entries.push({
|
|
206
|
+
name: memberName,
|
|
207
|
+
kind: 'function',
|
|
208
|
+
modulePath,
|
|
209
|
+
text,
|
|
210
|
+
parent: className,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return entries;
|
|
214
|
+
}
|
|
215
|
+
function isClassMemberSignature(member) {
|
|
216
|
+
return (ts.isMethodDeclaration(member) ||
|
|
217
|
+
ts.isMethodSignature(member) ||
|
|
218
|
+
ts.isConstructorDeclaration(member) ||
|
|
219
|
+
ts.isGetAccessorDeclaration(member) ||
|
|
220
|
+
ts.isSetAccessorDeclaration(member));
|
|
221
|
+
}
|
|
222
|
+
function getClassMemberName(member) {
|
|
223
|
+
if (ts.isConstructorDeclaration(member))
|
|
224
|
+
return 'constructor';
|
|
225
|
+
const name = member.name;
|
|
226
|
+
if (!name)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
229
|
+
return name.text;
|
|
230
|
+
}
|
|
231
|
+
return name.getText();
|
|
232
|
+
}
|
|
233
|
+
function buildClassMemberSignatureText(member, checker) {
|
|
234
|
+
const signature = checker.getSignatureFromDeclaration(member);
|
|
235
|
+
if (!signature)
|
|
236
|
+
return undefined;
|
|
237
|
+
let text = checker.signatureToString(signature);
|
|
238
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
239
|
+
text = text.replace(/^new(?=\s|\()/, 'constructor');
|
|
240
|
+
}
|
|
241
|
+
const modifiers = member.modifiers
|
|
242
|
+
? member.modifiers.map((modifier) => modifier.getText()).join(' ')
|
|
243
|
+
: '';
|
|
244
|
+
const prefix = modifiers ? `${modifiers} ` : '';
|
|
245
|
+
return `${prefix}${text}`.trim();
|
|
246
|
+
}
|
|
247
|
+
function formatParameters(parameters, checker) {
|
|
248
|
+
return parameters
|
|
249
|
+
.map((param) => {
|
|
250
|
+
const name = param.name.getText();
|
|
251
|
+
const typeNode = param.type;
|
|
252
|
+
const type = typeNode ? checker.getTypeAtLocation(typeNode) : checker.getTypeAtLocation(param);
|
|
253
|
+
const typeText = checker.typeToString(type);
|
|
254
|
+
const optional = param.questionToken ? '?' : '';
|
|
255
|
+
return `${name}${optional}: ${typeText}`;
|
|
256
|
+
})
|
|
257
|
+
.join(', ');
|
|
258
|
+
}
|
|
259
|
+
function buildConstSignature(symbol, declaration, checker) {
|
|
260
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
261
|
+
return `const ${symbol.getName()}: ${checker.typeToString(type)}`;
|
|
262
|
+
}
|
|
263
|
+
function buildTypeSignature(declaration) {
|
|
264
|
+
return normalizeTypeText(PRINTER.printNode(ts.EmitHint.Unspecified, declaration, declaration.getSourceFile()));
|
|
265
|
+
}
|
|
266
|
+
function extractDocTags(symbol, keepTags) {
|
|
267
|
+
const normalized = new Set();
|
|
268
|
+
const lowerK = keepTags.map((tag) => tag.toLowerCase());
|
|
269
|
+
for (const tag of symbol.getJsDocTags()) {
|
|
270
|
+
const name = `@${tag.name ?? ''}`.toLowerCase();
|
|
271
|
+
if (lowerK.includes(name)) {
|
|
272
|
+
normalized.add(name);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return Array.from(normalized);
|
|
276
|
+
}
|
|
277
|
+
function resolveModuleSpecifier(value, source, compilerOptions) {
|
|
278
|
+
const resolution = ts.resolveModuleName(value, source.fileName, compilerOptions, ts.sys);
|
|
279
|
+
if (!resolution.resolvedModule)
|
|
280
|
+
return undefined;
|
|
281
|
+
return path.resolve(resolution.resolvedModule.resolvedFileName);
|
|
282
|
+
}
|
|
283
|
+
function ensureModuleExposure(moduleEntrypoints, queue, targetPath, exposures) {
|
|
284
|
+
const existing = moduleEntrypoints.get(targetPath);
|
|
285
|
+
if (existing) {
|
|
286
|
+
for (const entry of exposures)
|
|
287
|
+
existing.add(entry);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
moduleEntrypoints.set(targetPath, new Set(exposures));
|
|
291
|
+
queue.push(targetPath);
|
|
292
|
+
}
|
|
293
|
+
function buildStableSymbolId(sourcePath, kind, name) {
|
|
294
|
+
return `${sourcePath}#${kind}:${name}`;
|
|
295
|
+
}
|
|
296
|
+
export function buildModuleExports(symbols) {
|
|
297
|
+
const modules = new Map();
|
|
298
|
+
for (const record of symbols.values()) {
|
|
299
|
+
for (const entry of record.summary.entrypoints) {
|
|
300
|
+
const moduleEntry = modules.get(entry) ?? { modulePath: entry, symbols: [] };
|
|
301
|
+
moduleEntry.symbols.push(record.summary);
|
|
302
|
+
modules.set(entry, moduleEntry);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const moduleEntry of modules.values()) {
|
|
306
|
+
moduleEntry.symbols.sort((a, b) => a.name.localeCompare(b.name));
|
|
307
|
+
}
|
|
308
|
+
return Array.from(modules.values()).sort((a, b) => a.modulePath.localeCompare(b.modulePath));
|
|
309
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { normalizeRelativePath, normalizeTypeText, PRINTER } from './utils.js';
|
|
4
|
+
export class TypeRegistry {
|
|
5
|
+
checker;
|
|
6
|
+
rootDir;
|
|
7
|
+
candidateFiles;
|
|
8
|
+
entries = new Map();
|
|
9
|
+
depthDirty = true;
|
|
10
|
+
constructor(checker, rootDir, candidateFiles) {
|
|
11
|
+
this.checker = checker;
|
|
12
|
+
this.rootDir = rootDir;
|
|
13
|
+
this.candidateFiles = candidateFiles;
|
|
14
|
+
}
|
|
15
|
+
ensureTypeDeclaration(symbol, declaration) {
|
|
16
|
+
return this.ensureEntry(symbol, declaration, true);
|
|
17
|
+
}
|
|
18
|
+
collectDependenciesForType(key, symbol, declaration) {
|
|
19
|
+
const dependencies = new Set();
|
|
20
|
+
const declaredType = this.checker.getDeclaredTypeOfSymbol(symbol);
|
|
21
|
+
if (declaredType) {
|
|
22
|
+
this.traverseType(declaredType, key, dependencies, new Set());
|
|
23
|
+
}
|
|
24
|
+
return this.sortedArray(dependencies);
|
|
25
|
+
}
|
|
26
|
+
collectDependenciesForSymbol(record) {
|
|
27
|
+
const dependencies = new Set();
|
|
28
|
+
const type = this.checker.getTypeOfSymbolAtLocation(record.symbol, record.declaration);
|
|
29
|
+
if (record.summary.kind === 'function') {
|
|
30
|
+
const signatures = this.checker.getSignaturesOfType(type, ts.SignatureKind.Call);
|
|
31
|
+
for (const signature of signatures) {
|
|
32
|
+
this.traverseSignature(signature, undefined, dependencies, new Set());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.traverseType(type, undefined, dependencies, new Set());
|
|
37
|
+
}
|
|
38
|
+
return this.sortedArray(dependencies);
|
|
39
|
+
}
|
|
40
|
+
buildDefinitions() {
|
|
41
|
+
const ordered = this.orderEntries();
|
|
42
|
+
return ordered.map((entry) => ({
|
|
43
|
+
name: entry.name,
|
|
44
|
+
kind: entry.kind,
|
|
45
|
+
text: entry.text,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
dropNextDependency() {
|
|
49
|
+
const candidates = this.getDependencyCandidates();
|
|
50
|
+
if (!candidates.length)
|
|
51
|
+
return false;
|
|
52
|
+
const target = candidates[0];
|
|
53
|
+
this.entries.delete(target.key);
|
|
54
|
+
for (const entry of this.entries.values()) {
|
|
55
|
+
entry.dependencies.delete(target.key);
|
|
56
|
+
}
|
|
57
|
+
this.depthDirty = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
ensureEntry(symbol, declaration, isSurface) {
|
|
61
|
+
if (!this.isCandidateDeclaration(declaration))
|
|
62
|
+
return undefined;
|
|
63
|
+
const kind = this.determineDeclarationKind(declaration);
|
|
64
|
+
if (!kind)
|
|
65
|
+
return undefined;
|
|
66
|
+
const key = this.buildTypeKey(symbol, declaration);
|
|
67
|
+
let entry = this.entries.get(key);
|
|
68
|
+
if (!entry) {
|
|
69
|
+
entry = {
|
|
70
|
+
key,
|
|
71
|
+
name: symbol.getName(),
|
|
72
|
+
kind,
|
|
73
|
+
text: normalizeTypeText(PRINTER.printNode(ts.EmitHint.Unspecified, declaration, declaration.getSourceFile())),
|
|
74
|
+
sourcePath: normalizeRelativePath(path.relative(this.rootDir, declaration.getSourceFile().fileName)),
|
|
75
|
+
dependencies: new Set(),
|
|
76
|
+
isSurface,
|
|
77
|
+
};
|
|
78
|
+
this.entries.set(key, entry);
|
|
79
|
+
}
|
|
80
|
+
else if (isSurface) {
|
|
81
|
+
entry.isSurface = true;
|
|
82
|
+
}
|
|
83
|
+
this.depthDirty = true;
|
|
84
|
+
return entry.key;
|
|
85
|
+
}
|
|
86
|
+
traverseType(type, ownerKey, dependencyNames, visited) {
|
|
87
|
+
if (!type)
|
|
88
|
+
return;
|
|
89
|
+
const typeId = this.getTypeId(type);
|
|
90
|
+
if (typeId !== undefined && visited.has(typeId))
|
|
91
|
+
return;
|
|
92
|
+
if (typeId !== undefined)
|
|
93
|
+
visited.add(typeId);
|
|
94
|
+
const referencedSymbol = type.aliasSymbol ?? type.getSymbol();
|
|
95
|
+
if (referencedSymbol) {
|
|
96
|
+
const declaration = referencedSymbol.valueDeclaration ?? referencedSymbol.declarations?.[0];
|
|
97
|
+
if (declaration) {
|
|
98
|
+
const childKey = this.ensureEntry(referencedSymbol, declaration, false);
|
|
99
|
+
if (childKey && ownerKey && ownerKey !== childKey) {
|
|
100
|
+
this.entries.get(ownerKey)?.dependencies.add(childKey);
|
|
101
|
+
}
|
|
102
|
+
if (childKey) {
|
|
103
|
+
dependencyNames.add(this.entries.get(childKey).name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (type.isUnionOrIntersection()) {
|
|
108
|
+
for (const part of type.types) {
|
|
109
|
+
this.traverseType(part, ownerKey, dependencyNames, visited);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const reference = type;
|
|
113
|
+
const typeArguments = reference.typeArguments ?? type.aliasTypeArguments;
|
|
114
|
+
if (typeArguments) {
|
|
115
|
+
for (const arg of typeArguments) {
|
|
116
|
+
this.traverseType(arg, ownerKey, dependencyNames, visited);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (reference.target) {
|
|
120
|
+
this.traverseType(reference.target, ownerKey, dependencyNames, visited);
|
|
121
|
+
}
|
|
122
|
+
for (const signature of type.getCallSignatures()) {
|
|
123
|
+
this.traverseSignature(signature, ownerKey, dependencyNames, visited);
|
|
124
|
+
}
|
|
125
|
+
for (const signature of type.getConstructSignatures()) {
|
|
126
|
+
this.traverseSignature(signature, ownerKey, dependencyNames, visited);
|
|
127
|
+
}
|
|
128
|
+
const properties = type.getProperties();
|
|
129
|
+
for (const property of properties) {
|
|
130
|
+
const decl = property.valueDeclaration ?? property.declarations?.[0];
|
|
131
|
+
if (!decl)
|
|
132
|
+
continue;
|
|
133
|
+
const propType = this.checker.getTypeAtLocation(decl);
|
|
134
|
+
this.traverseType(propType, ownerKey, dependencyNames, visited);
|
|
135
|
+
}
|
|
136
|
+
const stringIndex = type.getStringIndexType();
|
|
137
|
+
if (stringIndex)
|
|
138
|
+
this.traverseType(stringIndex, ownerKey, dependencyNames, visited);
|
|
139
|
+
const numberIndex = type.getNumberIndexType();
|
|
140
|
+
if (numberIndex)
|
|
141
|
+
this.traverseType(numberIndex, ownerKey, dependencyNames, visited);
|
|
142
|
+
for (const base of type.getBaseTypes() ?? []) {
|
|
143
|
+
this.traverseType(base, ownerKey, dependencyNames, visited);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
traverseSignature(signature, ownerKey, dependencyNames, visited) {
|
|
147
|
+
this.traverseType(signature.getReturnType(), ownerKey, dependencyNames, visited);
|
|
148
|
+
for (const parameter of signature.getParameters()) {
|
|
149
|
+
const decl = parameter.valueDeclaration ?? parameter.declarations?.[0];
|
|
150
|
+
if (!decl)
|
|
151
|
+
continue;
|
|
152
|
+
this.traverseType(this.checker.getTypeAtLocation(decl), ownerKey, dependencyNames, visited);
|
|
153
|
+
}
|
|
154
|
+
for (const typeParam of signature.typeParameters ?? []) {
|
|
155
|
+
const constraint = typeParam.getConstraint();
|
|
156
|
+
if (constraint) {
|
|
157
|
+
this.traverseType(constraint, ownerKey, dependencyNames, visited);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
getTypeId(type) {
|
|
162
|
+
return type.id;
|
|
163
|
+
}
|
|
164
|
+
isCandidateDeclaration(declaration) {
|
|
165
|
+
const file = path.resolve(declaration.getSourceFile().fileName);
|
|
166
|
+
return this.candidateFiles.has(file);
|
|
167
|
+
}
|
|
168
|
+
determineDeclarationKind(declaration) {
|
|
169
|
+
if (ts.isInterfaceDeclaration(declaration))
|
|
170
|
+
return 'interface';
|
|
171
|
+
if (ts.isTypeAliasDeclaration(declaration))
|
|
172
|
+
return 'type-alias';
|
|
173
|
+
if (ts.isEnumDeclaration(declaration))
|
|
174
|
+
return 'enum';
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
buildTypeKey(symbol, declaration) {
|
|
178
|
+
const relative = normalizeRelativePath(path.relative(this.rootDir, declaration.getSourceFile().fileName));
|
|
179
|
+
return `${symbol.getName()}@${relative}:${declaration.getStart()}`;
|
|
180
|
+
}
|
|
181
|
+
orderEntries() {
|
|
182
|
+
const dependencyCounts = new Map();
|
|
183
|
+
const dependents = new Map();
|
|
184
|
+
for (const entry of this.entries.values()) {
|
|
185
|
+
const filtered = Array.from(entry.dependencies).filter((dep) => this.entries.has(dep));
|
|
186
|
+
dependencyCounts.set(entry.key, filtered.length);
|
|
187
|
+
for (const dep of filtered) {
|
|
188
|
+
const parents = dependents.get(dep) ?? new Set();
|
|
189
|
+
parents.add(entry.key);
|
|
190
|
+
dependents.set(dep, parents);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const compare = (a, b) => {
|
|
194
|
+
if (a.name !== b.name)
|
|
195
|
+
return a.name.localeCompare(b.name);
|
|
196
|
+
return a.sourcePath.localeCompare(b.sourcePath);
|
|
197
|
+
};
|
|
198
|
+
const available = Array.from(this.entries.values()).filter((entry) => (dependencyCounts.get(entry.key) ?? 0) === 0);
|
|
199
|
+
available.sort(compare);
|
|
200
|
+
const ordered = [];
|
|
201
|
+
const processed = new Set();
|
|
202
|
+
while (available.length > 0) {
|
|
203
|
+
const current = available.shift();
|
|
204
|
+
ordered.push(current);
|
|
205
|
+
processed.add(current.key);
|
|
206
|
+
const children = dependents.get(current.key);
|
|
207
|
+
if (children) {
|
|
208
|
+
for (const childKey of Array.from(children)) {
|
|
209
|
+
const count = (dependencyCounts.get(childKey) ?? 0) - 1;
|
|
210
|
+
dependencyCounts.set(childKey, count);
|
|
211
|
+
if (count <= 0 && !processed.has(childKey)) {
|
|
212
|
+
const child = this.entries.get(childKey);
|
|
213
|
+
if (child) {
|
|
214
|
+
available.push(child);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
dependents.delete(current.key);
|
|
219
|
+
}
|
|
220
|
+
available.sort(compare);
|
|
221
|
+
}
|
|
222
|
+
if (ordered.length < this.entries.size) {
|
|
223
|
+
const remaining = Array.from(this.entries.values()).filter((entry) => !processed.has(entry.key));
|
|
224
|
+
remaining.sort(compare);
|
|
225
|
+
ordered.push(...remaining);
|
|
226
|
+
}
|
|
227
|
+
return ordered;
|
|
228
|
+
}
|
|
229
|
+
computeDepths() {
|
|
230
|
+
const surfaceKeys = new Set();
|
|
231
|
+
for (const entry of this.entries.values()) {
|
|
232
|
+
if (entry.isSurface)
|
|
233
|
+
surfaceKeys.add(entry.key);
|
|
234
|
+
}
|
|
235
|
+
for (const entry of this.entries.values()) {
|
|
236
|
+
entry.depth = undefined;
|
|
237
|
+
}
|
|
238
|
+
const queue = [];
|
|
239
|
+
for (const key of surfaceKeys) {
|
|
240
|
+
const entry = this.entries.get(key);
|
|
241
|
+
if (entry) {
|
|
242
|
+
entry.depth = 0;
|
|
243
|
+
queue.push(key);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
while (queue.length) {
|
|
247
|
+
const parentKey = queue.shift();
|
|
248
|
+
const parent = this.entries.get(parentKey);
|
|
249
|
+
if (!parent)
|
|
250
|
+
continue;
|
|
251
|
+
for (const dependencyKey of parent.dependencies) {
|
|
252
|
+
const child = this.entries.get(dependencyKey);
|
|
253
|
+
if (!child)
|
|
254
|
+
continue;
|
|
255
|
+
const depthCandidate = (parent.depth ?? 0) + 1;
|
|
256
|
+
if (child.depth === undefined || depthCandidate < child.depth) {
|
|
257
|
+
child.depth = depthCandidate;
|
|
258
|
+
queue.push(child.key);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.depthDirty = false;
|
|
263
|
+
}
|
|
264
|
+
ensureDepths() {
|
|
265
|
+
if (this.depthDirty) {
|
|
266
|
+
this.computeDepths();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
getDependencyCandidates() {
|
|
270
|
+
this.ensureDepths();
|
|
271
|
+
const candidates = Array.from(this.entries.values()).filter((entry) => !entry.isSurface);
|
|
272
|
+
candidates.sort((a, b) => {
|
|
273
|
+
const depthDiff = (b.depth ?? 0) - (a.depth ?? 0);
|
|
274
|
+
if (depthDiff !== 0)
|
|
275
|
+
return depthDiff;
|
|
276
|
+
if (a.name !== b.name)
|
|
277
|
+
return a.name.localeCompare(b.name);
|
|
278
|
+
return a.sourcePath.localeCompare(b.sourcePath);
|
|
279
|
+
});
|
|
280
|
+
return candidates;
|
|
281
|
+
}
|
|
282
|
+
sortedArray(set) {
|
|
283
|
+
return Array.from(set).sort();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const ROBOT_PACK_SYMBOL_KINDS = [
|
|
2
|
+
'function',
|
|
3
|
+
'class',
|
|
4
|
+
'interface',
|
|
5
|
+
'type-alias',
|
|
6
|
+
'enum',
|
|
7
|
+
'const',
|
|
8
|
+
];
|
|
9
|
+
export const ROBOT_PACK_DEFAULT_SETTINGS = {
|
|
10
|
+
rootDir: '',
|
|
11
|
+
scopeDir: '',
|
|
12
|
+
reportMethod: 'editor',
|
|
13
|
+
includeGlobs: ['src/**/*.{ts,tsx}'],
|
|
14
|
+
excludeGlobs: [
|
|
15
|
+
'**/*.test.*',
|
|
16
|
+
'**/*.spec.*',
|
|
17
|
+
'**/__tests__/**',
|
|
18
|
+
'**/dist/**',
|
|
19
|
+
'**/build/**',
|
|
20
|
+
'**/*.d.ts',
|
|
21
|
+
],
|
|
22
|
+
entrypoints: ['src/index.ts', 'src/**/index.ts'],
|
|
23
|
+
exportMode: 'all-files',
|
|
24
|
+
visibility: 'all',
|
|
25
|
+
includeKinds: [...ROBOT_PACK_SYMBOL_KINDS],
|
|
26
|
+
closure: 'surface+deps',
|
|
27
|
+
maxExemplars: 8,
|
|
28
|
+
tokenBudget: undefined,
|
|
29
|
+
preferTypeSurface: true,
|
|
30
|
+
exemplarHeuristics: {
|
|
31
|
+
usage: 6,
|
|
32
|
+
boundary: 1.2,
|
|
33
|
+
flow: 1.3,
|
|
34
|
+
clarity: 0.8,
|
|
35
|
+
redundancy: 0.6,
|
|
36
|
+
tau: 280,
|
|
37
|
+
},
|
|
38
|
+
keepJSDocTags: [
|
|
39
|
+
'@deprecated',
|
|
40
|
+
'@throws',
|
|
41
|
+
'@pure',
|
|
42
|
+
'@sideEffects',
|
|
43
|
+
'@internal',
|
|
44
|
+
'@public',
|
|
45
|
+
'@experimental',
|
|
46
|
+
'@llm',
|
|
47
|
+
],
|
|
48
|
+
};
|