@happyvertical/smrt-scanner 0.51.31 → 0.51.33

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.
@@ -0,0 +1,266 @@
1
+ /**
2
+ * OXC Scanner
3
+ *
4
+ * High-level scanner that orchestrates OXC parsing and inheritance resolution.
5
+ * Provides a simple API for scanning TypeScript files for SMRT classes.
6
+ */
7
+ import type { ExternalManifest, OxcScannerOptions, ResolvedClassDefinition, ScanResults } from './types.js';
8
+ /**
9
+ * Prunes that always apply, on top of whatever `exclude` a caller passes.
10
+ *
11
+ * `exclude` replaces {@link DEFAULT_EXCLUDE} wholesale, so every caller that
12
+ * narrowed the excludes also silently reopened `node_modules` — and installed
13
+ * dependencies are never a project's own `@smrt()` sources. Dot directories are
14
+ * generated or tool state (`.git`, `.svelte-kit`, `.turbo`, `.vercel`, agent
15
+ * scratch) and are pruned for the same reason: nothing authored lives there.
16
+ * `**\/.*` keeps hidden FILES out of the result too, so turning `dot` on to
17
+ * make these prunes work does not quietly widen what gets scanned.
18
+ *
19
+ * These are load-bearing for termination, not just for speed. See
20
+ * {@link OxcScanner.discoverFiles}.
21
+ */
22
+ /**
23
+ * High-performance TypeScript scanner that discovers `@smrt()`-decorated
24
+ * classes in a project's source files.
25
+ *
26
+ * Orchestrates the two-phase scan pipeline:
27
+ * 1. **Phase 1 — Parse** (`scan()`): uses OXC (Rust) to parse TypeScript files
28
+ * in parallel and extract raw class, field, method, and decorator metadata.
29
+ * 2. **Phase 2 — Resolve** (`resolve()`): walks inheritance chains, detects STI
30
+ * hierarchies, and merges fields from ancestor classes.
31
+ *
32
+ * The common path is {@link scanAndResolve} which runs both phases in sequence.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { OxcScanner } from '@happyvertical/smrt-scanner';
37
+ *
38
+ * const scanner = new OxcScanner({
39
+ * cwd: process.cwd(),
40
+ * include: ['src/**\/*.ts'],
41
+ * exclude: ['**\/*.test.ts'],
42
+ * });
43
+ *
44
+ * const { results, resolved } = await scanner.scanAndResolve();
45
+ * console.log(`Found ${resolved.length} SMRT classes in ${results.fileCount} files`);
46
+ * ```
47
+ *
48
+ * @see {@link OxcScannerOptions} for all available configuration options.
49
+ * @see {@link scanDirectory} for a one-liner convenience wrapper.
50
+ */
51
+ export declare class OxcScanner {
52
+ private options;
53
+ private resolver;
54
+ private scanResults;
55
+ /**
56
+ * Create a new `OxcScanner` with the given options.
57
+ *
58
+ * All options are optional. By default the scanner targets every `.ts` and
59
+ * `.tsx` file under `process.cwd()`, excluding `node_modules`, `dist`,
60
+ * `build`, declaration files, and test files.
61
+ *
62
+ * @param options - Scanner configuration. See {@link OxcScannerOptions}.
63
+ */
64
+ constructor(options?: OxcScannerOptions);
65
+ /**
66
+ * Phase 1 — Discover and parse TypeScript files using OXC.
67
+ *
68
+ * Uses `fast-glob` to enumerate matching files and then parses them in
69
+ * parallel with OXC (Rust). The raw class definitions are registered with
70
+ * the internal {@link InheritanceResolver} for use in the subsequent
71
+ * {@link resolve} call.
72
+ *
73
+ * @returns A {@link ScanResults} object containing all classes found, any
74
+ * parse errors, accumulated type aliases, SMRT import metadata, and
75
+ * aggregate timing information.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const scanner = new OxcScanner({ cwd: '/project' });
80
+ * const results = await scanner.scan();
81
+ * console.log(`Parsed ${results.fileCount} files in ${results.totalParseTimeMs.toFixed(1)}ms`);
82
+ * ```
83
+ */
84
+ scan(): Promise<ScanResults>;
85
+ /**
86
+ * Phase 2 — Resolve inheritance chains for all scanned classes.
87
+ *
88
+ * Must be called after {@link scan}. Walks each class's extends chain,
89
+ * detects STI hierarchies, merges ancestor fields for STI subclasses, and
90
+ * marks framework base classes.
91
+ *
92
+ * @returns An array of {@link ResolvedClassDefinition} objects — one for
93
+ * every class that either carries `@smrt()` or extends a framework base
94
+ * class (`SmrtObject`, `SmrtClass`, `SmrtCollection`).
95
+ *
96
+ * @throws {Error} If called before {@link scan}.
97
+ *
98
+ * @see {@link scanAndResolve} to run both phases in one call.
99
+ */
100
+ resolve(): ResolvedClassDefinition[];
101
+ /**
102
+ * Run both scan phases in a single call.
103
+ *
104
+ * Equivalent to calling `await scanner.scan()` followed by
105
+ * `scanner.resolve()`. This is the most common entry point for callers
106
+ * that want the fully-resolved manifest-ready data in one step.
107
+ *
108
+ * @returns An object with:
109
+ * - `results` — raw {@link ScanResults} from Phase 1.
110
+ * - `resolved` — array of {@link ResolvedClassDefinition} from Phase 2.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const scanner = new OxcScanner({ cwd: '/project/src' });
115
+ * const { results, resolved } = await scanner.scanAndResolve();
116
+ * // resolved is ready to pass to ManifestAdapter.toManifest()
117
+ * ```
118
+ *
119
+ * @see {@link ManifestAdapter} to convert `resolved` into a manifest JSON.
120
+ */
121
+ scanAndResolve(): Promise<{
122
+ results: ScanResults;
123
+ resolved: ResolvedClassDefinition[];
124
+ }>;
125
+ /**
126
+ * Register an external package manifest for cross-package base class resolution.
127
+ *
128
+ * When a project class extends a class defined in an installed SMRT package,
129
+ * the resolver needs access to that package's class definitions to walk the
130
+ * full inheritance chain. Call this method with each external package's
131
+ * {@link ExternalManifest} before calling {@link scan} or {@link resolve}.
132
+ *
133
+ * @param manifest - The external manifest to register, including `packageName`,
134
+ * `packageVersion`, and a `classes` map keyed by class name.
135
+ *
136
+ * @see {@link ExternalManifest}
137
+ */
138
+ addExternalManifest(manifest: ExternalManifest): void;
139
+ /**
140
+ * Scan all discovered files for @happyvertical/smrt-* imports.
141
+ * Returns a map of package name → Set of imported class names.
142
+ *
143
+ * Used for tree-shaking: only external objects that are actually imported
144
+ * in the project's source files will be included in the manifest.
145
+ *
146
+ * Must be called after scan() or as part of scanAndResolve().
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const scanner = new OxcScanner({ cwd: process.cwd() });
151
+ * await scanner.scan();
152
+ * const imports = scanner.scanSmrtImports();
153
+ * // Map { '@happyvertical/smrt-profiles' => Set { 'Person', 'Organization' } }
154
+ * ```
155
+ */
156
+ scanSmrtImports(): Map<string, Set<string>>;
157
+ /**
158
+ * Return aggregate statistics about the last scan.
159
+ *
160
+ * Can be called after {@link scan} has completed. Returns counts useful for
161
+ * diagnostics and the `--stats` CLI flag.
162
+ *
163
+ * @returns An object with:
164
+ * - `totalClasses` — total class declarations seen (including non-SMRT).
165
+ * - `smrtClasses` — classes with `@smrt()` decorator.
166
+ * - `stiClasses` — SMRT classes participating in an STI hierarchy.
167
+ * - `maxInheritanceDepth` — length of the deepest inheritance chain.
168
+ * - `fileCount` — number of files scanned.
169
+ * - `parseTimeMs` — total wall-clock parse time in milliseconds.
170
+ */
171
+ getStats(): {
172
+ totalClasses: number;
173
+ smrtClasses: number;
174
+ stiClasses: number;
175
+ maxInheritanceDepth: number;
176
+ fileCount: number;
177
+ parseTimeMs: number;
178
+ };
179
+ /**
180
+ * Discover files to scan using fast-glob.
181
+ *
182
+ * Two settings here decide whether discovery terminates at all when the
183
+ * scanner is pointed at an application root rather than a package `src/`:
184
+ *
185
+ * - `dot: true`. Without it a `**` in an ignore pattern cannot cross a
186
+ * dot segment, so `**\/node_modules/**` prunes `node_modules` at the root
187
+ * but NOT `.svelte-kit/…/node_modules` or any other `node_modules` under a
188
+ * dot directory. Those subtrees were then walked in full and every entry
189
+ * discarded — unbounded work that could never produce a match.
190
+ * - `followSymbolicLinks: false`. pnpm materializes `node_modules` as a
191
+ * symlink graph with cycles, so a link-following walk revisits the same
192
+ * real directories once per path that reaches them.
193
+ *
194
+ * Together they were enough to exhaust a 4 GB heap on a consumer app that
195
+ * installs the published packages (#2275).
196
+ *
197
+ * Patterns are rewritten relative to `cwd` first. fast-glob matches `ignore`
198
+ * in whatever space the patterns use, so an absolute pattern would hand
199
+ * `**\/.*\/**` the project's own ancestors — a checkout under `~/.worktrees`
200
+ * or `~/.cache` would then match nothing at all, silently.
201
+ */
202
+ private discoverFiles;
203
+ /**
204
+ * Find declarations in files the CLASS scan did not cover.
205
+ *
206
+ * The class `include` is routinely narrowed to where models live, but an
207
+ * intent sidecar lives beside its component. Without this pass those
208
+ * declarations would be missing from every artifact with no diagnostic — a
209
+ * silent omission, and in the shipped SvelteKit template's own layout at
210
+ * that. Files already parsed by the class scan are skipped so a declaration
211
+ * is never counted twice and cannot collide with itself.
212
+ *
213
+ * @param alreadyScanned - Absolute paths the class scan already parsed.
214
+ */
215
+ private scanDeclarationsOutsideClassGlob;
216
+ /**
217
+ * Search `.svelte` files for declarations the scanner can never read.
218
+ *
219
+ * A `.svelte` file is not a TypeScript program and is never parsed here, so
220
+ * this pass produces diagnostics only — never an emitted entry. It exists
221
+ * because the alternative is silence: an intent declared inline in a
222
+ * component simply would not appear anywhere, with nothing to explain why.
223
+ */
224
+ private scanSvelteDeclarations;
225
+ /**
226
+ * Record a declaring module as a `cwd`-relative POSIX path.
227
+ *
228
+ * Emitted entries land in checked-in artifacts, so an absolute path would
229
+ * make them machine-specific and a Windows separator would make them
230
+ * platform-specific — either one churns a snapshot that is supposed to prove
231
+ * two builds agree.
232
+ */
233
+ private relativizeSourcePath;
234
+ /**
235
+ * Parse a single file with timing
236
+ */
237
+ private parseFileWithTiming;
238
+ }
239
+ /**
240
+ * Convenience wrapper that creates an {@link OxcScanner} for `dir` and runs
241
+ * both scan phases in a single call.
242
+ *
243
+ * @param dir - Directory to scan (resolved to an absolute path automatically).
244
+ * @param options - Scanner options, excluding `cwd` which is set from `dir`.
245
+ * See {@link OxcScannerOptions}.
246
+ * @returns An object with `results` ({@link ScanResults}) and `resolved`
247
+ * ({@link ResolvedClassDefinition}[]).
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * import { scanDirectory } from '@happyvertical/smrt-scanner';
252
+ *
253
+ * const { resolved } = await scanDirectory('src/', {
254
+ * include: ['**\/*.ts'],
255
+ * exclude: ['**\/*.test.ts'],
256
+ * });
257
+ * console.log(`Found ${resolved.length} SMRT classes`);
258
+ * ```
259
+ *
260
+ * @see {@link OxcScanner} for the full class API with more control.
261
+ */
262
+ export declare function scanDirectory(dir: string, options?: Omit<OxcScannerOptions, 'cwd'>): Promise<{
263
+ results: ScanResults;
264
+ resolved: ResolvedClassDefinition[];
265
+ }>;
266
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,OAAO,KAAK,EAEV,gBAAgB,EAEhB,iBAAiB,EACjB,uBAAuB,EACvB,WAAW,EACZ,MAAM,YAAY,CAAC;AA2DpB;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,WAAW,CAA4B;IAE/C;;;;;;;;OAQG;gBACS,OAAO,GAAE,iBAAsB;IAwB3C;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA8DlC;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,uBAAuB,EAAE;IAQpC;;;;;;;;;;;;;;;;;;;OAmBG;IACG,cAAc,IAAI,OAAO,CAAC;QAC9B,OAAO,EAAE,WAAW,CAAC;QACrB,QAAQ,EAAE,uBAAuB,EAAE,CAAC;KACrC,CAAC;IAOF;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAIrD;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAwB3C;;;;;;;;;;;;;OAaG;IACH,QAAQ,IAAI;QACV,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;KACrB;IAUD;;;;;;;;;;;;;;;;;;;;;;OAsBG;YACW,aAAa;IAS3B;;;;;;;;;;;OAWG;YACW,gCAAgC;IAmC9C;;;;;;;OAOG;YACW,sBAAsB;IAoCpC;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;YACW,mBAAmB;CAIlC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAM,GAC3C,OAAO,CAAC;IACT,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACrC,CAAC,CAOD"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Source-offset helpers shared by the OXC class parser and the agent-surface
3
+ * matcher.
4
+ *
5
+ * Lives in its own module so `agent-surface.ts` can resolve a diagnostic's
6
+ * line/column without importing `oxc-parser.ts`, which imports it back.
7
+ */
8
+ /**
9
+ * Extract line/column from offset in source text.
10
+ *
11
+ * Note: oxc-parser v0.108+ removed magicString, so this is computed manually.
12
+ *
13
+ * @param sourceText - Full source text the offset indexes into.
14
+ * @param offset - 0-based character offset.
15
+ * @returns 1-based line and column, or `undefined` when the offset is out of
16
+ * range.
17
+ */
18
+ export declare function getLineColumn(sourceText: string, offset: number): {
19
+ line: number;
20
+ column: number;
21
+ } | undefined;
22
+ //# sourceMappingURL=source-location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-location.d.ts","sourceRoot":"","sources":["../src/source-location.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAmB9C"}