teamsecret-pull 0.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/package.json +18 -0
- package/teamsecret-pull.js +93 -0
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "teamsecret-pull",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pull your team's secrets into .env — encrypted, versioned, access-controlled.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"teamsecret-pull": "teamsecret-pull.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"teamsecret-pull.js"
|
|
11
|
+
],
|
|
12
|
+
"keywords": ["env", "secrets", "dotenv", "environment", "team", "cli"],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/Tristan1075/teamsecret"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = ".teamsecret.json";
|
|
8
|
+
const KEY_FILE = join(homedir(), ".teamsecret");
|
|
9
|
+
const OUTPUT_FILE = ".env";
|
|
10
|
+
|
|
11
|
+
function fatal(msg) {
|
|
12
|
+
console.error(`\x1b[31m[teamsecret]\x1b[0m ${msg}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function info(msg) {
|
|
17
|
+
console.log(`\x1b[36m[teamsecret]\x1b[0m ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 0. Read environment from CLI argument
|
|
21
|
+
const environment = process.argv[2];
|
|
22
|
+
if (!environment) {
|
|
23
|
+
fatal(
|
|
24
|
+
`Missing environment argument.\n` +
|
|
25
|
+
` Usage: npx teamsecret-pull <environment>\n` +
|
|
26
|
+
` Example: npx teamsecret-pull development`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1. Read .teamsecret.json from project root
|
|
31
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
32
|
+
fatal(
|
|
33
|
+
`${CONFIG_FILE} not found. Create it with:\n` +
|
|
34
|
+
` { "url": "http://localhost:3000", "projectId": "..." }`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let config;
|
|
39
|
+
try {
|
|
40
|
+
config = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
41
|
+
} catch {
|
|
42
|
+
fatal(`Failed to parse ${CONFIG_FILE}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { url, projectId } = config;
|
|
46
|
+
if (!url || !projectId) {
|
|
47
|
+
fatal(`${CONFIG_FILE} must have "url" and "projectId" fields.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Read API key from ~/.teamsecret or TEAMSECRET_API_KEY env var
|
|
51
|
+
let apiKey = process.env.TEAMSECRET_API_KEY;
|
|
52
|
+
if (!apiKey && existsSync(KEY_FILE)) {
|
|
53
|
+
apiKey = readFileSync(KEY_FILE, "utf-8").trim();
|
|
54
|
+
}
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
fatal(
|
|
57
|
+
`API key not found. Either:\n` +
|
|
58
|
+
` - Set TEAMSECRET_API_KEY environment variable\n` +
|
|
59
|
+
` - Save your key in ~/.teamsecret`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Fetch secrets from TeamSecret
|
|
64
|
+
info(`Pulling ${environment} secrets from ${url}...`);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${url}/api/v1/pull?env=${encodeURIComponent(environment)}`, {
|
|
68
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const body = await res.json().catch(() => ({}));
|
|
73
|
+
fatal(`API error (${res.status}): ${body.error || "Unknown error"}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = await res.json();
|
|
77
|
+
|
|
78
|
+
// 4. Write .env file
|
|
79
|
+
const lines = [
|
|
80
|
+
`# Generated by TeamSecret — ${data.project} (${data.environment})`,
|
|
81
|
+
`# Pulled at ${new Date().toISOString()}`,
|
|
82
|
+
`# DO NOT COMMIT THIS FILE`,
|
|
83
|
+
"",
|
|
84
|
+
...Object.entries(data.secrets).map(([key, value]) => `${key}=${value}`),
|
|
85
|
+
"",
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
writeFileSync(OUTPUT_FILE, lines.join("\n"));
|
|
89
|
+
info(`Wrote ${Object.keys(data.secrets).length} secrets to ${OUTPUT_FILE}`);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (e.message?.includes("API error")) throw e;
|
|
92
|
+
fatal(`Failed to connect to ${url}: ${e.message}`);
|
|
93
|
+
}
|