@layr-labs/ecloud-cli 0.0.1-rfc.1 → 0.1.0-dev.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 +264 -48
- package/VERSION +2 -0
- package/dist/commands/auth/generate.js +116 -10
- package/dist/commands/auth/generate.js.map +1 -1
- package/dist/commands/auth/login.js +37 -35
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +2 -8
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/migrate.js +32 -37
- package/dist/commands/auth/migrate.js.map +1 -1
- package/dist/commands/auth/whoami.js +53 -21
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/cancel.js +83 -22
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +92 -29
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +86 -31
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/compute/app/configure/tls.js +150 -0
- package/dist/commands/compute/app/configure/tls.js.map +1 -0
- package/dist/commands/compute/app/create.js +134 -0
- package/dist/commands/compute/app/create.js.map +1 -0
- package/dist/commands/compute/app/deploy.js +1101 -0
- package/dist/commands/compute/app/deploy.js.map +1 -0
- package/dist/commands/compute/app/info.js +818 -0
- package/dist/commands/compute/app/info.js.map +1 -0
- package/dist/commands/compute/app/list.js +578 -0
- package/dist/commands/compute/app/list.js.map +1 -0
- package/dist/commands/compute/app/logs.js +639 -0
- package/dist/commands/compute/app/logs.js.map +1 -0
- package/dist/commands/compute/app/profile/set.js +1086 -0
- package/dist/commands/compute/app/profile/set.js.map +1 -0
- package/dist/commands/compute/app/start.js +675 -0
- package/dist/commands/compute/app/start.js.map +1 -0
- package/dist/commands/compute/app/stop.js +675 -0
- package/dist/commands/compute/app/stop.js.map +1 -0
- package/dist/commands/compute/app/terminate.js +681 -0
- package/dist/commands/compute/app/terminate.js.map +1 -0
- package/dist/commands/compute/app/upgrade.js +1072 -0
- package/dist/commands/compute/app/upgrade.js.map +1 -0
- package/dist/commands/compute/environment/list.js +89 -0
- package/dist/commands/compute/environment/list.js.map +1 -0
- package/dist/commands/compute/environment/set.js +215 -0
- package/dist/commands/compute/environment/set.js.map +1 -0
- package/dist/commands/compute/environment/show.js +96 -0
- package/dist/commands/compute/environment/show.js.map +1 -0
- package/dist/commands/compute/undelegate.js +259 -0
- package/dist/commands/compute/undelegate.js.map +1 -0
- package/dist/commands/upgrade.js +91 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/version.js +65 -0
- package/dist/commands/version.js.map +1 -0
- package/package.json +30 -5
- package/dist/commands/app/create.js +0 -29
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/deploy.js +0 -142
- package/dist/commands/app/deploy.js.map +0 -1
- package/dist/commands/app/logs.js +0 -108
- package/dist/commands/app/logs.js.map +0 -1
- package/dist/commands/app/start.js +0 -121
- package/dist/commands/app/start.js.map +0 -1
- package/dist/commands/app/stop.js +0 -121
- package/dist/commands/app/stop.js.map +0 -1
- package/dist/commands/app/terminate.js +0 -128
- package/dist/commands/app/terminate.js.map +0 -1
- package/dist/commands/app/upgrade.js +0 -142
- package/dist/commands/app/upgrade.js.map +0 -1
- package/dist/keys/mainnet-alpha/prod/kms-encryption-public-key.pem +0 -14
- package/dist/keys/mainnet-alpha/prod/kms-signing-public-key.pem +0 -4
- package/dist/keys/sepolia/dev/kms-encryption-public-key.pem +0 -14
- package/dist/keys/sepolia/dev/kms-signing-public-key.pem +0 -4
- package/dist/keys/sepolia/prod/kms-encryption-public-key.pem +0 -14
- package/dist/keys/sepolia/prod/kms-signing-public-key.pem +0 -4
- package/dist/templates/Dockerfile.layered.tmpl +0 -58
- package/dist/templates/compute-source-env.sh.tmpl +0 -110
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/compute/app/upgrade.ts
|
|
4
|
+
import { Command, Args, Flags as Flags2 } from "@oclif/core";
|
|
5
|
+
import {
|
|
6
|
+
getEnvironmentConfig as getEnvironmentConfig2,
|
|
7
|
+
UserApiClient as UserApiClient3,
|
|
8
|
+
isMainnet,
|
|
9
|
+
prepareUpgrade,
|
|
10
|
+
executeUpgrade,
|
|
11
|
+
watchUpgrade
|
|
12
|
+
} from "@layr-labs/ecloud-sdk";
|
|
13
|
+
|
|
14
|
+
// src/flags.ts
|
|
15
|
+
import { Flags } from "@oclif/core";
|
|
16
|
+
|
|
17
|
+
// src/utils/prompts.ts
|
|
18
|
+
import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
19
|
+
import fs3 from "fs";
|
|
20
|
+
import path3 from "path";
|
|
21
|
+
import os3 from "os";
|
|
22
|
+
import { isAddress as isAddress2 } from "viem";
|
|
23
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
24
|
+
import {
|
|
25
|
+
getEnvironmentConfig,
|
|
26
|
+
getAvailableEnvironments,
|
|
27
|
+
isEnvironmentAvailable,
|
|
28
|
+
getAllAppsByDeveloper as getAllAppsByDeveloper2,
|
|
29
|
+
getCategoryDescriptions,
|
|
30
|
+
fetchTemplateCatalog,
|
|
31
|
+
PRIMARY_LANGUAGES,
|
|
32
|
+
validateAppName,
|
|
33
|
+
validateImageReference,
|
|
34
|
+
validateFilePath,
|
|
35
|
+
validatePrivateKeyFormat,
|
|
36
|
+
extractAppNameFromImage,
|
|
37
|
+
UserApiClient as UserApiClient2
|
|
38
|
+
} from "@layr-labs/ecloud-sdk";
|
|
39
|
+
|
|
40
|
+
// src/utils/appResolver.ts
|
|
41
|
+
import { isAddress } from "viem";
|
|
42
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
43
|
+
import {
|
|
44
|
+
UserApiClient,
|
|
45
|
+
getAllAppsByDeveloper
|
|
46
|
+
} from "@layr-labs/ecloud-sdk";
|
|
47
|
+
|
|
48
|
+
// src/utils/globalConfig.ts
|
|
49
|
+
import * as fs from "fs";
|
|
50
|
+
import * as path from "path";
|
|
51
|
+
import * as os from "os";
|
|
52
|
+
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
|
|
53
|
+
import { getBuildType } from "@layr-labs/ecloud-sdk";
|
|
54
|
+
var GLOBAL_CONFIG_FILE = "config.yaml";
|
|
55
|
+
var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
56
|
+
function getGlobalConfigDir() {
|
|
57
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
58
|
+
let baseDir;
|
|
59
|
+
if (configHome && path.isAbsolute(configHome)) {
|
|
60
|
+
baseDir = configHome;
|
|
61
|
+
} else {
|
|
62
|
+
baseDir = path.join(os.homedir(), ".config");
|
|
63
|
+
}
|
|
64
|
+
const buildType = getBuildType();
|
|
65
|
+
const buildSuffix = buildType === "dev" ? "-dev" : "";
|
|
66
|
+
const configDirName = `ecloud${buildSuffix}`;
|
|
67
|
+
return path.join(baseDir, configDirName);
|
|
68
|
+
}
|
|
69
|
+
function getGlobalConfigPath() {
|
|
70
|
+
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
|
|
71
|
+
}
|
|
72
|
+
function loadGlobalConfig() {
|
|
73
|
+
const configPath = getGlobalConfigPath();
|
|
74
|
+
if (!fs.existsSync(configPath)) {
|
|
75
|
+
return {
|
|
76
|
+
first_run: true
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
81
|
+
const config = loadYaml(content);
|
|
82
|
+
return config || { first_run: true };
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
first_run: true
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function saveGlobalConfig(config) {
|
|
90
|
+
const configPath = getGlobalConfigPath();
|
|
91
|
+
const configDir = path.dirname(configPath);
|
|
92
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
93
|
+
const content = dumpYaml(config, { lineWidth: -1 });
|
|
94
|
+
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
95
|
+
}
|
|
96
|
+
function getProfileCache(environment) {
|
|
97
|
+
const config = loadGlobalConfig();
|
|
98
|
+
const cacheEntry = config.profile_cache?.[environment];
|
|
99
|
+
if (!cacheEntry) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return cacheEntry.profiles;
|
|
107
|
+
}
|
|
108
|
+
function setProfileCache(environment, profiles) {
|
|
109
|
+
const config = loadGlobalConfig();
|
|
110
|
+
if (!config.profile_cache) {
|
|
111
|
+
config.profile_cache = {};
|
|
112
|
+
}
|
|
113
|
+
config.profile_cache[environment] = {
|
|
114
|
+
updated_at: Date.now(),
|
|
115
|
+
profiles
|
|
116
|
+
};
|
|
117
|
+
saveGlobalConfig(config);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/utils/appNames.ts
|
|
121
|
+
import * as fs2 from "fs";
|
|
122
|
+
import * as path2 from "path";
|
|
123
|
+
import * as os2 from "os";
|
|
124
|
+
import { load as loadYaml2, dump as dumpYaml2 } from "js-yaml";
|
|
125
|
+
var CONFIG_DIR = path2.join(os2.homedir(), ".eigenx");
|
|
126
|
+
var APPS_DIR = path2.join(CONFIG_DIR, "apps");
|
|
127
|
+
var APP_REGISTRY_VERSION = "1.0.0";
|
|
128
|
+
function getAppRegistryPath(environment) {
|
|
129
|
+
return path2.join(APPS_DIR, `${environment}.yaml`);
|
|
130
|
+
}
|
|
131
|
+
function loadAppRegistry(environment) {
|
|
132
|
+
const filePath = getAppRegistryPath(environment);
|
|
133
|
+
if (!fs2.existsSync(filePath)) {
|
|
134
|
+
return {
|
|
135
|
+
version: APP_REGISTRY_VERSION,
|
|
136
|
+
apps: {}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
141
|
+
const registry = loadYaml2(content);
|
|
142
|
+
if (!registry.apps) {
|
|
143
|
+
registry.apps = {};
|
|
144
|
+
}
|
|
145
|
+
return registry;
|
|
146
|
+
} catch {
|
|
147
|
+
return {
|
|
148
|
+
version: APP_REGISTRY_VERSION,
|
|
149
|
+
apps: {}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function listApps(environment) {
|
|
154
|
+
const registry = loadAppRegistry(environment);
|
|
155
|
+
const result = {};
|
|
156
|
+
for (const [name, app] of Object.entries(registry.apps)) {
|
|
157
|
+
if (app?.app_id) {
|
|
158
|
+
result[name] = String(app.app_id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/utils/version.ts
|
|
165
|
+
function getCliVersion() {
|
|
166
|
+
return true ? "0.1.0-dev.1" : "0.0.0";
|
|
167
|
+
}
|
|
168
|
+
function getClientId() {
|
|
169
|
+
return `ecloud-cli/v${getCliVersion()}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/utils/appResolver.ts
|
|
173
|
+
var CHUNK_SIZE = 10;
|
|
174
|
+
async function getAppInfosChunked(userApiClient, appIds, addressCount) {
|
|
175
|
+
if (appIds.length === 0) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const chunks = [];
|
|
179
|
+
for (let i = 0; i < appIds.length; i += CHUNK_SIZE) {
|
|
180
|
+
chunks.push(appIds.slice(i, i + CHUNK_SIZE));
|
|
181
|
+
}
|
|
182
|
+
const chunkResults = await Promise.all(
|
|
183
|
+
chunks.map((chunk) => userApiClient.getInfos(chunk, addressCount))
|
|
184
|
+
);
|
|
185
|
+
return chunkResults.flat();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/utils/prompts.ts
|
|
189
|
+
function addHexPrefix(value) {
|
|
190
|
+
if (value.startsWith("0x")) {
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
return `0x${value}`;
|
|
194
|
+
}
|
|
195
|
+
async function getDockerfileInteractive(dockerfilePath) {
|
|
196
|
+
if (dockerfilePath) {
|
|
197
|
+
return dockerfilePath;
|
|
198
|
+
}
|
|
199
|
+
const cwd = process.env.INIT_CWD || process.cwd();
|
|
200
|
+
const dockerfilePath_resolved = path3.join(cwd, "Dockerfile");
|
|
201
|
+
if (!fs3.existsSync(dockerfilePath_resolved)) {
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
console.log(`
|
|
205
|
+
Found Dockerfile in ${cwd}`);
|
|
206
|
+
const choice = await select({
|
|
207
|
+
message: "Choose deployment method:",
|
|
208
|
+
choices: [
|
|
209
|
+
{ name: "Build and deploy from Dockerfile", value: "build" },
|
|
210
|
+
{ name: "Deploy existing image from registry", value: "existing" }
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
switch (choice) {
|
|
214
|
+
case "build":
|
|
215
|
+
return dockerfilePath_resolved;
|
|
216
|
+
case "existing":
|
|
217
|
+
return "";
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`Unexpected choice: ${choice}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function extractHostname(registry) {
|
|
223
|
+
let hostname = registry.replace(/^https?:\/\//, "");
|
|
224
|
+
hostname = hostname.split("/")[0];
|
|
225
|
+
hostname = hostname.split(":")[0];
|
|
226
|
+
return hostname.toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
function isDockerHub(registry) {
|
|
229
|
+
const hostname = extractHostname(registry);
|
|
230
|
+
return hostname === "docker.io" || hostname === "index.docker.io" || hostname === "registry-1.docker.io";
|
|
231
|
+
}
|
|
232
|
+
function isGHCR(registry) {
|
|
233
|
+
const hostname = extractHostname(registry);
|
|
234
|
+
return hostname === "ghcr.io";
|
|
235
|
+
}
|
|
236
|
+
function isGCR(registry) {
|
|
237
|
+
const hostname = extractHostname(registry);
|
|
238
|
+
return hostname === "gcr.io" || hostname.endsWith(".gcr.io");
|
|
239
|
+
}
|
|
240
|
+
async function getCredentialsFromHelper(registry) {
|
|
241
|
+
const dockerConfigPath = path3.join(os3.homedir(), ".docker", "config.json");
|
|
242
|
+
if (!fs3.existsSync(dockerConfigPath)) {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const config = JSON.parse(fs3.readFileSync(dockerConfigPath, "utf-8"));
|
|
247
|
+
const credsStore = config.credsStore;
|
|
248
|
+
if (!credsStore) {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
const { execSync } = await import("child_process");
|
|
252
|
+
const helper = `docker-credential-${credsStore}`;
|
|
253
|
+
try {
|
|
254
|
+
const registryVariants = [];
|
|
255
|
+
if (isDockerHub(registry)) {
|
|
256
|
+
registryVariants.push("https://index.docker.io/v1/");
|
|
257
|
+
registryVariants.push("https://index.docker.io/v1");
|
|
258
|
+
registryVariants.push("index.docker.io");
|
|
259
|
+
registryVariants.push("docker.io");
|
|
260
|
+
} else {
|
|
261
|
+
const baseRegistry = registry.replace(/^https?:\/\//, "").replace(/\/v1\/?$/, "").replace(/\/$/, "");
|
|
262
|
+
registryVariants.push(`https://${baseRegistry}`);
|
|
263
|
+
registryVariants.push(`https://${baseRegistry}/v1/`);
|
|
264
|
+
registryVariants.push(baseRegistry);
|
|
265
|
+
}
|
|
266
|
+
for (const variant of registryVariants) {
|
|
267
|
+
try {
|
|
268
|
+
const output = execSync(`echo "${variant}" | ${helper} get`, {
|
|
269
|
+
encoding: "utf-8"
|
|
270
|
+
});
|
|
271
|
+
const creds = JSON.parse(output);
|
|
272
|
+
if (creds.Username && creds.Secret) {
|
|
273
|
+
return { username: creds.Username, password: creds.Secret };
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
async function getAvailableRegistries() {
|
|
288
|
+
const dockerConfigPath = path3.join(os3.homedir(), ".docker", "config.json");
|
|
289
|
+
if (!fs3.existsSync(dockerConfigPath)) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const configContent = fs3.readFileSync(dockerConfigPath, "utf-8");
|
|
294
|
+
const config = JSON.parse(configContent);
|
|
295
|
+
const auths = config.auths || {};
|
|
296
|
+
const credsStore = config.credsStore;
|
|
297
|
+
const gcrProjects = /* @__PURE__ */ new Map();
|
|
298
|
+
const registries = [];
|
|
299
|
+
for (const [registry, auth] of Object.entries(auths)) {
|
|
300
|
+
const authData = auth;
|
|
301
|
+
const hostname = extractHostname(registry);
|
|
302
|
+
if (hostname.includes("access-token") || hostname.includes("refresh-token")) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
let username = authData.username;
|
|
306
|
+
let registryType = "other";
|
|
307
|
+
let normalizedURL = registry;
|
|
308
|
+
if (isDockerHub(registry)) {
|
|
309
|
+
registryType = "dockerhub";
|
|
310
|
+
normalizedURL = "https://index.docker.io/v1/";
|
|
311
|
+
} else if (isGHCR(registry)) {
|
|
312
|
+
registryType = "ghcr";
|
|
313
|
+
normalizedURL = registry.replace(/^https?:\/\//, "").replace(/\/v1\/?$/, "");
|
|
314
|
+
} else if (isGCR(registry)) {
|
|
315
|
+
registryType = "gcr";
|
|
316
|
+
normalizedURL = "gcr.io";
|
|
317
|
+
}
|
|
318
|
+
if (!username && credsStore) {
|
|
319
|
+
const creds = await getCredentialsFromHelper(registry);
|
|
320
|
+
if (creds) {
|
|
321
|
+
username = creds.username;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!username) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const info = {
|
|
328
|
+
URL: normalizedURL,
|
|
329
|
+
Username: username,
|
|
330
|
+
Type: registryType
|
|
331
|
+
};
|
|
332
|
+
if (registryType === "gcr") {
|
|
333
|
+
if (!gcrProjects.has(username)) {
|
|
334
|
+
gcrProjects.set(username, info);
|
|
335
|
+
}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
registries.push(info);
|
|
339
|
+
}
|
|
340
|
+
for (const gcrInfo of Array.from(gcrProjects.values())) {
|
|
341
|
+
registries.push(gcrInfo);
|
|
342
|
+
}
|
|
343
|
+
registries.sort((a, b) => {
|
|
344
|
+
if (a.Type === "dockerhub") return -1;
|
|
345
|
+
if (b.Type === "dockerhub") return 1;
|
|
346
|
+
return a.Type.localeCompare(b.Type);
|
|
347
|
+
});
|
|
348
|
+
return registries;
|
|
349
|
+
} catch {
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function getDefaultAppName() {
|
|
354
|
+
try {
|
|
355
|
+
const cwd = process.env.INIT_CWD || process.cwd();
|
|
356
|
+
return path3.basename(cwd);
|
|
357
|
+
} catch {
|
|
358
|
+
return "myapp";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function suggestImageReference(registry, imageName, tag) {
|
|
362
|
+
imageName = imageName.toLowerCase().replace(/_/g, "-");
|
|
363
|
+
if (!tag) {
|
|
364
|
+
tag = "latest";
|
|
365
|
+
}
|
|
366
|
+
switch (registry.Type) {
|
|
367
|
+
case "dockerhub":
|
|
368
|
+
return `${registry.Username}/${imageName}:${tag}`;
|
|
369
|
+
case "ghcr":
|
|
370
|
+
return `ghcr.io/${registry.Username}/${imageName}:${tag}`;
|
|
371
|
+
case "gcr":
|
|
372
|
+
return `gcr.io/${registry.Username}/${imageName}:${tag}`;
|
|
373
|
+
default:
|
|
374
|
+
let host = registry.URL;
|
|
375
|
+
if (host.startsWith("https://")) {
|
|
376
|
+
host = host.substring(8);
|
|
377
|
+
} else if (host.startsWith("http://")) {
|
|
378
|
+
host = host.substring(7);
|
|
379
|
+
}
|
|
380
|
+
host = host.replace(/\/$/, "");
|
|
381
|
+
return `${host}/${registry.Username}/${imageName}:${tag}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function displayDetectedRegistries(registries, appName) {
|
|
385
|
+
console.log("Detected authenticated registries:");
|
|
386
|
+
for (const reg of registries) {
|
|
387
|
+
const suggestion = suggestImageReference(reg, appName, "latest");
|
|
388
|
+
console.log(` ${reg.Type}: ${suggestion}`);
|
|
389
|
+
}
|
|
390
|
+
console.log();
|
|
391
|
+
}
|
|
392
|
+
function displayAuthenticationInstructions() {
|
|
393
|
+
console.log("No authenticated registries detected.");
|
|
394
|
+
console.log("To authenticate:");
|
|
395
|
+
console.log(" docker login <registry-url>");
|
|
396
|
+
console.log();
|
|
397
|
+
}
|
|
398
|
+
function displayRegistryExamples(appName) {
|
|
399
|
+
console.log("Examples:");
|
|
400
|
+
console.log(` docker.io/${appName.toLowerCase()}:latest`);
|
|
401
|
+
console.log(` ghcr.io/username/${appName.toLowerCase()}:latest`);
|
|
402
|
+
console.log(` gcr.io/project-id/${appName.toLowerCase()}:latest`);
|
|
403
|
+
console.log();
|
|
404
|
+
}
|
|
405
|
+
async function selectRegistryInteractive(registries, imageName, tag) {
|
|
406
|
+
if (registries.length === 1) {
|
|
407
|
+
const defaultRef = suggestImageReference(registries[0], imageName, tag);
|
|
408
|
+
return input({
|
|
409
|
+
message: "Enter image reference:",
|
|
410
|
+
default: defaultRef,
|
|
411
|
+
validate: (value) => {
|
|
412
|
+
const result = validateImageReference(value);
|
|
413
|
+
return result === true ? true : result;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const choices = registries.map((reg) => ({
|
|
418
|
+
name: suggestImageReference(reg, imageName, tag),
|
|
419
|
+
value: suggestImageReference(reg, imageName, tag)
|
|
420
|
+
}));
|
|
421
|
+
choices.push({ name: "Enter custom image reference", value: "custom" });
|
|
422
|
+
const choice = await select({
|
|
423
|
+
message: "Select image destination:",
|
|
424
|
+
choices
|
|
425
|
+
});
|
|
426
|
+
if (choice === "custom") {
|
|
427
|
+
return input({
|
|
428
|
+
message: "Enter image reference:",
|
|
429
|
+
default: "",
|
|
430
|
+
validate: (value) => {
|
|
431
|
+
const result = validateImageReference(value);
|
|
432
|
+
return result === true ? true : result;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return choice;
|
|
437
|
+
}
|
|
438
|
+
async function getImageReferenceInteractive(imageRef, buildFromDockerfile = false) {
|
|
439
|
+
if (imageRef) {
|
|
440
|
+
return imageRef;
|
|
441
|
+
}
|
|
442
|
+
const registries = await getAvailableRegistries();
|
|
443
|
+
const appName = getDefaultAppName();
|
|
444
|
+
if (buildFromDockerfile) {
|
|
445
|
+
console.log("\n\u{1F4E6} Build & Push Configuration");
|
|
446
|
+
console.log("Your Docker image will be built and pushed to a registry");
|
|
447
|
+
console.log("so that EigenX can pull and run it in the TEE.");
|
|
448
|
+
console.log();
|
|
449
|
+
if (registries.length > 0) {
|
|
450
|
+
displayDetectedRegistries(registries, appName);
|
|
451
|
+
return selectRegistryInteractive(registries, appName, "latest");
|
|
452
|
+
}
|
|
453
|
+
displayAuthenticationInstructions();
|
|
454
|
+
} else {
|
|
455
|
+
console.log("\n\u{1F433} Docker Image Selection");
|
|
456
|
+
console.log("Specify an existing Docker image from a registry to run in the TEE.");
|
|
457
|
+
console.log();
|
|
458
|
+
}
|
|
459
|
+
displayRegistryExamples(appName);
|
|
460
|
+
const imageRefInput = await input({
|
|
461
|
+
message: "Enter Docker image reference:",
|
|
462
|
+
default: "",
|
|
463
|
+
validate: (value) => {
|
|
464
|
+
const result = validateImageReference(value);
|
|
465
|
+
return result === true ? true : result;
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
return imageRefInput;
|
|
469
|
+
}
|
|
470
|
+
async function getEnvFileInteractive(envFilePath) {
|
|
471
|
+
if (envFilePath && fs3.existsSync(envFilePath)) {
|
|
472
|
+
return envFilePath;
|
|
473
|
+
}
|
|
474
|
+
if (fs3.existsSync(".env")) {
|
|
475
|
+
return ".env";
|
|
476
|
+
}
|
|
477
|
+
console.log("\nEnvironment file not found.");
|
|
478
|
+
console.log("Environment files contain variables like RPC_URL, etc.");
|
|
479
|
+
const choice = await select({
|
|
480
|
+
message: "Choose an option:",
|
|
481
|
+
choices: [
|
|
482
|
+
{ name: "Enter path to existing env file", value: "enter" },
|
|
483
|
+
{ name: "Continue without env file", value: "continue" }
|
|
484
|
+
]
|
|
485
|
+
});
|
|
486
|
+
switch (choice) {
|
|
487
|
+
case "enter":
|
|
488
|
+
const envFile = await input({
|
|
489
|
+
message: "Enter environment file path:",
|
|
490
|
+
default: "",
|
|
491
|
+
validate: (value) => {
|
|
492
|
+
const result = validateFilePath(value);
|
|
493
|
+
return result === true ? true : result;
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return envFile;
|
|
497
|
+
case "continue":
|
|
498
|
+
return "";
|
|
499
|
+
default:
|
|
500
|
+
throw new Error(`Unexpected choice: ${choice}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTypes) {
|
|
504
|
+
if (instanceType) {
|
|
505
|
+
const valid = availableTypes.find((t) => t.sku === instanceType);
|
|
506
|
+
if (valid) {
|
|
507
|
+
return instanceType;
|
|
508
|
+
}
|
|
509
|
+
const validSKUs = availableTypes.map((t) => t.sku).join(", ");
|
|
510
|
+
throw new Error(`Invalid instance-type: ${instanceType} (must be one of: ${validSKUs})`);
|
|
511
|
+
}
|
|
512
|
+
const isCurrentType = defaultSKU !== "";
|
|
513
|
+
if (defaultSKU === "" && availableTypes.length > 0) {
|
|
514
|
+
defaultSKU = availableTypes[0].sku;
|
|
515
|
+
}
|
|
516
|
+
if (isCurrentType && defaultSKU) {
|
|
517
|
+
console.log(`
|
|
518
|
+
Select instance type (current: ${defaultSKU}):`);
|
|
519
|
+
} else {
|
|
520
|
+
console.log("\nSelect instance type:");
|
|
521
|
+
}
|
|
522
|
+
const choices = availableTypes.map((it) => {
|
|
523
|
+
let name = `${it.sku} - ${it.description}`;
|
|
524
|
+
if (it.sku === defaultSKU) {
|
|
525
|
+
name += isCurrentType ? " (current)" : " (default)";
|
|
526
|
+
}
|
|
527
|
+
return { name, value: it.sku };
|
|
528
|
+
});
|
|
529
|
+
const choice = await select({
|
|
530
|
+
message: "Choose instance:",
|
|
531
|
+
choices
|
|
532
|
+
});
|
|
533
|
+
return choice;
|
|
534
|
+
}
|
|
535
|
+
async function getLogSettingsInteractive(logVisibility) {
|
|
536
|
+
if (logVisibility) {
|
|
537
|
+
switch (logVisibility) {
|
|
538
|
+
case "public":
|
|
539
|
+
return { logRedirect: "always", publicLogs: true };
|
|
540
|
+
case "private":
|
|
541
|
+
return { logRedirect: "always", publicLogs: false };
|
|
542
|
+
case "off":
|
|
543
|
+
return { logRedirect: "", publicLogs: false };
|
|
544
|
+
default:
|
|
545
|
+
throw new Error(
|
|
546
|
+
`Invalid log-visibility: ${logVisibility} (must be public, private, or off)`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const choice = await select({
|
|
551
|
+
message: "Do you want to view your app's logs?",
|
|
552
|
+
choices: [
|
|
553
|
+
{ name: "Yes, but only viewable by app and platform admins", value: "private" },
|
|
554
|
+
{ name: "Yes, publicly viewable by anyone", value: "public" },
|
|
555
|
+
{ name: "No, disable logs entirely", value: "off" }
|
|
556
|
+
]
|
|
557
|
+
});
|
|
558
|
+
switch (choice) {
|
|
559
|
+
case "private":
|
|
560
|
+
return { logRedirect: "always", publicLogs: false };
|
|
561
|
+
case "public":
|
|
562
|
+
return { logRedirect: "always", publicLogs: true };
|
|
563
|
+
case "off":
|
|
564
|
+
return { logRedirect: "", publicLogs: false };
|
|
565
|
+
default:
|
|
566
|
+
throw new Error(`Unexpected choice: ${choice}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
var ContractAppStatusStarted = 1;
|
|
570
|
+
var ContractAppStatusStopped = 2;
|
|
571
|
+
var ContractAppStatusTerminated = 3;
|
|
572
|
+
var ContractAppStatusSuspended = 4;
|
|
573
|
+
function getContractStatusString(status) {
|
|
574
|
+
switch (status) {
|
|
575
|
+
case ContractAppStatusStarted:
|
|
576
|
+
return "Started";
|
|
577
|
+
case ContractAppStatusStopped:
|
|
578
|
+
return "Stopped";
|
|
579
|
+
case ContractAppStatusTerminated:
|
|
580
|
+
return "Terminated";
|
|
581
|
+
case ContractAppStatusSuspended:
|
|
582
|
+
return "Suspended";
|
|
583
|
+
default:
|
|
584
|
+
return "Unknown";
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function getStatusPriority(status, isExited) {
|
|
588
|
+
if (isExited) {
|
|
589
|
+
return 1;
|
|
590
|
+
}
|
|
591
|
+
switch (status) {
|
|
592
|
+
case ContractAppStatusStarted:
|
|
593
|
+
return 0;
|
|
594
|
+
case ContractAppStatusStopped:
|
|
595
|
+
return 2;
|
|
596
|
+
case ContractAppStatusTerminated:
|
|
597
|
+
return 3;
|
|
598
|
+
default:
|
|
599
|
+
return 4;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function formatAppDisplay(environmentName, appID, profileName) {
|
|
603
|
+
if (profileName) {
|
|
604
|
+
return `${profileName} (${environmentName}:${appID})`;
|
|
605
|
+
}
|
|
606
|
+
return `${environmentName}:${appID}`;
|
|
607
|
+
}
|
|
608
|
+
async function getOrPromptAppID(appIDOrOptions, environment) {
|
|
609
|
+
let options;
|
|
610
|
+
if (environment !== void 0) {
|
|
611
|
+
options = {
|
|
612
|
+
appID: appIDOrOptions,
|
|
613
|
+
environment
|
|
614
|
+
};
|
|
615
|
+
} else if (appIDOrOptions && typeof appIDOrOptions === "object" && "environment" in appIDOrOptions) {
|
|
616
|
+
options = appIDOrOptions;
|
|
617
|
+
} else {
|
|
618
|
+
options = {
|
|
619
|
+
appID: appIDOrOptions,
|
|
620
|
+
environment: "sepolia"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (options.appID) {
|
|
624
|
+
const normalized = typeof options.appID === "string" ? addHexPrefix(options.appID) : options.appID;
|
|
625
|
+
if (isAddress2(normalized)) {
|
|
626
|
+
return normalized;
|
|
627
|
+
}
|
|
628
|
+
const profileCache = getProfileCache(options.environment);
|
|
629
|
+
if (profileCache) {
|
|
630
|
+
const searchName = options.appID.toLowerCase();
|
|
631
|
+
for (const [appId, name] of Object.entries(profileCache)) {
|
|
632
|
+
if (name.toLowerCase() === searchName) {
|
|
633
|
+
return appId;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const apps = listApps(options.environment);
|
|
638
|
+
const foundAppID = apps[options.appID];
|
|
639
|
+
if (foundAppID) {
|
|
640
|
+
return addHexPrefix(foundAppID);
|
|
641
|
+
}
|
|
642
|
+
throw new Error(
|
|
643
|
+
`App name '${options.appID}' not found in environment '${options.environment}'`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
return getAppIDInteractive(options);
|
|
647
|
+
}
|
|
648
|
+
async function getAppIDInteractive(options) {
|
|
649
|
+
const action = options.action || "view";
|
|
650
|
+
const environment = options.environment || "sepolia";
|
|
651
|
+
const environmentConfig = getEnvironmentConfig(environment);
|
|
652
|
+
if (!options.privateKey || !options.rpcUrl) {
|
|
653
|
+
return getAppIDInteractiveFromRegistry(environment, action);
|
|
654
|
+
}
|
|
655
|
+
console.log(`
|
|
656
|
+
Select an app to ${action}:
|
|
657
|
+
`);
|
|
658
|
+
const privateKeyHex = addHexPrefix(options.privateKey);
|
|
659
|
+
const account = privateKeyToAccount2(privateKeyHex);
|
|
660
|
+
const developerAddr = account.address;
|
|
661
|
+
const { apps, appConfigs } = await getAllAppsByDeveloper2(
|
|
662
|
+
options.rpcUrl,
|
|
663
|
+
environmentConfig,
|
|
664
|
+
developerAddr
|
|
665
|
+
);
|
|
666
|
+
if (apps.length === 0) {
|
|
667
|
+
throw new Error("no apps found for your address");
|
|
668
|
+
}
|
|
669
|
+
const profileNames = {};
|
|
670
|
+
let cachedProfiles = getProfileCache(environment);
|
|
671
|
+
if (!cachedProfiles) {
|
|
672
|
+
try {
|
|
673
|
+
const userApiClient = new UserApiClient2(
|
|
674
|
+
environmentConfig,
|
|
675
|
+
options.privateKey,
|
|
676
|
+
options.rpcUrl,
|
|
677
|
+
getClientId()
|
|
678
|
+
);
|
|
679
|
+
const appInfos = await getAppInfosChunked(userApiClient, apps);
|
|
680
|
+
const freshProfiles = {};
|
|
681
|
+
for (const info of appInfos) {
|
|
682
|
+
if (info.profile?.name) {
|
|
683
|
+
const normalizedId = String(info.address).toLowerCase();
|
|
684
|
+
freshProfiles[normalizedId] = info.profile.name;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
setProfileCache(environment, freshProfiles);
|
|
688
|
+
cachedProfiles = freshProfiles;
|
|
689
|
+
} catch {
|
|
690
|
+
cachedProfiles = {};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const [appId, name] of Object.entries(cachedProfiles)) {
|
|
694
|
+
profileNames[appId.toLowerCase()] = name;
|
|
695
|
+
}
|
|
696
|
+
const localApps = listApps(environment);
|
|
697
|
+
for (const [name, appID] of Object.entries(localApps)) {
|
|
698
|
+
const normalizedID = String(appID).toLowerCase();
|
|
699
|
+
if (!profileNames[normalizedID]) {
|
|
700
|
+
profileNames[normalizedID] = name;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const isEligible = (status) => {
|
|
704
|
+
switch (action) {
|
|
705
|
+
case "view":
|
|
706
|
+
case "view info for":
|
|
707
|
+
case "set profile for":
|
|
708
|
+
return true;
|
|
709
|
+
case "start":
|
|
710
|
+
return status === ContractAppStatusStopped || status === ContractAppStatusSuspended;
|
|
711
|
+
case "stop":
|
|
712
|
+
return status === ContractAppStatusStarted;
|
|
713
|
+
default:
|
|
714
|
+
return status !== ContractAppStatusTerminated && status !== ContractAppStatusSuspended;
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
const appItems = [];
|
|
718
|
+
for (let i = 0; i < apps.length; i++) {
|
|
719
|
+
const appAddr = apps[i];
|
|
720
|
+
const config = appConfigs[i];
|
|
721
|
+
const status = config.status;
|
|
722
|
+
if (!isEligible(status)) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const statusStr = getContractStatusString(status);
|
|
726
|
+
const profileName = profileNames[String(appAddr).toLowerCase()] || "";
|
|
727
|
+
const displayName = formatAppDisplay(environmentConfig.name, appAddr, profileName);
|
|
728
|
+
appItems.push({
|
|
729
|
+
addr: appAddr,
|
|
730
|
+
display: `${displayName} - ${statusStr}`,
|
|
731
|
+
status,
|
|
732
|
+
index: i
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
appItems.sort((a, b) => {
|
|
736
|
+
const aPriority = getStatusPriority(a.status, false);
|
|
737
|
+
const bPriority = getStatusPriority(b.status, false);
|
|
738
|
+
if (aPriority !== bPriority) {
|
|
739
|
+
return aPriority - bPriority;
|
|
740
|
+
}
|
|
741
|
+
return b.index - a.index;
|
|
742
|
+
});
|
|
743
|
+
if (appItems.length === 0) {
|
|
744
|
+
switch (action) {
|
|
745
|
+
case "start":
|
|
746
|
+
throw new Error("no startable apps found - only Stopped apps can be started");
|
|
747
|
+
case "stop":
|
|
748
|
+
throw new Error("no running apps found - only Running apps can be stopped");
|
|
749
|
+
default:
|
|
750
|
+
throw new Error("no active apps found");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const choices = appItems.map((item) => ({
|
|
754
|
+
name: item.display,
|
|
755
|
+
value: item.addr
|
|
756
|
+
}));
|
|
757
|
+
const selected = await select({
|
|
758
|
+
message: "Select app:",
|
|
759
|
+
choices
|
|
760
|
+
});
|
|
761
|
+
return selected;
|
|
762
|
+
}
|
|
763
|
+
async function getAppIDInteractiveFromRegistry(environment, action) {
|
|
764
|
+
const allApps = {};
|
|
765
|
+
const cachedProfiles = getProfileCache(environment);
|
|
766
|
+
if (cachedProfiles) {
|
|
767
|
+
for (const [appId, name] of Object.entries(cachedProfiles)) {
|
|
768
|
+
allApps[name] = appId;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const localApps = listApps(environment);
|
|
772
|
+
for (const [name, appId] of Object.entries(localApps)) {
|
|
773
|
+
if (!allApps[name]) {
|
|
774
|
+
allApps[name] = appId;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (Object.keys(allApps).length === 0) {
|
|
778
|
+
console.log("\nNo apps found in registry.");
|
|
779
|
+
console.log("You can enter an app ID (address) or app name.");
|
|
780
|
+
console.log();
|
|
781
|
+
const appIDInput = await input({
|
|
782
|
+
message: "Enter app ID or name:",
|
|
783
|
+
default: "",
|
|
784
|
+
validate: (value) => {
|
|
785
|
+
if (!value) {
|
|
786
|
+
return "App ID or name cannot be empty";
|
|
787
|
+
}
|
|
788
|
+
const normalized2 = addHexPrefix(value);
|
|
789
|
+
if (isAddress2(normalized2)) {
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
return "Invalid app ID address";
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
const normalized = addHexPrefix(appIDInput);
|
|
796
|
+
if (isAddress2(normalized)) {
|
|
797
|
+
return normalized;
|
|
798
|
+
}
|
|
799
|
+
throw new Error(`Invalid app ID address: ${appIDInput}`);
|
|
800
|
+
}
|
|
801
|
+
const choices = Object.entries(allApps).map(([name, appID]) => {
|
|
802
|
+
const displayName = `${name} (${appID})`;
|
|
803
|
+
return { name: displayName, value: appID };
|
|
804
|
+
});
|
|
805
|
+
choices.push({ name: "Enter custom app ID or name", value: "custom" });
|
|
806
|
+
console.log(`
|
|
807
|
+
Select an app to ${action}:`);
|
|
808
|
+
const selected = await select({
|
|
809
|
+
message: "Choose app:",
|
|
810
|
+
choices
|
|
811
|
+
});
|
|
812
|
+
if (selected === "custom") {
|
|
813
|
+
const appIDInput = await input({
|
|
814
|
+
message: "Enter app ID or name:",
|
|
815
|
+
default: "",
|
|
816
|
+
validate: (value) => {
|
|
817
|
+
if (!value) {
|
|
818
|
+
return "App ID or name cannot be empty";
|
|
819
|
+
}
|
|
820
|
+
const normalized2 = addHexPrefix(value);
|
|
821
|
+
if (isAddress2(normalized2)) {
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
if (allApps[value]) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
return "Invalid app ID or name not found";
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
const normalized = addHexPrefix(appIDInput);
|
|
831
|
+
if (isAddress2(normalized)) {
|
|
832
|
+
return normalized;
|
|
833
|
+
}
|
|
834
|
+
const foundAppID = allApps[appIDInput];
|
|
835
|
+
if (foundAppID) {
|
|
836
|
+
return addHexPrefix(foundAppID);
|
|
837
|
+
}
|
|
838
|
+
throw new Error(`Failed to resolve app ID from input: ${appIDInput}`);
|
|
839
|
+
}
|
|
840
|
+
return addHexPrefix(selected);
|
|
841
|
+
}
|
|
842
|
+
async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
|
|
843
|
+
if (resourceUsageMonitoring) {
|
|
844
|
+
switch (resourceUsageMonitoring) {
|
|
845
|
+
case "enable":
|
|
846
|
+
case "disable":
|
|
847
|
+
return resourceUsageMonitoring;
|
|
848
|
+
default:
|
|
849
|
+
throw new Error(
|
|
850
|
+
`Invalid resource-usage-monitoring: ${resourceUsageMonitoring} (must be enable or disable)`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const choice = await select({
|
|
855
|
+
message: "Show resource usage (CPU/memory) for your app?",
|
|
856
|
+
choices: [
|
|
857
|
+
{ name: "Yes, enable resource usage monitoring", value: "enable" },
|
|
858
|
+
{ name: "No, disable resource usage monitoring", value: "disable" }
|
|
859
|
+
]
|
|
860
|
+
});
|
|
861
|
+
return choice;
|
|
862
|
+
}
|
|
863
|
+
async function confirm(prompt) {
|
|
864
|
+
return confirmWithDefault(prompt, false);
|
|
865
|
+
}
|
|
866
|
+
async function confirmWithDefault(prompt, defaultValue = false) {
|
|
867
|
+
return await inquirerConfirm({
|
|
868
|
+
message: prompt,
|
|
869
|
+
default: defaultValue
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
async function getPrivateKeyInteractive(privateKey) {
|
|
873
|
+
if (privateKey) {
|
|
874
|
+
if (!validatePrivateKeyFormat(privateKey)) {
|
|
875
|
+
throw new Error("Invalid private key format");
|
|
876
|
+
}
|
|
877
|
+
return privateKey;
|
|
878
|
+
}
|
|
879
|
+
const { getPrivateKeyWithSource } = await import("@layr-labs/ecloud-sdk");
|
|
880
|
+
const result = await getPrivateKeyWithSource({ privateKey: void 0 });
|
|
881
|
+
if (result) {
|
|
882
|
+
return result.key;
|
|
883
|
+
}
|
|
884
|
+
const key = await password({
|
|
885
|
+
message: "Enter private key:",
|
|
886
|
+
mask: true,
|
|
887
|
+
validate: (value) => {
|
|
888
|
+
if (!value.trim()) {
|
|
889
|
+
return "Private key is required";
|
|
890
|
+
}
|
|
891
|
+
if (!validatePrivateKeyFormat(value)) {
|
|
892
|
+
return "Invalid private key format (must be 64 hex characters, optionally prefixed with 0x)";
|
|
893
|
+
}
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
return key.trim();
|
|
898
|
+
}
|
|
899
|
+
var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
900
|
+
|
|
901
|
+
// src/flags.ts
|
|
902
|
+
var commonFlags = {
|
|
903
|
+
environment: Flags.string({
|
|
904
|
+
required: false,
|
|
905
|
+
description: "Deployment environment to use",
|
|
906
|
+
env: "ECLOUD_ENV"
|
|
907
|
+
}),
|
|
908
|
+
"private-key": Flags.string({
|
|
909
|
+
required: false,
|
|
910
|
+
description: "Private key for signing transactions",
|
|
911
|
+
env: "ECLOUD_PRIVATE_KEY"
|
|
912
|
+
}),
|
|
913
|
+
"rpc-url": Flags.string({
|
|
914
|
+
required: false,
|
|
915
|
+
description: "RPC URL to connect to blockchain",
|
|
916
|
+
env: "ECLOUD_RPC_URL"
|
|
917
|
+
}),
|
|
918
|
+
verbose: Flags.boolean({
|
|
919
|
+
required: false,
|
|
920
|
+
description: "Enable verbose logging (default: false)",
|
|
921
|
+
default: false
|
|
922
|
+
})
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/commands/compute/app/upgrade.ts
|
|
926
|
+
import chalk from "chalk";
|
|
927
|
+
var AppUpgrade = class _AppUpgrade extends Command {
|
|
928
|
+
static description = "Upgrade existing deployment";
|
|
929
|
+
static args = {
|
|
930
|
+
"app-id": Args.string({
|
|
931
|
+
description: "App ID or name to upgrade",
|
|
932
|
+
required: false
|
|
933
|
+
})
|
|
934
|
+
};
|
|
935
|
+
static flags = {
|
|
936
|
+
...commonFlags,
|
|
937
|
+
dockerfile: Flags2.string({
|
|
938
|
+
required: false,
|
|
939
|
+
description: "Path to Dockerfile",
|
|
940
|
+
env: "ECLOUD_DOCKERFILE_PATH"
|
|
941
|
+
}),
|
|
942
|
+
"image-ref": Flags2.string({
|
|
943
|
+
required: false,
|
|
944
|
+
description: "Image reference pointing to registry",
|
|
945
|
+
env: "ECLOUD_IMAGE_REF"
|
|
946
|
+
}),
|
|
947
|
+
"env-file": Flags2.string({
|
|
948
|
+
required: false,
|
|
949
|
+
description: 'Environment file to use (default: ".env")',
|
|
950
|
+
default: ".env",
|
|
951
|
+
env: "ECLOUD_ENVFILE_PATH"
|
|
952
|
+
}),
|
|
953
|
+
"log-visibility": Flags2.string({
|
|
954
|
+
required: false,
|
|
955
|
+
description: "Log visibility setting: public, private, or off",
|
|
956
|
+
options: ["public", "private", "off"],
|
|
957
|
+
env: "ECLOUD_LOG_VISIBILITY"
|
|
958
|
+
}),
|
|
959
|
+
"instance-type": Flags2.string({
|
|
960
|
+
required: false,
|
|
961
|
+
description: "Machine instance type to use e.g. g1-standard-4t, g1-standard-8t",
|
|
962
|
+
env: "ECLOUD_INSTANCE_TYPE"
|
|
963
|
+
}),
|
|
964
|
+
"resource-usage-monitoring": Flags2.string({
|
|
965
|
+
required: false,
|
|
966
|
+
description: "Resource usage monitoring: enable or disable",
|
|
967
|
+
options: ["enable", "disable"],
|
|
968
|
+
env: "ECLOUD_RESOURCE_USAGE_MONITORING"
|
|
969
|
+
})
|
|
970
|
+
};
|
|
971
|
+
async run() {
|
|
972
|
+
const { args, flags } = await this.parse(_AppUpgrade);
|
|
973
|
+
const logger = {
|
|
974
|
+
info: (msg) => this.log(msg),
|
|
975
|
+
warn: (msg) => this.warn(msg),
|
|
976
|
+
error: (msg) => this.error(msg),
|
|
977
|
+
debug: (msg) => flags.verbose && this.log(msg)
|
|
978
|
+
};
|
|
979
|
+
const environment = flags.environment || "sepolia";
|
|
980
|
+
const environmentConfig = getEnvironmentConfig2(environment);
|
|
981
|
+
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
982
|
+
const privateKey = await getPrivateKeyInteractive(flags["private-key"]);
|
|
983
|
+
const appID = await getOrPromptAppID({
|
|
984
|
+
appID: args["app-id"],
|
|
985
|
+
environment,
|
|
986
|
+
privateKey,
|
|
987
|
+
rpcUrl,
|
|
988
|
+
action: "upgrade"
|
|
989
|
+
});
|
|
990
|
+
const dockerfilePath = await getDockerfileInteractive(flags.dockerfile);
|
|
991
|
+
const buildFromDockerfile = dockerfilePath !== "";
|
|
992
|
+
const imageRef = await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
993
|
+
const envFilePath = await getEnvFileInteractive(flags["env-file"]);
|
|
994
|
+
let currentInstanceType = "";
|
|
995
|
+
try {
|
|
996
|
+
const userApiClient = new UserApiClient3(environmentConfig, privateKey, rpcUrl, getClientId());
|
|
997
|
+
const infos = await userApiClient.getInfos([appID], 1);
|
|
998
|
+
if (infos.length > 0) {
|
|
999
|
+
currentInstanceType = infos[0].machineType || "";
|
|
1000
|
+
}
|
|
1001
|
+
} catch {
|
|
1002
|
+
}
|
|
1003
|
+
const availableTypes = await fetchAvailableInstanceTypes(environmentConfig, privateKey, rpcUrl);
|
|
1004
|
+
const instanceType = await getInstanceTypeInteractive(
|
|
1005
|
+
flags["instance-type"],
|
|
1006
|
+
currentInstanceType,
|
|
1007
|
+
availableTypes
|
|
1008
|
+
);
|
|
1009
|
+
const logSettings = await getLogSettingsInteractive(
|
|
1010
|
+
flags["log-visibility"]
|
|
1011
|
+
);
|
|
1012
|
+
const resourceUsageMonitoring = await getResourceUsageMonitoringInteractive(
|
|
1013
|
+
flags["resource-usage-monitoring"]
|
|
1014
|
+
);
|
|
1015
|
+
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1016
|
+
const { prepared, gasEstimate } = await prepareUpgrade(
|
|
1017
|
+
{
|
|
1018
|
+
appId: appID,
|
|
1019
|
+
privateKey,
|
|
1020
|
+
rpcUrl,
|
|
1021
|
+
environment,
|
|
1022
|
+
dockerfilePath,
|
|
1023
|
+
imageRef,
|
|
1024
|
+
envFilePath,
|
|
1025
|
+
instanceType,
|
|
1026
|
+
logVisibility,
|
|
1027
|
+
resourceUsageMonitoring
|
|
1028
|
+
},
|
|
1029
|
+
logger
|
|
1030
|
+
);
|
|
1031
|
+
this.log(`
|
|
1032
|
+
Estimated transaction cost: ${chalk.cyan(gasEstimate.maxCostEth)} ETH`);
|
|
1033
|
+
if (isMainnet(environmentConfig)) {
|
|
1034
|
+
const confirmed = await confirm(`Continue with upgrade?`);
|
|
1035
|
+
if (!confirmed) {
|
|
1036
|
+
this.log(`
|
|
1037
|
+
${chalk.gray(`Upgrade cancelled`)}`);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
const res = await executeUpgrade(
|
|
1042
|
+
prepared,
|
|
1043
|
+
{
|
|
1044
|
+
maxFeePerGas: gasEstimate.maxFeePerGas,
|
|
1045
|
+
maxPriorityFeePerGas: gasEstimate.maxPriorityFeePerGas
|
|
1046
|
+
},
|
|
1047
|
+
logger
|
|
1048
|
+
);
|
|
1049
|
+
await watchUpgrade(res.appId, privateKey, rpcUrl, environment, logger, getClientId());
|
|
1050
|
+
this.log(
|
|
1051
|
+
`
|
|
1052
|
+
\u2705 ${chalk.green(`App upgraded successfully ${chalk.bold(`(id: ${res.appId}, image: ${res.imageRef})`)}`)}`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
async function fetchAvailableInstanceTypes(environmentConfig, privateKey, rpcUrl) {
|
|
1057
|
+
try {
|
|
1058
|
+
const userApiClient = new UserApiClient3(environmentConfig, privateKey, rpcUrl, getClientId());
|
|
1059
|
+
const skuList = await userApiClient.getSKUs();
|
|
1060
|
+
if (skuList.skus.length === 0) {
|
|
1061
|
+
throw new Error("No instance types available from server");
|
|
1062
|
+
}
|
|
1063
|
+
return skuList.skus;
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
1066
|
+
return [{ sku: "g1-standard-4t", description: "Standard 4-thread instance" }];
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
export {
|
|
1070
|
+
AppUpgrade as default
|
|
1071
|
+
};
|
|
1072
|
+
//# sourceMappingURL=upgrade.js.map
|