Skip to content

Commit

Permalink
Jai/hyp 2574 fix async functions to use async fs (#36)
Browse files Browse the repository at this point in the history
* improve async functions and writing installation to settings
  • Loading branch information
jairad26 authored Nov 5, 2024
1 parent 9689d19 commit 15affd3
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 77 deletions.
32 changes: 20 additions & 12 deletions src/commands/link/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Command} from '@oclif/core'
import chalk from 'chalk'
import * as fs from 'node:fs'
import * as fs from "../../util/fs.js";
import * as http from 'node:http'
import {URL} from 'node:url'
import open from 'open'
Expand All @@ -10,8 +10,9 @@ import {
getProjectsByOrgReq, sendCreateProjectRepoReq, sendCreateProjectReq, sendGetRepoIdReq,
} from '../../util/graphql.js'
import {
confirmExistingProjectLink, confirmOverwriteCiHypFile, fileExists, getCiHypFilePath, getEnvFilePath, getGitConfigFilePath,
confirmExistingProjectLink, confirmOverwriteCiHypFile, fileExists, getCiHypFilePath, getSettingsFilePath, getGitConfigFilePath,
getGitRemoteUrl, getGithubWorkflowDir, promptProjectLinkSelection, promptProjectName, readSettingsJson,
writeGithubInstallationIdToSettingsFile,
} from '../../util/index.js'

