@orieljs/http 0.1.0-alpha.10 → 0.1.0-alpha.11
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.d.ts +64 -2
- package/dist/index.js +229 -49
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -412,6 +412,59 @@ declare function stackFrames(error: Error): StackFrame[];
|
|
|
412
412
|
/** The APP_DEBUG page: class, message, request, a source excerpt of the first app frame, the stack and causes. */
|
|
413
413
|
declare function debugPage(error: unknown, method: string, url: string): Promise<string>;
|
|
414
414
|
|
|
415
|
+
interface ExportOptions {
|
|
416
|
+
/** The folder the site is written to. */
|
|
417
|
+
output: string;
|
|
418
|
+
/** The URL the pages are requested at (the app's `app.url`); only its origin's links are followed. */
|
|
419
|
+
baseUrl: string;
|
|
420
|
+
/** Follow every same-site link from `/` (and from `paths`). */
|
|
421
|
+
crawl: boolean;
|
|
422
|
+
/** Paths exported besides `/` (pages nothing links to, a feed, …). */
|
|
423
|
+
paths: string[];
|
|
424
|
+
/** Paths left out; `*` matches anything (`/drafts/*`). */
|
|
425
|
+
exclude: string[];
|
|
426
|
+
/** `/docs/routing` → `docs/routing.html` (`'html'`) or `docs/routing/index.html` (`'directory'`). */
|
|
427
|
+
fileNames: 'html' | 'directory';
|
|
428
|
+
/** Copied into the output first (the built assets, images); false copies nothing. */
|
|
429
|
+
publicPath: string | false;
|
|
430
|
+
}
|
|
431
|
+
interface ExportResult {
|
|
432
|
+
/** The paths written, in the order they were found. */
|
|
433
|
+
pages: string[];
|
|
434
|
+
/** Redirects met while following links: listed, not written. */
|
|
435
|
+
redirects: Array<{
|
|
436
|
+
path: string;
|
|
437
|
+
location: string;
|
|
438
|
+
}>;
|
|
439
|
+
/** Whether the app's 404 page was written as `404.html`. */
|
|
440
|
+
notFoundPage: boolean;
|
|
441
|
+
}
|
|
442
|
+
/** A page answered with an error: the export stops, and the last export stays as it was. */
|
|
443
|
+
declare class ExportError extends Error {
|
|
444
|
+
readonly path: string;
|
|
445
|
+
readonly status: number;
|
|
446
|
+
readonly name = "ExportError";
|
|
447
|
+
constructor(path: string, status: number);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Exports an app's pages as files: requests `/` and `paths` inside the process (as `serve` handles a request),
|
|
451
|
+
* follows same-site links, and writes every page into `output`, after a copy of `publicPath`. The site is written
|
|
452
|
+
* into a temporary folder beside `output` and swapped in at the end, so a failed export leaves the last one intact.
|
|
453
|
+
*/
|
|
454
|
+
declare function exportSite(app: Application, options: ExportOptions): Promise<ExportResult>;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The file a page is written to, relative to the output folder: `/` → `index.html`, `/docs/routing` →
|
|
458
|
+
* `docs/routing.html` (or `docs/routing/index.html`). A path with an extension (`/feed.xml`) keeps its name.
|
|
459
|
+
*/
|
|
460
|
+
declare function exportFileName(path: string, style: 'html' | 'directory'): string;
|
|
461
|
+
/**
|
|
462
|
+
* The paths a page links to on the same site (the origin of `baseUrl`): resolved against the page's URL, decoded,
|
|
463
|
+
* without query, hash or a trailing slash, each once, in document order. Links to other origins, `mailto:`, `tel:`,
|
|
464
|
+
* `javascript:` and to an anchor on the same page are left out.
|
|
465
|
+
*/
|
|
466
|
+
declare function sameSiteLinks(html: string, pageUrl: string, baseUrl: string): string[];
|
|
467
|
+
|
|
415
468
|
type Runtime = 'node' | 'bun';
|
|
416
469
|
interface ServeOptions {
|
|
417
470
|
/** Default: PORT, then SERVER_PORT, then 8000 (Laravel's serve). 0 picks a free port. */
|
|
@@ -472,6 +525,15 @@ declare function serveBun(app: Application, options: ResolvedServeOptions): Prom
|
|
|
472
525
|
declare function toWebRequest(req: IncomingMessage, res: ServerResponse, fallbackHost: string): Request;
|
|
473
526
|
/** Web Response → ServerResponse, streaming the body with backpressure. Several Set-Cookie headers stay separate. */
|
|
474
527
|
declare function writeResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
528
|
+
/**
|
|
529
|
+
* A node:http request listener for the app: a public file when one matches, else the app's answer, then the
|
|
530
|
+
* terminable middleware. `serve` uses it, and so does any host that hands Oriel Node's request and response (Vercel's
|
|
531
|
+
* functions). `fallbackHost` is the Host used when a request has none.
|
|
532
|
+
*/
|
|
533
|
+
declare function nodeRequestHandler(app: Application, options: {
|
|
534
|
+
publicPath?: string;
|
|
535
|
+
fallbackHost: string;
|
|
536
|
+
}): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
475
537
|
declare function serveNode(app: Application, options: ResolvedServeOptions): Promise<RunningServer>;
|
|
476
538
|
|
|
477
539
|
declare function detectRuntime(): Runtime;
|
|
@@ -549,7 +611,7 @@ interface HttpCommandOptions {
|
|
|
549
611
|
/** Replaces `serve --watch`'s restarting server (tests). Resolves the command's exit code. */
|
|
550
612
|
watch?: (options: WatchedServerOptions) => Promise<number>;
|
|
551
613
|
}
|
|
552
|
-
/** Registers `serve`, `route:list` and `install:api` as closure commands (PC1). */
|
|
614
|
+
/** Registers `serve`, `route:list`, `export` and `install:api` as closure commands (PC1). */
|
|
553
615
|
declare function registerHttpCommands(kernel: ConsoleKernel, app: Application, options?: HttpCommandOptions): void;
|
|
554
616
|
|
|
555
617
|
/**
|
|
@@ -565,4 +627,4 @@ declare class HttpServiceProvider extends ServiceProvider {
|
|
|
565
627
|
boot(): void;
|
|
566
628
|
}
|
|
567
629
|
|
|
568
|
-
export { API_ROUTES_STUB, ConvertEmptyStringsToNull, type ErrorClass, ErrorHandler, ExceptionsConfiguration, FormUploadedFile, type HttpCommandOptions, HttpException, HttpRedirectResponse, HttpRedirector, HttpResponseException, HttpResponseFactory, HttpServerError, HttpServiceProvider, IncomingRequest, type IncomingRequestOptions, type JsonWhenCallback, Kernel, MiddlewareConfiguration, type RenderCallback, type ReportCallback, type ResolvedServeOptions, type RouteListFilters, type RouteListRow, type RunningServer, type Runtime, type ServeOptions, type StackFrame, TrimStrings, type TypedRedirector, abort, abortIf, abortUnless, addApiRouting, applyExceptionCallbacks, applyMiddlewareCallbacks, back, bunRuntime, clientAddress, contentDisposition, currentRequest, debugPage, detectRuntime, escapeHtml, exceedsJsonDepth, extensionFromMime, filterRouteList, formatRouteList, fromEntries, ipMatches, isBlank, mergeRecursive, mimeFromExtension, minimalPage, normalizeLists, optionalCookieJar, optionalSession, parseBracketKey, readBody, redirect, registerHttpCommands, request, requestScope, response, route, routeListRows, sanitize, serve, serveBun, serveNode, servePublicFile, setClientAddress, stackFrames, statusFor, statusText, summarizeValidation, toWebRequest, transformLeaves, url, writeResponse };
|
|
630
|
+
export { API_ROUTES_STUB, ConvertEmptyStringsToNull, type ErrorClass, ErrorHandler, ExceptionsConfiguration, ExportError, type ExportOptions, type ExportResult, FormUploadedFile, type HttpCommandOptions, HttpException, HttpRedirectResponse, HttpRedirector, HttpResponseException, HttpResponseFactory, HttpServerError, HttpServiceProvider, IncomingRequest, type IncomingRequestOptions, type JsonWhenCallback, Kernel, MiddlewareConfiguration, type RenderCallback, type ReportCallback, type ResolvedServeOptions, type RouteListFilters, type RouteListRow, type RunningServer, type Runtime, type ServeOptions, type StackFrame, TrimStrings, type TypedRedirector, abort, abortIf, abortUnless, addApiRouting, applyExceptionCallbacks, applyMiddlewareCallbacks, back, bunRuntime, clientAddress, contentDisposition, currentRequest, debugPage, detectRuntime, escapeHtml, exceedsJsonDepth, exportFileName, exportSite, extensionFromMime, filterRouteList, formatRouteList, fromEntries, ipMatches, isBlank, mergeRecursive, mimeFromExtension, minimalPage, nodeRequestHandler, normalizeLists, optionalCookieJar, optionalSession, parseBracketKey, readBody, redirect, registerHttpCommands, request, requestScope, response, route, routeListRows, sameSiteLinks, sanitize, serve, serveBun, serveNode, servePublicFile, setClientAddress, stackFrames, statusFor, statusText, summarizeValidation, toWebRequest, transformLeaves, url, writeResponse };
|
package/dist/index.js
CHANGED
|
@@ -1562,6 +1562,11 @@ function applyExceptionCallbacks(app, handler) {
|
|
|
1562
1562
|
for (const callback of app.make(ExceptionCallbacks)) callback(configurator);
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
|
+
// export/export.ts
|
|
1566
|
+
import { existsSync, lstatSync, readdirSync, statSync } from "fs";
|
|
1567
|
+
import { cp, mkdir, mkdtemp, rename, rm, writeFile } from "fs/promises";
|
|
1568
|
+
import { basename as basename2, dirname, join, resolve as resolve2 } from "path";
|
|
1569
|
+
|
|
1565
1570
|
// server/types.ts
|
|
1566
1571
|
import { HttpKernel } from "@orieljs/contracts";
|
|
1567
1572
|
|
|
@@ -1631,6 +1636,136 @@ function exitOnSignals(close, closed, app) {
|
|
|
1631
1636
|
}
|
|
1632
1637
|
var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
1633
1638
|
|
|
1639
|
+
// export/paths.ts
|
|
1640
|
+
import { posix } from "path";
|
|
1641
|
+
var ANCHOR = /<a\b[^>]*?\shref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/gi;
|
|
1642
|
+
var decode = (path) => {
|
|
1643
|
+
try {
|
|
1644
|
+
return decodeURIComponent(path);
|
|
1645
|
+
} catch {
|
|
1646
|
+
return path;
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
function exportFileName(path, style) {
|
|
1650
|
+
const trimmed = decode(path).replace(/^\/+|\/+$/g, "");
|
|
1651
|
+
if (trimmed === "") return "index.html";
|
|
1652
|
+
if (posix.extname(trimmed) !== "") return trimmed;
|
|
1653
|
+
return style === "directory" ? `${trimmed}/index.html` : `${trimmed}.html`;
|
|
1654
|
+
}
|
|
1655
|
+
function sameSiteLinks(html, pageUrl, baseUrl) {
|
|
1656
|
+
const origin = new URL(baseUrl).origin;
|
|
1657
|
+
const found = [];
|
|
1658
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1659
|
+
for (const match of html.matchAll(ANCHOR)) {
|
|
1660
|
+
const href = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
1661
|
+
if (href === "" || href.startsWith("#")) continue;
|
|
1662
|
+
const path = sitePath(href, pageUrl, origin);
|
|
1663
|
+
if (path === void 0 || seen.has(path)) continue;
|
|
1664
|
+
seen.add(path);
|
|
1665
|
+
found.push(path);
|
|
1666
|
+
}
|
|
1667
|
+
return found;
|
|
1668
|
+
}
|
|
1669
|
+
function sitePath(href, pageUrl, origin) {
|
|
1670
|
+
let url2;
|
|
1671
|
+
try {
|
|
1672
|
+
url2 = new URL(href, pageUrl);
|
|
1673
|
+
} catch {
|
|
1674
|
+
return void 0;
|
|
1675
|
+
}
|
|
1676
|
+
if (url2.origin !== new URL(origin).origin) return void 0;
|
|
1677
|
+
return decode(url2.pathname.length > 1 ? url2.pathname.replace(/\/+$/, "") : url2.pathname);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// export/export.ts
|
|
1681
|
+
var ExportError = class extends Error {
|
|
1682
|
+
constructor(path, status) {
|
|
1683
|
+
super(`[${path}] answered ${status}.`);
|
|
1684
|
+
this.path = path;
|
|
1685
|
+
this.status = status;
|
|
1686
|
+
}
|
|
1687
|
+
path;
|
|
1688
|
+
status;
|
|
1689
|
+
name = "ExportError";
|
|
1690
|
+
};
|
|
1691
|
+
var MARKER = ".oriel-export";
|
|
1692
|
+
var NOT_FOUND_PATH = "/__oriel-export-not-found__";
|
|
1693
|
+
async function exportSite(app, options) {
|
|
1694
|
+
const output = resolve2(options.output);
|
|
1695
|
+
const publicPath = options.publicPath === false ? void 0 : resolve2(options.publicPath);
|
|
1696
|
+
if (publicPath !== void 0 && existsSync(join(publicPath, "hot"))) {
|
|
1697
|
+
throw new Error("The Vite dev server is running (public/hot exists): stop `npm run dev`, build the assets with `npm run build`, then export.");
|
|
1698
|
+
}
|
|
1699
|
+
assertReplaceable(output);
|
|
1700
|
+
const staging = await mkdtemp(join(dirname(output), `.${basename2(output)}-`));
|
|
1701
|
+
try {
|
|
1702
|
+
if (publicPath !== void 0 && existsSync(publicPath)) {
|
|
1703
|
+
await cp(publicPath, staging, { recursive: true, filter: (source) => basename2(source) !== "hot" && !lstatSync(source).isSymbolicLink() });
|
|
1704
|
+
}
|
|
1705
|
+
const excluded = (path) => options.exclude.some((pattern) => wildcard(pattern).test(path));
|
|
1706
|
+
const isPublicFile = (path) => publicPath !== void 0 && path !== "/" && isFile(join(publicPath, path));
|
|
1707
|
+
const queue = [.../* @__PURE__ */ new Set(["/", ...options.paths.map(rooted)])].filter((path) => path === "/" || !excluded(path));
|
|
1708
|
+
const seen = new Set(queue);
|
|
1709
|
+
const pages = [];
|
|
1710
|
+
const redirects = [];
|
|
1711
|
+
while (queue.length > 0) {
|
|
1712
|
+
const path = queue.shift();
|
|
1713
|
+
const url2 = new URL(path, options.baseUrl).href;
|
|
1714
|
+
const answer = await fetchPage(app, url2);
|
|
1715
|
+
if (answer.status >= 300 && answer.status < 400) {
|
|
1716
|
+
const location = new URL(answer.headers.get("location") ?? "/", url2).href;
|
|
1717
|
+
redirects.push({ path, location });
|
|
1718
|
+
const target = sitePath(location, url2, options.baseUrl);
|
|
1719
|
+
if (target !== void 0 && !seen.has(target) && !excluded(target) && !isPublicFile(target)) {
|
|
1720
|
+
seen.add(target);
|
|
1721
|
+
queue.push(target);
|
|
1722
|
+
}
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if (answer.status >= 400) throw new ExportError(path, answer.status);
|
|
1726
|
+
await writeInto(staging, exportFileName(path, options.fileNames), answer.body);
|
|
1727
|
+
pages.push(path);
|
|
1728
|
+
if (!options.crawl || !isHtml(answer.headers)) continue;
|
|
1729
|
+
for (const link of sameSiteLinks(answer.body.toString("utf8"), url2, options.baseUrl)) {
|
|
1730
|
+
if (seen.has(link) || excluded(link) || isPublicFile(link)) continue;
|
|
1731
|
+
seen.add(link);
|
|
1732
|
+
queue.push(link);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const notFound = await fetchPage(app, new URL(NOT_FOUND_PATH, options.baseUrl).href);
|
|
1736
|
+
const notFoundPage = notFound.status === 404 && isHtml(notFound.headers);
|
|
1737
|
+
if (notFoundPage) await writeInto(staging, "404.html", notFound.body);
|
|
1738
|
+
await writeFile(join(staging, MARKER), "");
|
|
1739
|
+
await rm(output, { recursive: true, force: true });
|
|
1740
|
+
await rename(staging, output);
|
|
1741
|
+
return { pages, redirects, notFoundPage };
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
await rm(staging, { recursive: true, force: true });
|
|
1744
|
+
throw error;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
function assertReplaceable(output) {
|
|
1748
|
+
if (!existsSync(output)) return;
|
|
1749
|
+
if (statSync(output).isDirectory() && (existsSync(join(output, MARKER)) || readdirSync(output).length === 0)) return;
|
|
1750
|
+
throw new Error(`[${output}] exists and wasn't made by oriel export: remove it, or export somewhere else with --output.`);
|
|
1751
|
+
}
|
|
1752
|
+
async function fetchPage(app, url2) {
|
|
1753
|
+
const request2 = new Request(url2, { headers: { accept: "text/html" } });
|
|
1754
|
+
const response2 = await handleSafely(app, request2);
|
|
1755
|
+
const body = Buffer.from(await response2.arrayBuffer());
|
|
1756
|
+
await terminateRequest(app, request2, response2);
|
|
1757
|
+
return { status: response2.status, headers: response2.headers, body };
|
|
1758
|
+
}
|
|
1759
|
+
async function writeInto(folder, file, body) {
|
|
1760
|
+
const path = join(folder, file);
|
|
1761
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1762
|
+
await writeFile(path, body);
|
|
1763
|
+
}
|
|
1764
|
+
var isHtml = (headers) => (headers.get("content-type") ?? "").includes("text/html");
|
|
1765
|
+
var rooted = (path) => path.startsWith("/") ? path : `/${path}`;
|
|
1766
|
+
var isFile = (path) => existsSync(path) && statSync(path).isFile();
|
|
1767
|
+
var wildcard = (pattern) => new RegExp(`^${rooted(pattern).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
|
1768
|
+
|
|
1634
1769
|
// server/bun.ts
|
|
1635
1770
|
function bunRuntime() {
|
|
1636
1771
|
return globalThis.Bun;
|
|
@@ -1639,7 +1774,7 @@ async function serveBun(app, options) {
|
|
|
1639
1774
|
const bun = bunRuntime();
|
|
1640
1775
|
if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
|
|
1641
1776
|
let markClosed = () => void 0;
|
|
1642
|
-
const closed = new Promise((
|
|
1777
|
+
const closed = new Promise((resolve4) => markClosed = resolve4);
|
|
1643
1778
|
const server = bun.serve({
|
|
1644
1779
|
port: options.port,
|
|
1645
1780
|
hostname: options.hostname,
|
|
@@ -1657,8 +1792,8 @@ async function serveBun(app, options) {
|
|
|
1657
1792
|
let closing;
|
|
1658
1793
|
const close = () => closing ??= (async () => {
|
|
1659
1794
|
let timer;
|
|
1660
|
-
const forced = new Promise((
|
|
1661
|
-
timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(
|
|
1795
|
+
const forced = new Promise((resolve4) => {
|
|
1796
|
+
timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve4), options.shutdownTimeout);
|
|
1662
1797
|
});
|
|
1663
1798
|
await Promise.race([Promise.resolve(server.stop(false)), forced]);
|
|
1664
1799
|
clearTimeout(timer);
|
|
@@ -1761,40 +1896,44 @@ async function writeResponse(res, response2) {
|
|
|
1761
1896
|
}
|
|
1762
1897
|
await pipeline(Readable3.fromWeb(response2.body), res);
|
|
1763
1898
|
}
|
|
1899
|
+
function nodeRequestHandler(app, options) {
|
|
1900
|
+
return async (req, res) => {
|
|
1901
|
+
let request2;
|
|
1902
|
+
try {
|
|
1903
|
+
request2 = toWebRequest(req, res, options.fallbackHost);
|
|
1904
|
+
} catch {
|
|
1905
|
+
res.statusCode = 400;
|
|
1906
|
+
res.end("Bad Request");
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
setClientAddress(request2, req.socket.remoteAddress);
|
|
1910
|
+
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
1911
|
+
try {
|
|
1912
|
+
await writeResponse(res, response2);
|
|
1913
|
+
} catch {
|
|
1914
|
+
res.destroy();
|
|
1915
|
+
}
|
|
1916
|
+
await terminateRequest(app, request2, response2);
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1764
1919
|
async function serveNode(app, options) {
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
let request2;
|
|
1769
|
-
try {
|
|
1770
|
-
request2 = toWebRequest(req, res, fallbackHost);
|
|
1771
|
-
} catch {
|
|
1772
|
-
res.statusCode = 400;
|
|
1773
|
-
res.end("Bad Request");
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
setClientAddress(request2, req.socket.remoteAddress);
|
|
1777
|
-
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
1778
|
-
try {
|
|
1779
|
-
await writeResponse(res, response2);
|
|
1780
|
-
} catch {
|
|
1781
|
-
res.destroy();
|
|
1782
|
-
}
|
|
1783
|
-
await terminateRequest(app, request2, response2);
|
|
1784
|
-
})();
|
|
1920
|
+
const handle = nodeRequestHandler(app, {
|
|
1921
|
+
...options.publicPath !== void 0 && { publicPath: options.publicPath },
|
|
1922
|
+
fallbackHost: `${displayHost(options.hostname)}:${options.port}`
|
|
1785
1923
|
});
|
|
1786
|
-
|
|
1924
|
+
const server = createServer((req, res) => void handle(req, res));
|
|
1925
|
+
await new Promise((resolve4, reject) => {
|
|
1787
1926
|
server.once("error", reject);
|
|
1788
1927
|
server.listen(options.port, options.hostname, () => {
|
|
1789
1928
|
server.off("error", reject);
|
|
1790
|
-
|
|
1929
|
+
resolve4();
|
|
1791
1930
|
});
|
|
1792
1931
|
});
|
|
1793
1932
|
const address = server.address();
|
|
1794
|
-
const closed = new Promise((
|
|
1933
|
+
const closed = new Promise((resolve4) => server.once("close", () => resolve4()));
|
|
1795
1934
|
let closing;
|
|
1796
1935
|
const close = () => closing ??= (async () => {
|
|
1797
|
-
const done = new Promise((
|
|
1936
|
+
const done = new Promise((resolve4) => server.close(() => resolve4()));
|
|
1798
1937
|
server.closeIdleConnections();
|
|
1799
1938
|
const idle = setInterval(() => server.closeIdleConnections(), 50);
|
|
1800
1939
|
const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
|
|
@@ -1834,16 +1973,17 @@ async function serve(app, options = {}) {
|
|
|
1834
1973
|
}
|
|
1835
1974
|
|
|
1836
1975
|
// commands.ts
|
|
1837
|
-
import { existsSync as
|
|
1838
|
-
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
1839
|
-
import { dirname } from "path";
|
|
1976
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1977
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
1978
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
1840
1979
|
import {
|
|
1980
|
+
ConfigRepository as ConfigRepository4,
|
|
1841
1981
|
Router as Router3
|
|
1842
1982
|
} from "@orieljs/contracts";
|
|
1843
1983
|
|
|
1844
1984
|
// watch/environment.ts
|
|
1845
|
-
import { existsSync, readFileSync } from "fs";
|
|
1846
|
-
import { join } from "path";
|
|
1985
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1986
|
+
import { join as join2 } from "path";
|
|
1847
1987
|
import * as util from "util";
|
|
1848
1988
|
var launchEnvironment = Object.freeze({ ...process.env });
|
|
1849
1989
|
var BUN_ENV_FILES = [
|
|
@@ -1865,8 +2005,8 @@ function serverEnvironment(options = {}) {
|
|
|
1865
2005
|
if (typeof parseEnv2 !== "function") return env;
|
|
1866
2006
|
const cwd = options.cwd ?? process.cwd();
|
|
1867
2007
|
for (const name of BUN_ENV_FILES) {
|
|
1868
|
-
const file =
|
|
1869
|
-
if (!
|
|
2008
|
+
const file = join2(cwd, name);
|
|
2009
|
+
if (!existsSync2(file)) continue;
|
|
1870
2010
|
let values;
|
|
1871
2011
|
try {
|
|
1872
2012
|
values = parseEnv2(readFileSync(file, "utf8"));
|
|
@@ -1882,8 +2022,8 @@ function serverEnvironment(options = {}) {
|
|
|
1882
2022
|
}
|
|
1883
2023
|
|
|
1884
2024
|
// watch/files.ts
|
|
1885
|
-
import { existsSync as
|
|
1886
|
-
import { join as
|
|
2025
|
+
import { existsSync as existsSync3, statSync as statSync2, watch as watchPath } from "fs";
|
|
2026
|
+
import { join as join3 } from "path";
|
|
1887
2027
|
var WATCHED_FOLDERS = ["app", "bootstrap", "config", "lang", "resources/lang", "resources/views", "routes"];
|
|
1888
2028
|
var WATCHED_ROOT_FILES = ["package.json", "tsconfig.json"];
|
|
1889
2029
|
var IGNORED_FOLDERS = ["bootstrap/ssr", "bootstrap/cache"];
|
|
@@ -1914,7 +2054,7 @@ function watchFiles(basePath, onChange, options = {}) {
|
|
|
1914
2054
|
const reportedLate = (path) => {
|
|
1915
2055
|
if (Date.now() - startedAt >= settle) return false;
|
|
1916
2056
|
try {
|
|
1917
|
-
const modified =
|
|
2057
|
+
const modified = statSync2(join3(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
|
|
1918
2058
|
return modified === void 0 || modified < startedAt + 1;
|
|
1919
2059
|
} catch {
|
|
1920
2060
|
return false;
|
|
@@ -1933,7 +2073,7 @@ function watchFiles(basePath, onChange, options = {}) {
|
|
|
1933
2073
|
const handles = [];
|
|
1934
2074
|
const start = (folder, recursive, toPath) => {
|
|
1935
2075
|
try {
|
|
1936
|
-
const handle = watch(folder === "." ? basePath :
|
|
2076
|
+
const handle = watch(folder === "." ? basePath : join3(basePath, folder), { recursive }, (_event, file) => {
|
|
1937
2077
|
const path = toPath(file);
|
|
1938
2078
|
if (path !== void 0) record(path);
|
|
1939
2079
|
});
|
|
@@ -1945,7 +2085,7 @@ function watchFiles(basePath, onChange, options = {}) {
|
|
|
1945
2085
|
};
|
|
1946
2086
|
start(".", false, (file) => file === null ? void 0 : toSlashes(file));
|
|
1947
2087
|
for (const folder of WATCHED_FOLDERS) {
|
|
1948
|
-
if (
|
|
2088
|
+
if (existsSync3(join3(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
|
|
1949
2089
|
}
|
|
1950
2090
|
return {
|
|
1951
2091
|
close() {
|
|
@@ -1959,13 +2099,13 @@ function watchFiles(basePath, onChange, options = {}) {
|
|
|
1959
2099
|
// watch/port.ts
|
|
1960
2100
|
import { createServer as createServer2 } from "net";
|
|
1961
2101
|
function claimPort(hostname, port) {
|
|
1962
|
-
return new Promise((
|
|
2102
|
+
return new Promise((resolve4, reject) => {
|
|
1963
2103
|
const server = createServer2();
|
|
1964
2104
|
server.once("error", reject);
|
|
1965
2105
|
try {
|
|
1966
2106
|
server.listen({ port, host: hostname, exclusive: true }, () => {
|
|
1967
2107
|
const { port: claimed } = server.address();
|
|
1968
|
-
server.close(() =>
|
|
2108
|
+
server.close(() => resolve4(claimed));
|
|
1969
2109
|
});
|
|
1970
2110
|
} catch (error) {
|
|
1971
2111
|
reject(error);
|
|
@@ -1996,7 +2136,7 @@ var WatchedServer = class {
|
|
|
1996
2136
|
};
|
|
1997
2137
|
/** Starts the server and keeps it current until stopped. Resolves 0 once the last server has exited. */
|
|
1998
2138
|
run() {
|
|
1999
|
-
const done = new Promise((
|
|
2139
|
+
const done = new Promise((resolve4) => this.finish = resolve4);
|
|
2000
2140
|
const stopListening = this.listenForSignals();
|
|
2001
2141
|
const watcher = this.options.watch((files) => void this.changed(files));
|
|
2002
2142
|
this.start();
|
|
@@ -2035,14 +2175,14 @@ var WatchedServer = class {
|
|
|
2035
2175
|
stdio: ["ignore", "inherit", "inherit"]
|
|
2036
2176
|
});
|
|
2037
2177
|
const running = { child, stopping: false, killed: false, exited: Promise.resolve() };
|
|
2038
|
-
running.exited = new Promise((
|
|
2178
|
+
running.exited = new Promise((resolve4) => {
|
|
2039
2179
|
let settled = false;
|
|
2040
2180
|
const settle = (report) => {
|
|
2041
2181
|
if (settled) return;
|
|
2042
2182
|
settled = true;
|
|
2043
2183
|
if (this.running === running) this.running = void 0;
|
|
2044
2184
|
if (!running.stopping && !this.stopping) this.options.error(`${report}. Waiting for changes before restarting...`);
|
|
2045
|
-
|
|
2185
|
+
resolve4();
|
|
2046
2186
|
};
|
|
2047
2187
|
child.once("exit", (code, signal) => settle(signal !== null ? `The server stopped (signal ${signal})` : `The server stopped (exit code ${code})`));
|
|
2048
2188
|
child.once("error", (error) => {
|
|
@@ -2180,6 +2320,38 @@ async function serveWatching(app, out, port, run) {
|
|
|
2180
2320
|
error: (message) => out.error(message)
|
|
2181
2321
|
});
|
|
2182
2322
|
}
|
|
2323
|
+
async function exportCommand(app, out) {
|
|
2324
|
+
const config = (key, fallback) => app.bound(ConfigRepository4) ? app.make(ConfigRepository4).get(key, fallback) : fallback;
|
|
2325
|
+
const fileNames = config("export.file_names", "html");
|
|
2326
|
+
if (fileNames !== "html" && fileNames !== "directory") {
|
|
2327
|
+
out.error(`export.file_names is "html" or "directory", not "${fileNames}".`);
|
|
2328
|
+
return 1;
|
|
2329
|
+
}
|
|
2330
|
+
const output = out.option("output") ?? config("export.output", "dist");
|
|
2331
|
+
const baseUrl = config("app.url", "http://localhost");
|
|
2332
|
+
const started = performance.now();
|
|
2333
|
+
try {
|
|
2334
|
+
const result = await exportSite(app, {
|
|
2335
|
+
output: resolve3(app.basePath(), output),
|
|
2336
|
+
baseUrl,
|
|
2337
|
+
crawl: config("export.crawl", true),
|
|
2338
|
+
paths: config("export.paths", []),
|
|
2339
|
+
exclude: config("export.exclude", []),
|
|
2340
|
+
fileNames,
|
|
2341
|
+
publicPath: app.publicPath()
|
|
2342
|
+
});
|
|
2343
|
+
const origin = new URL(baseUrl).origin;
|
|
2344
|
+
for (const { path, location } of result.redirects) {
|
|
2345
|
+
out.line(`Redirect ${path} \u2192 ${location.startsWith(origin) ? location.slice(origin.length) || "/" : location}, not written.`);
|
|
2346
|
+
}
|
|
2347
|
+
const pages = result.pages.length === 1 ? "1 page" : `${result.pages.length} pages`;
|
|
2348
|
+
out.info(`Exported ${pages} to [${output}] in ${Math.round(performance.now() - started)} ms.`);
|
|
2349
|
+
return 0;
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
out.error(error instanceof ExportError ? error.message : error instanceof Error ? error.message : String(error));
|
|
2352
|
+
return 1;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2183
2355
|
function registerHttpCommands(kernel, app, options = {}) {
|
|
2184
2356
|
const serveApp = options.serve ?? serve;
|
|
2185
2357
|
const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
|
|
@@ -2238,25 +2410,28 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
2238
2410
|
return 0;
|
|
2239
2411
|
}
|
|
2240
2412
|
).purpose("List all registered routes");
|
|
2413
|
+
kernel.command("export {--output= : The folder to write the site to (default: config export.output, else dist)}", function() {
|
|
2414
|
+
return exportCommand(app, io(this));
|
|
2415
|
+
}).purpose("Export the application's pages as static files");
|
|
2241
2416
|
kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
|
|
2242
2417
|
const out = io(this);
|
|
2243
2418
|
const routesFile = app.basePath("routes/api.ts");
|
|
2244
|
-
if (
|
|
2419
|
+
if (existsSync4(routesFile) && !out.flag("force")) {
|
|
2245
2420
|
out.error("API routes file already exists.");
|
|
2246
2421
|
} else {
|
|
2247
|
-
await
|
|
2248
|
-
await
|
|
2422
|
+
await mkdir2(dirname2(routesFile), { recursive: true });
|
|
2423
|
+
await writeFile2(routesFile, API_ROUTES_STUB);
|
|
2249
2424
|
out.info("Published API routes file.");
|
|
2250
2425
|
}
|
|
2251
2426
|
const bootstrapFile = app.bootstrapPath("app.ts");
|
|
2252
|
-
const source =
|
|
2427
|
+
const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
|
|
2253
2428
|
const updated = source === void 0 ? void 0 : addApiRouting(source);
|
|
2254
2429
|
if (updated === void 0) {
|
|
2255
2430
|
out.warn(
|
|
2256
2431
|
"Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
|
|
2257
2432
|
);
|
|
2258
2433
|
} else if (updated !== source) {
|
|
2259
|
-
await
|
|
2434
|
+
await writeFile2(bootstrapFile, updated);
|
|
2260
2435
|
}
|
|
2261
2436
|
out.info("API scaffolding installed.");
|
|
2262
2437
|
return 0;
|
|
@@ -2301,6 +2476,7 @@ export {
|
|
|
2301
2476
|
ConvertEmptyStringsToNull,
|
|
2302
2477
|
ErrorHandler,
|
|
2303
2478
|
ExceptionsConfiguration,
|
|
2479
|
+
ExportError,
|
|
2304
2480
|
FormUploadedFile,
|
|
2305
2481
|
HttpException,
|
|
2306
2482
|
HttpRedirectResponse,
|
|
@@ -2328,6 +2504,8 @@ export {
|
|
|
2328
2504
|
detectRuntime,
|
|
2329
2505
|
escapeHtml,
|
|
2330
2506
|
exceedsJsonDepth,
|
|
2507
|
+
exportFileName,
|
|
2508
|
+
exportSite,
|
|
2331
2509
|
extensionFromMime,
|
|
2332
2510
|
filterRouteList,
|
|
2333
2511
|
formatRouteList,
|
|
@@ -2337,6 +2515,7 @@ export {
|
|
|
2337
2515
|
mergeRecursive,
|
|
2338
2516
|
mimeFromExtension,
|
|
2339
2517
|
minimalPage,
|
|
2518
|
+
nodeRequestHandler,
|
|
2340
2519
|
normalizeLists,
|
|
2341
2520
|
optionalCookieJar,
|
|
2342
2521
|
optionalSession,
|
|
@@ -2349,6 +2528,7 @@ export {
|
|
|
2349
2528
|
response,
|
|
2350
2529
|
route,
|
|
2351
2530
|
routeListRows,
|
|
2531
|
+
sameSiteLinks,
|
|
2352
2532
|
sanitize,
|
|
2353
2533
|
serve,
|
|
2354
2534
|
serveBun,
|