Skip to content

Commit

Permalink
add org switching cli command
Browse files Browse the repository at this point in the history
  • Loading branch information
jairad26 committed Oct 4, 2024
1 parent fc81b48 commit a0765f4
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 86 deletions.
59 changes: 59 additions & 0 deletions src/commands/change-org/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Command} from '@oclif/core'
import chalk from "chalk";
import * as fs from 'node:fs'
import { createInterface } from "node:readline";

import { fileExists, getEnvFilePath, promptOrgSelection, readEnvFile, sendGraphQLRequest } from '../../util/index.js'


export default class ChangeOrg extends Command {
static override args = {
}

static override description = 'Change the current Hypermode organization'

static override examples = [
'<%= config.bin %> <%= command.id %>',
]

static override flags = {
}

public async run(): Promise<void> {
const envFilePath = getEnvFilePath();
if(!fileExists(envFilePath)) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.');
return;
}

const res = readEnvFile(envFilePath);

if (!res.email || !res.jwt || !res.orgId) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.');
return;
}

const rl = createInterface({
input: process.stdin,
output: process.stdout,
});

const orgs = await sendGraphQLRequest(res.jwt);
const selectedOrg = await promptOrgSelection(rl, orgs);

const updatedContent = res.content
.split('\n')
.map((line) => {
if (line.startsWith('HYP_ORG_ID')) {
return `HYP_ORG_ID=${selectedOrg.id}`;
}

return line;
})
.join('\n');

fs.writeFileSync(envFilePath, updatedContent.trim() + '\n', { flag: 'w' });

rl.close();
}
}
72 changes: 6 additions & 66 deletions src/commands/login/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import {Command} from '@oclif/core'
import chalk from "chalk";
import * as fs from 'node:fs'
import * as http from 'node:http'
import * as path from 'node:path'
import { createInterface } from "node:readline";
import fetch from 'node-fetch'
import open from 'open'

import { ask, clearLine } from '../../util/index.js'

type Org = {
id: string;
slug: string;
}
import { fileExists, getEnvDir, getEnvFilePath, promptOrgSelection, sendGraphQLRequest } from '../../util/index.js'

