@layr-labs/ecloud-cli 0.1.2 → 0.2.0-dev.1

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.
Files changed (56) hide show
  1. package/VERSION +2 -2
  2. package/dist/commands/auth/generate.js.map +1 -1
  3. package/dist/commands/auth/login.js.map +1 -1
  4. package/dist/commands/auth/logout.js.map +1 -1
  5. package/dist/commands/auth/migrate.js.map +1 -1
  6. package/dist/commands/auth/whoami.js.map +1 -1
  7. package/dist/commands/billing/cancel.js +1 -0
  8. package/dist/commands/billing/cancel.js.map +1 -1
  9. package/dist/commands/billing/status.js +1 -0
  10. package/dist/commands/billing/status.js.map +1 -1
  11. package/dist/commands/billing/subscribe.js +2 -1
  12. package/dist/commands/billing/subscribe.js.map +1 -1
  13. package/dist/commands/compute/app/create.js.map +1 -1
  14. package/dist/commands/compute/app/deploy.js +477 -16
  15. package/dist/commands/compute/app/deploy.js.map +1 -1
  16. package/dist/commands/compute/app/info.js +6 -3
  17. package/dist/commands/compute/app/info.js.map +1 -1
  18. package/dist/commands/compute/app/list.js +4 -2
  19. package/dist/commands/compute/app/list.js.map +1 -1
  20. package/dist/commands/compute/app/logs.js +7 -3
  21. package/dist/commands/compute/app/logs.js.map +1 -1
  22. package/dist/commands/compute/app/profile/set.js +7 -3
  23. package/dist/commands/compute/app/profile/set.js.map +1 -1
  24. package/dist/commands/compute/app/releases.js +1111 -0
  25. package/dist/commands/compute/app/releases.js.map +1 -0
  26. package/dist/commands/compute/app/start.js +7 -3
  27. package/dist/commands/compute/app/start.js.map +1 -1
  28. package/dist/commands/compute/app/stop.js +7 -3
  29. package/dist/commands/compute/app/stop.js.map +1 -1
  30. package/dist/commands/compute/app/terminate.js +7 -3
  31. package/dist/commands/compute/app/terminate.js.map +1 -1
  32. package/dist/commands/compute/app/upgrade.js +449 -9
  33. package/dist/commands/compute/app/upgrade.js.map +1 -1
  34. package/dist/commands/compute/build/info.js +500 -0
  35. package/dist/commands/compute/build/info.js.map +1 -0
  36. package/dist/commands/compute/build/list.js +494 -0
  37. package/dist/commands/compute/build/list.js.map +1 -0
  38. package/dist/commands/compute/build/logs.js +459 -0
  39. package/dist/commands/compute/build/logs.js.map +1 -0
  40. package/dist/commands/compute/build/status.js +481 -0
  41. package/dist/commands/compute/build/status.js.map +1 -0
  42. package/dist/commands/compute/build/submit.js +618 -0
  43. package/dist/commands/compute/build/submit.js.map +1 -0
  44. package/dist/commands/compute/build/verify.js +439 -0
  45. package/dist/commands/compute/build/verify.js.map +1 -0
  46. package/dist/commands/compute/environment/list.js.map +1 -1
  47. package/dist/commands/compute/environment/set.js.map +1 -1
  48. package/dist/commands/compute/environment/show.js.map +1 -1
  49. package/dist/commands/compute/undelegate.js +6 -3
  50. package/dist/commands/compute/undelegate.js.map +1 -1
  51. package/dist/commands/telemetry/disable.js.map +1 -1
  52. package/dist/commands/telemetry/enable.js.map +1 -1
  53. package/dist/commands/telemetry/status.js.map +1 -1
  54. package/dist/commands/upgrade.js.map +1 -1
  55. package/dist/commands/version.js.map +1 -1
  56. package/package.json +6 -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 getEnvironmentConfig3, isMainnet, UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
5
+ import { getEnvironmentConfig as getEnvironmentConfig3, UserApiClient as UserApiClient3, isMainnet } from "@layr-labs/ecloud-sdk";
6
6
 
