@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.
- package/README.md +275 -0
- package/bin/eve.js +2 -0
- package/dist/commands/api.js +350 -0
- package/dist/commands/auth.js +99 -0
- package/dist/commands/db.js +149 -0
- package/dist/commands/env.js +303 -0
- package/dist/commands/event.js +273 -0
- package/dist/commands/harness.js +96 -0
- package/dist/commands/job.js +2266 -0
- package/dist/commands/org.js +97 -0
- package/dist/commands/pipeline.js +403 -0
- package/dist/commands/profile.js +103 -0
- package/dist/commands/project.js +185 -0
- package/dist/commands/secrets.js +147 -0
- package/dist/commands/system.js +457 -0
- package/dist/commands/workflow.js +337 -0
- package/dist/index.js +97 -0
- package/dist/lib/args.js +57 -0
- package/dist/lib/client.js +116 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/context.js +45 -0
- package/dist/lib/help.js +938 -0
- package/dist/lib/logs.js +51 -0
- package/dist/lib/output.js +14 -0
- package/package.json +36 -0
|
@@ -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
|
+
}
|