@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
@@ -5,42 +5,342 @@
5
5
  * and import relationships in the package generation system.
6
6
  *
7
7
  * All functions are prefixed with `module_` for clarity.
8
+ *
9
+ * @module
10
+ */
11
+
12
+ /**
13
+ * Analyzer type for source files.
14
+ *
15
+ * - `'typescript'` - TypeScript/JavaScript files analyzed via TypeScript Compiler API
16
+ * - `'svelte'` - Svelte components analyzed via svelte2tsx + TypeScript Compiler API
17
+ */
18
+ export type AnalyzerType = 'typescript' | 'svelte';
19
+
20
+ /**
21
+ * File information for source analysis.
22
+ *
23
+ * Can be constructed from Gro's Disknode or from plain file system access.
24
+ * This abstraction enables non-Gro usage while keeping Gro support via adapter.
25
+ *
26
+ * Note: `content` is required to keep analysis functions pure (no hidden I/O).
27
+ * Callers are responsible for reading file content before analysis.
8
28
  */
29
+ export interface SourceFileInfo {
30
+ /** Absolute path to the file. */
31
+ id: string;
32
+ /** File content (required - analysis functions don't read from disk). */
33
+ content: string;
34
+ /**
35
+ * Absolute file paths of modules this file imports (optional).
36
+ * Only include resolved local imports, not node_modules.
37
+ * Order should be declaration order in source for deterministic output.
38
+ */
39
+ dependencies?: ReadonlyArray<string>;
40
+ /**
41
+ * Absolute file paths of modules that import this file (optional).
42
+ * Only include resolved local imports, not node_modules.
43
+ */
44
+ dependents?: ReadonlyArray<string>;
45
+ }
9
46
 
10
47
  /**
11
- * Configuration for module source detection.
48
+ * Configuration for module source detection and path extraction.
49
+ *
50
+ * Uses proper path semantics with `project_root` as the base for all path operations.
51
+ * Paths are matched using `startsWith` rather than substring search, which correctly
52
+ * handles nested directories without special heuristics.
12
53
  *
13
- * Allows customizing which paths are considered source modules,
14
- * useful for projects with non-standard directory structures.
54
+ * @example
55
+ * const options = module_create_source_options(process.cwd(), {
56
+ * source_paths: ['src/lib', 'src/routes'],
57
+ * source_root: 'src',
58
+ * });
15
59
  */
16
60
  export interface ModuleSourceOptions {
17
- /** Source directory paths to include. @default ['/src/lib/'] */
18
- source_paths?: Array<string>;
19
- /** File extensions to analyze. @default ['.ts', '.js', '.svelte'] */
20
- extensions?: Array<string>;
21
- /** Patterns to exclude (matched against full path). @default [/\.test\.ts$/] */
22
- exclude_patterns?: Array<RegExp>;
61
+ /**
62
+ * Absolute path to the project root directory.
63
+ *
64
+ * All `source_paths` are relative to this. Typically `process.cwd()` when
65
+ * running from the project root via Gro, Vite, or other build tools.
66
+ *
67
+ * @example '/home/user/my-project'
68
+ */
69
+ project_root: string;
70
+ /**
71
+ * Source directory paths to include, relative to `project_root`.
72
+ *
73
+ * Paths should not have leading or trailing slashes - they are added
74
+ * internally for correct matching.
75
+ *
76
+ * @example ['src/lib'] - single source directory
77
+ * @example ['src/lib', 'src/routes'] - multiple directories
78
+ */
79
+ source_paths: Array<string>;
80
+ /**
81
+ * Source root for extracting relative module paths, relative to `project_root`.
82
+ *
83
+ * When omitted:
84
+ * - Single `source_path`: defaults to that path
85
+ * - Multiple `source_paths`: required (no auto-derivation)
86
+ *
87
+ * @example 'src/lib' - module paths like 'foo.ts', 'utils/bar.ts'
88
+ * @example 'src' - module paths like 'lib/foo.ts', 'routes/page.svelte'
89
+ */
90
+ source_root?: string;
91
+ /** Patterns to exclude (matched against full path). */
92
+ exclude_patterns: Array<RegExp>;
93
+ /**
94
+ * Determine which analyzer to use for a file path.
95
+ *
96
+ * Called for files in source directories. Return `'typescript'`, `'svelte'`,
97
+ * or `null` to skip the file. This is the single source of truth for which
98
+ * files are analyzable and how to analyze them.
99
+ *
100
+ * @default Uses file extension: `.svelte` → svelte, `.ts`/`.js` → typescript
101
+ *
102
+ * @example
103
+ * // Add MDsveX support
104
+ * get_analyzer: (path) => {
105
+ * if (path.endsWith('.svelte') || path.endsWith('.svx')) return 'svelte';
106
+ * if (path.endsWith('.ts') || path.endsWith('.js')) return 'typescript';
107
+ * return null;
108
+ * }
109
+ *
110
+ * @example
111
+ * // Include .d.ts files
112
+ * get_analyzer: (path) => {
113
+ * if (path.endsWith('.svelte')) return 'svelte';
114
+ * if (path.endsWith('.ts') || path.endsWith('.d.ts') || path.endsWith('.js')) return 'typescript';
115
+ * return null;
116
+ * }
117
+ */
118
+ get_analyzer: (path: string) => AnalyzerType | null;
23
119
  }
