nobalmako 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.
Files changed (123) hide show
  1. package/README.md +112 -0
  2. package/components.json +22 -0
  3. package/dist/nobalmako.js +272 -0
  4. package/drizzle/0000_pink_spiral.sql +126 -0
  5. package/drizzle/meta/0000_snapshot.json +1027 -0
  6. package/drizzle/meta/_journal.json +13 -0
  7. package/drizzle.config.ts +10 -0
  8. package/eslint.config.mjs +18 -0
  9. package/next.config.ts +7 -0
  10. package/package.json +80 -0
  11. package/postcss.config.mjs +7 -0
  12. package/public/file.svg +1 -0
  13. package/public/globe.svg +1 -0
  14. package/public/next.svg +1 -0
  15. package/public/vercel.svg +1 -0
  16. package/public/window.svg +1 -0
  17. package/server/index.ts +118 -0
  18. package/src/app/api/api-keys/[id]/route.ts +147 -0
  19. package/src/app/api/api-keys/route.ts +151 -0
  20. package/src/app/api/audit-logs/route.ts +84 -0
  21. package/src/app/api/auth/forgot-password/route.ts +47 -0
  22. package/src/app/api/auth/login/route.ts +99 -0
  23. package/src/app/api/auth/logout/route.ts +15 -0
  24. package/src/app/api/auth/me/route.ts +23 -0
  25. package/src/app/api/auth/mfa/setup/route.ts +33 -0
  26. package/src/app/api/auth/mfa/verify/route.ts +45 -0
  27. package/src/app/api/auth/register/route.ts +140 -0
  28. package/src/app/api/auth/reset-password/route.ts +52 -0
  29. package/src/app/api/auth/update/route.ts +71 -0
  30. package/src/app/api/auth/verify/route.ts +39 -0
  31. package/src/app/api/environments/route.ts +227 -0
  32. package/src/app/api/team-members/route.ts +385 -0
  33. package/src/app/api/teams/route.ts +217 -0
  34. package/src/app/api/variable-history/route.ts +218 -0
  35. package/src/app/api/variables/route.ts +476 -0
  36. package/src/app/api/webhooks/route.ts +77 -0
  37. package/src/app/api-keys/APIKeysClient.tsx +316 -0
  38. package/src/app/api-keys/page.tsx +10 -0
  39. package/src/app/api-reference/page.tsx +324 -0
  40. package/src/app/audit-log/AuditLogClient.tsx +229 -0
  41. package/src/app/audit-log/page.tsx +10 -0
  42. package/src/app/auth/forgot-password/page.tsx +121 -0
  43. package/src/app/auth/login/LoginForm.tsx +145 -0
  44. package/src/app/auth/login/page.tsx +11 -0
  45. package/src/app/auth/register/RegisterForm.tsx +156 -0
  46. package/src/app/auth/register/page.tsx +16 -0
  47. package/src/app/auth/reset-password/page.tsx +160 -0
  48. package/src/app/dashboard/DashboardClient.tsx +219 -0
  49. package/src/app/dashboard/page.tsx +11 -0
  50. package/src/app/docs/page.tsx +251 -0
  51. package/src/app/favicon.ico +0 -0
  52. package/src/app/globals.css +123 -0
  53. package/src/app/layout.tsx +35 -0
  54. package/src/app/page.tsx +231 -0
  55. package/src/app/profile/ProfileClient.tsx +230 -0
  56. package/src/app/profile/page.tsx +10 -0
  57. package/src/app/project/[id]/ProjectDetailsClient.tsx +512 -0
  58. package/src/app/project/[id]/page.tsx +17 -0
  59. package/src/bin/nobalmako.ts +341 -0
  60. package/src/components/ApiKeysManager.tsx +529 -0
  61. package/src/components/AppLayout.tsx +193 -0
  62. package/src/components/BulkActions.tsx +138 -0
  63. package/src/components/CreateEnvironmentDialog.tsx +207 -0
  64. package/src/components/CreateTeamDialog.tsx +174 -0
  65. package/src/components/CreateVariableDialog.tsx +311 -0
  66. package/src/components/DeleteEnvironmentDialog.tsx +104 -0
  67. package/src/components/DeleteTeamDialog.tsx +112 -0
  68. package/src/components/DeleteVariableDialog.tsx +103 -0
  69. package/src/components/EditEnvironmentDialog.tsx +202 -0
  70. package/src/components/EditMemberDialog.tsx +143 -0
  71. package/src/components/EditTeamDialog.tsx +178 -0
  72. package/src/components/EditVariableDialog.tsx +231 -0
  73. package/src/components/ImportVariablesDialog.tsx +347 -0
  74. package/src/components/InviteMemberDialog.tsx +191 -0
  75. package/src/components/LeaveProjectDialog.tsx +111 -0
  76. package/src/components/MFASettings.tsx +136 -0
  77. package/src/components/ProjectDiff.tsx +123 -0
  78. package/src/components/Providers.tsx +24 -0
  79. package/src/components/RemoveMemberDialog.tsx +112 -0
  80. package/src/components/SearchDialog.tsx +276 -0
  81. package/src/components/SecurityOverview.tsx +92 -0
  82. package/src/components/TeamMembersManager.tsx +103 -0
  83. package/src/components/VariableHistoryDialog.tsx +265 -0
  84. package/src/components/WebhooksManager.tsx +169 -0
  85. package/src/components/ui/alert-dialog.tsx +160 -0
  86. package/src/components/ui/alert.tsx +59 -0
  87. package/src/components/ui/avatar.tsx +53 -0
  88. package/src/components/ui/badge.tsx +46 -0
  89. package/src/components/ui/button.tsx +62 -0
  90. package/src/components/ui/card.tsx +92 -0
  91. package/src/components/ui/checkbox.tsx +32 -0
  92. package/src/components/ui/dialog.tsx +143 -0
  93. package/src/components/ui/dropdown-menu.tsx +257 -0
  94. package/src/components/ui/input.tsx +21 -0
  95. package/src/components/ui/label.tsx +24 -0
  96. package/src/components/ui/select.tsx +190 -0
  97. package/src/components/ui/separator.tsx +28 -0
  98. package/src/components/ui/sonner.tsx +37 -0
  99. package/src/components/ui/switch.tsx +31 -0
  100. package/src/components/ui/table.tsx +117 -0
  101. package/src/components/ui/tabs.tsx +66 -0
  102. package/src/components/ui/textarea.tsx +18 -0
  103. package/src/hooks/use-api-keys.ts +95 -0
  104. package/src/hooks/use-audit-logs.ts +58 -0
  105. package/src/hooks/use-auth.tsx +121 -0
  106. package/src/hooks/use-environments.ts +33 -0
  107. package/src/hooks/use-project-permissions.ts +49 -0
  108. package/src/hooks/use-team-members.ts +30 -0
  109. package/src/hooks/use-teams.ts +33 -0
  110. package/src/hooks/use-variables.ts +38 -0
  111. package/src/lib/audit.ts +36 -0
  112. package/src/lib/auth.ts +108 -0
  113. package/src/lib/crypto.ts +39 -0
  114. package/src/lib/db.ts +15 -0
  115. package/src/lib/dynamic-providers.ts +19 -0
  116. package/src/lib/email.ts +110 -0
  117. package/src/lib/mail.ts +51 -0
  118. package/src/lib/permissions.ts +51 -0
  119. package/src/lib/schema.ts +240 -0
  120. package/src/lib/seed.ts +107 -0
  121. package/src/lib/utils.ts +6 -0
  122. package/src/lib/webhooks.ts +42 -0
  123. package/tsconfig.json +34 -0
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Nobalmako - Secure Environment Variable Manager
2
+
3
+ Nobalmako is a full-stack MVP designed to help developers and teams securely store, manage, and share secrets. It features end-to-end encryption for sensitive values and robust role-based access control.
4
+
5
+ ## Features
6
+
7
+ - **End-to-End Security:** All environment variable values are encrypted using AES-256-CBC before storage.
8
+ - **Projects & Environments:** Organize secrets by project and categorize them into `development`, `staging`, or `production`.
9
+ - **Role-Based Access Control (RBAC):**
10
+ - `Owner`: Full control over the project and members.
11
+ - `Admin`: Manage members and secrets.
12
+ - `Developer`: View and manage secrets.
13
+ - `Viewer`: Read-only access to secrets.
14
+ - **Audit Logs:** Track every action performed on your secrets for compliance and security.
15
+ - **API Keys:** Secure programmatic access for CI/CD and CLI integrations.
16
+ - **Variable History:** View and compare previous versions of any secret.
17
+
18
+ ## Nobalmako CLI
19
+
20
+ Manage your secrets directly from your terminal or CI/CD pipeline.
21
+
22
+ ### Installation
23
+
24
+ For global system-wide access:
25
+ \`bash
26
+ # Build the CLI
27
+ npm run build:cli
28
+
29
+ # Install globally
30
+ npm install -g .
31
+ \`
32
+
33
+ Alternatively, run without installing using \`npx\`:
34
+ \`bash
35
+ # Build first
36
+ npm run build:cli
37
+
38
+ # Run via npx (from project root)
39
+ npx nobalmako --help
40
+ \`
41
+
42
+ ### Usage
43
+
44
+ 1. **Local Authentication:**
45
+ Authenticate your CLI once and it will remember your session:
46
+ \`bash
47
+ nobalmako login --email dev@nobalmako.com --password "your_password"
48
+ \`
49
+
50
+ Alternatively, use an API Token for CI/CD:
51
+ \`bash
52
+ export NOBALMAKO_TOKEN="nm_your_api_key_here"
53
+ \`
54
+
55
+ 2. **Pull Secrets:**
56
+ \`bash
57
+ nobalmako pull -p "Project Name" -e "production" -f ".env"
58
+ \`
59
+
60
+ 3. **Push Secrets:**
61
+ \`bash
62
+ nobalmako push -p "Project Name" -e "staging" -f ".env"
63
+ \`
64
+
65
+ ## Tech Stack
66
+
67
+ - **Frontend:** Next.js 15, React, Tailwind CSS, Lucide Icons, Radix UI.
68
+ - **Backend:** Node.js, Express (custom server), Next.js API Routes.
69
+ - **Database:** PostgreSQL (using Drizzle ORM).
70
+ - **Authentication:** JWT (JSON Web Tokens).
71
+
72
+ ## Setup Instructions
73
+
74
+ ### 1. Prerequisites
75
+ - Node.js 18+
76
+ - PostgreSQL database (or a Neon/Supabase project)
77
+
78
+ ### 2. Environment Variables
79
+ Copy `.env.example` to `.env` and fill in the values:
80
+ \`bash
81
+ cp .env.example .env
82
+ \`
83
+
84
+ ### 3. Installation
85
+ \`bash
86
+ npm install
87
+ \`
88
+
89
+ ### 4. Database Setup
90
+ \`bash
91
+ # Push schema to database
92
+ npx drizzle-kit push
93
+ \`
94
+
95
+ ### 5. Running the Application
96
+ \`bash
97
+ # Development mode
98
+ npm run dev
99
+
100
+ # Build and Start
101
+ npm run build
102
+ npm start
103
+ \`
104
+
105
+ ## Security Model
106
+
107
+ 1. **At-Rest:** Secrets are encrypted using AES-256-CBC. Even if the database is compromised, the values remain unreadable without the `ENCRYPTION_KEY`.
108
+ 2. **In-Transit:** All requests are handled via HTTPS (recommended for production).
109
+ 3. **Application Level:** RBAC is enforced on every API request. A `Viewer` cannot perform `POST/PUT/DELETE` operations on secrets.
110
+
111
+ ## License
112
+ MIT
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ },
21
+ "registries": {}
22
+ }
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/bin/nobalmako.ts
27
+ var import_commander = require("commander");
28
+ var import_fs = __toESM(require("fs"));
29
+ var import_path = __toESM(require("path"));
30
+ var import_dotenv = __toESM(require("dotenv"));
31
+ var import_os = __toESM(require("os"));
32
+ var import_child_process = require("child_process");
33
+ var import_enquirer = require("enquirer");
34
+ var program = new import_commander.Command();
35
+ var CONFIG_FILE = import_path.default.join(import_os.default.homedir(), ".nobalmako.json");
36
+ var PROJECT_CONFIG = import_path.default.join(process.cwd(), "nobalmako.json");
37
+ function saveConfig(config) {
38
+ const current = loadConfig();
39
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify({ ...current, ...config }, null, 2));
40
+ }
41
+ function loadConfig() {
42
+ if (import_fs.default.existsSync(CONFIG_FILE)) {
43
+ return JSON.parse(import_fs.default.readFileSync(CONFIG_FILE, "utf-8"));
44
+ }
45
+ return {};
46
+ }
47
+ function loadProjectConfig() {
48
+ if (import_fs.default.existsSync(PROJECT_CONFIG)) {
49
+ return JSON.parse(import_fs.default.readFileSync(PROJECT_CONFIG, "utf-8"));
50
+ }
51
+ return {};
52
+ }
53
+ function getToken(optionsToken) {
54
+ const config = loadConfig();
55
+ return optionsToken || process.env.NOBALMAKO_TOKEN || config.token;
56
+ }
57
+ program.name("nobalmako").description("Securing your environment variables").version("1.0.0");
58
+ program.command("login").description("Login to Nobalmako").option("--email <email>", "User email").option("--password <password>", "User password").option("--api-url <url>", "Base API URL", "http://localhost:3000/api").action(async (options) => {
59
+ let { email, password } = options;
60
+ if (!email) {
61
+ const response = await (0, import_enquirer.prompt)({
62
+ type: "input",
63
+ name: "email",
64
+ message: "Enter your email:"
65
+ });
66
+ email = response.email;
67
+ }
68
+ if (!password) {
69
+ const response = await (0, import_enquirer.prompt)({
70
+ type: "password",
71
+ name: "password",
72
+ message: "Enter your password:"
73
+ });
74
+ password = response.password;
75
+ }
76
+ try {
77
+ const response = await fetch(`${options.apiUrl}/auth/login`, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ email, password })
81
+ });
82
+ if (!response.ok) {
83
+ throw new Error("Login failed. Please check your credentials.");
84
+ }
85
+ const data = await response.json();
86
+ saveConfig({ token: data.token, email: data.user.email });
87
+ console.log(`\x1B[32mSuccessfully logged in as ${data.user.email}!\x1B[0m`);
88
+ } catch (error) {
89
+ console.error(`\x1B[31mError:\x1B[0m ${error.message}`);
90
+ process.exit(1);
91
+ }
92
+ });
93
+ program.command("logout").description("Clear local credentials").action(() => {
94
+ if (import_fs.default.existsSync(CONFIG_FILE)) {
95
+ import_fs.default.unlinkSync(CONFIG_FILE);
96
+ console.log("\x1B[32mSuccessfully logged out.\x1B[0m");
97
+ } else {
98
+ console.log("You are not logged in.");
99
+ }
100
+ });
101
+ program.command("init").description("Initialize a local project configuration").option("-p, --project <project>", "Project name").option("-e, --env <environment>", "Default environment name").action(async (options) => {
102
+ let { project, env } = options;
103
+ if (!project) {
104
+ const response = await (0, import_enquirer.prompt)({
105
+ type: "input",
106
+ name: "project",
107
+ message: "Project name (slug):"
108
+ });
109
+ project = response.project;
110
+ }
111
+ if (!env) {
112
+ const response = await (0, import_enquirer.prompt)({
113
+ type: "input",
114
+ name: "env",
115
+ message: "Default environment (e.g. production):"
116
+ });
117
+ env = response.env;
118
+ }
119
+ import_fs.default.writeFileSync(PROJECT_CONFIG, JSON.stringify({ project, environment: env }, null, 2));
120
+ console.log(`\x1B[32mCreated nobalmako.json with defaults.\x1B[0m`);
121
+ });
122
+ program.command("pull").description("Pull environment variables from Nobalmako and save to .env").option("-p, --project <project>", "Project name").option("-e, --env <environment>", "Environment name (e.g. production, staging)").option("-f, --file <filename>", "Output filename").option("--api-url <url>", "Base API URL", "http://localhost:3000/api").option("-t, --token <token>", "API Token (overrides login)").action(async (options) => {
123
+ const token = getToken(options.token);
124
+ const pConfig = loadProjectConfig();
125
+ const project = options.project || pConfig.project;
126
+ const env = options.env || pConfig.environment;
127
+ const file = options.file || pConfig.file || ".env";
128
+ if (!token) {
129
+ console.error("\x1B[31mError: Not logged in.\x1B[0m Run \x1B[33mnobalmako login\x1B[0m or set \x1B[33mNOBALMAKO_TOKEN\x1B[0m.");
130
+ process.exit(1);
131
+ }
132
+ if (!project || !env) {
133
+ console.error("\x1B[31mError: Project and Environment are required.\x1B[0m Use \x1B[33mnobalmako init\x1B[0m or provide flags.");
134
+ process.exit(1);
135
+ }
136
+ console.log(`\x1B[34m[Nobalmako]\x1B[0m Fetching secrets for project \x1B[36m${project}\x1B[0m (\x1B[35m${env}\x1B[0m)...`);
137
+ try {
138
+ const response = await fetch(`${options.apiUrl}/variables?team=${encodeURIComponent(project)}&environment=${encodeURIComponent(env)}`, {
139
+ headers: {
140
+ "Authorization": `Bearer ${token}`
141
+ }
142
+ });
143
+ if (!response.ok) {
144
+ const errorData = await response.json();
145
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
146
+ }
147
+ const data = await response.json();
148
+ const variables = data.variables || [];
149
+ if (variables.length === 0) {
150
+ console.log("\x1B[33mNo variables found for this project and environment.\x1B[0m");
151
+ return;
152
+ }
153
+ let envContent = `# Generated by Nobalmako on ${(/* @__PURE__ */ new Date()).toISOString()}
154
+ `;
155
+ variables.forEach((v) => {
156
+ if (v.description) {
157
+ envContent += `# ${v.description}
158
+ `;
159
+ }
160
+ envContent += `${v.key}=${v.value}
161
+ `;
162
+ });
163
+ const outputPath = import_path.default.resolve(process.cwd(), file);
164
+ import_fs.default.writeFileSync(outputPath, envContent);
165
+ console.log(`\x1B[32mSuccess!\x1B[0m Pulled ${variables.length} variables into \x1B[33m${file}\x1B[0m`);
166
+ } catch (error) {
167
+ console.error(`\x1B[31mPull failed:\x1B[0m ${error.message}`);
168
+ process.exit(1);
169
+ }
170
+ });
171
+ program.command("push").description("Push environment variables from a .env file to Nobalmako").option("-p, --project <project>", "Project name").option("-e, --env <environment>", "Environment name (e.g. production, staging)").option("-f, --file <filename>", "Input filename").option("-s, --secret", "Mark all variables as secret", false).option("--api-url <url>", "Base API URL", "http://localhost:3000/api").option("-t, --token <token>", "API Token (overrides login)").action(async (options) => {
172
+ const token = getToken(options.token);
173
+ const pConfig = loadProjectConfig();
174
+ const project = options.project || pConfig.project;
175
+ const env = options.env || pConfig.environment;
176
+ const file = options.file || pConfig.file || ".env";
177
+ if (!token) {
178
+ console.error("\x1B[31mError: Not logged in.\x1B[0m Run \x1B[33mnobalmako login\x1B[0m or set \x1B[33mNOBALMAKO_TOKEN\x1B[0m.");
179
+ process.exit(1);
180
+ }
181
+ if (!project || !env) {
182
+ console.error("\x1B[31mError: Project and Environment are required.\x1B[0m Use \x1B[33mnobalmako init\x1B[0m or provide flags.");
183
+ process.exit(1);
184
+ }
185
+ const inputPath = import_path.default.resolve(process.cwd(), file);
186
+ if (!import_fs.default.existsSync(inputPath)) {
187
+ console.error(`\x1B[31mError: File not found: ${file}\x1B[0m`);
188
+ process.exit(1);
189
+ }
190
+ const envContent = import_fs.default.readFileSync(inputPath, "utf-8");
191
+ const envVars = import_dotenv.default.parse(envContent);
192
+ const keys = Object.keys(envVars);
193
+ console.log(`\x1B[34m[Nobalmako]\x1B[0m Pushing ${keys.length} variables to \x1B[36m${project}\x1B[0m (\x1B[35m${env}\x1B[0m)...`);
194
+ try {
195
+ const envsResponse = await fetch(`${options.apiUrl}/environments`, {
196
+ headers: { "Authorization": `Bearer ${token}` }
197
+ });
198
+ if (!envsResponse.ok) throw new Error("Failed to fetch environments");
199
+ const envsData = await envsResponse.json();
200
+ const targetEnv = envsData.environments.find(
201
+ (e) => e.teamName === project && e.name === env
202
+ );
203
+ if (!targetEnv) {
204
+ throw new Error(`Could not find environment "${env}" for project "${project}".`);
205
+ }
206
+ let successCount = 0;
207
+ for (const [key, value] of Object.entries(envVars)) {
208
+ const response = await fetch(`${options.apiUrl}/variables`, {
209
+ method: "POST",
210
+ headers: {
211
+ "Authorization": `Bearer ${token}`,
212
+ "Content-Type": "application/json"
213
+ },
214
+ body: JSON.stringify({
215
+ key,
216
+ value,
217
+ teamId: targetEnv.teamId,
218
+ environmentId: targetEnv.id,
219
+ isSecret: options.secret
220
+ })
221
+ });
222
+ if (response.ok) {
223
+ successCount++;
224
+ process.stdout.write(`\rProgress: ${successCount}/${keys.length}`);
225
+ }
226
+ }
227
+ console.log(`
228
+ \x1B[32mSuccess!\x1B[0m Pushed ${successCount} variables to \x1B[33m${project}\x1B[0m`);
229
+ } catch (error) {
230
+ console.error(`\x1B[31mPush failed:\x1B[0m ${error.message}`);
231
+ process.exit(1);
232
+ }
233
+ });
234
+ program.command("run").description("Run a command with environment variables injected").argument("<command...>", "The command to execute").option("-p, --project <project>", "Project name").option("-e, --env <environment>", "Environment name").option("--api-url <url>", "Base API URL", "http://localhost:3000/api").option("-t, --token <token>", "API Token").action(async (commandArgs, options) => {
235
+ const token = getToken(options.token);
236
+ const pConfig = loadProjectConfig();
237
+ const project = options.project || pConfig.project;
238
+ const env = options.env || pConfig.environment;
239
+ if (!token) {
240
+ console.error("\x1B[31mError: Not logged in.\x1B[0m");
241
+ process.exit(1);
242
+ }
243
+ if (!project || !env) {
244
+ console.error("\x1B[31mError: Project and Environment are required.\x1B[0m");
245
+ process.exit(1);
246
+ }
247
+ try {
248
+ const response = await fetch(`${options.apiUrl}/variables?team=${encodeURIComponent(project)}&environment=${encodeURIComponent(env)}`, {
249
+ headers: { "Authorization": `Bearer ${token}` }
250
+ });
251
+ if (!response.ok) throw new Error("Failed to fetch variables");
252
+ const data = await response.json();
253
+ const variables = data.variables || [];
254
+ const newEnv = { ...process.env };
255
+ variables.forEach((v) => {
256
+ newEnv[v.key] = v.value;
257
+ });
258
+ const [cmd, ...args] = commandArgs;
259
+ const child = (0, import_child_process.spawn)(cmd, args, {
260
+ stdio: "inherit",
261
+ env: newEnv,
262
+ shell: true
263
+ });
264
+ child.on("exit", (code) => {
265
+ process.exit(code || 0);
266
+ });
267
+ } catch (error) {
268
+ console.error(`\x1B[31mRun failed:\x1B[0m ${error.message}`);
269
+ process.exit(1);
270
+ }
271
+ });
272
+ program.parse(process.argv);
@@ -0,0 +1,126 @@
1
+ CREATE TABLE "api_keys" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "user_id" uuid NOT NULL,
4
+ "team_id" uuid,
5
+ "name" text NOT NULL,
6
+ "key_hash" text NOT NULL,
7
+ "permissions" jsonb DEFAULT '["read"]'::jsonb,
8
+ "last_used" timestamp,
9
+ "expires_at" timestamp,
10
+ "created_at" timestamp DEFAULT now() NOT NULL,
11
+ "updated_at" timestamp DEFAULT now() NOT NULL
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE TABLE "audit_logs" (
15
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
16
+ "user_id" uuid,
17
+ "team_id" uuid,
18
+ "action" text NOT NULL,
19
+ "resource_type" text NOT NULL,
20
+ "resource_id" uuid NOT NULL,
21
+ "old_value" jsonb,
22
+ "new_value" jsonb,
23
+ "ip_address" text,
24
+ "user_agent" text,
25
+ "created_at" timestamp DEFAULT now() NOT NULL
26
+ );
27
+ --> statement-breakpoint
28
+ CREATE TABLE "environment_variables" (
29
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
30
+ "team_id" uuid NOT NULL,
31
+ "environment_id" uuid NOT NULL,
32
+ "key" text NOT NULL,
33
+ "value" text NOT NULL,
34
+ "description" text,
35
+ "is_secret" boolean DEFAULT false,
36
+ "tags" jsonb,
37
+ "created_by" uuid NOT NULL,
38
+ "updated_by" uuid,
39
+ "created_at" timestamp DEFAULT now() NOT NULL,
40
+ "updated_at" timestamp DEFAULT now() NOT NULL
41
+ );
42
+ --> statement-breakpoint
43
+ CREATE TABLE "environments" (
44
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
45
+ "team_id" uuid NOT NULL,
46
+ "name" text NOT NULL,
47
+ "description" text,
48
+ "color" text DEFAULT '#3b82f6',
49
+ "is_default" boolean DEFAULT false,
50
+ "created_at" timestamp DEFAULT now() NOT NULL,
51
+ "updated_at" timestamp DEFAULT now() NOT NULL
52
+ );
53
+ --> statement-breakpoint
54
+ CREATE TABLE "team_members" (
55
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
56
+ "team_id" uuid NOT NULL,
57
+ "user_id" uuid NOT NULL,
58
+ "role" text DEFAULT 'developer' NOT NULL,
59
+ "joined_at" timestamp DEFAULT now() NOT NULL
60
+ );
61
+ --> statement-breakpoint
62
+ CREATE TABLE "teams" (
63
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
64
+ "name" text NOT NULL,
65
+ "description" text,
66
+ "color" text DEFAULT '#3b82f6',
67
+ "owner_id" uuid NOT NULL,
68
+ "created_at" timestamp DEFAULT now() NOT NULL,
69
+ "updated_at" timestamp DEFAULT now() NOT NULL
70
+ );
71
+ --> statement-breakpoint
72
+ CREATE TABLE "users" (
73
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
74
+ "email" text NOT NULL,
75
+ "full_name" text NOT NULL,
76
+ "password" text NOT NULL,
77
+ "avatar" text,
78
+ "role" text DEFAULT 'user' NOT NULL,
79
+ "created_at" timestamp DEFAULT now() NOT NULL,
80
+ "updated_at" timestamp DEFAULT now() NOT NULL,
81
+ CONSTRAINT "users_email_unique" UNIQUE("email")
82
+ );
83
+ --> statement-breakpoint
84
+ CREATE TABLE "variable_history" (
85
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
86
+ "variable_id" uuid NOT NULL,
87
+ "team_id" uuid NOT NULL,
88
+ "environment_id" uuid NOT NULL,
89
+ "key" text NOT NULL,
90
+ "value" text NOT NULL,
91
+ "description" text,
92
+ "is_secret" boolean DEFAULT false,
93
+ "changed_by" uuid NOT NULL,
94
+ "change_type" text NOT NULL,
95
+ "created_at" timestamp DEFAULT now() NOT NULL
96
+ );
97
+ --> statement-breakpoint
98
+ ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
99
+ ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
100
+ ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
101
+ ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
102
+ ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
103
+ ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
104
+ ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
105
+ ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_updated_by_users_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
106
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
107
+ ALTER TABLE "team_members" ADD CONSTRAINT "team_members_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
108
+ ALTER TABLE "team_members" ADD CONSTRAINT "team_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
109
+ ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
110
+ ALTER TABLE "variable_history" ADD CONSTRAINT "variable_history_variable_id_environment_variables_id_fk" FOREIGN KEY ("variable_id") REFERENCES "public"."environment_variables"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
111
+ ALTER TABLE "variable_history" ADD CONSTRAINT "variable_history_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
112
+ ALTER TABLE "variable_history" ADD CONSTRAINT "variable_history_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
113
+ ALTER TABLE "variable_history" ADD CONSTRAINT "variable_history_changed_by_users_id_fk" FOREIGN KEY ("changed_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
114
+ CREATE INDEX "api_keys_user_idx" ON "api_keys" USING btree ("user_id");--> statement-breakpoint
115
+ CREATE INDEX "api_keys_team_idx" ON "api_keys" USING btree ("team_id");--> statement-breakpoint
116
+ CREATE INDEX "audit_logs_user_idx" ON "audit_logs" USING btree ("user_id");--> statement-breakpoint
117
+ CREATE INDEX "audit_logs_team_idx" ON "audit_logs" USING btree ("team_id");--> statement-breakpoint
118
+ CREATE INDEX "audit_logs_resource_idx" ON "audit_logs" USING btree ("resource_type","resource_id");--> statement-breakpoint
119
+ CREATE INDEX "env_vars_team_env_idx" ON "environment_variables" USING btree ("team_id","environment_id");--> statement-breakpoint
120
+ CREATE INDEX "env_vars_key_idx" ON "environment_variables" USING btree ("key");--> statement-breakpoint
121
+ CREATE INDEX "env_vars_created_by_idx" ON "environment_variables" USING btree ("created_by");--> statement-breakpoint
122
+ CREATE INDEX "environments_team_idx" ON "environments" USING btree ("team_id");--> statement-breakpoint
123
+ CREATE INDEX "team_members_team_user_idx" ON "team_members" USING btree ("team_id","user_id");--> statement-breakpoint
124
+ CREATE INDEX "teams_owner_idx" ON "teams" USING btree ("owner_id");--> statement-breakpoint
125
+ CREATE INDEX "variable_history_variable_idx" ON "variable_history" USING btree ("variable_id");--> statement-breakpoint
126
+ CREATE INDEX "variable_history_team_idx" ON "variable_history" USING btree ("team_id");