@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
@@ -1,285 +1,218 @@
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
22
 
20
- import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
21
23
  import type {Logger} from '@fuzdev/fuz_util/log.js';
22
- import type {ModuleJson, SourceJson} from '@fuzdev/fuz_util/source_json.js';
23
- import {library_json_parse, type LibraryJson} from '@fuzdev/fuz_util/library_json.js';
24
- import type {Disknode} from '@ryanatkn/gro/disknode.js';
25
- import type ts from 'typescript';
26
- import type {PathId} from '@fuzdev/fuz_util/path.js';
24
+ import type {DeclarationJson, ModuleJson, SourceJson} from '@fuzdev/fuz_util/source_json.js';
27
25
 
28
- import {ts_analyze_module_exports, type ReExportInfo} from './ts_helpers.js';
29
- import {svelte_analyze_file} from './svelte_helpers.js';
26
+ import type {ReExportInfo} from './library_analysis.js';
30
27
  import {
31
- module_extract_path,
32
- module_is_typescript,
33
- module_is_svelte,
34
- module_matches_source,
28
+ type SourceFileInfo,
29
+ type ModuleSourceOptions,
30
+ module_is_source,
31
+ module_validate_source_options,
32
+ module_get_source_root,
35
33
  } from './module_helpers.js';
36
34
 
37
35
  /**
38
- * Result of analyzing a TypeScript file.
39
- * Includes both the module metadata and re-export information for post-processing.
36
+ * A duplicate declaration with its full metadata and module path.
40
37
  */