export default class LoginIndex extends Command {
static override args = {
Expand Down Expand Up @@ -56,8 +48,8 @@ export default class LoginIndex extends Command {
output: process.stdout,
});

const orgs = await this.sendGraphQLRequest(jwt);
const selectedOrg = await this.promptOrgSelection(rl, orgs);
const orgs = await sendGraphQLRequest(jwt);
const selectedOrg = await promptOrgSelection(rl, orgs);
// Store JWT and email securely
this.writeToEnvFile(jwt, email, selectedOrg.id);

Expand Down Expand Up @@ -95,66 +87,14 @@ export default class LoginIndex extends Command {

}

private async promptOrgSelection(rl: ReturnType<typeof createInterface>, orgs: Org[]): Promise<Org> {
this.log('Please select an organization:');
for (const [index, org] of orgs.entries()) {
this.log(chalk.dim(`${index + 1}. ${org.slug}`));
}

const selectedIndex = Number.parseInt(((await ask(chalk.dim("-> "), rl)) || "1").trim(), 10) - 1;

const org = orgs[selectedIndex];
clearLine();
clearLine();

if (!org) {
this.log(chalk.red('Invalid selection. Please try again.'));
return this.promptOrgSelection(rl, orgs);
}

this.log(`Selected organization: ${chalk.dim(org.slug)}`);

return org;
}



private async sendGraphQLRequest(jwt: string): Promise<Org[]> {
const url = 'https://api.hypermode-stage.com/graphql';
const query = `
query GetOrgs {
getOrgs {
id
slug
}
}`

const options = {
body: JSON.stringify({ query }),
headers: {
'Authorization': `${jwt}`,
'Content-Type': 'application/json'
},
method: 'POST'
};

const response = await fetch(url, options);


/* eslint-disable @typescript-eslint/no-explicit-any */
const data: any = await response.json();

const orgs: Org[] = data.data.getOrgs;

return orgs;
}

private async writeToEnvFile(jwt: string, email: string, orgId: string): Promise<void> {
const envDir = path.join(process.env.HOME || '', '.hypermode');
const envFilePath = path.join(envDir, '.env.local');
const envDir = getEnvDir();
const envFilePath = getEnvFilePath();

// Create the directory if it doesn't exist
if (!fs.existsSync(envDir)) {
if (!fileExists(envDir)) {
fs.mkdirSync(envDir, { recursive: true });
}

Expand Down
34 changes: 20 additions & 14 deletions src/commands/logout/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {Command} from '@oclif/core'
import chalk from "chalk";
import * as fs from 'node:fs'
import * as path from 'node:path'

import { fileExists, getEnvFilePath, readEnvFile } from '../../util/index.js'


export default class LogoutIndex extends Command {
Expand All @@ -18,30 +20,34 @@ export default class LogoutIndex extends Command {

public async run(): Promise<void> {

const envDir = path.join(process.env.HOME || '', '.hypermode');
const envFilePath = path.join(envDir, '.env.local');
const envFilePath = getEnvFilePath();

// Check if .env.local file exists
if (!fs.existsSync(envFilePath)) {
this.log('Not logged in.');
if (!fileExists(envFilePath)) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.');
return;
}

const existingContent = fs.readFileSync(envFilePath, 'utf8');

// Extract email from .env.local file
const emailMatch = existingContent.match(/HYP_EMAIL=(.*)/);
const email = emailMatch ? emailMatch[1] : null;
const res = readEnvFile(envFilePath);

if (!email) {
this.log('Not logged in.');
if (!res.email) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.');
return;
}

console.log('Logging out of email: ' + email);
console.log('Logging out of email: ' + chalk.dim(res.email));

// Remove JWT and email from .env.local file
const updatedContent = existingContent.replace(/HYP_JWT=(.*)\n/, '').replace(/HYP_EMAIL=(.*)\n/, '').replace(/HYP_ORG_ID=(.*)\n/,'');
const updatedContent = res.content
.split('\n')
.map((line) => {
if (line.startsWith('HYP_JWT') || line.startsWith('HYP_EMAIL') || line.startsWith('HYP_ORG_ID')) {
return '';
}

return line;
})
.join('\n');

fs.writeFileSync(envFilePath, updatedContent.trim() + '\n', { flag: 'w' });
}
Expand Down
95 changes: 89 additions & 6 deletions src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Interface } from "node:readline";
import chalk from "chalk";
import * as fs from 'node:fs'
import * as path from 'node:path'
import { Interface , createInterface } from "node:readline";
import fetch from 'node-fetch'


type Org = {
id: string;
slug: string;
}


export function ask(question: string, rl: Interface, placeholder?: string): Promise<string> {
Expand All @@ -9,9 +19,82 @@ export function ask(question: string, rl: Interface, placeholder?: string): Prom
});
}

export function clearLine(): void {
process.stdout.write(`\u001B[1A`);
process.stdout.write("\u001B[2K");
process.stdout.write("\u001B[0G");
export async function promptOrgSelection(rl: ReturnType<typeof createInterface>, orgs: Org[]): Promise<Org> {
console.log('Please select an organization:');
for (const [index, org] of orgs.entries()) {
console.log(chalk.dim(`${index + 1}. ${org.slug}`));
}

const selectedIndex = Number.parseInt(((await ask(chalk.dim("-> "), rl)) || "1").trim(), 10) - 1;

const org = orgs[selectedIndex];

if (!org) {
console.log(chalk.red('Invalid selection. Please try again.'));
return promptOrgSelection(rl, orgs);
}

console.log(`Selected organization: ${chalk.dim(org.slug)}`);

return org;
}



export async function sendGraphQLRequest(jwt: string): Promise<Org[]> {
const url = 'https://api.hypermode-stage.com/graphql';
const query = `
query GetOrgs {
getOrgs {
id
slug
}
}`

const options = {
body: JSON.stringify({ query }),
headers: {
'Authorization': `${jwt}`,
'Content-Type': 'application/json'
},
method: 'POST'
};

const response = await fetch(url, options);


/* eslint-disable @typescript-eslint/no-explicit-any */
const data: any = await response.json();

const orgs: Org[] = data.data.getOrgs;

return orgs;
}

export function getEnvDir(): string {
return path.join(process.env.HOME || '', '.hypermode');
}

export function getEnvFilePath(): string {
const envDir = getEnvDir();
return path.join(envDir, '.env.local');
}

export function fileExists(filePath: string): boolean {
return fs.existsSync(filePath);
}

export function readEnvFile(filePath: string): {content: string, email: null | string, jwt: null | string, orgId: null | string} {
const content = fs.readFileSync(filePath, 'utf8');

const jwtMatch = content.match(/HYP_JWT=(.*)/);
const jwt = jwtMatch ? jwtMatch[1] : null;

const emailMatch = content.match(/HYP_EMAIL=(.*)/);
const email = emailMatch ? emailMatch[1] : null;

const orgIdMatch = content.match(/HYP_ORG_ID=(.*)/);
const orgId = orgIdMatch ? orgIdMatch[1] : null;

return {content, email, jwt, orgId};
}

14 changes: 14 additions & 0 deletions test/commands/change-org/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'

describe('change-org:index', () => {
it('runs change-org:index cmd', async () => {
const {stdout} = await runCommand('change-org:index')
expect(stdout).to.contain('hello world')
})

it('runs change-org:index --name oclif', async () => {
const {stdout} = await runCommand('change-org:index --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

0 comments on commit a0765f4

Please sign in to comment.