Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Allow init to create any directory name #3338

Merged
merged 22 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cli-verification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ jobs:
EXPECTED_VERSION="Nango CLI version: 0.0.1-$GIT_HASH"
[ "$VERSION_OUTPUT" = "$EXPECTED_VERSION" ] || { echo "Version mismatch. Expected: $EXPECTED_VERSION, got: $VERSION_OUTPUT"; exit 1; }
npx nango version --debug
npx nango init --debug
npx nango init nango-integrations --debug
cd nango-integrations
npx nango generate --debug
npx nango compile --debug
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ All your integrations live in a folder called `nango-integrations`. Whether loca

To initialize your integrations folder (e.g. at the root of your repository), run:
```bash
nango init
nango init nango-integrations
```

This creates the `./nango-integrations` folder with some initial configuration and an example
Expand Down
4 changes: 2 additions & 2 deletions docs-v2/reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ npm install nango -g

In the folder where you want your integration folder (e.g. root of your project), run:
```bash
nango init
nango init nango-integrations
```

This creates the `./nango-integrations` folder with some initial configuration and an example
Expand Down Expand Up @@ -56,7 +56,7 @@ nango [command] --help

| Command | Description | Details |
| - | - | - |
| `nango init` | Creates the `nango-integrations` directory with a demo Github integration. | Generates `models.ts`, but not the compiled `.js` files. |
| `nango init <dir>` | Creates integrations directory `dir` with a demo Github integration. | Generates `models.ts`, but not the compiled `.js` files. |
| `nango dev` | Necessary to edit integration configurations and scripts. | Watches the `nango.yaml` and integration scripts, re-generates `models.ts` and compiled `.js` files. |
| `nango generate` | Generates an integration script `.ts` file with initial scaffold when new syncs appear in your `nango.yaml`. | Re-generates `models.ts`, not the compiled `.js` files. |
| `nango dryrun <params>` | Lets you test integration scripts locally. | |
Expand Down
90 changes: 1 addition & 89 deletions packages/cli/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as dotenv from 'dotenv';

import { getNangoRootPath, printDebug } from './utils.js';
import { loadYamlAndGenerate } from './services/model.service.js';
import { NANGO_INTEGRATIONS_NAME, TYPES_FILE_NAME, exampleSyncName } from './constants.js';
import { TYPES_FILE_NAME, exampleSyncName } from './constants.js';
import { compileAllFiles, compileSingleFile, getFileToCompile } from './services/compile.service.js';
import { getLayoutMode } from './utils/layoutMode.js';
import { getProviderConfigurationFromPath, nangoConfigFile } from '@nangohq/nango-yaml';
Expand Down Expand Up @@ -129,94 +129,6 @@ export function generate({ fullPath, debug = false }: { fullPath: string; debug?
}
}

/**
* Init
* If we're not currently in the nango-integrations directory create one
* and create an example nango.yaml file
*/
export function init({ absolutePath, debug = false }: { absolutePath: string; debug?: boolean }) {
const yamlData = fs.readFileSync(path.resolve(__dirname, `./templates/${nangoConfigFile}`), 'utf8');

// if currently in the nango-integrations directory then don't create another one
const currentDirectory = path.basename(absolutePath);
let fullPath: string;

if (currentDirectory === NANGO_INTEGRATIONS_NAME) {
if (debug) {
printDebug(`Currently in the ${NANGO_INTEGRATIONS_NAME} directory so the directory will not be created`);
}
fullPath = absolutePath;
} else {
fullPath = path.resolve(absolutePath, NANGO_INTEGRATIONS_NAME);
}

if (fs.existsSync(fullPath)) {
console.log(chalk.red(`The ${NANGO_INTEGRATIONS_NAME} directory already exists. You should run commands from within this directory`));
} else {
if (debug) {
printDebug(`Creating the nango integrations directory at ${absolutePath}`);
}
fs.mkdirSync(fullPath);
}

const configFileLocation = path.resolve(fullPath, nangoConfigFile);
if (!fs.existsSync(configFileLocation)) {
if (debug) {
printDebug(`Creating the ${nangoConfigFile} file at ${configFileLocation}`);
}
fs.writeFileSync(configFileLocation, yamlData);
} else {
if (debug) {
printDebug(`Nango config file already exists at ${configFileLocation} so not creating a new one`);
}
}

const envFileLocation = path.resolve(fullPath, '.env');
if (!fs.existsSync(envFileLocation)) {
if (debug) {
printDebug(`Creating the .env file at ${envFileLocation}`);
}
fs.writeFileSync(
envFileLocation,
`# Authenticates the CLI (get the keys in the dashboard's Environment Settings).
#NANGO_SECRET_KEY_DEV=xxxx-xxx-xxxx
#NANGO_SECRET_KEY_PROD=xxxx-xxx-xxxx

# Nango's instance URL (OSS: change to http://localhost:3003 or your instance URL).
NANGO_HOSTPORT=https://api.nango.dev # Default value

# How to handle CLI upgrades ("prompt", "auto" or "ignore").
NANGO_CLI_UPGRADE_MODE=prompt # Default value

# Whether to prompt before deployments.
NANGO_DEPLOY_AUTO_CONFIRM=false # Default value`
);
} else {
if (debug) {
printDebug(`.env file already exists at ${envFileLocation} so not creating a new one`);
}
}

const gitIgnoreFileLocation = path.resolve(fullPath, '.gitignore');
if (!fs.existsSync(gitIgnoreFileLocation)) {
if (debug) {
printDebug(`Creating the .gitignore file at ${gitIgnoreFileLocation}`);
}
fs.writeFileSync(
gitIgnoreFileLocation,
`dist
.env
`
);
} else {
if (debug) {
printDebug(`.gitignore file already exists at ${gitIgnoreFileLocation} so not creating a new one`);
}
}

generate({ debug, fullPath });
}

