@cldmv/slothlet 2.4.3 → 2.5.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.
@@ -0,0 +1,1455 @@
1
+ /*
2
+ Copyright 2025 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+
18
+
19
+
20
+
21
+ import fs from "node:fs/promises";
22
+ import path from "node:path";
23
+ import { pathToFileURL } from "node:url";
24
+
25
+
26
+
27
+
28
+
29
+
30
+ export async function analyzeModule(modulePath, options = {}) {
31
+ const { debug = false } = options;
32
+
33
+ const moduleUrl = pathToFileURL(modulePath).href;
34
+ const rawModule = await import(moduleUrl);
35
+
36
+
37
+ let processedModule = rawModule;
38
+ const isCjs = modulePath.endsWith(".cjs") && "default" in rawModule;
39
+
40
+ if (isCjs) {
41
+ processedModule = rawModule.default;
42
+ }
43
+
44
+ const hasDefault = !!processedModule.default;
45
+ const isFunction = typeof processedModule.default === "function";
46
+ const exports = Object.entries(processedModule);
47
+ const namedExports = Object.entries(processedModule).filter(([k]) => k !== "default");
48
+
49
+
50
+ let defaultExportType = null;
51
+ if (hasDefault) {
52
+ defaultExportType = typeof processedModule.default === "function" ? "function" : "object";
53
+ }
54
+
55
+
56
+ let shouldWrapAsCallable = false;
57
+
58
+
59
+ if (
60
+ hasDefault &&
61
+ typeof processedModule.default === "object" &&
62
+ processedModule.default !== null &&
63
+ typeof processedModule.default.default === "function"
64
+ ) {
65
+ shouldWrapAsCallable = true;
66
+ }
67
+
68
+
69
+ if (!shouldWrapAsCallable) {
70
+ for (const [_, exportValue] of namedExports) {
71
+ if (typeof exportValue === "object" && exportValue !== null && typeof exportValue.default === "function") {
72
+ shouldWrapAsCallable = true;
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ if (debug) {
79
+ console.log(`[DEBUG] analyzeModule(${path.basename(modulePath)}):`, {
80
+ isCjs,
81
+ hasDefault,
82
+ isFunction,
83
+ defaultExportType,
84
+ shouldWrapAsCallable,
85
+ namedExportsCount: namedExports.length
86
+ });
87
+ }
88
+
89
+ return {
90
+ rawModule,
91
+ processedModule,
92
+ isFunction,
93
+ hasDefault,
94
+ isCjs,
95
+ exports,
96
+ defaultExportType,
97
+ shouldWrapAsCallable,
98
+ namedExports,
99
+ metadata: { modulePath }
100
+ };
101
+ }
102
+
103
+
104
+ export function processModuleFromAnalysis(analysis, options = {}) {
105
+ const { instance, debug = false } = options;
106
+ const { processedModule, isFunction, hasDefault, shouldWrapAsCallable, namedExports } = analysis;
107
+
108
+ if (!instance) {
109
+ throw new Error("processModuleFromAnalysis requires instance parameter for _toapiPathKey access");
110
+ }
111
+
112
+
113
+ if (isFunction) {
114
+ let fn = processedModule.default;
115
+
116
+
117
+ if (hasDefault) {
118
+ try {
119
+ Object.defineProperty(fn, "__slothletDefault", {
120
+ value: true,
121
+ writable: false,
122
+ enumerable: false,
123
+ configurable: true
124
+ });
125
+ } catch {
126
+
127
+ }
128
+ }
129
+
130
+
131
+ for (const [exportName, exportValue] of Object.entries(processedModule)) {
132
+ if (exportName !== "default") {
133
+ fn[instance._toapiPathKey(exportName)] = exportValue;
134
+ }
135
+ }
136
+ return fn;
137
+ }
138
+
139
+
140
+ if (shouldWrapAsCallable) {
141
+ let callableObject = null;
142
+ let objectName = "callable";
143
+
144
+
145
+ if (
146
+ hasDefault &&
147
+ typeof processedModule.default === "object" &&
148
+ processedModule.default !== null &&
149
+ typeof processedModule.default.default === "function"
150
+ ) {
151
+ callableObject = processedModule.default;
152
+ objectName = processedModule.default.name || (namedExports[0] && namedExports[0][0] !== "default" ? namedExports[0][0] : "callable");
153
+ } else {
154
+
155
+ for (const [exportName, exportValue] of namedExports) {
156
+ if (typeof exportValue === "object" && exportValue !== null && typeof exportValue.default === "function") {
157
+ callableObject = exportValue;
158
+ objectName = exportName;
159
+ break;
160
+ }
161
+ }
162
+ }
163
+
164
+ if (callableObject) {
165
+ const callableApi = {
166
+ [objectName]: function (...args) {
167
+ return callableObject.default.apply(callableObject, args);
168
+ }
169
+ }[objectName];
170
+
171
+
172
+ for (const [methodName, method] of Object.entries(callableObject)) {
173
+ if (methodName === "default") continue;
174
+ callableApi[methodName] = method;
175
+ }
176
+
177
+ if (debug) {
178
+ console.log(`[DEBUG] Created callable wrapper for ${objectName}`);
179
+ }
180
+
181
+ return callableApi;
182
+ }
183
+ }
184
+
185
+
186
+ if (hasDefault && typeof processedModule.default === "object") {
187
+ const obj = { ...processedModule.default };
188
+
189
+
190
+ for (const [exportName, exportValue] of Object.entries(processedModule)) {
191
+ if (exportName !== "default" && exportValue !== obj) {
192
+ obj[instance._toapiPathKey(exportName)] = exportValue;
193
+ }
194
+ }
195
+
196
+ return obj;
197
+ }
198
+
199
+
200
+ if (namedExports.length > 0) {
201
+ const apiExport = {};
202
+ for (const [exportName, exportValue] of namedExports) {
203
+ apiExport[instance._toapiPathKey(exportName)] = exportValue;
204
+ }
205
+ return apiExport;
206
+ }
207
+
208
+
209
+ throw new Error(`No valid exports found in processed module`);
210
+ }
211
+
212
+
213
+ export async function analyzeDirectoryStructure(categoryPath, options = {}) {
214
+ const { instance, currentDepth = 0, debug = false } = options;
215
+
216
+ if (!instance || typeof instance._toapiPathKey !== "function") {
217
+ throw new Error("analyzeDirectoryStructure requires a valid slothlet instance");
218
+ }
219
+
220
+ const files = await fs.readdir(categoryPath, { withFileTypes: true });
221
+ const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
222
+ const categoryName = instance._toapiPathKey(path.basename(categoryPath));
223
+ const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
224
+
225
+
226
+ let processingStrategy;
227
+ if (moduleFiles.length === 0) {
228
+ processingStrategy = "empty";
229
+ } else if (moduleFiles.length === 1 && subDirs.length === 0) {
230
+ processingStrategy = "single-file";
231
+ } else {
232
+ processingStrategy = "multi-file";
233
+ }
234
+
235
+
236
+ let multiDefaultAnalysis = null;
237
+ if (processingStrategy === "multi-file") {
238
+ const { multidefault_analyzeModules } = await import("@cldmv/slothlet/helpers/multidefault");
239
+ multiDefaultAnalysis = await multidefault_analyzeModules(moduleFiles, categoryPath, debug);
240
+ }
241
+
242
+
243
+ const flatteningHints = {
244
+ shouldFlattenSingleFile: processingStrategy === "single-file",
245
+ shouldFlattenToParent: currentDepth > 0 && processingStrategy === "single-file",
246
+ hasMultipleDefaults: multiDefaultAnalysis?.hasMultipleDefaultExports || false,
247
+ selfReferentialFiles: multiDefaultAnalysis?.selfReferentialFiles || new Set()
248
+ };
249
+
250
+ if (debug) {
251
+ console.log(`[DEBUG] analyzeDirectoryStructure(${categoryName}):`, {
252
+ processingStrategy,
253
+ moduleCount: moduleFiles.length,
254
+ subDirCount: subDirs.length,
255
+ hasMultipleDefaults: flatteningHints.hasMultipleDefaults
256
+ });
257
+ }
258
+
259
+ return {
260
+ isSingleFile: processingStrategy === "single-file",
261
+ shouldAutoFlatten: flatteningHints.shouldFlattenSingleFile,
262
+ categoryName,
263
+ moduleFiles,
264
+ subDirs,
265
+ multiDefaultAnalysis,
266
+ processingStrategy,
267
+ flatteningHints
268
+ };
269
+ }
270
+
271
+
272
+ export async function getCategoryBuildingDecisions(categoryPath, options = {}) {
273
+ const { instance, currentDepth = 0, maxDepth = Infinity, debug = false } = options;
274
+
275
+ if (!instance) {
276
+ throw new Error("getCategoryBuildingDecisions requires a valid slothlet instance");
277
+ }
278
+
279
+ const analysis = await analyzeDirectoryStructure(categoryPath, { instance, currentDepth, maxDepth, debug });
280
+ const { processingStrategy, categoryName, moduleFiles, subDirs, multiDefaultAnalysis } = analysis;
281
+
282
+ const processedModules = [];
283
+ const subDirectories = [];
284
+
285
+
286
+ if (processingStrategy !== "empty") {
287
+ for (const file of moduleFiles) {
288
+ const moduleExt = path.extname(file.name);
289
+ const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
290
+ const modulePath = path.join(categoryPath, file.name);
291
+
292
+
293
+ const analysis = await analyzeModule(modulePath, {
294
+ debug,
295
+ instance
296
+ });
297
+ const processedModule = processModuleFromAnalysis(analysis, {
298
+ debug,
299
+ instance
300
+ });
301
+
302
+
303
+ const flatteningInfo = {
304
+ shouldFlatten: false,
305
+ apiPathKey: moduleName,
306
+ reason: "default"
307
+ };
308
+
309
+
310
+ if (processingStrategy === "single-file") {
311
+
312
+ const functionNameMatchesFolder =
313
+ typeof processedModule === "function" &&
314
+ processedModule.name &&
315
+ processedModule.name.toLowerCase() === categoryName.toLowerCase();
316
+
317
+ const moduleNameMatchesCategory = moduleName === categoryName && typeof processedModule === "function" && currentDepth > 0;
318
+
319
+ if (functionNameMatchesFolder && currentDepth > 0) {
320
+ flatteningInfo.shouldFlatten = true;
321
+ flatteningInfo.apiPathKey = processedModule.name;
322
+ flatteningInfo.reason = "function name matches folder";
323
+ } else if (moduleNameMatchesCategory) {
324
+ flatteningInfo.shouldFlatten = true;
325
+ flatteningInfo.apiPathKey = categoryName;
326
+ flatteningInfo.reason = "module name matches category";
327
+ }
328
+ }
329
+
330
+ processedModules.push({
331
+ file,
332
+ moduleName,
333
+ processedModule,
334
+ flattening: flatteningInfo
335
+ });
336
+ }
337
+ }
338
+
339
+
340
+ for (const subDirEntry of subDirs) {
341
+ if (currentDepth < maxDepth) {
342
+ const apiPathKey = instance._toapiPathKey(subDirEntry.name);
343
+ subDirectories.push({ dirEntry: subDirEntry, apiPathKey });
344
+ }
345
+ }
346
+
347
+
348
+ const upwardFlatteningCandidate = { shouldFlatten: false, apiPathKey: null };
349
+ if (processedModules.length === 1 && subDirectories.length === 0) {
350
+ const single = processedModules[0];
351
+ if (single.moduleName === categoryName) {
352
+ upwardFlatteningCandidate.shouldFlatten = true;
353
+ upwardFlatteningCandidate.apiPathKey = single.moduleName;
354
+ }
355
+ }
356
+
357
+ if (debug) {
358
+ console.log(`[DEBUG] getCategoryBuildingDecisions(${categoryName}):`, {
359
+ processingStrategy,
360
+ moduleCount: processedModules.length,
361
+ subDirCount: subDirectories.length,
362
+ upwardFlattening: upwardFlatteningCandidate.shouldFlatten
363
+ });
364
+ }
365
+
366
+ return {
367
+ processingStrategy,
368
+ categoryName,
369
+ shouldFlattenSingle: processingStrategy === "single-file",
370
+ processedModules,
371
+ subDirectories,
372
+ multiDefaultAnalysis,
373
+ flatteningDecisions: analysis.flatteningHints,
374
+ upwardFlatteningCandidate
375
+ };
376
+ }
377
+
378
+
379
+ export function getFlatteningDecision(options) {
380
+ const {
381
+ mod,
382
+ fileName,
383
+ apiPathKey,
384
+ hasMultipleDefaultExports,
385
+ isSelfReferential,
386
+
387
+
388
+ moduleHasDefault = !!mod.default,
389
+ categoryName,
390
+ totalModules = 1
391
+ } = options;
392
+
393
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
394
+
395
+
396
+ if (isSelfReferential) {
397
+ return {
398
+ shouldFlatten: false,
399
+ flattenToRoot: false,
400
+ flattenToCategory: false,
401
+ preserveAsNamespace: true,
402
+ useAutoFlattening: false,
403
+ reason: "self-referential export"
404
+ };
405
+ }
406
+
407
+
408
+ if (hasMultipleDefaultExports) {
409
+ if (moduleHasDefault) {
410
+
411
+ return {
412
+ shouldFlatten: false,
413
+ flattenToRoot: false,
414
+ flattenToCategory: false,
415
+ preserveAsNamespace: true,
416
+ useAutoFlattening: false,
417
+ reason: "multi-default context with default export"
418
+ };
419
+ } else {
420
+
421
+ return {
422
+ shouldFlatten: true,
423
+ flattenToRoot: true,
424
+ flattenToCategory: true,
425
+ preserveAsNamespace: false,
426
+ useAutoFlattening: false,
427
+ reason: "multi-default context without default export"
428
+ };
429
+ }
430
+ }
431
+
432
+
433
+ if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
434
+ return {
435
+ shouldFlatten: true,
436
+ flattenToRoot: false,
437
+ flattenToCategory: false,
438
+ preserveAsNamespace: false,
439
+ useAutoFlattening: true,
440
+ reason: "auto-flatten single named export matching filename"
441
+ };
442
+ }
443
+
444
+
445
+ if (categoryName && fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0) {
446
+ return {
447
+ shouldFlatten: true,
448
+ flattenToRoot: false,
449
+ flattenToCategory: true,
450
+ preserveAsNamespace: false,
451
+ useAutoFlattening: false,
452
+ reason: "filename matches container, flatten to category"
453
+ };
454
+ }
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+ return {
472
+ shouldFlatten: false,
473
+ flattenToRoot: false,
474
+ flattenToCategory: false,
475
+ preserveAsNamespace: true,
476
+ useAutoFlattening: false,
477
+ reason: "traditional namespace preservation"
478
+ };
479
+ }
480
+
481
+
482
+ export function processModuleForAPI(options) {
483
+ const {
484
+ mod,
485
+ fileName,
486
+ apiPathKey,
487
+ hasMultipleDefaultExports,
488
+ isSelfReferential,
489
+ api,
490
+ getRootDefault,
491
+ setRootDefault,
492
+ context = {},
493
+ originalAnalysis = null
494
+ } = options;
495
+
496
+ const { debug = false, mode = "unknown", categoryName, totalModules = 1 } = context;
497
+
498
+ let processed = false;
499
+ let rootDefaultSet = false;
500
+ let flattened = false;
501
+ let namespaced = false;
502
+ const apiAssignments = {};
503
+
504
+
505
+
506
+
507
+ const hasDefaultFunction = (mod && typeof mod.default === "function") || (mod && typeof mod === "function" && !mod.default);
508
+
509
+
510
+ const defaultFunction = mod?.default || (typeof mod === "function" ? mod : null);
511
+
512
+ if (hasDefaultFunction) {
513
+ processed = true;
514
+
515
+ if (hasMultipleDefaultExports && !isSelfReferential) {
516
+
517
+ apiAssignments[apiPathKey] = mod;
518
+ namespaced = true;
519
+
520
+
521
+
522
+
523
+ if (debug) {
524
+ console.log(
525
+ `[DEBUG] ${mode}: Multi-default function - using filename '${apiPathKey}' for default export, mod type: ${typeof mod}, function name: ${defaultFunction?.name}`
526
+ );
527
+ }
528
+ } else if (isSelfReferential) {
529
+
530
+ apiAssignments[apiPathKey] = mod;
531
+ namespaced = true;
532
+
533
+ if (debug) {
534
+ console.log(`[DEBUG] ${mode}: Self-referential function - preserving ${fileName} as namespace`);
535
+ }
536
+ } else {
537
+
538
+ if (debug) {
539
+ console.log(
540
+ `[DEBUG] ${mode}: Processing traditional default function: hasMultipleDefaultExports=${hasMultipleDefaultExports}, rootDefaultFunction=${!!(getRootDefault && getRootDefault())}`
541
+ );
542
+ }
543
+
544
+
545
+ if (mode === "root" && getRootDefault && setRootDefault && !hasMultipleDefaultExports && !getRootDefault()) {
546
+ setRootDefault(defaultFunction);
547
+ rootDefaultSet = true;
548
+
549
+ if (debug) {
550
+ console.log(`[DEBUG] ${mode}: Set rootDefaultFunction to:`, defaultFunction.name);
551
+ }
552
+
553
+
554
+
555
+ } else {
556
+
557
+ apiAssignments[apiPathKey] = mod;
558
+ namespaced = true;
559
+
560
+
561
+
562
+ }
563
+ }
564
+ } else {
565
+
566
+ processed = true;
567
+
568
+ if (debug) {
569
+ console.log(`[DEBUG] ${mode}: Processing non-function or named-only exports for ${fileName}`);
570
+ }
571
+
572
+
573
+ const decision = getFlatteningDecision({
574
+ mod,
575
+ fileName,
576
+ apiPathKey,
577
+ hasMultipleDefaultExports,
578
+ isSelfReferential,
579
+
580
+
581
+
582
+ moduleHasDefault: originalAnalysis ? originalAnalysis.hasDefault : !!mod.default,
583
+ categoryName,
584
+ totalModules,
585
+ debug
586
+ });
587
+
588
+ if (debug) {
589
+ console.log(`[DEBUG] ${mode}: Flattening decision for ${fileName}: ${decision.reason}`);
590
+ }
591
+
592
+ if (decision.useAutoFlattening) {
593
+
594
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
595
+ apiAssignments[apiPathKey] = mod[moduleKeys[0]];
596
+ flattened = true;
597
+ } else if (decision.flattenToRoot || decision.flattenToCategory) {
598
+
599
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
600
+ for (const key of moduleKeys) {
601
+ apiAssignments[key] = mod[key];
602
+ if (debug) {
603
+ console.log(`[DEBUG] ${mode}: Flattened ${fileName}.${key} to ${decision.flattenToRoot ? "root" : "category"}.${key}`);
604
+ }
605
+ }
606
+ flattened = true;
607
+ } else if (isSelfReferential) {
608
+
609
+ apiAssignments[apiPathKey] = mod[apiPathKey] || mod;
610
+ namespaced = true;
611
+ } else {
612
+
613
+ apiAssignments[apiPathKey] = mod;
614
+ namespaced = true;
615
+ }
616
+ }
617
+
618
+
619
+ for (const [key, value] of Object.entries(apiAssignments)) {
620
+ if (debug && key && typeof value === "function" && value.name) {
621
+ console.log(`[DEBUG] ${mode}: Assigning key '${key}' to function '${value.name}'`);
622
+ }
623
+ api[key] = value;
624
+ }
625
+
626
+ return {
627
+ processed,
628
+ rootDefaultSet,
629
+ flattened,
630
+ namespaced,
631
+ apiAssignments
632
+ };
633
+ }
634
+
635
+
636
+ export function applyFunctionNamePreference(options) {
637
+ const { mod, fileName, apiPathKey, categoryModules, toapiPathKey, debug = false } = options;
638
+
639
+ let hasPreferredName = false;
640
+ let preferredKey = apiPathKey;
641
+
642
+
643
+ for (const [, exportValue] of Object.entries(mod)) {
644
+ if (typeof exportValue === "function" && exportValue.name) {
645
+ const functionNameLower = exportValue.name.toLowerCase();
646
+ const filenameLower = fileName.toLowerCase();
647
+
648
+
649
+ if (functionNameLower === filenameLower && exportValue.name !== apiPathKey) {
650
+
651
+ preferredKey = exportValue.name;
652
+ hasPreferredName = true;
653
+
654
+ if (debug) {
655
+ console.log(`[DEBUG] Using function name preference: ${exportValue.name} instead of ${apiPathKey} for ${fileName}`);
656
+ }
657
+ break;
658
+ }
659
+
660
+
661
+ const sanitizedFunctionName = toapiPathKey(exportValue.name);
662
+ if (sanitizedFunctionName.toLowerCase() === apiPathKey.toLowerCase() && exportValue.name !== apiPathKey) {
663
+ preferredKey = exportValue.name;
664
+ hasPreferredName = true;
665
+
666
+ if (debug) {
667
+ console.log(`[DEBUG] Using function name preference: ${exportValue.name} instead of ${apiPathKey} for ${fileName}`);
668
+ }
669
+ break;
670
+ }
671
+ }
672
+ }
673
+
674
+ if (hasPreferredName) {
675
+
676
+ categoryModules[preferredKey] = mod;
677
+ }
678
+
679
+ return { hasPreferredName, preferredKey };
680
+ }
681
+
682
+
683
+ export async function buildCategoryStructure(categoryPath, options = {}) {
684
+ const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler, instance } = options;
685
+
686
+ if (!instance || typeof instance._toapiPathKey !== "function" || typeof instance._shouldIncludeFile !== "function") {
687
+ throw new Error("buildCategoryStructure requires a valid slothlet instance");
688
+ }
689
+
690
+ const debug = instance.config?.debug || false;
691
+
692
+ if (debug) {
693
+ console.log(`[DEBUG] buildCategoryStructure called with path: ${categoryPath}, mode: ${mode}, depth: ${currentDepth}`);
694
+ }
695
+
696
+ const files = await fs.readdir(categoryPath, { withFileTypes: true });
697
+ const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
698
+ const categoryName = instance._toapiPathKey(path.basename(categoryPath));
699
+ const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
700
+
701
+
702
+ if (moduleFiles.length === 1 && subDirs.length === 0) {
703
+ const moduleExt = path.extname(moduleFiles[0].name);
704
+ const moduleName = instance._toapiPathKey(path.basename(moduleFiles[0].name, moduleExt));
705
+
706
+
707
+ const analysis = await analyzeModule(path.join(categoryPath, moduleFiles[0].name), {
708
+ debug,
709
+ instance
710
+ });
711
+
712
+
713
+ const mod = processModuleFromAnalysis(analysis, { instance, debug });
714
+
715
+
716
+ const functionNameMatchesFolder = typeof mod === "function" && mod.name && mod.name.toLowerCase() === categoryName.toLowerCase();
717
+
718
+ const functionNameMatchesFilename =
719
+ typeof mod === "function" &&
720
+ mod.name &&
721
+ instance._toapiPathKey(mod.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
722
+ mod.name !== instance._toapiPathKey(moduleName);
723
+
724
+
725
+
726
+
727
+
728
+
729
+
730
+ if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
731
+ try {
732
+ Object.defineProperty(mod, "name", { value: categoryName, configurable: true });
733
+ } catch {
734
+
735
+ }
736
+ return mod;
737
+ }
738
+
739
+
740
+ if (moduleName === categoryName && mod && typeof mod === "object" && !Array.isArray(mod) && currentDepth > 0) {
741
+ if (debug) {
742
+ console.log(`[DEBUG] Single-file auto-flattening: ${categoryName}/${moduleFiles[0].name} -> flatten object contents`);
743
+ }
744
+
745
+
746
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
747
+ if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
748
+ return mod[moduleName];
749
+ }
750
+
751
+
752
+
753
+
754
+
755
+
756
+
757
+ if (moduleKeys.length > 1) {
758
+ if (debug) {
759
+ console.log(`[DEBUG] Default export flattening: ${categoryName}/${moduleFiles[0].name} -> flatten default object contents`);
760
+ }
761
+ return mod;
762
+ }
763
+ return mod;
764
+ }
765
+
766
+
767
+ if (moduleFiles.length === 1 && currentDepth > 0 && mod && typeof mod === "object" && !Array.isArray(mod)) {
768
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
769
+ const fileName = moduleFiles[0].name.replace(/\.(mjs|cjs|js)$/, "");
770
+
771
+
772
+ const isGenericFilename = ["singlefile", "index", "main", "default"].includes(fileName.toLowerCase());
773
+
774
+
775
+ if (moduleKeys.length === 1 && isGenericFilename) {
776
+ if (debug) {
777
+ console.log(
778
+ `[DEBUG] Single-file parent-level auto-flattening: ${categoryName}/${moduleFiles[0].name} -> flatten to parent level`
779
+ );
780
+ }
781
+ const exportValue = mod[moduleKeys[0]];
782
+ return { [moduleKeys[0]]: exportValue };
783
+ }
784
+ }
785
+
786
+
787
+ if (functionNameMatchesFolder && currentDepth > 0) {
788
+ try {
789
+ Object.defineProperty(mod, "name", { value: mod.name, configurable: true });
790
+ } catch {
791
+
792
+ }
793
+ return mod;
794
+ }
795
+
796
+
797
+ if (functionNameMatchesFilename) {
798
+ return { [mod.name]: mod };
799
+ }
800
+
801
+
802
+ if (typeof mod === "function" && (!mod.name || mod.name === "default" || mod.__slothletDefault === true) && currentDepth > 0) {
803
+ try {
804
+ Object.defineProperty(mod, "name", { value: categoryName, configurable: true });
805
+ } catch {
806
+
807
+ }
808
+ return mod;
809
+ }
810
+
811
+
812
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
813
+ if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
814
+ return mod[moduleName];
815
+ }
816
+
817
+
818
+ return { [moduleName]: mod };
819
+ }
820
+
821
+
822
+ const categoryModules = {};
823
+
824
+
825
+ const { multidefault_analyzeModules } = await import("@cldmv/slothlet/helpers/multidefault");
826
+ const analysis = await multidefault_analyzeModules(moduleFiles, categoryPath, debug);
827
+ const { totalDefaultExports, hasMultipleDefaultExports, selfReferentialFiles, defaultExportFiles: analysisDefaults } = analysis;
828
+
829
+
830
+ const defaultExportFiles = [];
831
+ for (const { fileName } of analysisDefaults) {
832
+ const file = moduleFiles.find((f) => path.basename(f.name, path.extname(f.name)) === fileName);
833
+ if (file) {
834
+ const analysis = await analyzeModule(path.join(categoryPath, file.name), {
835
+ debug,
836
+ instance
837
+ });
838
+ const processedMod = processModuleFromAnalysis(analysis, {
839
+ debug,
840
+ instance
841
+ });
842
+ defaultExportFiles.push({ file, moduleName: instance._toapiPathKey(fileName), mod: processedMod });
843
+ }
844
+ }
845
+
846
+ if (debug) {
847
+ console.log(`[DEBUG] buildCategoryStructure: Multi-default analysis results`);
848
+ console.log(`[DEBUG] - totalDefaultExports: ${totalDefaultExports}`);
849
+ console.log(`[DEBUG] - hasMultipleDefaultExports: ${hasMultipleDefaultExports}`);
850
+ console.log(`[DEBUG] - selfReferentialFiles: ${Array.from(selfReferentialFiles)}`);
851
+ }
852
+
853
+
854
+ for (const file of moduleFiles) {
855
+ const moduleExt = path.extname(file.name);
856
+ const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
857
+ const fileName = path.basename(file.name, moduleExt);
858
+ const apiPathKey = instance._toapiPathKey(fileName);
859
+
860
+ if (debug && moduleName === "config") {
861
+ console.log(`[DEBUG] Processing config file: ${file.name}, moduleName: ${moduleName}`);
862
+ console.log(`[DEBUG] selfReferentialFiles has config? ${selfReferentialFiles.has(moduleName)}`);
863
+ }
864
+
865
+
866
+ let mod = null;
867
+ const existingDefault = defaultExportFiles.find((def) => def.moduleName === moduleName);
868
+ if (existingDefault) {
869
+ mod = existingDefault.mod;
870
+ } else {
871
+ const analysis = await analyzeModule(path.join(categoryPath, file.name), {
872
+ debug,
873
+ instance
874
+ });
875
+ mod = processModuleFromAnalysis(analysis, {
876
+ debug,
877
+ instance
878
+ });
879
+ }
880
+
881
+
882
+ processModuleForAPI({
883
+ mod,
884
+ fileName,
885
+ apiPathKey,
886
+ hasMultipleDefaultExports,
887
+ isSelfReferential: selfReferentialFiles.has(moduleName),
888
+ api: categoryModules,
889
+ getRootDefault: () => null,
890
+ setRootDefault: () => {},
891
+ context: {
892
+ debug,
893
+ mode: "category",
894
+ categoryName,
895
+ totalModules: moduleFiles.length
896
+ }
897
+ });
898
+ }
899
+
900
+
901
+ for (const subDirEntry of subDirs) {
902
+ if (currentDepth < maxDepth) {
903
+ const key = instance._toapiPathKey(subDirEntry.name);
904
+ const subDirPath = path.join(categoryPath, subDirEntry.name);
905
+ let subModule;
906
+
907
+ if (mode === "lazy" && typeof subdirHandler === "function") {
908
+
909
+ subModule = subdirHandler({
910
+ subDirEntry,
911
+ subDirPath,
912
+ key,
913
+ categoryModules,
914
+ currentDepth,
915
+ maxDepth
916
+ });
917
+ } else {
918
+
919
+ subModule = await buildCategoryStructure(subDirPath, {
920
+ currentDepth: currentDepth + 1,
921
+ maxDepth,
922
+ mode: "eager",
923
+ instance
924
+ });
925
+ }
926
+
927
+
928
+
929
+ if (
930
+ typeof subModule === "function" &&
931
+ subModule.name &&
932
+ subModule.name.toLowerCase() === key.toLowerCase() &&
933
+ subModule.name !== key
934
+ ) {
935
+ categoryModules[subModule.name] = subModule;
936
+ } else {
937
+ categoryModules[key] = subModule;
938
+ }
939
+ }
940
+ }
941
+
942
+ return categoryModules;
943
+ }
944
+
945
+
946
+ export async function buildRootAPI(dir, options = {}) {
947
+ const { lazy = false, maxDepth = Infinity, instance } = options;
948
+
949
+ if (!instance || typeof instance._shouldIncludeFile !== "function" || typeof instance._loadCategory !== "function") {
950
+ throw new Error("buildRootAPI requires a valid slothlet instance");
951
+ }
952
+
953
+ const debug = instance.config?.debug || false;
954
+
955
+ if (debug) {
956
+ console.log(`[DEBUG] buildRootAPI called with dir: ${dir}, lazy: ${lazy}, maxDepth: ${maxDepth}`);
957
+ }
958
+
959
+ const entries = await fs.readdir(dir, { withFileTypes: true });
960
+ const api = {};
961
+ let rootDefaultFunction = null;
962
+
963
+
964
+ const moduleFiles = entries.filter((e) => instance._shouldIncludeFile(e));
965
+
966
+ if (moduleFiles.length > 0) {
967
+
968
+ const { multidefault_analyzeModules } = await import("@cldmv/slothlet/helpers/multidefault");
969
+ const analysis = await multidefault_analyzeModules(moduleFiles, dir, debug);
970
+ const { hasMultipleDefaultExports, selfReferentialFiles } = analysis;
971
+
972
+
973
+ for (const entry of moduleFiles) {
974
+ const ext = path.extname(entry.name);
975
+ const fileName = path.basename(entry.name, ext);
976
+ const apiPathKey = instance._toapiPathKey(fileName);
977
+
978
+ const analysis = await analyzeModule(path.join(dir, entry.name), {
979
+ debug,
980
+ instance
981
+ });
982
+ const mod = processModuleFromAnalysis(analysis, {
983
+ debug,
984
+ instance
985
+ });
986
+
987
+
988
+ processModuleForAPI({
989
+ mod,
990
+ fileName,
991
+ apiPathKey,
992
+ hasMultipleDefaultExports,
993
+ isSelfReferential: selfReferentialFiles.has(fileName),
994
+ api,
995
+ getRootDefault: () => rootDefaultFunction,
996
+ setRootDefault: (fn) => {
997
+ rootDefaultFunction = fn;
998
+ },
999
+ context: {
1000
+ debug,
1001
+ mode: "root",
1002
+ totalModules: moduleFiles.length
1003
+ }
1004
+ });
1005
+ }
1006
+ }
1007
+
1008
+
1009
+ for (const entry of entries) {
1010
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
1011
+ const categoryPath = path.join(dir, entry.name);
1012
+
1013
+ if (lazy) {
1014
+
1015
+ api[instance._toapiPathKey(entry.name)] = await instance._loadCategory(categoryPath, 0, maxDepth);
1016
+ } else {
1017
+
1018
+ api[instance._toapiPathKey(entry.name)] = await buildCategoryStructure(categoryPath, {
1019
+ currentDepth: 1,
1020
+ maxDepth,
1021
+ mode: "eager",
1022
+ instance
1023
+ });
1024
+ }
1025
+ }
1026
+ }
1027
+
1028
+
1029
+ let finalApi;
1030
+ if (debug) {
1031
+ console.log(`[DEBUG] Final assembly: rootDefaultFunction=${!!rootDefaultFunction}`);
1032
+ console.log(`[DEBUG] API object keys before final assembly:`, Object.keys(api));
1033
+ }
1034
+
1035
+ if (rootDefaultFunction) {
1036
+
1037
+ Object.assign(rootDefaultFunction, api);
1038
+ finalApi = rootDefaultFunction;
1039
+
1040
+ if (debug) {
1041
+ console.log(`[DEBUG] Applied root contributor pattern - final API is function`);
1042
+ }
1043
+ } else {
1044
+
1045
+ finalApi = api;
1046
+
1047
+ if (debug) {
1048
+ console.log(`[DEBUG] No root function - final API is object`);
1049
+ }
1050
+ }
1051
+
1052
+ return finalApi;
1053
+ }
1054
+
1055
+
1056
+ export async function buildCategoryDecisions(categoryPath, options = {}) {
1057
+ const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler } = options;
1058
+ const { instance } = options;
1059
+
1060
+ if (!instance || typeof instance._toapiPathKey !== "function") {
1061
+ throw new Error("buildCategoryDecisions requires instance parameter with _toapiPathKey method");
1062
+ }
1063
+
1064
+ const debug = instance.config?.debug || false;
1065
+
1066
+
1067
+ if (debug) {
1068
+ console.log(`[DEBUG] buildCategoryDecisions called with path: ${categoryPath}, mode: ${mode}`);
1069
+ }
1070
+
1071
+ const fs = await import("fs/promises");
1072
+ const path = await import("path");
1073
+
1074
+ const files = await fs.readdir(categoryPath, { withFileTypes: true });
1075
+ const moduleFiles = files.filter((f) => instance._shouldIncludeFile(f));
1076
+ const categoryName = instance._toapiPathKey(path.basename(categoryPath));
1077
+ const subDirs = files.filter((e) => e.isDirectory() && !e.name.startsWith("."));
1078
+
1079
+ const decisions = {
1080
+ type: null,
1081
+ categoryName,
1082
+ moduleFiles,
1083
+ subDirs,
1084
+ currentDepth,
1085
+ maxDepth,
1086
+ mode,
1087
+ subdirHandler,
1088
+
1089
+ singleFile: null,
1090
+ shouldFlatten: false,
1091
+ flattenType: null,
1092
+ preferredName: null,
1093
+
1094
+ multifileAnalysis: null,
1095
+ processedModules: [],
1096
+ categoryModules: {},
1097
+
1098
+ subdirectoryDecisions: []
1099
+ };
1100
+
1101
+
1102
+ if (moduleFiles.length === 1 && subDirs.length === 0) {
1103
+ decisions.type = "single-file";
1104
+ const moduleFile = moduleFiles[0];
1105
+ const moduleExt = path.extname(moduleFile.name);
1106
+ const moduleName = instance._toapiPathKey(path.basename(moduleFile.name, moduleExt));
1107
+
1108
+ decisions.singleFile = {
1109
+ file: moduleFile,
1110
+ moduleName,
1111
+ moduleExt
1112
+ };
1113
+
1114
+
1115
+ const analysis = await analyzeModule(path.join(categoryPath, moduleFile.name), {
1116
+ debug,
1117
+ instance
1118
+ });
1119
+ const mod = processModuleFromAnalysis(analysis, {
1120
+ debug,
1121
+ instance
1122
+ });
1123
+
1124
+ decisions.singleFile.mod = mod;
1125
+
1126
+
1127
+ const functionNameMatchesFolder = typeof mod === "function" && mod.name && mod.name.toLowerCase() === categoryName.toLowerCase();
1128
+
1129
+
1130
+ const functionNameMatchesFilename =
1131
+ typeof mod === "function" &&
1132
+ mod.name &&
1133
+ instance._toapiPathKey(mod.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
1134
+ mod.name !== instance._toapiPathKey(moduleName);
1135
+
1136
+
1137
+
1138
+ if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
1139
+ decisions.shouldFlatten = true;
1140
+ decisions.flattenType = "function-folder-match";
1141
+ decisions.preferredName = categoryName;
1142
+ return decisions;
1143
+ }
1144
+
1145
+
1146
+
1147
+ if (analysis.hasDefault && analysis.defaultExportType === "object" && moduleName === categoryName && currentDepth > 0) {
1148
+ if (debug) {
1149
+ console.log(`[DEBUG] Default export flattening: ${categoryName}/${moduleFile.name} -> flatten default object contents`);
1150
+ }
1151
+ decisions.shouldFlatten = true;
1152
+ decisions.flattenType = "default-export-flatten";
1153
+ return decisions;
1154
+ }
1155
+
1156
+
1157
+
1158
+ if (moduleName === categoryName && mod && typeof mod === "object" && !Array.isArray(mod) && currentDepth > 0) {
1159
+
1160
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1161
+ if (debug) {
1162
+ console.log(
1163
+ `[DEBUG] Auto-flatten check: moduleName="${moduleName}" categoryName="${categoryName}" moduleKeys=[${moduleKeys}] match=${moduleKeys.length === 1 && moduleKeys[0] === moduleName}`
1164
+ );
1165
+ }
1166
+ if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
1167
+ if (debug) {
1168
+ console.log(`[DEBUG] Single-file auto-flattening: ${categoryName}/${moduleFile.name} -> flatten object contents`);
1169
+ }
1170
+ decisions.shouldFlatten = true;
1171
+ decisions.flattenType = "object-auto-flatten";
1172
+ decisions.preferredName = moduleKeys[0];
1173
+ return decisions;
1174
+ }
1175
+
1176
+
1177
+
1178
+ const fileBaseName = moduleFile.name.replace(/\.(mjs|cjs|js)$/, "");
1179
+ if (fileBaseName === categoryName && moduleKeys.length > 0) {
1180
+ if (debug) {
1181
+ console.log(
1182
+ `[DEBUG] Single-file filename-folder exact match flattening: ${categoryName}/${moduleFile.name} -> avoid double nesting`
1183
+ );
1184
+ }
1185
+ decisions.shouldFlatten = true;
1186
+ decisions.flattenType = "filename-folder-match-flatten";
1187
+ return decisions;
1188
+ }
1189
+ }
1190
+
1191
+
1192
+
1193
+
1194
+
1195
+ if (moduleFiles.length === 1 && currentDepth > 0 && mod && typeof mod === "object" && !Array.isArray(mod)) {
1196
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1197
+ const fileName = moduleFile.name.replace(/\.(mjs|cjs|js)$/, "");
1198
+
1199
+
1200
+
1201
+ const isGenericFilename = ["singlefile", "index", "main", "default"].includes(fileName.toLowerCase());
1202
+
1203
+
1204
+ if (moduleKeys.length === 1 && isGenericFilename) {
1205
+ if (debug) {
1206
+ console.log(`[DEBUG] Single-file parent-level auto-flattening: ${categoryName}/${moduleFile.name} -> flatten to parent level`);
1207
+ }
1208
+ decisions.shouldFlatten = true;
1209
+ decisions.flattenType = "parent-level-flatten";
1210
+ decisions.preferredName = moduleKeys[0];
1211
+ return decisions;
1212
+ }
1213
+ }
1214
+
1215
+
1216
+
1217
+ if (functionNameMatchesFolder && currentDepth > 0) {
1218
+ decisions.shouldFlatten = true;
1219
+ decisions.flattenType = "function-folder-match";
1220
+ decisions.preferredName = mod.name;
1221
+ return decisions;
1222
+ }
1223
+
1224
+
1225
+ if (functionNameMatchesFilename) {
1226
+ decisions.shouldFlatten = false;
1227
+ decisions.preferredName = mod.name;
1228
+ return decisions;
1229
+ }
1230
+
1231
+
1232
+
1233
+
1234
+ if (
1235
+ typeof mod === "function" &&
1236
+ (!mod.name || mod.name === "default" || mod.__slothletDefault === true) &&
1237
+ currentDepth > 0
1238
+ ) {
1239
+ decisions.shouldFlatten = true;
1240
+ decisions.flattenType = "default-function";
1241
+ decisions.preferredName = categoryName;
1242
+ return decisions;
1243
+ }
1244
+
1245
+
1246
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1247
+ if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
1248
+
1249
+ decisions.shouldFlatten = true;
1250
+ decisions.flattenType = "object-auto-flatten";
1251
+ decisions.preferredName = moduleName;
1252
+ return decisions;
1253
+ }
1254
+
1255
+
1256
+ decisions.shouldFlatten = false;
1257
+ decisions.preferredName = moduleName;
1258
+ if (debug && moduleName === "nest") {
1259
+ console.log(
1260
+ `[DEBUG] buildCategoryDecisions single-file default: moduleName="${moduleName}" shouldFlatten=false preferredName="${decisions.preferredName}"`
1261
+ );
1262
+ }
1263
+ return decisions;
1264
+ }
1265
+
1266
+
1267
+ decisions.type = "multi-file";
1268
+ if (debug) {
1269
+ console.log(`[DEBUG] buildCategoryDecisions: Processing multi-file case for ${categoryPath}`);
1270
+ }
1271
+
1272
+
1273
+ const { multidefault_analyzeModules } = await import("@cldmv/slothlet/helpers/multidefault");
1274
+ const analysis = await multidefault_analyzeModules(moduleFiles, categoryPath, debug);
1275
+
1276
+ decisions.multifileAnalysis = analysis;
1277
+
1278
+ const { totalDefaultExports, hasMultipleDefaultExports, selfReferentialFiles, defaultExportFiles: analysisDefaults } = analysis;
1279
+
1280
+
1281
+ const defaultExportFiles = [];
1282
+ for (const { fileName } of analysisDefaults) {
1283
+ const file = moduleFiles.find((f) => path.basename(f.name, path.extname(f.name)) === fileName);
1284
+ if (file) {
1285
+ const analysis = await analyzeModule(path.join(categoryPath, file.name), {
1286
+ debug,
1287
+ instance
1288
+ });
1289
+ const processedMod = processModuleFromAnalysis(analysis, {
1290
+ debug,
1291
+ instance
1292
+ });
1293
+ defaultExportFiles.push({ file, moduleName: instance._toapiPathKey(fileName), mod: processedMod });
1294
+ }
1295
+ }
1296
+
1297
+ if (debug) {
1298
+ console.log(`[DEBUG] buildCategoryDecisions: Using shared multidefault utility results`);
1299
+ console.log(`[DEBUG] - totalDefaultExports: ${totalDefaultExports}`);
1300
+ console.log(`[DEBUG] - hasMultipleDefaultExports: ${hasMultipleDefaultExports}`);
1301
+ console.log(`[DEBUG] - selfReferentialFiles: ${Array.from(selfReferentialFiles)}`);
1302
+ }
1303
+
1304
+
1305
+ for (const file of moduleFiles) {
1306
+ const moduleExt = path.extname(file.name);
1307
+ const moduleName = instance._toapiPathKey(path.basename(file.name, moduleExt));
1308
+
1309
+
1310
+ let mod = null;
1311
+ const existingDefault = defaultExportFiles.find((def) => def.moduleName === moduleName);
1312
+ if (existingDefault) {
1313
+ mod = existingDefault.mod;
1314
+ } else {
1315
+
1316
+ const analysis = await analyzeModule(path.join(categoryPath, file.name), {
1317
+ debug,
1318
+ instance
1319
+ });
1320
+ mod = processModuleFromAnalysis(analysis, {
1321
+ debug,
1322
+ instance
1323
+ });
1324
+ }
1325
+
1326
+ const moduleDecision = {
1327
+ file,
1328
+ moduleName,
1329
+ mod,
1330
+ type: null,
1331
+ apiPathKey: null,
1332
+ shouldFlatten: false,
1333
+ flattenType: null,
1334
+ specialHandling: null
1335
+ };
1336
+
1337
+ if (moduleName === categoryName && mod && typeof mod === "object") {
1338
+ moduleDecision.type = "category-match-object";
1339
+ moduleDecision.specialHandling = "category-merge";
1340
+ } else if (typeof mod === "function") {
1341
+ moduleDecision.type = "function";
1342
+
1343
+
1344
+ const isSelfReferential = selfReferentialFiles.has(moduleName);
1345
+
1346
+ if (hasMultipleDefaultExports && mod.__slothletDefault === true && !isSelfReferential) {
1347
+
1348
+ moduleDecision.apiPathKey = moduleName;
1349
+ moduleDecision.specialHandling = "multi-default-filename";
1350
+ if (debug) {
1351
+ console.log(
1352
+ `[DEBUG] Multi-default function case: ${moduleName} => ${moduleDecision.apiPathKey} (hasMultiple=${hasMultipleDefaultExports}, __slothletDefault=${mod.__slothletDefault}, isSelfRef=${isSelfReferential})`
1353
+ );
1354
+ }
1355
+ } else if (selfReferentialFiles.has(moduleName)) {
1356
+
1357
+ moduleDecision.type = "self-referential";
1358
+ moduleDecision.specialHandling = "self-referential-namespace";
1359
+ } else {
1360
+
1361
+ const fnName = mod.name && mod.name !== "default" ? mod.name : moduleName;
1362
+ if (debug) {
1363
+ console.log(
1364
+ `[DEBUG] Standard function case: ${moduleName}, fnName=${fnName}, mod.__slothletDefault=${mod.__slothletDefault}, hasMultiple=${hasMultipleDefaultExports}`
1365
+ );
1366
+ }
1367
+
1368
+
1369
+
1370
+ if (fnName && fnName.toLowerCase() === moduleName.toLowerCase() && fnName !== moduleName) {
1371
+
1372
+ moduleDecision.apiPathKey = fnName;
1373
+ moduleDecision.specialHandling = "prefer-function-name";
1374
+ } else {
1375
+
1376
+ moduleDecision.apiPathKey = instance._toapiPathKey(fnName);
1377
+ }
1378
+ }
1379
+ } else {
1380
+ moduleDecision.type = "object";
1381
+
1382
+
1383
+ let hasPreferredName = false;
1384
+ const modWithPreferredNames = {};
1385
+
1386
+ for (const [exportName, exportValue] of Object.entries(mod)) {
1387
+ if (
1388
+ typeof exportValue === "function" &&
1389
+ exportValue.name &&
1390
+ instance._toapiPathKey(exportValue.name).toLowerCase() === instance._toapiPathKey(moduleName).toLowerCase() &&
1391
+ exportValue.name !== instance._toapiPathKey(moduleName)
1392
+ ) {
1393
+
1394
+ modWithPreferredNames[exportValue.name] = exportValue;
1395
+ hasPreferredName = true;
1396
+ } else {
1397
+ modWithPreferredNames[instance._toapiPathKey(exportName)] = exportValue;
1398
+ }
1399
+ }
1400
+
1401
+ if (hasPreferredName) {
1402
+ moduleDecision.specialHandling = "preferred-export-names";
1403
+ moduleDecision.processedExports = modWithPreferredNames;
1404
+ } else if (selfReferentialFiles.has(moduleName)) {
1405
+
1406
+ moduleDecision.type = "self-referential";
1407
+ moduleDecision.specialHandling = "self-referential-namespace";
1408
+ } else {
1409
+
1410
+ const moduleKeys = Object.keys(mod).filter((k) => k !== "default");
1411
+ const apiPathKey = instance._toapiPathKey(moduleName);
1412
+
1413
+
1414
+
1415
+ if (!hasMultipleDefaultExports && mod.default && typeof mod.default === "object") {
1416
+ moduleDecision.shouldFlatten = true;
1417
+ moduleDecision.flattenType = "single-default-object";
1418
+ moduleDecision.apiPathKey = apiPathKey;
1419
+ } else if (hasMultipleDefaultExports && !mod.default && moduleKeys.length > 0) {
1420
+
1421
+ moduleDecision.shouldFlatten = true;
1422
+ moduleDecision.flattenType = "multi-default-no-default";
1423
+ } else if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
1424
+
1425
+ moduleDecision.shouldFlatten = true;
1426
+ moduleDecision.flattenType = "single-named-export-match";
1427
+ moduleDecision.apiPathKey = apiPathKey;
1428
+ } else if (!mod.default && moduleKeys.length > 0 && moduleName === categoryName) {
1429
+
1430
+ moduleDecision.shouldFlatten = true;
1431
+ moduleDecision.flattenType = "category-name-match-flatten";
1432
+ } else {
1433
+
1434
+ moduleDecision.apiPathKey = apiPathKey;
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ decisions.processedModules.push(moduleDecision);
1440
+ }
1441
+
1442
+
1443
+ for (const subDir of subDirs) {
1444
+ const subDirPath = path.join(categoryPath, subDir.name);
1445
+ const subDirDecision = {
1446
+ name: subDir.name,
1447
+ path: subDirPath,
1448
+ apiPathKey: instance._toapiPathKey(subDir.name),
1449
+ shouldRecurse: currentDepth < maxDepth
1450
+ };
1451
+ decisions.subdirectoryDecisions.push(subDirDecision);
1452
+ }
1453
+
1454
+ return decisions;
1455
+ }