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