nango 0.24.11 → 0.24.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -86
- package/dist/index.js.map +1 -1
- package/dist/nango-sync.d.ts +5 -5
- package/dist/sync.d.ts +4 -8
- package/dist/sync.js +247 -276
- package/dist/sync.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils.d.ts +3 -6
- package/dist/utils.js +23 -73
- package/dist/utils.js.map +1 -1
- package/docker/docker-compose.yaml +4 -4
- package/package.json +2 -2
- package/dist/entry.js +0 -89
package/dist/sync.js
CHANGED
|
@@ -14,214 +14,31 @@ import parser from '@babel/parser';
|
|
|
14
14
|
import traverse from '@babel/traverse';
|
|
15
15
|
import promptly from 'promptly';
|
|
16
16
|
import { loadSimplifiedConfig, cloudHost, stagingHost, SyncType, syncRunService, nangoConfigFile, checkForIntegrationFile } from '@nangohq/shared';
|
|
17
|
-
import { hostport, port, httpsAgent, verifyNecessaryFiles, getConnection, NANGO_INTEGRATIONS_LOCATION,
|
|
17
|
+
import { hostport, port, httpsAgent, verifyNecessaryFiles, getConnection, NANGO_INTEGRATIONS_LOCATION, buildInterfaces, enrichHeaders, getNangoRootPath, printDebug } from './utils.js';
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
20
20
|
dotenv.config();
|
|
21
21
|
const TYPES_FILE_NAME = 'models.ts';
|
|
22
22
|
const NangoSyncTypesFileLocation = 'dist/nango-sync.d.ts';
|
|
23
23
|
const exampleSyncName = 'github-issue-example';
|
|
24
|
-
|
|
25
|
-
if (debug) {
|
|
26
|
-
printDebug('Looking up the version first for a local path first then globally');
|
|
27
|
-
}
|
|
28
|
-
const packageJson = JSON.parse(fs.readFileSync(path.resolve(getNangoRootPath(debug), 'package.json'), 'utf8'));
|
|
29
|
-
const dockerComposeYaml = fs.readFileSync(path.resolve(getNangoRootPath(), 'docker/docker-compose.yaml'), 'utf8');
|
|
30
|
-
const dockerCompose = yaml.load(dockerComposeYaml);
|
|
31
|
-
const nangoServerImage = dockerCompose.services['nango-server'].image;
|
|
32
|
-
const nangoWorkerImage = dockerCompose.services['nango-worker'].image;
|
|
33
|
-
const nangoServerVersion = nangoServerImage.split(':').pop();
|
|
34
|
-
const nangoWorkerVersion = nangoWorkerImage.split(':').pop();
|
|
35
|
-
console.log(chalk.green('Nango Server version:'), nangoServerVersion);
|
|
36
|
-
console.log(chalk.green('Nango Worker version:'), nangoWorkerVersion);
|
|
37
|
-
console.log(chalk.green('Nango CLI version:'), packageJson.version);
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Init
|
|
41
|
-
* If we're not currently in the nango-integrations directory create one
|
|
42
|
-
* and create an example nango.yaml file
|
|
43
|
-
*/
|
|
44
|
-
export const init = (debug = false) => {
|
|
45
|
-
const data = {
|
|
46
|
-
integrations: {
|
|
47
|
-
'demo-github-integration': {
|
|
48
|
-
[exampleSyncName]: {
|
|
49
|
-
runs: 'every half hour',
|
|
50
|
-
returns: ['GithubIssue']
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
models: {
|
|
55
|
-
GithubIssue: {
|
|
56
|
-
id: 'integer',
|
|
57
|
-
owner: 'string',
|
|
58
|
-
repo: 'string',
|
|
59
|
-
issue_number: 'number',
|
|
60
|
-
title: 'string',
|
|
61
|
-
author: 'string',
|
|
62
|
-
author_id: 'string',
|
|
63
|
-
state: 'string',
|
|
64
|
-
date_created: 'date',
|
|
65
|
-
date_last_modified: 'date',
|
|
66
|
-
body: 'string'
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const yamlData = yaml.dump(data);
|
|
71
|
-
// if currently in the nango-integrations directory then don't create another one
|
|
24
|
+
const createModelFile = (notify = false) => {
|
|
72
25
|
const cwd = process.cwd();
|
|
73
|
-
const
|
|
74
|
-
const currentDirectory = currentDirectorySplit[currentDirectorySplit.length - 1];
|
|
75
|
-
let dirExists = false;
|
|
76
|
-
let inParentDirectory = true;
|
|
77
|
-
if (currentDirectory === NANGO_INTEGRATIONS_NAME) {
|
|
78
|
-
dirExists = true;
|
|
79
|
-
inParentDirectory = false;
|
|
80
|
-
if (debug) {
|
|
81
|
-
printDebug(`Currently in the ${NANGO_INTEGRATIONS_NAME} directory so the directory will not be created`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (fs.existsSync(`./${NANGO_INTEGRATIONS_NAME}`)) {
|
|
85
|
-
dirExists = true;
|
|
86
|
-
console.log(chalk.red(`The ${NANGO_INTEGRATIONS_NAME} directory already exists. You should run commands from within this directory`));
|
|
87
|
-
}
|
|
88
|
-
if (!dirExists) {
|
|
89
|
-
if (debug) {
|
|
90
|
-
printDebug(`Creating the nango integrations directory at ./${NANGO_INTEGRATIONS_NAME}`);
|
|
91
|
-
}
|
|
92
|
-
fs.mkdirSync(`./${NANGO_INTEGRATIONS_NAME}`);
|
|
93
|
-
}
|
|
94
|
-
const configFileLocation = inParentDirectory ? `./${NANGO_INTEGRATIONS_NAME}/${nangoConfigFile}` : `./${nangoConfigFile}`;
|
|
95
|
-
if (!fs.existsSync(configFileLocation)) {
|
|
96
|
-
if (debug) {
|
|
97
|
-
printDebug(`Creating the ${nangoConfigFile} file at ${configFileLocation}`);
|
|
98
|
-
}
|
|
99
|
-
fs.writeFileSync(configFileLocation, yamlData);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
if (debug) {
|
|
103
|
-
printDebug(`Nango config file already exists at ${configFileLocation} so not creating a new one`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const envFileLocation = inParentDirectory ? `./${NANGO_INTEGRATIONS_NAME}/.env` : './.env';
|
|
107
|
-
if (!fs.existsSync(envFileLocation)) {
|
|
108
|
-
if (debug) {
|
|
109
|
-
printDebug(`Creating the .env file at ${envFileLocation}`);
|
|
110
|
-
}
|
|
111
|
-
fs.writeFileSync(envFileLocation, `# Authenticates the CLI (get the keys in the dashboard's Projects Settings).
|
|
112
|
-
#NANGO_SECRET_KEY_DEV=xxxx-xxx-xxxx
|
|
113
|
-
#NANGO_SECRET_KEY_PROD=xxxx-xxx-xxxx
|
|
114
|
-
|
|
115
|
-
# Nango's instance URL (OSS: change to http://localhost:3003 or your instance URL).
|
|
116
|
-
NANGO_HOSTPORT=https://api.nango.dev # Default value
|
|
117
|
-
|
|
118
|
-
# How to handle CLI upgrades ("prompt", "auto" or "ignore").
|
|
119
|
-
NANGO_CLI_UPGRADE_MODE=prompt # Default value
|
|
120
|
-
|
|
121
|
-
# Whether to prompt before deployments.
|
|
122
|
-
NANGO_DEPLOY_AUTO_CONFIRM=false # Default value`);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
if (debug) {
|
|
126
|
-
printDebug(`.env file already exists at ${envFileLocation} so not creating a new one`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
console.log(chalk.green(`Nango integrations initialized!`));
|
|
130
|
-
};
|
|
131
|
-
export const generate = async (debug = false) => {
|
|
132
|
-
const templateContents = fs.readFileSync(path.resolve(__dirname, './integration.ejs'), 'utf8');
|
|
133
|
-
const githubExampleTemplateContents = fs.readFileSync(path.resolve(__dirname, './integration.github.ejs'), 'utf8');
|
|
134
|
-
const configContents = fs.readFileSync(`./${nangoConfigFile}`, 'utf8');
|
|
26
|
+
const configContents = fs.readFileSync(path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`), 'utf8');
|
|
135
27
|
const configData = yaml.load(configContents);
|
|
136
|
-
const { integrations } = configData;
|
|
137
28
|
const { models } = configData;
|
|
138
|
-
const interfaceDefinitions = buildInterfaces(models
|
|
29
|
+
const interfaceDefinitions = buildInterfaces(models);
|
|
139
30
|
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`, interfaceDefinitions.join('\n'));
|
|
140
|
-
if (debug) {
|
|
141
|
-
printDebug(`Interfaces from the ${nangoConfigFile} file written to ${TYPES_FILE_NAME}`);
|
|
142
|
-
}
|
|
143
31
|
// insert NangoSync types to the bottom of the file
|
|
144
32
|
const typesContent = fs.readFileSync(`${getNangoRootPath()}/${NangoSyncTypesFileLocation}`, 'utf8');
|
|
145
33
|
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`, typesContent, { flag: 'a' });
|
|
146
|
-
if (debug) {
|
|
147
|
-
printDebug(`NangoSync types written to ${TYPES_FILE_NAME}`);
|
|
148
|
-
}
|
|
149
|
-
const allSyncNames = {};
|
|
150
|
-
for (let i = 0; i < Object.keys(integrations).length; i++) {
|
|
151
|
-
const providerConfigKey = Object.keys(integrations)[i];
|
|
152
|
-
if (debug) {
|
|
153
|
-
printDebug(`Generating ${providerConfigKey} integrations`);
|
|
154
|
-
}
|
|
155
|
-
const syncObject = integrations[providerConfigKey];
|
|
156
|
-
const syncNames = Object.keys(syncObject);
|
|
157
|
-
for (let k = 0; k < syncNames.length; k++) {
|
|
158
|
-
const syncName = syncNames[k];
|
|
159
|
-
if (allSyncNames[syncName] === undefined) {
|
|
160
|
-
allSyncNames[syncName] = true;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
console.log(chalk.red(`The sync name ${syncName} is duplicated in the ${nangoConfigFile} file. All sync names must be unique.`));
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
if (debug) {
|
|
167
|
-
printDebug(`Generating ${syncName} integration`);
|
|
168
|
-
}
|
|
169
|
-
const syncData = syncObject[syncName];
|
|
170
|
-
if (!syncData.returns) {
|
|
171
|
-
console.log(chalk.red(`The ${syncName} integration is missing a returns property for what models the sync returns. Make sure you have "returns" instead of "return"`));
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
const { returns: models } = syncData;
|
|
175
|
-
const syncNameCamel = syncName
|
|
176
|
-
.split('-')
|
|
177
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
178
|
-
.join('');
|
|
179
|
-
const ejsTeamplateContents = syncName === exampleSyncName ? githubExampleTemplateContents : templateContents;
|
|
180
|
-
const rendered = ejs.render(ejsTeamplateContents, {
|
|
181
|
-
syncName: syncNameCamel,
|
|
182
|
-
interfaceFileName: TYPES_FILE_NAME.replace('.ts', ''),
|
|
183
|
-
interfaceNames: models.map((model) => {
|
|
184
|
-
const singularModel = model?.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
185
|
-
return `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`;
|
|
186
|
-
}),
|
|
187
|
-
mappings: models.map((model) => {
|
|
188
|
-
const singularModel = model.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
189
|
-
return {
|
|
190
|
-
name: model,
|
|
191
|
-
type: `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`
|
|
192
|
-
};
|
|
193
|
-
})
|
|
194
|
-
});
|
|
195
|
-
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`)) {
|
|
196
|
-
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`, rendered);
|
|
197
|
-
if (debug) {
|
|
198
|
-
printDebug(`Created ${syncName}.ts file`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
if (debug) {
|
|
203
|
-
printDebug(`${syncName}.ts file already exists, so will not overwrite it.`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
console.log(chalk.green(`Integration files have been created`));
|
|
209
|
-
};
|
|
210
|
-
const createModelFile = (notify = false) => {
|
|
211
|
-
const configContents = fs.readFileSync(`./${nangoConfigFile}`, 'utf8');
|
|
212
|
-
const configData = yaml.load(configContents);
|
|
213
|
-
const { models } = configData;
|
|
214
|
-
const interfaceDefinitions = buildInterfaces(models);
|
|
215
|
-
fs.writeFileSync(`./${TYPES_FILE_NAME}`, interfaceDefinitions.join('\n'));
|
|
216
|
-
// insert NangoSync types to the bottom of the file
|
|
217
|
-
const typesContent = fs.readFileSync(`${getNangoRootPath()}/${NangoSyncTypesFileLocation}`, 'utf8');
|
|
218
|
-
fs.writeFileSync(`./${TYPES_FILE_NAME}`, typesContent, { flag: 'a' });
|
|
219
34
|
if (notify) {
|
|
220
|
-
|
|
35
|
+
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
36
|
+
console.log(chalk.green(`${rawNangoIntegrationLocation}/${nangoConfigFile} was updated. The interface file (${rawNangoIntegrationLocation}/${TYPES_FILE_NAME}) was updated to reflect the updated config`));
|
|
221
37
|
}
|
|
222
38
|
};
|
|
223
39
|
const getConfig = async (debug = false) => {
|
|
224
|
-
const
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const config = await loadSimplifiedConfig(path.resolve(cwd, NANGO_INTEGRATIONS_LOCATION));
|
|
225
42
|
if (!config) {
|
|
226
43
|
throw new Error(`Error loading the ${nangoConfigFile} file`);
|
|
227
44
|
}
|
|
@@ -230,40 +47,21 @@ const getConfig = async (debug = false) => {
|
|
|
230
47
|
}
|
|
231
48
|
return config;
|
|
232
49
|
};
|
|
233
|
-
async
|
|
234
|
-
|
|
50
|
+
export const deploy = async (options, debug = false) => {
|
|
51
|
+
const { env, version, sync: optionalSyncName, secretKey, host, autoConfirm } = options;
|
|
52
|
+
await verifyNecessaryFiles(autoConfirm);
|
|
53
|
+
if (host) {
|
|
235
54
|
if (debug) {
|
|
236
|
-
printDebug(`
|
|
55
|
+
printDebug(`Global host flag is set, setting NANGO_HOSTPORT to ${host}.`);
|
|
237
56
|
}
|
|
238
|
-
process.env['
|
|
57
|
+
process.env['NANGO_HOSTPORT'] = host;
|
|
239
58
|
}
|
|
240
|
-
if (
|
|
59
|
+
if (secretKey) {
|
|
241
60
|
if (debug) {
|
|
242
|
-
printDebug(`
|
|
61
|
+
printDebug(`Global secretKey flag is set, setting NANGO_SECRET_KEY.`);
|
|
243
62
|
}
|
|
244
|
-
process.env['NANGO_SECRET_KEY'] =
|
|
63
|
+
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
245
64
|
}
|
|
246
|
-
if (!process.env['NANGO_SECRET_KEY']) {
|
|
247
|
-
console.log(chalk.red(`NANGO_SECRET_KEY environment variable is not set. Please set it now`));
|
|
248
|
-
try {
|
|
249
|
-
const secretKey = await promptly.prompt('Secret Key: ');
|
|
250
|
-
if (secretKey) {
|
|
251
|
-
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
console.log('Error occurred while trying to prompt for secret key:', error);
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
export const deploy = async (options, environment, debug = false) => {
|
|
264
|
-
const { env, version, sync: optionalSyncName, autoConfirm } = options;
|
|
265
|
-
await verifyNecessaryFiles(autoConfirm);
|
|
266
|
-
await parseSecretKey(environment, debug);
|
|
267
65
|
if (!process.env['NANGO_HOSTPORT']) {
|
|
268
66
|
switch (env) {
|
|
269
67
|
case 'local':
|
|
@@ -279,10 +77,26 @@ export const deploy = async (options, environment, debug = false) => {
|
|
|
279
77
|
}
|
|
280
78
|
if (debug) {
|
|
281
79
|
printDebug(`NANGO_HOSTPORT is set to ${process.env['NANGO_HOSTPORT']}.`);
|
|
282
|
-
|
|
80
|
+
}
|
|
81
|
+
if (process.env['NANGO_HOSTPORT'] !== `http://localhost:${port}` && !process.env['NANGO_SECRET_KEY']) {
|
|
82
|
+
console.log(chalk.red(`NANGO_SECRET_KEY environment variable is not set. Please set it now`));
|
|
83
|
+
try {
|
|
84
|
+
const secretKey = await promptly.prompt('Secret Key: ');
|
|
85
|
+
if (secretKey) {
|
|
86
|
+
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.log('Error occurred while trying to prompt for secret key:', error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
283
96
|
}
|
|
284
97
|
tsc(debug);
|
|
285
98
|
const config = await getConfig(debug);
|
|
99
|
+
const cwd = process.cwd();
|
|
286
100
|
const postData = [];
|
|
287
101
|
for (const integration of config) {
|
|
288
102
|
const { providerConfigKey } = integration;
|
|
@@ -292,7 +106,7 @@ export const deploy = async (options, environment, debug = false) => {
|
|
|
292
106
|
}
|
|
293
107
|
for (const sync of syncs) {
|
|
294
108
|
const { name: syncName, runs, returns: models, models: model_schema } = sync;
|
|
295
|
-
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName,
|
|
109
|
+
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName, path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}`));
|
|
296
110
|
if (!integrationFileResult) {
|
|
297
111
|
console.log(chalk.red(`No integration file found for ${syncName} at ${integrationFilePath}. Skipping...`));
|
|
298
112
|
continue;
|
|
@@ -313,7 +127,11 @@ export const deploy = async (options, environment, debug = false) => {
|
|
|
313
127
|
}
|
|
314
128
|
}
|
|
315
129
|
const url = process.env['NANGO_HOSTPORT'] + `/sync/deploy`;
|
|
316
|
-
if (
|
|
130
|
+
if (postData.length === 0) {
|
|
131
|
+
console.log(chalk.red(`No syncs found to deploy. Please make sure your integration files compiled successfully and exist in your dist directory`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!process.env['NANGO_DEPLOY_AUTO_CONFIRM'] && !autoConfirm) {
|
|
317
135
|
const confirmationUrl = process.env['NANGO_HOSTPORT'] + `/sync/deploy/confirmation`;
|
|
318
136
|
try {
|
|
319
137
|
const response = await axios.post(confirmationUrl, { syncs: postData, reconcile: false, debug }, { headers: enrichHeaders(), httpsAgent: httpsAgent() });
|
|
@@ -334,13 +152,8 @@ export const deploy = async (options, environment, debug = false) => {
|
|
|
334
152
|
.post(process.env['NANGO_HOSTPORT'] + `/sync/deploy`, { syncs: postData, reconcile: true, debug }, { headers: enrichHeaders(), httpsAgent: httpsAgent() })
|
|
335
153
|
.then((response) => {
|
|
336
154
|
const results = response.data;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
const nameAndVersions = results.map((result) => `${result.sync_name}@v${result.version}`);
|
|
342
|
-
console.log(chalk.green(`Successfully deployed the syncs: ${nameAndVersions.join(', ')}!`));
|
|
343
|
-
}
|
|
155
|
+
const nameAndVersions = results.map((result) => `${result.sync_name}@v${result.version}`);
|
|
156
|
+
console.log(chalk.green(`Successfully deployed the syncs: ${nameAndVersions.join(', ')}!`));
|
|
344
157
|
})
|
|
345
158
|
.catch((err) => {
|
|
346
159
|
const errorMessage = JSON.stringify(err.response.data, null, 2);
|
|
@@ -379,13 +192,8 @@ async function deploySyncs(url, body) {
|
|
|
379
192
|
.post(url, body, { headers: enrichHeaders(), httpsAgent: httpsAgent() })
|
|
380
193
|
.then((response) => {
|
|
381
194
|
const results = response.data;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
const nameAndVersions = results.map((result) => `${result.sync_name}@v${result.version}`);
|
|
387
|
-
console.log(chalk.green(`Successfully deployed the syncs: ${nameAndVersions.join(', ')}!`));
|
|
388
|
-
}
|
|
195
|
+
const nameAndVersions = results.map((result) => `${result.sync_name}@v${result.version}`);
|
|
196
|
+
console.log(chalk.green(`Successfully deployed the syncs: ${nameAndVersions.join(', ')}!`));
|
|
389
197
|
})
|
|
390
198
|
.catch((err) => {
|
|
391
199
|
let errorMessage;
|
|
@@ -401,38 +209,198 @@ async function deploySyncs(url, body) {
|
|
|
401
209
|
process.exit(1);
|
|
402
210
|
});
|
|
403
211
|
}
|
|
404
|
-
export const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
212
|
+
export const version = (debug) => {
|
|
213
|
+
if (debug) {
|
|
214
|
+
printDebug('Looking up the version first for a local path first then globally');
|
|
215
|
+
}
|
|
216
|
+
const packageJson = JSON.parse(fs.readFileSync(path.resolve(getNangoRootPath(debug), 'package.json'), 'utf8'));
|
|
217
|
+
const dockerComposeYaml = fs.readFileSync(path.resolve(getNangoRootPath(), 'docker/docker-compose.yaml'), 'utf8');
|
|
218
|
+
const dockerCompose = yaml.load(dockerComposeYaml);
|
|
219
|
+
const nangoServerImage = dockerCompose.services['nango-server'].image;
|
|
220
|
+
const nangoWorkerImage = dockerCompose.services['nango-worker'].image;
|
|
221
|
+
const nangoServerVersion = nangoServerImage.split(':').pop();
|
|
222
|
+
const nangoWorkerVersion = nangoWorkerImage.split(':').pop();
|
|
223
|
+
console.log(chalk.green('Nango Server version:'), nangoServerVersion);
|
|
224
|
+
console.log(chalk.green('Nango Worker version:'), nangoWorkerVersion);
|
|
225
|
+
console.log(chalk.green('Nango CLI version:'), packageJson.version);
|
|
226
|
+
};
|
|
227
|
+
export const init = (debug = false) => {
|
|
228
|
+
const data = {
|
|
229
|
+
integrations: {
|
|
230
|
+
github: {
|
|
231
|
+
[exampleSyncName]: {
|
|
232
|
+
runs: 'every half hour',
|
|
233
|
+
returns: ['GithubIssue']
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
models: {
|
|
238
|
+
GithubIssue: {
|
|
239
|
+
id: 'integer',
|
|
240
|
+
owner: 'string',
|
|
241
|
+
repo: 'string',
|
|
242
|
+
issue_number: 'number',
|
|
243
|
+
title: 'string',
|
|
244
|
+
author: 'string',
|
|
245
|
+
author_id: 'string',
|
|
246
|
+
state: 'string',
|
|
247
|
+
date_created: 'date',
|
|
248
|
+
date_last_modified: 'date',
|
|
249
|
+
body: 'string'
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const yamlData = yaml.dump(data);
|
|
254
|
+
if (!fs.existsSync(NANGO_INTEGRATIONS_LOCATION)) {
|
|
409
255
|
if (debug) {
|
|
410
|
-
printDebug(
|
|
256
|
+
printDebug('Creating the nango integrations directory');
|
|
411
257
|
}
|
|
412
|
-
|
|
258
|
+
fs.mkdirSync(NANGO_INTEGRATIONS_LOCATION);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (debug) {
|
|
262
|
+
printDebug('Nango integrations directory already exists');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`)) {
|
|
266
|
+
if (debug) {
|
|
267
|
+
printDebug(`Creating the ${nangoConfigFile} file}`);
|
|
268
|
+
}
|
|
269
|
+
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`, yamlData);
|
|
413
270
|
}
|
|
271
|
+
else {
|
|
272
|
+
if (debug) {
|
|
273
|
+
printDebug('Nango config file already exists');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// check if a .env file exists and if not create it with some default content
|
|
277
|
+
if (!fs.existsSync('.env')) {
|
|
278
|
+
if (debug) {
|
|
279
|
+
printDebug('Creating the .env file');
|
|
280
|
+
}
|
|
281
|
+
fs.writeFileSync('.env', `#NANGO_HOSTPORT=https://api-staging.nango.dev
|
|
282
|
+
#NANGO_AUTO_UPGRADE=true # set to true to automatically upgrade to the latest version of nango
|
|
283
|
+
#NANGO_NO_PROMPT_FOR_UPGRADE=true # set to true to not prompt for upgrade
|
|
284
|
+
#NANGO_DEPLOY_AUTO_CONFIRM=true # set to true to automatically confirm deployment without prompting
|
|
285
|
+
#NANGO_SECRET_KEY=xxxx-xxx-xxxx # required if deploying to cloud
|
|
286
|
+
#NANGO_INTEGRATIONS_LOCATION=use-this-to-override-where-the-nango-integrations-directory-goes
|
|
287
|
+
#NANGO_PORT=use-this-to-override-the-default-3003
|
|
288
|
+
#NANGO_DB_PORT=use-this-to-override-the-default-5432`);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
if (debug) {
|
|
292
|
+
printDebug('.env file already exists');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
console.log(chalk.green(`Nango integrations initialized!`));
|
|
296
|
+
};
|
|
297
|
+
export const generate = async (debug = false) => {
|
|
298
|
+
const templateContents = fs.readFileSync(path.resolve(__dirname, './integration.ejs'), 'utf8');
|
|
299
|
+
const githubExampleTemplateContents = fs.readFileSync(path.resolve(__dirname, './integration.github.ejs'), 'utf8');
|
|
300
|
+
const cwd = process.cwd();
|
|
301
|
+
const configContents = fs.readFileSync(path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`), 'utf8');
|
|
302
|
+
const configData = yaml.load(configContents);
|
|
303
|
+
const { integrations } = configData;
|
|
304
|
+
const { models } = configData;
|
|
305
|
+
const interfaceDefinitions = buildInterfaces(models);
|
|
306
|
+
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`, interfaceDefinitions.join('\n'));
|
|
414
307
|
if (debug) {
|
|
415
|
-
printDebug(`
|
|
308
|
+
printDebug(`Interfaces from the ${nangoConfigFile} file written to ${TYPES_FILE_NAME}`);
|
|
309
|
+
}
|
|
310
|
+
// insert NangoSync types to the bottom of the file
|
|
311
|
+
const typesContent = fs.readFileSync(`${getNangoRootPath()}/${NangoSyncTypesFileLocation}`, 'utf8');
|
|
312
|
+
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`, typesContent, { flag: 'a' });
|
|
313
|
+
if (debug) {
|
|
314
|
+
printDebug(`NangoSync types written to ${TYPES_FILE_NAME}`);
|
|
315
|
+
}
|
|
316
|
+
for (let i = 0; i < Object.keys(integrations).length; i++) {
|
|
317
|
+
const providerConfigKey = Object.keys(integrations)[i];
|
|
318
|
+
if (debug) {
|
|
319
|
+
printDebug(`Generating ${providerConfigKey} integrations`);
|
|
320
|
+
}
|
|
321
|
+
const syncObject = integrations[providerConfigKey];
|
|
322
|
+
const syncNames = Object.keys(syncObject);
|
|
323
|
+
for (let k = 0; k < syncNames.length; k++) {
|
|
324
|
+
const syncName = syncNames[k];
|
|
325
|
+
if (debug) {
|
|
326
|
+
printDebug(`Generating ${syncName} integration`);
|
|
327
|
+
}
|
|
328
|
+
const syncData = syncObject[syncName];
|
|
329
|
+
const { returns: models } = syncData;
|
|
330
|
+
const syncNameCamel = syncName
|
|
331
|
+
.split('-')
|
|
332
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
333
|
+
.join('');
|
|
334
|
+
const ejsTeamplateContents = syncName === exampleSyncName ? githubExampleTemplateContents : templateContents;
|
|
335
|
+
const rendered = ejs.render(ejsTeamplateContents, {
|
|
336
|
+
syncName: syncNameCamel,
|
|
337
|
+
interfaceFileName: TYPES_FILE_NAME.replace('.ts', ''),
|
|
338
|
+
interfaceNames: models.map((model) => {
|
|
339
|
+
const singularModel = model?.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
340
|
+
return `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`;
|
|
341
|
+
}),
|
|
342
|
+
mappings: models.map((model) => {
|
|
343
|
+
const singularModel = model.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
344
|
+
return {
|
|
345
|
+
name: model,
|
|
346
|
+
type: `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`
|
|
347
|
+
};
|
|
348
|
+
})
|
|
349
|
+
});
|
|
350
|
+
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`)) {
|
|
351
|
+
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`, rendered);
|
|
352
|
+
if (debug) {
|
|
353
|
+
printDebug(`Created ${syncName}.ts file`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
if (debug) {
|
|
358
|
+
printDebug(`${syncName}.ts file already exists, so will not overwrite it.`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
console.log(chalk.green(`Integration files have been created`));
|
|
364
|
+
};
|
|
365
|
+
export const run = async (args, options, debug = false) => {
|
|
366
|
+
let syncName, providerConfigKey, connectionId, suppliedLastSyncDate, host, secretKey;
|
|
367
|
+
if (args.length > 0) {
|
|
368
|
+
[syncName, providerConfigKey, connectionId, suppliedLastSyncDate] = args;
|
|
416
369
|
}
|
|
417
370
|
if (Object.keys(options).length > 0) {
|
|
418
|
-
({ sync: syncName, connectionId, lastSyncDate: suppliedLastSyncDate } = options);
|
|
371
|
+
({ sync: syncName, provider: providerConfigKey, connection: connectionId, lastSyncDate: suppliedLastSyncDate, host, secretKey } = options);
|
|
419
372
|
}
|
|
420
373
|
if (!syncName) {
|
|
421
374
|
console.log(chalk.red('Sync name is required'));
|
|
422
375
|
return;
|
|
423
376
|
}
|
|
377
|
+
if (!providerConfigKey) {
|
|
378
|
+
console.log(chalk.red('Provider config key is required'));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
424
381
|
if (!connectionId) {
|
|
425
382
|
console.log(chalk.red('Connection id is required'));
|
|
426
383
|
return;
|
|
427
384
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
385
|
+
if (debug) {
|
|
386
|
+
if (host) {
|
|
387
|
+
printDebug(`Host value is set to ${host}. This will override the value in the .env file`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (host) {
|
|
391
|
+
process.env['NANGO_HOSTPORT'] = host;
|
|
392
|
+
}
|
|
393
|
+
if (secretKey) {
|
|
394
|
+
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
395
|
+
}
|
|
396
|
+
if (!process.env['NANGO_HOSTPORT']) {
|
|
397
|
+
if (debug) {
|
|
398
|
+
printDebug(`NANGO_HOSTPORT is not set. Setting the default to ${hostport}`);
|
|
399
|
+
}
|
|
400
|
+
process.env['NANGO_HOSTPORT'] = hostport;
|
|
433
401
|
}
|
|
434
402
|
if (debug) {
|
|
435
|
-
printDebug(`
|
|
403
|
+
printDebug(`NANGO_HOSTPORT is set to ${process.env['NANGO_HOSTPORT']}`);
|
|
436
404
|
}
|
|
437
405
|
const nangoConnection = (await getConnection(providerConfigKey, connectionId, {
|
|
438
406
|
'Nango-Is-Sync': true,
|
|
@@ -442,9 +410,6 @@ export const dryRun = async (options, environment, debug = false) => {
|
|
|
442
410
|
console.log(chalk.red('Connection not found'));
|
|
443
411
|
return;
|
|
444
412
|
}
|
|
445
|
-
if (debug) {
|
|
446
|
-
printDebug(`Connection found with ${JSON.stringify(nangoConnection, null, 2)}`);
|
|
447
|
-
}
|
|
448
413
|
if (process.env['NANGO_HOSTPORT'] === cloudHost || process.env['NANGO_HOSTPORT'] === stagingHost) {
|
|
449
414
|
process.env['NANGO_CLOUD'] = 'true';
|
|
450
415
|
}
|
|
@@ -455,12 +420,13 @@ export const dryRun = async (options, environment, debug = false) => {
|
|
|
455
420
|
}
|
|
456
421
|
lastSyncDate = new Date(suppliedLastSyncDate);
|
|
457
422
|
}
|
|
423
|
+
const cwd = process.cwd();
|
|
458
424
|
const syncRun = new syncRunService({
|
|
459
425
|
writeToDb: false,
|
|
460
426
|
nangoConnection,
|
|
461
427
|
syncName,
|
|
462
428
|
syncType: SyncType.INITIAL,
|
|
463
|
-
loadLocation:
|
|
429
|
+
loadLocation: path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}`),
|
|
464
430
|
debug
|
|
465
431
|
});
|
|
466
432
|
try {
|
|
@@ -474,35 +440,38 @@ export const dryRun = async (options, environment, debug = false) => {
|
|
|
474
440
|
}
|
|
475
441
|
};
|
|
476
442
|
export const tsc = (debug = false) => {
|
|
443
|
+
const cwd = process.cwd();
|
|
477
444
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
478
|
-
const distDir =
|
|
445
|
+
const distDir = path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/dist`);
|
|
479
446
|
if (!fs.existsSync(distDir)) {
|
|
480
447
|
if (debug) {
|
|
481
448
|
printDebug(`Creating ${distDir} directory`);
|
|
482
449
|
}
|
|
483
450
|
fs.mkdirSync(distDir);
|
|
484
451
|
}
|
|
485
|
-
if (!fs.existsSync(
|
|
452
|
+
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`)) {
|
|
486
453
|
if (debug) {
|
|
487
454
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
488
455
|
}
|
|
489
456
|
createModelFile();
|
|
490
457
|
}
|
|
458
|
+
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
491
459
|
const compiler = tsNode.create({
|
|
492
460
|
compilerOptions: JSON.parse(tsconfig).compilerOptions
|
|
493
461
|
});
|
|
494
462
|
if (debug) {
|
|
495
463
|
printDebug(`Compiler options: ${JSON.stringify(JSON.parse(tsconfig).compilerOptions, null, 2)}`);
|
|
496
464
|
}
|
|
497
|
-
const integrationFiles = glob.sync(
|
|
465
|
+
const integrationFiles = glob.sync(path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/*.ts`));
|
|
498
466
|
for (const filePath of integrationFiles) {
|
|
499
467
|
try {
|
|
500
468
|
if (!nangoCallsAreAwaited(filePath)) {
|
|
501
469
|
return;
|
|
502
470
|
}
|
|
503
471
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
504
|
-
const jsFilePath =
|
|
505
|
-
|
|
472
|
+
const jsFilePath = path.join(path.dirname(filePath), path.basename(filePath, '.ts') + '.js');
|
|
473
|
+
const distJSFilePath = jsFilePath.replace(rawNangoIntegrationLocation, `${rawNangoIntegrationLocation}/dist`);
|
|
474
|
+
fs.writeFileSync(distJSFilePath, result);
|
|
506
475
|
console.log(chalk.green(`Compiled "${filePath}" successfully`));
|
|
507
476
|
}
|
|
508
477
|
catch (error) {
|
|
@@ -535,51 +504,52 @@ const nangoCallsAreAwaited = (filePath) => {
|
|
|
535
504
|
return areAwaited;
|
|
536
505
|
};
|
|
537
506
|
export const tscWatch = (debug = false) => {
|
|
507
|
+
const cwd = process.cwd();
|
|
538
508
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
539
|
-
const watchPath = [
|
|
509
|
+
const watchPath = [`${NANGO_INTEGRATIONS_LOCATION}/*.ts`, `${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`];
|
|
540
510
|
if (debug) {
|
|
541
511
|
printDebug(`Watching ${watchPath.join(', ')}`);
|
|
542
512
|
}
|
|
513
|
+
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
543
514
|
const watcher = chokidar.watch(watchPath, {
|
|
544
515
|
ignoreInitial: false,
|
|
545
516
|
ignored: (filePath) => {
|
|
546
|
-
return filePath === TYPES_FILE_NAME
|
|
517
|
+
return filePath === `${rawNangoIntegrationLocation}/${TYPES_FILE_NAME}`;
|
|
547
518
|
}
|
|
548
519
|
});
|
|
549
|
-
const distDir =
|
|
520
|
+
const distDir = path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/dist`);
|
|
550
521
|
if (!fs.existsSync(distDir)) {
|
|
551
522
|
if (debug) {
|
|
552
523
|
printDebug(`Creating ${distDir} directory`);
|
|
553
524
|
}
|
|
554
525
|
fs.mkdirSync(distDir);
|
|
555
526
|
}
|
|
556
|
-
if (!fs.existsSync(
|
|
527
|
+
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${TYPES_FILE_NAME}`)) {
|
|
557
528
|
if (debug) {
|
|
558
529
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
559
530
|
}
|
|
560
531
|
createModelFile();
|
|
561
532
|
}
|
|
562
533
|
watcher.on('add', (filePath) => {
|
|
563
|
-
if (filePath === nangoConfigFile) {
|
|
534
|
+
if (filePath === `${rawNangoIntegrationLocation}/${nangoConfigFile}`) {
|
|
564
535
|
return;
|
|
565
536
|
}
|
|
566
537
|
compileFile(filePath);
|
|
567
538
|
});
|
|
568
539
|
watcher.on('unlink', (filePath) => {
|
|
569
|
-
if (filePath === nangoConfigFile) {
|
|
540
|
+
if (filePath === `${rawNangoIntegrationLocation}/${nangoConfigFile}`) {
|
|
570
541
|
return;
|
|
571
542
|
}
|
|
572
|
-
const jsFilePath =
|
|
573
|
-
|
|
543
|
+
const jsFilePath = path.join(path.dirname(filePath), path.basename(filePath, '.ts') + '.js');
|
|
544
|
+
const distJSFilePath = jsFilePath.replace(rawNangoIntegrationLocation, `${rawNangoIntegrationLocation}/dist`);
|
|
545
|
+
fs.unlinkSync(distJSFilePath);
|
|
574
546
|
});
|
|
575
547
|
watcher.on('change', (filePath) => {
|
|
576
|
-
if (filePath === nangoConfigFile) {
|
|
548
|
+
if (filePath === `${rawNangoIntegrationLocation}/${nangoConfigFile}`) {
|
|
577
549
|
// config file changed, re-compile each ts file
|
|
578
|
-
const integrationFiles = glob.sync(
|
|
550
|
+
const integrationFiles = glob.sync(path.resolve(cwd, `${NANGO_INTEGRATIONS_LOCATION}/*.ts`));
|
|
579
551
|
for (const file of integrationFiles) {
|
|
580
|
-
|
|
581
|
-
const strippedFile = file.replace(/^.*[\\\/]/, '');
|
|
582
|
-
compileFile(strippedFile);
|
|
552
|
+
compileFile(file);
|
|
583
553
|
}
|
|
584
554
|
return;
|
|
585
555
|
}
|
|
@@ -594,8 +564,9 @@ export const tscWatch = (debug = false) => {
|
|
|
594
564
|
return;
|
|
595
565
|
}
|
|
596
566
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
597
|
-
const jsFilePath =
|
|
598
|
-
|
|
567
|
+
const jsFilePath = path.join(path.dirname(filePath), path.basename(filePath, '.ts') + '.js');
|
|
568
|
+
const distJSFilePath = jsFilePath.replace(rawNangoIntegrationLocation, `${rawNangoIntegrationLocation}/dist`);
|
|
569
|
+
fs.writeFileSync(distJSFilePath, result);
|
|
599
570
|
console.log(chalk.green(`Compiled ${filePath} successfully`));
|
|
600
571
|
}
|
|
601
572
|
catch (error) {
|
|
@@ -606,7 +577,7 @@ export const tscWatch = (debug = false) => {
|
|
|
606
577
|
}
|
|
607
578
|
};
|
|
608
579
|
export const configWatch = (debug = false) => {
|
|
609
|
-
const watchPath =
|
|
580
|
+
const watchPath = `${NANGO_INTEGRATIONS_LOCATION}/${nangoConfigFile}`;
|
|
610
581
|
if (debug) {
|
|
611
582
|
printDebug(`Watching ${watchPath}`);
|
|
612
583
|
}
|