nango 0.24.13 → 0.25.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.
- package/dist/index.js +86 -78
- package/dist/index.js.map +1 -1
- package/dist/nango-sync.d.ts +21 -10
- package/dist/sync.d.ts +8 -4
- package/dist/sync.js +276 -247
- package/dist/sync.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/utils.d.ts +6 -3
- package/dist/utils.js +73 -23
- package/dist/utils.js.map +1 -1
- package/docker/docker-compose.yaml +2 -2
- package/package.json +2 -2
package/dist/sync.js
CHANGED
|
@@ -14,31 +14,214 @@ 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, buildInterfaces, enrichHeaders, getNangoRootPath, printDebug } from './utils.js';
|
|
17
|
+
import { hostport, port, httpsAgent, verifyNecessaryFiles, getConnection, NANGO_INTEGRATIONS_LOCATION, NANGO_INTEGRATIONS_NAME, 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
|
-
const
|
|
24
|
+
export const version = (debug) => {
|
|
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
|
|
25
72
|
const cwd = process.cwd();
|
|
26
|
-
const
|
|
73
|
+
const currentDirectorySplit = cwd.split('/');
|
|
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');
|
|
27
135
|
const configData = yaml.load(configContents);
|
|
136
|
+
const { integrations } = configData;
|
|
28
137
|
const { models } = configData;
|
|
29
|
-
const interfaceDefinitions = buildInterfaces(models);
|
|
138
|
+
const interfaceDefinitions = buildInterfaces(models, debug);
|
|
30
139
|
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
|
+
}
|
|
31
143
|
// insert NangoSync types to the bottom of the file
|
|
32
144
|
const typesContent = fs.readFileSync(`${getNangoRootPath()}/${NangoSyncTypesFileLocation}`, 'utf8');
|
|
33
145
|
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' });
|
|
34
219
|
if (notify) {
|
|
35
|
-
|
|
36
|
-
console.log(chalk.green(`${rawNangoIntegrationLocation}/${nangoConfigFile} was updated. The interface file (${rawNangoIntegrationLocation}/${TYPES_FILE_NAME}) was updated to reflect the updated config`));
|
|
220
|
+
console.log(chalk.green(`The ${nangoConfigFile} was updated. The interface file (${TYPES_FILE_NAME}) was updated to reflect the updated config`));
|
|
37
221
|
}
|
|
38
222
|
};
|
|
39
223
|
const getConfig = async (debug = false) => {
|
|
40
|
-
const
|
|
41
|
-
const config = await loadSimplifiedConfig(path.resolve(cwd, NANGO_INTEGRATIONS_LOCATION));
|
|
224
|
+
const config = await loadSimplifiedConfig('./');
|
|
42
225
|
if (!config) {
|
|
43
226
|
throw new Error(`Error loading the ${nangoConfigFile} file`);
|
|
44
227
|
}
|
|
@@ -47,21 +230,40 @@ const getConfig = async (debug = false) => {
|
|
|
47
230
|
}
|
|
48
231
|
return config;
|
|
49
232
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await verifyNecessaryFiles(autoConfirm);
|
|
53
|
-
if (host) {
|
|
233
|
+
async function parseSecretKey(environment, debug = false) {
|
|
234
|
+
if (process.env['NANGO_SECRET_KEY_PROD'] && environment === 'prod') {
|
|
54
235
|
if (debug) {
|
|
55
|
-
printDebug(`
|
|
236
|
+
printDebug(`Environment is set to prod, setting NANGO_SECRET_KEY to NANGO_SECRET_KEY_PROD.`);
|
|
56
237
|
}
|
|
57
|
-
process.env['
|
|
238
|
+
process.env['NANGO_SECRET_KEY'] = process.env['NANGO_SECRET_KEY_PROD'];
|
|
58
239
|
}
|
|
59
|
-
if (
|
|
240
|
+
if (process.env['NANGO_SECRET_KEY_DEV'] && environment === 'dev') {
|
|
60
241
|
if (debug) {
|
|
61
|
-
printDebug(`
|
|
242
|
+
printDebug(`Environment is set to dev, setting NANGO_SECRET_KEY to NANGO_SECRET_KEY_DEV.`);
|
|
62
243
|
}
|
|
63
|
-
process.env['NANGO_SECRET_KEY'] =
|
|
244
|
+
process.env['NANGO_SECRET_KEY'] = process.env['NANGO_SECRET_KEY_DEV'];
|
|
64
245
|
}
|
|
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);
|
|
65
267
|
if (!process.env['NANGO_HOSTPORT']) {
|
|
66
268
|
switch (env) {
|
|
67
269
|
case 'local':
|
|
@@ -77,26 +279,10 @@ export const deploy = async (options, debug = false) => {
|
|
|
77
279
|
}
|
|
78
280
|
if (debug) {
|
|
79
281
|
printDebug(`NANGO_HOSTPORT is set to ${process.env['NANGO_HOSTPORT']}.`);
|
|
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
|
-
}
|
|
282
|
+
printDebug(`Environment is set to ${environment}`);
|
|
96
283
|
}
|
|
97
284
|
tsc(debug);
|
|
98
285
|
const config = await getConfig(debug);
|
|
99
|
-
const cwd = process.cwd();
|
|
100
286
|
const postData = [];
|
|
101
287
|
for (const integration of config) {
|
|
102
288
|
const { providerConfigKey } = integration;
|
|
@@ -106,7 +292,7 @@ export const deploy = async (options, debug = false) => {
|
|
|
106
292
|
}
|
|
107
293
|
for (const sync of syncs) {
|
|
108
294
|
const { name: syncName, runs, returns: models, models: model_schema } = sync;
|
|
109
|
-
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName,
|
|
295
|
+
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName, './');
|
|
110
296
|
if (!integrationFileResult) {
|
|
111
297
|
console.log(chalk.red(`No integration file found for ${syncName} at ${integrationFilePath}. Skipping...`));
|
|
112
298
|
continue;
|
|
@@ -127,11 +313,7 @@ export const deploy = async (options, debug = false) => {
|
|
|
127
313
|
}
|
|
128
314
|
}
|
|
129
315
|
const url = process.env['NANGO_HOSTPORT'] + `/sync/deploy`;
|
|
130
|
-
if (
|
|
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) {
|
|
316
|
+
if (process.env['NANGO_DEPLOY_AUTO_CONFIRM'] !== 'true' && !autoConfirm) {
|
|
135
317
|
const confirmationUrl = process.env['NANGO_HOSTPORT'] + `/sync/deploy/confirmation`;
|
|
136
318
|
try {
|
|
137
319
|
const response = await axios.post(confirmationUrl, { syncs: postData, reconcile: false, debug }, { headers: enrichHeaders(), httpsAgent: httpsAgent() });
|
|
@@ -152,8 +334,13 @@ export const deploy = async (options, debug = false) => {
|
|
|
152
334
|
.post(process.env['NANGO_HOSTPORT'] + `/sync/deploy`, { syncs: postData, reconcile: true, debug }, { headers: enrichHeaders(), httpsAgent: httpsAgent() })
|
|
153
335
|
.then((response) => {
|
|
154
336
|
const results = response.data;
|
|
155
|
-
|
|
156
|
-
|
|
337
|
+
if (results.length === 0) {
|
|
338
|
+
console.log(chalk.green(`Successfully removed the syncs.`));
|
|
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
|
+
}
|
|
157
344
|
})
|
|
158
345
|
.catch((err) => {
|
|
159
346
|
const errorMessage = JSON.stringify(err.response.data, null, 2);
|
|
@@ -192,8 +379,13 @@ async function deploySyncs(url, body) {
|
|
|
192
379
|
.post(url, body, { headers: enrichHeaders(), httpsAgent: httpsAgent() })
|
|
193
380
|
.then((response) => {
|
|
194
381
|
const results = response.data;
|
|
195
|
-
|
|
196
|
-
|
|
382
|
+
if (results.length === 0) {
|
|
383
|
+
console.log(chalk.green(`Successfully removed the syncs.`));
|
|
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
|
+
}
|
|
197
389
|
})
|
|
198
390
|
.catch((err) => {
|
|
199
391
|
let errorMessage;
|
|
@@ -209,198 +401,38 @@ async function deploySyncs(url, body) {
|
|
|
209
401
|
process.exit(1);
|
|
210
402
|
});
|
|
211
403
|
}
|
|
212
|
-
export const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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)) {
|
|
255
|
-
if (debug) {
|
|
256
|
-
printDebug('Creating the nango integrations directory');
|
|
257
|
-
}
|
|
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);
|
|
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 {
|
|
404
|
+
export const dryRun = async (options, environment, debug = false) => {
|
|
405
|
+
let syncName = '';
|
|
406
|
+
let connectionId, suppliedLastSyncDate;
|
|
407
|
+
await parseSecretKey(environment, debug);
|
|
408
|
+
if (!process.env['NANGO_HOSTPORT']) {
|
|
291
409
|
if (debug) {
|
|
292
|
-
printDebug(
|
|
410
|
+
printDebug(`NANGO_HOSTPORT is not set. Setting the default to ${hostport}`);
|
|
293
411
|
}
|
|
412
|
+
process.env['NANGO_HOSTPORT'] = hostport;
|
|
294
413
|
}
|
|
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'));
|
|
307
|
-
if (debug) {
|
|
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
414
|
if (debug) {
|
|
314
|
-
printDebug(`
|
|
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;
|
|
415
|
+
printDebug(`NANGO_HOSTPORT is set to ${process.env['NANGO_HOSTPORT']}`);
|
|
369
416
|
}
|
|
370
417
|
if (Object.keys(options).length > 0) {
|
|
371
|
-
({ sync: syncName,
|
|
418
|
+
({ sync: syncName, connectionId, lastSyncDate: suppliedLastSyncDate } = options);
|
|
372
419
|
}
|
|
373
420
|
if (!syncName) {
|
|
374
421
|
console.log(chalk.red('Sync name is required'));
|
|
375
422
|
return;
|
|
376
423
|
}
|
|
377
|
-
if (!providerConfigKey) {
|
|
378
|
-
console.log(chalk.red('Provider config key is required'));
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
424
|
if (!connectionId) {
|
|
382
425
|
console.log(chalk.red('Connection id is required'));
|
|
383
426
|
return;
|
|
384
427
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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;
|
|
428
|
+
const config = await getConfig(debug);
|
|
429
|
+
const providerConfigKey = config.find((config) => config.syncs.find((sync) => sync.name === syncName))?.providerConfigKey;
|
|
430
|
+
if (!providerConfigKey) {
|
|
431
|
+
console.log(chalk.red(`Provider config key not found, please check that the provider exists for this sync name: ${syncName}`));
|
|
432
|
+
return;
|
|
401
433
|
}
|
|
402
434
|
if (debug) {
|
|
403
|
-
printDebug(`
|
|
435
|
+
printDebug(`Provider config key found to be ${providerConfigKey}`);
|
|
404
436
|
}
|
|
405
437
|
const nangoConnection = (await getConnection(providerConfigKey, connectionId, {
|
|
406
438
|
'Nango-Is-Sync': true,
|
|
@@ -410,6 +442,9 @@ export const run = async (args, options, debug = false) => {
|
|
|
410
442
|
console.log(chalk.red('Connection not found'));
|
|
411
443
|
return;
|
|
412
444
|
}
|
|
445
|
+
if (debug) {
|
|
446
|
+
printDebug(`Connection found with ${JSON.stringify(nangoConnection, null, 2)}`);
|
|
447
|
+
}
|
|
413
448
|
if (process.env['NANGO_HOSTPORT'] === cloudHost || process.env['NANGO_HOSTPORT'] === stagingHost) {
|
|
414
449
|
process.env['NANGO_CLOUD'] = 'true';
|
|
415
450
|
}
|
|
@@ -420,13 +455,12 @@ export const run = async (args, options, debug = false) => {
|
|
|
420
455
|
}
|
|
421
456
|
lastSyncDate = new Date(suppliedLastSyncDate);
|
|
422
457
|
}
|
|
423
|
-
const cwd = process.cwd();
|
|
424
458
|
const syncRun = new syncRunService({
|
|
425
459
|
writeToDb: false,
|
|
426
460
|
nangoConnection,
|
|
427
461
|
syncName,
|
|
428
462
|
syncType: SyncType.INITIAL,
|
|
429
|
-
loadLocation:
|
|
463
|
+
loadLocation: './',
|
|
430
464
|
debug
|
|
431
465
|
});
|
|
432
466
|
try {
|
|
@@ -440,38 +474,35 @@ export const run = async (args, options, debug = false) => {
|
|
|
440
474
|
}
|
|
441
475
|
};
|
|
442
476
|
export const tsc = (debug = false) => {
|
|
443
|
-
const cwd = process.cwd();
|
|
444
477
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
445
|
-
const distDir =
|
|
478
|
+
const distDir = './dist';
|
|
446
479
|
if (!fs.existsSync(distDir)) {
|
|
447
480
|
if (debug) {
|
|
448
481
|
printDebug(`Creating ${distDir} directory`);
|
|
449
482
|
}
|
|
450
483
|
fs.mkdirSync(distDir);
|
|
451
484
|
}
|
|
452
|
-
if (!fs.existsSync(
|
|
485
|
+
if (!fs.existsSync(`./${TYPES_FILE_NAME}`)) {
|
|
453
486
|
if (debug) {
|
|
454
487
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
455
488
|
}
|
|
456
489
|
createModelFile();
|
|
457
490
|
}
|
|
458
|
-
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
459
491
|
const compiler = tsNode.create({
|
|
460
492
|
compilerOptions: JSON.parse(tsconfig).compilerOptions
|
|
461
493
|
});
|
|
462
494
|
if (debug) {
|
|
463
495
|
printDebug(`Compiler options: ${JSON.stringify(JSON.parse(tsconfig).compilerOptions, null, 2)}`);
|
|
464
496
|
}
|
|
465
|
-
const integrationFiles = glob.sync(
|
|
497
|
+
const integrationFiles = glob.sync(`./*.ts`);
|
|
466
498
|
for (const filePath of integrationFiles) {
|
|
467
499
|
try {
|
|
468
500
|
if (!nangoCallsAreAwaited(filePath)) {
|
|
469
501
|
return;
|
|
470
502
|
}
|
|
471
503
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
472
|
-
const jsFilePath =
|
|
473
|
-
|
|
474
|
-
fs.writeFileSync(distJSFilePath, result);
|
|
504
|
+
const jsFilePath = filePath.replace(/\/[^\/]*$/, `/dist/${path.basename(filePath.replace('.ts', '.js'))}`);
|
|
505
|
+
fs.writeFileSync(jsFilePath, result);
|
|
475
506
|
console.log(chalk.green(`Compiled "${filePath}" successfully`));
|
|
476
507
|
}
|
|
477
508
|
catch (error) {
|
|
@@ -504,52 +535,51 @@ const nangoCallsAreAwaited = (filePath) => {
|
|
|
504
535
|
return areAwaited;
|
|
505
536
|
};
|
|
506
537
|
export const tscWatch = (debug = false) => {
|
|
507
|
-
const cwd = process.cwd();
|
|
508
538
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
509
|
-
const watchPath = [
|
|
539
|
+
const watchPath = [`./*.ts`, `./${nangoConfigFile}`];
|
|
510
540
|
if (debug) {
|
|
511
541
|
printDebug(`Watching ${watchPath.join(', ')}`);
|
|
512
542
|
}
|
|
513
|
-
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
514
543
|
const watcher = chokidar.watch(watchPath, {
|
|
515
544
|
ignoreInitial: false,
|
|
516
545
|
ignored: (filePath) => {
|
|
517
|
-
return filePath ===
|
|
546
|
+
return filePath === TYPES_FILE_NAME;
|
|
518
547
|
}
|
|
519
548
|
});
|
|
520
|
-
const distDir =
|
|
549
|
+
const distDir = './dist';
|
|
521
550
|
if (!fs.existsSync(distDir)) {
|
|
522
551
|
if (debug) {
|
|
523
552
|
printDebug(`Creating ${distDir} directory`);
|
|
524
553
|
}
|
|
525
554
|
fs.mkdirSync(distDir);
|
|
526
555
|
}
|
|
527
|
-
if (!fs.existsSync(
|
|
556
|
+
if (!fs.existsSync(`./${TYPES_FILE_NAME}`)) {
|
|
528
557
|
if (debug) {
|
|
529
558
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
530
559
|
}
|
|
531
560
|
createModelFile();
|
|
532
561
|
}
|
|
533
562
|
watcher.on('add', (filePath) => {
|
|
534
|
-
if (filePath ===
|
|
563
|
+
if (filePath === nangoConfigFile) {
|
|
535
564
|
return;
|
|
536
565
|
}
|
|
537
566
|
compileFile(filePath);
|
|
538
567
|
});
|
|
539
568
|
watcher.on('unlink', (filePath) => {
|
|
540
|
-
if (filePath ===
|
|
569
|
+
if (filePath === nangoConfigFile) {
|
|
541
570
|
return;
|
|
542
571
|
}
|
|
543
|
-
const jsFilePath = path.
|
|
544
|
-
|
|
545
|
-
fs.unlinkSync(distJSFilePath);
|
|
572
|
+
const jsFilePath = `./dist/${path.basename(filePath.replace('.ts', '.js'))}`;
|
|
573
|
+
fs.unlinkSync(jsFilePath);
|
|
546
574
|
});
|
|
547
575
|
watcher.on('change', (filePath) => {
|
|
548
|
-
if (filePath ===
|
|
576
|
+
if (filePath === nangoConfigFile) {
|
|
549
577
|
// config file changed, re-compile each ts file
|
|
550
|
-
const integrationFiles = glob.sync(
|
|
578
|
+
const integrationFiles = glob.sync(`./*.ts`);
|
|
551
579
|
for (const file of integrationFiles) {
|
|
552
|
-
|
|
580
|
+
// strip the file to just the last part
|
|
581
|
+
const strippedFile = file.replace(/^.*[\\\/]/, '');
|
|
582
|
+
compileFile(strippedFile);
|
|
553
583
|
}
|
|
554
584
|
return;
|
|
555
585
|
}
|
|
@@ -564,9 +594,8 @@ export const tscWatch = (debug = false) => {
|
|
|
564
594
|
return;
|
|
565
595
|
}
|
|
566
596
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
567
|
-
const jsFilePath = path.
|
|
568
|
-
|
|
569
|
-
fs.writeFileSync(distJSFilePath, result);
|
|
597
|
+
const jsFilePath = `./dist/${path.basename(filePath.replace('.ts', '.js'))}`;
|
|
598
|
+
fs.writeFileSync(jsFilePath, result);
|
|
570
599
|
console.log(chalk.green(`Compiled ${filePath} successfully`));
|
|
571
600
|
}
|
|
572
601
|
catch (error) {
|
|
@@ -577,7 +606,7 @@ export const tscWatch = (debug = false) => {
|
|
|
577
606
|
}
|
|
578
607
|
};
|
|
579
608
|
export const configWatch = (debug = false) => {
|
|
580
|
-
const watchPath =
|
|
609
|
+
const watchPath = `./${nangoConfigFile}`;
|
|
581
610
|
if (debug) {
|
|
582
611
|
printDebug(`Watching ${watchPath}`);
|
|
583
612
|
}
|