@ryanatkn/gro 0.156.0 → 0.157.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/constants.d.ts +6 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +6 -0
  4. package/dist/esbuild_plugin_svelte.d.ts.map +1 -1
  5. package/dist/esbuild_plugin_svelte.js +1 -2
  6. package/dist/gro_plugin_sveltekit_app.js +1 -1
  7. package/dist/loader.d.ts.map +1 -1
  8. package/dist/loader.js +1 -2
  9. package/dist/package.d.ts +44 -13
  10. package/dist/package.d.ts.map +1 -1
  11. package/dist/package.gen.js +1 -1
  12. package/dist/package.js +44 -22
  13. package/dist/parse_exports.d.ts +20 -0
  14. package/dist/parse_exports.d.ts.map +1 -0
  15. package/dist/parse_exports.js +65 -0
  16. package/dist/parse_exports_context.d.ts +21 -0
  17. package/dist/parse_exports_context.d.ts.map +1 -0
  18. package/dist/parse_exports_context.js +332 -0
  19. package/dist/parse_imports.d.ts.map +1 -1
  20. package/dist/parse_imports.js +1 -2
  21. package/dist/paths.d.ts +8 -0
  22. package/dist/paths.d.ts.map +1 -1
  23. package/dist/paths.js +6 -3
  24. package/dist/src_json.d.ts +68 -66
  25. package/dist/src_json.d.ts.map +1 -1
  26. package/dist/src_json.js +58 -55
  27. package/dist/test_helpers.d.ts +21 -0
  28. package/dist/test_helpers.d.ts.map +1 -0
  29. package/dist/test_helpers.js +122 -0
  30. package/package.json +21 -13
  31. package/src/lib/constants.ts +6 -0
  32. package/src/lib/esbuild_plugin_svelte.ts +1 -2
  33. package/src/lib/gro_plugin_sveltekit_app.ts +1 -1
  34. package/src/lib/loader.ts +6 -2
  35. package/src/lib/package.gen.ts +1 -1
  36. package/src/lib/package.ts +44 -22
  37. package/src/lib/parse_exports.ts +108 -0
  38. package/src/lib/parse_exports_context.ts +394 -0
  39. package/src/lib/parse_imports.ts +1 -2
  40. package/src/lib/paths.ts +13 -3
  41. package/src/lib/src_json.ts +80 -68
  42. package/src/lib/test_helpers.ts +159 -0
  43. package/dist/svelte_helpers.d.ts +0 -3
  44. package/dist/svelte_helpers.d.ts.map +0 -1
  45. package/dist/svelte_helpers.js +0 -2
  46. package/src/lib/svelte_helpers.ts +0 -2
