@onexapis/cli 1.1.43 → 1.1.45

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.mjs CHANGED
@@ -12,8 +12,6 @@ import inquirer from 'inquirer';
12
12
  import fs from 'fs-extra';
13
13
  import ejs from 'ejs';
14
14
  import os from 'os';
15
- import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
16
- import archiver from 'archiver';
17
15
  import AdmZip from 'adm-zip';
18
16
 
19
17
  var __defProp = Object.defineProperty;
@@ -1323,8 +1321,8 @@ function validateThemeName(name) {
1323
1321
  return /^[a-z][a-z0-9-]*$/.test(name);
1324
1322
  }
1325
1323
  function pathExists(filePath) {
1326
- const fs12 = __require("fs-extra");
1327
- return fs12.existsSync(filePath);
1324
+ const fs11 = __require("fs-extra");
1325
+ return fs11.existsSync(filePath);
1328
1326
  }
1329
1327
  function validateCategory(category) {
1330
1328
  const validCategories = [
@@ -1506,10 +1504,110 @@ async function installDependencies(projectPath, packageManager = "npm") {
1506
1504
  });
1507
1505
  }
1508
1506
  var AUTH_DIR = path8.join(os.homedir(), ".onexthm");
1509
- path8.join(AUTH_DIR, "auth.json");
1507
+ var AUTH_FILE = path8.join(AUTH_DIR, "auth.json");
1510
1508
  function getApiUrl() {
1511
1509
  return process.env.ONEXTHM_API_URL || process.env.NEXT_PUBLIC_API_URL || "https://platform-dev.onexeos.com";
1512
1510
  }
1511
+ async function saveAuthTokens(tokens) {
1512
+ await fs.ensureDir(AUTH_DIR);
1513
+ const key = getMachineKey();
1514
+ const data = JSON.stringify(tokens);
1515
+ const encrypted = encrypt(data, key);
1516
+ await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
1517
+ }
1518
+ function loadAuthTokens() {
1519
+ try {
1520
+ if (!fs.existsSync(AUTH_FILE)) return null;
1521
+ const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
1522
+ const key = getMachineKey();
1523
+ const data = decrypt(encrypted, key);
1524
+ return JSON.parse(data);
1525
+ } catch {
1526
+ return null;
1527
+ }
1528
+ }
1529
+ async function clearAuthTokens() {
1530
+ try {
1531
+ await fs.remove(AUTH_FILE);
1532
+ } catch {
1533
+ }
1534
+ }
1535
+ function isTokenExpired(tokens) {
1536
+ return Date.now() / 1e3 > tokens.expiresAt - 60;
1537
+ }
1538
+ async function getValidTokens() {
1539
+ const tokens = loadAuthTokens();
1540
+ if (!tokens) return null;
1541
+ if (!isTokenExpired(tokens)) return tokens;
1542
+ try {
1543
+ const apiUrl = getApiUrl();
1544
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
1545
+ method: "POST",
1546
+ headers: { "Content-Type": "application/json" },
1547
+ body: JSON.stringify({ refresh_token: tokens.refreshToken })
1548
+ });
1549
+ if (!response.ok) {
1550
+ await clearAuthTokens();
1551
+ return null;
1552
+ }
1553
+ const data = await response.json();
1554
+ const body = data.statusCode ? data.body : data;
1555
+ const refreshed = {
1556
+ ...tokens,
1557
+ accessToken: body.AccessToken || tokens.accessToken,
1558
+ idToken: body.IdToken || tokens.idToken,
1559
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
1560
+ };
1561
+ await saveAuthTokens(refreshed);
1562
+ return refreshed;
1563
+ } catch {
1564
+ await clearAuthTokens();
1565
+ return null;
1566
+ }
1567
+ }
1568
+ async function authenticatedFetch(url, init) {
1569
+ const tokens = await getValidTokens();
1570
+ if (!tokens) {
1571
+ throw new Error("Not logged in. Run: onexthm login");
1572
+ }
1573
+ const headers = new Headers(init?.headers);
1574
+ headers.set("Authorization", `Bearer ${tokens.idToken}`);
1575
+ headers.set("Content-Type", "application/json");
1576
+ return fetch(url, { ...init, headers });
1577
+ }
1578
+ function getMachineKey() {
1579
+ let seed;
1580
+ if (process.platform === "darwin") {
1581
+ seed = `onexthm:${os.hostname()}:${os.userInfo().username}`;
1582
+ } else if (process.platform === "linux") {
1583
+ try {
1584
+ seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
1585
+ } catch {
1586
+ seed = `onexthm:${os.hostname()}:${os.userInfo().username}`;
1587
+ }
1588
+ } else {
1589
+ seed = `onexthm:${os.hostname()}:${os.userInfo().username}`;
1590
+ }
1591
+ return crypto.createHash("sha256").update(seed).digest();
1592
+ }
1593
+ function encrypt(text, key) {
1594
+ const iv = crypto.randomBytes(16);
1595
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
1596
+ let encrypted = cipher.update(text, "utf-8", "hex");
1597
+ encrypted += cipher.final("hex");
1598
+ const tag = cipher.getAuthTag();
1599
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
1600
+ }
1601
+ function decrypt(text, key) {
1602
+ const [ivHex, tagHex, encrypted] = text.split(":");
1603
+ const iv = Buffer.from(ivHex, "hex");
1604
+ const tag = Buffer.from(tagHex, "hex");
1605
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
1606
+ decipher.setAuthTag(tag);
1607
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
1608
+ decrypted += decipher.final("utf-8");
1609
+ return decrypted;
1610
+ }
1513
1611
 
