@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.js +355 -230
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +353 -228
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -9,21 +9,21 @@ var fs8 = require('fs/promises');
|
|
|
9
9
|
var crypto = require('crypto');
|
|
10
10
|
var glob = require('glob');
|
|
11
11
|
var module$1 = require('module');
|
|
12
|
+
var http = require('http');
|
|
13
|
+
var fs3 = require('fs');
|
|
14
|
+
var ws = require('ws');
|
|
12
15
|
var os = require('os');
|
|
13
16
|
var dotenv = require('dotenv');
|
|
14
17
|
var fs = require('fs-extra');
|
|
15
18
|
var ejs = require('ejs');
|
|
16
19
|
var child_process = require('child_process');
|
|
17
20
|
var commander = require('commander');
|
|
18
|
-
var fs3 = require('fs');
|
|
19
21
|
var inquirer = require('inquirer');
|
|
20
22
|
var archiver = require('archiver');
|
|
21
23
|
var FormData = require('form-data');
|
|
22
24
|
var fetch2 = require('node-fetch');
|
|
23
25
|
var AdmZip = require('adm-zip');
|
|
24
26
|
var chokidar = require('chokidar');
|
|
25
|
-
var http = require('http');
|
|
26
|
-
var ws = require('ws');
|
|
27
27
|
var semver = require('semver');
|
|
28
28
|
|
|
29
29
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -53,18 +53,18 @@ var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
|
|
|
53
53
|
var path9__default = /*#__PURE__*/_interopDefault(path9);
|
|
54
54
|
var fs8__default = /*#__PURE__*/_interopDefault(fs8);
|
|
55
55
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
56
|
+
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
57
|
+
var fs3__default = /*#__PURE__*/_interopDefault(fs3);
|
|
56
58
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
57
59
|
var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
|
|
58
60
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
59
61
|
var ejs__default = /*#__PURE__*/_interopDefault(ejs);
|
|
60
|
-
var fs3__default = /*#__PURE__*/_interopDefault(fs3);
|
|
61
62
|
var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
|
|
62
63
|
var archiver__default = /*#__PURE__*/_interopDefault(archiver);
|
|
63
64
|
var FormData__default = /*#__PURE__*/_interopDefault(FormData);
|
|
64
65
|
var fetch2__default = /*#__PURE__*/_interopDefault(fetch2);
|
|
65
66
|
var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
|
|
66
67
|
var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
|
|
67
|
-
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
68
68
|
var semver__default = /*#__PURE__*/_interopDefault(semver);
|
|
69
69
|
|
|
70
70
|
var __defProp = Object.defineProperty;
|
|
@@ -1380,6 +1380,240 @@ export const {
|
|
|
1380
1380
|
}
|
|
1381
1381
|
});
|
|
1382
1382
|
|
|
1383
|
+
// src/utils/dev-server.ts
|
|
1384
|
+
var dev_server_exports = {};
|
|
1385
|
+
__export(dev_server_exports, {
|
|
1386
|
+
createDevServer: () => createDevServer
|
|
1387
|
+
});
|
|
1388
|
+
function createDevServer(options) {
|
|
1389
|
+
const clients = /* @__PURE__ */ new Set();
|
|
1390
|
+
const themeDataPath = path9__default.default.join(options.distDir, "theme-data.json");
|
|
1391
|
+
const server = http__default.default.createServer((req, res) => {
|
|
1392
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1393
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
1394
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
1395
|
+
if (req.method === "OPTIONS") {
|
|
1396
|
+
res.writeHead(200);
|
|
1397
|
+
res.end();
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const url = new URL(req.url || "/", `http://localhost:${options.port}`);
|
|
1401
|
+
const pathname = url.pathname;
|
|
1402
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
1403
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1404
|
+
res.end(
|
|
1405
|
+
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
1406
|
+
);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (pathname === "/preview-runtime.js") {
|
|
1410
|
+
serveFile(res, options.previewRuntimePath);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (pathname.startsWith("/_assets/")) {
|
|
1414
|
+
const parts = pathname.replace(/^\/_assets\//, "").split("/");
|
|
1415
|
+
const assetSubpath = parts.slice(1).join("/");
|
|
1416
|
+
const assetPath = path9__default.default.join(options.themePath, "assets", assetSubpath);
|
|
1417
|
+
if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
|
|
1418
|
+
res.writeHead(403);
|
|
1419
|
+
res.end("Forbidden");
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
serveFile(res, assetPath);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (pathname.startsWith("/themes/")) {
|
|
1426
|
+
const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
|
|
1427
|
+
if (match) {
|
|
1428
|
+
const assetPath = path9__default.default.join(options.themePath, "assets", match[1]);
|
|
1429
|
+
if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
|
|
1430
|
+
res.writeHead(403);
|
|
1431
|
+
res.end("Forbidden");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
serveFile(res, assetPath);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
if (pathname.startsWith("/assets/")) {
|
|
1439
|
+
const subpath = pathname.replace(/^\/assets\//, "");
|
|
1440
|
+
const segments = subpath.split("/");
|
|
1441
|
+
const assetsBase = path9__default.default.join(options.themePath, "assets");
|
|
1442
|
+
let assetPath;
|
|
1443
|
+
if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
|
|
1444
|
+
assetPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
|
|
1445
|
+
} else {
|
|
1446
|
+
assetPath = path9__default.default.join(assetsBase, subpath);
|
|
1447
|
+
}
|
|
1448
|
+
if (assetPath.startsWith(assetsBase) && fs3__default.default.existsSync(assetPath)) {
|
|
1449
|
+
serveFile(res, assetPath);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (segments.length > 1) {
|
|
1453
|
+
const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
|
|
1454
|
+
if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
|
|
1455
|
+
serveFile(res, fallbackPath);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const filePath = path9__default.default.join(options.distDir, pathname);
|
|
1461
|
+
if (!filePath.startsWith(options.distDir)) {
|
|
1462
|
+
res.writeHead(403);
|
|
1463
|
+
res.end("Forbidden");
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (fs3__default.default.existsSync(filePath) && fs3__default.default.statSync(filePath).isFile()) {
|
|
1467
|
+
serveFile(res, filePath);
|
|
1468
|
+
} else {
|
|
1469
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1470
|
+
res.end(
|
|
1471
|
+
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
const wss = new ws.WebSocketServer({ server });
|
|
1476
|
+
wss.on("connection", (ws) => {
|
|
1477
|
+
clients.add(ws);
|
|
1478
|
+
ws.on("close", () => clients.delete(ws));
|
|
1479
|
+
});
|
|
1480
|
+
server.listen(options.port);
|
|
1481
|
+
return {
|
|
1482
|
+
broadcast(message) {
|
|
1483
|
+
const data = JSON.stringify(message);
|
|
1484
|
+
for (const client of clients) {
|
|
1485
|
+
if (client.readyState === ws.WebSocket.OPEN) {
|
|
1486
|
+
client.send(data);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1490
|
+
close() {
|
|
1491
|
+
wss.close();
|
|
1492
|
+
server.close();
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function serveFile(res, filePath) {
|
|
1497
|
+
try {
|
|
1498
|
+
if (!fs3__default.default.existsSync(filePath)) {
|
|
1499
|
+
res.writeHead(404);
|
|
1500
|
+
res.end("Not Found");
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const ext = path9__default.default.extname(filePath);
|
|
1504
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
1505
|
+
const content = fs3__default.default.readFileSync(filePath);
|
|
1506
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
1507
|
+
res.end(content);
|
|
1508
|
+
} catch {
|
|
1509
|
+
res.writeHead(500);
|
|
1510
|
+
res.end("Internal Server Error");
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function generatePreviewHTML(themeName, port, themeDataPath) {
|
|
1514
|
+
let fontLinks = "";
|
|
1515
|
+
let fontVarsCSS = "";
|
|
1516
|
+
if (themeDataPath) {
|
|
1517
|
+
try {
|
|
1518
|
+
const themeData = JSON.parse(fs3__default.default.readFileSync(themeDataPath, "utf-8"));
|
|
1519
|
+
const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
|
|
1520
|
+
if (typography) {
|
|
1521
|
+
const fontFamilies = /* @__PURE__ */ new Set();
|
|
1522
|
+
for (const value of Object.values(typography)) {
|
|
1523
|
+
const primary = value.split(",")[0].trim();
|
|
1524
|
+
if (primary && ![
|
|
1525
|
+
"serif",
|
|
1526
|
+
"sans-serif",
|
|
1527
|
+
"monospace",
|
|
1528
|
+
"system-ui",
|
|
1529
|
+
"Georgia",
|
|
1530
|
+
"Inter",
|
|
1531
|
+
"Consolas"
|
|
1532
|
+
].includes(primary)) {
|
|
1533
|
+
fontFamilies.add(primary);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (fontFamilies.size > 0) {
|
|
1537
|
+
const families = Array.from(fontFamilies).map(
|
|
1538
|
+
(f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
|
|
1539
|
+
).join("&");
|
|
1540
|
+
fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1541
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1542
|
+
<link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
|
|
1543
|
+
}
|
|
1544
|
+
const heading = typography.heading || typography.body || "system-ui";
|
|
1545
|
+
const body = typography.body || "system-ui";
|
|
1546
|
+
fontVarsCSS = `
|
|
1547
|
+
:root {
|
|
1548
|
+
--font-heading: ${heading};
|
|
1549
|
+
--font-body: ${body};
|
|
1550
|
+
}
|
|
1551
|
+
body { font-family: var(--font-body); }
|
|
1552
|
+
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
|
|
1553
|
+
}
|
|
1554
|
+
} catch {
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return `<!DOCTYPE html>
|
|
1558
|
+
<html lang="en">
|
|
1559
|
+
<head>
|
|
1560
|
+
<meta charset="UTF-8">
|
|
1561
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1562
|
+
<title>OneX Dev \u2014 ${themeName}</title>
|
|
1563
|
+
${fontLinks}
|
|
1564
|
+
<!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
|
|
1565
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
1566
|
+
<script>
|
|
1567
|
+
tailwind.config = {
|
|
1568
|
+
theme: {
|
|
1569
|
+
extend: {
|
|
1570
|
+
aspectRatio: {
|
|
1571
|
+
'2/1': '2 / 1',
|
|
1572
|
+
'3/2': '3 / 2',
|
|
1573
|
+
'4/3': '4 / 3',
|
|
1574
|
+
'3/4': '3 / 4',
|
|
1575
|
+
'5/4': '5 / 4',
|
|
1576
|
+
'16/9': '16 / 9',
|
|
1577
|
+
'21/9': '21 / 9',
|
|
1578
|
+
},
|
|
1579
|
+
fontFamily: {
|
|
1580
|
+
playfair: ['Playfair Display', 'Georgia', 'serif'],
|
|
1581
|
+
roboto: ['Roboto', 'system-ui', 'sans-serif'],
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
}
|
|
1586
|
+
</script>
|
|
1587
|
+
<style>
|
|
1588
|
+
#onex-preview-root { margin-top: 0; }${fontVarsCSS}
|
|
1589
|
+
</style>
|
|
1590
|
+
</head>
|
|
1591
|
+
<body>
|
|
1592
|
+
<div id="onex-preview-root"></div>
|
|
1593
|
+
<script type="module" src="/preview-runtime.js"></script>
|
|
1594
|
+
</body>
|
|
1595
|
+
</html>`;
|
|
1596
|
+
}
|
|
1597
|
+
var MIME_TYPES;
|
|
1598
|
+
var init_dev_server = __esm({
|
|
1599
|
+
"src/utils/dev-server.ts"() {
|
|
1600
|
+
MIME_TYPES = {
|
|
1601
|
+
".js": "application/javascript",
|
|
1602
|
+
".mjs": "application/javascript",
|
|
1603
|
+
".css": "text/css",
|
|
1604
|
+
".json": "application/json",
|
|
1605
|
+
".html": "text/html",
|
|
1606
|
+
".svg": "image/svg+xml",
|
|
1607
|
+
".png": "image/png",
|
|
1608
|
+
".jpg": "image/jpeg",
|
|
1609
|
+
".jpeg": "image/jpeg",
|
|
1610
|
+
".webp": "image/webp",
|
|
1611
|
+
".gif": "image/gif",
|
|
1612
|
+
".map": "application/json"
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1383
1617
|
// src/utils/file-helpers.ts
|
|
1384
1618
|
init_logger();
|
|
1385
1619
|
async function renderTemplate(templatePath, data) {
|
|
@@ -4055,231 +4289,7 @@ async function cloneCommand(themeName, options) {
|
|
|
4055
4289
|
// src/commands/dev.ts
|
|
4056
4290
|
init_logger();
|
|
4057
4291
|
init_compile_theme();
|
|
4058
|
-
|
|
4059
|
-
".js": "application/javascript",
|
|
4060
|
-
".mjs": "application/javascript",
|
|
4061
|
-
".css": "text/css",
|
|
4062
|
-
".json": "application/json",
|
|
4063
|
-
".html": "text/html",
|
|
4064
|
-
".svg": "image/svg+xml",
|
|
4065
|
-
".png": "image/png",
|
|
4066
|
-
".jpg": "image/jpeg",
|
|
4067
|
-
".jpeg": "image/jpeg",
|
|
4068
|
-
".webp": "image/webp",
|
|
4069
|
-
".gif": "image/gif",
|
|
4070
|
-
".map": "application/json"
|
|
4071
|
-
};
|
|
4072
|
-
function createDevServer(options) {
|
|
4073
|
-
const clients = /* @__PURE__ */ new Set();
|
|
4074
|
-
const themeDataPath = path9__default.default.join(options.distDir, "theme-data.json");
|
|
4075
|
-
const server = http__default.default.createServer((req, res) => {
|
|
4076
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4077
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
4078
|
-
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
4079
|
-
if (req.method === "OPTIONS") {
|
|
4080
|
-
res.writeHead(200);
|
|
4081
|
-
res.end();
|
|
4082
|
-
return;
|
|
4083
|
-
}
|
|
4084
|
-
const url = new URL(req.url || "/", `http://localhost:${options.port}`);
|
|
4085
|
-
const pathname = url.pathname;
|
|
4086
|
-
if (pathname === "/" || pathname === "/index.html") {
|
|
4087
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4088
|
-
res.end(
|
|
4089
|
-
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
4090
|
-
);
|
|
4091
|
-
return;
|
|
4092
|
-
}
|
|
4093
|
-
if (pathname === "/preview-runtime.js") {
|
|
4094
|
-
serveFile(res, options.previewRuntimePath);
|
|
4095
|
-
return;
|
|
4096
|
-
}
|
|
4097
|
-
if (pathname.startsWith("/_assets/")) {
|
|
4098
|
-
const parts = pathname.replace(/^\/_assets\//, "").split("/");
|
|
4099
|
-
const assetSubpath = parts.slice(1).join("/");
|
|
4100
|
-
const assetPath = path9__default.default.join(options.themePath, "assets", assetSubpath);
|
|
4101
|
-
if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
|
|
4102
|
-
res.writeHead(403);
|
|
4103
|
-
res.end("Forbidden");
|
|
4104
|
-
return;
|
|
4105
|
-
}
|
|
4106
|
-
serveFile(res, assetPath);
|
|
4107
|
-
return;
|
|
4108
|
-
}
|
|
4109
|
-
if (pathname.startsWith("/themes/")) {
|
|
4110
|
-
const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
|
|
4111
|
-
if (match) {
|
|
4112
|
-
const assetPath = path9__default.default.join(options.themePath, "assets", match[1]);
|
|
4113
|
-
if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
|
|
4114
|
-
res.writeHead(403);
|
|
4115
|
-
res.end("Forbidden");
|
|
4116
|
-
return;
|
|
4117
|
-
}
|
|
4118
|
-
serveFile(res, assetPath);
|
|
4119
|
-
return;
|
|
4120
|
-
}
|
|
4121
|
-
}
|
|
4122
|
-
if (pathname.startsWith("/assets/")) {
|
|
4123
|
-
const subpath = pathname.replace(/^\/assets\//, "");
|
|
4124
|
-
const segments = subpath.split("/");
|
|
4125
|
-
const assetsBase = path9__default.default.join(options.themePath, "assets");
|
|
4126
|
-
let assetPath;
|
|
4127
|
-
if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
|
|
4128
|
-
assetPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
|
|
4129
|
-
} else {
|
|
4130
|
-
assetPath = path9__default.default.join(assetsBase, subpath);
|
|
4131
|
-
}
|
|
4132
|
-
if (assetPath.startsWith(assetsBase) && fs3__default.default.existsSync(assetPath)) {
|
|
4133
|
-
serveFile(res, assetPath);
|
|
4134
|
-
return;
|
|
4135
|
-
}
|
|
4136
|
-
if (segments.length > 1) {
|
|
4137
|
-
const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
|
|
4138
|
-
if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
|
|
4139
|
-
serveFile(res, fallbackPath);
|
|
4140
|
-
return;
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
}
|
|
4144
|
-
const filePath = path9__default.default.join(options.distDir, pathname);
|
|
4145
|
-
if (!filePath.startsWith(options.distDir)) {
|
|
4146
|
-
res.writeHead(403);
|
|
4147
|
-
res.end("Forbidden");
|
|
4148
|
-
return;
|
|
4149
|
-
}
|
|
4150
|
-
if (fs3__default.default.existsSync(filePath) && fs3__default.default.statSync(filePath).isFile()) {
|
|
4151
|
-
serveFile(res, filePath);
|
|
4152
|
-
} else {
|
|
4153
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4154
|
-
res.end(
|
|
4155
|
-
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
4156
|
-
);
|
|
4157
|
-
}
|
|
4158
|
-
});
|
|
4159
|
-
const wss = new ws.WebSocketServer({ server });
|
|
4160
|
-
wss.on("connection", (ws) => {
|
|
4161
|
-
clients.add(ws);
|
|
4162
|
-
ws.on("close", () => clients.delete(ws));
|
|
4163
|
-
});
|
|
4164
|
-
server.listen(options.port);
|
|
4165
|
-
return {
|
|
4166
|
-
broadcast(message) {
|
|
4167
|
-
const data = JSON.stringify(message);
|
|
4168
|
-
for (const client of clients) {
|
|
4169
|
-
if (client.readyState === ws.WebSocket.OPEN) {
|
|
4170
|
-
client.send(data);
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
},
|
|
4174
|
-
close() {
|
|
4175
|
-
wss.close();
|
|
4176
|
-
server.close();
|
|
4177
|
-
}
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
function serveFile(res, filePath) {
|
|
4181
|
-
try {
|
|
4182
|
-
if (!fs3__default.default.existsSync(filePath)) {
|
|
4183
|
-
res.writeHead(404);
|
|
4184
|
-
res.end("Not Found");
|
|
4185
|
-
return;
|
|
4186
|
-
}
|
|
4187
|
-
const ext = path9__default.default.extname(filePath);
|
|
4188
|
-
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
4189
|
-
const content = fs3__default.default.readFileSync(filePath);
|
|
4190
|
-
res.writeHead(200, { "Content-Type": contentType });
|
|
4191
|
-
res.end(content);
|
|
4192
|
-
} catch {
|
|
4193
|
-
res.writeHead(500);
|
|
4194
|
-
res.end("Internal Server Error");
|
|
4195
|
-
}
|
|
4196
|
-
}
|
|
4197
|
-
function generatePreviewHTML(themeName, port, themeDataPath) {
|
|
4198
|
-
let fontLinks = "";
|
|
4199
|
-
let fontVarsCSS = "";
|
|
4200
|
-
if (themeDataPath) {
|
|
4201
|
-
try {
|
|
4202
|
-
const themeData = JSON.parse(fs3__default.default.readFileSync(themeDataPath, "utf-8"));
|
|
4203
|
-
const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
|
|
4204
|
-
if (typography) {
|
|
4205
|
-
const fontFamilies = /* @__PURE__ */ new Set();
|
|
4206
|
-
for (const value of Object.values(typography)) {
|
|
4207
|
-
const primary = value.split(",")[0].trim();
|
|
4208
|
-
if (primary && ![
|
|
4209
|
-
"serif",
|
|
4210
|
-
"sans-serif",
|
|
4211
|
-
"monospace",
|
|
4212
|
-
"system-ui",
|
|
4213
|
-
"Georgia",
|
|
4214
|
-
"Inter",
|
|
4215
|
-
"Consolas"
|
|
4216
|
-
].includes(primary)) {
|
|
4217
|
-
fontFamilies.add(primary);
|
|
4218
|
-
}
|
|
4219
|
-
}
|
|
4220
|
-
if (fontFamilies.size > 0) {
|
|
4221
|
-
const families = Array.from(fontFamilies).map(
|
|
4222
|
-
(f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
|
|
4223
|
-
).join("&");
|
|
4224
|
-
fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
4225
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
4226
|
-
<link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
|
|
4227
|
-
}
|
|
4228
|
-
const heading = typography.heading || typography.body || "system-ui";
|
|
4229
|
-
const body = typography.body || "system-ui";
|
|
4230
|
-
fontVarsCSS = `
|
|
4231
|
-
:root {
|
|
4232
|
-
--font-heading: ${heading};
|
|
4233
|
-
--font-body: ${body};
|
|
4234
|
-
}
|
|
4235
|
-
body { font-family: var(--font-body); }
|
|
4236
|
-
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
|
|
4237
|
-
}
|
|
4238
|
-
} catch {
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
return `<!DOCTYPE html>
|
|
4242
|
-
<html lang="en">
|
|
4243
|
-
<head>
|
|
4244
|
-
<meta charset="UTF-8">
|
|
4245
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4246
|
-
<title>OneX Dev \u2014 ${themeName}</title>
|
|
4247
|
-
${fontLinks}
|
|
4248
|
-
<!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
|
|
4249
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
4250
|
-
<script>
|
|
4251
|
-
tailwind.config = {
|
|
4252
|
-
theme: {
|
|
4253
|
-
extend: {
|
|
4254
|
-
aspectRatio: {
|
|
4255
|
-
'2/1': '2 / 1',
|
|
4256
|
-
'3/2': '3 / 2',
|
|
4257
|
-
'4/3': '4 / 3',
|
|
4258
|
-
'3/4': '3 / 4',
|
|
4259
|
-
'5/4': '5 / 4',
|
|
4260
|
-
'16/9': '16 / 9',
|
|
4261
|
-
'21/9': '21 / 9',
|
|
4262
|
-
},
|
|
4263
|
-
fontFamily: {
|
|
4264
|
-
playfair: ['Playfair Display', 'Georgia', 'serif'],
|
|
4265
|
-
roboto: ['Roboto', 'system-ui', 'sans-serif'],
|
|
4266
|
-
},
|
|
4267
|
-
},
|
|
4268
|
-
},
|
|
4269
|
-
}
|
|
4270
|
-
</script>
|
|
4271
|
-
<style>
|
|
4272
|
-
#onex-preview-root { margin-top: 0; }${fontVarsCSS}
|
|
4273
|
-
</style>
|
|
4274
|
-
</head>
|
|
4275
|
-
<body>
|
|
4276
|
-
<div id="onex-preview-root"></div>
|
|
4277
|
-
<script type="module" src="/preview-runtime.js"></script>
|
|
4278
|
-
</body>
|
|
4279
|
-
</html>`;
|
|
4280
|
-
}
|
|
4281
|
-
|
|
4282
|
-
// src/commands/dev.ts
|
|
4292
|
+
init_dev_server();
|
|
4283
4293
|
async function devCommand(options) {
|
|
4284
4294
|
logger.header("OneX Dev Server");
|
|
4285
4295
|
let themePath;
|
|
@@ -5114,6 +5124,121 @@ Or use the --bump flag:
|
|
|
5114
5124
|
}
|
|
5115
5125
|
logger.newLine();
|
|
5116
5126
|
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
5127
|
+
await uploadThumbnail(apiUrl, themeId, themePath, distDir);
|
|
5128
|
+
}
|
|
5129
|
+
async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
|
|
5130
|
+
const THUMBNAIL_CANDIDATES = [
|
|
5131
|
+
{ file: "thumbnail.png", mime: "image/png" },
|
|
5132
|
+
{ file: "thumbnail.jpg", mime: "image/jpeg" },
|
|
5133
|
+
{ file: "thumbnail.jpeg", mime: "image/jpeg" },
|
|
5134
|
+
{ file: "thumbnail.webp", mime: "image/webp" }
|
|
5135
|
+
];
|
|
5136
|
+
let imageBase64 = null;
|
|
5137
|
+
let mimeType = "image/png";
|
|
5138
|
+
for (const { file, mime } of THUMBNAIL_CANDIDATES) {
|
|
5139
|
+
const candidate = path9__default.default.join(themePath, file);
|
|
5140
|
+
if (fs__default.default.existsSync(candidate)) {
|
|
5141
|
+
const buf = fs__default.default.readFileSync(candidate);
|
|
5142
|
+
imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
|
|
5143
|
+
mimeType = mime;
|
|
5144
|
+
logger.info(`Using local thumbnail: ${file}`);
|
|
5145
|
+
break;
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
if (!imageBase64) {
|
|
5149
|
+
logger.startSpinner("Taking screenshot for thumbnail...");
|
|
5150
|
+
try {
|
|
5151
|
+
const buf = await screenshotHomePage(themePath, distDir);
|
|
5152
|
+
imageBase64 = `data:image/png;base64,${buf.toString("base64")}`;
|
|
5153
|
+
mimeType = "image/png";
|
|
5154
|
+
logger.stopSpinner(true, "Screenshot captured");
|
|
5155
|
+
} catch (err) {
|
|
5156
|
+
logger.stopSpinner(false, "Screenshot failed \u2014 skipping thumbnail");
|
|
5157
|
+
logger.info(
|
|
5158
|
+
"Tip: add thumbnail.png to your theme root to set a custom thumbnail"
|
|
5159
|
+
);
|
|
5160
|
+
return;
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
logger.startSpinner("Uploading thumbnail...");
|
|
5164
|
+
try {
|
|
5165
|
+
const uploadRes = await authenticatedFetch(`${apiUrl}/media/images/upload`, {
|
|
5166
|
+
method: "POST",
|
|
5167
|
+
body: JSON.stringify({
|
|
5168
|
+
prefix: `themes/${themeId}`,
|
|
5169
|
+
image: imageBase64,
|
|
5170
|
+
name: "thumbnail.png"
|
|
5171
|
+
})
|
|
5172
|
+
});
|
|
5173
|
+
const uploadData = await uploadRes.json();
|
|
5174
|
+
const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
|
|
5175
|
+
if (!uploadRes.ok || !uploadBody.url) {
|
|
5176
|
+
throw new Error(uploadBody.error || "Upload failed");
|
|
5177
|
+
}
|
|
5178
|
+
const patchRes = await authenticatedFetch(
|
|
5179
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
|
|
5180
|
+
{
|
|
5181
|
+
method: "PATCH",
|
|
5182
|
+
body: JSON.stringify({ thumbnail_url: uploadBody.url })
|
|
5183
|
+
}
|
|
5184
|
+
);
|
|
5185
|
+
if (!patchRes.ok) {
|
|
5186
|
+
const patchData = await patchRes.json();
|
|
5187
|
+
const patchBody = patchData.statusCode ? patchData.body : patchData;
|
|
5188
|
+
throw new Error(patchBody.error || "Failed to set thumbnail");
|
|
5189
|
+
}
|
|
5190
|
+
logger.stopSpinner(true, "Thumbnail set");
|
|
5191
|
+
} catch (err) {
|
|
5192
|
+
logger.stopSpinner(false, "Thumbnail upload skipped");
|
|
5193
|
+
logger.info(
|
|
5194
|
+
`Theme published successfully. Thumbnail can be updated later.`
|
|
5195
|
+
);
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
async function screenshotHomePage(themePath, distDir) {
|
|
5199
|
+
const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
|
|
5200
|
+
const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
|
|
5201
|
+
const previewRuntimePath = await compilePreviewRuntime2(themePath);
|
|
5202
|
+
const themeName = path9__default.default.basename(themePath);
|
|
5203
|
+
const port = await findFreePort(4500);
|
|
5204
|
+
const server = createDevServer2({
|
|
5205
|
+
port,
|
|
5206
|
+
distDir,
|
|
5207
|
+
previewRuntimePath,
|
|
5208
|
+
themeName,
|
|
5209
|
+
themePath
|
|
5210
|
+
});
|
|
5211
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
5212
|
+
const puppeteer = await import('puppeteer');
|
|
5213
|
+
const browser = await puppeteer.default.launch({
|
|
5214
|
+
headless: true,
|
|
5215
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
5216
|
+
});
|
|
5217
|
+
try {
|
|
5218
|
+
const page = await browser.newPage();
|
|
5219
|
+
await page.setViewport({ width: 1440, height: 900 });
|
|
5220
|
+
await page.goto(`http://localhost:${port}`, {
|
|
5221
|
+
waitUntil: "networkidle0",
|
|
5222
|
+
timeout: 3e4
|
|
5223
|
+
});
|
|
5224
|
+
await page.waitForSelector("#onex-preview-root > *", { timeout: 15e3 });
|
|
5225
|
+
const screenshot = await page.screenshot({ fullPage: true, type: "png" });
|
|
5226
|
+
return Buffer.from(screenshot);
|
|
5227
|
+
} finally {
|
|
5228
|
+
await browser.close();
|
|
5229
|
+
server.close();
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
async function findFreePort(start) {
|
|
5233
|
+
const net = await import('net');
|
|
5234
|
+
return new Promise((resolve) => {
|
|
5235
|
+
const srv = net.createServer();
|
|
5236
|
+
srv.listen(start, () => {
|
|
5237
|
+
const addr = srv.address();
|
|
5238
|
+
srv.close(() => resolve(addr.port));
|
|
5239
|
+
});
|
|
5240
|
+
srv.on("error", () => resolve(findFreePort(start + 1)));
|
|
5241
|
+
});
|
|
5117
5242
|
}
|
|
5118
5243
|
async function uploadVideoMultipart(apiUrl, themeId, video) {
|
|
5119
5244
|
const fileName = path9__default.default.basename(video.originalPath);
|