@reliverse/dler 1.7.75 → 1.7.76

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.
@@ -1,800 +0,0 @@
1
- import path from "@reliverse/pathkit";
2
- import fs from "@reliverse/relifso";
3
- import { relinka } from "@reliverse/relinka";
4
- import { selectPrompt, confirmPrompt, inputPrompt } from "@reliverse/rempts";
5
-
6
- import { getConfigDler } from "~/libs/sdk/sdk-impl/config/load";
7
-
8
- const AGGREGATOR_START = "// AUTO-GENERATED AGGREGATOR START (via `dler agg`)";
9
- const AGGREGATOR_END = "// AUTO-GENERATED AGGREGATOR END";
10
-
11
- /**
12
- * Checks if a file exists at the given path
13
- */
14
- async function fileExists(filePath: string): Promise<boolean> {
15
- return await fs.pathExists(filePath);
16
- }
17
-
18
- /**
19
- * Checks if the first line of a file contains the disable aggregation comment
20
- */
21
- async function isAggregationDisabled(filePath: string): Promise<boolean> {
22
- try {
23
- const content = await fs.readFile(filePath, "utf-8");
24
- const firstLine = content.split("\n")[0]?.trim();
25
- return firstLine === "// <dler-disable-agg>";
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- /**
32
- * Finds the main package based on dler configuration with fallbacks
33
- */
34
- async function findMainEntryFile(config: any): Promise<string | null> {
35
- const { coreEntryFile, coreEntrySrcDir } = config;
36
-
37
- // Check the configured entry file first
38
- if (coreEntryFile && coreEntrySrcDir) {
39
- const configuredPath = path.join(coreEntrySrcDir, coreEntryFile);
40
- if (await fileExists(configuredPath)) {
41
- return configuredPath;
42
- }
43
- }
44
-
45
- // Fallback to common entry file patterns
46
- const fallbackPatterns = [
47
- path.join(coreEntrySrcDir || "src", "mod.ts"),
48
- path.join(coreEntrySrcDir || "src", "index.ts"),
49
- path.join(coreEntrySrcDir || "src", "mod.js"),
50
- path.join(coreEntrySrcDir || "src", "index.js"),
51
- ];
52
-
53
- for (const pattern of fallbackPatterns) {
54
- if (await fileExists(pattern)) {
55
- return pattern;
56
- }
57
- }
58
-
59
- return null;
60
- }
61
-
62
- export async function promptAggCommand() {
63
- // Try to load config and check for libs
64
- const config = await getConfigDler();
65
- let selectedLibName: string | null = null;
66
-
67
- // Check for main package
68
- const mainEntryFile = await findMainEntryFile(config);
69
- const isMainDisabled = mainEntryFile ? await isAggregationDisabled(mainEntryFile) : false;
70
-
71
- if (config?.libsList && Object.keys(config.libsList).length > 0) {
72
- const libEntries = await Promise.all(
73
- Object.entries(config.libsList).map(async ([name, lib]) => {
74
- const libMainFile = `${config.libsDirSrc}/${lib.libMainFile}`;
75
- const isLibDisabled = await isAggregationDisabled(libMainFile);
76
-
77
- return {
78
- name,
79
- lib,
80
- isDisabled: isLibDisabled,
81
- };
82
- }),
83
- );
84
-
85
- const libs = libEntries
86
- .filter(({ isDisabled }) => !isDisabled)
87
- .map(({ name, lib }) => ({
88
- value: name,
89
- label: name,
90
- hint: `${config.libsDirSrc}/${lib.libDirName}/${lib.libDirName}-impl`,
91
- }));
92
-
93
- // Add main package option if found and not disabled
94
- if (mainEntryFile && !isMainDisabled) {
95
- libs.unshift({
96
- value: "main",
97
- label: "Main package",
98
- hint: mainEntryFile,
99
- });
100
- }
101
-
102
- // Add "Skip" option
103
- libs.push({ value: "", label: "Skip selection", hint: "" });
104
-
105
- selectedLibName = await selectPrompt({
106
- title: "Select a package to aggregate or skip",
107
- options: libs,
108
- });
109
- } else if (mainEntryFile && !isMainDisabled) {
110
- // If no libs but main package exists and is not disabled, offer it as the only option
111
- const shouldUseMain = await confirmPrompt({
112
- title: `Use main package for aggregation? (Found: ${mainEntryFile})`,
113
- defaultValue: true,
114
- });
115
-
116
- if (shouldUseMain) {
117
- selectedLibName = "main";
118
- }
119
- }
120
-
121
- // If lib selected, use its config
122
- let imports = false;
123
- let input = "";
124
- let named = true;
125
- let out = "";
126
- let recursive = true;
127
- let strip = "";
128
- let separateTypesFile = false;
129
- let typesOut = "";
130
-
131
- if (selectedLibName && selectedLibName !== "") {
132
- if (selectedLibName === "main" && mainEntryFile && !isMainDisabled) {
133
- // Use main package configuration
134
- const entryDir = path.dirname(mainEntryFile);
135
-
136
- input = entryDir;
137
- out = mainEntryFile;
138
- strip = entryDir;
139
- } else if (selectedLibName === "main" && isMainDisabled) {
140
- // Main package is disabled, exit early
141
- relinka.log("Main package aggregation is disabled due to <dler-disable-agg> comment.");
142
- return;
143
- } else {
144
- // Use library configuration
145
- const libConfig = config?.libsList?.[selectedLibName];
146
- if (config && libConfig) {
147
- input = `${config.libsDirSrc}/${libConfig.libDirName}/${libConfig.libDirName}-impl`;
148
- out = `${config.libsDirSrc}/${libConfig.libMainFile}`;
149
- strip = `${config.libsDirSrc}/${libConfig.libDirName}`;
150
- }
151
- }
152
- }
153
-
154
- // Only prompt for values not set by lib config
155
- if (!selectedLibName || !input) {
156
- input = await inputPrompt({
157
- title: "Enter the input directory",
158
- defaultValue: input,
159
- });
160
-
161
- // Check if manually entered input corresponds to a disabled file
162
- if (input) {
163
- // Check if the input is pointing to a disabled main file (directory or file)
164
- if (mainEntryFile && isMainDisabled) {
165
- const mainEntryDir = path.dirname(mainEntryFile);
166
- if (
167
- path.resolve(input) === path.resolve(mainEntryDir) ||
168
- path.resolve(input) === path.resolve(mainEntryFile)
169
- ) {
170
- relinka.log("Main package aggregation is disabled due to <dler-disable-agg> comment.");
171
- return;
172
- }
173
- }
174
-
175
- // Check if the input is pointing to a disabled library
176
- if (config?.libsList) {
177
- for (const [libName, libConfig] of Object.entries(config.libsList)) {
178
- const libImplPath = `${config.libsDirSrc}/${libConfig.libDirName}/${libConfig.libDirName}-impl`;
179
- const libMainFile = `${config.libsDirSrc}/${libConfig.libMainFile}`;
180
-
181
- if (
182
- path.resolve(input) === path.resolve(libImplPath) ||
183
- path.resolve(input) === path.resolve(libMainFile)
184
- ) {
185
- const isLibDisabled = await isAggregationDisabled(libMainFile);
186
- if (isLibDisabled) {
187
- relinka.log(
188
- `Library "${libName}" aggregation is disabled due to <dler-disable-agg> comment.`,
189
- );
190
- return;
191
- }
192
- }
193
- }
194
- }
195
- }
196
- }
197
-
198
- // Ask for verbose mode first to determine if we should show additional options
199
- const verbose = await confirmPrompt({
200
- title: "Enable verbose logging and additional options?",
201
- defaultValue: false,
202
- });
203
-
204
- // Default values for non-essential options
205
- let sortLines = false;
206
- let headerComment = "";
207
- let includeInternal = false;
208
- let internalMarker = "#";
209
- let overrideFile = false;
210
- let extensions = ".ts,.js,.mts,.cts,.mjs,.cjs";
211
-
212
- // Only ask non-essential questions if verbose mode is enabled
213
- if (verbose) {
214
- sortLines = await confirmPrompt({
215
- title: "Sort aggregated lines alphabetically?",
216
- defaultValue: false,
217
- });
218
-
219
- headerComment = await inputPrompt({
220
- title: "Add a header comment to the aggregator output (optional):",
221
- defaultValue: "",
222
- });
223
-
224
- includeInternal = await confirmPrompt({
225
- title: "Include files marked as internal (starting with #)?",
226
- defaultValue: false,
227
- });
228
-
229
- internalMarker = await inputPrompt({
230
- title: "Marker for internal files:",
231
- defaultValue: "#",
232
- });
233
-
234
- overrideFile = await confirmPrompt({
235
- title: "Override entire file instead of updating only the aggregator block?",
236
- defaultValue: false,
237
- });
238
-
239
- extensions = await inputPrompt({
240
- title: "File extensions to process (comma-separated):",
241
- defaultValue: ".ts,.js,.mts,.cts,.mjs,.cjs",
242
- });
243
-
244
- imports = await confirmPrompt({
245
- title: "Do you want to generate imports instead of exports? (N generates exports)",
246
- defaultValue: imports,
247
- });
248
-
249
- named = await confirmPrompt({
250
- title: imports
251
- ? "Do you want to generate named imports?"
252
- : "Do you want to generate named exports?",
253
- defaultValue: named,
254
- });
255
-
256
- recursive = await confirmPrompt({
257
- title: "Do you want to recursively scan subdirectories?",
258
- defaultValue: recursive,
259
- });
260
-
261
- separateTypesFile = await confirmPrompt({
262
- title: "Do you want to create a separate file for type exports?",
263
- defaultValue: separateTypesFile,
264
- });
265
- }
266
-
267
- if (!selectedLibName || !out) {
268
- out = await inputPrompt({
269
- title: "Enter the output file",
270
- defaultValue: out,
271
- });
272
- }
273
-
274
- if (!selectedLibName || !strip) {
275
- strip = await inputPrompt({
276
- title: "Enter the path to strip from the final imports/exports",
277
- defaultValue: strip,
278
- });
279
- }
280
-
281
- if (separateTypesFile) {
282
- typesOut = await inputPrompt({
283
- title: "Enter the output file for types",
284
- defaultValue: out.replace(/\.(ts|js)$/, ".types.$1"),
285
- });
286
- }
287
-
288
- await useAggregator({
289
- inputDir: path.resolve(input),
290
- isRecursive: recursive,
291
- outFile: path.resolve(out),
292
- stripPrefix: strip ? path.resolve(strip) : "",
293
- useImport: imports,
294
- useNamed: named,
295
- sortLines: sortLines,
296
- headerComment: headerComment,
297
- verbose: verbose,
298
- includeInternal: includeInternal,
299
- internalMarker: internalMarker,
300
- overrideFile: overrideFile,
301
- fileExtensions: extensions.split(",").map((ext) => ext.trim()),
302
- separateTypesFile: separateTypesFile,
303
- typesOutFile: typesOut ? path.resolve(typesOut) : undefined,
304
- });
305
- }
306
-
307
- /**
308
- * Aggregator supporting:
309
- * - --import or default export,
310
- * - star or named exports,
311
- * - separate "type" vs "value" for both import and export.
312
- *
313
- * Options:
314
- * - Option to ignore specific directories (default: node_modules, .git)
315
- * - Option to sort aggregated lines alphabetically.
316
- * - Option to add a header comment in the aggregator output.
317
- * - Option to enable verbose logging.
318
- * - Deduplicates overloaded export names.
319
- * - Skips files whose basenames start with an internal marker (default: "#")
320
- * unless includeInternal is true or an alternative marker is provided.
321
- * - By default, updates only the auto-generated block in the aggregator file,
322
- * leaving any other content intact. Pass `overrideFile: true` to rewrite the entire file.
323
- */
324
- export async function useAggregator({
325
- inputDir,
326
- isRecursive,
327
- outFile,
328
- stripPrefix,
329
- useImport,
330
- useNamed,
331
- ignoreDirs = ["node_modules", ".git"],
332
- sortLines = false,
333
- headerComment = "",
334
- verbose = false,
335
- includeInternal = false,
336
- internalMarker = "#",
337
- overrideFile = false,
338
- fileExtensions = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"],
339
- separateTypesFile = false,
340
- typesOutFile,
341
- }: {
342
- inputDir: string;
343
- isRecursive: boolean;
344
- outFile: string;
345
- stripPrefix: string;
346
- useImport: boolean;
347
- useNamed: boolean;
348
- ignoreDirs?: string[];
349
- sortLines?: boolean;
350
- headerComment?: string;
351
- verbose?: boolean;
352
- includeInternal?: boolean;
353
- internalMarker?: string;
354
- overrideFile?: boolean;
355
- fileExtensions?: string[];
356
- separateTypesFile?: boolean;
357
- typesOutFile?: string;
358
- }) {
359
- try {
360
- // Validate input directory
361
- const st = await fs.stat(inputDir).catch(() => null);
362
- if (!st?.isDirectory()) {
363
- relinka("error", `Error: --input is not a valid directory: ${inputDir}`);
364
- process.exit(1);
365
- }
366
-
367
- // Validate output file directory exists or can be created
368
- const outDir = path.dirname(outFile);
369
- try {
370
- await fs.ensureDir(outDir);
371
- } catch (error) {
372
- relinka("error", `Error: Cannot create output directory: ${outDir}\n${error}`);
373
- process.exit(1);
374
- }
375
-
376
- // Validate types output file directory if separateTypesFile is true
377
- if (separateTypesFile && typesOutFile) {
378
- const typesOutDir = path.dirname(typesOutFile);
379
- try {
380
- await fs.ensureDir(typesOutDir);
381
- } catch (error) {
382
- relinka("error", `Error: Cannot create types output directory: ${typesOutDir}\n${error}`);
383
- process.exit(1);
384
- }
385
- }
386
-
387
- // Validate output file extension matches input extensions
388
- const outExt = path.extname(outFile).toLowerCase();
389
- if (!fileExtensions.includes(outExt)) {
390
- relinka(
391
- "warn",
392
- `Warning: Output file extension (${outExt}) doesn't match any of the input extensions: ${fileExtensions.join(", ")}`,
393
- );
394
- }
395
-
396
- // Validate strip prefix is a valid directory if provided
397
- if (stripPrefix) {
398
- const stripSt = await fs.stat(stripPrefix).catch(() => null);
399
- if (!stripSt?.isDirectory()) {
400
- relinka("error", `Error: --strip is not a valid directory: ${stripPrefix}`);
401
- process.exit(1);
402
- }
403
- }
404
-
405
- // Collect files with specified extensions
406
- if (verbose)
407
- relinka(
408
- "log",
409
- `Scanning directory ${inputDir} for files with extensions: ${fileExtensions.join(", ")}`,
410
- );
411
- const filePaths = await collectFiles(
412
- inputDir,
413
- fileExtensions,
414
- isRecursive,
415
- ignoreDirs,
416
- verbose,
417
- includeInternal,
418
- internalMarker,
419
- outFile,
420
- );
421
- if (!filePaths.length) {
422
- relinka(
423
- "warn",
424
- `No matching files found in ${inputDir} with extensions: ${fileExtensions.join(", ")}`,
425
- );
426
- if (!overrideFile) {
427
- relinka("warn", "No changes will be made to the output file.");
428
- return;
429
- }
430
- }
431
-
432
- // Generate aggregator lines concurrently with unique star-import identifiers
433
- const usedIdentifiers = new Set<string>();
434
- const aggregatorLinesArrays = await Promise.all(
435
- filePaths.map((fp) =>
436
- generateAggregatorLines(
437
- fp,
438
- inputDir,
439
- stripPrefix,
440
- useImport,
441
- useNamed,
442
- usedIdentifiers,
443
- ).catch((error) => {
444
- relinka("error", `Error processing file ${fp}: ${error}`);
445
- return [];
446
- }),
447
- ),
448
- );
449
-
450
- // Separate type and value lines
451
- const allLines = aggregatorLinesArrays.flat();
452
- const typeLines: string[] = [];
453
- const valueLines: string[] = [];
454
-
455
- for (const line of allLines) {
456
- if (line.includes("type {")) {
457
- typeLines.push(line);
458
- } else {
459
- valueLines.push(line);
460
- }
461
- }
462
-
463
- // Optionally sort lines alphabetically
464
- if (sortLines) {
465
- typeLines.sort();
466
- valueLines.sort();
467
- if (verbose) relinka("log", "Sorted aggregator lines alphabetically.");
468
- }
469
-
470
- // Build the aggregator block content
471
- const buildAggregatorBlock = (lines: string[]) =>
472
- `${headerComment ? `${headerComment}\n` : ""}${AGGREGATOR_START}\n${lines.join("\n")}\n${AGGREGATOR_END}\n`;
473
-
474
- if (separateTypesFile && typesOutFile) {
475
- // Write type exports to separate file
476
- const typeBlock = buildAggregatorBlock(typeLines);
477
- await fs.ensureFile(typesOutFile);
478
- await fs.writeFile(typesOutFile, typeBlock, "utf8");
479
-
480
- // Write value exports to main file, including type file import
481
- const valueBlock = buildAggregatorBlock([
482
- ...valueLines,
483
- `export * from "${path.relative(path.dirname(outFile), typesOutFile).replace(/\\/g, "/")}";`,
484
- ]);
485
- await fs.ensureFile(outFile);
486
- await fs.writeFile(outFile, valueBlock, "utf8");
487
-
488
- relinka(
489
- "success",
490
- `Aggregator done: processed ${typeLines.length} type lines in: ${typesOutFile} and ${valueLines.length} value lines in: ${outFile}`,
491
- );
492
- } else {
493
- // Write all lines to single file
494
- const aggregatorBlock = buildAggregatorBlock(allLines);
495
- await fs.ensureFile(outFile);
496
- await fs.writeFile(outFile, aggregatorBlock, "utf8");
497
-
498
- relinka("success", `Aggregator done: processed ${allLines.length} lines in: ${outFile}`);
499
- }
500
- } catch (error) {
501
- relinka("error", `Aggregator failed: ${error}`);
502
- process.exit(1);
503
- }
504
- }
505
-
506
- /**
507
- * Build a relative import/export path, removing `stripPrefix` if it is truly a prefix,
508
- * converting .ts -> .js, and ensuring it starts with "./" or "../".
509
- */
510
- function buildPathRelative(filePath: string, inputDir: string, stripPrefix: string): string {
511
- let resolved = path.resolve(filePath);
512
- const resolvedStrip = stripPrefix ? path.resolve(stripPrefix) : "";
513
-
514
- // If stripPrefix applies, remove it; otherwise, compute a relative path.
515
- if (resolvedStrip && resolved.startsWith(resolvedStrip)) {
516
- resolved = resolved.slice(resolvedStrip.length);
517
- } else {
518
- resolved = path.relative(path.resolve(inputDir), resolved);
519
- }
520
-
521
- // Remove any leading path separator(s)
522
- while (resolved.startsWith(path.sep)) {
523
- resolved = resolved.slice(1);
524
- }
525
-
526
- // Normalize backslashes to forward slashes
527
- resolved = resolved.replace(/\\/g, "/");
528
-
529
- // Convert .ts -> .js extension
530
- if (resolved.toLowerCase().endsWith(".ts")) {
531
- resolved = `${resolved.slice(0, -3)}.js`;
532
- }
533
-
534
- // Ensure the path starts with "./" or "../" only if it doesn't already
535
- if (!resolved.startsWith("./") && !resolved.startsWith("../")) {
536
- resolved = `./${resolved}`;
537
- }
538
-
539
- // Fix any double slashes in the path
540
- resolved = resolved.replace(/\/+/g, "/");
541
-
542
- return resolved;
543
- }
544
-
545
- /**
546
- * Recursively collects files with given extensions, ignoring specified directories
547
- * and files marked as internal.
548
- */
549
- async function collectFiles(
550
- dir: string,
551
- exts: string[],
552
- recursive: boolean,
553
- ignoreDirs: string[],
554
- verbose: boolean,
555
- includeInternal: boolean,
556
- internalMarker: string,
557
- outFile?: string,
558
- ): Promise<string[]> {
559
- const found: string[] = [];
560
- const entries = await fs.readdir(dir, { withFileTypes: true });
561
-
562
- for (const entry of entries) {
563
- const fullPath = path.join(dir, entry.name);
564
-
565
- // Skip the output file if it matches
566
- if (outFile && path.resolve(fullPath) === path.resolve(outFile)) {
567
- if (verbose) {
568
- relinka("log", `Skipping output file: ${fullPath}`);
569
- }
570
- continue;
571
- }
572
-
573
- if (entry.isDirectory()) {
574
- if (ignoreDirs.includes(entry.name)) {
575
- if (verbose) {
576
- relinka("log", `Skipping ignored directory: ${fullPath}`);
577
- }
578
- continue;
579
- }
580
- if (recursive) {
581
- const sub = await collectFiles(
582
- fullPath,
583
- exts,
584
- recursive,
585
- ignoreDirs,
586
- verbose,
587
- includeInternal,
588
- internalMarker,
589
- outFile,
590
- );
591
- found.push(...sub);
592
- }
593
- } else if (entry.isFile()) {
594
- // Skip file if its basename starts with the internal marker and internal files are not included.
595
- if (!includeInternal && path.basename(fullPath).startsWith(internalMarker)) {
596
- if (verbose) {
597
- relinka("log", `Skipping internal file: ${fullPath}`);
598
- }
599
- continue;
600
- }
601
- if (exts.some((ext) => entry.name.toLowerCase().endsWith(ext))) {
602
- if (verbose) {
603
- relinka("log", `Found file: ${fullPath}`);
604
- }
605
- found.push(fullPath);
606
- }
607
- }
608
- }
609
- return found;
610
- }
611
-
612
- /**
613
- * Creates aggregator lines for a single file.
614
- *
615
- * If `useNamed` is true, parses named exports and produces up to two lines:
616
- * - import type { ... } / export type { ... }
617
- * - import { ... } / export { ... }
618
- *
619
- * If `useNamed` is false, produces a single star import or export.
620
- *
621
- * @param usedIdentifiers A set to track and ensure unique identifiers for star imports.
622
- */
623
- async function generateAggregatorLines(
624
- filePath: string,
625
- inputDir: string,
626
- stripPrefix: string,
627
- useImport: boolean,
628
- useNamed: boolean,
629
- usedIdentifiers?: Set<string>,
630
- ): Promise<string[]> {
631
- const importPath = buildPathRelative(filePath, inputDir, stripPrefix);
632
-
633
- // Star import/export approach when not using named exports
634
- if (!useNamed) {
635
- if (useImport) {
636
- let ident = guessStarImportIdentifier(filePath);
637
- if (usedIdentifiers) {
638
- let uniqueIdent = ident;
639
- let counter = 1;
640
- while (usedIdentifiers.has(uniqueIdent)) {
641
- uniqueIdent = `${ident}_${counter}`;
642
- counter++;
643
- }
644
- usedIdentifiers.add(uniqueIdent);
645
- ident = uniqueIdent;
646
- }
647
- return [`import * as ${ident} from "${importPath}";`];
648
- }
649
- return [`export * from "${importPath}";`];
650
- }
651
-
652
- // For named exports, parse the file to extract export names.
653
- const { typeNames, valueNames } = await getNamedExports(filePath);
654
- if (!typeNames.length && !valueNames.length) {
655
- return [];
656
- }
657
-
658
- if (useImport) {
659
- const lines: string[] = [];
660
- if (typeNames.length > 0) {
661
- lines.push(`import type { ${typeNames.join(", ")} } from "${importPath}";`);
662
- }
663
- if (valueNames.length > 0) {
664
- lines.push(`import { ${valueNames.join(", ")} } from "${importPath}";`);
665
- }
666
- return lines;
667
- }
668
-
669
- // For exports
670
- const lines: string[] = [];
671
- if (typeNames.length > 0) {
672
- lines.push(`export type { ${typeNames.join(", ")} } from "${importPath}";`);
673
- }
674
- if (valueNames.length > 0) {
675
- lines.push(`export { ${valueNames.join(", ")} } from "${importPath}";`);
676
- }
677
- return lines;
678
- }
679
-
680
- /**
681
- * Parses a file to extract named exports, separating type exports from value exports.
682
- * Deduplicates export names (to handle overloads).
683
- */
684
- async function getNamedExports(
685
- filePath: string,
686
- ): Promise<{ typeNames: string[]; valueNames: string[] }> {
687
- try {
688
- const code = await fs.readFile(filePath, "utf8");
689
- const typeNamesSet = new Set<string>();
690
- const valueNamesSet = new Set<string>();
691
-
692
- // Match various export patterns:
693
- // 1. Regular exports: export const/let/var/function/class/interface/type/enum
694
- // 2. Default exports: export default class/function/const/interface
695
- // 3. Named exports: export { name, name2 as alias }
696
- // 4. Re-exports: export { name } from './other'
697
- // 5. Export assignments: export = name
698
- const patterns = [
699
- // Regular exports and default exports
700
- /^export\s+(?:default\s+)?(?:async\s+)?(function|const|class|let|var|type|interface|enum)\s+([A-Za-z0-9_$]+)/gm,
701
- // Named exports and re-exports
702
- /^export\s*{([^}]+)}(?:\s+from\s+['"][^'"]+['"])?/gm,
703
- // Export assignments
704
- /^export\s*=\s*([A-Za-z0-9_$]+)/gm,
705
- ];
706
-
707
- for (const pattern of patterns) {
708
- let match: RegExpExecArray | null;
709
- while (true) {
710
- match = pattern.exec(code);
711
- if (!match) break;
712
-
713
- const matchGroups = match as RegExpExecArray & Record<number, string>;
714
- if (pattern.source.includes("{([^}]+)}") && matchGroups[1]) {
715
- // Handle named exports/re-exports
716
- const exports = (matchGroups[1] ?? "").split(",").map(
717
- (e) =>
718
- e
719
- ?.trim()
720
- ?.split(/\s+as\s+/)?.[0]
721
- ?.trim() ?? "",
722
- );
723
- for (const exp of exports) {
724
- // Skip 'type' keyword in named exports
725
- const name = exp.replace(/^type\s+/, "");
726
- if (exp.startsWith("type ")) {
727
- typeNamesSet.add(name);
728
- } else {
729
- valueNamesSet.add(name);
730
- }
731
- }
732
- } else if (pattern.source.includes("=\\s*([A-Za-z0-9_$]+)") && matchGroups[1]) {
733
- // Handle export assignments
734
- valueNamesSet.add(matchGroups[1]);
735
- } else {
736
- // Handle regular exports
737
- const keyword = matchGroups[1];
738
- const name = matchGroups[2];
739
- if (keyword && name) {
740
- if (keyword === "type" || keyword === "interface" || keyword === "enum") {
741
- typeNamesSet.add(name);
742
- } else {
743
- valueNamesSet.add(name);
744
- }
745
- }
746
- }
747
- }
748
- }
749
-
750
- return {
751
- typeNames: Array.from(typeNamesSet),
752
- valueNames: Array.from(valueNamesSet),
753
- };
754
- } catch (error) {
755
- relinka("error", `Error reading file ${filePath}: ${error}`);
756
- return { typeNames: [], valueNames: [] };
757
- }
758
- }
759
-
760
- /**
761
- * Generates a valid identifier for star imports based on the file name.
762
- * For star imports, we generate an identifier from the filename, e.g.
763
- * 'utils-cwd.ts' -> 'utils_cwd'
764
- * so we do: import * as utils_cwd from "./utils-cwd";
765
- */
766
- function guessStarImportIdentifier(filePath: string): string {
767
- // e.g. "foo-bar.ts" => baseName is "foo-bar"
768
- const base = path.basename(filePath, path.extname(filePath));
769
- // Replace invalid chars
770
- let identifier = base.replace(/[^a-zA-Z0-9_$]/g, "_");
771
- // If it starts with digit, prefix underscore
772
- if (/^\d/.test(identifier)) {
773
- identifier = `_${identifier}`;
774
- }
775
- return identifier || "file";
776
- }
777
-
778
- /**
779
- * Prints usage examples based on whether dev mode or not.
780
- */
781
- export function printUsage(isDev?: boolean) {
782
- relinka("log", "====================");
783
- relinka("log", "TOOLS USAGE EXAMPLES");
784
- relinka("log", "====================");
785
- relinka(
786
- "log",
787
- `${isDev ? "bun dev:agg" : "dler tools"} --tool agg --input <dir> --out <file> [options]`,
788
- );
789
- if (isDev) {
790
- relinka(
791
- "log",
792
- "bun dev:tools agg --input src/libs/sdk/sdk-impl --out src/libs/sdk/sdk-mod.ts --recursive --named --strip src/libs/sdk",
793
- );
794
- } else {
795
- relinka(
796
- "log",
797
- "dler tools --tool agg --input src/libs --out aggregator.ts --recursive --named",
798
- );
799
- }
800
- }