@fuzdev/fuz_ui 0.175.0 → 0.176.1

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.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/dist/Alert.svelte +2 -0
  3. package/dist/Alert.svelte.d.ts.map +1 -1
  4. package/dist/ApiModule.svelte +5 -6
  5. package/dist/ApiModule.svelte.d.ts.map +1 -1
  6. package/dist/DeclarationDetail.svelte +2 -1
  7. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  8. package/dist/Details.svelte +2 -0
  9. package/dist/Details.svelte.d.ts.map +1 -1
  10. package/dist/Themed.svelte +2 -0
  11. package/dist/Themed.svelte.d.ts.map +1 -1
  12. package/dist/analysis_context.d.ts +195 -0
  13. package/dist/analysis_context.d.ts.map +1 -0
  14. package/dist/analysis_context.js +134 -0
  15. package/dist/library_analysis.d.ts +112 -0
  16. package/dist/library_analysis.d.ts.map +1 -0
  17. package/dist/library_analysis.js +106 -0
  18. package/dist/library_gen.d.ts +88 -5
  19. package/dist/library_gen.d.ts.map +1 -1
  20. package/dist/library_gen.js +163 -69
  21. package/dist/library_gen_helpers.d.ts +81 -72
  22. package/dist/library_gen_helpers.d.ts.map +1 -1
  23. package/dist/library_gen_helpers.js +115 -156
  24. package/dist/library_gen_output.d.ts +34 -0
  25. package/dist/library_gen_output.d.ts.map +1 -0
  26. package/dist/library_gen_output.js +40 -0
  27. package/dist/mdz.d.ts +3 -0
  28. package/dist/mdz.d.ts.map +1 -1
  29. package/dist/mdz.js +12 -3
  30. package/dist/module_helpers.d.ts +246 -24
  31. package/dist/module_helpers.d.ts.map +1 -1
  32. package/dist/module_helpers.js +250 -42
  33. package/dist/svelte_helpers.d.ts +65 -10
  34. package/dist/svelte_helpers.d.ts.map +1 -1
  35. package/dist/svelte_helpers.js +171 -49
  36. package/dist/ts_helpers.d.ts +132 -61
  37. package/dist/ts_helpers.d.ts.map +1 -1
  38. package/dist/ts_helpers.js +423 -282
  39. package/dist/tsdoc_helpers.d.ts +11 -0
  40. package/dist/tsdoc_helpers.d.ts.map +1 -1
  41. package/dist/tsdoc_helpers.js +22 -47
  42. package/dist/tsdoc_mdz.d.ts +36 -0
  43. package/dist/tsdoc_mdz.d.ts.map +1 -0
  44. package/dist/tsdoc_mdz.js +56 -0
  45. package/dist/vite_plugin_library_well_known.js +5 -5
  46. package/package.json +11 -6
  47. package/src/lib/analysis_context.ts +250 -0
  48. package/src/lib/library_analysis.ts +168 -0
  49. package/src/lib/library_gen.ts +247 -84
  50. package/src/lib/library_gen_helpers.ts +148 -215
  51. package/src/lib/library_gen_output.ts +63 -0
  52. package/src/lib/mdz.ts +13 -4
  53. package/src/lib/module_helpers.ts +392 -47
  54. package/src/lib/svelte_helpers.ts +291 -55
  55. package/src/lib/ts_helpers.ts +538 -338
  56. package/src/lib/tsdoc_helpers.ts +24 -49
  57. package/src/lib/tsdoc_mdz.ts +62 -0
  58. package/src/lib/vite_plugin_library_well_known.ts +5 -5
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Library source analysis - unified entry point and shared types.
3
+ *
4
+ * Provides a single function for analyzing TypeScript and Svelte source files,
5
+ * dispatching to the appropriate domain-specific analyzer.
6
+ *
7
+ * This module also exports shared types used by both analyzers:
8
+ * - `DeclarationAnalysis` - A declaration with its nodocs flag
9
+ * - `ReExportInfo` - Information about a same-name re-export
10
+ * - `ModuleAnalysis` - Result of analyzing a module (unified structure)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import {library_analyze_module} from '@fuzdev/fuz_ui/library_analysis.js';
15
+ * import {ts_create_program} from '@fuzdev/fuz_ui/ts_helpers.js';
16
+ * import {module_create_source_options} from '@fuzdev/fuz_ui/module_helpers.js';
17
+ * import {AnalysisContext} from '@fuzdev/fuz_ui/analysis_context.js';
18
+ *
19
+ * const {program} = ts_create_program({root: './my-project'});
20
+ * const ctx = new AnalysisContext();
21
+ * const options = module_create_source_options('/my-project');
22
+ *
23
+ * const result = library_analyze_module(
24
+ * {id: '/my-project/src/lib/file.ts', content: '...'},
25
+ * program,
26
+ * options,
27
+ * ctx,
28
+ * );
29
+ *
30
+ * if (result) {
31
+ * // Filter out @nodocs declarations
32
+ * const declarations = result.declarations
33
+ * .filter(d => !d.nodocs)
34
+ * .map(d => d.declaration);
35
+ * console.log('Declarations:', declarations);
36
+ * }
37
+ * ```
38
+ *
39
+ * @see ts_helpers.ts for TypeScript-specific analysis
40
+ * @see svelte_helpers.ts for Svelte component analysis
41
+ * @see module_helpers.ts for path utilities and SourceFileInfo
42
+ *
43
+ * @module
44
+ */
45
+
46
+ import ts from 'typescript';
47
+ import type {Logger} from '@fuzdev/fuz_util/log.js';
48
+ import type {DeclarationJson} from '@fuzdev/fuz_util/source_json.js';
49
+
50
+ import {ts_analyze_module} from './ts_helpers.js';
51
+ import {svelte_analyze_module} from './svelte_helpers.js';
52
+ import {
53
+ type SourceFileInfo,
54
+ type ModuleSourceOptions,
55
+ module_extract_path,
56
+ } from './module_helpers.js';
57
+ import type {AnalysisContext} from './analysis_context.js';
58
+
59
+ /**
60
+ * Result of analyzing a single declaration.
61
+ * Used by both TypeScript and Svelte analyzers for uniform handling.
62
+ */
63
+ export interface DeclarationAnalysis {
64
+ /** The analyzed declaration metadata. */
65
+ declaration: DeclarationJson;
66
+ /** Whether the declaration is marked @nodocs (should be excluded from documentation). */
67
+ nodocs: boolean;
68
+ }
69
+
70
+ /**
71
+ * Information about a same-name re-export.
72
+ * Used for post-processing to build `also_exported_from` arrays.
73
+ */
74
+ export interface ReExportInfo {
75
+ /** Name of the re-exported declaration. */
76
+ name: string;
77
+ /** Module path (relative to src/lib) where the declaration is originally declared. */
78
+ original_module: string;
79
+ }
80
+
81
+ /**
82
+ * Result of analyzing a module (TypeScript or Svelte).
83
+ * Both analyzers return this same structure for uniform handling.
84
+ */
85
+ export interface ModuleAnalysis {
86
+ /** Module path relative to source root. */
87
+ path: string;
88
+ /** Module-level documentation comment. */
89
+ module_comment?: string;
90
+ /** All declarations with nodocs flags - consumer filters based on policy. */
91
+ declarations: Array<DeclarationAnalysis>;
92
+ /** Dependencies (other source modules this module imports). Empty if none. */
93
+ dependencies: Array<string>;
94
+ /** Dependents (other source modules that import this module). Empty if none. */
95
+ dependents: Array<string>;
96
+ /** Star exports (`export * from './module'`). Empty for Svelte components. */
97
+ star_exports: Array<string>;
98
+ /** Re-exports discovered during analysis. Empty for Svelte components. */
99
+ re_exports: Array<ReExportInfo>;
100
+ }
101
+
102
+ /**
103
+ * Analyze a source file and extract module metadata.
104
+ *
105
+ * Unified entry point that dispatches to the appropriate analyzer based on file type:
106
+ * - TypeScript/JavaScript files → `ts_analyze_module`
107
+ * - Svelte components → `svelte_analyze_module`
108
+ *
109
+ * Returns raw analysis data including `nodocs` flags on declarations.
110
+ * Consumer is responsible for filtering based on their policy.
111
+ *
112
+ * This function can be called incrementally - consumers may cache results and
113
+ * only re-analyze changed files. The TypeScript program should include all files
114
+ * for accurate type resolution, but only changed files need re-analysis.
115
+ *
116
+ * @param source_file The source file info with content and optional dependency data
117
+ * @param program TypeScript program (used for type checking and source file lookup)
118
+ * @param options Module source options for path extraction
119
+ * @param ctx Analysis context for collecting diagnostics
120
+ * @param log Optional logger for warnings
121
+ * @returns Module metadata and re-exports, or undefined if source file not found in program
122
+ */
123
+ export const library_analyze_module = (
124
+ source_file: SourceFileInfo,
125
+ program: ts.Program,
126
+ options: ModuleSourceOptions,
127
+ ctx: AnalysisContext,
128
+ log?: Logger,
129
+ ): ModuleAnalysis | undefined => {
130
+ const checker = program.getTypeChecker();
131
+ const module_path = module_extract_path(source_file.id, options);
132
+ const analyzer_type = options.get_analyzer(source_file.id);
133
+
134
+ if (analyzer_type === 'svelte') {
135
+ return svelte_analyze_module(source_file, module_path, checker, options, ctx);
136
+ }
137
+
138
+ if (analyzer_type === 'typescript') {
139
+ const ts_source_file = program.getSourceFile(source_file.id);
140
+ if (!ts_source_file) {
141
+ ctx.add({
142
+ kind: 'module_skipped',
143
+ file: module_path,
144
+ line: null,
145
+ column: null,
146
+ message: `Could not get source file from program: ${source_file.id}`,
147
+ severity: 'warning',
148
+ reason: 'not_in_program',
149
+ });
150
+ log?.warn(`Could not get source file from program: ${source_file.id}`);
151
+ return undefined;
152
+ }
153
+ return ts_analyze_module(source_file, ts_source_file, module_path, checker, options, ctx);
154
+ }
155
+
156
+ // analyzer_type is null - skip this file
157
+ ctx.add({
158
+ kind: 'module_skipped',
159
+ file: module_path,
160
+ line: null,
161
+ column: null,
162
+ message: `No analyzer for file type: ${source_file.id}`,
163
+ severity: 'warning',
164
+ reason: 'no_analyzer',
165
+ });
166
+ log?.warn(`No analyzer for file: ${source_file.id}`);
167
+ return undefined;
168
+ };
@@ -11,27 +11,183 @@
11
11
  * - Dependency graphs
12
12
  * - Svelte component props
13
13
  *
14
+ * This file contains Gro-specific integration. The actual analysis logic is in
15
+ * build-tool agnostic helpers that work with `SourceFileInfo`.
16
+ *
14
17
  * @see @fuzdev/fuz_util/source_json.js for type definitions
15
- * @see src/lib/library_gen_helpers.ts for buildtime-only helpers
16
- * @see src/lib/tsdoc_helpers.ts for JSDoc/TSDoc parsing
17
- * @see src/lib/ts_helpers.ts for TypeScript analysis
18
- * @see src/lib/svelte_helpers.ts for Svelte component analysis
18
+ * @see `library_analysis.ts` for the unified analysis entry point
19
+ * @see `library_gen_helpers.ts` for pipeline orchestration helpers
20
+ * @see `tsdoc_helpers.ts` for JSDoc/TSDoc parsing
21
+ * @see `ts_helpers.ts` for TypeScript analysis
22
+ * @see `svelte_helpers.ts` for Svelte component analysis
23
+ *
24
+ * @module
19
25
  */
20
26
 
21
27
  import type {Gen} from '@ryanatkn/gro';
22
28
  import {package_json_load} from '@ryanatkn/gro/package_json.js';
23
- import type {SourceJson} from '@fuzdev/fuz_util/source_json.js';
29
+ import type {Disknode} from '@ryanatkn/gro/disknode.js';
30
+ import type {SourceJson, ModuleJson} from '@fuzdev/fuz_util/source_json.js';
24
31
 
25
- import {ts_create_program, type ReExportInfo} from './ts_helpers.ts';
26
- import {module_extract_path, module_is_svelte} from './module_helpers.ts';
32
+ import {ts_create_program} from './ts_helpers.js';
27
33
  import {
28
- library_gen_collect_source_files,
29
- library_gen_sort_modules,
30
- library_gen_validate_no_duplicates,
31
- library_gen_generate_json,
32
- library_gen_analyze_svelte_file,
33
- library_gen_analyze_typescript_file,
34
- } from './library_gen_helpers.ts';
34
+ type SourceFileInfo,
35
+ type ModuleSourceOptions,
36
+ type ModuleSourcePartial,
37
+ module_create_source_options,
38
+ module_validate_source_options,
39
+ module_is_source,
40
+ module_get_source_root,
41
+ } from './module_helpers.js';
42
+ import {library_analyze_module} from './library_analysis.js';
43
+ import {
44
+ library_sort_modules,
45
+ library_find_duplicates,
46
+ library_merge_re_exports,
47
+ type CollectedReExport,
48
+ type DuplicateInfo,
49
+ } from './library_gen_helpers.js';
50
+ import {library_generate_json} from './library_gen_output.js';
51
+ import {AnalysisContext, format_diagnostic} from './analysis_context.js';
52
+
53
+ /** Options for library generation. */
54
+ export interface LibraryGenOptions {
55
+ /**
56
+ * Module source options for filtering and path extraction.
57
+ *
58
+ * Can provide full `ModuleSourceOptions` or partial options that will be
59
+ * merged with defaults. The `project_root` is automatically set to
60
+ * `process.cwd()` if not provided.
61
+ */
62
+ source?: ModuleSourceOptions | Partial<ModuleSourcePartial>;
63
+ /**
64
+ * Callback invoked when duplicate declaration names are found.
65
+ *
66
+ * Consumers decide how to handle duplicates: throw, warn, or ignore.
67
+ * Use `library_gen_throw_on_duplicates` for strict flat namespace enforcement.
68
+ *
69
+ * @example
70
+ * // Throw on duplicates (strict flat namespace)
71
+ * library_gen({ on_duplicates: library_gen_throw_on_duplicates });
72
+ *
73
+ * // Warn but continue
74
+ * library_gen({
75
+ * on_duplicates: (dupes, log) => {
76
+ * for (const [name, locs] of dupes) {
77
+ * log.warn(`Duplicate: ${name} in ${locs.map(l => l.module).join(', ')}`);
78
+ * }
79
+ * }
80
+ * });
81
+ */
82
+ on_duplicates?: OnDuplicatesCallback;
83
+ }
84
+
85
+ /**
86
+ * Callback for handling duplicate declaration names.
87
+ *
88
+ * @param duplicates Map of declaration names to their occurrences across modules
89
+ * @param log Logger for reporting
90
+ */
91
+ export type OnDuplicatesCallback = (
92
+ duplicates: Map<string, Array<DuplicateInfo>>,
93
+ log: {error: (...args: Array<unknown>) => void},
94
+ ) => void;
95
+
96
+ /**
97
+ * Strict duplicate handler that throws on any duplicate declaration names.
98
+ *
99
+ * Use this callback with `library_gen({ on_duplicates: library_gen_throw_on_duplicates })`
100
+ * to enforce a flat namespace where all declaration names must be unique.
101
+ *
102
+ * @throws Error if any duplicate declaration names are found
103
+ */
104
+ export const library_gen_throw_on_duplicates: OnDuplicatesCallback = (duplicates, log) => {
105
+ if (duplicates.size === 0) return;
106
+
107
+ log.error('Duplicate declaration names detected in flat namespace:');
108
+ for (const [name, occurrences] of duplicates) {
109
+ log.error(` "${name}" found in:`);
110
+ for (const {declaration, module} of occurrences) {
111
+ const line_info = declaration.source_line !== undefined ? `:${declaration.source_line}` : '';
112
+ log.error(` - ${module}${line_info} (${declaration.kind})`);
113
+ }
114
+ }
115
+ throw new Error(
116
+ `Found ${duplicates.size} duplicate declaration name${duplicates.size === 1 ? '' : 's'} across modules. ` +
117
+ 'The flat namespace requires unique names. To resolve: ' +
118
+ '(1) rename one of the conflicting declarations, or ' +
119
+ '(2) add /** @nodocs */ to exclude from documentation. ' +
120
+ 'See CLAUDE.md "Declaration namespacing" section for details.',
121
+ );
122
+ };
123
+
124
+ /**
125
+ * Convert Gro's Disknode to the build-tool agnostic SourceFileInfo interface.
126
+ *
127
+ * Use this when you want to analyze files using Gro's filer directly.
128
+ *
129
+ * @throws Error if disknode has no content (should be loaded by Gro filer)
130
+ */
131
+ export const source_file_from_disknode = (disknode: Disknode): SourceFileInfo => {
132
+ if (disknode.contents == null) {
133
+ throw new Error(
134
+ `Source file has no content: ${disknode.id} (ensure Gro filer loads file contents)`,
135
+ );
136
+ }
137
+ return {
138
+ id: disknode.id,
139
+ content: disknode.contents,
140
+ dependencies: [...disknode.dependencies.keys()],
141
+ dependents: [...disknode.dependents.keys()],
142
+ };
143
+ };
144
+
145
+ // TODO more generic helpers
146
+ /**
147
+ * Collect source files from Gro disknodes, filtering BEFORE conversion to SourceFileInfo.
148
+ *
149
+ * This avoids errors from files outside source directories (like test fixtures that may
150
+ * have malformed paths or missing content). The filtering uses `module_is_source` which
151
+ * checks `source_paths` to only include files in configured source directories.
152
+ *
153
+ * @param disknodes Iterator of Gro disknodes from filer
154
+ * @param options Module source options for filtering
155
+ * @param log Optional logger for status messages
156
+ */
157
+ export const library_collect_source_files_from_disknodes = (
158
+ disknodes: Iterable<Disknode>,
159
+ options: ModuleSourceOptions,
160
+ log?: {info: (...args: Array<unknown>) => void; warn: (...args: Array<unknown>) => void},
161
+ ): Array<SourceFileInfo> => {
162
+ // Validate options early to fail fast on misconfiguration
163
+ module_validate_source_options(options);
164
+
165
+ const all_disknodes = Array.from(disknodes);
166
+ log?.info(`received ${all_disknodes.length} files total from filer`);
167
+
168
+ const source_files: Array<SourceFileInfo> = [];
169
+ for (const disknode of all_disknodes) {
170
+ // Filter by source_paths BEFORE trying to convert
171
+ // This avoids errors from test fixtures or other non-source files
172
+ if (!module_is_source(disknode.id, options)) {
173
+ continue;
174
+ }
175
+ source_files.push(source_file_from_disknode(disknode));
176
+ }
177
+
178
+ log?.info(`found ${source_files.length} source files to analyze`);
179
+
180
+ if (source_files.length === 0) {
181
+ const effective_root = module_get_source_root(options);
182
+ log?.warn(`No source files found in ${effective_root} - generating empty library metadata`);
183
+ return [];
184
+ }
185
+
186
+ // Sort for deterministic output (stable alphabetical module ordering)
187
+ source_files.sort((a, b) => a.id.localeCompare(b.id));
188
+
189
+ return source_files;
190
+ };
35
191
 
36
192
  /**
37
193
  * Creates a Gen object for generating library metadata with full TypeScript analysis.
@@ -43,12 +199,24 @@ import {
43
199
  *
44
200
  * export const gen = library_gen();
45
201
  * ```
