@verentis/cli 0.2.3 → 0.2.5

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/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { mkdir, stat, writeFile, readFile, readdir, mkdtemp, rm, glob } from 'fs/promises';
3
+ import { mkdir, stat, writeFile, readFile, readdir, realpath, mkdtemp, rm, glob } from 'fs/promises';
4
4
  import { tmpdir, homedir } from 'os';
5
- import { resolve, basename, dirname, posix, join, sep, relative } from 'path';
6
- import { spawn } from 'child_process';
5
+ import { resolve, basename, dirname, posix, join, isAbsolute, extname, relative, sep } from 'path';
6
+ import { spawn, spawnSync } from 'child_process';
7
7
  import { generateKeyPairSync, createHash, randomUUID, randomBytes, createCipheriv, createPublicKey, verify, diffieHellman, hkdfSync, sign, createPrivateKey } from 'crypto';
8
- import { parse } from 'yaml';
9
- import { createWriteStream, createReadStream } from 'fs';
8
+ import { parse, stringify } from 'yaml';
9
+ import { createWriteStream, readFileSync, createReadStream } from 'fs';
10
10
  import { Readable } from 'stream';
11
11
  import { pipeline } from 'stream/promises';
12
12
  import * as tar from 'tar';
13
+ import { fileURLToPath } from 'url';
13
14
 
14
15
  var configDir = () => process.env.VERENTIS_CONFIG_DIR ?? join(homedir(), ".verentis");
15
16
  var configPath = () => join(configDir(), "config.json");
@@ -129,7 +130,7 @@ async function refreshIdentityToken(apiUrl, refreshToken) {
129
130
  `Session could not be renewed${payload?.error ? ` (${payload.error})` : ` (${res.status})`}. Run \`verentis login\` again.`
130
131
  );
131
132
  }
132
- var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
133
+ var sleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, ms));
133
134
 
134
135
  // src/http.ts