export function tscWatch({ fullPath, debug = false }: { fullPath: string; debug?: boolean }) {
const tsconfig = fs.readFileSync(path.resolve(getNangoRootPath(), 'tsconfig.dev.json'), 'utf8');
const parsed = loadYamlAndGenerate({ fullPath, debug });
Expand Down
14 changes: 9 additions & 5 deletions packages/cli/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import figlet from 'figlet';
import path from 'path';
import * as dotenv from 'dotenv';

import { init, generate, tscWatch, configWatch, version } from './cli.js';
import { generate, tscWatch, configWatch, version } from './cli.js';
import deployService from './services/deploy.service.js';
import { compileAllFiles } from './services/compile.service.js';
import verificationService from './services/verification.service.js';
Expand All @@ -21,6 +21,7 @@ import { getNangoRootPath, upgradeAction, NANGO_INTEGRATIONS_LOCATION, printDebu
import type { DeployOptions } from './types.js';
import { parse } from './services/config.service.js';
import { nangoConfigFile } from '@nangohq/nango-yaml';
import { init } from './services/init.service.js';

class NangoCommand extends Command {
override createCommand(name: string) {
Expand Down Expand Up @@ -56,7 +57,7 @@ In addition for self-Hosting: set the NANGO_HOSTPORT env variable.

Global flag: --auto-confirm - automatically confirm yes to all prompts.

Available environment variables available:
Available environment variables:

# Recommendation: in a ".env" file in ./nango-integrations.

Expand Down Expand Up @@ -87,13 +88,16 @@ program

program
.command('init')
.argument('[path]', 'Optional: The path to initialize the Nango project in. Defaults to the current directory.')
.description('Initialize a new Nango project')
.action(function (this: Command) {
const { debug } = this.opts();
const fullPath = process.cwd();
init({ absolutePath: fullPath, debug });
const absolutePath = path.resolve(process.cwd(), this.args[0] || '');
const ok = init({ absolutePath, debug });

console.log(chalk.green(`Nango integrations initialized!`));
if (ok) {
console.log(chalk.green(`Nango integrations initialized!`));
}
});

program
Expand Down
81 changes: 81 additions & 0 deletions packages/cli/lib/services/__snapshots__/init.service.unit.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`init > should init the expected files in the nango-integrations directory 1`] = `
"// ---------------------------
// This file was generated by Nango (vTest)
// You can version this file
// ---------------------------

export interface GithubIssue {
id: number;
owner: string;
repo: string;
issue_number: number;
title: string;
author: string;
author_id: string;
state: string;
date_created: Date;
date_last_modified: Date;
};
"
`;

exports[`init > should init the expected files in the nango-integrations directory 2`] = `
"{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"GithubIssue": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"owner": {
"type": "string"
},
"repo": {
"type": "string"
},
"issue_number": {
"type": "number"
},
"title": {
"type": "string"
},
"author": {
"type": "string"
},
"author_id": {
"type": "string"
},
"state": {
"type": "string"
},
"date_created": {
"type": "string",
"format": "date-time"
},
"date_last_modified": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"owner",
"repo",
"issue_number",
"title",
"author",
"author_id",
"state",
"date_created",
"date_last_modified"
],
"additionalProperties": false
}
},
"$comment": "This file was generated by Nango (vTest)"
}"
`;
116 changes: 116 additions & 0 deletions packages/cli/lib/services/init.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import fs from 'node:fs';
import path, { dirname } from 'node:path';
import chalk from 'chalk';
import { printDebug } from '../utils.js';
import { nangoConfigFile } from '@nangohq/nango-yaml';
import { generate } from '../cli.js';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/**
* Init
* If we're not currently in the nango-integrations directory create one
* and create an example nango.yaml file
*/
export function init({ absolutePath, debug = false }: { absolutePath: string; debug?: boolean }): boolean {
const yamlData = fs.readFileSync(path.resolve(__dirname, `../templates/${nangoConfigFile}`), 'utf8');

const stat = fs.statSync(absolutePath, { throwIfNoEntry: false });
if (!stat) {
if (debug) {
printDebug(`Creating the nango integrations directory at ${absolutePath}`);
}
fs.mkdirSync(absolutePath);
} else if (!stat.isDirectory()) {
console.log(chalk.red(`The path provided is not a directory. Exiting.`));
return false;
}

const dotNangoPath = path.resolve(absolutePath, '.nango');
const dotNangoStat = fs.statSync(dotNangoPath, { throwIfNoEntry: false });

if (!dotNangoStat) {
if (debug) {
printDebug(`Creating the .nango directory at ${dotNangoPath}`);
}
fs.mkdirSync(dotNangoPath);
} else if (!dotNangoStat.isDirectory()) {
console.log(chalk.red(`.nango exists but is not a directory. Exiting.`));
return false;
} else {
console.log(chalk.red(`.nango directory already exists. Exiting.`));
return false;
}

const gitkeepPath = path.resolve(absolutePath, '.nango/.gitkeep');
if (!fs.existsSync(gitkeepPath)) {
if (debug) {
printDebug(`Creating the .gitkeep file at ${gitkeepPath}`);
}
fs.writeFileSync(gitkeepPath, '');
} else {
if (debug) {
printDebug(`.gitkeep file already exists at ${gitkeepPath} so not creating a new one`);
}
}

const yamlPath = path.resolve(absolutePath, nangoConfigFile);
if (!fs.existsSync(yamlPath)) {
if (debug) {
printDebug(`Creating the ${nangoConfigFile} file at ${yamlPath}`);
}
fs.writeFileSync(yamlPath, yamlData);
} else {
if (debug) {
printDebug(`Nango config file already exists at ${yamlPath} so not creating a new one`);
}
}

const envPath = path.resolve(absolutePath, '.env');
if (!fs.existsSync(envPath)) {
if (debug) {
printDebug(`Creating the .env file at ${envPath}`);
}
fs.writeFileSync(
envPath,
`# Authenticates the CLI (get the keys in the dashboard's Environment Settings).
#NANGO_SECRET_KEY_DEV=xxxx-xxx-xxxx
#NANGO_SECRET_KEY_PROD=xxxx-xxx-xxxx

# Nango's instance URL (OSS: change to http://localhost:3003 or your instance URL).
NANGO_HOSTPORT=https://api.nango.dev # Default value

# How to handle CLI upgrades ("prompt", "auto" or "ignore").
NANGO_CLI_UPGRADE_MODE=prompt # Default value

# Whether to prompt before deployments.
NANGO_DEPLOY_AUTO_CONFIRM=false # Default value`
);
} else {
if (debug) {
printDebug(`.env file already exists at ${envPath} so not creating a new one`);
}
}

const gitIgnorePath = path.resolve(absolutePath, '.gitignore');
if (!fs.existsSync(gitIgnorePath)) {
if (debug) {
printDebug(`Creating the .gitignore file at ${gitIgnorePath}`);
}
fs.writeFileSync(
gitIgnorePath,
`dist
.env
`
);
} else {
if (debug) {
printDebug(`.gitignore file already exists at ${gitIgnorePath} so not creating a new one`);
}
}

generate({ debug, fullPath: absolutePath });
return true;
}
Loading
Loading