@lockr-dev/cli 1.0.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 +129 -0
- package/dist/api.d.ts +9 -0
- package/dist/api.js +54 -0
- package/dist/commands/auth.d.ts +10 -0
- package/dist/commands/auth.js +50 -0
- package/dist/commands/envs.d.ts +1 -0
- package/dist/commands/envs.js +10 -0
- package/dist/commands/pull.d.ts +8 -0
- package/dist/commands/pull.js +37 -0
- package/dist/commands/run.d.ts +5 -0
- package/dist/commands/run.js +41 -0
- package/dist/commands/sync.d.ts +9 -0
- package/dist/commands/sync.js +164 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +13 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +67 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Lockr CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for managing secrets with Lockr.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @lockr/cli
|
|
9
|
+
# or
|
|
10
|
+
npx @lockr/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Authentication
|
|
14
|
+
|
|
15
|
+
First, create an API token in the Lockr dashboard:
|
|
16
|
+
1. Go to **Integrations** → **API Tokens (Pull)**
|
|
17
|
+
2. Click **Create API Token**
|
|
18
|
+
3. Copy your token
|
|
19
|
+
|
|
20
|
+
Then authenticate:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
lockr auth login --token YOUR_API_TOKEN
|
|
24
|
+
# or set the environment variable
|
|
25
|
+
export LOCKR_API_TOKEN=YOUR_API_TOKEN
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
### Pull Secrets
|
|
31
|
+
|
|
32
|
+
Pull secrets from Lockr to your local environment:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Pull all secrets for production
|
|
36
|
+
lockr pull --env production
|
|
37
|
+
|
|
38
|
+
# Pull to a specific file
|
|
39
|
+
lockr pull --env production --output .env
|
|
40
|
+
|
|
41
|
+
# Pull specific keys only
|
|
42
|
+
lockr pull --env production --keys API_KEY,DATABASE_URL
|
|
43
|
+
|
|
44
|
+
# Output as JSON
|
|
45
|
+
lockr pull --env production --format json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Run with Secrets
|
|
49
|
+
|
|
50
|
+
Run a command with secrets injected as environment variables:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Run your app with production secrets
|
|
54
|
+
lockr run --env production -- npm start
|
|
55
|
+
|
|
56
|
+
# Run with staging secrets
|
|
57
|
+
lockr run --env staging -- docker-compose up
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### List Environments
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
lockr envs list
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Whoami
|
|
67
|
+
|
|
68
|
+
Check your current authentication:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
lockr whoami
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Create a `.lockrrc` file in your project root:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"project": "my-project",
|
|
81
|
+
"defaultEnv": "development"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CI/CD Usage
|
|
86
|
+
|
|
87
|
+
### GitHub Actions
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
steps:
|
|
91
|
+
- name: Pull secrets
|
|
92
|
+
run: npx @lockr/cli pull --env production --output .env
|
|
93
|
+
env:
|
|
94
|
+
LOCKR_API_TOKEN: ${{ secrets.LOCKR_API_TOKEN }}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### GitLab CI
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
deploy:
|
|
101
|
+
script:
|
|
102
|
+
- npx @lockr/cli pull --env production --output .env
|
|
103
|
+
- docker build -t myapp .
|
|
104
|
+
variables:
|
|
105
|
+
LOCKR_API_TOKEN: $LOCKR_API_TOKEN
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### CircleCI
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
jobs:
|
|
112
|
+
build:
|
|
113
|
+
steps:
|
|
114
|
+
- run:
|
|
115
|
+
name: Pull secrets
|
|
116
|
+
command: npx @lockr/cli pull --env production --output .env
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Environment Variables
|
|
120
|
+
|
|
121
|
+
| Variable | Description |
|
|
122
|
+
|----------|-------------|
|
|
123
|
+
| `LOCKR_API_TOKEN` | Your API token for authentication |
|
|
124
|
+
| `LOCKR_API_URL` | Custom API URL (default: production) |
|
|
125
|
+
| `LOCKR_PROJECT` | Default project to use |
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface PullOptions {
|
|
2
|
+
environment?: string;
|
|
3
|
+
keys?: string[];
|
|
4
|
+
format?: 'json' | 'env' | 'dotenv';
|
|
5
|
+
}
|
|
6
|
+
export declare function fetchSecrets(options: PullOptions): Promise<Record<string, string>>;
|
|
7
|
+
export declare function formatAsEnv(secrets: Record<string, string>): string;
|
|
8
|
+
export declare function formatAsJson(secrets: Record<string, string>): string;
|
|
9
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getApiToken, getApiUrl } from './config.js';
|
|
2
|
+
export async function fetchSecrets(options) {
|
|
3
|
+
const token = getApiToken();
|
|
4
|
+
if (!token) {
|
|
5
|
+
throw new Error('Not authenticated. Run `lockr auth login --token YOUR_TOKEN` or set LOCKR_API_TOKEN environment variable.');
|
|
6
|
+
}
|
|
7
|
+
const apiUrl = getApiUrl();
|
|
8
|
+
const url = new URL(`${apiUrl}/pull-secrets`);
|
|
9
|
+
if (options.environment) {
|
|
10
|
+
url.searchParams.set('environment', options.environment);
|
|
11
|
+
}
|
|
12
|
+
if (options.format) {
|
|
13
|
+
url.searchParams.set('format', options.format);
|
|
14
|
+
}
|
|
15
|
+
if (options.keys && options.keys.length > 0) {
|
|
16
|
+
url.searchParams.set('keys', options.keys.join(','));
|
|
17
|
+
}
|
|
18
|
+
const response = await fetch(url.toString(), {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: {
|
|
21
|
+
'X-API-Key': token,
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
27
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
28
|
+
}
|
|
29
|
+
// Handle different response formats
|
|
30
|
+
const contentType = response.headers.get('content-type');
|
|
31
|
+
if (contentType?.includes('text/plain')) {
|
|
32
|
+
// .env format - parse it
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
const secrets = {};
|
|
35
|
+
for (const line of text.split('\n')) {
|
|
36
|
+
const match = line.match(/^([^=]+)="(.*)"/);
|
|
37
|
+
if (match) {
|
|
38
|
+
secrets[match[1]] = match[2].replace(/\\"/g, '"');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return secrets;
|
|
42
|
+
}
|
|
43
|
+
// JSON format
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
return data.secrets;
|
|
46
|
+
}
|
|
47
|
+
export function formatAsEnv(secrets) {
|
|
48
|
+
return Object.entries(secrets)
|
|
49
|
+
.map(([key, value]) => `${key}="${value.replace(/"/g, '\\"')}"`)
|
|
50
|
+
.join('\n');
|
|
51
|
+
}
|
|
52
|
+
export function formatAsJson(secrets) {
|
|
53
|
+
return JSON.stringify(secrets, null, 2);
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { setApiToken, clearApiToken, getApiToken, getApiUrl } from '../config.js';
|
|
4
|
+
async function login(options) {
|
|
5
|
+
const token = options.token || process.env.LOCKR_API_TOKEN;
|
|
6
|
+
if (!token) {
|
|
7
|
+
console.error(chalk.red('No token provided.'));
|
|
8
|
+
console.log('\nUsage:');
|
|
9
|
+
console.log(' lockr auth login --token YOUR_API_TOKEN');
|
|
10
|
+
console.log(' # or');
|
|
11
|
+
console.log(' export LOCKR_API_TOKEN=YOUR_API_TOKEN && lockr auth login');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const spinner = ora('Validating token...').start();
|
|
15
|
+
try {
|
|
16
|
+
// Validate the token by making a request
|
|
17
|
+
const response = await fetch(`${getApiUrl()}/pull-secrets`, {
|
|
18
|
+
method: 'GET',
|
|
19
|
+
headers: {
|
|
20
|
+
'X-API-Key': token,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
// 404 is OK (no environment specified), 401/403 means bad token
|
|
24
|
+
if (response.status === 401 || response.status === 403) {
|
|
25
|
+
throw new Error('Invalid API token');
|
|
26
|
+
}
|
|
27
|
+
// Store the token
|
|
28
|
+
setApiToken(token);
|
|
29
|
+
spinner.succeed(chalk.green('Successfully authenticated!'));
|
|
30
|
+
console.log(chalk.dim('\nYour token has been saved. You can now use lockr commands.'));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
spinner.fail('Authentication failed');
|
|
34
|
+
console.error(chalk.red(error.message));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function logout() {
|
|
39
|
+
const token = getApiToken();
|
|
40
|
+
if (!token) {
|
|
41
|
+
console.log(chalk.yellow('Not currently authenticated.'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
clearApiToken();
|
|
45
|
+
console.log(chalk.green('Successfully logged out.'));
|
|
46
|
+
}
|
|
47
|
+
export const authCommand = {
|
|
48
|
+
login,
|
|
49
|
+
logout,
|
|
50
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function envsCommand(): Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export async function envsCommand() {
|
|
3
|
+
console.log(chalk.yellow('This command requires project context.'));
|
|
4
|
+
console.log(chalk.dim('\nEnvironments are fetched from your Lockr project.'));
|
|
5
|
+
console.log(chalk.dim('Common environments: development, staging, production'));
|
|
6
|
+
console.log('\nUsage:');
|
|
7
|
+
console.log(' lockr pull --env development');
|
|
8
|
+
console.log(' lockr pull --env staging');
|
|
9
|
+
console.log(' lockr pull --env production');
|
|
10
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fetchSecrets, formatAsEnv, formatAsJson } from '../api.js';
|
|
5
|
+
export async function pullCommand(options) {
|
|
6
|
+
const spinner = ora(`Pulling secrets from ${chalk.cyan(options.env)}...`).start();
|
|
7
|
+
try {
|
|
8
|
+
const keys = options.keys?.split(',').map(k => k.trim()).filter(Boolean);
|
|
9
|
+
const secrets = await fetchSecrets({
|
|
10
|
+
environment: options.env,
|
|
11
|
+
keys,
|
|
12
|
+
format: options.format === 'json' ? 'json' : 'env',
|
|
13
|
+
});
|
|
14
|
+
spinner.succeed(`Pulled ${chalk.green(Object.keys(secrets).length)} secrets`);
|
|
15
|
+
// Format output
|
|
16
|
+
let output;
|
|
17
|
+
if (options.format === 'json') {
|
|
18
|
+
output = formatAsJson(secrets);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
output = formatAsEnv(secrets);
|
|
22
|
+
}
|
|
23
|
+
// Write to file or stdout
|
|
24
|
+
if (options.output) {
|
|
25
|
+
fs.writeFileSync(options.output, output + '\n');
|
|
26
|
+
console.log(chalk.dim(`Written to ${options.output}`));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log('\n' + output);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
spinner.fail('Failed to pull secrets');
|
|
34
|
+
console.error(chalk.red(error.message));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { fetchSecrets } from '../api.js';
|
|
5
|
+
export async function runCommand(command, options) {
|
|
6
|
+
const spinner = ora(`Loading secrets from ${chalk.cyan(options.env)}...`).start();
|
|
7
|
+
try {
|
|
8
|
+
const secrets = await fetchSecrets({
|
|
9
|
+
environment: options.env,
|
|
10
|
+
});
|
|
11
|
+
spinner.succeed(`Loaded ${chalk.green(Object.keys(secrets).length)} secrets`);
|
|
12
|
+
// Get the command and args
|
|
13
|
+
const [cmd, ...args] = command;
|
|
14
|
+
if (!cmd) {
|
|
15
|
+
console.error(chalk.red('No command specified'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
console.log(chalk.dim(`Running: ${cmd} ${args.join(' ')}\n`));
|
|
19
|
+
// Spawn the process with secrets as environment variables
|
|
20
|
+
const child = spawn(cmd, args, {
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
...secrets,
|
|
24
|
+
},
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
shell: true,
|
|
27
|
+
});
|
|
28
|
+
child.on('error', (error) => {
|
|
29
|
+
console.error(chalk.red(`Failed to start process: ${error.message}`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
child.on('exit', (code) => {
|
|
33
|
+
process.exit(code ?? 0);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
spinner.fail('Failed to load secrets');
|
|
38
|
+
console.error(chalk.red(error.message));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getApiToken, getApiUrl } from '../config.js';
|
|
4
|
+
async function fetchIntegrations() {
|
|
5
|
+
const token = getApiToken();
|
|
6
|
+
const apiUrl = getApiUrl();
|
|
7
|
+
const response = await fetch(`${apiUrl}/integrations`, {
|
|
8
|
+
method: 'GET',
|
|
9
|
+
headers: {
|
|
10
|
+
'X-API-Key': token,
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
16
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
return response.json();
|
|
19
|
+
}
|
|
20
|
+
async function syncIntegration(integrationId, environment) {
|
|
21
|
+
const token = getApiToken();
|
|
22
|
+
const apiUrl = getApiUrl();
|
|
23
|
+
const url = new URL(`${apiUrl}/sync-integration`);
|
|
24
|
+
url.searchParams.set('integration_id', integrationId);
|
|
25
|
+
if (environment) {
|
|
26
|
+
url.searchParams.set('environment', environment);
|
|
27
|
+
}
|
|
28
|
+
const response = await fetch(url.toString(), {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'X-API-Key': token,
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
37
|
+
return {
|
|
38
|
+
integration_id: integrationId,
|
|
39
|
+
integration_name: 'Unknown',
|
|
40
|
+
status: 'error',
|
|
41
|
+
error: error.error || `HTTP ${response.status}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return response.json();
|
|
45
|
+
}
|
|
46
|
+
export async function syncCommand(options) {
|
|
47
|
+
const token = getApiToken();
|
|
48
|
+
if (!token) {
|
|
49
|
+
console.error(chalk.red('✗ Not authenticated.'));
|
|
50
|
+
console.error(chalk.dim('Run `lockr auth login --token YOUR_TOKEN` or set LOCKR_API_TOKEN'));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const spinner = ora('Fetching integrations...').start();
|
|
54
|
+
try {
|
|
55
|
+
const integrations = await fetchIntegrations();
|
|
56
|
+
if (integrations.length === 0) {
|
|
57
|
+
spinner.fail('No integrations found');
|
|
58
|
+
console.log(chalk.dim('\nConnect integrations at https://lockrdev.com/integrations'));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Filter integrations if specific one requested
|
|
62
|
+
let targetIntegrations = integrations.filter(i => i.status === 'connected');
|
|
63
|
+
if (options.integration) {
|
|
64
|
+
targetIntegrations = targetIntegrations.filter(i => i.name.toLowerCase().includes(options.integration.toLowerCase()) ||
|
|
65
|
+
i.provider.toLowerCase() === options.integration.toLowerCase());
|
|
66
|
+
if (targetIntegrations.length === 0) {
|
|
67
|
+
spinner.fail(`No matching integration found: ${options.integration}`);
|
|
68
|
+
console.log(chalk.dim('\nAvailable integrations:'));
|
|
69
|
+
integrations.forEach(i => {
|
|
70
|
+
console.log(chalk.dim(` - ${i.name} (${i.provider})`));
|
|
71
|
+
});
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!options.all && targetIntegrations.length > 1 && !options.integration) {
|
|
76
|
+
spinner.stop();
|
|
77
|
+
console.log(chalk.yellow('⚠ Multiple integrations found. Use --all to sync all, or --integration to specify one.\n'));
|
|
78
|
+
console.log('Available integrations:');
|
|
79
|
+
targetIntegrations.forEach(i => {
|
|
80
|
+
console.log(` ${chalk.cyan(i.name)} ${chalk.dim(`(${i.provider})`)}`);
|
|
81
|
+
});
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (options.dryRun) {
|
|
85
|
+
spinner.stop();
|
|
86
|
+
console.log(chalk.blue('ℹ Dry run mode - no changes will be made\n'));
|
|
87
|
+
console.log('Would sync to:');
|
|
88
|
+
targetIntegrations.forEach(i => {
|
|
89
|
+
console.log(` ${chalk.cyan(i.name)} ${chalk.dim(`(${i.provider})`)}`);
|
|
90
|
+
});
|
|
91
|
+
if (options.env) {
|
|
92
|
+
console.log(chalk.dim(`\nEnvironment: ${options.env}`));
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
spinner.text = `Syncing secrets to ${targetIntegrations.length} integration(s)...`;
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const integration of targetIntegrations) {
|
|
99
|
+
spinner.text = `Syncing to ${integration.name}...`;
|
|
100
|
+
const result = await syncIntegration(integration.id, options.env);
|
|
101
|
+
result.integration_name = integration.name;
|
|
102
|
+
results.push(result);
|
|
103
|
+
}
|
|
104
|
+
spinner.stop();
|
|
105
|
+
// Print results
|
|
106
|
+
console.log(chalk.bold('\nSync Results:\n'));
|
|
107
|
+
let successCount = 0;
|
|
108
|
+
let errorCount = 0;
|
|
109
|
+
for (const result of results) {
|
|
110
|
+
if (result.status === 'success') {
|
|
111
|
+
successCount++;
|
|
112
|
+
console.log(chalk.green('✓'), chalk.bold(result.integration_name), chalk.dim(`- ${result.secrets_synced || 0} secrets synced`));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
errorCount++;
|
|
116
|
+
console.log(chalk.red('✗'), chalk.bold(result.integration_name), chalk.red(`- ${result.error}`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log('');
|
|
120
|
+
if (errorCount > 0) {
|
|
121
|
+
console.log(chalk.yellow(`⚠ ${successCount} succeeded, ${errorCount} failed`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(chalk.green(`✓ Successfully synced to ${successCount} integration(s)`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
spinner.fail('Sync failed');
|
|
130
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// List available integrations
|
|
135
|
+
export async function listIntegrationsCommand() {
|
|
136
|
+
const token = getApiToken();
|
|
137
|
+
if (!token) {
|
|
138
|
+
console.error(chalk.red('✗ Not authenticated.'));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
const spinner = ora('Fetching integrations...').start();
|
|
142
|
+
try {
|
|
143
|
+
const integrations = await fetchIntegrations();
|
|
144
|
+
spinner.stop();
|
|
145
|
+
if (integrations.length === 0) {
|
|
146
|
+
console.log(chalk.yellow('No integrations configured'));
|
|
147
|
+
console.log(chalk.dim('\nConnect integrations at https://lockrdev.com/integrations'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(chalk.bold('\nConfigured Integrations:\n'));
|
|
151
|
+
for (const integration of integrations) {
|
|
152
|
+
const statusIcon = integration.status === 'connected'
|
|
153
|
+
? chalk.green('●')
|
|
154
|
+
: chalk.yellow('○');
|
|
155
|
+
console.log(` ${statusIcon} ${chalk.bold(integration.name)}`, chalk.dim(`(${integration.provider})`), chalk.dim(`- ${integration.status}`));
|
|
156
|
+
}
|
|
157
|
+
console.log('');
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
spinner.fail('Failed to fetch integrations');
|
|
161
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function whoamiCommand(): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getApiToken, getApiUrl } from '../config.js';
|
|
3
|
+
export async function whoamiCommand() {
|
|
4
|
+
const token = getApiToken();
|
|
5
|
+
if (!token) {
|
|
6
|
+
console.log(chalk.yellow('Not authenticated.'));
|
|
7
|
+
console.log(chalk.dim('\nRun `lockr auth login --token YOUR_TOKEN` to authenticate.'));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.log(chalk.green('✓ Authenticated'));
|
|
11
|
+
console.log(chalk.dim(`Token: ${token.substring(0, 8)}...`));
|
|
12
|
+
console.log(chalk.dim(`API URL: ${getApiUrl()}`));
|
|
13
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
interface Config {
|
|
3
|
+
apiToken?: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
project?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const config: Conf<Config>;
|
|
8
|
+
export declare function getApiToken(): string | undefined;
|
|
9
|
+
export declare function setApiToken(token: string): void;
|
|
10
|
+
export declare function clearApiToken(): void;
|
|
11
|
+
export declare function getApiUrl(): string;
|
|
12
|
+
export declare function getProject(): string | undefined;
|
|
13
|
+
export { config };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
const config = new Conf({
|
|
3
|
+
projectName: 'lockr-cli',
|
|
4
|
+
defaults: {
|
|
5
|
+
apiUrl: 'https://cqtzvcifscaqxuieqhsn.supabase.co/functions/v1',
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
export function getApiToken() {
|
|
9
|
+
return process.env.LOCKR_API_TOKEN || config.get('apiToken');
|
|
10
|
+
}
|
|
11
|
+
export function setApiToken(token) {
|
|
12
|
+
config.set('apiToken', token);
|
|
13
|
+
}
|
|
14
|
+
export function clearApiToken() {
|
|
15
|
+
config.delete('apiToken');
|
|
16
|
+
}
|
|
17
|
+
export function getApiUrl() {
|
|
18
|
+
return process.env.LOCKR_API_URL || config.get('apiUrl');
|
|
19
|
+
}
|
|
20
|
+
export function getProject() {
|
|
21
|
+
return process.env.LOCKR_PROJECT || config.get('project');
|
|
22
|
+
}
|
|
23
|
+
export { config };
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { pullCommand } from './commands/pull.js';
|
|
4
|
+
import { runCommand } from './commands/run.js';
|
|
5
|
+
import { authCommand } from './commands/auth.js';
|
|
6
|
+
import { envsCommand } from './commands/envs.js';
|
|
7
|
+
import { whoamiCommand } from './commands/whoami.js';
|
|
8
|
+
import { syncCommand, listIntegrationsCommand } from './commands/sync.js';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name('lockr')
|
|
12
|
+
.description('CLI for managing secrets with Lockr')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
// Pull secrets command
|
|
15
|
+
program
|
|
16
|
+
.command('pull')
|
|
17
|
+
.description('Pull secrets from Lockr')
|
|
18
|
+
.option('-e, --env <environment>', 'Environment to pull from', 'development')
|
|
19
|
+
.option('-o, --output <file>', 'Output file path (default: stdout)')
|
|
20
|
+
.option('-f, --format <format>', 'Output format: env, json, dotenv', 'env')
|
|
21
|
+
.option('-k, --keys <keys>', 'Comma-separated list of specific keys to pull')
|
|
22
|
+
.action(pullCommand);
|
|
23
|
+
// Run with secrets command
|
|
24
|
+
program
|
|
25
|
+
.command('run')
|
|
26
|
+
.description('Run a command with secrets injected as environment variables')
|
|
27
|
+
.option('-e, --env <environment>', 'Environment to use', 'development')
|
|
28
|
+
.argument('<command...>', 'Command to run')
|
|
29
|
+
.action(runCommand);
|
|
30
|
+
// Auth commands
|
|
31
|
+
const auth = program
|
|
32
|
+
.command('auth')
|
|
33
|
+
.description('Authentication commands');
|
|
34
|
+
auth
|
|
35
|
+
.command('login')
|
|
36
|
+
.description('Authenticate with Lockr')
|
|
37
|
+
.option('-t, --token <token>', 'API token')
|
|
38
|
+
.action(authCommand.login);
|
|
39
|
+
auth
|
|
40
|
+
.command('logout')
|
|
41
|
+
.description('Remove stored credentials')
|
|
42
|
+
.action(authCommand.logout);
|
|
43
|
+
// Environments command
|
|
44
|
+
program
|
|
45
|
+
.command('envs')
|
|
46
|
+
.description('List available environments')
|
|
47
|
+
.action(envsCommand);
|
|
48
|
+
// Whoami command
|
|
49
|
+
program
|
|
50
|
+
.command('whoami')
|
|
51
|
+
.description('Show current authentication status')
|
|
52
|
+
.action(whoamiCommand);
|
|
53
|
+
// Sync command
|
|
54
|
+
program
|
|
55
|
+
.command('sync')
|
|
56
|
+
.description('Sync secrets to connected integrations (for CI/CD)')
|
|
57
|
+
.option('-e, --env <environment>', 'Environment to sync from', 'production')
|
|
58
|
+
.option('-i, --integration <name>', 'Specific integration to sync to')
|
|
59
|
+
.option('-a, --all', 'Sync to all connected integrations')
|
|
60
|
+
.option('--dry-run', 'Show what would be synced without making changes')
|
|
61
|
+
.action(syncCommand);
|
|
62
|
+
// Integrations command
|
|
63
|
+
program
|
|
64
|
+
.command('integrations')
|
|
65
|
+
.description('List configured integrations')
|
|
66
|
+
.action(listIntegrationsCommand);
|
|
67
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lockr-dev/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Command-line interface for Lockr secrets management - securely pull, sync, and inject environment variables",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"lockr": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "ts-node src/index.ts",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"secrets",
|
|
23
|
+
"environment",
|
|
24
|
+
"cli",
|
|
25
|
+
"lockr",
|
|
26
|
+
"env",
|
|
27
|
+
"configuration",
|
|
28
|
+
"dotenv",
|
|
29
|
+
"secrets-management",
|
|
30
|
+
"environment-variables",
|
|
31
|
+
"devops",
|
|
32
|
+
"ci-cd"
|
|
33
|
+
],
|
|
34
|
+
"author": "LockrDev <hello@lockrdev.com>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"homepage": "https://lockrdev.com",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/lockrdev/cli/issues"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"commander": "^11.1.0",
|
|
42
|
+
"chalk": "^5.3.0",
|
|
43
|
+
"ora": "^8.0.1",
|
|
44
|
+
"conf": "^12.0.0",
|
|
45
|
+
"dotenv": "^16.3.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.10.0",
|
|
49
|
+
"typescript": "^5.3.0",
|
|
50
|
+
"ts-node": "^10.9.2"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/lockrdev/cli.git"
|
|
58
|
+
}
|
|
59
|
+
}
|