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

Dx #51

Merged
merged 9 commits into from
Jun 6, 2022
Merged

Dx #51

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
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [Unreleased](https://github.com/dotenv-org/dotenv-vault/compare/v1.2.3...master)
## [Unreleased](https://github.com/dotenv-org/dotenv-vault/compare/v1.3.0...master)

## 1.3.0

### Added

- Add terminal colors 🎨
- Protect developer from accidently overwriting .env.project file 🔐
- Add `login` command 🎉
- Update `push` and `pull` commands to be less verbose 🧹

## 1.2.3

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
],
"dependencies": {
"@oclif/core": "^1",
"@oclif/errors": "^1.3.5",
"@oclif/plugin-help": "^5.1.12",
"@oclif/plugin-not-found": "^2.3.1",
"@oclif/plugin-update": "^3.0.0",
"@oclif/plugin-warn-if-update-available": "^2.0.4",
"axios": "^0.27.2",
"chalk": "^4.1.2",
"dotenv": "^16.0.1"
},
"devDependencies": {
Expand Down
15 changes: 15 additions & 0 deletions src/commands/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Command} from '@oclif/core'

import {LoginService} from '../services/login-service'

export default class Login extends Command {
static description = 'Login to Dotenv Vault'

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

public async run(): Promise<void> {
new LoginService({cmd: this}).run()
}
}
91 changes: 91 additions & 0 deletions src/services/abort-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import chalk from 'chalk'
import {vars} from '../vars'
import {LogService} from '../services/log-service'

interface AbortServiceAttrs {
cmd;
}

class AbortService {
public cmd;
public log;

constructor(attrs: AbortServiceAttrs = {} as AbortServiceAttrs) {
this.cmd = attrs.cmd
this.log = new LogService({cmd: attrs.cmd})
}

missingEnvVault(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Missing ${vars.vaultFilename} (${vars.vaultKey}).`, {
code: 'MISSING_DOTENV_VAULT',
ref: '',
suggestions: [`Missing ${vars.vaultFilename} (${vars.vaultKey}). To create it, run: npx dotenv-vault@latest new`],
})
}

emptyEnvVault(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Empty ${vars.vaultFilename} (${vars.vaultKey}).`, {
code: 'EMPTY_DOTENV_VAULT',
ref: '',
suggestions: [`Empty ${vars.vaultFilename} (${vars.vaultKey}). To fix it, run: npx dotenv-vault@latest new`],
})
}

invalidEnvVault(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Invalid ${vars.vaultFilename} (${vars.vaultKey}).`, {
code: 'INVALID_DOTENV_VAULT',
ref: '',
suggestions: [`Invalid ${vars.vaultFilename} (${vars.vaultKey}). To fix it, run: npx dotenv-vault@latest new`],
})
}

existingEnvVault(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Existing ${vars.vaultFilename} (${vars.vaultKey}).`, {
code: 'EXISTING_DOTENV_VAULT',
ref: '',
suggestions: [`Existing ${vars.vaultFilename} (${vars.vaultKey}). To fix it, delete ${vars.vaultFilename} and then run: npx dotenv-vault@latest new`],
})
}

missingEnvMe(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error('Missing .env.me (DOTENV_ME).', {
code: 'MISSING_DOTENV_ME',
ref: '',
suggestions: ['Missing .env.me (DOTENV_ME). To create it, run: npx dotenv-vault@latest login'],
})
}

emptyEnvMe(): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error('Empty .env.me (DOTENV_ME).', {
code: 'EMPTY_DOTENV_ME',
ref: '',
suggestions: ['Empty .env.me (DOTENV_ME). To create it, run: npx dotenv-vault@latest login'],
})
}