7
7
  // src/telemetry.ts
8
8
  import {
@@ -273,7 +273,7 @@ function findAvailableName(environment, baseName) {
273
273
 
274
274
  // src/utils/version.ts
275
275
  function getCliVersion() {
276
- return true ? "0.1.2" : "0.0.0";
276
+ return true ? "0.2.0-dev.1" : "0.0.0";
277
277
  }
278
278
  function getClientId() {
279
279
  return `ecloud-cli/v${getCliVersion()}`;
@@ -307,6 +307,113 @@ Found Dockerfile in ${cwd}`);
307
307
  throw new Error(`Unexpected choice: ${choice}`);
308
308
  }
309
309
  }
310
+ async function promptUseVerifiableBuild() {
311
+ return confirmWithDefault("Build from verifiable source?", false);
312
+ }
313
+ async function promptVerifiableSourceType() {
314
+ return select({
315
+ message: "Choose verifiable source type:",
316
+ choices: [
317
+ { name: "Build from git source (public repo required)", value: "git" },
318
+ { name: "Use a prebuilt verifiable image (eigencloud-containers)", value: "prebuilt" }
319
+ ]
320
+ });
321
+ }
322
+ async function promptVerifiableGitSourceInputs() {
323
+ const repoUrl = (await input({
324
+ message: "Enter public git repository URL:",
325
+ default: "",
326
+ validate: (value) => {
327
+ if (!value.trim()) return "Repository URL is required";
328
+ try {
329
+ const url = new URL(value.trim());
330
+ if (url.protocol !== "https:") return "Repository URL must start with https://";
331
+ if (url.hostname.toLowerCase() !== "github.com")
332
+ return "Repository URL must be a public GitHub HTTPS URL (github.com)";
333
+ const parts = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
334
+ if (parts.length < 2) return "Repository URL must be https://github.com/<owner>/<repo>";
335
+ const [owner, repo] = parts;
336
+ if (!owner || !repo) return "Repository URL must be https://github.com/<owner>/<repo>";
337
+ if (repo.toLowerCase() === "settings") return "Repository URL looks invalid";
338
+ if (url.search || url.hash)
339
+ return "Repository URL must not include query params or fragments";
340
+ } catch {
341
+ return "Invalid URL format";
342
+ }
343
+ return true;
344
+ }
345
+ })).trim();
346
+ const gitRef = (await input({
347
+ message: "Enter git commit SHA (40 hex chars):",
348
+ default: "",
349
+ validate: (value) => {
350
+ const trimmed = value.trim();
351
+ if (!trimmed) return "Commit SHA is required";
352
+ if (!/^[0-9a-f]{40}$/i.test(trimmed))
353
+ return "Commit must be a 40-character hexadecimal SHA";
354
+ return true;
355
+ }
356
+ })).trim();
357
+ const buildContextPath = (await input({
358
+ message: "Enter build context path (relative to repo):",
359
+ default: ".",
360
+ validate: (value) => value.trim() ? true : "Build context path cannot be empty"
361
+ })).trim();
362
+ const dockerfilePath = (await input({
363
+ message: "Enter Dockerfile path (relative to build context):",
364
+ default: "Dockerfile",
365
+ validate: (value) => value.trim() ? true : "Dockerfile path cannot be empty"
366
+ })).trim();
367
+ const caddyfileRaw = (await input({
368
+ message: "Enter Caddyfile path (relative to build context, optional):",
369
+ default: "",
370
+ validate: (value) => {
371
+ const trimmed = value.trim();
372
+ if (!trimmed) return true;
373
+ if (trimmed.includes("..")) return "Caddyfile path must not contain '..'";
374
+ return true;
375
+ }
376
+ })).trim();
377
+ const depsRaw = (await input({
378
+ message: "Enter dependency digests (comma-separated sha256:..., optional):",
379
+ default: "",
380
+ validate: (value) => {
381
+ const trimmed = value.trim();
382
+ if (!trimmed) return true;
383
+ const parts = trimmed.split(",").map((p) => p.trim()).filter(Boolean);
384
+ for (const p of parts) {
385
+ if (!/^sha256:[0-9a-f]{64}$/i.test(p)) {
386
+ return `Invalid dependency digest: ${p} (expected sha256:<64 hex>)`;
387
+ }
388
+ }
389
+ return true;
390
+ }
391
+ })).trim();
392
+ const dependencies = depsRaw === "" ? [] : depsRaw.split(",").map((p) => p.trim()).filter(Boolean);
393
+ return {
394
+ repoUrl,
395
+ gitRef,
396
+ dockerfilePath,
397
+ caddyfilePath: caddyfileRaw === "" ? void 0 : caddyfileRaw,
398
+ buildContextPath,
399
+ dependencies
400
+ };
401
+ }
402
+ async function promptVerifiablePrebuiltImageRef() {
403
+ const ref = await input({
404
+ message: "Enter prebuilt verifiable image ref:",
405
+ default: "docker.io/eigenlayer/eigencloud-containers:",
406
+ validate: (value) => {
407
+ const trimmed = value.trim();
408
+ if (!trimmed) return "Image reference is required";
409
+ if (!/^docker\.io\/eigenlayer\/eigencloud-containers:[^@\s]+$/i.test(trimmed)) {
410
+ return "Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>";
411
+ }
412
+ return true;
413
+ }
414
+ });
415
+ return ref.trim();
416
+ }
310
417
  function extractHostname(registry) {
311
418
  let hostname = registry.replace(/^https?:\/\//, "");
312
419
  hostname = hostname.split("/")[0];
@@ -555,9 +662,9 @@ async function getImageReferenceInteractive(imageRef, buildFromDockerfile = fals
555
662
  });
556
663
  return imageRefInput;
557
664
  }
558
- async function getAvailableAppNameInteractive(environment, imageRef) {
559
- const baseName = extractAppNameFromImage(imageRef);
560
- const suggestedName = findAvailableName(environment, baseName);
665
+ async function getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName) {
666
+ const baseName = skipDefaultName ? void 0 : suggestedBaseName || extractAppNameFromImage(imageRef);
667
+ const suggestedName = baseName ? findAvailableName(environment, baseName) : void 0;
561
668
  while (true) {
562
669
  console.log("\nApp name selection:");
563
670
  const name = await input({
@@ -580,16 +687,16 @@ async function getAvailableAppNameInteractive(environment, imageRef) {
580
687
  console.log(`Suggested alternative: ${newSuggested}`);
581
688
  }
582
689
  }
583
- async function getOrPromptAppName(appName, environment, imageRef) {
690
+ async function getOrPromptAppName(appName, environment, imageRef, suggestedBaseName, skipDefaultName) {
584
691
  if (appName) {
585
692
  validateAppName(appName);
586
693
  if (isAppNameAvailable(environment, appName)) {
587
694
  return appName;
588
695
  }
589
696
  console.log(`Warning: App name '${appName}' is already taken.`);
590
- return getAvailableAppNameInteractive(environment, imageRef);
697
+ return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
591
698
  }
592
- return getAvailableAppNameInteractive(environment, imageRef);
699
+ return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
593
700
  }
594
701
  async function getEnvFileInteractive(envFilePath) {
595
702
  if (envFilePath && fs3.existsSync(envFilePath)) {
@@ -1037,9 +1144,11 @@ var commonFlags = {
1037
1144
  default: false
1038
1145
  })
1039
1146
  };
1040
- async function validateCommonFlags(flags) {
1147
+ async function validateCommonFlags(flags, options) {
1041
1148
  flags["environment"] = await getEnvironmentInteractive(flags["environment"]);
1042
- flags["private-key"] = await getPrivateKeyInteractive(flags["private-key"]);
1149
+ if (options?.requirePrivateKey !== false) {
1150
+ flags["private-key"] = await getPrivateKeyInteractive(flags["private-key"]);
1151
+ }
1043
1152
  return flags;
1044
1153
  }
1045
1154
 
@@ -1047,6 +1156,7 @@ async function validateCommonFlags(flags) {
1047
1156
  import {
1048
1157
  createComputeModule,
1049
1158
  createBillingModule,
1159
+ createBuildModule,
1050
1160
  getEnvironmentConfig as getEnvironmentConfig2,
1051
1161
  requirePrivateKey,
1052
1162
  getPrivateKeyWithSource
@@ -1072,9 +1182,189 @@ async function createComputeClient(flags) {
1072
1182
  // CLI already has telemetry, skip SDK telemetry
1073
1183
  });
1074
1184
  }
1185
+ async function createBuildClient(flags) {
1186
+ flags = await validateCommonFlags(flags, { requirePrivateKey: false });
1187
+ return createBuildModule({
1188
+ verbose: flags.verbose,
1189
+ privateKey: flags["private-key"],
1190
+ environment: flags.environment,
1191
+ clientId: getClientId(),
1192
+ skipTelemetry: true
1193
+ // CLI already has telemetry, skip SDK telemetry
1194
+ });
1195
+ }
1075
1196
 
1076
1197
  // src/commands/compute/app/deploy.ts
1077
1198
  import chalk from "chalk";
1199
+
1200
+ // src/utils/build.ts
1201
+ function formatSourceLink(repoUrl, gitRef) {
1202
+ const normalizedRepo = repoUrl.replace(/\.git$/, "");
1203
+ try {
1204
+ const url = new URL(normalizedRepo);
1205
+ const host = url.host.toLowerCase();
1206
+ if (host === "github.com") {
1207
+ const path4 = url.pathname.replace(/\/+$/, "");
1208
+ if (path4.split("/").filter(Boolean).length >= 2) {
1209
+ return `https://github.com${path4}/tree/${gitRef}`;
1210
+ }
1211
+ }
1212
+ } catch {
1213
+ }
1214
+ return `${repoUrl}@${gitRef}`;
1215
+ }
1216
+ function extractRepoName(repoUrl) {
1217
+ const normalized = repoUrl.replace(/\.git$/, "");
1218
+ const match = normalized.match(/\/([^/]+?)$/);
1219
+ return match?.[1];
1220
+ }
1221
+ function formatDependencyLines(dependencies) {
1222
+ if (!dependencies || Object.keys(dependencies).length === 0) return [];
1223
+ const lines = [];
1224
+ lines.push("Dependencies (resolved builds):");
1225
+ for (const [digest, dep] of Object.entries(dependencies)) {
1226
+ const name = extractRepoName(dep.repoUrl);
1227
+ const depSource = formatSourceLink(dep.repoUrl, dep.gitRef);
1228
+ lines.push(` - ${digest} \u2713${name ? ` ${name}` : ""}`);
1229
+ lines.push(` ${depSource}`);
1230
+ }
1231
+ return lines;
1232
+ }
1233
+ function formatVerifiableBuildSummary(options) {
1234
+ const lines = [];
1235
+ lines.push("Build completed successfully \u2713");
1236
+ lines.push("");
1237
+ lines.push(`Image: ${options.imageUrl}`);
1238
+ lines.push(`Digest: ${options.imageDigest}`);
1239
+ lines.push(`Source: ${formatSourceLink(options.repoUrl, options.gitRef)}`);
1240
+ const depLines = formatDependencyLines(options.dependencies);
1241
+ if (depLines.length) {
1242
+ lines.push("");
1243
+ lines.push(...depLines);
1244
+ }
1245
+ lines.push("");
1246
+ lines.push("Provenance signature verified \u2713");
1247
+ lines.push(`provenance_signature: ${options.provenanceSignature}`);
1248
+ if (options.buildId) {
1249
+ lines.push("");
1250
+ lines.push(`Build ID: ${options.buildId}`);
1251
+ }
1252
+ return lines;
1253
+ }
1254
+
1255
+ // src/utils/verifiableBuild.ts
1256
+ import { BUILD_STATUS } from "@layr-labs/ecloud-sdk";
1257
+ function assertCommitSha40(commit) {
1258
+ if (!/^[0-9a-f]{40}$/i.test(commit)) {
1259
+ throw new Error("Commit must be a 40-character hexadecimal SHA");
1260
+ }
1261
+ }
1262
+ async function runVerifiableBuildAndVerify(client, request, options = {}) {
1263
+ const { buildId } = await client.submit(request);
1264
+ const completed = await client.waitForBuild(buildId, { onLog: options.onLog });
1265
+ if (completed.status !== BUILD_STATUS.SUCCESS) {
1266
+ throw new Error(`Build did not complete successfully (status: ${completed.status})`);
1267
+ }
1268
+ const [build, verify] = await Promise.all([client.get(buildId), client.verify(buildId)]);
1269
+ if (verify.status !== "verified") {
1270
+ throw new Error(`Provenance verification failed: ${verify.error}`);
1271
+ }
1272
+ return { build, verified: verify };
1273
+ }
1274
+
1275
+ // src/utils/dockerhub.ts
1276
+ var DOCKERHUB_OWNER = "eigenlayer";
1277
+ var DOCKERHUB_REPO = "eigencloud-containers";
1278
+ function parseEigencloudContainersImageRef(imageRef) {
1279
+ const trimmed = imageRef.trim();
1280
+ const match = /^docker\.io\/([^/]+)\/([^:@]+):([^@\s]+)$/i.exec(trimmed);
1281
+ if (!match) {
1282
+ throw new Error("Image ref must match docker.io/eigenlayer/eigencloud-containers:<tag>");
1283
+ }
1284
+ const owner = match[1].toLowerCase();
1285
+ const repo = match[2].toLowerCase();
1286
+ const tag = match[3];
1287
+ if (owner !== DOCKERHUB_OWNER || repo !== DOCKERHUB_REPO) {
1288
+ throw new Error(`Image ref must be from docker.io/${DOCKERHUB_OWNER}/${DOCKERHUB_REPO}:<tag>`);
1289
+ }
1290
+ if (!tag.trim()) {
1291
+ throw new Error("Image tag cannot be empty");
1292
+ }
1293
+ return { owner, repo, tag };
1294
+ }
1295
+ function assertEigencloudContainersImageRef(imageRef) {
1296
+ parseEigencloudContainersImageRef(imageRef);
1297
+ }
1298
+ async function getDockerHubToken(owner, repo) {
1299
+ const url = new URL("https://auth.docker.io/token");
1300
+ url.searchParams.set("service", "registry.docker.io");
1301
+ url.searchParams.set("scope", `repository:${owner}/${repo}:pull`);
1302
+ const res = await fetch(url.toString(), { method: "GET" });
1303
+ if (!res.ok) {
1304
+ const body = await safeReadText(res);
1305
+ throw new Error(`Failed to fetch Docker Hub token (${res.status}): ${body || res.statusText}`);
1306
+ }
1307
+ const data = await res.json();
1308
+ if (!data.token) {
1309
+ throw new Error("Docker Hub token response missing 'token'");
1310
+ }
1311
+ return data.token;
1312
+ }
1313
+ async function safeReadText(res) {
1314
+ try {
1315
+ return (await res.text()).trim();
1316
+ } catch {
1317
+ return "";
1318
+ }
1319
+ }
1320
+ async function resolveDockerHubImageDigest(imageRef) {
1321
+ const { owner, repo, tag } = parseEigencloudContainersImageRef(imageRef);
1322
+ const token = await getDockerHubToken(owner, repo);
1323
+ const manifestUrl = `https://registry-1.docker.io/v2/${owner}/${repo}/manifests/${encodeURIComponent(tag)}`;
1324
+ const headers = {
1325
+ Authorization: `Bearer ${token}`,
1326
+ Accept: "application/vnd.docker.distribution.manifest.v2+json"
1327
+ };
1328
+ let res = await fetch(manifestUrl, { method: "HEAD", headers });
1329
+ if (!res.ok) {
1330
+ res = await fetch(manifestUrl, { method: "GET", headers });
1331
+ }
1332
+ if (!res.ok) {
1333
+ const body = await safeReadText(res);
1334
+ throw new Error(
1335
+ `Failed to resolve digest for ${imageRef} (${res.status}) at ${manifestUrl}: ${body || res.statusText}`
1336
+ );
1337
+ }
1338
+ const digest = res.headers.get("docker-content-digest") || res.headers.get("Docker-Content-Digest");
1339
+ if (!digest) {
1340
+ throw new Error(
1341
+ `Docker registry response missing Docker-Content-Digest header for ${imageRef}`
1342
+ );
1343
+ }
1344
+ if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
1345
+ throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
1346
+ }
1347
+ return digest;
1348
+ }
1349
+
1350
+ // src/utils/tls.ts
1351
+ import fs4 from "fs";
1352
+ function isTlsEnabledFromDomain(domain) {
1353
+ const d = (domain ?? "").trim();
1354
+ if (!d) return false;
1355
+ if (d.toLowerCase() === "localhost") return false;
1356
+ return true;
1357
+ }
1358
+ function isTlsEnabledFromEnvFile(envFilePath) {
1359
+ if (!envFilePath) return false;
1360
+ if (!fs4.existsSync(envFilePath)) return false;
1361
+ const envContent = fs4.readFileSync(envFilePath, "utf-8");
1362
+ const match = envContent.match(/^DOMAIN=(.+)$/m);
1363
+ if (!match?.[1]) return false;
1364
+ return isTlsEnabledFromDomain(match[1]);
1365
+ }
1366
+
1367
+ // src/commands/compute/app/deploy.ts
1078
1368
  var AppDeploy = class _AppDeploy extends Command {
1079
1369
  static description = "Deploy new app";
1080
1370
  static flags = {
@@ -1137,6 +1427,33 @@ var AppDeploy = class _AppDeploy extends Command {
1137
1427
  image: Flags2.string({
1138
1428
  required: false,
1139
1429
  description: "Path to app icon/logo image - JPG/PNG, max 4MB, square recommended (optional)"
1430
+ }),
1431
+ // Verifiable build flags
1432
+ verifiable: Flags2.boolean({
1433
+ description: "Enable verifiable build mode (either build from git source via --repo/--commit, or deploy a prebuilt verifiable image via --image-ref)",
1434
+ default: false
1435
+ }),
1436
+ repo: Flags2.string({
1437
+ description: "Git repository URL (required with --verifiable git source mode)",
1438
+ env: "ECLOUD_BUILD_REPO"
1439
+ }),
1440
+ commit: Flags2.string({
1441
+ description: "Git commit SHA (required with --verifiable git source mode)",
1442
+ env: "ECLOUD_BUILD_COMMIT"
1443
+ }),
1444
+ "build-dockerfile": Flags2.string({
1445
+ description: "Dockerfile path for verifiable build (git source mode)",
1446
+ default: "Dockerfile",
1447
+ env: "ECLOUD_BUILD_DOCKERFILE"
1448
+ }),
1449
+ "build-context": Flags2.string({
1450
+ description: "Build context path for verifiable build (git source mode)",
1451
+ default: ".",
1452
+ env: "ECLOUD_BUILD_CONTEXT"
1453
+ }),
1454
+ "build-dependencies": Flags2.string({
1455
+ description: "Dependency digests for verifiable build (git source mode) (sha256:...)",
1456
+ multiple: true
1140
1457
  })
1141
1458
  };
1142
1459
  async run() {
@@ -1147,11 +1464,147 @@ var AppDeploy = class _AppDeploy extends Command {
1147
1464
  const environmentConfig = getEnvironmentConfig3(environment);
1148
1465
  const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
1149
1466
  const privateKey = flags["private-key"];
1150
- const dockerfilePath = await getDockerfileInteractive(flags.dockerfile);
1467
+ let buildClient;
1468
+ const getBuildClient = async () => {
1469
+ if (buildClient) return buildClient;
1470
+ buildClient = await createBuildClient({
1471
+ ...flags,
1472
+ "private-key": privateKey
1473
+ });
1474
+ return buildClient;
1475
+ };
1476
+ let verifiableImageUrl;
1477
+ let verifiableImageDigest;
1478
+ let suggestedAppBaseName;
1479
+ let skipDefaultAppName = false;
1480
+ let verifiableMode = "none";
1481
+ let envFilePath;
1482
+ const suggestAppBaseNameFromRepoUrl = (repoUrl) => {
1483
+ const normalized = String(repoUrl || "").trim().replace(/\.git$/i, "").replace(/\/+$/, "");
1484
+ if (!normalized) return void 0;
1485
+ const lastSlash = normalized.lastIndexOf("/");
1486
+ const lastColon = normalized.lastIndexOf(":");
1487
+ const idx = Math.max(lastSlash, lastColon);
1488
+ const raw = (idx >= 0 ? normalized.slice(idx + 1) : normalized).trim();
1489
+ if (!raw) return void 0;
1490
+ const cleaned = raw.toLowerCase().replace(/_/g, "-").replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1491
+ return cleaned || void 0;
1492
+ };
1493
+ if (flags.verifiable) {
1494
+ if (flags.repo || flags.commit) {
1495
+ verifiableMode = "git";
1496
+ if (!flags.repo)
1497
+ this.error("--repo is required when using --verifiable (git source mode)");
1498
+ if (!flags.commit)
1499
+ this.error("--commit is required when using --verifiable (git source mode)");
1500
+ try {
1501
+ assertCommitSha40(flags.commit);
1502
+ } catch (e) {
1503
+ this.error(e?.message || String(e));
1504
+ }
1505
+ } else if (flags["image-ref"]) {
1506
+ verifiableMode = "prebuilt";
1507
+ try {
1508
+ assertEigencloudContainersImageRef(flags["image-ref"]);
1509
+ } catch (e) {
1510
+ this.error(e?.message || String(e));
1511
+ }
1512
+ } else {
1513
+ this.error(
1514
+ "When using --verifiable, you must provide either --repo/--commit or --image-ref"
1515
+ );
1516
+ }
1517
+ } else {
1518
+ if (!flags.dockerfile) {
1519
+ const useVerifiable = await promptUseVerifiableBuild();
1520
+ if (useVerifiable) {
1521
+ const sourceType = await promptVerifiableSourceType();
1522
+ verifiableMode = sourceType;
1523
+ }
1524
+ }
1525
+ }
1526
+ if (verifiableMode === "git") {
1527
+ const inputs = flags.verifiable ? {
1528
+ repoUrl: flags.repo,
1529
+ gitRef: flags.commit,
1530
+ dockerfilePath: flags["build-dockerfile"],
1531
+ caddyfilePath: void 0,
1532
+ buildContextPath: flags["build-context"],
1533
+ dependencies: flags["build-dependencies"]
1534
+ } : await promptVerifiableGitSourceInputs();
1535
+ envFilePath = await getEnvFileInteractive(flags["env-file"]);
1536
+ const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
1537
+ if (includeTlsCaddyfile && !inputs.caddyfilePath) {
1538
+ inputs.caddyfilePath = "Caddyfile";
1539
+ }
1540
+ this.log(chalk.blue("Building from source with verifiable build..."));
1541
+ this.log("");
1542
+ const buildClient2 = await getBuildClient();
1543
+ const { build, verified } = await runVerifiableBuildAndVerify(buildClient2, inputs, {
1544
+ onLog: (chunk) => process.stdout.write(chunk)
1545
+ });
1546
+ if (!build.imageUrl || !build.imageDigest) {
1547
+ this.error(
1548
+ "Build completed but did not return imageUrl/imageDigest; cannot deploy verifiable build"
1549
+ );
1550
+ }
1551
+ verifiableImageUrl = build.imageUrl;
1552
+ verifiableImageDigest = build.imageDigest;
1553
+ suggestedAppBaseName = suggestAppBaseNameFromRepoUrl(build.repoUrl);
1554
+ for (const line of formatVerifiableBuildSummary({
1555
+ buildId: build.buildId,
1556
+ imageUrl: build.imageUrl,
1557
+ imageDigest: build.imageDigest,
1558
+ repoUrl: build.repoUrl,
1559
+ gitRef: build.gitRef,
1560
+ dependencies: build.dependencies,
1561
+ provenanceSignature: verified.provenanceSignature
1562
+ })) {
1563
+ this.log(line);
1564
+ }
1565
+ }
1566
+ if (verifiableMode === "prebuilt") {
1567
+ const imageRef2 = flags.verifiable ? flags["image-ref"] : await promptVerifiablePrebuiltImageRef();
1568
+ try {
1569
+ assertEigencloudContainersImageRef(imageRef2);
1570
+ } catch (e) {
1571
+ this.error(e?.message || String(e));
1572
+ }
1573
+ this.log(chalk.blue("Resolving and verifying prebuilt verifiable image..."));
1574
+ this.log("");
1575
+ const digest = await resolveDockerHubImageDigest(imageRef2);
1576
+ const buildClient2 = await getBuildClient();
1577
+ const verify = await buildClient2.verify(digest);
1578
+ if (verify.status !== "verified") {
1579
+ this.error(`Provenance verification failed: ${verify.error}`);
1580
+ }
1581
+ verifiableImageUrl = imageRef2;
1582
+ verifiableImageDigest = digest;
1583
+ skipDefaultAppName = true;
1584
+ for (const line of formatVerifiableBuildSummary({
1585
+ buildId: verify.buildId,
1586
+ imageUrl: imageRef2,
1587
+ imageDigest: digest,
1588
+ repoUrl: verify.repoUrl,
1589
+ gitRef: verify.gitRef,
1590
+ dependencies: void 0,
1591
+ provenanceSignature: verify.provenanceSignature
1592
+ })) {
1593
+ this.log(line);
1594
+ }
1595
+ }
1596
+ const isVerifiable = verifiableMode !== "none";
1597
+ const dockerfilePath = isVerifiable ? "" : await getDockerfileInteractive(flags.dockerfile);
1151
1598
  const buildFromDockerfile = dockerfilePath !== "";
1152
- const imageRef = await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
1153
- const appName = await getOrPromptAppName(flags.name, environment, imageRef);
1154
- const envFilePath = await getEnvFileInteractive(flags["env-file"]);
1599
+ const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
1600
+ const appName = await getOrPromptAppName(
1601
+ flags.name,
1602
+ environment,
1603
+ imageRef,
1604
+ suggestedAppBaseName,
1605
+ skipDefaultAppName
1606
+ );
1607
+ envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
1155
1608
  const availableTypes = await fetchAvailableInstanceTypes(
1156
1609
  environmentConfig,
1157
1610
  privateKey,
@@ -1170,7 +1623,15 @@ var AppDeploy = class _AppDeploy extends Command {
1170
1623
  flags["resource-usage-monitoring"]
1171
1624
  );
1172
1625
  const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
1173
- const { prepared, gasEstimate } = await compute.app.prepareDeploy({
1626
+ const { prepared, gasEstimate } = isVerifiable ? await compute.app.prepareDeployFromVerifiableBuild({
1627
+ name: appName,
1628
+ imageRef,
1629
+ imageDigest: verifiableImageDigest,
1630
+ envFile: envFilePath,
1631
+ instanceType,
1632
+ logVisibility,
1633
+ resourceUsageMonitoring
1634
+ }) : await compute.app.prepareDeploy({
1174
1635
  name: appName,
1175
1636
  dockerfile: dockerfilePath,
1176
1637
  imageRef,
@@ -1238,7 +1699,7 @@ ${chalk.gray(`Deployment cancelled`)}`);
1238
1699
  const cwd = process.env.INIT_CWD || process.cwd();
1239
1700
  setLinkedAppForDirectory(environment, cwd, res.appId);
1240
1701
  } catch (err) {
1241
- logger.debug(`Failed to link directory to app: ${err.message}`);
1702
+ this.debug(`Failed to link directory to app: ${err.message}`);
1242
1703
  }
1243
1704
  this.log(
1244
1705
  `