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.
Files changed (2) hide show
  1. package/package.json +18 -0
  2. 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
+ }