@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,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/compute/app/deploy.ts
|
|
4
4
|
import { Command, Flags as Flags2 } from "@oclif/core";
|
|
5
|
-
import { getEnvironmentConfig as getEnvironmentConfig4,
|
|
5
|
+
import { getEnvironmentConfig as getEnvironmentConfig4, isMainnet, WatchTimeoutError } from "@layr-labs/ecloud-sdk";
|
|
6
6
|
|
|
7
7
|
// src/telemetry.ts
|
|
8
8
|
import {
|
|
@@ -304,7 +304,7 @@ function findAvailableName(environment, baseName) {
|
|
|
304
304
|
|
|
305
305
|
// src/utils/version.ts
|
|
306
306
|
function getCliVersion() {
|
|
307
|
-
return true ? "1.0.0-dev.
|
|
307
|
+
return true ? "1.0.0-dev.8" : "0.0.0";
|
|
308
308
|
}
|
|
309
309
|
function getClientId() {
|
|
310
310
|
return `ecloud-cli/v${getCliVersion()}`;
|
|
@@ -319,7 +319,30 @@ function ensureInteractive(missingFlagHint) {
|
|
|
319
319
|
);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
function isNonInteractive(flags) {
|
|
323
|
+
if (flags?.["non-interactive"]) return true;
|
|
324
|
+
if (process.env.CI === "true") return true;
|
|
325
|
+
return !process.stdin.isTTY;
|
|
326
|
+
}
|
|
327
|
+
function warnDefaulted(flagHint, chosen) {
|
|
328
|
+
console.warn(`Warning: ${flagHint} not set in non-interactive mode; defaulting to ${chosen}.`);
|
|
329
|
+
}
|
|
330
|
+
function collectMissingRequiredInputs(state, identityFlag) {
|
|
331
|
+
const missing = [];
|
|
332
|
+
const hasImageSource = !!state.imageRef || !!state.dockerfile || !!state.verifiable && !!state.repo && !!state.commit;
|
|
333
|
+
if (!hasImageSource) {
|
|
334
|
+
missing.push(
|
|
335
|
+
"an image source (one of: --image-ref, --dockerfile, or --verifiable with --repo and --commit)"
|
|
336
|
+
);
|
|
337
|
+
} else if (state.dockerfile && !state.imageRef && !state.verifiable) {
|
|
338
|
+
missing.push("--image-ref (registry destination for the built image)");
|
|
339
|
+
}
|
|
340
|
+
if (identityFlag === "name" && !state.name) {
|
|
341
|
+
missing.push("--name");
|
|
342
|
+
}
|
|
343
|
+
return missing;
|
|
344
|
+
}
|
|
345
|
+
async function getDockerfile(dockerfilePath, nonInteractive = false) {
|
|
323
346
|
if (dockerfilePath) {
|
|
324
347
|
return dockerfilePath;
|
|
325
348
|
}
|
|
@@ -328,6 +351,10 @@ async function getDockerfileInteractive(dockerfilePath) {
|
|
|
328
351
|
if (!fs3.existsSync(dockerfilePath_resolved)) {
|
|
329
352
|
return "";
|
|
330
353
|
}
|
|
354
|
+
if (nonInteractive) {
|
|
355
|
+
warnDefaulted("--dockerfile/--image-ref", `build from '${dockerfilePath_resolved}'`);
|
|
356
|
+
return dockerfilePath_resolved;
|
|
357
|
+
}
|
|
331
358
|
ensureInteractive("--dockerfile or --image-ref");
|
|
332
359
|
console.log(`
|
|
333
360
|
Found Dockerfile in ${cwd}`);
|
|
@@ -784,13 +811,17 @@ async function getOrPromptAppName(appName, environment, imageRef, suggestedBaseN
|
|
|
784
811
|
}
|
|
785
812
|
return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
|
|
786
813
|
}
|
|
787
|
-
async function
|
|
814
|
+
async function getEnvFile(envFilePath, nonInteractive = false) {
|
|
788
815
|
if (envFilePath && fs3.existsSync(envFilePath)) {
|
|
789
816
|
return envFilePath;
|
|
790
817
|
}
|
|
791
818
|
if (fs3.existsSync(".env")) {
|
|
792
819
|
return ".env";
|
|
793
820
|
}
|
|
821
|
+
if (nonInteractive) {
|
|
822
|
+
warnDefaulted("--env-file", "no env file");
|
|
823
|
+
return "";
|
|
824
|
+
}
|
|
794
825
|
ensureInteractive("--env-file");
|
|
795
826
|
console.log("\nEnvironment file not found.");
|
|
796
827
|
console.log("Environment files contain variables like RPC_URL, etc.");
|
|
@@ -818,6 +849,7 @@ async function getEnvFileInteractive(envFilePath) {
|
|
|
818
849
|
throw new Error(`Unexpected choice: ${choice}`);
|
|
819
850
|
}
|
|
820
851
|
}
|
|
852
|
+
var DEFAULT_NONINTERACTIVE_SKU = "g1-standard-2s";
|
|
821
853
|
function formatSkuChoice(it) {
|
|
822
854
|
if (it.vcpus != null && it.memory_mb != null && it.monthly_price_usd != null && it.hourly_price_usd != null) {
|
|
823
855
|
const tier = it.friendly_name ?? it.sku;
|
|
@@ -834,7 +866,7 @@ function formatSkuChoice(it) {
|
|
|
834
866
|
}
|
|
835
867
|
return `${it.sku} - ${it.description}`;
|
|
836
868
|
}
|
|
837
|
-
async function
|
|
869
|
+
async function getInstanceType(instanceType, defaultSKU, availableTypes, nonInteractive = false) {
|
|
838
870
|
if (instanceType) {
|
|
839
871
|
const valid = availableTypes.find((t) => t.sku === instanceType);
|
|
840
872
|
if (valid) {
|
|
@@ -843,6 +875,20 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
|
|
|
843
875
|
const validSKUs = availableTypes.map((t) => t.sku).join(", ");
|
|
844
876
|
throw new Error(`Invalid instance-type: ${instanceType} (must be one of: ${validSKUs})`);
|
|
845
877
|
}
|
|
878
|
+
if (nonInteractive) {
|
|
879
|
+
if (defaultSKU) {
|
|
880
|
+
warnDefaulted("--instance-type", `current type '${defaultSKU}'`);
|
|
881
|
+
return defaultSKU;
|
|
882
|
+
}
|
|
883
|
+
if (availableTypes.some((t) => t.sku === DEFAULT_NONINTERACTIVE_SKU)) {
|
|
884
|
+
warnDefaulted("--instance-type", `'${DEFAULT_NONINTERACTIVE_SKU}'`);
|
|
885
|
+
return DEFAULT_NONINTERACTIVE_SKU;
|
|
886
|
+
}
|
|
887
|
+
const validSKUs = availableTypes.map((t) => t.sku).join(", ");
|
|
888
|
+
throw new Error(
|
|
889
|
+
`Cannot pick a default --instance-type in non-interactive mode: '${DEFAULT_NONINTERACTIVE_SKU}' not offered (available: ${validSKUs}). Provide --instance-type.`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
846
892
|
ensureInteractive("--instance-type");
|
|
847
893
|
const isCurrentType = defaultSKU !== "";
|
|
848
894
|
const hasPricing = availableTypes.some((t) => t.monthly_price_usd != null);
|
|
@@ -874,7 +920,7 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
|
|
|
874
920
|
});
|
|
875
921
|
return choice;
|
|
876
922
|
}
|
|
877
|
-
async function
|
|
923
|
+
async function getLogSettings(logVisibility, nonInteractive = false) {
|
|
878
924
|
if (logVisibility) {
|
|
879
925
|
switch (logVisibility) {
|
|
880
926
|
case "public":
|
|
@@ -889,6 +935,10 @@ async function getLogSettingsInteractive(logVisibility) {
|
|
|
889
935
|
);
|
|
890
936
|
}
|
|
891
937
|
}
|
|
938
|
+
if (nonInteractive) {
|
|
939
|
+
warnDefaulted("--log-visibility", "'private'");
|
|
940
|
+
return { logRedirect: "always", publicLogs: false };
|
|
941
|
+
}
|
|
892
942
|
ensureInteractive("--log-visibility");
|
|
893
943
|
const choice = await select({
|
|
894
944
|
message: "Do you want to view your app's logs?",
|
|
@@ -909,7 +959,7 @@ async function getLogSettingsInteractive(logVisibility) {
|
|
|
909
959
|
throw new Error(`Unexpected choice: ${choice}`);
|
|
910
960
|
}
|
|
911
961
|
}
|
|
912
|
-
async function
|
|
962
|
+
async function getResourceUsageMonitoring(resourceUsageMonitoring, nonInteractive = false) {
|
|
913
963
|
if (resourceUsageMonitoring) {
|
|
914
964
|
switch (resourceUsageMonitoring) {
|
|
915
965
|
case "enable":
|
|
@@ -921,6 +971,10 @@ async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
|
|
|
921
971
|
);
|
|
922
972
|
}
|
|
923
973
|
}
|
|
974
|
+
if (nonInteractive) {
|
|
975
|
+
warnDefaulted("--resource-usage-monitoring", "'disable'");
|
|
976
|
+
return "disable";
|
|
977
|
+
}
|
|
924
978
|
ensureInteractive("--resource-usage-monitoring");
|
|
925
979
|
const choice = await select({
|
|
926
980
|
message: "Show resource usage (CPU/memory) for your app?",
|
|
@@ -936,9 +990,7 @@ async function confirm(prompt) {
|
|
|
936
990
|
}
|
|
937
991
|
async function confirmWithDefault(prompt, defaultValue = false) {
|
|
938
992
|
if (!process.stdin.isTTY) {
|
|
939
|
-
|
|
940
|
-
`Cannot confirm "${prompt}" in non-interactive mode. Use --force to skip confirmation prompts.`
|
|
941
|
-
);
|
|
993
|
+
return defaultValue;
|
|
942
994
|
}
|
|
943
995
|
return await inquirerConfirm({
|
|
944
996
|
message: prompt,
|
|
@@ -1257,7 +1309,7 @@ var commonFlags = {
|
|
|
1257
1309
|
required: false,
|
|
1258
1310
|
description: "Deployment environment to use",
|
|
1259
1311
|
env: "ECLOUD_ENV",
|
|
1260
|
-
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "
|
|
1312
|
+
default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "mainnet-alpha")
|
|
1261
1313
|
}),
|
|
1262
1314
|
"private-key": Flags.string({
|
|
1263
1315
|
required: false,
|
|
@@ -1287,6 +1339,12 @@ var commonFlags = {
|
|
|
1287
1339
|
nonce: Flags.string({
|
|
1288
1340
|
required: false,
|
|
1289
1341
|
description: 'Override transaction nonce (integer or "latest" to replace a stuck transaction)'
|
|
1342
|
+
}),
|
|
1343
|
+
"non-interactive": Flags.boolean({
|
|
1344
|
+
required: false,
|
|
1345
|
+
description: "Assume non-interactive mode: default safe prompts and error all-at-once on missing required inputs",
|
|
1346
|
+
env: "ECLOUD_NON_INTERACTIVE",
|
|
1347
|
+
default: false
|
|
1290
1348
|
})
|
|
1291
1349
|
};
|
|
1292
1350
|
async function applyTxOverrides(estimate, flags, opts) {
|
|
@@ -1320,7 +1378,9 @@ async function applyTxOverrides(estimate, flags, opts) {
|
|
|
1320
1378
|
} else {
|
|
1321
1379
|
const parsed = Number(nonceStr);
|
|
1322
1380
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
1323
|
-
throw new Error(
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
`Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`
|
|
1383
|
+
);
|
|
1324
1384
|
}
|
|
1325
1385
|
nonce = parsed;
|
|
1326
1386
|
}
|
|
@@ -1344,7 +1404,7 @@ import {
|
|
|
1344
1404
|
getEnvironmentConfig as getEnvironmentConfig3,
|
|
1345
1405
|
requirePrivateKey
|
|
1346
1406
|
} from "@layr-labs/ecloud-sdk";
|
|
1347
|
-
async function createComputeClient(flags) {
|
|
1407
|
+
async function createComputeClient(flags, options = {}) {
|
|
1348
1408
|
flags = await validateCommonFlags(flags);
|
|
1349
1409
|
const environment = flags.environment;
|
|
1350
1410
|
const environmentConfig = getEnvironmentConfig3(environment);
|
|
@@ -1366,8 +1426,9 @@ async function createComputeClient(flags) {
|
|
|
1366
1426
|
publicClient,
|
|
1367
1427
|
environment,
|
|
1368
1428
|
clientId: getClientId(),
|
|
1369
|
-
skipTelemetry: true
|
|
1429
|
+
skipTelemetry: true,
|
|
1370
1430
|
// CLI already has telemetry, skip SDK telemetry
|
|
1431
|
+
logger: options.logger
|
|
1371
1432
|
});
|
|
1372
1433
|
}
|
|
1373
1434
|
async function createBuildClient(flags) {
|
|
@@ -1393,6 +1454,29 @@ async function createBuildClient(flags) {
|
|
|
1393
1454
|
});
|
|
1394
1455
|
}
|
|
1395
1456
|
|
|
1457
|
+
// src/utils/instanceTypes.ts
|
|
1458
|
+
import { UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
|
|
1459
|
+
async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
|
|
1460
|
+
try {
|
|
1461
|
+
const { publicClient, walletClient } = createViemClients({
|
|
1462
|
+
privateKey,
|
|
1463
|
+
rpcUrl,
|
|
1464
|
+
environment
|
|
1465
|
+
});
|
|
1466
|
+
const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
|
|
1467
|
+
clientId: getClientId()
|
|
1468
|
+
});
|
|
1469
|
+
const skuList = await userApiClient.getSKUs();
|
|
1470
|
+
if (skuList.skus.length === 0) {
|
|
1471
|
+
throw new Error("No instance types available from server");
|
|
1472
|
+
}
|
|
1473
|
+
return skuList.skus;
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
1476
|
+
return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1396
1480
|
// src/commands/compute/app/deploy.ts
|
|
1397
1481
|
import chalk2 from "chalk";
|
|
1398
1482
|
|
|
@@ -1554,8 +1638,63 @@ async function resolveDockerHubImageDigest(imageRef) {
|
|
|
1554
1638
|
if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
|
|
1555
1639
|
throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
|
|
1556
1640
|
}
|
|
1641
|
+
await assertImageHasAmd64(owner, repo, tag, token, imageRef);
|
|
1557
1642
|
return digest;
|
|
1558
1643
|
}
|
|
1644
|
+
var AMD64_ACCEPT = [
|
|
1645
|
+
"application/vnd.oci.image.index.v1+json",
|
|
1646
|
+
"application/vnd.docker.distribution.manifest.list.v2+json",
|
|
1647
|
+
"application/vnd.oci.image.manifest.v1+json",
|
|
1648
|
+
"application/vnd.docker.distribution.manifest.v2+json"
|
|
1649
|
+
].join(", ");
|
|
1650
|
+
async function assertImageHasAmd64(owner, repo, tag, token, imageRef) {
|
|
1651
|
+
const base = `https://registry-1.docker.io/v2/${owner}/${repo}`;
|
|
1652
|
+
const headers = { Authorization: `Bearer ${token}`, Accept: AMD64_ACCEPT };
|
|
1653
|
+
const res = await fetch(`${base}/manifests/${encodeURIComponent(tag)}`, { headers });
|
|
1654
|
+
if (!res.ok) {
|
|
1655
|
+
const body = await safeReadText(res);
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
`Failed to read manifest for ${imageRef} (${res.status}): ${body || res.statusText}`
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
const manifest = await res.json();
|
|
1661
|
+
const isAmd64 = (os5, arch) => os5 === "linux" && arch === "amd64";
|
|
1662
|
+
if (Array.isArray(manifest.manifests) && manifest.manifests.length > 0) {
|
|
1663
|
+
const platforms = manifest.manifests.map(
|
|
1664
|
+
(m) => m.platform ? `${m.platform.os}/${m.platform.architecture}` : "unknown"
|
|
1665
|
+
);
|
|
1666
|
+
if (manifest.manifests.some((m) => isAmd64(m.platform?.os, m.platform?.architecture))) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
throw amd64Error(imageRef, platforms);
|
|
1670
|
+
}
|
|
1671
|
+
const configDigest = manifest.config?.digest;
|
|
1672
|
+
if (!configDigest) {
|
|
1673
|
+
throw amd64Error(imageRef, ["unknown (no platform info in manifest)"]);
|
|
1674
|
+
}
|
|
1675
|
+
const cfgRes = await fetch(`${base}/blobs/${configDigest}`, {
|
|
1676
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1677
|
+
});
|
|
1678
|
+
if (!cfgRes.ok) {
|
|
1679
|
+
throw amd64Error(imageRef, ["unknown (could not read image config)"]);
|
|
1680
|
+
}
|
|
1681
|
+
const cfg = await cfgRes.json();
|
|
1682
|
+
if (isAmd64(cfg.os, cfg.architecture)) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
throw amd64Error(imageRef, [`${cfg.os ?? "unknown"}/${cfg.architecture ?? "unknown"}`]);
|
|
1686
|
+
}
|
|
1687
|
+
function amd64Error(imageRef, platforms) {
|
|
1688
|
+
return new Error(
|
|
1689
|
+
`ecloud requires linux/amd64 images for TEE deployment.
|
|
1690
|
+
|
|
1691
|
+
Image: ${imageRef}
|
|
1692
|
+
Found platform(s): ${platforms.join(", ")}
|
|
1693
|
+
Required platform: linux/amd64
|
|
1694
|
+
|
|
1695
|
+
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.`
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1559
1698
|
|
|
1560
1699
|
// src/utils/tls.ts
|
|
1561
1700
|
import fs4 from "fs";
|
|
@@ -1620,6 +1759,39 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
|
|
|
1620
1759
|
return tmpFile;
|
|
1621
1760
|
}
|
|
1622
1761
|
|
|
1762
|
+
// src/utils/exitCodes.ts
|
|
1763
|
+
import { InsufficientGasError } from "@layr-labs/ecloud-sdk";
|
|
1764
|
+
var EXIT_CODES = {
|
|
1765
|
+
INVALID_INPUT: 2,
|
|
1766
|
+
BUILD_FAILED: 3,
|
|
1767
|
+
ONCHAIN_FAILED: 4
|
|
1768
|
+
};
|
|
1769
|
+
function errorMessage(err) {
|
|
1770
|
+
if (err instanceof Error) return err.message;
|
|
1771
|
+
return String(err);
|
|
1772
|
+
}
|
|
1773
|
+
function stageFailure(operation, stage, err) {
|
|
1774
|
+
const noun = operation === "deploy" ? "deployment" : "upgrade";
|
|
1775
|
+
if (err instanceof InsufficientGasError) {
|
|
1776
|
+
stage = "onchain";
|
|
1777
|
+
}
|
|
1778
|
+
switch (stage) {
|
|
1779
|
+
case "invalid-input":
|
|
1780
|
+
return { message: errorMessage(err), exit: EXIT_CODES.INVALID_INPUT };
|
|
1781
|
+
case "build":
|
|
1782
|
+
return {
|
|
1783
|
+
message: `Build/push failed (no ${noun} was attempted): ${errorMessage(err)}`,
|
|
1784
|
+
exit: EXIT_CODES.BUILD_FAILED
|
|
1785
|
+
};
|
|
1786
|
+
case "onchain":
|
|
1787
|
+
return {
|
|
1788
|
+
message: `On-chain ${noun} failed after the image was built and pushed: ${errorMessage(err)}
|
|
1789
|
+
The image is already pushed \u2014 re-running ${operation} will reuse it.`,
|
|
1790
|
+
exit: EXIT_CODES.ONCHAIN_FAILED
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1623
1795
|
// src/commands/compute/app/deploy.ts
|
|
1624
1796
|
var AppDeploy = class _AppDeploy extends Command {
|
|
1625
1797
|
static description = "Deploy new app";
|
|
@@ -1653,7 +1825,7 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1653
1825
|
}),
|
|
1654
1826
|
"log-visibility": Flags2.string({
|
|
1655
1827
|
required: false,
|
|
1656
|
-
description: "Log visibility setting: public, private, or off",
|
|
1828
|
+
description: "Log visibility setting: public, private, or off (non-interactive default: private)",
|
|
1657
1829
|
options: ["public", "private", "off"],
|
|
1658
1830
|
env: "ECLOUD_LOG_VISIBILITY"
|
|
1659
1831
|
}),
|
|
@@ -1669,7 +1841,7 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1669
1841
|
}),
|
|
1670
1842
|
"resource-usage-monitoring": Flags2.string({
|
|
1671
1843
|
required: false,
|
|
1672
|
-
description: "Resource usage monitoring: enable or disable",
|
|
1844
|
+
description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
|
|
1673
1845
|
options: ["enable", "disable"],
|
|
1674
1846
|
env: "ECLOUD_RESOURCE_USAGE_MONITORING"
|
|
1675
1847
|
}),
|
|
@@ -1723,12 +1895,40 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1723
1895
|
}),
|
|
1724
1896
|
force: Flags2.boolean({
|
|
1725
1897
|
description: "Skip all confirmation prompts",
|
|
1726
|
-
default: false
|
|
1898
|
+
default: false,
|
|
1899
|
+
env: "ECLOUD_FORCE"
|
|
1900
|
+
}),
|
|
1901
|
+
"watch-timeout": Flags2.integer({
|
|
1902
|
+
description: "Maximum seconds to wait for the app to start before returning a recovery hint (default: 600)",
|
|
1903
|
+
env: "ECLOUD_WATCH_TIMEOUT_SECONDS"
|
|
1727
1904
|
})
|
|
1728
1905
|
};
|
|
1729
1906
|
async run() {
|
|
1730
1907
|
return withTelemetry(this, async () => {
|
|
1731
1908
|
const { flags } = await this.parse(_AppDeploy);
|
|
1909
|
+
const nonInteractive = isNonInteractive(flags);
|
|
1910
|
+
if (nonInteractive) {
|
|
1911
|
+
const missing = collectMissingRequiredInputs(
|
|
1912
|
+
{
|
|
1913
|
+
imageRef: flags["image-ref"],
|
|
1914
|
+
dockerfile: flags.dockerfile,
|
|
1915
|
+
verifiable: flags.verifiable,
|
|
1916
|
+
repo: flags.repo,
|
|
1917
|
+
commit: flags.commit,
|
|
1918
|
+
name: flags.name
|
|
1919
|
+
},
|
|
1920
|
+
"name"
|
|
1921
|
+
);
|
|
1922
|
+
if (missing.length > 0) {
|
|
1923
|
+
const { message, exit } = stageFailure(
|
|
1924
|
+
"deploy",
|
|
1925
|
+
"invalid-input",
|
|
1926
|
+
`Missing required input(s) for non-interactive deploy:
|
|
1927
|
+
- ${missing.join("\n - ")}`
|
|
1928
|
+
);
|
|
1929
|
+
this.error(message, { exit });
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1732
1932
|
const compute = await createComputeClient(flags);
|
|
1733
1933
|
const environment = flags.environment;
|
|
1734
1934
|
const environmentConfig = getEnvironmentConfig4(environment);
|
|
@@ -1831,7 +2031,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1831
2031
|
buildContextPath: flags["build-context"],
|
|
1832
2032
|
dependencies: flags["build-dependencies"]
|
|
1833
2033
|
} : await promptVerifiableGitSourceInputs();
|
|
1834
|
-
envFilePath = await
|
|
2034
|
+
envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
|
|
1835
2035
|
const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
|
|
1836
2036
|
if (includeTlsCaddyfile && !inputs.caddyfilePath) {
|
|
1837
2037
|
inputs.caddyfilePath = "Caddyfile";
|
|
@@ -1893,7 +2093,8 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1893
2093
|
}
|
|
1894
2094
|
}
|
|
1895
2095
|
const isVerifiable = verifiableMode !== "none";
|
|
1896
|
-
const
|
|
2096
|
+
const deployExistingImageRef = !!flags["image-ref"] && !flags.dockerfile;
|
|
2097
|
+
const dockerfilePath = isVerifiable || deployExistingImageRef ? "" : await getDockerfile(flags.dockerfile, nonInteractive);
|
|
1897
2098
|
const buildFromDockerfile = dockerfilePath !== "";
|
|
1898
2099
|
const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1899
2100
|
const appName = await getOrPromptAppName(
|
|
@@ -1903,7 +2104,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1903
2104
|
suggestedAppBaseName,
|
|
1904
2105
|
skipDefaultAppName
|
|
1905
2106
|
);
|
|
1906
|
-
envFilePath = envFilePath ?? await
|
|
2107
|
+
envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
|
|
1907
2108
|
if (flags.env && flags.env.length > 0) {
|
|
1908
2109
|
envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
|
|
1909
2110
|
}
|
|
@@ -1913,38 +2114,48 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1913
2114
|
privateKey,
|
|
1914
2115
|
rpcUrl
|
|
1915
2116
|
);
|
|
1916
|
-
const instanceType = await
|
|
2117
|
+
const instanceType = await getInstanceType(
|
|
1917
2118
|
flags["instance-type"],
|
|
1918
2119
|
"",
|
|
1919
|
-
// No default for new deployments
|
|
1920
|
-
availableTypes
|
|
2120
|
+
// No pinned default for new deployments; non-interactive falls back to g1-standard-2s
|
|
2121
|
+
availableTypes,
|
|
2122
|
+
nonInteractive
|
|
1921
2123
|
);
|
|
1922
|
-
const logSettings = await
|
|
1923
|
-
flags["log-visibility"]
|
|
2124
|
+
const logSettings = await getLogSettings(
|
|
2125
|
+
flags["log-visibility"],
|
|
2126
|
+
nonInteractive
|
|
1924
2127
|
);
|
|
1925
|
-
const resourceUsageMonitoring = await
|
|
1926
|
-
flags["resource-usage-monitoring"]
|
|
2128
|
+
const resourceUsageMonitoring = await getResourceUsageMonitoring(
|
|
2129
|
+
flags["resource-usage-monitoring"],
|
|
2130
|
+
nonInteractive
|
|
1927
2131
|
);
|
|
1928
2132
|
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2133
|
+
let prepared;
|
|
2134
|
+
let gasEstimate;
|
|
2135
|
+
try {
|
|
2136
|
+
({ prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareDeployFromVerifiableBuild({
|
|
2137
|
+
name: appName,
|
|
2138
|
+
imageRef,
|
|
2139
|
+
imageDigest: verifiableImageDigest,
|
|
2140
|
+
envFile: envFilePath,
|
|
2141
|
+
instanceType,
|
|
2142
|
+
logVisibility,
|
|
2143
|
+
resourceUsageMonitoring,
|
|
2144
|
+
billTo: "developer"
|
|
2145
|
+
}) : await compute.app.prepareDeploy({
|
|
2146
|
+
name: appName,
|
|
2147
|
+
dockerfile: dockerfilePath,
|
|
2148
|
+
imageRef,
|
|
2149
|
+
envFile: envFilePath,
|
|
2150
|
+
instanceType,
|
|
2151
|
+
logVisibility,
|
|
2152
|
+
resourceUsageMonitoring,
|
|
2153
|
+
billTo: "developer"
|
|
2154
|
+
}));
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
const { message, exit } = stageFailure("deploy", "build", err);
|
|
2157
|
+
this.error(message, { exit });
|
|
2158
|
+
}
|
|
1948
2159
|
const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
|
|
1949
2160
|
if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
|
|
1950
2161
|
this.log(
|
|
@@ -1967,7 +2178,13 @@ ${chalk2.gray(`Deployment cancelled`)}`);
|
|
|
1967
2178
|
return;
|
|
1968
2179
|
}
|
|
1969
2180
|
}
|
|
1970
|
-
|
|
2181
|
+
let res;
|
|
2182
|
+
try {
|
|
2183
|
+
res = await compute.app.executeDeploy(prepared, finalTx);
|
|
2184
|
+
} catch (err) {
|
|
2185
|
+
const { message, exit } = stageFailure("deploy", "onchain", err);
|
|
2186
|
+
this.error(message, { exit });
|
|
2187
|
+
}
|
|
1971
2188
|
if (!flags["skip-profile"]) {
|
|
1972
2189
|
const hasProfileFlags = flags.website || flags.description || flags["x-url"] || flags.image;
|
|
1973
2190
|
let profile = null;
|
|
@@ -2010,7 +2227,42 @@ ${chalk2.gray(`Deployment cancelled`)}`);
|
|
|
2010
2227
|
}
|
|
2011
2228
|
}
|
|
2012
2229
|
}
|
|
2013
|
-
|
|
2230
|
+
let ipAddress;
|
|
2231
|
+
try {
|
|
2232
|
+
ipAddress = await compute.app.watchDeployment(res.appId, {
|
|
2233
|
+
timeoutSeconds: flags["watch-timeout"]
|
|
2234
|
+
});
|
|
2235
|
+
} catch (watchErr) {
|
|
2236
|
+
if (watchErr instanceof WatchTimeoutError) {
|
|
2237
|
+
this.log(
|
|
2238
|
+
`
|
|
2239
|
+
${chalk2.yellow("\u26A0")} ${chalk2.yellow(
|
|
2240
|
+
`Deployment did not reach Running within ${watchErr.elapsedSeconds}s (last status: ${watchErr.lastStatus ?? "unknown"}).`
|
|
2241
|
+
)}`
|
|
2242
|
+
);
|
|
2243
|
+
this.log(
|
|
2244
|
+
chalk2.gray(
|
|
2245
|
+
`The deploy transaction succeeded, but the orchestrator hasn't reported the app as Running yet.`
|
|
2246
|
+
)
|
|
2247
|
+
);
|
|
2248
|
+
this.log(chalk2.gray(` appId: ${res.appId}`));
|
|
2249
|
+
if (res.txHash) {
|
|
2250
|
+
this.log(chalk2.gray(` txHash: ${res.txHash}`));
|
|
2251
|
+
}
|
|
2252
|
+
this.log(
|
|
2253
|
+
chalk2.gray(
|
|
2254
|
+
`Check progress later with: ${chalk2.cyan(`ecloud compute app info ${res.appId}`)}`
|
|
2255
|
+
)
|
|
2256
|
+
);
|
|
2257
|
+
this.log(
|
|
2258
|
+
chalk2.gray(
|
|
2259
|
+
`Override the watch timeout with the ${chalk2.cyan("ECLOUD_WATCH_TIMEOUT_SECONDS")} environment variable.`
|
|
2260
|
+
)
|
|
2261
|
+
);
|
|
2262
|
+
this.exit(1);
|
|
2263
|
+
}
|
|
2264
|
+
throw watchErr;
|
|
2265
|
+
}
|
|
2014
2266
|
try {
|
|
2015
2267
|
const cwd = process.env.INIT_CWD || process.cwd();
|
|
2016
2268
|
setLinkedAppForDirectory(environment, cwd, res.appId);
|
|
@@ -2036,26 +2288,6 @@ Note: "Running" means the container started \u2014 verify it is serving traffic
|
|
|
2036
2288
|
});
|
|
2037
2289
|
}
|
|
2038
2290
|
};
|
|
2039
|
-
async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
|
|
2040
|
-
try {
|
|
2041
|
-
const { publicClient, walletClient } = createViemClients({
|
|
2042
|
-
privateKey,
|
|
2043
|
-
rpcUrl,
|
|
2044
|
-
environment
|
|
2045
|
-
});
|
|
2046
|
-
const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
|
|
2047
|
-
clientId: getClientId()
|
|
2048
|
-
});
|
|
2049
|
-
const skuList = await userApiClient.getSKUs();
|
|
2050
|
-
if (skuList.skus.length === 0) {
|
|
2051
|
-
throw new Error("No instance types available from server");
|
|
2052
|
-
}
|
|
2053
|
-
return skuList.skus;
|
|
2054
|
-
} catch (err) {
|
|
2055
|
-
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
2056
|
-
return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
2291
|
export {
|
|
2060
2292
|
AppDeploy as default
|
|
2061
2293
|
};
|