@layr-labs/ecloud-cli 0.2.0-dev.2 → 0.2.0-dev.3
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/VERSION +2 -2
- package/dist/commands/auth/generate.js +2 -2
- package/dist/commands/auth/generate.js.map +1 -1
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/migrate.js.map +1 -1
- package/dist/commands/auth/whoami.js +5 -3
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/cancel.js +6 -3
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +6 -3
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +12 -7
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +614 -86
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +59 -10
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +10 -9
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +61 -11
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +145 -20
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +1111 -0
- package/dist/commands/compute/app/releases.js.map +1 -0
- package/dist/commands/compute/app/start.js +61 -11
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/stop.js +61 -11
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +61 -11
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +615 -52
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +500 -0
- package/dist/commands/compute/build/info.js.map +1 -0
- package/dist/commands/compute/build/list.js +494 -0
- package/dist/commands/compute/build/list.js.map +1 -0
- package/dist/commands/compute/build/logs.js +459 -0
- package/dist/commands/compute/build/logs.js.map +1 -0
- package/dist/commands/compute/build/status.js +481 -0
- package/dist/commands/compute/build/status.js.map +1 -0
- package/dist/commands/compute/build/submit.js +618 -0
- package/dist/commands/compute/build/submit.js.map +1 -0
- package/dist/commands/compute/build/verify.js +439 -0
- package/dist/commands/compute/build/verify.js.map +1 -0
- package/dist/commands/compute/environment/list.js.map +1 -1
- package/dist/commands/compute/environment/set.js.map +1 -1
- package/dist/commands/compute/environment/show.js.map +1 -1
- package/dist/commands/compute/undelegate.js +11 -9
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/version.js.map +1 -1
- package/package.json +7 -2
|
@@ -2,14 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/compute/app/upgrade.ts
|
|
4
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";
|
|
5
|
+
import { getEnvironmentConfig as getEnvironmentConfig3, UserApiClient as UserApiClient3, isMainnet } from "@layr-labs/ecloud-sdk";
|
|
13
6
|
|
|
14
7
|
// src/telemetry.ts
|
|
15
8
|
import {
|
|
@@ -71,6 +64,42 @@ function saveGlobalConfig(config) {
|
|
|
71
64
|
const content = dumpYaml(config, { lineWidth: -1 });
|
|
72
65
|
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
73
66
|
}
|
|
67
|
+
function normalizeDirectoryPath(directoryPath) {
|
|
68
|
+
const resolved = path.resolve(directoryPath);
|
|
69
|
+
try {
|
|
70
|
+
return fs.realpathSync(resolved);
|
|
71
|
+
} catch {
|
|
72
|
+
return resolved;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getLinkedAppForDirectory(environment, directoryPath) {
|
|
76
|
+
if (!directoryPath) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const config = loadGlobalConfig();
|
|
80
|
+
const links = config.directory_links?.[environment];
|
|
81
|
+
if (!links) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const normalizedPath = normalizeDirectoryPath(directoryPath);
|
|
85
|
+
const appId = links[normalizedPath];
|
|
86
|
+
return appId || null;
|
|
87
|
+
}
|
|
88
|
+
function setLinkedAppForDirectory(environment, directoryPath, appId) {
|
|
89
|
+
if (!directoryPath || !environment) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const config = loadGlobalConfig();
|
|
93
|
+
if (!config.directory_links) {
|
|
94
|
+
config.directory_links = {};
|
|
95
|
+
}
|
|
96
|
+
if (!config.directory_links[environment]) {
|
|
97
|
+
config.directory_links[environment] = {};
|
|
98
|
+
}
|
|
99
|
+
const normalizedPath = normalizeDirectoryPath(directoryPath);
|
|
100
|
+
config.directory_links[environment][normalizedPath] = appId.toLowerCase();
|
|
101
|
+
saveGlobalConfig(config);
|
|
102
|
+
}
|
|
74
103
|
function getDefaultEnvironment() {
|
|
75
104
|
const config = loadGlobalConfig();
|
|
76
105
|
return config.default_environment;
|
|
@@ -172,6 +201,7 @@ async function withTelemetry(command, action) {
|
|
|
172
201
|
|
|
173
202
|
// src/flags.ts
|
|
174
203
|
import { Flags } from "@oclif/core";
|
|
204
|
+
import { getBuildType as getBuildType3 } from "@layr-labs/ecloud-sdk";
|
|
175
205
|
|
|
176
206
|
// src/utils/prompts.ts
|
|
177
207
|
import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
@@ -250,7 +280,7 @@ function listApps(environment) {
|
|
|
250
280
|
|
|
251
281
|
// src/utils/version.ts
|
|
252
282
|
function getCliVersion() {
|
|
253
|
-
return true ? "0.2.0-dev.
|
|
283
|
+
return true ? "0.2.0-dev.3" : "0.0.0";
|
|
254
284
|
}
|
|
255
285
|
function getClientId() {
|
|
256
286
|
return `ecloud-cli/v${getCliVersion()}`;
|
|
@@ -306,6 +336,113 @@ Found Dockerfile in ${cwd}`);
|
|
|
306
336
|
throw new Error(`Unexpected choice: ${choice}`);
|
|
307
337
|
}
|
|
308
338
|
}
|
|
339
|
+
async function promptUseVerifiableBuild() {
|
|
340
|
+
return confirmWithDefault("Build from verifiable source?", false);
|
|
341
|
+
}
|
|
342
|
+
async function promptVerifiableSourceType() {
|
|
343
|
+
return select({
|
|
344
|
+
message: "Choose verifiable source type:",
|
|
345
|
+
choices: [
|
|
346
|
+
{ name: "Build from git source (public repo required)", value: "git" },
|
|
347
|
+
{ name: "Use a prebuilt verifiable image (eigencloud-containers)", value: "prebuilt" }
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async function promptVerifiableGitSourceInputs() {
|
|
352
|
+
const repoUrl = (await input({
|
|
353
|
+
message: "Enter public git repository URL:",
|
|
354
|
+
default: "",
|
|
355
|
+
validate: (value) => {
|
|
356
|
+
if (!value.trim()) return "Repository URL is required";
|
|
357
|
+
try {
|
|
358
|
+
const url = new URL(value.trim());
|
|
359
|
+
if (url.protocol !== "https:") return "Repository URL must start with https://";
|
|
360
|
+
if (url.hostname.toLowerCase() !== "github.com")
|
|
361
|
+
return "Repository URL must be a public GitHub HTTPS URL (github.com)";
|
|
362
|
+
const parts = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
|
|
363
|
+
if (parts.length < 2) return "Repository URL must be https://github.com/<owner>/<repo>";
|
|
364
|
+
const [owner, repo] = parts;
|
|
365
|
+
if (!owner || !repo) return "Repository URL must be https://github.com/<owner>/<repo>";
|
|
366
|
+
if (repo.toLowerCase() === "settings") return "Repository URL looks invalid";
|
|
367
|
+
if (url.search || url.hash)
|
|
368
|
+
return "Repository URL must not include query params or fragments";
|
|
369
|
+
} catch {
|
|
370
|
+
return "Invalid URL format";
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
})).trim();
|
|
375
|
+
const gitRef = (await input({
|
|
376
|
+
message: "Enter git commit SHA (40 hex chars):",
|
|
377
|
+
default: "",
|
|
378
|
+
validate: (value) => {
|
|
379
|
+
const trimmed = value.trim();
|
|
380
|
+
if (!trimmed) return "Commit SHA is required";
|
|
381
|
+
if (!/^[0-9a-f]{40}$/i.test(trimmed))
|
|
382
|
+
return "Commit must be a 40-character hexadecimal SHA";
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
})).trim();
|
|
386
|
+
const buildContextPath = (await input({
|
|
387
|
+
message: "Enter build context path (relative to repo):",
|
|
388
|
+
default: ".",
|
|
389
|
+
validate: (value) => value.trim() ? true : "Build context path cannot be empty"
|
|
390
|
+
})).trim();
|
|
391
|
+
const dockerfilePath = (await input({
|
|
392
|
+
message: "Enter Dockerfile path (relative to build context):",
|
|
393
|
+
default: "Dockerfile",
|
|
394
|
+
validate: (value) => value.trim() ? true : "Dockerfile path cannot be empty"
|
|
395
|
+
})).trim();
|
|
396
|
+
const caddyfileRaw = (await input({
|
|
397
|
+
message: "Enter Caddyfile path (relative to build context, optional):",
|
|
398
|
+
default: "",
|
|
399
|
+
validate: (value) => {
|
|
400
|
+
const trimmed = value.trim();
|
|
401
|
+
if (!trimmed) return true;
|
|
402
|
+
if (trimmed.includes("..")) return "Caddyfile path must not contain '..'";
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
})).trim();
|
|
406
|
+
const depsRaw = (await input({
|
|
407
|
+
message: "Enter dependency digests (comma-separated sha256:..., optional):",
|
|
408
|
+
default: "",
|
|
409
|
+
validate: (value) => {
|
|
410
|
+
const trimmed = value.trim();
|
|
411
|
+
if (!trimmed) return true;
|
|
412
|
+
const parts = trimmed.split(",").map((p) => p.trim()).filter(Boolean);
|
|
413
|
+
for (const p of parts) {
|
|
414
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(p)) {
|
|
415
|
+
return `Invalid dependency digest: ${p} (expected sha256:<64 hex>)`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
})).trim();
|
|
421
|
+
const dependencies = depsRaw === "" ? [] : depsRaw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
422
|
+
return {
|
|
423
|
+
repoUrl,
|
|
424
|
+
gitRef,
|
|
425
|
+
dockerfilePath,
|
|
426
|
+
caddyfilePath: caddyfileRaw === "" ? void 0 : caddyfileRaw,
|
|
427
|
+
buildContextPath,
|
|
428
|
+
dependencies
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async function promptVerifiablePrebuiltImageRef() {
|
|
432
|
+
const ref = await input({
|
|
433
|
+
message: "Enter prebuilt verifiable image ref:",
|
|
434
|
+
default: "docker.io/eigenlayer/eigencloud-containers:",
|
|
435
|
+
validate: (value) => {
|
|
436
|
+
const trimmed = value.trim();
|
|
437
|
+
if (!trimmed) return "Image reference is required";
|
|
438
|
+
if (!/^docker\.io\/eigenlayer\/eigencloud-containers:[^@\s]+$/i.test(trimmed)) {
|
|
439
|
+
return "Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>";
|
|
440
|
+
}
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return ref.trim();
|
|
445
|
+
}
|
|
309
446
|
function extractHostname(registry) {
|
|
310
447
|
let hostname = registry.replace(/^https?:\/\//, "");
|
|
311
448
|
hostname = hostname.split("/")[0];
|
|
@@ -445,6 +582,9 @@ function getDefaultAppName() {
|
|
|
445
582
|
return "myapp";
|
|
446
583
|
}
|
|
447
584
|
}
|
|
585
|
+
function getCurrentProjectPath() {
|
|
586
|
+
return process.env.INIT_CWD || process.cwd();
|
|
587
|
+
}
|
|
448
588
|
function suggestImageReference(registry, imageName, tag) {
|
|
449
589
|
imageName = imageName.toLowerCase().replace(/_/g, "-");
|
|
450
590
|
if (!tag) {
|
|
@@ -791,6 +931,7 @@ Select an app to ${action}:
|
|
|
791
931
|
switch (action) {
|
|
792
932
|
case "view":
|
|
793
933
|
case "view info for":
|
|
934
|
+
case "view releases for":
|
|
794
935
|
case "set profile for":
|
|
795
936
|
return true;
|
|
796
937
|
case "start":
|
|
@@ -819,7 +960,19 @@ Select an app to ${action}:
|
|
|
819
960
|
index: i
|
|
820
961
|
});
|
|
821
962
|
}
|
|
963
|
+
const linkedAppId = getLinkedAppForDirectory(environment, getCurrentProjectPath());
|
|
964
|
+
const normalizedLinkedAppId = linkedAppId ? linkedAppId.toLowerCase() : "";
|
|
822
965
|
appItems.sort((a, b) => {
|
|
966
|
+
if (normalizedLinkedAppId) {
|
|
967
|
+
const aLinked = String(a.addr).toLowerCase() === normalizedLinkedAppId;
|
|
968
|
+
const bLinked = String(b.addr).toLowerCase() === normalizedLinkedAppId;
|
|
969
|
+
if (aLinked && !bLinked) {
|
|
970
|
+
return -1;
|
|
971
|
+
}
|
|
972
|
+
if (bLinked && !aLinked) {
|
|
973
|
+
return 1;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
823
976
|
const aPriority = getStatusPriority(a.status, false);
|
|
824
977
|
const bPriority = getStatusPriority(b.status, false);
|
|
825
978
|
if (aPriority !== bPriority) {
|
|
@@ -885,7 +1038,18 @@ async function getAppIDInteractiveFromRegistry(environment, action) {
|
|
|
885
1038
|
}
|
|
886
1039
|
throw new Error(`Invalid app ID address: ${appIDInput}`);
|
|
887
1040
|
}
|
|
888
|
-
const
|
|
1041
|
+
const entries = Object.entries(allApps);
|
|
1042
|
+
const linkedAppId = getLinkedAppForDirectory(environment, getCurrentProjectPath());
|
|
1043
|
+
if (linkedAppId) {
|
|
1044
|
+
const linkedIndex = entries.findIndex(
|
|
1045
|
+
([, appId]) => String(appId).toLowerCase() === linkedAppId.toLowerCase()
|
|
1046
|
+
);
|
|
1047
|
+
if (linkedIndex > 0) {
|
|
1048
|
+
const [linkedEntry] = entries.splice(linkedIndex, 1);
|
|
1049
|
+
entries.unshift(linkedEntry);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const choices = entries.map(([name, appID]) => {
|
|
889
1053
|
const displayName = `${name} (${appID})`;
|
|
890
1054
|
return { name: displayName, value: appID };
|
|
891
1055
|
});
|
|
@@ -963,8 +1127,8 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
963
1127
|
}
|
|
964
1128
|
return privateKey;
|
|
965
1129
|
}
|
|
966
|
-
const { getPrivateKeyWithSource } = await import("@layr-labs/ecloud-sdk");
|
|
967
|
-
const result = await
|
|
1130
|
+
const { getPrivateKeyWithSource: getPrivateKeyWithSource2 } = await import("@layr-labs/ecloud-sdk");
|
|
1131
|
+
const result = await getPrivateKeyWithSource2({ privateKey: void 0 });
|
|
968
1132
|
if (result) {
|
|
969
1133
|
return result.key;
|
|
970
1134
|
}
|
|
@@ -983,6 +1147,50 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
983
1147
|
});
|
|
984
1148
|
return key.trim();
|
|
985
1149
|
}
|
|
1150
|
+
async function getEnvironmentInteractive(environment) {
|
|
1151
|
+
if (environment) {
|
|
1152
|
+
try {
|
|
1153
|
+
getEnvironmentConfig(environment);
|
|
1154
|
+
if (!isEnvironmentAvailable(environment)) {
|
|
1155
|
+
throw new Error(`Environment ${environment} is not available in this build`);
|
|
1156
|
+
}
|
|
1157
|
+
return environment;
|
|
1158
|
+
} catch {
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
const availableEnvs = getAvailableEnvironments();
|
|
1162
|
+
let defaultEnv;
|
|
1163
|
+
const configDefaultEnv = getDefaultEnvironment();
|
|
1164
|
+
if (configDefaultEnv && availableEnvs.includes(configDefaultEnv)) {
|
|
1165
|
+
try {
|
|
1166
|
+
getEnvironmentConfig(configDefaultEnv);
|
|
1167
|
+
defaultEnv = configDefaultEnv;
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const choices = [];
|
|
1172
|
+
if (availableEnvs.includes("sepolia")) {
|
|
1173
|
+
choices.push({ name: "sepolia - Ethereum Sepolia testnet", value: "sepolia" });
|
|
1174
|
+
}
|
|
1175
|
+
if (availableEnvs.includes("sepolia-dev")) {
|
|
1176
|
+
choices.push({ name: "sepolia-dev - Ethereum Sepolia testnet (dev)", value: "sepolia-dev" });
|
|
1177
|
+
}
|
|
1178
|
+
if (availableEnvs.includes("mainnet-alpha")) {
|
|
1179
|
+
choices.push({
|
|
1180
|
+
name: "mainnet-alpha - Ethereum mainnet (\u26A0\uFE0F uses real funds)",
|
|
1181
|
+
value: "mainnet-alpha"
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
if (choices.length === 0) {
|
|
1185
|
+
throw new Error("No environments available in this build");
|
|
1186
|
+
}
|
|
1187
|
+
const env = await select({
|
|
1188
|
+
message: "Select environment:",
|
|
1189
|
+
choices,
|
|
1190
|
+
default: defaultEnv
|
|
1191
|
+
});
|
|
1192
|
+
return env;
|
|
1193
|
+
}
|
|
986
1194
|
var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
987
1195
|
|
|
988
1196
|
// src/flags.ts
|
|
@@ -990,7 +1198,8 @@ var commonFlags = {
|
|
|
990
1198
|
environment: Flags.string({
|
|
991
1199
|
required: false,
|
|
992
1200
|
description: "Deployment environment to use",
|
|
993
|
-
env: "ECLOUD_ENV"
|
|
1201
|
+
env: "ECLOUD_ENV",
|
|
1202
|
+
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "sepolia")
|
|
994
1203
|
}),
|
|
995
1204
|
"private-key": Flags.string({
|
|
996
1205
|
required: false,
|
|
@@ -1008,9 +1217,227 @@ var commonFlags = {
|
|
|
1008
1217
|
default: false
|
|
1009
1218
|
})
|
|
1010
1219
|
};
|
|
1220
|
+
async function validateCommonFlags(flags, options) {
|
|
1221
|
+
flags["environment"] = await getEnvironmentInteractive(flags["environment"]);
|
|
1222
|
+
if (options?.requirePrivateKey !== false) {
|
|
1223
|
+
flags["private-key"] = await getPrivateKeyInteractive(flags["private-key"]);
|
|
1224
|
+
}
|
|
1225
|
+
return flags;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/client.ts
|
|
1229
|
+
import {
|
|
1230
|
+
createComputeModule,
|
|
1231
|
+
createBillingModule,
|
|
1232
|
+
createBuildModule,
|
|
1233
|
+
getEnvironmentConfig as getEnvironmentConfig2,
|
|
1234
|
+
requirePrivateKey,
|
|
1235
|
+
getPrivateKeyWithSource
|
|
1236
|
+
} from "@layr-labs/ecloud-sdk";
|
|
1237
|
+
async function createComputeClient(flags) {
|
|
1238
|
+
flags = await validateCommonFlags(flags);
|
|
1239
|
+
const environment = flags.environment;
|
|
1240
|
+
const environmentConfig = getEnvironmentConfig2(environment);
|
|
1241
|
+
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1242
|
+
const { key: privateKey, source } = await requirePrivateKey({
|
|
1243
|
+
privateKey: flags["private-key"]
|
|
1244
|
+
});
|
|
1245
|
+
if (flags.verbose) {
|
|
1246
|
+
console.log(`Using private key from: ${source}`);
|
|
1247
|
+
}
|
|
1248
|
+
return createComputeModule({
|
|
1249
|
+
verbose: flags.verbose,
|
|
1250
|
+
privateKey,
|
|
1251
|
+
rpcUrl,
|
|
1252
|
+
environment,
|
|
1253
|
+
clientId: getClientId(),
|
|
1254
|
+
skipTelemetry: true
|
|
1255
|
+
// CLI already has telemetry, skip SDK telemetry
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
async function createBuildClient(flags) {
|
|
1259
|
+
flags = await validateCommonFlags(flags, { requirePrivateKey: false });
|
|
1260
|
+
return createBuildModule({
|
|
1261
|
+
verbose: flags.verbose,
|
|
1262
|
+
privateKey: flags["private-key"],
|
|
1263
|
+
environment: flags.environment,
|
|
1264
|
+
clientId: getClientId(),
|
|
1265
|
+
skipTelemetry: true
|
|
1266
|
+
// CLI already has telemetry, skip SDK telemetry
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1011
1269
|
|
|
1012
1270
|
// src/commands/compute/app/upgrade.ts
|
|
1013
1271
|
import chalk from "chalk";
|
|
1272
|
+
|
|
1273
|
+
// src/utils/build.ts
|
|
1274
|
+
function formatSourceLink(repoUrl, gitRef) {
|
|
1275
|
+
const normalizedRepo = repoUrl.replace(/\.git$/, "");
|
|
1276
|
+
try {
|
|
1277
|
+
const url = new URL(normalizedRepo);
|
|
1278
|
+
const host = url.host.toLowerCase();
|
|
1279
|
+
if (host === "github.com") {
|
|
1280
|
+
const path4 = url.pathname.replace(/\/+$/, "");
|
|
1281
|
+
if (path4.split("/").filter(Boolean).length >= 2) {
|
|
1282
|
+
return `https://github.com${path4}/tree/${gitRef}`;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
return `${repoUrl}@${gitRef}`;
|
|
1288
|
+
}
|
|
1289
|
+
function extractRepoName(repoUrl) {
|
|
1290
|
+
const normalized = repoUrl.replace(/\.git$/, "");
|
|
1291
|
+
const match = normalized.match(/\/([^/]+?)$/);
|
|
1292
|
+
return match?.[1];
|
|
1293
|
+
}
|
|
1294
|
+
function formatDependencyLines(dependencies) {
|
|
1295
|
+
if (!dependencies || Object.keys(dependencies).length === 0) return [];
|
|
1296
|
+
const lines = [];
|
|
1297
|
+
lines.push("Dependencies (resolved builds):");
|
|
1298
|
+
for (const [digest, dep] of Object.entries(dependencies)) {
|
|
1299
|
+
const name = extractRepoName(dep.repoUrl);
|
|
1300
|
+
const depSource = formatSourceLink(dep.repoUrl, dep.gitRef);
|
|
1301
|
+
lines.push(` - ${digest} \u2713${name ? ` ${name}` : ""}`);
|
|
1302
|
+
lines.push(` ${depSource}`);
|
|
1303
|
+
}
|
|
1304
|
+
return lines;
|
|
1305
|
+
}
|
|
1306
|
+
function formatVerifiableBuildSummary(options) {
|
|
1307
|
+
const lines = [];
|
|
1308
|
+
lines.push("Build completed successfully \u2713");
|
|
1309
|
+
lines.push("");
|
|
1310
|
+
lines.push(`Image: ${options.imageUrl}`);
|
|
1311
|
+
lines.push(`Digest: ${options.imageDigest}`);
|
|
1312
|
+
lines.push(`Source: ${formatSourceLink(options.repoUrl, options.gitRef)}`);
|
|
1313
|
+
const depLines = formatDependencyLines(options.dependencies);
|
|
1314
|
+
if (depLines.length) {
|
|
1315
|
+
lines.push("");
|
|
1316
|
+
lines.push(...depLines);
|
|
1317
|
+
}
|
|
1318
|
+
lines.push("");
|
|
1319
|
+
lines.push("Provenance signature verified \u2713");
|
|
1320
|
+
lines.push(`provenance_signature: ${options.provenanceSignature}`);
|
|
1321
|
+
if (options.buildId) {
|
|
1322
|
+
lines.push("");
|
|
1323
|
+
lines.push(`Build ID: ${options.buildId}`);
|
|
1324
|
+
}
|
|
1325
|
+
return lines;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/utils/verifiableBuild.ts
|
|
1329
|
+
import { BUILD_STATUS } from "@layr-labs/ecloud-sdk";
|
|
1330
|
+
function assertCommitSha40(commit) {
|
|
1331
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) {
|
|
1332
|
+
throw new Error("Commit must be a 40-character hexadecimal SHA");
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async function runVerifiableBuildAndVerify(client, request, options = {}) {
|
|
1336
|
+
const { buildId } = await client.submit(request);
|
|
1337
|
+
const completed = await client.waitForBuild(buildId, { onLog: options.onLog });
|
|
1338
|
+
if (completed.status !== BUILD_STATUS.SUCCESS) {
|
|
1339
|
+
throw new Error(`Build did not complete successfully (status: ${completed.status})`);
|
|
1340
|
+
}
|
|
1341
|
+
const [build, verify] = await Promise.all([client.get(buildId), client.verify(buildId)]);
|
|
1342
|
+
if (verify.status !== "verified") {
|
|
1343
|
+
throw new Error(`Provenance verification failed: ${verify.error}`);
|
|
1344
|
+
}
|
|
1345
|
+
return { build, verified: verify };
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// src/utils/dockerhub.ts
|
|
1349
|
+
var DOCKERHUB_OWNER = "eigenlayer";
|
|
1350
|
+
var DOCKERHUB_REPO = "eigencloud-containers";
|
|
1351
|
+
function parseEigencloudContainersImageRef(imageRef) {
|
|
1352
|
+
const trimmed = imageRef.trim();
|
|
1353
|
+
const match = /^docker\.io\/([^/]+)\/([^:@]+):([^@\s]+)$/i.exec(trimmed);
|
|
1354
|
+
if (!match) {
|
|
1355
|
+
throw new Error("Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>");
|
|
1356
|
+
}
|
|
1357
|
+
const owner = match[1].toLowerCase();
|
|
1358
|
+
const repo = match[2].toLowerCase();
|
|
1359
|
+
const tag = match[3];
|
|
1360
|
+
if (owner !== DOCKERHUB_OWNER || repo !== DOCKERHUB_REPO) {
|
|
1361
|
+
throw new Error(`Image ref must be from docker.io/${DOCKERHUB_OWNER}/${DOCKERHUB_REPO}:<tag>`);
|
|
1362
|
+
}
|
|
1363
|
+
if (!tag.trim()) {
|
|
1364
|
+
throw new Error("Image tag cannot be empty");
|
|
1365
|
+
}
|
|
1366
|
+
return { owner, repo, tag };
|
|
1367
|
+
}
|
|
1368
|
+
function assertEigencloudContainersImageRef(imageRef) {
|
|
1369
|
+
parseEigencloudContainersImageRef(imageRef);
|
|
1370
|
+
}
|
|
1371
|
+
async function getDockerHubToken(owner, repo) {
|
|
1372
|
+
const url = new URL("https://auth.docker.io/token");
|
|
1373
|
+
url.searchParams.set("service", "registry.docker.io");
|
|
1374
|
+
url.searchParams.set("scope", `repository:${owner}/${repo}:pull`);
|
|
1375
|
+
const res = await fetch(url.toString(), { method: "GET" });
|
|
1376
|
+
if (!res.ok) {
|
|
1377
|
+
const body = await safeReadText(res);
|
|
1378
|
+
throw new Error(`Failed to fetch Docker Hub token (${res.status}): ${body || res.statusText}`);
|
|
1379
|
+
}
|
|
1380
|
+
const data = await res.json();
|
|
1381
|
+
if (!data.token) {
|
|
1382
|
+
throw new Error("Docker Hub token response missing 'token'");
|
|
1383
|
+
}
|
|
1384
|
+
return data.token;
|
|
1385
|
+
}
|
|
1386
|
+
async function safeReadText(res) {
|
|
1387
|
+
try {
|
|
1388
|
+
return (await res.text()).trim();
|
|
1389
|
+
} catch {
|
|
1390
|
+
return "";
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async function resolveDockerHubImageDigest(imageRef) {
|
|
1394
|
+
const { owner, repo, tag } = parseEigencloudContainersImageRef(imageRef);
|
|
1395
|
+
const token = await getDockerHubToken(owner, repo);
|
|
1396
|
+
const manifestUrl = `https://registry-1.docker.io/v2/${owner}/${repo}/manifests/${encodeURIComponent(tag)}`;
|
|
1397
|
+
const headers = {
|
|
1398
|
+
Authorization: `Bearer ${token}`,
|
|
1399
|
+
Accept: "application/vnd.docker.distribution.manifest.v2+json"
|
|
1400
|
+
};
|
|
1401
|
+
let res = await fetch(manifestUrl, { method: "HEAD", headers });
|
|
1402
|
+
if (!res.ok) {
|
|
1403
|
+
res = await fetch(manifestUrl, { method: "GET", headers });
|
|
1404
|
+
}
|
|
1405
|
+
if (!res.ok) {
|
|
1406
|
+
const body = await safeReadText(res);
|
|
1407
|
+
throw new Error(
|
|
1408
|
+
`Failed to resolve digest for ${imageRef} (${res.status}) at ${manifestUrl}: ${body || res.statusText}`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
const digest = res.headers.get("docker-content-digest") || res.headers.get("Docker-Content-Digest");
|
|
1412
|
+
if (!digest) {
|
|
1413
|
+
throw new Error(
|
|
1414
|
+
`Docker registry response missing Docker-Content-Digest header for ${imageRef}`
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
|
|
1418
|
+
throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
|
|
1419
|
+
}
|
|
1420
|
+
return digest;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/utils/tls.ts
|
|
1424
|
+
import fs4 from "fs";
|
|
1425
|
+
function isTlsEnabledFromDomain(domain) {
|
|
1426
|
+
const d = (domain ?? "").trim();
|
|
1427
|
+
if (!d) return false;
|
|
1428
|
+
if (d.toLowerCase() === "localhost") return false;
|
|
1429
|
+
return true;
|
|
1430
|
+
}
|
|
1431
|
+
function isTlsEnabledFromEnvFile(envFilePath) {
|
|
1432
|
+
if (!envFilePath) return false;
|
|
1433
|
+
if (!fs4.existsSync(envFilePath)) return false;
|
|
1434
|
+
const envContent = fs4.readFileSync(envFilePath, "utf-8");
|
|
1435
|
+
const match = envContent.match(/^DOMAIN=(.+)$/m);
|
|
1436
|
+
if (!match?.[1]) return false;
|
|
1437
|
+
return isTlsEnabledFromDomain(match[1]);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/commands/compute/app/upgrade.ts
|
|
1014
1441
|
var AppUpgrade = class _AppUpgrade extends Command {
|
|
1015
1442
|
static description = "Upgrade existing deployment";
|
|
1016
1443
|
static args = {
|
|
@@ -1053,21 +1480,43 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1053
1480
|
description: "Resource usage monitoring: enable or disable",
|
|
1054
1481
|
options: ["enable", "disable"],
|
|
1055
1482
|
env: "ECLOUD_RESOURCE_USAGE_MONITORING"
|
|
1483
|
+
}),
|
|
1484
|
+
// Verifiable build flags
|
|
1485
|
+
verifiable: Flags2.boolean({
|
|
1486
|
+
description: "Enable verifiable build mode (either build from git source via --repo/--commit, or upgrade to a prebuilt verifiable image via --image-ref)",
|
|
1487
|
+
default: false
|
|
1488
|
+
}),
|
|
1489
|
+
repo: Flags2.string({
|
|
1490
|
+
description: "Git repository URL (required with --verifiable git source mode)",
|
|
1491
|
+
env: "ECLOUD_BUILD_REPO"
|
|
1492
|
+
}),
|
|
1493
|
+
commit: Flags2.string({
|
|
1494
|
+
description: "Git commit SHA (required with --verifiable git source mode)",
|
|
1495
|
+
env: "ECLOUD_BUILD_COMMIT"
|
|
1496
|
+
}),
|
|
1497
|
+
"build-dockerfile": Flags2.string({
|
|
1498
|
+
description: "Dockerfile path for verifiable build (git source mode)",
|
|
1499
|
+
default: "Dockerfile",
|
|
1500
|
+
env: "ECLOUD_BUILD_DOCKERFILE"
|
|
1501
|
+
}),
|
|
1502
|
+
"build-context": Flags2.string({
|
|
1503
|
+
description: "Build context path for verifiable build (git source mode)",
|
|
1504
|
+
default: ".",
|
|
1505
|
+
env: "ECLOUD_BUILD_CONTEXT"
|
|
1506
|
+
}),
|
|
1507
|
+
"build-dependencies": Flags2.string({
|
|
1508
|
+
description: "Dependency digests for verifiable build (git source mode) (sha256:...)",
|
|
1509
|
+
multiple: true
|
|
1056
1510
|
})
|
|
1057
1511
|
};
|
|
1058
1512
|
async run() {
|
|
1059
1513
|
return withTelemetry(this, async () => {
|
|
1060
1514
|
const { args, flags } = await this.parse(_AppUpgrade);
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
error: (msg) => this.error(msg),
|
|
1065
|
-
debug: (msg) => flags.verbose && this.log(msg)
|
|
1066
|
-
};
|
|
1067
|
-
const environment = flags.environment || "sepolia";
|
|
1068
|
-
const environmentConfig = getEnvironmentConfig2(environment);
|
|
1515
|
+
const compute = await createComputeClient(flags);
|
|
1516
|
+
const environment = flags.environment;
|
|
1517
|
+
const environmentConfig = getEnvironmentConfig3(environment);
|
|
1069
1518
|
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1070
|
-
const privateKey =
|
|
1519
|
+
const privateKey = flags["private-key"];
|
|
1071
1520
|
const appID = await getOrPromptAppID({
|
|
1072
1521
|
appID: args["app-id"],
|
|
1073
1522
|
environment,
|
|
@@ -1075,10 +1524,125 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1075
1524
|
rpcUrl,
|
|
1076
1525
|
action: "upgrade"
|
|
1077
1526
|
});
|
|
1078
|
-
|
|
1527
|
+
let buildClient;
|
|
1528
|
+
const getBuildClient = async () => {
|
|
1529
|
+
if (buildClient) return buildClient;
|
|
1530
|
+
buildClient = await createBuildClient({
|
|
1531
|
+
...flags,
|
|
1532
|
+
"private-key": privateKey
|
|
1533
|
+
});
|
|
1534
|
+
return buildClient;
|
|
1535
|
+
};
|
|
1536
|
+
let verifiableImageUrl;
|
|
1537
|
+
let verifiableImageDigest;
|
|
1538
|
+
let verifiableMode = "none";
|
|
1539
|
+
let envFilePath;
|
|
1540
|
+
if (flags.verifiable) {
|
|
1541
|
+
if (flags.repo || flags.commit) {
|
|
1542
|
+
verifiableMode = "git";
|
|
1543
|
+
if (!flags.repo)
|
|
1544
|
+
this.error("--repo is required when using --verifiable (git source mode)");
|
|
1545
|
+
if (!flags.commit)
|
|
1546
|
+
this.error("--commit is required when using --verifiable (git source mode)");
|
|
1547
|
+
try {
|
|
1548
|
+
assertCommitSha40(flags.commit);
|
|
1549
|
+
} catch (e) {
|
|
1550
|
+
this.error(e?.message || String(e));
|
|
1551
|
+
}
|
|
1552
|
+
} else if (flags["image-ref"]) {
|
|
1553
|
+
verifiableMode = "prebuilt";
|
|
1554
|
+
try {
|
|
1555
|
+
assertEigencloudContainersImageRef(flags["image-ref"]);
|
|
1556
|
+
} catch (e) {
|
|
1557
|
+
this.error(e?.message || String(e));
|
|
1558
|
+
}
|
|
1559
|
+
} else {
|
|
1560
|
+
this.error(
|
|
1561
|
+
"When using --verifiable, you must provide either --repo/--commit or --image-ref"
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
} else {
|
|
1565
|
+
if (!flags.dockerfile) {
|
|
1566
|
+
const useVerifiable = await promptUseVerifiableBuild();
|
|
1567
|
+
if (useVerifiable) {
|
|
1568
|
+
const sourceType = await promptVerifiableSourceType();
|
|
1569
|
+
verifiableMode = sourceType;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (verifiableMode === "git") {
|
|
1574
|
+
const inputs = flags.verifiable ? {
|
|
1575
|
+
repoUrl: flags.repo,
|
|
1576
|
+
gitRef: flags.commit,
|
|
1577
|
+
dockerfilePath: flags["build-dockerfile"],
|
|
1578
|
+
caddyfilePath: void 0,
|
|
1579
|
+
buildContextPath: flags["build-context"],
|
|
1580
|
+
dependencies: flags["build-dependencies"]
|
|
1581
|
+
} : await promptVerifiableGitSourceInputs();
|
|
1582
|
+
envFilePath = await getEnvFileInteractive(flags["env-file"]);
|
|
1583
|
+
const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
|
|
1584
|
+
if (includeTlsCaddyfile && !inputs.caddyfilePath) {
|
|
1585
|
+
inputs.caddyfilePath = "Caddyfile";
|
|
1586
|
+
}
|
|
1587
|
+
this.log(chalk.blue("Building from source with verifiable build..."));
|
|
1588
|
+
this.log("");
|
|
1589
|
+
const buildClient2 = await getBuildClient();
|
|
1590
|
+
const { build, verified } = await runVerifiableBuildAndVerify(buildClient2, inputs, {
|
|
1591
|
+
onLog: (chunk) => process.stdout.write(chunk)
|
|
1592
|
+
});
|
|
1593
|
+
if (!build.imageUrl || !build.imageDigest) {
|
|
1594
|
+
this.error(
|
|
1595
|
+
"Build completed but did not return imageUrl/imageDigest; cannot upgrade verifiable build"
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
verifiableImageUrl = build.imageUrl;
|
|
1599
|
+
verifiableImageDigest = build.imageDigest;
|
|
1600
|
+
for (const line of formatVerifiableBuildSummary({
|
|
1601
|
+
buildId: build.buildId,
|
|
1602
|
+
imageUrl: build.imageUrl,
|
|
1603
|
+
imageDigest: build.imageDigest,
|
|
1604
|
+
repoUrl: build.repoUrl,
|
|
1605
|
+
gitRef: build.gitRef,
|
|
1606
|
+
dependencies: build.dependencies,
|
|
1607
|
+
provenanceSignature: verified.provenanceSignature
|
|
1608
|
+
})) {
|
|
1609
|
+
this.log(line);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
if (verifiableMode === "prebuilt") {
|
|
1613
|
+
const imageRef2 = flags.verifiable ? flags["image-ref"] : await promptVerifiablePrebuiltImageRef();
|
|
1614
|
+
try {
|
|
1615
|
+
assertEigencloudContainersImageRef(imageRef2);
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
this.error(e?.message || String(e));
|
|
1618
|
+
}
|
|
1619
|
+
this.log(chalk.blue("Resolving and verifying prebuilt verifiable image..."));
|
|
1620
|
+
this.log("");
|
|
1621
|
+
const digest = await resolveDockerHubImageDigest(imageRef2);
|
|
1622
|
+
const buildClient2 = await getBuildClient();
|
|
1623
|
+
const verify = await buildClient2.verify(digest);
|
|
1624
|
+
if (verify.status !== "verified") {
|
|
1625
|
+
this.error(`Provenance verification failed: ${verify.error}`);
|
|
1626
|
+
}
|
|
1627
|
+
verifiableImageUrl = imageRef2;
|
|
1628
|
+
verifiableImageDigest = digest;
|
|
1629
|
+
for (const line of formatVerifiableBuildSummary({
|
|
1630
|
+
buildId: verify.buildId,
|
|
1631
|
+
imageUrl: imageRef2,
|
|
1632
|
+
imageDigest: digest,
|
|
1633
|
+
repoUrl: verify.repoUrl,
|
|
1634
|
+
gitRef: verify.gitRef,
|
|
1635
|
+
dependencies: void 0,
|
|
1636
|
+
provenanceSignature: verify.provenanceSignature
|
|
1637
|
+
})) {
|
|
1638
|
+
this.log(line);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const isVerifiable = verifiableMode !== "none";
|
|
1642
|
+
const dockerfilePath = isVerifiable ? "" : await getDockerfileInteractive(flags.dockerfile);
|
|
1079
1643
|
const buildFromDockerfile = dockerfilePath !== "";
|
|
1080
|
-
const imageRef = await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1081
|
-
|
|
1644
|
+
const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1645
|
+
envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
|
|
1082
1646
|
let currentInstanceType = "";
|
|
1083
1647
|
try {
|
|
1084
1648
|
const userApiClient = new UserApiClient3(
|
|
@@ -1110,22 +1674,21 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1110
1674
|
flags["resource-usage-monitoring"]
|
|
1111
1675
|
);
|
|
1112
1676
|
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1113
|
-
const { prepared, gasEstimate } = await
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
);
|
|
1677
|
+
const { prepared, gasEstimate } = isVerifiable ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, {
|
|
1678
|
+
imageRef,
|
|
1679
|
+
imageDigest: verifiableImageDigest,
|
|
1680
|
+
envFile: envFilePath,
|
|
1681
|
+
instanceType,
|
|
1682
|
+
logVisibility,
|
|
1683
|
+
resourceUsageMonitoring
|
|
1684
|
+
}) : await compute.app.prepareUpgrade(appID, {
|
|
1685
|
+
dockerfile: dockerfilePath,
|
|
1686
|
+
imageRef,
|
|
1687
|
+
envFile: envFilePath,
|
|
1688
|
+
instanceType,
|
|
1689
|
+
logVisibility,
|
|
1690
|
+
resourceUsageMonitoring
|
|
1691
|
+
});
|
|
1129
1692
|
this.log(`
|
|
1130
1693
|
Estimated transaction cost: ${chalk.cyan(gasEstimate.maxCostEth)} ETH`);
|
|
1131
1694
|
if (isMainnet(environmentConfig)) {
|
|
@@ -1136,17 +1699,17 @@ ${chalk.gray(`Upgrade cancelled`)}`);
|
|
|
1136
1699
|
return;
|
|
1137
1700
|
}
|
|
1138
1701
|
}
|
|
1139
|
-
const res = await executeUpgrade(
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1702
|
+
const res = await compute.app.executeUpgrade(prepared, {
|
|
1703
|
+
maxFeePerGas: gasEstimate.maxFeePerGas,
|
|
1704
|
+
maxPriorityFeePerGas: gasEstimate.maxPriorityFeePerGas
|
|
1705
|
+
});
|
|
1706
|
+
await compute.app.watchUpgrade(res.appId);
|
|
1707
|
+
try {
|
|
1708
|
+
const cwd = process.env.INIT_CWD || process.cwd();
|
|
1709
|
+
setLinkedAppForDirectory(environment, cwd, res.appId);
|
|
1710
|
+
} catch (err) {
|
|
1711
|
+
this.debug(`Failed to link directory to app: ${err.message}`);
|
|
1712
|
+
}
|
|
1150
1713
|
this.log(
|
|
1151
1714
|
`
|
|
1152
1715
|
\u2705 ${chalk.green(`App upgraded successfully ${chalk.bold(`(id: ${res.appId}, image: ${res.imageRef})`)}`)}`
|