nango 0.59.13 → 0.60.0

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 (51) hide show
  1. package/dist/index.js +64 -10
  2. package/dist/index.js.map +1 -1
  3. package/dist/migrations/toZeroYaml.d.ts +50 -0
  4. package/dist/migrations/toZeroYaml.js +969 -0
  5. package/dist/migrations/toZeroYaml.js.map +1 -0
  6. package/dist/migrations/toZeroYaml.unit.test.js +298 -0
  7. package/dist/migrations/toZeroYaml.unit.test.js.map +1 -0
  8. package/dist/services/dryrun.service.d.ts +5 -4
  9. package/dist/services/dryrun.service.js +77 -23
  10. package/dist/services/dryrun.service.js.map +1 -1
  11. package/dist/services/sdk.js +2 -1
  12. package/dist/services/sdk.js.map +1 -1
  13. package/dist/services/sdk.service.unit.test.js +2 -1
  14. package/dist/services/sdk.service.unit.test.js.map +1 -1
  15. package/dist/version.d.ts +1 -1
  16. package/dist/version.js +1 -1
  17. package/dist/version.js.map +1 -1
  18. package/dist/zeroYaml/compile.d.ts +8 -0
  19. package/dist/zeroYaml/compile.js +22 -71
  20. package/dist/zeroYaml/compile.js.map +1 -1
  21. package/dist/zeroYaml/compile.unit.cli-test.d.ts +1 -0
  22. package/dist/zeroYaml/{compile.unit.test.js → compile.unit.cli-test.js} +2 -1
  23. package/dist/zeroYaml/compile.unit.cli-test.js.map +1 -0
  24. package/dist/zeroYaml/constants.d.ts +17 -0
  25. package/dist/zeroYaml/constants.js +60 -0
  26. package/dist/zeroYaml/constants.js.map +1 -0
  27. package/dist/zeroYaml/definitions.d.ts +2 -1
  28. package/dist/zeroYaml/definitions.js +7 -11
  29. package/dist/zeroYaml/definitions.js.map +1 -1
  30. package/dist/zeroYaml/definitions.unit.test.js +5 -2
  31. package/dist/zeroYaml/definitions.unit.test.js.map +1 -1
  32. package/dist/zeroYaml/deploy.d.ts +7 -0
  33. package/dist/zeroYaml/deploy.js +426 -0
  34. package/dist/zeroYaml/deploy.js.map +1 -0
  35. package/dist/zeroYaml/dev.d.ts +4 -0
  36. package/dist/zeroYaml/dev.js +295 -0
  37. package/dist/zeroYaml/dev.js.map +1 -0
  38. package/dist/zeroYaml/init.js +6 -2
  39. package/dist/zeroYaml/init.js.map +1 -1
  40. package/dist/zeroYaml/zodToNango.js +1 -1
  41. package/dist/zeroYaml/zodToNango.unit.test.js +5 -3
  42. package/dist/zeroYaml/zodToNango.unit.test.js.map +1 -1
  43. package/example/.env.example +16 -0
  44. package/example/.nango/.gitkeep +0 -0
  45. package/example/github/actions/createIssue.ts +1 -1
  46. package/example/github/syncs/fetchIssues.ts +3 -4
  47. package/example/index.ts +3 -3
  48. package/example/tsconfig.json +30 -0
  49. package/package.json +18 -16
  50. package/dist/zeroYaml/compile.unit.test.js.map +0 -1
  51. /package/dist/{zeroYaml/compile.unit.test.d.ts → migrations/toZeroYaml.unit.test.d.ts} +0 -0
