@onexapis/cli 1.0.0 → 1.0.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.
package/dist/index.js CHANGED
@@ -9,7 +9,9 @@ var ora = require('ora');
9
9
  var fs = require('fs-extra');
10
10
  var ejs = require('ejs');
11
11
  var clientS3 = require('@aws-sdk/client-s3');
12
- var glob = require('glob');
12
+ var os = require('os');
13
+ var archiver = require('archiver');
14
+ var AdmZip = require('adm-zip');
13
15
 
14
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
17
 
@@ -20,6 +22,9 @@ var chalk4__default = /*#__PURE__*/_interopDefault(chalk4);
20
22
  var ora__default = /*#__PURE__*/_interopDefault(ora);
21
23
  var fs__default = /*#__PURE__*/_interopDefault(fs);
22
24
  var ejs__default = /*#__PURE__*/_interopDefault(ejs);
25
+ var os__default = /*#__PURE__*/_interopDefault(os);
26
+ var archiver__default = /*#__PURE__*/_interopDefault(archiver);
27
+ var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
23
28
 
24
29
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
25
30
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -99,8 +104,8 @@ function validateThemeName(name) {
99
104
  return /^[a-z][a-z0-9-]*$/.test(name);
100
105
  }
101
106
  function pathExists(filePath) {
102
- const fs7 = __require("fs-extra");
103
- return fs7.existsSync(filePath);
107
+ const fs8 = __require("fs-extra");
108
+ return fs8.existsSync(filePath);
104
109
  }