@@ -0,0 +1,394 @@
1
+ import ts from 'typescript';
2
+ import type {Logger} from '@ryanatkn/belt/log.js';
3
+
4
+ import type {Declaration_Kind, Export_Declaration} from './parse_exports.ts';
5
+
6
+ /**
7
+ * A class to track export context and determine export kinds.
8
+ */
9
+ export class Parse_Exports_Context {
10
+ readonly #checker: ts.TypeChecker;
11
+
12
+ // Map of source file paths to their symbols
13
+ readonly #file_symbols: Map<string, ts.Symbol> = new Map();
14
+ // Cache for resolved symbols to avoid repeated resolution
15
+ readonly #symbol_kind_cache: Map<ts.Symbol, Declaration_Kind> = new Map();
16
+
17
+ readonly log: Logger | undefined;
18
+ debug = process.env.DEBUG_EXPORTS === 'true';
19
+
20
+ constructor(program: ts.Program, log?: Logger) {
21
+ this.log = log;
22
+ this.#checker = program.getTypeChecker();
23
+ }
24
+
25
+ /**
26
+ * Log a debug message if debug mode is enabled.
27
+ */
28
+ #log(...args: Array<unknown>): void {
29
+ if (this.debug && this.log) {
30
+ this.log.info(...args);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Analyze a source file to prepare for export processing.
36
+ */
37
+ analyze_source_file(source_file: ts.SourceFile): void {
38
+ const file_path = source_file.fileName;
39
+
40
+ // Skip if we've already analyzed this file
41
+ if (this.#file_symbols.has(file_path)) {
42
+ return;
43
+ }
44
+
45
+ // Get the source file symbol and cache it
46
+ const symbol = this.#checker.getSymbolAtLocation(source_file);
47
+ if (symbol) {
48
+ this.#file_symbols.set(file_path, symbol);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Process a list of exported symbols and identify their kinds.
54
+ */
55
+ process_exports(
56
+ source_file: ts.SourceFile,
57
+ exports: Array<ts.Symbol>,
58
+ declarations: Array<Export_Declaration> = [],
59
+ ): Array<Export_Declaration> {
60
+ for (const export_symbol of exports) {
61
+ const name = export_symbol.name;
62
+ this.#log(`Determining kind for export: ${name}`);
63
+
64
+ const kind = this.#determine_export_kind(source_file, export_symbol);
65
+ declarations.push({
66
+ name,
67
+ kind,
68
+ });
69
+ }
70
+
71
+ return declarations;
72
+ }
73
+
74
+ /**
75
+ * Determine the kind of an export based on its symbol.
76
+ */
77
+ #determine_export_kind(source_file: ts.SourceFile, symbol: ts.Symbol): Declaration_Kind {
78
+ // Check if this is a type-only export (no value export)
79
+ if (this.#is_type_only_export(source_file, symbol)) {
80
+ return 'type';
81
+ }
82
+
83
+ // Get the true symbol by resolving aliases
84
+ const resolved_symbol = this.#resolve_symbol(symbol);
85
+
86
+ // Check if we've already determined this symbol's kind
87
+ if (this.#symbol_kind_cache.has(resolved_symbol)) {
88
+ return this.#symbol_kind_cache.get(resolved_symbol)!;
89
+ }
90
+
91
+ // Determine the kind based on declaration and type information
92
+ const kind = this.#infer_declaration_kind(resolved_symbol);
93
+
94
+ // Cache the result for future lookups
95
+ this.#symbol_kind_cache.set(resolved_symbol, kind);
96
+
97
+ return kind;
98
+ }
99
+
100
+ /**
101
+ * Resolve a symbol through aliases to its original declaration.
102
+ */
103
+ #resolve_symbol(symbol: ts.Symbol): ts.Symbol {
104
+ try {
105
+ if (symbol.flags & ts.SymbolFlags.Alias) {
106
+ return this.#checker.getAliasedSymbol(symbol);
107
+ }
108
+ } catch {
109
+ // If resolution fails, return the original symbol
110
+ }
111
+ return symbol;
112
+ }
113
+
114
+ /**
115
+ * Infer the declaration kind from a symbol's declaration and type information.
116
+ */
117
+ #infer_declaration_kind(symbol: ts.Symbol): Declaration_Kind {
118
+ // Check symbol flags first for direct type matching
119
+ if (this.#is_class_symbol(symbol)) {
120
+ return 'class';
121
+ }
122
+
123
+ if (this.#is_function_symbol(symbol)) {
124
+ return 'function';
125
+ }
126
+
127
+ // If no direct match from flags, look at declarations
128
+ if (symbol.declarations && symbol.declarations.length > 0) {
129
+ const decl = symbol.declarations[0];
130
+ const kind_from_decl = this.#infer_kind_from_declaration(decl);
131
+ if (kind_from_decl) {
132
+ return kind_from_decl;
133
+ }
134
+ }
135
+
136
+ // Check for callable type as a fallback for functions
137
+ if (this.#is_callable(symbol)) {
138
+ return 'function';
139
+ }
140
+
141
+ // Default to variable if no other type can be determined
142
+ return 'variable';
143
+ }
144
+
145
+ /**
146
+ * Check if a symbol represents a callable type (function-like).
147
+ */
148
+ #is_callable(symbol: ts.Symbol): boolean {
149
+ try {
150
+ // Try to get valid declaration node
151
+ const declaration = this.#get_valid_declaration(symbol);
152
+ if (!declaration) {
153
+ return false;
154
+ }
155
+
156
+ // Get the type at the declaration location
157
+ const type = this.#checker.getTypeOfSymbolAtLocation(symbol, declaration);
158
+
159
+ // Check if the type has call signatures (making it function-like)
160
+ return type.getCallSignatures().length > 0;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get a valid declaration for a symbol, preferring valueDeclaration.
168
+ */
169
+ #get_valid_declaration(symbol: ts.Symbol): ts.Node | undefined {
170
+ if (symbol.valueDeclaration) {
171
+ return symbol.valueDeclaration;
172
+ }
173
+
174
+ if (symbol.declarations && symbol.declarations.length > 0) {
175
+ return symbol.declarations[0];
176
+ }
177
+
178
+ return undefined;
179
+ }
180
+
181
+ /**
182
+ * Infer the declaration kind from a specific declaration node.
183
+ */
184
+ #infer_kind_from_declaration(decl: ts.Declaration): Declaration_Kind | null {
185
+ if (ts.isFunctionDeclaration(decl)) {
186
+ return 'function';
187
+ }
188
+
189
+ if (ts.isClassDeclaration(decl)) {
190
+ return 'class';
191
+ }
192
+
193
+ if (ts.isInterfaceDeclaration(decl) || ts.isTypeAliasDeclaration(decl)) {
194
+ return 'type';
195
+ }
196
+
197
+ if (ts.isVariableDeclaration(decl)) {
198
+ // Handle initializers for variable declarations
199
+ if (decl.initializer) {
200
+ if (ts.isFunctionExpression(decl.initializer) || ts.isArrowFunction(decl.initializer)) {
201
+ return 'function';
202
+ }
203
+
204
+ if (ts.isClassExpression(decl.initializer)) {
205
+ return 'class';
206
+ }
207
+
208
+ // Handle identifiers pointing to other declarations
209
+ if (ts.isIdentifier(decl.initializer)) {
210
+ try {
211
+ const referred_symbol = this.#checker.getSymbolAtLocation(decl.initializer);
212
+ if (referred_symbol) {
213
+ // Avoid infinite recursion by not resolving symbols here
214
+ if (this.#is_function_symbol(referred_symbol)) {
215
+ return 'function';
216
+ }
217
+ if (this.#is_class_symbol(referred_symbol)) {
218
+ return 'class';
219
+ }
220
+ }
221
+ } catch {
222
+ // Ignore failures to resolve identifiers
223
+ }
224
+ }
225
+ }
226
+
227
+ // As a fallback, check if the variable's type is callable
228
+ try {
229
+ const symbol = this.#checker.getSymbolAtLocation(decl.name);
230
+ if (symbol && this.#is_callable(symbol)) {
231
+ return 'function';
232
+ }
233
+ } catch {
234
+ // Ignore errors in type checking
235
+ }
236
+ }
237
+
238
+ return null;
239
+ }
240
+
241
+ /**
242
+ * Check if a symbol is exported as a type-only export.
243
+ * A type-only export means it's ONLY exported as a type with no value export.
244
+ */
245
+ #is_type_only_export(source_file: ts.SourceFile, symbol: ts.Symbol): boolean {
246
+ // First, check if the symbol has an explicit type-only export
247
+ let has_type_only_export = false;
248
+
249
+ // Check if it has a corresponding value export
250
+ const has_value_export = this.#has_value_export(source_file, symbol);
251
+
252
+ // If it has both type and value exports (dual purpose), it's not type-only
253
+ if (has_value_export) {
254
+ return false;
255
+ }
256
+
257
+ // Check export declarations for explicit type-only exports
258
+ ts.forEachChild(source_file, (node) => {
259
+ if (
260
+ ts.isExportDeclaration(node) &&
261
+ node.exportClause &&
262
+ ts.isNamedExports(node.exportClause)
263
+ ) {
264
+ // Check if it's a type-only export declaration (export type {...})
265
+ if (node.isTypeOnly) {
266
+ for (const specifier of node.exportClause.elements) {
267
+ if (specifier.name.text === symbol.name) {
268
+ has_type_only_export = true;
269
+ }
270
+ }
271
+ } else {
272
+ // Check if it's a specific type export (export {type X})
273
+ for (const specifier of node.exportClause.elements) {
274
+ if (specifier.name.text === symbol.name && specifier.isTypeOnly) {
275
+ has_type_only_export = true;
276
+ }
277
+ }
278
+ }
279
+ }
280
+ });
281
+
282
+ // If explicitly marked as a type-only export, use that
283
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
284
+ if (has_type_only_export) {
285
+ return true;
286
+ }
287
+
288
+ // If not explicitly marked as type-only, check if the symbol itself is a type
289
+ // AND there's no value export for it
290
+ const resolved_symbol = this.#resolve_symbol(symbol);
291
+ return this.#is_type_symbol(resolved_symbol) && !has_value_export;
292
+ }
293
+
294
+ /**
295
+ * Check if a symbol has a value export in the source file.
296
+ */
297
+ #has_value_export(source_file: ts.SourceFile, symbol: ts.Symbol): boolean {
298
+ let has_value_export = false;
299
+
300
+ // Check export declarations
301
+ ts.forEachChild(source_file, (node) => {
302
+ if (
303
+ ts.isExportDeclaration(node) &&
304
+ node.exportClause &&
305
+ ts.isNamedExports(node.exportClause)
306
+ ) {
307
+ // Skip type-only exports
308
+ if (node.isTypeOnly) return;
309
+
310
+ // Check if it's a regular export (not type-only)
311
+ for (const specifier of node.exportClause.elements) {
312
+ if (specifier.name.text === symbol.name && !specifier.isTypeOnly) {
313
+ has_value_export = true;
314
+ }
315
+ }
316
+ }
317
+ // Check for default export
318
+ else if (ts.isExportAssignment(node) && symbol.name === 'default') {
319
+ has_value_export = true;
320
+ }
321
+ // Check for direct exports (export const x = ...)
322
+ else if (this.#is_direct_export(node) && this.#get_export_name(node) === symbol.name) {
323
+ has_value_export = true;
324
+ }
325
+ });
326
+
327
+ return has_value_export;
328
+ }
329
+
330
+ /**
331
+ * Check if a node is a direct export (export const/function/class).
332
+ */
333
+ #is_direct_export(node: ts.Node): boolean {
334
+ return (
335
+ (ts.isVariableStatement(node) ||
336
+ ts.isFunctionDeclaration(node) ||
337
+ ts.isClassDeclaration(node)) &&
338
+ this.#has_export_modifier(node)
339
+ );
340
+ }
341
+
342
+ /**
343
+ * Get the export name from a direct export node.
344
+ */
345
+ #get_export_name(node: ts.Node): string | undefined {
346
+ if (ts.isVariableStatement(node) && node.declarationList.declarations.length > 0) {
347
+ const decl = node.declarationList.declarations[0];
348
+ if (ts.isIdentifier(decl.name)) {
349
+ return decl.name.text;
350
+ }
351
+ } else if (ts.isFunctionDeclaration(node) && node.name) {
352
+ return node.name.text;
353
+ } else if (ts.isClassDeclaration(node) && node.name) {
354
+ return node.name.text;
355
+ }
356
+ return undefined;
357
+ }
358
+
359
+ /**
360
+ * Check if a node has an export modifier.
361
+ */
362
+ #has_export_modifier(node: ts.Node): boolean {
363
+ return (
364
+ (ts.canHaveModifiers(node) &&
365
+ ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) ||
366
+ false
367
+ );
368
+ }
369
+
370
+ /**
371
+ * Check if a symbol is a function symbol.
372
+ */
373
+ #is_function_symbol(symbol: ts.Symbol): boolean {
374
+ return !!(symbol.flags & ts.SymbolFlags.Function || symbol.flags & ts.SymbolFlags.Method);
375
+ }
376
+
377
+ /**
378
+ * Check if a symbol is a class symbol.
379
+ */
380
+ #is_class_symbol(symbol: ts.Symbol): boolean {
381
+ return !!(symbol.flags & ts.SymbolFlags.Class);
382
+ }
383
+
384
+ /**
385
+ * Check if a symbol is a type-only symbol (interface, type alias, etc.).
386
+ */
387
+ #is_type_symbol(symbol: ts.Symbol): boolean {
388
+ return !!(
389
+ symbol.flags & ts.SymbolFlags.Interface ||
390
+ symbol.flags & ts.SymbolFlags.TypeAlias ||
391
+ symbol.flags & ts.SymbolFlags.TypeParameter
392
+ );
393
+ }
394
+ }
@@ -3,8 +3,7 @@ import type {Flavored} from '@ryanatkn/belt/types.js';
3
3
  import {Unreachable_Error} from '@ryanatkn/belt/error.js';