@@ -0,0 +1,969 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import chalk from 'chalk';
5
+ import { glob } from 'glob';
6
+ import jscodeshift from 'jscodeshift';
7
+ import ora from 'ora';
8
+ import { Err, Ok } from '../utils/result.js';
9
+ import { printDebug } from '../utils.js';
10
+ import { NANGO_VERSION } from '../version.js';
11
+ import { compileAll } from '../zeroYaml/compile.js';
12
+ import { compileAllFiles } from '../services/compile.service.js';
13
+ import { loadYamlAndGenerate } from '../services/model.service.js';
14
+ const allowedTypesImports = ['ActionError', 'ProxyConfiguration'];
15
+ const batchMethods = ['batchSave', 'batchUpdate', 'batchDelete'];
16
+ export async function migrateToZeroYaml({ fullPath, debug }) {
17
+ const spinner = ora({ text: 'Precompiling' }).start();
18
+ const { success } = await compileAllFiles({ fullPath, debug });
19
+ if (!success) {
20
+ spinner.fail();
21
+ console.log(chalk.red('Failed to compile. Exiting'));
22
+ return Err('failed_to_precompile');
23
+ }
24
+ const parsed = loadYamlAndGenerate({ fullPath, debug });
25
+ if (!parsed) {
26
+ return Err('failed_to_parse');
27
+ }
28
+ spinner.succeed();
29
+ {
30
+ const spinner = ora({ text: 'Add package.json' }).start();
31
+ await addPackageJson({ fullPath, debug });
32
+ spinner.succeed();
33
+ }
34
+ {
35
+ const spinner = ora({ text: 'Generating models.ts' }).start();
36
+ const content = generateModelsTs({ parsed });
37
+ // Write to models.ts
38
+ const modelsPath = path.join(fullPath, 'models.ts');
39
+ await fs.promises.writeFile(modelsPath, content);
40
+ spinner.succeed();
41
+ }
42
+ console.log('Processing scripts');
43
+ for (const integration of parsed.integrations) {
44
+ for (const sync of integration.syncs) {
45
+ const fp = path.join(integration.providerConfigKey, 'syncs', `${sync.name}.ts`);
46
+ const targetFile = path.join(fullPath, fp);
47
+ const spinner = ora({ text: `Migrating: ${fp}` }).start();
48
+ try {
49
+ if (await hasSymlinkInPath(targetFile, fullPath)) {
50
+ spinner.warn('Skipping symlink');
51
+ continue;
52
+ }
53
+ const content = await getContent({ targetFile });
54
+ const transformed = transformSync({ content, sync, models: parsed.models });
55
+ await fs.promises.writeFile(targetFile, transformed);
56
+ spinner.succeed();
57
+ }
58
+ catch (err) {
59
+ spinner.fail();
60
+ console.error(chalk.red(err));
61
+ return Err('failed_to_compile_one_file');
62
+ }
63
+ }
64
+ for (const action of integration.actions) {
65
+ const fp = path.join(integration.providerConfigKey, 'actions', `${action.name}.ts`);
66
+ const targetFile = path.join(fullPath, fp);
67
+ const spinner = ora({ text: `Migrating: ${fp}` }).start();
68
+ try {
69
+ if (await hasSymlinkInPath(targetFile, fullPath)) {
70
+ spinner.warn('Skipping symlink');
71
+ continue;
72
+ }
73
+ const content = await getContent({ targetFile });
74
+ const transformed = transformAction({ content, action, models: parsed.models });
75
+ await fs.promises.writeFile(targetFile, transformed);
76
+ spinner.succeed();
77
+ }
78
+ catch (err) {
79
+ spinner.fail();
80
+ console.error(chalk.red(err));
81
+ return Err('failed_to_compile_one_file');
82
+ }
83
+ }
84
+ for (const onEventScript of Object.entries(integration.onEventScripts)) {
85
+ for (const eventName of onEventScript[1]) {
86
+ const fp = path.join(integration.providerConfigKey, 'on-events', `${eventName}.ts`);
87
+ const targetFile = path.join(fullPath, fp);
88
+ const spinner = ora({ text: `Migrating: ${fp}` }).start();
89
+ try {
90
+ if (await hasSymlinkInPath(targetFile, fullPath)) {
91
+ spinner.warn('Skipping symlink');
92
+ continue;
93
+ }
94
+ const content = await getContent({ targetFile });
95
+ const transformed = transformOnEvents({ eventType: onEventScript[0], content, models: parsed.models });
96
+ await fs.promises.writeFile(targetFile, transformed);
97
+ spinner.succeed();
98
+ }
99
+ catch (err) {
100
+ spinner.fail();
101
+ console.error(chalk.red(err));
102
+ return Err('failed_to_compile_one_file');
103
+ }
104
+ }
105
+ }
106
+ }
107
+ // After migration, process all remaining .ts files in fullPath
108
+ {
109
+ console.log('Processing helper files');
110
+ await processHelperFiles({ fullPath, parsed });
111
+ }
112
+ {
113
+ const spinner = ora({ text: 'Running npm install' }).start();
114
+ await runNpmInstall(fullPath);
115
+ spinner.succeed();
116
+ }
117
+ {
118
+ const spinner = ora({ text: 'Generating index.ts' }).start();
119
+ await generateIndexTs({ fullPath, parsed });
120
+ spinner.succeed();
121
+ }
122
+ {
123
+ const spinner = ora({ text: 'Deleting nango.yaml' }).start();
124
+ await fs.promises.rm(path.join(fullPath, 'nango.yaml'));
125
+ spinner.succeed();
126
+ }
127
+ {
128
+ await compileAll({ fullPath, debug });
129
+ }
130
+ return Ok(undefined);
131
+ }
132
+ /**
133
+ * Get script content
134
+ */
135
+ async function getContent({ targetFile }) {
136
+ const res = await fs.promises.readFile(targetFile);
137
+ return res.toString();
138
+ }
139
+ /**
140
+ * Runs npm install in the given directory
141
+ */
142
+ async function runNpmInstall(fullPath) {
143
+ await new Promise((resolve, reject) => {
144
+ const proc = spawn('npm', ['install', '--no-audit', '--no-fund', '--no-progress'], {
145
+ cwd: fullPath,
146
+ stdio: 'inherit',
147
+ shell: true
148
+ });
149
+ proc.on('close', (code) => {
150
+ if (code === 0) {
151
+ resolve(undefined);
152
+ }
153
+ else {
154
+ reject(new Error(`npm install failed with exit code ${code}`));
155
+ }
156
+ });
157
+ });
158
+ }
159
+ /**
160
+ * Helper to remove type annotations from parameters and build an exec property for jscodeshift AST nodes
161
+ */
162
+ function buildExecProp(j, func, execReturnType) {
163
+ let params;
164
+ let bodyNode;
165
+ let isAsync = false;
166
+ if (func.type === 'FunctionDeclaration') {
167
+ params = func.params.map((param) => {
168
+ if (param.type === 'Identifier') {
169
+ return j.identifier(param.name);
170
+ }
171
+ return param;
172
+ });
173
+ bodyNode = func.body;
174
+ isAsync = !!func.async;
175
+ }
176
+ else {
177
+ params = func.params;
178
+ bodyNode = func.body;
179
+ isAsync = !!func.async;
180
+ }
181
+ params[0].extra = { ...(params[0].extra || {}), parenthesized: true };
182
+ const execArrow = j.arrowFunctionExpression(params, bodyNode);
183
+ execArrow.async = isAsync;
184
+ // Add return type if provided
185
+ if (execReturnType) {
186
+ execArrow.returnType = j.tsTypeAnnotation(j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([j.tsTypeReference(j.identifier(execReturnType))])));
187
+ }
188
+ return j.objectProperty(j.identifier('exec'), execArrow);
189
+ }
190
+ /**
191
+ * Transforms a sync to zero-yaml
192
+ */
193
+ export function transformSync({ content, sync, models }) {
194
+ const j = jscodeshift.withParser('ts');
195
+ const root = j(content);
196
+ removeLegacyImports({ root, j });
197
+ // Add import { createSync } from 'nango'
198
+ const importDecl = j.importDeclaration([j.importSpecifier(j.identifier('createSync'))], j.literal('nango'));
199
+ root.get().node.program.body.unshift(importDecl);
200
+ // Remove batch type arguments from all functions
201
+ removeBatchTypeArguments({ root, j });
202
+ // Wrap default function
203
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
204
+ const func = path.node.declaration;
205
+ if (func.type !== 'FunctionDeclaration') {
206
+ return;
207
+ }
208
+ // Preserve leading comments
209
+ const leadingComments = func.leadingComments || path.node.leadingComments;
210
+ const execProp = buildExecProp(j, func);
211
+ // Creats default props
212
+ const descriptionProp = j.objectProperty(j.identifier('description'), j.stringLiteral(sync.description));
213
+ const versionProp = j.objectProperty(j.identifier('version'), j.stringLiteral(sync.version || '0.0.1'));
214
+ const frequencyProp = j.objectProperty(j.identifier('frequency'), j.stringLiteral(sync.runs));
215
+ const autoStartProp = j.objectProperty(j.identifier('autoStart'), j.booleanLiteral(sync.auto_start));
216
+ const syncTypeProp = j.objectProperty(j.identifier('syncType'), j.stringLiteral(sync.sync_type));
217
+ const trackDeletesProp = j.objectProperty(j.identifier('trackDeletes'), j.booleanLiteral(sync.track_deletes));
218
+ const endpointsProp = j.objectProperty(j.identifier('endpoints'), j.arrayExpression(sync.endpoints.map((ep) => j.objectExpression(Object.entries(ep)
219
+ .map(([k, v]) => {
220
+ if (typeof v === 'string') {
221
+ return j.objectProperty(j.identifier(k), j.stringLiteral(v));
222
+ }
223
+ return null;
224
+ })
225
+ .filter((prop) => !!prop)))));
226
+ const props = [descriptionProp, versionProp, frequencyProp, autoStartProp, syncTypeProp, trackDeletesProp, endpointsProp];
227
+ if (sync.webhookSubscriptions.length > 0) {
228
+ const webhookSubscriptionsProp = j.objectProperty(j.identifier('webhookSubscriptions'), Array.isArray(sync.webhookSubscriptions) ? j.arrayExpression(sync.webhookSubscriptions.map((w) => j.stringLiteral(w))) : j.arrayExpression([]));
229
+ props.push(webhookSubscriptionsProp);
230
+ }
231
+ if (Array.isArray(sync.scopes) && sync.scopes.length > 0) {
232
+ const scopesProp = j.objectProperty(j.identifier('scopes'), j.arrayExpression(sync.scopes.map((s) => j.stringLiteral(s))));
233
+ props.push(scopesProp);
234
+ }
235
+ if (sync.output) {
236
+ const modelProps = sync.output
237
+ .map((modelName) => {
238
+ return j.objectProperty(j.identifier(modelName), j.identifier(modelName));
239
+ })
240
+ .filter((prop) => !!prop);
241
+ if (modelProps.length > 0) {
242
+ const modelsProp = j.objectProperty(j.identifier('models'), j.objectExpression(modelProps));
243
+ props.push(modelsProp);
244
+ }
245
+ }
246
+ if (sync.input) {
247
+ const metadataProp = j.objectProperty(j.identifier('metadata'), j.identifier(sync.input));
248
+ props.push(metadataProp);
249
+ }
250
+ else {
251
+ // Default to z.object({}) if no input to avoid more errors on migration
252
+ const metadataProp = j.objectProperty(j.identifier('metadata'), j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('object')), [j.objectExpression([])]));
253
+ props.push(metadataProp);
254
+ }
255
+ // Add exec prop
256
+ props.push(execProp);
257
+ // Find and move onWebhookPayloadReceived if present
258
+ let onWebhookArrow = null;
259
+ let onWebhookComments = null;
260
+ root.find(j.ExportNamedDeclaration).forEach((p) => {
261
+ const decl = p.node.declaration;
262
+ if (decl && decl.type === 'FunctionDeclaration' && decl.id && decl.id.name === 'onWebhookPayloadReceived') {
263
+ const webhookExecProp = buildExecProp(j, decl);
264
+ // Extract the arrow function from the exec property
265
+ onWebhookArrow = webhookExecProp.value;
266
+ // Capture comments
267
+ onWebhookComments = decl.leadingComments || p.node.leadingComments;
268
+ // Remove the original function declaration
269
+ j(p).remove();
270
+ }
271
+ });
272
+ if (onWebhookArrow) {
273
+ const onWebhookProp = j.objectProperty(j.identifier('onWebhook'), onWebhookArrow);
274
+ if (onWebhookComments) {
275
+ onWebhookProp.comments = onWebhookComments;
276
+ }
277
+ props.push(onWebhookProp);
278
+ }
279
+ const obj = j.objectExpression(props);
280
+ const syncVar = j.variableDeclaration('const', [j.variableDeclarator(j.identifier('sync'), j.callExpression(j.identifier('createSync'), [obj]))]);
281
+ if (leadingComments) {
282
+ syncVar.comments = leadingComments;
283
+ }
284
+ const nangoType = createNangoLocalType({ j, name: 'NangoSyncLocal', variable: 'sync' });
285
+ const exportDefault = j.exportDefaultDeclaration(j.identifier('sync'));
286
+ path.replace(syncVar, nangoType, exportDefault);
287
+ });
288
+ // Replace all type references to NangoAction with new type Nango
289
+ root.find(j.TSTypeReference).forEach((path) => {
290
+ if (path.node.typeName.type === 'Identifier' && path.node.typeName.name === 'NangoSync') {
291
+ path.node.typeName.name = 'NangoSyncLocal';
292
+ }
293
+ });
294
+ // Find all used Types in the file that might be available in "nango"
295
+ const allModelNames = Array.from(models.keys());
296
+ reImportTypes({ root, j, usedModels: allModelNames });
297
+ const transformed = root.toSource();
298
+ return transformed;
299
+ }
300
+ /**
301
+ * Transforms an action to zero-yaml
302
+ */
303
+ export function transformAction({ content, action, models }) {
304
+ const j = jscodeshift.withParser('ts');
305
+ const root = j(content);
306
+ removeLegacyImports({ root, j });
307
+ // Add import { createAction } from 'nango'
308
+ const importDecl = j.importDeclaration([j.importSpecifier(j.identifier('createAction'))], j.literal('nango'));
309
+ root.get().node.program.body.unshift(importDecl);
310
+ // Remove batch type arguments from all functions
311
+ removeBatchTypeArguments({ root, j });
312
+ // Find the default export async function (runAction or similar)
313
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
314
+ const func = path.node.declaration;
315
+ if (func.type !== 'FunctionDeclaration') {
316
+ return;
317
+ }
318
+ // Preserve leading comments
319
+ const leadingComments = func.leadingComments || path.node.leadingComments;
320
+ // Determine output type for exec return type
321
+ let outputType = undefined;
322
+ if (Array.isArray(action.output) && action.output.length > 0 && typeof action.output[0] === 'string') {
323
+ outputType = action.output[0];
324
+ }
325
+ else if (typeof action.output === 'string') {
326
+ outputType = action.output;
327
+ }
328
+ const execProp = buildExecProp(j, func, outputType);
329
+ // Build createAction object
330
+ const descriptionProp = j.objectProperty(j.identifier('description'), j.stringLiteral(action.description));
331
+ const versionProp = j.objectProperty(j.identifier('version'), j.stringLiteral(action.version || '0.0.1'));
332
+ const props = [descriptionProp, versionProp];
333
+ if (action.endpoint) {
334
+ const endpointProp = j.objectProperty(j.identifier('endpoint'), j.objectExpression(Object.entries(action.endpoint)
335
+ .map(([k, v]) => {
336
+ if (typeof v === 'string') {
337
+ return j.objectProperty(j.identifier(k), j.stringLiteral(v));
338
+ }
339
+ return null;
340
+ })
341
+ .filter((prop) => !!prop)));
342
+ props.push(endpointProp);
343
+ }
344
+ const inputProp = j.objectProperty(j.identifier('input'), typeof action.input === 'string' ? j.identifier(action.input) : j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('void')), []));
345
+ props.push(inputProp);
346
+ let outputProp = null;
347
+ if (Array.isArray(action.output) && action.output.length > 0 && typeof action.output[0] === 'string') {
348
+ outputProp = j.objectProperty(j.identifier('output'), j.identifier(action.output[0]));
349
+ }
350
+ else if (typeof action.output === 'string') {
351
+ outputProp = j.objectProperty(j.identifier('output'), j.identifier(action.output));
352
+ }
353
+ else {
354
+ outputProp = j.objectProperty(j.identifier('output'), j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('void')), []));
355
+ }
356
+ props.push(outputProp);
357
+ if (Array.isArray(action.scopes) && action.scopes.length > 0) {
358
+ const scopesProp = j.objectProperty(j.identifier('scopes'), j.arrayExpression(action.scopes.map((s) => j.stringLiteral(s))));
359
+ props.push(scopesProp);
360
+ }
361
+ props.push(execProp);
362
+ const obj = j.objectExpression(props);
363
+ const actionVar = j.variableDeclaration('const', [j.variableDeclarator(j.identifier('action'), j.callExpression(j.identifier('createAction'), [obj]))]);
364
+ if (leadingComments) {
365
+ actionVar.comments = leadingComments;
366
+ }
367
+ const nangoType = createNangoLocalType({ j, name: 'NangoActionLocal', variable: 'action' });
368
+ const exportDefault = j.exportDefaultDeclaration(j.identifier('action'));
369
+ path.replace(actionVar, nangoType, exportDefault);
370
+ });
371
+ // Replace all type references to NangoAction with new type Nango
372
+ root.find(j.TSTypeReference).forEach((path) => {
373
+ if (path.node.typeName.type === 'Identifier' && path.node.typeName.name === 'NangoAction') {
374
+ path.node.typeName.name = 'NangoActionLocal';
375
+ }
376
+ });
377
+ // Find all used Types in the file that might be available in "nango"
378
+ const allModelNames = Array.from(models.keys());
379
+ reImportTypes({ root, j, usedModels: allModelNames });
380
+ const transformed = root.toSource();
381
+ return transformed;
382
+ }
383
+ /**
384
+ * Transforms an on-event script to zero-yaml
385
+ */
386
+ export function transformOnEvents({ content, eventType, models }) {
387
+ const j = jscodeshift.withParser('ts');
388
+ const root = j(content);
389
+ removeLegacyImports({ root, j });
390
+ // Add import { createOnEvent } from 'nango'
391
+ const importDecl = j.importDeclaration([j.importSpecifier(j.identifier('createOnEvent'))], j.literal('nango'));
392
+ root.get().node.program.body.unshift(importDecl);
393
+ // Remove batch type arguments from all functions
394
+ removeBatchTypeArguments({ root, j });
395
+ // Find the default export async function (onEvent or similar)
396
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
397
+ const func = path.node.declaration;
398
+ if (func.type !== 'FunctionDeclaration') {
399
+ return;
400
+ }
401
+ // Preserve leading comments
402
+ const leadingComments = func.leadingComments || path.node.leadingComments;
403
+ const execProp = buildExecProp(j, func);
404
+ // Build createOnEvent object
405
+ const eventProp = j.objectProperty(j.identifier('event'), j.stringLiteral(eventType));
406
+ const descriptionProp = j.objectProperty(j.identifier('description'), j.stringLiteral(`${eventType} event handler`));
407
+ const props = [eventProp, descriptionProp, execProp];
408
+ const obj = j.objectExpression(props);
409
+ const exportDefault = j.exportDefaultDeclaration(j.callExpression(j.identifier('createOnEvent'), [obj]));
410
+ if (leadingComments) {
411
+ exportDefault.comments = leadingComments;
412
+ }
413
+ path.replace(exportDefault);
414
+ });
415
+ // Find all used Types in the file that might be available in "nango"
416
+ const allModelNames = Array.from(models.keys());
417
+ reImportTypes({ root, j, usedModels: allModelNames });
418
+ const transformed = root.toSource();
419
+ return transformed;
420
+ }
421
+ /**
422
+ * Removes legacy imports
423
+ */
424
+ function removeLegacyImports({ root, j }) {
425
+ root.find(j.ImportDeclaration)
426
+ .filter((path) => {
427
+ const source = path.node.source.value;
428
+ return typeof source === 'string' && (/models(\.js)?$/.test(source) || /models(\.js)?$/.test(source.replace(/^.*\//, '')));
429
+ })
430
+ .remove();
431
+ }
432
+ /**
433
+ * Re-imports allowed types if they are used
434
+ */
435
+ function reImportTypes({ root, j, usedModels }) {
436
+ // Find all used allowedTypesImports in the file
437
+ const usedAllowedTypes = new Set();
438
+ const usedModelTypes = new Set();
439
+ root.find(j.TSTypeReference).forEach((path) => {
440
+ if (path.node.typeName.type !== 'Identifier') {
441
+ return;
442
+ }
443
+ const name = path.node.typeName.name;
444
+ if (allowedTypesImports.includes(name)) {
445
+ usedAllowedTypes.add(name);
446
+ }
447
+ else if (usedModels.includes(name)) {
448
+ usedModelTypes.add(name);
449
+ }
450
+ });
451
+ // Also find types used in interface extends (e.g., interface X extends ProxyConfiguration)
452
+ root.find(j.TSInterfaceDeclaration).forEach((path) => {
453
+ if (!path.node.extends) {
454
+ return;
455
+ }
456
+ path.node.extends.forEach((ext) => {
457
+ if (ext.expression.type !== 'Identifier') {
458
+ return;
459
+ }
460
+ const name = ext.expression.name;
461
+ if (allowedTypesImports.includes(name)) {
462
+ usedAllowedTypes.add(name);
463
+ }
464
+ });
465
+ });
466
+ // Also find implicit model usages in object properties (e.g., { models: { Input: InputSchema } })
467
+ root.find(j.ObjectProperty).forEach((path) => {
468
+ if (path.node.value.type === 'Identifier' && usedModels.includes(path.node.value.name)) {
469
+ usedModelTypes.add(path.node.value.name);
470
+ }
471
+ });
472
+ // Also find generic type arguments in CallExpression and NewExpression (e.g., nango.ActionError<ActionErrorResponse>)
473
+ function scanTypeArguments(node) {
474
+ const typeArgs = node.typeArguments || node.typeParameters;
475
+ if (!typeArgs || !typeArgs.params) {
476
+ return;
477
+ }
478
+ typeArgs.params.forEach((param) => {
479
+ if (param.type === 'TSTypeReference' && param.typeName.type === 'Identifier') {
480
+ const name = param.typeName.name;
481
+ if (allowedTypesImports.includes(name)) {
482
+ usedAllowedTypes.add(name);
483
+ }
484
+ else if (usedModels.includes(name)) {
485
+ usedModelTypes.add(name);
486
+ }
487
+ }
488
+ });
489
+ }
490
+ root.find(j.CallExpression).forEach((path) => scanTypeArguments(path.node));
491
+ root.find(j.NewExpression).forEach((path) => scanTypeArguments(path.node));
492
+ // Fallback: scan for all Identifier nodes that match a model name
493
+ root.find(j.Identifier).forEach((path) => {
494
+ const name = path.node.name;
495
+ if (usedModels.includes(name)) {
496
+ usedModelTypes.add(name);
497
+ }
498
+ });
499
+ // Prepare imports
500
+ const importDecls = [];
501
+ if (usedAllowedTypes.size > 0) {
502
+ const importTypeDecl = j.importDeclaration(Array.from(usedAllowedTypes).map((type) => j.importSpecifier(j.identifier(type))), j.literal('nango'));
503
+ importTypeDecl.importKind = 'type';
504
+ importDecls.push(importTypeDecl);
505
+ }
506
+ if (usedModelTypes.size > 0) {
507
+ importDecls.push(j.importDeclaration([...Array.from(usedModelTypes.values()).map((name) => j.importSpecifier(j.identifier(name)))], j.literal('../../models.js')));
508
+ }
509
+ // Import z if any z.* is used (e.g., z.never())
510
+ const usesZ = root.find(j.Identifier, { name: 'z' }).size() > 0;
511
+ const hasZodImport = root
512
+ .find(j.ImportDeclaration)
513
+ .filter((path) => path.node.source.value === 'zod')
514
+ .size() > 0;
515
+ if (usesZ && !hasZodImport) {
516
+ importDecls.push(j.importDeclaration([j.importSpecifier(j.identifier('z'))], j.literal('zod')));
517
+ }
518
+ // Insert all at once, in order, after the last import
519
+ if (importDecls.length > 0) {
520
+ const body = root.get().node.program.body;
521
+ let insertIdx = 0;
522
+ for (let i = 0; i < body.length; i++) {
523
+ if (body[i].type === 'ImportDeclaration') {
524
+ insertIdx = i + 1;
525
+ }
526
+ }
527
+ body.splice(insertIdx, 0, ...importDecls);
528
+ }
529
+ }
530
+ /**
531
+ * Adds a package.json file to the given directory if it doesn't exist
532
+ * Otherwise, it updates the existing package.json file
533
+ */
534
+ async function addPackageJson({ fullPath, debug }) {
535
+ // Ensure package.json exists and has nango in devDependencies
536
+ const packageJsonPath = path.join(fullPath, 'package.json');
537
+ const examplePackageJsonPath = path.join(import.meta.dirname, '../../example/package.json');
538
+ let packageJsonExists = false;
539
+ try {
540
+ await fs.promises.access(packageJsonPath, fs.constants.F_OK);
541
+ packageJsonExists = true;
542
+ }
543
+ catch (_err) {
544
+ packageJsonExists = false;
545
+ }
546
+ const examplePkgRaw = await fs.promises.readFile(examplePackageJsonPath, 'utf-8');
547
+ const examplePkg = JSON.parse(examplePkgRaw);
548
+ let pkg;
549
+ if (!packageJsonExists) {
550
+ printDebug('package.json does not exist', debug);
551
+ pkg = examplePkg;
552
+ }
553
+ else {
554
+ printDebug('package.json exists, updating', debug);
555
+ const pkgRaw = await fs.promises.readFile(packageJsonPath, 'utf-8');
556
+ pkg = JSON.parse(pkgRaw);
557
+ }
558
+ pkg.devDependencies = pkg.devDependencies || {};
559
+ pkg.devDependencies['nango'] = NANGO_VERSION;
560
+ const zodVersion = (examplePkg.devDependencies && examplePkg.devDependencies['zod']);
561
+ pkg.devDependencies['zod'] = zodVersion;
562
+ // Remove nango and zod from dependencies just in case they were added as prod
563
+ if (pkg.dependencies?.['nango']) {
564
+ delete pkg.dependencies['nango'];
565
+ }
566
+ if (pkg.dependencies?.['zod']) {
567
+ delete pkg.dependencies['zod'];
568
+ }
569
+ await fs.promises.writeFile(packageJsonPath, JSON.stringify(pkg, null, 2));
570
+ }
571
+ /**
572
+ * Generates an index.ts file importing all syncs, actions, and on-event scripts for the given integrations.
573
+ */
574
+ async function generateIndexTs({ fullPath, parsed }) {
575
+ const indexLines = [];
576
+ for (const integration of parsed.integrations) {
577
+ const base = integration.providerConfigKey;
578
+ indexLines.push(`// -- Integration: ${base}`);
579
+ for (const sync of integration.syncs) {
580
+ indexLines.push(`import './${base}/syncs/${sync.name}.js';`);
581
+ }
582
+ for (const action of integration.actions) {
583
+ indexLines.push(`import './${base}/actions/${action.name}.js';`);
584
+ }
585
+ for (const [_eventType, eventNames] of Object.entries(integration.onEventScripts)) {
586
+ for (const eventName of eventNames) {
587
+ indexLines.push(`import './${base}/on-events/${eventName}.js';`);
588
+ }
589
+ }
590
+ indexLines.push('');
591
+ }
592
+ const indexPath = path.join(fullPath, 'index.ts');
593
+ await fs.promises.writeFile(indexPath, indexLines.join('\n'));
594
+ }
595
+ /**
596
+ * Removes type arguments from nango.batchSave, batchUpdate, batchDelete calls in all function bodies in the file
597
+ */
598
+ function removeBatchTypeArguments({ root, j }) {
599
+ root.find(j.FunctionDeclaration).forEach((funcPath) => {
600
+ j(funcPath.node.body)
601
+ .find(j.CallExpression)
602
+ .forEach((callPath) => {
603
+ const callee = callPath.node.callee;
604
+ if (callee.type === 'MemberExpression' &&
605
+ callee.object.type === 'Identifier' &&
606
+ callee.object.name === 'nango' &&
607
+ callee.property.type === 'Identifier' &&
608
+ batchMethods.includes(callee.property.name)) {
609
+ if ('typeArguments' in callPath.node && callPath.node.typeArguments) {
610
+ callPath.node.typeArguments = null;
611
+ }
612
+ if ('typeParameters' in callPath.node && callPath.node.typeParameters) {
613
+ callPath.node.typeParameters = null;
614
+ }
615
+ }
616
+ });
617
+ });
618
+ }
619
+ /**
620
+ * Generate models.ts with Zod models/types for all models in parsed.models
621
+ */
622
+ export function generateModelsTs({ parsed }) {
623
+ const j = jscodeshift.withParser('ts');
624
+ const root = j('');
625
+ // Add import { z } from 'zod';
626
+ root.get().node.program.body.push(j.importDeclaration([j.importSpecifier(j.identifier('z'))], j.literal('zod')));
627
+ // Generate all models as Zod schemas and type aliases, and export them
628
+ const allModelNames = Array.from(parsed.models.keys());
629
+ const sortedModels = topoSortModels(allModelNames, parsed.models);
630
+ for (const modelName of sortedModels) {
631
+ const model = parsed.models.get(modelName);
632
+ if (!model) {
633
+ continue;
634
+ }
635
+ // Exported Zod model declaration
636
+ root.get().node.program.body.push(j.exportNamedDeclaration(j.variableDeclaration('const', [j.variableDeclarator(j.identifier(modelName), nangoModelToZod({ j, model, referencedModels: allModelNames }))]), []));
637
+ // Exported type alias
638
+ root.get().node.program.body.push(j.exportNamedDeclaration(j.tsTypeAliasDeclaration(j.identifier(modelName), j.tsTypeReference(j.tsQualifiedName(j.identifier('z'), j.identifier('infer')), j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(modelName))]))), []));
639
+ }
640
+ // Export all models
641
+ root.get().node.program.body.push(j.exportNamedDeclaration(j.variableDeclaration('const', [
642
+ j.variableDeclarator(j.identifier('models'), j.objectExpression(sortedModels.map((modelName) => j.objectProperty(j.identifier(modelName), j.identifier(modelName)))))
643
+ ])));
644
+ return root.toSource();
645
+ }
646
+ /**
647
+ * Converts a NangoModel type to Zod AST
648
+ */
649
+ export function nangoModelToZod({ j, model, referencedModels }) {
650
+ // Check for dynamic field
651
+ const isDynamic = model.fields.find((field) => {
652
+ if (field.dynamic) {
653
+ return field;
654
+ }
655
+ return false;
656
+ });
657
+ if (isDynamic) {
658
+ // z.object({ ... }).catchall(valueType)
659
+ const valueType = nangoTypeToZodAst({ j, field: isDynamic, referencedModels: referencedModels || [] });
660
+ const safeValueType = valueType ?? j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('any')), []);
661
+ // All other fields
662
+ const otherFields = model.fields.filter((f) => f !== isDynamic);
663
+ const otherProps = otherFields
664
+ .map((field) => {
665
+ const zodAst = nangoTypeToZodAst({ j, field, referencedModels: referencedModels || [] });
666
+ if (!zodAst)
667
+ return undefined;
668
+ return j.objectProperty(j.identifier(field.name), zodAst);
669
+ })
670
+ .filter((prop) => !!prop);
671
+ const objectExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('object')), [j.objectExpression(otherProps)]);
672
+ return j.callExpression(j.memberExpression(objectExpr, j.identifier('catchall')), [safeValueType]);
673
+ }
674
+ if (model.isAnon) {
675
+ return nangoTypeToZodAst({ j, field: model.fields[0], referencedModels: referencedModels || [] });
676
+ }
677
+ // regular object
678
+ const properties = model.fields
679
+ .map((field) => {
680
+ const zodAst = nangoTypeToZodAst({ j, field, referencedModels: referencedModels || [] });
681
+ if (!zodAst)
682
+ return undefined;
683
+ return j.objectProperty(j.identifier(field.name), zodAst);
684
+ })
685
+ .filter((prop) => !!prop);
686
+ return j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('object')), [j.objectExpression(properties)]);
687
+ }
688
+ /**
689
+ * Converts a NangoModelField type to Zod AST
690
+ */
691
+ function nangoTypeToZodAst({ j, field, referencedModels }) {
692
+ // Handle union
693
+ if (field.union && Array.isArray(field.value)) {
694
+ const unionArgs = field.value
695
+ .map((v) => nangoTypeToZodAst({ j, field: v, referencedModels }))
696
+ .filter((arg) => !!arg);
697
+ let unionExpr = unionArgs.length <= 1
698
+ ? unionArgs[0]
699
+ : j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('union')), [j.arrayExpression(unionArgs)]);
700
+ if (field.optional) {
701
+ unionExpr = j.callExpression(j.memberExpression(unionExpr, j.identifier('optional')), []);
702
+ }
703
+ return unionExpr;
704
+ }
705
+ // Handle array
706
+ if (field.array) {
707
+ let arrExpr;
708
+ // Array of ts types (no value)
709
+ if (!Array.isArray(field.value)) {
710
+ return j.callExpression(j.memberExpression(nangoTypeToZodAst({ j, field: { ...field, array: false }, referencedModels }), j.identifier('array')), []);
711
+ }
712
+ // Array of one or multiple values
713
+ const elementTypes = [];
714
+ for (const value of field.value) {
715
+ const ast = nangoTypeToZodAst({ j, field: { ...value }, referencedModels });
716
+ if (ast !== undefined) {
717
+ elementTypes.push(ast);
718
+ }
719
+ }
720
+ if (elementTypes.length === 0) {
721
+ return;
722
+ }
723
+ let elementType;
724
+ if (elementTypes.length === 1) {
725
+ elementType = elementTypes[0];
726
+ }
727
+ else {
728
+ elementType = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('union')), [j.arrayExpression(elementTypes)]);
729
+ }
730
+ arrExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('array')), [elementType]);
731
+ if (field.optional) {
732
+ arrExpr = j.callExpression(j.memberExpression(arrExpr, j.identifier('optional')), []);
733
+ }
734
+ return arrExpr;
735
+ }
736
+ // Handle base types and nested objects
737
+ let baseExpr;
738
+ if (typeof field.value === 'string') {
739
+ switch (field.value) {
740
+ case 'string':
741
+ case 'number':
742
+ case 'boolean':
743
+ case 'Date':
744
+ case 'any':
745
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier(field.value.toLocaleLowerCase())), []);
746
+ break;
747
+ case 'null':
748
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('nullable')), []);
749
+ break;
750
+ case 'undefined':
751
+ // return on purpose to skip
752
+ return;
753
+ case 'Record<string, any>':
754
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('object')), [j.objectExpression([])]);
755
+ break;
756
+ case 'any[]':
757
+ baseExpr = j.callExpression(j.memberExpression(j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('any')), []), j.identifier('array')), []);
758
+ break;
759
+ default:
760
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('literal')), [j.stringLiteral(field.value)]);
761
+ }
762
+ }
763
+ else if (Array.isArray(field.value)) {
764
+ // If not union/array, treat as nested object
765
+ const nested = nangoModelToZod({ j, model: { name: '', fields: field.value }, referencedModels: referencedModels || [] });
766
+ baseExpr = nested
767
+ ? nested.type === 'Identifier'
768
+ ? j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('lazy')), [j.arrowFunctionExpression([], nested)])
769
+ : nested
770
+ : j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('any')), []);
771
+ }
772
+ else if (field.value === null) {
773
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('null')), []);
774
+ }
775
+ else {
776
+ baseExpr = j.callExpression(j.memberExpression(j.identifier('z'), j.identifier('any')), []);
777
+ }
778
+ if (field.optional) {
779
+ baseExpr = j.callExpression(j.memberExpression(baseExpr, j.identifier('optional')), []);
780
+ }
781
+ // Handle model reference
782
+ if (field.model && typeof field.value === 'string' && referencedModels && referencedModels.includes(field.value)) {
783
+ return j.identifier(field.value);
784
+ }
785
+ return baseExpr;
786
+ }
787
+ // Helper to extract dependencies for a model
788
+ function getModelDependencies(model) {
789
+ const deps = new Set();
790
+ function walkField(field) {
791
+ if (field.model && typeof field.value === 'string') {
792
+ deps.add(field.value);
793
+ }
794
+ if (Array.isArray(field.value)) {
795
+ for (const v of field.value) {
796
+ walkField(v);
797
+ }
798
+ }
799
+ if (field.union && Array.isArray(field.value)) {
800
+ for (const v of field.value) {
801
+ walkField(v);
802
+ }
803
+ }
804
+ }
805
+ for (const field of model.fields) {
806
+ walkField(field);
807
+ }
808
+ return deps;
809
+ }
810
+ // Topological sort
811
+ function topoSortModels(modelNames, models) {
812
+ const visited = new Set();
813
+ const temp = new Set();
814
+ const result = [];
815
+ function visit(name) {
816
+ if (visited.has(name))
817
+ return;
818
+ if (temp.has(name))
819
+ return; // Prevent cycles
820
+ temp.add(name);
821
+ const model = models.get(name);
822
+ if (model) {
823
+ for (const dep of getModelDependencies(model)) {
824
+ if (modelNames.includes(dep)) {
825
+ visit(dep);
826
+ }
827
+ }
828
+ }
829
+ temp.delete(name);
830
+ visited.add(name);
831
+ result.push(name);
832
+ }
833
+ for (const name of modelNames) {
834
+ visit(name);
835
+ }
836
+ return result;
837
+ }
838
+ // export type NangoSyncLocal = Parameters<(typeof sync)['exec']>[0]
839
+ function createNangoLocalType({ j, name, variable }) {
840
+ return j.exportNamedDeclaration(j.tsTypeAliasDeclaration(j.identifier(name), j.tsIndexedAccessType(j.tsTypeReference(j.identifier('Parameters'), j.tsTypeParameterInstantiation([j.tsIndexedAccessType(j.tsTypeQuery(j.identifier(variable)), j.tsLiteralType(j.stringLiteral('exec')))])), j.tsLiteralType(j.numericLiteral(0)))));
841
+ }
842
+ // Helper: For each file in the list, update model imports for NangoSync/NangoAction
843
+ async function processHelperFiles({ fullPath, parsed }) {
844
+ const files = await glob('**/*.ts', {
845
+ cwd: fullPath,
846
+ ignore: ['**/.nango/**', '**/node_modules/**', '**/dist/**', '**/build/**'],
847
+ absolute: true
848
+ });
849
+ // Build list of integration files to process
850
+ const integrationFiles = new Set();
851
+ for (const integration of parsed.integrations) {
852
+ for (const sync of integration.syncs) {
853
+ integrationFiles.add(path.join(fullPath, integration.providerConfigKey, 'syncs', `${sync.name}.ts`));
854
+ }
855
+ for (const action of integration.actions) {
856
+ integrationFiles.add(path.join(fullPath, integration.providerConfigKey, 'actions', `${action.name}.ts`));
857
+ }
858
+ for (const [_, eventNames] of Object.entries(integration.onEventScripts)) {
859
+ for (const name of eventNames) {
860
+ integrationFiles.add(path.join(fullPath, integration.providerConfigKey, 'on-events', `${name}.ts`));
861
+ }
862
+ }
863
+ }
864
+ // Filter out integration files from the glob list since they were already processed
865
+ for (const absPath of files) {
866
+ if (integrationFiles.has(absPath)) {
867
+ continue;
868
+ }
869
+ const spinner = ora({ text: `Migrating ${absPath.replace(fullPath, '')}` }).start();
870
+ if (await hasSymlinkInPath(absPath, fullPath)) {
871
+ spinner.warn('Skipping symlink');
872
+ continue;
873
+ }
874
+ const content = await fs.promises.readFile(absPath, 'utf-8');
875
+ const { root, changed } = processHelperFile({ content });
876
+ if (changed) {
877
+ await fs.promises.writeFile(absPath, root.toSource());
878
+ }
879
+ spinner.succeed();
880
+ }
881
+ }
882
+ export function processHelperFile({ content }) {
883
+ const allowedTypesImportsHelper = [...allowedTypesImports, 'NangoAction', 'NangoSync'];
884
+ const j = jscodeshift.withParser('ts');
885
+ const root = j(content);
886
+ let changed = false;
887
+ // Find import from models
888
+ root.find(j.ImportDeclaration).forEach((pathNode) => {
889
+ const source = pathNode.node.source.value;
890
+ if (typeof source !== 'string') {
891
+ return;
892
+ }
893
+ // Find NangoSync/NangoAction specifiers (only ImportSpecifier)
894
+ const specifiers = pathNode.node.specifiers || [];
895
+ const nangoSpecifiers = specifiers.filter((s) => isImportSpecifier(s) && allowedTypesImportsHelper.includes(s.imported.name));
896
+ if (nangoSpecifiers.length === 0) {
897
+ return;
898
+ }
899
+ // Remove NangoSync/NangoAction from models import
900
+ pathNode.node.specifiers = specifiers.filter((s) => !(isImportSpecifier(s) && allowedTypesImportsHelper.includes(s.imported.name)));
901
+ changed = true;
902
+ // Add or update import from 'nango'
903
+ const nangoImport = root.find(j.ImportDeclaration, { source: { value: 'nango' } });
904
+ if (nangoImport.size() > 0) {
905
+ // Add to existing import
906
+ nangoImport.get(0).node.specifiers = [
907
+ ...nangoImport.get(0).node.specifiers,
908
+ ...nangoSpecifiers
909
+ .filter(isImportSpecifier)
910
+ .filter((s) => typeof s.imported.name === 'string')
911
+ .map((s) => {
912
+ const name = s.imported.name;
913
+ return j.importSpecifier(j.identifier(name));
914
+ })
915
+ ];
916
+ }
917
+ else {
918
+ // Add new import
919
+ root.get().node.program.body.unshift(j.importDeclaration(nangoSpecifiers
920
+ .filter(isImportSpecifier)
921
+ .filter((s) => typeof s.imported.name === 'string')
922
+ .map((s) => {
923
+ const name = s.imported.name;
924
+ return j.importSpecifier(j.identifier(name));
925
+ }), j.literal('nango')));
926
+ }
927
+ // If models import is now empty, remove it
928
+ if (!pathNode.node.specifiers || pathNode.node.specifiers.length === 0) {
929
+ j(pathNode).remove();
930
+ }
931
+ });
932
+ return { root, changed };
933
+ }
934
+ // Helper type guard for ImportSpecifier
935
+ function isImportSpecifier(s) {
936
+ return (typeof s === 'object' &&
937
+ s !== null &&
938
+ 'type' in s &&
939
+ s.type === 'ImportSpecifier' &&
940
+ 'imported' in s &&
941
+ s.imported &&
942
+ s.imported.type === 'Identifier');
943
+ }
944
+ // Helper to check if a file or any parent directory is a symlink
945
+ async function hasSymlinkInPath(filePath, stopAtPath) {
946
+ let current = path.resolve(filePath);
947
+ while (true) {
948
+ try {
949
+ const stat = await fs.promises.lstat(current);
950
+ if (stat.isSymbolicLink()) {
951
+ return true;
952
+ }
953
+ }
954
+ catch {
955
+ // ignore
956
+ }
957
+ if (current === stopAtPath) {
958
+ break;
959
+ }
960
+ const parent = path.dirname(current);
961
+ if (parent === current) {
962
+ // reached root
963
+ break;
964
+ }
965
+ current = parent;
966
+ }
967
+ return false;
968
+ }
969
+ //# sourceMappingURL=toZeroYaml.js.map