@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.js CHANGED
@@ -14,8 +14,6 @@ var inquirer = require('inquirer');
14
14
  var fs = require('fs-extra');
15
15
  var ejs = require('ejs');
16
16
  var os = require('os');
17
- var clientS3 = require('@aws-sdk/client-s3');
18
- var archiver = require('archiver');
19
17
  var AdmZip = require('adm-zip');
20
18
 
21
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -50,7 +48,6 @@ var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
50
48
  var fs__default = /*#__PURE__*/_interopDefault(fs);
51
49
  var ejs__default = /*#__PURE__*/_interopDefault(ejs);
52
50
  var os__default = /*#__PURE__*/_interopDefault(os);
53
- var archiver__default = /*#__PURE__*/_interopDefault(archiver);
54
51
  var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
55
52
 
56
53
  var __defProp = Object.defineProperty;
@@ -1360,8 +1357,8 @@ function validateThemeName(name) {
1360
1357
  return /^[a-z][a-z0-9-]*$/.test(name);
1361
1358
  }
1362
1359
  function pathExists(filePath) {
1363
- const fs12 = __require("fs-extra");
1364
- return fs12.existsSync(filePath);
1360
+ const fs11 = __require("fs-extra");
1361
+ return fs11.existsSync(filePath);
1365
1362
  }
1366
1363
  function validateCategory(category) {
1367
1364
  const validCategories = [
@@ -1543,10 +1540,110 @@ async function installDependencies(projectPath, packageManager = "npm") {
1543
1540
  });
1544
1541
  }
1545
1542
  var AUTH_DIR = path8__default.default.join(os__default.default.homedir(), ".onexthm");
1546
- path8__default.default.join(AUTH_DIR, "auth.json");
1543
+ var AUTH_FILE = path8__default.default.join(AUTH_DIR, "auth.json");
1547
1544
  function getApiUrl() {
1548
1545
  return process.env.ONEXTHM_API_URL || process.env.NEXT_PUBLIC_API_URL || "https://platform-dev.onexeos.com";
1549
1546
  }
1547
+ async function saveAuthTokens(tokens) {
1548
+ await fs__default.default.ensureDir(AUTH_DIR);
1549
+ const key = getMachineKey();
1550
+ const data = JSON.stringify(tokens);
1551
+ const encrypted = encrypt(data, key);
1552
+ await fs__default.default.writeFile(AUTH_FILE, encrypted, "utf-8");
1553
+ }
1554
+ function loadAuthTokens() {
1555
+ try {
1556
+ if (!fs__default.default.existsSync(AUTH_FILE)) return null;
1557
+ const encrypted = fs__default.default.readFileSync(AUTH_FILE, "utf-8");
1558
+ const key = getMachineKey();
1559
+ const data = decrypt(encrypted, key);
1560
+ return JSON.parse(data);
1561
+ } catch {
1562
+ return null;
1563
+ }
1564
+ }
1565
+ async function clearAuthTokens() {
1566
+ try {
1567
+ await fs__default.default.remove(AUTH_FILE);
1568
+ } catch {
1569
+ }
1570
+ }
1571
+ function isTokenExpired(tokens) {
1572
+ return Date.now() / 1e3 > tokens.expiresAt - 60;
1573
+ }
1574
+ async function getValidTokens() {
1575
+ const tokens = loadAuthTokens();
1576
+ if (!tokens) return null;
1577
+ if (!isTokenExpired(tokens)) return tokens;
1578
+ try {
1579
+ const apiUrl = getApiUrl();
1580
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
1581
+ method: "POST",
1582
+ headers: { "Content-Type": "application/json" },
1583
+ body: JSON.stringify({ refresh_token: tokens.refreshToken })
1584
+ });
1585
+ if (!response.ok) {
1586
+ await clearAuthTokens();
1587
+ return null;
1588
+ }
1589
+ const data = await response.json();
1590
+ const body = data.statusCode ? data.body : data;
1591
+ const refreshed = {
1592
+ ...tokens,
1593
+ accessToken: body.AccessToken || tokens.accessToken,
1594
+ idToken: body.IdToken || tokens.idToken,
1595
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
1596
+ };
1597
+ await saveAuthTokens(refreshed);
1598
+ return refreshed;
1599
+ } catch {
1600
+ await clearAuthTokens();
1601
+ return null;
1602
+ }
1603
+ }
1604
+ async function authenticatedFetch(url, init) {
1605
+ const tokens = await getValidTokens();
1606
+ if (!tokens) {
1607
+ throw new Error("Not logged in. Run: onexthm login");
1608
+ }
1609
+ const headers = new Headers(init?.headers);
1610
+ headers.set("Authorization", `Bearer ${tokens.idToken}`);
1611
+ headers.set("Content-Type", "application/json");
1612
+ return fetch(url, { ...init, headers });
1613
+ }
1614
+ function getMachineKey() {
1615
+ let seed;
1616
+ if (process.platform === "darwin") {
1617
+ seed = `onexthm:${os__default.default.hostname()}:${os__default.default.userInfo().username}`;
1618
+ } else if (process.platform === "linux") {
1619
+ try {
1620
+ seed = `onexthm:${fs__default.default.readFileSync("/etc/machine-id", "utf-8").trim()}`;
1621
+ } catch {
1622
+ seed = `onexthm:${os__default.default.hostname()}:${os__default.default.userInfo().username}`;
1623
+ }
1624
+ } else {
1625
+ seed = `onexthm:${os__default.default.hostname()}:${os__default.default.userInfo().username}`;
1626
+ }
1627
+ return crypto__default.default.createHash("sha256").update(seed).digest();
1628
+ }
1629
+ function encrypt(text, key) {
1630
+ const iv = crypto__default.default.randomBytes(16);
1631
+ const cipher = crypto__default.default.createCipheriv("aes-256-gcm", key, iv);
1632
+ let encrypted = cipher.update(text, "utf-8", "hex");
1633
+ encrypted += cipher.final("hex");
1634
+ const tag = cipher.getAuthTag();
1635
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
1636
+ }
1637
+ function decrypt(text, key) {
1638
+ const [ivHex, tagHex, encrypted] = text.split(":");
1639
+ const iv = Buffer.from(ivHex, "hex");
1640
+ const tag = Buffer.from(tagHex, "hex");
1641
+ const decipher = crypto__default.default.createDecipheriv("aes-256-gcm", key, iv);
1642
+ decipher.setAuthTag(tag);
1643
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
1644
+ decrypted += decipher.final("utf-8");
1645
+ return decrypted;
1646
+ }
1550
1647
 
