@fuzdev/fuz_ui 0.174.0 → 0.176.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.
Files changed (61) 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/EcosystemLinks.svelte +3 -3
  11. package/dist/EcosystemLinks.svelte.d.ts +1 -1
  12. package/dist/EcosystemLinks.svelte.d.ts.map +1 -1
  13. package/dist/Themed.svelte +2 -0
  14. package/dist/Themed.svelte.d.ts.map +1 -1
  15. package/dist/analysis_context.d.ts +195 -0
  16. package/dist/analysis_context.d.ts.map +1 -0
  17. package/dist/analysis_context.js +134 -0
  18. package/dist/library_analysis.d.ts +112 -0
  19. package/dist/library_analysis.d.ts.map +1 -0
  20. package/dist/library_analysis.js +106 -0
  21. package/dist/library_gen.d.ts +73 -5
  22. package/dist/library_gen.d.ts.map +1 -1
  23. package/dist/library_gen.js +130 -68
  24. package/dist/library_gen_helpers.d.ts +81 -72
  25. package/dist/library_gen_helpers.d.ts.map +1 -1
  26. package/dist/library_gen_helpers.js +115 -156
  27. package/dist/library_gen_output.d.ts +34 -0
  28. package/dist/library_gen_output.d.ts.map +1 -0
  29. package/dist/library_gen_output.js +40 -0
  30. package/dist/mdz.d.ts +3 -0
  31. package/dist/mdz.d.ts.map +1 -1
  32. package/dist/mdz.js +12 -3
  33. package/dist/module_helpers.d.ts +246 -24
  34. package/dist/module_helpers.d.ts.map +1 -1
  35. package/dist/module_helpers.js +250 -42
  36. package/dist/svelte_helpers.d.ts +65 -10
  37. package/dist/svelte_helpers.d.ts.map +1 -1
  38. package/dist/svelte_helpers.js +171 -49
  39. package/dist/ts_helpers.d.ts +132 -61
  40. package/dist/ts_helpers.d.ts.map +1 -1
  41. package/dist/ts_helpers.js +423 -282
  42. package/dist/tsdoc_helpers.d.ts +11 -0
  43. package/dist/tsdoc_helpers.d.ts.map +1 -1
  44. package/dist/tsdoc_helpers.js +22 -47
  45. package/dist/tsdoc_mdz.d.ts +36 -0
  46. package/dist/tsdoc_mdz.d.ts.map +1 -0
  47. package/dist/tsdoc_mdz.js +56 -0
  48. package/dist/vite_plugin_library_well_known.js +5 -5
  49. package/package.json +12 -7
  50. package/src/lib/analysis_context.ts +250 -0
  51. package/src/lib/library_analysis.ts +168 -0
  52. package/src/lib/library_gen.ts +199 -83
  53. package/src/lib/library_gen_helpers.ts +148 -215
  54. package/src/lib/library_gen_output.ts +63 -0
  55. package/src/lib/mdz.ts +13 -4
  56. package/src/lib/module_helpers.ts +392 -47
  57. package/src/lib/svelte_helpers.ts +291 -55
  58. package/src/lib/ts_helpers.ts +538 -338
  59. package/src/lib/tsdoc_helpers.ts +24 -49
  60. package/src/lib/tsdoc_mdz.ts +62 -0
  61. package/src/lib/vite_plugin_library_well_known.ts +5 -5
