@onexapis/cli 1.1.44 → 1.1.46
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 +409 -473
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +409 -473
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +44 -15
- package/dist/index.d.ts +44 -15
- package/dist/index.js +285 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +285 -443
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
- package/templates/default/.cursorrules +126 -0
- package/templates/default/AGENTS.md +126 -0
- package/templates/default/CLAUDE.md +88 -1726
- package/templates/default/THEME_REFERENCE.md +1764 -0
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';
|
|
@@ -2674,12 +2673,12 @@ async function validateCommand(options) {
|
|
|
2674
2673
|
}
|
|
2675
2674
|
themeToValidate = options.theme;
|
|
2676
2675
|
} else {
|
|
2677
|
-
const
|
|
2676
|
+
const isThemeDir2 = [
|
|
2678
2677
|
"theme.config.ts",
|
|
2679
2678
|
"bundle-entry.ts",
|
|
2680
2679
|
"manifest.ts"
|
|
2681
2680
|
].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
|
|
2682
|
-
if (
|
|
2681
|
+
if (isThemeDir2) {
|
|
2683
2682
|
themeToValidate = path9.basename(process.cwd());
|
|
2684
2683
|
logger.info(`Validating current theme: ${themeToValidate}`);
|
|
2685
2684
|
} else {
|
|
@@ -3071,12 +3070,12 @@ async function buildCommand(options) {
|
|
|
3071
3070
|
process.exit(1);
|
|
3072
3071
|
}
|
|
3073
3072
|
} else {
|
|
3074
|
-
const
|
|
3073
|
+
const isThemeDir2 = [
|
|
3075
3074
|
"theme.config.ts",
|
|
3076
3075
|
"bundle-entry.ts",
|
|
3077
3076
|
"manifest.ts"
|
|
3078
3077
|
].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
|
|
3079
|
-
if (
|
|
3078
|
+
if (isThemeDir2) {
|
|
3080
3079
|
themePath = process.cwd();
|
|
3081
3080
|
themeName = path9.basename(themePath);
|
|
3082
3081
|
logger.info(`Building current theme: ${themeName}`);
|
|
@@ -3207,12 +3206,12 @@ async function packageCommand(options) {
|
|
|
3207
3206
|
process.exit(1);
|
|
3208
3207
|
}
|
|
3209
3208
|
} else {
|
|
3210
|
-
const
|
|
3209
|
+
const isThemeDir2 = [
|
|
3211
3210
|
"theme.config.ts",
|
|
3212
3211
|
"bundle-entry.ts",
|
|
3213
3212
|
"manifest.ts"
|
|
3214
3213
|
].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
|
|
3215
|
-
if (
|
|
3214
|
+
if (isThemeDir2) {
|
|
3216
3215
|
themePath = process.cwd();
|
|
3217
3216
|
themeName = path9.basename(themePath);
|
|
3218
3217
|
logger.info(`Packaging current theme: ${themeName}`);
|
|
@@ -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
|
-
});
|
|
3943
|
-
}
|
|
3944
|
-
if (adapterMode === "local") {
|
|
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
|
-
});
|
|
3681
|
+
function unwrapEnvelope2(raw) {
|
|
3682
|
+
if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
|
|
3683
|
+
return raw.body;
|
|
3954
3684
|
}
|
|
3955
|
-
return
|
|
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) => {
|
|
@@ -4005,8 +3747,8 @@ function runInstall(cwd) {
|
|
|
4005
3747
|
});
|
|
4006
3748
|
}
|
|
4007
3749
|
async function promptThemeName(originalName) {
|
|
4008
|
-
const { default:
|
|
4009
|
-
const { themeName } = await
|
|
3750
|
+
const { default: inquirer8 } = await import('inquirer');
|
|
3751
|
+
const { themeName } = await inquirer8.prompt([
|
|
4010
3752
|
{
|
|
4011
3753
|
type: "input",
|
|
4012
3754
|
name: "themeName",
|
|
@@ -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;
|
|
@@ -4485,12 +4225,12 @@ async function devCommand(options) {
|
|
|
4485
4225
|
process.exit(1);
|
|
4486
4226
|
}
|
|
4487
4227
|
} else {
|
|
4488
|
-
const
|
|
4228
|
+
const isThemeDir2 = [
|
|
4489
4229
|
"theme.config.ts",
|
|
4490
4230
|
"bundle-entry.ts",
|
|
4491
4231
|
"manifest.ts"
|
|
4492
4232
|
].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
|
|
4493
|
-
if (
|
|
4233
|
+
if (isThemeDir2) {
|
|
4494
4234
|
themePath = process.cwd();
|
|
4495
4235
|
themeName = path9.basename(themePath);
|
|
4496
4236
|
} else {
|
|
@@ -4918,12 +4658,12 @@ async function publishCommand(options) {
|
|
|
4918
4658
|
if (options.theme) {
|
|
4919
4659
|
themePath = path9.resolve(options.theme);
|
|
4920
4660
|
} else {
|
|
4921
|
-
const
|
|
4661
|
+
const isThemeDir2 = [
|
|
4922
4662
|
"theme.config.ts",
|
|
4923
4663
|
"bundle-entry.ts",
|
|
4924
4664
|
"manifest.ts"
|
|
4925
4665
|
].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
|
|
4926
|
-
if (
|
|
4666
|
+
if (isThemeDir2) {
|
|
4927
4667
|
themePath = process.cwd();
|
|
4928
4668
|
} else {
|
|
4929
4669
|
logger.error(
|
|
@@ -5256,11 +4996,11 @@ Or use the --bump flag:
|
|
|
5256
4996
|
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
5257
4997
|
}
|
|
5258
4998
|
async function createZip(sourceDir, outputPath, exclude) {
|
|
5259
|
-
const
|
|
4999
|
+
const archiver2 = (await import('archiver')).default;
|
|
5260
5000
|
const { createWriteStream } = await import('fs');
|
|
5261
5001
|
return new Promise((resolve, reject) => {
|
|
5262
5002
|
const output = createWriteStream(outputPath);
|
|
5263
|
-
const archive =
|
|
5003
|
+
const archive = archiver2("zip", { zlib: { level: 9 } });
|
|
5264
5004
|
output.on("close", resolve);
|
|
5265
5005
|
archive.on("error", reject);
|
|
5266
5006
|
archive.pipe(output);
|
|
@@ -5273,6 +5013,210 @@ async function createZip(sourceDir, outputPath, exclude) {
|
|
|
5273
5013
|
});
|
|
5274
5014
|
}
|
|
5275
5015
|
|
|
5016
|
+
// src/commands/mcp.ts
|
|
5017
|
+
init_logger();
|
|
5018
|
+
var AI_CONTEXT_FILES = [
|
|
5019
|
+
"CLAUDE.md",
|
|
5020
|
+
"AGENTS.md",
|
|
5021
|
+
".cursorrules",
|
|
5022
|
+
"THEME_REFERENCE.md",
|
|
5023
|
+
".mcp.json"
|
|
5024
|
+
];
|
|
5025
|
+
function resolveTargetDir(opts) {
|
|
5026
|
+
return path9.resolve(opts.cwd ?? process.cwd());
|
|
5027
|
+
}
|
|
5028
|
+
function resolveDefaultTemplateDir() {
|
|
5029
|
+
return path9.join(getTemplatesDir(), "default");
|
|
5030
|
+
}
|
|
5031
|
+
function isThemeDir(dir) {
|
|
5032
|
+
return fs.existsSync(path9.join(dir, "theme.config.ts")) || fs.existsSync(path9.join(dir, "theme.config.js"));
|
|
5033
|
+
}
|
|
5034
|
+
function inspectFiles(templateDir, targetDir) {
|
|
5035
|
+
return AI_CONTEXT_FILES.map((name) => {
|
|
5036
|
+
const templatePath = path9.join(templateDir, name);
|
|
5037
|
+
const targetPath = path9.join(targetDir, name);
|
|
5038
|
+
const exists = fs.existsSync(targetPath);
|
|
5039
|
+
let identical = false;
|
|
5040
|
+
if (exists && fs.existsSync(templatePath)) {
|
|
5041
|
+
try {
|
|
5042
|
+
const a = fs.readFileSync(templatePath, "utf-8");
|
|
5043
|
+
const b = fs.readFileSync(targetPath, "utf-8");
|
|
5044
|
+
identical = a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
|
|
5045
|
+
} catch {
|
|
5046
|
+
identical = false;
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
return { name, templatePath, targetPath, exists, identical };
|
|
5050
|
+
});
|
|
5051
|
+
}
|
|
5052
|
+
async function mcpSetupCommand(options = {}) {
|
|
5053
|
+
const targetDir = resolveTargetDir(options);
|
|
5054
|
+
const templateDir = resolveDefaultTemplateDir();
|
|
5055
|
+
logger.header("Install OneX MCP context files");
|
|
5056
|
+
logger.log(`Target: ${targetDir}`);
|
|
5057
|
+
logger.log("");
|
|
5058
|
+
if (!isThemeDir(targetDir)) {
|
|
5059
|
+
logger.error(
|
|
5060
|
+
`${targetDir} does not look like an OneX theme (no theme.config.ts found).`
|
|
5061
|
+
);
|
|
5062
|
+
logger.log("Run this command from the root of your theme project.");
|
|
5063
|
+
process.exitCode = 1;
|
|
5064
|
+
return;
|
|
5065
|
+
}
|
|
5066
|
+
const statuses = inspectFiles(templateDir, targetDir);
|
|
5067
|
+
const missing = statuses.filter((s) => !s.exists);
|
|
5068
|
+
if (missing.length === 0) {
|
|
5069
|
+
logger.success("All AI context files are already present.");
|
|
5070
|
+
logger.log("Run `onexthm mcp upgrade` to refresh them to the latest version.");
|
|
5071
|
+
return;
|
|
5072
|
+
}
|
|
5073
|
+
logger.log(`Will install ${missing.length} file(s):`);
|
|
5074
|
+
for (const s of missing) logger.log(` + ${s.name}`);
|
|
5075
|
+
logger.log("");
|
|
5076
|
+
if (!options.yes) {
|
|
5077
|
+
const { confirm } = await inquirer.prompt([
|
|
5078
|
+
{
|
|
5079
|
+
type: "confirm",
|
|
5080
|
+
name: "confirm",
|
|
5081
|
+
message: "Proceed?",
|
|
5082
|
+
default: true
|
|
5083
|
+
}
|
|
5084
|
+
]);
|
|
5085
|
+
if (!confirm) {
|
|
5086
|
+
logger.log("Cancelled.");
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
for (const s of missing) {
|
|
5091
|
+
if (!fs.existsSync(s.templatePath)) {
|
|
5092
|
+
logger.warning(` ! ${s.name} not in template \u2014 skipped`);
|
|
5093
|
+
continue;
|
|
5094
|
+
}
|
|
5095
|
+
await fs.copy(s.templatePath, s.targetPath);
|
|
5096
|
+
logger.success(` \u2713 ${s.name}`);
|
|
5097
|
+
}
|
|
5098
|
+
logger.log("");
|
|
5099
|
+
logger.success("Done. Restart your AI client to pick up the new MCP server.");
|
|
5100
|
+
logger.log("");
|
|
5101
|
+
logger.log("Tip: if your theme uses the Figma MCP, edit .mcp.json and");
|
|
5102
|
+
logger.log("replace __FIGMA_API_KEY__ with your Figma personal access token.");
|
|
5103
|
+
}
|
|
5104
|
+
async function mcpUpgradeCommand(options = {}) {
|
|
5105
|
+
const targetDir = resolveTargetDir(options);
|
|
5106
|
+
const templateDir = resolveDefaultTemplateDir();
|
|
5107
|
+
logger.header("Upgrade OneX MCP context files");
|
|
5108
|
+
logger.log(`Target: ${targetDir}`);
|
|
5109
|
+
logger.log("");
|
|
5110
|
+
if (!isThemeDir(targetDir)) {
|
|
5111
|
+
logger.error(
|
|
5112
|
+
`${targetDir} does not look like an OneX theme (no theme.config.ts found).`
|
|
5113
|
+
);
|
|
5114
|
+
process.exitCode = 1;
|
|
5115
|
+
return;
|
|
5116
|
+
}
|
|
5117
|
+
const statuses = inspectFiles(templateDir, targetDir);
|
|
5118
|
+
const toUpgrade = statuses.filter(
|
|
5119
|
+
(s) => !s.identical && s.name !== ".mcp.json"
|
|
5120
|
+
);
|
|
5121
|
+
const mcpJsonStatus = statuses.find((s) => s.name === ".mcp.json");
|
|
5122
|
+
if (mcpJsonStatus && !mcpJsonStatus.exists) {
|
|
5123
|
+
logger.warning(
|
|
5124
|
+
".mcp.json is missing. Run `onexthm mcp setup` to install it."
|
|
5125
|
+
);
|
|
5126
|
+
}
|
|
5127
|
+
if (toUpgrade.length === 0) {
|
|
5128
|
+
logger.success("All AI context files are already up to date.");
|
|
5129
|
+
return;
|
|
5130
|
+
}
|
|
5131
|
+
logger.log("Files to update:");
|
|
5132
|
+
for (const s of toUpgrade) {
|
|
5133
|
+
const tag = !s.exists ? "+ new" : "~ changed";
|
|
5134
|
+
logger.log(` ${tag} ${s.name}`);
|
|
5135
|
+
}
|
|
5136
|
+
logger.log("");
|
|
5137
|
+
logger.log("(.mcp.json is never auto-upgraded \u2014 edit by hand if needed.)");
|
|
5138
|
+
logger.log("");
|
|
5139
|
+
if (!options.yes) {
|
|
5140
|
+
const { confirm } = await inquirer.prompt([
|
|
5141
|
+
{
|
|
5142
|
+
type: "confirm",
|
|
5143
|
+
name: "confirm",
|
|
5144
|
+
message: "Overwrite the file(s) above?",
|
|
5145
|
+
default: true
|
|
5146
|
+
}
|
|
5147
|
+
]);
|
|
5148
|
+
if (!confirm) {
|
|
5149
|
+
logger.log("Cancelled.");
|
|
5150
|
+
return;
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
for (const s of toUpgrade) {
|
|
5154
|
+
if (!fs.existsSync(s.templatePath)) {
|
|
5155
|
+
logger.warning(` ! ${s.name} not in template \u2014 skipped`);
|
|
5156
|
+
continue;
|
|
5157
|
+
}
|
|
5158
|
+
await fs.copy(s.templatePath, s.targetPath, { overwrite: true });
|
|
5159
|
+
logger.success(` \u2713 ${s.name}`);
|
|
5160
|
+
}
|
|
5161
|
+
logger.log("");
|
|
5162
|
+
logger.success("Done. Restart your AI client to pick up the new context.");
|
|
5163
|
+
}
|
|
5164
|
+
async function mcpDoctorCommand(options = {}) {
|
|
5165
|
+
const targetDir = resolveTargetDir(options);
|
|
5166
|
+
const templateDir = resolveDefaultTemplateDir();
|
|
5167
|
+
logger.header("OneX MCP doctor");
|
|
5168
|
+
logger.log(`Target: ${targetDir}`);
|
|
5169
|
+
logger.log("");
|
|
5170
|
+
if (!isThemeDir(targetDir)) {
|
|
5171
|
+
logger.error("Not an OneX theme directory (no theme.config.ts).");
|
|
5172
|
+
process.exitCode = 1;
|
|
5173
|
+
return;
|
|
5174
|
+
}
|
|
5175
|
+
logger.success("theme.config.ts present");
|
|
5176
|
+
const mcpJsonPath = path9.join(targetDir, ".mcp.json");
|
|
5177
|
+
if (!fs.existsSync(mcpJsonPath)) {
|
|
5178
|
+
logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
|
|
5179
|
+
} else {
|
|
5180
|
+
try {
|
|
5181
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
5182
|
+
const servers = mcpJson?.mcpServers ?? {};
|
|
5183
|
+
if (servers.onexthm) {
|
|
5184
|
+
logger.success(".mcp.json registers `onexthm`");
|
|
5185
|
+
} else {
|
|
5186
|
+
logger.error(".mcp.json does not register `onexthm`");
|
|
5187
|
+
}
|
|
5188
|
+
if (servers.figma) {
|
|
5189
|
+
const arg = (servers.figma.args ?? []).join(" ");
|
|
5190
|
+
if (arg.includes("__FIGMA_API_KEY__")) {
|
|
5191
|
+
logger.warning("figma server uses placeholder API key \u2014 replace __FIGMA_API_KEY__");
|
|
5192
|
+
} else {
|
|
5193
|
+
logger.success(".mcp.json registers `figma`");
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
} catch (err) {
|
|
5197
|
+
logger.error(`.mcp.json could not be parsed: ${err.message}`);
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
const statuses = inspectFiles(templateDir, targetDir).filter(
|
|
5201
|
+
(s) => s.name !== ".mcp.json"
|
|
5202
|
+
);
|
|
5203
|
+
for (const s of statuses) {
|
|
5204
|
+
if (!s.exists) {
|
|
5205
|
+
logger.warning(`${s.name} missing`);
|
|
5206
|
+
} else if (!s.identical) {
|
|
5207
|
+
logger.warning(`${s.name} is out of date \u2014 run \`onexthm mcp upgrade\``);
|
|
5208
|
+
} else {
|
|
5209
|
+
logger.success(`${s.name} up to date`);
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
const registryPath = path9.join(targetDir, "sections-registry.ts");
|
|
5213
|
+
if (fs.existsSync(registryPath)) {
|
|
5214
|
+
logger.success("sections-registry.ts present");
|
|
5215
|
+
} else {
|
|
5216
|
+
logger.warning("sections-registry.ts missing \u2014 section tools won't auto-register");
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5276
5220
|
// src/cli.ts
|
|
5277
5221
|
dotenv.config({
|
|
5278
5222
|
path: path9.join(process.cwd(), ".env.local"),
|
|
@@ -5323,33 +5267,25 @@ program.command("deploy").description("Upload theme package to API server").opti
|
|
|
5323
5267
|
"-e, --environment <env>",
|
|
5324
5268
|
"Environment (production, staging, development)"
|
|
5325
5269
|
).action(deployCommand);
|
|
5326
|
-
program.command("upload").description("
|
|
5327
|
-
|
|
5328
|
-
"Environment (staging|production)",
|
|
5329
|
-
"staging"
|
|
5330
|
-
).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);
|
|
5331
|
-
program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
|
|
5270
|
+
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);
|
|
5271
|
+
program.command("download").description("Download a published theme via the website-api").option("-t, --theme-id <id>", "Theme ID to download").option(
|
|
5332
5272
|
"-v, --version <version>",
|
|
5333
5273
|
"Theme version (default: latest)",
|
|
5334
5274
|
"latest"
|
|
5335
|
-
).option("-b, --bucket <name>", "
|
|
5336
|
-
|
|
5337
|
-
"Environment (staging|production)",
|
|
5338
|
-
"staging"
|
|
5339
|
-
).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
5340
|
-
program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
|
|
5275
|
+
).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
5276
|
+
program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
|
|
5341
5277
|
"-v, --version <version>",
|
|
5342
5278
|
"Theme version (default: latest)",
|
|
5343
5279
|
"latest"
|
|
5344
|
-
).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "
|
|
5345
|
-
"-e, --environment <env>",
|
|
5346
|
-
"Environment (staging|production)",
|
|
5347
|
-
"staging"
|
|
5348
|
-
).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
5280
|
+
).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);
|
|
5349
5281
|
program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
|
|
5350
5282
|
program.command("login").description("Login to OneX platform").action(loginCommand);
|
|
5351
5283
|
program.command("logout").description("Logout from OneX platform").action(logoutCommand);
|
|
5352
5284
|
program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
|
|
5285
|
+
var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
|
|
5286
|
+
mcpCmd.command("setup").description("Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme").option("-y, --yes", "Skip confirmation prompts").action(mcpSetupCommand);
|
|
5287
|
+
mcpCmd.command("upgrade").description("Refresh AI-context files to the latest version from the bundled template").option("-y, --yes", "Skip confirmation prompts").action(mcpUpgradeCommand);
|
|
5288
|
+
mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme directory").action(mcpDoctorCommand);
|
|
5353
5289
|
program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
|
|
5354
5290
|
"--bump <type>",
|
|
5355
5291
|
"Auto-bump version before publish (patch|minor|major)"
|