@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.
- package/README.md +1 -1
- package/dist/Alert.svelte +2 -0
- package/dist/Alert.svelte.d.ts.map +1 -1
- package/dist/ApiModule.svelte +5 -6
- package/dist/ApiModule.svelte.d.ts.map +1 -1
- package/dist/DeclarationDetail.svelte +2 -1
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
- package/dist/Details.svelte +2 -0
- package/dist/Details.svelte.d.ts.map +1 -1
- package/dist/EcosystemLinks.svelte +3 -3
- package/dist/EcosystemLinks.svelte.d.ts +1 -1
- package/dist/EcosystemLinks.svelte.d.ts.map +1 -1
- package/dist/Themed.svelte +2 -0
- package/dist/Themed.svelte.d.ts.map +1 -1
- package/dist/analysis_context.d.ts +195 -0
- package/dist/analysis_context.d.ts.map +1 -0
- package/dist/analysis_context.js +134 -0
- package/dist/library_analysis.d.ts +112 -0
- package/dist/library_analysis.d.ts.map +1 -0
- package/dist/library_analysis.js +106 -0
- package/dist/library_gen.d.ts +73 -5
- package/dist/library_gen.d.ts.map +1 -1
- package/dist/library_gen.js +130 -68
- package/dist/library_gen_helpers.d.ts +81 -72
- package/dist/library_gen_helpers.d.ts.map +1 -1
- package/dist/library_gen_helpers.js +115 -156
- package/dist/library_gen_output.d.ts +34 -0
- package/dist/library_gen_output.d.ts.map +1 -0
- package/dist/library_gen_output.js +40 -0
- package/dist/mdz.d.ts +3 -0
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +12 -3
- package/dist/module_helpers.d.ts +246 -24
- package/dist/module_helpers.d.ts.map +1 -1
- package/dist/module_helpers.js +250 -42
- package/dist/svelte_helpers.d.ts +65 -10
- package/dist/svelte_helpers.d.ts.map +1 -1
- package/dist/svelte_helpers.js +171 -49
- package/dist/ts_helpers.d.ts +132 -61
- package/dist/ts_helpers.d.ts.map +1 -1
- package/dist/ts_helpers.js +423 -282
- package/dist/tsdoc_helpers.d.ts +11 -0
- package/dist/tsdoc_helpers.d.ts.map +1 -1
- package/dist/tsdoc_helpers.js +22 -47
- package/dist/tsdoc_mdz.d.ts +36 -0
- package/dist/tsdoc_mdz.d.ts.map +1 -0
- package/dist/tsdoc_mdz.js +56 -0
- package/dist/vite_plugin_library_well_known.js +5 -5
- package/package.json +12 -7
- package/src/lib/analysis_context.ts +250 -0
- package/src/lib/library_analysis.ts +168 -0
- package/src/lib/library_gen.ts +199 -83
- package/src/lib/library_gen_helpers.ts +148 -215
- package/src/lib/library_gen_output.ts +63 -0
- package/src/lib/mdz.ts +13 -4
- package/src/lib/module_helpers.ts +392 -47
- package/src/lib/svelte_helpers.ts +291 -55
- package/src/lib/ts_helpers.ts +538 -338
- package/src/lib/tsdoc_helpers.ts +24 -49
- package/src/lib/tsdoc_mdz.ts +62 -0
- package/src/lib/vite_plugin_library_well_known.ts +5 -5
|
@@ -1,285 +1,218 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Library metadata generation helpers - pipeline orchestration.
|
|
3
3
|
*
|
|
4
|
-
* These functions handle
|
|
5
|
-
*
|
|
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
|
-
* - `
|
|
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
|
-
*
|
|
12
|
-
*
|
|
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
|
|
15
|
-
* @see
|
|
16
|
-
* @see
|
|
17
|
-
*
|
|
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
|
|
29
|
-
import {svelte_analyze_file} from './svelte_helpers.js';
|
|
26
|
+
import type {ReExportInfo} from './library_analysis.js';
|
|
30
27
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
*
|
|
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
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
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
|
-
*
|
|
50
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
55
|
-
|
|
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
|
|
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 (!
|
|
62
|
-
|
|
72
|
+
if (!all_occurrences.has(name)) {
|
|
73
|
+
all_occurrences.set(name, []);
|
|
63
74
|
}
|
|
64
|
-
|
|
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
|
-
//
|
|
72
|
-
const duplicates:
|
|
73
|
-
for (const [name,
|
|
74
|
-
if (
|
|
75
|
-
duplicates.
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
106
|
-
*
|
|
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
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
|
|
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
|
-
*
|
|
117
|
-
*
|
|
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
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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
|
|
126
|
-
package_json: PackageJson,
|
|
138
|
+
export const library_merge_re_exports = (
|
|
127
139
|
source_json: SourceJson,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
146
|
+
for (const {re_exporting_module, re_export} of collected_re_exports) {
|
|
147
|
+
const {name, original_module} = re_export;
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
*
|
|
176
|
+
* Collect and filter source files.
|
|
190
177
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
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
|
-
*
|
|
217
|
-
*
|
|
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
|
-
*
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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);
|