Skip to content

Commit 15affd3

Browse files
authored
Jai/hyp 2574 fix async functions to use async fs (#36)
* improve async functions and writing installation to settings
1 parent 9689d19 commit 15affd3

File tree

6 files changed

+109
-77
lines changed

6 files changed

+109
-77
lines changed

src/commands/link/index.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Command} from '@oclif/core'
22
import chalk from 'chalk'
3-
import * as fs from 'node:fs'
3+
import * as fs from "../../util/fs.js";
44
import * as http from 'node:http'
55
import {URL} from 'node:url'
66
import open from 'open'
@@ -10,8 +10,9 @@ import {
1010
getProjectsByOrgReq, sendCreateProjectRepoReq, sendCreateProjectReq, sendGetRepoIdReq,
1111
} from '../../util/graphql.js'
1212
import {
13-
confirmExistingProjectLink, confirmOverwriteCiHypFile, fileExists, getCiHypFilePath, getEnvFilePath, getGitConfigFilePath,
13+
confirmExistingProjectLink, confirmOverwriteCiHypFile, fileExists, getCiHypFilePath, getSettingsFilePath, getGitConfigFilePath,
1414
getGitRemoteUrl, getGithubWorkflowDir, promptProjectLinkSelection, promptProjectName, readSettingsJson,
15+
writeGithubInstallationIdToSettingsFile,
1516
} from '../../util/index.js'
1617

