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
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import dotenv from 'dotenv';
6
+ import os from 'os';
7
+ import { spawn } from 'child_process';
8
+ // @ts-ignore
9
+ import { prompt } from 'enquirer';
10
+
11
+ const program = new Command();
12
+ const CONFIG_FILE = path.join(os.homedir(), '.nobalmako.json');
13
+ const PROJECT_CONFIG = path.join(process.cwd(), 'nobalmako.json');
14
+
15
+ function saveConfig(config: any) {
16
+ const current = loadConfig();
17
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...current, ...config }, null, 2));
18
+ }
19
+
20
+ function loadConfig() {
21
+ if (fs.existsSync(CONFIG_FILE)) {
22
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
23
+ }
24
+ return {};
25
+ }
26
+
27
+ function loadProjectConfig() {
28
+ if (fs.existsSync(PROJECT_CONFIG)) {
29
+ return JSON.parse(fs.readFileSync(PROJECT_CONFIG, 'utf-8'));
30
+ }
31
+ return {};
32
+ }
33
+
34
+ function getToken(optionsToken?: string) {
35
+ const config = loadConfig();
36
+ return optionsToken || process.env.NOBALMAKO_TOKEN || config.token;
37
+ }
38
+
39
+ program
40
+ .name('nobalmako')
41
+ .description('Securing your environment variables')
42
+ .version('1.0.0');
43
+
44
+ program
45
+ .command('login')
46
+ .description('Login to Nobalmako')
47
+ .option('--email <email>', 'User email')
48
+ .option('--password <password>', 'User password')
49
+ .option('--api-url <url>', 'Base API URL', 'http://localhost:3000/api')
50
+ .action(async (options) => {
51
+ let { email, password } = options;
52
+
53
+ if (!email) {
54
+ const response = await prompt({
55
+ type: 'input',
56
+ name: 'email',
57
+ message: 'Enter your email:'
58
+ }) as any;
59
+ email = response.email;
60
+ }
61
+
62
+ if (!password) {
63
+ const response = await prompt({
64
+ type: 'password',
65
+ name: 'password',
66
+ message: 'Enter your password:'
67
+ }) as any;
68
+ password = response.password;
69
+ }
70
+
71
+ try {
72
+ const response = await fetch(`${options.apiUrl}/auth/login`, {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify({ email, password })
76
+ });
77
+
78
+ if (!response.ok) {
79
+ throw new Error('Login failed. Please check your credentials.');
80
+ }
81
+
82
+ const data = await response.json();
83
+ saveConfig({ token: data.token, email: data.user.email });
84
+
85
+ console.log(`\x1b[32mSuccessfully logged in as ${data.user.email}!\x1b[0m`);
86
+ } catch (error: any) {
87
+ console.error(`\x1b[31mError:\x1b[0m ${error.message}`);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ program
93
+ .command('logout')
94
+ .description('Clear local credentials')
95
+ .action(() => {
96
+ if (fs.existsSync(CONFIG_FILE)) {
97
+ fs.unlinkSync(CONFIG_FILE);
98
+ console.log('\x1b[32mSuccessfully logged out.\x1b[0m');
99
+ } else {
100
+ console.log('You are not logged in.');
101
+ }
102
+ });
103
+
104
+ program
105
+ .command('init')
106
+ .description('Initialize a local project configuration')
107
+ .option('-p, --project <project>', 'Project name')
108
+ .option('-e, --env <environment>', 'Default environment name')
109
+ .action(async (options) => {
110
+ let { project, env } = options;
111
+
112
+ if (!project) {
113
+ const response = await prompt({
114
+ type: 'input',
115
+ name: 'project',
116
+ message: 'Project name (slug):'
117
+ }) as any;
118
+ project = response.project;
119
+ }
120
+
121
+ if (!env) {
122
+ const response = await prompt({
123
+ type: 'input',
124
+ name: 'env',
125
+ message: 'Default environment (e.g. production):'
126
+ }) as any;
127
+ env = response.env;
128
+ }
129
+
130
+ fs.writeFileSync(PROJECT_CONFIG, JSON.stringify({ project, environment: env }, null, 2));
131
+ console.log(`\x1b[32mCreated nobalmako.json with defaults.\x1b[0m`);
132
+ });
133
+
134
+ program
135
+ .command('pull')
136
+ .description('Pull environment variables from Nobalmako and save to .env')
137
+ .option('-p, --project <project>', 'Project name')
138
+ .option('-e, --env <environment>', 'Environment name (e.g. production, staging)')
139
+ .option('-f, --file <filename>', 'Output filename')
140
+ .option('--api-url <url>', 'Base API URL', 'http://localhost:3000/api')
141
+ .option('-t, --token <token>', 'API Token (overrides login)')
142
+ .action(async (options) => {
143
+ const token = getToken(options.token);
144
+ const pConfig = loadProjectConfig();
145
+
146
+ const project = options.project || pConfig.project;
147
+ const env = options.env || pConfig.environment;
148
+ const file = options.file || pConfig.file || '.env';
149
+
150
+ if (!token) {
151
+ console.error('\x1b[31mError: Not logged in.\x1b[0m Run \x1b[33mnobalmako login\x1b[0m or set \x1b[33mNOBALMAKO_TOKEN\x1b[0m.');
152
+ process.exit(1);
153
+ }
154
+
155
+ if (!project || !env) {
156
+ console.error('\x1b[31mError: Project and Environment are required.\x1b[0m Use \x1b[33mnobalmako init\x1b[0m or provide flags.');
157
+ process.exit(1);
158
+ }
159
+
160
+ console.log(`\x1b[34m[Nobalmako]\x1b[0m Fetching secrets for project \x1b[36m${project}\x1b[0m (\x1b[35m${env}\x1b[0m)...`);
161
+
162
+ try {
163
+ const response = await fetch(`${options.apiUrl}/variables?team=${encodeURIComponent(project)}&environment=${encodeURIComponent(env)}`, {
164
+ headers: {
165
+ 'Authorization': `Bearer ${token}`
166
+ }
167
+ });
168
+
169
+ if (!response.ok) {
170
+ const errorData = await response.json();
171
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
172
+ }
173
+
174
+ const data = await response.json();
175
+ const variables = data.variables || [];
176
+
177
+ if (variables.length === 0) {
178
+ console.log('\x1b[33mNo variables found for this project and environment.\x1b[0m');
179
+ return;
180
+ }
181
+
182
+ let envContent = `# Generated by Nobalmako on ${new Date().toISOString()}\n`;
183
+ variables.forEach((v: any) => {
184
+ if (v.description) {
185
+ envContent += `# ${v.description}\n`;
186
+ }
187
+ envContent += `${v.key}=${v.value}\n`;
188
+ });
189
+
190
+ const outputPath = path.resolve(process.cwd(), file);
191
+ fs.writeFileSync(outputPath, envContent);
192
+
193
+ console.log(`\x1b[32mSuccess!\x1b[0m Pulled ${variables.length} variables into \x1b[33m${file}\x1b[0m`);
194
+ } catch (error: any) {
195
+ console.error(`\x1b[31mPull failed:\x1b[0m ${error.message}`);
196
+ process.exit(1);
197
+ }
198
+ });
199
+
200
+ program
201
+ .command('push')
202
+ .description('Push environment variables from a .env file to Nobalmako')
203
+ .option('-p, --project <project>', 'Project name')
204
+ .option('-e, --env <environment>', 'Environment name (e.g. production, staging)')
205
+ .option('-f, --file <filename>', 'Input filename')
206
+ .option('-s, --secret', 'Mark all variables as secret', false)
207
+ .option('--api-url <url>', 'Base API URL', 'http://localhost:3000/api')
208
+ .option('-t, --token <token>', 'API Token (overrides login)')
209
+ .action(async (options) => {
210
+ const token = getToken(options.token);
211
+ const pConfig = loadProjectConfig();
212
+
213
+ const project = options.project || pConfig.project;
214
+ const env = options.env || pConfig.environment;
215
+ const file = options.file || pConfig.file || '.env';
216
+
217
+ if (!token) {
218
+ console.error('\x1b[31mError: Not logged in.\x1b[0m Run \x1b[33mnobalmako login\x1b[0m or set \x1b[33mNOBALMAKO_TOKEN\x1b[0m.');
219
+ process.exit(1);
220
+ }
221
+
222
+ if (!project || !env) {
223
+ console.error('\x1b[31mError: Project and Environment are required.\x1b[0m Use \x1b[33mnobalmako init\x1b[0m or provide flags.');
224
+ process.exit(1);
225
+ }
226
+
227
+ const inputPath = path.resolve(process.cwd(), file);
228
+ if (!fs.existsSync(inputPath)) {
229
+ console.error(`\x1b[31mError: File not found: ${file}\x1b[0m`);
230
+ process.exit(1);
231
+ }
232
+
233
+ const envContent = fs.readFileSync(inputPath, 'utf-8');
234
+ const envVars = dotenv.parse(envContent);
235
+ const keys = Object.keys(envVars);
236
+
237
+ console.log(`\x1b[34m[Nobalmako]\x1b[0m Pushing ${keys.length} variables to \x1b[36m${project}\x1b[0m (\x1b[35m${env}\x1b[0m)...`);
238
+
239
+ try {
240
+ // 1. Get Project ID and Environment ID
241
+ const envsResponse = await fetch(`${options.apiUrl}/environments`, {
242
+ headers: { 'Authorization': `Bearer ${token}` }
243
+ });
244
+ if (!envsResponse.ok) throw new Error('Failed to fetch environments');
245
+ const envsData = await envsResponse.json();
246
+ const targetEnv = envsData.environments.find((e: any) =>
247
+ e.teamName === project && e.name === env
248
+ );
249
+
250
+ if (!targetEnv) {
251
+ throw new Error(`Could not find environment "${env}" for project "${project}".`);
252
+ }
253
+
254
+ // 2. Push each variable
255
+ let successCount = 0;
256
+ for (const [key, value] of Object.entries(envVars)) {
257
+ const response = await fetch(`${options.apiUrl}/variables`, {
258
+ method: 'POST',
259
+ headers: {
260
+ 'Authorization': `Bearer ${token}`,
261
+ 'Content-Type': 'application/json'
262
+ },
263
+ body: JSON.stringify({
264
+ key,
265
+ value,
266
+ teamId: targetEnv.teamId,
267
+ environmentId: targetEnv.id,
268
+ isSecret: options.secret
269
+ })
270
+ });
271
+
272
+ if (response.ok) {
273
+ successCount++;
274
+ process.stdout.write(`\rProgress: ${successCount}/${keys.length}`);
275
+ }
276
+ }
277
+
278
+ console.log(`\n\x1b[32mSuccess!\x1b[0m Pushed ${successCount} variables to \x1b[33m${project}\x1b[0m`);
279
+ } catch (error: any) {
280
+ console.error(`\x1b[31mPush failed:\x1b[0m ${error.message}`);
281
+ process.exit(1);
282
+ }
283
+ });
284
+
285
+ program
286
+ .command('run')
287
+ .description('Run a command with environment variables injected')
288
+ .argument('<command...>', 'The command to execute')
289
+ .option('-p, --project <project>', 'Project name')
290
+ .option('-e, --env <environment>', 'Environment name')
291
+ .option('--api-url <url>', 'Base API URL', 'http://localhost:3000/api')
292
+ .option('-t, --token <token>', 'API Token')
293
+ .action(async (commandArgs, options) => {
294
+ const token = getToken(options.token);
295
+ const pConfig = loadProjectConfig();
296
+
297
+ const project = options.project || pConfig.project;
298
+ const env = options.env || pConfig.environment;
299
+
300
+ if (!token) {
301
+ console.error('\x1b[31mError: Not logged in.\x1b[0m');
302
+ process.exit(1);
303
+ }
304
+
305
+ if (!project || !env) {
306
+ console.error('\x1b[31mError: Project and Environment are required.\x1b[0m');
307
+ process.exit(1);
308
+ }
309
+
310
+ try {
311
+ const response = await fetch(`${options.apiUrl}/variables?team=${encodeURIComponent(project)}&environment=${encodeURIComponent(env)}`, {
312
+ headers: { 'Authorization': `Bearer ${token}` }
313
+ });
314
+
315
+ if (!response.ok) throw new Error('Failed to fetch variables');
316
+ const data = await response.json();
317
+ const variables = data.variables || [];
318
+
319
+ // Create new env object
320
+ const newEnv = { ...process.env };
321
+ variables.forEach((v: any) => {
322
+ newEnv[v.key] = v.value;
323
+ });
324
+
325
+ const [cmd, ...args] = commandArgs;
326
+ const child = spawn(cmd, args, {
327
+ stdio: 'inherit',
328
+ env: newEnv,
329
+ shell: true
330
+ });
331
+
332
+ child.on('exit', (code) => {
333
+ process.exit(code || 0);
334
+ });
335
+ } catch (error: any) {
336
+ console.error(`\x1b[31mRun failed:\x1b[0m ${error.message}`);
337
+ process.exit(1);
338
+ }
339
+ });
340
+
341
+ program.parse(process.argv);