1551
1648
  // src/commands/init.ts
1552
1649
  async function initCommand(projectName, options = {}) {
@@ -2755,343 +2852,94 @@ function runCommand(command, args, cwd) {
2755
2852
 
2756
2853
  // src/commands/upload.ts
2757
2854
  init_logger();
2758
- function getS3Client() {
2759
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2760
- if (adapterMode === "vps") {
2761
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2762
- const secure = process.env.MINIO_SECURE === "true";
2763
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2764
- return new clientS3.S3Client({
2765
- endpoint: endpointUrl,
2766
- region: "us-east-1",
2767
- credentials: {
2768
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
2769
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
2770
- },
2771
- forcePathStyle: true
2772
- });
2773
- }
2774
- if (adapterMode === "local") {
2775
- return new clientS3.S3Client({
2776
- endpoint: "http://localhost:4569",
2777
- region: "ap-southeast-1",
2778
- credentials: {
2779
- accessKeyId: "S3RVER",
2780
- secretAccessKey: "S3RVER"
2781
- },
2782
- forcePathStyle: true
2783
- });
2784
- }
2785
- return new clientS3.S3Client({
2786
- region: process.env.AWS_REGION || "ap-southeast-1"
2787
- });
2788
- }
2789
- function getBucketName(env) {
2790
- if (process.env.BUCKET_NAME) {
2791
- return process.env.BUCKET_NAME;
2792
- }
2793
- const environment = env || process.env.ENVIRONMENT || "staging";
2794
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
2795
- }
2796
- async function findCompiledThemeDir(themeId, version) {
2797
- const searchPaths = [path8__default.default.resolve(process.cwd(), "dist")];
2798
- for (const dir of searchPaths) {
2799
- if (await fs__default.default.pathExists(dir)) {
2800
- const hasManifest = await fs__default.default.pathExists(path8__default.default.join(dir, "manifest.json"));
2801
- const hasThemeEntry = await fs__default.default.pathExists(path8__default.default.join(dir, "bundle-entry.js")) || await fs__default.default.pathExists(path8__default.default.join(dir, "theme.config.js")) || await fs__default.default.pathExists(path8__default.default.join(dir, "index.js"));
2802
- if (hasManifest || hasThemeEntry) {
2803
- return dir;
2804
- }
2805
- }
2806
- }
2807
- return null;
2808
- }
2809
- async function readManifest() {
2810
- const manifestTsPath = path8__default.default.resolve(process.cwd(), "manifest.ts");
2811
- if (await fs__default.default.pathExists(manifestTsPath)) {
2812
- try {
2813
- const module = await import(manifestTsPath);
2814
- return module.default || module;
2815
- } catch (error) {
2816
- exports.logger.warning("Failed to import manifest.ts, trying package.json");
2817
- }
2818
- }
2819
- const packageJsonPath = path8__default.default.resolve(process.cwd(), "package.json");
2820
- if (await fs__default.default.pathExists(packageJsonPath)) {
2821
- const pkg = await fs__default.default.readJson(packageJsonPath);
2822
- return {
2823
- themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
2824
- version: pkg.version || "1.0.0"
2825
- };
2826
- }
2827
- throw new Error(
2828
- "No manifest.ts or package.json found. Are you in a theme directory?"
2855
+ async function uploadCommand(_options) {
2856
+ exports.logger.header("Upload Theme to S3 \u2014 DEPRECATED");
2857
+ console.log();
2858
+ console.log(
2859
+ chalk4__default.default.yellow.bold(
2860
+ "`onexthm upload` is deprecated and no longer functional."
2861
+ )
2829
2862
  );
2830
- }
2831
- async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
2832
- return new Promise((resolve, reject) => {
2833
- const output = fs__default.default.createWriteStream(outputPath);
2834
- const archive = archiver__default.default("zip", { zlib: { level: 6 } });
2835
- output.on("close", () => resolve());
2836
- archive.on("error", (err) => reject(err));
2837
- archive.pipe(output);
2838
- archive.glob("**/*", {
2839
- cwd: sourceDir,
2840
- dot: true,
2841
- ignore: excludePatterns
2842
- });
2843
- archive.finalize();
2844
- });
2845
- }
2846
- async function findSourceDir(themeId, explicitDir) {
2847
- if (explicitDir) {
2848
- if (await fs__default.default.pathExists(explicitDir)) return explicitDir;
2849
- return null;
2850
- }
2851
- const searchPaths = [
2852
- process.cwd(),
2853
- path8__default.default.resolve(process.cwd(), `../../themes/${themeId}`),
2854
- path8__default.default.resolve(process.cwd(), `../themes/${themeId}`)
2855
- ];
2856
- const markers = ["theme.config.ts", "bundle-entry.ts"];
2857
- for (const dir of searchPaths) {
2858
- for (const marker of markers) {
2859
- if (await fs__default.default.pathExists(path8__default.default.join(dir, marker))) {
2860
- return dir;
2861
- }
2862
- }
2863
- }
2864
- return null;
2865
- }
2866
- async function updateLatestPointer(s3Client, bucket, themeId, version) {
2867
- const latestData = {
2868
- version,
2869
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
2870
- };
2871
- await s3Client.send(
2872
- new clientS3.PutObjectCommand({
2873
- Bucket: bucket,
2874
- Key: `themes/${themeId}/latest.json`,
2875
- Body: JSON.stringify(latestData, null, 2),
2876
- ContentType: "application/json"
2877
- })
2863
+ console.log();
2864
+ console.log(
2865
+ "The platform no longer exposes themes via direct S3 access, so this"
2878
2866
  );
2879
- }
2880
- async function uploadCommand(options) {
2881
- exports.logger.header("Upload Theme to S3");
2882
- const spinner = ora__default.default("Preparing theme upload...").start();
2883
- try {
2884
- let themeId;
2885
- let version;
2886
- if (options.theme) {
2887
- themeId = options.theme;
2888
- version = options.version || "1.0.0";
2889
- } else {
2890
- const manifest = await readManifest();
2891
- themeId = manifest.themeId;
2892
- version = options.version || manifest.version || "1.0.0";
2893
- }
2894
- spinner.text = `Found theme: ${themeId}@${version}`;
2895
- const bucket = options.bucket || getBucketName(options.environment);
2896
- const s3Client = getS3Client();
2897
- const compiledDir = await findCompiledThemeDir(themeId, version);
2898
- if (!compiledDir) {
2899
- spinner.fail(
2900
- chalk4__default.default.red(
2901
- `Compiled theme not found for ${themeId}@${version}. Run 'onexthm build' first.`
2902
- )
2903
- );
2904
- exports.logger.info(chalk4__default.default.gray(`Expected location:
2905
- - ./dist/`));
2906
- process.exit(1);
2907
- }
2908
- spinner.succeed(`Found compiled theme at: ${compiledDir}`);
2909
- spinner.start("Creating bundle.zip...");
2910
- const tmpDir = os__default.default.tmpdir();
2911
- const bundleZipPath = path8__default.default.join(tmpDir, `${themeId}-${version}-bundle.zip`);
2912
- await createZipFromDir(compiledDir, bundleZipPath);
2913
- const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
2914
- const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
2915
- spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
2916
- if (options.dryRun) {
2917
- spinner.info(chalk4__default.default.yellow("Dry run mode \u2014 no files will be uploaded"));
2918
- console.log();
2919
- console.log(chalk4__default.default.gray(` bundle.zip: ${bundleSizeMB} MB`));
2920
- console.log(
2921
- chalk4__default.default.cyan(
2922
- ` S3 path: s3://${bucket}/themes/${themeId}/${version}/bundle.zip`
2923
- )
2924
- );
2925
- if (!options.skipSource) {
2926
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
2927
- if (sourceDir) {
2928
- console.log(chalk4__default.default.gray(` source dir: ${sourceDir}`));
2929
- console.log(
2930
- chalk4__default.default.cyan(
2931
- ` S3 path: s3://${bucket}/themes/${themeId}/${version}/source.zip`
2932
- )
2933
- );
2934
- } else {
2935
- console.log(
2936
- chalk4__default.default.yellow(" source dir: not found (source.zip will be skipped)")
2937
- );
2938
- }
2939
- }
2940
- console.log();
2941
- await fs__default.default.remove(bundleZipPath);
2942
- return;
2943
- }
2944
- spinner.start("Uploading bundle.zip to S3...");
2945
- const bundleS3Key = `themes/${themeId}/${version}/bundle.zip`;
2946
- await s3Client.send(
2947
- new clientS3.PutObjectCommand({
2948
- Bucket: bucket,
2949
- Key: bundleS3Key,
2950
- Body: bundleZipBuffer,
2951
- ContentType: "application/zip"
2952
- })
2953
- );
2954
- spinner.succeed(
2955
- `Uploaded bundle.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
2956
- );
2957
- await fs__default.default.remove(bundleZipPath);
2958
- let sourceUploaded = false;
2959
- if (!options.skipSource) {
2960
- spinner.start("Looking for source directory...");
2961
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
2962
- if (sourceDir) {
2963
- spinner.succeed(`Found source at: ${sourceDir}`);
2964
- spinner.start("Creating source.zip...");
2965
- const sourceZipPath = path8__default.default.join(
2966
- tmpDir,
2967
- `${themeId}-${version}-source.zip`
2968
- );
2969
- await createZipFromDir(sourceDir, sourceZipPath, [
2970
- "node_modules/**",
2971
- "dist/**",
2972
- ".git/**",
2973
- "*.zip",
2974
- ".next/**",
2975
- ".turbo/**"
2976
- ]);
2977
- const sourceZipBuffer = await fs__default.default.readFile(sourceZipPath);
2978
- const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
2979
- spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
2980
- spinner.start("Uploading source.zip to S3...");
2981
- const sourceS3Key = `themes/${themeId}/${version}/source.zip`;
2982
- await s3Client.send(
2983
- new clientS3.PutObjectCommand({
2984
- Bucket: bucket,
2985
- Key: sourceS3Key,
2986
- Body: sourceZipBuffer,
2987
- ContentType: "application/zip"
2988
- })
2989
- );
2990
- spinner.succeed(
2991
- `Uploaded source.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
2992
- );
2993
- await fs__default.default.remove(sourceZipPath);
2994
- sourceUploaded = true;
2995
- } else {
2996
- spinner.warn(
2997
- chalk4__default.default.yellow("Source directory not found \u2014 skipping source.zip")
2998
- );
2999
- }
3000
- }
3001
- spinner.start("Updating latest.json pointer...");
3002
- await updateLatestPointer(s3Client, bucket, themeId, version);
3003
- spinner.succeed("Updated latest.json pointer");
3004
- console.log();
3005
- exports.logger.success(chalk4__default.default.green.bold("Theme uploaded successfully!"));
3006
- console.log();
3007
- console.log(
3008
- chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version}`)
3009
- );
3010
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3011
- console.log(
3012
- chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
3013
- );
3014
- console.log(
3015
- chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
3016
- );
3017
- console.log();
3018
- } catch (error) {
3019
- spinner.fail(chalk4__default.default.red(`Upload failed: ${error.message}`));
3020
- exports.logger.error(error.stack || error.message);
3021
- process.exit(1);
3022
- }
2867
+ console.log(
2868
+ "command can no longer reach the bucket. Use `onexthm publish` instead:"
2869
+ );
2870
+ console.log();
2871
+ console.log(chalk4__default.default.cyan(" cd themes/your-theme"));
2872
+ console.log(chalk4__default.default.cyan(" onexthm login # one-time, refreshes JWT"));
2873
+ console.log(
2874
+ chalk4__default.default.cyan(" onexthm publish # builds + uploads + confirms")
2875
+ );
2876
+ console.log();
2877
+ console.log(
2878
+ "`publish` does everything this command did (build, version bump,"
2879
+ );
2880
+ console.log(
2881
+ "bundle + source upload) plus content-hashed asset upload, security"
2882
+ );
2883
+ console.log("scanning, and atomic version registration in one step.");
2884
+ console.log();
2885
+ process.exit(1);
3023
2886
  }
3024
2887
 
3025
2888
  // src/commands/download.ts
3026
2889
  init_logger();
3027
- function getS3Client2() {
3028
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3029
- if (adapterMode === "vps") {
3030
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3031
- const secure = process.env.MINIO_SECURE === "true";
3032
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3033
- return new clientS3.S3Client({
3034
- endpoint: endpointUrl,
3035
- region: "us-east-1",
3036
- credentials: {
3037
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3038
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3039
- },
3040
- forcePathStyle: true
3041
- });
3042
- }
3043
- if (adapterMode === "local") {
3044
- return new clientS3.S3Client({
3045
- endpoint: "http://localhost:4569",
3046
- region: "ap-southeast-1",
3047
- credentials: {
3048
- accessKeyId: "S3RVER",
3049
- secretAccessKey: "S3RVER"
3050
- },
3051
- forcePathStyle: true
3052
- });
2890
+ function unwrapEnvelope(raw) {
2891
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
2892
+ return raw.body;
3053
2893
  }
3054
- return new clientS3.S3Client({
3055
- region: process.env.AWS_REGION || "ap-southeast-1"
3056
- });
2894
+ return raw;
3057
2895
  }
3058
- function getBucketName2(env) {
3059
- if (process.env.BUCKET_NAME) {
3060
- return process.env.BUCKET_NAME;
2896
+ async function resolveLatestVersion(apiUrl, themeId) {
2897
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
2898
+ let response;
2899
+ try {
2900
+ response = await fetch(url, { cache: "no-store" });
2901
+ } catch (err) {
2902
+ throw new Error(
2903
+ `Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
2904
+ );
3061
2905
  }
3062
- const environment = env || process.env.ENVIRONMENT || "staging";
3063
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3064
- }
3065
- async function streamToString(stream) {
3066
- const chunks = [];
3067
- for await (const chunk of stream) {
3068
- chunks.push(Buffer.from(chunk));
2906
+ if (!response.ok) {
2907
+ throw new Error(
2908
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
2909
+ );
3069
2910
  }
3070
- return Buffer.concat(chunks).toString("utf-8");
3071
- }
3072
- async function streamToBuffer(stream) {
3073
- const chunks = [];
3074
- for await (const chunk of stream) {
3075
- chunks.push(Buffer.from(chunk));
2911
+ const raw = await response.json();
2912
+ const data = unwrapEnvelope(raw);
2913
+ const latest = data?.latest_version;
2914
+ if (typeof latest !== "string" || latest.length === 0) {
2915
+ throw new Error(
2916
+ `Theme "${themeId}" has no published versions yet (no latest_version in response)`
2917
+ );
3076
2918
  }
3077
- return Buffer.concat(chunks);
2919
+ return latest;
3078
2920
  }
3079
- async function resolveLatestVersion(s3Client, bucket, themeId) {
3080
- try {
3081
- const response = await s3Client.send(
3082
- new clientS3.GetObjectCommand({
3083
- Bucket: bucket,
3084
- Key: `themes/${themeId}/latest.json`
3085
- })
3086
- );
3087
- const body = await streamToString(response.Body);
3088
- const data = JSON.parse(body);
3089
- return data.version;
3090
- } catch (error) {
2921
+ async function downloadBundleZip(apiUrl, themeId, version) {
2922
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version)}/download`;
2923
+ const response = await fetch(url);
2924
+ if (!response.ok) {
3091
2925
  throw new Error(
3092
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
2926
+ `Bundle download failed for "${themeId}@${version}" (HTTP ${response.status})`
3093
2927
  );
3094
2928
  }
2929
+ const contentType = response.headers.get("content-type") || "";
2930
+ if (contentType.includes("application/json")) {
2931
+ const raw = await response.json();
2932
+ const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
2933
+ const body = envelope.body;
2934
+ if (typeof body !== "string") {
2935
+ throw new Error(
2936
+ "Unexpected /download response shape: expected base64 string in body"
2937
+ );
2938
+ }
2939
+ return Buffer.from(body, "base64");
2940
+ }
2941
+ const arrayBuffer = await response.arrayBuffer();
2942
+ return Buffer.from(arrayBuffer);
3095
2943
  }
3096
2944
  async function createCompatibilityFiles(outputDir, manifest) {
3097
2945
  const entryFile = manifest.output?.entry || "bundle-entry.js";
@@ -3115,47 +2963,58 @@ export * from './bundle-entry.js';
3115
2963
  const pkgJsonPath = path8__default.default.join(outputDir, "package.json");
3116
2964
  await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3117
2965
  }
3118
- function showDownloadFailureHelp(themeId, bucket) {
2966
+ function showDownloadFailureHelp(themeId, apiUrl) {
3119
2967
  console.log();
3120
2968
  exports.logger.error(chalk4__default.default.red.bold("Theme download failed"));
3121
2969
  console.log();
3122
2970
  console.log(chalk4__default.default.yellow("Possible reasons:"));
3123
- console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
3124
- console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
3125
- console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
3126
- console.log(chalk4__default.default.gray(" 4. bundle.zip is missing or corrupted"));
2971
+ console.log(
2972
+ chalk4__default.default.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
2973
+ );
2974
+ console.log(
2975
+ chalk4__default.default.gray(
2976
+ " 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
2977
+ )
2978
+ );
2979
+ console.log(
2980
+ chalk4__default.default.gray(" 3. API base URL is wrong or the website-api is unreachable")
2981
+ );
3127
2982
  console.log();
3128
2983
  console.log(chalk4__default.default.cyan.bold("To fix this:"));
3129
2984
  console.log();
3130
- console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
3131
- console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
3132
- console.log(chalk4__default.default.gray(" pnpm build"));
3133
- console.log(chalk4__default.default.gray(" onexthm upload"));
3134
- console.log();
3135
- console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
2985
+ console.log(chalk4__default.default.white("1. Verify the theme is published:"));
3136
2986
  console.log(
3137
- chalk4__default.default.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
2987
+ chalk4__default.default.gray(
2988
+ ` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
2989
+ )
3138
2990
  );
3139
- console.log(chalk4__default.default.gray(" - Or use AWS_PROFILE=your-profile"));
3140
- console.log(chalk4__default.default.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
3141
2991
  console.log();
3142
- console.log(chalk4__default.default.white("3. Check bucket configuration:"));
3143
- console.log(chalk4__default.default.gray(` Current bucket: ${bucket}`));
2992
+ console.log(chalk4__default.default.white("2. Check API URL configuration:"));
2993
+ console.log(chalk4__default.default.gray(` Current API URL: ${apiUrl}`));
3144
2994
  console.log(
3145
- chalk4__default.default.gray(" Set BUCKET_NAME environment variable if different")
2995
+ chalk4__default.default.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3146
2996
  );
3147
2997
  console.log();
3148
- console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
3149
- console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2998
+ console.log(chalk4__default.default.white("3. Pin a specific version (CI/production):"));
2999
+ console.log(
3000
+ chalk4__default.default.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
3001
+ );
3150
3002
  console.log();
3151
3003
  }
3152
3004
  async function downloadCommand(options) {
3153
- exports.logger.header("Download Theme from S3");
3005
+ exports.logger.header("Download Theme");
3154
3006
  const spinner = ora__default.default("Initializing download...").start();
3007
+ if (options.bucket || options.environment) {
3008
+ spinner.stop();
3009
+ exports.logger.warning(
3010
+ "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3011
+ );
3012
+ spinner.start();
3013
+ }
3014
+ const apiUrl = getApiUrl();
3155
3015
  try {
3156
3016
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3157
- const version = options.version || process.env.THEME_VERSION || "latest";
3158
- const bucket = options.bucket || getBucketName2(options.environment);
3017
+ const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
3159
3018
  const outputDir = options.output || "./active-theme";
3160
3019
  if (!themeId) {
3161
3020
  spinner.fail(
@@ -3165,12 +3024,10 @@ async function downloadCommand(options) {
3165
3024
  );
3166
3025
  process.exit(1);
3167
3026
  }
3168
- spinner.text = `Downloading ${themeId}@${version} from ${bucket}...`;
3169
- const s3Client = getS3Client2();
3170
- let resolvedVersion = version;
3171
- if (version === "latest") {
3172
- spinner.text = "Resolving latest version...";
3173
- resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
3027
+ spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
3028
+ let resolvedVersion = requestedVersion;
3029
+ if (requestedVersion === "latest") {
3030
+ resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
3174
3031
  spinner.succeed(
3175
3032
  `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
3176
3033
  );
@@ -3180,24 +3037,19 @@ async function downloadCommand(options) {
3180
3037
  chalk4__default.default.yellow(
3181
3038
  `
3182
3039
  Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
3183
- For production builds, pin to a specific version:
3040
+ For reproducible builds, pin to a specific version:
3184
3041
  THEME_VERSION=${resolvedVersion}
3185
3042
  `
3186
3043
  )
3187
3044
  );
3188
3045
  }
3046
+ } else {
3047
+ spinner.succeed(`Using version: ${chalk4__default.default.cyan(resolvedVersion)}`);
3189
3048
  }
3190
3049
  spinner.start(
3191
3050
  `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
3192
3051
  );
3193
- const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
3194
- const response = await s3Client.send(
3195
- new clientS3.GetObjectCommand({
3196
- Bucket: bucket,
3197
- Key: s3Key
3198
- })
3199
- );
3200
- const zipBuffer = await streamToBuffer(response.Body);
3052
+ const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
3201
3053
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3202
3054
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3203
3055
  spinner.start("Extracting bundle...");
@@ -3216,7 +3068,7 @@ async function downloadCommand(options) {
3216
3068
  console.log(
3217
3069
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
3218
3070
  );
3219
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3071
+ console.log(chalk4__default.default.cyan(" Source: ") + chalk4__default.default.white(apiUrl));
3220
3072
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
3221
3073
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
3222
3074
  if (manifest.counts) {
@@ -3228,82 +3080,70 @@ async function downloadCommand(options) {
3228
3080
  } catch (error) {
3229
3081
  spinner.fail(chalk4__default.default.red("Download failed"));
3230
3082
  exports.logger.error(error.message);
3231
- const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
3232
- const bucket = options.bucket || getBucketName2(options.environment);
3233
- showDownloadFailureHelp(themeId, bucket);
3083
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
3084
+ showDownloadFailureHelp(themeId, apiUrl);
3234
3085
  process.exit(1);
3235
3086
  }
3236
3087
  }
3237
3088
 
3238
3089
  // src/commands/clone.ts
3239
3090
  init_logger();
3240
- function getS3Client3() {
3241
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3242
- if (adapterMode === "vps") {
3243
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3244
- const secure = process.env.MINIO_SECURE === "true";
3245
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3246
- return new clientS3.S3Client({
3247
- endpoint: endpointUrl,
3248
- region: "us-east-1",
3249
- credentials: {
3250
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3251
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3252
- },
3253
- forcePathStyle: true
3254
- });
3255
- }
3256
- if (adapterMode === "local") {
3257
- return new clientS3.S3Client({
3258
- endpoint: "http://localhost:4569",
3259
- region: "ap-southeast-1",
3260
- credentials: {
3261
- accessKeyId: "S3RVER",
3262
- secretAccessKey: "S3RVER"
3263
- },
3264
- forcePathStyle: true
3265
- });
3266
- }
3267
- return new clientS3.S3Client({
3268
- region: process.env.AWS_REGION || "ap-southeast-1"
3269
- });
3270
- }
3271
- function getBucketName3(env) {
3272
- if (process.env.BUCKET_NAME) {
3273
- return process.env.BUCKET_NAME;
3091
+ function unwrapEnvelope2(raw) {
3092
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3093
+ return raw.body;
3274
3094
  }
3275
- const environment = env || process.env.ENVIRONMENT || "staging";
3276
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3095
+ return raw;
3277
3096
  }
3278
- async function streamToString2(stream) {
3279
- const chunks = [];
3280
- for await (const chunk of stream) {
3281
- chunks.push(Buffer.from(chunk));
3097
+ async function resolveLatestVersion2(apiUrl, themeId) {
3098
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3099
+ const response = await fetch(url, { cache: "no-store" });
3100
+ if (!response.ok) {
3101
+ throw new Error(
3102
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3103
+ );
3282
3104
  }
3283
- return Buffer.concat(chunks).toString("utf-8");
3284
- }
3285
- async function streamToBuffer2(stream) {
3286
- const chunks = [];
3287
- for await (const chunk of stream) {
3288
- chunks.push(Buffer.from(chunk));
3105
+ const raw = await response.json();
3106
+ const data = unwrapEnvelope2(raw);
3107
+ const latest = data?.latest_version;
3108
+ if (typeof latest !== "string" || latest.length === 0) {
3109
+ throw new Error(`Theme "${themeId}" has no published versions yet`);
3289
3110
  }
3290
- return Buffer.concat(chunks);
3111
+ return latest;
3291
3112
  }
3292
- async function resolveLatestVersion2(s3Client, bucket, themeId) {
3293
- try {
3294
- const response = await s3Client.send(
3295
- new clientS3.GetObjectCommand({
3296
- Bucket: bucket,
3297
- Key: `themes/${themeId}/latest.json`
3298
- })
3113
+ async function fetchSourceZip(apiUrl, themeId, version) {
3114
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version)}`;
3115
+ const response = await authenticatedFetch(url, {
3116
+ method: "GET"
3117
+ });
3118
+ if (!response.ok) {
3119
+ if (response.status === 404) {
3120
+ throw new Error(
3121
+ `Source not found for ${themeId}@${version}. The theme may not have been published with source upload enabled.`
3122
+ );
3123
+ }
3124
+ if (response.status === 401 || response.status === 403) {
3125
+ throw new Error(
3126
+ `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
3127
+ );
3128
+ }
3129
+ throw new Error(
3130
+ `Source URL request failed for "${themeId}@${version}" (HTTP ${response.status})`
3299
3131
  );
3300
- const body = await streamToString2(response.Body);
3301
- return JSON.parse(body).version;
3302
- } catch (error) {
3132
+ }
3133
+ const raw = await response.json();
3134
+ const data = unwrapEnvelope2(raw);
3135
+ const presignedUrl = data?.download_url;
3136
+ if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
3137
+ throw new Error("Unexpected /source response shape: missing download_url");
3138
+ }
3139
+ const zipResponse = await fetch(presignedUrl);
3140
+ if (!zipResponse.ok) {
3303
3141
  throw new Error(
3304
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3142
+ `Presigned source download failed (HTTP ${zipResponse.status})`
3305
3143
  );
3306
3144
  }
3145
+ const arrayBuffer = await zipResponse.arrayBuffer();
3146
+ return Buffer.from(arrayBuffer);
3307
3147
  }
3308
3148
  function runInstall(cwd) {
3309
3149
  return new Promise((resolve) => {
@@ -3402,15 +3242,24 @@ async function renameTheme(themeDir, oldName, newName) {
3402
3242
  }
3403
3243
  async function cloneCommand(themeName, options) {
3404
3244
  exports.logger.header("Clone Theme Source");
3245
+ if (options.bucket || options.environment) {
3246
+ exports.logger.warning(
3247
+ "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
3248
+ );
3249
+ }
3250
+ const tokens = await getValidTokens();
3251
+ if (!tokens) {
3252
+ exports.logger.error("Not logged in. Run: onexthm login");
3253
+ process.exit(1);
3254
+ }
3405
3255
  let newName = options.name;
3406
3256
  if (!newName) {
3407
3257
  newName = await promptThemeName(themeName);
3408
3258
  }
3409
3259
  const spinner = ora__default.default("Initializing clone...").start();
3410
3260
  try {
3411
- const bucket = options.bucket || getBucketName3(options.environment);
3261
+ const apiUrl = getApiUrl();
3412
3262
  const outputDir = options.output || path8__default.default.resolve(process.cwd(), newName);
3413
- const s3Client = getS3Client3();
3414
3263
  if (await fs__default.default.pathExists(outputDir)) {
3415
3264
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
3416
3265
  exports.logger.info(
@@ -3423,28 +3272,20 @@ async function cloneCommand(themeName, options) {
3423
3272
  let version = options.version || "latest";
3424
3273
  if (version === "latest") {
3425
3274
  spinner.text = "Resolving latest version...";
3426
- version = await resolveLatestVersion2(s3Client, bucket, themeName);
3275
+ version = await resolveLatestVersion2(apiUrl, themeName);
3427
3276
  spinner.succeed(`Resolved latest version: ${chalk4__default.default.cyan(version)}`);
3428
3277
  }
3429
3278
  spinner.start(`Downloading source.zip for ${themeName}@${version}...`);
3430
- const s3Key = `themes/${themeName}/${version}/source.zip`;
3431
3279
  let zipBuffer;
3432
3280
  try {
3433
- const response = await s3Client.send(
3434
- new clientS3.GetObjectCommand({
3435
- Bucket: bucket,
3436
- Key: s3Key
3437
- })
3438
- );
3439
- zipBuffer = await streamToBuffer2(response.Body);
3281
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version);
3440
3282
  } catch (error) {
3441
- spinner.fail(chalk4__default.default.red(`Source not found: s3://${bucket}/${s3Key}`));
3283
+ spinner.fail(chalk4__default.default.red(error.message));
3442
3284
  console.log();
3443
3285
  console.log(
3444
- chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
3445
- );
3446
- console.log(
3447
- chalk4__default.default.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3286
+ chalk4__default.default.gray(
3287
+ `Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
3288
+ )
3448
3289
  );
3449
3290
  console.log();
3450
3291
  process.exit(1);