envx-cli-tmr 1.0.8
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/bin/envx.js +45 -0
- package/commands/create.js +36 -0
- package/commands/init.js +38 -0
- package/commands/invite.js +28 -0
- package/commands/join.js +54 -0
- package/commands/login.js +100 -0
- package/commands/projects.js +54 -0
- package/commands/pull.js +62 -0
- package/commands/push.js +57 -0
- package/lib/api.js +36 -0
- package/lib/auth.js +13 -0
- package/package.json +25 -0
package/bin/envx.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { login } from "./commands/login.js";
|
|
5
|
+
import { projects } from "./commands/projects.js";
|
|
6
|
+
import { pull } from "./commands/pull.js";
|
|
7
|
+
import { push } from "./commands/push.js";
|
|
8
|
+
import { join } from "./commands/join.js";
|
|
9
|
+
import { invite } from "./commands/invite.js";
|
|
10
|
+
import { create } from "./commands/create.js";
|
|
11
|
+
import { init } from "./commands/init.js"; // 1. Added init import
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program.name("envx").description("envx CLI").version("1.0.0");
|
|
16
|
+
|
|
17
|
+
program.command("login").action(login);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command("create")
|
|
21
|
+
.description("Create a new project space")
|
|
22
|
+
.action(create);
|
|
23
|
+
|
|
24
|
+
program.command("projects [nameOrId]").action(projects);
|
|
25
|
+
|
|
26
|
+
program.command("pull [projectId]").action(pull);
|
|
27
|
+
|
|
28
|
+
program.command("push [projectId]").action(push);
|
|
29
|
+
|
|
30
|
+
program.command("join").action(join);
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command("invite <projectId> <email>")
|
|
34
|
+
.action(invite);
|
|
35
|
+
|
|
36
|
+
// 2. Registered the init command
|
|
37
|
+
program
|
|
38
|
+
.command("init <projectId>")
|
|
39
|
+
.description("Generate a safe .env.example template for the project")
|
|
40
|
+
.action(init);
|
|
41
|
+
|
|
42
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
43
|
+
console.error("โ CLI Error:", err.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// commands/create.js
|
|
2
|
+
import { input } from "@inquirer/prompts";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
|
|
5
|
+
export async function create() {
|
|
6
|
+
try {
|
|
7
|
+
const name = await input({
|
|
8
|
+
message: "Enter the name for your new project:",
|
|
9
|
+
validate: (value) => {
|
|
10
|
+
if (value.trim().length === 0) return "Project name cannot be empty.";
|
|
11
|
+
return true;
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const res = await api("/api/projects", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: JSON.stringify({ name }),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (res.success && res.project) {
|
|
21
|
+
console.log(`\nโ
Project '${res.project.name}' created successfully!`);
|
|
22
|
+
console.log(`๐ Project ID: ${res.project.id}\n`);
|
|
23
|
+
|
|
24
|
+
console.log(`Next steps:`);
|
|
25
|
+
console.log(` 1. Run 'envx init ${res.project.id}' to create a local .env.example`);
|
|
26
|
+
console.log(` 2. Add your secrets to your local .env file`);
|
|
27
|
+
console.log(` 3. Run 'envx push ${res.project.id}' to upload them to the cloud`);
|
|
28
|
+
|
|
29
|
+
return res.project.id;
|
|
30
|
+
} else {
|
|
31
|
+
console.log(`โ Failed to create project: ${res.message || "Unknown error"}`);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error("โ Error creating project:", err.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/commands/init.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
|
|
5
|
+
export async function init(projectId) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await api(`/api/secrets/${projectId}`);
|
|
8
|
+
const secrets = res.secrets;
|
|
9
|
+
|
|
10
|
+
if (!secrets || Object.keys(secrets).length === 0) {
|
|
11
|
+
console.log("โ ๏ธ No secrets found to create a template from.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let envExample = "";
|
|
16
|
+
|
|
17
|
+
// Convert the JSON object keys into a blank .env format (KEY=)
|
|
18
|
+
if (typeof secrets === "object" && secrets !== null) {
|
|
19
|
+
envExample = Object.keys(secrets)
|
|
20
|
+
.map((key) => `${key}=`)
|
|
21
|
+
.join("\n");
|
|
22
|
+
} else if (typeof secrets === "string") {
|
|
23
|
+
// Fallback just in case it ever returns a string
|
|
24
|
+
envExample = secrets
|
|
25
|
+
.split("\n")
|
|
26
|
+
.map((line) => {
|
|
27
|
+
const [key] = line.split("=");
|
|
28
|
+
return `${key}=`;
|
|
29
|
+
})
|
|
30
|
+
.join("\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(path.join(process.cwd(), ".env.example"), envExample);
|
|
34
|
+
console.log("๐งพ .env.example created successfully!");
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error("โ Init failed:", err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { api } from "../lib/api.js";
|
|
2
|
+
|
|
3
|
+
export async function invite(projectId, email) {
|
|
4
|
+
try {
|
|
5
|
+
if (!projectId || !email) {
|
|
6
|
+
console.log("โ Usage: envx invite <projectId> <email>");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const res = await api("/api/projects/invite", {
|
|
11
|
+
method: "POST",
|
|
12
|
+
body: JSON.stringify({ projectId, email }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!res.success) {
|
|
16
|
+
console.log(`โ Invite failed: ${res.message}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`\nโ
Invite created successfully!\n`);
|
|
21
|
+
console.log(`๐ง Email: ${email}`);
|
|
22
|
+
console.log(`๐ Invite Token:\n${res.inviteToken}\n`);
|
|
23
|
+
console.log(`โฐ Expires: ${new Date(res.expiresAt).toLocaleString()}\n`);
|
|
24
|
+
console.log("Share this token with the user to join your project.\n");
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("โ Invite failed:", err.message);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/commands/join.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { select, Separator } from "@inquirer/prompts";
|
|
2
|
+
import { api } from "../lib/api.js";
|
|
3
|
+
|
|
4
|
+
export async function join(inviteToken) {
|
|
5
|
+
try {
|
|
6
|
+
// If invite token is provided directly
|
|
7
|
+
if (inviteToken) {
|
|
8
|
+
await api("/api/projects/accept-invite", {
|
|
9
|
+
method: "POST",
|
|
10
|
+
body: JSON.stringify({ token: inviteToken }),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
console.log("โ
Joined project successfully!");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// No token provided โ fetch and show dropdown
|
|
18
|
+
const res = await api("/api/invites");
|
|
19
|
+
const invites = res.invites ?? [];
|
|
20
|
+
|
|
21
|
+
if (!invites.length) {
|
|
22
|
+
console.log("๐ญ No pending invites found.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const choices = invites.map((i) => ({
|
|
27
|
+
name: `${i.projectName} (${i.email})`,
|
|
28
|
+
value: i.token,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
choices.push(new Separator());
|
|
32
|
+
choices.push({ name: "โ Cancel", value: "cancel" });
|
|
33
|
+
|
|
34
|
+
const selectedToken = await select({
|
|
35
|
+
message: "Select an invite to join:",
|
|
36
|
+
choices: choices,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (selectedToken === "cancel") {
|
|
40
|
+
console.log("Operation cancelled.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await api("/api/projects/accept-invite", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: JSON.stringify({ token: selectedToken }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const project = invites.find(i => i.token === selectedToken);
|
|
50
|
+
console.log(`โ
Joined ${project.projectName} successfully!`);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("โ Failed to join project:", err.message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import open from "open";
|
|
3
|
+
import { saveToken } from "../lib/auth.js";
|
|
4
|
+
|
|
5
|
+
const PORT = 5544;
|
|
6
|
+
|
|
7
|
+
export async function login() {
|
|
8
|
+
try {
|
|
9
|
+
const callbackUrl = `http://localhost:${PORT}/callback`;
|
|
10
|
+
|
|
11
|
+
const server = http.createServer((req, res) => {
|
|
12
|
+
const url = new URL(req.url, callbackUrl);
|
|
13
|
+
|
|
14
|
+
if (url.pathname === "/callback") {
|
|
15
|
+
const token = url.searchParams.get("token");
|
|
16
|
+
|
|
17
|
+
if (!token) {
|
|
18
|
+
res.writeHead(400, {
|
|
19
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
20
|
+
});
|
|
21
|
+
res.end(`
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="utf-8">
|
|
26
|
+
<title>Login Failed</title>
|
|
27
|
+
<style>
|
|
28
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
|
29
|
+
.container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 10px 25px rgba(0,0,0,0.2); text-align: center; }
|
|
30
|
+
h1 { color: #d32f2f; margin-top: 0; }
|
|
31
|
+
p { color: #666; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="container">
|
|
36
|
+
<h1>โ Login Failed</h1>
|
|
37
|
+
<p>Missing authentication token. Please try again.</p>
|
|
38
|
+
</div>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
`);
|
|
42
|
+
server.close();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
saveToken(token);
|
|
47
|
+
|
|
48
|
+
res.writeHead(200, {
|
|
49
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
50
|
+
});
|
|
51
|
+
res.end(`
|
|
52
|
+
<!DOCTYPE html>
|
|
53
|
+
<html>
|
|
54
|
+
<head>
|
|
55
|
+
<meta charset="utf-8">
|
|
56
|
+
<title>Login Successful</title>
|
|
57
|
+
<style>
|
|
58
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
|
59
|
+
.container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 10px 25px rgba(0,0,0,0.2); text-align: center; }
|
|
60
|
+
h1 { color: #333; margin-top: 0; }
|
|
61
|
+
.emoji { font-size: 60px; margin-bottom: 20px; }
|
|
62
|
+
p { color: #666; margin: 10px 0; }
|
|
63
|
+
</style>
|
|
64
|
+
</head>
|
|
65
|
+
<body>
|
|
66
|
+
<div class="container">
|
|
67
|
+
<div class="emoji">โ
</div>
|
|
68
|
+
<h1>Login Successful!</h1>
|
|
69
|
+
<p>You have been authenticated.</p>
|
|
70
|
+
<p>You can now close this tab and return to your terminal.</p>
|
|
71
|
+
</div>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
server.close(() => process.exit(0));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
server.listen(PORT, () => {
|
|
81
|
+
console.log("๐ Starting login...");
|
|
82
|
+
console.log(`โณ Waiting for authentication callback on port ${PORT}...\n`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// IMPORTANT: send full callback URL in state
|
|
86
|
+
const state = `cli:${callbackUrl}`;
|
|
87
|
+
|
|
88
|
+
// โ
Updated to point to the live Render backend
|
|
89
|
+
const url = `https://envx-backend-j8nj.onrender.com/auth/github?state=${encodeURIComponent(
|
|
90
|
+
state
|
|
91
|
+
)}`;
|
|
92
|
+
|
|
93
|
+
await open(url);
|
|
94
|
+
|
|
95
|
+
console.log("๐ Browser opened. Please complete authentication.\n");
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error("โ Login error:", err.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { select, Separator } from "@inquirer/prompts";
|
|
2
|
+
import { api } from "../lib/api.js";
|
|
3
|
+
|
|
4
|
+
export async function projects(nameOrId) {
|
|
5
|
+
try {
|
|
6
|
+
const res = await api("/api/projects");
|
|
7
|
+
const projectsList = res.projects ?? [];
|
|
8
|
+
|
|
9
|
+
if (!projectsList.length) {
|
|
10
|
+
console.log("๐ No projects found. Create one or get an invite!");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Safety check: ensure nameOrId is actually a string, not a Commander object
|
|
15
|
+
if (nameOrId && typeof nameOrId === "string") {
|
|
16
|
+
const project = projectsList.find(
|
|
17
|
+
(p) => p.name.toLowerCase() === nameOrId.toLowerCase() || p.id === nameOrId
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (project) {
|
|
21
|
+
console.log(`โ
Selected Project: ${project.name}`);
|
|
22
|
+
return project.id;
|
|
23
|
+
} else {
|
|
24
|
+
console.log(`โ Project "${nameOrId}" not found.`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// No valid string argument provided โ show dropdown
|
|
30
|
+
const choices = projectsList.map((p) => ({
|
|
31
|
+
name: p.name,
|
|
32
|
+
value: p.id,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
choices.push(new Separator());
|
|
36
|
+
choices.push({ name: "โ Cancel", value: "cancel" });
|
|
37
|
+
|
|
38
|
+
const selectedId = await select({
|
|
39
|
+
message: "Select a project:",
|
|
40
|
+
choices: choices,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (selectedId === "cancel") {
|
|
44
|
+
console.log("Operation cancelled.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const selected = projectsList.find(p => p.id === selectedId);
|
|
49
|
+
console.log(`โ
Selected: ${selected.name}`);
|
|
50
|
+
return selectedId;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("โ Failed to fetch projects:", err.message);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/commands/pull.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { select, Separator } from "@inquirer/prompts";
|
|
4
|
+
import { api } from "../lib/api.js";
|
|
5
|
+
|
|
6
|
+
export async function pull(projectId) {
|
|
7
|
+
try {
|
|
8
|
+
// If no projectId โ prompt the user with a dropdown
|
|
9
|
+
if (!projectId) {
|
|
10
|
+
const res = await api("/api/projects");
|
|
11
|
+
const projects = res.projects ?? [];
|
|
12
|
+
|
|
13
|
+
if (!projects.length) {
|
|
14
|
+
console.log("๐ No projects found. Create or join a project first.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const choices = projects.map((p) => ({
|
|
19
|
+
name: p.name,
|
|
20
|
+
value: p.id,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
choices.push(new Separator());
|
|
24
|
+
choices.push({ name: "โ Cancel", value: "cancel" });
|
|
25
|
+
|
|
26
|
+
const selectedId = await select({
|
|
27
|
+
message: "Select project to pull secrets from:",
|
|
28
|
+
choices: choices,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (selectedId === "cancel") {
|
|
32
|
+
console.log("Operation cancelled.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
projectId = selectedId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const res = await api(`/api/secrets/${projectId}`);
|
|
40
|
+
|
|
41
|
+
if (!res.secrets || (typeof res.secrets === 'object' && Object.keys(res.secrets).length === 0)) {
|
|
42
|
+
console.log("โ ๏ธ No secrets found for this project.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let envContent = res.secrets;
|
|
47
|
+
|
|
48
|
+
// Convert JSON object back to .env format (KEY=VALUE)
|
|
49
|
+
if (typeof envContent === "object" && envContent !== null) {
|
|
50
|
+
envContent = Object.entries(envContent)
|
|
51
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
52
|
+
.join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
56
|
+
fs.writeFileSync(envPath, envContent);
|
|
57
|
+
|
|
58
|
+
console.log("โ
.env pulled successfully");
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error("โ Pull failed:", err.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
package/commands/push.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { select, Separator } from "@inquirer/prompts";
|
|
4
|
+
import { api } from "../lib/api.js";
|
|
5
|
+
|
|
6
|
+
export async function push(projectId) {
|
|
7
|
+
try {
|
|
8
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(envPath)) {
|
|
11
|
+
console.log("โ No .env file found in current directory");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// If no projectId provided, ask user to select
|
|
16
|
+
if (!projectId) {
|
|
17
|
+
const res = await api("/api/projects");
|
|
18
|
+
const projects = res.projects ?? [];
|
|
19
|
+
|
|
20
|
+
if (!projects.length) {
|
|
21
|
+
console.log("๐ No projects found. Create or join a project first.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const choices = projects.map((p) => ({
|
|
26
|
+
name: p.name,
|
|
27
|
+
value: p.id,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
choices.push(new Separator());
|
|
31
|
+
choices.push({ name: "โ Cancel", value: "cancel" });
|
|
32
|
+
|
|
33
|
+
const selectedId = await select({
|
|
34
|
+
message: "Select project to push secrets to:",
|
|
35
|
+
choices: choices,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (selectedId === "cancel") {
|
|
39
|
+
console.log("Operation cancelled.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
projectId = selectedId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const secrets = fs.readFileSync(envPath, "utf-8");
|
|
47
|
+
|
|
48
|
+
await api("/api/secrets", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: JSON.stringify({ projectId, secrets }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log("โ
.env pushed successfully");
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error("โ Push failed:", err.message);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import { getToken } from "./auth.js";
|
|
3
|
+
|
|
4
|
+
const API_BASE = "https://envx-backend-j8nj.onrender.com";
|
|
5
|
+
|
|
6
|
+
export async function api(path, options = {}) {
|
|
7
|
+
const token = getToken();
|
|
8
|
+
|
|
9
|
+
const headers = {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
...(options.headers || {}),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (token) {
|
|
15
|
+
headers.Authorization = `Bearer ${token}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
19
|
+
...options,
|
|
20
|
+
headers,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let data;
|
|
24
|
+
try {
|
|
25
|
+
data = await res.json();
|
|
26
|
+
} catch {
|
|
27
|
+
data = {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
console.error("API ERROR:", data);
|
|
32
|
+
throw new Error(data.message || `HTTP ${res.status}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return data;
|
|
36
|
+
}
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const TOKEN_PATH = path.join(process.env.HOME, ".envx-token");
|
|
5
|
+
|
|
6
|
+
export function saveToken(token) {
|
|
7
|
+
fs.writeFileSync(TOKEN_PATH, token);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getToken() {
|
|
11
|
+
if (!fs.existsSync(TOKEN_PATH)) return null;
|
|
12
|
+
return fs.readFileSync(TOKEN_PATH, "utf8");
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "envx-cli-tmr",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "CLI for envx secret manager",
|
|
5
|
+
"bin": {
|
|
6
|
+
"envx": "./bin/envx.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"No tests yet\""
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@inquirer/prompts": "^8.5.1",
|
|
17
|
+
"boxen": "^8.0.1",
|
|
18
|
+
"chalk": "^5.6.2",
|
|
19
|
+
"commander": "^14.0.3",
|
|
20
|
+
"inquirer": "^13.4.3",
|
|
21
|
+
"node-fetch": "^3.3.2",
|
|
22
|
+
"open": "^11.0.0",
|
|
23
|
+
"ora": "^9.4.0"
|
|
24
|
+
}
|
|
25
|
+
}
|