@akshxy/envgit 0.5.1 → 0.6.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/README.md +73 -14
- package/bin/envgit.js +117 -92
- package/package.json +2 -1
- package/src/commands/add-env.js +4 -0
- package/src/commands/audit.js +4 -1
- package/src/commands/copy.js +15 -13
- package/src/commands/delete.js +11 -5
- package/src/commands/doctor.js +3 -6
- package/src/commands/envs.js +16 -10
- package/src/commands/fix.js +130 -0
- package/src/commands/get.js +6 -5
- package/src/commands/init.js +2 -2
- package/src/commands/join.js +1 -1
- package/src/commands/rename-key.js +8 -9
- package/src/commands/rotate-key.js +2 -5
- package/src/commands/scan.js +207 -0
- package/src/commands/set.js +13 -12
- package/src/commands/status.js +25 -13
- package/src/commands/unpack.js +19 -13
- package/src/commands/use.js +29 -0
- package/src/interactive.js +83 -0
- package/src/keystore.js +2 -2
- package/src/ui.js +30 -5
- package/src/commands/keygen.js +0 -71
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { search, input, password, confirm } from '@inquirer/prompts';
|
|
2
|
+
|
|
3
|
+
// Fuzzy match — returns true if all chars of query appear in order in str
|
|
4
|
+
function fuzzy(str, query) {
|
|
5
|
+
if (!query) return true;
|
|
6
|
+
let si = 0;
|
|
7
|
+
const s = str.toLowerCase();
|
|
8
|
+
const q = query.toLowerCase();
|
|
9
|
+
for (let i = 0; i < q.length; i++) {
|
|
10
|
+
si = s.indexOf(q[i], si);
|
|
11
|
+
if (si === -1) return false;
|
|
12
|
+
si++;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interactive fuzzy key picker.
|
|
19
|
+
* @param {Record<string,string>} vars — existing key/value pairs
|
|
20
|
+
* @param {string} message
|
|
21
|
+
* @param {{ allowNew?: boolean }} opts
|
|
22
|
+
* @returns {Promise<string>} chosen key name
|
|
23
|
+
*/
|
|
24
|
+
export async function pickKey(vars, message = 'Select a key', { allowNew = false } = {}) {
|
|
25
|
+
const keys = Object.keys(vars).sort();
|
|
26
|
+
|
|
27
|
+
return search({
|
|
28
|
+
message,
|
|
29
|
+
source(term) {
|
|
30
|
+
const matches = keys.filter(k => fuzzy(k, term));
|
|
31
|
+
const choices = matches.map(k => ({ name: k, value: k }));
|
|
32
|
+
|
|
33
|
+
// If the typed term isn't an exact match and allowNew is set, offer to create it
|
|
34
|
+
if (allowNew && term && !keys.includes(term)) {
|
|
35
|
+
choices.unshift({ name: `+ create "${term}"`, value: term });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return choices;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Interactive fuzzy environment picker.
|
|
45
|
+
* @param {string[]} envs
|
|
46
|
+
* @param {string} message
|
|
47
|
+
* @returns {Promise<string>} chosen env name
|
|
48
|
+
*/
|
|
49
|
+
export async function pickEnv(envs, message = 'Select an environment') {
|
|
50
|
+
return search({
|
|
51
|
+
message,
|
|
52
|
+
source(term) {
|
|
53
|
+
return envs
|
|
54
|
+
.filter(e => fuzzy(e, term))
|
|
55
|
+
.map(e => ({ name: e, value: e }));
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Prompt for a secret value (masked by default).
|
|
62
|
+
*/
|
|
63
|
+
export async function promptValue(keyName, existing) {
|
|
64
|
+
const msg = existing !== undefined
|
|
65
|
+
? `New value for ${keyName} (current: ${'*'.repeat(Math.min(existing.length, 8))})`
|
|
66
|
+
: `Value for ${keyName}`;
|
|
67
|
+
|
|
68
|
+
return password({ message: msg, mask: false });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Prompt for a plain (visible) string.
|
|
73
|
+
*/
|
|
74
|
+
export async function promptInput(message, defaultValue) {
|
|
75
|
+
return input({ message, default: defaultValue });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Prompt for confirmation.
|
|
80
|
+
*/
|
|
81
|
+
export async function promptConfirm(message, defaultValue = false) {
|
|
82
|
+
return confirm({ message, default: defaultValue });
|
|
83
|
+
}
|
package/src/keystore.js
CHANGED
|
@@ -46,7 +46,7 @@ export function loadKey(projectRoot) {
|
|
|
46
46
|
}
|
|
47
47
|
// Project found, but this machine doesn't have the key yet
|
|
48
48
|
console.error(
|
|
49
|
-
`No key found for this project. Ask
|
|
49
|
+
`No key found for this project. Ask a teammate to run:\n envgit share\nthen run the join command they send you.`
|
|
50
50
|
);
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
@@ -59,7 +59,7 @@ export function loadKey(projectRoot) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
console.error(
|
|
62
|
-
`No key found for this project. Ask
|
|
62
|
+
`No key found for this project. Ask a teammate to run:\n envgit share\nthen run the join command they send you.`
|
|
63
63
|
);
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
package/src/ui.js
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
|
-
export const ok
|
|
4
|
-
export const fail
|
|
5
|
-
export const warn
|
|
3
|
+
export const ok = (msg) => console.log(chalk.green(`✓ ${msg}`));
|
|
4
|
+
export const fail = (msg) => console.log(chalk.red(`✗ ${msg}`));
|
|
5
|
+
export const warn = (msg) => console.log(chalk.yellow(`⚠ ${msg}`));
|
|
6
6
|
export const fatal = (msg) => { console.error(chalk.red(`✗ ${msg}`)); process.exit(1); };
|
|
7
|
+
export const dim = (text) => chalk.dim(text);
|
|
8
|
+
export const bold = (text) => chalk.bold(text);
|
|
9
|
+
|
|
10
|
+
// Plain label (non-env uses)
|
|
7
11
|
export const label = (text) => chalk.cyan(`[${text}]`);
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
|
|
13
|
+
// Well-known env signals — only these get special colors
|
|
14
|
+
const PROD_RE = /^(prod|production)$/i;
|
|
15
|
+
const STAGING_RE = /^(staging|stage|uat|preprod|pre-prod|preview)$/i;
|
|
16
|
+
const DEV_RE = /^(dev|development|local|localhost)$/i;
|
|
17
|
+
|
|
18
|
+
// Any other env name gets a stable unique color derived from the name —
|
|
19
|
+
// "john", "feature-x", "client-abc", "solo" each always render the same color
|
|
20
|
+
const PALETTE = [
|
|
21
|
+
'cyan', 'blue', 'magenta', 'white',
|
|
22
|
+
'cyanBright', 'blueBright', 'magentaBright', 'whiteBright',
|
|
23
|
+
];
|
|
24
|
+
function hashColor(name) {
|
|
25
|
+
const n = [...name].reduce((acc, c) => acc * 31 + c.charCodeAt(0), 7);
|
|
26
|
+
return PALETTE[Math.abs(n) % PALETTE.length];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function envLabel(name) {
|
|
30
|
+
if (PROD_RE.test(name)) return chalk.bold.red(`[${name}]`);
|
|
31
|
+
if (STAGING_RE.test(name)) return chalk.bold.yellow(`[${name}]`);
|
|
32
|
+
if (DEV_RE.test(name)) return chalk.bold.green(`[${name}]`);
|
|
33
|
+
return chalk.bold[hashColor(name)](`[${name}]`);
|
|
34
|
+
}
|
package/src/commands/keygen.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { generateKey } from '../crypto.js';
|
|
2
|
-
import { findProjectRoot, saveKey, loadKey, globalKeyPath } from '../keystore.js';
|
|
3
|
-
import { loadConfig } from '../config.js';
|
|
4
|
-
import { ok, warn, fatal, bold, dim } from '../ui.js';
|
|
5
|
-
|
|
6
|
-
export async function keygen(options) {
|
|
7
|
-
const projectRoot = findProjectRoot();
|
|
8
|
-
|
|
9
|
-
if (options.show) {
|
|
10
|
-
if (!projectRoot) {
|
|
11
|
-
fatal('No envgit project found — cannot show key.');
|
|
12
|
-
}
|
|
13
|
-
let key;
|
|
14
|
-
try {
|
|
15
|
-
key = loadKey(projectRoot);
|
|
16
|
-
} catch (e) {
|
|
17
|
-
fatal(e.message);
|
|
18
|
-
}
|
|
19
|
-
const hint = key.slice(0, 8);
|
|
20
|
-
console.log('');
|
|
21
|
-
console.log(bold('Current key:'));
|
|
22
|
-
console.log(` ${key}`);
|
|
23
|
-
console.log('');
|
|
24
|
-
console.log(dim(`Hint (first 8 chars): ${hint}`));
|
|
25
|
-
console.log(dim('Share via a secure channel (not git, not chat).'));
|
|
26
|
-
console.log(dim('Teammate saves it with: envgit keygen --set <key>'));
|
|
27
|
-
console.log('');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (options.set) {
|
|
32
|
-
const key = options.set;
|
|
33
|
-
const decoded = Buffer.from(key, 'base64');
|
|
34
|
-
if (decoded.length !== 32) {
|
|
35
|
-
fatal(`Invalid key — must decode to exactly 32 bytes (got ${decoded.length}). Generate one with: envgit keygen`);
|
|
36
|
-
}
|
|
37
|
-
if (!projectRoot) {
|
|
38
|
-
fatal('No envgit project found. Run envgit init first, or clone a repo that uses envgit.');
|
|
39
|
-
}
|
|
40
|
-
const keyPath = saveKey(projectRoot, key);
|
|
41
|
-
ok(`Key saved for this project`);
|
|
42
|
-
console.log(dim(` Stored at: ${keyPath}`));
|
|
43
|
-
console.log(dim(` Hint: ${key.slice(0, 8)}`));
|
|
44
|
-
console.log('');
|
|
45
|
-
console.log(dim('Run `envgit verify` to confirm it works.'));
|
|
46
|
-
console.log('');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Generate new key
|
|
51
|
-
const key = generateKey();
|
|
52
|
-
const hint = key.slice(0, 8);
|
|
53
|
-
|
|
54
|
-
if (projectRoot) {
|
|
55
|
-
const keyPath = saveKey(projectRoot, key);
|
|
56
|
-
ok(`New key generated`);
|
|
57
|
-
console.log(dim(` Stored at: ${keyPath}`));
|
|
58
|
-
} else {
|
|
59
|
-
console.log('');
|
|
60
|
-
console.log(bold('Generated key (no project found — not saved):'));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log(bold('Key:'));
|
|
65
|
-
console.log(` ${key}`);
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log(dim(`Hint (first 8 chars): ${hint}`));
|
|
68
|
-
console.log(dim('Share via a secure channel (not git, not chat).'));
|
|
69
|
-
console.log(dim('Teammate saves it with: envgit keygen --set <key>'));
|
|
70
|
-
console.log('');
|
|
71
|
-
}
|