@@ -0,0 +1,106 @@
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 './library_analysis.js';
15
+ * import {ts_create_program} from './ts_helpers.js';
16
+ * import {module_create_source_options} from './module_helpers.js';
17
+ * import {AnalysisContext} from './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
+ import ts from 'typescript';
46
+ import { ts_analyze_module } from './ts_helpers.js';
47
+ import { svelte_analyze_module } from './svelte_helpers.js';
48
+ import { module_extract_path, } from './module_helpers.js';
49
+ /**
50
+ * Analyze a source file and extract module metadata.
51
+ *
52
+ * Unified entry point that dispatches to the appropriate analyzer based on file type:
53
+ * - TypeScript/JavaScript files → `ts_analyze_module`
54
+ * - Svelte components → `svelte_analyze_module`
55
+ *
56
+ * Returns raw analysis data including `nodocs` flags on declarations.
57
+ * Consumer is responsible for filtering based on their policy.
58
+ *
59
+ * This function can be called incrementally - consumers may cache results and
60
+ * only re-analyze changed files. The TypeScript program should include all files
61
+ * for accurate type resolution, but only changed files need re-analysis.
62
+ *
63
+ * @param source_file The source file info with content and optional dependency data
64
+ * @param program TypeScript program (used for type checking and source file lookup)
65
+ * @param options Module source options for path extraction
66
+ * @param ctx Analysis context for collecting diagnostics
67
+ * @param log Optional logger for warnings
68
+ * @returns Module metadata and re-exports, or undefined if source file not found in program
69
+ */
70
+ export const library_analyze_module = (source_file, program, options, ctx, log) => {
71
+ const checker = program.getTypeChecker();
72
+ const module_path = module_extract_path(source_file.id, options);
73
+ const analyzer_type = options.get_analyzer(source_file.id);
74
+ if (analyzer_type === 'svelte') {
75
+ return svelte_analyze_module(source_file, module_path, checker, options, ctx);
76
+ }
77
+ if (analyzer_type === 'typescript') {
78
+ const ts_source_file = program.getSourceFile(source_file.id);
79
+ if (!ts_source_file) {
80
+ ctx.add({
81
+ kind: 'module_skipped',
82
+ file: module_path,
83
+ line: null,
84
+ column: null,
85
+ message: `Could not get source file from program: ${source_file.id}`,
86
+ severity: 'warning',
87
+ reason: 'not_in_program',
88
+ });
89
+ log?.warn(`Could not get source file from program: ${source_file.id}`);
90
+ return undefined;
91
+ }
92
+ return ts_analyze_module(source_file, ts_source_file, module_path, checker, options, ctx);
93
+ }
94
+ // analyzer_type is null - skip this file
95
+ ctx.add({
96
+ kind: 'module_skipped',
97
+ file: module_path,
98
+ line: null,
99
+ column: null,
100
+ message: `No analyzer for file type: ${source_file.id}`,
101
+ severity: 'warning',
102
+ reason: 'no_analyzer',
103
+ });
104
+ log?.warn(`No analyzer for file: ${source_file.id}`);
105
+ return undefined;
106
+ };
@@ -11,13 +11,79 @@
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
  import type { Gen } from '@ryanatkn/gro';
