@hexclave/cli 1.0.68 → 1.0.70

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
@@ -18,6 +18,7 @@ import fs from "node:fs";
18
18
  import path from "node:path";
19
19
  import { createUrlIfValid } from "@hexclave/shared/dist/utils/urls";
20
20
  import { createTar } from "@hexclave/shared/dist/utils/tar";
21
+ import * as zlib from "node:zlib";
21
22
  import { gzipSync } from "node:zlib";
22
23
  import { hexclaveDevEnvStatePath } from "@hexclave/shared/dist/utils/dev-env-state-path";
23
24
  import { createHash, randomBytes } from "crypto";
@@ -32,6 +33,7 @@ import { createInitPrompt } from "@hexclave/shared/dist/helpers/init-prompt";
32
33
  import { execFileSync, spawn } from "child_process";
33
34
  import { Readable } from "stream";
34
35
  import { pipeline } from "stream/promises";
36
+ import * as tar from "tar";
35
37
  import extractZip from "extract-zip";
36
38
  import { randomBytes as randomBytes$1 } from "node:crypto";
37
39
  import { KnownErrors } from "@hexclave/shared";
@@ -884,7 +886,7 @@ async function dashboardRequest(path, options, secret, port) {
884
886
  throw new CliError(`Failed to reach local Hexclave dashboard at ${url}: ${errorMessage(error)}`);
885
887
  }
886
888
  }
