@onexapis/cli 1.1.52 → 1.1.54
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 +367 -235
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +365 -233
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
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) {
|
|
@@ -1565,10 +1799,14 @@ async function getValidTokens() {
|
|
|
1565
1799
|
}
|
|
1566
1800
|
const data = await response.json();
|
|
1567
1801
|
const body = data.statusCode ? data.body : data;
|
|
1802
|
+
if (!body.IdToken) {
|
|
1803
|
+
await clearAuthTokens();
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1568
1806
|
const refreshed = {
|
|
1569
1807
|
...tokens,
|
|
1570
1808
|
accessToken: body.AccessToken || tokens.accessToken,
|
|
1571
|
-
idToken: body.IdToken
|
|
1809
|
+
idToken: body.IdToken,
|
|
1572
1810
|
expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
|
|
1573
1811
|
};
|
|
1574
1812
|
await saveAuthTokens(refreshed);
|
|
@@ -4012,231 +4250,7 @@ async function cloneCommand(themeName, options) {
|
|
|
4012
4250
|
// src/commands/dev.ts
|
|
4013
4251
|
init_logger();
|
|
4014
4252
|
init_compile_theme();
|
|
4015
|
-
|
|
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
|
|
4253
|
+
init_dev_server();
|
|
4240
4254
|
async function devCommand(options) {
|
|
4241
4255
|
logger.header("OneX Dev Server");
|
|
4242
4256
|
let themePath;
|
|
@@ -4767,10 +4781,13 @@ async function publishCommand(options) {
|
|
|
4767
4781
|
);
|
|
4768
4782
|
const regData = await regResponse.json();
|
|
4769
4783
|
const regBody = regData.statusCode ? regData.body : regData;
|
|
4770
|
-
if (!regResponse.ok
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4784
|
+
if (!regResponse.ok) {
|
|
4785
|
+
const errMsg = regBody.error || regBody.message || "Registration failed";
|
|
4786
|
+
if (!errMsg.includes("already registered")) {
|
|
4787
|
+
logger.stopSpinner(false, "Registration failed");
|
|
4788
|
+
logger.error(errMsg);
|
|
4789
|
+
process.exit(1);
|
|
4790
|
+
}
|
|
4774
4791
|
}
|
|
4775
4792
|
logger.stopSpinner(true, regBody.message || "Theme registered");
|
|
4776
4793
|
} catch (error) {
|
|
@@ -5071,6 +5088,121 @@ Or use the --bump flag:
|
|
|
5071
5088
|
}
|
|
5072
5089
|
logger.newLine();
|
|
5073
5090
|
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
5091
|
+
await uploadThumbnail(apiUrl, themeId, themePath, distDir);
|
|
5092
|
+
}
|
|
5093
|
+
async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
|
|
5094
|
+
const THUMBNAIL_CANDIDATES = [
|
|
5095
|
+
{ file: "thumbnail.png", mime: "image/png" },
|
|
5096
|
+
{ file: "thumbnail.jpg", mime: "image/jpeg" },
|
|
5097
|
+
{ file: "thumbnail.jpeg", mime: "image/jpeg" },
|
|
5098
|
+
{ file: "thumbnail.webp", mime: "image/webp" }
|
|
5099
|
+
];
|
|
5100
|
+
let imageBase64 = null;
|
|
5101
|
+
let mimeType = "image/png";
|
|
5102
|
+
for (const { file, mime } of THUMBNAIL_CANDIDATES) {
|
|
5103
|
+
const candidate = path9.join(themePath, file);
|
|
5104
|
+
if (fs.existsSync(candidate)) {
|
|
5105
|
+
const buf = fs.readFileSync(candidate);
|
|
5106
|
+
imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
|
|
5107
|
+
mimeType = mime;
|
|
5108
|
+
logger.info(`Using local thumbnail: ${file}`);
|
|
5109
|
+
break;
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
if (!imageBase64) {
|
|
5113
|
+
logger.startSpinner("Taking screenshot for thumbnail...");
|
|
5114
|
+
try {
|
|
5115
|
+
const buf = await screenshotHomePage(themePath, distDir);
|
|
5116
|
+
imageBase64 = `data:image/png;base64,${buf.toString("base64")}`;
|
|
5117
|
+
mimeType = "image/png";
|
|
5118
|
+
logger.stopSpinner(true, "Screenshot captured");
|
|
5119
|
+
} catch (err) {
|
|
5120
|
+
logger.stopSpinner(false, "Screenshot failed \u2014 skipping thumbnail");
|
|
5121
|
+
logger.info(
|
|
5122
|
+
"Tip: add thumbnail.png to your theme root to set a custom thumbnail"
|
|
5123
|
+
);
|
|
5124
|
+
return;
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
logger.startSpinner("Uploading thumbnail...");
|
|
5128
|
+
try {
|
|
5129
|
+
const uploadRes = await authenticatedFetch(`${apiUrl}/media/images/upload`, {
|
|
5130
|
+
method: "POST",
|
|
5131
|
+
body: JSON.stringify({
|
|
5132
|
+
prefix: `themes/${themeId}`,
|
|
5133
|
+
image: imageBase64,
|
|
5134
|
+
name: "thumbnail.png"
|
|
5135
|
+
})
|
|
5136
|
+
});
|
|
5137
|
+
const uploadData = await uploadRes.json();
|
|
5138
|
+
const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
|
|
5139
|
+
if (!uploadRes.ok || !uploadBody.url) {
|
|
5140
|
+
throw new Error(uploadBody.error || "Upload failed");
|
|
5141
|
+
}
|
|
5142
|
+
const patchRes = await authenticatedFetch(
|
|
5143
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
|
|
5144
|
+
{
|
|
5145
|
+
method: "PATCH",
|
|
5146
|
+
body: JSON.stringify({ thumbnail_url: uploadBody.url })
|
|
5147
|
+
}
|
|
5148
|
+
);
|
|
5149
|
+
if (!patchRes.ok) {
|
|
5150
|
+
const patchData = await patchRes.json();
|
|
5151
|
+
const patchBody = patchData.statusCode ? patchData.body : patchData;
|
|
5152
|
+
throw new Error(patchBody.error || "Failed to set thumbnail");
|
|
5153
|
+
}
|
|
5154
|
+
logger.stopSpinner(true, "Thumbnail set");
|
|
5155
|
+
} catch (err) {
|
|
5156
|
+
logger.stopSpinner(false, "Thumbnail upload skipped");
|
|
5157
|
+
logger.info(
|
|
5158
|
+
`Theme published successfully. Thumbnail can be updated later.`
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
async function screenshotHomePage(themePath, distDir) {
|
|
5163
|
+
const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
|
|
5164
|
+
const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
|
|
5165
|
+
const previewRuntimePath = await compilePreviewRuntime2(themePath);
|
|
5166
|
+
const themeName = path9.basename(themePath);
|
|
5167
|
+
const port = await findFreePort(4500);
|
|
5168
|
+
const server = createDevServer2({
|
|
5169
|
+
port,
|
|
5170
|
+
distDir,
|
|
5171
|
+
previewRuntimePath,
|
|
5172
|
+
themeName,
|
|
5173
|
+
themePath
|
|
5174
|
+
});
|
|
5175
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
5176
|
+
const puppeteer = await import('puppeteer');
|
|
5177
|
+
const browser = await puppeteer.default.launch({
|
|
5178
|
+
headless: true,
|
|
5179
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
5180
|
+
});
|
|
5181
|
+
try {
|
|
5182
|
+
const page = await browser.newPage();
|
|
5183
|
+
await page.setViewport({ width: 1440, height: 900 });
|
|
5184
|
+
await page.goto(`http://localhost:${port}`, {
|
|
5185
|
+
waitUntil: "networkidle0",
|
|
5186
|
+
timeout: 3e4
|
|
5187
|
+
});
|
|
5188
|
+
await page.waitForSelector("#onex-preview-root > *", { timeout: 15e3 });
|
|
5189
|
+
const screenshot = await page.screenshot({ fullPage: true, type: "png" });
|
|
5190
|
+
return Buffer.from(screenshot);
|
|
5191
|
+
} finally {
|
|
5192
|
+
await browser.close();
|
|
5193
|
+
server.close();
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
async function findFreePort(start) {
|
|
5197
|
+
const net = await import('net');
|
|
5198
|
+
return new Promise((resolve) => {
|
|
5199
|
+
const srv = net.createServer();
|
|
5200
|
+
srv.listen(start, () => {
|
|
5201
|
+
const addr = srv.address();
|
|
5202
|
+
srv.close(() => resolve(addr.port));
|
|
5203
|
+
});
|
|
5204
|
+
srv.on("error", () => resolve(findFreePort(start + 1)));
|
|
5205
|
+
});
|
|
5074
5206
|
}
|
|
5075
5207
|
async function uploadVideoMultipart(apiUrl, themeId, video) {
|
|
5076
5208
|
const fileName = path9.basename(video.originalPath);
|