@onexapis/cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -14,7 +14,8 @@ var archiver = require('archiver');
14
14
  var FormData = require('form-data');
15
15
  var fetch = require('node-fetch');
16
16
  var clientS3 = require('@aws-sdk/client-s3');
17
- var glob = require('glob');
17
+ var os = require('os');
18
+ var AdmZip = require('adm-zip');
18
19
 
19
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
21
 
@@ -28,6 +29,8 @@ var ejs__default = /*#__PURE__*/_interopDefault(ejs);
28
29
  var archiver__default = /*#__PURE__*/_interopDefault(archiver);
29
30
  var FormData__default = /*#__PURE__*/_interopDefault(FormData);
30
31
  var fetch__default = /*#__PURE__*/_interopDefault(fetch);
32
+ var os__default = /*#__PURE__*/_interopDefault(os);
33
+ var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
31
34
 
32
35
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
33
36
  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);
@@ -195,18 +198,19 @@ function getProjectRoot() {
195
198
  return process.cwd();
196
199
  }
197
200
  function getThemesDir() {
198
- try {
199
- return path__default.default.join(getProjectRoot(), "src/themes");
200
- } catch (e) {
201
- return path__default.default.join(getProjectRoot(), "themes");
201
+ const root = getProjectRoot();
202
+ const themesDir = path__default.default.join(root, "themes");
203
+ if (fs__default.default.existsSync(themesDir)) {
204
+ return themesDir;
202
205
  }
206
+ return path__default.default.join(root, "src/themes");
203
207
  }
204
208
  function getFeaturesDir() {
205
209
  return path__default.default.join(getProjectRoot(), "src/features");
206
210
  }
207
211
  function isOneXProject() {
208
212
  const root = getProjectRoot();
209
- return fs__default.default.existsSync(path__default.default.join(root, "src/themes")) && fs__default.default.existsSync(path__default.default.join(root, "src/lib/registry"));
213
+ return fs__default.default.existsSync(path__default.default.join(root, "themes")) || fs__default.default.existsSync(path__default.default.join(root, "src/themes"));
210
214
  }
211
215
  function ensureOneXProject() {
212
216
  if (!isOneXProject()) {
@@ -223,12 +227,12 @@ function listThemes() {
223
227
  }
224
228
  return fs__default.default.readdirSync(themesDir).filter((name) => {
225
229
  const themePath = path__default.default.join(themesDir, name);
226
- return fs__default.default.statSync(themePath).isDirectory() && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
230
+ return fs__default.default.statSync(themePath).isDirectory() && (fs__default.default.existsSync(path__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts")));
227
231
  });
228
232
  }
229
233
  function themeExists(themeName) {
230
234
  const themePath = path__default.default.join(getThemesDir(), themeName);
231
- return fs__default.default.existsSync(themePath) && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
235
+ return fs__default.default.existsSync(themePath) && (fs__default.default.existsSync(path__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts")));
232
236
  }
233
237
  function detectPackageManager() {
234
238
  const userAgent = process.env.npm_config_user_agent || "";
@@ -422,7 +426,9 @@ Add your theme-specific blocks here.
422
426
  logger.newLine();
423
427
  logger.section("Theme structure:");
424
428
  logger.log(" src/manifest.ts - Theme manifest and exports");
425
- logger.log(" src/config.ts - Design tokens (colors, typography, etc.)");
429
+ logger.log(
430
+ " src/config.ts - Design tokens (colors, typography, etc.)"
431
+ );
426
432
  logger.log(" src/layout.ts - Header and footer configuration");
427
433
  logger.log(" src/sections/ - Custom sections for your theme");
428
434
  logger.log(" src/blocks/ - Reusable blocks");
@@ -1425,8 +1431,16 @@ async function listThemesInfo() {
1425
1431
  }
1426
1432
  logger.log("");
1427
1433
  for (const theme of themes) {
1428
- const manifestPath = path__default.default.join(getThemesDir(), theme, "manifest.ts");
1429
- const manifestContent = fs__default.default.readFileSync(manifestPath, "utf-8");
1434
+ const themeDir = path__default.default.join(getThemesDir(), theme);
1435
+ const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
1436
+ let manifestContent = "";
1437
+ for (const candidate of candidates) {
1438
+ const candidatePath = path__default.default.join(themeDir, candidate);
1439
+ if (fs__default.default.existsSync(candidatePath)) {
1440
+ manifestContent = fs__default.default.readFileSync(candidatePath, "utf-8");
1441
+ break;
1442
+ }
1443
+ }
1430
1444
  const nameMatch = manifestContent.match(/name:\s*["'](.+)["']/);
1431
1445
  const versionMatch = manifestContent.match(/version:\s*["'](.+)["']/);
1432
1446
  const descMatch = manifestContent.match(/description:\s*["'](.+)["']/);
@@ -1968,13 +1982,11 @@ function getS3Client() {
1968
1982
  return new clientS3.S3Client({
1969
1983
  endpoint: endpointUrl,
1970
1984
  region: "us-east-1",
1971
- // MinIO doesn't use real regions
1972
1985
  credentials: {
1973
1986
  accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
1974
1987
  secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
1975
1988
  },
1976
1989
  forcePathStyle: true
1977
- // Required for MinIO
1978
1990
  });
1979
1991
  }
1980
1992
  if (adapterMode === "local") {
@@ -2044,36 +2056,40 @@ async function readManifest() {
2044
2056
  "No manifest.ts or package.json found. Are you in a theme directory?"
2045
2057
  );
2046
2058
  }
2047
- function getContentType(filename) {
2048
- const ext = path__default.default.extname(filename).toLowerCase();
2049
- const types = {
2050
- ".js": "application/javascript",
2051
- ".mjs": "application/javascript",
2052
- ".json": "application/json",
2053
- ".css": "text/css",
2054
- ".png": "image/png",
2055
- ".jpg": "image/jpeg",
2056
- ".jpeg": "image/jpeg",
2057
- ".svg": "image/svg+xml",
2058
- ".woff2": "font/woff2",
2059
- ".woff": "font/woff",
2060
- ".ttf": "font/ttf",
2061
- ".ts": "text/plain"
2062
- };
2063
- return types[ext] || "application/octet-stream";
2059
+ async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
2060
+ return new Promise((resolve, reject) => {
2061
+ const output = fs__default.default.createWriteStream(outputPath);
2062
+ const archive = archiver__default.default("zip", { zlib: { level: 6 } });
2063
+ output.on("close", () => resolve());
2064
+ archive.on("error", (err) => reject(err));
2065
+ archive.pipe(output);
2066
+ archive.glob("**/*", {
2067
+ cwd: sourceDir,
2068
+ dot: true,
2069
+ ignore: excludePatterns
2070
+ });
2071
+ archive.finalize();
2072
+ });
2064
2073
  }
2065
- async function uploadFile(s3Client, bucket, themeId, version, baseDir, file) {
2066
- const filePath = path__default.default.join(baseDir, file);
2067
- const content = await fs__default.default.readFile(filePath);
2068
- const s3Key = `themes/${themeId}/${version}/${file}`;
2069
- await s3Client.send(
2070
- new clientS3.PutObjectCommand({
2071
- Bucket: bucket,
2072
- Key: s3Key,
2073
- Body: content,
2074
- ContentType: getContentType(file)
2075
- })
2076
- );
2074
+ async function findSourceDir(themeId, explicitDir) {
2075
+ if (explicitDir) {
2076
+ if (await fs__default.default.pathExists(explicitDir)) return explicitDir;
2077
+ return null;
2078
+ }
2079
+ const searchPaths = [
2080
+ process.cwd(),
2081
+ path__default.default.resolve(process.cwd(), `../../themes/${themeId}`),
2082
+ path__default.default.resolve(process.cwd(), `../themes/${themeId}`)
2083
+ ];
2084
+ const markers = ["theme.config.ts", "bundle-entry.ts"];
2085
+ for (const dir of searchPaths) {
2086
+ for (const marker of markers) {
2087
+ if (await fs__default.default.pathExists(path__default.default.join(dir, marker))) {
2088
+ return dir;
2089
+ }
2090
+ }
2091
+ }
2092
+ return null;
2077
2093
  }
2078
2094
  async function updateLatestPointer(s3Client, bucket, themeId, version) {
2079
2095
  const latestData = {
@@ -2091,12 +2107,18 @@ async function updateLatestPointer(s3Client, bucket, themeId, version) {
2091
2107
  }
2092
2108
  async function uploadCommand(options) {
2093
2109
  logger.header("Upload Theme to S3");
2094
- ensureOneXProject();
2095
2110
  const spinner = ora__default.default("Preparing theme upload...").start();
2096
2111
  try {
2097
- const manifest = await readManifest();
2098
- const themeId = manifest.themeId;
2099
- const version = options.version || manifest.version || "1.0.0";
2112
+ let themeId;
2113
+ let version;
2114
+ if (options.theme) {
2115
+ themeId = options.theme;
2116
+ version = options.version || "1.0.0";
2117
+ } else {
2118
+ const manifest = await readManifest();
2119
+ themeId = manifest.themeId;
2120
+ version = options.version || manifest.version || "1.0.0";
2121
+ }
2100
2122
  spinner.text = `Found theme: ${themeId}@${version}`;
2101
2123
  const bucket = options.bucket || getBucketName(options.environment);
2102
2124
  const s3Client = getS3Client();
@@ -2117,67 +2139,113 @@ async function uploadCommand(options) {
2117
2139
  process.exit(1);
2118
2140
  }
2119
2141
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
2120
- spinner.start(`Scanning files...`);
2121
- const files = await glob.glob("**/*", {
2122
- cwd: compiledDir,
2123
- nodir: true,
2124
- dot: true
2125
- });
2126
- if (files.length === 0) {
2127
- spinner.fail(chalk4__default.default.red("No files found in compiled theme directory"));
2128
- process.exit(1);
2129
- }
2130
- spinner.succeed(`Found ${files.length} files to upload`);
2142
+ spinner.start("Creating bundle.zip...");
2143
+ const tmpDir = os__default.default.tmpdir();
2144
+ const bundleZipPath = path__default.default.join(tmpDir, `${themeId}-${version}-bundle.zip`);
2145
+ await createZipFromDir(compiledDir, bundleZipPath);
2146
+ const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
2147
+ const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
2148
+ spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
2131
2149
  if (options.dryRun) {
2132
- spinner.info(chalk4__default.default.yellow("Dry run mode - no files will be uploaded"));
2133
- console.log(chalk4__default.default.gray("\nFiles to upload:"));
2134
- const displayFiles = files.slice(0, 10);
2135
- displayFiles.forEach((file) => console.log(chalk4__default.default.gray(` - ${file}`)));
2136
- if (files.length > 10) {
2137
- console.log(chalk4__default.default.gray(` ... and ${files.length - 10} more files
2138
- `));
2139
- }
2140
- console.log(chalk4__default.default.cyan(`
2141
- S3 bucket: ${bucket}`));
2150
+ spinner.info(chalk4__default.default.yellow("Dry run mode \u2014 no files will be uploaded"));
2151
+ console.log();
2152
+ console.log(chalk4__default.default.gray(` bundle.zip: ${bundleSizeMB} MB`));
2142
2153
  console.log(
2143
- chalk4__default.default.cyan(`S3 path: s3://${bucket}/themes/${themeId}/${version}/
2144
- `)
2154
+ chalk4__default.default.cyan(
2155
+ ` S3 path: s3://${bucket}/themes/${themeId}/${version}/bundle.zip`
2156
+ )
2145
2157
  );
2158
+ if (!options.skipSource) {
2159
+ const sourceDir = await findSourceDir(themeId, options.sourceDir);
2160
+ if (sourceDir) {
2161
+ console.log(chalk4__default.default.gray(` source dir: ${sourceDir}`));
2162
+ console.log(
2163
+ chalk4__default.default.cyan(
2164
+ ` S3 path: s3://${bucket}/themes/${themeId}/${version}/source.zip`
2165
+ )
2166
+ );
2167
+ } else {
2168
+ console.log(
2169
+ chalk4__default.default.yellow(" source dir: not found (source.zip will be skipped)")
2170
+ );
2171
+ }
2172
+ }
2173
+ console.log();
2174
+ await fs__default.default.remove(bundleZipPath);
2146
2175
  return;
2147
2176
  }
2148
- spinner.start(`Uploading to S3: ${bucket}...`);
2149
- let uploaded = 0;
2150
- const total = files.length;
2151
- const batchSize = 10;
2152
- for (let i = 0; i < files.length; i += batchSize) {
2153
- const batch = files.slice(i, i + batchSize);
2154
- await Promise.all(
2155
- batch.map(async (file) => {
2156
- await uploadFile(
2157
- s3Client,
2158
- bucket,
2159
- themeId,
2160
- version,
2161
- compiledDir,
2162
- file
2163
- );
2164
- uploaded++;
2165
- spinner.text = `Uploading... ${uploaded}/${total} files (${Math.round(uploaded / total * 100)}%)`;
2166
- })
2167
- );
2177
+ spinner.start("Uploading bundle.zip to S3...");
2178
+ const bundleS3Key = `themes/${themeId}/${version}/bundle.zip`;
2179
+ await s3Client.send(
2180
+ new clientS3.PutObjectCommand({
2181
+ Bucket: bucket,
2182
+ Key: bundleS3Key,
2183
+ Body: bundleZipBuffer,
2184
+ ContentType: "application/zip"
2185
+ })
2186
+ );
2187
+ spinner.succeed(
2188
+ `Uploaded bundle.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
2189
+ );
2190
+ await fs__default.default.remove(bundleZipPath);
2191
+ let sourceUploaded = false;
2192
+ if (!options.skipSource) {
2193
+ spinner.start("Looking for source directory...");
2194
+ const sourceDir = await findSourceDir(themeId, options.sourceDir);
2195
+ if (sourceDir) {
2196
+ spinner.succeed(`Found source at: ${sourceDir}`);
2197
+ spinner.start("Creating source.zip...");
2198
+ const sourceZipPath = path__default.default.join(
2199
+ tmpDir,
2200
+ `${themeId}-${version}-source.zip`
2201
+ );
2202
+ await createZipFromDir(sourceDir, sourceZipPath, [
2203
+ "node_modules/**",
2204
+ "dist/**",
2205
+ ".git/**",
2206
+ "*.zip",
2207
+ ".next/**",
2208
+ ".turbo/**"
2209
+ ]);
2210
+ const sourceZipBuffer = await fs__default.default.readFile(sourceZipPath);
2211
+ const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
2212
+ spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
2213
+ spinner.start("Uploading source.zip to S3...");
2214
+ const sourceS3Key = `themes/${themeId}/${version}/source.zip`;
2215
+ await s3Client.send(
2216
+ new clientS3.PutObjectCommand({
2217
+ Bucket: bucket,
2218
+ Key: sourceS3Key,
2219
+ Body: sourceZipBuffer,
2220
+ ContentType: "application/zip"
2221
+ })
2222
+ );
2223
+ spinner.succeed(
2224
+ `Uploaded source.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
2225
+ );
2226
+ await fs__default.default.remove(sourceZipPath);
2227
+ sourceUploaded = true;
2228
+ } else {
2229
+ spinner.warn(
2230
+ chalk4__default.default.yellow("Source directory not found \u2014 skipping source.zip")
2231
+ );
2232
+ }
2168
2233
  }
2169
- spinner.succeed(`Uploaded ${total} files to S3`);
2170
2234
  spinner.start("Updating latest.json pointer...");
2171
2235
  await updateLatestPointer(s3Client, bucket, themeId, version);
2172
2236
  spinner.succeed("Updated latest.json pointer");
2173
2237
  console.log();
2174
- logger.success(chalk4__default.default.green.bold("\u2705 Theme uploaded successfully!"));
2238
+ logger.success(chalk4__default.default.green.bold("Theme uploaded successfully!"));
2175
2239
  console.log();
2176
2240
  console.log(
2177
2241
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version}`)
2178
2242
  );
2179
2243
  console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
2180
- console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
2244
+ console.log(
2245
+ chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(
2246
+ `bundle.zip${sourceUploaded ? " + source.zip" : ""}`
2247
+ )
2248
+ );
2181
2249
  console.log(
2182
2250
  chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
2183
2251
  );
@@ -2281,79 +2349,45 @@ async function resolveLatestVersion(s3Client, bucket, themeId) {
2281
2349
  );
2282
2350
  }
2283
2351
  }
2284
- async function downloadManifest(s3Client, bucket, themeId, version) {
2285
- try {
2286
- const response = await s3Client.send(
2287
- new clientS3.GetObjectCommand({
2288
- Bucket: bucket,
2289
- Key: `themes/${themeId}/${version}/manifest.json`
2290
- })
2291
- );
2292
- const body = await streamToString(response.Body);
2293
- return JSON.parse(body);
2294
- } catch (error) {
2295
- throw new Error(
2296
- `Failed to download manifest for ${themeId}@${version}: ${error.message}
2297
- The theme may not exist in S3 bucket "${bucket}".`
2298
- );
2299
- }
2300
- }
2301
- async function downloadFile(s3Client, bucket, themeId, version, file, outputDir) {
2302
- const s3Key = `themes/${themeId}/${version}/${file}`;
2303
- const localPath = path__default.default.join(outputDir, file);
2304
- await fs__default.default.ensureDir(path__default.default.dirname(localPath));
2305
- try {
2306
- const response = await s3Client.send(
2307
- new clientS3.GetObjectCommand({
2308
- Bucket: bucket,
2309
- Key: s3Key
2310
- })
2311
- );
2312
- const content = await streamToBuffer(response.Body);
2313
- await fs__default.default.writeFile(localPath, content);
2314
- } catch (error) {
2315
- throw new Error(`Failed to download ${file}: ${error.message}`);
2316
- }
2317
- }
2318
- function collectFilesToDownload(manifest) {
2319
- const files = ["manifest.json"];
2320
- if (manifest.output) {
2321
- if (manifest.output.entry) {
2322
- files.push(manifest.output.entry);
2323
- }
2324
- if (manifest.output.chunks) {
2325
- files.push(...manifest.output.chunks);
2326
- }
2327
- if (manifest.output.assets) {
2328
- files.push(...manifest.output.assets);
2352
+ async function createCompatibilityFiles(outputDir, manifest) {
2353
+ var _a;
2354
+ const entryFile = ((_a = manifest.output) == null ? void 0 : _a.entry) || "bundle-entry.js";
2355
+ if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
2356
+ const hashedPath = path__default.default.join(outputDir, entryFile);
2357
+ const stablePath = path__default.default.join(outputDir, "bundle-entry.js");
2358
+ if (await fs__default.default.pathExists(hashedPath)) {
2359
+ await fs__default.default.copy(hashedPath, stablePath);
2360
+ const mapPath = hashedPath + ".map";
2361
+ if (await fs__default.default.pathExists(mapPath)) {
2362
+ await fs__default.default.copy(mapPath, stablePath + ".map");
2363
+ }
2329
2364
  }
2330
2365
  }
2331
- return files;
2332
- }
2333
- async function createCompatibilityFiles(outputDir) {
2334
2366
  const sectionsRegistryPath = path__default.default.join(outputDir, "sections-registry.js");
2335
2367
  const content = `// Re-export all sections from bundle-entry
2336
2368
  // This file exists to maintain compatibility with the import path
2337
2369
  export * from './bundle-entry.js';
2338
2370
  `;
2339
2371
  await fs__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
2372
+ const pkgJsonPath = path__default.default.join(outputDir, "package.json");
2373
+ await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
2340
2374
  }
2341
2375
  function showDownloadFailureHelp(themeId, bucket) {
2342
2376
  console.log();
2343
- logger.error(chalk4__default.default.red.bold("\u274C Theme download failed"));
2377
+ logger.error(chalk4__default.default.red.bold("Theme download failed"));
2344
2378
  console.log();
2345
2379
  console.log(chalk4__default.default.yellow("Possible reasons:"));
2346
2380
  console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
2347
2381
  console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
2348
2382
  console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
2349
- console.log(chalk4__default.default.gray(" 4. Theme files are incomplete or corrupted"));
2383
+ console.log(chalk4__default.default.gray(" 4. bundle.zip is missing or corrupted"));
2350
2384
  console.log();
2351
2385
  console.log(chalk4__default.default.cyan.bold("To fix this:"));
2352
2386
  console.log();
2353
2387
  console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
2354
2388
  console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
2355
2389
  console.log(chalk4__default.default.gray(" pnpm build"));
2356
- console.log(chalk4__default.default.gray(" pnpm upload"));
2390
+ console.log(chalk4__default.default.gray(" onex upload"));
2357
2391
  console.log();
2358
2392
  console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
2359
2393
  console.log(
@@ -2369,7 +2403,11 @@ function showDownloadFailureHelp(themeId, bucket) {
2369
2403
  );
2370
2404
  console.log();
2371
2405
  console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
2372
- console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2406
+ console.log(
2407
+ chalk4__default.default.gray(
2408
+ ` aws s3 ls s3://${bucket}/themes/${themeId}/`
2409
+ )
2410
+ );
2373
2411
  console.log();
2374
2412
  }
2375
2413
  async function downloadCommand(options) {
@@ -2397,55 +2435,44 @@ async function downloadCommand(options) {
2397
2435
  spinner.succeed(
2398
2436
  `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
2399
2437
  );
2400
- spinner.start("Downloading manifest...");
2401
2438
  }
2402
- const manifest = await downloadManifest(
2403
- s3Client,
2404
- bucket,
2405
- themeId,
2406
- resolvedVersion
2439
+ spinner.start(
2440
+ `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
2441
+ );
2442
+ const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
2443
+ const response = await s3Client.send(
2444
+ new clientS3.GetObjectCommand({
2445
+ Bucket: bucket,
2446
+ Key: s3Key
2447
+ })
2407
2448
  );
2408
- spinner.succeed("Downloaded manifest");
2409
- spinner.start("Preparing output directory...");
2449
+ const zipBuffer = await streamToBuffer(response.Body);
2450
+ const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
2451
+ spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
2452
+ spinner.start("Extracting bundle...");
2410
2453
  await fs__default.default.remove(outputDir);
2411
2454
  await fs__default.default.ensureDir(outputDir);
2412
- spinner.succeed("Output directory ready");
2413
- spinner.start("Downloading theme files...");
2414
- const files = collectFilesToDownload(manifest);
2415
- let downloaded = 0;
2416
- const total = files.length;
2417
- const batchSize = 10;
2418
- for (let i = 0; i < files.length; i += batchSize) {
2419
- const batch = files.slice(i, i + batchSize);
2420
- await Promise.all(
2421
- batch.map(async (file) => {
2422
- await downloadFile(
2423
- s3Client,
2424
- bucket,
2425
- themeId,
2426
- resolvedVersion,
2427
- file,
2428
- outputDir
2429
- );
2430
- downloaded++;
2431
- spinner.text = `Downloading... ${downloaded}/${total} files (${Math.round(downloaded / total * 100)}%)`;
2432
- })
2433
- );
2434
- }
2435
- spinner.succeed(`Downloaded ${total} files`);
2436
- await createCompatibilityFiles(outputDir);
2455
+ const zip = new AdmZip__default.default(zipBuffer);
2456
+ zip.extractAllTo(outputDir, true);
2457
+ const entries = zip.getEntries().filter((e) => !e.isDirectory);
2458
+ spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
2459
+ const manifestPath = path__default.default.join(outputDir, "manifest.json");
2460
+ const manifest = await fs__default.default.readJson(manifestPath);
2461
+ await createCompatibilityFiles(outputDir, manifest);
2437
2462
  console.log();
2438
- logger.success(chalk4__default.default.green.bold("\u2705 Theme downloaded successfully!"));
2463
+ logger.success(chalk4__default.default.green.bold("Theme downloaded successfully!"));
2439
2464
  console.log();
2440
2465
  console.log(
2441
2466
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
2442
2467
  );
2443
2468
  console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
2444
2469
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
2445
- console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
2446
- console.log(
2447
- chalk4__default.default.cyan(" Sections:") + chalk4__default.default.white(` ${manifest.counts.sections}`)
2448
- );
2470
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
2471
+ if (manifest.counts) {
2472
+ console.log(
2473
+ chalk4__default.default.cyan(" Sections:") + chalk4__default.default.white(` ${manifest.counts.sections}`)
2474
+ );
2475
+ }
2449
2476
  console.log();
2450
2477
  } catch (error) {
2451
2478
  spinner.fail(chalk4__default.default.red("Download failed"));
@@ -2456,6 +2483,210 @@ async function downloadCommand(options) {
2456
2483
  process.exit(1);
2457
2484
  }
2458
2485
  }
2486
+ function getS3Client3() {
2487
+ const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2488
+ if (adapterMode === "vps") {
2489
+ const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2490
+ const secure = process.env.MINIO_SECURE === "true";
2491
+ const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2492
+ return new clientS3.S3Client({
2493
+ endpoint: endpointUrl,
2494
+ region: "us-east-1",
2495
+ credentials: {
2496
+ accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
2497
+ secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
2498
+ },
2499
+ forcePathStyle: true
2500
+ });
2501
+ }
2502
+ if (adapterMode === "local") {
2503
+ return new clientS3.S3Client({
2504
+ endpoint: "http://localhost:4569",
2505
+ region: "ap-southeast-1",
2506
+ credentials: {
2507
+ accessKeyId: "S3RVER",
2508
+ secretAccessKey: "S3RVER"
2509
+ },
2510
+ forcePathStyle: true
2511
+ });
2512
+ }
2513
+ return new clientS3.S3Client({
2514
+ region: process.env.AWS_REGION || "ap-southeast-1"
2515
+ });
2516
+ }
2517
+ function getBucketName3(env) {
2518
+ if (process.env.BUCKET_NAME) {
2519
+ return process.env.BUCKET_NAME;
2520
+ }
2521
+ const environment = env || process.env.ENVIRONMENT || "staging";
2522
+ return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
2523
+ }
2524
+ async function streamToString2(stream) {
2525
+ const chunks = [];
2526
+ try {
2527
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2528
+ const chunk = temp.value;
2529
+ chunks.push(Buffer.from(chunk));
2530
+ }
2531
+ } catch (temp) {
2532
+ error = [temp];
2533
+ } finally {
2534
+ try {
2535
+ more && (temp = iter.return) && await temp.call(iter);
2536
+ } finally {
2537
+ if (error)
2538
+ throw error[0];
2539
+ }
2540
+ }
2541
+ return Buffer.concat(chunks).toString("utf-8");
2542
+ }
2543
+ async function streamToBuffer2(stream) {
2544
+ const chunks = [];
2545
+ try {
2546
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2547
+ const chunk = temp.value;
2548
+ chunks.push(Buffer.from(chunk));
2549
+ }
2550
+ } catch (temp) {
2551
+ error = [temp];
2552
+ } finally {
2553
+ try {
2554
+ more && (temp = iter.return) && await temp.call(iter);
2555
+ } finally {
2556
+ if (error)
2557
+ throw error[0];
2558
+ }
2559
+ }
2560
+ return Buffer.concat(chunks);
2561
+ }
2562
+ async function resolveLatestVersion2(s3Client, bucket, themeId) {
2563
+ try {
2564
+ const response = await s3Client.send(
2565
+ new clientS3.GetObjectCommand({
2566
+ Bucket: bucket,
2567
+ Key: `themes/${themeId}/latest.json`
2568
+ })
2569
+ );
2570
+ const body = await streamToString2(response.Body);
2571
+ return JSON.parse(body).version;
2572
+ } catch (error) {
2573
+ throw new Error(
2574
+ `Failed to resolve latest version for theme "${themeId}": ${error.message}`
2575
+ );
2576
+ }
2577
+ }
2578
+ function runInstall(cwd) {
2579
+ return new Promise((resolve) => {
2580
+ const proc = child_process.spawn("pnpm", ["install"], {
2581
+ cwd,
2582
+ stdio: "inherit",
2583
+ shell: true
2584
+ });
2585
+ proc.on("close", (code) => resolve(code === 0));
2586
+ proc.on("error", () => resolve(false));
2587
+ });
2588
+ }
2589
+ async function cloneCommand(themeName, options) {
2590
+ logger.header("Clone Theme Source");
2591
+ const spinner = ora__default.default("Initializing clone...").start();
2592
+ try {
2593
+ const bucket = options.bucket || getBucketName3(options.environment);
2594
+ const outputDir = options.output || path__default.default.resolve(process.cwd(), themeName);
2595
+ const s3Client = getS3Client3();
2596
+ if (await fs__default.default.pathExists(outputDir)) {
2597
+ spinner.fail(
2598
+ chalk4__default.default.red(`Directory already exists: ${outputDir}`)
2599
+ );
2600
+ logger.info(
2601
+ chalk4__default.default.gray(
2602
+ "Use -o to specify a different output directory, or remove the existing directory."
2603
+ )
2604
+ );
2605
+ process.exit(1);
2606
+ }
2607
+ let version = options.version || "latest";
2608
+ if (version === "latest") {
2609
+ spinner.text = "Resolving latest version...";
2610
+ version = await resolveLatestVersion2(s3Client, bucket, themeName);
2611
+ spinner.succeed(`Resolved latest version: ${chalk4__default.default.cyan(version)}`);
2612
+ }
2613
+ spinner.start(
2614
+ `Downloading source.zip for ${themeName}@${version}...`
2615
+ );
2616
+ const s3Key = `themes/${themeName}/${version}/source.zip`;
2617
+ let zipBuffer;
2618
+ try {
2619
+ const response = await s3Client.send(
2620
+ new clientS3.GetObjectCommand({
2621
+ Bucket: bucket,
2622
+ Key: s3Key
2623
+ })
2624
+ );
2625
+ zipBuffer = await streamToBuffer2(response.Body);
2626
+ } catch (error) {
2627
+ spinner.fail(chalk4__default.default.red(`Source not found: s3://${bucket}/${s3Key}`));
2628
+ console.log();
2629
+ console.log(
2630
+ chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
2631
+ );
2632
+ console.log(
2633
+ chalk4__default.default.gray(
2634
+ `Upload source with: onex upload --theme ${themeName}`
2635
+ )
2636
+ );
2637
+ console.log();
2638
+ process.exit(1);
2639
+ }
2640
+ const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
2641
+ spinner.succeed(`Downloaded source.zip (${sizeMB} MB)`);
2642
+ spinner.start(`Extracting to ${outputDir}...`);
2643
+ await fs__default.default.ensureDir(outputDir);
2644
+ const zip = new AdmZip__default.default(zipBuffer);
2645
+ zip.extractAllTo(outputDir, true);
2646
+ const entries = zip.getEntries().filter((e) => !e.isDirectory);
2647
+ spinner.succeed(`Extracted ${entries.length} files`);
2648
+ if (options.install !== false) {
2649
+ const hasPkgJson = await fs__default.default.pathExists(
2650
+ path__default.default.join(outputDir, "package.json")
2651
+ );
2652
+ if (hasPkgJson) {
2653
+ spinner.start("Installing dependencies...");
2654
+ const success = await runInstall(outputDir);
2655
+ if (success) {
2656
+ spinner.succeed("Dependencies installed");
2657
+ } else {
2658
+ spinner.warn(
2659
+ chalk4__default.default.yellow(
2660
+ "Failed to install dependencies \u2014 run 'pnpm install' manually"
2661
+ )
2662
+ );
2663
+ }
2664
+ }
2665
+ }
2666
+ console.log();
2667
+ logger.success(chalk4__default.default.green.bold("Theme cloned successfully!"));
2668
+ console.log();
2669
+ console.log(
2670
+ chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeName}@${version}`)
2671
+ );
2672
+ console.log(chalk4__default.default.cyan(" Location: ") + chalk4__default.default.white(outputDir));
2673
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
2674
+ console.log();
2675
+ console.log(chalk4__default.default.cyan("Next steps:"));
2676
+ console.log(
2677
+ chalk4__default.default.gray(` cd ${path__default.default.relative(process.cwd(), outputDir)}`)
2678
+ );
2679
+ if (options.install === false) {
2680
+ console.log(chalk4__default.default.gray(" pnpm install"));
2681
+ }
2682
+ console.log(chalk4__default.default.gray(" onex build"));
2683
+ console.log();
2684
+ } catch (error) {
2685
+ spinner.fail(chalk4__default.default.red(`Clone failed: ${error.message}`));
2686
+ logger.error(error.stack || error.message);
2687
+ process.exit(1);
2688
+ }
2689
+ }
2459
2690
 
2460
2691
  // src/cli.ts
2461
2692
  var program = new commander.Command();
@@ -2489,7 +2720,7 @@ program.command("upload").description("Upload compiled theme to S3 bucket").opti
2489
2720
  "-e, --environment <env>",
2490
2721
  "Environment (staging|production)",
2491
2722
  "staging"
2492
- ).option("--dry-run", "Show what would be uploaded without uploading").action(uploadCommand);
2723
+ ).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);
2493
2724
  program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
2494
2725
  "-v, --version <version>",
2495
2726
  "Theme version (default: latest)",
@@ -2499,6 +2730,15 @@ program.command("download").description("Download theme from S3 bucket").option(
2499
2730
  "Environment (staging|production)",
2500
2731
  "staging"
2501
2732
  ).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
2733
+ program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
2734
+ "-v, --version <version>",
2735
+ "Theme version (default: latest)",
2736
+ "latest"
2737
+ ).option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
2738
+ "-e, --environment <env>",
2739
+ "Environment (staging|production)",
2740
+ "staging"
2741
+ ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
2502
2742
  program.configureOutput({
2503
2743
  writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))
2504
2744
  });