4
4
 
5
5
  import type {Path_Id} from './path.ts';
6
- import {SVELTE_MATCHER} from './svelte_helpers.ts';
7
- import {JS_MATCHER, TS_MATCHER, SVELTE_SCRIPT_MATCHER} from './constants.ts';
6
+ import {JS_MATCHER, TS_MATCHER, SVELTE_MATCHER, SVELTE_SCRIPT_MATCHER} from './constants.ts';
8
7
 
9
8
  export type Import_Specifier = Flavored<string, 'Import_Specifier'>;
10
9
 
package/src/lib/paths.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {join, extname, relative, basename} from 'node:path';
2
2
  import {fileURLToPath} from 'node:url';
3
- import {strip_end} from '@ryanatkn/belt/string.js';
3
+ import {ensure_end, strip_end} from '@ryanatkn/belt/string.js';
4
4
  import {styleText as st} from 'node:util';
5
5
 
6
6
  import {
@@ -22,21 +22,27 @@ It's the same name that Rollup uses.
22
22
 
23
23
  export const LIB_DIRNAME = basename(default_svelte_config.lib_path);
24
24
  export const LIB_PATH = SOURCE_DIR + LIB_DIRNAME;
25
+ /** @trailing_slash */
25
26
  export const LIB_DIR = LIB_PATH + '/';
26
27
  export const ROUTES_DIRNAME = basename(default_svelte_config.routes_path);
27
28
 
28
29
  export interface Paths {
30
+ /** @trailing_slash */
29
31
  root: string;
32
+ /** @trailing_slash */
30
33
  source: string;
34
+ /** @trailing_slash */
31
35
  lib: string;
36
+ /** @trailing_slash */
32
37
  build: string;
38
+ /** @trailing_slash */
33
39
  build_dev: string;
34
40
  config: string;
35
41
  }
36
42
 
37
43
  export const create_paths = (root_dir: string): Paths => {
38
44
  // TODO remove reliance on trailing slash towards windows support
39
- const root = strip_end(root_dir, '/') + '/';
45
+ const root = ensure_end(root_dir, '/');
40
46
  return {
41
47
  root,
42
48
  source: root + SOURCE_DIR,
@@ -48,7 +54,9 @@ export const create_paths = (root_dir: string): Paths => {
48
54
  };
49
55
 
50
56
  export const infer_paths = (id: Path_Id): Paths => (is_gro_id(id) ? gro_paths : paths);
51
- export const is_gro_id = (id: Path_Id): boolean => id.startsWith(strip_end(gro_paths.root, '/')); // strip `/` in case we're looking at the Gro root without a trailing slash
57
+
58
+ export const is_gro_id = (id: Path_Id): boolean =>
59
+ id.startsWith(gro_paths.root) || gro_paths.root === ensure_end(id, '/');
52
60
 
53
61
  // '/home/me/app/src/foo/bar/baz.ts' → 'src/foo/bar/baz.ts'
54
62
  export const to_root_path = (id: Path_Id, p = infer_paths(id)): string =>
@@ -81,6 +89,7 @@ export const replace_extension = (path: string, new_extension: string): string =
81
89
  */
82
90
  export const paths = create_paths(process.cwd());
83
91
 
92
+ /** @trailing_slash */
84
93
  export const GRO_PACKAGE_DIR = 'gro/';
85
94
  // TODO document these conditions with comments
86
95
  // TODO there's probably a more robust way to do this
@@ -98,4 +107,5 @@ export const IS_THIS_GRO = gro_package_dir_path === paths.root;
98
107
  * Paths for the Gro package being used by the user repo.
99
108
  */
100
109
  export const gro_paths = IS_THIS_GRO ? paths : create_paths(gro_package_dir_path);
110
+ /** @trailing_slash */
101
111
  export const GRO_DIST_DIR = gro_paths.root + SVELTEKIT_DIST_DIRNAME + '/';
@@ -1,8 +1,8 @@
1
1
  import {z} from 'zod';
2
- import {join} from 'node:path';
3
- import {strip_start} from '@ryanatkn/belt/string.js';
4
- import {Project} from 'ts-morph';
2
+ import {join, extname} from 'node:path';
3
+ import {ensure_end, strip_start} from '@ryanatkn/belt/string.js';
5
4
  import {existsSync} from 'node:fs';
5
+ import ts from 'typescript';
6
6
 
7
7
  import {paths, replace_extension} from './paths.ts';
8
8
  import {
@@ -10,6 +10,18 @@ import {
10
10
  type Package_Json,
11
11
  type Package_Json_Exports,
12
12
  } from './package_json.ts';
13
+ import {parse_exports} from './parse_exports.ts';
14
+
15
+ export const Src_Module_Declaration_Kind = z.enum([
16
+ 'type',
17
+ 'function',
18
+ 'variable',
19
+ 'class',
20
+ 'component',
21
+ 'json',
22
+ 'css',
23
+ ]);
24
+ export type Src_Module_Declaration_Kind = z.infer<typeof Src_Module_Declaration_Kind>;
13
25
 
14
26
  // TODO @many rename to prefix with `Src_Json_`?
15
27
  export const Src_Module_Declaration = z
@@ -17,7 +29,7 @@ export const Src_Module_Declaration = z
17
29
  name: z.string(), // the export identifier
18
30
  // TODO these are poorly named, and they're somewhat redundant with `kind`,
19
31
  // they were added to distinguish `VariableDeclaration` functions and non-functions
20
- kind: z.enum(['type', 'function', 'variable', 'class']).nullable(),
32
+ kind: Src_Module_Declaration_Kind.nullable(),
21
33
  // code: z.string(), // TODO experiment with `getType().getText()`, some of them return the same as `name`
22
34
  })
23
35
  .passthrough();
@@ -68,71 +80,71 @@ export const to_src_modules = (
68
80
  ): Src_Modules | undefined => {
69
81
  if (!exports) return;
70
82
 
71
- const project = new Project();
72
- project.addSourceFilesAtPaths(join(lib_path, '**/*.ts'));
73
-
74
- return Object.fromEntries(
75
- Object.entries(exports).map(([k, _v]) => {
76
- // TODO hacky - doesn't handle any but the typical mappings, also add a helper?
77
- const source_file_path =
78
- k === '.' || k === './'
79
- ? 'index.ts'
80
- : strip_start(k.endsWith('.js') ? replace_extension(k, '.ts') : k, './');
81
- if (!source_file_path.endsWith('.ts')) {
82
- // TODO support more than just TypeScript - maybe use @sveltejs/language-tools,
83
- // see how @sveltejs/package generates types, or maybe use its generated declaration files with ts-morph
84
- const src_module: Src_Module = {path: source_file_path, declarations: []};
85
- return [k, src_module];
86
- }
87
- const source_file_id = join(lib_path, source_file_path);
88
- if (!existsSync(source_file_id)) {
89
- throw Error(
90
- `Failed to infer source file from package.json export path ${k} - the inferred file ${source_file_id} does not exist`,
91
- );
92
- }
83
+ // Prepare a list of files to analyze
84
+ const file_paths: Array<{export_key: string; file_path: string}> = [];
85
+ for (const [k, _v] of Object.entries(exports)) {
86
+ // Handle different file types
87
+ const source_file_path =
88
+ k === '.' || k === './'
89
+ ? 'index.ts'
90
+ : strip_start(k.endsWith('.js') ? replace_extension(k, '.ts') : k, './');
93
91
 
94
- const declarations: Array<Src_Module_Declaration> = [];
95
-
96
- const source_file = project.getSourceFile(source_file_id);
97
- if (source_file) {
98
- for (const [name, decls] of source_file.getExportedDeclarations()) {
99
- // TODO how to correctly handle multiples?
100
- for (const decl of decls) {
101
- // TODO helper
102
- const decl_type = decl.getType();
103
- const k = decl.getKindName();
104
- const kind =
105
- k === 'InterfaceDeclaration' || k === 'TypeAliasDeclaration'
106
- ? 'type'
107
- : k === 'ClassDeclaration'
108
- ? 'class'
109
- : k === 'VariableDeclaration'
110
- ? decl_type.getCallSignatures().length
111
- ? 'function'
112
- : 'variable' // TODO name?
113
- : null;
114
- // TODO
115
- // const code =
116
- // k === 'InterfaceDeclaration' || k === 'TypeAliasDeclaration'
117
- // ? decl_type.getText(source_file) // TODO
118
- // : decl_type.getText(source_file);
119
- const found = declarations.find((d) => d.name === name);
120
- if (found) {
121
- // TODO hacky, this only was added to prevent `TypeAliasDeclaration` from overriding `VariableDeclaration`
122
- if (found.kind === 'type') {
123
- found.kind = kind;
124
- // found.code = code;
125
- }
126
- } else {
127
- // TODO more
128
- declarations.push({name, kind}); // code
129
- }
130
- }
131
- }
92
+ const source_file_id = join(lib_path, source_file_path);
93
+
94
+ // Check if file exists
95
+ if (!existsSync(source_file_id)) {
96
+ // Handle non-TypeScript files (Svelte, CSS, JSON)
97
+ const extension = extname(source_file_id);
98
+ if (extension === '.svelte' || extension === '.css' || extension === '.json') {
99
+ file_paths.push({export_key: k, file_path: source_file_id});
100
+ continue;
132
101
  }
133
102
 
134
- const src_module: Src_Module = {path: source_file_path, declarations};
135
- return [k, src_module];
136
- }),
137
- );
103
+ throw Error(
104
+ `Failed to infer source file from package.json export path ${k} - the inferred file ${source_file_id} does not exist`,
105
+ );
106
+ }
107
+
108
+ file_paths.push({export_key: k, file_path: source_file_id});
109
+ }
110
+
111
+ // Create a TypeScript program for all TypeScript files
112
+ const ts_files = file_paths
113
+ .filter(({file_path}) => file_path.endsWith('.ts') || file_path.endsWith('.tsx'))
114
+ .map(({file_path}) => file_path);
115
+
116
+ let program: ts.Program | undefined;
117
+ if (ts_files.length > 0) {
118
+ program = ts.createProgram(
119
+ ts_files,
120
+ // TODO get from tsconfig?
121
+ {
122
+ target: ts.ScriptTarget.ESNext,
123
+ module: ts.ModuleKind.ESNext,
124
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
125
+ verbatimModuleSyntax: true,
126
+ isolatedModules: true,
127
+ },
128
+ );
129
+ }
130
+
131
+ const result: Src_Modules = {};
132
+
133
+ // Process each file
134
+ for (const {export_key, file_path} of file_paths) {
135
+ const relative_path = file_path.replace(ensure_end(lib_path, '/'), '');
136
+
137
+ // Use parse_exports for all file types
138
+ const declarations = parse_exports(file_path, program).map(({name, kind}) => ({
139
+ name,
140
+ kind: kind as Src_Module_Declaration_Kind | null,
141
+ }));
142
+
143
+ result[export_key] = {
144
+ path: relative_path,
145
+ declarations,
146
+ };
147
+ }
148
+
149
+ return result;
138
150
  };