@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +3 -3
  3. package/package.json +8 -4
  4. package/src/cli/actions/armor/down.js +37 -0
  5. package/src/cli/actions/armor/move.js +42 -0
  6. package/src/cli/actions/armor/pull.js +37 -0
  7. package/src/cli/actions/armor/push.js +37 -0
  8. package/src/cli/actions/armor/up.js +37 -0
  9. package/src/cli/actions/decrypt.js +1 -1
  10. package/src/cli/actions/encrypt.js +1 -1
  11. package/src/cli/actions/login.js +63 -0
  12. package/src/cli/actions/logout.js +36 -0
  13. package/src/cli/actions/normalizeArmorOptions.js +1 -2
  14. package/src/cli/actions/set.js +1 -1
  15. package/src/cli/commands/armor.js +72 -0
  16. package/src/cli/dotenvx.js +17 -19
  17. package/src/db/device.js +73 -0
  18. package/src/db/session.js +187 -4
  19. package/src/lib/api/getAccount.js +32 -0
  20. package/src/lib/api/postArmorDown.js +48 -0
  21. package/src/lib/api/postArmorMove.js +48 -0
  22. package/src/lib/api/postArmorPull.js +48 -0
  23. package/src/lib/api/postArmorPush.js +48 -0
  24. package/src/lib/api/postArmorUp.js +51 -0
  25. package/src/lib/api/postKeypair.js +60 -0
  26. package/src/lib/api/postLogout.js +34 -0
  27. package/src/lib/api/postOauthDeviceCode.js +38 -0
  28. package/src/lib/api/postOauthToken.js +35 -0
  29. package/src/lib/helpers/armoredKeyDisplay.js +10 -0
  30. package/src/lib/helpers/buildApiError.js +16 -0
  31. package/src/lib/helpers/buildOauthError.js +14 -0
  32. package/src/lib/helpers/createSpinner.js +1 -1
  33. package/src/lib/helpers/cryptography/armorKeypair.js +32 -11
  34. package/src/lib/helpers/cryptography/armorKeypairSync.js +48 -7
  35. package/src/lib/helpers/cryptography/provision.js +2 -2
  36. package/src/lib/helpers/cryptography/provisionSync.js +2 -2
  37. package/src/lib/helpers/decryptDeviceValue.js +10 -0
  38. package/src/lib/helpers/encryptDeviceValue.js +9 -0
  39. package/src/lib/helpers/formatCode.js +11 -0
  40. package/src/lib/helpers/http.js +7 -0
  41. package/src/lib/helpers/jsonToEnv.js +7 -0
  42. package/src/lib/helpers/keyResolution/index.js +1 -1
  43. package/src/lib/helpers/keyResolution/{keyNames.js → keyNamesForEnvFile.js} +2 -2
  44. package/src/lib/helpers/keyResolution/keyValues.js +2 -2
  45. package/src/lib/helpers/keyResolution/keyValuesSync.js +2 -2
  46. package/src/lib/helpers/keypairMetadata.js +77 -0
  47. package/src/lib/helpers/listenForOpenKey.js +46 -0
  48. package/src/lib/helpers/normalizeToken.js +5 -0
  49. package/src/lib/helpers/openUrl.js +7 -0
  50. package/src/lib/helpers/readEnvKey.js +31 -0
  51. package/src/lib/helpers/removeEnvKey.js +50 -0
  52. package/src/lib/helpers/sanitizeCommandForMetadata.js +64 -0
  53. package/src/lib/helpers/teamChoicesFromMeta.js +8 -0
  54. package/src/lib/helpers/upsertEnvKey.js +61 -0
  55. package/src/lib/main.d.ts +0 -24
  56. package/src/lib/main.js +1 -1
  57. package/src/lib/services/armorDown.js +71 -0
  58. package/src/lib/services/armorKeypair.js +156 -0
  59. package/src/lib/services/armorMove.js +54 -0
  60. package/src/lib/services/armorPull.js +71 -0
  61. package/src/lib/services/armorPush.js +76 -0
  62. package/src/lib/services/armorUp.js +73 -0
  63. package/src/lib/services/decrypt.js +2 -2
  64. package/src/lib/services/encrypt.js +2 -2
  65. package/src/lib/services/keypair.js +5 -7
  66. package/src/lib/services/login.js +26 -0
  67. package/src/lib/services/loginPoll.js +35 -0
  68. package/src/lib/services/logout.js +26 -0
  69. package/src/lib/services/rotate.js +2 -2
  70. package/src/lib/services/run.js +3 -3
  71. package/src/lib/services/sets.js +3 -3
  72. 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 Armor = require('../../extensions/armor')
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 keypairOptions = {
5
- token: options.token,
6
- envFilepath: options.envFilepath,
7
- command: options.command
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 kp = await new Armor().keypair(existingPublicKey, keypairOptions)
11
-
12
- const publicKey = kp.public_key
13
- const privateKey = kp.private_key
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 Armor = require('../../extensions/armor')
1
+ const path = require('path')
2
+ const childProcess = require('child_process')
2
3
 
3
- function armorKeypairSync (existingPublicKey, options = {}) {
4
- const kp = new Armor().keypairSync(existingPublicKey, options)
5
- const publicKey = kp.public_key
6
- const privateKey = kp.private_key
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 { keyNames } = require('../keyResolution')
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 } = keyNames(envFilepath)
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 { keyNames } = require('../keyResolution')
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 } = keyNames(envFilepath)
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
@@ -0,0 +1,9 @@
1
+ const { encrypt } = require('eciesjs')
2
+
3
+ function encryptDeviceValue (value, publicKey) {
4
+ const ciphertext = encrypt(publicKey, Buffer.from(value))
5
+
6
+ return Buffer.from(ciphertext, 'hex').toString('base64')
7
+ }
8
+
9
+ module.exports = encryptDeviceValue
@@ -0,0 +1,11 @@
1
+ function formatCode (str) {
2
+ const parts = []
3
+
4
+ for (let i = 0; i < str.length; i += 4) {
5
+ parts.push(str.substring(i, i + 4))
6
+ }
7
+
8
+ return parts.join('-')
9
+ }
10
+
11
+ module.exports = formatCode
@@ -0,0 +1,7 @@
1
+ const { request } = require('undici')
2
+
3
+ async function http (url, opts = {}) {
4
+ return await request(url, opts)
5
+ }
6
+
7
+ module.exports = { http }
@@ -0,0 +1,7 @@
1
+ function jsonToEnv (json) {
2
+ return Object.entries(json).map(function ([key, value]) {
3
+ return key + '=' + `"${value}"`
4
+ }).join('\n')
5
+ }
6
+
7
+ module.exports = jsonToEnv
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- keyNames: require('./keyNames'),
2
+ keyNamesForEnvFile: require('./keyNamesForEnvFile'),
3
3
  keyValues: require('./keyValues'),
4
4
  keyValuesSync: require('./keyValuesSync'),
5
5
  keyValuesFromEnvSrc: require('./keyValuesFromEnvSrc'),
@@ -1,7 +1,7 @@
1
1
  const canonicalEnvFilename = require('./../canonicalEnvFilename')
2
2
  const environment = require('./../envResolution/environment')
3
3
 
4
- function keyNames (filepath) {
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 = keyNames
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 keyNames = require('./keyNames')
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 = keyNames(filepath)
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 keyNames = require('./keyNames')
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 = keyNames(filepath)
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,5 @@
1
+ function normalizeToken (token) {
2
+ return token == null ? '' : token
3
+ }
4
+
5
+ module.exports = normalizeToken
@@ -0,0 +1,7 @@
1
+ const open = require('open')
2
+
3
+ async function openUrl (url) {
4
+ return await open(url, { wait: false })
5
+ }
6
+
7
+ module.exports = openUrl
@@ -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
@@ -0,0 +1,8 @@
1
+ function teamChoicesFromMeta (meta) {
2
+ return meta.organizations.map(org => ({
3
+ name: org.provider_slug,
4
+ value: org.provider_slug
5
+ }))
6
+ }
7
+
8
+ module.exports = teamChoicesFromMeta