887
- function isRecord$1(value) {
889
+ function isRecord$2(value) {
888
890
  return typeof value === "object" && value !== null && !Array.isArray(value);
889
891
  }
890
892
  async function responseErrorMessage(response) {
@@ -892,10 +894,10 @@ async function responseErrorMessage(response) {
892
894
  if (text.length === 0) return "empty response body";
893
895
  try {
894
896
  const parsed = JSON.parse(text);
895
- if (isRecord$1(parsed)) {
897
+ if (isRecord$2(parsed)) {
896
898
  const error = parsed.error;
897
899
  if (typeof error === "string") return error;
898
- if (isRecord$1(error) && typeof error.message === "string") return error.message;
900
+ if (isRecord$2(error) && typeof error.message === "string") return error.message;
899
901
  }
900
902
  } catch {}
901
903
  return text;
@@ -1842,6 +1844,9 @@ const DASHBOARD_CACHE_DIR_NAME = "dashboards";
1842
1844
  const DASHBOARD_COMPLETE_MARKER = ".hexclave-complete";
1843
1845
  const LOG_PREFIX$1 = "[Hexclave] ";
1844
1846
  const SAFE_VERSION_REGEX = /^v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/;
1847
+ function isRecord$1(value) {
1848
+ return value != null && typeof value === "object" && !Array.isArray(value);
1849
+ }
1845
1850
  const MANIFEST_FETCH_TIMEOUT_MS = 1e4;
1846
1851
  const DASHBOARD_DOWNLOAD_TIMEOUT_MS = 5 * 6e4;
1847
1852
  function isAllowedDownloadUrl(url) {
@@ -1855,19 +1860,65 @@ function isAllowedDownloadUrl(url) {
1855
1860
  if (parsed.protocol === "http:") return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]" || parsed.hostname === "::1";
1856
1861
  return false;
1857
1862
  }
1863
+ const DASHBOARD_PLATFORM_KEYS = [
1864
+ "darwin-arm64",
1865
+ "darwin-x64",
1866
+ "linux-arm64",
1867
+ "linux-x64",
1868
+ "win32-arm64",
1869
+ "win32-x64"
1870
+ ];
1858
1871
  function logDashboard(message) {
1859
1872
  console.warn(`${LOG_PREFIX$1}${message}`);
1860
1873
  }
1861
1874
  function parseDashboardManifest(raw) {
1862
- if (raw == null || typeof raw !== "object") return null;
1875
+ if (!isRecord$1(raw)) return null;
1863
1876
  const manifest = raw;
1864
1877
  if (typeof manifest.version !== "string" || !SAFE_VERSION_REGEX.test(manifest.version)) return null;
1865
1878
  if (typeof manifest.sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(manifest.sha256)) return null;
1866
1879
  if (typeof manifest.url !== "string" || !isAllowedDownloadUrl(manifest.url)) return null;
1880
+ let platformArchives;
1881
+ if (manifest.platformArchives != null) {
1882
+ if (!isRecord$1(manifest.platformArchives)) return {
1883
+ version: manifest.version,
1884
+ sha256: manifest.sha256.toLowerCase(),
1885
+ url: manifest.url
1886
+ };
1887
+ const validPlatformArchives = {};
1888
+ for (const [platform, value] of Object.entries(manifest.platformArchives)) {
1889
+ if (!DASHBOARD_PLATFORM_KEYS.some((supportedKey) => supportedKey === platform)) continue;
1890
+ if (!isRecord$1(value)) continue;
1891
+ const archive = value;
1892
+ if (typeof archive.sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(archive.sha256) || typeof archive.url !== "string" || !isAllowedDownloadUrl(archive.url)) continue;
1893
+ validPlatformArchives[platform] = {
1894
+ sha256: archive.sha256.toLowerCase(),
1895
+ url: archive.url
1896
+ };
1897
+ }
1898
+ if (Object.keys(validPlatformArchives).length > 0) platformArchives = validPlatformArchives;
1899
+ }
1867
1900
  return {
1868
1901
  version: manifest.version,
1869
1902
  sha256: manifest.sha256.toLowerCase(),
1903
+ url: manifest.url,
1904
+ ...platformArchives == null ? {} : { platformArchives }
1905
+ };
1906
+ }
1907
+ function dashboardPlatformKey(platform = process.platform, arch = process.arch) {
1908
+ const key = `${platform}-${arch}`;
1909
+ return DASHBOARD_PLATFORM_KEYS.find((supportedKey) => supportedKey === key);
1910
+ }
1911
+ function hasZstdSupport(zlibModule = zlib) {
1912
+ return typeof zlibModule.createZstdCompress === "function" && typeof zlibModule.createZstdDecompress === "function";
1913
+ }
1914
+ function selectDashboardArchive(manifest, platformKey = dashboardPlatformKey(), zstdAvailable = hasZstdSupport()) {
1915
+ const platformArchive = !zstdAvailable || platformKey == null ? void 0 : manifest.platformArchives?.[platformKey];
1916
+ return platformArchive == null ? {
1917
+ sha256: manifest.sha256,
1870
1918
  url: manifest.url
1919
+ } : {
1920
+ ...platformArchive,
1921
+ cacheSuffix: platformKey
1871
1922
  };
1872
1923
  }
1873
1924
  function dashboardDirOverride(env = process.env) {
@@ -1881,11 +1932,11 @@ function dashboardManifestUrl(env = process.env) {
1881
1932
  function dashboardCacheRoot() {
1882
1933
  return join(dirname(devEnvStatePath()), DASHBOARD_CACHE_DIR_NAME);
1883
1934
  }
1884
- function dashboardVersionDir(version) {
1885
- return join(dashboardCacheRoot(), version);
1935
+ function dashboardVersionDir(version, cacheSuffix) {
1936
+ return join(dashboardCacheRoot(), cacheSuffix == null ? version : `${version}--${cacheSuffix}`);
1886
1937
  }
1887
- function isDashboardCached(version) {
1888
- const dir = dashboardVersionDir(version);
1938
+ function isDashboardCached(version, cacheSuffix, cacheRoot = dashboardCacheRoot()) {
1939
+ const dir = join(cacheRoot, cacheSuffix == null ? version : `${version}--${cacheSuffix}`);
1889
1940
  return existsSync(join(dir, DASHBOARD_COMPLETE_MARKER)) && existsSync(join(dir, DASHBOARD_SERVER_RELATIVE_PATH));
1890
1941
  }
1891
1942
  function parseVersion(version) {
@@ -1917,10 +1968,19 @@ function isVersionNewer$1(candidate, current) {
1917
1968
  if (candidate.hasPrerelease !== current.hasPrerelease) return !candidate.hasPrerelease;
1918
1969
  return false;
1919
1970
  }
1920
- function latestCachedDashboardVersion() {
1921
- const root = dashboardCacheRoot();
1922
- if (!existsSync(root)) return void 0;
1923
- return pickLatestVersion(readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isDashboardCached(entry.name)).map((entry) => entry.name));
1971
+ function latestCachedDashboard(platformKey, cacheRoot = dashboardCacheRoot()) {
1972
+ if (!existsSync(cacheRoot)) return void 0;
1973
+ const suffix = platformKey == null ? "" : `--${platformKey}`;
1974
+ const cached = readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (platformKey == null ? !DASHBOARD_PLATFORM_KEYS.some((supportedKey) => entry.name.endsWith(`--${supportedKey}`)) : entry.name.endsWith(suffix))).map((entry) => {
1975
+ const version = suffix.length === 0 ? entry.name : entry.name.slice(0, -suffix.length);
1976
+ const root = join(cacheRoot, platformKey == null ? version : `${version}--${platformKey}`);
1977
+ return isDashboardCached(version, platformKey, cacheRoot) ? {
1978
+ version,
1979
+ root
1980
+ } : void 0;
1981
+ }).filter((entry) => entry != null);
1982
+ const version = pickLatestVersion(cached.map((entry) => entry.version));
1983
+ return version == null ? void 0 : cached.find((entry) => entry.version === version);
1924
1984
  }
1925
1985
  async function fetchDashboardManifest(env = process.env) {
1926
1986
  const url = dashboardManifestUrl(env);
@@ -1945,39 +2005,44 @@ async function sha256File(path) {
1945
2005
  await pipeline(createReadStream(path), hash);
1946
2006
  return hash.digest("hex");
1947
2007
  }
1948
- async function downloadDashboardRelease(manifest, onProgress) {
2008
+ async function downloadDashboardRelease(manifest, archive, onProgress) {
1949
2009
  const cacheRoot = dashboardCacheRoot();
1950
2010
  mkdirSync(cacheRoot, { recursive: true });
1951
2011
  const suffix = `${process.pid}-${randomBytes(8).toString("hex")}`;
1952
- const tmpZip = join(cacheRoot, `.download-${manifest.version}-${suffix}.zip`);
2012
+ const tmpArchive = join(cacheRoot, `.download-${manifest.version}-${suffix}${archive.cacheSuffix == null ? ".zip" : ".tar.zst"}`);
1953
2013
  const tmpDir = join(cacheRoot, `.extract-${manifest.version}-${suffix}`);
1954
- const targetDir = dashboardVersionDir(manifest.version);
2014
+ const targetDir = dashboardVersionDir(manifest.version, archive.cacheSuffix);
1955
2015
  try {
1956
2016
  onProgress?.(`Downloading Hexclave dashboard ${manifest.version}`);
1957
- const response = await fetch(manifest.url, {
2017
+ const response = await fetch(archive.url, {
1958
2018
  redirect: "follow",
1959
2019
  signal: AbortSignal.timeout(DASHBOARD_DOWNLOAD_TIMEOUT_MS)
1960
2020
  });
1961
2021
  if (!isAllowedDownloadUrl(response.url)) throw new CliError(`Dashboard ${manifest.version} download was redirected to a disallowed URL (${response.url}).`);
1962
- if (!response.ok || response.body == null) throw new CliError(`Failed to download dashboard ${manifest.version} (HTTP ${response.status}) from ${manifest.url}.`);
1963
- await pipeline(Readable.fromWeb(response.body), createWriteStream(tmpZip));
2022
+ if (!response.ok || response.body == null) throw new CliError(`Failed to download dashboard ${manifest.version} (HTTP ${response.status}) from ${archive.url}.`);
2023
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(tmpArchive));
1964
2024
  onProgress?.(`Verifying Hexclave dashboard ${manifest.version}`);
1965
- const digest = await sha256File(tmpZip);
1966
- if (digest !== manifest.sha256) throw new CliError(`Dashboard ${manifest.version} failed its integrity check (expected ${manifest.sha256}, got ${digest}).`);
2025
+ const digest = await sha256File(tmpArchive);
2026
+ if (digest !== archive.sha256) throw new CliError(`Dashboard ${manifest.version} failed its integrity check (expected ${archive.sha256}, got ${digest}).`);
1967
2027
  rmSync(tmpDir, {
1968
2028
  recursive: true,
1969
2029
  force: true
1970
2030
  });
1971
2031
  mkdirSync(tmpDir, { recursive: true });
1972
2032
  onProgress?.(`Extracting Hexclave dashboard ${manifest.version}`);
1973
- await extractZip(tmpZip, { dir: tmpDir });
2033
+ if (archive.cacheSuffix == null) await extractZip(tmpArchive, { dir: tmpDir });
2034
+ else await pipeline(createReadStream(tmpArchive), zlib.createZstdDecompress(), tar.x({
2035
+ cwd: tmpDir,
2036
+ strict: true,
2037
+ preservePaths: false
2038
+ }));
1974
2039
  if (!existsSync(join(tmpDir, DASHBOARD_SERVER_RELATIVE_PATH))) throw new CliError(`Dashboard ${manifest.version} archive is missing its server entrypoint.`);
1975
- writeFileSync(join(tmpDir, DASHBOARD_COMPLETE_MARKER), `${manifest.sha256}\n`);
1976
- if (isDashboardCached(manifest.version)) return;
2040
+ writeFileSync(join(tmpDir, DASHBOARD_COMPLETE_MARKER), `${archive.sha256}\n`);
2041
+ if (isDashboardCached(manifest.version, archive.cacheSuffix)) return;
1977
2042
  try {
1978
2043
  renameSync(tmpDir, targetDir);
1979
2044
  } catch {
1980
- if (isDashboardCached(manifest.version)) return;
2045
+ if (isDashboardCached(manifest.version, archive.cacheSuffix)) return;
1981
2046
  rmSync(targetDir, {
1982
2047
  recursive: true,
1983
2048
  force: true
@@ -1985,7 +2050,7 @@ async function downloadDashboardRelease(manifest, onProgress) {
1985
2050
  renameSync(tmpDir, targetDir);
1986
2051
  }
1987
2052
  } finally {
1988
- rmSync(tmpZip, { force: true });
2053
+ rmSync(tmpArchive, { force: true });
1989
2054
  rmSync(tmpDir, {
1990
2055
  recursive: true,
1991
2056
  force: true
@@ -2003,35 +2068,30 @@ async function resolveDashboardRuntime(opts = {}) {
2003
2068
  }
2004
2069
  const manifest = opts.manifest !== void 0 ? opts.manifest : await fetchDashboardManifest();
2005
2070
  if (manifest != null) {
2006
- if (isDashboardCached(manifest.version)) return {
2007
- root: dashboardVersionDir(manifest.version),
2071
+ const archive = selectDashboardArchive(manifest);
2072
+ if (isDashboardCached(manifest.version, archive.cacheSuffix)) return {
2073
+ root: dashboardVersionDir(manifest.version, archive.cacheSuffix),
2008
2074
  version: manifest.version
2009
2075
  };
2010
2076
  try {
2011
- await downloadDashboardRelease(manifest, opts.onProgress);
2077
+ await downloadDashboardRelease(manifest, archive, opts.onProgress);
2012
2078
  return {
2013
- root: dashboardVersionDir(manifest.version),
2079
+ root: dashboardVersionDir(manifest.version, archive.cacheSuffix),
2014
2080
  version: manifest.version
2015
2081
  };
2016
2082
  } catch (error) {
2017
- const cached = latestCachedDashboardVersion();
2083
+ const cached = latestCachedDashboard(dashboardPlatformKey()) ?? latestCachedDashboard();
2018
2084
  if (cached != null) {
2019
- logDashboard(`Failed to download dashboard ${manifest.version} (${errorMessage$1(error)}); using cached ${cached}.`);
2020
- return {
2021
- root: dashboardVersionDir(cached),
2022
- version: cached
2023
- };
2085
+ logDashboard(`Failed to download dashboard ${manifest.version} (${errorMessage$1(error)}); using cached ${cached.version}.`);
2086
+ return cached;
2024
2087
  }
2025
2088
  throw error;
2026
2089
  }
2027
2090
  }
2028
- const cached = latestCachedDashboardVersion();
2091
+ const cached = latestCachedDashboard(dashboardPlatformKey()) ?? latestCachedDashboard();
2029
2092
  if (cached != null) {
2030
- logDashboard(`Offline: using cached Hexclave dashboard ${cached}.`);
2031
- return {
2032
- root: dashboardVersionDir(cached),
2033
- version: cached
2034
- };
2093
+ logDashboard(`Offline: using cached Hexclave dashboard ${cached.version}.`);
2094
+ return cached;
2035
2095
  }
2036
2096
  throw new CliError(["Could not download the Hexclave development-environment dashboard and no cached copy is available.", `Check your network connection, or set ${DASHBOARD_DIR_OVERRIDE_ENV_VAR} to a local dashboard build.`].join(" "));
2037
2097
  }