@eve-horizon/cli 0.0.1

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.
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleAuth = handleAuth;
4
+ const args_1 = require("../lib/args");
5
+ const config_1 = require("../lib/config");
6
+ const client_1 = require("../lib/client");
7
+ const output_1 = require("../lib/output");
8
+ async function handleAuth(subcommand, flags, context, credentials) {
9
+ const json = Boolean(flags.json);
10
+ switch (subcommand) {
11
+ case 'login': {
12
+ const status = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true, tokenOverride: '' });
13
+ if (status.ok) {
14
+ const data = status.data;
15
+ if (data.auth_enabled === false) {
16
+ (0, output_1.outputJson)(data, json, 'Auth disabled for this stack; login not required.');
17
+ return;
18
+ }
19
+ }
20
+ const email = (0, args_1.getStringFlag)(flags, ['email']) ?? process.env.EVE_AUTH_EMAIL;
21
+ const password = (0, args_1.getStringFlag)(flags, ['password']) ?? process.env.EVE_AUTH_PASSWORD;
22
+ if (!email || !password) {
23
+ throw new Error('Usage: eve auth login --email <email> --password <password>');
24
+ }
25
+ const supabaseUrl = (0, args_1.getStringFlag)(flags, ['supabase-url']) ||
26
+ process.env.EVE_SUPABASE_URL ||
27
+ context.profile.supabase_url;
28
+ const supabaseAnonKey = (0, args_1.getStringFlag)(flags, ['supabase-anon-key']) ||
29
+ process.env.EVE_SUPABASE_ANON_KEY ||
30
+ context.profile.supabase_anon_key;
31
+ if (!supabaseUrl || !supabaseAnonKey) {
32
+ throw new Error('Missing Supabase config. Provide --supabase-url and --supabase-anon-key.');
33
+ }
34
+ const loginResponse = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=password`, {
35
+ method: 'POST',
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ apikey: supabaseAnonKey,
39
+ Authorization: `Bearer ${supabaseAnonKey}`,
40
+ },
41
+ body: JSON.stringify({ email, password }),
42
+ });
43
+ const loginText = await loginResponse.text();
44
+ let loginData = null;
45
+ if (loginText) {
46
+ try {
47
+ loginData = JSON.parse(loginText);
48
+ }
49
+ catch {
50
+ loginData = loginText;
51
+ }
52
+ }
53
+ if (!loginResponse.ok || !loginData || typeof loginData !== 'object') {
54
+ throw new Error(`Supabase login failed: ${loginText}`);
55
+ }
56
+ const payload = loginData;
57
+ if (!payload.access_token) {
58
+ throw new Error('Supabase login response missing access_token');
59
+ }
60
+ const expiresAt = payload.expires_in
61
+ ? Math.floor(Date.now() / 1000) + payload.expires_in
62
+ : undefined;
63
+ credentials.profiles[context.profileName] = {
64
+ access_token: payload.access_token,
65
+ refresh_token: payload.refresh_token,
66
+ expires_at: expiresAt,
67
+ token_type: payload.token_type,
68
+ };
69
+ (0, config_1.saveCredentials)(credentials);
70
+ (0, output_1.outputJson)({ profile: context.profileName, token_type: payload.token_type }, json, '✓ Logged in');
71
+ return;
72
+ }
73
+ case 'logout': {
74
+ if (credentials.profiles[context.profileName]) {
75
+ delete credentials.profiles[context.profileName];
76
+ (0, config_1.saveCredentials)(credentials);
77
+ }
78
+ (0, output_1.outputJson)({ profile: context.profileName }, json, '✓ Logged out');
79
+ return;
80
+ }
81
+ case 'status':
82
+ case 'whoami': {
83
+ const response = await (0, client_1.requestRaw)(context, '/auth/me', { allowError: true });
84
+ if (!response.ok) {
85
+ (0, output_1.outputJson)({ auth_enabled: true, authenticated: false }, json, 'Not authenticated');
86
+ return;
87
+ }
88
+ const data = response.data;
89
+ if (data.auth_enabled === false) {
90
+ (0, output_1.outputJson)(data, json, 'Auth disabled for this stack.');
91
+ return;
92
+ }
93
+ (0, output_1.outputJson)(data, json);
94
+ return;
95
+ }
96
+ default:
97
+ throw new Error('Usage: eve auth <login|logout|status|whoami>');
98
+ }
99
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleDb = handleDb;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const yaml_1 = require("yaml");
10
+ async function handleDb(subcommand, positionals, flags, context) {
11
+ const jsonOutput = Boolean(flags.json);
12
+ switch (subcommand) {
13
+ case 'schema':
14
+ return handleSchema(positionals, flags, context, jsonOutput);
15
+ case 'rls':
16
+ return handleRls(positionals, flags, context, jsonOutput);
17
+ case 'sql':
18
+ return handleSql(positionals, flags, context, jsonOutput);
19
+ case 'migrate':
20
+ return handleMigrate(positionals, flags, context, jsonOutput);
21
+ default:
22
+ throw new Error('Usage: eve db <schema|rls|sql|migrate> --env <name> [options]\n' +
23
+ ' schema --env <name> - show DB schema info\n' +
24
+ ' rls --env <name> - show RLS policies and tables\n' +
25
+ ' sql --env <name> --sql <stmt> [--write] - run parameterized SQL\n' +
26
+ ' migrate --env <name> [--component <name>] [--path <dir>] - apply pending migrations');
27
+ }
28
+ }
29
+ async function handleSchema(positionals, flags, context, jsonOutput) {
30
+ const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
31
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/db/schema`);
32
+ (0, output_1.outputJson)(response, jsonOutput);
33
+ }
34
+ async function handleRls(positionals, flags, context, jsonOutput) {
35
+ const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
36
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/db/rls`);
37
+ (0, output_1.outputJson)(response, jsonOutput);
38
+ }
39
+ async function handleSql(positionals, flags, context, jsonOutput) {
40
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
41
+ const envFromFlag = (0, args_1.getStringFlag)(flags, ['env']);
42
+ const envName = envFromFlag ?? positionals[0];
43
+ if (!projectId || !envName) {
44
+ throw new Error('Missing project or env. Usage: --env <name> [--project <id>]');
45
+ }
46
+ const sqlPositionals = envFromFlag ? positionals : positionals.slice(1);
47
+ const sqlInput = (0, args_1.getStringFlag)(flags, ['sql']) ?? sqlPositionals.join(' ');
48
+ const fileInput = (0, args_1.getStringFlag)(flags, ['file']);
49
+ const sqlText = fileInput ? (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(fileInput), 'utf-8') : sqlInput;
50
+ if (!sqlText) {
51
+ throw new Error('Usage: eve db sql --env <name> --sql <statement> [--params <json>] [--write]');
52
+ }
53
+ const paramsFlag = (0, args_1.getStringFlag)(flags, ['params']);
54
+ const params = paramsFlag ? parseJson(paramsFlag, '--params') : undefined;
55
+ const allowWrite = (0, args_1.toBoolean)(flags.write);
56
+ if (allowWrite) {
57
+ console.warn('⚠️ Write mode enabled - changes will be committed to the database');
58
+ }
59
+ const body = {
60
+ sql: sqlText,
61
+ ...(params !== undefined ? { params } : {}),
62
+ ...(allowWrite !== undefined ? { allow_write: allowWrite } : {}),
63
+ };
64
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/db/sql`, {
65
+ method: 'POST',
66
+ body,
67
+ });
68
+ (0, output_1.outputJson)(response, jsonOutput);
69
+ }
70
+ async function handleMigrate(positionals, flags, context, jsonOutput) {
71
+ const { projectId, envName } = resolveProjectEnv(positionals, flags, context);
72
+ const manifest = loadLocalManifest();
73
+ const migrationsPath = getMigrationsPath(flags, manifest);
74
+ const migrations = loadMigrations(migrationsPath);
75
+ if (migrations.length === 0) {
76
+ throw new Error(`No migrations found in ${migrationsPath}`);
77
+ }
78
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/migrate`, {
79
+ method: 'POST',
80
+ body: { migrations },
81
+ });
82
+ (0, output_1.outputJson)(response, jsonOutput);
83
+ }
84
+ function resolveProjectEnv(positionals, flags, context) {
85
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
86
+ const envName = (0, args_1.getStringFlag)(flags, ['env']) ?? positionals[0];
87
+ if (!projectId || !envName) {
88
+ throw new Error('Missing project or env. Usage: --env <name> [--project <id>]');
89
+ }
90
+ return { projectId, envName };
91
+ }
92
+ function loadLocalManifest() {
93
+ const manifestPath = (0, node_path_1.resolve)('.eve/manifest.yaml');
94
+ if (!(0, node_fs_1.existsSync)(manifestPath)) {
95
+ return undefined;
96
+ }
97
+ const content = (0, node_fs_1.readFileSync)(manifestPath, 'utf-8');
98
+ return (0, yaml_1.parse)(content);
99
+ }
100
+ function getMigrationsPath(flags, manifest) {
101
+ // 1. --path flag takes precedence
102
+ const pathFlag = (0, args_1.getStringFlag)(flags, ['path']);
103
+ if (pathFlag) {
104
+ return (0, node_path_1.resolve)(pathFlag);
105
+ }
106
+ // 2. Check --component flag or find first database component
107
+ const componentName = (0, args_1.getStringFlag)(flags, ['component']);
108
+ const components = manifest?.components;
109
+ if (components) {
110
+ if (componentName) {
111
+ // Specific component requested
112
+ const component = components[componentName];
113
+ if (component?.migrations?.path) {
114
+ return (0, node_path_1.resolve)(component.migrations.path);
115
+ }
116
+ }
117
+ else {
118
+ // Find first database component with migrations
119
+ for (const [name, component] of Object.entries(components)) {
120
+ if (component?.type === 'database' && component?.migrations?.path) {
121
+ return (0, node_path_1.resolve)(component.migrations.path);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ // 3. Default to .eve/migrations
127
+ return (0, node_path_1.resolve)('.eve/migrations');
128
+ }
129
+ function parseJson(value, label) {
130
+ try {
131
+ return JSON.parse(value);
132
+ }
133
+ catch (error) {
134
+ throw new Error(`${label} must be valid JSON: ${error.message}`);
135
+ }
136
+ }
137
+ function loadMigrations(dir) {
138
+ if (!(0, node_fs_1.existsSync)(dir)) {
139
+ throw new Error(`Migrations directory not found: ${dir}`);
140
+ }
141
+ const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })
142
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.sql'))
143
+ .map((entry) => entry.name)
144
+ .sort();
145
+ return entries.map((name) => ({
146
+ name,
147
+ sql: (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(dir, name), 'utf-8'),
148
+ }));
149
+ }
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleEnv = handleEnv;
4
+ const args_1 = require("../lib/args");
5
+ const client_1 = require("../lib/client");
6
+ const output_1 = require("../lib/output");
7
+ const child_process_1 = require("child_process");
8
+ // ============================================================================
9
+ // Main Handler
10
+ // ============================================================================
11
+ async function handleEnv(subcommand, positionals, flags, context) {
12
+ const json = Boolean(flags.json);
13
+ switch (subcommand) {
14
+ case 'list':
15
+ return handleList(positionals, flags, context, json);
16
+ case 'show':
17
+ return handleShow(positionals, flags, context, json);
18
+ case 'create':
19
+ return handleCreate(positionals, flags, context, json);
20
+ case 'deploy':
21
+ return handleDeploy(positionals, flags, context, json);
22
+ default:
23
+ throw new Error('Usage: eve env <list|show|create|deploy>\n' +
24
+ ' list [project] - list environments for a project\n' +
25
+ ' show <project> <name> - show details of an environment\n' +
26
+ ' create <name> --type=<type> [options] - create an environment\n' +
27
+ ' deploy <project> <name> [--tag=<tag>] - deploy to an environment');
28
+ }
29
+ }
30
+ // ============================================================================
31
+ // Subcommand Handlers
32
+ // ============================================================================
33
+ /**
34
+ * eve env list [project]
35
+ * List environments for a project
36
+ */
37
+ async function handleList(positionals, flags, context, json) {
38
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
39
+ if (!projectId) {
40
+ throw new Error('Usage: eve env list [project] [--project=<id>]');
41
+ }
42
+ const query = buildQuery({
43
+ limit: (0, args_1.getStringFlag)(flags, ['limit']),
44
+ offset: (0, args_1.getStringFlag)(flags, ['offset']),
45
+ });
46
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs${query}`);
47
+ if (json) {
48
+ (0, output_1.outputJson)(response, json);
49
+ }
50
+ else {
51
+ if (response.data.length === 0) {
52
+ console.log('No environments found.');
53
+ return;
54
+ }
55
+ formatEnvironmentsTable(response.data);
56
+ }
57
+ }
58
+ /**
59
+ * eve env show <project> <name>
60
+ * Show details of an environment
61
+ */
62
+ async function handleShow(positionals, flags, context, json) {
63
+ const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
64
+ const envName = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['name']);
65
+ if (!projectId || !envName) {
66
+ throw new Error('Usage: eve env show <project> <name> [--project=<id>] [--name=<name>]');
67
+ }
68
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}`);
69
+ if (json) {
70
+ (0, output_1.outputJson)(response, json);
71
+ }
72
+ else {
73
+ formatEnvironmentDetails(response);
74
+ }
75
+ }
76
+ /**
77
+ * eve env create <name> --type=<type> [--namespace=<ns>] [--db-ref=<ref>]
78
+ * Create an environment
79
+ */
80
+ async function handleCreate(positionals, flags, context, json) {
81
+ const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
82
+ const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
83
+ const type = (0, args_1.getStringFlag)(flags, ['type']);
84
+ if (!projectId) {
85
+ throw new Error('Usage: eve env create <name> --type=<type> [--project=<id>]');
86
+ }
87
+ if (!name) {
88
+ throw new Error('Usage: eve env create <name> --type=<type>');
89
+ }
90
+ if (!type || !['persistent', 'temporary'].includes(type)) {
91
+ throw new Error('--type must be either "persistent" or "temporary"');
92
+ }
93
+ const body = {
94
+ name,
95
+ type,
96
+ };
97
+ const namespace = (0, args_1.getStringFlag)(flags, ['namespace']);
98
+ if (namespace) {
99
+ body.namespace = namespace;
100
+ }
101
+ const dbRef = (0, args_1.getStringFlag)(flags, ['db-ref', 'dbRef']);
102
+ if (dbRef) {
103
+ body.db_ref = dbRef;
104
+ }
105
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs`, {
106
+ method: 'POST',
107
+ body,
108
+ });
109
+ if (json) {
110
+ (0, output_1.outputJson)(response, json);
111
+ }
112
+ else {
113
+ console.log(`✓ Environment created: ${response.name} (${response.id})`);
114
+ console.log(` Type: ${response.type}`);
115
+ console.log(` Namespace: ${response.namespace || '(none)'}`);
116
+ console.log(` DB Ref: ${response.db_ref || '(none)'}`);
117
+ }
118
+ }
119
+ /**
120
+ * eve env deploy [project] <name>
121
+ * Deploy to an environment
122
+ * If project is in profile, can use: eve env deploy <name>
123
+ */
124
+ async function handleDeploy(positionals, flags, context, json) {
125
+ // Smart positional parsing:
126
+ // - 2 positionals: [project, envName]
127
+ // - 1 positional + project in context: envName
128
+ // - 1 positional + no project in context: assume it's project, error for missing name
129
+ let projectId;
130
+ let envName;
131
+ const flagProject = (0, args_1.getStringFlag)(flags, ['project']);
132
+ const flagName = (0, args_1.getStringFlag)(flags, ['name']);
133
+ if (positionals.length >= 2) {
134
+ projectId = positionals[0];
135
+ envName = positionals[1];
136
+ }
137
+ else if (positionals.length === 1) {
138
+ if (flagProject || context.projectId) {
139
+ // Single positional is the env name
140
+ projectId = flagProject ?? context.projectId;
141
+ envName = positionals[0];
142
+ }
143
+ else {
144
+ // Single positional must be project, no env name
145
+ projectId = positionals[0];
146
+ }
147
+ }
148
+ else {
149
+ projectId = flagProject ?? context.projectId;
150
+ envName = flagName;
151
+ }
152
+ if (!projectId || !envName) {
153
+ throw new Error('Usage: eve env deploy [project] <name> [--tag=<tag>] [--project=<id>]');
154
+ }
155
+ // Get git SHA from current directory
156
+ const gitSha = getGitSha();
157
+ if (!gitSha) {
158
+ throw new Error('Failed to get git SHA. Are you in a git repository?');
159
+ }
160
+ if (!json) {
161
+ console.log(`Deploying commit ${gitSha.substring(0, 8)} to ${envName}...`);
162
+ }
163
+ // Get manifest hash from server
164
+ const manifest = await (0, client_1.requestJson)(context, `/projects/${projectId}/manifest`);
165
+ if (!json) {
166
+ console.log(`Using manifest ${manifest.manifest_hash.substring(0, 8)}...`);
167
+ }
168
+ // Get optional image tag for local testing
169
+ const imageTag = (0, args_1.getStringFlag)(flags, ['tag']);
170
+ // POST deployment
171
+ const body = {
172
+ git_sha: gitSha,
173
+ manifest_hash: manifest.manifest_hash,
174
+ };
175
+ if (imageTag) {
176
+ body.image_tag = imageTag;
177
+ }
178
+ const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/envs/${envName}/deploy`, {
179
+ method: 'POST',
180
+ body,
181
+ });
182
+ if (json) {
183
+ (0, output_1.outputJson)(response, json);
184
+ }
185
+ else {
186
+ console.log('');
187
+ console.log(`Deployment successful!`);
188
+ console.log(` Release ID: ${response.release.id}`);
189
+ console.log(` Environment: ${response.environment.name}`);
190
+ console.log(` Namespace: ${response.environment.namespace || '(none)'}`);
191
+ }
192
+ }
193
+ // ============================================================================
194
+ // Helper Functions
195
+ // ============================================================================
196
+ /**
197
+ * Build query string from parameters
198
+ */
199
+ function buildQuery(params) {
200
+ const search = new URLSearchParams();
201
+ Object.entries(params).forEach(([key, value]) => {
202
+ if (value === undefined || value === '')
203
+ return;
204
+ search.set(key, String(value));
205
+ });
206
+ const query = search.toString();
207
+ return query ? `?${query}` : '';
208
+ }
209
+ /**
210
+ * Get current git SHA from working directory
211
+ */
212
+ function getGitSha() {
213
+ try {
214
+ const sha = (0, child_process_1.execSync)('git rev-parse HEAD', {
215
+ encoding: 'utf8',
216
+ stdio: ['pipe', 'pipe', 'pipe'],
217
+ }).trim();
218
+ return sha;
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ }
224
+ /**
225
+ * Format environments as a human-readable table
226
+ */
227
+ function formatEnvironmentsTable(environments) {
228
+ if (environments.length === 0) {
229
+ console.log('No environments found.');
230
+ return;
231
+ }
232
+ // Calculate column widths
233
+ const nameWidth = Math.max(4, ...environments.map((e) => e.name.length));
234
+ const typeWidth = Math.max(4, ...environments.map((e) => e.type.length));
235
+ const namespaceWidth = Math.max(9, ...environments.map((e) => e.namespace?.length ?? 0));
236
+ // Header
237
+ const header = [
238
+ padRight('Name', nameWidth),
239
+ padRight('Type', typeWidth),
240
+ padRight('Namespace', namespaceWidth),
241
+ 'Current Release',
242
+ ].join(' ');
243
+ console.log(header);
244
+ console.log('-'.repeat(header.length));
245
+ // Rows
246
+ for (const env of environments) {
247
+ const releaseDisplay = env.current_release_id
248
+ ? env.current_release_id.substring(0, 12) + '...'
249
+ : '-';
250
+ const row = [
251
+ padRight(env.name, nameWidth),
252
+ padRight(env.type, typeWidth),
253
+ padRight(env.namespace || '-', namespaceWidth),
254
+ releaseDisplay,
255
+ ].join(' ');
256
+ console.log(row);
257
+ }
258
+ console.log('');
259
+ console.log(`Total: ${environments.length} environment(s)`);
260
+ }
261
+ /**
262
+ * Format a single environment's details
263
+ */
264
+ function formatEnvironmentDetails(env) {
265
+ console.log(`Environment: ${env.name}`);
266
+ console.log('');
267
+ console.log(` ID: ${env.id}`);
268
+ console.log(` Project: ${env.project_id}`);
269
+ console.log(` Type: ${env.type}`);
270
+ console.log(` Namespace: ${env.namespace || '(none)'}`);
271
+ console.log(` Database Ref: ${env.db_ref || '(none)'}`);
272
+ console.log(` Current Release: ${env.current_release_id || '(none)'}`);
273
+ if (env.overrides && Object.keys(env.overrides).length > 0) {
274
+ console.log('');
275
+ console.log(' Overrides:');
276
+ for (const [key, value] of Object.entries(env.overrides)) {
277
+ console.log(` ${key}: ${JSON.stringify(value)}`);
278
+ }
279
+ }
280
+ console.log('');
281
+ console.log(` Created: ${formatDate(env.created_at)}`);
282
+ console.log(` Updated: ${formatDate(env.updated_at)}`);
283
+ }
284
+ /**
285
+ * Format a date string for display
286
+ */
287
+ function formatDate(dateStr) {
288
+ try {
289
+ const date = new Date(dateStr);
290
+ return date.toLocaleString();
291
+ }
292
+ catch {
293
+ return dateStr;
294
+ }
295
+ }
296
+ /**
297
+ * Pad a string to the right with spaces
298
+ */
299
+ function padRight(str, width) {
300
+ if (str.length >= width)
301
+ return str;
302
+ return str + ' '.repeat(width - str.length);
303
+ }