export default class LinkIndex extends Command {
Expand Down Expand Up @@ -103,22 +104,22 @@ export default class LinkIndex extends Command {
// check if the directory has a .git/config with a remote named 'origin', if not, throw an error and ask them to set that up
const gitConfigFilePath = getGitConfigFilePath()

if (!fileExists(gitConfigFilePath)) {
if (!await fileExists(gitConfigFilePath)) {
throw new Error(chalk.red('No .git found in this directory. Please initialize a git repository with `git init`.'))
}

const gitUrl = getGitRemoteUrl(gitConfigFilePath)
const gitUrl = await getGitRemoteUrl(gitConfigFilePath)

// check the .hypermode/settings.json and see if there is a installationId with a key for the github owner. if there is,
// continue, if not send them to github app installation page, and then go to callback server, and add installation id to settings.json

const envFilePath = getEnvFilePath()
if (!fileExists(envFilePath)) {
const settingsFilePath = getSettingsFilePath()
if (!await fileExists(settingsFilePath)) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.')
return
}

const settings = readSettingsJson(envFilePath)
const settings = await readSettingsJson(settingsFilePath)

if (!settings.email || !settings.jwt || !settings.orgId) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.')
Expand All @@ -129,7 +130,14 @@ export default class LinkIndex extends Command {

const repoName = gitUrl.split('/')[4].replace(/\.git$/, '')

const installationId = (!settings.installationIds || !settings.installationIds[gitOwner]) ? await this.getUserInstallationThroughAuthFlow() : settings.installationIds[gitOwner]
let installationId = null

if (!settings.installationIds || !settings.installationIds[gitOwner]) {
installationId = await this.getUserInstallationThroughAuthFlow()
await writeGithubInstallationIdToSettingsFile(gitOwner, installationId)
} else {
installationId = settings.installationIds[gitOwner]
}

// call hypermode getRepoId with the installationId and the git url, if it returns a repoId, continue, if not, throw an error
const repoId = await sendGetRepoIdReq(settings.jwt, installationId, gitUrl)
Expand Down Expand Up @@ -171,13 +179,13 @@ export default class LinkIndex extends Command {
const githubWorkflowDir = getGithubWorkflowDir()
const ciHypFilePath = getCiHypFilePath()

if (!fileExists(githubWorkflowDir)) {
if (!await fileExists(githubWorkflowDir)) {
// create the directory
fs.mkdirSync(githubWorkflowDir, {recursive: true})
await fs.mkdir(githubWorkflowDir, {recursive: true})
}

let shouldCreateCIFile = true
if (fileExists(ciHypFilePath)) {
if (await fileExists(ciHypFilePath)) {
// prompt if they want to replace it
const confirmOverwrite = await confirmOverwriteCiHypFile()
if (!confirmOverwrite) {
Expand All @@ -187,7 +195,7 @@ export default class LinkIndex extends Command {
}

if (shouldCreateCIFile) {
fs.writeFileSync(ciHypFilePath, ciStr)
await fs.writeFile(ciHypFilePath, ciStr, {flag: 'w'})
this.log(chalk.green('Successfully created ci-hyp.yml! 🎉'))
}

Expand Down
30 changes: 2 additions & 28 deletions src/commands/login/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {Command} from '@oclif/core'
import chalk from 'chalk'
import * as fs from 'node:fs'
import * as http from 'node:http'
import {URL} from 'node:url'
import open from 'open'

import {sendGetOrgsReq} from '../../util/graphql.js'
import {
fileExists, getEnvDir, getEnvFilePath, promptOrgSelection, readSettingsJson,
writeToSettingsFile, promptOrgSelection,
} from '../../util/index.js'

const loginHTML = `<!-- src/commands/login/login.html -->
Expand Down Expand Up @@ -107,7 +106,7 @@ export default class LoginIndex extends Command {
try {
const orgs = await sendGetOrgsReq(jwt)
const selectedOrg = await promptOrgSelection(orgs)
this.writeToEnvFile(jwt, email, selectedOrg.id)
await writeToSettingsFile(jwt, email, selectedOrg.id)
this.log('Successfully logged in as ' + chalk.dim(email) + '! 🎉')
resolve()
} catch (error) {
Expand Down Expand Up @@ -152,29 +151,4 @@ export default class LoginIndex extends Command {
})
})
}

private async writeToEnvFile(jwt: string, email: string, orgId: string): Promise<void> {
const envDir = getEnvDir()
const envFilePath = getEnvFilePath()

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

const newEnvContent: { HYP_EMAIL: string; HYP_JWT: string; HYP_ORG_ID: string; INSTALLATION_IDS: { [key: string]: string } | null } = {
HYP_EMAIL: email,
HYP_JWT: jwt,
HYP_ORG_ID: orgId,
INSTALLATION_IDS: null,
};

if (fileExists(envFilePath)) {
const settings = readSettingsJson(envFilePath)
newEnvContent.INSTALLATION_IDS = settings.installationIds
}

// Write the new content to the file, replacing any existing content
fs.writeFileSync(envFilePath, JSON.stringify(newEnvContent, null, 2), {flag: 'w'})
}
}
14 changes: 7 additions & 7 deletions src/commands/logout/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Command} from '@oclif/core'
import chalk from 'chalk'
import * as fs from 'node:fs'
import * as fs from "../../util/fs.js";

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

export default class LogoutIndex extends Command {
static override args = {}
Expand All @@ -14,15 +14,15 @@ export default class LogoutIndex extends Command {
static override flags = {}

public async run(): Promise<void> {
const envFilePath = getEnvFilePath()
const settingsFilePath = getSettingsFilePath()

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

const res = readSettingsJson(envFilePath)
const res = await readSettingsJson(settingsFilePath)

if (!res.email) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.')
Expand All @@ -31,14 +31,14 @@ export default class LogoutIndex extends Command {

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

const newEnvContent = {
const newSettingsContent = {
HYP_EMAIL: null,
HYP_JWT: null,
HYP_ORG_ID: null,
INSTALLATION_IDS: res.installationIds,
}

// remove all content from settings.json
fs.writeFileSync(envFilePath, JSON.stringify(newEnvContent, null, 2), {flag: 'w'})
await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
}
}
18 changes: 6 additions & 12 deletions src/commands/org/switch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Command} from '@oclif/core'
import chalk from 'chalk'
import * as fs from 'node:fs'

import {sendGetOrgsReq} from '../../util/graphql.js'
import {
fileExists, getEnvFilePath, promptOrgSelection, readSettingsJson,
fileExists, getSettingsFilePath, promptOrgSelection, readSettingsJson,
writeToSettingsFile,
} from '../../util/index.js'

export default class OrgSwitch extends Command {
Expand All @@ -17,13 +17,13 @@ export default class OrgSwitch extends Command {
static override flags = {}

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

const res = readSettingsJson(envFilePath)
const res = await readSettingsJson(settingsFilePath)

if (!res.email || !res.jwt || !res.orgId) {
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.')
Expand All @@ -33,12 +33,6 @@ export default class OrgSwitch extends Command {
const orgs = await sendGetOrgsReq(res.jwt)
const selectedOrg = await promptOrgSelection(orgs)

const updatedContent = {
HYP_EMAIL: res.email,
HYP_JWT: res.jwt,
HYP_ORG_ID: selectedOrg.id,
}

fs.writeFileSync(envFilePath, JSON.stringify(updatedContent, null, 2), {flag: 'w'})
await writeToSettingsFile(res.jwt, res.email, selectedOrg.id)
}
}
20 changes: 20 additions & 0 deletions src/util/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 Hypermode Inc.
* Licensed under the terms of the Apache License, Version 2.0
* See the LICENSE file that accompanied this code for further details.
*
* SPDX-FileCopyrightText: 2024 Hypermode Inc. <[email protected]>
* SPDX-License-Identifier: Apache-2.0
*/

import fs from "node:fs";
export * from "node:fs/promises";

export async function exists(path: string) {
try {
await fs.promises.stat(path);
return true;
} catch {
return false;
}
}
72 changes: 54 additions & 18 deletions src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {ExitPromptError} from '@inquirer/core'
import * as inquirer from '@inquirer/prompts'
import chalk from 'chalk'
import * as fs from "../util/fs.js";
import slugify from "@sindresorhus/slugify"
import * as fs from 'node:fs'
import * as path from 'node:path'
import {Interface} from 'node:readline'

Expand Down Expand Up @@ -88,24 +88,24 @@ export async function confirmOverwriteCiHypFile(): Promise<boolean> {
})
}

export function confirmExistingProjectLink(): Promise<boolean> {
export async function confirmExistingProjectLink(): Promise<boolean> {
return inquirer.confirm({
default: true,
message: 'You have existing projects with no linked repositories. Would you like to select from these projects?',
})
}

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

export function getEnvFilePath(): string {
const envDir = getEnvDir()
return path.join(envDir, 'settings.json')
export function getSettingsFilePath(): string {
const settingsDir = getSettingsDir()
return path.join(settingsDir, 'settings.json')
}

export function fileExists(filePath: string): boolean {
return fs.existsSync(filePath)
export async function fileExists(filePath: string): Promise<boolean> {
return fs.exists(filePath)
}

export function getGitDir(): string {
Expand All @@ -124,8 +124,8 @@ export function getGitConfigFilePath(): string {
return path.join(getGitDir(), 'config')
}

export function getGitRemoteUrl(filePath: string): string {
const content = fs.readFileSync(filePath, 'utf8')
export async function getGitRemoteUrl(filePath: string): Promise<string> {
const content = await fs.readFile(filePath, 'utf8')
const remoteMatch = content.match(/\[remote "origin"]\n\s+url = (.*)/)
if (!remoteMatch) {
throw new Error(chalk.red('No remote origin found in .git/config, please set up a remote origin with `git remote add origin <url>`.'))
Expand All @@ -134,14 +134,8 @@ export function getGitRemoteUrl(filePath: string): string {
return remoteMatch[1]
}

export function readSettingsJson(filePath: string): {
content: string
email: null | string
installationIds: { [key: string]: string } | null
jwt: null | string
orgId: null | string
} {
const content = fs.readFileSync(filePath, 'utf8')
export async function readSettingsJson(filePath: string): Promise<{ content: string; email: null | string; installationIds: { [key: string]: string; } | null; jwt: null | string; orgId: null | string; }> {
const content = await fs.readFile(filePath, 'utf8')

let email: null | string = null
let jwt: null | string = null
Expand All @@ -162,3 +156,45 @@ export function readSettingsJson(filePath: string): {
content, email, installationIds, jwt, orgId,
}
}

export async function writeToSettingsFile(jwt: string, email: string, orgId: string): Promise<void> {
const settingsDir = getSettingsDir()
const settingsFilePath = getSettingsFilePath()

// Create the directory if it doesn't exist
if (!await fileExists(settingsDir)) {
await fs.mkdir(settingsDir, {recursive: true})
}

const newSettingsContent: { HYP_EMAIL: string; HYP_JWT: string; HYP_ORG_ID: string; INSTALLATION_IDS: { [key: string]: string } | null } = {
HYP_EMAIL: email,
HYP_JWT: jwt,
HYP_ORG_ID: orgId,
INSTALLATION_IDS: null,
};

if (await fileExists(settingsFilePath)) {
const settings = await readSettingsJson(settingsFilePath)
newSettingsContent.INSTALLATION_IDS = settings.installationIds
}

// Write the new content to the file, replacing any existing content
await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
}

export async function writeGithubInstallationIdToSettingsFile(gitOwner: string, installationId: string): Promise<void> {
const settingsFilePath = getSettingsFilePath()
const settings = await readSettingsJson(settingsFilePath)

settings.installationIds = settings.installationIds || {}
settings.installationIds[gitOwner] = installationId

const newSettingsContent = {
HYP_EMAIL: settings.email,
HYP_JWT: settings.jwt,
HYP_ORG_ID: settings.orgId,
INSTALLATION_IDS: settings.installationIds,
}

await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
}

0 comments on commit 15affd3

Please sign in to comment.