@zapier/zapier-sdk-cli 0.16.1 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cli.cjs +7 -7
  3. package/dist/cli.mjs +7 -7
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/package.json +8 -2
  7. package/dist/src/cli.js +2 -1
  8. package/dist/src/utils/cli-generator.js +8 -7
  9. package/dist/tsconfig.tsbuildinfo +1 -1
  10. package/package.json +11 -5
  11. package/src/cli.test.ts +0 -28
  12. package/src/cli.ts +0 -96
  13. package/src/generators/ast-generator.test.ts +0 -908
  14. package/src/generators/ast-generator.ts +0 -774
  15. package/src/index.ts +0 -12
  16. package/src/plugins/add/index.test.ts +0 -58
  17. package/src/plugins/add/index.ts +0 -177
  18. package/src/plugins/add/schemas.ts +0 -35
  19. package/src/plugins/buildManifest/index.test.ts +0 -679
  20. package/src/plugins/buildManifest/index.ts +0 -131
  21. package/src/plugins/buildManifest/schemas.ts +0 -55
  22. package/src/plugins/bundleCode/index.ts +0 -128
  23. package/src/plugins/bundleCode/schemas.ts +0 -24
  24. package/src/plugins/generateAppTypes/index.test.ts +0 -679
  25. package/src/plugins/generateAppTypes/index.ts +0 -227
  26. package/src/plugins/generateAppTypes/schemas.ts +0 -61
  27. package/src/plugins/getLoginConfigPath/index.ts +0 -45
  28. package/src/plugins/getLoginConfigPath/schemas.ts +0 -10
  29. package/src/plugins/index.ts +0 -8
  30. package/src/plugins/login/index.ts +0 -135
  31. package/src/plugins/login/schemas.ts +0 -13
  32. package/src/plugins/logout/index.ts +0 -37
  33. package/src/plugins/logout/schemas.ts +0 -8
  34. package/src/plugins/mcp/index.ts +0 -43
  35. package/src/plugins/mcp/schemas.ts +0 -13
  36. package/src/sdk.ts +0 -45
  37. package/src/telemetry/builders.ts +0 -113
  38. package/src/telemetry/events.ts +0 -39
  39. package/src/types/sdk.ts +0 -8
  40. package/src/utils/api/client.ts +0 -44
  41. package/src/utils/auth/login.ts +0 -214
  42. package/src/utils/cli-generator-utils.ts +0 -169
  43. package/src/utils/cli-generator.test.ts +0 -347
  44. package/src/utils/cli-generator.ts +0 -807
  45. package/src/utils/constants.ts +0 -9
  46. package/src/utils/directory-detection.ts +0 -23
  47. package/src/utils/errors.ts +0 -26
  48. package/src/utils/getCallablePromise.ts +0 -21
  49. package/src/utils/log.ts +0 -23
  50. package/src/utils/manifest-helpers.ts +0 -25
  51. package/src/utils/package-manager-detector.ts +0 -83
  52. package/src/utils/parameter-resolver.ts +0 -1075
  53. package/src/utils/schema-formatter.ts +0 -153
  54. package/src/utils/serializeAsync.ts +0 -26
  55. package/src/utils/spinner.ts +0 -23
  56. package/src/utils/version-checker.test.ts +0 -239
  57. package/src/utils/version-checker.ts +0 -237
  58. package/tsconfig.build.json +0 -18
  59. package/tsconfig.json +0 -19
  60. package/tsup.config.ts +0 -23
