@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/deploy.ts
|
|
4
4
|
import { Command, Flags as Flags2 } from "@oclif/core";
|
|
5
|
-
import {
|
|
6
|
-
getEnvironmentConfig as getEnvironmentConfig2,
|
|
7
|
-
UserApiClient as UserApiClient3,
|
|
8
|
-
isMainnet,
|
|
9
|
-
prepareDeploy,
|
|
10
|
-
executeDeploy,
|
|
11
|
-
watchDeployment
|
|
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,29 @@ 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 setLinkedAppForDirectory(environment, directoryPath, appId) {
|
|
76
|
+
if (!directoryPath || !environment) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const config = loadGlobalConfig();
|
|
80
|
+
if (!config.directory_links) {
|
|
81
|
+
config.directory_links = {};
|
|
82
|
+
}
|
|
83
|
+
if (!config.directory_links[environment]) {
|
|
84
|
+
config.directory_links[environment] = {};
|
|
85
|
+
}
|
|
86
|
+
const normalizedPath = normalizeDirectoryPath(directoryPath);
|
|
87
|
+
config.directory_links[environment][normalizedPath] = appId.toLowerCase();
|
|
88
|
+
saveGlobalConfig(config);
|
|
89
|
+
}
|
|
74
90
|
function getDefaultEnvironment() {
|
|
75
91
|
const config = loadGlobalConfig();
|
|
76
92
|
return config.default_environment;
|
|
@@ -161,6 +177,7 @@ async function withTelemetry(command, action) {
|
|
|
161
177
|
|
|
162
178
|
// src/flags.ts
|
|
163
179
|
import { Flags } from "@oclif/core";
|
|
180
|
+
import { getBuildType as getBuildType3 } from "@layr-labs/ecloud-sdk";
|
|
164
181
|
|
|
165
182
|
// src/utils/prompts.ts
|
|
166
183
|
import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
@@ -256,7 +273,7 @@ function findAvailableName(environment, baseName) {
|
|
|
256
273
|
|
|
257
274
|
// src/utils/version.ts
|
|
258
275
|
function getCliVersion() {
|
|
259
|
-
return true ? "0.2.0-dev.
|
|
276
|
+
return true ? "0.2.0-dev.3" : "0.0.0";
|
|
260
277
|
}
|
|
261
278
|
function getClientId() {
|
|
262
279
|
return `ecloud-cli/v${getCliVersion()}`;
|
|
@@ -290,6 +307,113 @@ Found Dockerfile in ${cwd}`);
|
|
|
290
307
|
throw new Error(`Unexpected choice: ${choice}`);
|
|
291
308
|
}
|
|
292
309
|
}
|
|
310
|
+
async function promptUseVerifiableBuild() {
|
|
311
|
+
return confirmWithDefault("Build from verifiable source?", false);
|
|
312
|
+
}
|
|
313
|
+
async function promptVerifiableSourceType() {
|
|
314
|
+
return select({
|
|
315
|
+
message: "Choose verifiable source type:",
|
|
316
|
+
choices: [
|
|
317
|
+
{ name: "Build from git source (public repo required)", value: "git" },
|
|
318
|
+
{ name: "Use a prebuilt verifiable image (eigencloud-containers)", value: "prebuilt" }
|
|
319
|
+
]
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async function promptVerifiableGitSourceInputs() {
|
|
323
|
+
const repoUrl = (await input({
|
|
324
|
+
message: "Enter public git repository URL:",
|
|
325
|
+
default: "",
|
|
326
|
+
validate: (value) => {
|
|
327
|
+
if (!value.trim()) return "Repository URL is required";
|
|
328
|
+
try {
|
|
329
|
+
const url = new URL(value.trim());
|
|
330
|
+
if (url.protocol !== "https:") return "Repository URL must start with https://";
|
|
331
|
+
if (url.hostname.toLowerCase() !== "github.com")
|
|
332
|
+
return "Repository URL must be a public GitHub HTTPS URL (github.com)";
|
|
333
|
+
const parts = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
|
|
334
|
+
if (parts.length < 2) return "Repository URL must be https://github.com/<owner>/<repo>";
|
|
335
|
+
const [owner, repo] = parts;
|
|
336
|
+
if (!owner || !repo) return "Repository URL must be https://github.com/<owner>/<repo>";
|
|
337
|
+
if (repo.toLowerCase() === "settings") return "Repository URL looks invalid";
|
|
338
|
+
if (url.search || url.hash)
|
|
339
|
+
return "Repository URL must not include query params or fragments";
|
|
340
|
+
} catch {
|
|
341
|
+
return "Invalid URL format";
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
})).trim();
|
|
346
|
+
const gitRef = (await input({
|
|
347
|
+
message: "Enter git commit SHA (40 hex chars):",
|
|
348
|
+
default: "",
|
|
349
|
+
validate: (value) => {
|
|
350
|
+
const trimmed = value.trim();
|
|
351
|
+
if (!trimmed) return "Commit SHA is required";
|
|
352
|
+
if (!/^[0-9a-f]{40}$/i.test(trimmed))
|
|
353
|
+
return "Commit must be a 40-character hexadecimal SHA";
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
})).trim();
|
|
357
|
+
const buildContextPath = (await input({
|
|
358
|
+
message: "Enter build context path (relative to repo):",
|
|
359
|
+
default: ".",
|
|
360
|
+
validate: (value) => value.trim() ? true : "Build context path cannot be empty"
|
|
361
|
+
})).trim();
|
|
362
|
+
const dockerfilePath = (await input({
|
|
363
|
+
message: "Enter Dockerfile path (relative to build context):",
|
|
364
|
+
default: "Dockerfile",
|
|
365
|
+
validate: (value) => value.trim() ? true : "Dockerfile path cannot be empty"
|
|
366
|
+
})).trim();
|
|
367
|
+
const caddyfileRaw = (await input({
|
|
368
|
+
message: "Enter Caddyfile path (relative to build context, optional):",
|
|
369
|
+
default: "",
|
|
370
|
+
validate: (value) => {
|
|
371
|
+
const trimmed = value.trim();
|
|
372
|
+
if (!trimmed) return true;
|
|
373
|
+
if (trimmed.includes("..")) return "Caddyfile path must not contain '..'";
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
})).trim();
|
|
377
|
+
const depsRaw = (await input({
|
|
378
|
+
message: "Enter dependency digests (comma-separated sha256:..., optional):",
|
|
379
|
+
default: "",
|
|
380
|
+
validate: (value) => {
|
|
381
|
+
const trimmed = value.trim();
|
|
382
|
+
if (!trimmed) return true;
|
|
383
|
+
const parts = trimmed.split(",").map((p) => p.trim()).filter(Boolean);
|
|
384
|
+
for (const p of parts) {
|
|
385
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(p)) {
|
|
386
|
+
return `Invalid dependency digest: ${p} (expected sha256:<64 hex>)`;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
})).trim();
|
|
392
|
+
const dependencies = depsRaw === "" ? [] : depsRaw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
393
|
+
return {
|
|
394
|
+
repoUrl,
|
|
395
|
+
gitRef,
|
|
396
|
+
dockerfilePath,
|
|
397
|
+
caddyfilePath: caddyfileRaw === "" ? void 0 : caddyfileRaw,
|
|
398
|
+
buildContextPath,
|
|
399
|
+
dependencies
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async function promptVerifiablePrebuiltImageRef() {
|
|
403
|
+
const ref = await input({
|
|
404
|
+
message: "Enter prebuilt verifiable image ref:",
|
|
405
|
+
default: "docker.io/eigenlayer/eigencloud-containers:",
|
|
406
|
+
validate: (value) => {
|
|
407
|
+
const trimmed = value.trim();
|
|
408
|
+
if (!trimmed) return "Image reference is required";
|
|
409
|
+
if (!/^docker\.io\/eigenlayer\/eigencloud-containers:[^@\s]+$/i.test(trimmed)) {
|
|
410
|
+
return "Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>";
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
return ref.trim();
|
|
416
|
+
}
|
|
293
417
|
function extractHostname(registry) {
|
|
294
418
|
let hostname = registry.replace(/^https?:\/\//, "");
|
|
295
419
|
hostname = hostname.split("/")[0];
|
|
@@ -538,9 +662,9 @@ async function getImageReferenceInteractive(imageRef, buildFromDockerfile = fals
|
|
|
538
662
|
});
|
|
539
663
|
return imageRefInput;
|
|
540
664
|
}
|
|
541
|
-
async function getAvailableAppNameInteractive(environment, imageRef) {
|
|
542
|
-
const baseName = extractAppNameFromImage(imageRef);
|
|
543
|
-
const suggestedName = findAvailableName(environment, baseName);
|
|
665
|
+
async function getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName) {
|
|
666
|
+
const baseName = skipDefaultName ? void 0 : suggestedBaseName || extractAppNameFromImage(imageRef);
|
|
667
|
+
const suggestedName = baseName ? findAvailableName(environment, baseName) : void 0;
|
|
544
668
|
while (true) {
|
|
545
669
|
console.log("\nApp name selection:");
|
|
546
670
|
const name = await input({
|
|
@@ -563,16 +687,16 @@ async function getAvailableAppNameInteractive(environment, imageRef) {
|
|
|
563
687
|
console.log(`Suggested alternative: ${newSuggested}`);
|
|
564
688
|
}
|
|
565
689
|
}
|
|
566
|
-
async function getOrPromptAppName(appName, environment, imageRef) {
|
|
690
|
+
async function getOrPromptAppName(appName, environment, imageRef, suggestedBaseName, skipDefaultName) {
|
|
567
691
|
if (appName) {
|
|
568
692
|
validateAppName(appName);
|
|
569
693
|
if (isAppNameAvailable(environment, appName)) {
|
|
570
694
|
return appName;
|
|
571
695
|
}
|
|
572
696
|
console.log(`Warning: App name '${appName}' is already taken.`);
|
|
573
|
-
return getAvailableAppNameInteractive(environment, imageRef);
|
|
697
|
+
return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
|
|
574
698
|
}
|
|
575
|
-
return getAvailableAppNameInteractive(environment, imageRef);
|
|
699
|
+
return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
|
|
576
700
|
}
|
|
577
701
|
async function getEnvFileInteractive(envFilePath) {
|
|
578
702
|
if (envFilePath && fs3.existsSync(envFilePath)) {
|
|
@@ -710,8 +834,8 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
710
834
|
}
|
|
711
835
|
return privateKey;
|
|
712
836
|
}
|
|
713
|
-
const { getPrivateKeyWithSource } = await import("@layr-labs/ecloud-sdk");
|
|
714
|
-
const result = await
|
|
837
|
+
const { getPrivateKeyWithSource: getPrivateKeyWithSource2 } = await import("@layr-labs/ecloud-sdk");
|
|
838
|
+
const result = await getPrivateKeyWithSource2({ privateKey: void 0 });
|
|
715
839
|
if (result) {
|
|
716
840
|
return result.key;
|
|
717
841
|
}
|
|
@@ -730,6 +854,50 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
730
854
|
});
|
|
731
855
|
return key.trim();
|
|
732
856
|
}
|
|
857
|
+
async function getEnvironmentInteractive(environment) {
|
|
858
|
+
if (environment) {
|
|
859
|
+
try {
|
|
860
|
+
getEnvironmentConfig(environment);
|
|
861
|
+
if (!isEnvironmentAvailable(environment)) {
|
|
862
|
+
throw new Error(`Environment ${environment} is not available in this build`);
|
|
863
|
+
}
|
|
864
|
+
return environment;
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const availableEnvs = getAvailableEnvironments();
|
|
869
|
+
let defaultEnv;
|
|
870
|
+
const configDefaultEnv = getDefaultEnvironment();
|
|
871
|
+
if (configDefaultEnv && availableEnvs.includes(configDefaultEnv)) {
|
|
872
|
+
try {
|
|
873
|
+
getEnvironmentConfig(configDefaultEnv);
|
|
874
|
+
defaultEnv = configDefaultEnv;
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const choices = [];
|
|
879
|
+
if (availableEnvs.includes("sepolia")) {
|
|
880
|
+
choices.push({ name: "sepolia - Ethereum Sepolia testnet", value: "sepolia" });
|
|
881
|
+
}
|
|
882
|
+
if (availableEnvs.includes("sepolia-dev")) {
|
|
883
|
+
choices.push({ name: "sepolia-dev - Ethereum Sepolia testnet (dev)", value: "sepolia-dev" });
|
|
884
|
+
}
|
|
885
|
+
if (availableEnvs.includes("mainnet-alpha")) {
|
|
886
|
+
choices.push({
|
|
887
|
+
name: "mainnet-alpha - Ethereum mainnet (\u26A0\uFE0F uses real funds)",
|
|
888
|
+
value: "mainnet-alpha"
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (choices.length === 0) {
|
|
892
|
+
throw new Error("No environments available in this build");
|
|
893
|
+
}
|
|
894
|
+
const env = await select({
|
|
895
|
+
message: "Select environment:",
|
|
896
|
+
choices,
|
|
897
|
+
default: defaultEnv
|
|
898
|
+
});
|
|
899
|
+
return env;
|
|
900
|
+
}
|
|
733
901
|
var MAX_DESCRIPTION_LENGTH = 1e3;
|
|
734
902
|
var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
735
903
|
var VALID_IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png"];
|
|
@@ -957,7 +1125,8 @@ var commonFlags = {
|
|
|
957
1125
|
environment: Flags.string({
|
|
958
1126
|
required: false,
|
|
959
1127
|
description: "Deployment environment to use",
|
|
960
|
-
env: "ECLOUD_ENV"
|
|
1128
|
+
env: "ECLOUD_ENV",
|
|
1129
|
+
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "sepolia")
|
|
961
1130
|
}),
|
|
962
1131
|
"private-key": Flags.string({
|
|
963
1132
|
required: false,
|
|
@@ -975,9 +1144,227 @@ var commonFlags = {
|
|
|
975
1144
|
default: false
|
|
976
1145
|
})
|
|
977
1146
|
};
|
|
1147
|
+
async function validateCommonFlags(flags, options) {
|
|
1148
|
+
flags["environment"] = await getEnvironmentInteractive(flags["environment"]);
|
|
1149
|
+
if (options?.requirePrivateKey !== false) {
|
|
1150
|
+
flags["private-key"] = await getPrivateKeyInteractive(flags["private-key"]);
|
|
1151
|
+
}
|
|
1152
|
+
return flags;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// src/client.ts
|
|
1156
|
+
import {
|
|
1157
|
+
createComputeModule,
|
|
1158
|
+
createBillingModule,
|
|
1159
|
+
createBuildModule,
|
|
1160
|
+
getEnvironmentConfig as getEnvironmentConfig2,
|
|
1161
|
+
requirePrivateKey,
|
|
1162
|
+
getPrivateKeyWithSource
|
|
1163
|
+
} from "@layr-labs/ecloud-sdk";
|
|
1164
|
+
async function createComputeClient(flags) {
|
|
1165
|
+
flags = await validateCommonFlags(flags);
|
|
1166
|
+
const environment = flags.environment;
|
|
1167
|
+
const environmentConfig = getEnvironmentConfig2(environment);
|
|
1168
|
+
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1169
|
+
const { key: privateKey, source } = await requirePrivateKey({
|
|
1170
|
+
privateKey: flags["private-key"]
|
|
1171
|
+
});
|
|
1172
|
+
if (flags.verbose) {
|
|
1173
|
+
console.log(`Using private key from: ${source}`);
|
|
1174
|
+
}
|
|
1175
|
+
return createComputeModule({
|
|
1176
|
+
verbose: flags.verbose,
|
|
1177
|
+
privateKey,
|
|
1178
|
+
rpcUrl,
|
|
1179
|
+
environment,
|
|
1180
|
+
clientId: getClientId(),
|
|
1181
|
+
skipTelemetry: true
|
|
1182
|
+
// CLI already has telemetry, skip SDK telemetry
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
async function createBuildClient(flags) {
|
|
1186
|
+
flags = await validateCommonFlags(flags, { requirePrivateKey: false });
|
|
1187
|
+
return createBuildModule({
|
|
1188
|
+
verbose: flags.verbose,
|
|
1189
|
+
privateKey: flags["private-key"],
|
|
1190
|
+
environment: flags.environment,
|
|
1191
|
+
clientId: getClientId(),
|
|
1192
|
+
skipTelemetry: true
|
|
1193
|
+
// CLI already has telemetry, skip SDK telemetry
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
978
1196
|
|
|
979
1197
|
// src/commands/compute/app/deploy.ts
|
|
980
1198
|
import chalk from "chalk";
|
|
1199
|
+
|
|
1200
|
+
// src/utils/build.ts
|
|
1201
|
+
function formatSourceLink(repoUrl, gitRef) {
|
|
1202
|
+
const normalizedRepo = repoUrl.replace(/\.git$/, "");
|
|
1203
|
+
try {
|
|
1204
|
+
const url = new URL(normalizedRepo);
|
|
1205
|
+
const host = url.host.toLowerCase();
|
|
1206
|
+
if (host === "github.com") {
|
|
1207
|
+
const path4 = url.pathname.replace(/\/+$/, "");
|
|
1208
|
+
if (path4.split("/").filter(Boolean).length >= 2) {
|
|
1209
|
+
return `https://github.com${path4}/tree/${gitRef}`;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
} catch {
|
|
1213
|
+
}
|
|
1214
|
+
return `${repoUrl}@${gitRef}`;
|
|
1215
|
+
}
|
|
1216
|
+
function extractRepoName(repoUrl) {
|
|
1217
|
+
const normalized = repoUrl.replace(/\.git$/, "");
|
|
1218
|
+
const match = normalized.match(/\/([^/]+?)$/);
|
|
1219
|
+
return match?.[1];
|
|
1220
|
+
}
|
|
1221
|
+
function formatDependencyLines(dependencies) {
|
|
1222
|
+
if (!dependencies || Object.keys(dependencies).length === 0) return [];
|
|
1223
|
+
const lines = [];
|
|
1224
|
+
lines.push("Dependencies (resolved builds):");
|
|
1225
|
+
for (const [digest, dep] of Object.entries(dependencies)) {
|
|
1226
|
+
const name = extractRepoName(dep.repoUrl);
|
|
1227
|
+
const depSource = formatSourceLink(dep.repoUrl, dep.gitRef);
|
|
1228
|
+
lines.push(` - ${digest} \u2713${name ? ` ${name}` : ""}`);
|
|
1229
|
+
lines.push(` ${depSource}`);
|
|
1230
|
+
}
|
|
1231
|
+
return lines;
|
|
1232
|
+
}
|
|
1233
|
+
function formatVerifiableBuildSummary(options) {
|
|
1234
|
+
const lines = [];
|
|
1235
|
+
lines.push("Build completed successfully \u2713");
|
|
1236
|
+
lines.push("");
|
|
1237
|
+
lines.push(`Image: ${options.imageUrl}`);
|
|
1238
|
+
lines.push(`Digest: ${options.imageDigest}`);
|
|
1239
|
+
lines.push(`Source: ${formatSourceLink(options.repoUrl, options.gitRef)}`);
|
|
1240
|
+
const depLines = formatDependencyLines(options.dependencies);
|
|
1241
|
+
if (depLines.length) {
|
|
1242
|
+
lines.push("");
|
|
1243
|
+
lines.push(...depLines);
|
|
1244
|
+
}
|
|
1245
|
+
lines.push("");
|
|
1246
|
+
lines.push("Provenance signature verified \u2713");
|
|
1247
|
+
lines.push(`provenance_signature: ${options.provenanceSignature}`);
|
|
1248
|
+
if (options.buildId) {
|
|
1249
|
+
lines.push("");
|
|
1250
|
+
lines.push(`Build ID: ${options.buildId}`);
|
|
1251
|
+
}
|
|
1252
|
+
return lines;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/utils/verifiableBuild.ts
|
|
1256
|
+
import { BUILD_STATUS } from "@layr-labs/ecloud-sdk";
|
|
1257
|
+
function assertCommitSha40(commit) {
|
|
1258
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) {
|
|
1259
|
+
throw new Error("Commit must be a 40-character hexadecimal SHA");
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
async function runVerifiableBuildAndVerify(client, request, options = {}) {
|
|
1263
|
+
const { buildId } = await client.submit(request);
|
|
1264
|
+
const completed = await client.waitForBuild(buildId, { onLog: options.onLog });
|
|
1265
|
+
if (completed.status !== BUILD_STATUS.SUCCESS) {
|
|
1266
|
+
throw new Error(`Build did not complete successfully (status: ${completed.status})`);
|
|
1267
|
+
}
|
|
1268
|
+
const [build, verify] = await Promise.all([client.get(buildId), client.verify(buildId)]);
|
|
1269
|
+
if (verify.status !== "verified") {
|
|
1270
|
+
throw new Error(`Provenance verification failed: ${verify.error}`);
|
|
1271
|
+
}
|
|
1272
|
+
return { build, verified: verify };
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/utils/dockerhub.ts
|
|
1276
|
+
var DOCKERHUB_OWNER = "eigenlayer";
|
|
1277
|
+
var DOCKERHUB_REPO = "eigencloud-containers";
|
|
1278
|
+
function parseEigencloudContainersImageRef(imageRef) {
|
|
1279
|
+
const trimmed = imageRef.trim();
|
|
1280
|
+
const match = /^docker\.io\/([^/]+)\/([^:@]+):([^@\s]+)$/i.exec(trimmed);
|
|
1281
|
+
if (!match) {
|
|
1282
|
+
throw new Error("Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>");
|
|
1283
|
+
}
|
|
1284
|
+
const owner = match[1].toLowerCase();
|
|
1285
|
+
const repo = match[2].toLowerCase();
|
|
1286
|
+
const tag = match[3];
|
|
1287
|
+
if (owner !== DOCKERHUB_OWNER || repo !== DOCKERHUB_REPO) {
|
|
1288
|
+
throw new Error(`Image ref must be from docker.io/${DOCKERHUB_OWNER}/${DOCKERHUB_REPO}:<tag>`);
|
|
1289
|
+
}
|
|
1290
|
+
if (!tag.trim()) {
|
|
1291
|
+
throw new Error("Image tag cannot be empty");
|
|
1292
|
+
}
|
|
1293
|
+
return { owner, repo, tag };
|
|
1294
|
+
}
|
|
1295
|
+
function assertEigencloudContainersImageRef(imageRef) {
|
|
1296
|
+
parseEigencloudContainersImageRef(imageRef);
|
|
1297
|
+
}
|
|
1298
|
+
async function getDockerHubToken(owner, repo) {
|
|
1299
|
+
const url = new URL("https://auth.docker.io/token");
|
|
1300
|
+
url.searchParams.set("service", "registry.docker.io");
|
|
1301
|
+
url.searchParams.set("scope", `repository:${owner}/${repo}:pull`);
|
|
1302
|
+
const res = await fetch(url.toString(), { method: "GET" });
|
|
1303
|
+
if (!res.ok) {
|
|
1304
|
+
const body = await safeReadText(res);
|
|
1305
|
+
throw new Error(`Failed to fetch Docker Hub token (${res.status}): ${body || res.statusText}`);
|
|
1306
|
+
}
|
|
1307
|
+
const data = await res.json();
|
|
1308
|
+
if (!data.token) {
|
|
1309
|
+
throw new Error("Docker Hub token response missing 'token'");
|
|
1310
|
+
}
|
|
1311
|
+
return data.token;
|
|
1312
|
+
}
|
|
1313
|
+
async function safeReadText(res) {
|
|
1314
|
+
try {
|
|
1315
|
+
return (await res.text()).trim();
|
|
1316
|
+
} catch {
|
|
1317
|
+
return "";
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
async function resolveDockerHubImageDigest(imageRef) {
|
|
1321
|
+
const { owner, repo, tag } = parseEigencloudContainersImageRef(imageRef);
|
|
1322
|
+
const token = await getDockerHubToken(owner, repo);
|
|
1323
|
+
const manifestUrl = `https://registry-1.docker.io/v2/${owner}/${repo}/manifests/${encodeURIComponent(tag)}`;
|
|
1324
|
+
const headers = {
|
|
1325
|
+
Authorization: `Bearer ${token}`,
|
|
1326
|
+
Accept: "application/vnd.docker.distribution.manifest.v2+json"
|
|
1327
|
+
};
|
|
1328
|
+
let res = await fetch(manifestUrl, { method: "HEAD", headers });
|
|
1329
|
+
if (!res.ok) {
|
|
1330
|
+
res = await fetch(manifestUrl, { method: "GET", headers });
|
|
1331
|
+
}
|
|
1332
|
+
if (!res.ok) {
|
|
1333
|
+
const body = await safeReadText(res);
|
|
1334
|
+
throw new Error(
|
|
1335
|
+
`Failed to resolve digest for ${imageRef} (${res.status}) at ${manifestUrl}: ${body || res.statusText}`
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
const digest = res.headers.get("docker-content-digest") || res.headers.get("Docker-Content-Digest");
|
|
1339
|
+
if (!digest) {
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
`Docker registry response missing Docker-Content-Digest header for ${imageRef}`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
|
|
1345
|
+
throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
|
|
1346
|
+
}
|
|
1347
|
+
return digest;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// src/utils/tls.ts
|
|
1351
|
+
import fs4 from "fs";
|
|
1352
|
+
function isTlsEnabledFromDomain(domain) {
|
|
1353
|
+
const d = (domain ?? "").trim();
|
|
1354
|
+
if (!d) return false;
|
|
1355
|
+
if (d.toLowerCase() === "localhost") return false;
|
|
1356
|
+
return true;
|
|
1357
|
+
}
|
|
1358
|
+
function isTlsEnabledFromEnvFile(envFilePath) {
|
|
1359
|
+
if (!envFilePath) return false;
|
|
1360
|
+
if (!fs4.existsSync(envFilePath)) return false;
|
|
1361
|
+
const envContent = fs4.readFileSync(envFilePath, "utf-8");
|
|
1362
|
+
const match = envContent.match(/^DOMAIN=(.+)$/m);
|
|
1363
|
+
if (!match?.[1]) return false;
|
|
1364
|
+
return isTlsEnabledFromDomain(match[1]);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// src/commands/compute/app/deploy.ts
|
|
981
1368
|
var AppDeploy = class _AppDeploy extends Command {
|
|
982
1369
|
static description = "Deploy new app";
|
|
983
1370
|
static flags = {
|
|
@@ -1040,26 +1427,184 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1040
1427
|
image: Flags2.string({
|
|
1041
1428
|
required: false,
|
|
1042
1429
|
description: "Path to app icon/logo image - JPG/PNG, max 4MB, square recommended (optional)"
|
|
1430
|
+
}),
|
|
1431
|
+
// Verifiable build flags
|
|
1432
|
+
verifiable: Flags2.boolean({
|
|
1433
|
+
description: "Enable verifiable build mode (either build from git source via --repo/--commit, or deploy a prebuilt verifiable image via --image-ref)",
|
|
1434
|
+
default: false
|
|
1435
|
+
}),
|
|
1436
|
+
repo: Flags2.string({
|
|
1437
|
+
description: "Git repository URL (required with --verifiable git source mode)",
|
|
1438
|
+
env: "ECLOUD_BUILD_REPO"
|
|
1439
|
+
}),
|
|
1440
|
+
commit: Flags2.string({
|
|
1441
|
+
description: "Git commit SHA (required with --verifiable git source mode)",
|
|
1442
|
+
env: "ECLOUD_BUILD_COMMIT"
|
|
1443
|
+
}),
|
|
1444
|
+
"build-dockerfile": Flags2.string({
|
|
1445
|
+
description: "Dockerfile path for verifiable build (git source mode)",
|
|
1446
|
+
default: "Dockerfile",
|
|
1447
|
+
env: "ECLOUD_BUILD_DOCKERFILE"
|
|
1448
|
+
}),
|
|
1449
|
+
"build-context": Flags2.string({
|
|
1450
|
+
description: "Build context path for verifiable build (git source mode)",
|
|
1451
|
+
default: ".",
|
|
1452
|
+
env: "ECLOUD_BUILD_CONTEXT"
|
|
1453
|
+
}),
|
|
1454
|
+
"build-dependencies": Flags2.string({
|
|
1455
|
+
description: "Dependency digests for verifiable build (git source mode) (sha256:...)",
|
|
1456
|
+
multiple: true
|
|
1043
1457
|
})
|
|
1044
1458
|
};
|
|
1045
1459
|
async run() {
|
|
1046
1460
|
return withTelemetry(this, async () => {
|
|
1047
1461
|
const { flags } = await this.parse(_AppDeploy);
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
error: (msg) => this.error(msg),
|
|
1052
|
-
debug: (msg) => flags.verbose && this.log(msg)
|
|
1053
|
-
};
|
|
1054
|
-
const environment = flags.environment || "sepolia";
|
|
1055
|
-
const environmentConfig = getEnvironmentConfig2(environment);
|
|
1462
|
+
const compute = await createComputeClient(flags);
|
|
1463
|
+
const environment = flags.environment;
|
|
1464
|
+
const environmentConfig = getEnvironmentConfig3(environment);
|
|
1056
1465
|
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1057
|
-
const privateKey =
|
|
1058
|
-
|
|
1466
|
+
const privateKey = flags["private-key"];
|
|
1467
|
+
let buildClient;
|
|
1468
|
+
const getBuildClient = async () => {
|
|
1469
|
+
if (buildClient) return buildClient;
|
|
1470
|
+
buildClient = await createBuildClient({
|
|
1471
|
+
...flags,
|
|
1472
|
+
"private-key": privateKey
|
|
1473
|
+
});
|
|
1474
|
+
return buildClient;
|
|
1475
|
+
};
|
|
1476
|
+
let verifiableImageUrl;
|
|
1477
|
+
let verifiableImageDigest;
|
|
1478
|
+
let suggestedAppBaseName;
|
|
1479
|
+
let skipDefaultAppName = false;
|
|
1480
|
+
let verifiableMode = "none";
|
|
1481
|
+
let envFilePath;
|
|
1482
|
+
const suggestAppBaseNameFromRepoUrl = (repoUrl) => {
|
|
1483
|
+
const normalized = String(repoUrl || "").trim().replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
1484
|
+
if (!normalized) return void 0;
|
|
1485
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
1486
|
+
const lastColon = normalized.lastIndexOf(":");
|
|
1487
|
+
const idx = Math.max(lastSlash, lastColon);
|
|
1488
|
+
const raw = (idx >= 0 ? normalized.slice(idx + 1) : normalized).trim();
|
|
1489
|
+
if (!raw) return void 0;
|
|
1490
|
+
const cleaned = raw.toLowerCase().replace(/_/g, "-").replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
1491
|
+
return cleaned || void 0;
|
|
1492
|
+
};
|
|
1493
|
+
if (flags.verifiable) {
|
|
1494
|
+
if (flags.repo || flags.commit) {
|
|
1495
|
+
verifiableMode = "git";
|
|
1496
|
+
if (!flags.repo)
|
|
1497
|
+
this.error("--repo is required when using --verifiable (git source mode)");
|
|
1498
|
+
if (!flags.commit)
|
|
1499
|
+
this.error("--commit is required when using --verifiable (git source mode)");
|
|
1500
|
+
try {
|
|
1501
|
+
assertCommitSha40(flags.commit);
|
|
1502
|
+
} catch (e) {
|
|
1503
|
+
this.error(e?.message || String(e));
|
|
1504
|
+
}
|
|
1505
|
+
} else if (flags["image-ref"]) {
|
|
1506
|
+
verifiableMode = "prebuilt";
|
|
1507
|
+
try {
|
|
1508
|
+
assertEigencloudContainersImageRef(flags["image-ref"]);
|
|
1509
|
+
} catch (e) {
|
|
1510
|
+
this.error(e?.message || String(e));
|
|
1511
|
+
}
|
|
1512
|
+
} else {
|
|
1513
|
+
this.error(
|
|
1514
|
+
"When using --verifiable, you must provide either --repo/--commit or --image-ref"
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
} else {
|
|
1518
|
+
if (!flags.dockerfile) {
|
|
1519
|
+
const useVerifiable = await promptUseVerifiableBuild();
|
|
1520
|
+
if (useVerifiable) {
|
|
1521
|
+
const sourceType = await promptVerifiableSourceType();
|
|
1522
|
+
verifiableMode = sourceType;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (verifiableMode === "git") {
|
|
1527
|
+
const inputs = flags.verifiable ? {
|
|
1528
|
+
repoUrl: flags.repo,
|
|
1529
|
+
gitRef: flags.commit,
|
|
1530
|
+
dockerfilePath: flags["build-dockerfile"],
|
|
1531
|
+
caddyfilePath: void 0,
|
|
1532
|
+
buildContextPath: flags["build-context"],
|
|
1533
|
+
dependencies: flags["build-dependencies"]
|
|
1534
|
+
} : await promptVerifiableGitSourceInputs();
|
|
1535
|
+
envFilePath = await getEnvFileInteractive(flags["env-file"]);
|
|
1536
|
+
const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
|
|
1537
|
+
if (includeTlsCaddyfile && !inputs.caddyfilePath) {
|
|
1538
|
+
inputs.caddyfilePath = "Caddyfile";
|
|
1539
|
+
}
|
|
1540
|
+
this.log(chalk.blue("Building from source with verifiable build..."));
|
|
1541
|
+
this.log("");
|
|
1542
|
+
const buildClient2 = await getBuildClient();
|
|
1543
|
+
const { build, verified } = await runVerifiableBuildAndVerify(buildClient2, inputs, {
|
|
1544
|
+
onLog: (chunk) => process.stdout.write(chunk)
|
|
1545
|
+
});
|
|
1546
|
+
if (!build.imageUrl || !build.imageDigest) {
|
|
1547
|
+
this.error(
|
|
1548
|
+
"Build completed but did not return imageUrl/imageDigest; cannot deploy verifiable build"
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
verifiableImageUrl = build.imageUrl;
|
|
1552
|
+
verifiableImageDigest = build.imageDigest;
|
|
1553
|
+
suggestedAppBaseName = suggestAppBaseNameFromRepoUrl(build.repoUrl);
|
|
1554
|
+
for (const line of formatVerifiableBuildSummary({
|
|
1555
|
+
buildId: build.buildId,
|
|
1556
|
+
imageUrl: build.imageUrl,
|
|
1557
|
+
imageDigest: build.imageDigest,
|
|
1558
|
+
repoUrl: build.repoUrl,
|
|
1559
|
+
gitRef: build.gitRef,
|
|
1560
|
+
dependencies: build.dependencies,
|
|
1561
|
+
provenanceSignature: verified.provenanceSignature
|
|
1562
|
+
})) {
|
|
1563
|
+
this.log(line);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (verifiableMode === "prebuilt") {
|
|
1567
|
+
const imageRef2 = flags.verifiable ? flags["image-ref"] : await promptVerifiablePrebuiltImageRef();
|
|
1568
|
+
try {
|
|
1569
|
+
assertEigencloudContainersImageRef(imageRef2);
|
|
1570
|
+
} catch (e) {
|
|
1571
|
+
this.error(e?.message || String(e));
|
|
1572
|
+
}
|
|
1573
|
+
this.log(chalk.blue("Resolving and verifying prebuilt verifiable image..."));
|
|
1574
|
+
this.log("");
|
|
1575
|
+
const digest = await resolveDockerHubImageDigest(imageRef2);
|
|
1576
|
+
const buildClient2 = await getBuildClient();
|
|
1577
|
+
const verify = await buildClient2.verify(digest);
|
|
1578
|
+
if (verify.status !== "verified") {
|
|
1579
|
+
this.error(`Provenance verification failed: ${verify.error}`);
|
|
1580
|
+
}
|
|
1581
|
+
verifiableImageUrl = imageRef2;
|
|
1582
|
+
verifiableImageDigest = digest;
|
|
1583
|
+
skipDefaultAppName = true;
|
|
1584
|
+
for (const line of formatVerifiableBuildSummary({
|
|
1585
|
+
buildId: verify.buildId,
|
|
1586
|
+
imageUrl: imageRef2,
|
|
1587
|
+
imageDigest: digest,
|
|
1588
|
+
repoUrl: verify.repoUrl,
|
|
1589
|
+
gitRef: verify.gitRef,
|
|
1590
|
+
dependencies: void 0,
|
|
1591
|
+
provenanceSignature: verify.provenanceSignature
|
|
1592
|
+
})) {
|
|
1593
|
+
this.log(line);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
const isVerifiable = verifiableMode !== "none";
|
|
1597
|
+
const dockerfilePath = isVerifiable ? "" : await getDockerfileInteractive(flags.dockerfile);
|
|
1059
1598
|
const buildFromDockerfile = dockerfilePath !== "";
|
|
1060
|
-
const imageRef = await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1061
|
-
const appName = await getOrPromptAppName(
|
|
1062
|
-
|
|
1599
|
+
const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1600
|
+
const appName = await getOrPromptAppName(
|
|
1601
|
+
flags.name,
|
|
1602
|
+
environment,
|
|
1603
|
+
imageRef,
|
|
1604
|
+
suggestedAppBaseName,
|
|
1605
|
+
skipDefaultAppName
|
|
1606
|
+
);
|
|
1607
|
+
envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
|
|
1063
1608
|
const availableTypes = await fetchAvailableInstanceTypes(
|
|
1064
1609
|
environmentConfig,
|
|
1065
1610
|
privateKey,
|
|
@@ -1078,22 +1623,23 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1078
1623
|
flags["resource-usage-monitoring"]
|
|
1079
1624
|
);
|
|
1080
1625
|
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1081
|
-
const { prepared, gasEstimate } = await
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1626
|
+
const { prepared, gasEstimate } = isVerifiable ? await compute.app.prepareDeployFromVerifiableBuild({
|
|
1627
|
+
name: appName,
|
|
1628
|
+
imageRef,
|
|
1629
|
+
imageDigest: verifiableImageDigest,
|
|
1630
|
+
envFile: envFilePath,
|
|
1631
|
+
instanceType,
|
|
1632
|
+
logVisibility,
|
|
1633
|
+
resourceUsageMonitoring
|
|
1634
|
+
}) : await compute.app.prepareDeploy({
|
|
1635
|
+
name: appName,
|
|
1636
|
+
dockerfile: dockerfilePath,
|
|
1637
|
+
imageRef,
|
|
1638
|
+
envFile: envFilePath,
|
|
1639
|
+
instanceType,
|
|
1640
|
+
logVisibility,
|
|
1641
|
+
resourceUsageMonitoring
|
|
1642
|
+
});
|
|
1097
1643
|
this.log(`
|
|
1098
1644
|
Estimated transaction cost: ${chalk.cyan(gasEstimate.maxCostEth)} ETH`);
|
|
1099
1645
|
if (isMainnet(environmentConfig)) {
|
|
@@ -1104,16 +1650,10 @@ ${chalk.gray(`Deployment cancelled`)}`);
|
|
|
1104
1650
|
return;
|
|
1105
1651
|
}
|
|
1106
1652
|
}
|
|
1107
|
-
const res = await executeDeploy(
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
maxPriorityFeePerGas: gasEstimate.maxPriorityFeePerGas
|
|
1112
|
-
},
|
|
1113
|
-
logger,
|
|
1114
|
-
true
|
|
1115
|
-
// skipTelemetry
|
|
1116
|
-
);
|
|
1653
|
+
const res = await compute.app.executeDeploy(prepared, {
|
|
1654
|
+
maxFeePerGas: gasEstimate.maxFeePerGas,
|
|
1655
|
+
maxPriorityFeePerGas: gasEstimate.maxPriorityFeePerGas
|
|
1656
|
+
});
|
|
1117
1657
|
if (!flags["skip-profile"]) {
|
|
1118
1658
|
const hasProfileFlags = flags.website || flags.description || flags["x-url"] || flags.image;
|
|
1119
1659
|
let profile = null;
|
|
@@ -1132,47 +1672,35 @@ ${chalk.gray(`Deployment cancelled`)}`);
|
|
|
1132
1672
|
try {
|
|
1133
1673
|
profile = await getAppProfileInteractive(appName, true) || null;
|
|
1134
1674
|
} catch {
|
|
1135
|
-
|
|
1675
|
+
if (flags.verbose) {
|
|
1676
|
+
this.log("Profile collection skipped or cancelled");
|
|
1677
|
+
}
|
|
1136
1678
|
}
|
|
1137
1679
|
}
|
|
1138
1680
|
if (profile) {
|
|
1139
|
-
|
|
1681
|
+
this.log("Uploading app profile...");
|
|
1140
1682
|
try {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
privateKey,
|
|
1144
|
-
rpcUrl,
|
|
1145
|
-
getClientId()
|
|
1146
|
-
);
|
|
1147
|
-
await userApiClient.uploadAppProfile(
|
|
1148
|
-
res.appId,
|
|
1149
|
-
profile.name,
|
|
1150
|
-
profile.website,
|
|
1151
|
-
profile.description,
|
|
1152
|
-
profile.xURL,
|
|
1153
|
-
profile.imagePath
|
|
1154
|
-
);
|
|
1155
|
-
logger.info("\u2713 Profile uploaded successfully");
|
|
1683
|
+
await compute.app.setProfile(res.appId, profile);
|
|
1684
|
+
this.log("\u2713 Profile uploaded successfully");
|
|
1156
1685
|
try {
|
|
1157
1686
|
invalidateProfileCache(environment);
|
|
1158
1687
|
} catch (cacheErr) {
|
|
1159
|
-
|
|
1688
|
+
if (flags.verbose) {
|
|
1689
|
+
this.log(`Failed to invalidate profile cache: ${cacheErr.message}`);
|
|
1690
|
+
}
|
|
1160
1691
|
}
|
|
1161
1692
|
} catch (uploadErr) {
|
|
1162
|
-
|
|
1693
|
+
this.warn(`Failed to upload profile: ${uploadErr.message}`);
|
|
1163
1694
|
}
|
|
1164
1695
|
}
|
|
1165
1696
|
}
|
|
1166
|
-
const ipAddress = await watchDeployment(
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
true
|
|
1174
|
-
// skipTelemetry - CLI already has telemetry
|
|
1175
|
-
);
|
|
1697
|
+
const ipAddress = await compute.app.watchDeployment(res.appId);
|
|
1698
|
+
try {
|
|
1699
|
+
const cwd = process.env.INIT_CWD || process.cwd();
|
|
1700
|
+
setLinkedAppForDirectory(environment, cwd, res.appId);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
this.debug(`Failed to link directory to app: ${err.message}`);
|
|
1703
|
+
}
|
|
1176
1704
|
this.log(
|
|
1177
1705
|
`
|
|
1178
1706
|
\u2705 ${chalk.green(`App deployed successfully ${chalk.bold(`(id: ${res.appId}, ip: ${ipAddress})`)}`)}`
|