41
- export interface TsFileAnalysis {
42
- /** Module metadata for inclusion in source_json. */
43
- module: ModuleJson;
44
- /** Re-exports from this module for building also_exported_from. */
45
- re_exports: Array<ReExportInfo>;
38
+ export interface DuplicateInfo {
39
+ /** The full declaration metadata. */
40
+ declaration: DeclarationJson;
41
+ /** Module path where this declaration is defined. */
42
+ module: string;
46
43
  }
47
44
 
48
45
  /**
49
- * Validates that no declaration names are duplicated across modules.
50
- * The flat namespace is intentional - duplicates should fail fast.
46
+ * Find duplicate declaration names across modules.
47
+ *
48
+ * Returns a Map of declaration names to their full metadata (only includes duplicates).
49
+ * Callers can decide how to handle duplicates (throw, warn, ignore).
51
50
  *
52
- * @throws Error if duplicate declaration names are found
51
+ * @example
52
+ * const duplicates = library_find_duplicates(source_json);
53
+ * if (duplicates.size > 0) {
54
+ * for (const [name, occurrences] of duplicates) {
55
+ * console.error(`"${name}" found in:`);
56
+ * for (const {declaration, module} of occurrences) {
57
+ * console.error(` - ${module}:${declaration.source_line} (${declaration.kind})`);
58
+ * }
59
+ * }
60
+ * throw new Error(`Found ${duplicates.size} duplicate declaration names`);
61
+ * }
53
62
  */
54
- export const library_gen_validate_no_duplicates = (source_json: SourceJson, log: Logger): void => {
55
- const declaration_locations: Map<string, Array<{module: string; kind: string}>> = new Map();
63
+ export const library_find_duplicates = (
64
+ source_json: SourceJson,
65
+ ): Map<string, Array<DuplicateInfo>> => {
66
+ const all_occurrences: Map<string, Array<DuplicateInfo>> = new Map();
56
67
 
57
- // Collect all declaration names and their locations
68
+ // Collect all declaration names and their full metadata
58
69
  for (const mod of source_json.modules ?? []) {
59
70
  for (const declaration of mod.declarations ?? []) {
60
71
  const name = declaration.name;
61
- if (!declaration_locations.has(name)) {
62
- declaration_locations.set(name, []);
72
+ if (!all_occurrences.has(name)) {
73
+ all_occurrences.set(name, []);
63
74
  }
64
- declaration_locations.get(name)!.push({
75
+ all_occurrences.get(name)!.push({
76
+ declaration,
65
77
  module: mod.path,
66
- kind: declaration.kind,
67
78
  });
68
79
  }
69
80
  }
70
81
 
71
- // Check for duplicates
72
- const duplicates: Array<{name: string; locations: Array<{module: string; kind: string}>}> = [];
73
- for (const [name, locations] of declaration_locations.entries()) {
74
- if (locations.length > 1) {
75
- duplicates.push({name, locations});
82
+ // Filter to only duplicates
83
+ const duplicates: Map<string, Array<DuplicateInfo>> = new Map();
84
+ for (const [name, occurrences] of all_occurrences) {
85
+ if (occurrences.length > 1) {
86
+ duplicates.set(name, occurrences);
76
87
  }
77
88
  }
78
89
 
79
- if (duplicates.length > 0) {
80
- log.error('Duplicate declaration names detected in flat namespace:');
81
- for (const {name, locations} of duplicates) {
82
- log.error(` "${name}" found in:`);
83
- for (const {module, kind} of locations) {
84
- log.error(` - ${module} (${kind})`);
85
- }
86
- }
87
- throw new Error(
88
- `Found ${duplicates.length} duplicate declaration name${duplicates.length === 1 ? '' : 's'} across modules. ` +
89
- 'The flat namespace requires unique names. To resolve: ' +
90
- '(1) rename one of the conflicting declarations, or ' +
91
- '(2) add /** @nodocs */ to exclude from documentation. ' +
92
- 'See CLAUDE.md "Declaration namespacing" section for details.',
93
- );
94
- }
90
+ return duplicates;
95
91
  };
96
92
 
97
93
  /**
98
94
  * Sort modules alphabetically by path for deterministic output and cleaner diffs.
99
95
  */
100
- export const library_gen_sort_modules = (modules: Array<ModuleJson>): Array<ModuleJson> => {
96
+ export const library_sort_modules = (modules: Array<ModuleJson>): Array<ModuleJson> => {
101
97
  return modules.slice().sort((a, b) => a.path.localeCompare(b.path));
102
98
  };
103
99
 
104
100
  /**
105
- * Result of generating library files.
106
- * Contains both the JSON data and the TypeScript wrapper file.
101
+ * A collected re-export with its source module context.
102
+ *
103
+ * Used during the two-phase re-export resolution:
104
+ * 1. Phase 1: Collect re-exports from each module during analysis
105
+ * 2. Phase 2: Group by original module and merge into `also_exported_from`
107
106
  */
108
- export interface LibraryGenOutput {
109
- /** JSON content for library.json */
110
- json_content: string;
111
- /** TypeScript wrapper content for library.ts */
112
- ts_content: string;
107
+ export interface CollectedReExport {
108
+ /** The module that re-exports the declaration. */
109
+ re_exporting_module: string;
110
+ /** The re-export info (name and original module). */
111
+ re_export: ReExportInfo;
113
112
  }
114
113
 
115
114
  /**
116
- * Generate the library.json and library.ts file contents.
117
- * Parses at generation time so runtime only needs the pre-computed result.
115
+ * Build `also_exported_from` arrays from collected re-export data.
116
+ *
117
+ * This function resolves the two-phase re-export problem:
118
118
  *
119
- * Returns JSON + .ts wrapper because:
120
- * - JSON is natively importable by Node.js and Vite without TypeScript loaders
121
- * - Works in CI environments that don't have TS compilation
122
- * - The .ts wrapper validates with zod and exports with proper types
123
- * (JSON imports get widened types like `string` instead of literal unions)
119
+ * **Problem**: When module A re-exports from module B, we discover this while
120
+ * analyzing A, but need to update B's declarations. However, B may already be
121
+ * processed or may be processed later.
122
+ *
123
+ * **Solution**: Collect all re-exports in phase 1, then merge them in phase 2
124
+ * after all modules are analyzed.
125
+ *
126
+ * @example
127
+ * // helpers.ts exports: foo, bar
128
+ * // index.ts does: export {foo, bar} from './helpers.js'
129
+ * //
130
+ * // After processing:
131
+ * // - helpers.ts foo declaration gets: also_exported_from: ['index.ts']
132
+ * // - helpers.ts bar declaration gets: also_exported_from: ['index.ts']
133
+ *
134
+ * @param source_json The source JSON with all modules (will be mutated)
135
+ * @param collected_re_exports Array of re-exports collected during phase 1
136
+ * @mutates source_json - adds `also_exported_from` to declarations
124
137
  */
125
- export const library_gen_generate_json = (
126
- package_json: PackageJson,
138
+ export const library_merge_re_exports = (
127
139
  source_json: SourceJson,
128
- ): LibraryGenOutput => {
129
- const is_this_fuz_util = package_json.name === '@fuzdev/fuz_util';
130
- const fuz_util_prefix = is_this_fuz_util ? './' : '@fuzdev/fuz_util/';
131
-
132
- // Parse at generation time, not runtime
133
- const library_json: LibraryJson = library_json_parse(package_json, source_json);
134
-
135
- const json_content = JSON.stringify(library_json, null, '\t') + '\n';
136
-
137
- const banner = '// generated by library.gen.ts - do not edit';
138
-
139
- const ts_content = `${banner}
140
+ collected_re_exports: Array<CollectedReExport>,
141
+ ): void => {
142
+ // Group re-exports by original module and declaration name
143
+ // Structure: Map<original_module_path, Map<declaration_name, Array<re_exporting_module_path>>>
144
+ const re_export_map: Map<string, Map<string, Array<string>>> = new Map();
140
145
 
141
- import type {LibraryJson} from '${fuz_util_prefix}library_json.js';
146
+ for (const {re_exporting_module, re_export} of collected_re_exports) {
147
+ const {name, original_module} = re_export;
142
148
 
143
- import json from './library.json' with {type: 'json'};
144
-
145
- export const library_json: LibraryJson = json as LibraryJson;
146
-
147
- ${banner}
148
- `;
149
-
150
- return {json_content, ts_content};
151
- };
152
-
153
- /**
154
- * Collect and filter source files from filer.
155
- *
156
- * Returns disknodes for TypeScript/JS files and Svelte components from src/lib, excluding test files.
157
- * Returns an empty array with a warning if no source files are found.
158
- */
159
- export const library_gen_collect_source_files = (
160
- files: Map<PathId, Disknode>,
161
- log: Logger,
162
- ): Array<Disknode> => {
163
- log.info(`filer has ${files.size} files total`);
149
+ if (!re_export_map.has(original_module)) {
150
+ re_export_map.set(original_module, new Map());
151
+ }
152
+ const module_map = re_export_map.get(original_module)!;
164
153
 
165
- const source_disknodes: Array<Disknode> = [];
166
- for (const [id, disknode] of files.entries()) {
167
- if (module_matches_source(id)) {
168
- // Include TypeScript/JS files and Svelte components
169
- if (module_is_typescript(id) || module_is_svelte(id)) {
170
- source_disknodes.push(disknode);
171
- }
154
+ if (!module_map.has(name)) {
155
+ module_map.set(name, []);
172
156
  }
157
+ module_map.get(name)!.push(re_exporting_module);
173
158
  }
174
159
 
175
- log.info(`found ${source_disknodes.length} source files to analyze`);
160
+ // Merge into original declarations
161
+ for (const mod of source_json.modules ?? []) {
162
+ const module_re_exports = re_export_map.get(mod.path);
163
+ if (!module_re_exports) continue;
176
164
 
177
- if (source_disknodes.length === 0) {
178
- log.warn('No source files found in src/lib - generating empty library metadata');
179
- return [];
165
+ for (const declaration of mod.declarations ?? []) {
166
+ const re_exporters = module_re_exports.get(declaration.name);
167
+ if (re_exporters?.length) {
168
+ // Sort for deterministic output
169
+ declaration.also_exported_from = re_exporters.sort();
170
+ }
171
+ }
180
172
  }
181
-
182
- // Sort for deterministic output (stable alphabetical module ordering)
183
- source_disknodes.sort((a, b) => a.id.localeCompare(b.id));
184
-
185
- return source_disknodes;
186
173
  };
187
174
 
188
175
  /**
189
- * Analyze a Svelte component file and extract metadata.
176
+ * Collect and filter source files.
190
177
  *
191
- * Uses `svelte_analyze_file` for core analysis, then adds
192
- * Gro-specific dependency information from the disknode.
193
- */
194
- export const library_gen_analyze_svelte_file = (
195
- disknode: Disknode,
196
- module_path: string,
197
- checker: ts.TypeChecker,
198
- ): ModuleJson => {
199
- // Use the extracted helper for core analysis
200
- const declaration_json = svelte_analyze_file(disknode.id, module_path, checker);
201
-
202
- // Extract dependencies and dependents (Gro-specific)
203
- const {dependencies, dependents} = library_gen_extract_dependencies(disknode);
204
-
205
- return {
206
- path: module_path,
207
- declarations: [declaration_json],
208
- dependencies: dependencies.length > 0 ? dependencies : undefined,
209
- dependents: dependents.length > 0 ? dependents : undefined,
210
- };
211
- };
212
-
213
- /**
214
- * Analyze a TypeScript file and extract all declarations.
178
+ * Returns source files for TypeScript/JS files and Svelte components, excluding test files.
179
+ * Returns an empty array with a warning if no source files are found.
215
180
  *
216
- * Uses `ts_analyze_module_exports` for core analysis, then adds
217
- * Gro-specific dependency information from the disknode.
181
+ * File types are determined by `options.get_analyzer`. By default, `.ts`, `.js`, and `.svelte`
182
+ * files are supported. Customize `get_analyzer` to support additional file types like `.svx`.
218
183
  *
219
- * Returns both the module metadata and re-export information for post-processing.
184
+ * @param files Iterable of source file info (from Gro filer, file system, or other source)
185
+ * @param options Module source options for filtering
186
+ * @param log Optional logger for status messages
220
187
  */
221
- export const library_gen_analyze_typescript_file = (
222
- disknode: Disknode,
223
- source_file: ts.SourceFile,
224
- module_path: string,
225
- checker: ts.TypeChecker,
226
- ): TsFileAnalysis => {
227
- // Use the extracted helper for core analysis
228
- const {module_comment, declarations, re_exports} = ts_analyze_module_exports(
229
- source_file,
230
- checker,
231
- );
232
-
233
- const mod: ModuleJson = {
234
- path: module_path,
235
- declarations,
236
- };
237
-
238
- if (module_comment) {
239
- mod.module_comment = module_comment;
240
- }
241
-
242
- // Extract dependencies and dependents (Gro-specific)
243
- const {dependencies, dependents} = library_gen_extract_dependencies(disknode);
244
- if (dependencies.length > 0) {
245
- mod.dependencies = dependencies;
246
- }
247
- if (dependents.length > 0) {
248
- mod.dependents = dependents;
249
- }
250
-
251
- return {module: mod, re_exports};
252
- };
253
-
254
- /**
255
- * Extract dependencies and dependents for a module from the filer's dependency graph.
256
- *
257
- * Filters to only include source modules from src/lib (excludes external packages, node_modules, tests).
258
- * Returns sorted arrays of module paths (relative to src/lib) for deterministic output.
259
- */
260
- export const library_gen_extract_dependencies = (
261
- disknode: Disknode,
262
- ): {dependencies: Array<string>; dependents: Array<string>} => {
263
- const dependencies: Array<string> = [];
264
- const dependents: Array<string> = [];
265
-
266
- // Extract dependencies (files this module imports)
267
- for (const dep_id of disknode.dependencies.keys()) {
268
- if (module_matches_source(dep_id)) {
269
- dependencies.push(module_extract_path(dep_id));
188
+ export const library_collect_source_files = (
189
+ files: Iterable<SourceFileInfo>,
190
+ options: ModuleSourceOptions,
191
+ log?: Logger,
192
+ ): Array<SourceFileInfo> => {
193
+ // Validate options early to fail fast on misconfiguration
194
+ module_validate_source_options(options);
195
+
196
+ const all_files = Array.from(files);
197
+ log?.info(`received ${all_files.length} files total`);
198
+
199
+ const source_files: Array<SourceFileInfo> = [];
200
+ for (const file of all_files) {
201
+ if (module_is_source(file.id, options)) {
202
+ source_files.push(file);
270
203
  }
271
204
  }
272
205
 
273
- // Extract dependents (files that import this module)
274
- for (const dependent_id of disknode.dependents.keys()) {
275
- if (module_matches_source(dependent_id)) {
276
- dependents.push(module_extract_path(dependent_id));
277
- }
206
+ log?.info(`found ${source_files.length} source files to analyze`);
207
+
208
+ if (source_files.length === 0) {
209
+ const effective_root = module_get_source_root(options);
210
+ log?.warn(`No source files found in ${effective_root} - generating empty library metadata`);
211
+ return [];
278
212
  }
279
213
 
280
- // Sort for deterministic output
281
- dependencies.sort();
282
- dependents.sort();
214
+ // Sort for deterministic output (stable alphabetical module ordering)
215
+ source_files.sort((a, b) => a.id.localeCompare(b.id));
283
216
 
284
- return {dependencies, dependents};
217
+ return source_files;
285
218
  };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Library output generation.
3
+ *
4
+ * Generates the library.json and library.ts files from analyzed metadata.
5
+ *
6
+ * @see library_gen_helpers.ts for orchestration functions
7
+ * @see library_gen.ts for the main generation task (Gro-specific)
8
+ *
9
+ * @module
10
+ */
11
+
12
+ import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
13
+ import type {SourceJson} from '@fuzdev/fuz_util/source_json.js';
14
+ import {library_json_parse, type LibraryJson} from '@fuzdev/fuz_util/library_json.js';
15
+
16
+ /**
17
+ * Result of generating library files.
18
+ * Contains both the JSON data and the TypeScript wrapper file.
19
+ */
20
+ export interface LibraryGenResult {
21
+ /** JSON content for library.json */
22
+ json_content: string;
23
+ /** TypeScript wrapper content for library.ts */
24
+ ts_content: string;
25
+ }
26
+
27
+ /**
28
+ * Generate the library.json and library.ts file contents.
29
+ * Parses at generation time so runtime only needs the pre-computed result.
30
+ *
31
+ * Returns JSON + .ts wrapper because:
32
+ * - JSON is natively importable by Node.js and Vite without TypeScript loaders
33
+ * - Works in CI environments that don't have TS compilation
34
+ * - The .ts wrapper validates with zod and exports with proper types
35
+ * (JSON imports get widened types like `string` instead of literal unions)
36
+ */
37
+ export const library_generate_json = (
38
+ package_json: PackageJson,
39
+ source_json: SourceJson,
40
+ ): LibraryGenResult => {
41
+ const is_this_fuz_util = package_json.name === '@fuzdev/fuz_util';
42
+ const fuz_util_prefix = is_this_fuz_util ? './' : '@fuzdev/fuz_util/';
43
+
44
+ // Parse at generation time, not runtime
45
+ const library_json: LibraryJson = library_json_parse(package_json, source_json);
46
+
47
+ const json_content = JSON.stringify(library_json, null, '\t') + '\n';
48
+
49
+ const banner = '// generated by library.gen.ts - do not edit';
50
+
51
+ const ts_content = `${banner}
52
+
53
+ import type {LibraryJson} from '${fuz_util_prefix}library_json.js';
54
+
55
+ import json from './library.json' with {type: 'json'};
56
+
57
+ export const library_json: LibraryJson = json as LibraryJson;
58
+
59
+ ${banner}
60
+ `;
61
+
62
+ return {json_content, ts_content};
63
+ };
package/src/lib/mdz.ts CHANGED
@@ -25,6 +25,8 @@
25
25
  * ## Status
26
26
  *
27
27
  * This is an early proof of concept with missing features and edge cases.
28
+ *
29
+ * @module
28
30
  */
29
31
 
30
32
  // TODO design incremental parsing or some system that preserves Svelte components across re-renders when possible
@@ -658,8 +660,7 @@ export class MdzParser {
658
660
  this.#index = close_paren + 1;
659
661
 
660
662
  // Determine link type (external vs internal)
661
- const link_type =
662
- reference.startsWith('https://') || reference.startsWith('http://') ? 'external' : 'internal';
663
+ const link_type = mdz_is_url(reference) ? 'external' : 'internal';
663
664
 
664
665
  return {
665
666
  type: 'Link',
@@ -1069,7 +1070,7 @@ export class MdzParser {
1069
1070
  }
1070
1071
 
1071
1072
  /**
1072
- * Check if current position is the start of an external URL (https:// or http://).
1073
+ * Check if current position is the start of an external URL (`https://` or `http://`).
1073
1074
  */
1074
1075
  #is_at_url(): boolean {
1075
1076
  if (this.#match('https://')) {
@@ -1119,7 +1120,7 @@ export class MdzParser {
1119
1120
  }
1120
1121
 
1121
1122
  /**
1122
- * Parse auto-detected external URL (https:// or http://).
1123
+ * Parse auto-detected external URL (`https://` or `http://`).
1123
1124
  * Uses RFC 3986 whitelist validation for valid URI characters.
1124
1125
  */
1125
1126
  #parse_auto_link_url(): MdzLinkNode {
@@ -1817,3 +1818,11 @@ export class MdzParser {
1817
1818
  throw Error('Code block not properly closed');
1818
1819
  }
1819
1820
  }
1821
+
1822
+ /**
1823
+ * Check if a string is a URL (`https://` or `http://`).
1824
+ * Requires at least one valid character after the protocol.
1825
+ * Rejects whitespace and characters that can't start a valid hostname.
1826
+ */
1827
+ const URL_PATTERN = /^https?:\/\/[^\s)\]}<>.,:/?#!]/;
1828
+ export const mdz_is_url = (s: string): boolean => URL_PATTERN.test(s);