@acta-dev/cli 1.1.0 → 1.2.0
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/index.js +213 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -250,9 +250,9 @@ var LINK_DESCRIPTIONS = {
|
|
|
250
250
|
};
|
|
251
251
|
function table(header, rows) {
|
|
252
252
|
const head = `| ${header.join(" | ")} |`;
|
|
253
|
-
const
|
|
253
|
+
const sep2 = `| ${header.map(() => "---").join(" | ")} |`;
|
|
254
254
|
const body = rows.map((r) => `| ${r.join(" | ")} |`).join("\n");
|
|
255
|
-
return [head,
|
|
255
|
+
return [head, sep2, body].join("\n");
|
|
256
256
|
}
|
|
257
257
|
function renderSkill() {
|
|
258
258
|
const config = resolveConfig2({}, { rootDir: process.cwd() });
|
|
@@ -1217,8 +1217,11 @@ function formatShowDate(value) {
|
|
|
1217
1217
|
|
|
1218
1218
|
// src/commands/site.ts
|
|
1219
1219
|
import { spawn } from "child_process";
|
|
1220
|
+
import { createReadStream } from "fs";
|
|
1221
|
+
import { stat } from "fs/promises";
|
|
1222
|
+
import { createServer } from "http";
|
|
1220
1223
|
import { createRequire } from "module";
|
|
1221
|
-
import { dirname as dirname2, join as join6, resolve as resolve3 } from "path";
|
|
1224
|
+
import { dirname as dirname2, extname, join as join6, relative as relative2, resolve as resolve3, sep } from "path";
|
|
1222
1225
|
import { buildArtifacts as buildArtifacts2 } from "@acta-dev/core";
|
|
1223
1226
|
import { defineCommand as defineCommand8 } from "citty";
|
|
1224
1227
|
import kleur6 from "kleur";
|
|
@@ -1229,6 +1232,16 @@ function resolveSiteOptions(config, args, cwd = process.cwd()) {
|
|
|
1229
1232
|
site: args.site ?? config.site.url
|
|
1230
1233
|
};
|
|
1231
1234
|
}
|
|
1235
|
+
function resolveSiteServeOptions(args) {
|
|
1236
|
+
const port = args.port === void 0 ? 4321 : Number(args.port);
|
|
1237
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
1238
|
+
throw new Error("Expected --port to be an integer between 0 and 65535.");
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
host: args.host ?? "127.0.0.1",
|
|
1242
|
+
port
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1232
1245
|
function buildSiteEnv(config, options) {
|
|
1233
1246
|
const env = {
|
|
1234
1247
|
...process.env,
|
|
@@ -1264,6 +1277,19 @@ var siteCommand = defineCommand8({
|
|
|
1264
1277
|
description: "Reuse existing artifacts instead of running `acta build` first",
|
|
1265
1278
|
default: false
|
|
1266
1279
|
},
|
|
1280
|
+
serve: {
|
|
1281
|
+
type: "boolean",
|
|
1282
|
+
description: "Serve the generated site locally after building it",
|
|
1283
|
+
default: false
|
|
1284
|
+
},
|
|
1285
|
+
host: {
|
|
1286
|
+
type: "string",
|
|
1287
|
+
description: "Host for --serve (default: 127.0.0.1)"
|
|
1288
|
+
},
|
|
1289
|
+
port: {
|
|
1290
|
+
type: "string",
|
|
1291
|
+
description: "Port for --serve (default: 4321)"
|
|
1292
|
+
},
|
|
1267
1293
|
config: {
|
|
1268
1294
|
type: "string",
|
|
1269
1295
|
alias: "c",
|
|
@@ -1278,6 +1304,18 @@ var siteCommand = defineCommand8({
|
|
|
1278
1304
|
async run({ args }) {
|
|
1279
1305
|
const { config } = await resolveContext({ config: args.config });
|
|
1280
1306
|
const json = Boolean(args.json);
|
|
1307
|
+
const serve = Boolean(args.serve);
|
|
1308
|
+
if (json && serve) {
|
|
1309
|
+
return exitUsage("`acta site --serve` cannot be combined with --json.");
|
|
1310
|
+
}
|
|
1311
|
+
let serveOptions;
|
|
1312
|
+
if (serve) {
|
|
1313
|
+
try {
|
|
1314
|
+
serveOptions = resolveSiteServeOptions(args);
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
return exitUsage(error instanceof Error ? error.message : String(error));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1281
1319
|
let documentCount = 0;
|
|
1282
1320
|
if (!args["skip-build"]) {
|
|
1283
1321
|
if (!json) printLine("Building artifacts...");
|
|
@@ -1320,6 +1358,23 @@ var siteCommand = defineCommand8({
|
|
|
1320
1358
|
printSuccess("Site built");
|
|
1321
1359
|
printLine(` ${kleur6.bold("Output:")} ${options.outDir}`);
|
|
1322
1360
|
if (options.base) printLine(` ${kleur6.bold("Base:")} ${options.base}`);
|
|
1361
|
+
if (serveOptions) {
|
|
1362
|
+
try {
|
|
1363
|
+
const preview = await serveStaticSite({
|
|
1364
|
+
root: options.outDir,
|
|
1365
|
+
base: options.base,
|
|
1366
|
+
...serveOptions
|
|
1367
|
+
});
|
|
1368
|
+
printLine(` ${kleur6.bold("Serving:")} ${preview.url}`);
|
|
1369
|
+
printLine();
|
|
1370
|
+
printLine(kleur6.dim("Press Ctrl+C to stop the preview server."));
|
|
1371
|
+
await waitForShutdown(preview);
|
|
1372
|
+
return;
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1375
|
+
return exitFailure(message);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1323
1378
|
printLine();
|
|
1324
1379
|
printLine(kleur6.dim("Deploy the contents of the output directory to any static host."));
|
|
1325
1380
|
}
|
|
@@ -1356,6 +1411,161 @@ function runAstroBuild(astroBin, webDir, env, json) {
|
|
|
1356
1411
|
child.on("error", () => resolvePromise(1));
|
|
1357
1412
|
});
|
|
1358
1413
|
}
|
|
1414
|
+
var mimeTypes = {
|
|
1415
|
+
".css": "text/css; charset=utf-8",
|
|
1416
|
+
".html": "text/html; charset=utf-8",
|
|
1417
|
+
".ico": "image/x-icon",
|
|
1418
|
+
".jpeg": "image/jpeg",
|
|
1419
|
+
".jpg": "image/jpeg",
|
|
1420
|
+
".js": "text/javascript; charset=utf-8",
|
|
1421
|
+
".json": "application/json; charset=utf-8",
|
|
1422
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
1423
|
+
".png": "image/png",
|
|
1424
|
+
".svg": "image/svg+xml",
|
|
1425
|
+
".txt": "text/plain; charset=utf-8",
|
|
1426
|
+
".wasm": "application/wasm",
|
|
1427
|
+
".webp": "image/webp"
|
|
1428
|
+
};
|
|
1429
|
+
function previewUrl(host, port, base) {
|
|
1430
|
+
const basePath = normalizeBasePath(base);
|
|
1431
|
+
const path = basePath === "/" ? "/" : `${basePath}/`;
|
|
1432
|
+
return `http://${host}:${port}${path}`;
|
|
1433
|
+
}
|
|
1434
|
+
async function serveStaticSite(options) {
|
|
1435
|
+
const root = resolve3(options.root);
|
|
1436
|
+
const basePath = normalizeBasePath(options.base);
|
|
1437
|
+
const server = createServer(async (request, response) => {
|
|
1438
|
+
const result = await resolveStaticSiteResponse({
|
|
1439
|
+
root,
|
|
1440
|
+
base: basePath,
|
|
1441
|
+
method: request.method ?? "GET",
|
|
1442
|
+
url: request.url ?? "/"
|
|
1443
|
+
});
|
|
1444
|
+
if (!result.path) {
|
|
1445
|
+
if (request.method === "HEAD") {
|
|
1446
|
+
response.statusCode = result.status;
|
|
1447
|
+
response.setHeader("Content-Type", result.contentType);
|
|
1448
|
+
response.end();
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
sendText(response, result.status, result.text ?? "Not Found");
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
response.statusCode = result.status;
|
|
1455
|
+
response.setHeader("Content-Type", result.contentType);
|
|
1456
|
+
if (result.contentLength !== void 0) {
|
|
1457
|
+
response.setHeader("Content-Length", String(result.contentLength));
|
|
1458
|
+
}
|
|
1459
|
+
if (request.method === "HEAD") {
|
|
1460
|
+
response.end();
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
createReadStream(result.path).pipe(response);
|
|
1464
|
+
});
|
|
1465
|
+
await new Promise((resolvePromise, reject) => {
|
|
1466
|
+
server.once("error", reject);
|
|
1467
|
+
server.listen(options.port, options.host, () => {
|
|
1468
|
+
server.off("error", reject);
|
|
1469
|
+
resolvePromise();
|
|
1470
|
+
});
|
|
1471
|
+
}).catch((error) => {
|
|
1472
|
+
if (isAddressInUse(error)) {
|
|
1473
|
+
throw new Error(
|
|
1474
|
+
`Port ${options.port} is already in use on ${options.host}. Try a different --port.`
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
throw error;
|
|
1478
|
+
});
|
|
1479
|
+
const address = server.address();
|
|
1480
|
+
const port = typeof address === "object" && address ? address.port : options.port;
|
|
1481
|
+
return {
|
|
1482
|
+
server,
|
|
1483
|
+
url: previewUrl(options.host, port, options.base),
|
|
1484
|
+
close: () => new Promise((resolvePromise, reject) => {
|
|
1485
|
+
server.close((error) => error ? reject(error) : resolvePromise());
|
|
1486
|
+
})
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
async function resolveStaticSiteResponse(options) {
|
|
1490
|
+
if (options.method !== "GET" && options.method !== "HEAD") {
|
|
1491
|
+
return {
|
|
1492
|
+
status: 405,
|
|
1493
|
+
contentType: "text/plain; charset=utf-8",
|
|
1494
|
+
text: "Method Not Allowed"
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
const root = resolve3(options.root);
|
|
1498
|
+
const basePath = normalizeBasePath(options.base);
|
|
1499
|
+
const filePath = await resolveRequestPath(root, basePath, options.url);
|
|
1500
|
+
if (!filePath) {
|
|
1501
|
+
return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
|
|
1502
|
+
}
|
|
1503
|
+
try {
|
|
1504
|
+
const fileStat = await stat(filePath);
|
|
1505
|
+
const finalPath = fileStat.isDirectory() ? join6(filePath, "index.html") : filePath;
|
|
1506
|
+
const finalStat = fileStat.isDirectory() ? await stat(finalPath) : fileStat;
|
|
1507
|
+
if (!finalStat.isFile()) {
|
|
1508
|
+
return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
status: 200,
|
|
1512
|
+
contentType: mimeTypes[extname(finalPath)] ?? "application/octet-stream",
|
|
1513
|
+
contentLength: finalStat.size,
|
|
1514
|
+
path: finalPath
|
|
1515
|
+
};
|
|
1516
|
+
} catch {
|
|
1517
|
+
return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
async function resolveRequestPath(root, basePath, requestUrl) {
|
|
1521
|
+
let pathname;
|
|
1522
|
+
try {
|
|
1523
|
+
pathname = decodeURIComponent(new URL(requestUrl, "http://localhost").pathname);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return void 0;
|
|
1526
|
+
}
|
|
1527
|
+
if (basePath !== "/") {
|
|
1528
|
+
if (pathname === basePath) {
|
|
1529
|
+
pathname = "/";
|
|
1530
|
+
} else if (pathname.startsWith(`${basePath}/`)) {
|
|
1531
|
+
pathname = pathname.slice(basePath.length);
|
|
1532
|
+
} else {
|
|
1533
|
+
return void 0;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
1537
|
+
const candidate = resolve3(root, `.${normalized}`);
|
|
1538
|
+
const rel = relative2(root, candidate);
|
|
1539
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || rel === "" || rel.startsWith("/") || rel === ".") {
|
|
1540
|
+
return rel === "." || rel === "" ? root : void 0;
|
|
1541
|
+
}
|
|
1542
|
+
return candidate;
|
|
1543
|
+
}
|
|
1544
|
+
function sendText(response, status, text) {
|
|
1545
|
+
response.statusCode = status;
|
|
1546
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
1547
|
+
response.end(text);
|
|
1548
|
+
}
|
|
1549
|
+
function normalizeBasePath(base) {
|
|
1550
|
+
if (!base || base === "/") return "/";
|
|
1551
|
+
const withLeading = base.startsWith("/") ? base : `/${base}`;
|
|
1552
|
+
return withLeading.replace(/\/+$/, "") || "/";
|
|
1553
|
+
}
|
|
1554
|
+
function waitForShutdown(preview) {
|
|
1555
|
+
return new Promise((resolvePromise) => {
|
|
1556
|
+
const shutdown = async () => {
|
|
1557
|
+
process.off("SIGINT", shutdown);
|
|
1558
|
+
process.off("SIGTERM", shutdown);
|
|
1559
|
+
await preview.close();
|
|
1560
|
+
resolvePromise();
|
|
1561
|
+
};
|
|
1562
|
+
process.once("SIGINT", shutdown);
|
|
1563
|
+
process.once("SIGTERM", shutdown);
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
function isAddressInUse(error) {
|
|
1567
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
1568
|
+
}
|
|
1359
1569
|
|
|
1360
1570
|
// src/commands/validate.ts
|
|
1361
1571
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|