@dinanathdash/envault 1.0.2 → 1.1.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/.env +20 -0
- package/CHANGELOG.md +7 -0
- package/README.md +0 -4
- package/bin/envault.js +6 -1
- package/package.json +4 -1
- package/release.config.js +1 -1
- package/src/commands/deploy.js +53 -0
- package/src/commands/init.js +36 -7
- package/src/commands/login.js +10 -1
- package/src/commands/pull.js +41 -2
- package/src/lib/api.js +23 -1
package/.env
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Environment Variables Example
|
|
2
|
+
|
|
3
|
+
Copy this file to `.env.local` and fill in your values.
|
|
4
|
+
|
|
5
|
+
## Supabase Configuration
|
|
6
|
+
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here
|
|
7
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
|
8
|
+
|
|
9
|
+
## Encryption Key (Required)
|
|
10
|
+
# Generate a new key with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
11
|
+
# IMPORTANT: Keep this secret and never commit it to version control
|
|
12
|
+
# If you lose this key, all encrypted data becomes unrecoverable
|
|
13
|
+
ENCRYPTION_KEY=your_64_character_hex_encryption_key_here
|
|
14
|
+
|
|
15
|
+
## Supabase Service Role Key (Required)
|
|
16
|
+
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key_here
|
|
17
|
+
|
|
18
|
+
# Redis (Upstash)
|
|
19
|
+
UPSTASH_REDIS_REST_URL=your_upstash_redis_rest_url_here
|
|
20
|
+
UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_rest_token_here
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/DinanathDash/Envault/compare/v1.0.2...v1.1.0) (2026-02-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Implement CLI device authentication, add new CLI API routes for projects and secrets, and enhance CLI commands with improved warnings. ([e4871c4](https://github.com/DinanathDash/Envault/commit/e4871c453d7e3974e94505e1f65014350a15a53a))
|
|
7
|
+
|
|
1
8
|
## [1.0.2](https://github.com/DinanathDash/Envault/compare/v1.0.1...v1.0.2) (2026-02-01)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
package/bin/envault.js
CHANGED
|
@@ -7,6 +7,10 @@ import { login } from '../src/commands/login.js';
|
|
|
7
7
|
import { init } from '../src/commands/init.js';
|
|
8
8
|
import { deploy } from '../src/commands/deploy.js';
|
|
9
9
|
import { pull } from '../src/commands/pull.js';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const pkg = require('../package.json');
|
|
10
14
|
|
|
11
15
|
const program = new Command();
|
|
12
16
|
|
|
@@ -26,7 +30,7 @@ const showLogo = () => {
|
|
|
26
30
|
program
|
|
27
31
|
.name('envault')
|
|
28
32
|
.description('Cliff-side security for your environment variables')
|
|
29
|
-
.version(
|
|
33
|
+
.version(pkg.version)
|
|
30
34
|
.hook('preAction', (thisCommand) => {
|
|
31
35
|
// Show logo on all commands? Maybe too noisy.
|
|
32
36
|
// Let's show it only on help or specific ones.
|
|
@@ -50,6 +54,7 @@ program.command('deploy')
|
|
|
50
54
|
.description('Deploy local .env to Envault (Encrypt & Push)')
|
|
51
55
|
.option('-p, --project <id>', 'Project ID')
|
|
52
56
|
.option('--dry-run', 'Show what would change without pushing')
|
|
57
|
+
.option('-f, --force', 'Skip confirmation prompts')
|
|
53
58
|
.action(async (options) => {
|
|
54
59
|
await deploy(options);
|
|
55
60
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinanathdash/envault",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Envault CLI - Securely manage your environment variables",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,5 +37,8 @@
|
|
|
37
37
|
"@semantic-release/exec": "^7.1.0",
|
|
38
38
|
"@semantic-release/git": "^10.0.1",
|
|
39
39
|
"semantic-release": "^25.0.3"
|
|
40
|
+
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"tar": "^7.5.7"
|
|
40
43
|
}
|
|
41
44
|
}
|
package/release.config.js
CHANGED
|
@@ -13,7 +13,7 @@ const config = {
|
|
|
13
13
|
[
|
|
14
14
|
'@semantic-release/git',
|
|
15
15
|
{
|
|
16
|
-
assets: ['package.json', 'CHANGELOG.md', 'README.md'],
|
|
16
|
+
assets: ['package.json', 'package-lock.json', 'CHANGELOG.md', 'README.md'],
|
|
17
17
|
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
|
18
18
|
},
|
|
19
19
|
],
|
package/src/commands/deploy.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import boxen from 'boxen';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
1
3
|
|
|
2
4
|
import fs from 'fs';
|
|
3
5
|
import dotenv from 'dotenv';
|
|
@@ -41,6 +43,57 @@ export async function deploy(options) {
|
|
|
41
43
|
return;
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
if (!options.force) {
|
|
47
|
+
let projectName = 'Envault';
|
|
48
|
+
try {
|
|
49
|
+
// Attempt to fetch project name. If fails, fallback to 'Envault'
|
|
50
|
+
// We use the list endpoint as we know it exists from init.js
|
|
51
|
+
const { data } = await api.get('/projects');
|
|
52
|
+
const projects = data.projects || data.data || [];
|
|
53
|
+
const project = projects.find(p => p.id === projectId);
|
|
54
|
+
if (project) {
|
|
55
|
+
projectName = project.name;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// Ignore error and use default
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://envault.tech';
|
|
62
|
+
|
|
63
|
+
console.log(boxen(
|
|
64
|
+
chalk.red.bold('WARNING: OVERWRITING REMOTE SECRETS') +
|
|
65
|
+
'\n\n' +
|
|
66
|
+
chalk.white('You are about to ') + chalk.red.bold('DEPLOY') + chalk.white(' local variables to your project:') +
|
|
67
|
+
'\n' +
|
|
68
|
+
chalk.cyan.bold(projectName) +
|
|
69
|
+
'\n\n' +
|
|
70
|
+
chalk.white('Existing secrets in the project will be ') + chalk.red.bold('OVERWRITTEN') + chalk.white(' by values in your .env.') +
|
|
71
|
+
'\n\n' +
|
|
72
|
+
chalk.dim('We recommend checking the dashboard for differences:') +
|
|
73
|
+
'\n' +
|
|
74
|
+
chalk.cyan(`${appUrl}/projects/${projectId}`),
|
|
75
|
+
{
|
|
76
|
+
padding: 1,
|
|
77
|
+
margin: 1,
|
|
78
|
+
borderStyle: 'double',
|
|
79
|
+
borderColor: 'red',
|
|
80
|
+
title: 'Deploy Warning',
|
|
81
|
+
titleAlignment: 'center'
|
|
82
|
+
}
|
|
83
|
+
));
|
|
84
|
+
|
|
85
|
+
const { confirm } = await inquirer.prompt([{
|
|
86
|
+
type: 'confirm',
|
|
87
|
+
name: 'confirm',
|
|
88
|
+
message: `Are you sure you want to deploy ${secrets.length} secrets to the project?`,
|
|
89
|
+
default: false
|
|
90
|
+
}]);
|
|
91
|
+
if (!confirm) {
|
|
92
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
44
97
|
const spinner = ora('Encrypting and deploying secrets...').start();
|
|
45
98
|
|
|
46
99
|
try {
|
package/src/commands/init.js
CHANGED
|
@@ -43,17 +43,46 @@ export async function init() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (projects.length === 0) {
|
|
46
|
-
console.log(chalk.yellow('No projects found.
|
|
47
|
-
return;
|
|
46
|
+
console.log(chalk.yellow('No existing projects found.'));
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
const {
|
|
49
|
+
const { selectedProjectId } = await inquirer.prompt([{
|
|
51
50
|
type: 'list',
|
|
52
|
-
name: '
|
|
51
|
+
name: 'selectedProjectId',
|
|
53
52
|
message: 'Select the project to link:',
|
|
54
|
-
choices:
|
|
53
|
+
choices: [
|
|
54
|
+
new inquirer.Separator(),
|
|
55
|
+
{ name: '+ Create New Project', value: 'CREATE_NEW' },
|
|
56
|
+
new inquirer.Separator(),
|
|
57
|
+
...projects.map(p => ({ name: p.name, value: p.id }))
|
|
58
|
+
]
|
|
55
59
|
}]);
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
let projectId = selectedProjectId;
|
|
62
|
+
|
|
63
|
+
if (selectedProjectId === 'CREATE_NEW') {
|
|
64
|
+
const { newProjectName } = await inquirer.prompt([{
|
|
65
|
+
type: 'input',
|
|
66
|
+
name: 'newProjectName',
|
|
67
|
+
message: 'Enter name for the new project:',
|
|
68
|
+
validate: input => input.trim().length > 0 ? true : 'Project name cannot be empty'
|
|
69
|
+
}]);
|
|
70
|
+
|
|
71
|
+
const createSpinner = ora('Creating project...').start();
|
|
72
|
+
try {
|
|
73
|
+
const { data } = await api.post('/projects', { name: newProjectName });
|
|
74
|
+
projectId = data.project.id;
|
|
75
|
+
createSpinner.succeed(chalk.green(`Project "${data.project.name}" created!`));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
createSpinner.fail('Failed to create project.');
|
|
78
|
+
console.error(chalk.red(handleApiError(error)));
|
|
79
|
+
return; // Exit if creation fails
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Only write config if we have a valid projectId
|
|
84
|
+
if (projectId) {
|
|
85
|
+
fs.writeFileSync('envault.json', JSON.stringify({ projectId }, null, 2));
|
|
86
|
+
console.log(chalk.green(`\n✔ Project linked! (ID: ${projectId})`));
|
|
87
|
+
}
|
|
59
88
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -5,6 +5,7 @@ import ora from 'ora';
|
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import { api, handleApiError } from '../lib/api.js';
|
|
7
7
|
import { setToken } from '../lib/config.js';
|
|
8
|
+
import os from 'os';
|
|
8
9
|
|
|
9
10
|
export async function login() {
|
|
10
11
|
console.log(chalk.blue(' Starting Device Authentication Flow...\n'));
|
|
@@ -13,8 +14,16 @@ export async function login() {
|
|
|
13
14
|
const spinner = ora('Contacting Envault servers...').start();
|
|
14
15
|
let deviceCode, userCode, verificationUri, interval;
|
|
15
16
|
|
|
17
|
+
const deviceInfo = {
|
|
18
|
+
hostname: os.hostname(),
|
|
19
|
+
platform: os.platform(),
|
|
20
|
+
release: os.release(),
|
|
21
|
+
type: os.type(),
|
|
22
|
+
arch: os.arch()
|
|
23
|
+
};
|
|
24
|
+
|
|
16
25
|
try {
|
|
17
|
-
const { data } = await api.post('/auth/device/code');
|
|
26
|
+
const { data } = await api.post('/auth/device/code', { device_info: deviceInfo });
|
|
18
27
|
deviceCode = data.device_code;
|
|
19
28
|
userCode = data.user_code; // 8-char
|
|
20
29
|
verificationUri = data.verification_uri;
|
package/src/commands/pull.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
+
import boxen from 'boxen';
|
|
6
|
+
|
|
5
7
|
import inquirer from 'inquirer';
|
|
6
8
|
import { api, handleApiError } from '../lib/api.js';
|
|
7
9
|
|
|
@@ -22,13 +24,50 @@ export async function pull(options) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
if (fs.existsSync('.env') && !options.force) {
|
|
27
|
+
let projectName = 'Envault';
|
|
28
|
+
try {
|
|
29
|
+
const { data } = await api.get('/projects');
|
|
30
|
+
const projects = data.projects || data.data || [];
|
|
31
|
+
const project = projects.find(p => p.id === projectId);
|
|
32
|
+
if (project) {
|
|
33
|
+
projectName = project.name;
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Ignore
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://envault.tech';
|
|
40
|
+
|
|
41
|
+
console.log(boxen(
|
|
42
|
+
chalk.red.bold('WARNING: POTENTIAL DATA LOSS') +
|
|
43
|
+
'\n\n' +
|
|
44
|
+
chalk.white('You are about to ') + chalk.red.bold('OVERWRITE') + chalk.white(' your local ') + chalk.yellow('.env') + chalk.white(' file.') +
|
|
45
|
+
'\n\n' +
|
|
46
|
+
chalk.white('Any local changes not synced to ') + chalk.cyan.bold(projectName) + chalk.white(' will be ') + chalk.red.bold('PERMANENTLY LOST.') +
|
|
47
|
+
'\n\n' +
|
|
48
|
+
chalk.dim('We recommend checking the dashboard for differences:') +
|
|
49
|
+
'\n' +
|
|
50
|
+
chalk.cyan(`${appUrl}/projects/${projectId}`),
|
|
51
|
+
{
|
|
52
|
+
padding: 1,
|
|
53
|
+
margin: 1,
|
|
54
|
+
borderStyle: 'double',
|
|
55
|
+
borderColor: 'red',
|
|
56
|
+
title: 'Sync Warning',
|
|
57
|
+
titleAlignment: 'center'
|
|
58
|
+
}
|
|
59
|
+
));
|
|
60
|
+
|
|
25
61
|
const { confirm } = await inquirer.prompt([{
|
|
26
62
|
type: 'confirm',
|
|
27
63
|
name: 'confirm',
|
|
28
|
-
message: '
|
|
64
|
+
message: 'Are you sure you want to overwrite your local .env file?',
|
|
29
65
|
default: false
|
|
30
66
|
}]);
|
|
31
|
-
if (!confirm)
|
|
67
|
+
if (!confirm) {
|
|
68
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
const spinner = ora('Fetching secrets...').start();
|
package/src/lib/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { getToken, getApiUrl } from './config.js';
|
|
3
|
+
import { getToken, getApiUrl, clearToken } from './config.js';
|
|
4
4
|
|
|
5
5
|
export const api = axios.create({
|
|
6
6
|
baseURL: getApiUrl(),
|
|
@@ -17,6 +17,28 @@ api.interceptors.request.use((config) => {
|
|
|
17
17
|
return config;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
// Handle token expiration
|
|
21
|
+
api.interceptors.response.use(
|
|
22
|
+
(response) => response,
|
|
23
|
+
(error) => {
|
|
24
|
+
if (error.response?.status === 401) {
|
|
25
|
+
const errorMsg = error.response?.data?.error;
|
|
26
|
+
if (errorMsg === 'token_expired') {
|
|
27
|
+
console.error('\n❌ Your session has expired (tokens are valid for 3 days).');
|
|
28
|
+
console.error('Please run "envault login" to authenticate again.\n');
|
|
29
|
+
clearToken();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
} else if (errorMsg === 'Invalid token' || errorMsg === 'Missing or invalid authorization header') {
|
|
32
|
+
console.error('\n❌ Authentication required.');
|
|
33
|
+
console.error('Please run "envault login" to authenticate.\n');
|
|
34
|
+
clearToken();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
20
42
|
export function handleApiError(error) {
|
|
21
43
|
if (error.response) {
|
|
22
44
|
return error.response.data.error || error.response.statusText;
|