@onexapis/cli 1.1.52 → 1.1.53

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.mjs CHANGED
@@ -7,21 +7,21 @@ import fs8 from 'fs/promises';
7
7
  import crypto from 'crypto';
8
8
  import { glob } from 'glob';
9
9
  import { createRequire } from 'module';
10
+ import http from 'http';
11
+ import fs3 from 'fs';
12
+ import { WebSocketServer, WebSocket } from 'ws';
10
13
  import os from 'os';
11
14
  import dotenv from 'dotenv';
12
15
  import fs from 'fs-extra';
13
16
  import ejs from 'ejs';
14
17
  import { execSync, spawn } from 'child_process';
15
18
  import { Command } from 'commander';
16
- import fs3 from 'fs';
17
19
  import inquirer from 'inquirer';
18
20
  import archiver from 'archiver';
19
21
  import FormData from 'form-data';
20
22
  import fetch2 from 'node-fetch';
21
23
  import AdmZip from 'adm-zip';
22
24
  import chokidar from 'chokidar';
23
- import http from 'http';
24
- import { WebSocketServer, WebSocket } from 'ws';
25
25
  import semver from 'semver';
26
26
 
27
27
  var __defProp = Object.defineProperty;
@@ -1337,6 +1337,240 @@ export const {
1337
1337
  }
1338
1338
  });
1339
1339
 
