Skip to content

Commit

Permalink
Merge pull request #51 from dotenv-org/dx
Browse files Browse the repository at this point in the history
Dx
  • Loading branch information
motdotla committed Jun 6, 2022
2 parents 8863a2a + a9d5ee4 commit 5261ae4
Show file tree
Hide file tree
Showing 11 changed files with 545 additions and 541 deletions.
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

0 comments on commit 5261ae4

Please sign in to comment.