27
+ import type { Disknode } from '@ryanatkn/gro/disknode.js';
28
+ import { type SourceFileInfo, type ModuleSourceOptions, type ModuleSourcePartial } from './module_helpers.js';
29
+ import { type DuplicateInfo } from './library_gen_helpers.js';
30
+ /** Options for library generation. */
31
+ export interface LibraryGenOptions {
32
+ /**
33
+ * Module source options for filtering and path extraction.
34
+ *
35
+ * Can provide full `ModuleSourceOptions` or partial options that will be
36
+ * merged with defaults. The `project_root` is automatically set to
37
+ * `process.cwd()` if not provided.
38
+ */
39
+ source?: ModuleSourceOptions | Partial<ModuleSourcePartial>;
40
+ /**
41
+ * Callback invoked when duplicate declaration names are found.
42
+ *
43
+ * Consumers decide how to handle duplicates: throw, warn, or ignore.
44
+ * Use `library_gen_throw_on_duplicates` for strict flat namespace enforcement.
45
+ *
46
+ * @example
47
+ * // Throw on duplicates (strict flat namespace)
48
+ * library_gen({ on_duplicates: library_gen_throw_on_duplicates });
49
+ *
50
+ * // Warn but continue
51
+ * library_gen({
52
+ * on_duplicates: (dupes, log) => {
53
+ * for (const [name, locs] of dupes) {
54
+ * log.warn(`Duplicate: ${name} in ${locs.map(l => l.module).join(', ')}`);
55
+ * }
56
+ * }
57
+ * });
58
+ */
59
+ on_duplicates?: OnDuplicatesCallback;
60
+ }
61
+ /**
62
+ * Callback for handling duplicate declaration names.
63
+ *
64
+ * @param duplicates Map of declaration names to their occurrences across modules
65
+ * @param log Logger for reporting
66
+ */
67
+ export type OnDuplicatesCallback = (duplicates: Map<string, Array<DuplicateInfo>>, log: {
68
+ error: (...args: Array<unknown>) => void;
69
+ }) => void;
70
+ /**
71
+ * Strict duplicate handler that throws on any duplicate declaration names.
72
+ *
73
+ * Use this callback with `library_gen({ on_duplicates: library_gen_throw_on_duplicates })`
74
+ * to enforce a flat namespace where all declaration names must be unique.
75
+ *
76
+ * @throws Error if any duplicate declaration names are found
77
+ */
78
+ export declare const library_gen_throw_on_duplicates: OnDuplicatesCallback;
79
+ /**
80
+ * Convert Gro's Disknode to the build-tool agnostic SourceFileInfo interface.
81
+ *
82
+ * Use this when you want to analyze files using Gro's filer directly.
83
+ *
84
+ * @throws Error if disknode has no content (should be loaded by Gro filer)
85
+ */
86
+ export declare const source_file_from_disknode: (disknode: Disknode) => SourceFileInfo;
21
87
  /**
22
88
  * Creates a Gen object for generating library metadata with full TypeScript analysis.
23
89
  *
@@ -28,6 +94,8 @@ import type { Gen } from '@ryanatkn/gro';
28
94
  *
29
95
  * export const gen = library_gen();
30
96
  * ```
97
+ *
98
+ * @param options Optional generation options
31
99
  */
32
- export declare const library_gen: () => Gen;
100
+ export declare const library_gen: (options?: LibraryGenOptions) => Gen;
33
101
  //# sourceMappingURL=library_gen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"library_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/library_gen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,eAAe,CAAC;AAevC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,QAAO,GAiH9B,CAAC"}
1
+ {"version":3,"file":"library_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/library_gen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,eAAe,CAAC;AAEvC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AAIxD,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EAGxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAMN,KAAK,aAAa,EAClB,MAAM,0BAA0B,CAAC;AAIlC,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IACjC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5D;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACrC;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAClC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,EAC7C,GAAG,EAAE;IAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAAC,KAC3C,IAAI,CAAC;AAEV;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,oBAkB7C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GAAI,UAAU,QAAQ,KAAG,cAY9D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,iBAAiB,KAAG,GAuHzD,CAAC"}
@@ -11,16 +11,68 @@
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
  import { package_json_load } from '@ryanatkn/gro/package_json.js';