1340
+ // src/utils/dev-server.ts
1341
+ var dev_server_exports = {};
1342
+ __export(dev_server_exports, {
1343
+ createDevServer: () => createDevServer
1344
+ });
1345
+ function createDevServer(options) {
1346
+ const clients = /* @__PURE__ */ new Set();
1347
+ const themeDataPath = path9.join(options.distDir, "theme-data.json");
1348
+ const server = http.createServer((req, res) => {
1349
+ res.setHeader("Access-Control-Allow-Origin", "*");
1350
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
1351
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1352
+ if (req.method === "OPTIONS") {
1353
+ res.writeHead(200);
1354
+ res.end();
1355
+ return;
1356
+ }
1357
+ const url = new URL(req.url || "/", `http://localhost:${options.port}`);
1358
+ const pathname = url.pathname;
1359
+ if (pathname === "/" || pathname === "/index.html") {
1360
+ res.writeHead(200, { "Content-Type": "text/html" });
1361
+ res.end(
1362
+ generatePreviewHTML(options.themeName, options.port, themeDataPath)
1363
+ );
1364
+ return;
1365
+ }
1366
+ if (pathname === "/preview-runtime.js") {
1367
+ serveFile(res, options.previewRuntimePath);
1368
+ return;
1369
+ }
1370
+ if (pathname.startsWith("/_assets/")) {
1371
+ const parts = pathname.replace(/^\/_assets\//, "").split("/");
1372
+ const assetSubpath = parts.slice(1).join("/");
1373
+ const assetPath = path9.join(options.themePath, "assets", assetSubpath);
1374
+ if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1375
+ res.writeHead(403);
1376
+ res.end("Forbidden");
1377
+ return;
1378
+ }
1379
+ serveFile(res, assetPath);
1380
+ return;
1381
+ }
1382
+ if (pathname.startsWith("/themes/")) {
1383
+ const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
1384
+ if (match) {
1385
+ const assetPath = path9.join(options.themePath, "assets", match[1]);
1386
+ if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1387
+ res.writeHead(403);
1388
+ res.end("Forbidden");
1389
+ return;
1390
+ }
1391
+ serveFile(res, assetPath);
1392
+ return;
1393
+ }
1394
+ }
1395
+ if (pathname.startsWith("/assets/")) {
1396
+ const subpath = pathname.replace(/^\/assets\//, "");
1397
+ const segments = subpath.split("/");
1398
+ const assetsBase = path9.join(options.themePath, "assets");
1399
+ let assetPath;
1400
+ if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
1401
+ assetPath = path9.join(assetsBase, segments.slice(1).join("/"));
1402
+ } else {
1403
+ assetPath = path9.join(assetsBase, subpath);
1404
+ }
1405
+ if (assetPath.startsWith(assetsBase) && fs3.existsSync(assetPath)) {
1406
+ serveFile(res, assetPath);
1407
+ return;
1408
+ }
1409
+ if (segments.length > 1) {
1410
+ const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
1411
+ if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
1412
+ serveFile(res, fallbackPath);
1413
+ return;
1414
+ }
1415
+ }
1416
+ }
1417
+ const filePath = path9.join(options.distDir, pathname);
1418
+ if (!filePath.startsWith(options.distDir)) {
1419
+ res.writeHead(403);
1420
+ res.end("Forbidden");
1421
+ return;
1422
+ }
1423
+ if (fs3.existsSync(filePath) && fs3.statSync(filePath).isFile()) {
1424
+ serveFile(res, filePath);
1425
+ } else {
1426
+ res.writeHead(200, { "Content-Type": "text/html" });
1427
+ res.end(
1428
+ generatePreviewHTML(options.themeName, options.port, themeDataPath)
1429
+ );
1430
+ }
1431
+ });
1432
+ const wss = new WebSocketServer({ server });
1433
+ wss.on("connection", (ws) => {
1434
+ clients.add(ws);
1435
+ ws.on("close", () => clients.delete(ws));
1436
+ });
1437
+ server.listen(options.port);
1438
+ return {
1439
+ broadcast(message) {
1440
+ const data = JSON.stringify(message);
1441
+ for (const client of clients) {
1442
+ if (client.readyState === WebSocket.OPEN) {
1443
+ client.send(data);
1444
+ }
1445
+ }
1446
+ },
1447
+ close() {
1448
+ wss.close();
1449
+ server.close();
1450
+ }
1451
+ };
1452
+ }
1453
+ function serveFile(res, filePath) {
1454
+ try {
1455
+ if (!fs3.existsSync(filePath)) {
1456
+ res.writeHead(404);
1457
+ res.end("Not Found");
1458
+ return;
1459
+ }
1460
+ const ext = path9.extname(filePath);
1461
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
1462
+ const content = fs3.readFileSync(filePath);
1463
+ res.writeHead(200, { "Content-Type": contentType });
1464
+ res.end(content);
1465
+ } catch {
1466
+ res.writeHead(500);
1467
+ res.end("Internal Server Error");
1468
+ }
1469
+ }
1470
+ function generatePreviewHTML(themeName, port, themeDataPath) {
1471
+ let fontLinks = "";
1472
+ let fontVarsCSS = "";
1473
+ if (themeDataPath) {
1474
+ try {
1475
+ const themeData = JSON.parse(fs3.readFileSync(themeDataPath, "utf-8"));
1476
+ const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
1477
+ if (typography) {
1478
+ const fontFamilies = /* @__PURE__ */ new Set();
1479
+ for (const value of Object.values(typography)) {
1480
+ const primary = value.split(",")[0].trim();
1481
+ if (primary && ![
1482
+ "serif",
1483
+ "sans-serif",
1484
+ "monospace",
1485
+ "system-ui",
1486
+ "Georgia",
1487
+ "Inter",
1488
+ "Consolas"
1489
+ ].includes(primary)) {
1490
+ fontFamilies.add(primary);
1491
+ }
1492
+ }
1493
+ if (fontFamilies.size > 0) {
1494
+ const families = Array.from(fontFamilies).map(
1495
+ (f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
1496
+ ).join("&");
1497
+ fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
1498
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1499
+ <link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
1500
+ }
1501
+ const heading = typography.heading || typography.body || "system-ui";
1502
+ const body = typography.body || "system-ui";
1503
+ fontVarsCSS = `
1504
+ :root {
1505
+ --font-heading: ${heading};
1506
+ --font-body: ${body};
1507
+ }
1508
+ body { font-family: var(--font-body); }
1509
+ h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
1510
+ }
1511
+ } catch {
1512
+ }
1513
+ }
1514
+ return `<!DOCTYPE html>
1515
+ <html lang="en">
1516
+ <head>
1517
+ <meta charset="UTF-8">
1518
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1519
+ <title>OneX Dev \u2014 ${themeName}</title>
1520
+ ${fontLinks}
1521
+ <!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
1522
+ <script src="https://cdn.tailwindcss.com"></script>
1523
+ <script>
1524
+ tailwind.config = {
1525
+ theme: {
1526
+ extend: {
1527
+ aspectRatio: {
1528
+ '2/1': '2 / 1',
1529
+ '3/2': '3 / 2',
1530
+ '4/3': '4 / 3',
1531
+ '3/4': '3 / 4',
1532
+ '5/4': '5 / 4',
1533
+ '16/9': '16 / 9',
1534
+ '21/9': '21 / 9',
1535
+ },
1536
+ fontFamily: {
1537
+ playfair: ['Playfair Display', 'Georgia', 'serif'],
1538
+ roboto: ['Roboto', 'system-ui', 'sans-serif'],
1539
+ },
1540
+ },
1541
+ },
1542
+ }
1543
+ </script>
1544
+ <style>
1545
+ #onex-preview-root { margin-top: 0; }${fontVarsCSS}
1546
+ </style>
1547
+ </head>
1548
+ <body>
1549
+ <div id="onex-preview-root"></div>
1550
+ <script type="module" src="/preview-runtime.js"></script>
1551
+ </body>
1552
+ </html>`;
1553
+ }
1554
+ var MIME_TYPES;
1555
+ var init_dev_server = __esm({
1556
+ "src/utils/dev-server.ts"() {
1557
+ MIME_TYPES = {
1558
+ ".js": "application/javascript",
1559
+ ".mjs": "application/javascript",
1560
+ ".css": "text/css",
1561
+ ".json": "application/json",
1562
+ ".html": "text/html",
1563
+ ".svg": "image/svg+xml",
1564
+ ".png": "image/png",
1565
+ ".jpg": "image/jpeg",
1566
+ ".jpeg": "image/jpeg",
1567
+ ".webp": "image/webp",
1568
+ ".gif": "image/gif",
1569
+ ".map": "application/json"
1570
+ };
1571
+ }
1572
+ });
1573
+
1340
1574
  // src/utils/file-helpers.ts
1341
1575
  init_logger();
1342
1576
  async function renderTemplate(templatePath, data) {
@@ -4012,231 +4246,7 @@ async function cloneCommand(themeName, options) {
4012
4246
  // src/commands/dev.ts
4013
4247
  init_logger();
4014
4248
  init_compile_theme();
4015
- var MIME_TYPES = {
4016
- ".js": "application/javascript",
4017
- ".mjs": "application/javascript",
4018
- ".css": "text/css",
4019
- ".json": "application/json",
4020
- ".html": "text/html",
4021
- ".svg": "image/svg+xml",
4022
- ".png": "image/png",
4023
- ".jpg": "image/jpeg",
4024
- ".jpeg": "image/jpeg",
4025
- ".webp": "image/webp",
4026
- ".gif": "image/gif",
4027
- ".map": "application/json"
4028
- };
4029
- function createDevServer(options) {
4030
- const clients = /* @__PURE__ */ new Set();
4031
- const themeDataPath = path9.join(options.distDir, "theme-data.json");
4032
- const server = http.createServer((req, res) => {
4033
- res.setHeader("Access-Control-Allow-Origin", "*");
4034
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
4035
- res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
4036
- if (req.method === "OPTIONS") {
4037
- res.writeHead(200);
4038
- res.end();
4039
- return;
4040
- }
4041
- const url = new URL(req.url || "/", `http://localhost:${options.port}`);
4042
- const pathname = url.pathname;
4043
- if (pathname === "/" || pathname === "/index.html") {
4044
- res.writeHead(200, { "Content-Type": "text/html" });
4045
- res.end(
4046
- generatePreviewHTML(options.themeName, options.port, themeDataPath)
4047
- );
4048
- return;
4049
- }
4050
- if (pathname === "/preview-runtime.js") {
4051
- serveFile(res, options.previewRuntimePath);
4052
- return;
4053
- }
4054
- if (pathname.startsWith("/_assets/")) {
4055
- const parts = pathname.replace(/^\/_assets\//, "").split("/");
4056
- const assetSubpath = parts.slice(1).join("/");
4057
- const assetPath = path9.join(options.themePath, "assets", assetSubpath);
4058
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
4059
- res.writeHead(403);
4060
- res.end("Forbidden");
4061
- return;
4062
- }
4063
- serveFile(res, assetPath);
4064
- return;
4065
- }
4066
- if (pathname.startsWith("/themes/")) {
4067
- const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
4068
- if (match) {
4069
- const assetPath = path9.join(options.themePath, "assets", match[1]);
4070
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
4071
- res.writeHead(403);
4072
- res.end("Forbidden");
4073
- return;
4074
- }
4075
- serveFile(res, assetPath);
4076
- return;
4077
- }
4078
- }
4079
- if (pathname.startsWith("/assets/")) {
4080
- const subpath = pathname.replace(/^\/assets\//, "");
4081
- const segments = subpath.split("/");
4082
- const assetsBase = path9.join(options.themePath, "assets");
4083
- let assetPath;
4084
- if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
4085
- assetPath = path9.join(assetsBase, segments.slice(1).join("/"));
4086
- } else {
4087
- assetPath = path9.join(assetsBase, subpath);
4088
- }
4089
- if (assetPath.startsWith(assetsBase) && fs3.existsSync(assetPath)) {
4090
- serveFile(res, assetPath);
4091
- return;
4092
- }
4093
- if (segments.length > 1) {
4094
- const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
4095
- if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
4096
- serveFile(res, fallbackPath);
4097
- return;
4098
- }
4099
- }
4100
- }
4101
- const filePath = path9.join(options.distDir, pathname);
4102
- if (!filePath.startsWith(options.distDir)) {
4103
- res.writeHead(403);
4104
- res.end("Forbidden");
4105
- return;
4106
- }
4107
- if (fs3.existsSync(filePath) && fs3.statSync(filePath).isFile()) {
4108
- serveFile(res, filePath);
4109
- } else {
4110
- res.writeHead(200, { "Content-Type": "text/html" });
4111
- res.end(
4112
- generatePreviewHTML(options.themeName, options.port, themeDataPath)
4113
- );
4114
- }
4115
- });
4116
- const wss = new WebSocketServer({ server });
4117
- wss.on("connection", (ws) => {
4118
- clients.add(ws);
4119
- ws.on("close", () => clients.delete(ws));
4120
- });
4121
- server.listen(options.port);
4122
- return {
4123
- broadcast(message) {
4124
- const data = JSON.stringify(message);
4125
- for (const client of clients) {
4126
- if (client.readyState === WebSocket.OPEN) {
4127
- client.send(data);
4128
- }
4129
- }
4130
- },
4131
- close() {
4132
- wss.close();
4133
- server.close();
4134
- }
4135
- };
4136
- }
4137
- function serveFile(res, filePath) {
4138
- try {
4139
- if (!fs3.existsSync(filePath)) {
4140
- res.writeHead(404);
4141
- res.end("Not Found");
4142
- return;
4143
- }
4144
- const ext = path9.extname(filePath);
4145
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
4146
- const content = fs3.readFileSync(filePath);
4147
- res.writeHead(200, { "Content-Type": contentType });
4148
- res.end(content);
4149
- } catch {
4150
- res.writeHead(500);
4151
- res.end("Internal Server Error");
4152
- }
4153
- }
4154
- function generatePreviewHTML(themeName, port, themeDataPath) {
4155
- let fontLinks = "";
4156
- let fontVarsCSS = "";
4157
- if (themeDataPath) {
4158
- try {
4159
- const themeData = JSON.parse(fs3.readFileSync(themeDataPath, "utf-8"));
4160
- const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
4161
- if (typography) {
4162
- const fontFamilies = /* @__PURE__ */ new Set();
4163
- for (const value of Object.values(typography)) {
4164
- const primary = value.split(",")[0].trim();
4165
- if (primary && ![
4166
- "serif",
4167
- "sans-serif",
4168
- "monospace",
4169
- "system-ui",
4170
- "Georgia",
4171
- "Inter",
4172
- "Consolas"
4173
- ].includes(primary)) {
4174
- fontFamilies.add(primary);
4175
- }
4176
- }
4177
- if (fontFamilies.size > 0) {
4178
- const families = Array.from(fontFamilies).map(
4179
- (f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
4180
- ).join("&");
4181
- fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
4182
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
4183
- <link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
4184
- }
4185
- const heading = typography.heading || typography.body || "system-ui";
4186
- const body = typography.body || "system-ui";
4187
- fontVarsCSS = `
4188
- :root {
4189
- --font-heading: ${heading};
4190
- --font-body: ${body};
4191
- }
4192
- body { font-family: var(--font-body); }
4193
- h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
4194
- }
4195
- } catch {
4196
- }
4197
- }
4198
- return `<!DOCTYPE html>
4199
- <html lang="en">
4200
- <head>
4201
- <meta charset="UTF-8">
4202
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
4203
- <title>OneX Dev \u2014 ${themeName}</title>
4204
- ${fontLinks}
4205
- <!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
4206
- <script src="https://cdn.tailwindcss.com"></script>
4207
- <script>
4208
- tailwind.config = {
4209
- theme: {
4210
- extend: {
4211
- aspectRatio: {
4212
- '2/1': '2 / 1',
4213
- '3/2': '3 / 2',
4214
- '4/3': '4 / 3',
4215
- '3/4': '3 / 4',
4216
- '5/4': '5 / 4',
4217
- '16/9': '16 / 9',
4218
- '21/9': '21 / 9',
4219
- },
4220
- fontFamily: {
4221
- playfair: ['Playfair Display', 'Georgia', 'serif'],
4222
- roboto: ['Roboto', 'system-ui', 'sans-serif'],
4223
- },
4224
- },
4225
- },
4226
- }
4227
- </script>
4228
- <style>
4229
- #onex-preview-root { margin-top: 0; }${fontVarsCSS}
4230
- </style>
4231
- </head>
4232
- <body>
4233
- <div id="onex-preview-root"></div>
4234
- <script type="module" src="/preview-runtime.js"></script>
4235
- </body>
4236
- </html>`;
4237
- }
4238
-
4239
- // src/commands/dev.ts
4249
+ init_dev_server();
4240
4250
  async function devCommand(options) {
4241
4251
  logger.header("OneX Dev Server");
4242
4252
  let themePath;
@@ -5071,6 +5081,121 @@ Or use the --bump flag:
5071
5081
  }
5072
5082
  logger.newLine();
5073
5083
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5084
+ await uploadThumbnail(apiUrl, themeId, themePath, distDir);
5085
+ }
5086
+ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5087
+ const THUMBNAIL_CANDIDATES = [
5088
+ { file: "thumbnail.png", mime: "image/png" },
5089
+ { file: "thumbnail.jpg", mime: "image/jpeg" },
5090
+ { file: "thumbnail.jpeg", mime: "image/jpeg" },
5091
+ { file: "thumbnail.webp", mime: "image/webp" }
5092
+ ];
5093
+ let imageBase64 = null;
5094
+ let mimeType = "image/png";
5095
+ for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5096
+ const candidate = path9.join(themePath, file);
5097
+ if (fs.existsSync(candidate)) {
5098
+ const buf = fs.readFileSync(candidate);
5099
+ imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
5100
+ mimeType = mime;
5101
+ logger.info(`Using local thumbnail: ${file}`);
5102
+ break;
5103
+ }
5104
+ }
5105
+ if (!imageBase64) {
5106
+ logger.startSpinner("Taking screenshot for thumbnail...");
5107
+ try {
5108
+ const buf = await screenshotHomePage(themePath, distDir);
5109
+ imageBase64 = `data:image/png;base64,${buf.toString("base64")}`;
5110
+ mimeType = "image/png";
5111
+ logger.stopSpinner(true, "Screenshot captured");
5112
+ } catch (err) {
5113
+ logger.stopSpinner(false, "Screenshot failed \u2014 skipping thumbnail");
5114
+ logger.info(
5115
+ "Tip: add thumbnail.png to your theme root to set a custom thumbnail"
5116
+ );
5117
+ return;
5118
+ }
5119
+ }
5120
+ logger.startSpinner("Uploading thumbnail...");
5121
+ try {
5122
+ const uploadRes = await authenticatedFetch(`${apiUrl}/media/images/upload`, {
5123
+ method: "POST",
5124
+ body: JSON.stringify({
5125
+ prefix: `themes/${themeId}`,
5126
+ image: imageBase64,
5127
+ name: "thumbnail.png"
5128
+ })
5129
+ });
5130
+ const uploadData = await uploadRes.json();
5131
+ const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
5132
+ if (!uploadRes.ok || !uploadBody.url) {
5133
+ throw new Error(uploadBody.error || "Upload failed");
5134
+ }
5135
+ const patchRes = await authenticatedFetch(
5136
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
5137
+ {
5138
+ method: "PATCH",
5139
+ body: JSON.stringify({ thumbnail_url: uploadBody.url })
5140
+ }
5141
+ );
5142
+ if (!patchRes.ok) {
5143
+ const patchData = await patchRes.json();
5144
+ const patchBody = patchData.statusCode ? patchData.body : patchData;
5145
+ throw new Error(patchBody.error || "Failed to set thumbnail");
5146
+ }
5147
+ logger.stopSpinner(true, "Thumbnail set");
5148
+ } catch (err) {
5149
+ logger.stopSpinner(false, "Thumbnail upload skipped");
5150
+ logger.info(
5151
+ `Theme published successfully. Thumbnail can be updated later.`
5152
+ );
5153
+ }
5154
+ }
5155
+ async function screenshotHomePage(themePath, distDir) {
5156
+ const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5157
+ const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5158
+ const previewRuntimePath = await compilePreviewRuntime2(themePath);
5159
+ const themeName = path9.basename(themePath);
5160
+ const port = await findFreePort(4500);
5161
+ const server = createDevServer2({
5162
+ port,
5163
+ distDir,
5164
+ previewRuntimePath,
5165
+ themeName,
5166
+ themePath
5167
+ });
5168
+ await new Promise((resolve) => setTimeout(resolve, 300));
5169
+ const puppeteer = await import('puppeteer');
5170
+ const browser = await puppeteer.default.launch({
5171
+ headless: true,
5172
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
5173
+ });
5174
+ try {
5175
+ const page = await browser.newPage();
5176
+ await page.setViewport({ width: 1440, height: 900 });
5177
+ await page.goto(`http://localhost:${port}`, {
5178
+ waitUntil: "networkidle0",
5179
+ timeout: 3e4
5180
+ });
5181
+ await page.waitForSelector("#onex-preview-root > *", { timeout: 15e3 });
5182
+ const screenshot = await page.screenshot({ fullPage: true, type: "png" });
5183
+ return Buffer.from(screenshot);
5184
+ } finally {
5185
+ await browser.close();
5186
+ server.close();
5187
+ }
5188
+ }
5189
+ async function findFreePort(start) {
5190
+ const net = await import('net');
5191
+ return new Promise((resolve) => {
5192
+ const srv = net.createServer();
5193
+ srv.listen(start, () => {
5194
+ const addr = srv.address();
5195
+ srv.close(() => resolve(addr.port));
5196
+ });
5197
+ srv.on("error", () => resolve(findFreePort(start + 1)));
5198
+ });
5074
5199
  }
5075
5200
  async function uploadVideoMultipart(apiUrl, themeId, video) {
5076
5201
  const fileName = path9.basename(video.originalPath);