@dotenvx/dotenvx 1.72.0 → 1.73.1
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 +15 -1
- package/README.md +1 -1
- package/package.json +1 -2
- 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/login.js +0 -1
- package/src/cli/commands/armor.js +72 -0
- package/src/cli/dotenvx.js +3 -0
- package/src/db/session.js +90 -22
- 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/helpers/armoredKeyDisplay.js +10 -0
- 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/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/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/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/loginPoll.js +1 -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,48 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildApiError = require('../helpers/buildApiError')
|
|
3
|
+
const packageJson = require('../helpers/packageJson')
|
|
4
|
+
const normalizeToken = require('../helpers/normalizeToken')
|
|
5
|
+
|
|
6
|
+
class PostArmorPull {
|
|
7
|
+
constructor (hostname, token, devicePublicKey, publicKey, team) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.token = token
|
|
10
|
+
this.devicePublicKey = devicePublicKey
|
|
11
|
+
this.publicKey = publicKey
|
|
12
|
+
this.team = team
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async run () {
|
|
16
|
+
const token = normalizeToken(this.token)
|
|
17
|
+
const devicePublicKey = this.devicePublicKey
|
|
18
|
+
const publicKey = this.publicKey
|
|
19
|
+
const team = this.team
|
|
20
|
+
const url = `${this.hostname}/api/armor/pull`
|
|
21
|
+
|
|
22
|
+
const body = {
|
|
23
|
+
device_public_key: devicePublicKey,
|
|
24
|
+
cli_version: packageJson.version,
|
|
25
|
+
public_key: publicKey,
|
|
26
|
+
team
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const resp = await http(url, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
'Content-Type': 'application/json'
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const json = await resp.body.json()
|
|
39
|
+
|
|
40
|
+
if (resp.statusCode >= 400) {
|
|
41
|
+
throw buildApiError(resp.statusCode, json)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return json
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = PostArmorPull
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildApiError = require('../helpers/buildApiError')
|
|
3
|
+
const packageJson = require('../helpers/packageJson')
|
|
4
|
+
const normalizeToken = require('../helpers/normalizeToken')
|
|
5
|
+
|
|
6
|
+
class PostArmorPush {
|
|
7
|
+
constructor (hostname, token, devicePublicKey, privateKey, team) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.token = token
|
|
10
|
+
this.devicePublicKey = devicePublicKey
|
|
11
|
+
this.privateKey = privateKey
|
|
12
|
+
this.team = team
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async run () {
|
|
16
|
+
const token = normalizeToken(this.token)
|
|
17
|
+
const devicePublicKey = this.devicePublicKey
|
|
18
|
+
const privateKey = this.privateKey
|
|
19
|
+
const team = this.team
|
|
20
|
+
const url = `${this.hostname}/api/armor/push`
|
|
21
|
+
|
|
22
|
+
const body = {
|
|
23
|
+
device_public_key: devicePublicKey,
|
|
24
|
+
cli_version: packageJson.version,
|
|
25
|
+
private_key: privateKey,
|
|
26
|
+
team
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const resp = await http(url, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
'Content-Type': 'application/json'
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const json = await resp.body.json()
|
|
39
|
+
|
|
40
|
+
if (resp.statusCode >= 400) {
|
|
41
|
+
throw buildApiError(resp.statusCode, json)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return json
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = PostArmorPush
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildApiError = require('../helpers/buildApiError')
|
|
3
|
+
const packageJson = require('../helpers/packageJson')
|
|
4
|
+
const normalizeToken = require('../helpers/normalizeToken')
|
|
5
|
+
|
|
6
|
+
class PostArmorUp {
|
|
7
|
+
constructor (hostname, token, devicePublicKey, publicKey, privateKey, team) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.token = token
|
|
10
|
+
this.devicePublicKey = devicePublicKey
|
|
11
|
+
this.publicKey = publicKey
|
|
12
|
+
this.privateKey = privateKey
|
|
13
|
+
this.team = team
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async run () {
|
|
17
|
+
const token = normalizeToken(this.token)
|
|
18
|
+
const devicePublicKey = this.devicePublicKey
|
|
19
|
+
const publicKey = this.publicKey
|
|
20
|
+
const privateKey = this.privateKey
|
|
21
|
+
const team = this.team
|
|
22
|
+
const url = `${this.hostname}/api/armor/up`
|
|
23
|
+
|
|
24
|
+
const body = {
|
|
25
|
+
device_public_key: devicePublicKey,
|
|
26
|
+
cli_version: packageJson.version,
|
|
27
|
+
public_key: publicKey,
|
|
28
|
+
private_key: privateKey,
|
|
29
|
+
team
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const resp = await http(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${token}`,
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(body)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const json = await resp.body.json()
|
|
42
|
+
|
|
43
|
+
if (resp.statusCode >= 400) {
|
|
44
|
+
throw buildApiError(resp.statusCode, json)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return json
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = PostArmorUp
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { http } = require('../helpers/http')
|
|
2
|
+
const buildApiError = require('../helpers/buildApiError')
|
|
3
|
+
const packageJson = require('../helpers/packageJson')
|
|
4
|
+
const normalizeToken = require('../helpers/normalizeToken')
|
|
5
|
+
|
|
6
|
+
class PostKeypair {
|
|
7
|
+
constructor (hostname, token, devicePublicKey, publicKey, team, metadata, grantToken) {
|
|
8
|
+
this.hostname = hostname || 'https://armor.dotenvx.com'
|
|
9
|
+
this.token = token
|
|
10
|
+
this.devicePublicKey = devicePublicKey
|
|
11
|
+
this.publicKey = publicKey
|
|
12
|
+
this.team = team
|
|
13
|
+
this.metadata = metadata
|
|
14
|
+
this.grantToken = grantToken
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async run () {
|
|
18
|
+
const token = normalizeToken(this.token)
|
|
19
|
+
const url = `${this.hostname}/api/keypair`
|
|
20
|
+
const body = {
|
|
21
|
+
device_public_key: this.devicePublicKey,
|
|
22
|
+
cli_version: packageJson.version
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.publicKey) {
|
|
26
|
+
body.public_key = this.publicKey
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.team) {
|
|
30
|
+
body.team = this.team
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.metadata && Object.keys(this.metadata).length > 0) {
|
|
34
|
+
body.metadata = this.metadata
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.grantToken) {
|
|
38
|
+
body.grant_token = this.grantToken
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resp = await http(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: `Bearer ${token}`,
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(body)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const json = await resp.body.json()
|
|
51
|
+
|
|
52
|
+
if (resp.statusCode >= 400) {
|
|
53
|
+
throw buildApiError(resp.statusCode, json)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return json
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = PostKeypair
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,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
|