202
+ *
203
+ * @param options Optional generation options
46
204
  */
47
- export const library_gen = (): Gen => {
205
+ export const library_gen = (options?: LibraryGenOptions): Gen => {
48
206
  return {
49
207
  generate: async ({log, filer}) => {
50
208
  log.info('generating library metadata with full TypeScript analysis...');
51
209
 
210
+ // Build source options with project_root from cwd
211
+ const source_options: ModuleSourceOptions =
212
+ options?.source && 'project_root' in options.source
213
+ ? options.source
214
+ : module_create_source_options(process.cwd(), options?.source);
215
+
216
+ // Validate options early to fail fast on misconfiguration
217
+ // (before expensive operations like program creation)
218
+ module_validate_source_options(source_options);
219
+
52
220
  // Ensure filer is initialized
53
221
  await filer.init();
54
222
 
@@ -56,100 +224,95 @@ export const library_gen = (): Gen => {
56
224
  const package_json = await package_json_load();
57
225
 
58
226
  // Create TypeScript program
59
- const program = ts_create_program(log);
60
- if (!program) {
61
- throw new Error('Failed to create TypeScript program - cannot generate library metadata');
62
- }
227
+ const {program} = ts_create_program(undefined, log);
63
228
 
64
- const checker = program.getTypeChecker();
229
+ // Create analysis context for collecting diagnostics
230
+ const ctx = new AnalysisContext();
65
231
 
66
- // Collect source files from filer
67
- const source_disknodes = library_gen_collect_source_files(filer.files, log);
232
+ // Collect source files, filtering by source_options BEFORE converting to SourceFileInfo
233
+ // This avoids errors from files outside source directories (like test fixtures)
234
+ const source_files = library_collect_source_files_from_disknodes(
235
+ filer.files.values(),
236
+ source_options,
237
+ log,
238
+ );
239
+
240
+ // Collect modules (declared before source_json to include directly)
241
+ const modules: Array<ModuleJson> = [];
68
242
 
69
243
  // Build source_json with array-based modules
70
244
  // Phase 1: Analyze all modules and collect re-exports
71
245
  const source_json: SourceJson = {
72
246
  name: package_json.name,
73
247
  version: package_json.version,
74
- modules: [],
248
+ modules,
75
249
  };
76
250
 
77
- // Collect all re-exports: Map<declaration_name, Set<re_exporting_module_path>>
78
- // The Set tracks which modules re-export each declaration
79
- const all_re_exports: Array<{re_exporting_module: string; re_export: ReExportInfo}> = [];
80
-
81
- for (const disknode of source_disknodes) {
82
- const source_id = disknode.id;
83
- const module_path = module_extract_path(source_id);
84
- const is_svelte = module_is_svelte(module_path);
85
-
86
- // Handle Svelte files separately (before trying to get TypeScript source file)
87
- if (is_svelte) {
88
- const mod = library_gen_analyze_svelte_file(disknode, module_path, checker);
89
- source_json.modules!.push(mod);
90
- } else {
91
- // For TypeScript/JS files, get the source file from the program
92
- const source_file = program.getSourceFile(source_id);
93
- if (!source_file) {
94
- log.warn(`Could not get source file: ${source_id}`);
95
- continue;
96
- }
251
+ // Collect re-exports for phase 2 merging
252
+ // See library_merge_re_exports for the two-phase resolution strategy
253
+ const collected_re_exports: Array<CollectedReExport> = [];
97
254
 
98
- // May throw, which we want to see
99
- const {module: mod, re_exports} = library_gen_analyze_typescript_file(
100
- disknode,
101
- source_file,
102
- module_path,
103
- checker,
104
- );
105
- source_json.modules!.push(mod);
106
-
107
- // Collect re-exports for post-processing
108
- for (const re_export of re_exports) {
109
- all_re_exports.push({re_exporting_module: module_path, re_export});
110
- }
255
+ for (const source_file of source_files) {
256
+ // Use unified analyzer that dispatches based on file type
257
+ const result = library_analyze_module(source_file, program, source_options, ctx, log);
258
+ if (!result) continue;
259
+
260
+ // Build ModuleJson, filtering out @nodocs declarations
261
+ const module: ModuleJson = {
262
+ path: result.path,
263
+ declarations: result.declarations.filter((d) => !d.nodocs).map((d) => d.declaration),
264
+ };
265
+ if (result.module_comment) module.module_comment = result.module_comment;
266
+ if (result.dependencies.length > 0) module.dependencies = result.dependencies;
267
+ if (result.dependents.length > 0) module.dependents = result.dependents;
268
+ if (result.star_exports.length > 0) module.star_exports = result.star_exports;
269
+
270
+ modules.push(module);
271
+
272
+ // Collect re-exports for phase 2 merging
273
+ for (const re_export of result.re_exports) {
274
+ collected_re_exports.push({re_exporting_module: result.path, re_export});
111
275
  }
112
276
  }
113
277
 
114
278
  // Phase 2: Build also_exported_from arrays from re-export data
115
- // Group re-exports by original module and declaration name
116
- const re_export_map: Map<string, Map<string, Array<string>>> = new Map();
117
- for (const {re_exporting_module, re_export} of all_re_exports) {
118
- const {name, original_module} = re_export;
119
- if (!re_export_map.has(original_module)) {
120
- re_export_map.set(original_module, new Map());
121
- }
122
- const module_map = re_export_map.get(original_module)!;
123
- if (!module_map.has(name)) {
124
- module_map.set(name, []);
279
+ library_merge_re_exports(source_json, collected_re_exports);
280
+
281
+ // Sort modules alphabetically for deterministic output and cleaner diffs
282
+ source_json.modules = library_sort_modules(modules);
283
+
284
+ // Check for duplicate declaration names and invoke callback if provided
285
+ if (options?.on_duplicates) {
286
+ const duplicates = library_find_duplicates(source_json);
287
+ if (duplicates.size > 0) {
288
+ options.on_duplicates(duplicates, log);
125
289
  }
126
- module_map.get(name)!.push(re_exporting_module);
127
290
  }
128
291
 
129
- // Merge into original declarations
130
- for (const mod of source_json.modules ?? []) {
131
- const module_re_exports = re_export_map.get(mod.path);
132
- if (!module_re_exports) continue;
292
+ // Report any analysis diagnostics
293
+ if (ctx.diagnostics.length > 0) {
294
+ const errors = ctx.errors();
295
+ const warnings = ctx.warnings();
296
+ const format_options = {strip_base: process.cwd()};
133
297
 
134
- for (const declaration of mod.declarations ?? []) {
135
- const re_exporters = module_re_exports.get(declaration.name);
136
- if (re_exporters?.length) {
137
- declaration.also_exported_from = re_exporters.sort();
298
+ if (errors.length > 0) {
299
+ log.error(`Analysis completed with ${errors.length} error(s):`);
300
+ for (const diagnostic of errors) {
301
+ log.error(` ${format_diagnostic(diagnostic, format_options)}`);
138
302
  }
139
303
  }
140
- }
141
-
142
- // Sort modules alphabetically for deterministic output and cleaner diffs
143
- source_json.modules = source_json.modules
144
- ? library_gen_sort_modules(source_json.modules)
145
- : undefined;
146
304
 
147
- // Validate no duplicate declaration names across modules
148
- library_gen_validate_no_duplicates(source_json, log);
305
+ if (warnings.length > 0) {
306
+ log.warn(`Analysis completed with ${warnings.length} warning(s):`);
307
+ for (const diagnostic of warnings) {
308
+ log.warn(` ${format_diagnostic(diagnostic, format_options)}`);
309
+ }
310
+ }
311
+ }
149
312
 
150
313
  log.info('library metadata generation complete');
151
314
 
152
- const {json_content, ts_content} = library_gen_generate_json(package_json, source_json);
315
+ const {json_content, ts_content} = library_generate_json(package_json, source_json);
153
316
 
154
317
  // Return array of files:
155
318
  // - library.json (default from .gen.json.ts naming)