24
120
 
25
121
  /**
26
- * Default options for module source detection.
122
+ * Default analyzer resolver based on file extension.
123
+ *
124
+ * - `.svelte` → `'svelte'`
125
+ * - `.ts`, `.js` → `'typescript'`
126
+ * - Other extensions → `null` (skip)
127
+ */
128
+ export const module_get_analyzer_default = (path: string): AnalyzerType | null => {
129
+ if (module_is_svelte(path)) return 'svelte';
130
+ if (module_is_typescript(path)) return 'typescript';
131
+ return null;
132
+ };
133
+
134
+ /**
135
+ * Partial source options without `project_root`.
136
+ *
137
+ * Use with `module_create_source_options` to build complete options.
27
138
  */
28
- export const MODULE_SOURCE_DEFAULTS: Required<ModuleSourceOptions> = {
29
- source_paths: ['/src/lib/'],
30
- extensions: ['.ts', '.js', '.svelte'],
139
+ export type ModuleSourcePartial = Omit<ModuleSourceOptions, 'project_root'>;
140
+
141
+ /**
142
+ * Default partial options for standard SvelteKit library structure.
143
+ *
144
+ * Does not include `project_root` - use `module_create_source_options()` to create
145
+ * complete options with your project root.
146
+ */
147
+ export const MODULE_SOURCE_PARTIAL: ModuleSourcePartial = {
148
+ source_paths: ['src/lib'],
31
149
  exclude_patterns: [/\.test\.ts$/],
150
+ get_analyzer: module_get_analyzer_default,
32
151
  };
33
152
 
34
153
  /**
35
- * Extract module path relative to src/lib from absolute source ID.
154
+ * Create complete source options from project root and optional overrides.
155
+ *
156
+ * @param project_root Absolute path to project root (typically `process.cwd()`)
157
+ * @param overrides Optional overrides for default options
158
+ *
159
+ * @example
160
+ * // Standard SvelteKit library
161
+ * const options = module_create_source_options(process.cwd());
162
+ *
163
+ * @example
164
+ * // Multiple source directories
165
+ * const options = module_create_source_options(process.cwd(), {
166
+ * source_paths: ['src/lib', 'src/routes'],
167
+ * source_root: 'src',
168
+ * });
169
+ *
170
+ * @example
171
+ * // Custom exclusions
172
+ * const options = module_create_source_options(process.cwd(), {
173
+ * exclude_patterns: [/\.test\.ts$/, /\.internal\.ts$/],
174
+ * });
175
+ */
176
+ export const module_create_source_options = (
177
+ project_root: string,
178
+ overrides?: Partial<ModuleSourcePartial>,
179
+ ): ModuleSourceOptions => ({
180
+ project_root,
181
+ ...MODULE_SOURCE_PARTIAL,
182
+ ...overrides,
183
+ });
184
+
185
+ /**
186
+ * Validate `ModuleSourceOptions` format and consistency.
187
+ *
188
+ * Checks:
189
+ * 1. `project_root` is an absolute path (starts with `/`)
190
+ * 2. `source_paths` entries don't have leading/trailing slashes
191
+ * 3. `source_root` (if provided) doesn't have leading/trailing slashes
192
+ * 4. Multiple `source_paths` require explicit `source_root`
193
+ * 5. `source_root` is a prefix of all `source_paths`
194
+ *
195
+ * @throws Error if validation fails
196
+ *
197
+ * @example
198
+ * // Valid - single source path (source_root auto-derived)
199
+ * module_validate_source_options({
200
+ * project_root: '/home/user/project',
201
+ * source_paths: ['src/lib'],
202
+ * ...
203
+ * });
204
+ *
205
+ * @example
206
+ * // Valid - multiple source paths with explicit source_root
207
+ * module_validate_source_options({
208
+ * project_root: '/home/user/project',
209
+ * source_paths: ['src/lib', 'src/routes'],
210
+ * source_root: 'src',
211
+ * ...
212
+ * });
36
213
  *
37
214
  * @example
38
- * module_extract_path('/home/user/project/src/lib/foo.ts') // => 'foo.ts'
39
- * module_extract_path('/home/user/project/src/lib/nested/bar.svelte') // => 'nested/bar.svelte'
215
+ * // Invalid - multiple source paths without source_root
216
+ * module_validate_source_options({
217
+ * project_root: '/home/user/project',
218
+ * source_paths: ['src/lib', 'src/routes'], // throws
219
+ * ...
220
+ * });
40
221
  */
41
- export const module_extract_path = (source_id: string): string => {
42
- const lib_index = source_id.indexOf('/src/lib/');
43
- return lib_index !== -1 ? source_id.substring(lib_index + 9) : source_id;
222
+ export const module_validate_source_options = (options: ModuleSourceOptions): void => {
223
+ const {project_root, source_paths, source_root} = options;
224
+
225
+ // Validate project_root is absolute
226
+ if (!project_root.startsWith('/')) {
227
+ throw new Error(`project_root must be an absolute path (start with "/"): "${project_root}"`);
228
+ }
229
+
230
+ // Validate project_root doesn't have trailing slash (we add it internally)
231
+ if (project_root.endsWith('/')) {
232
+ throw new Error(
233
+ `project_root should not have trailing slash: "${project_root}". ` +
234
+ `Trailing slashes are added internally for correct matching.`,
235
+ );
236
+ }
237
+
238
+ // Validate source_paths
239
+ if (source_paths.length === 0) {
240
+ throw new Error('source_paths must have at least one entry');
241
+ }
242
+
243
+ for (const source_path of source_paths) {
244
+ if (source_path.startsWith('/')) {
245
+ throw new Error(
246
+ `source_paths entry should not start with "/": "${source_path}". ` +
247
+ `Paths are relative to project_root.`,
248
+ );
249
+ }
250
+ if (source_path.endsWith('/')) {
251
+ throw new Error(
252
+ `source_paths entry should not end with "/": "${source_path}". ` +
253
+ `Trailing slashes are added internally for correct matching.`,
254
+ );
255
+ }
256
+ }
257
+
258
+ // Validate source_root if provided
259
+ if (source_root !== undefined) {
260
+ if (source_root.startsWith('/')) {
261
+ throw new Error(
262
+ `source_root should not start with "/": "${source_root}". ` +
263
+ `Paths are relative to project_root.`,
264
+ );
265
+ }
266
+ if (source_root.endsWith('/')) {
267
+ throw new Error(
268
+ `source_root should not end with "/": "${source_root}". ` +
269
+ `Trailing slashes are added internally for correct matching.`,
270
+ );
271
+ }
272
+
273
+ // Validate each source_path starts with source_root
274
+ for (const source_path of source_paths) {
275
+ // source_path should equal source_root or start with source_root/
276
+ if (source_path !== source_root && !source_path.startsWith(source_root + '/')) {
277
+ throw new Error(
278
+ `source_paths entry "${source_path}" must start with source_root "${source_root}". ` +
279
+ `module_extract_path uses source_root to compute module paths.`,
280
+ );
281
+ }
282
+ }
283
+ } else if (source_paths.length > 1) {
284
+ // Multiple source_paths without source_root - error
285
+ throw new Error(
286
+ `source_root is required when source_paths has multiple entries. ` +
287
+ `Got source_paths: [${source_paths.map((p) => `"${p}"`).join(', ')}]. ` +
288
+ `Provide source_root to specify the common prefix for module path extraction.`,
289
+ );
290
+ }
291
+ };
292
+
293
+ /**
294
+ * Get the effective source_root from options.
295
+ *
296
+ * Returns `source_root` if provided, otherwise returns `source_paths[0]` for single-path configs.
297
+ *
298
+ * @throws Error if source_root is required but not provided (multiple source_paths)
299
+ */
300
+ export const module_get_source_root = (options: ModuleSourceOptions): string => {
301
+ if (options.source_root !== undefined) {
302
+ return options.source_root;
303
+ }
304
+ if (options.source_paths.length === 1) {
305
+ return options.source_paths[0]!;
306
+ }
307
+ throw new Error(
308
+ `source_root is required when source_paths has multiple entries. ` +
309
+ `Got source_paths: [${options.source_paths.map((p) => `"${p}"`).join(', ')}].`,
310
+ );
311
+ };
312
+
313
+ /**
314
+ * Extract module path relative to source root from absolute source ID.
315
+ *
316
+ * Uses proper path semantics: strips `project_root/source_root/` prefix.
317
+ *
318
+ * @param source_id Absolute path to the source file
319
+ * @param options Module source options for path extraction
320
+ *
321
+ * @example
322
+ * const options = module_create_source_options('/home/user/project');
323
+ * module_extract_path('/home/user/project/src/lib/foo.ts', options) // => 'foo.ts'
324
+ * module_extract_path('/home/user/project/src/lib/nested/bar.svelte', options) // => 'nested/bar.svelte'
325
+ *
326
+ * @example
327
+ * const options = module_create_source_options('/home/user/project', {
328
+ * source_paths: ['src/lib', 'src/routes'],
329
+ * source_root: 'src',
330
+ * });
331
+ * module_extract_path('/home/user/project/src/lib/foo.ts', options) // => 'lib/foo.ts'
332
+ * module_extract_path('/home/user/project/src/routes/page.svelte', options) // => 'routes/page.svelte'
333
+ */
334
+ export const module_extract_path = (source_id: string, options: ModuleSourceOptions): string => {
335
+ const effective_root = module_get_source_root(options);
336
+ // Build the full prefix: project_root + '/' + source_root + '/'
337
+ const prefix = options.project_root + '/' + effective_root + '/';
338
+
339
+ if (source_id.startsWith(prefix)) {
340
+ return source_id.slice(prefix.length);
341
+ }
342
+ // Fallback: return full path if prefix doesn't match (shouldn't happen with valid inputs)
343
+ return source_id;
44
344
  };
45
345
 
46
346
  /**
@@ -61,8 +361,14 @@ export const module_get_component_name = (module_path: string): string =>
61
361
  */
62
362
  export const module_get_key = (module_path: string): string => `./${module_path}`;
63
363
 
364
+ /**
365
+ * Check if a path is a TypeScript or JavaScript file.
366
+ *
367
+ * Includes both `.ts` and `.js` files since JS files are valid in TS projects.
368
+ * Excludes `.d.ts` declaration files - use a custom `get_analyzer` to include them.
369
+ */
64
370
  export const module_is_typescript = (path: string): boolean =>
65
- path.endsWith('.ts') || path.endsWith('.js'); // hackyy but fine?
371
+ (path.endsWith('.ts') && !path.endsWith('.d.ts')) || path.endsWith('.js');
66
372
 
67
373
  export const module_is_svelte = (path: string): boolean => path.endsWith('.svelte');
68
374
 
@@ -73,41 +379,80 @@ export const module_is_json = (path: string): boolean => path.endsWith('.json');
73
379
  export const module_is_test = (path: string): boolean => path.endsWith('.test.ts');
74
380
 
75
381
  /**
76
- * Check if a path matches source criteria.
382
+ * Check if a path is an analyzable source file.
383
+ *
384
+ * Combines all filtering: exclusion patterns, source directory paths,
385
+ * and analyzer availability. This is the single check for whether a
386
+ * file should be included in library analysis.
77
387
  *
78
- * Checks source directory paths, file extensions, and exclusion patterns.
79
- * Uses defaults if no options provided.
388
+ * Uses proper path semantics with `startsWith` matching against
389
+ * `project_root/source_path/`. No heuristics needed - nested directories
390
+ * are correctly excluded by the prefix check.
80
391
  *
81
- * Rejects nested repo paths by ensuring the first `/src/` leads to the source directory
82
- * (e.g. rejects `/src/fixtures/repos/foo/src/lib/index.ts` because `/src/fixtures/` `/src/lib/`).
392
+ * @param path Full absolute path to check
393
+ * @param options Module source options for filtering
394
+ * @returns True if the path is an analyzable source file
83
395
  *
84
- * @param path Full path to check
85
- * @param options Configuration options (uses defaults if not provided)
86
- * @returns True if the path matches all criteria
396
+ * @example
397
+ * const options = module_create_source_options('/home/user/project');
398
+ * module_is_source('/home/user/project/src/lib/foo.ts', options) // => true
399
+ * module_is_source('/home/user/project/src/lib/foo.test.ts', options) // => false (excluded)
400
+ * module_is_source('/home/user/project/src/fixtures/mini/src/lib/bar.ts', options) // => false (wrong prefix)
87
401
  */
88
- export const module_matches_source = (path: string, options?: ModuleSourceOptions): boolean => {
89
- const opts = {...MODULE_SOURCE_DEFAULTS, ...options};
402
+ export const module_is_source = (path: string, options: ModuleSourceOptions): boolean => {
403
+ // Check exclusion patterns first (fast regex check)
404
+ const is_excluded = options.exclude_patterns.some((pattern) => pattern.test(path));
405
+ if (is_excluded) return false;
90
406
 
91
- // Check if path is in one of the source directories
92
- // The first /src/ in the path must lead directly to the source directory
93
- // This rejects nested repos like /src/fixtures/repos/foo/src/lib/
94
- const in_source_dir = opts.source_paths.some((source_path) => {
95
- if (!path.includes(source_path)) return false;
96
- // Find the first /src/ and verify it's part of the source_path
97
- const first_src_index = path.indexOf('/src/');
98
- if (first_src_index === -1) return false;
99
- // The source_path should start at that position
100
- return path.substring(first_src_index).startsWith(source_path);
407
+ // Check if path starts with project_root/source_path/
408
+ // Using startsWith with trailing slash ensures correct directory matching
409
+ const in_source_dir = options.source_paths.some((source_path) => {
410
+ const full_prefix = options.project_root + '/' + source_path + '/';
411
+ return path.startsWith(full_prefix);
101
412
  });
102
413
  if (!in_source_dir) return false;
103
414
 
104
- // Check if extension matches
105
- const has_valid_extension = opts.extensions.some((ext) => path.endsWith(ext));
106
- if (!has_valid_extension) return false;
415
+ // Check if file type is analyzable
416
+ return options.get_analyzer(path) !== null;
417
+ };
107
418
 
108
- // Check exclusion patterns
109
- const is_excluded = opts.exclude_patterns.some((pattern) => pattern.test(path));
110
- if (is_excluded) return false;
419
+ /**
420
+ * Extract dependencies and dependents for a module from source file info.
421
+ *
422
+ * Filters to only include source modules (excludes external packages, node_modules, tests).
423
+ * Returns sorted arrays of module paths (relative to source_root) for deterministic output.
424
+ *
425
+ * @param source_file The source file info to extract dependencies from
426
+ * @param options Module source options for filtering and path extraction
427
+ */
428
+ export const module_extract_dependencies = (
429
+ source_file: SourceFileInfo,
430
+ options: ModuleSourceOptions,
431
+ ): {dependencies: Array<string>; dependents: Array<string>} => {
432
+ const dependencies: Array<string> = [];
433
+ const dependents: Array<string> = [];
434
+
435
+ // Extract dependencies (files this module imports) if provided
436
+ if (source_file.dependencies) {
437
+ for (const dep_id of source_file.dependencies) {
438
+ if (module_is_source(dep_id, options)) {
439
+ dependencies.push(module_extract_path(dep_id, options));
440
+ }
441
+ }
442
+ }
443
+
444
+ // Extract dependents (files that import this module) if provided
445
+ if (source_file.dependents) {
446
+ for (const dependent_id of source_file.dependents) {
447
+ if (module_is_source(dependent_id, options)) {
448
+ dependents.push(module_extract_path(dependent_id, options));
449
+ }
450
+ }
451
+ }
452
+
453
+ // Sort for deterministic output
454
+ dependencies.sort();
455
+ dependents.sort();
111
456
 
112
- return true;
457
+ return {dependencies, dependents};
113
458
  };