21
- import { ts_create_program } from "./ts_helpers.js";
22
- import { module_extract_path, module_is_svelte } from "./module_helpers.js";
23
- import { library_gen_collect_source_files, library_gen_sort_modules, library_gen_validate_no_duplicates, library_gen_generate_json, library_gen_analyze_svelte_file, library_gen_analyze_typescript_file, } from "./library_gen_helpers.js";
27
+ import { ts_create_program } from './ts_helpers.js';
28
+ import { module_create_source_options, module_validate_source_options, } from './module_helpers.js';
29
+ import { library_analyze_module } from './library_analysis.js';
30
+ import { library_collect_source_files, library_sort_modules, library_find_duplicates, library_merge_re_exports, } from './library_gen_helpers.js';
31
+ import { library_generate_json } from './library_gen_output.js';
32
+ import { AnalysisContext, format_diagnostic } from './analysis_context.js';
33
+ /**
34
+ * Strict duplicate handler that throws on any duplicate declaration names.
35
+ *
36
+ * Use this callback with `library_gen({ on_duplicates: library_gen_throw_on_duplicates })`
37
+ * to enforce a flat namespace where all declaration names must be unique.
38
+ *
39
+ * @throws Error if any duplicate declaration names are found
40
+ */
41
+ export const library_gen_throw_on_duplicates = (duplicates, log) => {
42
+ if (duplicates.size === 0)
43
+ return;
44
+ log.error('Duplicate declaration names detected in flat namespace:');
45
+ for (const [name, occurrences] of duplicates) {
46
+ log.error(` "${name}" found in:`);
47
+ for (const { declaration, module } of occurrences) {
48
+ const line_info = declaration.source_line !== undefined ? `:${declaration.source_line}` : '';
49
+ log.error(` - ${module}${line_info} (${declaration.kind})`);
50
+ }
51
+ }
52
+ throw new Error(`Found ${duplicates.size} duplicate declaration name${duplicates.size === 1 ? '' : 's'} across modules. ` +
53
+ 'The flat namespace requires unique names. To resolve: ' +
54
+ '(1) rename one of the conflicting declarations, or ' +
55
+ '(2) add /** @nodocs */ to exclude from documentation. ' +
56
+ 'See CLAUDE.md "Declaration namespacing" section for details.');
57
+ };
58
+ /**
59
+ * Convert Gro's Disknode to the build-tool agnostic SourceFileInfo interface.
60
+ *
61
+ * Use this when you want to analyze files using Gro's filer directly.
62
+ *
63
+ * @throws Error if disknode has no content (should be loaded by Gro filer)
64
+ */
65
+ export const source_file_from_disknode = (disknode) => {
66
+ if (disknode.contents == null) {
67
+ throw new Error(`Source file has no content: ${disknode.id} (ensure Gro filer loads file contents)`);
68
+ }
69
+ return {
70
+ id: disknode.id,
71
+ content: disknode.contents,
72
+ dependencies: [...disknode.dependencies.keys()],
73
+ dependents: [...disknode.dependents.keys()],
74
+ };
75
+ };
24
76
  /**
25
77
  * Creates a Gen object for generating library metadata with full TypeScript analysis.
26
78
  *
@@ -31,92 +83,102 @@ import { library_gen_collect_source_files, library_gen_sort_modules, library_gen
31
83
  *
32
84
  * export const gen = library_gen();
33
85
  * ```
86
+ *
87
+ * @param options Optional generation options
34
88
  */
