@agentuity/cli 0.0.42 → 0.0.44
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/AGENTS.md +1 -1
- package/README.md +1 -1
- package/bin/cli.ts +7 -5
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +10 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/banner.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/api.d.ts +4 -4
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/signup.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/add.d.ts +2 -0
- package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/api.d.ts +16 -0
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/delete.d.ts +2 -0
- package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/index.d.ts +3 -0
- package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/list.d.ts +2 -0
- package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
- package/dist/cmd/auth/whoami.d.ts +2 -0
- package/dist/cmd/auth/whoami.d.ts.map +1 -0
- package/dist/cmd/bundle/ast.d.ts +14 -3
- package/dist/cmd/bundle/ast.d.ts.map +1 -1
- package/dist/cmd/bundle/ast.test.d.ts +2 -0
- package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
- package/dist/cmd/bundle/bundler.d.ts +6 -1
- package/dist/cmd/bundle/bundler.d.ts.map +1 -1
- package/dist/cmd/bundle/file.d.ts.map +1 -1
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
- package/dist/cmd/bundle/index.d.ts +1 -1
- package/dist/cmd/bundle/index.d.ts.map +1 -1
- package/dist/cmd/bundle/plugin.d.ts +2 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -0
- package/dist/cmd/cloud/domain.d.ts +17 -0
- package/dist/cmd/cloud/domain.d.ts.map +1 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/add.d.ts +2 -0
- package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/delete.d.ts +2 -0
- package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/index.d.ts +3 -0
- package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/list.d.ts +2 -0
- package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/download.d.ts +2 -0
- package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/index.d.ts +3 -0
- package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/upload.d.ts +2 -0
- package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/ssh.d.ts +2 -0
- package/dist/cmd/cloud/ssh.d.ts.map +1 -0
- package/dist/cmd/dev/api.d.ts +18 -0
- package/dist/cmd/dev/api.d.ts.map +1 -0
- package/dist/cmd/dev/download.d.ts +11 -0
- package/dist/cmd/dev/download.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/templates.d.ts +3 -0
- package/dist/cmd/dev/templates.d.ts.map +1 -0
- package/dist/cmd/env/delete.d.ts +2 -0
- package/dist/cmd/env/delete.d.ts.map +1 -0
- package/dist/cmd/env/get.d.ts +2 -0
- package/dist/cmd/env/get.d.ts.map +1 -0
- package/dist/cmd/env/import.d.ts +2 -0
- package/dist/cmd/env/import.d.ts.map +1 -0
- package/dist/cmd/env/index.d.ts +2 -0
- package/dist/cmd/env/index.d.ts.map +1 -0
- package/dist/cmd/env/list.d.ts.map +1 -0
- package/dist/cmd/env/pull.d.ts +2 -0
- package/dist/cmd/env/pull.d.ts.map +1 -0
- package/dist/cmd/env/push.d.ts +2 -0
- package/dist/cmd/env/push.d.ts.map +1 -0
- package/dist/cmd/env/set.d.ts +2 -0
- package/dist/cmd/env/set.d.ts.map +1 -0
- package/dist/cmd/profile/show.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/delete.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/list.d.ts.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +5 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/secret/delete.d.ts +2 -0
- package/dist/cmd/secret/delete.d.ts.map +1 -0
- package/dist/cmd/secret/get.d.ts +2 -0
- package/dist/cmd/secret/get.d.ts.map +1 -0
- package/dist/cmd/secret/import.d.ts +2 -0
- package/dist/cmd/secret/import.d.ts.map +1 -0
- package/dist/cmd/secret/index.d.ts +2 -0
- package/dist/cmd/secret/index.d.ts.map +1 -0
- package/dist/cmd/secret/list.d.ts +2 -0
- package/dist/cmd/secret/list.d.ts.map +1 -0
- package/dist/cmd/secret/pull.d.ts +2 -0
- package/dist/cmd/secret/pull.d.ts.map +1 -0
- package/dist/cmd/secret/push.d.ts +2 -0
- package/dist/cmd/secret/push.d.ts.map +1 -0
- package/dist/cmd/secret/set.d.ts +2 -0
- package/dist/cmd/secret/set.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/config.d.ts +11 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/crypto/box.d.ts +65 -0
- package/dist/crypto/box.d.ts.map +1 -0
- package/dist/crypto/box.test.d.ts +2 -0
- package/dist/crypto/box.test.d.ts.map +1 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/env-util.d.ts +67 -0
- package/dist/env-util.d.ts.map +1 -0
- package/dist/env-util.test.d.ts +2 -0
- package/dist/env-util.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/steps.d.ts +4 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/terminal.d.ts.map +1 -1
- package/dist/tui.d.ts +32 -2
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +250 -127
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detectSubagent.d.ts +15 -0
- package/dist/utils/detectSubagent.d.ts.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/package.json +11 -3
- package/src/api-errors.md +2 -2
- package/src/api.ts +12 -7
- package/src/auth.ts +116 -7
- package/src/banner.ts +13 -6
- package/src/cli.ts +709 -36
- package/src/cmd/auth/api.ts +10 -16
- package/src/cmd/auth/index.ts +3 -1
- package/src/cmd/auth/login.ts +24 -8
- package/src/cmd/auth/signup.ts +15 -11
- package/src/cmd/auth/ssh/add.ts +263 -0
- package/src/cmd/auth/ssh/api.ts +94 -0
- package/src/cmd/auth/ssh/delete.ts +102 -0
- package/src/cmd/auth/ssh/index.ts +10 -0
- package/src/cmd/auth/ssh/list.ts +74 -0
- package/src/cmd/auth/whoami.ts +69 -0
- package/src/cmd/bundle/ast.test.ts +565 -0
- package/src/cmd/bundle/ast.ts +457 -44
- package/src/cmd/bundle/bundler.ts +255 -57
- package/src/cmd/bundle/file.ts +6 -12
- package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
- package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
- package/src/cmd/bundle/index.ts +11 -11
- package/src/cmd/bundle/patch/aisdk.ts +1 -1
- package/src/cmd/bundle/plugin.ts +373 -53
- package/src/cmd/cloud/deploy.ts +336 -0
- package/src/cmd/cloud/domain.ts +92 -0
- package/src/cmd/cloud/index.ts +11 -0
- package/src/cmd/cloud/resource/add.ts +56 -0
- package/src/cmd/cloud/resource/delete.ts +120 -0
- package/src/cmd/cloud/resource/index.ts +11 -0
- package/src/cmd/cloud/resource/list.ts +69 -0
- package/src/cmd/cloud/scp/download.ts +59 -0
- package/src/cmd/cloud/scp/index.ts +9 -0
- package/src/cmd/cloud/scp/upload.ts +62 -0
- package/src/cmd/cloud/ssh.ts +68 -0
- package/src/cmd/dev/api.ts +46 -0
- package/src/cmd/dev/download.ts +111 -0
- package/src/cmd/dev/index.ts +362 -34
- package/src/cmd/dev/templates.ts +84 -0
- package/src/cmd/env/delete.ts +47 -0
- package/src/cmd/env/get.ts +53 -0
- package/src/cmd/env/import.ts +102 -0
- package/src/cmd/env/index.ts +22 -0
- package/src/cmd/env/list.ts +56 -0
- package/src/cmd/env/pull.ts +80 -0
- package/src/cmd/env/push.ts +37 -0
- package/src/cmd/env/set.ts +71 -0
- package/src/cmd/index.ts +2 -2
- package/src/cmd/profile/show.ts +15 -6
- package/src/cmd/project/create.ts +7 -2
- package/src/cmd/project/delete.ts +75 -18
- package/src/cmd/project/download.ts +3 -3
- package/src/cmd/project/list.ts +8 -8
- package/src/cmd/project/show.ts +3 -7
- package/src/cmd/project/template-flow.ts +186 -48
- package/src/cmd/secret/delete.ts +40 -0
- package/src/cmd/secret/get.ts +54 -0
- package/src/cmd/secret/import.ts +64 -0
- package/src/cmd/secret/index.ts +22 -0
- package/src/cmd/secret/list.ts +56 -0
- package/src/cmd/secret/pull.ts +78 -0
- package/src/cmd/secret/push.ts +37 -0
- package/src/cmd/secret/set.ts +45 -0
- package/src/cmd/version/index.ts +2 -1
- package/src/config.ts +257 -27
- package/src/crypto/box.test.ts +431 -0
- package/src/crypto/box.ts +477 -0
- package/src/download.ts +1 -0
- package/src/env-util.test.ts +194 -0
- package/src/env-util.ts +290 -0
- package/src/index.ts +5 -1
- package/src/schema-parser.ts +2 -3
- package/src/steps.ts +144 -10
- package/src/terminal.ts +24 -23
- package/src/tui.ts +208 -68
- package/src/types.ts +292 -202
- package/src/utils/detectSubagent.ts +31 -0
- package/src/utils/zip.ts +38 -0
- package/dist/cmd/example/create-user.d.ts +0 -2
- package/dist/cmd/example/create-user.d.ts.map +0 -1
- package/dist/cmd/example/create.d.ts +0 -2
- package/dist/cmd/example/create.d.ts.map +0 -1
- package/dist/cmd/example/deploy.d.ts.map +0 -1
- package/dist/cmd/example/index.d.ts.map +0 -1
- package/dist/cmd/example/list.d.ts.map +0 -1
- package/dist/cmd/example/optional-auth.d.ts +0 -3
- package/dist/cmd/example/optional-auth.d.ts.map +0 -1
- package/dist/cmd/example/run-command.d.ts +0 -2
- package/dist/cmd/example/run-command.d.ts.map +0 -1
- package/dist/cmd/example/sound.d.ts +0 -3
- package/dist/cmd/example/sound.d.ts.map +0 -1
- package/dist/cmd/example/spinner.d.ts +0 -2
- package/dist/cmd/example/spinner.d.ts.map +0 -1
- package/dist/cmd/example/steps.d.ts +0 -2
- package/dist/cmd/example/steps.d.ts.map +0 -1
- package/dist/cmd/example/version.d.ts +0 -2
- package/dist/cmd/example/version.d.ts.map +0 -1
- package/dist/logger.d.ts +0 -24
- package/dist/logger.d.ts.map +0 -1
- package/src/cmd/example/create-user.ts +0 -38
- package/src/cmd/example/create.ts +0 -31
- package/src/cmd/example/deploy.ts +0 -36
- package/src/cmd/example/index.ts +0 -29
- package/src/cmd/example/list.ts +0 -32
- package/src/cmd/example/optional-auth.ts +0 -38
- package/src/cmd/example/run-command.ts +0 -45
- package/src/cmd/example/sound.ts +0 -14
- package/src/cmd/example/spinner.ts +0 -44
- package/src/cmd/example/steps.ts +0 -66
- package/src/cmd/example/version.ts +0 -13
- package/src/logger.ts +0 -235
- /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
- /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
- /package/dist/cmd/{example → env}/list.d.ts +0 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { createPublicKey } from 'node:crypto';
|
|
3
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { createSubcommand } from '../../types';
|
|
6
|
+
import * as tui from '../../tui';
|
|
7
|
+
import { saveProjectDir } from '../../config';
|
|
8
|
+
import { runSteps, stepSuccess, stepSkipped, stepError, Step, ProgressCallback } from '../../steps';
|
|
9
|
+
import { bundle } from '../bundle/bundler';
|
|
10
|
+
import { loadBuildMetadata } from '../../config';
|
|
11
|
+
import {
|
|
12
|
+
projectEnvUpdate,
|
|
13
|
+
projectDeploymentCreate,
|
|
14
|
+
projectDeploymentUpdate,
|
|
15
|
+
projectDeploymentComplete,
|
|
16
|
+
type Deployment,
|
|
17
|
+
type BuildMetadata,
|
|
18
|
+
type DeploymentInstructions,
|
|
19
|
+
type DeploymentComplete,
|
|
20
|
+
} from '@agentuity/server';
|
|
21
|
+
import {
|
|
22
|
+
findEnvFile,
|
|
23
|
+
readEnvFile,
|
|
24
|
+
filterAgentuitySdkKeys,
|
|
25
|
+
splitEnvAndSecrets,
|
|
26
|
+
} from '../../env-util';
|
|
27
|
+
import { zipDir } from '../../utils/zip';
|
|
28
|
+
import { encryptFIPSKEMDEMStream } from '../../crypto/box';
|
|
29
|
+
import { checkCustomDomainForDNS } from './domain';
|
|
30
|
+
|
|
31
|
+
export const deploySubcommand = createSubcommand({
|
|
32
|
+
name: 'deploy',
|
|
33
|
+
description: 'Deploy project to the Agentuity Cloud',
|
|
34
|
+
toplevel: true,
|
|
35
|
+
requires: { auth: true, project: true, apiClient: true },
|
|
36
|
+
|
|
37
|
+
async handler(ctx) {
|
|
38
|
+
const { project, apiClient, projectDir, config, options } = ctx;
|
|
39
|
+
|
|
40
|
+
let deployment: Deployment | undefined;
|
|
41
|
+
let build: BuildMetadata | undefined;
|
|
42
|
+
let instructions: DeploymentInstructions | undefined;
|
|
43
|
+
let complete: DeploymentComplete | undefined;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await saveProjectDir(projectDir);
|
|
47
|
+
|
|
48
|
+
await runSteps(
|
|
49
|
+
[
|
|
50
|
+
!project.deployment?.domains?.length
|
|
51
|
+
? null
|
|
52
|
+
: {
|
|
53
|
+
label: `Validate Custom Domain${project.deployment.domains.length > 1 ? `s: ${project.deployment.domains.join(', ')}` : `: ${project.deployment.domains[0]}`}`,
|
|
54
|
+
run: async () => {
|
|
55
|
+
if (project.deployment?.domains?.length) {
|
|
56
|
+
const result = await checkCustomDomainForDNS(
|
|
57
|
+
project.projectId,
|
|
58
|
+
project.deployment.domains,
|
|
59
|
+
config
|
|
60
|
+
);
|
|
61
|
+
for (const r of result) {
|
|
62
|
+
if (r.success) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (r.message) {
|
|
66
|
+
return stepError(r.message);
|
|
67
|
+
}
|
|
68
|
+
return stepError('unknown dns error'); // shouldn't get here
|
|
69
|
+
}
|
|
70
|
+
return stepSuccess();
|
|
71
|
+
}
|
|
72
|
+
return stepSkipped();
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: 'Sync Env & Secrets',
|
|
77
|
+
run: async () => {
|
|
78
|
+
try {
|
|
79
|
+
// Read local env file (.env.production or .env)
|
|
80
|
+
const envFilePath = await findEnvFile(projectDir);
|
|
81
|
+
const localEnv = await readEnvFile(envFilePath);
|
|
82
|
+
|
|
83
|
+
// Filter out AGENTUITY_ keys
|
|
84
|
+
const filteredEnv = filterAgentuitySdkKeys(localEnv);
|
|
85
|
+
|
|
86
|
+
if (Object.keys(filteredEnv).length === 0) {
|
|
87
|
+
return stepSkipped('no variables to sync');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Split into env and secrets
|
|
91
|
+
const { env, secrets } = splitEnvAndSecrets(filteredEnv);
|
|
92
|
+
|
|
93
|
+
if (Object.keys(env).length === 0 && Object.keys(secrets).length === 0) {
|
|
94
|
+
return stepSkipped('no variables to sync');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Push to cloud
|
|
98
|
+
await projectEnvUpdate(apiClient, {
|
|
99
|
+
id: project.projectId,
|
|
100
|
+
env,
|
|
101
|
+
secrets,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return stepSuccess();
|
|
105
|
+
} catch (ex) {
|
|
106
|
+
// Non-fatal: log warning but continue deployment
|
|
107
|
+
const _ex = ex as Error;
|
|
108
|
+
return stepSkipped(_ex.message ?? 'failed to sync env variables');
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: 'Create Deployment',
|
|
114
|
+
run: async () => {
|
|
115
|
+
try {
|
|
116
|
+
deployment = await projectDeploymentCreate(
|
|
117
|
+
apiClient,
|
|
118
|
+
project.projectId,
|
|
119
|
+
project.deployment
|
|
120
|
+
);
|
|
121
|
+
return stepSuccess();
|
|
122
|
+
} catch (ex) {
|
|
123
|
+
return stepError(ex instanceof Error ? ex.message : String(ex));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'Build, Verify and Package',
|
|
129
|
+
run: async () => {
|
|
130
|
+
if (!deployment) {
|
|
131
|
+
return stepError('deployment was null');
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await bundle({
|
|
135
|
+
rootDir: resolve(projectDir),
|
|
136
|
+
dev: false,
|
|
137
|
+
deploymentId: deployment.id,
|
|
138
|
+
orgId: deployment.orgId,
|
|
139
|
+
projectId: project.projectId,
|
|
140
|
+
project,
|
|
141
|
+
});
|
|
142
|
+
build = await loadBuildMetadata(join(projectDir, '.agentuity'));
|
|
143
|
+
instructions = await projectDeploymentUpdate(
|
|
144
|
+
apiClient,
|
|
145
|
+
deployment.id,
|
|
146
|
+
build
|
|
147
|
+
);
|
|
148
|
+
return stepSuccess();
|
|
149
|
+
} catch (ex) {
|
|
150
|
+
const _ex = ex as Error;
|
|
151
|
+
return stepError(_ex.message ?? 'Error building your project');
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'progress',
|
|
157
|
+
label: 'Encrypt and Upload Deployment',
|
|
158
|
+
run: async (progress: ProgressCallback) => {
|
|
159
|
+
if (!deployment) {
|
|
160
|
+
return stepError('deployment was null');
|
|
161
|
+
}
|
|
162
|
+
if (!instructions) {
|
|
163
|
+
return stepError('deployment instructions were null');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
progress(5);
|
|
167
|
+
ctx.logger.trace('Starting deployment zip creation');
|
|
168
|
+
// zip up the assets folder
|
|
169
|
+
const deploymentZip = join(tmpdir(), `${deployment.id}.zip`);
|
|
170
|
+
await zipDir(join(projectDir, '.agentuity'), deploymentZip, {
|
|
171
|
+
filter: (_filename: string, relative: string) => {
|
|
172
|
+
// we don't include assets in the deployment
|
|
173
|
+
if (relative.startsWith('web/assets/')) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (relative.startsWith('web/chunk/')) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (relative.startsWith('web/public/')) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// exclude sourcemaps from deployment
|
|
183
|
+
if (relative.endsWith('.map')) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
ctx.logger.trace(`Deployment zip created: ${deploymentZip}`);
|
|
190
|
+
|
|
191
|
+
progress(20);
|
|
192
|
+
// Encrypt the deployment zip using the public key from deployment
|
|
193
|
+
const encryptedZip = join(tmpdir(), `${deployment.id}.enc.zip`);
|
|
194
|
+
try {
|
|
195
|
+
ctx.logger.trace('Creating public key');
|
|
196
|
+
const publicKey = createPublicKey({
|
|
197
|
+
key: deployment.publicKey,
|
|
198
|
+
format: 'pem',
|
|
199
|
+
type: 'spki',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
ctx.logger.trace('Creating read/write streams');
|
|
203
|
+
const src = createReadStream(deploymentZip);
|
|
204
|
+
const dst = createWriteStream(encryptedZip);
|
|
205
|
+
|
|
206
|
+
ctx.logger.trace('Starting encryption');
|
|
207
|
+
// Wait for encryption to complete
|
|
208
|
+
await encryptFIPSKEMDEMStream(publicKey, src, dst);
|
|
209
|
+
ctx.logger.trace('Encryption complete');
|
|
210
|
+
|
|
211
|
+
progress(40);
|
|
212
|
+
ctx.logger.trace('Waiting for stream to finish');
|
|
213
|
+
// End stream and wait for it to finish writing
|
|
214
|
+
await new Promise<void>((resolve, reject) => {
|
|
215
|
+
dst.once('finish', resolve);
|
|
216
|
+
dst.once('error', reject);
|
|
217
|
+
dst.end();
|
|
218
|
+
});
|
|
219
|
+
ctx.logger.trace('Stream finished');
|
|
220
|
+
|
|
221
|
+
progress(50);
|
|
222
|
+
ctx.logger.trace(`Uploading deployment to ${instructions.deployment}`);
|
|
223
|
+
const zipfile = Bun.file(encryptedZip);
|
|
224
|
+
const fileSize = await zipfile.size;
|
|
225
|
+
ctx.logger.trace(`Upload file size: ${fileSize} bytes`);
|
|
226
|
+
const resp = await fetch(instructions.deployment, {
|
|
227
|
+
method: 'PUT',
|
|
228
|
+
duplex: 'half',
|
|
229
|
+
headers: {
|
|
230
|
+
'Content-Type': 'application/zip',
|
|
231
|
+
},
|
|
232
|
+
body: zipfile,
|
|
233
|
+
});
|
|
234
|
+
ctx.logger.trace(`Upload response: ${resp.status}`);
|
|
235
|
+
if (!resp.ok) {
|
|
236
|
+
return stepError(`Error uploading deployment: ${await resp.text()}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
progress(70);
|
|
240
|
+
ctx.logger.trace('Consuming response body');
|
|
241
|
+
// Wait for response body to be consumed before deleting
|
|
242
|
+
await resp.arrayBuffer();
|
|
243
|
+
ctx.logger.trace('Deleting encrypted zip');
|
|
244
|
+
await zipfile.delete();
|
|
245
|
+
} finally {
|
|
246
|
+
ctx.logger.trace('Cleanup');
|
|
247
|
+
// Cleanup in case of error
|
|
248
|
+
if (await Bun.file(encryptedZip).exists()) {
|
|
249
|
+
await Bun.file(encryptedZip).delete();
|
|
250
|
+
}
|
|
251
|
+
await Bun.file(deploymentZip).delete();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
progress(80);
|
|
255
|
+
if (build?.assets) {
|
|
256
|
+
ctx.logger.trace(`Uploading ${build.assets.length} assets`);
|
|
257
|
+
if (!instructions.assets) {
|
|
258
|
+
return stepError(
|
|
259
|
+
'server did not provide asset upload URLs; upload aborted'
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const promises: Promise<Response>[] = [];
|
|
264
|
+
for (const asset of build.assets) {
|
|
265
|
+
const assetUrl = instructions.assets[asset.filename];
|
|
266
|
+
if (!assetUrl) {
|
|
267
|
+
return stepError(
|
|
268
|
+
`server did not provide upload URL for asset "${asset.filename}"; upload aborted`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const file = Bun.file(
|
|
273
|
+
join(projectDir, '.agentuity', 'web', asset.filename)
|
|
274
|
+
);
|
|
275
|
+
promises.push(
|
|
276
|
+
fetch(assetUrl, {
|
|
277
|
+
method: 'PUT',
|
|
278
|
+
duplex: 'half',
|
|
279
|
+
headers: {
|
|
280
|
+
'Content-Type': asset.contentType,
|
|
281
|
+
},
|
|
282
|
+
body: file,
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
ctx.logger.trace('Waiting for asset uploads');
|
|
287
|
+
const resps = await Promise.all(promises);
|
|
288
|
+
for (const r of resps) {
|
|
289
|
+
if (!r.ok) {
|
|
290
|
+
return stepError(`error uploading asset: ${await r.text()}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
ctx.logger.trace('Asset uploads complete');
|
|
294
|
+
progress(95);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
progress(100);
|
|
298
|
+
return stepSuccess();
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
label: 'Provision Deployment',
|
|
303
|
+
run: async () => {
|
|
304
|
+
if (!deployment) {
|
|
305
|
+
return stepError('deployment was null');
|
|
306
|
+
}
|
|
307
|
+
complete = await projectDeploymentComplete(apiClient, deployment.id);
|
|
308
|
+
return stepSuccess();
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
].filter(Boolean) as Step[],
|
|
312
|
+
options.logLevel
|
|
313
|
+
);
|
|
314
|
+
tui.success('Your project was deployed!');
|
|
315
|
+
|
|
316
|
+
if (complete?.publicUrls && deployment) {
|
|
317
|
+
tui.arrow(tui.bold(tui.padRight('Deployment ID:', 17)) + tui.link(deployment.id));
|
|
318
|
+
if (complete.publicUrls.custom?.length) {
|
|
319
|
+
for (const url of complete.publicUrls.custom) {
|
|
320
|
+
tui.arrow(tui.bold(tui.padRight('Deployment URL:', 17)) + tui.link(url));
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
tui.arrow(
|
|
324
|
+
tui.bold(tui.padRight('Deployment URL:', 17)) +
|
|
325
|
+
tui.link(complete.publicUrls.deployment)
|
|
326
|
+
);
|
|
327
|
+
tui.arrow(
|
|
328
|
+
tui.bold(tui.padRight('Project URL:', 17)) + tui.link(complete.publicUrls.latest)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch (ex) {
|
|
333
|
+
tui.fatal(`unexpected error trying to deploy project. ${ex}`);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as dns from 'node:dns';
|
|
2
|
+
import type { Config } from '../../types';
|
|
3
|
+
|
|
4
|
+
export interface DNSResult {
|
|
5
|
+
domain: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
message?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This function will check for each of the custom domains and make sure they are correctly
|
|
12
|
+
* configured in DNS
|
|
13
|
+
*
|
|
14
|
+
* @param projectId the project id
|
|
15
|
+
* @param config Config
|
|
16
|
+
* @param domains array of domains to check
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
export async function checkCustomDomainForDNS(
|
|
20
|
+
projectId: string,
|
|
21
|
+
domains: string[],
|
|
22
|
+
config?: Config | null
|
|
23
|
+
): Promise<DNSResult[]> {
|
|
24
|
+
const suffix = config?.overrides?.api_url?.includes('agentuity.io')
|
|
25
|
+
? 'agentuity.io'
|
|
26
|
+
: 'agentuity.com';
|
|
27
|
+
const id = Bun.hash.xxHash64(`${projectId}latest`).toString(16);
|
|
28
|
+
const proxy = `p${id}.${suffix}`;
|
|
29
|
+
|
|
30
|
+
return Promise.all(
|
|
31
|
+
domains.map(async (domain) => {
|
|
32
|
+
try {
|
|
33
|
+
const timeoutMs = 5000;
|
|
34
|
+
let timeoutId: Timer | undefined;
|
|
35
|
+
|
|
36
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
37
|
+
timeoutId = setTimeout(() => {
|
|
38
|
+
reject(new Error(`DNS lookup timed out after ${timeoutMs}ms`));
|
|
39
|
+
}, timeoutMs);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = await Promise.race([
|
|
43
|
+
dns.promises.resolveCname(domain),
|
|
44
|
+
timeoutPromise,
|
|
45
|
+
]).finally(() => {
|
|
46
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (result.length) {
|
|
50
|
+
if (result[0] === proxy) {
|
|
51
|
+
return {
|
|
52
|
+
domain,
|
|
53
|
+
success: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
domain,
|
|
58
|
+
success: false,
|
|
59
|
+
message: `DNS record for ${domain} must have a CNAME record with the value: ${proxy}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
} catch (ex) {
|
|
63
|
+
const _ex = ex as { code: string; message?: string };
|
|
64
|
+
if (_ex.message?.includes('timed out')) {
|
|
65
|
+
return {
|
|
66
|
+
domain,
|
|
67
|
+
success: false,
|
|
68
|
+
message: `DNS lookup for ${domain} timed out after 5 seconds. Please check your DNS configuration.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (_ex.code !== 'ENOTFOUND') {
|
|
72
|
+
const errMsg =
|
|
73
|
+
ex instanceof Error
|
|
74
|
+
? ex.message
|
|
75
|
+
: typeof ex === 'string'
|
|
76
|
+
? ex
|
|
77
|
+
: JSON.stringify(ex);
|
|
78
|
+
return {
|
|
79
|
+
domain,
|
|
80
|
+
success: false,
|
|
81
|
+
message: `DNS returned an error resolving ${domain}: ${errMsg}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
domain,
|
|
87
|
+
success: false,
|
|
88
|
+
message: `To enable the custom domain ${domain}, create a CNAME DNS record with the value: ${proxy} and a TTL of 600`,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createCommand } from '../../types';
|
|
2
|
+
import { deploySubcommand } from './deploy';
|
|
3
|
+
import { resourceSubcommand } from './resource';
|
|
4
|
+
import { sshSubcommand } from './ssh';
|
|
5
|
+
import { scpSubcommand } from './scp';
|
|
6
|
+
|
|
7
|
+
export const command = createCommand({
|
|
8
|
+
name: 'cloud',
|
|
9
|
+
description: 'Cloud related commands',
|
|
10
|
+
subcommands: [deploySubcommand, resourceSubcommand, sshSubcommand, scpSubcommand],
|
|
11
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createResources } from '@agentuity/server';
|
|
3
|
+
import enquirer from 'enquirer';
|
|
4
|
+
import { createSubcommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
7
|
+
|
|
8
|
+
export const addSubcommand = createSubcommand({
|
|
9
|
+
name: 'add',
|
|
10
|
+
aliases: ['create'],
|
|
11
|
+
description: 'Add a cloud resource for an organization',
|
|
12
|
+
requires: { auth: true, org: true, region: true },
|
|
13
|
+
schema: {
|
|
14
|
+
options: z.object({
|
|
15
|
+
type: z.enum(['database', 'storage']).optional().describe('Resource type'),
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async handler(ctx) {
|
|
20
|
+
const { logger, opts, orgId, region, config, auth } = ctx;
|
|
21
|
+
|
|
22
|
+
// Determine resource type
|
|
23
|
+
let resourceType = opts.type;
|
|
24
|
+
if (!resourceType) {
|
|
25
|
+
const response = await enquirer.prompt<{ type: 'database' | 'storage' }>({
|
|
26
|
+
type: 'select',
|
|
27
|
+
name: 'type',
|
|
28
|
+
message: 'Select resource type:',
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: 'database', message: 'Database (PostgreSQL)' },
|
|
31
|
+
{ name: 'storage', message: 'Storage (S3)' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
resourceType = response.type;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Map user-friendly type to API type
|
|
38
|
+
const apiType = resourceType === 'database' ? 'db' : 's3';
|
|
39
|
+
|
|
40
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
41
|
+
|
|
42
|
+
const created = await tui.spinner({
|
|
43
|
+
message: `Creating ${resourceType} in ${region}`,
|
|
44
|
+
clearOnSuccess: true,
|
|
45
|
+
callback: async () => {
|
|
46
|
+
return createResources(catalystClient, orgId, region!, [{ type: apiType }]);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (created.length > 0) {
|
|
51
|
+
tui.success(`Created ${resourceType}: ${tui.bold(created[0].name)}`);
|
|
52
|
+
} else {
|
|
53
|
+
tui.fatal('Failed to create resource');
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources, deleteResources } from '@agentuity/server';
|
|
3
|
+
import enquirer from 'enquirer';
|
|
4
|
+
import { createSubcommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
7
|
+
|
|
8
|
+
export const deleteSubcommand = createSubcommand({
|
|
9
|
+
name: 'delete',
|
|
10
|
+
description: 'Delete cloud resource(s) for an organization',
|
|
11
|
+
aliases: ['rm', 'del', 'remove'],
|
|
12
|
+
requires: { auth: true, org: true, region: true },
|
|
13
|
+
schema: {
|
|
14
|
+
options: z.object({
|
|
15
|
+
type: z.enum(['db', 's3']).optional().describe('Resource type (db or s3)'),
|
|
16
|
+
name: z.string().optional().describe('Resource name to delete'),
|
|
17
|
+
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async handler(ctx) {
|
|
22
|
+
const { logger, opts, config, orgId, region, auth } = ctx;
|
|
23
|
+
|
|
24
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
25
|
+
|
|
26
|
+
// Determine what to delete
|
|
27
|
+
let resourcesToDelete: Array<{ type: 'db' | 's3'; name: string }> = [];
|
|
28
|
+
|
|
29
|
+
if (opts.type && opts.name) {
|
|
30
|
+
// Command line arguments provided
|
|
31
|
+
resourcesToDelete = [{ type: opts.type, name: opts.name }];
|
|
32
|
+
} else {
|
|
33
|
+
// Fetch resources and prompt for selection
|
|
34
|
+
const resources = await tui.spinner({
|
|
35
|
+
message: `Fetching resources for ${orgId} in ${region}`,
|
|
36
|
+
clearOnSuccess: true,
|
|
37
|
+
callback: async () => {
|
|
38
|
+
return listResources(catalystClient, orgId, region!);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (resources.db.length === 0 && resources.s3.length === 0) {
|
|
43
|
+
tui.info('No resources found to delete');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build choices for multi-select and resource map
|
|
48
|
+
const choices: Array<{ name: string; message: string }> = [];
|
|
49
|
+
const resourceMap = new Map<string, { type: 'db' | 's3'; name: string }>();
|
|
50
|
+
|
|
51
|
+
for (const db of resources.db) {
|
|
52
|
+
const key = `db:${db.name}`;
|
|
53
|
+
choices.push({
|
|
54
|
+
name: key,
|
|
55
|
+
message: `Database: ${db.name}`,
|
|
56
|
+
});
|
|
57
|
+
resourceMap.set(key, { type: 'db', name: db.name });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const s3 of resources.s3) {
|
|
61
|
+
const key = `s3:${s3.bucket_name}`;
|
|
62
|
+
choices.push({
|
|
63
|
+
name: key,
|
|
64
|
+
message: `Storage: ${s3.bucket_name}`,
|
|
65
|
+
});
|
|
66
|
+
resourceMap.set(key, { type: 's3', name: s3.bucket_name });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await enquirer.prompt<{ resources: string[] }>({
|
|
70
|
+
type: 'multiselect',
|
|
71
|
+
name: 'resources',
|
|
72
|
+
message: 'Select resource(s) to delete:',
|
|
73
|
+
choices,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Map selected keys back to resource objects
|
|
77
|
+
resourcesToDelete = response.resources
|
|
78
|
+
.map((key) => resourceMap.get(key))
|
|
79
|
+
.filter((r): r is { type: 'db' | 's3'; name: string } => r !== undefined);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (resourcesToDelete.length === 0) {
|
|
83
|
+
tui.info('No resources selected for deletion');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Confirm deletion
|
|
88
|
+
if (!opts.confirm) {
|
|
89
|
+
const resourceNames = resourcesToDelete.map((r) => `${r.type}:${r.name}`).join(', ');
|
|
90
|
+
tui.warning(`You are about to delete: ${tui.bold(resourceNames)}`);
|
|
91
|
+
|
|
92
|
+
const confirm = await enquirer.prompt<{ confirm: boolean }>({
|
|
93
|
+
type: 'confirm',
|
|
94
|
+
name: 'confirm',
|
|
95
|
+
message: 'Are you sure you want to delete these resources?',
|
|
96
|
+
initial: false,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!confirm.confirm) {
|
|
100
|
+
tui.info('Deletion cancelled');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Delete resources
|
|
106
|
+
const deleted = await tui.spinner({
|
|
107
|
+
message: `Deleting ${resourcesToDelete.length} resource(s)`,
|
|
108
|
+
clearOnSuccess: true,
|
|
109
|
+
callback: async () => {
|
|
110
|
+
return deleteResources(catalystClient, orgId, region!, resourcesToDelete);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (deleted.length > 0) {
|
|
115
|
+
tui.success(`Deleted ${deleted.length} resource(s): ${deleted.join(', ')}`);
|
|
116
|
+
} else {
|
|
117
|
+
tui.error('Failed to delete resources');
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SubcommandDefinition } from '../../../types';
|
|
2
|
+
import { listSubcommand } from './list';
|
|
3
|
+
import { addSubcommand } from './add';
|
|
4
|
+
import { deleteSubcommand } from './delete';
|
|
5
|
+
|
|
6
|
+
export const resourceSubcommand: SubcommandDefinition = {
|
|
7
|
+
name: 'resource',
|
|
8
|
+
aliases: ['resources'],
|
|
9
|
+
description: 'Manage cloud resources',
|
|
10
|
+
subcommands: [listSubcommand, addSubcommand, deleteSubcommand],
|
|
11
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listResources } from '@agentuity/server';
|
|
3
|
+
import { createSubcommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { getCatalystAPIClient } from '../../../config';
|
|
6
|
+
|
|
7
|
+
export const listSubcommand = createSubcommand({
|
|
8
|
+
name: 'list',
|
|
9
|
+
description: 'List cloud resources for an organization',
|
|
10
|
+
aliases: ['ls'],
|
|
11
|
+
requires: { auth: true, org: true, region: true },
|
|
12
|
+
schema: {
|
|
13
|
+
options: z.object({
|
|
14
|
+
format: z
|
|
15
|
+
.enum(['text', 'json'])
|
|
16
|
+
.optional()
|
|
17
|
+
.default('text')
|
|
18
|
+
.describe('Output format (text or json)'),
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async handler(ctx) {
|
|
23
|
+
const { logger, opts, orgId, region, config, auth } = ctx;
|
|
24
|
+
|
|
25
|
+
const catalystClient = getCatalystAPIClient(config, logger, auth);
|
|
26
|
+
|
|
27
|
+
const resources = await tui.spinner({
|
|
28
|
+
message: `Fetching resources for ${orgId} in ${region}`,
|
|
29
|
+
clearOnSuccess: true,
|
|
30
|
+
callback: async () => {
|
|
31
|
+
return listResources(catalystClient, orgId, region);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Output based on format
|
|
36
|
+
if (opts.format === 'json') {
|
|
37
|
+
console.log(JSON.stringify(resources, null, 2));
|
|
38
|
+
} else {
|
|
39
|
+
// Text table format
|
|
40
|
+
if (resources.db.length === 0 && resources.s3.length === 0) {
|
|
41
|
+
tui.info('No resources found');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (resources.db.length > 0) {
|
|
46
|
+
tui.info(tui.bold('Databases'));
|
|
47
|
+
tui.newline();
|
|
48
|
+
for (const db of resources.db) {
|
|
49
|
+
console.log(tui.bold(db.name));
|
|
50
|
+
if (db.url) console.log(` URL: ${tui.muted(db.url)}`);
|
|
51
|
+
tui.newline();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (resources.s3.length > 0) {
|
|
56
|
+
tui.info(tui.bold('Storage'));
|
|
57
|
+
tui.newline();
|
|
58
|
+
for (const s3 of resources.s3) {
|
|
59
|
+
console.log(tui.bold(s3.bucket_name));
|
|
60
|
+
if (s3.access_key) console.log(` Access Key: ${tui.muted(s3.access_key)}`);
|
|
61
|
+
if (s3.secret_key) console.log(` Secret Key: ${tui.muted(s3.secret_key)}`);
|
|
62
|
+
if (s3.region) console.log(` Region: ${tui.muted(s3.region)}`);
|
|
63
|
+
if (s3.endpoint) console.log(` Endpoint: ${tui.muted(s3.endpoint)}`);
|
|
64
|
+
tui.newline();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|