1718
export default class LinkIndex extends Command {
@@ -103,22 +104,22 @@ export default class LinkIndex extends Command {
103104
// 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
104105
const gitConfigFilePath = getGitConfigFilePath()
105106

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

110-
const gitUrl = getGitRemoteUrl(gitConfigFilePath)
111+
const gitUrl = await getGitRemoteUrl(gitConfigFilePath)
111112

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

115-
const envFilePath = getEnvFilePath()
116-
if (!fileExists(envFilePath)) {
116+
const settingsFilePath = getSettingsFilePath()
117+
if (!await fileExists(settingsFilePath)) {
117118
this.log(chalk.red('Not logged in.') + ' Log in with `hyp login`.')
118119
return
119120
}
120121

121-
const settings = readSettingsJson(envFilePath)
122+
const settings = await readSettingsJson(settingsFilePath)
122123

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

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

132-
const installationId = (!settings.installationIds || !settings.installationIds[gitOwner]) ? await this.getUserInstallationThroughAuthFlow() : settings.installationIds[gitOwner]
133+
let installationId = null
134+
135+
if (!settings.installationIds || !settings.installationIds[gitOwner]) {
136+
installationId = await this.getUserInstallationThroughAuthFlow()
137+
await writeGithubInstallationIdToSettingsFile(gitOwner, installationId)
138+
} else {
139+
installationId = settings.installationIds[gitOwner]
140+
}
133141

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

174-
if (!fileExists(githubWorkflowDir)) {
182+
if (!await fileExists(githubWorkflowDir)) {
175183
// create the directory
176-
fs.mkdirSync(githubWorkflowDir, {recursive: true})
184+
await fs.mkdir(githubWorkflowDir, {recursive: true})
177185
}
178186

179187
let shouldCreateCIFile = true
180-
if (fileExists(ciHypFilePath)) {
188+
if (await fileExists(ciHypFilePath)) {
181189
// prompt if they want to replace it
182190
const confirmOverwrite = await confirmOverwriteCiHypFile()
183191
if (!confirmOverwrite) {
@@ -187,7 +195,7 @@ export default class LinkIndex extends Command {
187195
}
188196

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

src/commands/login/index.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import {Command} from '@oclif/core'
22
import chalk from 'chalk'
3-
import * as fs from 'node:fs'
43
import * as http from 'node:http'
54
import {URL} from 'node:url'
65
import open from 'open'
76

87
import {sendGetOrgsReq} from '../../util/graphql.js'
98
import {
10-
fileExists, getEnvDir, getEnvFilePath, promptOrgSelection, readSettingsJson,
9+
writeToSettingsFile, promptOrgSelection,
1110
} from '../../util/index.js'
1211

1312
const loginHTML = `<!-- src/commands/login/login.html -->
@@ -107,7 +106,7 @@ export default class LoginIndex extends Command {
107106
try {
108107
const orgs = await sendGetOrgsReq(jwt)
109108
const selectedOrg = await promptOrgSelection(orgs)
110-
this.writeToEnvFile(jwt, email, selectedOrg.id)
109+
await writeToSettingsFile(jwt, email, selectedOrg.id)
111110
this.log('Successfully logged in as ' + chalk.dim(email) + '! 🎉')
112111
resolve()
113112
} catch (error) {
@@ -152,29 +151,4 @@ export default class LoginIndex extends Command {
152151
})
153152
})
154153
}
155-
156-
private async writeToEnvFile(jwt: string, email: string, orgId: string): Promise<void> {
157-
const envDir = getEnvDir()
158-
const envFilePath = getEnvFilePath()
159-
160-
// Create the directory if it doesn't exist
161-
if (!fileExists(envDir)) {
162-
fs.mkdirSync(envDir, {recursive: true})
163-
}
164-
165-
const newEnvContent: { HYP_EMAIL: string; HYP_JWT: string; HYP_ORG_ID: string; INSTALLATION_IDS: { [key: string]: string } | null } = {
166-
HYP_EMAIL: email,
167-
HYP_JWT: jwt,
168-
HYP_ORG_ID: orgId,
169-
INSTALLATION_IDS: null,
170-
};
171-
172-
if (fileExists(envFilePath)) {
173-
const settings = readSettingsJson(envFilePath)
174-
newEnvContent.INSTALLATION_IDS = settings.installationIds
175-
}
176-
177-
// Write the new content to the file, replacing any existing content
178-
fs.writeFileSync(envFilePath, JSON.stringify(newEnvContent, null, 2), {flag: 'w'})
179-
}
180154
}

src/commands/logout/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {Command} from '@oclif/core'
22
import chalk from 'chalk'
3-
import * as fs from 'node:fs'
3+
import * as fs from "../../util/fs.js";
44

5-
import {fileExists, getEnvFilePath, readSettingsJson} from '../../util/index.js'
5+
import {fileExists, getSettingsFilePath, readSettingsJson} from '../../util/index.js'
66

77
export default class LogoutIndex extends Command {
88
static override args = {}
@@ -14,15 +14,15 @@ export default class LogoutIndex extends Command {
1414
static override flags = {}
1515

1616
public async run(): Promise<void> {
17-
const envFilePath = getEnvFilePath()
17+
const settingsFilePath = getSettingsFilePath()
1818

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

25-
const res = readSettingsJson(envFilePath)
25+
const res = await readSettingsJson(settingsFilePath)
2626

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

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

34-
const newEnvContent = {
34+
const newSettingsContent = {
3535
HYP_EMAIL: null,
3636
HYP_JWT: null,
3737
HYP_ORG_ID: null,
3838
INSTALLATION_IDS: res.installationIds,
3939
}
4040

4141
// remove all content from settings.json
42-
fs.writeFileSync(envFilePath, JSON.stringify(newEnvContent, null, 2), {flag: 'w'})
42+
await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
4343
}
4444
}

src/commands/org/switch.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {Command} from '@oclif/core'
22
import chalk from 'chalk'
3-
import * as fs from 'node:fs'
43

54
import {sendGetOrgsReq} from '../../util/graphql.js'
65
import {
7-
fileExists, getEnvFilePath, promptOrgSelection, readSettingsJson,
6+
fileExists, getSettingsFilePath, promptOrgSelection, readSettingsJson,
7+
writeToSettingsFile,
88
} from '../../util/index.js'
99

1010
export default class OrgSwitch extends Command {
@@ -17,13 +17,13 @@ export default class OrgSwitch extends Command {
1717
static override flags = {}
1818

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

26-
const res = readSettingsJson(envFilePath)
26+
const res = await readSettingsJson(settingsFilePath)
2727

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

36-
const updatedContent = {
37-
HYP_EMAIL: res.email,
38-
HYP_JWT: res.jwt,
39-
HYP_ORG_ID: selectedOrg.id,
40-
}
41-
42-
fs.writeFileSync(envFilePath, JSON.stringify(updatedContent, null, 2), {flag: 'w'})
36+
await writeToSettingsFile(res.jwt, res.email, selectedOrg.id)
4337
}
4438
}

src/util/fs.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2024 Hypermode Inc.
3+
* Licensed under the terms of the Apache License, Version 2.0
4+
* See the LICENSE file that accompanied this code for further details.
5+
*
6+
* SPDX-FileCopyrightText: 2024 Hypermode Inc. <[email protected]>
7+
* SPDX-License-Identifier: Apache-2.0
8+
*/
9+
10+
import fs from "node:fs";
11+
export * from "node:fs/promises";
12+
13+
export async function exists(path: string) {
14+
try {
15+
await fs.promises.stat(path);
16+
return true;
17+
} catch {
18+
return false;
19+
}
20+
}

src/util/index.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {ExitPromptError} from '@inquirer/core'
22
import * as inquirer from '@inquirer/prompts'
33
import chalk from 'chalk'
4+
import * as fs from "../util/fs.js";
45
import slugify from "@sindresorhus/slugify"
5-
import * as fs from 'node:fs'
66
import * as path from 'node:path'
77
import {Interface} from 'node:readline'
88

@@ -88,24 +88,24 @@ export async function confirmOverwriteCiHypFile(): Promise<boolean> {
8888
})
8989
}
9090

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

98-
export function getEnvDir(): string {
98+
export function getSettingsDir(): string {
9999
return path.join(process.env.HOME || '', '.hypermode')
100100
}
101101

102-
export function getEnvFilePath(): string {
103-
const envDir = getEnvDir()
104-
return path.join(envDir, 'settings.json')
102+
export function getSettingsFilePath(): string {
103+
const settingsDir = getSettingsDir()
104+
return path.join(settingsDir, 'settings.json')
105105
}
106106

107-
export function fileExists(filePath: string): boolean {
108-
return fs.existsSync(filePath)
107+
export async function fileExists(filePath: string): Promise<boolean> {
108+
return fs.exists(filePath)
109109
}
110110

111111
export function getGitDir(): string {
@@ -124,8 +124,8 @@ export function getGitConfigFilePath(): string {
124124
return path.join(getGitDir(), 'config')
125125
}
126126

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

137-
export function readSettingsJson(filePath: string): {
138-
content: string
139-
email: null | string
140-
installationIds: { [key: string]: string } | null
141-
jwt: null | string
142-
orgId: null | string
143-
} {
144-
const content = fs.readFileSync(filePath, 'utf8')
137+
export async function readSettingsJson(filePath: string): Promise<{ content: string; email: null | string; installationIds: { [key: string]: string; } | null; jwt: null | string; orgId: null | string; }> {
138+
const content = await fs.readFile(filePath, 'utf8')
145139

146140
let email: null | string = null
147141
let jwt: null | string = null
@@ -162,3 +156,45 @@ export function readSettingsJson(filePath: string): {
162156
content, email, installationIds, jwt, orgId,
163157
}
164158
}
159+
160+
export async function writeToSettingsFile(jwt: string, email: string, orgId: string): Promise<void> {
161+
const settingsDir = getSettingsDir()
162+
const settingsFilePath = getSettingsFilePath()
163+
164+
// Create the directory if it doesn't exist
165+
if (!await fileExists(settingsDir)) {
166+
await fs.mkdir(settingsDir, {recursive: true})
167+
}
168+
169+
const newSettingsContent: { HYP_EMAIL: string; HYP_JWT: string; HYP_ORG_ID: string; INSTALLATION_IDS: { [key: string]: string } | null } = {
170+
HYP_EMAIL: email,
171+
HYP_JWT: jwt,
172+
HYP_ORG_ID: orgId,
173+
INSTALLATION_IDS: null,
174+
};
175+
176+
if (await fileExists(settingsFilePath)) {
177+
const settings = await readSettingsJson(settingsFilePath)
178+
newSettingsContent.INSTALLATION_IDS = settings.installationIds
179+
}
180+
181+
// Write the new content to the file, replacing any existing content
182+
await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
183+
}
184+
185+
export async function writeGithubInstallationIdToSettingsFile(gitOwner: string, installationId: string): Promise<void> {
186+
const settingsFilePath = getSettingsFilePath()
187+
const settings = await readSettingsJson(settingsFilePath)
188+
189+
settings.installationIds = settings.installationIds || {}
190+
settings.installationIds[gitOwner] = installationId
191+
192+
const newSettingsContent = {
193+
HYP_EMAIL: settings.email,
194+
HYP_JWT: settings.jwt,
195+
HYP_ORG_ID: settings.orgId,
196+
INSTALLATION_IDS: settings.installationIds,
197+
}
198+
199+
await fs.writeFile(settingsFilePath, JSON.stringify(newSettingsContent, null, 2), {flag: 'w'})
200+
}

0 commit comments

Comments
 (0)