35
- export const library_gen = () => {
89
+ export const library_gen = (options) => {
36
90
  return {
37
91
  generate: async ({ log, filer }) => {
38
92
  log.info('generating library metadata with full TypeScript analysis...');
93
+ // Build source options with project_root from cwd
94
+ const source_options = options?.source && 'project_root' in options.source
95
+ ? options.source
96
+ : module_create_source_options(process.cwd(), options?.source);
97
+ // Validate options early to fail fast on misconfiguration
98
+ // (before expensive operations like program creation)
99
+ module_validate_source_options(source_options);
39
100
  // Ensure filer is initialized
40
101
  await filer.init();
41
102
  // Read package.json
42
103
  const package_json = await package_json_load();
43
104
  // Create TypeScript program
44
- const program = ts_create_program(log);
45
- if (!program) {
46
- throw new Error('Failed to create TypeScript program - cannot generate library metadata');
105
+ const { program } = ts_create_program(undefined, log);
106
+ // Create analysis context for collecting diagnostics
107
+ const ctx = new AnalysisContext();
108
+ // Convert Gro's filer files to build-tool agnostic SourceFileInfo
109
+ const all_source_files = [];
110
+ for (const disknode of filer.files.values()) {
111
+ all_source_files.push(source_file_from_disknode(disknode));
47
112
  }
48
- const checker = program.getTypeChecker();
49
- // Collect source files from filer
50
- const source_disknodes = library_gen_collect_source_files(filer.files, log);
113
+ // Collect and filter source files
114
+ const source_files = library_collect_source_files(all_source_files, source_options, log);
115
+ // Collect modules (declared before source_json to include directly)
116
+ const modules = [];
51
117
  // Build source_json with array-based modules
52
118
  // Phase 1: Analyze all modules and collect re-exports
53
119
  const source_json = {
54
120
  name: package_json.name,
55
121
  version: package_json.version,
56
- modules: [],
122
+ modules,
57
123
  };
58
- // Collect all re-exports: Map<declaration_name, Set<re_exporting_module_path>>
59
- // The Set tracks which modules re-export each declaration
60
- const all_re_exports = [];
61
- for (const disknode of source_disknodes) {
62
- const source_id = disknode.id;
63
- const module_path = module_extract_path(source_id);
64
- const is_svelte = module_is_svelte(module_path);
65
- // Handle Svelte files separately (before trying to get TypeScript source file)
66
- if (is_svelte) {
67
- const mod = library_gen_analyze_svelte_file(disknode, module_path, checker);
68
- source_json.modules.push(mod);
69
- }
70
- else {
71
- // For TypeScript/JS files, get the source file from the program
72
- const source_file = program.getSourceFile(source_id);
73
- if (!source_file) {
74
- log.warn(`Could not get source file: ${source_id}`);
75
- continue;
76
- }
77
- // May throw, which we want to see
78
- const { module: mod, re_exports } = library_gen_analyze_typescript_file(disknode, source_file, module_path, checker);
79
- source_json.modules.push(mod);
80
- // Collect re-exports for post-processing
81
- for (const re_export of re_exports) {
82
- all_re_exports.push({ re_exporting_module: module_path, re_export });
83
- }
124
+ // Collect re-exports for phase 2 merging
125
+ // See library_merge_re_exports for the two-phase resolution strategy
126
+ const collected_re_exports = [];
127
+ for (const source_file of source_files) {
128
+ // Use unified analyzer that dispatches based on file type
129
+ const result = library_analyze_module(source_file, program, source_options, ctx, log);
130
+ if (!result)
131
+ continue;
132
+ // Build ModuleJson, filtering out @nodocs declarations
133
+ const module = {
134
+ path: result.path,
135
+ declarations: result.declarations.filter((d) => !d.nodocs).map((d) => d.declaration),
136
+ };
137
+ if (result.module_comment)
138
+ module.module_comment = result.module_comment;
139
+ if (result.dependencies.length > 0)
140
+ module.dependencies = result.dependencies;
141
+ if (result.dependents.length > 0)
142
+ module.dependents = result.dependents;
143
+ if (result.star_exports.length > 0)
144
+ module.star_exports = result.star_exports;
145
+ modules.push(module);
146
+ // Collect re-exports for phase 2 merging
147
+ for (const re_export of result.re_exports) {
148
+ collected_re_exports.push({ re_exporting_module: result.path, re_export });
84
149
  }
85
150
  }
86
151
  // Phase 2: Build also_exported_from arrays from re-export data
87
- // Group re-exports by original module and declaration name
88
- const re_export_map = new Map();
89
- for (const { re_exporting_module, re_export } of all_re_exports) {
90
- const { name, original_module } = re_export;
91
- if (!re_export_map.has(original_module)) {
92
- re_export_map.set(original_module, new Map());
93
- }
94
- const module_map = re_export_map.get(original_module);
95
- if (!module_map.has(name)) {
96
- module_map.set(name, []);
152
+ library_merge_re_exports(source_json, collected_re_exports);
153
+ // Sort modules alphabetically for deterministic output and cleaner diffs
154
+ source_json.modules = library_sort_modules(modules);
155
+ // Check for duplicate declaration names and invoke callback if provided
156
+ if (options?.on_duplicates) {
157
+ const duplicates = library_find_duplicates(source_json);
158
+ if (duplicates.size > 0) {
159
+ options.on_duplicates(duplicates, log);
97
160
  }
98
- module_map.get(name).push(re_exporting_module);
99
161
  }
100
- // Merge into original declarations
101
- for (const mod of source_json.modules ?? []) {
102
- const module_re_exports = re_export_map.get(mod.path);
103
- if (!module_re_exports)
104
- continue;
105
- for (const declaration of mod.declarations ?? []) {
106
- const re_exporters = module_re_exports.get(declaration.name);
107
- if (re_exporters?.length) {
108
- declaration.also_exported_from = re_exporters.sort();
162
+ // Report any analysis diagnostics
163
+ if (ctx.diagnostics.length > 0) {
164
+ const errors = ctx.errors();
165
+ const warnings = ctx.warnings();
166
+ const format_options = { strip_base: process.cwd() };
167
+ if (errors.length > 0) {
168
+ log.error(`Analysis completed with ${errors.length} error(s):`);
169
+ for (const diagnostic of errors) {
170
+ log.error(` ${format_diagnostic(diagnostic, format_options)}`);
171
+ }
172
+ }
173
+ if (warnings.length > 0) {
174
+ log.warn(`Analysis completed with ${warnings.length} warning(s):`);
175
+ for (const diagnostic of warnings) {
176
+ log.warn(` ${format_diagnostic(diagnostic, format_options)}`);
109
177
  }
110
178
  }
111
179
  }
112
- // Sort modules alphabetically for deterministic output and cleaner diffs
113
- source_json.modules = source_json.modules
114
- ? library_gen_sort_modules(source_json.modules)
115
- : undefined;
116
- // Validate no duplicate declaration names across modules
117
- library_gen_validate_no_duplicates(source_json, log);
118
180
  log.info('library metadata generation complete');
119
- const { json_content, ts_content } = library_gen_generate_json(package_json, source_json);
181
+ const { json_content, ts_content } = library_generate_json(package_json, source_json);
120
182
  // Return array of files:
121
183
  // - library.json (default from .gen.json.ts naming)
122
184
  // - library.ts (typed wrapper that validates with zod)
@@ -1,101 +1,110 @@
1
1
  /**
2
- * Build-time helpers for library metadata generation.
2
+ * Library metadata generation helpers - pipeline orchestration.
3
3
  *
4
- * These functions handle Gro-specific concerns like file collection and dependency
5
- * graph extraction. Core analysis logic has been extracted to reusable helpers:
4
+ * These functions handle collection, validation, and transformation of library metadata
5
+ * during the generation pipeline. They are internal to the generation process.
6
6
  *
7
- * - `ts_helpers.ts` - `ts_analyze_module_exports`
8
- * - `svelte_helpers.ts` - `svelte_analyze_file`
9
- * - `module_helpers.ts` - path utilities and source detection
7
+ * For source analysis (the consumer-facing API), see `library_analysis.ts`.
10
8
  *
11
- * Design philosophy: Fail fast with clear errors rather than silently producing invalid
12
- * metadata. All validation errors halt the build immediately with actionable messages.
9
+ * Pipeline stages:
10
+ * 1. **Collection** - `library_collect_source_files` gathers and filters source files
11
+ * 2. **Analysis** - `library_analyze_module` (in library_analysis.ts) extracts metadata
12
+ * 3. **Validation** - `library_find_duplicates` checks flat namespace constraints
13
+ * 4. **Transformation** - `library_merge_re_exports` resolves re-export relationships
14
+ * 5. **Output** - `library_sort_modules` prepares deterministic output
13
15
  *
14
- * @see library_gen.ts for the main generation task
15
- * @see @fuzdev/fuz_util/source_json.js for type definitions
16
- * @see ts_helpers.ts for reusable TypeScript analysis
17
- * @see svelte_helpers.ts for reusable Svelte component analysis
16
+ * @see library_analysis.ts for the analysis entry point
17
+ * @see library_gen_output.ts for output file generation (JSON/TS wrapper)
18
+ * @see library_gen.ts for the main generation task (Gro-specific)
19
+ *
20
+ * @module
18
21
  */
19
- import type { PackageJson } from '@fuzdev/fuz_util/package_json.js';
20
22
  import type { Logger } from '@fuzdev/fuz_util/log.js';
21
- import type { ModuleJson, SourceJson } from '@fuzdev/fuz_util/source_json.js';
22
- import type { Disknode } from '@ryanatkn/gro/disknode.js';
23
- import type ts from 'typescript';
24
- import type { PathId } from '@fuzdev/fuz_util/path.js';
25
- import { type ReExportInfo } from './ts_helpers.js';
23
+ import type { DeclarationJson, ModuleJson, SourceJson } from '@fuzdev/fuz_util/source_json.js';
24
+ import type { ReExportInfo } from './library_analysis.js';
25
+ import { type SourceFileInfo, type ModuleSourceOptions } from './module_helpers.js';
26
26
  /**
27
- * Result of analyzing a TypeScript file.
28
- * Includes both the module metadata and re-export information for post-processing.
27
+ * A duplicate declaration with its full metadata and module path.
29
28
  */
30
- export interface TsFileAnalysis {
31
- /** Module metadata for inclusion in source_json. */
32
- module: ModuleJson;
33
- /** Re-exports from this module for building also_exported_from. */
34
- re_exports: Array<ReExportInfo>;
29
+ export interface DuplicateInfo {
30
+ /** The full declaration metadata. */
31
+ declaration: DeclarationJson;
32
+ /** Module path where this declaration is defined. */
33
+ module: string;
35
34
  }
36
35
  /**
37
- * Validates that no declaration names are duplicated across modules.
38
- * The flat namespace is intentional - duplicates should fail fast.
36
+ * Find duplicate declaration names across modules.
37
+ *
38
+ * Returns a Map of declaration names to their full metadata (only includes duplicates).
39
+ * Callers can decide how to handle duplicates (throw, warn, ignore).
39
40
  *
40
- * @throws Error if duplicate declaration names are found
41
+ * @example
42
+ * const duplicates = library_find_duplicates(source_json);
43
+ * if (duplicates.size > 0) {
44
+ * for (const [name, occurrences] of duplicates) {
45
+ * console.error(`"${name}" found in:`);
46
+ * for (const {declaration, module} of occurrences) {
47
+ * console.error(` - ${module}:${declaration.source_line} (${declaration.kind})`);
48
+ * }
49
+ * }
50
+ * throw new Error(`Found ${duplicates.size} duplicate declaration names`);
51
+ * }
41
52
  */
42
- export declare const library_gen_validate_no_duplicates: (source_json: SourceJson, log: Logger) => void;
53
+ export declare const library_find_duplicates: (source_json: SourceJson) => Map<string, Array<DuplicateInfo>>;
43
54
  /**
44
55
  * Sort modules alphabetically by path for deterministic output and cleaner diffs.
45
56
  */
46
- export declare const library_gen_sort_modules: (modules: Array<ModuleJson>) => Array<ModuleJson>;
57
+ export declare const library_sort_modules: (modules: Array<ModuleJson>) => Array<ModuleJson>;
47
58
  /**
48
- * Result of generating library files.
49
- * Contains both the JSON data and the TypeScript wrapper file.
59
+ * A collected re-export with its source module context.
60
+ *
61
+ * Used during the two-phase re-export resolution:
62
+ * 1. Phase 1: Collect re-exports from each module during analysis
63
+ * 2. Phase 2: Group by original module and merge into `also_exported_from`
50
64
  */
51
- export interface LibraryGenOutput {
52
- /** JSON content for library.json */
53
- json_content: string;
54
- /** TypeScript wrapper content for library.ts */
55
- ts_content: string;
65
+ export interface CollectedReExport {
66
+ /** The module that re-exports the declaration. */
67
+ re_exporting_module: string;
68
+ /** The re-export info (name and original module). */
69
+ re_export: ReExportInfo;
56
70
  }
57
71
  /**
58
- * Generate the library.json and library.ts file contents.
59
- * Parses at generation time so runtime only needs the pre-computed result.
60
- *
61
- * Returns JSON + .ts wrapper because:
62
- * - JSON is natively importable by Node.js and Vite without TypeScript loaders
63
- * - Works in CI environments that don't have TS compilation
64
- * - The .ts wrapper validates with zod and exports with proper types
65
- * (JSON imports get widened types like `string` instead of literal unions)
66
- */
67
- export declare const library_gen_generate_json: (package_json: PackageJson, source_json: SourceJson) => LibraryGenOutput;
68
- /**
69
- * Collect and filter source files from filer.
72
+ * Build `also_exported_from` arrays from collected re-export data.
70
73
  *
71
- * Returns disknodes for TypeScript/JS files and Svelte components from src/lib, excluding test files.
72
- * Returns an empty array with a warning if no source files are found.
73
- */
74
- export declare const library_gen_collect_source_files: (files: Map<PathId, Disknode>, log: Logger) => Array<Disknode>;
75
- /**
76
- * Analyze a Svelte component file and extract metadata.
74
+ * This function resolves the two-phase re-export problem:
77
75
  *
78
- * Uses `svelte_analyze_file` for core analysis, then adds
79
- * Gro-specific dependency information from the disknode.
80
- */
81
- export declare const library_gen_analyze_svelte_file: (disknode: Disknode, module_path: string, checker: ts.TypeChecker) => ModuleJson;
82
- /**
83
- * Analyze a TypeScript file and extract all declarations.
76
+ * **Problem**: When module A re-exports from module B, we discover this while
77
+ * analyzing A, but need to update B's declarations. However, B may already be
78
+ * processed or may be processed later.
79
+ *
80
+ * **Solution**: Collect all re-exports in phase 1, then merge them in phase 2
81
+ * after all modules are analyzed.
84
82
  *
85
- * Uses `ts_analyze_module_exports` for core analysis, then adds
86
- * Gro-specific dependency information from the disknode.
83
+ * @example
84
+ * // helpers.ts exports: foo, bar
85
+ * // index.ts does: export {foo, bar} from './helpers.js'
86
+ * //
87
+ * // After processing:
88
+ * // - helpers.ts foo declaration gets: also_exported_from: ['index.ts']
89
+ * // - helpers.ts bar declaration gets: also_exported_from: ['index.ts']
87
90
  *
88
- * Returns both the module metadata and re-export information for post-processing.
91
+ * @param source_json The source JSON with all modules (will be mutated)
92
+ * @param collected_re_exports Array of re-exports collected during phase 1
93
+ * @mutates source_json - adds `also_exported_from` to declarations
89
94
  */
90
- export declare const library_gen_analyze_typescript_file: (disknode: Disknode, source_file: ts.SourceFile, module_path: string, checker: ts.TypeChecker) => TsFileAnalysis;
95
+ export declare const library_merge_re_exports: (source_json: SourceJson, collected_re_exports: Array<CollectedReExport>) => void;
91
96
  /**
92
- * Extract dependencies and dependents for a module from the filer's dependency graph.
97
+ * Collect and filter source files.
98
+ *
99
+ * Returns source files for TypeScript/JS files and Svelte components, excluding test files.
100
+ * Returns an empty array with a warning if no source files are found.
101
+ *
102
+ * File types are determined by `options.get_analyzer`. By default, `.ts`, `.js`, and `.svelte`
103
+ * files are supported. Customize `get_analyzer` to support additional file types like `.svx`.
93
104
  *
94
- * Filters to only include source modules from src/lib (excludes external packages, node_modules, tests).
95
- * Returns sorted arrays of module paths (relative to src/lib) for deterministic output.
105
+ * @param files Iterable of source file info (from Gro filer, file system, or other source)
106
+ * @param options Module source options for filtering
107
+ * @param log Optional logger for status messages
96
108
  */
97
- export declare const library_gen_extract_dependencies: (disknode: Disknode) => {
98
- dependencies: Array<string>;
99
- dependents: Array<string>;
100
- };
109
+ export declare const library_collect_source_files: (files: Iterable<SourceFileInfo>, options: ModuleSourceOptions, log?: Logger) => Array<SourceFileInfo>;
101
110
  //# sourceMappingURL=library_gen_helpers.d.ts.map