@dotenvx/dotenvx 1.71.3 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/README.md +3 -3
- package/package.json +8 -4
- package/src/cli/actions/armor/down.js +37 -0
- package/src/cli/actions/armor/move.js +42 -0
- package/src/cli/actions/armor/pull.js +37 -0
- package/src/cli/actions/armor/push.js +37 -0
- package/src/cli/actions/armor/up.js +37 -0
- package/src/cli/actions/decrypt.js +1 -1
- package/src/cli/actions/encrypt.js +1 -1
- package/src/cli/actions/login.js +63 -0
- package/src/cli/actions/logout.js +36 -0
- package/src/cli/actions/normalizeArmorOptions.js +1 -2
- package/src/cli/actions/set.js +1 -1
- package/src/cli/commands/armor.js +72 -0
- package/src/cli/dotenvx.js +17 -19
- package/src/db/device.js +73 -0
- package/src/db/session.js +187 -4
- package/src/lib/api/getAccount.js +32 -0
- package/src/lib/api/postArmorDown.js +48 -0
- package/src/lib/api/postArmorMove.js +48 -0
- package/src/lib/api/postArmorPull.js +48 -0
- package/src/lib/api/postArmorPush.js +48 -0
- package/src/lib/api/postArmorUp.js +51 -0
- package/src/lib/api/postKeypair.js +60 -0
- package/src/lib/api/postLogout.js +34 -0
- package/src/lib/api/postOauthDeviceCode.js +38 -0
- package/src/lib/api/postOauthToken.js +35 -0
- package/src/lib/helpers/armoredKeyDisplay.js +10 -0
- package/src/lib/helpers/buildApiError.js +16 -0
- package/src/lib/helpers/buildOauthError.js +14 -0
- package/src/lib/helpers/createSpinner.js +1 -1
- package/src/lib/helpers/cryptography/armorKeypair.js +32 -11
- package/src/lib/helpers/cryptography/armorKeypairSync.js +48 -7
- package/src/lib/helpers/cryptography/provision.js +2 -2
- package/src/lib/helpers/cryptography/provisionSync.js +2 -2
- package/src/lib/helpers/decryptDeviceValue.js +10 -0
- package/src/lib/helpers/encryptDeviceValue.js +9 -0
- package/src/lib/helpers/formatCode.js +11 -0
- package/src/lib/helpers/http.js +7 -0
- package/src/lib/helpers/jsonToEnv.js +7 -0
- package/src/lib/helpers/keyResolution/index.js +1 -1
- package/src/lib/helpers/keyResolution/{keyNames.js → keyNamesForEnvFile.js} +2 -2
- package/src/lib/helpers/keyResolution/keyValues.js +2 -2
- package/src/lib/helpers/keyResolution/keyValuesSync.js +2 -2
- package/src/lib/helpers/keypairMetadata.js +77 -0
- package/src/lib/helpers/listenForOpenKey.js +46 -0
- package/src/lib/helpers/normalizeToken.js +5 -0
- package/src/lib/helpers/openUrl.js +7 -0
- package/src/lib/helpers/readEnvKey.js +31 -0
- package/src/lib/helpers/removeEnvKey.js +50 -0
- package/src/lib/helpers/sanitizeCommandForMetadata.js +64 -0
- package/src/lib/helpers/teamChoicesFromMeta.js +8 -0
- package/src/lib/helpers/upsertEnvKey.js +61 -0
- package/src/lib/main.d.ts +0 -24
- package/src/lib/main.js +1 -1
- package/src/lib/services/armorDown.js +71 -0
- package/src/lib/services/armorKeypair.js +156 -0
- package/src/lib/services/armorMove.js +54 -0
- package/src/lib/services/armorPull.js +71 -0
- package/src/lib/services/armorPush.js +76 -0
- package/src/lib/services/armorUp.js +73 -0
- package/src/lib/services/decrypt.js +2 -2
- package/src/lib/services/encrypt.js +2 -2
- package/src/lib/services/keypair.js +5 -7
- package/src/lib/services/login.js +26 -0
- package/src/lib/services/loginPoll.js +35 -0
- package/src/lib/services/logout.js +26 -0
- package/src/lib/services/rotate.js +2 -2
- package/src/lib/services/run.js +3 -3
- package/src/lib/services/sets.js +3 -3
- package/src/lib/extensions/armor.js +0 -204
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildOauthError = require('../helpers/buildOauthError')
|
|
3
|
+
|
|
4
|
+
const OAUTH_CLIENT_ID = 'oac_dotenvxcli'
|
|
5
|
+
|
|
6
|
+
class PostOauthToken {
|
|
7
|
+
constructor (hostname, deviceCode) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.deviceCode = deviceCode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run () {
|
|
13
|
+
const resp = await http(`${this.hostname}/oauth/token`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
client_id: OAUTH_CLIENT_ID,
|
|
20
|
+
device_code: this.deviceCode,
|
|
21
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const json = await resp.body.json()
|
|
26
|
+
|
|
27
|
+
if (resp.statusCode >= 400) {
|
|
28
|
+
throw buildOauthError(resp.statusCode, json)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return json
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = PostOauthToken
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function armoredKeyDisplay (publicKey) {
|
|
2
|
+
if (!publicKey) return ''
|
|
3
|
+
|
|
4
|
+
const prefix = String(publicKey).slice(0, 6).toUpperCase()
|
|
5
|
+
if (prefix.length <= 3) return prefix
|
|
6
|
+
|
|
7
|
+
return `${prefix.slice(0, 3)} ${prefix.slice(3)}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = armoredKeyDisplay
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function buildApiError (statusCode, json) {
|
|
2
|
+
const code = json.error.code || statusCode.toString()
|
|
3
|
+
const message = `[${code}] ${json.error.message}`
|
|
4
|
+
const help = `[${code}] ${json.error.help || JSON.stringify(json)}`
|
|
5
|
+
const meta = json.error.meta
|
|
6
|
+
|
|
7
|
+
const error = new Error(message)
|
|
8
|
+
error.code = code
|
|
9
|
+
error.help = help
|
|
10
|
+
error.meta = meta
|
|
11
|
+
error.json = json
|
|
12
|
+
|
|
13
|
+
return error
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = buildApiError
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function buildOauthError (statusCode, json) {
|
|
2
|
+
const code = json.error
|
|
3
|
+
const message = `[${code}] ${json.error_description}`
|
|
4
|
+
const help = `[${code}] ${JSON.stringify(json)}`
|
|
5
|
+
|
|
6
|
+
const error = new Error(message)
|
|
7
|
+
error.code = code
|
|
8
|
+
error.help = help
|
|
9
|
+
error.statusCode = statusCode
|
|
10
|
+
|
|
11
|
+
return error
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = buildOauthError
|
|
@@ -4,7 +4,7 @@ const FRAME_INTERVAL_MS = 80
|
|
|
4
4
|
async function createSpinner (options = {}) {
|
|
5
5
|
const stream = process.stderr
|
|
6
6
|
const hasCursorControls = typeof stream.cursorTo === 'function' && typeof stream.clearLine === 'function'
|
|
7
|
-
const enabled = Boolean(stream.isTTY && hasCursorControls && !options.quiet && !options.verbose && !options.debug)
|
|
7
|
+
const enabled = Boolean(stream.isTTY && hasCursorControls && options.spinner !== false && !options.quiet && !options.verbose && !options.debug)
|
|
8
8
|
if (!enabled) return null
|
|
9
9
|
|
|
10
10
|
const text = options.text || 'thinking'
|
|
@@ -1,20 +1,41 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Session = require('../../../db/session')
|
|
2
|
+
const ArmorKeypair = require('../../services/armorKeypair')
|
|
3
|
+
const sanitizeCommandForMetadata = require('../sanitizeCommandForMetadata')
|
|
4
|
+
|
|
5
|
+
function metadataFromOptions (options) {
|
|
6
|
+
if (options.metadata) return options.metadata
|
|
7
|
+
if (!options.command) return undefined
|
|
8
|
+
|
|
9
|
+
return JSON.stringify({
|
|
10
|
+
command: sanitizeCommandForMetadata(options.command)
|
|
11
|
+
})
|
|
12
|
+
}
|
|
2
13
|
|
|
3
14
|
async function armorKeypair (existingPublicKey, options = {}) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
const sesh = new Session()
|
|
16
|
+
const token = options.token || sesh.token()
|
|
17
|
+
if (!token) {
|
|
18
|
+
return {
|
|
19
|
+
publicKey: undefined,
|
|
20
|
+
privateKey: undefined
|
|
21
|
+
}
|
|
8
22
|
}
|
|
9
23
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
const json = await new ArmorKeypair(
|
|
25
|
+
options.hostname || sesh.hostname(),
|
|
26
|
+
token,
|
|
27
|
+
sesh.devicePublicKey(),
|
|
28
|
+
existingPublicKey,
|
|
29
|
+
{
|
|
30
|
+
envFile: options.envFilepath,
|
|
31
|
+
team: options.team,
|
|
32
|
+
metadata: metadataFromOptions(options)
|
|
33
|
+
}
|
|
34
|
+
).run()
|
|
14
35
|
|
|
15
36
|
return {
|
|
16
|
-
publicKey,
|
|
17
|
-
privateKey
|
|
37
|
+
publicKey: json.public_key,
|
|
38
|
+
privateKey: json.private_key
|
|
18
39
|
}
|
|
19
40
|
}
|
|
20
41
|
|
|
@@ -1,13 +1,54 @@
|
|
|
1
|
-
const
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const childProcess = require('child_process')
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const EXEC_TIMEOUT = 5 * 60 * 1000
|
|
5
|
+
|
|
6
|
+
function cliPath () {
|
|
7
|
+
return path.resolve(__dirname, '../../../cli/dotenvx.js')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function execEnv () {
|
|
11
|
+
const env = { ...process.env }
|
|
12
|
+
env._TAPJS_PROCESSINFO_COVERAGE_ = '0'
|
|
13
|
+
|
|
14
|
+
if (env.NODE_OPTIONS && env.NODE_OPTIONS.includes('@tapjs/processinfo')) {
|
|
15
|
+
const nodeOptions = env.NODE_OPTIONS
|
|
16
|
+
.split(/\s+/)
|
|
17
|
+
.filter((option) => !option.includes('@tapjs/processinfo'))
|
|
18
|
+
.join(' ')
|
|
19
|
+
.trim()
|
|
20
|
+
|
|
21
|
+
if (nodeOptions) {
|
|
22
|
+
env.NODE_OPTIONS = nodeOptions
|
|
23
|
+
} else {
|
|
24
|
+
delete env.NODE_OPTIONS
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return env
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function armorKeypairSync (_existingPublicKey, options = {}) {
|
|
32
|
+
const args = [cliPath(), 'keypair', '--format', 'json']
|
|
33
|
+
if (options.envFilepath) args.push('-f', options.envFilepath)
|
|
34
|
+
|
|
35
|
+
let keypairs = {}
|
|
36
|
+
try {
|
|
37
|
+
keypairs = JSON.parse(childProcess.execFileSync(process.execPath, args, {
|
|
38
|
+
env: execEnv(),
|
|
39
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
40
|
+
timeout: EXEC_TIMEOUT
|
|
41
|
+
}).toString().trim())
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
keypairs = {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const publicKeyName = Object.keys(keypairs).find((key) => key === 'DOTENV_PUBLIC_KEY' || key.startsWith('DOTENV_PUBLIC_KEY_'))
|
|
47
|
+
const privateKeyName = Object.keys(keypairs).find((key) => key === 'DOTENV_PRIVATE_KEY' || key.startsWith('DOTENV_PRIVATE_KEY_'))
|
|
7
48
|
|
|
8
49
|
return {
|
|
9
|
-
publicKey,
|
|
10
|
-
privateKey
|
|
50
|
+
publicKey: keypairs[publicKeyName],
|
|
51
|
+
privateKey: keypairs[privateKeyName]
|
|
11
52
|
}
|
|
12
53
|
}
|
|
13
54
|
|
|
@@ -2,7 +2,7 @@ const mutateSrc = require('./mutateSrc')
|
|
|
2
2
|
const mutateKeysSrc = require('./mutateKeysSrc')
|
|
3
3
|
const armorKeypair = require('./armorKeypair')
|
|
4
4
|
const localKeypair = require('./localKeypair')
|
|
5
|
-
const {
|
|
5
|
+
const { keyNamesForEnvFile } = require('../keyResolution')
|
|
6
6
|
|
|
7
7
|
async function provision ({ envSrc, envFilepath, keysFilepath, noArmor, token, selectKeyStorage, command }) {
|
|
8
8
|
noArmor = noArmor !== false
|
|
@@ -10,7 +10,7 @@ async function provision ({ envSrc, envFilepath, keysFilepath, noArmor, token, s
|
|
|
10
10
|
noArmor = await selectKeyStorage() !== 'armored'
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const { publicKeyName, privateKeyName } =
|
|
13
|
+
const { publicKeyName, privateKeyName } = keyNamesForEnvFile(envFilepath)
|
|
14
14
|
|
|
15
15
|
let publicKey
|
|
16
16
|
let privateKey
|
|
@@ -2,11 +2,11 @@ const mutateSrc = require('./mutateSrc')
|
|
|
2
2
|
const mutateKeysSrcSync = require('./mutateKeysSrcSync')
|
|
3
3
|
const armorKeypairSync = require('./armorKeypairSync')
|
|
4
4
|
const localKeypair = require('./localKeypair')
|
|
5
|
-
const {
|
|
5
|
+
const { keyNamesForEnvFile } = require('../keyResolution')
|
|
6
6
|
|
|
7
7
|
function provisionSync ({ envSrc, envFilepath, keysFilepath, noArmor, command }) {
|
|
8
8
|
noArmor = noArmor !== false
|
|
9
|
-
const { publicKeyName, privateKeyName } =
|
|
9
|
+
const { publicKeyName, privateKeyName } = keyNamesForEnvFile(envFilepath)
|
|
10
10
|
|
|
11
11
|
let publicKey
|
|
12
12
|
let privateKey
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { decrypt } = require('eciesjs')
|
|
2
|
+
|
|
3
|
+
function decryptDeviceValue (value, privateKey) {
|
|
4
|
+
const secret = Buffer.from(privateKey, 'hex')
|
|
5
|
+
const ciphertext = Buffer.from(value, 'base64')
|
|
6
|
+
|
|
7
|
+
return decrypt(secret, ciphertext).toString()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = decryptDeviceValue
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const canonicalEnvFilename = require('./../canonicalEnvFilename')
|
|
2
2
|
const environment = require('./../envResolution/environment')
|
|
3
3
|
|
|
4
|
-
function
|
|
4
|
+
function keyNamesForEnvFile (filepath) {
|
|
5
5
|
const filename = canonicalEnvFilename(filepath)
|
|
6
6
|
|
|
7
7
|
// .env
|
|
@@ -21,4 +21,4 @@ function keyNames (filepath) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
module.exports =
|
|
24
|
+
module.exports = keyNamesForEnvFile
|
|
@@ -2,7 +2,7 @@ const path = require('path')
|
|
|
2
2
|
|
|
3
3
|
const fsx = require('./../fsx')
|
|
4
4
|
const dotenvParse = require('./../dotenvParse')
|
|
5
|
-
const
|
|
5
|
+
const keyNamesForEnvFile = require('./keyNamesForEnvFile')
|
|
6
6
|
const readProcessKey = require('./readProcessKey')
|
|
7
7
|
const readFileKey = require('./readFileKey')
|
|
8
8
|
const armorKeypair = require('../cryptography/armorKeypair')
|
|
@@ -35,7 +35,7 @@ async function invertForPrivateKeyName (filepath) {
|
|
|
35
35
|
async function keyValues (filepath, opts = {}) {
|
|
36
36
|
let keysFilepath = opts.keysFilepath || null
|
|
37
37
|
const noArmor = opts.noArmor === true
|
|
38
|
-
const names =
|
|
38
|
+
const names = keyNamesForEnvFile(filepath)
|
|
39
39
|
const publicKeyName = names.publicKeyName // DOTENV_PUBLIC_KEY_${ENVIRONMENT}
|
|
40
40
|
let privateKeyName = names.privateKeyName // DOTENV_PRIVATE_KEY_${ENVIRONMENT}
|
|
41
41
|
|
|
@@ -2,7 +2,7 @@ const path = require('path')
|
|
|
2
2
|
|
|
3
3
|
const fsx = require('./../fsx')
|
|
4
4
|
const dotenvParse = require('./../dotenvParse')
|
|
5
|
-
const
|
|
5
|
+
const keyNamesForEnvFile = require('./keyNamesForEnvFile')
|
|
6
6
|
const readProcessKey = require('./readProcessKey')
|
|
7
7
|
const readFileKeySync = require('./readFileKeySync')
|
|
8
8
|
const armorKeypairSync = require('../cryptography/armorKeypairSync')
|
|
@@ -35,7 +35,7 @@ function invertForPrivateKeyName (filepath) {
|
|
|
35
35
|
function keyValuesSync (filepath, opts = {}) {
|
|
36
36
|
let keysFilepath = opts.keysFilepath || null
|
|
37
37
|
const noArmor = opts.noArmor === true
|
|
38
|
-
const names =
|
|
38
|
+
const names = keyNamesForEnvFile(filepath)
|
|
39
39
|
const publicKeyName = names.publicKeyName // DOTENV_PUBLIC_KEY_${ENVIRONMENT}
|
|
40
40
|
let privateKeyName = names.privateKeyName // DOTENV_PRIVATE_KEY_${ENVIRONMENT}
|
|
41
41
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const execa = require('execa')
|
|
4
|
+
const dotenvParse = require('./dotenvParse')
|
|
5
|
+
const canonicalEnvFilename = require('./canonicalEnvFilename')
|
|
6
|
+
const environment = require('./envResolution/environment')
|
|
7
|
+
const sanitizeCommandForMetadata = require('./sanitizeCommandForMetadata')
|
|
8
|
+
|
|
9
|
+
function compact (object) {
|
|
10
|
+
return Object.entries(object).reduce((acc, [key, value]) => {
|
|
11
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
12
|
+
acc[key] = value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return acc
|
|
16
|
+
}, {})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function commandFromMetadata (metadata) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(metadata)
|
|
22
|
+
return parsed && parsed.command ? sanitizeCommandForMetadata(parsed.command) : parsed && parsed.command
|
|
23
|
+
} catch (_error) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function gitRoot () {
|
|
29
|
+
try {
|
|
30
|
+
return execa.sync('git', ['rev-parse', '--show-toplevel']).stdout.toString().trim()
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeFilepath (envFile) {
|
|
37
|
+
const root = gitRoot()
|
|
38
|
+
const filepath = path.resolve(envFile)
|
|
39
|
+
|
|
40
|
+
if (root) {
|
|
41
|
+
return path.relative(root, filepath).replace(/\\/g, '/')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return path.relative(process.cwd(), filepath).replace(/\\/g, '/')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function projectName () {
|
|
48
|
+
return path.basename(gitRoot() || process.cwd())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function envKeys (envFile) {
|
|
52
|
+
let src
|
|
53
|
+
try {
|
|
54
|
+
src = fs.readFileSync(envFile)
|
|
55
|
+
} catch (_error) {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const parsed = dotenvParse(src)
|
|
60
|
+
|
|
61
|
+
if (!parsed || Object.keys(parsed).length === 0) return null
|
|
62
|
+
|
|
63
|
+
return Object.keys(parsed)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function keypairMetadata (envFile = '.env', metadata = undefined) {
|
|
67
|
+
return compact({
|
|
68
|
+
filepath: normalizeFilepath(envFile),
|
|
69
|
+
filename: canonicalEnvFilename(envFile),
|
|
70
|
+
environment: environment(envFile),
|
|
71
|
+
project_name: projectName(),
|
|
72
|
+
keys: envKeys(envFile),
|
|
73
|
+
command: commandFromMetadata(metadata)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = keypairMetadata
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function listenForOpenKey (onOpen) {
|
|
2
|
+
const stdin = process.stdin
|
|
3
|
+
if (!stdin.isTTY) return () => {}
|
|
4
|
+
|
|
5
|
+
const canSetRawMode = typeof stdin.setRawMode === 'function'
|
|
6
|
+
const wasRawMode = Boolean(stdin.isRaw)
|
|
7
|
+
let didHandleOpenChoice = false
|
|
8
|
+
|
|
9
|
+
const cleanup = () => {
|
|
10
|
+
stdin.off('data', onData)
|
|
11
|
+
if (canSetRawMode) stdin.setRawMode(wasRawMode)
|
|
12
|
+
stdin.pause()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const onData = (chunk) => {
|
|
16
|
+
const key = String(chunk)
|
|
17
|
+
const lower = key.toLowerCase()
|
|
18
|
+
|
|
19
|
+
if (key === '\u0003') {
|
|
20
|
+
cleanup()
|
|
21
|
+
process.kill(process.pid, 'SIGINT')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (key === '\r' || key === '\n' || lower === 'y') {
|
|
26
|
+
if (!didHandleOpenChoice) {
|
|
27
|
+
didHandleOpenChoice = true
|
|
28
|
+
Promise.resolve(onOpen()).catch(() => {})
|
|
29
|
+
}
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (lower === 'n') {
|
|
34
|
+
cleanup()
|
|
35
|
+
process.kill(process.pid, 'SIGINT')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (canSetRawMode) stdin.setRawMode(true)
|
|
40
|
+
stdin.resume()
|
|
41
|
+
stdin.on('data', onData)
|
|
42
|
+
|
|
43
|
+
return cleanup
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = listenForOpenKey
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const dotenvParse = require('./dotenvParse')
|
|
3
|
+
const Errors = require('./errors')
|
|
4
|
+
|
|
5
|
+
function ignored (error, options) {
|
|
6
|
+
return (options.ignore || []).includes(error.code)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readEnvKey (key, filepath, options = {}) {
|
|
10
|
+
let src
|
|
11
|
+
try {
|
|
12
|
+
src = fs.readFileSync(filepath)
|
|
13
|
+
} catch (_error) {
|
|
14
|
+
const error = new Errors({ envFilepath: filepath }).missingEnvFile()
|
|
15
|
+
if (ignored(error, options)) return undefined
|
|
16
|
+
if (options.strict) throw error
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const parsed = dotenvParse(src)
|
|
21
|
+
const value = parsed[key]
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
const error = new Errors({ key }).missingKey()
|
|
24
|
+
if (ignored(error, options)) return undefined
|
|
25
|
+
if (options.strict) throw error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = readEnvKey
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
function escapeForRegex (value) {
|
|
5
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function removeEnvKey (key, keysFilepath = '.env.keys') {
|
|
9
|
+
const resolvedKeysFilepath = path.resolve(keysFilepath)
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(resolvedKeysFilepath)) {
|
|
12
|
+
return {
|
|
13
|
+
changed: false,
|
|
14
|
+
key,
|
|
15
|
+
filepath: keysFilepath
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const src = fs.readFileSync(resolvedKeysFilepath, 'utf8')
|
|
20
|
+
const eol = src.includes('\r\n') ? '\r\n' : '\n'
|
|
21
|
+
const keyPattern = new RegExp(`^\\s*(?:export\\s+)?${escapeForRegex(key)}\\s*=`)
|
|
22
|
+
const envKeyPattern = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=/
|
|
23
|
+
const lines = src.length > 0 ? src.split(/\r?\n/) : []
|
|
24
|
+
|
|
25
|
+
while (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
26
|
+
lines.pop()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const nextLines = lines.filter((line) => !keyPattern.test(line))
|
|
30
|
+
const changed = nextLines.length !== lines.length
|
|
31
|
+
|
|
32
|
+
if (changed) {
|
|
33
|
+
const hasRemainingKeys = nextLines.some((line) => envKeyPattern.test(line))
|
|
34
|
+
|
|
35
|
+
if (hasRemainingKeys) {
|
|
36
|
+
const nextSrc = `${nextLines.join(eol)}${eol}`
|
|
37
|
+
fs.writeFileSync(resolvedKeysFilepath, nextSrc, 'utf8')
|
|
38
|
+
} else {
|
|
39
|
+
fs.rmSync(resolvedKeysFilepath, { force: true })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
changed,
|
|
45
|
+
key,
|
|
46
|
+
filepath: keysFilepath
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = removeEnvKey
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function commandParts (command) {
|
|
2
|
+
if (Array.isArray(command)) {
|
|
3
|
+
return command.map(arg => `${arg}`)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const parts = []
|
|
7
|
+
const src = `${command}`
|
|
8
|
+
const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g
|
|
9
|
+
let match
|
|
10
|
+
|
|
11
|
+
while ((match = regex.exec(src)) !== null) {
|
|
12
|
+
parts.push(match[1] || match[2] || match[3])
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return parts
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function flagName (part) {
|
|
19
|
+
if (!part.startsWith('--')) return null
|
|
20
|
+
return part.slice(2).split('=')[0].toLowerCase()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isSecretFlag (part) {
|
|
24
|
+
const name = flagName(part)
|
|
25
|
+
if (!name) return false
|
|
26
|
+
if (name === 'pass' || name === 'password' || name === 'passphrase' || name === 'pwd') return true
|
|
27
|
+
|
|
28
|
+
return name.includes('token') ||
|
|
29
|
+
name.includes('password') ||
|
|
30
|
+
name.includes('secret') ||
|
|
31
|
+
name.includes('api-key') ||
|
|
32
|
+
name.includes('apikey') ||
|
|
33
|
+
name.includes('private-key')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sanitizeCommandForMetadata (command) {
|
|
37
|
+
const parts = commandParts(command)
|
|
38
|
+
const sanitized = []
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < parts.length; i++) {
|
|
41
|
+
const part = parts[i]
|
|
42
|
+
|
|
43
|
+
if (isSecretFlag(part) && part.includes('=')) {
|
|
44
|
+
const name = part.split('=')[0]
|
|
45
|
+
sanitized.push(`${name}=[REDACTED]`)
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isSecretFlag(part)) {
|
|
50
|
+
sanitized.push(part)
|
|
51
|
+
if (i + 1 < parts.length) {
|
|
52
|
+
sanitized.push('[REDACTED]')
|
|
53
|
+
i++
|
|
54
|
+
}
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sanitized.push(part)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return sanitized.join(' ')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = sanitizeCommandForMetadata
|