@onexapis/cli 1.0.0 → 1.0.2
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/README.md +211 -208
- package/dist/cli.js +468 -221
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +462 -218
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +418 -184
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +416 -186
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import chalk4 from 'chalk';
|
|
4
2
|
import path from 'path';
|
|
5
|
-
import
|
|
6
|
-
import { execSync, spawn } from 'child_process';
|
|
7
|
-
import inquirer from 'inquirer';
|
|
8
|
-
import ora from 'ora';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
9
4
|
import fs from 'fs-extra';
|
|
10
5
|
import ejs from 'ejs';
|
|
6
|
+
import { execSync, spawn } from 'child_process';
|
|
7
|
+
import chalk4 from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import fs2 from 'fs';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
11
12
|
import archiver from 'archiver';
|
|
12
13
|
import FormData from 'form-data';
|
|
13
14
|
import fetch from 'node-fetch';
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
15
|
+
import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
16
|
+
import os from 'os';
|
|
17
|
+
import AdmZip from 'adm-zip';
|
|
16
18
|
|
|
17
19
|
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
18
20
|
var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
|
|
@@ -68,34 +70,7 @@ var Logger = class {
|
|
|
68
70
|
};
|
|
69
71
|
var logger = new Logger();
|
|
70
72
|
|
|
71
|
-
// src/utils/
|
|
72
|
-
function validateName(name) {
|
|
73
|
-
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
|
|
74
|
-
}
|
|
75
|
-
function toKebabCase(str) {
|
|
76
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
77
|
-
}
|
|
78
|
-
function toPascalCase(str) {
|
|
79
|
-
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
80
|
-
}
|
|
81
|
-
function validateThemeName(name) {
|
|
82
|
-
return /^[a-z][a-z0-9-]*$/.test(name);
|
|
83
|
-
}
|
|
84
|
-
function getValidCategories() {
|
|
85
|
-
return [
|
|
86
|
-
"headers",
|
|
87
|
-
"heroes",
|
|
88
|
-
"content",
|
|
89
|
-
"features",
|
|
90
|
-
"testimonials",
|
|
91
|
-
"galleries",
|
|
92
|
-
"cta",
|
|
93
|
-
"footers",
|
|
94
|
-
"ecommerce",
|
|
95
|
-
"blog",
|
|
96
|
-
"contact"
|
|
97
|
-
];
|
|
98
|
-
}
|
|
73
|
+
// src/utils/file-helpers.ts
|
|
99
74
|
async function renderTemplate(templatePath, data) {
|
|
100
75
|
const template = await fs.readFile(templatePath, "utf-8");
|
|
101
76
|
return ejs.render(template, data);
|
|
@@ -180,18 +155,19 @@ function getProjectRoot() {
|
|
|
180
155
|
return process.cwd();
|
|
181
156
|
}
|
|
182
157
|
function getThemesDir() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
158
|
+
const root = getProjectRoot();
|
|
159
|
+
const themesDir = path.join(root, "themes");
|
|
160
|
+
if (fs.existsSync(themesDir)) {
|
|
161
|
+
return themesDir;
|
|
187
162
|
}
|
|
163
|
+
return path.join(root, "src/themes");
|
|
188
164
|
}
|
|
189
165
|
function getFeaturesDir() {
|
|
190
166
|
return path.join(getProjectRoot(), "src/features");
|
|
191
167
|
}
|
|
192
168
|
function isOneXProject() {
|
|
193
169
|
const root = getProjectRoot();
|
|
194
|
-
return fs.existsSync(path.join(root, "
|
|
170
|
+
return fs.existsSync(path.join(root, "themes")) || fs.existsSync(path.join(root, "src/themes"));
|
|
195
171
|
}
|
|
196
172
|
function ensureOneXProject() {
|
|
197
173
|
if (!isOneXProject()) {
|
|
@@ -208,12 +184,12 @@ function listThemes() {
|
|
|
208
184
|
}
|
|
209
185
|
return fs.readdirSync(themesDir).filter((name) => {
|
|
210
186
|
const themePath = path.join(themesDir, name);
|
|
211
|
-
return fs.statSync(themePath).isDirectory() && fs.existsSync(path.join(themePath, "manifest.ts"));
|
|
187
|
+
return fs.statSync(themePath).isDirectory() && (fs.existsSync(path.join(themePath, "theme.config.ts")) || fs.existsSync(path.join(themePath, "bundle-entry.ts")) || fs.existsSync(path.join(themePath, "manifest.ts")));
|
|
212
188
|
});
|
|
213
189
|
}
|
|
214
190
|
function themeExists(themeName) {
|
|
215
191
|
const themePath = path.join(getThemesDir(), themeName);
|
|
216
|
-
return fs.existsSync(themePath) && fs.existsSync(path.join(themePath, "manifest.ts"));
|
|
192
|
+
return fs.existsSync(themePath) && (fs.existsSync(path.join(themePath, "theme.config.ts")) || fs.existsSync(path.join(themePath, "bundle-entry.ts")) || fs.existsSync(path.join(themePath, "manifest.ts")));
|
|
217
193
|
}
|
|
218
194
|
function detectPackageManager() {
|
|
219
195
|
const userAgent = process.env.npm_config_user_agent || "";
|
|
@@ -241,6 +217,35 @@ async function installDependencies(projectPath, packageManager = "npm") {
|
|
|
241
217
|
});
|
|
242
218
|
}
|
|
243
219
|
|
|
220
|
+
// src/utils/validators.ts
|
|
221
|
+
function validateName(name) {
|
|
222
|
+
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
|
|
223
|
+
}
|
|
224
|
+
function toKebabCase(str) {
|
|
225
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
226
|
+
}
|
|
227
|
+
function toPascalCase(str) {
|
|
228
|
+
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
229
|
+
}
|
|
230
|
+
function validateThemeName(name) {
|
|
231
|
+
return /^[a-z][a-z0-9-]*$/.test(name);
|
|
232
|
+
}
|
|
233
|
+
function getValidCategories() {
|
|
234
|
+
return [
|
|
235
|
+
"headers",
|
|
236
|
+
"heroes",
|
|
237
|
+
"content",
|
|
238
|
+
"features",
|
|
239
|
+
"testimonials",
|
|
240
|
+
"galleries",
|
|
241
|
+
"cta",
|
|
242
|
+
"footers",
|
|
243
|
+
"ecommerce",
|
|
244
|
+
"blog",
|
|
245
|
+
"contact"
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
|
|
244
249
|
// src/commands/init.ts
|
|
245
250
|
async function initCommand(projectName, options = {}) {
|
|
246
251
|
logger.header("Create New OneX Theme Project");
|
|
@@ -407,7 +412,9 @@ Add your theme-specific blocks here.
|
|
|
407
412
|
logger.newLine();
|
|
408
413
|
logger.section("Theme structure:");
|
|
409
414
|
logger.log(" src/manifest.ts - Theme manifest and exports");
|
|
410
|
-
logger.log(
|
|
415
|
+
logger.log(
|
|
416
|
+
" src/config.ts - Design tokens (colors, typography, etc.)"
|
|
417
|
+
);
|
|
411
418
|
logger.log(" src/layout.ts - Header and footer configuration");
|
|
412
419
|
logger.log(" src/sections/ - Custom sections for your theme");
|
|
413
420
|
logger.log(" src/blocks/ - Reusable blocks");
|
|
@@ -1410,8 +1417,16 @@ async function listThemesInfo() {
|
|
|
1410
1417
|
}
|
|
1411
1418
|
logger.log("");
|
|
1412
1419
|
for (const theme of themes) {
|
|
1413
|
-
const
|
|
1414
|
-
const
|
|
1420
|
+
const themeDir = path.join(getThemesDir(), theme);
|
|
1421
|
+
const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
|
|
1422
|
+
let manifestContent = "";
|
|
1423
|
+
for (const candidate of candidates) {
|
|
1424
|
+
const candidatePath = path.join(themeDir, candidate);
|
|
1425
|
+
if (fs.existsSync(candidatePath)) {
|
|
1426
|
+
manifestContent = fs.readFileSync(candidatePath, "utf-8");
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1415
1430
|
const nameMatch = manifestContent.match(/name:\s*["'](.+)["']/);
|
|
1416
1431
|
const versionMatch = manifestContent.match(/version:\s*["'](.+)["']/);
|
|
1417
1432
|
const descMatch = manifestContent.match(/description:\s*["'](.+)["']/);
|
|
@@ -1953,13 +1968,11 @@ function getS3Client() {
|
|
|
1953
1968
|
return new S3Client({
|
|
1954
1969
|
endpoint: endpointUrl,
|
|
1955
1970
|
region: "us-east-1",
|
|
1956
|
-
// MinIO doesn't use real regions
|
|
1957
1971
|
credentials: {
|
|
1958
1972
|
accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
|
|
1959
1973
|
secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
|
|
1960
1974
|
},
|
|
1961
1975
|
forcePathStyle: true
|
|
1962
|
-
// Required for MinIO
|
|
1963
1976
|
});
|
|
1964
1977
|
}
|
|
1965
1978
|
if (adapterMode === "local") {
|
|
@@ -2029,36 +2042,40 @@ async function readManifest() {
|
|
|
2029
2042
|
"No manifest.ts or package.json found. Are you in a theme directory?"
|
|
2030
2043
|
);
|
|
2031
2044
|
}
|
|
2032
|
-
function
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
"
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
".ts": "text/plain"
|
|
2047
|
-
};
|
|
2048
|
-
return types[ext] || "application/octet-stream";
|
|
2045
|
+
async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
|
|
2046
|
+
return new Promise((resolve, reject) => {
|
|
2047
|
+
const output = fs.createWriteStream(outputPath);
|
|
2048
|
+
const archive = archiver("zip", { zlib: { level: 6 } });
|
|
2049
|
+
output.on("close", () => resolve());
|
|
2050
|
+
archive.on("error", (err) => reject(err));
|
|
2051
|
+
archive.pipe(output);
|
|
2052
|
+
archive.glob("**/*", {
|
|
2053
|
+
cwd: sourceDir,
|
|
2054
|
+
dot: true,
|
|
2055
|
+
ignore: excludePatterns
|
|
2056
|
+
});
|
|
2057
|
+
archive.finalize();
|
|
2058
|
+
});
|
|
2049
2059
|
}
|
|
2050
|
-
async function
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
)
|
|
2060
|
+
async function findSourceDir(themeId, explicitDir) {
|
|
2061
|
+
if (explicitDir) {
|
|
2062
|
+
if (await fs.pathExists(explicitDir)) return explicitDir;
|
|
2063
|
+
return null;
|
|
2064
|
+
}
|
|
2065
|
+
const searchPaths = [
|
|
2066
|
+
process.cwd(),
|
|
2067
|
+
path.resolve(process.cwd(), `../../themes/${themeId}`),
|
|
2068
|
+
path.resolve(process.cwd(), `../themes/${themeId}`)
|
|
2069
|
+
];
|
|
2070
|
+
const markers = ["theme.config.ts", "bundle-entry.ts"];
|
|
2071
|
+
for (const dir of searchPaths) {
|
|
2072
|
+
for (const marker of markers) {
|
|
2073
|
+
if (await fs.pathExists(path.join(dir, marker))) {
|
|
2074
|
+
return dir;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return null;
|
|
2062
2079
|
}
|
|
2063
2080
|
async function updateLatestPointer(s3Client, bucket, themeId, version) {
|
|
2064
2081
|
const latestData = {
|
|
@@ -2076,12 +2093,18 @@ async function updateLatestPointer(s3Client, bucket, themeId, version) {
|
|
|
2076
2093
|
}
|
|
2077
2094
|
async function uploadCommand(options) {
|
|
2078
2095
|
logger.header("Upload Theme to S3");
|
|
2079
|
-
ensureOneXProject();
|
|
2080
2096
|
const spinner = ora("Preparing theme upload...").start();
|
|
2081
2097
|
try {
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2098
|
+
let themeId;
|
|
2099
|
+
let version;
|
|
2100
|
+
if (options.theme) {
|
|
2101
|
+
themeId = options.theme;
|
|
2102
|
+
version = options.version || "1.0.0";
|
|
2103
|
+
} else {
|
|
2104
|
+
const manifest = await readManifest();
|
|
2105
|
+
themeId = manifest.themeId;
|
|
2106
|
+
version = options.version || manifest.version || "1.0.0";
|
|
2107
|
+
}
|
|
2085
2108
|
spinner.text = `Found theme: ${themeId}@${version}`;
|
|
2086
2109
|
const bucket = options.bucket || getBucketName(options.environment);
|
|
2087
2110
|
const s3Client = getS3Client();
|
|
@@ -2102,67 +2125,113 @@ async function uploadCommand(options) {
|
|
|
2102
2125
|
process.exit(1);
|
|
2103
2126
|
}
|
|
2104
2127
|
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
2105
|
-
spinner.start(
|
|
2106
|
-
const
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
spinner.fail(chalk4.red("No files found in compiled theme directory"));
|
|
2113
|
-
process.exit(1);
|
|
2114
|
-
}
|
|
2115
|
-
spinner.succeed(`Found ${files.length} files to upload`);
|
|
2128
|
+
spinner.start("Creating bundle.zip...");
|
|
2129
|
+
const tmpDir = os.tmpdir();
|
|
2130
|
+
const bundleZipPath = path.join(tmpDir, `${themeId}-${version}-bundle.zip`);
|
|
2131
|
+
await createZipFromDir(compiledDir, bundleZipPath);
|
|
2132
|
+
const bundleZipBuffer = await fs.readFile(bundleZipPath);
|
|
2133
|
+
const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
|
|
2134
|
+
spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
|
|
2116
2135
|
if (options.dryRun) {
|
|
2117
|
-
spinner.info(chalk4.yellow("Dry run mode
|
|
2118
|
-
console.log(
|
|
2119
|
-
|
|
2120
|
-
displayFiles.forEach((file) => console.log(chalk4.gray(` - ${file}`)));
|
|
2121
|
-
if (files.length > 10) {
|
|
2122
|
-
console.log(chalk4.gray(` ... and ${files.length - 10} more files
|
|
2123
|
-
`));
|
|
2124
|
-
}
|
|
2125
|
-
console.log(chalk4.cyan(`
|
|
2126
|
-
S3 bucket: ${bucket}`));
|
|
2136
|
+
spinner.info(chalk4.yellow("Dry run mode \u2014 no files will be uploaded"));
|
|
2137
|
+
console.log();
|
|
2138
|
+
console.log(chalk4.gray(` bundle.zip: ${bundleSizeMB} MB`));
|
|
2127
2139
|
console.log(
|
|
2128
|
-
chalk4.cyan(
|
|
2129
|
-
`
|
|
2140
|
+
chalk4.cyan(
|
|
2141
|
+
` S3 path: s3://${bucket}/themes/${themeId}/${version}/bundle.zip`
|
|
2142
|
+
)
|
|
2130
2143
|
);
|
|
2144
|
+
if (!options.skipSource) {
|
|
2145
|
+
const sourceDir = await findSourceDir(themeId, options.sourceDir);
|
|
2146
|
+
if (sourceDir) {
|
|
2147
|
+
console.log(chalk4.gray(` source dir: ${sourceDir}`));
|
|
2148
|
+
console.log(
|
|
2149
|
+
chalk4.cyan(
|
|
2150
|
+
` S3 path: s3://${bucket}/themes/${themeId}/${version}/source.zip`
|
|
2151
|
+
)
|
|
2152
|
+
);
|
|
2153
|
+
} else {
|
|
2154
|
+
console.log(
|
|
2155
|
+
chalk4.yellow(" source dir: not found (source.zip will be skipped)")
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
console.log();
|
|
2160
|
+
await fs.remove(bundleZipPath);
|
|
2131
2161
|
return;
|
|
2132
2162
|
}
|
|
2133
|
-
spinner.start(
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2163
|
+
spinner.start("Uploading bundle.zip to S3...");
|
|
2164
|
+
const bundleS3Key = `themes/${themeId}/${version}/bundle.zip`;
|
|
2165
|
+
await s3Client.send(
|
|
2166
|
+
new PutObjectCommand({
|
|
2167
|
+
Bucket: bucket,
|
|
2168
|
+
Key: bundleS3Key,
|
|
2169
|
+
Body: bundleZipBuffer,
|
|
2170
|
+
ContentType: "application/zip"
|
|
2171
|
+
})
|
|
2172
|
+
);
|
|
2173
|
+
spinner.succeed(
|
|
2174
|
+
`Uploaded bundle.zip ${chalk4.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
|
|
2175
|
+
);
|
|
2176
|
+
await fs.remove(bundleZipPath);
|
|
2177
|
+
let sourceUploaded = false;
|
|
2178
|
+
if (!options.skipSource) {
|
|
2179
|
+
spinner.start("Looking for source directory...");
|
|
2180
|
+
const sourceDir = await findSourceDir(themeId, options.sourceDir);
|
|
2181
|
+
if (sourceDir) {
|
|
2182
|
+
spinner.succeed(`Found source at: ${sourceDir}`);
|
|
2183
|
+
spinner.start("Creating source.zip...");
|
|
2184
|
+
const sourceZipPath = path.join(
|
|
2185
|
+
tmpDir,
|
|
2186
|
+
`${themeId}-${version}-source.zip`
|
|
2187
|
+
);
|
|
2188
|
+
await createZipFromDir(sourceDir, sourceZipPath, [
|
|
2189
|
+
"node_modules/**",
|
|
2190
|
+
"dist/**",
|
|
2191
|
+
".git/**",
|
|
2192
|
+
"*.zip",
|
|
2193
|
+
".next/**",
|
|
2194
|
+
".turbo/**"
|
|
2195
|
+
]);
|
|
2196
|
+
const sourceZipBuffer = await fs.readFile(sourceZipPath);
|
|
2197
|
+
const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
|
|
2198
|
+
spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
|
|
2199
|
+
spinner.start("Uploading source.zip to S3...");
|
|
2200
|
+
const sourceS3Key = `themes/${themeId}/${version}/source.zip`;
|
|
2201
|
+
await s3Client.send(
|
|
2202
|
+
new PutObjectCommand({
|
|
2203
|
+
Bucket: bucket,
|
|
2204
|
+
Key: sourceS3Key,
|
|
2205
|
+
Body: sourceZipBuffer,
|
|
2206
|
+
ContentType: "application/zip"
|
|
2207
|
+
})
|
|
2208
|
+
);
|
|
2209
|
+
spinner.succeed(
|
|
2210
|
+
`Uploaded source.zip ${chalk4.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
|
|
2211
|
+
);
|
|
2212
|
+
await fs.remove(sourceZipPath);
|
|
2213
|
+
sourceUploaded = true;
|
|
2214
|
+
} else {
|
|
2215
|
+
spinner.warn(
|
|
2216
|
+
chalk4.yellow("Source directory not found \u2014 skipping source.zip")
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2153
2219
|
}
|
|
2154
|
-
spinner.succeed(`Uploaded ${total} files to S3`);
|
|
2155
2220
|
spinner.start("Updating latest.json pointer...");
|
|
2156
2221
|
await updateLatestPointer(s3Client, bucket, themeId, version);
|
|
2157
2222
|
spinner.succeed("Updated latest.json pointer");
|
|
2158
2223
|
console.log();
|
|
2159
|
-
logger.success(chalk4.green.bold("
|
|
2224
|
+
logger.success(chalk4.green.bold("Theme uploaded successfully!"));
|
|
2160
2225
|
console.log();
|
|
2161
2226
|
console.log(
|
|
2162
2227
|
chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${version}`)
|
|
2163
2228
|
);
|
|
2164
2229
|
console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
|
|
2165
|
-
console.log(
|
|
2230
|
+
console.log(
|
|
2231
|
+
chalk4.cyan(" Files: ") + chalk4.white(
|
|
2232
|
+
`bundle.zip${sourceUploaded ? " + source.zip" : ""}`
|
|
2233
|
+
)
|
|
2234
|
+
);
|
|
2166
2235
|
console.log(
|
|
2167
2236
|
chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
|
|
2168
2237
|
);
|
|
@@ -2266,79 +2335,45 @@ async function resolveLatestVersion(s3Client, bucket, themeId) {
|
|
|
2266
2335
|
);
|
|
2267
2336
|
}
|
|
2268
2337
|
}
|
|
2269
|
-
async function
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
`Failed to download manifest for ${themeId}@${version}: ${error.message}
|
|
2282
|
-
The theme may not exist in S3 bucket "${bucket}".`
|
|
2283
|
-
);
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
async function downloadFile(s3Client, bucket, themeId, version, file, outputDir) {
|
|
2287
|
-
const s3Key = `themes/${themeId}/${version}/${file}`;
|
|
2288
|
-
const localPath = path.join(outputDir, file);
|
|
2289
|
-
await fs.ensureDir(path.dirname(localPath));
|
|
2290
|
-
try {
|
|
2291
|
-
const response = await s3Client.send(
|
|
2292
|
-
new GetObjectCommand({
|
|
2293
|
-
Bucket: bucket,
|
|
2294
|
-
Key: s3Key
|
|
2295
|
-
})
|
|
2296
|
-
);
|
|
2297
|
-
const content = await streamToBuffer(response.Body);
|
|
2298
|
-
await fs.writeFile(localPath, content);
|
|
2299
|
-
} catch (error) {
|
|
2300
|
-
throw new Error(`Failed to download ${file}: ${error.message}`);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
function collectFilesToDownload(manifest) {
|
|
2304
|
-
const files = ["manifest.json"];
|
|
2305
|
-
if (manifest.output) {
|
|
2306
|
-
if (manifest.output.entry) {
|
|
2307
|
-
files.push(manifest.output.entry);
|
|
2308
|
-
}
|
|
2309
|
-
if (manifest.output.chunks) {
|
|
2310
|
-
files.push(...manifest.output.chunks);
|
|
2311
|
-
}
|
|
2312
|
-
if (manifest.output.assets) {
|
|
2313
|
-
files.push(...manifest.output.assets);
|
|
2338
|
+
async function createCompatibilityFiles(outputDir, manifest) {
|
|
2339
|
+
var _a;
|
|
2340
|
+
const entryFile = ((_a = manifest.output) == null ? void 0 : _a.entry) || "bundle-entry.js";
|
|
2341
|
+
if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
|
|
2342
|
+
const hashedPath = path.join(outputDir, entryFile);
|
|
2343
|
+
const stablePath = path.join(outputDir, "bundle-entry.js");
|
|
2344
|
+
if (await fs.pathExists(hashedPath)) {
|
|
2345
|
+
await fs.copy(hashedPath, stablePath);
|
|
2346
|
+
const mapPath = hashedPath + ".map";
|
|
2347
|
+
if (await fs.pathExists(mapPath)) {
|
|
2348
|
+
await fs.copy(mapPath, stablePath + ".map");
|
|
2349
|
+
}
|
|
2314
2350
|
}
|
|
2315
2351
|
}
|
|
2316
|
-
return files;
|
|
2317
|
-
}
|
|
2318
|
-
async function createCompatibilityFiles(outputDir) {
|
|
2319
2352
|
const sectionsRegistryPath = path.join(outputDir, "sections-registry.js");
|
|
2320
2353
|
const content = `// Re-export all sections from bundle-entry
|
|
2321
2354
|
// This file exists to maintain compatibility with the import path
|
|
2322
2355
|
export * from './bundle-entry.js';
|
|
2323
2356
|
`;
|
|
2324
2357
|
await fs.writeFile(sectionsRegistryPath, content, "utf-8");
|
|
2358
|
+
const pkgJsonPath = path.join(outputDir, "package.json");
|
|
2359
|
+
await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
|
|
2325
2360
|
}
|
|
2326
2361
|
function showDownloadFailureHelp(themeId, bucket) {
|
|
2327
2362
|
console.log();
|
|
2328
|
-
logger.error(chalk4.red.bold("
|
|
2363
|
+
logger.error(chalk4.red.bold("Theme download failed"));
|
|
2329
2364
|
console.log();
|
|
2330
2365
|
console.log(chalk4.yellow("Possible reasons:"));
|
|
2331
2366
|
console.log(chalk4.gray(" 1. Theme not uploaded to S3 yet"));
|
|
2332
2367
|
console.log(chalk4.gray(" 2. AWS credentials not configured correctly"));
|
|
2333
2368
|
console.log(chalk4.gray(" 3. Bucket name or region is incorrect"));
|
|
2334
|
-
console.log(chalk4.gray(" 4.
|
|
2369
|
+
console.log(chalk4.gray(" 4. bundle.zip is missing or corrupted"));
|
|
2335
2370
|
console.log();
|
|
2336
2371
|
console.log(chalk4.cyan.bold("To fix this:"));
|
|
2337
2372
|
console.log();
|
|
2338
2373
|
console.log(chalk4.white("1. Compile and upload the theme:"));
|
|
2339
2374
|
console.log(chalk4.gray(` cd themes/${themeId}`));
|
|
2340
2375
|
console.log(chalk4.gray(" pnpm build"));
|
|
2341
|
-
console.log(chalk4.gray("
|
|
2376
|
+
console.log(chalk4.gray(" onex upload"));
|
|
2342
2377
|
console.log();
|
|
2343
2378
|
console.log(chalk4.white("2. Verify AWS credentials are set:"));
|
|
2344
2379
|
console.log(
|
|
@@ -2354,7 +2389,11 @@ function showDownloadFailureHelp(themeId, bucket) {
|
|
|
2354
2389
|
);
|
|
2355
2390
|
console.log();
|
|
2356
2391
|
console.log(chalk4.white("4. Verify theme exists in S3:"));
|
|
2357
|
-
console.log(
|
|
2392
|
+
console.log(
|
|
2393
|
+
chalk4.gray(
|
|
2394
|
+
` aws s3 ls s3://${bucket}/themes/${themeId}/`
|
|
2395
|
+
)
|
|
2396
|
+
);
|
|
2358
2397
|
console.log();
|
|
2359
2398
|
}
|
|
2360
2399
|
async function downloadCommand(options) {
|
|
@@ -2382,55 +2421,44 @@ async function downloadCommand(options) {
|
|
|
2382
2421
|
spinner.succeed(
|
|
2383
2422
|
`Resolved latest version: ${chalk4.cyan(resolvedVersion)}`
|
|
2384
2423
|
);
|
|
2385
|
-
spinner.start("Downloading manifest...");
|
|
2386
2424
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2425
|
+
spinner.start(
|
|
2426
|
+
`Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
|
|
2427
|
+
);
|
|
2428
|
+
const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
|
|
2429
|
+
const response = await s3Client.send(
|
|
2430
|
+
new GetObjectCommand({
|
|
2431
|
+
Bucket: bucket,
|
|
2432
|
+
Key: s3Key
|
|
2433
|
+
})
|
|
2392
2434
|
);
|
|
2393
|
-
|
|
2394
|
-
|
|
2435
|
+
const zipBuffer = await streamToBuffer(response.Body);
|
|
2436
|
+
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
2437
|
+
spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
|
|
2438
|
+
spinner.start("Extracting bundle...");
|
|
2395
2439
|
await fs.remove(outputDir);
|
|
2396
2440
|
await fs.ensureDir(outputDir);
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
const
|
|
2400
|
-
|
|
2401
|
-
const
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2404
|
-
const batch = files.slice(i, i + batchSize);
|
|
2405
|
-
await Promise.all(
|
|
2406
|
-
batch.map(async (file) => {
|
|
2407
|
-
await downloadFile(
|
|
2408
|
-
s3Client,
|
|
2409
|
-
bucket,
|
|
2410
|
-
themeId,
|
|
2411
|
-
resolvedVersion,
|
|
2412
|
-
file,
|
|
2413
|
-
outputDir
|
|
2414
|
-
);
|
|
2415
|
-
downloaded++;
|
|
2416
|
-
spinner.text = `Downloading... ${downloaded}/${total} files (${Math.round(downloaded / total * 100)}%)`;
|
|
2417
|
-
})
|
|
2418
|
-
);
|
|
2419
|
-
}
|
|
2420
|
-
spinner.succeed(`Downloaded ${total} files`);
|
|
2421
|
-
await createCompatibilityFiles(outputDir);
|
|
2441
|
+
const zip = new AdmZip(zipBuffer);
|
|
2442
|
+
zip.extractAllTo(outputDir, true);
|
|
2443
|
+
const entries = zip.getEntries().filter((e) => !e.isDirectory);
|
|
2444
|
+
spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
|
|
2445
|
+
const manifestPath = path.join(outputDir, "manifest.json");
|
|
2446
|
+
const manifest = await fs.readJson(manifestPath);
|
|
2447
|
+
await createCompatibilityFiles(outputDir, manifest);
|
|
2422
2448
|
console.log();
|
|
2423
|
-
logger.success(chalk4.green.bold("
|
|
2449
|
+
logger.success(chalk4.green.bold("Theme downloaded successfully!"));
|
|
2424
2450
|
console.log();
|
|
2425
2451
|
console.log(
|
|
2426
2452
|
chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
|
|
2427
2453
|
);
|
|
2428
2454
|
console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
|
|
2429
2455
|
console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
|
|
2430
|
-
console.log(chalk4.cyan(" Files: ") + chalk4.white(
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2456
|
+
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
2457
|
+
if (manifest.counts) {
|
|
2458
|
+
console.log(
|
|
2459
|
+
chalk4.cyan(" Sections:") + chalk4.white(` ${manifest.counts.sections}`)
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2434
2462
|
console.log();
|
|
2435
2463
|
} catch (error) {
|
|
2436
2464
|
spinner.fail(chalk4.red("Download failed"));
|
|
@@ -2441,8 +2469,215 @@ async function downloadCommand(options) {
|
|
|
2441
2469
|
process.exit(1);
|
|
2442
2470
|
}
|
|
2443
2471
|
}
|
|
2472
|
+
function getS3Client3() {
|
|
2473
|
+
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
2474
|
+
if (adapterMode === "vps") {
|
|
2475
|
+
const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
|
|
2476
|
+
const secure = process.env.MINIO_SECURE === "true";
|
|
2477
|
+
const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
|
|
2478
|
+
return new S3Client({
|
|
2479
|
+
endpoint: endpointUrl,
|
|
2480
|
+
region: "us-east-1",
|
|
2481
|
+
credentials: {
|
|
2482
|
+
accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
|
|
2483
|
+
secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
|
|
2484
|
+
},
|
|
2485
|
+
forcePathStyle: true
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
if (adapterMode === "local") {
|
|
2489
|
+
return new S3Client({
|
|
2490
|
+
endpoint: "http://localhost:4569",
|
|
2491
|
+
region: "ap-southeast-1",
|
|
2492
|
+
credentials: {
|
|
2493
|
+
accessKeyId: "S3RVER",
|
|
2494
|
+
secretAccessKey: "S3RVER"
|
|
2495
|
+
},
|
|
2496
|
+
forcePathStyle: true
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
return new S3Client({
|
|
2500
|
+
region: process.env.AWS_REGION || "ap-southeast-1"
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
function getBucketName3(env) {
|
|
2504
|
+
if (process.env.BUCKET_NAME) {
|
|
2505
|
+
return process.env.BUCKET_NAME;
|
|
2506
|
+
}
|
|
2507
|
+
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
2508
|
+
return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
|
|
2509
|
+
}
|
|
2510
|
+
async function streamToString2(stream) {
|
|
2511
|
+
const chunks = [];
|
|
2512
|
+
try {
|
|
2513
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
2514
|
+
const chunk = temp.value;
|
|
2515
|
+
chunks.push(Buffer.from(chunk));
|
|
2516
|
+
}
|
|
2517
|
+
} catch (temp) {
|
|
2518
|
+
error = [temp];
|
|
2519
|
+
} finally {
|
|
2520
|
+
try {
|
|
2521
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
2522
|
+
} finally {
|
|
2523
|
+
if (error)
|
|
2524
|
+
throw error[0];
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
2528
|
+
}
|
|
2529
|
+
async function streamToBuffer2(stream) {
|
|
2530
|
+
const chunks = [];
|
|
2531
|
+
try {
|
|
2532
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
2533
|
+
const chunk = temp.value;
|
|
2534
|
+
chunks.push(Buffer.from(chunk));
|
|
2535
|
+
}
|
|
2536
|
+
} catch (temp) {
|
|
2537
|
+
error = [temp];
|
|
2538
|
+
} finally {
|
|
2539
|
+
try {
|
|
2540
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
2541
|
+
} finally {
|
|
2542
|
+
if (error)
|
|
2543
|
+
throw error[0];
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return Buffer.concat(chunks);
|
|
2547
|
+
}
|
|
2548
|
+
async function resolveLatestVersion2(s3Client, bucket, themeId) {
|
|
2549
|
+
try {
|
|
2550
|
+
const response = await s3Client.send(
|
|
2551
|
+
new GetObjectCommand({
|
|
2552
|
+
Bucket: bucket,
|
|
2553
|
+
Key: `themes/${themeId}/latest.json`
|
|
2554
|
+
})
|
|
2555
|
+
);
|
|
2556
|
+
const body = await streamToString2(response.Body);
|
|
2557
|
+
return JSON.parse(body).version;
|
|
2558
|
+
} catch (error) {
|
|
2559
|
+
throw new Error(
|
|
2560
|
+
`Failed to resolve latest version for theme "${themeId}": ${error.message}`
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
function runInstall(cwd) {
|
|
2565
|
+
return new Promise((resolve) => {
|
|
2566
|
+
const proc = spawn("pnpm", ["install"], {
|
|
2567
|
+
cwd,
|
|
2568
|
+
stdio: "inherit",
|
|
2569
|
+
shell: true
|
|
2570
|
+
});
|
|
2571
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
2572
|
+
proc.on("error", () => resolve(false));
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
async function cloneCommand(themeName, options) {
|
|
2576
|
+
logger.header("Clone Theme Source");
|
|
2577
|
+
const spinner = ora("Initializing clone...").start();
|
|
2578
|
+
try {
|
|
2579
|
+
const bucket = options.bucket || getBucketName3(options.environment);
|
|
2580
|
+
const outputDir = options.output || path.resolve(process.cwd(), themeName);
|
|
2581
|
+
const s3Client = getS3Client3();
|
|
2582
|
+
if (await fs.pathExists(outputDir)) {
|
|
2583
|
+
spinner.fail(
|
|
2584
|
+
chalk4.red(`Directory already exists: ${outputDir}`)
|
|
2585
|
+
);
|
|
2586
|
+
logger.info(
|
|
2587
|
+
chalk4.gray(
|
|
2588
|
+
"Use -o to specify a different output directory, or remove the existing directory."
|
|
2589
|
+
)
|
|
2590
|
+
);
|
|
2591
|
+
process.exit(1);
|
|
2592
|
+
}
|
|
2593
|
+
let version = options.version || "latest";
|
|
2594
|
+
if (version === "latest") {
|
|
2595
|
+
spinner.text = "Resolving latest version...";
|
|
2596
|
+
version = await resolveLatestVersion2(s3Client, bucket, themeName);
|
|
2597
|
+
spinner.succeed(`Resolved latest version: ${chalk4.cyan(version)}`);
|
|
2598
|
+
}
|
|
2599
|
+
spinner.start(
|
|
2600
|
+
`Downloading source.zip for ${themeName}@${version}...`
|
|
2601
|
+
);
|
|
2602
|
+
const s3Key = `themes/${themeName}/${version}/source.zip`;
|
|
2603
|
+
let zipBuffer;
|
|
2604
|
+
try {
|
|
2605
|
+
const response = await s3Client.send(
|
|
2606
|
+
new GetObjectCommand({
|
|
2607
|
+
Bucket: bucket,
|
|
2608
|
+
Key: s3Key
|
|
2609
|
+
})
|
|
2610
|
+
);
|
|
2611
|
+
zipBuffer = await streamToBuffer2(response.Body);
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
spinner.fail(chalk4.red(`Source not found: s3://${bucket}/${s3Key}`));
|
|
2614
|
+
console.log();
|
|
2615
|
+
console.log(
|
|
2616
|
+
chalk4.yellow("The theme source may not have been uploaded yet.")
|
|
2617
|
+
);
|
|
2618
|
+
console.log(
|
|
2619
|
+
chalk4.gray(
|
|
2620
|
+
`Upload source with: onex upload --theme ${themeName}`
|
|
2621
|
+
)
|
|
2622
|
+
);
|
|
2623
|
+
console.log();
|
|
2624
|
+
process.exit(1);
|
|
2625
|
+
}
|
|
2626
|
+
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
2627
|
+
spinner.succeed(`Downloaded source.zip (${sizeMB} MB)`);
|
|
2628
|
+
spinner.start(`Extracting to ${outputDir}...`);
|
|
2629
|
+
await fs.ensureDir(outputDir);
|
|
2630
|
+
const zip = new AdmZip(zipBuffer);
|
|
2631
|
+
zip.extractAllTo(outputDir, true);
|
|
2632
|
+
const entries = zip.getEntries().filter((e) => !e.isDirectory);
|
|
2633
|
+
spinner.succeed(`Extracted ${entries.length} files`);
|
|
2634
|
+
if (options.install !== false) {
|
|
2635
|
+
const hasPkgJson = await fs.pathExists(
|
|
2636
|
+
path.join(outputDir, "package.json")
|
|
2637
|
+
);
|
|
2638
|
+
if (hasPkgJson) {
|
|
2639
|
+
spinner.start("Installing dependencies...");
|
|
2640
|
+
const success = await runInstall(outputDir);
|
|
2641
|
+
if (success) {
|
|
2642
|
+
spinner.succeed("Dependencies installed");
|
|
2643
|
+
} else {
|
|
2644
|
+
spinner.warn(
|
|
2645
|
+
chalk4.yellow(
|
|
2646
|
+
"Failed to install dependencies \u2014 run 'pnpm install' manually"
|
|
2647
|
+
)
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
console.log();
|
|
2653
|
+
logger.success(chalk4.green.bold("Theme cloned successfully!"));
|
|
2654
|
+
console.log();
|
|
2655
|
+
console.log(
|
|
2656
|
+
chalk4.cyan(" Theme: ") + chalk4.white(`${themeName}@${version}`)
|
|
2657
|
+
);
|
|
2658
|
+
console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
|
|
2659
|
+
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
2660
|
+
console.log();
|
|
2661
|
+
console.log(chalk4.cyan("Next steps:"));
|
|
2662
|
+
console.log(
|
|
2663
|
+
chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`)
|
|
2664
|
+
);
|
|
2665
|
+
if (options.install === false) {
|
|
2666
|
+
console.log(chalk4.gray(" pnpm install"));
|
|
2667
|
+
}
|
|
2668
|
+
console.log(chalk4.gray(" onex build"));
|
|
2669
|
+
console.log();
|
|
2670
|
+
} catch (error) {
|
|
2671
|
+
spinner.fail(chalk4.red(`Clone failed: ${error.message}`));
|
|
2672
|
+
logger.error(error.stack || error.message);
|
|
2673
|
+
process.exit(1);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2444
2676
|
|
|
2445
2677
|
// src/cli.ts
|
|
2678
|
+
var projectRoot = getProjectRoot();
|
|
2679
|
+
dotenv.config({ path: path.join(projectRoot, ".env.local"), quiet: true });
|
|
2680
|
+
dotenv.config({ path: path.join(projectRoot, ".env"), quiet: true });
|
|
2446
2681
|
var program = new Command();
|
|
2447
2682
|
program.name("onex").description("CLI tool for OneX theme development").version("0.1.0");
|
|
2448
2683
|
program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
|
|
@@ -2474,7 +2709,7 @@ program.command("upload").description("Upload compiled theme to S3 bucket").opti
|
|
|
2474
2709
|
"-e, --environment <env>",
|
|
2475
2710
|
"Environment (staging|production)",
|
|
2476
2711
|
"staging"
|
|
2477
|
-
).option("--dry-run", "Show what would be uploaded without uploading").action(uploadCommand);
|
|
2712
|
+
).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);
|
|
2478
2713
|
program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
|
|
2479
2714
|
"-v, --version <version>",
|
|
2480
2715
|
"Theme version (default: latest)",
|
|
@@ -2484,6 +2719,15 @@ program.command("download").description("Download theme from S3 bucket").option(
|
|
|
2484
2719
|
"Environment (staging|production)",
|
|
2485
2720
|
"staging"
|
|
2486
2721
|
).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
2722
|
+
program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
|
|
2723
|
+
"-v, --version <version>",
|
|
2724
|
+
"Theme version (default: latest)",
|
|
2725
|
+
"latest"
|
|
2726
|
+
).option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
|
|
2727
|
+
"-e, --environment <env>",
|
|
2728
|
+
"Environment (staging|production)",
|
|
2729
|
+
"staging"
|
|
2730
|
+
).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
2487
2731
|
program.configureOutput({
|
|
2488
2732
|
writeErr: (str) => process.stderr.write(chalk4.red(str))
|
|
2489
2733
|
});
|