@bagelink/sdk 1.2.151 → 1.3.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.
package/bin/index.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  import fs from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
  import { openAPI } from '@bagelink/sdk'
6
+ import { splitClientCode } from './splitClientGen'
6
7
  import { formatAndWriteCode, getKwargs, handleAuthCode, runEsLintOnDir } from './utils'
7
8
 
8
- const { bagelinkDir, withAuth, openApiUrl, proc } = getKwargs()
9
+ const { bagelinkDir, withAuth, shouldSplitFiles, openApiUrl, proc } = getKwargs()
9
10
 
10
11
  export async function loadTypes() {
11
12
  const { types, code } = await openAPI(
@@ -15,6 +16,7 @@ export async function loadTypes() {
15
16
 
16
17
  if (!fs.existsSync(bagelinkDir)) fs.mkdirSync(bagelinkDir)
17
18
 
19
+ // Generate monolithic files first
18
20
  const typesPath = join(bagelinkDir, 'types.d.ts')
19
21
  await formatAndWriteCode(typesPath, types)
20
22
 
@@ -24,8 +26,26 @@ export async function loadTypes() {
24
26
  const indexPath = join(bagelinkDir, 'index.ts')
25
27
  await formatAndWriteCode(indexPath, `export * from './api'\nexport * from './types.d'`)
26
28
 
27
- await handleAuthCode(withAuth, bagelinkDir)
29
+ // Optionally split into organized files
30
+ if (shouldSplitFiles) {
31
+ try {
32
+ await splitClientCode({
33
+ bagelinkDir,
34
+ useDirectories: true,
35
+ })
36
+ // remove monolithic files if splitting is successful
37
+ console.log('Cleaning up monolithic temporary files...')
38
+ fs.rmSync(apiPath, { force: true })
39
+ fs.rmSync(typesPath, { force: true })
40
+ fs.rmSync(indexPath, { force: true })
41
+ } catch (error) {
42
+ console.error('Error splitting client code:', error)
43
+ // Continue with monolithic files if splitting fails
44
+ }
45
+ }
28
46
 
47
+ await handleAuthCode(withAuth, bagelinkDir)
48
+ console.log('Attempting to format generated files...')
29
49
  await runEsLintOnDir(bagelinkDir)
30
50
 
31
51
  console.log('Client & type files generated successfully!!!')
@@ -0,0 +1,718 @@
1
+ import type { TSESTree } from '@typescript-eslint/typescript-estree'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { parse, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'
5
+ import { formatAndWriteCode } from './utils'
6
+
7
+ /**
8
+ * Converts a string to camelCase format (first letter lowercase, rest unchanged)
9
+ * @param str - The string to convert
10
+ * @returns The camelCase version of the string
11
+ */
12
+ function toCamelCase(str: string): string {
13
+ if (!str) return ''
14
+ return str.charAt(0).toLowerCase() + str.slice(1)
15
+ }
16
+
17
+ /**
18
+ * Configuration for the client code splitter
19
+ */
20
+ export interface SplitClientOptions {
21
+ /**
22
+ * Directory containing the generated files
23
+ */
24
+ bagelinkDir: string
25
+
26
+ /**
27
+ * Whether to organize into separate directories
28
+ */
29
+ useDirectories?: boolean
30
+
31
+ /**
32
+ * Strategy for grouping API and type declarations
33
+ * Default is to use the first PascalCase word in the name
34
+ */
35
+ groupingStrategy?: GroupingStrategy
36
+ }
37
+
38
+ /**
39
+ * Enhanced type for controlling how files are grouped
40
+ */
41
+ export interface GroupingStrategy {
42
+ /**
43
+ * Extract the group name from a declaration name
44
+ * @param name The name of the type or API function
45
+ * @returns The group name to use
46
+ */
47
+ getGroupName: (name: string) => string
48
+
49
+ /**
50
+ * Get the file name for a group
51
+ * @param group The group name
52
+ * @param kind Whether this is a 'types' or 'api' file
53
+ * @returns The file name to use (without directory path)
54
+ */
55
+ getFileName: (group: string, kind: 'types' | 'api') => string
56
+ }
57
+
58
+ /**
59
+ * Default grouping strategy using the first PascalCase word
60
+ */
61
+ export function createDefaultGroupingStrategy(groupingPattern = /^([A-Z][a-z]+)/): GroupingStrategy {
62
+ return {
63
+ getGroupName(name: string): string {
64
+ const match = name.match(groupingPattern)
65
+ return match ? match[1] : 'Common'
66
+ },
67
+
68
+ getFileName(group: string, kind: 'types' | 'api'): string {
69
+ // Use the utility function for consistent camelCase conversion
70
+ const suffix = kind === 'types' ? 'Types' : 'Api'
71
+ return `${toCamelCase(group)}${suffix}.ts`
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Group information for code organization
78
+ */
79
+ interface GroupInfo {
80
+ types: Set<string>
81
+ apis: Set<string>
82
+ dependencies: Set<string>
83
+ }
84
+
85
+ /**
86
+ * Parse TypeScript code with consistent options
87
+ */
88
+ function parseTypeScript(code: string) {
89
+ return parse(code, {
90
+ ecmaVersion: 2020,
91
+ sourceType: 'module',
92
+ loc: true,
93
+ range: true, // Critical: This enables position tracking in the AST
94
+ tokens: true,
95
+ comment: true,
96
+ })
97
+ }
98
+
99
+ /**
100
+ * Splits the monolithic client code into organized files and directories
101
+ */
102
+ export async function splitClientCode(options: SplitClientOptions): Promise<void> {
103
+ const { bagelinkDir, useDirectories = true, groupingStrategy = createDefaultGroupingStrategy() } = options
104
+ console.log(`Splitting client code in: ${bagelinkDir}`)
105
+
106
+ // Create output directories
107
+ const typesDir = useDirectories ? path.join(bagelinkDir, 'types') : bagelinkDir
108
+ const apisDir = useDirectories ? path.join(bagelinkDir, 'api') : bagelinkDir
109
+
110
+ if (useDirectories) {
111
+ if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir, { recursive: true })
112
+ if (!fs.existsSync(apisDir)) fs.mkdirSync(apisDir, { recursive: true })
113
+ }
114
+
115
+ // Read the generated files
116
+ const typesFile = path.join(bagelinkDir, 'types.d.ts')
117
+ const apiFile = path.join(bagelinkDir, 'api.ts')
118
+
119
+ if (!fs.existsSync(typesFile) || !fs.existsSync(apiFile)) {
120
+ throw new Error('Generated client code files not found. Run the generator first.')
121
+ }
122
+
123
+ const typesContent = fs.readFileSync(typesFile, 'utf-8')
124
+ const apiContent = fs.readFileSync(apiFile, 'utf-8')
125
+
126
+ // Extract commonalities first - imports, utility functions, etc.
127
+ const { commonCode, axiosInstance } = extractCommonCode(apiContent)
128
+
129
+ // Parse and organize types
130
+ const typeGroups = organizeTypes(typesContent, groupingStrategy)
131
+
132
+ // Parse and organize API functions
133
+ const apiGroups = organizeApiFunctions(apiContent, groupingStrategy)
134
+
135
+ // Combine the groups information for dependency tracking
136
+ const groups = mergeGroupInfo(typeGroups, apiGroups)
137
+
138
+ // Write the common code
139
+ if (useDirectories) {
140
+ await formatAndWriteCode(
141
+ path.join(apisDir, 'common.ts'),
142
+ commonCode
143
+ )
144
+
145
+ // Create proper axios export
146
+ await formatAndWriteCode(
147
+ path.join(apisDir, 'axios.ts'),
148
+ axiosInstance
149
+ )
150
+ }
151
+
152
+ // Generate and write files for each group
153
+ for (const [group, info] of Object.entries(groups)) {
154
+ // Write types file
155
+ if (info.types.size > 0) {
156
+ const dependencies = getDependencyImports(info.dependencies, groups, 'types')
157
+ const typesOutput = generateTypesFile(group, info.types, typesContent, dependencies)
158
+ const typesFileName = useDirectories
159
+ ? path.join(typesDir, groupingStrategy.getFileName(group, 'types'))
160
+ : path.join(bagelinkDir, groupingStrategy.getFileName(group, 'types'))
161
+
162
+ await formatAndWriteCode(typesFileName, typesOutput)
163
+ }
164
+
165
+ // Write API file
166
+ if (info.apis.size > 0) {
167
+ const dependencies = getDependencyImports(info.dependencies, groups, 'apis')
168
+ const apisOutput = generateApiFile(
169
+ group,
170
+ info.apis,
171
+ apiContent,
172
+ dependencies,
173
+ useDirectories
174
+ )
175
+
176
+ const apiFileName = path.join(
177
+ apisDir,
178
+ groupingStrategy.getFileName(group, 'api')
179
+ )
180
+
181
+ await formatAndWriteCode(apiFileName, apisOutput)
182
+ }
183
+ }
184
+
185
+ // Create index files
186
+ if (useDirectories) {
187
+ // Types index
188
+ const typesIndexContent = Object.keys(groups)
189
+ .filter(group => groups[group].types.size > 0)
190
+ .map(group => `export * from './${toCamelCase(group)}Types';`)
191
+ .join('\n')
192
+
193
+ await formatAndWriteCode(
194
+ path.join(typesDir, 'index.ts'),
195
+ typesIndexContent
196
+ )
197
+
198
+ // API index - Export axios only ONCE
199
+ const apiIndexContent
200
+ = `export { axios } from './axios';\n${
201
+ Object.keys(groups)
202
+ .filter(group => groups[group].apis.size > 0)
203
+ .map(group => `export * from './${toCamelCase(group)}Api';`)
204
+ .join('\n')}`
205
+
206
+ await formatAndWriteCode(
207
+ path.join(apisDir, 'index.ts'),
208
+ apiIndexContent
209
+ )
210
+
211
+ // Root index
212
+ await formatAndWriteCode(
213
+ path.join(bagelinkDir, 'index.ts'),
214
+ `export * from './types';\nexport * from './api';`
215
+ )
216
+ } else {
217
+ // Flat structure - create a single index file
218
+ const indexContent = Object.keys(groups)
219
+ .map((group) => {
220
+ const exports = []
221
+ if (groups[group].types.size > 0) {
222
+ exports.push(`export * from './${toCamelCase(group)}Types';`)
223
+ }
224
+ if (groups[group].apis.size > 0) {
225
+ exports.push(`export * from './${toCamelCase(group)}Api';`)
226
+ }
227
+ return exports.join('\n')
228
+ })
229
+ .join('\n')
230
+
231
+ await formatAndWriteCode(
232
+ path.join(bagelinkDir, 'index.ts'),
233
+ indexContent
234
+ )
235
+ }
236
+
237
+ console.log('Client code successfully split into organized files!')
238
+ }
239
+
240
+ /**
241
+ * Extracts common code that should be shared across all API files
242
+ */
243
+ function extractCommonCode(apiContent: string): { commonCode: string, axiosInstance: string } {
244
+ // Use the consistent parsing function with range tracking
245
+ const ast = parseTypeScript(apiContent)
246
+
247
+ const imports: string[] = []
248
+ const axiosDefinition: string[] = []
249
+ const utilityTypes: string[] = []
250
+
251
+ for (const node of ast.body) {
252
+ // Safely access range information with proper null checks
253
+ if (!node.range) {
254
+ console.warn(`Node missing range information: ${node.type}`)
255
+ continue
256
+ }
257
+
258
+ // Get imports
259
+ if (node.type === 'ImportDeclaration') {
260
+ imports.push(apiContent.substring(node.range[0], node.range[1]))
261
+ }
262
+
263
+ // Get axios setup
264
+ if (node.type === 'ExportNamedDeclaration'
265
+ && node.declaration?.type === 'VariableDeclaration'
266
+ && node.declaration.declarations.some(d => d.id.type === 'Identifier' && d.id.name === 'axios')) {
267
+ axiosDefinition.push(apiContent.substring(node.range[0], node.range[1]))
268
+ }
269
+
270
+ // Get utility interfaces like UploadOptions
271
+ if (node.type === 'ExportNamedDeclaration'
272
+ && node.declaration?.type === 'TSInterfaceDeclaration') {
273
+ utilityTypes.push(apiContent.substring(node.range[0], node.range[1]))
274
+ }
275
+ }
276
+
277
+ const commonCode = [
278
+ ...imports,
279
+ ...utilityTypes
280
+ ].join('\n\n')
281
+
282
+ const axiosInstance = [
283
+ ...imports.filter(i => i.includes('axios')),
284
+ ...axiosDefinition
285
+ ].join('\n\n')
286
+
287
+ return { commonCode, axiosInstance }
288
+ }
289
+
290
+ /**
291
+ * Organizes type definitions into logical groups
292
+ */
293
+ function organizeTypes(typesContent: string, groupingStrategy: GroupingStrategy): Record<string, GroupInfo> {
294
+ const ast = parseTypeScript(typesContent)
295
+ const groups: Record<string, GroupInfo> = {}
296
+
297
+ for (const node of ast.body) {
298
+ // Skip nodes without range info
299
+ if (!node.range) continue
300
+
301
+ if (node.type === 'ExportNamedDeclaration'
302
+ && (node.declaration?.type === 'TSTypeAliasDeclaration'
303
+ || node.declaration?.type === 'TSInterfaceDeclaration')) {
304
+ const typeName = node.declaration.id.name
305
+ const group = groupingStrategy.getGroupName(typeName)
306
+
307
+ if (!groups[group]) {
308
+ groups[group] = {
309
+ types: new Set(),
310
+ apis: new Set(),
311
+ dependencies: new Set()
312
+ }
313
+ }
314
+
315
+ groups[group].types.add(typeName)
316
+
317
+ // Collect dependencies for this type
318
+ const dependencies = findTypeDependencies(node, typesContent)
319
+ for (const dep of dependencies) {
320
+ groups[group].dependencies.add(dep)
321
+ }
322
+ }
323
+ }
324
+
325
+ return groups
326
+ }
327
+
328
+ /**
329
+ * Organizes API functions into logical groups with enhanced type dependency detection
330
+ */
331
+ function organizeApiFunctions(apiContent: string, groupingStrategy: GroupingStrategy): Record<string, GroupInfo> {
332
+ const ast = parseTypeScript(apiContent)
333
+ const groups: Record<string, GroupInfo> = {}
334
+
335
+ // First pass: identify top-level exports
336
+ for (const node of ast.body) {
337
+ if (!node.range) continue
338
+
339
+ // Handle direct function exports
340
+ if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') {
341
+ for (const declarator of node.declaration.declarations) {
342
+ if (declarator.id.type === 'Identifier') {
343
+ const functionName = declarator.id.name
344
+
345
+ // Skip utility exports
346
+ if (['axios', 'UploadOptions', 'baseURL', 'endpoints', 'schemas'].includes(functionName)) {
347
+ continue
348
+ }
349
+
350
+ // Check if this is a nested API object (like `auth: {...}` or `dataExports: {...}`)
351
+ if (declarator.init?.type === 'ObjectExpression') {
352
+ // For object groups, use the object name as the group name directly
353
+ // rather than applying the grouping strategy
354
+ const groupName = functionName
355
+
356
+ if (!groups[groupName]) {
357
+ groups[groupName] = {
358
+ types: new Set(),
359
+ apis: new Set(),
360
+ dependencies: new Set()
361
+ }
362
+ }
363
+
364
+ groups[groupName].apis.add(functionName)
365
+
366
+ // Extract types from the entire object
367
+ const objectCode = apiContent.substring(node.range[0], node.range[1])
368
+ const typeRefs = findTypeReferences(objectCode)
369
+
370
+ // Add AxiosResponse explicitly as a dependency
371
+ typeRefs.push('AxiosResponse')
372
+
373
+ for (const typeRef of typeRefs) {
374
+ groups[groupName].dependencies.add(typeRef)
375
+ }
376
+
377
+ continue
378
+ }
379
+
380
+ // Regular function export - use grouping strategy
381
+ const group = groupingStrategy.getGroupName(functionName)
382
+
383
+ if (!groups[group]) {
384
+ groups[group] = {
385
+ types: new Set(),
386
+ apis: new Set(),
387
+ dependencies: new Set()
388
+ }
389
+ }
390
+
391
+ groups[group].apis.add(functionName)
392
+
393
+ // Find type dependencies in the function
394
+ const functionCode = apiContent.substring(node.range[0], node.range[1])
395
+ const typeRefs = findTypeReferences(functionCode)
396
+
397
+ for (const typeRef of typeRefs) {
398
+ groups[group].dependencies.add(typeRef)
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ return groups
406
+ }
407
+
408
+ /**
409
+ * Pre-compiled regex patterns for type reference detection
410
+ * Defining these outside functions prevents unnecessary recompilation
411
+ */
412
+ const TYPE_PATTERNS = {
413
+ // PascalCase type identifiers (handles leading underscores and interface patterns)
414
+ PASCAL_CASE_TYPE: /(?<!['"$_.])\b_?[A-Z]\w*\b(?!\s*:)/g,
415
+
416
+ // Type parameters in generics (e.g., Promise<UserResponse>)
417
+ GENERIC_TYPE_PARAMS: /<(_?[A-Z]\w*(?:, *_?[A-Z]\w*)*)>/g,
418
+
419
+ // Return type annotations including AxiosResponse pattern
420
+ RETURN_TYPE_ANNOTATION: /\): *Promise<(?:AxiosResponse<)?(_?[A-Z]\w*)(?:\[\])?>+/g,
421
+
422
+ // Parameter type annotations
423
+ PARAM_TYPE_ANNOTATION: /: *(_?[A-Z]\w*)\b/g,
424
+
425
+ // Types used in extends, implements, etc.
426
+ TYPE_RELATION: /(?:extends|implements|keyof|typeof|infer)\s+(_?[A-Z]\w*)/g
427
+ }
428
+
429
+ /**
430
+ * Type reference matcher that validates if a string is likely a proper type name
431
+ */
432
+ const TYPE_NAME_VALIDATOR = /^_?[A-Z]\w*/
433
+
434
+ /**
435
+ * Regex for cleaning up type names by removing brackets and other syntax characters
436
+ */
437
+ const TYPE_CLEANUP_PATTERN = /[<>[\]]/g
438
+
439
+ /**
440
+ * Enhanced type reference finder with more robust pattern matching
441
+ * using pre-compiled regex patterns
442
+ */
443
+ function findTypeReferences(code: string): string[] {
444
+ const matches = new Set<string>()
445
+
446
+ // Process each pattern with a clean approach to avoid regex stateful issues
447
+ Object.values(TYPE_PATTERNS).forEach((pattern) => {
448
+ // Create a fresh copy of the pattern to avoid stateful regex issues
449
+ const freshPattern = new RegExp(pattern.source, pattern.flags)
450
+
451
+ // Use matchAll for cleaner iteration over all matches
452
+ const allMatches = [...code.matchAll(freshPattern)]
453
+
454
+ for (const matchArray of allMatches) {
455
+ // Get the captured group if it exists, otherwise use the full match
456
+ const matchValue = matchArray[1] || matchArray[0]
457
+
458
+ // Process potential comma-separated type lists (for generics)
459
+ matchValue.split(',').forEach((type) => {
460
+ const trimmedType = type.trim().replace(TYPE_CLEANUP_PATTERN, '')
461
+
462
+ // Validate it's a proper type name using our validator
463
+ if (trimmedType && TYPE_NAME_VALIDATOR.test(trimmedType)) {
464
+ matches.add(trimmedType)
465
+ }
466
+ })
467
+ }
468
+ })
469
+
470
+ // Always include AxiosResponse for API files
471
+ matches.add('AxiosResponse')
472
+
473
+ return [...matches]
474
+ }
475
+
476
+ /**
477
+ * Merges type and API group information
478
+ */
479
+ function mergeGroupInfo(
480
+ typeGroups: Record<string, GroupInfo>,
481
+ apiGroups: Record<string, GroupInfo>
482
+ ): Record<string, GroupInfo> {
483
+ const result: Record<string, GroupInfo> = {}
484
+
485
+ // Start with all type groups
486
+ for (const [group, info] of Object.entries(typeGroups)) {
487
+ result[group] = {
488
+ types: new Set(info.types),
489
+ apis: new Set(),
490
+ dependencies: new Set(info.dependencies)
491
+ }
492
+ }
493
+
494
+ // Add or merge API groups
495
+ for (const [group, info] of Object.entries(apiGroups)) {
496
+ if (!result[group]) {
497
+ result[group] = {
498
+ types: new Set(),
499
+ apis: new Set(info.apis),
500
+ dependencies: new Set(info.dependencies)
501
+ }
502
+ } else {
503
+ for (const api of info.apis) {
504
+ result[group].apis.add(api)
505
+ }
506
+
507
+ for (const dep of info.dependencies) {
508
+ result[group].dependencies.add(dep)
509
+ }
510
+ }
511
+ }
512
+
513
+ return result
514
+ }
515
+
516
+ export type ProgramStatement = ReturnType<typeof parse>['body'][number]
517
+
518
+ /**
519
+ * Finds type dependencies in a type declaration
520
+ */
521
+ function findTypeDependencies(node: ProgramStatement, _typesContent: string): string[] {
522
+ const dependencies = new Set<string>()
523
+
524
+ // Type guard functions for type safety
525
+ function isTypeReference(node: unknown): node is TSESTree.TSTypeReference {
526
+ return node !== null && typeof node === 'object' && 'type' in node && node.type === AST_NODE_TYPES.TSTypeReference
527
+ }
528
+
529
+ function isIdentifier(node: unknown): node is TSESTree.Identifier {
530
+ return node !== null && typeof node === 'object' && 'type' in node && node.type === AST_NODE_TYPES.Identifier
531
+ }
532
+
533
+ // Simple approach - extract all identifiers from type references
534
+ function visit(node: unknown) {
535
+ // Handle type references
536
+ if (isTypeReference(node)) {
537
+ if ('typeName' in node && isIdentifier(node.typeName)) {
538
+ dependencies.add(node.typeName.name)
539
+ }
540
+ }
541
+
542
+ // Recursively visit child nodes
543
+ if (node && typeof node === 'object') {
544
+ // Handle arrays
545
+ if (Array.isArray(node)) {
546
+ node.forEach((item) => { visit(item) })
547
+ return
548
+ }
549
+
550
+ // Skip certain properties
551
+ const skipProps = ['parent', 'range', 'loc']
552
+
553
+ // Visit each property
554
+ Object.entries(node).forEach(([key, value]) => {
555
+ if (!skipProps.includes(key) && value && typeof value === 'object') {
556
+ visit(value)
557
+ }
558
+ })
559
+ }
560
+ }
561
+
562
+ visit(node)
563
+ return Array.from(dependencies)
564
+ }
565
+
566
+ /**
567
+ * Generates import statements for dependencies
568
+ */
569
+ function getDependencyImports(
570
+ dependencies: Set<string>,
571
+ groups: Record<string, GroupInfo>,
572
+ _kind: 'types' | 'apis'
573
+ ): Record<string, string[]> {
574
+ const imports: Record<string, string[]> = {}
575
+
576
+ // Find where each dependency is defined
577
+ for (const dep of dependencies) {
578
+ for (const [group, info] of Object.entries(groups)) {
579
+ if (info.types.has(dep)) {
580
+ if (!imports[group]) {
581
+ imports[group] = []
582
+ }
583
+ imports[group].push(dep)
584
+ break
585
+ }
586
+ }
587
+ }
588
+
589
+ return imports
590
+ }
591
+
592
+ /**
593
+ * Generates a types file for a group
594
+ */
595
+ function generateTypesFile(
596
+ group: string,
597
+ typeNames: Set<string>,
598
+ typesContent: string,
599
+ dependencies: Record<string, string[]>
600
+ ): string {
601
+ const ast = parseTypeScript(typesContent)
602
+
603
+ // Generate imports for dependencies with consistent camelCase
604
+ const imports = Object.entries(dependencies)
605
+ .filter(([depGroup]) => depGroup !== group) // Don't import from self
606
+ .map(([depGroup, types]) => `import type { ${types.sort().join(', ')} } from './${toCamelCase(depGroup)}Types';`
607
+ )
608
+
609
+ // Extract type declarations
610
+ const declarations: string[] = []
611
+ for (const node of ast.body) {
612
+ // Skip nodes without range info
613
+ if (!node.range) continue
614
+
615
+ if (node.type === 'ExportNamedDeclaration'
616
+ && (node.declaration?.type === 'TSTypeAliasDeclaration'
617
+ || node.declaration?.type === 'TSInterfaceDeclaration')) {
618
+ const typeName = node.declaration.id.name
619
+ if (typeNames.has(typeName)) {
620
+ declarations.push(typesContent.substring(node.range[0], node.range[1]))
621
+ }
622
+ }
623
+ }
624
+
625
+ return [
626
+ ...imports,
627
+ '',
628
+ ...declarations
629
+ ].join('\n')
630
+ }
631
+
632
+ /**
633
+ * Generates an API file for a group with improved handling of nested APIs
634
+ */
635
+ function generateApiFile(
636
+ _group: string,
637
+ apiFunctionNames: Set<string>,
638
+ apiContent: string,
639
+ dependencies: Record<string, string[]>,
640
+ useDirectories: boolean
641
+ ): string {
642
+ const ast = parseTypeScript(apiContent)
643
+
644
+ // Generate imports
645
+ const imports = [
646
+ useDirectories
647
+ ? `import { axios } from './axios';`
648
+ : `import { axios } from './common';`,
649
+ `import type { AxiosResponse } from 'axios';`
650
+ ]
651
+
652
+ // Add imports for the common utilities
653
+ if (useDirectories) {
654
+ imports.push(`import type { UploadOptions } from './common';`)
655
+ }
656
+
657
+ // Add imports for type dependencies with consistent camelCase
658
+ if (useDirectories) {
659
+ Object.entries(dependencies).forEach(([depGroup, types]) => {
660
+ // Filter out AxiosResponse as we're importing it directly
661
+ const filteredTypes = types.filter(t => t !== 'AxiosResponse')
662
+ if (filteredTypes.length > 0) {
663
+ imports.push(`import type { ${filteredTypes.sort().join(', ')} } from '../types/${toCamelCase(depGroup)}Types';`)
664
+ }
665
+ })
666
+ } else {
667
+ Object.entries(dependencies).forEach(([depGroup, types]) => {
668
+ // Filter out AxiosResponse as we're importing it directly
669
+ const filteredTypes = types.filter(t => t !== 'AxiosResponse')
670
+ if (filteredTypes.length > 0) {
671
+ imports.push(`import type { ${filteredTypes.sort().join(', ')} } from './${toCamelCase(depGroup)}Types';`)
672
+ }
673
+ })
674
+ }
675
+
676
+ // Extract API function declarations with their JSDoc comments
677
+ const declarations: string[] = []
678
+
679
+ for (const node of ast.body) {
680
+ if (!node.range) continue
681
+
682
+ // Handle export declarations
683
+ if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'VariableDeclaration') {
684
+ for (const declarator of node.declaration.declarations) {
685
+ if (declarator.id.type === 'Identifier') {
686
+ const functionName = declarator.id.name
687
+
688
+ // Check if this function should be included
689
+ const shouldInclude = apiFunctionNames.has(functionName)
690
+
691
+ if (shouldInclude) {
692
+ // Get the range of the entire declaration including comments
693
+ // Find leading comments by using AST comment tokens instead of node properties
694
+ let startPos = node.range[0]
695
+ const comments = ast.comments || []
696
+ const nodeLeadingComments = comments.filter(
697
+ comment => comment.range[1] <= node.range[0]
698
+ && !comments.some(c => comment.range[1] < c.range[1] && c.range[1] <= node.range[0])
699
+ )
700
+
701
+ if (nodeLeadingComments.length > 0) {
702
+ startPos = nodeLeadingComments[0].range[0]
703
+ }
704
+
705
+ declarations.push(apiContent.substring(startPos, node.range[1]))
706
+ break
707
+ }
708
+ }
709
+ }
710
+ }
711
+ }
712
+
713
+ return [
714
+ ...imports.filter(Boolean),
715
+ '',
716
+ ...declarations
717
+ ].join('\n')
718
+ }
package/bin/utils.ts CHANGED
@@ -13,6 +13,9 @@ const [_, __, dirFlag, withAuthArg] = proc.argv as Partial<string[]>
13
13
 
14
14
  const withAuth = withAuthArg === '--auth'
15
15
 
16
+ // Parse command line arguments
17
+ const shouldSplitFiles = proc.argv.includes('--split')
18
+
16
19
  if (dirFlag !== undefined) {
17
20
  console.log(`using passed in dir to generate sdk client: ${dirFlag}`)
18
21
  } else {
@@ -34,6 +37,7 @@ export function getKwargs() {
34
37
  openApiUrl,
35
38
  bagelinkDir,
36
39
  withAuth,
40
+ shouldSplitFiles,
37
41
  cwd,
38
42
  env,
39
43
  proc
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/sdk",
3
3
  "type": "module",
4
- "version": "1.2.151",
4
+ "version": "1.3.1",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -44,12 +44,14 @@
44
44
  "dist",
45
45
  "src",
46
46
  "bin/authClientCode.ts",
47
+ "bin/splitClientGen.ts",
47
48
  "bin/utils.ts"
48
49
  ],
49
50
  "publishConfig": {
50
51
  "access": "public"
51
52
  },
52
53
  "dependencies": {
54
+ "@typescript-eslint/typescript-estree": "^8.32.0",
53
55
  "axios": "^1.9.0"
54
56
  },
55
57
  "devDependencies": {