135
136
  var ApiError = class extends Error {
@@ -192,8 +193,8 @@ async function createApiClient(overrides) {
192
193
  }
193
194
  async function credentialFor(scopes, workspaceId) {
194
195
  const cacheKey = `${workspaceId ?? ""}:${[...scopes].sort().join(" ")}`;
195
- const cached = tokenCache.get(cacheKey);
196
- if (cached && !isExpired(cached)) return cached;
196
+ const cached2 = tokenCache.get(cacheKey);
197
+ if (cached2 && !isExpired(cached2)) return cached2;
197
198
  const token = await mintAccessToken({ scopes, workspaceId });
198
199
  tokenCache.set(cacheKey, token);
199
200
  return token;
@@ -297,7 +298,6 @@ ${truncate(text)}`;
297
298
  }
298
299
  var SCOPES = {
299
300
  publisherRead: "marketplace.publisher.read",
300
- publisherCreate: "marketplace.publisher.create",
301
301
  publisherUpdate: "marketplace.publisher.update",
302
302
  packageRead: "marketplace.package.read",
303
303
  packageReadAll: "marketplace.package.read-all",
@@ -366,6 +366,8 @@ var MARKETPLACE = {
366
366
  packageRead: "marketplace.package.read",
367
367
  packageUpdate: "marketplace.package.update",
368
368
  packageVisibilityManage: "marketplace.package.visibility.manage",
369
+ publisherCreate: "marketplace.publisher.create",
370
+ publisherUpdate: "marketplace.publisher.update",
369
371
  statsRead: "marketplace.stats.read"};
370
372
  var ENGINE_DEFAULT_PERMISSIONS = [
371
373
  NODE.fileRead,
@@ -454,6 +456,10 @@ var execution = {
454
456
  syncEngines: e({ method: "POST", path: "/v1/engines/sync", scopes: [EXECUTION.run], workspaceBound: true })
455
457
  };
456
458
  var marketplace = {
459
+ createPublisher: e({ method: "POST", path: "/v1/publishers", scopes: [MARKETPLACE.publisherCreate] }),
460
+ updatePublisher: e({ method: "PUT", path: "/v1/publishers/{id}", scopes: [MARKETPLACE.publisherUpdate] }),
461
+ setPublisherLogo: e({ method: "PUT", path: "/v1/publishers/{id}/logo", scopes: [MARKETPLACE.publisherUpdate] }),
462
+ removePublisherLogo: e({ method: "DELETE", path: "/v1/publishers/{id}/logo", scopes: [MARKETPLACE.publisherUpdate] }),
457
463
  getPackage: e({ method: "GET", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageRead] }),
458
464
  updatePackage: e({ method: "PUT", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageUpdate] }),
459
465
  archivePackage: e({ method: "DELETE", path: "/v1/packages/{id}", scopes: [MARKETPLACE.packageDelete] }),
@@ -844,6 +850,122 @@ function contextEnv(context, contextPath) {
844
850
  VERENTIS_TIMEOUT: String(context.execution.timeout)
845
851
  };
846
852
  }
853
+ var KIND_MAP = {
854
+ application: "Application",
855
+ executionengine: "ExecutionEngine",
856
+ bundle: "Bundle"
857
+ };
858
+ function normalizeKind(kind) {
859
+ const mapped = KIND_MAP[kind.replace(/[^a-z]/gi, "").toLowerCase()];
860
+ if (!mapped) throw new Error(`Unsupported manifest kind '${kind}' \u2014 expected Application, ExecutionEngine or Bundle.`);
861
+ return mapped;
862
+ }
863
+ function validateLaunchManifestKind(kind, spec) {
864
+ if (kind === "Application" || !spec || typeof spec !== "object" || Array.isArray(spec)) return void 0;
865
+ if (!Object.prototype.hasOwnProperty.call(spec, "launch")) return void 0;
866
+ return "spec.launch is only supported for Application manifests";
867
+ }
868
+ async function loadManifest(path) {
869
+ const text = await readFile(path, "utf8");
870
+ const manifest = parse(text);
871
+ if (!manifest || typeof manifest !== "object") throw new Error(`Failed to parse manifest at ${path}.`);
872
+ if (!manifest.kind) throw new Error(`Manifest at ${path} is missing 'kind'.`);
873
+ if (!manifest.metadata?.name) throw new Error(`Manifest at ${path} is missing 'metadata.name'.`);
874
+ if (!manifest.metadata?.version) throw new Error(`Manifest at ${path} is missing 'metadata.version'.`);
875
+ const kind = normalizeKind(manifest.kind);
876
+ const launchKindError = validateLaunchManifestKind(kind, manifest.spec);
877
+ if (launchKindError) throw new Error(`Invalid manifest at ${path}: ${launchKindError}.`);
878
+ const marketplaceErrors = validateMarketplaceSection(manifest.marketplace);
879
+ if (marketplaceErrors.length > 0) {
880
+ throw new Error(`Invalid marketplace section in ${path}: ${marketplaceErrors.join(" ")}`);
881
+ }
882
+ return manifest;
883
+ }
884
+ function validateMarketplaceSection(marketplace2) {
885
+ if (marketplace2 === void 0) return [];
886
+ if (!marketplace2 || typeof marketplace2 !== "object" || Array.isArray(marketplace2)) {
887
+ return ["marketplace must be an object."];
888
+ }
889
+ const section = marketplace2;
890
+ const errors = [];
891
+ for (const field of ["logo", "readme", "changelog"]) {
892
+ const value = section[field];
893
+ if (value !== void 0 && (typeof value !== "string" || value.trim().length === 0)) {
894
+ errors.push(`marketplace.${field} must be a non-empty relative path.`);
895
+ }
896
+ }
897
+ if (section.hero !== void 0) {
898
+ if (!section.hero || typeof section.hero !== "object" || Array.isArray(section.hero)) {
899
+ errors.push("marketplace.hero must be an object.");
900
+ } else {
901
+ const hero = section.hero;
902
+ if (typeof hero.source !== "string" || hero.source.trim().length === 0) {
903
+ errors.push("marketplace.hero.source must be a non-empty relative path.");
904
+ }
905
+ if (typeof hero.alt !== "string" || hero.alt.trim().length === 0) {
906
+ errors.push("marketplace.hero.alt is required and must not be empty.");
907
+ }
908
+ }
909
+ }
910
+ if (section.images !== void 0) {
911
+ if (!Array.isArray(section.images)) {
912
+ errors.push("marketplace.images must be a list.");
913
+ } else {
914
+ if (section.images.length > 8) errors.push("marketplace.images may contain at most 8 images.");
915
+ section.images.forEach((value, index) => {
916
+ const at = `marketplace.images[${index}]`;
917
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
918
+ errors.push(`${at} must be an object.`);
919
+ return;
920
+ }
921
+ const image = value;
922
+ if (typeof image.source !== "string" || image.source.trim().length === 0) {
923
+ errors.push(`${at}.source must be a non-empty relative path.`);
924
+ }
925
+ if (typeof image.alt !== "string" || image.alt.trim().length === 0) {
926
+ errors.push(`${at}.alt is required and must not be empty.`);
927
+ }
928
+ if (image.caption !== void 0 && typeof image.caption !== "string") {
929
+ errors.push(`${at}.caption must be a string when provided.`);
930
+ }
931
+ });
932
+ }
933
+ }
934
+ if (section.assets !== void 0) {
935
+ if (!Array.isArray(section.assets)) {
936
+ errors.push("marketplace.assets must be a list of relative glob strings.");
937
+ } else {
938
+ section.assets.forEach((value, index) => {
939
+ if (typeof value !== "string" || value.trim().length === 0) {
940
+ errors.push(`marketplace.assets[${index}] must be a non-empty relative glob string.`);
941
+ }
942
+ });
943
+ }
944
+ }
945
+ return errors;
946
+ }
947
+ async function findManifest(dir) {
948
+ const explicit = join(dir, "manifest.yaml");
949
+ try {
950
+ if ((await stat(explicit)).isFile()) return explicit;
951
+ } catch {
952
+ }
953
+ const entries = await readdir(dir);
954
+ const candidates = entries.filter((name) => /\.(app|engine|bundle)\.ya?ml$/.test(name));
955
+ if (candidates.length === 1) return join(dir, candidates[0]);
956
+ if (candidates.length === 0) {
957
+ throw new Error(`No manifest found in ${dir}. Expected manifest.yaml or *.app.yaml / *.engine.yaml / *.bundle.yaml.`);
958
+ }
959
+ throw new Error(`Multiple manifests found in ${dir} (${candidates.join(", ")}). Pass --manifest to disambiguate.`);
960
+ }
961
+ var NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
962
+ function validatePackageName(name, label) {
963
+ if (!NAME_PATTERN.test(name)) {
964
+ throw new Error(`Invalid ${label} '${name}' \u2014 use lowercase letters, digits and hyphens (no leading/trailing hyphen).`);
965
+ }
966
+ }
967
+
968
+ // src/dev/manifest.ts
847
969
  async function readManifestDocument(path) {
848
970
  const text = await readFile(path, "utf8");
849
971
  const doc = parse(text);
@@ -873,6 +995,8 @@ function validateManifest(manifest) {
873
995
  if (!meta[field]) error(`metadata.${field} is required`);
874
996
  }
875
997
  const spec = manifest.spec ?? {};
998
+ const launchKindError = validateLaunchManifestKind(kind, spec);
999
+ if (launchKindError) error(launchKindError);
876
1000
  if (kind === "ExecutionEngine") {
877
1001
  const runtimes = spec.runtimes ?? {};
878
1002
  if (Object.keys(runtimes).length === 0) {
@@ -901,12 +1025,62 @@ function validateManifest(manifest) {
901
1025
  "an Application must declare at least one contribution: spec.entry / spec.entry-points[] (iframe UI), spec.install[] (installed files), or spec.scopes[] (identity)"
902
1026
  );
903
1027
  }
1028
+ validateApplicationLaunch(spec, issues);
904
1029
  }
905
1030
  validateInstallDirectives(spec.install, issues);
1031
+ for (const message of validateMarketplaceSection(manifest.marketplace)) error(message);
906
1032
  const packaging = manifest.packaging ?? {};
907
1033
  if (!packaging.publisher) warning("packaging.publisher is not set \u2014 `verentis pack`/`publish` will refuse the package");
908
1034
  return issues;
909
1035
  }
1036
+ function validateApplicationLaunch(spec, issues) {
1037
+ const launch = spec.launch;
1038
+ if (launch === void 0) return;
1039
+ const error = (message) => issues.push({ level: "error", message });
1040
+ if (!launch || typeof launch !== "object" || Array.isArray(launch)) {
1041
+ error("spec.launch must be an object");
1042
+ return;
1043
+ }
1044
+ if (launch.pinnable === true && launch.enabled !== true) {
1045
+ error("spec.launch.pinnable requires spec.launch.enabled");
1046
+ }
1047
+ if (launch.enabled !== true) return;
1048
+ const launchEntryPoint = launch["entry-point"];
1049
+ if (launchEntryPoint !== void 0 && (typeof launchEntryPoint !== "string" || !launchEntryPoint.trim())) {
1050
+ error("spec.launch.entry-point must be a non-empty entry-point id");
1051
+ return;
1052
+ }
1053
+ if (typeof launchEntryPoint === "string") {
1054
+ const entryPoint = spec["entry-points"]?.find((candidate) => candidate.id === launchEntryPoint);
1055
+ if (!entryPoint) {
1056
+ error(`spec.launch.entry-point '${launchEntryPoint}' does not match spec.entry-points[].id`);
1057
+ return;
1058
+ }
1059
+ if (!resolvesIframeEntry(spec.entry, entryPoint.entry ?? entryPoint.route)) {
1060
+ error("spec.launch.enabled requires the selected entry point to resolve an iframe entry");
1061
+ }
1062
+ return;
1063
+ }
1064
+ if (!isAbsoluteHttpUrl(spec.entry)) {
1065
+ error("spec.launch.enabled requires spec.entry or spec.launch.entry-point");
1066
+ }
1067
+ }
1068
+ function isNonEmptyString(value) {
1069
+ return typeof value === "string" && value.trim().length > 0;
1070
+ }
1071
+ function resolvesIframeEntry(baseEntry, routeOrEntry) {
1072
+ if (isAbsoluteHttpUrl(routeOrEntry)) return true;
1073
+ return isAbsoluteHttpUrl(baseEntry);
1074
+ }
1075
+ function isAbsoluteHttpUrl(value) {
1076
+ if (!isNonEmptyString(value)) return false;
1077
+ try {
1078
+ const url = new URL(value);
1079
+ return url.protocol === "http:" || url.protocol === "https:";
1080
+ } catch {
1081
+ return false;
1082
+ }
1083
+ }
910
1084
  function validateInstallDirectives(install, issues) {
911
1085
  if (install === void 0) return;
912
1086
  const error = (message) => issues.push({ level: "error", message });
@@ -936,46 +1110,6 @@ function validateInstallDirectives(install, issues) {
936
1110
  }
937
1111
  });
938
1112
  }
939
- var KIND_MAP = {
940
- application: "Application",
941
- executionengine: "ExecutionEngine",
942
- bundle: "Bundle"
943
- };
944
- function normalizeKind(kind) {
945
- const mapped = KIND_MAP[kind.replace(/[^a-z]/gi, "").toLowerCase()];
946
- if (!mapped) throw new Error(`Unsupported manifest kind '${kind}' \u2014 expected Application, ExecutionEngine or Bundle.`);
947
- return mapped;
948
- }
949
- async function loadManifest(path) {
950
- const text = await readFile(path, "utf8");
951
- const manifest = parse(text);
952
- if (!manifest || typeof manifest !== "object") throw new Error(`Failed to parse manifest at ${path}.`);
953
- if (!manifest.kind) throw new Error(`Manifest at ${path} is missing 'kind'.`);
954
- if (!manifest.metadata?.name) throw new Error(`Manifest at ${path} is missing 'metadata.name'.`);
955
- if (!manifest.metadata?.version) throw new Error(`Manifest at ${path} is missing 'metadata.version'.`);
956
- normalizeKind(manifest.kind);
957
- return manifest;
958
- }
959
- async function findManifest(dir) {
960
- const explicit = join(dir, "manifest.yaml");
961
- try {
962
- if ((await stat(explicit)).isFile()) return explicit;
963
- } catch {
964
- }
965
- const entries = await readdir(dir);
966
- const candidates = entries.filter((name) => /\.(app|engine|bundle)\.ya?ml$/.test(name));
967
- if (candidates.length === 1) return join(dir, candidates[0]);
968
- if (candidates.length === 0) {
969
- throw new Error(`No manifest found in ${dir}. Expected manifest.yaml or *.app.yaml / *.engine.yaml / *.bundle.yaml.`);
970
- }
971
- throw new Error(`Multiple manifests found in ${dir} (${candidates.join(", ")}). Pass --manifest to disambiguate.`);
972
- }
973
- var NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
974
- function validatePackageName(name, label) {
975
- if (!NAME_PATTERN.test(name)) {
976
- throw new Error(`Invalid ${label} '${name}' \u2014 use lowercase letters, digits and hyphens (no leading/trailing hyphen).`);
977
- }
978
- }
979
1113
 
980
1114
  // src/commands/dev.ts
981
1115
  async function resolveDevScopes(flags) {
@@ -1325,7 +1459,7 @@ async function followExecution(api, workspaceId, executionId, json) {
1325
1459
  if (exec.status !== "Completed") process.exitCode = 1;
1326
1460
  return;
1327
1461
  }
1328
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
1462
+ await new Promise((resolve11) => setTimeout(resolve11, 1e3));
1329
1463
  }
1330
1464
  console.error("Timed out waiting for a terminal status \u2014 check `verentis exec get`.");
1331
1465
  process.exitCode = 1;
@@ -1478,10 +1612,31 @@ metadata:
1478
1612
  version: 0.1.0
1479
1613
  author: ""
1480
1614
 
1615
+ # Marketplace presentation (optional):
1616
+ # marketplace:
1617
+ # logo: ./marketplace/logo.png
1618
+ # hero:
1619
+ # source: ./marketplace/hero.webp
1620
+ # alt: A concise description of the hero background
1621
+ # images:
1622
+ # - source: ./marketplace/screenshot.png
1623
+ # alt: A concise description of the screenshot
1624
+ # caption: Optional visible caption
1625
+ # readme: ./README.md
1626
+ # changelog: ./CHANGELOG.md
1627
+ # assets:
1628
+ # - "marketplace/**/*.{png,jpg,jpeg,webp,svg}"
1629
+
1481
1630
  spec:
1482
1631
  entry: https://example.com # hosted app origin
1483
1632
  scope: global
1484
1633
 
1634
+ # Opt into a standalone workspace surface. Contextual file handlers should omit this block.
1635
+ # launch:
1636
+ # enabled: true
1637
+ # entry-point: home # optional; otherwise spec.entry is used
1638
+ # pinnable: true
1639
+
1485
1640
  entry-points: []
1486
1641
  capabilities: []
1487
1642
  permissions: []
@@ -1508,6 +1663,20 @@ metadata:
1508
1663
  version: 0.1.0
1509
1664
  author: ""
1510
1665
 
1666
+ # Marketplace presentation (optional):
1667
+ # marketplace:
1668
+ # logo: ./marketplace/logo.png
1669
+ # hero:
1670
+ # source: ./marketplace/hero.webp
1671
+ # alt: A concise description of the hero background
1672
+ # images:
1673
+ # - source: ./marketplace/screenshot.png
1674
+ # alt: A concise description of the screenshot
1675
+ # readme: ./README.md
1676
+ # changelog: ./CHANGELOG.md
1677
+ # assets:
1678
+ # - "marketplace/**/*.{png,jpg,jpeg,webp,svg}"
1679
+
1511
1680
  spec:
1512
1681
  runtimes: {}
1513
1682
  file-types: []
@@ -1529,6 +1698,20 @@ metadata:
1529
1698
  version: 0.1.0
1530
1699
  author: ""
1531
1700
 
1701
+ # Marketplace presentation (optional):
1702
+ # marketplace:
1703
+ # logo: ./marketplace/logo.png
1704
+ # hero:
1705
+ # source: ./marketplace/hero.webp
1706
+ # alt: A concise description of the hero background
1707
+ # images:
1708
+ # - source: ./marketplace/screenshot.png
1709
+ # alt: A concise description of the screenshot
1710
+ # readme: ./README.md
1711
+ # changelog: ./CHANGELOG.md
1712
+ # assets:
1713
+ # - "marketplace/**/*.{png,jpg,jpeg,webp,svg}"
1714
+
1532
1715
  packaging:
1533
1716
  publisher: "" # your publisher name
1534
1717
  files:
@@ -1908,20 +2091,260 @@ function rawX25519Private(key) {
1908
2091
  function publicKeyFromRaw(raw) {
1909
2092
  return createPublicKey({ key: { kty: "OKP", crv: "X25519", x: raw.toString("base64url") }, format: "jwk" });
1910
2093
  }
2094
+ var MIB = 1024 * 1024;
2095
+ var MAX_LOGO_BYTES = MIB;
2096
+ var MAX_IMAGE_BYTES = 5 * MIB;
2097
+ var MAX_TOTAL_IMAGE_BYTES = 25 * MIB;
2098
+ var MAX_MARKDOWN_BYTES = MIB;
2099
+ async function buildCatalog(projectDir, marketplace2) {
2100
+ const root = await realpath(projectDir);
2101
+ const entries = /* @__PURE__ */ new Map();
2102
+ const imageDigests = /* @__PURE__ */ new Map();
2103
+ const index = { schemaVersion: 1 };
2104
+ const addImage = async (source, label, maxBytes) => {
2105
+ const file = await resolveDirectFile(root, source, label);
2106
+ if (file.bytes.length > maxBytes) {
2107
+ throw new Error(`${label} exceeds the ${formatMiB(maxBytes)} MiB limit (${file.bytes.length} bytes).`);
2108
+ }
2109
+ const image = validateImage(file.bytes, file.path, label);
2110
+ const reference = addContentAddressedEntry(file, image, "assets", entries);
2111
+ imageDigests.set(reference.sha256, reference.size);
2112
+ return reference;
2113
+ };
2114
+ const addMarkdown = async (source, label) => {
2115
+ const file = await resolveDirectFile(root, source, label);
2116
+ if (file.bytes.length > MAX_MARKDOWN_BYTES) {
2117
+ throw new Error(`${label} exceeds the 1 MiB limit (${file.bytes.length} bytes).`);
2118
+ }
2119
+ validateMarkdown(file.bytes, file.path, label);
2120
+ return addContentAddressedEntry(file, { extension: ".md", mediaType: "text/markdown" }, "docs", entries);
2121
+ };
2122
+ if (marketplace2.logo) index.logo = await addImage(marketplace2.logo, "marketplace.logo", MAX_LOGO_BYTES);
2123
+ if (marketplace2.hero) {
2124
+ const reference = await addImage(marketplace2.hero.source, "marketplace.hero.source", MAX_IMAGE_BYTES);
2125
+ index.hero = { ...reference, alt: marketplace2.hero.alt.trim() };
2126
+ }
2127
+ if (marketplace2.images?.length) {
2128
+ index.images = [];
2129
+ for (const [position, image] of marketplace2.images.entries()) {
2130
+ const reference = await addImage(image.source, `marketplace.images[${position}].source`, MAX_IMAGE_BYTES);
2131
+ index.images.push({
2132
+ ...reference,
2133
+ alt: image.alt.trim(),
2134
+ ...image.caption !== void 0 ? { caption: image.caption } : {}
2135
+ });
2136
+ }
2137
+ }
2138
+ if (marketplace2.readme) index.readme = await addMarkdown(marketplace2.readme, "marketplace.readme");
2139
+ if (marketplace2.changelog) index.changelog = await addMarkdown(marketplace2.changelog, "marketplace.changelog");
2140
+ if (marketplace2.assets?.length) {
2141
+ index.assets = [];
2142
+ const assetFiles = await resolveAssetGlobs(root, marketplace2.assets);
2143
+ for (const file of assetFiles) {
2144
+ if (file.bytes.length > MAX_IMAGE_BYTES) {
2145
+ throw new Error(`marketplace.assets matched '${file.source}', which exceeds the 5 MiB per-image limit (${file.bytes.length} bytes).`);
2146
+ }
2147
+ const image = validateImage(file.bytes, file.path, `marketplace.assets entry '${file.source}'`);
2148
+ const reference = addContentAddressedEntry(file, image, "assets", entries);
2149
+ imageDigests.set(reference.sha256, reference.size);
2150
+ index.assets.push(reference);
2151
+ }
2152
+ }
2153
+ const totalImageBytes = [...imageDigests.values()].reduce((sum, size) => sum + size, 0);
2154
+ if (totalImageBytes > MAX_TOTAL_IMAGE_BYTES) {
2155
+ throw new Error(`Marketplace image assets exceed the 25 MiB total limit (${totalImageBytes} bytes).`);
2156
+ }
2157
+ const indexJson = `${JSON.stringify(index, null, 2)}
2158
+ `;
2159
+ return {
2160
+ index,
2161
+ indexJson,
2162
+ entries: [...entries.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([path, bytes]) => ({ path, bytes }))
2163
+ };
2164
+ }
2165
+ async function resolveDirectFile(root, source, label) {
2166
+ const normalized = normalizeRelativeReference(source, label);
2167
+ return readContainedFile(root, normalized, label);
2168
+ }
2169
+ async function resolveAssetGlobs(root, patterns) {
2170
+ const matched = /* @__PURE__ */ new Set();
2171
+ for (const sourcePattern of patterns) {
2172
+ const pattern = normalizeRelativeReference(sourcePattern, "marketplace.assets glob");
2173
+ for await (const entry of glob(pattern, { cwd: root })) {
2174
+ const normalized = toEntryPath(entry);
2175
+ const info = await stat(join(root, normalized)).catch(() => null);
2176
+ if (info?.isFile()) matched.add(normalized);
2177
+ }
2178
+ }
2179
+ const files2 = [];
2180
+ for (const source of [...matched].sort()) {
2181
+ files2.push(await readContainedFile(root, source, `marketplace.assets entry '${source}'`));
2182
+ }
2183
+ return files2;
2184
+ }
2185
+ async function readContainedFile(root, source, label) {
2186
+ const candidate = resolve(root, source);
2187
+ const actual = await realpath(candidate).catch(() => {
2188
+ throw new Error(`${label} references missing file '${source}'.`);
2189
+ });
2190
+ const fromRoot = relative(root, actual);
2191
+ if (fromRoot === "" || fromRoot.startsWith(`..${sep}`) || fromRoot === ".." || isAbsolute(fromRoot)) {
2192
+ throw new Error(`${label} references '${source}', which escapes the manifest directory.`);
2193
+ }
2194
+ const info = await stat(actual);
2195
+ if (!info.isFile()) throw new Error(`${label} references '${source}', which is not a file.`);
2196
+ return { source: toEntryPath(source), path: actual, bytes: await readFile(actual) };
2197
+ }
2198
+ function normalizeRelativeReference(source, label) {
2199
+ const value = source.trim();
2200
+ if (value.length === 0 || value.includes("\0") || value.includes("\\") || isAbsolute(value) || /^[a-z]:/i.test(value) || value.split("/").includes("..")) {
2201
+ throw new Error(`${label} must be relative to the manifest directory and must not escape it (got '${source}').`);
2202
+ }
2203
+ const normalized = value.replace(/^(?:\.\/)+/, "");
2204
+ if (normalized.length === 0 || normalized === ".") throw new Error(`${label} must reference a file inside the manifest directory.`);
2205
+ return normalized;
2206
+ }
2207
+ function addContentAddressedEntry(file, type, directory, entries) {
2208
+ const sha256 = createHash("sha256").update(file.bytes).digest("hex");
2209
+ const path = `catalog/${directory}/${sha256}${type.extension}`;
2210
+ entries.set(path, file.bytes);
2211
+ return {
2212
+ source: file.source,
2213
+ path,
2214
+ mediaType: type.mediaType,
2215
+ size: file.bytes.length,
2216
+ sha256
2217
+ };
2218
+ }
2219
+ function validateImage(bytes, path, label) {
2220
+ const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
2221
+ if (bytes.length >= 24 && bytes.subarray(0, 8).equals(pngSignature)) {
2222
+ if (bytes.readUInt32BE(8) !== 13 || bytes.toString("ascii", 12, 16) !== "IHDR") throw new Error(`${label} is not a valid PNG file.`);
2223
+ return { extension: ".png", mediaType: "image/png" };
2224
+ }
2225
+ if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216) {
2226
+ if (bytes[bytes.length - 2] !== 255 || bytes[bytes.length - 1] !== 217) throw new Error(`${label} is not a valid JPEG file.`);
2227
+ return { extension: ".jpg", mediaType: "image/jpeg" };
2228
+ }
2229
+ if (bytes.length >= 12 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP") {
2230
+ const declaredLength = bytes.readUInt32LE(4) + 8;
2231
+ if (declaredLength !== bytes.length) throw new Error(`${label} has an invalid WebP length.`);
2232
+ return { extension: ".webp", mediaType: "image/webp" };
2233
+ }
2234
+ const extension = extname(path).toLowerCase();
2235
+ if (extension === ".svg" || looksLikeSvg(bytes)) {
2236
+ validateSvg(bytes, label);
2237
+ return { extension: ".svg", mediaType: "image/svg+xml" };
2238
+ }
2239
+ throw new Error(`${label} must be a valid PNG, JPEG, WebP or SVG image.`);
2240
+ }
2241
+ function looksLikeSvg(bytes) {
2242
+ const prefix = bytes.subarray(0, Math.min(bytes.length, 512)).toString("utf8").replace(/^\uFEFF/, "").trimStart();
2243
+ return prefix.startsWith("<svg") || prefix.startsWith("<?xml") || prefix.startsWith("<!--");
2244
+ }
2245
+ function validateSvg(bytes, label) {
2246
+ let text;
2247
+ try {
2248
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2249
+ } catch {
2250
+ throw new Error(`${label} is not valid UTF-8 SVG content.`);
2251
+ }
2252
+ const trimmed = text.replace(/^\uFEFF/, "").trim();
2253
+ const root = /^(?:<\?xml[\s\S]*?\?>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg\b/i;
2254
+ if (!root.test(trimmed) || !/<\/svg>\s*$/i.test(trimmed) && !/<svg\b[\s\S]*\/>\s*$/i.test(trimmed)) {
2255
+ throw new Error(`${label} is not a well-formed SVG document.`);
2256
+ }
2257
+ if (/&#(?:x[0-9a-f]+|[0-9]+);/i.test(trimmed)) {
2258
+ throw new Error(`${label} contains numeric XML character references, which are not allowed in SVG content.`);
2259
+ }
2260
+ if (/<!DOCTYPE|<!ENTITY/i.test(trimmed) || /<(?:script|foreignObject|iframe|object|embed|audio|video)\b/i.test(trimmed) || /\son[a-z]+\s*=/i.test(trimmed) || /(?:javascript|vbscript)\s*:/i.test(trimmed) || /data\s*:\s*text\/html/i.test(trimmed)) {
2261
+ throw new Error(`${label} contains active SVG content, which is not allowed.`);
2262
+ }
2263
+ }
2264
+ function validateMarkdown(bytes, _path, label) {
2265
+ try {
2266
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2267
+ if (text.includes("\0")) throw new Error("NUL");
2268
+ } catch {
2269
+ throw new Error(`${label} must contain valid UTF-8 Markdown text.`);
2270
+ }
2271
+ }
2272
+ function formatMiB(bytes) {
2273
+ return bytes / MIB;
2274
+ }
2275
+ function toEntryPath(path) {
2276
+ return path.split(sep).join("/");
2277
+ }
2278
+ function isPlainObject(value) {
2279
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2280
+ }
2281
+ function deepMerge(base, overlay) {
2282
+ if (!isPlainObject(base) || !isPlainObject(overlay)) {
2283
+ return overlay;
2284
+ }
2285
+ const result = { ...base };
2286
+ for (const [key, value] of Object.entries(overlay)) {
2287
+ result[key] = key in result ? deepMerge(result[key], value) : value;
2288
+ }
2289
+ return result;
2290
+ }
2291
+ function applyOverlay(base, overlay) {
2292
+ return deepMerge(base, overlay);
2293
+ }
2294
+ function resolveOverlayPath(projectDir, selector) {
2295
+ if (selector.overlay) {
2296
+ return isAbsolute(selector.overlay) ? selector.overlay : resolve(selector.overlay);
2297
+ }
2298
+ if (selector.env) {
2299
+ return join(projectDir, "environments", `${selector.env}.yaml`);
2300
+ }
2301
+ return void 0;
2302
+ }
2303
+ async function loadOverlay(path) {
2304
+ let text;
2305
+ try {
2306
+ text = await readFile(path, "utf8");
2307
+ } catch (error) {
2308
+ if (error?.code === "ENOENT") {
2309
+ throw new Error(`Overlay file not found: ${path}`);
2310
+ }
2311
+ throw error;
2312
+ }
2313
+ const parsed = parse(text);
2314
+ if (parsed === null || parsed === void 0) return {};
2315
+ if (!isPlainObject(parsed)) {
2316
+ throw new Error(`Overlay ${path} must be a YAML mapping (got ${Array.isArray(parsed) ? "a list" : typeof parsed}).`);
2317
+ }
2318
+ return parsed;
2319
+ }
2320
+
2321
+ // src/vpkg/pack.ts
1911
2322
  async function pack(options) {
1912
2323
  const manifestPath = resolve(options.manifestPath);
1913
2324
  const projectDir = dirname(manifestPath);
1914
- const manifest = await loadManifest(manifestPath);
2325
+ const base = await loadManifest(manifestPath);
2326
+ const overlayPath = resolveOverlayPath(projectDir, { env: options.env, overlay: options.overlay });
2327
+ let manifest = overlayPath ? applyOverlay(base, await loadOverlay(overlayPath)) : base;
2328
+ let overrideApplied = overlayPath !== void 0;
2329
+ if (options.version !== void 0) {
2330
+ manifest = { ...manifest, metadata: { ...manifest.metadata, version: options.version } };
2331
+ overrideApplied = true;
2332
+ }
1915
2333
  const kind = normalizeKind(manifest.kind);
2334
+ const launchKindError = validateLaunchManifestKind(kind, manifest.spec);
2335
+ if (launchKindError) throw new Error(`Invalid manifest: ${launchKindError}.`);
2336
+ const marketplaceErrors = validateMarketplaceSection(manifest.marketplace);
2337
+ if (marketplaceErrors.length > 0) throw new Error(`Invalid marketplace section: ${marketplaceErrors.join(" ")}`);
1916
2338
  const name = (options.name ?? manifest.packaging?.name ?? manifest.metadata.name).toLowerCase();
1917
2339
  const publisher = options.publisher.toLowerCase();
1918
2340
  validatePackageName(name, "package name");
1919
2341
  validatePackageName(publisher, "publisher name");
1920
2342
  const version = manifest.metadata.version;
1921
2343
  const payloadFiles = await resolvePayloadFiles(projectDir, manifestPath, manifest);
2344
+ const catalog = manifest.marketplace ? await buildCatalog(projectDir, manifest.marketplace) : void 0;
1922
2345
  const staging = await mkdtemp(join(tmpdir(), "vpkg-"));
1923
2346
  try {
1924
- const manifestText = await readFile(manifestPath, "utf8");
2347
+ const manifestText = overrideApplied ? stringify(manifest) : await readFile(manifestPath, "utf8");
1925
2348
  await writeFile(join(staging, "manifest.yaml"), manifestText);
1926
2349
  const encrypt = (options.encryptFor?.length ?? 0) > 0;
1927
2350
  const contentKey = encrypt ? generateContentKey() : null;
@@ -1930,13 +2353,24 @@ async function pack(options) {
1930
2353
  };
1931
2354
  for (const file of payloadFiles) {
1932
2355
  const sourcePath = join(projectDir, file);
1933
- const entryName = `files/${toEntryPath(file)}`;
2356
+ const entryName = `files/${toEntryPath2(file)}`;
1934
2357
  const targetPath = join(staging, "files", file);
1935
2358
  await mkdir(dirname(targetPath), { recursive: true });
1936
2359
  const bytes = contentKey ? encryptBytes(contentKey, await readFile(sourcePath)) : await readFile(sourcePath);
1937
2360
  await writeFile(targetPath, bytes);
1938
2361
  digests[entryName] = sha256Hex(bytes);
1939
2362
  }
2363
+ if (catalog) {
2364
+ for (const entry of catalog.entries) {
2365
+ const targetPath = join(staging, entry.path);
2366
+ await mkdir(dirname(targetPath), { recursive: true });
2367
+ await writeFile(targetPath, entry.bytes);
2368
+ digests[entry.path] = sha256Hex(entry.bytes);
2369
+ }
2370
+ await mkdir(join(staging, "catalog"), { recursive: true });
2371
+ await writeFile(join(staging, "catalog", "index.json"), catalog.indexJson);
2372
+ digests["catalog/index.json"] = sha256Hex(Buffer.from(catalog.indexJson, "utf8"));
2373
+ }
1940
2374
  await mkdir(join(staging, ".verentis", "signatures"), { recursive: true });
1941
2375
  if (contentKey) {
1942
2376
  const encryption = {
@@ -1972,16 +2406,19 @@ async function pack(options) {
1972
2406
  ".verentis/digests.json",
1973
2407
  ...contentKey ? [".verentis/encryption.json"] : [],
1974
2408
  ...signed ? [".verentis/signatures/developer.dsse.json"] : [],
1975
- ...payloadFiles.map((file) => `files/${toEntryPath(file)}`)
2409
+ ...payloadFiles.map((file) => `files/${toEntryPath2(file)}`),
2410
+ ...catalog ? ["catalog/index.json", ...catalog.entries.map((entry) => entry.path)] : []
1976
2411
  ];
1977
- await tar.create({ gzip: true, file: outFile, cwd: staging, portable: true }, entries);
2412
+ await tar.create({ gzip: true, file: outFile, cwd: staging, portable: true, mtime: /* @__PURE__ */ new Date(0) }, entries);
1978
2413
  return {
1979
2414
  outFile,
1980
2415
  name,
1981
2416
  publisher,
1982
2417
  version,
1983
2418
  kind,
1984
- files: payloadFiles.map(toEntryPath),
2419
+ files: payloadFiles.map(toEntryPath2),
2420
+ catalog: catalog?.index,
2421
+ catalogFiles: catalog ? ["index.json", ...catalog.entries.map((entry) => entry.path.replace(/^catalog\//, ""))] : [],
1985
2422
  treeDigest: sha256Hex(Buffer.from(digestsJson, "utf8")),
1986
2423
  signed,
1987
2424
  encrypted: encrypt
@@ -2008,7 +2445,7 @@ async function resolvePayloadFiles(projectDir, manifestPath, manifest) {
2008
2445
  }
2009
2446
  function defaultIncludes(manifest) {
2010
2447
  const sources = /* @__PURE__ */ new Set();
2011
- collectSchemaSources(manifest, sources);
2448
+ collectSchemaSources(manifest.spec, sources);
2012
2449
  collectInstallSources(manifest, sources);
2013
2450
  if (sources.size === 0) return [];
2014
2451
  return [...sources];
@@ -2039,7 +2476,7 @@ function collectSchemaSources(node, sources) {
2039
2476
  }
2040
2477
  }
2041
2478
  }
2042
- function toEntryPath(file) {
2479
+ function toEntryPath2(file) {
2043
2480
  return file.split(sep).join("/");
2044
2481
  }
2045
2482
  function sha256Hex(data) {
@@ -2060,21 +2497,18 @@ async function readVpkg(vpkgPath) {
2060
2497
  });
2061
2498
  const metadata = parse(packageYaml);
2062
2499
  const declared = JSON.parse(digestsJson);
2063
- const files2 = [];
2064
- for await (const entry of glob("files/**/*", { cwd: staging })) {
2065
- const abs = join(staging, entry);
2066
- const info = await readFile(abs).catch(() => null);
2067
- if (info === null) continue;
2068
- files2.push(entry.split(sep).join("/"));
2069
- }
2070
- files2.sort();
2500
+ const files2 = await listFiles(staging, "files/**/*");
2501
+ const catalogFiles = await listFiles(staging, "catalog/**/*");
2502
+ const contentFiles = [...files2, ...catalogFiles];
2071
2503
  const digestErrors = [];
2072
2504
  const entries = declared.entries ?? {};
2073
2505
  const manifestDigest = createHash("sha256").update(manifestYaml, "utf8").digest("hex");
2074
- if (entries["manifest.yaml"] && entries["manifest.yaml"].toLowerCase() !== manifestDigest) {
2506
+ if (!entries["manifest.yaml"]) {
2507
+ digestErrors.push("Entry 'manifest.yaml' is not covered by digests.json.");
2508
+ } else if (entries["manifest.yaml"].toLowerCase() !== manifestDigest) {
2075
2509
  digestErrors.push("Digest mismatch for manifest.yaml.");
2076
2510
  }
2077
- for (const file of files2) {
2511
+ for (const file of contentFiles) {
2078
2512
  const declaredDigest = entries[file];
2079
2513
  if (!declaredDigest) {
2080
2514
  digestErrors.push(`Entry '${file}' is not covered by digests.json.`);
@@ -2084,10 +2518,21 @@ async function readVpkg(vpkgPath) {
2084
2518
  if (actual !== declaredDigest.toLowerCase()) digestErrors.push(`Digest mismatch for '${file}'.`);
2085
2519
  }
2086
2520
  for (const declaredPath of Object.keys(entries)) {
2087
- if (declaredPath !== "manifest.yaml" && !files2.includes(declaredPath)) {
2521
+ if (declaredPath !== "manifest.yaml" && !contentFiles.includes(declaredPath)) {
2088
2522
  digestErrors.push(`Digest declared for missing entry '${declaredPath}'.`);
2089
2523
  }
2090
2524
  }
2525
+ let catalog;
2526
+ if (catalogFiles.includes("catalog/index.json")) {
2527
+ try {
2528
+ catalog = JSON.parse(await readFile(join(staging, "catalog", "index.json"), "utf8"));
2529
+ if (catalog.schemaVersion !== 1) digestErrors.push(`Unsupported catalog schema version '${String(catalog.schemaVersion)}'.`);
2530
+ } catch {
2531
+ digestErrors.push("Invalid catalog/index.json.");
2532
+ }
2533
+ } else if (catalogFiles.length > 0) {
2534
+ digestErrors.push("Catalog entries are present but catalog/index.json is missing.");
2535
+ }
2091
2536
  const developerSignature = await readJsonIfExists(join(staging, ".verentis", "signatures", "developer.dsse.json"));
2092
2537
  const marketplaceSignature = await readJsonIfExists(join(staging, ".verentis", "signatures", "marketplace.dsse.json"));
2093
2538
  const encryption = await readJsonIfExists(join(staging, ".verentis", "encryption.json"));
@@ -2100,12 +2545,23 @@ async function readVpkg(vpkgPath) {
2100
2545
  marketplaceSignature: marketplaceSignature ?? void 0,
2101
2546
  encryption: encryption ?? void 0,
2102
2547
  files: files2.map((f) => f.replace(/^files\//, "")),
2548
+ catalog,
2549
+ catalogFiles: catalogFiles.map((f) => f.replace(/^catalog\//, "")),
2103
2550
  digestErrors
2104
2551
  };
2105
2552
  } finally {
2106
2553
  await rm(staging, { recursive: true, force: true });
2107
2554
  }
2108
2555
  }
2556
+ async function listFiles(root, pattern) {
2557
+ const files2 = [];
2558
+ for await (const entry of glob(pattern, { cwd: root })) {
2559
+ const abs = join(root, entry);
2560
+ const info = await readFile(abs).catch(() => null);
2561
+ if (info !== null) files2.push(entry.split(sep).join("/"));
2562
+ }
2563
+ return files2.sort();
2564
+ }
2109
2565
  async function readJsonIfExists(path) {
2110
2566
  try {
2111
2567
  return JSON.parse(await readFile(path, "utf8"));
@@ -2116,7 +2572,7 @@ async function readJsonIfExists(path) {
2116
2572
 
2117
2573
  // src/commands/pack.ts
2118
2574
  function registerPack(program2) {
2119
- program2.command("pack [dir]").description("Build a signed .vpkg from a project directory").option("--manifest <path>", "manifest path (default: auto-detected in the project directory)").option("--publisher <name>", "publisher name (default: packaging.publisher in the manifest)").option("--name <name>", "override the package name").option("--out <dir>", "output directory (default: the project directory)").option("--key <name>", "signing key name (default: the configured default key)").option("--no-sign", "produce an unsigned package").option("--encrypt", "seal the payload (AES-256-GCM); requires at least one recipient").option("--recipient <id=base64key...>", "additional X25519 recipient (repeatable)", collectRecipients, []).action(async (dir, options) => {
2575
+ program2.command("pack [dir]").description("Build a signed .vpkg from a project directory").option("--manifest <path>", "manifest path (default: auto-detected in the project directory)").option("--publisher <name>", "publisher name (default: packaging.publisher in the manifest)").option("--name <name>", "override the package name").option("--env <name>", "merge a per-environment overlay (environments/<name>.yaml) before packing").option("--overlay <path>", "merge an explicit overlay file before packing (wins over --env)").option("--set-version <version>", "override metadata.version (e.g. a CI-derived build number)").option("--out <dir>", "output directory (default: the project directory)").option("--key <name>", "signing key name (default: the configured default key)").option("--no-sign", "produce an unsigned package").option("--encrypt", "seal the payload (AES-256-GCM); requires at least one recipient").option("--recipient <id=base64key...>", "additional X25519 recipient (repeatable)", collectRecipients, []).action(async (dir, options) => {
2120
2576
  const projectDir = resolve(dir ?? ".");
2121
2577
  const manifestPath = options.manifest ? resolve(options.manifest) : await findManifest(projectDir);
2122
2578
  const manifest = await loadManifest(manifestPath);
@@ -2151,6 +2607,9 @@ function registerPack(program2) {
2151
2607
  manifestPath,
2152
2608
  publisher,
2153
2609
  name: options.name,
2610
+ env: options.env,
2611
+ overlay: options.overlay,
2612
+ version: options.setVersion,
2154
2613
  outDir: options.out,
2155
2614
  signingKey,
2156
2615
  encryptFor
@@ -2158,6 +2617,7 @@ function registerPack(program2) {
2158
2617
  console.log(`Packed ${result.publisher}/${result.name}@${result.version} (${result.kind})`);
2159
2618
  console.log(` ${result.outFile}`);
2160
2619
  console.log(` ${result.files.length} payload file(s), tree digest ${result.treeDigest.slice(0, 16)}\u2026, ${result.signed ? "signed" : "UNSIGNED"}${result.encrypted ? ", ENCRYPTED" : ""}`);
2620
+ if (result.catalog) console.log(` ${result.catalogFiles.length} plaintext catalog file(s)`);
2161
2621
  });
2162
2622
  program2.command("seal-keygen").description("Generate an X25519 sealing keypair (for platform escrow or custom recipients)").action(() => {
2163
2623
  const pair = generateSealingKeyPair();
@@ -2168,7 +2628,11 @@ function registerPack(program2) {
2168
2628
  program2.command("verify <vpkg>").description("Verify the digests (and, when possible, the developer signature) of a .vpkg").option("--key <name>", "verify the developer signature against a local key").action(async (vpkgPath, options) => {
2169
2629
  const contents = await readVpkg(resolve(vpkgPath));
2170
2630
  console.log(`${contents.metadata.publisher}/${contents.metadata.name}@${contents.metadata.version} (${contents.metadata.kind})`);
2171
- console.log(` files: ${contents.files.length}, tree digest ${contents.treeDigest.slice(0, 16)}\u2026`);
2631
+ console.log(` payload files: ${contents.files.length}, catalog files: ${contents.catalogFiles.length}, tree digest ${contents.treeDigest.slice(0, 16)}\u2026`);
2632
+ if (contents.catalog) {
2633
+ const docs = Number(!!contents.catalog.readme) + Number(!!contents.catalog.changelog);
2634
+ console.log(` catalog schema v${contents.catalog.schemaVersion}: ${contents.catalog.images?.length ?? 0} gallery image(s), ${docs} document(s)${contents.catalog.logo ? ", logo" : ""}${contents.catalog.hero ? ", hero" : ""}`);
2635
+ }
2172
2636
  if (contents.encryption) {
2173
2637
  console.log(` \u{1F512} encrypted (${contents.encryption.algorithm}); recipients: ${contents.encryption.recipients.map((r) => r.id).join(", ")}`);
2174
2638
  }
@@ -2309,19 +2773,62 @@ function registerPackage(program2) {
2309
2773
  output(pageItems(page), { packageName: "Package", version: "Version", status: "Status", installPath: "Path", id: "Id" }, options, page);
2310
2774
  });
2311
2775
  }
2776
+ var MAX_LOGO_BYTES2 = 1024 * 1024;
2777
+ var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
2778
+ async function readPublisherLogo(path) {
2779
+ const absolutePath = resolve(path);
2780
+ const info = await stat(absolutePath).catch(() => {
2781
+ throw new Error(`Publisher logo '${absolutePath}' does not exist.`);
2782
+ });
2783
+ if (!info.isFile()) throw new Error(`Publisher logo '${absolutePath}' is not a file.`);
2784
+ if (info.size > MAX_LOGO_BYTES2) {
2785
+ throw new Error(`Publisher logo exceeds the 1 MiB limit (${info.size} bytes).`);
2786
+ }
2787
+ const bytes = await readFile(absolutePath);
2788
+ return { path: absolutePath, bytes, contentType: detectPublisherLogoContentType(bytes) };
2789
+ }
2790
+ function detectPublisherLogoContentType(bytes) {
2791
+ if (bytes.length >= 24 && bytes.subarray(0, 8).equals(PNG_SIGNATURE) && bytes.readUInt32BE(8) === 13 && bytes.toString("ascii", 12, 16) === "IHDR") {
2792
+ return "image/png";
2793
+ }
2794
+ if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216 && bytes[bytes.length - 2] === 255 && bytes[bytes.length - 1] === 217) {
2795
+ return "image/jpeg";
2796
+ }
2797
+ if (bytes.length >= 12 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP" && bytes.readUInt32LE(4) + 8 === bytes.length) {
2798
+ return "image/webp";
2799
+ }
2800
+ let text;
2801
+ try {
2802
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "").trim();
2803
+ } catch {
2804
+ throw unsupportedLogoError();
2805
+ }
2806
+ const root = /^(?:<\?xml[\s\S]*?\?>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg\b/i;
2807
+ const closed = /<\/svg>\s*$/i.test(text) || /<svg\b[\s\S]*\/>\s*$/i.test(text);
2808
+ if (!root.test(text) || !closed) throw unsupportedLogoError();
2809
+ if (/&#(?:x[0-9a-f]+|[0-9]+);/i.test(text)) {
2810
+ throw new Error("Publisher logo SVG contains numeric XML character references, which are not allowed.");
2811
+ }
2812
+ if (/<!DOCTYPE|<!ENTITY/i.test(text) || /<(?:script|foreignObject|iframe|object|embed|audio|video)\b/i.test(text) || /\son[a-z]+\s*=/i.test(text) || /(?:javascript|vbscript)\s*:/i.test(text) || /data\s*:\s*text\/html/i.test(text)) {
2813
+ throw new Error("Publisher logo SVG contains active content, which is not allowed.");
2814
+ }
2815
+ return "image/svg+xml";
2816
+ }
2817
+ function unsupportedLogoError() {
2818
+ return new Error("Publisher logo must be a valid PNG, JPEG, WebP or SVG image.");
2819
+ }
2312
2820
 
2313
2821
  // src/commands/publisher.ts
2314
2822
  function registerPublisher(program2) {
2315
2823
  const publisherCmd = program2.command("publisher").description("Manage your publisher profile");
2316
- publisherCmd.command("create").description("Onboard your account as a marketplace publisher").requiredOption("--name <name>", "URL-safe publisher name (e.g. acme)").requiredOption("--display-name <displayName>", "human-readable publisher name").option("--description <description>").option("--website <url>").option("--email <email>").action(async (options) => {
2824
+ publisherCmd.command("create").description("Onboard your account as a marketplace publisher").requiredOption("--name <name>", "URL-safe publisher name (e.g. acme)").requiredOption("--display-name <displayName>", "human-readable publisher name").option("--description <description>").option("--website <url>").option("--email <email>").option("--logo <file>", "PNG, JPEG, WebP or SVG logo (maximum 1 MiB)").action(async (options) => {
2317
2825
  const api = await createApiClient();
2318
2826
  const existing = await getMyPublisher(api);
2319
2827
  if (existing) {
2320
2828
  console.log(`This account already has publisher '${existing.name}'.`);
2321
2829
  return;
2322
2830
  }
2323
- const id = await api.request("POST", "/v1/publishers", {
2324
- scopes: [SCOPES.publisherCreate],
2831
+ const id = await call(api, marketplace.createPublisher, {
2325
2832
  body: {
2326
2833
  name: options.name,
2327
2834
  displayName: options.displayName,
@@ -2331,8 +2838,51 @@ function registerPublisher(program2) {
2331
2838
  }
2332
2839
  });
2333
2840
  console.log(`Publisher '${options.name}' created (${id}).`);
2841
+ if (options.logo) {
2842
+ await uploadPublisherLogo(api, id, options.logo);
2843
+ console.log(`Publisher logo uploaded from ${options.logo}.`);
2844
+ }
2334
2845
  console.log("Next: generate and register a signing key with `verentis keygen`.");
2335
2846
  });
2847
+ publisherCmd.command("update").description("Update your publisher profile").option("--display-name <displayName>").option("--description <description>").option("--website <url>").option("--email <email>").action(async (options) => {
2848
+ if (Object.values(options).every((value) => value === void 0)) {
2849
+ throw new Error("No publisher fields supplied. Pass --display-name, --description, --website or --email.");
2850
+ }
2851
+ const api = await createApiClient();
2852
+ const current = await requireMyPublisher(api);
2853
+ await call(api, marketplace.updatePublisher, {
2854
+ params: { id: current.id },
2855
+ body: {
2856
+ id: current.id,
2857
+ displayName: options.displayName ?? current.displayName,
2858
+ description: options.description ?? current.description ?? null,
2859
+ websiteUrl: options.website ?? current.websiteUrl ?? null,
2860
+ contactEmail: options.email ?? current.contactEmail ?? null
2861
+ }
2862
+ });
2863
+ console.log(`Publisher '${current.name}' updated.`);
2864
+ });
2865
+ const logoCmd = publisherCmd.command("logo").description("Manage your publisher logo");
2866
+ logoCmd.command("set <file>").description("Upload a PNG, JPEG, WebP or SVG publisher logo (maximum 1 MiB)").action(async (file) => {
2867
+ const api = await createApiClient();
2868
+ const publisher = await requireMyPublisher(api);
2869
+ await uploadPublisherLogo(api, publisher.id, file);
2870
+ console.log(`Publisher logo uploaded from ${file}.`);
2871
+ });
2872
+ logoCmd.command("remove").description("Remove your publisher logo").action(async () => {
2873
+ const api = await createApiClient();
2874
+ const publisher = await requireMyPublisher(api);
2875
+ await call(api, marketplace.removePublisherLogo, { params: { id: publisher.id } });
2876
+ console.log("Publisher logo removed.");
2877
+ });
2878
+ }
2879
+ async function uploadPublisherLogo(api, publisherId, path) {
2880
+ const logo = await readPublisherLogo(path);
2881
+ await callRaw(api, marketplace.setPublisherLogo, {
2882
+ params: { id: publisherId },
2883
+ rawBody: new Blob([new Uint8Array(logo.bytes)], { type: logo.contentType }),
2884
+ contentType: logo.contentType
2885
+ });
2336
2886
  }
2337
2887
  function registerRegistry(program2) {
2338
2888
  program2.command("publish <vpkg>").description("Upload a packed .vpkg to the marketplace registry (creates the package on first publish)").option("--visibility <visibility>", "catalog visibility for a newly created package: Public | Private", "Private").action(async (vpkgPath, options) => {
@@ -2482,6 +3032,271 @@ function registerSettings(program2) {
2482
3032
  console.log(`Setting ${key} deleted.`);
2483
3033
  });
2484
3034
  }
3035
+ var PACKAGE_NAME = "@verentis/cli";
3036
+ var cached;
3037
+ function getCliVersion() {
3038
+ if (cached) return cached;
3039
+ cached = readVersion() ?? "0.0.0";
3040
+ return cached;
3041
+ }
3042
+ function readVersion() {
3043
+ const candidates = ["../package.json", "../../package.json"];
3044
+ for (const relative3 of candidates) {
3045
+ try {
3046
+ const raw = readFileSync(fileURLToPath(new URL(relative3, import.meta.url)), "utf8");
3047
+ const pkg = JSON.parse(raw);
3048
+ if (pkg.version && (relative3 === "../package.json" || pkg.name === PACKAGE_NAME)) return pkg.version;
3049
+ } catch {
3050
+ }
3051
+ }
3052
+ return void 0;
3053
+ }
3054
+
3055
+ // src/update/registry.ts
3056
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
3057
+ function registryUrl() {
3058
+ const configured = process.env.VERENTIS_UPDATE_REGISTRY ?? process.env.npm_config_registry ?? DEFAULT_REGISTRY;
3059
+ return configured.replace(/\/+$/, "");
3060
+ }
3061
+ async function fetchLatestVersion(options = {}) {
3062
+ const url = `${registryUrl()}/${PACKAGE_NAME.replace("/", "%2F")}`;
3063
+ const controller = new AbortController();
3064
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
3065
+ try {
3066
+ const res = await fetch(url, {
3067
+ // The abbreviated packument is far smaller than the full manifest but still carries dist-tags.
3068
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
3069
+ signal: controller.signal
3070
+ });
3071
+ if (!res.ok) return null;
3072
+ const body = await res.json();
3073
+ return body["dist-tags"]?.latest ?? null;
3074
+ } catch {
3075
+ return null;
3076
+ } finally {
3077
+ clearTimeout(timer);
3078
+ }
3079
+ }
3080
+
3081
+ // src/update/semver.ts
3082
+ function isNewerVersion(candidate, base) {
3083
+ return compareVersions(candidate, base) > 0;
3084
+ }
3085
+ function compareVersions(a, b) {
3086
+ const pa = parse5(a);
3087
+ const pb = parse5(b);
3088
+ for (let i = 0; i < 3; i++) {
3089
+ if (pa.main[i] !== pb.main[i]) return pa.main[i] < pb.main[i] ? -1 : 1;
3090
+ }
3091
+ if (pa.pre.length === 0 && pb.pre.length === 0) return 0;
3092
+ if (pa.pre.length === 0) return 1;
3093
+ if (pb.pre.length === 0) return -1;
3094
+ const len = Math.max(pa.pre.length, pb.pre.length);
3095
+ for (let i = 0; i < len; i++) {
3096
+ if (i >= pa.pre.length) return -1;
3097
+ if (i >= pb.pre.length) return 1;
3098
+ const x = pa.pre[i];
3099
+ const y = pb.pre[i];
3100
+ const xNum = /^\d+$/.test(x);
3101
+ const yNum = /^\d+$/.test(y);
3102
+ if (xNum && yNum) {
3103
+ const nx = Number(x);
3104
+ const ny = Number(y);
3105
+ if (nx !== ny) return nx < ny ? -1 : 1;
3106
+ } else if (xNum !== yNum) {
3107
+ return xNum ? -1 : 1;
3108
+ } else if (x !== y) {
3109
+ return x < y ? -1 : 1;
3110
+ }
3111
+ }
3112
+ return 0;
3113
+ }
3114
+ function parse5(version) {
3115
+ const cleaned = version.trim().replace(/^v/i, "").split("+", 1)[0];
3116
+ const dash = cleaned.indexOf("-");
3117
+ const core = dash === -1 ? cleaned : cleaned.slice(0, dash);
3118
+ const pre = dash === -1 ? "" : cleaned.slice(dash + 1);
3119
+ const parts = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
3120
+ return { main: [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0], pre: pre ? pre.split(".") : [] };
3121
+ }
3122
+ var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
3123
+ var statePath = () => join(configDir(), "update-check.json");
3124
+ async function readUpdateState() {
3125
+ try {
3126
+ return JSON.parse(await readFile(statePath(), "utf8"));
3127
+ } catch {
3128
+ return null;
3129
+ }
3130
+ }
3131
+ async function writeUpdateState(state) {
3132
+ await mkdir(configDir(), { recursive: true, mode: 448 });
3133
+ await writeFile(statePath(), JSON.stringify(state, null, 2) + "\n", { mode: 384 });
3134
+ }
3135
+ function isStale(state, maxAgeMs = DEFAULT_MAX_AGE_MS) {
3136
+ if (!state?.lastCheckAt) return true;
3137
+ const checkedAt = Date.parse(state.lastCheckAt);
3138
+ return !Number.isFinite(checkedAt) || Date.now() - checkedAt > maxAgeMs;
3139
+ }
3140
+
3141
+ // src/update/notifier.ts
3142
+ var SUPPRESSED_COMMANDS = /* @__PURE__ */ new Set(["update", "__update-check", "help"]);
3143
+ async function notifyOnExit(argv) {
3144
+ if (isUpdateCheckSuppressed(argv)) return;
3145
+ try {
3146
+ const current = getCliVersion();
3147
+ const state = await readUpdateState();
3148
+ if (state?.latestVersion && isNewerVersion(state.latestVersion, current)) {
3149
+ printUpdateNotice(current, state.latestVersion);
3150
+ }
3151
+ if (isStale(state)) spawnBackgroundRefresh();
3152
+ } catch {
3153
+ }
3154
+ }
3155
+ async function runUpdateCheckRefresh() {
3156
+ const latest = await fetchLatestVersion({ timeoutMs: 4e3 }).catch(() => null);
3157
+ await writeUpdateState({ lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion: latest ?? void 0 }).catch(() => {
3158
+ });
3159
+ }
3160
+ function isUpdateCheckSuppressed(argv) {
3161
+ if (isEnvDisabled()) return true;
3162
+ if (process.env.CI) return true;
3163
+ if (!process.stderr.isTTY) return true;
3164
+ const args = argv.slice(2);
3165
+ if (args.length === 0) return true;
3166
+ if (args.some((a) => a === "--json" || a === "-h" || a === "--help" || a === "-V" || a === "--version")) return true;
3167
+ const command = args.find((a) => !a.startsWith("-"));
3168
+ return command !== void 0 && SUPPRESSED_COMMANDS.has(command);
3169
+ }
3170
+ function isEnvDisabled() {
3171
+ const off = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
3172
+ const noCheck = process.env.VERENTIS_NO_UPDATE_CHECK;
3173
+ if (noCheck !== void 0 && noCheck !== "" && !off.has(noCheck.toLowerCase())) return true;
3174
+ const explicit = process.env.VERENTIS_UPDATE_CHECK;
3175
+ return explicit !== void 0 && off.has(explicit.toLowerCase());
3176
+ }
3177
+ function printUpdateNotice(current, latest) {
3178
+ const message = [
3179
+ `Update available ${current} \u2192 ${latest}`,
3180
+ "Run `verentis update` to install the latest version."
3181
+ ];
3182
+ const width = Math.max(...message.map((line) => line.length));
3183
+ const border = "\u2500".repeat(width + 2);
3184
+ const lines = ["", `\u256D${border}\u256E`, ...message.map((line) => `\u2502 ${line.padEnd(width)} \u2502`), `\u2570${border}\u256F`, ""];
3185
+ process.stderr.write(lines.join("\n") + "\n");
3186
+ }
3187
+ function spawnBackgroundRefresh() {
3188
+ try {
3189
+ const entry = process.argv[1];
3190
+ if (!entry) return;
3191
+ const child = spawn(process.execPath, [entry, "__update-check"], {
3192
+ detached: true,
3193
+ stdio: "ignore",
3194
+ windowsHide: true
3195
+ });
3196
+ child.on("error", () => {
3197
+ });
3198
+ child.unref();
3199
+ } catch {
3200
+ }
3201
+ }
3202
+
3203
+ // src/commands/update.ts
3204
+ function registerUpdate(program2) {
3205
+ program2.command("update").description("Update the Verentis CLI to the latest published version").option("--check", "report whether a newer version is available without installing").option("--to <version>", "install a specific version instead of the latest").action(async (options) => {
3206
+ const current = getCliVersion();
3207
+ const target = await resolveTarget(options.to);
3208
+ if (options.check) {
3209
+ reportCheck(current, target, Boolean(options.to));
3210
+ return;
3211
+ }
3212
+ if (!options.to && !isNewerVersion(target, current)) {
3213
+ console.log(`Already on the latest version (${current}).`);
3214
+ return;
3215
+ }
3216
+ const install = resolveInstallCommand(`${PACKAGE_NAME}@${target}`);
3217
+ if (!install) {
3218
+ throw new Error(
3219
+ `Automatic update is only supported for a global install of ${PACKAGE_NAME}.
3220
+ Update manually, for example:
3221
+ npm install -g ${PACKAGE_NAME}@${target}`
3222
+ );
3223
+ }
3224
+ console.log(`Updating ${PACKAGE_NAME}: ${current} \u2192 ${target}`);
3225
+ console.log(`$ ${install.command} ${install.args.join(" ")}`);
3226
+ const result = spawnSync(install.command, install.args, { stdio: "inherit" });
3227
+ if (result.error) {
3228
+ const code = result.error.code;
3229
+ const hint = code === "ENOENT" ? ` ('${install.command}' was not found on PATH).` : `: ${result.error.message}`;
3230
+ throw new Error(`Could not run the update${hint}
3231
+ Update manually:
3232
+ ${install.command} ${install.args.join(" ")}`);
3233
+ }
3234
+ if (typeof result.status === "number" && result.status !== 0) {
3235
+ throw new Error(
3236
+ `Update failed (${install.command} exited with code ${result.status}).
3237
+ You can retry manually:
3238
+ ${install.command} ${install.args.join(" ")}`
3239
+ );
3240
+ }
3241
+ console.log(`Updated to ${target}. Run \`verentis --version\` to confirm.`);
3242
+ });
3243
+ program2.command("__update-check", { hidden: true }).description("(internal) refresh the cached latest-version marker").action(async () => {
3244
+ await runUpdateCheckRefresh();
3245
+ });
3246
+ }
3247
+ async function resolveTarget(requested) {
3248
+ if (requested) return requested.replace(/^v/i, "");
3249
+ const latest = await fetchLatestVersion({ timeoutMs: 8e3 });
3250
+ if (!latest) {
3251
+ throw new Error(
3252
+ "Could not reach the package registry to determine the latest version. Check your connection, or point VERENTIS_UPDATE_REGISTRY at a reachable registry."
3253
+ );
3254
+ }
3255
+ await writeUpdateState({ lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion: latest }).catch(() => {
3256
+ });
3257
+ return latest;
3258
+ }
3259
+ function reportCheck(current, target, pinned) {
3260
+ if (pinned) {
3261
+ console.log(`Current version: ${current}`);
3262
+ console.log(`Requested version: ${target}`);
3263
+ return;
3264
+ }
3265
+ if (isNewerVersion(target, current)) {
3266
+ console.log(`A new version is available: ${current} \u2192 ${target}`);
3267
+ console.log("Run `verentis update` to install it.");
3268
+ } else {
3269
+ console.log(`You are on the latest version (${current}).`);
3270
+ }
3271
+ }
3272
+ function resolveInstallCommand(spec) {
3273
+ const location = packageLocation();
3274
+ if (!location || !location.replace(/\\/g, "/").includes("/node_modules/")) return null;
3275
+ switch (detectPackageManager(location)) {
3276
+ case "pnpm":
3277
+ return { command: "pnpm", args: ["add", "-g", spec] };
3278
+ case "yarn":
3279
+ return { command: "yarn", args: ["global", "add", spec] };
3280
+ case "bun":
3281
+ return { command: "bun", args: ["add", "-g", spec] };
3282
+ default:
3283
+ return { command: "npm", args: ["install", "-g", spec] };
3284
+ }
3285
+ }
3286
+ function packageLocation() {
3287
+ try {
3288
+ return fileURLToPath(import.meta.url);
3289
+ } catch {
3290
+ return process.argv[1];
3291
+ }
3292
+ }
3293
+ function detectPackageManager(location) {
3294
+ const path = location.replace(/\\/g, "/");
3295
+ if (/\/(\.pnpm|pnpm)\//.test(path)) return "pnpm";
3296
+ if (/\/(\.yarn|yarn)\//.test(path)) return "yarn";
3297
+ if (/\/\.bun\//.test(path)) return "bun";
3298
+ return "npm";
3299
+ }
2485
3300
 
2486
3301
  // src/commands/use.ts
2487
3302
  function registerUse(program2) {
@@ -2573,13 +3388,22 @@ function registerUser(program2) {
2573
3388
  });
2574
3389
  }
2575
3390
  function registerValidate(program2) {
2576
- program2.command("validate [pathOrDir]").description("Validate an engine/app/bundle manifest (structure + registration-time checks)").action(async (pathOrDir) => {
3391
+ program2.command("validate [pathOrDir]").description("Validate an engine/app/bundle manifest (structure + registration-time checks)").option("--env <name>", "merge a per-environment overlay (environments/<name>.yaml) before validating").option("--overlay <path>", "merge an explicit overlay file before validating (wins over --env)").action(async (pathOrDir, options) => {
2577
3392
  const target = resolve(pathOrDir ?? ".");
2578
3393
  const info = await stat(target).catch(() => null);
2579
3394
  const manifestPath = info?.isDirectory() ? await findManifest(target) : target;
2580
3395
  if (!info) throw new Error(`${target} does not exist.`);
2581
- const manifest = await readManifestDocument(manifestPath);
3396
+ const base = await readManifestDocument(manifestPath);
3397
+ const overlayPath = resolveOverlayPath(dirname(manifestPath), { env: options.env, overlay: options.overlay });
3398
+ const manifest = overlayPath ? applyOverlay(base, await loadOverlay(overlayPath)) : base;
2582
3399
  const issues = validateManifest(manifest);
3400
+ if (manifest.marketplace && !issues.some((issue) => issue.level === "error" && issue.message.startsWith("marketplace"))) {
3401
+ try {
3402
+ await buildCatalog(dirname(manifestPath), manifest.marketplace);
3403
+ } catch (error) {
3404
+ issues.push({ level: "error", message: error instanceof Error ? error.message : String(error) });
3405
+ }
3406
+ }
2583
3407
  const errors = issues.filter((issue) => issue.level === "error");
2584
3408
  const warnings = issues.filter((issue) => issue.level === "warning");
2585
3409
  console.log(`${manifestPath} \u2014 kind ${manifest.kind ?? "(unknown)"}, ${manifest.metadata?.name ?? "?"}@${manifest.metadata?.version ?? "?"}`);
@@ -2678,7 +3502,7 @@ function registerWorkspace(program2) {
2678
3502
  }
2679
3503
 
2680
3504
  // src/index.ts
2681
- var program = new Command().name("verentis").description("Verentis CLI \u2014 admin, marketplace publishing, and the engine/app developer loop").version("0.2.0");
3505
+ var program = new Command().enablePositionalOptions().name("verentis").description("Verentis CLI \u2014 admin, marketplace publishing, and the engine/app developer loop").version(getCliVersion());
2682
3506
  registerLogin(program);
2683
3507
  registerUse(program);
2684
3508
  registerAccount(program);
@@ -2697,13 +3521,20 @@ registerPack(program);
2697
3521
  registerRegistry(program);
2698
3522
  registerPackage(program);
2699
3523
  registerApi(program);
2700
- program.parseAsync().catch((error) => {
2701
- if (error instanceof ApiError || error instanceof Error) {
2702
- console.error(`Error: ${error.message}`);
2703
- } else {
2704
- console.error(error);
3524
+ registerUpdate(program);
3525
+ async function main() {
3526
+ try {
3527
+ await program.parseAsync();
3528
+ } catch (error) {
3529
+ if (error instanceof ApiError || error instanceof Error) {
3530
+ console.error(`Error: ${error.message}`);
3531
+ } else {
3532
+ console.error(error);
3533
+ }
3534
+ process.exitCode = 1;
2705
3535
  }
2706
- process.exitCode = 1;
2707
- });
3536
+ await notifyOnExit(process.argv);
3537
+ }
3538
+ void main();
2708
3539
  //# sourceMappingURL=index.js.map
2709
3540
  //# sourceMappingURL=index.js.map