1514
1612
  // src/commands/init.ts
1515
1613
  async function initCommand(projectName, options = {}) {
@@ -2718,343 +2816,94 @@ function runCommand(command, args, cwd) {
2718
2816
 
2719
2817
  // src/commands/upload.ts
2720
2818
  init_logger();
2721
- function getS3Client() {
2722
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2723
- if (adapterMode === "vps") {
2724
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2725
- const secure = process.env.MINIO_SECURE === "true";
2726
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2727
- return new S3Client({
2728
- endpoint: endpointUrl,
2729
- region: "us-east-1",
2730
- credentials: {
2731
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
2732
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
2733
- },
2734
- forcePathStyle: true
2735
- });
2736
- }
2737
- if (adapterMode === "local") {
2738
- return new S3Client({
2739
- endpoint: "http://localhost:4569",
2740
- region: "ap-southeast-1",
2741
- credentials: {
2742
- accessKeyId: "S3RVER",
2743
- secretAccessKey: "S3RVER"
2744
- },
2745
- forcePathStyle: true
2746
- });
2747
- }
2748
- return new S3Client({
2749
- region: process.env.AWS_REGION || "ap-southeast-1"
2750
- });
2751
- }
2752
- function getBucketName(env) {
2753
- if (process.env.BUCKET_NAME) {
2754
- return process.env.BUCKET_NAME;
2755
- }
2756
- const environment = env || process.env.ENVIRONMENT || "staging";
2757
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
2758
- }
2759
- async function findCompiledThemeDir(themeId, version) {
2760
- const searchPaths = [path8.resolve(process.cwd(), "dist")];
2761
- for (const dir of searchPaths) {
2762
- if (await fs.pathExists(dir)) {
2763
- const hasManifest = await fs.pathExists(path8.join(dir, "manifest.json"));
2764
- const hasThemeEntry = await fs.pathExists(path8.join(dir, "bundle-entry.js")) || await fs.pathExists(path8.join(dir, "theme.config.js")) || await fs.pathExists(path8.join(dir, "index.js"));
2765
- if (hasManifest || hasThemeEntry) {
2766
- return dir;
2767
- }
2768
- }
2769
- }
2770
- return null;
2771
- }
2772
- async function readManifest() {
2773
- const manifestTsPath = path8.resolve(process.cwd(), "manifest.ts");
2774
- if (await fs.pathExists(manifestTsPath)) {
2775
- try {
2776
- const module = await import(manifestTsPath);
2777
- return module.default || module;
2778
- } catch (error) {
2779
- logger.warning("Failed to import manifest.ts, trying package.json");
2780
- }
2781
- }
2782
- const packageJsonPath = path8.resolve(process.cwd(), "package.json");
2783
- if (await fs.pathExists(packageJsonPath)) {
2784
- const pkg = await fs.readJson(packageJsonPath);
2785
- return {
2786
- themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
2787
- version: pkg.version || "1.0.0"
2788
- };
2789
- }
2790
- throw new Error(
2791
- "No manifest.ts or package.json found. Are you in a theme directory?"
2819
+ async function uploadCommand(_options) {
2820
+ logger.header("Upload Theme to S3 \u2014 DEPRECATED");
2821
+ console.log();
2822
+ console.log(
2823
+ chalk4.yellow.bold(
2824
+ "`onexthm upload` is deprecated and no longer functional."
2825
+ )
2792
2826
  );
2793
- }
2794
- async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
2795
- return new Promise((resolve, reject) => {
2796
- const output = fs.createWriteStream(outputPath);
2797
- const archive = archiver("zip", { zlib: { level: 6 } });
2798
- output.on("close", () => resolve());
2799
- archive.on("error", (err) => reject(err));
2800
- archive.pipe(output);
2801
- archive.glob("**/*", {
2802
- cwd: sourceDir,
2803
- dot: true,
2804
- ignore: excludePatterns
2805
- });
2806
- archive.finalize();
2807
- });
2808
- }
2809
- async function findSourceDir(themeId, explicitDir) {
2810
- if (explicitDir) {
2811
- if (await fs.pathExists(explicitDir)) return explicitDir;
2812
- return null;
2813
- }
2814
- const searchPaths = [
2815
- process.cwd(),
2816
- path8.resolve(process.cwd(), `../../themes/${themeId}`),
2817
- path8.resolve(process.cwd(), `../themes/${themeId}`)
2818
- ];
2819
- const markers = ["theme.config.ts", "bundle-entry.ts"];
2820
- for (const dir of searchPaths) {
2821
- for (const marker of markers) {
2822
- if (await fs.pathExists(path8.join(dir, marker))) {
2823
- return dir;
2824
- }
2825
- }
2826
- }
2827
- return null;
2828
- }
2829
- async function updateLatestPointer(s3Client, bucket, themeId, version) {
2830
- const latestData = {
2831
- version,
2832
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
2833
- };
2834
- await s3Client.send(
2835
- new PutObjectCommand({
2836
- Bucket: bucket,
2837
- Key: `themes/${themeId}/latest.json`,
2838
- Body: JSON.stringify(latestData, null, 2),
2839
- ContentType: "application/json"
2840
- })
2827
+ console.log();
2828
+ console.log(
2829
+ "The platform no longer exposes themes via direct S3 access, so this"
2841
2830
  );
2842
- }
2843
- async function uploadCommand(options) {
2844
- logger.header("Upload Theme to S3");
2845
- const spinner = ora("Preparing theme upload...").start();
2846
- try {
2847
- let themeId;
2848
- let version;
2849
- if (options.theme) {
2850
- themeId = options.theme;
2851
- version = options.version || "1.0.0";
2852
- } else {
2853
- const manifest = await readManifest();
2854
- themeId = manifest.themeId;
2855
- version = options.version || manifest.version || "1.0.0";
2856
- }
2857
- spinner.text = `Found theme: ${themeId}@${version}`;
2858
- const bucket = options.bucket || getBucketName(options.environment);
2859
- const s3Client = getS3Client();
2860
- const compiledDir = await findCompiledThemeDir(themeId, version);
2861
- if (!compiledDir) {
2862
- spinner.fail(
2863
- chalk4.red(
2864
- `Compiled theme not found for ${themeId}@${version}. Run 'onexthm build' first.`
2865
- )
2866
- );
2867
- logger.info(chalk4.gray(`Expected location:
2868
- - ./dist/`));
2869
- process.exit(1);
2870
- }
2871
- spinner.succeed(`Found compiled theme at: ${compiledDir}`);
2872
- spinner.start("Creating bundle.zip...");
2873
- const tmpDir = os.tmpdir();
2874
- const bundleZipPath = path8.join(tmpDir, `${themeId}-${version}-bundle.zip`);
2875
- await createZipFromDir(compiledDir, bundleZipPath);
2876
- const bundleZipBuffer = await fs.readFile(bundleZipPath);
2877
- const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
2878
- spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
2879
- if (options.dryRun) {
2880
- spinner.info(chalk4.yellow("Dry run mode \u2014 no files will be uploaded"));
2881
- console.log();
2882
- console.log(chalk4.gray(` bundle.zip: ${bundleSizeMB} MB`));
2883
- console.log(
2884
- chalk4.cyan(
2885
- ` S3 path: s3://${bucket}/themes/${themeId}/${version}/bundle.zip`
2886
- )
2887
- );
2888
- if (!options.skipSource) {
2889
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
2890
- if (sourceDir) {
2891
- console.log(chalk4.gray(` source dir: ${sourceDir}`));
2892
- console.log(
2893
- chalk4.cyan(
2894
- ` S3 path: s3://${bucket}/themes/${themeId}/${version}/source.zip`
2895
- )
2896
- );
2897
- } else {
2898
- console.log(
2899
- chalk4.yellow(" source dir: not found (source.zip will be skipped)")
2900
- );
2901
- }
2902
- }
2903
- console.log();
2904
- await fs.remove(bundleZipPath);
2905
- return;
2906
- }
2907
- spinner.start("Uploading bundle.zip to S3...");
2908
- const bundleS3Key = `themes/${themeId}/${version}/bundle.zip`;
2909
- await s3Client.send(
2910
- new PutObjectCommand({
2911
- Bucket: bucket,
2912
- Key: bundleS3Key,
2913
- Body: bundleZipBuffer,
2914
- ContentType: "application/zip"
2915
- })
2916
- );
2917
- spinner.succeed(
2918
- `Uploaded bundle.zip ${chalk4.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
2919
- );
2920
- await fs.remove(bundleZipPath);
2921
- let sourceUploaded = false;
2922
- if (!options.skipSource) {
2923
- spinner.start("Looking for source directory...");
2924
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
2925
- if (sourceDir) {
2926
- spinner.succeed(`Found source at: ${sourceDir}`);
2927
- spinner.start("Creating source.zip...");
2928
- const sourceZipPath = path8.join(
2929
- tmpDir,
2930
- `${themeId}-${version}-source.zip`
2931
- );
2932
- await createZipFromDir(sourceDir, sourceZipPath, [
2933
- "node_modules/**",
2934
- "dist/**",
2935
- ".git/**",
2936
- "*.zip",
2937
- ".next/**",
2938
- ".turbo/**"
2939
- ]);
2940
- const sourceZipBuffer = await fs.readFile(sourceZipPath);
2941
- const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
2942
- spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
2943
- spinner.start("Uploading source.zip to S3...");
2944
- const sourceS3Key = `themes/${themeId}/${version}/source.zip`;
2945
- await s3Client.send(
2946
- new PutObjectCommand({
2947
- Bucket: bucket,
2948
- Key: sourceS3Key,
2949
- Body: sourceZipBuffer,
2950
- ContentType: "application/zip"
2951
- })
2952
- );
2953
- spinner.succeed(
2954
- `Uploaded source.zip ${chalk4.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
2955
- );
2956
- await fs.remove(sourceZipPath);
2957
- sourceUploaded = true;
2958
- } else {
2959
- spinner.warn(
2960
- chalk4.yellow("Source directory not found \u2014 skipping source.zip")
2961
- );
2962
- }
2963
- }
2964
- spinner.start("Updating latest.json pointer...");
2965
- await updateLatestPointer(s3Client, bucket, themeId, version);
2966
- spinner.succeed("Updated latest.json pointer");
2967
- console.log();
2968
- logger.success(chalk4.green.bold("Theme uploaded successfully!"));
2969
- console.log();
2970
- console.log(
2971
- chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${version}`)
2972
- );
2973
- console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
2974
- console.log(
2975
- chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
2976
- );
2977
- console.log(
2978
- chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
2979
- );
2980
- console.log();
2981
- } catch (error) {
2982
- spinner.fail(chalk4.red(`Upload failed: ${error.message}`));
2983
- logger.error(error.stack || error.message);
2984
- process.exit(1);
2985
- }
2831
+ console.log(
2832
+ "command can no longer reach the bucket. Use `onexthm publish` instead:"
2833
+ );
2834
+ console.log();
2835
+ console.log(chalk4.cyan(" cd themes/your-theme"));
2836
+ console.log(chalk4.cyan(" onexthm login # one-time, refreshes JWT"));
2837
+ console.log(
2838
+ chalk4.cyan(" onexthm publish # builds + uploads + confirms")
2839
+ );
2840
+ console.log();
2841
+ console.log(
2842
+ "`publish` does everything this command did (build, version bump,"
2843
+ );
2844
+ console.log(
2845
+ "bundle + source upload) plus content-hashed asset upload, security"
2846
+ );
2847
+ console.log("scanning, and atomic version registration in one step.");
2848
+ console.log();
2849
+ process.exit(1);
2986
2850
  }
2987
2851
 
2988
2852
  // src/commands/download.ts
2989
2853
  init_logger();
2990
- function getS3Client2() {
2991
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2992
- if (adapterMode === "vps") {
2993
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2994
- const secure = process.env.MINIO_SECURE === "true";
2995
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2996
- return new S3Client({
2997
- endpoint: endpointUrl,
2998
- region: "us-east-1",
2999
- credentials: {
3000
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3001
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3002
- },
3003
- forcePathStyle: true
3004
- });
3005
- }
3006
- if (adapterMode === "local") {
3007
- return new S3Client({
3008
- endpoint: "http://localhost:4569",
3009
- region: "ap-southeast-1",
3010
- credentials: {
3011
- accessKeyId: "S3RVER",
3012
- secretAccessKey: "S3RVER"
3013
- },
3014
- forcePathStyle: true
3015
- });
2854
+ function unwrapEnvelope(raw) {
2855
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
2856
+ return raw.body;
3016
2857
  }
3017
- return new S3Client({
3018
- region: process.env.AWS_REGION || "ap-southeast-1"
3019
- });
2858
+ return raw;
3020
2859
  }
3021
- function getBucketName2(env) {
3022
- if (process.env.BUCKET_NAME) {
3023
- return process.env.BUCKET_NAME;
2860
+ async function resolveLatestVersion(apiUrl, themeId) {
2861
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
2862
+ let response;
2863
+ try {
2864
+ response = await fetch(url, { cache: "no-store" });
2865
+ } catch (err) {
2866
+ throw new Error(
2867
+ `Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
2868
+ );
3024
2869
  }
3025
- const environment = env || process.env.ENVIRONMENT || "staging";
3026
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3027
- }
3028
- async function streamToString(stream) {
3029
- const chunks = [];
3030
- for await (const chunk of stream) {
3031
- chunks.push(Buffer.from(chunk));
2870
+ if (!response.ok) {
2871
+ throw new Error(
2872
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
2873
+ );
3032
2874
  }
3033
- return Buffer.concat(chunks).toString("utf-8");
3034
- }
3035
- async function streamToBuffer(stream) {
3036
- const chunks = [];
3037
- for await (const chunk of stream) {
3038
- chunks.push(Buffer.from(chunk));
2875
+ const raw = await response.json();
2876
+ const data = unwrapEnvelope(raw);
2877
+ const latest = data?.latest_version;
2878
+ if (typeof latest !== "string" || latest.length === 0) {
2879
+ throw new Error(
2880
+ `Theme "${themeId}" has no published versions yet (no latest_version in response)`
2881
+ );
3039
2882
  }
3040
- return Buffer.concat(chunks);
2883
+ return latest;
3041
2884
  }
3042
- async function resolveLatestVersion(s3Client, bucket, themeId) {
3043
- try {
3044
- const response = await s3Client.send(
3045
- new GetObjectCommand({
3046
- Bucket: bucket,
3047
- Key: `themes/${themeId}/latest.json`
3048
- })
3049
- );
3050
- const body = await streamToString(response.Body);
3051
- const data = JSON.parse(body);
3052
- return data.version;
3053
- } catch (error) {
2885
+ async function downloadBundleZip(apiUrl, themeId, version) {
2886
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version)}/download`;
2887
+ const response = await fetch(url);
2888
+ if (!response.ok) {
3054
2889
  throw new Error(
3055
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
2890
+ `Bundle download failed for "${themeId}@${version}" (HTTP ${response.status})`
3056
2891
  );
3057
2892
  }
2893
+ const contentType = response.headers.get("content-type") || "";
2894
+ if (contentType.includes("application/json")) {
2895
+ const raw = await response.json();
2896
+ const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
2897
+ const body = envelope.body;
2898
+ if (typeof body !== "string") {
2899
+ throw new Error(
2900
+ "Unexpected /download response shape: expected base64 string in body"
2901
+ );
2902
+ }
2903
+ return Buffer.from(body, "base64");
2904
+ }
2905
+ const arrayBuffer = await response.arrayBuffer();
2906
+ return Buffer.from(arrayBuffer);
3058
2907
  }
3059
2908
  async function createCompatibilityFiles(outputDir, manifest) {
3060
2909
  const entryFile = manifest.output?.entry || "bundle-entry.js";
@@ -3078,47 +2927,58 @@ export * from './bundle-entry.js';
3078
2927
  const pkgJsonPath = path8.join(outputDir, "package.json");
3079
2928
  await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3080
2929
  }
3081
- function showDownloadFailureHelp(themeId, bucket) {
2930
+ function showDownloadFailureHelp(themeId, apiUrl) {
3082
2931
  console.log();
3083
2932
  logger.error(chalk4.red.bold("Theme download failed"));
3084
2933
  console.log();
3085
2934
  console.log(chalk4.yellow("Possible reasons:"));
3086
- console.log(chalk4.gray(" 1. Theme not uploaded to S3 yet"));
3087
- console.log(chalk4.gray(" 2. AWS credentials not configured correctly"));
3088
- console.log(chalk4.gray(" 3. Bucket name or region is incorrect"));
3089
- console.log(chalk4.gray(" 4. bundle.zip is missing or corrupted"));
2935
+ console.log(
2936
+ chalk4.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
2937
+ );
2938
+ console.log(
2939
+ chalk4.gray(
2940
+ " 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
2941
+ )
2942
+ );
2943
+ console.log(
2944
+ chalk4.gray(" 3. API base URL is wrong or the website-api is unreachable")
2945
+ );
3090
2946
  console.log();
3091
2947
  console.log(chalk4.cyan.bold("To fix this:"));
3092
2948
  console.log();
3093
- console.log(chalk4.white("1. Compile and upload the theme:"));
3094
- console.log(chalk4.gray(` cd themes/${themeId}`));
3095
- console.log(chalk4.gray(" pnpm build"));
3096
- console.log(chalk4.gray(" onexthm upload"));
3097
- console.log();
3098
- console.log(chalk4.white("2. Verify AWS credentials are set:"));
2949
+ console.log(chalk4.white("1. Verify the theme is published:"));
3099
2950
  console.log(
3100
- chalk4.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
2951
+ chalk4.gray(
2952
+ ` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
2953
+ )
3101
2954
  );
3102
- console.log(chalk4.gray(" - Or use AWS_PROFILE=your-profile"));
3103
- console.log(chalk4.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
3104
2955
  console.log();
3105
- console.log(chalk4.white("3. Check bucket configuration:"));
3106
- console.log(chalk4.gray(` Current bucket: ${bucket}`));
2956
+ console.log(chalk4.white("2. Check API URL configuration:"));
2957
+ console.log(chalk4.gray(` Current API URL: ${apiUrl}`));
3107
2958
  console.log(
3108
- chalk4.gray(" Set BUCKET_NAME environment variable if different")
2959
+ chalk4.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3109
2960
  );
3110
2961
  console.log();
3111
- console.log(chalk4.white("4. Verify theme exists in S3:"));
3112
- console.log(chalk4.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2962
+ console.log(chalk4.white("3. Pin a specific version (CI/production):"));
2963
+ console.log(
2964
+ chalk4.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
2965
+ );
3113
2966
  console.log();
3114
2967
  }
3115
2968
  async function downloadCommand(options) {
3116
- logger.header("Download Theme from S3");
2969
+ logger.header("Download Theme");
3117
2970
  const spinner = ora("Initializing download...").start();
2971
+ if (options.bucket || options.environment) {
2972
+ spinner.stop();
2973
+ logger.warning(
2974
+ "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
2975
+ );
2976
+ spinner.start();
2977
+ }
2978
+ const apiUrl = getApiUrl();
3118
2979
  try {
3119
2980
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3120
- const version = options.version || process.env.THEME_VERSION || "latest";
3121
- const bucket = options.bucket || getBucketName2(options.environment);
2981
+ const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
3122
2982
  const outputDir = options.output || "./active-theme";
3123
2983
  if (!themeId) {
3124
2984
  spinner.fail(
@@ -3128,12 +2988,10 @@ async function downloadCommand(options) {
3128
2988
  );
3129
2989
  process.exit(1);
3130
2990
  }
3131
- spinner.text = `Downloading ${themeId}@${version} from ${bucket}...`;
3132
- const s3Client = getS3Client2();
3133
- let resolvedVersion = version;
3134
- if (version === "latest") {
3135
- spinner.text = "Resolving latest version...";
3136
- resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
2991
+ spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
2992
+ let resolvedVersion = requestedVersion;
2993
+ if (requestedVersion === "latest") {
2994
+ resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
3137
2995
  spinner.succeed(
3138
2996
  `Resolved latest version: ${chalk4.cyan(resolvedVersion)}`
3139
2997
  );
@@ -3143,24 +3001,19 @@ async function downloadCommand(options) {
3143
3001
  chalk4.yellow(
3144
3002
  `
3145
3003
  Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
3146
- For production builds, pin to a specific version:
3004
+ For reproducible builds, pin to a specific version:
3147
3005
  THEME_VERSION=${resolvedVersion}
3148
3006
  `
3149
3007
  )
3150
3008
  );
3151
3009
  }
3010
+ } else {
3011
+ spinner.succeed(`Using version: ${chalk4.cyan(resolvedVersion)}`);
3152
3012
  }
3153
3013
  spinner.start(
3154
3014
  `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
3155
3015
  );
3156
- const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
3157
- const response = await s3Client.send(
3158
- new GetObjectCommand({
3159
- Bucket: bucket,
3160
- Key: s3Key
3161
- })
3162
- );
3163
- const zipBuffer = await streamToBuffer(response.Body);
3016
+ const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
3164
3017
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3165
3018
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3166
3019
  spinner.start("Extracting bundle...");
@@ -3179,7 +3032,7 @@ async function downloadCommand(options) {
3179
3032
  console.log(
3180
3033
  chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
3181
3034
  );
3182
- console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
3035
+ console.log(chalk4.cyan(" Source: ") + chalk4.white(apiUrl));
3183
3036
  console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
3184
3037
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
3185
3038
  if (manifest.counts) {
@@ -3191,82 +3044,70 @@ async function downloadCommand(options) {
3191
3044
  } catch (error) {
3192
3045
  spinner.fail(chalk4.red("Download failed"));
3193
3046
  logger.error(error.message);
3194
- const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
3195
- const bucket = options.bucket || getBucketName2(options.environment);
3196
- showDownloadFailureHelp(themeId, bucket);
3047
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
3048
+ showDownloadFailureHelp(themeId, apiUrl);
3197
3049
  process.exit(1);
3198
3050
  }
3199
3051
  }
3200
3052
 
3201
3053
  // src/commands/clone.ts
3202
3054
  init_logger();
3203
- function getS3Client3() {
3204
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3205
- if (adapterMode === "vps") {
3206
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3207
- const secure = process.env.MINIO_SECURE === "true";
3208
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3209
- return new S3Client({
3210
- endpoint: endpointUrl,
3211
- region: "us-east-1",
3212
- credentials: {
3213
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3214
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3215
- },
3216
- forcePathStyle: true
3217
- });
3218
- }
3219
- if (adapterMode === "local") {
3220
- return new S3Client({
3221
- endpoint: "http://localhost:4569",
3222
- region: "ap-southeast-1",
3223
- credentials: {
3224
- accessKeyId: "S3RVER",
3225
- secretAccessKey: "S3RVER"
3226
- },
3227
- forcePathStyle: true
3228
- });
3229
- }
3230
- return new S3Client({
3231
- region: process.env.AWS_REGION || "ap-southeast-1"
3232
- });
3233
- }
3234
- function getBucketName3(env) {
3235
- if (process.env.BUCKET_NAME) {
3236
- return process.env.BUCKET_NAME;
3055
+ function unwrapEnvelope2(raw) {
3056
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3057
+ return raw.body;
3237
3058
  }
3238
- const environment = env || process.env.ENVIRONMENT || "staging";
3239
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3059
+ return raw;
3240
3060
  }
3241
- async function streamToString2(stream) {
3242
- const chunks = [];
3243
- for await (const chunk of stream) {
3244
- chunks.push(Buffer.from(chunk));
3061
+ async function resolveLatestVersion2(apiUrl, themeId) {
3062
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3063
+ const response = await fetch(url, { cache: "no-store" });
3064
+ if (!response.ok) {
3065
+ throw new Error(
3066
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3067
+ );
3245
3068
  }
3246
- return Buffer.concat(chunks).toString("utf-8");
3247
- }
3248
- async function streamToBuffer2(stream) {
3249
- const chunks = [];
3250
- for await (const chunk of stream) {
3251
- chunks.push(Buffer.from(chunk));
3069
+ const raw = await response.json();
3070
+ const data = unwrapEnvelope2(raw);
3071
+ const latest = data?.latest_version;
3072
+ if (typeof latest !== "string" || latest.length === 0) {
3073
+ throw new Error(`Theme "${themeId}" has no published versions yet`);
3252
3074
  }
3253
- return Buffer.concat(chunks);
3075
+ return latest;
3254
3076
  }
3255
- async function resolveLatestVersion2(s3Client, bucket, themeId) {
3256
- try {
3257
- const response = await s3Client.send(
3258
- new GetObjectCommand({
3259
- Bucket: bucket,
3260
- Key: `themes/${themeId}/latest.json`
3261
- })
3077
+ async function fetchSourceZip(apiUrl, themeId, version) {
3078
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version)}`;
3079
+ const response = await authenticatedFetch(url, {
3080
+ method: "GET"
3081
+ });
3082
+ if (!response.ok) {
3083
+ if (response.status === 404) {
3084
+ throw new Error(
3085
+ `Source not found for ${themeId}@${version}. The theme may not have been published with source upload enabled.`
3086
+ );
3087
+ }
3088
+ if (response.status === 401 || response.status === 403) {
3089
+ throw new Error(
3090
+ `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
3091
+ );
3092
+ }
3093
+ throw new Error(
3094
+ `Source URL request failed for "${themeId}@${version}" (HTTP ${response.status})`
3262
3095
  );
3263
- const body = await streamToString2(response.Body);
3264
- return JSON.parse(body).version;
3265
- } catch (error) {
3096
+ }
3097
+ const raw = await response.json();
3098
+ const data = unwrapEnvelope2(raw);
3099
+ const presignedUrl = data?.download_url;
3100
+ if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
3101
+ throw new Error("Unexpected /source response shape: missing download_url");
3102
+ }
3103
+ const zipResponse = await fetch(presignedUrl);
3104
+ if (!zipResponse.ok) {
3266
3105
  throw new Error(
3267
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3106
+ `Presigned source download failed (HTTP ${zipResponse.status})`
3268
3107
  );
3269
3108
  }
3109
+ const arrayBuffer = await zipResponse.arrayBuffer();
3110
+ return Buffer.from(arrayBuffer);
3270
3111
  }
3271
3112
  function runInstall(cwd) {
3272
3113
  return new Promise((resolve) => {
@@ -3365,15 +3206,24 @@ async function renameTheme(themeDir, oldName, newName) {
3365
3206
  }
3366
3207
  async function cloneCommand(themeName, options) {
3367
3208
  logger.header("Clone Theme Source");
3209
+ if (options.bucket || options.environment) {
3210
+ logger.warning(
3211
+ "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
3212
+ );
3213
+ }
3214
+ const tokens = await getValidTokens();
3215
+ if (!tokens) {
3216
+ logger.error("Not logged in. Run: onexthm login");
3217
+ process.exit(1);
3218
+ }
3368
3219
  let newName = options.name;
3369
3220
  if (!newName) {
3370
3221
  newName = await promptThemeName(themeName);
3371
3222
  }
3372
3223
  const spinner = ora("Initializing clone...").start();
3373
3224
  try {
3374
- const bucket = options.bucket || getBucketName3(options.environment);
3225
+ const apiUrl = getApiUrl();
3375
3226
  const outputDir = options.output || path8.resolve(process.cwd(), newName);
3376
- const s3Client = getS3Client3();
3377
3227
  if (await fs.pathExists(outputDir)) {
3378
3228
  spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
3379
3229
  logger.info(
@@ -3386,28 +3236,20 @@ async function cloneCommand(themeName, options) {
3386
3236
  let version = options.version || "latest";
3387
3237
  if (version === "latest") {
3388
3238
  spinner.text = "Resolving latest version...";
3389
- version = await resolveLatestVersion2(s3Client, bucket, themeName);
3239
+ version = await resolveLatestVersion2(apiUrl, themeName);
3390
3240
  spinner.succeed(`Resolved latest version: ${chalk4.cyan(version)}`);
3391
3241
  }
3392
3242
  spinner.start(`Downloading source.zip for ${themeName}@${version}...`);
3393
- const s3Key = `themes/${themeName}/${version}/source.zip`;
3394
3243
  let zipBuffer;
3395
3244
  try {
3396
- const response = await s3Client.send(
3397
- new GetObjectCommand({
3398
- Bucket: bucket,
3399
- Key: s3Key
3400
- })
3401
- );
3402
- zipBuffer = await streamToBuffer2(response.Body);
3245
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version);
3403
3246
  } catch (error) {
3404
- spinner.fail(chalk4.red(`Source not found: s3://${bucket}/${s3Key}`));
3247
+ spinner.fail(chalk4.red(error.message));
3405
3248
  console.log();
3406
3249
  console.log(
3407
- chalk4.yellow("The theme source may not have been uploaded yet.")
3408
- );
3409
- console.log(
3410
- chalk4.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3250
+ chalk4.gray(
3251
+ `Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
3252
+ )
3411
3253
  );
3412
3254
  console.log();
3413
3255
  process.exit(1);