missingEnv(filename: string | any = '.env'): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Missing ${filename}.`, {
code: 'MISSING_ENV_FILE',
ref: '',
suggestions: [`Missing ${filename}. Create it (touch ${filename}) and then try again. Or run, npx dotenv-vault@latest pull`],
})
}

emptyEnv(filename: string | any = '.env'): void {
this.log.plain(`${chalk.red('x')} Aborted.`)
this.cmd.error(`Empty ${filename}.`, {
code: 'EMPTY_ENV_FILE',
ref: '',
suggestions: [`Empty ${filename}. Populate it with values and then try again. Or run, npx dotenv-vault@latest pull`],
})
}
}

export {AbortService}
47 changes: 47 additions & 0 deletions src/services/log-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import chalk from 'chalk'

interface LogServiceAttrs {
cmd;
}

class LogService {
public cmd;

constructor(attrs: LogServiceAttrs = {} as LogServiceAttrs) {
this.cmd = attrs.cmd
}

get pretextLocal(): string {
return 'local: '
}

get pretextRemote(): string {
return 'remote: '
}

plain(msg: string): void {
if (msg === undefined) {
msg = ''
}

this.cmd.log(msg)
}

local(msg: string): void {
if (msg === undefined) {
msg = ''
}

this.cmd.log(`${chalk.dim(this.pretextLocal)}${msg}`)
}

remote(msg: string): void {
if (msg === undefined) {
msg = ''
}

this.cmd.log(`${chalk.dim(this.pretextRemote)}${msg}`)
}
}

export {LogService}
104 changes: 104 additions & 0 deletions src/services/login-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as crypto from 'node:crypto'
import chalk from 'chalk'
import axios, {AxiosRequestConfig} from 'axios'
import {existsSync, writeFileSync} from 'node:fs'
import {vars} from '../vars'
import {CliUx} from '@oclif/core'
import {AppendToDockerignoreService} from '../services/append-to-dockerignore-service'
import {AppendToGitignoreService} from '../services/append-to-gitignore-service'
import {AppendToNpmignoreService} from '../services/append-to-npmignore-service'
import {LogService} from '../services/log-service'
import {AbortService} from '../services/abort-service'

interface LoginServiceAttrs {
cmd;
}

class LoginService {
public cmd;
public log;
public requestUid;
public controller;
public abort;

constructor(attrs: LoginServiceAttrs = {} as LoginServiceAttrs) {
this.cmd = attrs.cmd
this.log = new LogService({cmd: attrs.cmd})
this.abort = new AbortService({cmd: attrs.cmd})

const rand = crypto.randomBytes(32).toString('hex')
this.requestUid = `req_${rand}`
}

async run(): Promise<void> {
new AppendToDockerignoreService().run()
new AppendToGitignoreService().run()
new AppendToNpmignoreService().run()

if (vars.missingEnvVault) {
this.abort.missingEnvVault()
}

if (vars.emptyEnvVault) {
this.abort.emptyEnvVault()
}

CliUx.ux.open(this.loginUrl)
CliUx.ux.action.start(`${chalk.dim(this.log.pretextLocal)}Waiting for login`)
this.check()
}

async check(): Promise<void> {
if (this.controller) {
this.controller.abort()
}

this.controller = new AbortController()

const options: AxiosRequestConfig = {
method: 'POST',
headers: {'content-type': 'application/json'},
data: {
vaultUid: vars.vaultValue,
requestUid: this.requestUid,
},
url: this.checkUrl,
signal: this.controller.signal,
}

let resp
try {
resp = await axios(options)
} catch (error: any) {
resp = error.response
} finally {
if (resp.status < 300) {
// Step 3
CliUx.ux.action.stop()
const meUid = resp.data.data.meUid
writeFileSync('.env.me', `DOTENV_ME=${meUid}`)
this.log.local(`Logged in as .env.me (DOTENV_ME=${meUid.slice(0, 9)}...)`)
this.log.plain('')
this.log.plain(`Next run ${chalk.bold('npx dotenv-vault@latest pull')} or ${chalk.bold('npx dotenv-vault@latest push')}`)
} else {
// 404 - keep trying
await CliUx.ux.wait(2000) // check every 2 seconds
this.check() // check again
}
}
}

get loginUrl(): string {
return `${vars.apiUrl}/login?vaultUid=${vars.vaultValue}&requestUid=${this.requestUid}`
}

get checkUrl(): string {
return `${vars.apiUrl}/check?vaultUid=${vars.vaultValue}&requestUid=${this.requestUid}`
}

get existingEnvVault(): boolean {
return existsSync(vars.vaultFilename)
}
}

export {LoginService}
Loading