@@ -1,774 +0,0 @@
1
- import * as ts from "typescript";
2
- import type {
3
- Action,
4
- InputFieldItem,
5
- GetSdkType,
6
- ListActionsPluginProvides,
7
- ListInputFieldsPluginProvides,
8
- ManifestPluginProvides,
9
- AppItem,
10
- } from "@zapier/zapier-sdk";
11
- import { toSnakeCase, batch } from "@zapier/zapier-sdk";
12
-
13
- interface ActionWithInputFields extends Omit<Action, "inputFields" | "type"> {
14
- inputFields: InputFieldItem[];
15
- app_key: string;
16
- action_type: Action["type"];
17
- title: string;
18
- type: "action";
19
- }
20
-
21
- interface GenerateTypesOptions {
22
- app: AppItem;
23
- authenticationId?: number;
24
- }
25
-
26
- /**
27
- * AST-based TypeScript type generator using the TypeScript Compiler API
28
- */
29
- export class AstTypeGenerator {
30
- private readonly factory = ts.factory;
31
- private readonly printer = ts.createPrinter({
32
- newLine: ts.NewLineKind.LineFeed,
33
- removeComments: false,
34
- omitTrailingSemicolon: false,
35
- });
36
-
37
- /**
38
- * Generate TypeScript types using AST for a specific app
39
- */
40
- async generateTypes(
41
- options: GenerateTypesOptions & {
42
- sdk: GetSdkType<
43
- ListActionsPluginProvides &
44
- ListInputFieldsPluginProvides &
45
- ManifestPluginProvides
46
- >;
47
- },
48
- ): Promise<string> {
49
- const { app, authenticationId, sdk } = options;
50
-
51
- // Fetch all actions for the app using implementation_id for correct versioning
52
- const actionsResult = await sdk.listActions({
53
- appKey: app.implementation_id,
54
- });
55
-
56
- const actions = actionsResult.data;
57
-
58
- // Fetch input fields for each action
59
- const actionsWithFields: ActionWithInputFields[] = [];
60
-
61
- // Fetch all input fields with concurrency limiting for better reliability
62
- // Using batch() instead of Promise.allSettled() prevents overwhelming the API
63
- // and triggering rate limits when apps have many actions
64
- const inputFieldsTasks = actions.map(
65
- (action) => () =>
66
- sdk.listInputFields({
67
- appKey: app.implementation_id,
68
- actionKey: action.key,
69
- actionType: action.action_type,
70
- authenticationId: authenticationId,
71
- }),
72
- );
73
-
74
- const results = await batch(inputFieldsTasks, {
75
- concurrency: 50, // Limit to 50 concurrent requests
76
- retry: true, // Automatically retry transient failures
77
- timeoutMs: 180_000, // 3 minute overall timeout
78
- taskTimeoutMs: 30_000, // 30 seconds per-task timeout (API calls shouldn't take longer)
79
- });
80
-
81
- const failedActions: string[] = [];
82
-
83
- results.forEach((result, i) => {
84
- const action = actions[i];
85
-
86
- if (result.status === "fulfilled") {
87
- actionsWithFields.push({
88
- ...action,
89
- inputFields: result.value.data as InputFieldItem[],
90
- name: action.title || action.key,
91
- });
92
- } else {
93
- failedActions.push(
94
- `${action.key} (${action.action_type}): ${result.reason?.message || "Unknown error"}`,
95
- );
96
- actionsWithFields.push({
97
- ...action,
98
- inputFields: [],
99
- name: action.title || action.key,
100
- app_key: action.app_key || app.implementation_id,
101
- action_type: (action.action_type as Action["type"]) || "write",
102
- title: action.title || action.key,
103
- type: "action" as const,
104
- description: action.description || "",
105
- });
106
- }
107
- });
108
-
109
- if (failedActions.length > 0) {
110
- console.warn(
111
- `Failed to fetch input fields for ${failedActions.length} action(s):`,
112
- );
113
- failedActions.forEach((failedAction) => {
114
- console.warn(` - ${failedAction}`);
115
- });
116
- }
117
-
118
- // Generate TypeScript AST nodes
119
- const sourceFile = this.createSourceFile(app, actionsWithFields);
120
-
121
- // Print the AST to string
122
- return this.printer.printFile(sourceFile);
123
- }
124
-
125
- private createSourceFile(
126
- app: AppItem,
127
- actions: ActionWithInputFields[],
128
- ): ts.SourceFile {
129
- const appName = this.getPreferredAppName(app);
130
- const versionComment = ` * Generated for ${app.implementation_id}`;
131
- const preferredKey = this.getPreferredProgrammaticKey(app);
132
- const myVariableName = `my${appName}`;
133
-
134
- // Create header comment
135
- const headerComment = `Auto-generated TypeScript types for Zapier ${app.key} actions
136
- ${versionComment.slice(3)}
137
- Generated on: ${new Date().toISOString()}
138
-
139
- This file automatically augments the base SDK types when present.
140
- No manual imports or type casting required.
141
-
142
- Usage:
143
- import { createZapierSdk } from "@zapier/zapier-sdk";
144
-
145
- const zapier = createZapierSdk();
146
- // Types are automatically available:
147
- await zapier.apps.${preferredKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
148
-
149
- // Factory usage (pinned auth):
150
- const ${myVariableName} = zapier.apps.${preferredKey}({ authenticationId: 123 })
151
- await ${myVariableName}.search.user_by_email({ inputs: { email } })`;
152
-
153
- const statements: ts.Statement[] = [
154
- // Import the SDK to activate module augmentation
155
- this.createImportStatement(["@zapier/zapier-sdk"]),
156
-
157
- // Import types we'll use
158
- this.createTypeImportStatement(
159
- [
160
- "ActionExecutionOptions",
161
- "ActionExecutionResult",
162
- "ZapierFetchInitOptions",
163
- ],
164
- "@zapier/zapier-sdk",
165
- ),
166
- ];
167
-
168
- // Group actions by type
169
- const actionsByType = this.groupActionsByType(actions);
170
-
171
- // Generate input interfaces for each action
172
- actions.forEach((action) => {
173
- if (action.inputFields.length > 0) {
174
- const inputInterface = this.createInputInterface(appName, action);
175
- statements.push(inputInterface);
176
- }
177
- });
178
-
179
- // Generate action type interfaces
180
- Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
181
- const actionInterface = this.createActionInterface(
182
- appName,
183
- actionType,
184
- typeActions,
185
- );
186
- statements.push(actionInterface);
187
- });
188
-
189
- // Generate app proxy interface
190
- const appProxyInterface = this.createAppProxyInterface(
191
- appName,
192
- actionsByType,
193
- );
194
- statements.push(appProxyInterface);
195
-
196
- // Generate app factory interface
197
- const appFactoryInterface = this.createAppFactoryInterface(appName);
198
- statements.push(appFactoryInterface);
199
-
200
- // Generate combined app type
201
- const appWithFactoryType = this.createAppWithFactoryType(appName);
202
- statements.push(appWithFactoryType);
203
-
204
- // Generate module augmentation for automatic type integration
205
- const allKeys = this.getAllKeys(app);
206
- const moduleAugmentation = this.createModuleAugmentation(allKeys, appName);
207
- statements.push(moduleAugmentation);
208
-
209
- // Add empty export to make this a module
210
- statements.push(
211
- this.factory.createExportDeclaration(
212
- undefined,
213
- false,
214
- this.factory.createNamedExports([]),
215
- ),
216
- );
217
-
218
- // Create source file
219
- const sourceFile = ts.createSourceFile(
220
- "generated.d.ts",
221
- "",
222
- ts.ScriptTarget.Latest,
223
- false,
224
- ts.ScriptKind.TS,
225
- );
226
-
227
- // Add the header comment and ESLint disable as leading trivia to the first statement
228
- if (statements.length > 0) {
229
- // Add ESLint disable comment
230
- ts.addSyntheticLeadingComment(
231
- statements[0],
232
- ts.SyntaxKind.MultiLineCommentTrivia,
233
- " eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any ",
234
- true,
235
- );
236
- // Add header comment
237
- ts.addSyntheticLeadingComment(
238
- statements[0],
239
- ts.SyntaxKind.MultiLineCommentTrivia,
240
- headerComment,
241
- true,
242
- );
243
- }
244
-
245
- return this.factory.updateSourceFile(sourceFile, statements);
246
- }
247
-
248
- private createImportStatement(
249
- imports: string[],
250
- from?: string,
251
- ): ts.ImportDeclaration {
252
- // Handle side-effect only import (no imports, just the module path)
253
- if (imports.length === 1 && !from && imports[0].startsWith("@")) {
254
- return this.factory.createImportDeclaration(
255
- undefined,
256
- undefined,
257
- this.factory.createStringLiteral(imports[0]),
258
- undefined,
259
- );
260
- }
261
-
262
- const fromModule = from || imports[0];
263
- const importNames = from ? imports : [];
264
-
265
- return this.factory.createImportDeclaration(
266
- undefined,
267
- importNames.length > 0
268
- ? this.factory.createImportClause(
269
- false,
270
- undefined,
271
- this.factory.createNamedImports(
272
- importNames.map((name) =>
273
- this.factory.createImportSpecifier(
274
- false,
275
- undefined,
276
- this.factory.createIdentifier(name),
277
- ),
278
- ),
279
- ),
280
- )
281
- : undefined,
282
- this.factory.createStringLiteral(fromModule),
283
- undefined,
284
- );
285
- }
286
-
287
- private createTypeImportStatement(
288
- imports: string[],
289
- from: string,
290
- ): ts.ImportDeclaration {
291
- return this.factory.createImportDeclaration(
292
- undefined,
293
- this.factory.createImportClause(
294
- true, // typeOnly: true
295
- undefined,
296
- this.factory.createNamedImports(
297
- imports.map((name) =>
298
- this.factory.createImportSpecifier(
299
- false,
300
- undefined,
301
- this.factory.createIdentifier(name),
302
- ),
303
- ),
304
- ),
305
- ),
306
- this.factory.createStringLiteral(from),
307
- undefined,
308
- );
309
- }
310
-
311
- private groupActionsByType(
312
- actions: ActionWithInputFields[],
313
- ): Record<string, ActionWithInputFields[]> {
314
- return actions.reduce(
315
- (acc: Record<string, ActionWithInputFields[]>, action) => {
316
- if (!acc[action.action_type]) {
317
- acc[action.action_type] = [];
318
- }
319
- acc[action.action_type].push(action);
320
- return acc;
321
- },
322
- {} as Record<string, ActionWithInputFields[]>,
323
- );
324
- }
325
-
326
- private createInputInterface(
327
- appName: string,
328
- action: ActionWithInputFields,
329
- ): ts.InterfaceDeclaration {
330
- const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(
331
- this.sanitizeActionName(action.key),
332
- )}Inputs`;
333
-
334
- const properties = action.inputFields.map((field: InputFieldItem) => {
335
- const fieldType = this.mapFieldTypeToTypeNode(field);
336
- const isOptional = !field.is_required;
337
-
338
- let property = this.factory.createPropertySignature(
339
- undefined,
340
- this.sanitizeFieldName(field.key),
341
- isOptional
342
- ? this.factory.createToken(ts.SyntaxKind.QuestionToken)
343
- : undefined,
344
- fieldType,
345
- );
346
-
347
- // Add JSDoc comment if description exists
348
- if (field.description) {
349
- property = ts.addSyntheticLeadingComment(
350
- property,
351
- ts.SyntaxKind.MultiLineCommentTrivia,
352
- `* ${this.escapeComment(field.description)} `,
353
- true,
354
- );
355
- }
356
-
357
- return property;
358
- });
359
-
360
- return this.factory.createInterfaceDeclaration(
361
- undefined,
362
- inputTypeName,
363
- undefined,
364
- undefined,
365
- properties,
366
- );
367
- }
368
-
369
- private createActionInterface(
370
- appName: string,
371
- actionType: string,
372
- typeActions: ActionWithInputFields[],
373
- ): ts.InterfaceDeclaration {
374
- const typeName = `${appName}${this.capitalize(actionType)}Actions`;
375
-
376
- const methods = typeActions.map((action) => {
377
- const actionName = this.sanitizeActionName(action.key);
378
-
379
- let methodSignature: ts.MethodSignature;
380
-
381
- if (action.inputFields.length > 0) {
382
- // Generate type-safe action method signature
383
- const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(
384
- this.sanitizeActionName(action.key),
385
- )}Inputs`;
386
-
387
- const inputsType = this.factory.createTypeLiteralNode([
388
- this.factory.createPropertySignature(
389
- undefined,
390
- "inputs",
391
- undefined,
392
- this.factory.createTypeReferenceNode(inputTypeName),
393
- ),
394
- ]);
395
-
396
- const omitType = this.factory.createTypeReferenceNode("Omit", [
397
- this.factory.createTypeReferenceNode("ActionExecutionOptions"),
398
- this.factory.createLiteralTypeNode(
399
- this.factory.createStringLiteral("inputs"),
400
- ),
401
- ]);
402
-
403
- const optionsType = this.factory.createIntersectionTypeNode([
404
- inputsType,
405
- omitType,
406
- ]);
407
-
408
- methodSignature = this.factory.createMethodSignature(
409
- undefined,
410
- actionName,
411
- undefined,
412
- undefined,
413
- [
414
- this.factory.createParameterDeclaration(
415
- undefined,
416
- undefined,
417
- "options",
418
- undefined,
419
- optionsType,
420
- ),
421
- ],
422
- this.factory.createTypeReferenceNode("Promise", [
423
- this.factory.createTypeReferenceNode("ActionExecutionResult"),
424
- ]),
425
- );
426
- } else {
427
- // No specific input fields available - use generic Record<string, any> for inputs
428
- const genericInputsType = this.factory.createTypeLiteralNode([
429
- this.factory.createPropertySignature(
430
- undefined,
431
- "inputs",
432
- this.factory.createToken(ts.SyntaxKind.QuestionToken),
433
- this.factory.createTypeReferenceNode("Record", [
434
- this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
435
- this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
436
- ]),
437
- ),
438
- ]);
439
-
440
- const intersectionType = this.factory.createIntersectionTypeNode([
441
- genericInputsType,
442
- this.factory.createTypeReferenceNode("ActionExecutionOptions"),
443
- ]);
444
-
445
- methodSignature = this.factory.createMethodSignature(
446
- undefined,
447
- actionName,
448
- undefined,
449
- undefined,
450
- [
451
- this.factory.createParameterDeclaration(
452
- undefined,
453
- undefined,
454
- "options",
455
- this.factory.createToken(ts.SyntaxKind.QuestionToken),
456
- intersectionType,
457
- ),
458
- ],
459
- this.factory.createTypeReferenceNode("Promise", [
460
- this.factory.createTypeReferenceNode("ActionExecutionResult"),
461
- ]),
462
- );
463
- }
464
-
465
- // Add JSDoc comment if description exists
466
- if (action.description) {
467
- methodSignature = ts.addSyntheticLeadingComment(
468
- methodSignature,
469
- ts.SyntaxKind.MultiLineCommentTrivia,
470
- `* ${this.escapeComment(action.description)} `,
471
- true,
472
- );
473
- }
474
-
475
- return methodSignature;
476
- });
477
-
478
- return this.factory.createInterfaceDeclaration(
479
- undefined,
480
- typeName,
481
- undefined,
482
- undefined,
483
- methods,
484
- );
485
- }
486
-
487
- private createAppProxyInterface(
488
- appName: string,
489
- actionsByType: Record<string, ActionWithInputFields[]>,
490
- ): ts.InterfaceDeclaration {
491
- const properties = [
492
- ...Object.keys(actionsByType).map((actionType) =>
493
- this.factory.createPropertySignature(
494
- undefined,
495
- actionType,
496
- undefined,
497
- this.factory.createTypeReferenceNode(
498
- `${appName}${this.capitalize(actionType)}Actions`,
499
- ),
500
- ),
501
- ),
502
- // Always include fetch method for authenticated HTTP requests
503
- this.createFetchMethodProperty(),
504
- ];
505
-
506
- return this.factory.createInterfaceDeclaration(
507
- undefined,
508
- `${appName}AppProxy`,
509
- undefined,
510
- undefined,
511
- properties,
512
- );
513
- }
514
-
515
- private createFetchMethodProperty(): ts.PropertySignature {
516
- let property = this.factory.createPropertySignature(
517
- undefined,
518
- "fetch",
519
- undefined,
520
- this.factory.createFunctionTypeNode(
521
- undefined,
522
- [
523
- this.factory.createParameterDeclaration(
524
- undefined,
525
- undefined,
526
- "url",
527
- undefined,
528
- this.factory.createUnionTypeNode([
529
- this.factory.createTypeReferenceNode("string"),
530
- this.factory.createTypeReferenceNode("URL"),
531
- ]),
532
- ),
533
- this.factory.createParameterDeclaration(
534
- undefined,
535
- undefined,
536
- "init",
537
- this.factory.createToken(ts.SyntaxKind.QuestionToken),
538
- this.factory.createTypeReferenceNode("ZapierFetchInitOptions"),
539
- ),
540
- ],
541
- this.factory.createTypeReferenceNode("Promise", [
542
- this.factory.createTypeReferenceNode("Response"),
543
- ]),
544
- ),
545
- );
546
-
547
- // Add JSDoc comment
548
- property = ts.addSyntheticLeadingComment(
549
- property,
550
- ts.SyntaxKind.MultiLineCommentTrivia,
551
- "* Make authenticated HTTP requests through Zapier's Relay service ",
552
- true,
553
- );
554
-
555
- return property;
556
- }
557
-
558
- private createAppFactoryInterface(appName: string): ts.InterfaceDeclaration {
559
- const callSignature = this.factory.createCallSignature(
560
- undefined,
561
- [
562
- this.factory.createParameterDeclaration(
563
- undefined,
564
- undefined,
565
- "options",
566
- undefined,
567
- this.factory.createTypeLiteralNode([
568
- this.factory.createPropertySignature(
569
- undefined,
570
- "authenticationId",
571
- undefined,
572
- this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
573
- ),
574
- ]),
575
- ),
576
- ],
577
- this.factory.createTypeReferenceNode(`${appName}AppProxy`),
578
- );
579
-
580
- return this.factory.createInterfaceDeclaration(
581
- undefined,
582
- `${appName}AppFactory`,
583
- undefined,
584
- undefined,
585
- [callSignature],
586
- );
587
- }
588
-
589
- private createAppWithFactoryType(appName: string): ts.TypeAliasDeclaration {
590
- return this.factory.createTypeAliasDeclaration(
591
- undefined,
592
- `${appName}AppWithFactory`,
593
- undefined,
594
- this.factory.createIntersectionTypeNode([
595
- this.factory.createTypeReferenceNode(`${appName}AppFactory`),
596
- this.factory.createTypeReferenceNode(`${appName}AppProxy`),
597
- ]),
598
- );
599
- }
600
-
601
- private createModuleAugmentation(
602
- appKeys: string[],
603
- appName: string,
604
- ): ts.ModuleDeclaration {
605
- // Create: declare module "@zapier/zapier-sdk" { interface ZapierSdkApps { [appKey]: AppWithFactory } }
606
- // This creates a new interface that we can merge with ZapierSdk
607
- // Create properties for all keys (main key + other_keys)
608
- const properties = appKeys.map((appKey) =>
609
- this.factory.createPropertySignature(
610
- undefined,
611
- this.createPropertyName(appKey),
612
- undefined,
613
- this.factory.createTypeReferenceNode(`${appName}AppWithFactory`),
614
- ),
615
- );
616
-
617
- return this.factory.createModuleDeclaration(
618
- [this.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
619
- this.factory.createStringLiteral("@zapier/zapier-sdk"),
620
- this.factory.createModuleBlock([
621
- this.factory.createInterfaceDeclaration(
622
- undefined,
623
- "ZapierSdkApps",
624
- undefined,
625
- undefined,
626
- properties,
627
- ),
628
- ]),
629
- );
630
- }
631
-
632
- private mapFieldTypeToTypeNode(field: InputFieldItem): ts.TypeNode {
633
- // TODO: Handle choices when needed - choices are handled separately through listInputFieldChoices
634
-
635
- // Map Zapier field types to TypeScript types
636
- // Handle the new enum types from InputFieldItem.value_type
637
- switch (field.value_type?.toLowerCase()) {
638
- case "string":
639
- case "text":
640
- case "email":
641
- case "url":
642
- case "password":
643
- case "datetime":
644
- case "date":
645
- case "file":
646
- return this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
647
- case "integer":
648
- case "number":
649
- return this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
650
- case "boolean":
651
- return this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
652
- case "array":
653
- return this.factory.createArrayTypeNode(
654
- this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
655
- );
656
- case "object":
657
- return this.factory.createTypeReferenceNode("Record", [
658
- this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
659
- this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
660
- ]);
661
- default:
662
- // Default to string | number | boolean for unknown types
663
- return this.factory.createUnionTypeNode([
664
- this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
665
- this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
666
- this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
667
- ]);
668
- }
669
- }
670
-
671
- private capitalize(str: string): string {
672
- return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
673
- }
674
-
675
- private sanitizeActionName(actionKey: string): string {
676
- // Ensure the action name is a valid TypeScript identifier
677
- let sanitized = actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
678
-
679
- // If it starts with a number, prepend an underscore
680
- if (/^[0-9]/.test(sanitized)) {
681
- sanitized = "_" + sanitized;
682
- }
683
-
684
- return sanitized;
685
- }
686
-
687
- private sanitizeFieldName(fieldKey: string): string {
688
- // Ensure the field name is a valid TypeScript identifier
689
- let sanitized = fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
690
-
691
- // If it starts with a number, prepend an underscore
692
- if (/^[0-9]/.test(sanitized)) {
693
- sanitized = "_" + sanitized;
694
- }
695
-
696
- return sanitized;
697
- }
698
-
699
- private escapeComment(comment: string): string {
700
- // Escape comment text to prevent breaking the JSDoc comment
701
- return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
702
- }
703
-
704
- private createPropertyName(name: string): ts.PropertyName {
705
- // Check if the name is a valid JavaScript identifier
706
- if (this.isValidIdentifier(name)) {
707
- return this.factory.createIdentifier(name);
708
- } else {
709
- // Use string literal for invalid identifiers (e.g., contains dashes)
710
- return this.factory.createStringLiteral(name);
711
- }
712
- }
713
-
714
- private isValidIdentifier(name: string): boolean {
715
- // JavaScript identifier rules: must start with letter, $, or _,
716
- // followed by letters, digits, $, or _
717
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
718
- }
719
-
720
- private getPreferredProgrammaticKey(app: AppItem): string {
721
- // If we have a slug, convert it to snake_case as the preferred programmatic key
722
- if (app.slug) {
723
- const snakeCaseSlug = toSnakeCase(app.slug);
724
- if (this.isValidIdentifier(snakeCaseSlug)) {
725
- return snakeCaseSlug;
726
- }
727
- }
728
-
729
- // Otherwise, use the main key if it's a valid identifier
730
- if (this.isValidIdentifier(app.key)) {
731
- return app.key;
732
- }
733
-
734
- // Fallback: sanitize the key to make it a valid identifier
735
- return this.sanitizeToIdentifier(app.key);
736
- }
737
-
738
- private getPreferredAppName(app: AppItem): string {
739
- const preferredKey = this.getPreferredProgrammaticKey(app);
740
-
741
- // If it's snake_case, convert to PascalCase
742
- if (preferredKey.includes("_")) {
743
- return preferredKey
744
- .split("_")
745
- .map((word) => this.capitalize(word.toLowerCase()))
746
- .join("");
747
- }
748
-
749
- // Otherwise, capitalize first letter for PascalCase
750
- return this.capitalize(preferredKey);
751
- }
752
-
753
- private sanitizeToIdentifier(name: string): string {
754
- // Convert any string to a valid JavaScript identifier
755
- let sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_");
756
-
757
- // If it starts with a number, prepend an underscore
758
- if (/^[0-9]/.test(sanitized)) {
759
- sanitized = "_" + sanitized;
760
- }
761
-
762
- return sanitized;
763
- }
764
-
765
- private getAllKeys(app: AppItem): string[] {
766
- // Return all possible keys for this app
767
- const allKeys = new Set([app.key]);
768
- if (app.slug) {
769
- allKeys.add(app.slug);
770
- allKeys.add(toSnakeCase(app.slug));
771
- }
772
- return Array.from(allKeys);
773
- }
774
- }