@layr-labs/ecloud-cli 1.0.0-dev.6 → 1.0.0-dev.8
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/admin/admins/add.js +7 -1
- package/dist/commands/admin/admins/add.js.map +1 -1
- package/dist/commands/admin/admins/list.js +7 -1
- package/dist/commands/admin/admins/list.js.map +1 -1
- package/dist/commands/admin/admins/remove.js +7 -1
- package/dist/commands/admin/admins/remove.js.map +1 -1
- package/dist/commands/admin/coupons/create.js +7 -1
- package/dist/commands/admin/coupons/create.js.map +1 -1
- package/dist/commands/admin/coupons/deactivate.js +7 -1
- package/dist/commands/admin/coupons/deactivate.js.map +1 -1
- package/dist/commands/admin/coupons/get.js +7 -1
- package/dist/commands/admin/coupons/get.js.map +1 -1
- package/dist/commands/admin/coupons/list.js +7 -1
- package/dist/commands/admin/coupons/list.js.map +1 -1
- package/dist/commands/admin/coupons/redeem.js +7 -1
- package/dist/commands/admin/coupons/redeem.js.map +1 -1
- package/dist/commands/auth/whoami.js +7 -1
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/__tests__/status.test.js +79 -3
- package/dist/commands/billing/__tests__/status.test.js.map +1 -1
- package/dist/commands/billing/__tests__/subscribe.test.js +7 -1
- package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
- package/dist/commands/billing/__tests__/top-up.test.js +473 -6
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
- package/dist/commands/billing/cancel.js +8 -3
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/list-cards.js +7 -1
- package/dist/commands/billing/list-cards.js.map +1 -1
- package/dist/commands/billing/redeem-coupon.js +7 -1
- package/dist/commands/billing/redeem-coupon.js.map +1 -1
- package/dist/commands/billing/status.js +33 -1
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +7 -1
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/billing/top-up.js +369 -7
- package/dist/commands/billing/top-up.js.map +1 -1
- package/dist/commands/compute/app/__tests__/status.test.js +1017 -0
- package/dist/commands/compute/app/__tests__/status.test.js.map +1 -0
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +300 -68
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +8 -2
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +8 -2
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +11 -4
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +11 -4
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +8 -2
- package/dist/commands/compute/app/releases.js.map +1 -1
- package/dist/commands/compute/app/start.js +15 -8
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/status.js +845 -0
- package/dist/commands/compute/app/status.js.map +1 -0
- package/dist/commands/compute/app/stop.js +15 -8
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +15 -8
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +297 -67
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +8 -2
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +8 -2
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +8 -2
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +8 -2
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +8 -2
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +8 -2
- package/dist/commands/compute/build/verify.js.map +1 -1
- package/dist/commands/compute/environment/set.js.map +1 -1
- package/dist/commands/compute/undelegate.js +11 -4
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/dist/hooks/init/__tests__/version-check.test.js +32 -2
- package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
- package/dist/hooks/init/version-check.js +6 -1
- package/dist/hooks/init/version-check.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/compute/app/upgrade.ts
|
|
4
4
|
import { Command, Args, Flags as Flags2 } from "@oclif/core";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getEnvironmentConfig as getEnvironmentConfig4,
|
|
7
|
+
UserApiClient as UserApiClient4,
|
|
8
|
+
isMainnet,
|
|
9
|
+
WatchTimeoutError
|
|
10
|
+
} from "@layr-labs/ecloud-sdk";
|
|
6
11
|
|
|
7
12
|
// src/telemetry.ts
|
|
8
13
|
import {
|
|
@@ -323,7 +328,7 @@ function listApps(environment) {
|
|
|
323
328
|
|
|
324
329
|
// src/utils/version.ts
|
|
325
330
|
function getCliVersion() {
|
|
326
|
-
return true ? "1.0.0-dev.
|
|
331
|
+
return true ? "1.0.0-dev.8" : "0.0.0";
|
|
327
332
|
}
|
|
328
333
|
function getClientId() {
|
|
329
334
|
return `ecloud-cli/v${getCliVersion()}`;
|
|
@@ -360,7 +365,30 @@ function ensureInteractive(missingFlagHint) {
|
|
|
360
365
|
);
|
|
361
366
|
}
|
|
362
367
|
}
|
|
363
|
-
|
|
368
|
+
function isNonInteractive(flags) {
|
|
369
|
+
if (flags?.["non-interactive"]) return true;
|
|
370
|
+
if (process.env.CI === "true") return true;
|
|
371
|
+
return !process.stdin.isTTY;
|
|
372
|
+
}
|
|
373
|
+
function warnDefaulted(flagHint, chosen) {
|
|
374
|
+
console.warn(`Warning: ${flagHint} not set in non-interactive mode; defaulting to ${chosen}.`);
|
|
375
|
+
}
|
|
376
|
+
function collectMissingRequiredInputs(state, identityFlag) {
|
|
377
|
+
const missing = [];
|
|
378
|
+
const hasImageSource = !!state.imageRef || !!state.dockerfile || !!state.verifiable && !!state.repo && !!state.commit;
|
|
379
|
+
if (!hasImageSource) {
|
|
380
|
+
missing.push(
|
|
381
|
+
"an image source (one of: --image-ref, --dockerfile, or --verifiable with --repo and --commit)"
|
|
382
|
+
);
|
|
383
|
+
} else if (state.dockerfile && !state.imageRef && !state.verifiable) {
|
|
384
|
+
missing.push("--image-ref (registry destination for the built image)");
|
|
385
|
+
}
|
|
386
|
+
if (identityFlag === "name" && !state.name) {
|
|
387
|
+
missing.push("--name");
|
|
388
|
+
}
|
|
389
|
+
return missing;
|
|
390
|
+
}
|
|
391
|
+
async function getDockerfile(dockerfilePath, nonInteractive = false) {
|
|
364
392
|
if (dockerfilePath) {
|
|
365
393
|
return dockerfilePath;
|
|
366
394
|
}
|
|
@@ -369,6 +397,10 @@ async function getDockerfileInteractive(dockerfilePath) {
|
|
|
369
397
|
if (!fs3.existsSync(dockerfilePath_resolved)) {
|
|
370
398
|
return "";
|
|
371
399
|
}
|
|
400
|
+
if (nonInteractive) {
|
|
401
|
+
warnDefaulted("--dockerfile/--image-ref", `build from '${dockerfilePath_resolved}'`);
|
|
402
|
+
return dockerfilePath_resolved;
|
|
403
|
+
}
|
|
372
404
|
ensureInteractive("--dockerfile or --image-ref");
|
|
373
405
|
console.log(`
|
|
374
406
|
Found Dockerfile in ${cwd}`);
|
|
@@ -786,13 +818,17 @@ async function getImageReferenceInteractive(imageRef, buildFromDockerfile = fals
|
|
|
786
818
|
});
|
|
787
819
|
return imageRefInput;
|
|
788
820
|
}
|
|
789
|
-
async function
|
|
821
|
+
async function getEnvFile(envFilePath, nonInteractive = false) {
|
|
790
822
|
if (envFilePath && fs3.existsSync(envFilePath)) {
|
|
791
823
|
return envFilePath;
|
|
792
824
|
}
|
|
793
825
|
if (fs3.existsSync(".env")) {
|
|
794
826
|
return ".env";
|
|
795
827
|
}
|
|
828
|
+
if (nonInteractive) {
|
|
829
|
+
warnDefaulted("--env-file", "no env file");
|
|
830
|
+
return "";
|
|
831
|
+
}
|
|
796
832
|
ensureInteractive("--env-file");
|
|
797
833
|
console.log("\nEnvironment file not found.");
|
|
798
834
|
console.log("Environment files contain variables like RPC_URL, etc.");
|
|
@@ -820,6 +856,7 @@ async function getEnvFileInteractive(envFilePath) {
|
|
|
820
856
|
throw new Error(`Unexpected choice: ${choice}`);
|
|
821
857
|
}
|
|
822
858
|
}
|
|
859
|
+
var DEFAULT_NONINTERACTIVE_SKU = "g1-standard-2s";
|
|
823
860
|
function formatSkuChoice(it) {
|
|
824
861
|
if (it.vcpus != null && it.memory_mb != null && it.monthly_price_usd != null && it.hourly_price_usd != null) {
|
|
825
862
|
const tier = it.friendly_name ?? it.sku;
|
|
@@ -836,7 +873,7 @@ function formatSkuChoice(it) {
|
|
|
836
873
|
}
|
|
837
874
|
return `${it.sku} - ${it.description}`;
|
|
838
875
|
}
|
|
839
|
-
async function
|
|
876
|
+
async function getInstanceType(instanceType, defaultSKU, availableTypes, nonInteractive = false) {
|
|
840
877
|
if (instanceType) {
|
|
841
878
|
const valid = availableTypes.find((t) => t.sku === instanceType);
|
|
842
879
|
if (valid) {
|
|
@@ -845,6 +882,20 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
|
|
|
845
882
|
const validSKUs = availableTypes.map((t) => t.sku).join(", ");
|
|
846
883
|
throw new Error(`Invalid instance-type: ${instanceType} (must be one of: ${validSKUs})`);
|
|
847
884
|
}
|
|
885
|
+
if (nonInteractive) {
|
|
886
|
+
if (defaultSKU) {
|
|
887
|
+
warnDefaulted("--instance-type", `current type '${defaultSKU}'`);
|
|
888
|
+
return defaultSKU;
|
|
889
|
+
}
|
|
890
|
+
if (availableTypes.some((t) => t.sku === DEFAULT_NONINTERACTIVE_SKU)) {
|
|
891
|
+
warnDefaulted("--instance-type", `'${DEFAULT_NONINTERACTIVE_SKU}'`);
|
|
892
|
+
return DEFAULT_NONINTERACTIVE_SKU;
|
|
893
|
+
}
|
|
894
|
+
const validSKUs = availableTypes.map((t) => t.sku).join(", ");
|
|
895
|
+
throw new Error(
|
|
896
|
+
`Cannot pick a default --instance-type in non-interactive mode: '${DEFAULT_NONINTERACTIVE_SKU}' not offered (available: ${validSKUs}). Provide --instance-type.`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
848
899
|
ensureInteractive("--instance-type");
|
|
849
900
|
const isCurrentType = defaultSKU !== "";
|
|
850
901
|
const hasPricing = availableTypes.some((t) => t.monthly_price_usd != null);
|
|
@@ -876,7 +927,7 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
|
|
|
876
927
|
});
|
|
877
928
|
return choice;
|
|
878
929
|
}
|
|
879
|
-
async function
|
|
930
|
+
async function getLogSettings(logVisibility, nonInteractive = false) {
|
|
880
931
|
if (logVisibility) {
|
|
881
932
|
switch (logVisibility) {
|
|
882
933
|
case "public":
|
|
@@ -891,6 +942,10 @@ async function getLogSettingsInteractive(logVisibility) {
|
|
|
891
942
|
);
|
|
892
943
|
}
|
|
893
944
|
}
|
|
945
|
+
if (nonInteractive) {
|
|
946
|
+
warnDefaulted("--log-visibility", "'private'");
|
|
947
|
+
return { logRedirect: "always", publicLogs: false };
|
|
948
|
+
}
|
|
894
949
|
ensureInteractive("--log-visibility");
|
|
895
950
|
const choice = await select({
|
|
896
951
|
message: "Do you want to view your app's logs?",
|
|
@@ -1211,7 +1266,7 @@ Select an app to ${action}:`);
|
|
|
1211
1266
|
}
|
|
1212
1267
|
return addHexPrefix2(selected);
|
|
1213
1268
|
}
|
|
1214
|
-
async function
|
|
1269
|
+
async function getResourceUsageMonitoring(resourceUsageMonitoring, nonInteractive = false) {
|
|
1215
1270
|
if (resourceUsageMonitoring) {
|
|
1216
1271
|
switch (resourceUsageMonitoring) {
|
|
1217
1272
|
case "enable":
|
|
@@ -1223,6 +1278,10 @@ async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
|
|
|
1223
1278
|
);
|
|
1224
1279
|
}
|
|
1225
1280
|
}
|
|
1281
|
+
if (nonInteractive) {
|
|
1282
|
+
warnDefaulted("--resource-usage-monitoring", "'disable'");
|
|
1283
|
+
return "disable";
|
|
1284
|
+
}
|
|
1226
1285
|
ensureInteractive("--resource-usage-monitoring");
|
|
1227
1286
|
const choice = await select({
|
|
1228
1287
|
message: "Show resource usage (CPU/memory) for your app?",
|
|
@@ -1238,9 +1297,7 @@ async function confirm(prompt) {
|
|
|
1238
1297
|
}
|
|
1239
1298
|
async function confirmWithDefault(prompt, defaultValue = false) {
|
|
1240
1299
|
if (!process.stdin.isTTY) {
|
|
1241
|
-
|
|
1242
|
-
`Cannot confirm "${prompt}" in non-interactive mode. Use --force to skip confirmation prompts.`
|
|
1243
|
-
);
|
|
1300
|
+
return defaultValue;
|
|
1244
1301
|
}
|
|
1245
1302
|
return await inquirerConfirm({
|
|
1246
1303
|
message: prompt,
|
|
@@ -1323,7 +1380,7 @@ var commonFlags = {
|
|
|
1323
1380
|
required: false,
|
|
1324
1381
|
description: "Deployment environment to use",
|
|
1325
1382
|
env: "ECLOUD_ENV",
|
|
1326
|
-
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "
|
|
1383
|
+
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "mainnet-alpha")
|
|
1327
1384
|
}),
|
|
1328
1385
|
"private-key": Flags.string({
|
|
1329
1386
|
required: false,
|
|
@@ -1353,6 +1410,12 @@ var commonFlags = {
|
|
|
1353
1410
|
nonce: Flags.string({
|
|
1354
1411
|
required: false,
|
|
1355
1412
|
description: 'Override transaction nonce (integer or "latest" to replace a stuck transaction)'
|
|
1413
|
+
}),
|
|
1414
|
+
"non-interactive": Flags.boolean({
|
|
1415
|
+
required: false,
|
|
1416
|
+
description: "Assume non-interactive mode: default safe prompts and error all-at-once on missing required inputs",
|
|
1417
|
+
env: "ECLOUD_NON_INTERACTIVE",
|
|
1418
|
+
default: false
|
|
1356
1419
|
})
|
|
1357
1420
|
};
|
|
1358
1421
|
async function applyTxOverrides(estimate, flags, opts) {
|
|
@@ -1386,7 +1449,9 @@ async function applyTxOverrides(estimate, flags, opts) {
|
|
|
1386
1449
|
} else {
|
|
1387
1450
|
const parsed = Number(nonceStr);
|
|
1388
1451
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
1389
|
-
throw new Error(
|
|
1452
|
+
throw new Error(
|
|
1453
|
+
`Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`
|
|
1454
|
+
);
|
|
1390
1455
|
}
|
|
1391
1456
|
nonce = parsed;
|
|
1392
1457
|
}
|
|
@@ -1410,7 +1475,7 @@ import {
|
|
|
1410
1475
|
getEnvironmentConfig as getEnvironmentConfig3,
|
|
1411
1476
|
requirePrivateKey
|
|
1412
1477
|
} from "@layr-labs/ecloud-sdk";
|
|
1413
|
-
async function createComputeClient(flags) {
|
|
1478
|
+
async function createComputeClient(flags, options = {}) {
|
|
1414
1479
|
flags = await validateCommonFlags(flags);
|
|
1415
1480
|
const environment = flags.environment;
|
|
1416
1481
|
const environmentConfig = getEnvironmentConfig3(environment);
|
|
@@ -1432,8 +1497,9 @@ async function createComputeClient(flags) {
|
|
|
1432
1497
|
publicClient,
|
|
1433
1498
|
environment,
|
|
1434
1499
|
clientId: getClientId(),
|
|
1435
|
-
skipTelemetry: true
|
|
1500
|
+
skipTelemetry: true,
|
|
1436
1501
|
// CLI already has telemetry, skip SDK telemetry
|
|
1502
|
+
logger: options.logger
|
|
1437
1503
|
});
|
|
1438
1504
|
}
|
|
1439
1505
|
async function createBuildClient(flags) {
|
|
@@ -1459,6 +1525,29 @@ async function createBuildClient(flags) {
|
|
|
1459
1525
|
});
|
|
1460
1526
|
}
|
|
1461
1527
|
|
|
1528
|
+
// src/utils/instanceTypes.ts
|
|
1529
|
+
import { UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
|
|
1530
|
+
async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
|
|
1531
|
+
try {
|
|
1532
|
+
const { publicClient, walletClient } = createViemClients({
|
|
1533
|
+
privateKey,
|
|
1534
|
+
rpcUrl,
|
|
1535
|
+
environment
|
|
1536
|
+
});
|
|
1537
|
+
const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
|
|
1538
|
+
clientId: getClientId()
|
|
1539
|
+
});
|
|
1540
|
+
const skuList = await userApiClient.getSKUs();
|
|
1541
|
+
if (skuList.skus.length === 0) {
|
|
1542
|
+
throw new Error("No instance types available from server");
|
|
1543
|
+
}
|
|
1544
|
+
return skuList.skus;
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
1547
|
+
return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1462
1551
|
// src/commands/compute/app/upgrade.ts
|
|
1463
1552
|
import chalk2 from "chalk";
|
|
1464
1553
|
|
|
@@ -1620,8 +1709,63 @@ async function resolveDockerHubImageDigest(imageRef) {
|
|
|
1620
1709
|
if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
|
|
1621
1710
|
throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
|
|
1622
1711
|
}
|
|
1712
|
+
await assertImageHasAmd64(owner, repo, tag, token, imageRef);
|
|
1623
1713
|
return digest;
|
|
1624
1714
|
}
|
|
1715
|
+
var AMD64_ACCEPT = [
|
|
1716
|
+
"application/vnd.oci.image.index.v1+json",
|
|
1717
|
+
"application/vnd.docker.distribution.manifest.list.v2+json",
|
|
1718
|
+
"application/vnd.oci.image.manifest.v1+json",
|
|
1719
|
+
"application/vnd.docker.distribution.manifest.v2+json"
|
|
1720
|
+
].join(", ");
|
|
1721
|
+
async function assertImageHasAmd64(owner, repo, tag, token, imageRef) {
|
|
1722
|
+
const base = `https://registry-1.docker.io/v2/${owner}/${repo}`;
|
|
1723
|
+
const headers = { Authorization: `Bearer ${token}`, Accept: AMD64_ACCEPT };
|
|
1724
|
+
const res = await fetch(`${base}/manifests/${encodeURIComponent(tag)}`, { headers });
|
|
1725
|
+
if (!res.ok) {
|
|
1726
|
+
const body = await safeReadText(res);
|
|
1727
|
+
throw new Error(
|
|
1728
|
+
`Failed to read manifest for ${imageRef} (${res.status}): ${body || res.statusText}`
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
const manifest = await res.json();
|
|
1732
|
+
const isAmd64 = (os5, arch) => os5 === "linux" && arch === "amd64";
|
|
1733
|
+
if (Array.isArray(manifest.manifests) && manifest.manifests.length > 0) {
|
|
1734
|
+
const platforms = manifest.manifests.map(
|
|
1735
|
+
(m) => m.platform ? `${m.platform.os}/${m.platform.architecture}` : "unknown"
|
|
1736
|
+
);
|
|
1737
|
+
if (manifest.manifests.some((m) => isAmd64(m.platform?.os, m.platform?.architecture))) {
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
throw amd64Error(imageRef, platforms);
|
|
1741
|
+
}
|
|
1742
|
+
const configDigest = manifest.config?.digest;
|
|
1743
|
+
if (!configDigest) {
|
|
1744
|
+
throw amd64Error(imageRef, ["unknown (no platform info in manifest)"]);
|
|
1745
|
+
}
|
|
1746
|
+
const cfgRes = await fetch(`${base}/blobs/${configDigest}`, {
|
|
1747
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1748
|
+
});
|
|
1749
|
+
if (!cfgRes.ok) {
|
|
1750
|
+
throw amd64Error(imageRef, ["unknown (could not read image config)"]);
|
|
1751
|
+
}
|
|
1752
|
+
const cfg = await cfgRes.json();
|
|
1753
|
+
if (isAmd64(cfg.os, cfg.architecture)) {
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
throw amd64Error(imageRef, [`${cfg.os ?? "unknown"}/${cfg.architecture ?? "unknown"}`]);
|
|
1757
|
+
}
|
|
1758
|
+
function amd64Error(imageRef, platforms) {
|
|
1759
|
+
return new Error(
|
|
1760
|
+
`ecloud requires linux/amd64 images for TEE deployment.
|
|
1761
|
+
|
|
1762
|
+
Image: ${imageRef}
|
|
1763
|
+
Found platform(s): ${platforms.join(", ")}
|
|
1764
|
+
Required platform: linux/amd64
|
|
1765
|
+
|
|
1766
|
+
To fix: rebuild for linux/amd64 (e.g. docker buildx build --platform linux/amd64 ... --push), or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which builds server-side.`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1625
1769
|
|
|
1626
1770
|
// src/utils/tls.ts
|
|
1627
1771
|
import fs4 from "fs";
|
|
@@ -1686,12 +1830,45 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
|
|
|
1686
1830
|
return tmpFile;
|
|
1687
1831
|
}
|
|
1688
1832
|
|
|
1833
|
+
// src/utils/exitCodes.ts
|
|
1834
|
+
import { InsufficientGasError } from "@layr-labs/ecloud-sdk";
|
|
1835
|
+
var EXIT_CODES = {
|
|
1836
|
+
INVALID_INPUT: 2,
|
|
1837
|
+
BUILD_FAILED: 3,
|
|
1838
|
+
ONCHAIN_FAILED: 4
|
|
1839
|
+
};
|
|
1840
|
+
function errorMessage(err) {
|
|
1841
|
+
if (err instanceof Error) return err.message;
|
|
1842
|
+
return String(err);
|
|
1843
|
+
}
|
|
1844
|
+
function stageFailure(operation, stage, err) {
|
|
1845
|
+
const noun = operation === "deploy" ? "deployment" : "upgrade";
|
|
1846
|
+
if (err instanceof InsufficientGasError) {
|
|
1847
|
+
stage = "onchain";
|
|
1848
|
+
}
|
|
1849
|
+
switch (stage) {
|
|
1850
|
+
case "invalid-input":
|
|
1851
|
+
return { message: errorMessage(err), exit: EXIT_CODES.INVALID_INPUT };
|
|
1852
|
+
case "build":
|
|
1853
|
+
return {
|
|
1854
|
+
message: `Build/push failed (no ${noun} was attempted): ${errorMessage(err)}`,
|
|
1855
|
+
exit: EXIT_CODES.BUILD_FAILED
|
|
1856
|
+
};
|
|
1857
|
+
case "onchain":
|
|
1858
|
+
return {
|
|
1859
|
+
message: `On-chain ${noun} failed after the image was built and pushed: ${errorMessage(err)}
|
|
1860
|
+
The image is already pushed \u2014 re-running ${operation} will reuse it.`,
|
|
1861
|
+
exit: EXIT_CODES.ONCHAIN_FAILED
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1689
1866
|
// src/commands/compute/app/upgrade.ts
|
|
1690
1867
|
var AppUpgrade = class _AppUpgrade extends Command {
|
|
1691
1868
|
static description = "Upgrade existing deployment";
|
|
1692
1869
|
static args = {
|
|
1693
1870
|
"app-id": Args.string({
|
|
1694
|
-
description: "App ID or name to upgrade",
|
|
1871
|
+
description: "App ID or name to upgrade (env: ECLOUD_APP_ID)",
|
|
1695
1872
|
required: false
|
|
1696
1873
|
})
|
|
1697
1874
|
};
|
|
@@ -1725,7 +1902,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1725
1902
|
}),
|
|
1726
1903
|
"log-visibility": Flags2.string({
|
|
1727
1904
|
required: false,
|
|
1728
|
-
description: "Log visibility setting: public, private, or off",
|
|
1905
|
+
description: "Log visibility setting: public, private, or off (non-interactive default: private)",
|
|
1729
1906
|
options: ["public", "private", "off"],
|
|
1730
1907
|
env: "ECLOUD_LOG_VISIBILITY"
|
|
1731
1908
|
}),
|
|
@@ -1736,7 +1913,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1736
1913
|
}),
|
|
1737
1914
|
"resource-usage-monitoring": Flags2.string({
|
|
1738
1915
|
required: false,
|
|
1739
|
-
description: "Resource usage monitoring: enable or disable",
|
|
1916
|
+
description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
|
|
1740
1917
|
options: ["enable", "disable"],
|
|
1741
1918
|
env: "ECLOUD_RESOURCE_USAGE_MONITORING"
|
|
1742
1919
|
}),
|
|
@@ -1774,19 +1951,50 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1774
1951
|
}),
|
|
1775
1952
|
force: Flags2.boolean({
|
|
1776
1953
|
description: "Skip all confirmation prompts",
|
|
1777
|
-
default: false
|
|
1954
|
+
default: false,
|
|
1955
|
+
env: "ECLOUD_FORCE"
|
|
1956
|
+
}),
|
|
1957
|
+
"watch-timeout": Flags2.integer({
|
|
1958
|
+
description: "Maximum seconds to wait for the upgrade to complete before returning a recovery hint (default: 600)",
|
|
1959
|
+
env: "ECLOUD_WATCH_TIMEOUT_SECONDS"
|
|
1778
1960
|
})
|
|
1779
1961
|
};
|
|
1780
1962
|
async run() {
|
|
1781
1963
|
return withTelemetry(this, async () => {
|
|
1782
1964
|
const { args, flags } = await this.parse(_AppUpgrade);
|
|
1965
|
+
const appIdInput = args["app-id"] ?? process.env.ECLOUD_APP_ID;
|
|
1966
|
+
const nonInteractive = isNonInteractive(flags);
|
|
1967
|
+
if (nonInteractive) {
|
|
1968
|
+
const missing = collectMissingRequiredInputs(
|
|
1969
|
+
{
|
|
1970
|
+
imageRef: flags["image-ref"],
|
|
1971
|
+
dockerfile: flags.dockerfile,
|
|
1972
|
+
verifiable: flags.verifiable,
|
|
1973
|
+
repo: flags.repo,
|
|
1974
|
+
commit: flags.commit
|
|
1975
|
+
},
|
|
1976
|
+
"app-id"
|
|
1977
|
+
);
|
|
1978
|
+
if (!appIdInput) {
|
|
1979
|
+
missing.push("app-id (positional arg or ECLOUD_APP_ID)");
|
|
1980
|
+
}
|
|
1981
|
+
if (missing.length > 0) {
|
|
1982
|
+
const { message, exit } = stageFailure(
|
|
1983
|
+
"upgrade",
|
|
1984
|
+
"invalid-input",
|
|
1985
|
+
`Missing required input(s) for non-interactive upgrade:
|
|
1986
|
+
- ${missing.join("\n - ")}`
|
|
1987
|
+
);
|
|
1988
|
+
this.error(message, { exit });
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1783
1991
|
const compute = await createComputeClient(flags);
|
|
1784
1992
|
const environment = flags.environment;
|
|
1785
1993
|
const environmentConfig = getEnvironmentConfig4(environment);
|
|
1786
1994
|
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1787
1995
|
const privateKey = flags["private-key"];
|
|
1788
1996
|
const appID = await getOrPromptAppID({
|
|
1789
|
-
appID:
|
|
1997
|
+
appID: appIdInput,
|
|
1790
1998
|
environment,
|
|
1791
1999
|
privateKey,
|
|
1792
2000
|
rpcUrl,
|
|
@@ -1847,7 +2055,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1847
2055
|
buildContextPath: flags["build-context"],
|
|
1848
2056
|
dependencies: flags["build-dependencies"]
|
|
1849
2057
|
} : await promptVerifiableGitSourceInputs();
|
|
1850
|
-
envFilePath = await
|
|
2058
|
+
envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
|
|
1851
2059
|
const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
|
|
1852
2060
|
if (includeTlsCaddyfile && !inputs.caddyfilePath) {
|
|
1853
2061
|
inputs.caddyfilePath = "Caddyfile";
|
|
@@ -1907,10 +2115,11 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1907
2115
|
}
|
|
1908
2116
|
}
|
|
1909
2117
|
const isVerifiable = verifiableMode !== "none";
|
|
1910
|
-
const
|
|
2118
|
+
const deployExistingImageRef = !!flags["image-ref"] && !flags.dockerfile;
|
|
2119
|
+
const dockerfilePath = isVerifiable || deployExistingImageRef ? "" : await getDockerfile(flags.dockerfile, nonInteractive);
|
|
1911
2120
|
const buildFromDockerfile = dockerfilePath !== "";
|
|
1912
2121
|
const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1913
|
-
envFilePath = envFilePath ?? await
|
|
2122
|
+
envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
|
|
1914
2123
|
if (flags.env && flags.env.length > 0) {
|
|
1915
2124
|
envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
|
|
1916
2125
|
}
|
|
@@ -1921,7 +2130,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1921
2130
|
});
|
|
1922
2131
|
let currentInstanceType = "";
|
|
1923
2132
|
try {
|
|
1924
|
-
const userApiClient = new
|
|
2133
|
+
const userApiClient = new UserApiClient4(environmentConfig, walletClient, publicClient, {
|
|
1925
2134
|
clientId: getClientId()
|
|
1926
2135
|
});
|
|
1927
2136
|
const infos = await userApiClient.getInfos([appID], 1);
|
|
@@ -1936,33 +2145,43 @@ var AppUpgrade = class _AppUpgrade extends Command {
|
|
|
1936
2145
|
privateKey,
|
|
1937
2146
|
rpcUrl
|
|
1938
2147
|
);
|
|
1939
|
-
const instanceType = await
|
|
2148
|
+
const instanceType = await getInstanceType(
|
|
1940
2149
|
flags["instance-type"],
|
|
1941
2150
|
currentInstanceType,
|
|
1942
|
-
availableTypes
|
|
2151
|
+
availableTypes,
|
|
2152
|
+
nonInteractive
|
|
1943
2153
|
);
|
|
1944
|
-
const logSettings = await
|
|
1945
|
-
flags["log-visibility"]
|
|
2154
|
+
const logSettings = await getLogSettings(
|
|
2155
|
+
flags["log-visibility"],
|
|
2156
|
+
nonInteractive
|
|
1946
2157
|
);
|
|
1947
|
-
const resourceUsageMonitoring = await
|
|
1948
|
-
flags["resource-usage-monitoring"]
|
|
2158
|
+
const resourceUsageMonitoring = await getResourceUsageMonitoring(
|
|
2159
|
+
flags["resource-usage-monitoring"],
|
|
2160
|
+
nonInteractive
|
|
1949
2161
|
);
|
|
1950
2162
|
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2163
|
+
let prepared;
|
|
2164
|
+
let gasEstimate;
|
|
2165
|
+
try {
|
|
2166
|
+
({ prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, {
|
|
2167
|
+
imageRef,
|
|
2168
|
+
imageDigest: verifiableImageDigest,
|
|
2169
|
+
envFile: envFilePath,
|
|
2170
|
+
instanceType,
|
|
2171
|
+
logVisibility,
|
|
2172
|
+
resourceUsageMonitoring
|
|
2173
|
+
}) : await compute.app.prepareUpgrade(appID, {
|
|
2174
|
+
dockerfile: dockerfilePath,
|
|
2175
|
+
imageRef,
|
|
2176
|
+
envFile: envFilePath,
|
|
2177
|
+
instanceType,
|
|
2178
|
+
logVisibility,
|
|
2179
|
+
resourceUsageMonitoring
|
|
2180
|
+
}));
|
|
2181
|
+
} catch (err) {
|
|
2182
|
+
const { message, exit } = stageFailure("upgrade", "build", err);
|
|
2183
|
+
this.error(message, { exit });
|
|
2184
|
+
}
|
|
1966
2185
|
const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
|
|
1967
2186
|
if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
|
|
1968
2187
|
this.log(
|
|
@@ -1985,8 +2204,39 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
|
|
|
1985
2204
|
return;
|
|
1986
2205
|
}
|
|
1987
2206
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
2207
|
+
let res;
|
|
2208
|
+
try {
|
|
2209
|
+
res = await compute.app.executeUpgrade(prepared, finalTx);
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
const { message, exit } = stageFailure("upgrade", "onchain", err);
|
|
2212
|
+
this.error(message, { exit });
|
|
2213
|
+
}
|
|
2214
|
+
try {
|
|
2215
|
+
await compute.app.watchUpgrade(res.appId, { timeoutSeconds: flags["watch-timeout"] });
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
if (err instanceof WatchTimeoutError) {
|
|
2218
|
+
this.log("");
|
|
2219
|
+
this.log(
|
|
2220
|
+
chalk2.yellow(
|
|
2221
|
+
`Timed out after ${err.elapsedSeconds}s waiting for upgrade to complete (last status: ${err.lastStatus ?? "unknown"}).`
|
|
2222
|
+
)
|
|
2223
|
+
);
|
|
2224
|
+
this.log(chalk2.gray("The on-chain transaction was submitted; the orchestrator may"));
|
|
2225
|
+
this.log(chalk2.gray("still be processing. To check the current status, run:"));
|
|
2226
|
+
this.log("");
|
|
2227
|
+
this.log(` ${chalk2.cyan(`ecloud compute app info ${res.appId}`)}`);
|
|
2228
|
+
this.log("");
|
|
2229
|
+
this.log(chalk2.gray(`appId: ${res.appId}`));
|
|
2230
|
+
this.log(chalk2.gray(`txHash: ${res.txHash}`));
|
|
2231
|
+
this.log(
|
|
2232
|
+
chalk2.gray(
|
|
2233
|
+
`(override the watch deadline with ECLOUD_WATCH_TIMEOUT_SECONDS, currently ${err.timeoutSeconds}s)`
|
|
2234
|
+
)
|
|
2235
|
+
);
|
|
2236
|
+
this.exit(1);
|
|
2237
|
+
}
|
|
2238
|
+
throw err;
|
|
2239
|
+
}
|
|
1990
2240
|
try {
|
|
1991
2241
|
const cwd = process.env.INIT_CWD || process.cwd();
|
|
1992
2242
|
setLinkedAppForDirectory(environment, cwd, res.appId);
|
|
@@ -2004,7 +2254,7 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
|
|
|
2004
2254
|
rpcUrl,
|
|
2005
2255
|
environment
|
|
2006
2256
|
});
|
|
2007
|
-
const userApiClient = new
|
|
2257
|
+
const userApiClient = new UserApiClient4(environmentConfig, walletClient2, publicClient2, {
|
|
2008
2258
|
clientId: getClientId()
|
|
2009
2259
|
});
|
|
2010
2260
|
const infos = await userApiClient.getInfos([res.appId], 1);
|
|
@@ -2033,26 +2283,6 @@ Note: "Running" means the container started. Verify your app is serving traffic
|
|
|
2033
2283
|
});
|
|
2034
2284
|
}
|
|
2035
2285
|
};
|
|
2036
|
-
async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
|
|
2037
|
-
try {
|
|
2038
|
-
const { publicClient, walletClient } = createViemClients({
|
|
2039
|
-
privateKey,
|
|
2040
|
-
rpcUrl,
|
|
2041
|
-
environment
|
|
2042
|
-
});
|
|
2043
|
-
const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
|
|
2044
|
-
clientId: getClientId()
|
|
2045
|
-
});
|
|
2046
|
-
const skuList = await userApiClient.getSKUs();
|
|
2047
|
-
if (skuList.skus.length === 0) {
|
|
2048
|
-
throw new Error("No instance types available from server");
|
|
2049
|
-
}
|
|
2050
|
-
return skuList.skus;
|
|
2051
|
-
} catch (err) {
|
|
2052
|
-
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
2053
|
-
return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
2286
|
export {
|
|
2057
2287
|
AppUpgrade as default
|
|
2058
2288
|
};
|