@layr-labs/ecloud-cli 1.0.0-devep7 → 1.0.0
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 +411 -0
- package/dist/commands/admin/admins/add.js.map +1 -0
- package/dist/commands/admin/admins/list.js +414 -0
- package/dist/commands/admin/admins/list.js.map +1 -0
- package/dist/commands/admin/admins/remove.js +409 -0
- package/dist/commands/admin/admins/remove.js.map +1 -0
- package/dist/commands/admin/coupons/create.js +424 -0
- package/dist/commands/admin/coupons/create.js.map +1 -0
- package/dist/commands/admin/coupons/deactivate.js +409 -0
- package/dist/commands/admin/coupons/deactivate.js.map +1 -0
- package/dist/commands/admin/coupons/get.js +418 -0
- package/dist/commands/admin/coupons/get.js.map +1 -0
- package/dist/commands/admin/coupons/list.js +442 -0
- package/dist/commands/admin/coupons/list.js.map +1 -0
- package/dist/commands/admin/coupons/redeem.js +413 -0
- package/dist/commands/admin/coupons/redeem.js.map +1 -0
- 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 +82 -4
- package/dist/commands/billing/__tests__/status.test.js.map +1 -1
- package/dist/commands/billing/__tests__/subscribe.test.js +10 -2
- package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
- package/dist/commands/billing/__tests__/top-up.test.js +565 -13
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
- package/dist/commands/billing/cancel.js +11 -4
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/list-cards.js +10 -2
- package/dist/commands/billing/list-cards.js.map +1 -1
- package/dist/commands/billing/redeem-coupon.js +421 -0
- package/dist/commands/billing/redeem-coupon.js.map +1 -0
- package/dist/commands/billing/status.js +36 -2
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +10 -2
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/billing/top-up.js +392 -11
- 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/configure/tls.js +70 -30
- package/dist/commands/compute/app/configure/tls.js.map +1 -1
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +301 -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 +12 -4
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +12 -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 +16 -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 +16 -8
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +16 -8
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +298 -67
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +9 -2
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +9 -2
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +9 -2
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +9 -2
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +9 -2
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +9 -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 +12 -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 +11 -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
|
|
307
|
+
return true ? "1.0.0" : "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
|
}
|
|
@@ -1340,10 +1400,11 @@ import {
|
|
|
1340
1400
|
createComputeModule,
|
|
1341
1401
|
createBillingModule,
|
|
1342
1402
|
createBuildModule,
|
|
1403
|
+
createAdminModule,
|
|
1343
1404
|
getEnvironmentConfig as getEnvironmentConfig3,
|
|
1344
1405
|
requirePrivateKey
|
|
1345
1406
|
} from "@layr-labs/ecloud-sdk";
|
|
1346
|
-
async function createComputeClient(flags) {
|
|
1407
|
+
async function createComputeClient(flags, options = {}) {
|
|
1347
1408
|
flags = await validateCommonFlags(flags);
|
|
1348
1409
|
const environment = flags.environment;
|
|
1349
1410
|
const environmentConfig = getEnvironmentConfig3(environment);
|
|
@@ -1365,8 +1426,9 @@ async function createComputeClient(flags) {
|
|
|
1365
1426
|
publicClient,
|
|
1366
1427
|
environment,
|
|
1367
1428
|
clientId: getClientId(),
|
|
1368
|
-
skipTelemetry: true
|
|
1429
|
+
skipTelemetry: true,
|
|
1369
1430
|
// CLI already has telemetry, skip SDK telemetry
|
|
1431
|
+
logger: options.logger
|
|
1370
1432
|
});
|
|
1371
1433
|
}
|
|
1372
1434
|
async function createBuildClient(flags) {
|
|
@@ -1392,6 +1454,29 @@ async function createBuildClient(flags) {
|
|
|
1392
1454
|
});
|
|
1393
1455
|
}
|
|
1394
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
|
+
|
|
1395
1480
|
// src/commands/compute/app/deploy.ts
|
|
1396
1481
|
import chalk2 from "chalk";
|
|
1397
1482
|
|
|
@@ -1553,8 +1638,63 @@ async function resolveDockerHubImageDigest(imageRef) {
|
|
|
1553
1638
|
if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
|
|
1554
1639
|
throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
|
|
1555
1640
|
}
|
|
1641
|
+
await assertImageHasAmd64(owner, repo, tag, token, imageRef);
|
|
1556
1642
|
return digest;
|
|
1557
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
|
+
}
|
|
1558
1698
|
|
|
1559
1699
|
// src/utils/tls.ts
|
|
1560
1700
|
import fs4 from "fs";
|
|
@@ -1619,6 +1759,39 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
|
|
|
1619
1759
|
return tmpFile;
|
|
1620
1760
|
}
|
|
1621
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
|
+
|
|
1622
1795
|
// src/commands/compute/app/deploy.ts
|
|
1623
1796
|
var AppDeploy = class _AppDeploy extends Command {
|
|
1624
1797
|
static description = "Deploy new app";
|
|
@@ -1652,7 +1825,7 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1652
1825
|
}),
|
|
1653
1826
|
"log-visibility": Flags2.string({
|
|
1654
1827
|
required: false,
|
|
1655
|
-
description: "Log visibility setting: public, private, or off",
|
|
1828
|
+
description: "Log visibility setting: public, private, or off (non-interactive default: private)",
|
|
1656
1829
|
options: ["public", "private", "off"],
|
|
1657
1830
|
env: "ECLOUD_LOG_VISIBILITY"
|
|
1658
1831
|
}),
|
|
@@ -1668,7 +1841,7 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1668
1841
|
}),
|
|
1669
1842
|
"resource-usage-monitoring": Flags2.string({
|
|
1670
1843
|
required: false,
|
|
1671
|
-
description: "Resource usage monitoring: enable or disable",
|
|
1844
|
+
description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
|
|
1672
1845
|
options: ["enable", "disable"],
|
|
1673
1846
|
env: "ECLOUD_RESOURCE_USAGE_MONITORING"
|
|
1674
1847
|
}),
|
|
@@ -1722,12 +1895,40 @@ var AppDeploy = class _AppDeploy extends Command {
|
|
|
1722
1895
|
}),
|
|
1723
1896
|
force: Flags2.boolean({
|
|
1724
1897
|
description: "Skip all confirmation prompts",
|
|
1725
|
-
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"
|
|
1726
1904
|
})
|
|
1727
1905
|
};
|
|
1728
1906
|
async run() {
|
|
1729
1907
|
return withTelemetry(this, async () => {
|
|
1730
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
|
+
}
|
|
1731
1932
|
const compute = await createComputeClient(flags);
|
|
1732
1933
|
const environment = flags.environment;
|
|
1733
1934
|
const environmentConfig = getEnvironmentConfig4(environment);
|
|
@@ -1830,7 +2031,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1830
2031
|
buildContextPath: flags["build-context"],
|
|
1831
2032
|
dependencies: flags["build-dependencies"]
|
|
1832
2033
|
} : await promptVerifiableGitSourceInputs();
|
|
1833
|
-
envFilePath = await
|
|
2034
|
+
envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
|
|
1834
2035
|
const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
|
|
1835
2036
|
if (includeTlsCaddyfile && !inputs.caddyfilePath) {
|
|
1836
2037
|
inputs.caddyfilePath = "Caddyfile";
|
|
@@ -1892,7 +2093,8 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1892
2093
|
}
|
|
1893
2094
|
}
|
|
1894
2095
|
const isVerifiable = verifiableMode !== "none";
|
|
1895
|
-
const
|
|
2096
|
+
const deployExistingImageRef = !!flags["image-ref"] && !flags.dockerfile;
|
|
2097
|
+
const dockerfilePath = isVerifiable || deployExistingImageRef ? "" : await getDockerfile(flags.dockerfile, nonInteractive);
|
|
1896
2098
|
const buildFromDockerfile = dockerfilePath !== "";
|
|
1897
2099
|
const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
|
|
1898
2100
|
const appName = await getOrPromptAppName(
|
|
@@ -1902,7 +2104,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1902
2104
|
suggestedAppBaseName,
|
|
1903
2105
|
skipDefaultAppName
|
|
1904
2106
|
);
|
|
1905
|
-
envFilePath = envFilePath ?? await
|
|
2107
|
+
envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
|
|
1906
2108
|
if (flags.env && flags.env.length > 0) {
|
|
1907
2109
|
envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
|
|
1908
2110
|
}
|
|
@@ -1912,38 +2114,48 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
|
|
|
1912
2114
|
privateKey,
|
|
1913
2115
|
rpcUrl
|
|
1914
2116
|
);
|
|
1915
|
-
const instanceType = await
|
|
2117
|
+
const instanceType = await getInstanceType(
|
|
1916
2118
|
flags["instance-type"],
|
|
1917
2119
|
"",
|
|
1918
|
-
// No default for new deployments
|
|
1919
|
-
availableTypes
|
|
2120
|
+
// No pinned default for new deployments; non-interactive falls back to g1-standard-2s
|
|
2121
|
+
availableTypes,
|
|
2122
|
+
nonInteractive
|
|
1920
2123
|
);
|
|
1921
|
-
const logSettings = await
|
|
1922
|
-
flags["log-visibility"]
|
|
2124
|
+
const logSettings = await getLogSettings(
|
|
2125
|
+
flags["log-visibility"],
|
|
2126
|
+
nonInteractive
|
|
1923
2127
|
);
|
|
1924
|
-
const resourceUsageMonitoring = await
|
|
1925
|
-
flags["resource-usage-monitoring"]
|
|
2128
|
+
const resourceUsageMonitoring = await getResourceUsageMonitoring(
|
|
2129
|
+
flags["resource-usage-monitoring"],
|
|
2130
|
+
nonInteractive
|
|
1926
2131
|
);
|
|
1927
2132
|
const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
+
}
|
|
1947
2159
|
const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
|
|
1948
2160
|
if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
|
|
1949
2161
|
this.log(
|
|
@@ -1966,7 +2178,13 @@ ${chalk2.gray(`Deployment cancelled`)}`);
|
|
|
1966
2178
|
return;
|
|
1967
2179
|
}
|
|
1968
2180
|
}
|
|
1969
|
-
|
|
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
|
+
}
|
|
1970
2188
|
if (!flags["skip-profile"]) {
|
|
1971
2189
|
const hasProfileFlags = flags.website || flags.description || flags["x-url"] || flags.image;
|
|
1972
2190
|
let profile = null;
|
|
@@ -2009,7 +2227,42 @@ ${chalk2.gray(`Deployment cancelled`)}`);
|
|
|
2009
2227
|
}
|
|
2010
2228
|
}
|
|
2011
2229
|
}
|
|
2012
|
-
|
|
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
|
+
}
|
|
2013
2266
|
try {
|
|
2014
2267
|
const cwd = process.env.INIT_CWD || process.cwd();
|
|
2015
2268
|
setLinkedAppForDirectory(environment, cwd, res.appId);
|
|
@@ -2035,26 +2288,6 @@ Note: "Running" means the container started \u2014 verify it is serving traffic
|
|
|
2035
2288
|
});
|
|
2036
2289
|
}
|
|
2037
2290
|
};
|
|
2038
|
-
async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
|
|
2039
|
-
try {
|
|
2040
|
-
const { publicClient, walletClient } = createViemClients({
|
|
2041
|
-
privateKey,
|
|
2042
|
-
rpcUrl,
|
|
2043
|
-
environment
|
|
2044
|
-
});
|
|
2045
|
-
const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
|
|
2046
|
-
clientId: getClientId()
|
|
2047
|
-
});
|
|
2048
|
-
const skuList = await userApiClient.getSKUs();
|
|
2049
|
-
if (skuList.skus.length === 0) {
|
|
2050
|
-
throw new Error("No instance types available from server");
|
|
2051
|
-
}
|
|
2052
|
-
return skuList.skus;
|
|
2053
|
-
} catch (err) {
|
|
2054
|
-
console.warn(`Failed to fetch instance types: ${err.message}`);
|
|
2055
|
-
return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
2291
|
export {
|
|
2059
2292
|
AppDeploy as default
|
|
2060
2293
|
};
|