@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 +361 -469
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +361 -469
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +38 -14
- package/dist/index.d.ts +38 -14
- package/dist/index.js +284 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +284 -442
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
package/dist/cli.mjs
CHANGED
|
@@ -18,7 +18,6 @@ import inquirer from 'inquirer';
|
|
|
18
18
|
import archiver from 'archiver';
|
|
19
19
|
import FormData from 'form-data';
|
|
20
20
|
import fetch2 from 'node-fetch';
|
|
21
|
-
import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
22
21
|
import AdmZip from 'adm-zip';
|
|
23
22
|
import chokidar from 'chokidar';
|
|
24
23
|
import http from 'http';
|
|
@@ -3443,343 +3442,94 @@ async function deployCommand(options) {
|
|
|
3443
3442
|
|
|
3444
3443
|
// src/commands/upload.ts
|
|
3445
3444
|
init_logger();
|
|
3446
|
-
function
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
endpoint: endpointUrl,
|
|
3454
|
-
region: "us-east-1",
|
|
3455
|
-
credentials: {
|
|
3456
|
-
accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
|
|
3457
|
-
secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
|
|
3458
|
-
},
|
|
3459
|
-
forcePathStyle: true
|
|
3460
|
-
});
|
|
3461
|
-
}
|
|
3462
|
-
if (adapterMode === "local") {
|
|
3463
|
-
return new S3Client({
|
|
3464
|
-
endpoint: "http://localhost:4569",
|
|
3465
|
-
region: "ap-southeast-1",
|
|
3466
|
-
credentials: {
|
|
3467
|
-
accessKeyId: "S3RVER",
|
|
3468
|
-
secretAccessKey: "S3RVER"
|
|
3469
|
-
},
|
|
3470
|
-
forcePathStyle: true
|
|
3471
|
-
});
|
|
3472
|
-
}
|
|
3473
|
-
return new S3Client({
|
|
3474
|
-
region: process.env.AWS_REGION || "ap-southeast-1"
|
|
3475
|
-
});
|
|
3476
|
-
}
|
|
3477
|
-
function getBucketName(env) {
|
|
3478
|
-
if (process.env.BUCKET_NAME) {
|
|
3479
|
-
return process.env.BUCKET_NAME;
|
|
3480
|
-
}
|
|
3481
|
-
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3482
|
-
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3483
|
-
}
|
|
3484
|
-
async function findCompiledThemeDir(themeId, version2) {
|
|
3485
|
-
const searchPaths = [path9.resolve(process.cwd(), "dist")];
|
|
3486
|
-
for (const dir of searchPaths) {
|
|
3487
|
-
if (await fs.pathExists(dir)) {
|
|
3488
|
-
const hasManifest = await fs.pathExists(path9.join(dir, "manifest.json"));
|
|
3489
|
-
const hasThemeEntry = await fs.pathExists(path9.join(dir, "bundle-entry.js")) || await fs.pathExists(path9.join(dir, "theme.config.js")) || await fs.pathExists(path9.join(dir, "index.js"));
|
|
3490
|
-
if (hasManifest || hasThemeEntry) {
|
|
3491
|
-
return dir;
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
return null;
|
|
3496
|
-
}
|
|
3497
|
-
async function readManifest() {
|
|
3498
|
-
const manifestTsPath = path9.resolve(process.cwd(), "manifest.ts");
|
|
3499
|
-
if (await fs.pathExists(manifestTsPath)) {
|
|
3500
|
-
try {
|
|
3501
|
-
const module = await import(manifestTsPath);
|
|
3502
|
-
return module.default || module;
|
|
3503
|
-
} catch (error) {
|
|
3504
|
-
logger.warning("Failed to import manifest.ts, trying package.json");
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
const packageJsonPath = path9.resolve(process.cwd(), "package.json");
|
|
3508
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3509
|
-
const pkg = await fs.readJson(packageJsonPath);
|
|
3510
|
-
return {
|
|
3511
|
-
themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
|
|
3512
|
-
version: pkg.version || "1.0.0"
|
|
3513
|
-
};
|
|
3514
|
-
}
|
|
3515
|
-
throw new Error(
|
|
3516
|
-
"No manifest.ts or package.json found. Are you in a theme directory?"
|
|
3445
|
+
async function uploadCommand(_options) {
|
|
3446
|
+
logger.header("Upload Theme to S3 \u2014 DEPRECATED");
|
|
3447
|
+
console.log();
|
|
3448
|
+
console.log(
|
|
3449
|
+
chalk4.yellow.bold(
|
|
3450
|
+
"`onexthm upload` is deprecated and no longer functional."
|
|
3451
|
+
)
|
|
3517
3452
|
);
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
const output = fs.createWriteStream(outputPath);
|
|
3522
|
-
const archive = archiver("zip", { zlib: { level: 6 } });
|
|
3523
|
-
output.on("close", () => resolve());
|
|
3524
|
-
archive.on("error", (err) => reject(err));
|
|
3525
|
-
archive.pipe(output);
|
|
3526
|
-
archive.glob("**/*", {
|
|
3527
|
-
cwd: sourceDir,
|
|
3528
|
-
dot: true,
|
|
3529
|
-
ignore: excludePatterns
|
|
3530
|
-
});
|
|
3531
|
-
archive.finalize();
|
|
3532
|
-
});
|
|
3533
|
-
}
|
|
3534
|
-
async function findSourceDir(themeId, explicitDir) {
|
|
3535
|
-
if (explicitDir) {
|
|
3536
|
-
if (await fs.pathExists(explicitDir)) return explicitDir;
|
|
3537
|
-
return null;
|
|
3538
|
-
}
|
|
3539
|
-
const searchPaths = [
|
|
3540
|
-
process.cwd(),
|
|
3541
|
-
path9.resolve(process.cwd(), `../../themes/${themeId}`),
|
|
3542
|
-
path9.resolve(process.cwd(), `../themes/${themeId}`)
|
|
3543
|
-
];
|
|
3544
|
-
const markers = ["theme.config.ts", "bundle-entry.ts"];
|
|
3545
|
-
for (const dir of searchPaths) {
|
|
3546
|
-
for (const marker of markers) {
|
|
3547
|
-
if (await fs.pathExists(path9.join(dir, marker))) {
|
|
3548
|
-
return dir;
|
|
3549
|
-
}
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
return null;
|
|
3553
|
-
}
|
|
3554
|
-
async function updateLatestPointer(s3Client, bucket, themeId, version2) {
|
|
3555
|
-
const latestData = {
|
|
3556
|
-
version: version2,
|
|
3557
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3558
|
-
};
|
|
3559
|
-
await s3Client.send(
|
|
3560
|
-
new PutObjectCommand({
|
|
3561
|
-
Bucket: bucket,
|
|
3562
|
-
Key: `themes/${themeId}/latest.json`,
|
|
3563
|
-
Body: JSON.stringify(latestData, null, 2),
|
|
3564
|
-
ContentType: "application/json"
|
|
3565
|
-
})
|
|
3453
|
+
console.log();
|
|
3454
|
+
console.log(
|
|
3455
|
+
"The platform no longer exposes themes via direct S3 access, so this"
|
|
3566
3456
|
);
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
if (!compiledDir) {
|
|
3587
|
-
spinner.fail(
|
|
3588
|
-
chalk4.red(
|
|
3589
|
-
`Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
|
|
3590
|
-
)
|
|
3591
|
-
);
|
|
3592
|
-
logger.info(chalk4.gray(`Expected location:
|
|
3593
|
-
- ./dist/`));
|
|
3594
|
-
process.exit(1);
|
|
3595
|
-
}
|
|
3596
|
-
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
3597
|
-
spinner.start("Creating bundle.zip...");
|
|
3598
|
-
const tmpDir = os.tmpdir();
|
|
3599
|
-
const bundleZipPath = path9.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
|
|
3600
|
-
await createZipFromDir(compiledDir, bundleZipPath);
|
|
3601
|
-
const bundleZipBuffer = await fs.readFile(bundleZipPath);
|
|
3602
|
-
const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
|
|
3603
|
-
spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
|
|
3604
|
-
if (options.dryRun) {
|
|
3605
|
-
spinner.info(chalk4.yellow("Dry run mode \u2014 no files will be uploaded"));
|
|
3606
|
-
console.log();
|
|
3607
|
-
console.log(chalk4.gray(` bundle.zip: ${bundleSizeMB} MB`));
|
|
3608
|
-
console.log(
|
|
3609
|
-
chalk4.cyan(
|
|
3610
|
-
` S3 path: s3://${bucket}/themes/${themeId}/${version2}/bundle.zip`
|
|
3611
|
-
)
|
|
3612
|
-
);
|
|
3613
|
-
if (!options.skipSource) {
|
|
3614
|
-
const sourceDir = await findSourceDir(themeId, options.sourceDir);
|
|
3615
|
-
if (sourceDir) {
|
|
3616
|
-
console.log(chalk4.gray(` source dir: ${sourceDir}`));
|
|
3617
|
-
console.log(
|
|
3618
|
-
chalk4.cyan(
|
|
3619
|
-
` S3 path: s3://${bucket}/themes/${themeId}/${version2}/source.zip`
|
|
3620
|
-
)
|
|
3621
|
-
);
|
|
3622
|
-
} else {
|
|
3623
|
-
console.log(
|
|
3624
|
-
chalk4.yellow(" source dir: not found (source.zip will be skipped)")
|
|
3625
|
-
);
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
console.log();
|
|
3629
|
-
await fs.remove(bundleZipPath);
|
|
3630
|
-
return;
|
|
3631
|
-
}
|
|
3632
|
-
spinner.start("Uploading bundle.zip to S3...");
|
|
3633
|
-
const bundleS3Key = `themes/${themeId}/${version2}/bundle.zip`;
|
|
3634
|
-
await s3Client.send(
|
|
3635
|
-
new PutObjectCommand({
|
|
3636
|
-
Bucket: bucket,
|
|
3637
|
-
Key: bundleS3Key,
|
|
3638
|
-
Body: bundleZipBuffer,
|
|
3639
|
-
ContentType: "application/zip"
|
|
3640
|
-
})
|
|
3641
|
-
);
|
|
3642
|
-
spinner.succeed(
|
|
3643
|
-
`Uploaded bundle.zip ${chalk4.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
|
|
3644
|
-
);
|
|
3645
|
-
await fs.remove(bundleZipPath);
|
|
3646
|
-
let sourceUploaded = false;
|
|
3647
|
-
if (!options.skipSource) {
|
|
3648
|
-
spinner.start("Looking for source directory...");
|
|
3649
|
-
const sourceDir = await findSourceDir(themeId, options.sourceDir);
|
|
3650
|
-
if (sourceDir) {
|
|
3651
|
-
spinner.succeed(`Found source at: ${sourceDir}`);
|
|
3652
|
-
spinner.start("Creating source.zip...");
|
|
3653
|
-
const sourceZipPath = path9.join(
|
|
3654
|
-
tmpDir,
|
|
3655
|
-
`${themeId}-${version2}-source.zip`
|
|
3656
|
-
);
|
|
3657
|
-
await createZipFromDir(sourceDir, sourceZipPath, [
|
|
3658
|
-
"node_modules/**",
|
|
3659
|
-
"dist/**",
|
|
3660
|
-
".git/**",
|
|
3661
|
-
"*.zip",
|
|
3662
|
-
".next/**",
|
|
3663
|
-
".turbo/**"
|
|
3664
|
-
]);
|
|
3665
|
-
const sourceZipBuffer = await fs.readFile(sourceZipPath);
|
|
3666
|
-
const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
|
|
3667
|
-
spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
|
|
3668
|
-
spinner.start("Uploading source.zip to S3...");
|
|
3669
|
-
const sourceS3Key = `themes/${themeId}/${version2}/source.zip`;
|
|
3670
|
-
await s3Client.send(
|
|
3671
|
-
new PutObjectCommand({
|
|
3672
|
-
Bucket: bucket,
|
|
3673
|
-
Key: sourceS3Key,
|
|
3674
|
-
Body: sourceZipBuffer,
|
|
3675
|
-
ContentType: "application/zip"
|
|
3676
|
-
})
|
|
3677
|
-
);
|
|
3678
|
-
spinner.succeed(
|
|
3679
|
-
`Uploaded source.zip ${chalk4.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
|
|
3680
|
-
);
|
|
3681
|
-
await fs.remove(sourceZipPath);
|
|
3682
|
-
sourceUploaded = true;
|
|
3683
|
-
} else {
|
|
3684
|
-
spinner.warn(
|
|
3685
|
-
chalk4.yellow("Source directory not found \u2014 skipping source.zip")
|
|
3686
|
-
);
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
spinner.start("Updating latest.json pointer...");
|
|
3690
|
-
await updateLatestPointer(s3Client, bucket, themeId, version2);
|
|
3691
|
-
spinner.succeed("Updated latest.json pointer");
|
|
3692
|
-
console.log();
|
|
3693
|
-
logger.success(chalk4.green.bold("Theme uploaded successfully!"));
|
|
3694
|
-
console.log();
|
|
3695
|
-
console.log(
|
|
3696
|
-
chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${version2}`)
|
|
3697
|
-
);
|
|
3698
|
-
console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
|
|
3699
|
-
console.log(
|
|
3700
|
-
chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
|
|
3701
|
-
);
|
|
3702
|
-
console.log(
|
|
3703
|
-
chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version2}/`)
|
|
3704
|
-
);
|
|
3705
|
-
console.log();
|
|
3706
|
-
} catch (error) {
|
|
3707
|
-
spinner.fail(chalk4.red(`Upload failed: ${error.message}`));
|
|
3708
|
-
logger.error(error.stack || error.message);
|
|
3709
|
-
process.exit(1);
|
|
3710
|
-
}
|
|
3457
|
+
console.log(
|
|
3458
|
+
"command can no longer reach the bucket. Use `onexthm publish` instead:"
|
|
3459
|
+
);
|
|
3460
|
+
console.log();
|
|
3461
|
+
console.log(chalk4.cyan(" cd themes/your-theme"));
|
|
3462
|
+
console.log(chalk4.cyan(" onexthm login # one-time, refreshes JWT"));
|
|
3463
|
+
console.log(
|
|
3464
|
+
chalk4.cyan(" onexthm publish # builds + uploads + confirms")
|
|
3465
|
+
);
|
|
3466
|
+
console.log();
|
|
3467
|
+
console.log(
|
|
3468
|
+
"`publish` does everything this command did (build, version bump,"
|
|
3469
|
+
);
|
|
3470
|
+
console.log(
|
|
3471
|
+
"bundle + source upload) plus content-hashed asset upload, security"
|
|
3472
|
+
);
|
|
3473
|
+
console.log("scanning, and atomic version registration in one step.");
|
|
3474
|
+
console.log();
|
|
3475
|
+
process.exit(1);
|
|
3711
3476
|
}
|
|
3712
3477
|
|
|
3713
3478
|
// src/commands/download.ts
|
|
3714
3479
|
init_logger();
|
|
3715
|
-
function
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
|
|
3719
|
-
const secure = process.env.MINIO_SECURE === "true";
|
|
3720
|
-
const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
|
|
3721
|
-
return new S3Client({
|
|
3722
|
-
endpoint: endpointUrl,
|
|
3723
|
-
region: "us-east-1",
|
|
3724
|
-
credentials: {
|
|
3725
|
-
accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
|
|
3726
|
-
secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
|
|
3727
|
-
},
|
|
3728
|
-
forcePathStyle: true
|
|
3729
|
-
});
|
|
3480
|
+
function unwrapEnvelope(raw) {
|
|
3481
|
+
if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
|
|
3482
|
+
return raw.body;
|
|
3730
3483
|
}
|
|
3731
|
-
|
|
3732
|
-
return new S3Client({
|
|
3733
|
-
endpoint: "http://localhost:4569",
|
|
3734
|
-
region: "ap-southeast-1",
|
|
3735
|
-
credentials: {
|
|
3736
|
-
accessKeyId: "S3RVER",
|
|
3737
|
-
secretAccessKey: "S3RVER"
|
|
3738
|
-
},
|
|
3739
|
-
forcePathStyle: true
|
|
3740
|
-
});
|
|
3741
|
-
}
|
|
3742
|
-
return new S3Client({
|
|
3743
|
-
region: process.env.AWS_REGION || "ap-southeast-1"
|
|
3744
|
-
});
|
|
3484
|
+
return raw;
|
|
3745
3485
|
}
|
|
3746
|
-
function
|
|
3747
|
-
|
|
3748
|
-
|
|
3486
|
+
async function resolveLatestVersion(apiUrl, themeId) {
|
|
3487
|
+
const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
|
|
3488
|
+
let response;
|
|
3489
|
+
try {
|
|
3490
|
+
response = await fetch(url, { cache: "no-store" });
|
|
3491
|
+
} catch (err) {
|
|
3492
|
+
throw new Error(
|
|
3493
|
+
`Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
|
|
3494
|
+
);
|
|
3749
3495
|
}
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
}
|
|
3753
|
-
|
|
3754
|
-
const chunks = [];
|
|
3755
|
-
for await (const chunk of stream) {
|
|
3756
|
-
chunks.push(Buffer.from(chunk));
|
|
3496
|
+
if (!response.ok) {
|
|
3497
|
+
throw new Error(
|
|
3498
|
+
`Version lookup failed for "${themeId}" (HTTP ${response.status})`
|
|
3499
|
+
);
|
|
3757
3500
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3501
|
+
const raw = await response.json();
|
|
3502
|
+
const data = unwrapEnvelope(raw);
|
|
3503
|
+
const latest = data?.latest_version;
|
|
3504
|
+
if (typeof latest !== "string" || latest.length === 0) {
|
|
3505
|
+
throw new Error(
|
|
3506
|
+
`Theme "${themeId}" has no published versions yet (no latest_version in response)`
|
|
3507
|
+
);
|
|
3764
3508
|
}
|
|
3765
|
-
return
|
|
3509
|
+
return latest;
|
|
3766
3510
|
}
|
|
3767
|
-
async function
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
Bucket: bucket,
|
|
3772
|
-
Key: `themes/${themeId}/latest.json`
|
|
3773
|
-
})
|
|
3774
|
-
);
|
|
3775
|
-
const body = await streamToString(response.Body);
|
|
3776
|
-
const data = JSON.parse(body);
|
|
3777
|
-
return data.version;
|
|
3778
|
-
} catch (error) {
|
|
3511
|
+
async function downloadBundleZip(apiUrl, themeId, version2) {
|
|
3512
|
+
const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version2)}/download`;
|
|
3513
|
+
const response = await fetch(url);
|
|
3514
|
+
if (!response.ok) {
|
|
3779
3515
|
throw new Error(
|
|
3780
|
-
`
|
|
3516
|
+
`Bundle download failed for "${themeId}@${version2}" (HTTP ${response.status})`
|
|
3781
3517
|
);
|
|
3782
3518
|
}
|
|
3519
|
+
const contentType = response.headers.get("content-type") || "";
|
|
3520
|
+
if (contentType.includes("application/json")) {
|
|
3521
|
+
const raw = await response.json();
|
|
3522
|
+
const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
|
|
3523
|
+
const body = envelope.body;
|
|
3524
|
+
if (typeof body !== "string") {
|
|
3525
|
+
throw new Error(
|
|
3526
|
+
"Unexpected /download response shape: expected base64 string in body"
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
return Buffer.from(body, "base64");
|
|
3530
|
+
}
|
|
3531
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3532
|
+
return Buffer.from(arrayBuffer);
|
|
3783
3533
|
}
|
|
3784
3534
|
async function createCompatibilityFiles(outputDir, manifest) {
|
|
3785
3535
|
const entryFile = manifest.output?.entry || "bundle-entry.js";
|
|
@@ -3803,47 +3553,58 @@ export * from './bundle-entry.js';
|
|
|
3803
3553
|
const pkgJsonPath = path9.join(outputDir, "package.json");
|
|
3804
3554
|
await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
|
|
3805
3555
|
}
|
|
3806
|
-
function showDownloadFailureHelp(themeId,
|
|
3556
|
+
function showDownloadFailureHelp(themeId, apiUrl) {
|
|
3807
3557
|
console.log();
|
|
3808
3558
|
logger.error(chalk4.red.bold("Theme download failed"));
|
|
3809
3559
|
console.log();
|
|
3810
3560
|
console.log(chalk4.yellow("Possible reasons:"));
|
|
3811
|
-
console.log(
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
console.log(
|
|
3561
|
+
console.log(
|
|
3562
|
+
chalk4.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
|
|
3563
|
+
);
|
|
3564
|
+
console.log(
|
|
3565
|
+
chalk4.gray(
|
|
3566
|
+
" 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
|
|
3567
|
+
)
|
|
3568
|
+
);
|
|
3569
|
+
console.log(
|
|
3570
|
+
chalk4.gray(" 3. API base URL is wrong or the website-api is unreachable")
|
|
3571
|
+
);
|
|
3815
3572
|
console.log();
|
|
3816
3573
|
console.log(chalk4.cyan.bold("To fix this:"));
|
|
3817
3574
|
console.log();
|
|
3818
|
-
console.log(chalk4.white("1.
|
|
3819
|
-
console.log(chalk4.gray(` cd themes/${themeId}`));
|
|
3820
|
-
console.log(chalk4.gray(" pnpm build"));
|
|
3821
|
-
console.log(chalk4.gray(" onexthm upload"));
|
|
3822
|
-
console.log();
|
|
3823
|
-
console.log(chalk4.white("2. Verify AWS credentials are set:"));
|
|
3575
|
+
console.log(chalk4.white("1. Verify the theme is published:"));
|
|
3824
3576
|
console.log(
|
|
3825
|
-
chalk4.gray(
|
|
3577
|
+
chalk4.gray(
|
|
3578
|
+
` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
|
|
3579
|
+
)
|
|
3826
3580
|
);
|
|
3827
|
-
console.log(chalk4.gray(" - Or use AWS_PROFILE=your-profile"));
|
|
3828
|
-
console.log(chalk4.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
|
|
3829
3581
|
console.log();
|
|
3830
|
-
console.log(chalk4.white("
|
|
3831
|
-
console.log(chalk4.gray(` Current
|
|
3582
|
+
console.log(chalk4.white("2. Check API URL configuration:"));
|
|
3583
|
+
console.log(chalk4.gray(` Current API URL: ${apiUrl}`));
|
|
3832
3584
|
console.log(
|
|
3833
|
-
chalk4.gray("
|
|
3585
|
+
chalk4.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
|
|
3834
3586
|
);
|
|
3835
3587
|
console.log();
|
|
3836
|
-
console.log(chalk4.white("
|
|
3837
|
-
console.log(
|
|
3588
|
+
console.log(chalk4.white("3. Pin a specific version (CI/production):"));
|
|
3589
|
+
console.log(
|
|
3590
|
+
chalk4.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
|
|
3591
|
+
);
|
|
3838
3592
|
console.log();
|
|
3839
3593
|
}
|
|
3840
3594
|
async function downloadCommand(options) {
|
|
3841
|
-
logger.header("Download Theme
|
|
3595
|
+
logger.header("Download Theme");
|
|
3842
3596
|
const spinner = ora("Initializing download...").start();
|
|
3597
|
+
if (options.bucket || options.environment) {
|
|
3598
|
+
spinner.stop();
|
|
3599
|
+
logger.warning(
|
|
3600
|
+
"--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
|
|
3601
|
+
);
|
|
3602
|
+
spinner.start();
|
|
3603
|
+
}
|
|
3604
|
+
const apiUrl = getApiUrl();
|
|
3843
3605
|
try {
|
|
3844
3606
|
const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
|
|
3845
|
-
const
|
|
3846
|
-
const bucket = options.bucket || getBucketName2(options.environment);
|
|
3607
|
+
const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
|
|
3847
3608
|
const outputDir = options.output || "./active-theme";
|
|
3848
3609
|
if (!themeId) {
|
|
3849
3610
|
spinner.fail(
|
|
@@ -3853,12 +3614,10 @@ async function downloadCommand(options) {
|
|
|
3853
3614
|
);
|
|
3854
3615
|
process.exit(1);
|
|
3855
3616
|
}
|
|
3856
|
-
spinner.text = `
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
spinner.text = "Resolving latest version...";
|
|
3861
|
-
resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
|
|
3617
|
+
spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
|
|
3618
|
+
let resolvedVersion = requestedVersion;
|
|
3619
|
+
if (requestedVersion === "latest") {
|
|
3620
|
+
resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
|
|
3862
3621
|
spinner.succeed(
|
|
3863
3622
|
`Resolved latest version: ${chalk4.cyan(resolvedVersion)}`
|
|
3864
3623
|
);
|
|
@@ -3868,24 +3627,19 @@ async function downloadCommand(options) {
|
|
|
3868
3627
|
chalk4.yellow(
|
|
3869
3628
|
`
|
|
3870
3629
|
Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
|
|
3871
|
-
For
|
|
3630
|
+
For reproducible builds, pin to a specific version:
|
|
3872
3631
|
THEME_VERSION=${resolvedVersion}
|
|
3873
3632
|
`
|
|
3874
3633
|
)
|
|
3875
3634
|
);
|
|
3876
3635
|
}
|
|
3636
|
+
} else {
|
|
3637
|
+
spinner.succeed(`Using version: ${chalk4.cyan(resolvedVersion)}`);
|
|
3877
3638
|
}
|
|
3878
3639
|
spinner.start(
|
|
3879
3640
|
`Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
|
|
3880
3641
|
);
|
|
3881
|
-
const
|
|
3882
|
-
const response = await s3Client.send(
|
|
3883
|
-
new GetObjectCommand({
|
|
3884
|
-
Bucket: bucket,
|
|
3885
|
-
Key: s3Key
|
|
3886
|
-
})
|
|
3887
|
-
);
|
|
3888
|
-
const zipBuffer = await streamToBuffer(response.Body);
|
|
3642
|
+
const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
|
|
3889
3643
|
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
3890
3644
|
spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
|
|
3891
3645
|
spinner.start("Extracting bundle...");
|
|
@@ -3904,7 +3658,7 @@ async function downloadCommand(options) {
|
|
|
3904
3658
|
console.log(
|
|
3905
3659
|
chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
|
|
3906
3660
|
);
|
|
3907
|
-
console.log(chalk4.cyan("
|
|
3661
|
+
console.log(chalk4.cyan(" Source: ") + chalk4.white(apiUrl));
|
|
3908
3662
|
console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
|
|
3909
3663
|
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
3910
3664
|
if (manifest.counts) {
|
|
@@ -3916,82 +3670,70 @@ async function downloadCommand(options) {
|
|
|
3916
3670
|
} catch (error) {
|
|
3917
3671
|
spinner.fail(chalk4.red("Download failed"));
|
|
3918
3672
|
logger.error(error.message);
|
|
3919
|
-
const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
|
|
3920
|
-
|
|
3921
|
-
showDownloadFailureHelp(themeId, bucket);
|
|
3673
|
+
const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
|
|
3674
|
+
showDownloadFailureHelp(themeId, apiUrl);
|
|
3922
3675
|
process.exit(1);
|
|
3923
3676
|
}
|
|
3924
3677
|
}
|
|
3925
3678
|
|
|
3926
3679
|
// src/commands/clone.ts
|
|
3927
3680
|
init_logger();
|
|
3928
|
-
function
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
|
|
3932
|
-
const secure = process.env.MINIO_SECURE === "true";
|
|
3933
|
-
const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
|
|
3934
|
-
return new S3Client({
|
|
3935
|
-
endpoint: endpointUrl,
|
|
3936
|
-
region: "us-east-1",
|
|
3937
|
-
credentials: {
|
|
3938
|
-
accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
|
|
3939
|
-
secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
|
|
3940
|
-
},
|
|
3941
|
-
forcePathStyle: true
|
|
3942
|
-
});
|
|
3681
|
+
function unwrapEnvelope2(raw) {
|
|
3682
|
+
if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
|
|
3683
|
+
return raw.body;
|
|
3943
3684
|
}
|
|
3944
|
-
|
|
3945
|
-
return new S3Client({
|
|
3946
|
-
endpoint: "http://localhost:4569",
|
|
3947
|
-
region: "ap-southeast-1",
|
|
3948
|
-
credentials: {
|
|
3949
|
-
accessKeyId: "S3RVER",
|
|
3950
|
-
secretAccessKey: "S3RVER"
|
|
3951
|
-
},
|
|
3952
|
-
forcePathStyle: true
|
|
3953
|
-
});
|
|
3954
|
-
}
|
|
3955
|
-
return new S3Client({
|
|
3956
|
-
region: process.env.AWS_REGION || "ap-southeast-1"
|
|
3957
|
-
});
|
|
3685
|
+
return raw;
|
|
3958
3686
|
}
|
|
3959
|
-
function
|
|
3960
|
-
|
|
3961
|
-
|
|
3687
|
+
async function resolveLatestVersion2(apiUrl, themeId) {
|
|
3688
|
+
const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
|
|
3689
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
3690
|
+
if (!response.ok) {
|
|
3691
|
+
throw new Error(
|
|
3692
|
+
`Version lookup failed for "${themeId}" (HTTP ${response.status})`
|
|
3693
|
+
);
|
|
3962
3694
|
}
|
|
3963
|
-
const
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
for await (const chunk of stream) {
|
|
3969
|
-
chunks.push(Buffer.from(chunk));
|
|
3695
|
+
const raw = await response.json();
|
|
3696
|
+
const data = unwrapEnvelope2(raw);
|
|
3697
|
+
const latest = data?.latest_version;
|
|
3698
|
+
if (typeof latest !== "string" || latest.length === 0) {
|
|
3699
|
+
throw new Error(`Theme "${themeId}" has no published versions yet`);
|
|
3970
3700
|
}
|
|
3971
|
-
return
|
|
3701
|
+
return latest;
|
|
3972
3702
|
}
|
|
3973
|
-
async function
|
|
3974
|
-
const
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3703
|
+
async function fetchSourceZip(apiUrl, themeId, version2) {
|
|
3704
|
+
const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
|
|
3705
|
+
const response = await authenticatedFetch(url, {
|
|
3706
|
+
method: "GET"
|
|
3707
|
+
});
|
|
3708
|
+
if (!response.ok) {
|
|
3709
|
+
if (response.status === 404) {
|
|
3710
|
+
throw new Error(
|
|
3711
|
+
`Source not found for ${themeId}@${version2}. The theme may not have been published with source upload enabled.`
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
if (response.status === 401 || response.status === 403) {
|
|
3715
|
+
throw new Error(
|
|
3716
|
+
`Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
throw new Error(
|
|
3720
|
+
`Source URL request failed for "${themeId}@${version2}" (HTTP ${response.status})`
|
|
3987
3721
|
);
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3722
|
+
}
|
|
3723
|
+
const raw = await response.json();
|
|
3724
|
+
const data = unwrapEnvelope2(raw);
|
|
3725
|
+
const presignedUrl = data?.download_url;
|
|
3726
|
+
if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
|
|
3727
|
+
throw new Error("Unexpected /source response shape: missing download_url");
|
|
3728
|
+
}
|
|
3729
|
+
const zipResponse = await fetch(presignedUrl);
|
|
3730
|
+
if (!zipResponse.ok) {
|
|
3991
3731
|
throw new Error(
|
|
3992
|
-
`
|
|
3732
|
+
`Presigned source download failed (HTTP ${zipResponse.status})`
|
|
3993
3733
|
);
|
|
3994
3734
|
}
|
|
3735
|
+
const arrayBuffer = await zipResponse.arrayBuffer();
|
|
3736
|
+
return Buffer.from(arrayBuffer);
|
|
3995
3737
|
}
|
|
3996
3738
|
function runInstall(cwd) {
|
|
3997
3739
|
return new Promise((resolve) => {
|
|
@@ -4090,15 +3832,24 @@ async function renameTheme(themeDir, oldName, newName) {
|
|
|
4090
3832
|
}
|
|
4091
3833
|
async function cloneCommand(themeName, options) {
|
|
4092
3834
|
logger.header("Clone Theme Source");
|
|
3835
|
+
if (options.bucket || options.environment) {
|
|
3836
|
+
logger.warning(
|
|
3837
|
+
"--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3840
|
+
const tokens = await getValidTokens();
|
|
3841
|
+
if (!tokens) {
|
|
3842
|
+
logger.error("Not logged in. Run: onexthm login");
|
|
3843
|
+
process.exit(1);
|
|
3844
|
+
}
|
|
4093
3845
|
let newName = options.name;
|
|
4094
3846
|
if (!newName) {
|
|
4095
3847
|
newName = await promptThemeName(themeName);
|
|
4096
3848
|
}
|
|
4097
3849
|
const spinner = ora("Initializing clone...").start();
|
|
4098
3850
|
try {
|
|
4099
|
-
const
|
|
3851
|
+
const apiUrl = getApiUrl();
|
|
4100
3852
|
const outputDir = options.output || path9.resolve(process.cwd(), newName);
|
|
4101
|
-
const s3Client = getS3Client3();
|
|
4102
3853
|
if (await fs.pathExists(outputDir)) {
|
|
4103
3854
|
spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
|
|
4104
3855
|
logger.info(
|
|
@@ -4111,28 +3862,20 @@ async function cloneCommand(themeName, options) {
|
|
|
4111
3862
|
let version2 = options.version || "latest";
|
|
4112
3863
|
if (version2 === "latest") {
|
|
4113
3864
|
spinner.text = "Resolving latest version...";
|
|
4114
|
-
version2 = await resolveLatestVersion2(
|
|
3865
|
+
version2 = await resolveLatestVersion2(apiUrl, themeName);
|
|
4115
3866
|
spinner.succeed(`Resolved latest version: ${chalk4.cyan(version2)}`);
|
|
4116
3867
|
}
|
|
4117
3868
|
spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
|
|
4118
|
-
const s3Key = `themes/${themeName}/${version2}/source.zip`;
|
|
4119
3869
|
let zipBuffer;
|
|
4120
3870
|
try {
|
|
4121
|
-
|
|
4122
|
-
new GetObjectCommand({
|
|
4123
|
-
Bucket: bucket,
|
|
4124
|
-
Key: s3Key
|
|
4125
|
-
})
|
|
4126
|
-
);
|
|
4127
|
-
zipBuffer = await streamToBuffer2(response.Body);
|
|
3871
|
+
zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
|
|
4128
3872
|
} catch (error) {
|
|
4129
|
-
spinner.fail(chalk4.red(
|
|
3873
|
+
spinner.fail(chalk4.red(error.message));
|
|
4130
3874
|
console.log();
|
|
4131
3875
|
console.log(
|
|
4132
|
-
chalk4.
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
chalk4.gray(`Upload source with: onexthm upload --theme ${themeName}`)
|
|
3876
|
+
chalk4.gray(
|
|
3877
|
+
`Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
|
|
3878
|
+
)
|
|
4136
3879
|
);
|
|
4137
3880
|
console.log();
|
|
4138
3881
|
process.exit(1);
|
|
@@ -4315,10 +4058,7 @@ function createDevServer(options) {
|
|
|
4315
4058
|
return;
|
|
4316
4059
|
}
|
|
4317
4060
|
if (segments.length > 1) {
|
|
4318
|
-
const fallbackPath = path9.join(
|
|
4319
|
-
assetsBase,
|
|
4320
|
-
segments.slice(1).join("/")
|
|
4321
|
-
);
|
|
4061
|
+
const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
|
|
4322
4062
|
if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
|
|
4323
4063
|
serveFile(res, fallbackPath);
|
|
4324
4064
|
return;
|
|
@@ -4830,6 +4570,82 @@ async function whoamiCommand() {
|
|
|
4830
4570
|
|
|
4831
4571
|
// src/commands/publish.ts
|
|
4832
4572
|
init_logger();
|
|
4573
|
+
var MIME_MAP = {
|
|
4574
|
+
".png": "image/png",
|
|
4575
|
+
".jpg": "image/jpeg",
|
|
4576
|
+
".jpeg": "image/jpeg",
|
|
4577
|
+
".gif": "image/gif",
|
|
4578
|
+
".webp": "image/webp",
|
|
4579
|
+
".avif": "image/avif",
|
|
4580
|
+
".svg": "image/svg+xml",
|
|
4581
|
+
".ico": "image/x-icon",
|
|
4582
|
+
".bmp": "image/bmp",
|
|
4583
|
+
".woff": "font/woff",
|
|
4584
|
+
".woff2": "font/woff2",
|
|
4585
|
+
".ttf": "font/ttf",
|
|
4586
|
+
".otf": "font/otf",
|
|
4587
|
+
".eot": "application/vnd.ms-fontobject",
|
|
4588
|
+
".mp4": "video/mp4",
|
|
4589
|
+
".webm": "video/webm",
|
|
4590
|
+
".mov": "video/quicktime",
|
|
4591
|
+
".ogg": "video/ogg",
|
|
4592
|
+
".json": "application/json"
|
|
4593
|
+
};
|
|
4594
|
+
var HASH_LEN = 8;
|
|
4595
|
+
function mimeFor(filename) {
|
|
4596
|
+
const ext = path9.extname(filename).toLowerCase();
|
|
4597
|
+
return MIME_MAP[ext] || "application/octet-stream";
|
|
4598
|
+
}
|
|
4599
|
+
async function sha256Prefix(absPath, len) {
|
|
4600
|
+
const buf = await fs.readFile(absPath);
|
|
4601
|
+
return crypto.createHash("sha256").update(buf).digest("hex").slice(0, len);
|
|
4602
|
+
}
|
|
4603
|
+
function insertHashIntoName(relPath, hash) {
|
|
4604
|
+
const dir = path9.posix.dirname(relPath);
|
|
4605
|
+
const base = path9.posix.basename(relPath);
|
|
4606
|
+
const ext = path9.posix.extname(base);
|
|
4607
|
+
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
4608
|
+
const hashed = `${stem}-${hash}${ext}`;
|
|
4609
|
+
return dir === "." ? hashed : `${dir}/${hashed}`;
|
|
4610
|
+
}
|
|
4611
|
+
async function scanThemeAssets(distDir) {
|
|
4612
|
+
const assetsDir = path9.join(distDir, "theme-assets");
|
|
4613
|
+
if (!await fs.pathExists(assetsDir)) return [];
|
|
4614
|
+
const files = await glob("**/*", {
|
|
4615
|
+
cwd: assetsDir,
|
|
4616
|
+
nodir: true,
|
|
4617
|
+
dot: false
|
|
4618
|
+
});
|
|
4619
|
+
const results = [];
|
|
4620
|
+
for (const rel of files) {
|
|
4621
|
+
const absPath = path9.join(assetsDir, rel);
|
|
4622
|
+
const stat = await fs.stat(absPath);
|
|
4623
|
+
if (!stat.isFile()) continue;
|
|
4624
|
+
const originalPath = rel.split(path9.sep).join("/");
|
|
4625
|
+
const hash = await sha256Prefix(absPath, HASH_LEN);
|
|
4626
|
+
const hashedPath = insertHashIntoName(originalPath, hash);
|
|
4627
|
+
const contentType = mimeFor(rel);
|
|
4628
|
+
results.push({
|
|
4629
|
+
originalPath,
|
|
4630
|
+
hashedPath,
|
|
4631
|
+
hash,
|
|
4632
|
+
size: stat.size,
|
|
4633
|
+
contentType,
|
|
4634
|
+
absPath
|
|
4635
|
+
});
|
|
4636
|
+
}
|
|
4637
|
+
results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
|
|
4638
|
+
return results;
|
|
4639
|
+
}
|
|
4640
|
+
function buildAssetMap(entries) {
|
|
4641
|
+
const map = {};
|
|
4642
|
+
for (const e of entries) {
|
|
4643
|
+
map[e.originalPath] = e.hashedPath;
|
|
4644
|
+
}
|
|
4645
|
+
return map;
|
|
4646
|
+
}
|
|
4647
|
+
|
|
4648
|
+
// src/commands/publish.ts
|
|
4833
4649
|
async function publishCommand(options) {
|
|
4834
4650
|
logger.header("OneX Theme Publish");
|
|
4835
4651
|
const tokens = await getValidTokens();
|
|
@@ -4964,37 +4780,123 @@ Or use the --bump flag:
|
|
|
4964
4780
|
logger.error(error instanceof Error ? error.message : "Build error");
|
|
4965
4781
|
process.exit(1);
|
|
4966
4782
|
}
|
|
4967
|
-
|
|
4783
|
+
const distDir = path9.join(themePath, "dist");
|
|
4784
|
+
let assetEntries = [];
|
|
4785
|
+
try {
|
|
4786
|
+
assetEntries = await scanThemeAssets(distDir);
|
|
4787
|
+
if (assetEntries.length > 0) {
|
|
4788
|
+
logger.info(`Found ${assetEntries.length} theme asset file(s)`);
|
|
4789
|
+
}
|
|
4790
|
+
} catch (error) {
|
|
4791
|
+
logger.error(
|
|
4792
|
+
`Asset scan failed: ${error instanceof Error ? error.message : "unknown"}`
|
|
4793
|
+
);
|
|
4794
|
+
process.exit(1);
|
|
4795
|
+
}
|
|
4796
|
+
try {
|
|
4797
|
+
const assetMap = buildAssetMap(assetEntries);
|
|
4798
|
+
const assetMapPath = path9.join(distDir, "asset-map.json");
|
|
4799
|
+
await fs.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
|
|
4800
|
+
} catch (error) {
|
|
4801
|
+
logger.error(
|
|
4802
|
+
`Failed to write asset-map.json: ${error instanceof Error ? error.message : "unknown"}`
|
|
4803
|
+
);
|
|
4804
|
+
process.exit(1);
|
|
4805
|
+
}
|
|
4806
|
+
logger.startSpinner("Getting upload URLs...");
|
|
4968
4807
|
let bundleUploadUrl;
|
|
4969
4808
|
let sourceUploadUrl;
|
|
4809
|
+
let assetUploads = [];
|
|
4810
|
+
let alreadyUploaded = [];
|
|
4970
4811
|
try {
|
|
4971
4812
|
const pubResponse = await authenticatedFetch(
|
|
4972
4813
|
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
|
|
4973
4814
|
{
|
|
4974
4815
|
method: "POST",
|
|
4975
|
-
body: JSON.stringify({
|
|
4816
|
+
body: JSON.stringify({
|
|
4817
|
+
version: version2,
|
|
4818
|
+
assets: assetEntries.map((a) => ({
|
|
4819
|
+
path: a.hashedPath,
|
|
4820
|
+
hash: a.hash,
|
|
4821
|
+
size: a.size,
|
|
4822
|
+
content_type: a.contentType
|
|
4823
|
+
}))
|
|
4824
|
+
})
|
|
4976
4825
|
}
|
|
4977
4826
|
);
|
|
4978
4827
|
const pubData = await pubResponse.json();
|
|
4979
4828
|
const pubBody = pubData.statusCode ? pubData.body : pubData;
|
|
4980
4829
|
if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
|
|
4981
|
-
logger.stopSpinner(false, "Failed to get upload
|
|
4830
|
+
logger.stopSpinner(false, "Failed to get upload URLs");
|
|
4982
4831
|
logger.error(pubBody.error || "Server error");
|
|
4983
4832
|
process.exit(1);
|
|
4984
4833
|
}
|
|
4985
4834
|
bundleUploadUrl = pubBody.bundleUploadUrl;
|
|
4986
4835
|
sourceUploadUrl = pubBody.sourceUploadUrl;
|
|
4987
|
-
|
|
4836
|
+
assetUploads = pubBody.assetUploads || [];
|
|
4837
|
+
alreadyUploaded = pubBody.alreadyUploaded || [];
|
|
4838
|
+
logger.stopSpinner(
|
|
4839
|
+
true,
|
|
4840
|
+
`Upload URLs obtained (${assetUploads.length} new, ${alreadyUploaded.length} reused)`
|
|
4841
|
+
);
|
|
4988
4842
|
} catch (error) {
|
|
4989
4843
|
logger.stopSpinner(false, "Failed");
|
|
4990
4844
|
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4991
4845
|
process.exit(1);
|
|
4992
4846
|
}
|
|
4847
|
+
if (assetUploads.length > 0) {
|
|
4848
|
+
logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
|
|
4849
|
+
const CONCURRENCY = 8;
|
|
4850
|
+
const byHashedPath = new Map(assetEntries.map((a) => [a.hashedPath, a]));
|
|
4851
|
+
const queue = [...assetUploads];
|
|
4852
|
+
let uploaded = 0;
|
|
4853
|
+
let failed = 0;
|
|
4854
|
+
async function worker() {
|
|
4855
|
+
while (queue.length > 0) {
|
|
4856
|
+
const item = queue.shift();
|
|
4857
|
+
if (!item) break;
|
|
4858
|
+
const entry = byHashedPath.get(item.path);
|
|
4859
|
+
if (!entry) {
|
|
4860
|
+
failed++;
|
|
4861
|
+
logger.error(` \u2717 ${item.path}: no local file found`);
|
|
4862
|
+
continue;
|
|
4863
|
+
}
|
|
4864
|
+
try {
|
|
4865
|
+
const buf = await fs.promises.readFile(entry.absPath);
|
|
4866
|
+
const res = await fetch(item.upload_url, {
|
|
4867
|
+
method: "PUT",
|
|
4868
|
+
headers: {
|
|
4869
|
+
"Content-Type": entry.contentType,
|
|
4870
|
+
"x-amz-acl": "public-read"
|
|
4871
|
+
},
|
|
4872
|
+
body: buf
|
|
4873
|
+
});
|
|
4874
|
+
if (!res.ok) {
|
|
4875
|
+
throw new Error(`HTTP ${res.status}`);
|
|
4876
|
+
}
|
|
4877
|
+
uploaded++;
|
|
4878
|
+
} catch (e) {
|
|
4879
|
+
failed++;
|
|
4880
|
+
logger.error(
|
|
4881
|
+
` \u2717 ${item.path}: ${e instanceof Error ? e.message : "failed"}`
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
await Promise.all(
|
|
4887
|
+
Array.from(
|
|
4888
|
+
{ length: Math.min(CONCURRENCY, assetUploads.length) },
|
|
4889
|
+
() => worker()
|
|
4890
|
+
)
|
|
4891
|
+
);
|
|
4892
|
+
if (failed > 0) {
|
|
4893
|
+
logger.stopSpinner(false, `${failed} asset(s) failed`);
|
|
4894
|
+
process.exit(1);
|
|
4895
|
+
}
|
|
4896
|
+
logger.stopSpinner(true, `Uploaded ${uploaded} asset(s)`);
|
|
4897
|
+
}
|
|
4993
4898
|
logger.startSpinner("Uploading bundle...");
|
|
4994
4899
|
try {
|
|
4995
|
-
const archiver3 = await import('archiver');
|
|
4996
|
-
const { createWriteStream } = await import('fs');
|
|
4997
|
-
const distDir = path9.join(themePath, "dist");
|
|
4998
4900
|
if (!fs.existsSync(distDir)) {
|
|
4999
4901
|
logger.stopSpinner(false, "No dist/ directory");
|
|
5000
4902
|
logger.error("Build the theme first: onexthm build");
|
|
@@ -5005,7 +4907,9 @@ Or use the --bump flag:
|
|
|
5005
4907
|
"bundle.zip",
|
|
5006
4908
|
"source.zip",
|
|
5007
4909
|
"preview-runtime.js",
|
|
5008
|
-
"preview-runtime.js.map"
|
|
4910
|
+
"preview-runtime.js.map",
|
|
4911
|
+
"theme-assets",
|
|
4912
|
+
"theme-assets/**"
|
|
5009
4913
|
]);
|
|
5010
4914
|
const bundleBuffer = fs.readFileSync(bundleZipPath);
|
|
5011
4915
|
const bundleRes = await fetch(bundleUploadUrl, {
|
|
@@ -5092,11 +4996,11 @@ Or use the --bump flag:
|
|
|
5092
4996
|
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
5093
4997
|
}
|
|
5094
4998
|
async function createZip(sourceDir, outputPath, exclude) {
|
|
5095
|
-
const
|
|
4999
|
+
const archiver2 = (await import('archiver')).default;
|
|
5096
5000
|
const { createWriteStream } = await import('fs');
|
|
5097
5001
|
return new Promise((resolve, reject) => {
|
|
5098
5002
|
const output = createWriteStream(outputPath);
|
|
5099
|
-
const archive =
|
|
5003
|
+
const archive = archiver2("zip", { zlib: { level: 9 } });
|
|
5100
5004
|
output.on("close", resolve);
|
|
5101
5005
|
archive.on("error", reject);
|
|
5102
5006
|
archive.pipe(output);
|
|
@@ -5159,29 +5063,17 @@ program.command("deploy").description("Upload theme package to API server").opti
|
|
|
5159
5063
|
"-e, --environment <env>",
|
|
5160
5064
|
"Environment (production, staging, development)"
|
|
5161
5065
|
).action(deployCommand);
|
|
5162
|
-
program.command("upload").description("
|
|
5163
|
-
|
|
5164
|
-
"Environment (staging|production)",
|
|
5165
|
-
"staging"
|
|
5166
|
-
).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);
|
|
5167
|
-
program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
|
|
5066
|
+
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);
|
|
5067
|
+
program.command("download").description("Download a published theme via the website-api").option("-t, --theme-id <id>", "Theme ID to download").option(
|
|
5168
5068
|
"-v, --version <version>",
|
|
5169
5069
|
"Theme version (default: latest)",
|
|
5170
5070
|
"latest"
|
|
5171
|
-
).option("-b, --bucket <name>", "
|
|
5172
|
-
|
|
5173
|
-
"Environment (staging|production)",
|
|
5174
|
-
"staging"
|
|
5175
|
-
).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
5176
|
-
program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
|
|
5071
|
+
).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
5072
|
+
program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
|
|
5177
5073
|
"-v, --version <version>",
|
|
5178
5074
|
"Theme version (default: latest)",
|
|
5179
5075
|
"latest"
|
|
5180
|
-
).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "
|
|
5181
|
-
"-e, --environment <env>",
|
|
5182
|
-
"Environment (staging|production)",
|
|
5183
|
-
"staging"
|
|
5184
|
-
).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
5076
|
+
).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);
|
|
5185
5077
|
program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
|
|
5186
5078
|
program.command("login").description("Login to OneX platform").action(loginCommand);
|
|
5187
5079
|
program.command("logout").description("Logout from OneX platform").action(logoutCommand);
|