@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/cli.js CHANGED
@@ -20,7 +20,6 @@ var inquirer = require('inquirer');
20
20
  var archiver = require('archiver');
21
21
  var FormData = require('form-data');
22
22
  var fetch2 = require('node-fetch');
23
- var clientS3 = require('@aws-sdk/client-s3');
24
23
  var AdmZip = require('adm-zip');
25
24
  var chokidar = require('chokidar');
26
25
  var http = require('http');
@@ -3486,343 +3485,94 @@ async function deployCommand(options) {
3486
3485
 
3487
3486
  // src/commands/upload.ts
3488
3487
  init_logger();
3489
- function getS3Client() {
3490
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3491
- if (adapterMode === "vps") {
3492
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3493
- const secure = process.env.MINIO_SECURE === "true";
3494
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3495
- return new clientS3.S3Client({
3496
- endpoint: endpointUrl,
3497
- region: "us-east-1",
3498
- credentials: {
3499
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3500
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3501
- },
3502
- forcePathStyle: true
3503
- });
3504
- }
3505
- if (adapterMode === "local") {
3506
- return new clientS3.S3Client({
3507
- endpoint: "http://localhost:4569",
3508
- region: "ap-southeast-1",
3509
- credentials: {
3510
- accessKeyId: "S3RVER",
3511
- secretAccessKey: "S3RVER"
3512
- },
3513
- forcePathStyle: true
3514
- });
3515
- }
3516
- return new clientS3.S3Client({
3517
- region: process.env.AWS_REGION || "ap-southeast-1"
3518
- });
3519
- }
3520
- function getBucketName(env) {
3521
- if (process.env.BUCKET_NAME) {
3522
- return process.env.BUCKET_NAME;
3523
- }
3524
- const environment = env || process.env.ENVIRONMENT || "staging";
3525
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3526
- }
3527
- async function findCompiledThemeDir(themeId, version2) {
3528
- const searchPaths = [path9__default.default.resolve(process.cwd(), "dist")];
3529
- for (const dir of searchPaths) {
3530
- if (await fs__default.default.pathExists(dir)) {
3531
- const hasManifest = await fs__default.default.pathExists(path9__default.default.join(dir, "manifest.json"));
3532
- const hasThemeEntry = await fs__default.default.pathExists(path9__default.default.join(dir, "bundle-entry.js")) || await fs__default.default.pathExists(path9__default.default.join(dir, "theme.config.js")) || await fs__default.default.pathExists(path9__default.default.join(dir, "index.js"));
3533
- if (hasManifest || hasThemeEntry) {
3534
- return dir;
3535
- }
3536
- }
3537
- }
3538
- return null;
3539
- }
3540
- async function readManifest() {
3541
- const manifestTsPath = path9__default.default.resolve(process.cwd(), "manifest.ts");
3542
- if (await fs__default.default.pathExists(manifestTsPath)) {
3543
- try {
3544
- const module = await import(manifestTsPath);
3545
- return module.default || module;
3546
- } catch (error) {
3547
- logger.warning("Failed to import manifest.ts, trying package.json");
3548
- }
3549
- }
3550
- const packageJsonPath = path9__default.default.resolve(process.cwd(), "package.json");
3551
- if (await fs__default.default.pathExists(packageJsonPath)) {
3552
- const pkg = await fs__default.default.readJson(packageJsonPath);
3553
- return {
3554
- themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
3555
- version: pkg.version || "1.0.0"
3556
- };
3557
- }
3558
- throw new Error(
3559
- "No manifest.ts or package.json found. Are you in a theme directory?"
3488
+ async function uploadCommand(_options) {
3489
+ logger.header("Upload Theme to S3 \u2014 DEPRECATED");
3490
+ console.log();
3491
+ console.log(
3492
+ chalk4__default.default.yellow.bold(
3493
+ "`onexthm upload` is deprecated and no longer functional."
3494
+ )
3560
3495
  );
3561
- }
3562
- async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
3563
- return new Promise((resolve, reject) => {
3564
- const output = fs__default.default.createWriteStream(outputPath);
3565
- const archive = archiver__default.default("zip", { zlib: { level: 6 } });
3566
- output.on("close", () => resolve());
3567
- archive.on("error", (err) => reject(err));
3568
- archive.pipe(output);
3569
- archive.glob("**/*", {
3570
- cwd: sourceDir,
3571
- dot: true,
3572
- ignore: excludePatterns
3573
- });
3574
- archive.finalize();
3575
- });
3576
- }
3577
- async function findSourceDir(themeId, explicitDir) {
3578
- if (explicitDir) {
3579
- if (await fs__default.default.pathExists(explicitDir)) return explicitDir;
3580
- return null;
3581
- }
3582
- const searchPaths = [
3583
- process.cwd(),
3584
- path9__default.default.resolve(process.cwd(), `../../themes/${themeId}`),
3585
- path9__default.default.resolve(process.cwd(), `../themes/${themeId}`)
3586
- ];
3587
- const markers = ["theme.config.ts", "bundle-entry.ts"];
3588
- for (const dir of searchPaths) {
3589
- for (const marker of markers) {
3590
- if (await fs__default.default.pathExists(path9__default.default.join(dir, marker))) {
3591
- return dir;
3592
- }
3593
- }
3594
- }
3595
- return null;
3596
- }
3597
- async function updateLatestPointer(s3Client, bucket, themeId, version2) {
3598
- const latestData = {
3599
- version: version2,
3600
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
3601
- };
3602
- await s3Client.send(
3603
- new clientS3.PutObjectCommand({
3604
- Bucket: bucket,
3605
- Key: `themes/${themeId}/latest.json`,
3606
- Body: JSON.stringify(latestData, null, 2),
3607
- ContentType: "application/json"
3608
- })
3496
+ console.log();
3497
+ console.log(
3498
+ "The platform no longer exposes themes via direct S3 access, so this"
3609
3499
  );
3610
- }
3611
- async function uploadCommand(options) {
3612
- logger.header("Upload Theme to S3");
3613
- const spinner = ora__default.default("Preparing theme upload...").start();
3614
- try {
3615
- let themeId;
3616
- let version2;
3617
- if (options.theme) {
3618
- themeId = options.theme;
3619
- version2 = options.version || "1.0.0";
3620
- } else {
3621
- const manifest = await readManifest();
3622
- themeId = manifest.themeId;
3623
- version2 = options.version || manifest.version || "1.0.0";
3624
- }
3625
- spinner.text = `Found theme: ${themeId}@${version2}`;
3626
- const bucket = options.bucket || getBucketName(options.environment);
3627
- const s3Client = getS3Client();
3628
- const compiledDir = await findCompiledThemeDir(themeId, version2);
3629
- if (!compiledDir) {
3630
- spinner.fail(
3631
- chalk4__default.default.red(
3632
- `Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
3633
- )
3634
- );
3635
- logger.info(chalk4__default.default.gray(`Expected location:
3636
- - ./dist/`));
3637
- process.exit(1);
3638
- }
3639
- spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3640
- spinner.start("Creating bundle.zip...");
3641
- const tmpDir = os__default.default.tmpdir();
3642
- const bundleZipPath = path9__default.default.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3643
- await createZipFromDir(compiledDir, bundleZipPath);
3644
- const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
3645
- const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
3646
- spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
3647
- if (options.dryRun) {
3648
- spinner.info(chalk4__default.default.yellow("Dry run mode \u2014 no files will be uploaded"));
3649
- console.log();
3650
- console.log(chalk4__default.default.gray(` bundle.zip: ${bundleSizeMB} MB`));
3651
- console.log(
3652
- chalk4__default.default.cyan(
3653
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/bundle.zip`
3654
- )
3655
- );
3656
- if (!options.skipSource) {
3657
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3658
- if (sourceDir) {
3659
- console.log(chalk4__default.default.gray(` source dir: ${sourceDir}`));
3660
- console.log(
3661
- chalk4__default.default.cyan(
3662
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/source.zip`
3663
- )
3664
- );
3665
- } else {
3666
- console.log(
3667
- chalk4__default.default.yellow(" source dir: not found (source.zip will be skipped)")
3668
- );
3669
- }
3670
- }
3671
- console.log();
3672
- await fs__default.default.remove(bundleZipPath);
3673
- return;
3674
- }
3675
- spinner.start("Uploading bundle.zip to S3...");
3676
- const bundleS3Key = `themes/${themeId}/${version2}/bundle.zip`;
3677
- await s3Client.send(
3678
- new clientS3.PutObjectCommand({
3679
- Bucket: bucket,
3680
- Key: bundleS3Key,
3681
- Body: bundleZipBuffer,
3682
- ContentType: "application/zip"
3683
- })
3684
- );
3685
- spinner.succeed(
3686
- `Uploaded bundle.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
3687
- );
3688
- await fs__default.default.remove(bundleZipPath);
3689
- let sourceUploaded = false;
3690
- if (!options.skipSource) {
3691
- spinner.start("Looking for source directory...");
3692
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3693
- if (sourceDir) {
3694
- spinner.succeed(`Found source at: ${sourceDir}`);
3695
- spinner.start("Creating source.zip...");
3696
- const sourceZipPath = path9__default.default.join(
3697
- tmpDir,
3698
- `${themeId}-${version2}-source.zip`
3699
- );
3700
- await createZipFromDir(sourceDir, sourceZipPath, [
3701
- "node_modules/**",
3702
- "dist/**",
3703
- ".git/**",
3704
- "*.zip",
3705
- ".next/**",
3706
- ".turbo/**"
3707
- ]);
3708
- const sourceZipBuffer = await fs__default.default.readFile(sourceZipPath);
3709
- const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
3710
- spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
3711
- spinner.start("Uploading source.zip to S3...");
3712
- const sourceS3Key = `themes/${themeId}/${version2}/source.zip`;
3713
- await s3Client.send(
3714
- new clientS3.PutObjectCommand({
3715
- Bucket: bucket,
3716
- Key: sourceS3Key,
3717
- Body: sourceZipBuffer,
3718
- ContentType: "application/zip"
3719
- })
3720
- );
3721
- spinner.succeed(
3722
- `Uploaded source.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
3723
- );
3724
- await fs__default.default.remove(sourceZipPath);
3725
- sourceUploaded = true;
3726
- } else {
3727
- spinner.warn(
3728
- chalk4__default.default.yellow("Source directory not found \u2014 skipping source.zip")
3729
- );
3730
- }
3731
- }
3732
- spinner.start("Updating latest.json pointer...");
3733
- await updateLatestPointer(s3Client, bucket, themeId, version2);
3734
- spinner.succeed("Updated latest.json pointer");
3735
- console.log();
3736
- logger.success(chalk4__default.default.green.bold("Theme uploaded successfully!"));
3737
- console.log();
3738
- console.log(
3739
- chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version2}`)
3740
- );
3741
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3742
- console.log(
3743
- chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
3744
- );
3745
- console.log(
3746
- chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version2}/`)
3747
- );
3748
- console.log();
3749
- } catch (error) {
3750
- spinner.fail(chalk4__default.default.red(`Upload failed: ${error.message}`));
3751
- logger.error(error.stack || error.message);
3752
- process.exit(1);
3753
- }
3500
+ console.log(
3501
+ "command can no longer reach the bucket. Use `onexthm publish` instead:"
3502
+ );
3503
+ console.log();
3504
+ console.log(chalk4__default.default.cyan(" cd themes/your-theme"));
3505
+ console.log(chalk4__default.default.cyan(" onexthm login # one-time, refreshes JWT"));
3506
+ console.log(
3507
+ chalk4__default.default.cyan(" onexthm publish # builds + uploads + confirms")
3508
+ );
3509
+ console.log();
3510
+ console.log(
3511
+ "`publish` does everything this command did (build, version bump,"
3512
+ );
3513
+ console.log(
3514
+ "bundle + source upload) plus content-hashed asset upload, security"
3515
+ );
3516
+ console.log("scanning, and atomic version registration in one step.");
3517
+ console.log();
3518
+ process.exit(1);
3754
3519
  }
3755
3520
 
3756
3521
  // src/commands/download.ts
3757
3522
  init_logger();
3758
- function getS3Client2() {
3759
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3760
- if (adapterMode === "vps") {
3761
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3762
- const secure = process.env.MINIO_SECURE === "true";
3763
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3764
- return new clientS3.S3Client({
3765
- endpoint: endpointUrl,
3766
- region: "us-east-1",
3767
- credentials: {
3768
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3769
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3770
- },
3771
- forcePathStyle: true
3772
- });
3523
+ function unwrapEnvelope(raw) {
3524
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3525
+ return raw.body;
3773
3526
  }
3774
- if (adapterMode === "local") {
3775
- return new clientS3.S3Client({
3776
- endpoint: "http://localhost:4569",
3777
- region: "ap-southeast-1",
3778
- credentials: {
3779
- accessKeyId: "S3RVER",
3780
- secretAccessKey: "S3RVER"
3781
- },
3782
- forcePathStyle: true
3783
- });
3784
- }
3785
- return new clientS3.S3Client({
3786
- region: process.env.AWS_REGION || "ap-southeast-1"
3787
- });
3527
+ return raw;
3788
3528
  }
3789
- function getBucketName2(env) {
3790
- if (process.env.BUCKET_NAME) {
3791
- return process.env.BUCKET_NAME;
3529
+ async function resolveLatestVersion(apiUrl, themeId) {
3530
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3531
+ let response;
3532
+ try {
3533
+ response = await fetch(url, { cache: "no-store" });
3534
+ } catch (err) {
3535
+ throw new Error(
3536
+ `Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
3537
+ );
3792
3538
  }
3793
- const environment = env || process.env.ENVIRONMENT || "staging";
3794
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3795
- }
3796
- async function streamToString(stream) {
3797
- const chunks = [];
3798
- for await (const chunk of stream) {
3799
- chunks.push(Buffer.from(chunk));
3539
+ if (!response.ok) {
3540
+ throw new Error(
3541
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3542
+ );
3800
3543
  }
3801
- return Buffer.concat(chunks).toString("utf-8");
3802
- }
3803
- async function streamToBuffer(stream) {
3804
- const chunks = [];
3805
- for await (const chunk of stream) {
3806
- chunks.push(Buffer.from(chunk));
3544
+ const raw = await response.json();
3545
+ const data = unwrapEnvelope(raw);
3546
+ const latest = data?.latest_version;
3547
+ if (typeof latest !== "string" || latest.length === 0) {
3548
+ throw new Error(
3549
+ `Theme "${themeId}" has no published versions yet (no latest_version in response)`
3550
+ );
3807
3551
  }
3808
- return Buffer.concat(chunks);
3552
+ return latest;
3809
3553
  }
3810
- async function resolveLatestVersion(s3Client, bucket, themeId) {
3811
- try {
3812
- const response = await s3Client.send(
3813
- new clientS3.GetObjectCommand({
3814
- Bucket: bucket,
3815
- Key: `themes/${themeId}/latest.json`
3816
- })
3817
- );
3818
- const body = await streamToString(response.Body);
3819
- const data = JSON.parse(body);
3820
- return data.version;
3821
- } catch (error) {
3554
+ async function downloadBundleZip(apiUrl, themeId, version2) {
3555
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version2)}/download`;
3556
+ const response = await fetch(url);
3557
+ if (!response.ok) {
3822
3558
  throw new Error(
3823
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3559
+ `Bundle download failed for "${themeId}@${version2}" (HTTP ${response.status})`
3824
3560
  );
3825
3561
  }
3562
+ const contentType = response.headers.get("content-type") || "";
3563
+ if (contentType.includes("application/json")) {
3564
+ const raw = await response.json();
3565
+ const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
3566
+ const body = envelope.body;
3567
+ if (typeof body !== "string") {
3568
+ throw new Error(
3569
+ "Unexpected /download response shape: expected base64 string in body"
3570
+ );
3571
+ }
3572
+ return Buffer.from(body, "base64");
3573
+ }
3574
+ const arrayBuffer = await response.arrayBuffer();
3575
+ return Buffer.from(arrayBuffer);
3826
3576
  }
3827
3577
  async function createCompatibilityFiles(outputDir, manifest) {
3828
3578
  const entryFile = manifest.output?.entry || "bundle-entry.js";
@@ -3846,47 +3596,58 @@ export * from './bundle-entry.js';
3846
3596
  const pkgJsonPath = path9__default.default.join(outputDir, "package.json");
3847
3597
  await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3848
3598
  }
3849
- function showDownloadFailureHelp(themeId, bucket) {
3599
+ function showDownloadFailureHelp(themeId, apiUrl) {
3850
3600
  console.log();
3851
3601
  logger.error(chalk4__default.default.red.bold("Theme download failed"));
3852
3602
  console.log();
3853
3603
  console.log(chalk4__default.default.yellow("Possible reasons:"));
3854
- console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
3855
- console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
3856
- console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
3857
- console.log(chalk4__default.default.gray(" 4. bundle.zip is missing or corrupted"));
3604
+ console.log(
3605
+ chalk4__default.default.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
3606
+ );
3607
+ console.log(
3608
+ chalk4__default.default.gray(
3609
+ " 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
3610
+ )
3611
+ );
3612
+ console.log(
3613
+ chalk4__default.default.gray(" 3. API base URL is wrong or the website-api is unreachable")
3614
+ );
3858
3615
  console.log();
3859
3616
  console.log(chalk4__default.default.cyan.bold("To fix this:"));
3860
3617
  console.log();
3861
- console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
3862
- console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
3863
- console.log(chalk4__default.default.gray(" pnpm build"));
3864
- console.log(chalk4__default.default.gray(" onexthm upload"));
3865
- console.log();
3866
- console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
3618
+ console.log(chalk4__default.default.white("1. Verify the theme is published:"));
3867
3619
  console.log(
3868
- chalk4__default.default.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
3620
+ chalk4__default.default.gray(
3621
+ ` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
3622
+ )
3869
3623
  );
3870
- console.log(chalk4__default.default.gray(" - Or use AWS_PROFILE=your-profile"));
3871
- console.log(chalk4__default.default.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
3872
3624
  console.log();
3873
- console.log(chalk4__default.default.white("3. Check bucket configuration:"));
3874
- console.log(chalk4__default.default.gray(` Current bucket: ${bucket}`));
3625
+ console.log(chalk4__default.default.white("2. Check API URL configuration:"));
3626
+ console.log(chalk4__default.default.gray(` Current API URL: ${apiUrl}`));
3875
3627
  console.log(
3876
- chalk4__default.default.gray(" Set BUCKET_NAME environment variable if different")
3628
+ chalk4__default.default.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3877
3629
  );
3878
3630
  console.log();
3879
- console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
3880
- console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
3631
+ console.log(chalk4__default.default.white("3. Pin a specific version (CI/production):"));
3632
+ console.log(
3633
+ chalk4__default.default.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
3634
+ );
3881
3635
  console.log();
3882
3636
  }
3883
3637
  async function downloadCommand(options) {
3884
- logger.header("Download Theme from S3");
3638
+ logger.header("Download Theme");
3885
3639
  const spinner = ora__default.default("Initializing download...").start();
3640
+ if (options.bucket || options.environment) {
3641
+ spinner.stop();
3642
+ logger.warning(
3643
+ "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3644
+ );
3645
+ spinner.start();
3646
+ }
3647
+ const apiUrl = getApiUrl();
3886
3648
  try {
3887
3649
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3888
- const version2 = options.version || process.env.THEME_VERSION || "latest";
3889
- const bucket = options.bucket || getBucketName2(options.environment);
3650
+ const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
3890
3651
  const outputDir = options.output || "./active-theme";
3891
3652
  if (!themeId) {
3892
3653
  spinner.fail(
@@ -3896,12 +3657,10 @@ async function downloadCommand(options) {
3896
3657
  );
3897
3658
  process.exit(1);
3898
3659
  }
3899
- spinner.text = `Downloading ${themeId}@${version2} from ${bucket}...`;
3900
- const s3Client = getS3Client2();
3901
- let resolvedVersion = version2;
3902
- if (version2 === "latest") {
3903
- spinner.text = "Resolving latest version...";
3904
- resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
3660
+ spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
3661
+ let resolvedVersion = requestedVersion;
3662
+ if (requestedVersion === "latest") {
3663
+ resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
3905
3664
  spinner.succeed(
3906
3665
  `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
3907
3666
  );
@@ -3911,24 +3670,19 @@ async function downloadCommand(options) {
3911
3670
  chalk4__default.default.yellow(
3912
3671
  `
3913
3672
  Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
3914
- For production builds, pin to a specific version:
3673
+ For reproducible builds, pin to a specific version:
3915
3674
  THEME_VERSION=${resolvedVersion}
3916
3675
  `
3917
3676
  )
3918
3677
  );
3919
3678
  }
3679
+ } else {
3680
+ spinner.succeed(`Using version: ${chalk4__default.default.cyan(resolvedVersion)}`);
3920
3681
  }
3921
3682
  spinner.start(
3922
3683
  `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
3923
3684
  );
3924
- const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
3925
- const response = await s3Client.send(
3926
- new clientS3.GetObjectCommand({
3927
- Bucket: bucket,
3928
- Key: s3Key
3929
- })
3930
- );
3931
- const zipBuffer = await streamToBuffer(response.Body);
3685
+ const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
3932
3686
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3933
3687
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3934
3688
  spinner.start("Extracting bundle...");
@@ -3947,7 +3701,7 @@ async function downloadCommand(options) {
3947
3701
  console.log(
3948
3702
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
3949
3703
  );
3950
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3704
+ console.log(chalk4__default.default.cyan(" Source: ") + chalk4__default.default.white(apiUrl));
3951
3705
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
3952
3706
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
3953
3707
  if (manifest.counts) {
@@ -3959,82 +3713,70 @@ async function downloadCommand(options) {
3959
3713
  } catch (error) {
3960
3714
  spinner.fail(chalk4__default.default.red("Download failed"));
3961
3715
  logger.error(error.message);
3962
- const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
3963
- const bucket = options.bucket || getBucketName2(options.environment);
3964
- showDownloadFailureHelp(themeId, bucket);
3716
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
3717
+ showDownloadFailureHelp(themeId, apiUrl);
3965
3718
  process.exit(1);
3966
3719
  }
3967
3720
  }
3968
3721
 
3969
3722
  // src/commands/clone.ts
3970
3723
  init_logger();
3971
- function getS3Client3() {
3972
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3973
- if (adapterMode === "vps") {
3974
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3975
- const secure = process.env.MINIO_SECURE === "true";
3976
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3977
- return new clientS3.S3Client({
3978
- endpoint: endpointUrl,
3979
- region: "us-east-1",
3980
- credentials: {
3981
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3982
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3983
- },
3984
- forcePathStyle: true
3985
- });
3724
+ function unwrapEnvelope2(raw) {
3725
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3726
+ return raw.body;
3986
3727
  }
3987
- if (adapterMode === "local") {
3988
- return new clientS3.S3Client({
3989
- endpoint: "http://localhost:4569",
3990
- region: "ap-southeast-1",
3991
- credentials: {
3992
- accessKeyId: "S3RVER",
3993
- secretAccessKey: "S3RVER"
3994
- },
3995
- forcePathStyle: true
3996
- });
3997
- }
3998
- return new clientS3.S3Client({
3999
- region: process.env.AWS_REGION || "ap-southeast-1"
4000
- });
3728
+ return raw;
4001
3729
  }
4002
- function getBucketName3(env) {
4003
- if (process.env.BUCKET_NAME) {
4004
- return process.env.BUCKET_NAME;
3730
+ async function resolveLatestVersion2(apiUrl, themeId) {
3731
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3732
+ const response = await fetch(url, { cache: "no-store" });
3733
+ if (!response.ok) {
3734
+ throw new Error(
3735
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3736
+ );
4005
3737
  }
4006
- const environment = env || process.env.ENVIRONMENT || "staging";
4007
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
4008
- }
4009
- async function streamToString2(stream) {
4010
- const chunks = [];
4011
- for await (const chunk of stream) {
4012
- chunks.push(Buffer.from(chunk));
3738
+ const raw = await response.json();
3739
+ const data = unwrapEnvelope2(raw);
3740
+ const latest = data?.latest_version;
3741
+ if (typeof latest !== "string" || latest.length === 0) {
3742
+ throw new Error(`Theme "${themeId}" has no published versions yet`);
4013
3743
  }
4014
- return Buffer.concat(chunks).toString("utf-8");
3744
+ return latest;
4015
3745
  }
4016
- async function streamToBuffer2(stream) {
4017
- const chunks = [];
4018
- for await (const chunk of stream) {
4019
- chunks.push(Buffer.from(chunk));
4020
- }
4021
- return Buffer.concat(chunks);
4022
- }
4023
- async function resolveLatestVersion2(s3Client, bucket, themeId) {
4024
- try {
4025
- const response = await s3Client.send(
4026
- new clientS3.GetObjectCommand({
4027
- Bucket: bucket,
4028
- Key: `themes/${themeId}/latest.json`
4029
- })
3746
+ async function fetchSourceZip(apiUrl, themeId, version2) {
3747
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
3748
+ const response = await authenticatedFetch(url, {
3749
+ method: "GET"
3750
+ });
3751
+ if (!response.ok) {
3752
+ if (response.status === 404) {
3753
+ throw new Error(
3754
+ `Source not found for ${themeId}@${version2}. The theme may not have been published with source upload enabled.`
3755
+ );
3756
+ }
3757
+ if (response.status === 401 || response.status === 403) {
3758
+ throw new Error(
3759
+ `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
3760
+ );
3761
+ }
3762
+ throw new Error(
3763
+ `Source URL request failed for "${themeId}@${version2}" (HTTP ${response.status})`
4030
3764
  );
4031
- const body = await streamToString2(response.Body);
4032
- return JSON.parse(body).version;
4033
- } catch (error) {
3765
+ }
3766
+ const raw = await response.json();
3767
+ const data = unwrapEnvelope2(raw);
3768
+ const presignedUrl = data?.download_url;
3769
+ if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
3770
+ throw new Error("Unexpected /source response shape: missing download_url");
3771
+ }
3772
+ const zipResponse = await fetch(presignedUrl);
3773
+ if (!zipResponse.ok) {
4034
3774
  throw new Error(
4035
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3775
+ `Presigned source download failed (HTTP ${zipResponse.status})`
4036
3776
  );
4037
3777
  }
3778
+ const arrayBuffer = await zipResponse.arrayBuffer();
3779
+ return Buffer.from(arrayBuffer);
4038
3780
  }
4039
3781
  function runInstall(cwd) {
4040
3782
  return new Promise((resolve) => {
@@ -4133,15 +3875,24 @@ async function renameTheme(themeDir, oldName, newName) {
4133
3875
  }
4134
3876
  async function cloneCommand(themeName, options) {
4135
3877
  logger.header("Clone Theme Source");
3878
+ if (options.bucket || options.environment) {
3879
+ logger.warning(
3880
+ "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
3881
+ );
3882
+ }
3883
+ const tokens = await getValidTokens();
3884
+ if (!tokens) {
3885
+ logger.error("Not logged in. Run: onexthm login");
3886
+ process.exit(1);
3887
+ }
4136
3888
  let newName = options.name;
4137
3889
  if (!newName) {
4138
3890
  newName = await promptThemeName(themeName);
4139
3891
  }
4140
3892
  const spinner = ora__default.default("Initializing clone...").start();
4141
3893
  try {
4142
- const bucket = options.bucket || getBucketName3(options.environment);
3894
+ const apiUrl = getApiUrl();
4143
3895
  const outputDir = options.output || path9__default.default.resolve(process.cwd(), newName);
4144
- const s3Client = getS3Client3();
4145
3896
  if (await fs__default.default.pathExists(outputDir)) {
4146
3897
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
4147
3898
  logger.info(
@@ -4154,28 +3905,20 @@ async function cloneCommand(themeName, options) {
4154
3905
  let version2 = options.version || "latest";
4155
3906
  if (version2 === "latest") {
4156
3907
  spinner.text = "Resolving latest version...";
4157
- version2 = await resolveLatestVersion2(s3Client, bucket, themeName);
3908
+ version2 = await resolveLatestVersion2(apiUrl, themeName);
4158
3909
  spinner.succeed(`Resolved latest version: ${chalk4__default.default.cyan(version2)}`);
4159
3910
  }
4160
3911
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4161
- const s3Key = `themes/${themeName}/${version2}/source.zip`;
4162
3912
  let zipBuffer;
4163
3913
  try {
4164
- const response = await s3Client.send(
4165
- new clientS3.GetObjectCommand({
4166
- Bucket: bucket,
4167
- Key: s3Key
4168
- })
4169
- );
4170
- zipBuffer = await streamToBuffer2(response.Body);
3914
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4171
3915
  } catch (error) {
4172
- spinner.fail(chalk4__default.default.red(`Source not found: s3://${bucket}/${s3Key}`));
3916
+ spinner.fail(chalk4__default.default.red(error.message));
4173
3917
  console.log();
4174
3918
  console.log(
4175
- chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
4176
- );
4177
- console.log(
4178
- chalk4__default.default.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3919
+ chalk4__default.default.gray(
3920
+ `Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
3921
+ )
4179
3922
  );
4180
3923
  console.log();
4181
3924
  process.exit(1);
@@ -4358,10 +4101,7 @@ function createDevServer(options) {
4358
4101
  return;
4359
4102
  }
4360
4103
  if (segments.length > 1) {
4361
- const fallbackPath = path9__default.default.join(
4362
- assetsBase,
4363
- segments.slice(1).join("/")
4364
- );
4104
+ const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
4365
4105
  if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
4366
4106
  serveFile(res, fallbackPath);
4367
4107
  return;
@@ -4873,6 +4613,82 @@ async function whoamiCommand() {
4873
4613
 
4874
4614
  // src/commands/publish.ts
4875
4615
  init_logger();
4616
+ var MIME_MAP = {
4617
+ ".png": "image/png",
4618
+ ".jpg": "image/jpeg",
4619
+ ".jpeg": "image/jpeg",
4620
+ ".gif": "image/gif",
4621
+ ".webp": "image/webp",
4622
+ ".avif": "image/avif",
4623
+ ".svg": "image/svg+xml",
4624
+ ".ico": "image/x-icon",
4625
+ ".bmp": "image/bmp",
4626
+ ".woff": "font/woff",
4627
+ ".woff2": "font/woff2",
4628
+ ".ttf": "font/ttf",
4629
+ ".otf": "font/otf",
4630
+ ".eot": "application/vnd.ms-fontobject",
4631
+ ".mp4": "video/mp4",
4632
+ ".webm": "video/webm",
4633
+ ".mov": "video/quicktime",
4634
+ ".ogg": "video/ogg",
4635
+ ".json": "application/json"
4636
+ };
4637
+ var HASH_LEN = 8;
4638
+ function mimeFor(filename) {
4639
+ const ext = path9__default.default.extname(filename).toLowerCase();
4640
+ return MIME_MAP[ext] || "application/octet-stream";
4641
+ }
4642
+ async function sha256Prefix(absPath, len) {
4643
+ const buf = await fs__default.default.readFile(absPath);
4644
+ return crypto__default.default.createHash("sha256").update(buf).digest("hex").slice(0, len);
4645
+ }
4646
+ function insertHashIntoName(relPath, hash) {
4647
+ const dir = path9__default.default.posix.dirname(relPath);
4648
+ const base = path9__default.default.posix.basename(relPath);
4649
+ const ext = path9__default.default.posix.extname(base);
4650
+ const stem = ext ? base.slice(0, -ext.length) : base;
4651
+ const hashed = `${stem}-${hash}${ext}`;
4652
+ return dir === "." ? hashed : `${dir}/${hashed}`;
4653
+ }
4654
+ async function scanThemeAssets(distDir) {
4655
+ const assetsDir = path9__default.default.join(distDir, "theme-assets");
4656
+ if (!await fs__default.default.pathExists(assetsDir)) return [];
4657
+ const files = await glob.glob("**/*", {
4658
+ cwd: assetsDir,
4659
+ nodir: true,
4660
+ dot: false
4661
+ });
4662
+ const results = [];
4663
+ for (const rel of files) {
4664
+ const absPath = path9__default.default.join(assetsDir, rel);
4665
+ const stat = await fs__default.default.stat(absPath);
4666
+ if (!stat.isFile()) continue;
4667
+ const originalPath = rel.split(path9__default.default.sep).join("/");
4668
+ const hash = await sha256Prefix(absPath, HASH_LEN);
4669
+ const hashedPath = insertHashIntoName(originalPath, hash);
4670
+ const contentType = mimeFor(rel);
4671
+ results.push({
4672
+ originalPath,
4673
+ hashedPath,
4674
+ hash,
4675
+ size: stat.size,
4676
+ contentType,
4677
+ absPath
4678
+ });
4679
+ }
4680
+ results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4681
+ return results;
4682
+ }
4683
+ function buildAssetMap(entries) {
4684
+ const map = {};
4685
+ for (const e of entries) {
4686
+ map[e.originalPath] = e.hashedPath;
4687
+ }
4688
+ return map;
4689
+ }
4690
+
4691
+ // src/commands/publish.ts
4876
4692
  async function publishCommand(options) {
4877
4693
  logger.header("OneX Theme Publish");
4878
4694
  const tokens = await getValidTokens();
@@ -5007,37 +4823,123 @@ Or use the --bump flag:
5007
4823
  logger.error(error instanceof Error ? error.message : "Build error");
5008
4824
  process.exit(1);
5009
4825
  }
5010
- logger.startSpinner("Getting upload URL...");
4826
+ const distDir = path9__default.default.join(themePath, "dist");
4827
+ let assetEntries = [];
4828
+ try {
4829
+ assetEntries = await scanThemeAssets(distDir);
4830
+ if (assetEntries.length > 0) {
4831
+ logger.info(`Found ${assetEntries.length} theme asset file(s)`);
4832
+ }
4833
+ } catch (error) {
4834
+ logger.error(
4835
+ `Asset scan failed: ${error instanceof Error ? error.message : "unknown"}`
4836
+ );
4837
+ process.exit(1);
4838
+ }
4839
+ try {
4840
+ const assetMap = buildAssetMap(assetEntries);
4841
+ const assetMapPath = path9__default.default.join(distDir, "asset-map.json");
4842
+ await fs__default.default.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4843
+ } catch (error) {
4844
+ logger.error(
4845
+ `Failed to write asset-map.json: ${error instanceof Error ? error.message : "unknown"}`
4846
+ );
4847
+ process.exit(1);
4848
+ }
4849
+ logger.startSpinner("Getting upload URLs...");
5011
4850
  let bundleUploadUrl;
5012
4851
  let sourceUploadUrl;
4852
+ let assetUploads = [];
4853
+ let alreadyUploaded = [];
5013
4854
  try {
5014
4855
  const pubResponse = await authenticatedFetch(
5015
4856
  `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
5016
4857
  {
5017
4858
  method: "POST",
5018
- body: JSON.stringify({ version: version2 })
4859
+ body: JSON.stringify({
4860
+ version: version2,
4861
+ assets: assetEntries.map((a) => ({
4862
+ path: a.hashedPath,
4863
+ hash: a.hash,
4864
+ size: a.size,
4865
+ content_type: a.contentType
4866
+ }))
4867
+ })
5019
4868
  }
5020
4869
  );
5021
4870
  const pubData = await pubResponse.json();
5022
4871
  const pubBody = pubData.statusCode ? pubData.body : pubData;
5023
4872
  if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
5024
- logger.stopSpinner(false, "Failed to get upload URL");
4873
+ logger.stopSpinner(false, "Failed to get upload URLs");
5025
4874
  logger.error(pubBody.error || "Server error");
5026
4875
  process.exit(1);
5027
4876
  }
5028
4877
  bundleUploadUrl = pubBody.bundleUploadUrl;
5029
4878
  sourceUploadUrl = pubBody.sourceUploadUrl;
5030
- logger.stopSpinner(true, "Upload URL obtained");
4879
+ assetUploads = pubBody.assetUploads || [];
4880
+ alreadyUploaded = pubBody.alreadyUploaded || [];
4881
+ logger.stopSpinner(
4882
+ true,
4883
+ `Upload URLs obtained (${assetUploads.length} new, ${alreadyUploaded.length} reused)`
4884
+ );
5031
4885
  } catch (error) {
5032
4886
  logger.stopSpinner(false, "Failed");
5033
4887
  logger.error(error instanceof Error ? error.message : "Connection failed");
5034
4888
  process.exit(1);
5035
4889
  }
4890
+ if (assetUploads.length > 0) {
4891
+ logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
4892
+ const CONCURRENCY = 8;
4893
+ const byHashedPath = new Map(assetEntries.map((a) => [a.hashedPath, a]));
4894
+ const queue = [...assetUploads];
4895
+ let uploaded = 0;
4896
+ let failed = 0;
4897
+ async function worker() {
4898
+ while (queue.length > 0) {
4899
+ const item = queue.shift();
4900
+ if (!item) break;
4901
+ const entry = byHashedPath.get(item.path);
4902
+ if (!entry) {
4903
+ failed++;
4904
+ logger.error(` \u2717 ${item.path}: no local file found`);
4905
+ continue;
4906
+ }
4907
+ try {
4908
+ const buf = await fs__default.default.promises.readFile(entry.absPath);
4909
+ const res = await fetch(item.upload_url, {
4910
+ method: "PUT",
4911
+ headers: {
4912
+ "Content-Type": entry.contentType,
4913
+ "x-amz-acl": "public-read"
4914
+ },
4915
+ body: buf
4916
+ });
4917
+ if (!res.ok) {
4918
+ throw new Error(`HTTP ${res.status}`);
4919
+ }
4920
+ uploaded++;
4921
+ } catch (e) {
4922
+ failed++;
4923
+ logger.error(
4924
+ ` \u2717 ${item.path}: ${e instanceof Error ? e.message : "failed"}`
4925
+ );
4926
+ }
4927
+ }
4928
+ }
4929
+ await Promise.all(
4930
+ Array.from(
4931
+ { length: Math.min(CONCURRENCY, assetUploads.length) },
4932
+ () => worker()
4933
+ )
4934
+ );
4935
+ if (failed > 0) {
4936
+ logger.stopSpinner(false, `${failed} asset(s) failed`);
4937
+ process.exit(1);
4938
+ }
4939
+ logger.stopSpinner(true, `Uploaded ${uploaded} asset(s)`);
4940
+ }
5036
4941
  logger.startSpinner("Uploading bundle...");
5037
4942
  try {
5038
- const archiver3 = await import('archiver');
5039
- const { createWriteStream } = await import('fs');
5040
- const distDir = path9__default.default.join(themePath, "dist");
5041
4943
  if (!fs__default.default.existsSync(distDir)) {
5042
4944
  logger.stopSpinner(false, "No dist/ directory");
5043
4945
  logger.error("Build the theme first: onexthm build");
@@ -5048,7 +4950,9 @@ Or use the --bump flag:
5048
4950
  "bundle.zip",
5049
4951
  "source.zip",
5050
4952
  "preview-runtime.js",
5051
- "preview-runtime.js.map"
4953
+ "preview-runtime.js.map",
4954
+ "theme-assets",
4955
+ "theme-assets/**"
5052
4956
  ]);
5053
4957
  const bundleBuffer = fs__default.default.readFileSync(bundleZipPath);
5054
4958
  const bundleRes = await fetch(bundleUploadUrl, {
@@ -5135,11 +5039,11 @@ Or use the --bump flag:
5135
5039
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5136
5040
  }
5137
5041
  async function createZip(sourceDir, outputPath, exclude) {
5138
- const archiver3 = (await import('archiver')).default;
5042
+ const archiver2 = (await import('archiver')).default;
5139
5043
  const { createWriteStream } = await import('fs');
5140
5044
  return new Promise((resolve, reject) => {
5141
5045
  const output = createWriteStream(outputPath);
5142
- const archive = archiver3("zip", { zlib: { level: 9 } });
5046
+ const archive = archiver2("zip", { zlib: { level: 9 } });
5143
5047
  output.on("close", resolve);
5144
5048
  archive.on("error", reject);
5145
5049
  archive.pipe(output);
@@ -5202,29 +5106,17 @@ program.command("deploy").description("Upload theme package to API server").opti
5202
5106
  "-e, --environment <env>",
5203
5107
  "Environment (production, staging, development)"
5204
5108
  ).action(deployCommand);
5205
- program.command("upload").description("Upload compiled theme to S3 bucket").option("-t, --theme <theme>", "Theme to upload").option("-b, --bucket <name>", "S3 bucket name").option("-v, --version <version>", "Theme version").option(
5206
- "-e, --environment <env>",
5207
- "Environment (staging|production)",
5208
- "staging"
5209
- ).option("--dry-run", "Show what would be uploaded without uploading").option("--skip-source", "Skip uploading source.zip").option("--source-dir <dir>", "Source directory path").action(uploadCommand);
5210
- program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
5109
+ program.command("upload").description("[deprecated] use `onexthm publish` instead").option("-t, --theme <theme>", "[deprecated] ignored").option("-b, --bucket <name>", "[deprecated] ignored").option("-v, --version <version>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--dry-run", "[deprecated] ignored").option("--skip-source", "[deprecated] ignored").option("--source-dir <dir>", "[deprecated] ignored").action(uploadCommand);
5110
+ program.command("download").description("Download a published theme via the website-api").option("-t, --theme-id <id>", "Theme ID to download").option(
5211
5111
  "-v, --version <version>",
5212
5112
  "Theme version (default: latest)",
5213
5113
  "latest"
5214
- ).option("-b, --bucket <name>", "S3 bucket name").option(
5215
- "-e, --environment <env>",
5216
- "Environment (staging|production)",
5217
- "staging"
5218
- ).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5219
- program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
5114
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5115
+ program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5220
5116
  "-v, --version <version>",
5221
5117
  "Theme version (default: latest)",
5222
5118
  "latest"
5223
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
5224
- "-e, --environment <env>",
5225
- "Environment (staging|production)",
5226
- "staging"
5227
- ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5119
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5228
5120
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5229
5121
  program.command("login").description("Login to OneX platform").action(loginCommand);
5230
5122
  program.command("logout").description("Logout from OneX platform").action(logoutCommand);