nango 0.23.30 → 0.24.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 +3 -3
- package/dist/sync.d.ts +8 -4
- package/dist/sync.js +256 -239
- 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 +67 -23
- package/dist/utils.js.map +1 -1
- package/docker/docker-compose.yaml +3 -8
- package/package.json +3 -2
package/dist/sync.js
CHANGED
|
@@ -14,31 +14,210 @@ 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, getProviderBySyncName, 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
|
+
const { returns: models } = syncData;
|
|
171
|
+
const syncNameCamel = syncName
|
|
172
|
+
.split('-')
|
|
173
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
174
|
+
.join('');
|
|
175
|
+
const ejsTeamplateContents = syncName === exampleSyncName ? githubExampleTemplateContents : templateContents;
|
|
176
|
+
const rendered = ejs.render(ejsTeamplateContents, {
|
|
177
|
+
syncName: syncNameCamel,
|
|
178
|
+
interfaceFileName: TYPES_FILE_NAME.replace('.ts', ''),
|
|
179
|
+
interfaceNames: models.map((model) => {
|
|
180
|
+
const singularModel = model?.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
181
|
+
return `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`;
|
|
182
|
+
}),
|
|
183
|
+
mappings: models.map((model) => {
|
|
184
|
+
const singularModel = model.charAt(model.length - 1) === 's' ? model.slice(0, -1) : model;
|
|
185
|
+
return {
|
|
186
|
+
name: model,
|
|
187
|
+
type: `${singularModel.charAt(0).toUpperCase()}${singularModel.slice(1)}`
|
|
188
|
+
};
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
if (!fs.existsSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`)) {
|
|
192
|
+
fs.writeFileSync(`${NANGO_INTEGRATIONS_LOCATION}/${syncName}.ts`, rendered);
|
|
193
|
+
if (debug) {
|
|
194
|
+
printDebug(`Created ${syncName}.ts file`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (debug) {
|
|
199
|
+
printDebug(`${syncName}.ts file already exists, so will not overwrite it.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
console.log(chalk.green(`Integration files have been created`));
|
|
205
|
+
};
|
|
206
|
+
const createModelFile = (notify = false) => {
|
|
207
|
+
const configContents = fs.readFileSync(`./${nangoConfigFile}`, 'utf8');
|
|
208
|
+
const configData = yaml.load(configContents);
|
|
209
|
+
const { models } = configData;
|
|
210
|
+
const interfaceDefinitions = buildInterfaces(models);
|
|
211
|
+
fs.writeFileSync(`./${TYPES_FILE_NAME}`, interfaceDefinitions.join('\n'));
|
|
212
|
+
// insert NangoSync types to the bottom of the file
|
|
213
|
+
const typesContent = fs.readFileSync(`${getNangoRootPath()}/${NangoSyncTypesFileLocation}`, 'utf8');
|
|
214
|
+
fs.writeFileSync(`./${TYPES_FILE_NAME}`, typesContent, { flag: 'a' });
|
|
34
215
|
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`));
|
|
216
|
+
console.log(chalk.green(`The ${nangoConfigFile} was updated. The interface file (${TYPES_FILE_NAME}) was updated to reflect the updated config`));
|
|
37
217
|
}
|
|
38
218
|
};
|
|
39
219
|
const getConfig = async (debug = false) => {
|
|
40
|
-
const
|
|
41
|
-
const config = await loadSimplifiedConfig(path.resolve(cwd, NANGO_INTEGRATIONS_LOCATION));
|
|
220
|
+
const config = await loadSimplifiedConfig('./');
|
|
42
221
|
if (!config) {
|
|
43
222
|
throw new Error(`Error loading the ${nangoConfigFile} file`);
|
|
44
223
|
}
|
|
@@ -47,21 +226,40 @@ const getConfig = async (debug = false) => {
|
|
|
47
226
|
}
|
|
48
227
|
return config;
|
|
49
228
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await verifyNecessaryFiles(autoConfirm);
|
|
53
|
-
if (host) {
|
|
229
|
+
async function parseSecretKey(environment, debug = false) {
|
|
230
|
+
if (process.env['NANGO_SECRET_KEY_PROD'] && environment === 'prod') {
|
|
54
231
|
if (debug) {
|
|
55
|
-
printDebug(`
|
|
232
|
+
printDebug(`Environment is set to prod, setting NANGO_SECRET_KEY to NANGO_SECRET_KEY_PROD.`);
|
|
56
233
|
}
|
|
57
|
-
process.env['
|
|
234
|
+
process.env['NANGO_SECRET_KEY'] = process.env['NANGO_SECRET_KEY_PROD'];
|
|
58
235
|
}
|
|
59
|
-
if (
|
|
236
|
+
if (process.env['NANGO_SECRET_KEY_DEV'] && environment === 'dev') {
|
|
60
237
|
if (debug) {
|
|
61
|
-
printDebug(`
|
|
238
|
+
printDebug(`Environment is set to dev, setting NANGO_SECRET_KEY to NANGO_SECRET_KEY_DEV.`);
|
|
239
|
+
}
|
|
240
|
+
process.env['NANGO_SECRET_KEY'] = process.env['NANGO_SECRET_KEY_DEV'];
|
|
241
|
+
}
|
|
242
|
+
if (!process.env['NANGO_SECRET_KEY']) {
|
|
243
|
+
console.log(chalk.red(`NANGO_SECRET_KEY environment variable is not set. Please set it now`));
|
|
244
|
+
try {
|
|
245
|
+
const secretKey = await promptly.prompt('Secret Key: ');
|
|
246
|
+
if (secretKey) {
|
|
247
|
+
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.log('Error occurred while trying to prompt for secret key:', error);
|
|
255
|
+
process.exit(1);
|
|
62
256
|
}
|
|
63
|
-
process.env['NANGO_SECRET_KEY'] = secretKey;
|
|
64
257
|
}
|
|
258
|
+
}
|
|
259
|
+
export const deploy = async (options, environment, debug = false) => {
|
|
260
|
+
const { env, version, sync: optionalSyncName, autoConfirm } = options;
|
|
261
|
+
await verifyNecessaryFiles(autoConfirm);
|
|
262
|
+
await parseSecretKey(environment, debug);
|
|
65
263
|
if (!process.env['NANGO_HOSTPORT']) {
|
|
66
264
|
switch (env) {
|
|
67
265
|
case 'local':
|
|
@@ -77,26 +275,10 @@ export const deploy = async (options, debug = false) => {
|
|
|
77
275
|
}
|
|
78
276
|
if (debug) {
|
|
79
277
|
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
|
-
}
|
|
278
|
+
printDebug(`Environment is set to ${environment}`);
|
|
96
279
|
}
|
|
97
280
|
tsc(debug);
|
|
98
281
|
const config = await getConfig(debug);
|
|
99
|
-
const cwd = process.cwd();
|
|
100
282
|
const postData = [];
|
|
101
283
|
for (const integration of config) {
|
|
102
284
|
const { providerConfigKey } = integration;
|
|
@@ -106,7 +288,7 @@ export const deploy = async (options, debug = false) => {
|
|
|
106
288
|
}
|
|
107
289
|
for (const sync of syncs) {
|
|
108
290
|
const { name: syncName, runs, returns: models, models: model_schema } = sync;
|
|
109
|
-
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName,
|
|
291
|
+
const { path: integrationFilePath, result: integrationFileResult } = checkForIntegrationFile(syncName, './');
|
|
110
292
|
if (!integrationFileResult) {
|
|
111
293
|
console.log(chalk.red(`No integration file found for ${syncName} at ${integrationFilePath}. Skipping...`));
|
|
112
294
|
continue;
|
|
@@ -131,7 +313,7 @@ export const deploy = async (options, debug = false) => {
|
|
|
131
313
|
console.log(chalk.red(`No syncs found to deploy. Please make sure your integration files compiled successfully and exist in your dist directory`));
|
|
132
314
|
return;
|
|
133
315
|
}
|
|
134
|
-
if (
|
|
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() });
|
|
@@ -209,198 +391,36 @@ async function deploySyncs(url, body) {
|
|
|
209
391
|
process.exit(1);
|
|
210
392
|
});
|
|
211
393
|
}
|
|
212
|
-
export const
|
|
213
|
-
|
|
214
|
-
|
|
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)) {
|
|
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 {
|
|
394
|
+
export const dryRun = async (options, environment, debug = false) => {
|
|
395
|
+
let syncName, connectionId, suppliedLastSyncDate;
|
|
396
|
+
await parseSecretKey(environment, debug);
|
|
397
|
+
if (!process.env['NANGO_HOSTPORT']) {
|
|
291
398
|
if (debug) {
|
|
292
|
-
printDebug(
|
|
399
|
+
printDebug(`NANGO_HOSTPORT is not set. Setting the default to ${hostport}`);
|
|
293
400
|
}
|
|
401
|
+
process.env['NANGO_HOSTPORT'] = hostport;
|
|
294
402
|
}
|
|
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
403
|
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;
|
|
404
|
+
printDebug(`NANGO_HOSTPORT is set to ${process.env['NANGO_HOSTPORT']}`);
|
|
369
405
|
}
|
|
370
406
|
if (Object.keys(options).length > 0) {
|
|
371
|
-
({ sync: syncName,
|
|
407
|
+
({ sync: syncName, connectionId, lastSyncDate: suppliedLastSyncDate } = options);
|
|
372
408
|
}
|
|
373
409
|
if (!syncName) {
|
|
374
410
|
console.log(chalk.red('Sync name is required'));
|
|
375
411
|
return;
|
|
376
412
|
}
|
|
377
|
-
if (!providerConfigKey) {
|
|
378
|
-
console.log(chalk.red('Provider config key is required'));
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
413
|
if (!connectionId) {
|
|
382
414
|
console.log(chalk.red('Connection id is required'));
|
|
383
415
|
return;
|
|
384
416
|
}
|
|
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;
|
|
417
|
+
const providerConfigKey = await getProviderBySyncName({ syncName }, debug);
|
|
418
|
+
if (!providerConfigKey) {
|
|
419
|
+
console.log(chalk.red(`Provider config key not found, please check that the provider exists for this sync name: ${syncName}`));
|
|
420
|
+
return;
|
|
401
421
|
}
|
|
402
422
|
if (debug) {
|
|
403
|
-
printDebug(`
|
|
423
|
+
printDebug(`Provider config key found to be ${providerConfigKey}`);
|
|
404
424
|
}
|
|
405
425
|
const nangoConnection = (await getConnection(providerConfigKey, connectionId, {
|
|
406
426
|
'Nango-Is-Sync': true,
|
|
@@ -410,6 +430,9 @@ export const run = async (args, options, debug = false) => {
|
|
|
410
430
|
console.log(chalk.red('Connection not found'));
|
|
411
431
|
return;
|
|
412
432
|
}
|
|
433
|
+
if (debug) {
|
|
434
|
+
printDebug(`Connection found with ${JSON.stringify(nangoConnection, null, 2)}`);
|
|
435
|
+
}
|
|
413
436
|
if (process.env['NANGO_HOSTPORT'] === cloudHost || process.env['NANGO_HOSTPORT'] === stagingHost) {
|
|
414
437
|
process.env['NANGO_CLOUD'] = 'true';
|
|
415
438
|
}
|
|
@@ -420,13 +443,12 @@ export const run = async (args, options, debug = false) => {
|
|
|
420
443
|
}
|
|
421
444
|
lastSyncDate = new Date(suppliedLastSyncDate);
|
|
422
445
|
}
|
|
423
|
-
const cwd = process.cwd();
|
|
424
446
|
const syncRun = new syncRunService({
|
|
425
447
|
writeToDb: false,
|
|
426
448
|
nangoConnection,
|
|
427
449
|
syncName,
|
|
428
450
|
syncType: SyncType.INITIAL,
|
|
429
|
-
loadLocation:
|
|
451
|
+
loadLocation: './',
|
|
430
452
|
debug
|
|
431
453
|
});
|
|
432
454
|
try {
|
|
@@ -440,38 +462,35 @@ export const run = async (args, options, debug = false) => {
|
|
|
440
462
|
}
|
|
441
463
|
};
|
|
442
464
|
export const tsc = (debug = false) => {
|
|
443
|
-
const cwd = process.cwd();
|
|
444
465
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
445
|
-
const distDir =
|
|
466
|
+
const distDir = './dist';
|
|
446
467
|
if (!fs.existsSync(distDir)) {
|
|
447
468
|
if (debug) {
|
|
448
469
|
printDebug(`Creating ${distDir} directory`);
|
|
449
470
|
}
|
|
450
471
|
fs.mkdirSync(distDir);
|
|
451
472
|
}
|
|
452
|
-
if (!fs.existsSync(
|
|
473
|
+
if (!fs.existsSync(`./${TYPES_FILE_NAME}`)) {
|
|
453
474
|
if (debug) {
|
|
454
475
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
455
476
|
}
|
|
456
477
|
createModelFile();
|
|
457
478
|
}
|
|
458
|
-
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
459
479
|
const compiler = tsNode.create({
|
|
460
480
|
compilerOptions: JSON.parse(tsconfig).compilerOptions
|
|
461
481
|
});
|
|
462
482
|
if (debug) {
|
|
463
483
|
printDebug(`Compiler options: ${JSON.stringify(JSON.parse(tsconfig).compilerOptions, null, 2)}`);
|
|
464
484
|
}
|
|
465
|
-
const integrationFiles = glob.sync(
|
|
485
|
+
const integrationFiles = glob.sync(`./*.ts`);
|
|
466
486
|
for (const filePath of integrationFiles) {
|
|
467
487
|
try {
|
|
468
488
|
if (!nangoCallsAreAwaited(filePath)) {
|
|
469
489
|
return;
|
|
470
490
|
}
|
|
471
491
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
472
|
-
const jsFilePath =
|
|
473
|
-
|
|
474
|
-
fs.writeFileSync(distJSFilePath, result);
|
|
492
|
+
const jsFilePath = filePath.replace(/\/[^\/]*$/, `/dist/${path.basename(filePath.replace('.ts', '.js'))}`);
|
|
493
|
+
fs.writeFileSync(jsFilePath, result);
|
|
475
494
|
console.log(chalk.green(`Compiled "${filePath}" successfully`));
|
|
476
495
|
}
|
|
477
496
|
catch (error) {
|
|
@@ -504,52 +523,51 @@ const nangoCallsAreAwaited = (filePath) => {
|
|
|
504
523
|
return areAwaited;
|
|
505
524
|
};
|
|
506
525
|
export const tscWatch = (debug = false) => {
|
|
507
|
-
const cwd = process.cwd();
|
|
508
526
|
const tsconfig = fs.readFileSync(`${getNangoRootPath()}/tsconfig.dev.json`, 'utf8');
|
|
509
|
-
const watchPath = [
|
|
527
|
+
const watchPath = [`./*.ts`, `./${nangoConfigFile}`];
|
|
510
528
|
if (debug) {
|
|
511
529
|
printDebug(`Watching ${watchPath.join(', ')}`);
|
|
512
530
|
}
|
|
513
|
-
const rawNangoIntegrationLocation = NANGO_INTEGRATIONS_LOCATION.replace('./', '');
|
|
514
531
|
const watcher = chokidar.watch(watchPath, {
|
|
515
532
|
ignoreInitial: false,
|
|
516
533
|
ignored: (filePath) => {
|
|
517
|
-
return filePath ===
|
|
534
|
+
return filePath === TYPES_FILE_NAME;
|
|
518
535
|
}
|
|
519
536
|
});
|
|
520
|
-
const distDir =
|
|
537
|
+
const distDir = './dist';
|
|
521
538
|
if (!fs.existsSync(distDir)) {
|
|
522
539
|
if (debug) {
|
|
523
540
|
printDebug(`Creating ${distDir} directory`);
|
|
524
541
|
}
|
|
525
542
|
fs.mkdirSync(distDir);
|
|
526
543
|
}
|
|
527
|
-
if (!fs.existsSync(
|
|
544
|
+
if (!fs.existsSync(`./${TYPES_FILE_NAME}`)) {
|
|
528
545
|
if (debug) {
|
|
529
546
|
printDebug(`Creating ${TYPES_FILE_NAME} file`);
|
|
530
547
|
}
|
|
531
548
|
createModelFile();
|
|
532
549
|
}
|
|
533
550
|
watcher.on('add', (filePath) => {
|
|
534
|
-
if (filePath ===
|
|
551
|
+
if (filePath === nangoConfigFile) {
|
|
535
552
|
return;
|
|
536
553
|
}
|
|
537
554
|
compileFile(filePath);
|
|
538
555
|
});
|
|
539
556
|
watcher.on('unlink', (filePath) => {
|
|
540
|
-
if (filePath ===
|
|
557
|
+
if (filePath === nangoConfigFile) {
|
|
541
558
|
return;
|
|
542
559
|
}
|
|
543
|
-
const jsFilePath = path.
|
|
544
|
-
|
|
545
|
-
fs.unlinkSync(distJSFilePath);
|
|
560
|
+
const jsFilePath = `./dist/${path.basename(filePath.replace('.ts', '.js'))}`;
|
|
561
|
+
fs.unlinkSync(jsFilePath);
|
|
546
562
|
});
|
|
547
563
|
watcher.on('change', (filePath) => {
|
|
548
|
-
if (filePath ===
|
|
564
|
+
if (filePath === nangoConfigFile) {
|
|
549
565
|
// config file changed, re-compile each ts file
|
|
550
|
-
const integrationFiles = glob.sync(
|
|
566
|
+
const integrationFiles = glob.sync(`./*.ts`);
|
|
551
567
|
for (const file of integrationFiles) {
|
|
552
|
-
|
|
568
|
+
// strip the file to just the last part
|
|
569
|
+
const strippedFile = file.replace(/^.*[\\\/]/, '');
|
|
570
|
+
compileFile(strippedFile);
|
|
553
571
|
}
|
|
554
572
|
return;
|
|
555
573
|
}
|
|
@@ -564,9 +582,8 @@ export const tscWatch = (debug = false) => {
|
|
|
564
582
|
return;
|
|
565
583
|
}
|
|
566
584
|
const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
567
|
-
const jsFilePath = path.
|
|
568
|
-
|
|
569
|
-
fs.writeFileSync(distJSFilePath, result);
|
|
585
|
+
const jsFilePath = `./dist/${path.basename(filePath.replace('.ts', '.js'))}`;
|
|
586
|
+
fs.writeFileSync(jsFilePath, result);
|
|
570
587
|
console.log(chalk.green(`Compiled ${filePath} successfully`));
|
|
571
588
|
}
|
|
572
589
|
catch (error) {
|
|
@@ -577,7 +594,7 @@ export const tscWatch = (debug = false) => {
|
|
|
577
594
|
}
|
|
578
595
|
};
|
|
579
596
|
export const configWatch = (debug = false) => {
|
|
580
|
-
const watchPath =
|
|
597
|
+
const watchPath = `./${nangoConfigFile}`;
|
|
581
598
|
if (debug) {
|
|
582
599
|
printDebug(`Watching ${watchPath}`);
|
|
583
600
|
}
|