105
110
  function validateCategory(category) {
106
111
  const validCategories = [
@@ -217,18 +222,19 @@ function getProjectRoot() {
217
222
  return process.cwd();
218
223
  }
219
224
  function getThemesDir() {
220
- try {
221
- return path__default.default.join(getProjectRoot(), "src/themes");
222
- } catch (e) {
223
- return path__default.default.join(getProjectRoot(), "themes");
225
+ const root = getProjectRoot();
226
+ const themesDir = path__default.default.join(root, "themes");
227
+ if (fs__default.default.existsSync(themesDir)) {
228
+ return themesDir;
224
229
  }
230
+ return path__default.default.join(root, "src/themes");
225
231
  }
226
232
  function getFeaturesDir() {
227
233
  return path__default.default.join(getProjectRoot(), "src/features");
228
234
  }
229
235
  function isOneXProject() {
230
236
  const root = getProjectRoot();
231
- return fs__default.default.existsSync(path__default.default.join(root, "src/themes")) && fs__default.default.existsSync(path__default.default.join(root, "src/lib/registry"));
237
+ return fs__default.default.existsSync(path__default.default.join(root, "themes")) || fs__default.default.existsSync(path__default.default.join(root, "src/themes"));
232
238
  }
233
239
  function ensureOneXProject() {
234
240
  if (!isOneXProject()) {
@@ -245,12 +251,12 @@ function listThemes() {
245
251
  }
246
252
  return fs__default.default.readdirSync(themesDir).filter((name) => {
247
253
  const themePath = path__default.default.join(themesDir, name);
248
- return fs__default.default.statSync(themePath).isDirectory() && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
254
+ return fs__default.default.statSync(themePath).isDirectory() && (fs__default.default.existsSync(path__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts")));
249
255
  });
250
256
  }
251
257
  function themeExists(themeName) {
252
258
  const themePath = path__default.default.join(getThemesDir(), themeName);
253
- return fs__default.default.existsSync(themePath) && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
259
+ return fs__default.default.existsSync(themePath) && (fs__default.default.existsSync(path__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts")));
254
260
  }
255
261
  function detectPackageManager() {
256
262
  const userAgent = process.env.npm_config_user_agent || "";
@@ -444,7 +450,9 @@ Add your theme-specific blocks here.
444
450
  logger.newLine();
445
451
  logger.section("Theme structure:");
446
452
  logger.log(" src/manifest.ts - Theme manifest and exports");
447
- logger.log(" src/config.ts - Design tokens (colors, typography, etc.)");
453
+ logger.log(
454
+ " src/config.ts - Design tokens (colors, typography, etc.)"
455
+ );
448
456
  logger.log(" src/layout.ts - Header and footer configuration");
449
457
  logger.log(" src/sections/ - Custom sections for your theme");
450
458
  logger.log(" src/blocks/ - Reusable blocks");
@@ -1447,8 +1455,16 @@ async function listThemesInfo() {
1447
1455
  }
1448
1456
  logger.log("");
1449
1457
  for (const theme of themes) {
1450
- const manifestPath = path__default.default.join(getThemesDir(), theme, "manifest.ts");
1451
- const manifestContent = fs__default.default.readFileSync(manifestPath, "utf-8");
1458
+ const themeDir = path__default.default.join(getThemesDir(), theme);
1459
+ const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
1460
+ let manifestContent = "";
1461
+ for (const candidate of candidates) {
1462
+ const candidatePath = path__default.default.join(themeDir, candidate);
1463
+ if (fs__default.default.existsSync(candidatePath)) {
1464
+ manifestContent = fs__default.default.readFileSync(candidatePath, "utf-8");
1465
+ break;
1466
+ }
1467
+ }
1452
1468
  const nameMatch = manifestContent.match(/name:\s*["'](.+)["']/);
1453
1469
  const versionMatch = manifestContent.match(/version:\s*["'](.+)["']/);
1454
1470
  const descMatch = manifestContent.match(/description:\s*["'](.+)["']/);
@@ -1581,13 +1597,11 @@ function getS3Client() {
1581
1597
  return new clientS3.S3Client({
1582
1598
  endpoint: endpointUrl,
1583
1599
  region: "us-east-1",
1584
- // MinIO doesn't use real regions
1585
1600
  credentials: {
1586
1601
  accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
1587
1602
  secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
1588
1603
  },
1589
1604
  forcePathStyle: true
1590
- // Required for MinIO
1591
1605
  });
1592
1606
  }
1593
1607
  if (adapterMode === "local") {
@@ -1657,36 +1671,40 @@ async function readManifest() {
1657
1671
  "No manifest.ts or package.json found. Are you in a theme directory?"
1658
1672
  );
1659
1673
  }
1660
- function getContentType(filename) {
1661
- const ext = path__default.default.extname(filename).toLowerCase();
1662
- const types = {
1663
- ".js": "application/javascript",
1664
- ".mjs": "application/javascript",
1665
- ".json": "application/json",
1666
- ".css": "text/css",
1667
- ".png": "image/png",
1668
- ".jpg": "image/jpeg",
1669
- ".jpeg": "image/jpeg",
1670
- ".svg": "image/svg+xml",
1671
- ".woff2": "font/woff2",
1672
- ".woff": "font/woff",
1673
- ".ttf": "font/ttf",
1674
- ".ts": "text/plain"
1675
- };
1676
- return types[ext] || "application/octet-stream";
1674
+ async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
1675
+ return new Promise((resolve, reject) => {
1676
+ const output = fs__default.default.createWriteStream(outputPath);
1677
+ const archive = archiver__default.default("zip", { zlib: { level: 6 } });
1678
+ output.on("close", () => resolve());
1679
+ archive.on("error", (err) => reject(err));
1680
+ archive.pipe(output);
1681
+ archive.glob("**/*", {
1682
+ cwd: sourceDir,
1683
+ dot: true,
1684
+ ignore: excludePatterns
1685
+ });
1686
+ archive.finalize();
1687
+ });
1677
1688
  }
1678
- async function uploadFile(s3Client, bucket, themeId, version, baseDir, file) {
1679
- const filePath = path__default.default.join(baseDir, file);
1680
- const content = await fs__default.default.readFile(filePath);
1681
- const s3Key = `themes/${themeId}/${version}/${file}`;
1682
- await s3Client.send(
1683
- new clientS3.PutObjectCommand({
1684
- Bucket: bucket,
1685
- Key: s3Key,
1686
- Body: content,
1687
- ContentType: getContentType(file)
1688
- })
1689
- );
1689
+ async function findSourceDir(themeId, explicitDir) {
1690
+ if (explicitDir) {
1691
+ if (await fs__default.default.pathExists(explicitDir)) return explicitDir;
1692
+ return null;
1693
+ }
1694
+ const searchPaths = [
1695
+ process.cwd(),
1696
+ path__default.default.resolve(process.cwd(), `../../themes/${themeId}`),
1697
+ path__default.default.resolve(process.cwd(), `../themes/${themeId}`)
1698
+ ];
1699
+ const markers = ["theme.config.ts", "bundle-entry.ts"];
1700
+ for (const dir of searchPaths) {
1701
+ for (const marker of markers) {
1702
+ if (await fs__default.default.pathExists(path__default.default.join(dir, marker))) {
1703
+ return dir;
1704
+ }
1705
+ }
1706
+ }
1707
+ return null;
1690
1708
  }
1691
1709
  async function updateLatestPointer(s3Client, bucket, themeId, version) {
1692
1710
  const latestData = {
@@ -1704,12 +1722,18 @@ async function updateLatestPointer(s3Client, bucket, themeId, version) {
1704
1722
  }
1705
1723
  async function uploadCommand(options) {
1706
1724
  logger.header("Upload Theme to S3");
1707
- ensureOneXProject();
1708
1725
  const spinner = ora__default.default("Preparing theme upload...").start();
1709
1726
  try {
1710
- const manifest = await readManifest();
1711
- const themeId = manifest.themeId;
1712
- const version = options.version || manifest.version || "1.0.0";
1727
+ let themeId;
1728
+ let version;
1729
+ if (options.theme) {
1730
+ themeId = options.theme;
1731
+ version = options.version || "1.0.0";
1732
+ } else {
1733
+ const manifest = await readManifest();
1734
+ themeId = manifest.themeId;
1735
+ version = options.version || manifest.version || "1.0.0";
1736
+ }
1713
1737
  spinner.text = `Found theme: ${themeId}@${version}`;
1714
1738
  const bucket = options.bucket || getBucketName(options.environment);
1715
1739
  const s3Client = getS3Client();
@@ -1730,67 +1754,113 @@ async function uploadCommand(options) {
1730
1754
  process.exit(1);
1731
1755
  }
1732
1756
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
1733
- spinner.start(`Scanning files...`);
1734
- const files = await glob.glob("**/*", {
1735
- cwd: compiledDir,
1736
- nodir: true,
1737
- dot: true
1738
- });
1739
- if (files.length === 0) {
1740
- spinner.fail(chalk4__default.default.red("No files found in compiled theme directory"));
1741
- process.exit(1);
1742
- }
1743
- spinner.succeed(`Found ${files.length} files to upload`);
1757
+ spinner.start("Creating bundle.zip...");
1758
+ const tmpDir = os__default.default.tmpdir();
1759
+ const bundleZipPath = path__default.default.join(tmpDir, `${themeId}-${version}-bundle.zip`);
1760
+ await createZipFromDir(compiledDir, bundleZipPath);
1761
+ const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
1762
+ const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
1763
+ spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
1744
1764
  if (options.dryRun) {
1745
- spinner.info(chalk4__default.default.yellow("Dry run mode - no files will be uploaded"));
1746
- console.log(chalk4__default.default.gray("\nFiles to upload:"));
1747
- const displayFiles = files.slice(0, 10);
1748
- displayFiles.forEach((file) => console.log(chalk4__default.default.gray(` - ${file}`)));
1749
- if (files.length > 10) {
1750
- console.log(chalk4__default.default.gray(` ... and ${files.length - 10} more files
1751
- `));
1752
- }
1753
- console.log(chalk4__default.default.cyan(`
1754
- S3 bucket: ${bucket}`));
1765
+ spinner.info(chalk4__default.default.yellow("Dry run mode \u2014 no files will be uploaded"));
1766
+ console.log();
1767
+ console.log(chalk4__default.default.gray(` bundle.zip: ${bundleSizeMB} MB`));
1755
1768
  console.log(
1756
- chalk4__default.default.cyan(`S3 path: s3://${bucket}/themes/${themeId}/${version}/
1757
- `)
1769
+ chalk4__default.default.cyan(
1770
+ ` S3 path: s3://${bucket}/themes/${themeId}/${version}/bundle.zip`
1771
+ )
1758
1772
  );
1773
+ if (!options.skipSource) {
1774
+ const sourceDir = await findSourceDir(themeId, options.sourceDir);
1775
+ if (sourceDir) {
1776
+ console.log(chalk4__default.default.gray(` source dir: ${sourceDir}`));
1777
+ console.log(
1778
+ chalk4__default.default.cyan(
1779
+ ` S3 path: s3://${bucket}/themes/${themeId}/${version}/source.zip`
1780
+ )
1781
+ );
1782
+ } else {
1783
+ console.log(
1784
+ chalk4__default.default.yellow(" source dir: not found (source.zip will be skipped)")
1785
+ );
1786
+ }
1787
+ }
1788
+ console.log();
1789
+ await fs__default.default.remove(bundleZipPath);
1759
1790
  return;
1760
1791
  }
1761
- spinner.start(`Uploading to S3: ${bucket}...`);
1762
- let uploaded = 0;
1763
- const total = files.length;
1764
- const batchSize = 10;
1765
- for (let i = 0; i < files.length; i += batchSize) {
1766
- const batch = files.slice(i, i + batchSize);
1767
- await Promise.all(
1768
- batch.map(async (file) => {
1769
- await uploadFile(
1770
- s3Client,
1771
- bucket,
1772
- themeId,
1773
- version,
1774
- compiledDir,
1775
- file
1776
- );
1777
- uploaded++;
1778
- spinner.text = `Uploading... ${uploaded}/${total} files (${Math.round(uploaded / total * 100)}%)`;
1779
- })
1780
- );
1792
+ spinner.start("Uploading bundle.zip to S3...");
1793
+ const bundleS3Key = `themes/${themeId}/${version}/bundle.zip`;
1794
+ await s3Client.send(
1795
+ new clientS3.PutObjectCommand({
1796
+ Bucket: bucket,
1797
+ Key: bundleS3Key,
1798
+ Body: bundleZipBuffer,
1799
+ ContentType: "application/zip"
1800
+ })
1801
+ );
1802
+ spinner.succeed(
1803
+ `Uploaded bundle.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
1804
+ );
1805
+ await fs__default.default.remove(bundleZipPath);
1806
+ let sourceUploaded = false;
1807
+ if (!options.skipSource) {
1808
+ spinner.start("Looking for source directory...");
1809
+ const sourceDir = await findSourceDir(themeId, options.sourceDir);
1810
+ if (sourceDir) {
1811
+ spinner.succeed(`Found source at: ${sourceDir}`);
1812
+ spinner.start("Creating source.zip...");
1813
+ const sourceZipPath = path__default.default.join(
1814
+ tmpDir,
1815
+ `${themeId}-${version}-source.zip`
1816
+ );
1817
+ await createZipFromDir(sourceDir, sourceZipPath, [
1818
+ "node_modules/**",
1819
+ "dist/**",
1820
+ ".git/**",
1821
+ "*.zip",
1822
+ ".next/**",
1823
+ ".turbo/**"
1824
+ ]);
1825
+ const sourceZipBuffer = await fs__default.default.readFile(sourceZipPath);
1826
+ const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
1827
+ spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
1828
+ spinner.start("Uploading source.zip to S3...");
1829
+ const sourceS3Key = `themes/${themeId}/${version}/source.zip`;
1830
+ await s3Client.send(
1831
+ new clientS3.PutObjectCommand({
1832
+ Bucket: bucket,
1833
+ Key: sourceS3Key,
1834
+ Body: sourceZipBuffer,
1835
+ ContentType: "application/zip"
1836
+ })
1837
+ );
1838
+ spinner.succeed(
1839
+ `Uploaded source.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
1840
+ );
1841
+ await fs__default.default.remove(sourceZipPath);
1842
+ sourceUploaded = true;
1843
+ } else {
1844
+ spinner.warn(
1845
+ chalk4__default.default.yellow("Source directory not found \u2014 skipping source.zip")
1846
+ );
1847
+ }
1781
1848
  }
1782
- spinner.succeed(`Uploaded ${total} files to S3`);
1783
1849
  spinner.start("Updating latest.json pointer...");
1784
1850
  await updateLatestPointer(s3Client, bucket, themeId, version);
1785
1851
  spinner.succeed("Updated latest.json pointer");
1786
1852
  console.log();
1787
- logger.success(chalk4__default.default.green.bold("\u2705 Theme uploaded successfully!"));
1853
+ logger.success(chalk4__default.default.green.bold("Theme uploaded successfully!"));
1788
1854
  console.log();
1789
1855
  console.log(
1790
1856
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version}`)
1791
1857
  );
1792
1858
  console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
1793
- console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
1859
+ console.log(
1860
+ chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(
1861
+ `bundle.zip${sourceUploaded ? " + source.zip" : ""}`
1862
+ )
1863
+ );
1794
1864
  console.log(
1795
1865
  chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
1796
1866
  );
@@ -1894,79 +1964,45 @@ async function resolveLatestVersion(s3Client, bucket, themeId) {
1894
1964
  );
1895
1965
  }
1896
1966
  }
1897
- async function downloadManifest(s3Client, bucket, themeId, version) {
1898
- try {
1899
- const response = await s3Client.send(
1900
- new clientS3.GetObjectCommand({
1901
- Bucket: bucket,
1902
- Key: `themes/${themeId}/${version}/manifest.json`
1903
- })
1904
- );
1905
- const body = await streamToString(response.Body);
1906
- return JSON.parse(body);
1907
- } catch (error) {
1908
- throw new Error(
1909
- `Failed to download manifest for ${themeId}@${version}: ${error.message}
1910
- The theme may not exist in S3 bucket "${bucket}".`
1911
- );
1912
- }
1913
- }
1914
- async function downloadFile(s3Client, bucket, themeId, version, file, outputDir) {
1915
- const s3Key = `themes/${themeId}/${version}/${file}`;
1916
- const localPath = path__default.default.join(outputDir, file);
1917
- await fs__default.default.ensureDir(path__default.default.dirname(localPath));
1918
- try {
1919
- const response = await s3Client.send(
1920
- new clientS3.GetObjectCommand({
1921
- Bucket: bucket,
1922
- Key: s3Key
1923
- })
1924
- );
1925
- const content = await streamToBuffer(response.Body);
1926
- await fs__default.default.writeFile(localPath, content);
1927
- } catch (error) {
1928
- throw new Error(`Failed to download ${file}: ${error.message}`);
1929
- }
1930
- }
1931
- function collectFilesToDownload(manifest) {
1932
- const files = ["manifest.json"];
1933
- if (manifest.output) {
1934
- if (manifest.output.entry) {
1935
- files.push(manifest.output.entry);
1936
- }
1937
- if (manifest.output.chunks) {
1938
- files.push(...manifest.output.chunks);
1939
- }
1940
- if (manifest.output.assets) {
1941
- files.push(...manifest.output.assets);
1967
+ async function createCompatibilityFiles(outputDir, manifest) {
1968
+ var _a;
1969
+ const entryFile = ((_a = manifest.output) == null ? void 0 : _a.entry) || "bundle-entry.js";
1970
+ if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
1971
+ const hashedPath = path__default.default.join(outputDir, entryFile);
1972
+ const stablePath = path__default.default.join(outputDir, "bundle-entry.js");
1973
+ if (await fs__default.default.pathExists(hashedPath)) {
1974
+ await fs__default.default.copy(hashedPath, stablePath);
1975
+ const mapPath = hashedPath + ".map";
1976
+ if (await fs__default.default.pathExists(mapPath)) {
1977
+ await fs__default.default.copy(mapPath, stablePath + ".map");
1978
+ }
1942
1979
  }
1943
1980
  }
1944
- return files;
1945
- }
1946
- async function createCompatibilityFiles(outputDir) {
1947
1981
  const sectionsRegistryPath = path__default.default.join(outputDir, "sections-registry.js");
1948
1982
  const content = `// Re-export all sections from bundle-entry
1949
1983
  // This file exists to maintain compatibility with the import path
1950
1984
  export * from './bundle-entry.js';
1951
1985
  `;
1952
1986
  await fs__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
1987
+ const pkgJsonPath = path__default.default.join(outputDir, "package.json");
1988
+ await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
1953
1989
  }
1954
1990
  function showDownloadFailureHelp(themeId, bucket) {
1955
1991
  console.log();
1956
- logger.error(chalk4__default.default.red.bold("\u274C Theme download failed"));
1992
+ logger.error(chalk4__default.default.red.bold("Theme download failed"));
1957
1993
  console.log();
1958
1994
  console.log(chalk4__default.default.yellow("Possible reasons:"));
1959
1995
  console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
1960
1996
  console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
1961
1997
  console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
1962
- console.log(chalk4__default.default.gray(" 4. Theme files are incomplete or corrupted"));
1998
+ console.log(chalk4__default.default.gray(" 4. bundle.zip is missing or corrupted"));
1963
1999
  console.log();
1964
2000
  console.log(chalk4__default.default.cyan.bold("To fix this:"));
1965
2001
  console.log();
1966
2002
  console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
1967
2003
  console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
1968
2004
  console.log(chalk4__default.default.gray(" pnpm build"));
1969
- console.log(chalk4__default.default.gray(" pnpm upload"));
2005
+ console.log(chalk4__default.default.gray(" onex upload"));
1970
2006
  console.log();
1971
2007
  console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
1972
2008
  console.log(
@@ -1982,7 +2018,11 @@ function showDownloadFailureHelp(themeId, bucket) {
1982
2018
  );
1983
2019
  console.log();
1984
2020
  console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
1985
- console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2021
+ console.log(
2022
+ chalk4__default.default.gray(
2023
+ ` aws s3 ls s3://${bucket}/themes/${themeId}/`
2024
+ )
2025
+ );
1986
2026
  console.log();
1987
2027
  }
1988
2028
  async function downloadCommand(options) {
@@ -2010,55 +2050,44 @@ async function downloadCommand(options) {
2010
2050
  spinner.succeed(
2011
2051
  `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
2012
2052
  );
2013
- spinner.start("Downloading manifest...");
2014
2053
  }
2015
- const manifest = await downloadManifest(
2016
- s3Client,
2017
- bucket,
2018
- themeId,
2019
- resolvedVersion
2054
+ spinner.start(
2055
+ `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
2056
+ );
2057
+ const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
2058
+ const response = await s3Client.send(
2059
+ new clientS3.GetObjectCommand({
2060
+ Bucket: bucket,
2061
+ Key: s3Key
2062
+ })
2020
2063
  );
2021
- spinner.succeed("Downloaded manifest");
2022
- spinner.start("Preparing output directory...");
2064
+ const zipBuffer = await streamToBuffer(response.Body);
2065
+ const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
2066
+ spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
2067
+ spinner.start("Extracting bundle...");
2023
2068
  await fs__default.default.remove(outputDir);
2024
2069
  await fs__default.default.ensureDir(outputDir);
2025
- spinner.succeed("Output directory ready");
2026
- spinner.start("Downloading theme files...");
2027
- const files = collectFilesToDownload(manifest);
2028
- let downloaded = 0;
2029
- const total = files.length;
2030
- const batchSize = 10;
2031
- for (let i = 0; i < files.length; i += batchSize) {
2032
- const batch = files.slice(i, i + batchSize);
2033
- await Promise.all(
2034
- batch.map(async (file) => {
2035
- await downloadFile(
2036
- s3Client,
2037
- bucket,
2038
- themeId,
2039
- resolvedVersion,
2040
- file,
2041
- outputDir
2042
- );
2043
- downloaded++;
2044
- spinner.text = `Downloading... ${downloaded}/${total} files (${Math.round(downloaded / total * 100)}%)`;
2045
- })
2046
- );
2047
- }
2048
- spinner.succeed(`Downloaded ${total} files`);
2049
- await createCompatibilityFiles(outputDir);
2070
+ const zip = new AdmZip__default.default(zipBuffer);
2071
+ zip.extractAllTo(outputDir, true);
2072
+ const entries = zip.getEntries().filter((e) => !e.isDirectory);
2073
+ spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
2074
+ const manifestPath = path__default.default.join(outputDir, "manifest.json");
2075
+ const manifest = await fs__default.default.readJson(manifestPath);
2076
+ await createCompatibilityFiles(outputDir, manifest);
2050
2077
  console.log();
2051
- logger.success(chalk4__default.default.green.bold("\u2705 Theme downloaded successfully!"));
2078
+ logger.success(chalk4__default.default.green.bold("Theme downloaded successfully!"));
2052
2079
  console.log();
2053
2080
  console.log(
2054
2081
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
2055
2082
  );
2056
2083
  console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
2057
2084
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
2058
- console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
2059
- console.log(
2060
- chalk4__default.default.cyan(" Sections:") + chalk4__default.default.white(` ${manifest.counts.sections}`)
2061
- );
2085
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
2086
+ if (manifest.counts) {
2087
+ console.log(
2088
+ chalk4__default.default.cyan(" Sections:") + chalk4__default.default.white(` ${manifest.counts.sections}`)
2089
+ );
2090
+ }
2062
2091
  console.log();
2063
2092
  } catch (error) {
2064
2093
  spinner.fail(chalk4__default.default.red("Download failed"));
@@ -2069,9 +2098,214 @@ async function downloadCommand(options) {
2069
2098
  process.exit(1);
2070
2099
  }
2071
2100
  }
2101
+ function getS3Client3() {
2102
+ const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2103
+ if (adapterMode === "vps") {
2104
+ const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2105
+ const secure = process.env.MINIO_SECURE === "true";
2106
+ const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2107
+ return new clientS3.S3Client({
2108
+ endpoint: endpointUrl,
2109
+ region: "us-east-1",
2110
+ credentials: {
2111
+ accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
2112
+ secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
2113
+ },
2114
+ forcePathStyle: true
2115
+ });
2116
+ }
2117
+ if (adapterMode === "local") {
2118
+ return new clientS3.S3Client({
2119
+ endpoint: "http://localhost:4569",
2120
+ region: "ap-southeast-1",
2121
+ credentials: {
2122
+ accessKeyId: "S3RVER",
2123
+ secretAccessKey: "S3RVER"
2124
+ },
2125
+ forcePathStyle: true
2126
+ });
2127
+ }
2128
+ return new clientS3.S3Client({
2129
+ region: process.env.AWS_REGION || "ap-southeast-1"
2130
+ });
2131
+ }
2132
+ function getBucketName3(env) {
2133
+ if (process.env.BUCKET_NAME) {
2134
+ return process.env.BUCKET_NAME;
2135
+ }
2136
+ const environment = env || process.env.ENVIRONMENT || "staging";
2137
+ return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
2138
+ }
2139
+ async function streamToString2(stream) {
2140
+ const chunks = [];
2141
+ try {
2142
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2143
+ const chunk = temp.value;
2144
+ chunks.push(Buffer.from(chunk));
2145
+ }
2146
+ } catch (temp) {
2147
+ error = [temp];
2148
+ } finally {
2149
+ try {
2150
+ more && (temp = iter.return) && await temp.call(iter);
2151
+ } finally {
2152
+ if (error)
2153
+ throw error[0];
2154
+ }
2155
+ }
2156
+ return Buffer.concat(chunks).toString("utf-8");
2157
+ }
2158
+ async function streamToBuffer2(stream) {
2159
+ const chunks = [];
2160
+ try {
2161
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2162
+ const chunk = temp.value;
2163
+ chunks.push(Buffer.from(chunk));
2164
+ }
2165
+ } catch (temp) {
2166
+ error = [temp];
2167
+ } finally {
2168
+ try {
2169
+ more && (temp = iter.return) && await temp.call(iter);
2170
+ } finally {
2171
+ if (error)
2172
+ throw error[0];
2173
+ }
2174
+ }
2175
+ return Buffer.concat(chunks);
2176
+ }
2177
+ async function resolveLatestVersion2(s3Client, bucket, themeId) {
2178
+ try {
2179
+ const response = await s3Client.send(
2180
+ new clientS3.GetObjectCommand({
2181
+ Bucket: bucket,
2182
+ Key: `themes/${themeId}/latest.json`
2183
+ })
2184
+ );
2185
+ const body = await streamToString2(response.Body);
2186
+ return JSON.parse(body).version;
2187
+ } catch (error) {
2188
+ throw new Error(
2189
+ `Failed to resolve latest version for theme "${themeId}": ${error.message}`
2190
+ );
2191
+ }
2192
+ }
2193
+ function runInstall(cwd) {
2194
+ return new Promise((resolve) => {
2195
+ const proc = child_process.spawn("pnpm", ["install"], {
2196
+ cwd,
2197
+ stdio: "inherit",
2198
+ shell: true
2199
+ });
2200
+ proc.on("close", (code) => resolve(code === 0));
2201
+ proc.on("error", () => resolve(false));
2202
+ });
2203
+ }
2204
+ async function cloneCommand(themeName, options) {
2205
+ logger.header("Clone Theme Source");
2206
+ const spinner = ora__default.default("Initializing clone...").start();
2207
+ try {
2208
+ const bucket = options.bucket || getBucketName3(options.environment);
2209
+ const outputDir = options.output || path__default.default.resolve(process.cwd(), themeName);
2210
+ const s3Client = getS3Client3();
2211
+ if (await fs__default.default.pathExists(outputDir)) {
2212
+ spinner.fail(
2213
+ chalk4__default.default.red(`Directory already exists: ${outputDir}`)
2214
+ );
2215
+ logger.info(
2216
+ chalk4__default.default.gray(
2217
+ "Use -o to specify a different output directory, or remove the existing directory."
2218
+ )
2219
+ );
2220
+ process.exit(1);
2221
+ }
2222
+ let version = options.version || "latest";
2223
+ if (version === "latest") {
2224
+ spinner.text = "Resolving latest version...";
2225
+ version = await resolveLatestVersion2(s3Client, bucket, themeName);
2226
+ spinner.succeed(`Resolved latest version: ${chalk4__default.default.cyan(version)}`);
2227
+ }
2228
+ spinner.start(
2229
+ `Downloading source.zip for ${themeName}@${version}...`
2230
+ );
2231
+ const s3Key = `themes/${themeName}/${version}/source.zip`;
2232
+ let zipBuffer;
2233
+ try {
2234
+ const response = await s3Client.send(
2235
+ new clientS3.GetObjectCommand({
2236
+ Bucket: bucket,
2237
+ Key: s3Key
2238
+ })
2239
+ );
2240
+ zipBuffer = await streamToBuffer2(response.Body);
2241
+ } catch (error) {
2242
+ spinner.fail(chalk4__default.default.red(`Source not found: s3://${bucket}/${s3Key}`));
2243
+ console.log();
2244
+ console.log(
2245
+ chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
2246
+ );
2247
+ console.log(
2248
+ chalk4__default.default.gray(
2249
+ `Upload source with: onex upload --theme ${themeName}`
2250
+ )
2251
+ );
2252
+ console.log();
2253
+ process.exit(1);
2254
+ }
2255
+ const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
2256
+ spinner.succeed(`Downloaded source.zip (${sizeMB} MB)`);
2257
+ spinner.start(`Extracting to ${outputDir}...`);
2258
+ await fs__default.default.ensureDir(outputDir);
2259
+ const zip = new AdmZip__default.default(zipBuffer);
2260
+ zip.extractAllTo(outputDir, true);
2261
+ const entries = zip.getEntries().filter((e) => !e.isDirectory);
2262
+ spinner.succeed(`Extracted ${entries.length} files`);
2263
+ if (options.install !== false) {
2264
+ const hasPkgJson = await fs__default.default.pathExists(
2265
+ path__default.default.join(outputDir, "package.json")
2266
+ );
2267
+ if (hasPkgJson) {
2268
+ spinner.start("Installing dependencies...");
2269
+ const success = await runInstall(outputDir);
2270
+ if (success) {
2271
+ spinner.succeed("Dependencies installed");
2272
+ } else {
2273
+ spinner.warn(
2274
+ chalk4__default.default.yellow(
2275
+ "Failed to install dependencies \u2014 run 'pnpm install' manually"
2276
+ )
2277
+ );
2278
+ }
2279
+ }
2280
+ }
2281
+ console.log();
2282
+ logger.success(chalk4__default.default.green.bold("Theme cloned successfully!"));
2283
+ console.log();
2284
+ console.log(
2285
+ chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeName}@${version}`)
2286
+ );
2287
+ console.log(chalk4__default.default.cyan(" Location: ") + chalk4__default.default.white(outputDir));
2288
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
2289
+ console.log();
2290
+ console.log(chalk4__default.default.cyan("Next steps:"));
2291
+ console.log(
2292
+ chalk4__default.default.gray(` cd ${path__default.default.relative(process.cwd(), outputDir)}`)
2293
+ );
2294
+ if (options.install === false) {
2295
+ console.log(chalk4__default.default.gray(" pnpm install"));
2296
+ }
2297
+ console.log(chalk4__default.default.gray(" onex build"));
2298
+ console.log();
2299
+ } catch (error) {
2300
+ spinner.fail(chalk4__default.default.red(`Clone failed: ${error.message}`));
2301
+ logger.error(error.stack || error.message);
2302
+ process.exit(1);
2303
+ }
2304
+ }
2072
2305
 
2073
2306
  exports.Logger = Logger;
2074
2307
  exports.buildCommand = buildCommand;
2308
+ exports.cloneCommand = cloneCommand;
2075
2309
  exports.copyTemplate = copyTemplate;
2076
2310
  exports.createBlockCommand = createBlockCommand;
2077
2311
  exports.createComponentCommand = createComponentCommand;