@orieljs/framework 0.1.0-alpha.10 → 0.1.0-alpha.12

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.
@@ -2,7 +2,7 @@ import {
2
2
  Arr,
3
3
  ClassRegistry as ClassRegistry2,
4
4
  Collection,
5
- Container,
5
+ Container as Container2,
6
6
  ContainerNotAvailableError,
7
7
  ServiceProvider,
8
8
  Str,
@@ -10,14 +10,16 @@ import {
10
10
  facade,
11
11
  fileType_exports,
12
12
  inject
13
- } from "./chunk-7ZQZYDOC.js";
13
+ } from "./chunk-MURTCTRH.js";
14
14
  import {
15
15
  Application,
16
16
  AuthFactory,
17
17
  BusDispatcher,
18
+ ClassNames,
18
19
  ClassRegistry,
19
20
  ConfigRepository,
20
21
  ConsoleKernel,
22
+ Container,
21
23
  CookieJar,
22
24
  CurrentRoute,
23
25
  DatabaseManager,
@@ -45,7 +47,7 @@ import {
45
47
  ValidationFactory,
46
48
  ViewFactory,
47
49
  token
48
- } from "./chunk-GVNLZZTN.js";
50
+ } from "./chunk-HTJKT2R2.js";
49
51
 
50
52
  // ../Config/dist/index.js
51
53
  import { existsSync, readFileSync } from "fs";
@@ -177,8 +179,137 @@ async function loadConfiguration(configPath) {
177
179
  import { join as join3 } from "path";
178
180
  import { isAbsolute, join as join4 } from "path";
179
181
  import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "fs";
182
+
183
+ // ../Obfuscate/dist/index.js
184
+ var FIRST = "abcdefghijklmnopqrstuvwxyz";
185
+ var REST = `${FIRST}0123456789`;
186
+ var CLASS_ATTRIBUTE = /^(class|o:[^\s=]*-class[^\s=]*)$/i;
187
+ var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title", "xmp", "noembed", "noframes"]);
188
+ function rewriteHtml(html, map) {
189
+ if (Object.keys(map).length === 0) return html;
190
+ return replaceHtmlClasses(html, (token2) => map[decodeEntities(token2)] ?? token2);
191
+ }
192
+ function replaceHtmlClasses(html, replace) {
193
+ let out = "";
194
+ let last = 0;
195
+ let i = 0;
196
+ while (i < html.length) {
197
+ const open = html.indexOf("<", i);
198
+ if (open === -1) break;
199
+ if (html.startsWith("<!--", open)) {
200
+ const close = html.indexOf("-->", open + 4);
201
+ i = close === -1 ? html.length : close + 3;
202
+ continue;
203
+ }
204
+ const tag = /^<([a-zA-Z][^\s/>]*)/.exec(html.slice(open, open + 64));
205
+ if (tag === null) {
206
+ i = open + 1;
207
+ continue;
208
+ }
209
+ let j = open + tag[0].length;
210
+ while (j < html.length && html[j] !== ">") {
211
+ if (/[\s/]/.test(html[j])) {
212
+ j++;
213
+ continue;
214
+ }
215
+ const nameEnd = attributeNameEnd(html, j);
216
+ const name = html.slice(j, nameEnd);
217
+ j = nameEnd;
218
+ while (/\s/.test(html[j] ?? "")) j++;
219
+ if (html[j] !== "=") continue;
220
+ j++;
221
+ while (/\s/.test(html[j] ?? "")) j++;
222
+ const quote2 = html[j] === '"' || html[j] === "'" ? html[j] : "";
223
+ const valueStart = quote2 === "" ? j : j + 1;
224
+ const valueEnd = quote2 === "" ? unquotedEnd(html, valueStart) : indexOrEnd(html, quote2, valueStart);
225
+ if (CLASS_ATTRIBUTE.test(name)) {
226
+ const value = html.slice(valueStart, valueEnd);
227
+ const renamed = value.replace(/\S+/g, replace);
228
+ if (renamed !== value) {
229
+ out += html.slice(last, valueStart) + renamed;
230
+ last = valueEnd;
231
+ }
232
+ }
233
+ j = quote2 === "" ? valueEnd : valueEnd + 1;
234
+ }
235
+ i = j + 1;
236
+ const element = tag[1].toLowerCase();
237
+ if (RAW_TEXT.has(element)) {
238
+ const close = new RegExp(`</${element}`, "gi");
239
+ close.lastIndex = i;
240
+ i = close.exec(html)?.index ?? html.length;
241
+ }
242
+ }
243
+ return out + html.slice(last);
244
+ }
245
+ function attributeNameEnd(html, i) {
246
+ let j = i + 1;
247
+ while (j < html.length && !/[\s/>=]/.test(html[j])) j++;
248
+ return j;
249
+ }
250
+ function unquotedEnd(html, i) {
251
+ let j = i;
252
+ while (j < html.length && !/[\s>]/.test(html[j])) j++;
253
+ return j;
254
+ }
255
+ function indexOrEnd(html, search, from) {
256
+ const index = html.indexOf(search, from);
257
+ return index === -1 ? html.length : index;
258
+ }
259
+ var NAMED = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
260
+ function decodeEntities(value) {
261
+ if (!value.includes("&")) return value;
262
+ return value.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (entity, body) => {
263
+ if (body[0] === "#") return String.fromCodePoint(body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number(body.slice(1)));
264
+ return NAMED[body.toLowerCase()] ?? entity;
265
+ });
266
+ }
267
+
268
+ // ../Foundation/dist/index.js
269
+ import { existsSync as existsSync22, readFileSync as readFileSync22, statSync as statSync2 } from "fs";
180
270
  import { createHash, randomBytes } from "crypto";
181
271
  import { join as join22 } from "path";
272
+ var RAW_TEXT2 = /<(script|style|textarea|title|xmp|noembed|noframes)\b[^>]*>(?![\s\S]*<\/\1)/i;
273
+ var RenameClasses = class {
274
+ #container = inject(Container);
275
+ async handle(request2, next) {
276
+ const response2 = await next(request2);
277
+ if (response2.body === null || !/^text\/html\b/i.test(response2.headers.get("content-type") ?? "") || response2.headers.has("content-encoding")) return response2;
278
+ if (!this.#container.bound(ClassNames)) return response2;
279
+ const classNames = this.#container.make(ClassNames);
280
+ if (!classNames.active) return response2;
281
+ const headers = new Headers(response2.headers);
282
+ headers.delete("content-length");
283
+ return new Response(response2.body.pipeThrough(renamer((html) => classNames.html(html))), { status: response2.status, statusText: response2.statusText, headers });
284
+ }
285
+ };
286
+ function renamer(rename2) {
287
+ const decoder = new TextDecoder();
288
+ const encoder = new TextEncoder();
289
+ let carry = "";
290
+ return new TransformStream({
291
+ transform(chunk2, controller) {
292
+ const text = carry + decoder.decode(chunk2, { stream: true });
293
+ const cut = safeCut(text);
294
+ carry = text.slice(cut);
295
+ if (cut > 0) controller.enqueue(encoder.encode(rename2(text.slice(0, cut))));
296
+ },
297
+ flush(controller) {
298
+ const text = carry + decoder.decode();
299
+ if (text !== "") controller.enqueue(encoder.encode(rename2(text)));
300
+ }
301
+ });
302
+ }
303
+ function safeCut(text) {
304
+ let cut = text.lastIndexOf(">") + 1;
305
+ if (cut === 0) return 0;
306
+ const head = text.slice(0, cut);
307
+ const comment = head.lastIndexOf("<!--");
308
+ if (comment !== -1 && head.indexOf("-->", comment + 4) === -1) cut = comment;
309
+ const raw = RAW_TEXT2.exec(text.slice(0, cut));
310
+ if (raw !== null) cut = raw.index;
311
+ return cut;
312
+ }
182
313
  var ApplicationBuilder = class {
183
314
  constructor(app) {
184
315
  this.app = app;
@@ -223,12 +354,48 @@ var ApplicationBuilder = class {
223
354
  loadEnvironment(this.app.basePath());
224
355
  this.app.instance(ConfigRepository, new Repository(await loadConfiguration(this.app.configPath())));
225
356
  if (this.routing) this.app.instance(RoutingPaths, this.routing);
226
- this.app.instance(MiddlewareCallbacks, this.middleware);
357
+ const middleware = [(m) => void m.prepend(RenameClasses), ...this.middleware];
358
+ this.app.instance(MiddlewareCallbacks, middleware);
227
359
  this.app.instance(ExceptionCallbacks, this.exceptions);
228
360
  for (const provider of this.providers) await this.app.register(provider);
229
361
  return this.app;
230
362
  }
231
363
  };
364
+ var ClassNameMap = class {
365
+ constructor(app) {
366
+ this.app = app;
367
+ }
368
+ app;
369
+ #loaded;
370
+ /** The map, or undefined: no scrambled build, the dev server is running, or the app is running its tests. */
371
+ classes() {
372
+ if (this.#testing() || existsSync3(this.app.publicPath("hot"))) return void 0;
373
+ const file = this.app.bootstrapPath("cache/classes.json");
374
+ let modified;
375
+ try {
376
+ modified = statSync(file).mtimeMs;
377
+ } catch {
378
+ this.#loaded = void 0;
379
+ return void 0;
380
+ }
381
+ if (this.#loaded?.modified !== modified) {
382
+ const parsed = JSON.parse(readFileSync2(file, "utf8"));
383
+ this.#loaded = { modified, classes: parsed.classes ?? {} };
384
+ }
385
+ return Object.keys(this.#loaded.classes).length === 0 ? void 0 : this.#loaded.classes;
386
+ }
387
+ /** The app runs its tests: Oriel's TestCase sets APP_ENV=testing, which a test that switches app.env leaves set. */
388
+ #testing() {
389
+ return this.app.runningUnitTests() || process.env.APP_ENV === "testing";
390
+ }
391
+ get active() {
392
+ return this.classes() !== void 0;
393
+ }
394
+ html(html) {
395
+ const classes = this.classes();
396
+ return classes === void 0 ? html : rewriteHtml(html, classes);
397
+ }
398
+ };
232
399
  var ProviderBootError = class extends Error {
233
400
  constructor(provider, cause) {
234
401
  super(`Service provider [${provider}] failed to boot: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
@@ -335,11 +502,11 @@ var Vite = class {
335
502
  return this.#hotFile ?? this.app.publicPath("hot");
336
503
  }
337
504
  isRunningHot() {
338
- return existsSync3(this.hotFile());
505
+ return existsSync22(this.hotFile());
339
506
  }
340
507
  /** The dev server URL written by @orieljs/vite, or undefined when it isn't running. */
341
508
  devServerUrl() {
342
- return this.isRunningHot() ? readFileSync2(this.hotFile(), "utf8").trimEnd() : void 0;
509
+ return this.isRunningHot() ? readFileSync22(this.hotFile(), "utf8").trimEnd() : void 0;
343
510
  }
344
511
  // Tags -----------------------------------------------------------------------------------------------------
345
512
  /** Laravel's @vite([...]): preloads (styles first), then stylesheets, then scripts. */
@@ -408,25 +575,25 @@ var Vite = class {
408
575
  content(path, buildDirectory = this.#buildDirectory) {
409
576
  const chunk2 = this.#chunk(this.manifest(buildDirectory), path);
410
577
  const file = this.app.publicPath(join22(buildDirectory, chunk2.file));
411
- if (!existsSync3(file)) throw new ViteError(`Unable to locate file from Vite manifest: ${file}.`);
412
- return readFileSync2(file, "utf8");
578
+ if (!existsSync22(file)) throw new ViteError(`Unable to locate file from Vite manifest: ${file}.`);
579
+ return readFileSync22(file, "utf8");
413
580
  }
414
581
  /** md5 of the manifest (a cache-busting asset version); undefined while hot or without a build. */
415
582
  manifestHash(buildDirectory = this.#buildDirectory) {
416
583
  if (this.isRunningHot()) return void 0;
417
584
  const path = this.manifestPath(buildDirectory);
418
- return existsSync3(path) ? createHash("md5").update(readFileSync2(path)).digest("hex") : void 0;
585
+ return existsSync22(path) ? createHash("md5").update(readFileSync22(path)).digest("hex") : void 0;
419
586
  }
420
587
  manifestPath(buildDirectory = this.#buildDirectory) {
421
588
  return this.app.publicPath(join22(buildDirectory, this.#manifestFilename));
422
589
  }
423
590
  manifest(buildDirectory = this.#buildDirectory) {
424
591
  const path = this.manifestPath(buildDirectory);
425
- if (!existsSync3(path)) throw new ViteError(`Vite manifest not found at: ${path}`);
426
- const { mtimeMs } = statSync(path);
592
+ if (!existsSync22(path)) throw new ViteError(`Vite manifest not found at: ${path}`);
593
+ const { mtimeMs } = statSync2(path);
427
594
  const cached = manifests.get(path);
428
595
  if (cached !== void 0 && cached.mtimeMs === mtimeMs) return cached.manifest;
429
- const manifest = JSON.parse(readFileSync2(path, "utf8"));
596
+ const manifest = JSON.parse(readFileSync22(path, "utf8"));
430
597
  manifests.set(path, { mtimeMs, manifest });
431
598
  return manifest;
432
599
  }
@@ -521,18 +688,19 @@ function viteReactRefresh() {
521
688
  return currentVite().reactRefresh();
522
689
  }
523
690
  var SHUTDOWN_TIMEOUT_MS = 1e4;
524
- var Application2 = class _Application extends Container {
691
+ var Application2 = class _Application extends Container2 {
525
692
  constructor(base) {
526
693
  super();
527
694
  this.base = base;
528
- Container.setInstance(this);
695
+ Container2.setInstance(this);
529
696
  this.instance(Application, this);
530
697
  this.instance(_Application, this);
531
698
  this.singleton(ClassRegistry, ClassRegistry2);
532
699
  this.scoped(Vite, () => new Vite(this));
700
+ this.singleton(ClassNames, () => new ClassNameMap(this));
533
701
  }
534
702
  base;
535
- version = "0.1.0-alpha.10";
703
+ version = "0.1.0-alpha.12";
536
704
  providers = [];
537
705
  bootingCallbacks = [];
538
706
  bootedCallbacks = [];
@@ -663,7 +831,7 @@ var Application2 = class _Application extends Container {
663
831
  return "done";
664
832
  })();
665
833
  let timer;
666
- const deadline = new Promise((resolve2) => timer = setTimeout(() => resolve2("timeout"), SHUTDOWN_TIMEOUT_MS));
834
+ const deadline = new Promise((resolve4) => timer = setTimeout(() => resolve4("timeout"), SHUTDOWN_TIMEOUT_MS));
667
835
  const outcome = await Promise.race([run, deadline]);
668
836
  clearTimeout(timer);
669
837
  if (outcome === "timeout") {
@@ -710,21 +878,25 @@ import { basename } from "path";
710
878
  import { Readable } from "stream";
711
879
  import { readFile } from "fs/promises";
712
880
  import { fileURLToPath } from "url";
881
+ import { existsSync as existsSync4, lstatSync, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
882
+ import { cp, mkdir, mkdtemp, rename, rm, writeFile } from "fs/promises";
883
+ import { basename as basename2, dirname, join as join5, resolve as resolve2 } from "path";
713
884
  import { createReadStream as createReadStream2 } from "fs";
714
885
  import { stat as stat2 } from "fs/promises";
715
886
  import { Readable as Readable2 } from "stream";
716
887
  import { relative, resolve, sep } from "path";
888
+ import { posix } from "path";
717
889
  import { createServer } from "http";
718
890
  import { Readable as Readable3 } from "stream";
719
891
  import { pipeline } from "stream/promises";
720
- import { existsSync as existsSync32 } from "fs";
721
- import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
722
- import { dirname } from "path";
723
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
724
- import { join as join5 } from "path";
725
- import * as util from "util";
726
- import { existsSync as existsSync22, statSync as statSync2, watch as watchPath } from "fs";
892
+ import { existsSync as existsSync42 } from "fs";
893
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
894
+ import { dirname as dirname2, resolve as resolve3 } from "path";
895
+ import { existsSync as existsSync23, readFileSync as readFileSync3 } from "fs";
727
896
  import { join as join23 } from "path";
897
+ import * as util from "util";
898
+ import { existsSync as existsSync32, statSync as statSync22, watch as watchPath } from "fs";
899
+ import { join as join32 } from "path";
728
900
  import { createServer as createServer2 } from "net";
729
901
  import { spawn as spawnProcess } from "child_process";
730
902
  var HttpException = class extends Error {
@@ -2250,6 +2422,131 @@ function exitOnSignals(close, closed, app) {
2250
2422
  });
2251
2423
  }
2252
2424
  var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
2425
+ var ANCHOR = /<a\b[^>]*?\shref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/gi;
2426
+ var decode = (path) => {
2427
+ try {
2428
+ return decodeURIComponent(path);
2429
+ } catch {
2430
+ return path;
2431
+ }
2432
+ };
2433
+ function exportFileName(path, style) {
2434
+ const trimmed = decode(path).replace(/^\/+|\/+$/g, "");
2435
+ if (trimmed === "") return "index.html";
2436
+ if (posix.extname(trimmed) !== "") return trimmed;
2437
+ return style === "directory" ? `${trimmed}/index.html` : `${trimmed}.html`;
2438
+ }
2439
+ function sameSiteLinks(html, pageUrl, baseUrl) {
2440
+ const origin = new URL(baseUrl).origin;
2441
+ const found = [];
2442
+ const seen = /* @__PURE__ */ new Set();
2443
+ for (const match of html.matchAll(ANCHOR)) {
2444
+ const href = (match[1] ?? match[2] ?? match[3] ?? "").trim();
2445
+ if (href === "" || href.startsWith("#")) continue;
2446
+ const path = sitePath(href, pageUrl, origin);
2447
+ if (path === void 0 || seen.has(path)) continue;
2448
+ seen.add(path);
2449
+ found.push(path);
2450
+ }
2451
+ return found;
2452
+ }
2453
+ function sitePath(href, pageUrl, origin) {
2454
+ let url2;
2455
+ try {
2456
+ url2 = new URL(href, pageUrl);
2457
+ } catch {
2458
+ return void 0;
2459
+ }
2460
+ if (url2.origin !== new URL(origin).origin) return void 0;
2461
+ return decode(url2.pathname.length > 1 ? url2.pathname.replace(/\/+$/, "") : url2.pathname);
2462
+ }
2463
+ var ExportError = class extends Error {
2464
+ constructor(path, status) {
2465
+ super(`[${path}] answered ${status}.`);
2466
+ this.path = path;
2467
+ this.status = status;
2468
+ }
2469
+ path;
2470
+ status;
2471
+ name = "ExportError";
2472
+ };
2473
+ var MARKER = ".oriel-export";
2474
+ var NOT_FOUND_PATH = "/__oriel-export-not-found__";
2475
+ async function exportSite(app, options) {
2476
+ const output = resolve2(options.output);
2477
+ const publicPath = options.publicPath === false ? void 0 : resolve2(options.publicPath);
2478
+ if (publicPath !== void 0 && existsSync4(join5(publicPath, "hot"))) {
2479
+ 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.");
2480
+ }
2481
+ assertReplaceable(output);
2482
+ const staging = await mkdtemp(join5(dirname(output), `.${basename2(output)}-`));
2483
+ try {
2484
+ if (publicPath !== void 0 && existsSync4(publicPath)) {
2485
+ await cp(publicPath, staging, { recursive: true, filter: (source) => basename2(source) !== "hot" && !lstatSync(source).isSymbolicLink() });
2486
+ }
2487
+ const excluded = (path) => options.exclude.some((pattern) => wildcard(pattern).test(path));
2488
+ const isPublicFile = (path) => publicPath !== void 0 && path !== "/" && isFile(join5(publicPath, path));
2489
+ const queue = [.../* @__PURE__ */ new Set(["/", ...options.paths.map(rooted)])].filter((path) => path === "/" || !excluded(path));
2490
+ const seen = new Set(queue);
2491
+ const pages = [];
2492
+ const redirects = [];
2493
+ while (queue.length > 0) {
2494
+ const path = queue.shift();
2495
+ const url2 = new URL(path, options.baseUrl).href;
2496
+ const answer = await fetchPage(app, url2);
2497
+ if (answer.status >= 300 && answer.status < 400) {
2498
+ const location = new URL(answer.headers.get("location") ?? "/", url2).href;
2499
+ redirects.push({ path, location });
2500
+ const target2 = sitePath(location, url2, options.baseUrl);
2501
+ if (target2 !== void 0 && !seen.has(target2) && !excluded(target2) && !isPublicFile(target2)) {
2502
+ seen.add(target2);
2503
+ queue.push(target2);
2504
+ }
2505
+ continue;
2506
+ }
2507
+ if (answer.status >= 400) throw new ExportError(path, answer.status);
2508
+ await writeInto(staging, exportFileName(path, options.fileNames), answer.body);
2509
+ pages.push(path);
2510
+ if (!options.crawl || !isHtml(answer.headers)) continue;
2511
+ for (const link of sameSiteLinks(answer.body.toString("utf8"), url2, options.baseUrl)) {
2512
+ if (seen.has(link) || excluded(link) || isPublicFile(link)) continue;
2513
+ seen.add(link);
2514
+ queue.push(link);
2515
+ }
2516
+ }
2517
+ const notFound = await fetchPage(app, new URL(NOT_FOUND_PATH, options.baseUrl).href);
2518
+ const notFoundPage = notFound.status === 404 && isHtml(notFound.headers);
2519
+ if (notFoundPage) await writeInto(staging, "404.html", notFound.body);
2520
+ await writeFile(join5(staging, MARKER), "");
2521
+ await rm(output, { recursive: true, force: true });
2522
+ await rename(staging, output);
2523
+ return { pages, redirects, notFoundPage };
2524
+ } catch (error) {
2525
+ await rm(staging, { recursive: true, force: true });
2526
+ throw error;
2527
+ }
2528
+ }
2529
+ function assertReplaceable(output) {
2530
+ if (!existsSync4(output)) return;
2531
+ if (statSync3(output).isDirectory() && (existsSync4(join5(output, MARKER)) || readdirSync2(output).length === 0)) return;
2532
+ throw new Error(`[${output}] exists and wasn't made by oriel export: remove it, or export somewhere else with --output.`);
2533
+ }
2534
+ async function fetchPage(app, url2) {
2535
+ const request2 = new Request(url2, { headers: { accept: "text/html" } });
2536
+ const response2 = await handleSafely(app, request2);
2537
+ const body = Buffer.from(await response2.arrayBuffer());
2538
+ await terminateRequest(app, request2, response2);
2539
+ return { status: response2.status, headers: response2.headers, body };
2540
+ }
2541
+ async function writeInto(folder, file, body) {
2542
+ const path = join5(folder, file);
2543
+ await mkdir(dirname(path), { recursive: true });
2544
+ await writeFile(path, body);
2545
+ }
2546
+ var isHtml = (headers) => (headers.get("content-type") ?? "").includes("text/html");
2547
+ var rooted = (path) => path.startsWith("/") ? path : `/${path}`;
2548
+ var isFile = (path) => existsSync4(path) && statSync3(path).isFile();
2549
+ var wildcard = (pattern) => new RegExp(`^${rooted(pattern).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
2253
2550
  function bunRuntime() {
2254
2551
  return globalThis.Bun;
2255
2552
  }
@@ -2257,7 +2554,7 @@ async function serveBun(app, options) {
2257
2554
  const bun = bunRuntime();
2258
2555
  if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
2259
2556
  let markClosed = () => void 0;
2260
- const closed = new Promise((resolve2) => markClosed = resolve2);
2557
+ const closed = new Promise((resolve4) => markClosed = resolve4);
2261
2558
  const server = bun.serve({
2262
2559
  port: options.port,
2263
2560
  hostname: options.hostname,
@@ -2275,8 +2572,8 @@ async function serveBun(app, options) {
2275
2572
  let closing;
2276
2573
  const close = () => closing ??= (async () => {
2277
2574
  let timer;
2278
- const forced = new Promise((resolve2) => {
2279
- timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve2), options.shutdownTimeout);
2575
+ const forced = new Promise((resolve4) => {
2576
+ timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve4), options.shutdownTimeout);
2280
2577
  });
2281
2578
  await Promise.race([Promise.resolve(server.stop(false)), forced]);
2282
2579
  clearTimeout(timer);
@@ -2374,40 +2671,44 @@ async function writeResponse(res, response2) {
2374
2671
  }
2375
2672
  await pipeline(Readable3.fromWeb(response2.body), res);
2376
2673
  }
2674
+ function nodeRequestHandler(app, options) {
2675
+ return async (req, res) => {
2676
+ let request2;
2677
+ try {
2678
+ request2 = toWebRequest(req, res, options.fallbackHost);
2679
+ } catch {
2680
+ res.statusCode = 400;
2681
+ res.end("Bad Request");
2682
+ return;
2683
+ }
2684
+ setClientAddress(request2, req.socket.remoteAddress);
2685
+ const response2 = await handleSafely(app, request2, options.publicPath);
2686
+ try {
2687
+ await writeResponse(res, response2);
2688
+ } catch {
2689
+ res.destroy();
2690
+ }
2691
+ await terminateRequest(app, request2, response2);
2692
+ };
2693
+ }
2377
2694
  async function serveNode(app, options) {
2378
- const fallbackHost = `${displayHost(options.hostname)}:${options.port}`;
2379
- const server = createServer((req, res) => {
2380
- void (async () => {
2381
- let request2;
2382
- try {
2383
- request2 = toWebRequest(req, res, fallbackHost);
2384
- } catch {
2385
- res.statusCode = 400;
2386
- res.end("Bad Request");
2387
- return;
2388
- }
2389
- setClientAddress(request2, req.socket.remoteAddress);
2390
- const response2 = await handleSafely(app, request2, options.publicPath);
2391
- try {
2392
- await writeResponse(res, response2);
2393
- } catch {
2394
- res.destroy();
2395
- }
2396
- await terminateRequest(app, request2, response2);
2397
- })();
2695
+ const handle = nodeRequestHandler(app, {
2696
+ ...options.publicPath !== void 0 && { publicPath: options.publicPath },
2697
+ fallbackHost: `${displayHost(options.hostname)}:${options.port}`
2398
2698
  });
2399
- await new Promise((resolve2, reject) => {
2699
+ const server = createServer((req, res) => void handle(req, res));
2700
+ await new Promise((resolve4, reject) => {
2400
2701
  server.once("error", reject);
2401
2702
  server.listen(options.port, options.hostname, () => {
2402
2703
  server.off("error", reject);
2403
- resolve2();
2704
+ resolve4();
2404
2705
  });
2405
2706
  });
2406
2707
  const address = server.address();
2407
- const closed = new Promise((resolve2) => server.once("close", () => resolve2()));
2708
+ const closed = new Promise((resolve4) => server.once("close", () => resolve4()));
2408
2709
  let closing;
2409
2710
  const close = () => closing ??= (async () => {
2410
- const done = new Promise((resolve2) => server.close(() => resolve2()));
2711
+ const done = new Promise((resolve4) => server.close(() => resolve4()));
2411
2712
  server.closeIdleConnections();
2412
2713
  const idle = setInterval(() => server.closeIdleConnections(), 50);
2413
2714
  const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
@@ -2463,8 +2764,8 @@ function serverEnvironment(options = {}) {
2463
2764
  if (typeof parseEnv2 !== "function") return env2;
2464
2765
  const cwd = options.cwd ?? process.cwd();
2465
2766
  for (const name of BUN_ENV_FILES) {
2466
- const file = join5(cwd, name);
2467
- if (!existsSync4(file)) continue;
2767
+ const file = join23(cwd, name);
2768
+ if (!existsSync23(file)) continue;
2468
2769
  let values;
2469
2770
  try {
2470
2771
  values = parseEnv2(readFileSync3(file, "utf8"));
@@ -2508,7 +2809,7 @@ function watchFiles(basePath, onChange, options = {}) {
2508
2809
  const reportedLate = (path) => {
2509
2810
  if (Date.now() - startedAt >= settle) return false;
2510
2811
  try {
2511
- const modified = statSync2(join23(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
2812
+ const modified = statSync22(join32(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
2512
2813
  return modified === void 0 || modified < startedAt + 1;
2513
2814
  } catch {
2514
2815
  return false;
@@ -2527,7 +2828,7 @@ function watchFiles(basePath, onChange, options = {}) {
2527
2828
  const handles = [];
2528
2829
  const start = (folder, recursive, toPath) => {
2529
2830
  try {
2530
- const handle = watch(folder === "." ? basePath : join23(basePath, folder), { recursive }, (_event, file) => {
2831
+ const handle = watch(folder === "." ? basePath : join32(basePath, folder), { recursive }, (_event, file) => {
2531
2832
  const path = toPath(file);
2532
2833
  if (path !== void 0) record(path);
2533
2834
  });
@@ -2539,7 +2840,7 @@ function watchFiles(basePath, onChange, options = {}) {
2539
2840
  };
2540
2841
  start(".", false, (file) => file === null ? void 0 : toSlashes(file));
2541
2842
  for (const folder of WATCHED_FOLDERS) {
2542
- if (existsSync22(join23(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
2843
+ if (existsSync32(join32(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
2543
2844
  }
2544
2845
  return {
2545
2846
  close() {
@@ -2550,13 +2851,13 @@ function watchFiles(basePath, onChange, options = {}) {
2550
2851
  };
2551
2852
  }
2552
2853
  function claimPort(hostname, port) {
2553
- return new Promise((resolve2, reject) => {
2854
+ return new Promise((resolve4, reject) => {
2554
2855
  const server = createServer2();
2555
2856
  server.once("error", reject);
2556
2857
  try {
2557
2858
  server.listen({ port, host: hostname, exclusive: true }, () => {
2558
2859
  const { port: claimed } = server.address();
2559
- server.close(() => resolve2(claimed));
2860
+ server.close(() => resolve4(claimed));
2560
2861
  });
2561
2862
  } catch (error) {
2562
2863
  reject(error);
@@ -2584,7 +2885,7 @@ var WatchedServer = class {
2584
2885
  };
2585
2886
  /** Starts the server and keeps it current until stopped. Resolves 0 once the last server has exited. */
2586
2887
  run() {
2587
- const done = new Promise((resolve2) => this.finish = resolve2);
2888
+ const done = new Promise((resolve4) => this.finish = resolve4);
2588
2889
  const stopListening = this.listenForSignals();
2589
2890
  const watcher = this.options.watch((files) => void this.changed(files));
2590
2891
  this.start();
@@ -2623,14 +2924,14 @@ var WatchedServer = class {
2623
2924
  stdio: ["ignore", "inherit", "inherit"]
2624
2925
  });
2625
2926
  const running = { child, stopping: false, killed: false, exited: Promise.resolve() };
2626
- running.exited = new Promise((resolve2) => {
2927
+ running.exited = new Promise((resolve4) => {
2627
2928
  let settled = false;
2628
2929
  const settle = (report) => {
2629
2930
  if (settled) return;
2630
2931
  settled = true;
2631
2932
  if (this.running === running) this.running = void 0;
2632
2933
  if (!running.stopping && !this.stopping) this.options.error(`${report}. Waiting for changes before restarting...`);
2633
- resolve2();
2934
+ resolve4();
2634
2935
  };
2635
2936
  child.once("exit", (code, signal) => settle(signal !== null ? `The server stopped (signal ${signal})` : `The server stopped (exit code ${code})`));
2636
2937
  child.once("error", (error) => {
@@ -2766,6 +3067,38 @@ async function serveWatching(app, out, port, run) {
2766
3067
  error: (message) => out.error(message)
2767
3068
  });
2768
3069
  }
3070
+ async function exportCommand(app, out) {
3071
+ const config = (key2, fallback) => app.bound(ConfigRepository) ? app.make(ConfigRepository).get(key2, fallback) : fallback;
3072
+ const fileNames = config("export.file_names", "html");
3073
+ if (fileNames !== "html" && fileNames !== "directory") {
3074
+ out.error(`export.file_names is "html" or "directory", not "${fileNames}".`);
3075
+ return 1;
3076
+ }
3077
+ const output = out.option("output") ?? config("export.output", "dist");
3078
+ const baseUrl = config("app.url", "http://localhost");
3079
+ const started = performance.now();
3080
+ try {
3081
+ const result = await exportSite(app, {
3082
+ output: resolve3(app.basePath(), output),
3083
+ baseUrl,
3084
+ crawl: config("export.crawl", true),
3085
+ paths: config("export.paths", []),
3086
+ exclude: config("export.exclude", []),
3087
+ fileNames,
3088
+ publicPath: app.publicPath()
3089
+ });
3090
+ const origin = new URL(baseUrl).origin;
3091
+ for (const { path, location } of result.redirects) {
3092
+ out.line(`Redirect ${path} \u2192 ${location.startsWith(origin) ? location.slice(origin.length) || "/" : location}, not written.`);
3093
+ }
3094
+ const pages = result.pages.length === 1 ? "1 page" : `${result.pages.length} pages`;
3095
+ out.info(`Exported ${pages} to [${output}] in ${Math.round(performance.now() - started)} ms.`);
3096
+ return 0;
3097
+ } catch (error) {
3098
+ out.error(error instanceof ExportError ? error.message : error instanceof Error ? error.message : String(error));
3099
+ return 1;
3100
+ }
3101
+ }
2769
3102
  function registerHttpCommands(kernel, app, options = {}) {
2770
3103
  const serveApp = options.serve ?? serve;
2771
3104
  const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
@@ -2824,25 +3157,28 @@ function registerHttpCommands(kernel, app, options = {}) {
2824
3157
  return 0;
2825
3158
  }
2826
3159
  ).purpose("List all registered routes");
3160
+ kernel.command("export {--output= : The folder to write the site to (default: config export.output, else dist)}", function() {
3161
+ return exportCommand(app, io(this));
3162
+ }).purpose("Export the application's pages as static files");
2827
3163
  kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
2828
3164
  const out = io(this);
2829
3165
  const routesFile = app.basePath("routes/api.ts");
2830
- if (existsSync32(routesFile) && !out.flag("force")) {
3166
+ if (existsSync42(routesFile) && !out.flag("force")) {
2831
3167
  out.error("API routes file already exists.");
2832
3168
  } else {
2833
- await mkdir(dirname(routesFile), { recursive: true });
2834
- await writeFile(routesFile, API_ROUTES_STUB);
3169
+ await mkdir2(dirname2(routesFile), { recursive: true });
3170
+ await writeFile2(routesFile, API_ROUTES_STUB);
2835
3171
  out.info("Published API routes file.");
2836
3172
  }
2837
3173
  const bootstrapFile = app.bootstrapPath("app.ts");
2838
- const source = existsSync32(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
3174
+ const source = existsSync42(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2839
3175
  const updated = source === void 0 ? void 0 : addApiRouting(source);
2840
3176
  if (updated === void 0) {
2841
3177
  out.warn(
2842
3178
  "Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
2843
3179
  );
2844
3180
  } else if (updated !== source) {
2845
- await writeFile(bootstrapFile, updated);
3181
+ await writeFile2(bootstrapFile, updated);
2846
3182
  }
2847
3183
  out.info("API scaffolding installed.");
2848
3184
  return 0;
@@ -2885,13 +3221,13 @@ import { isDeepStrictEqual } from "util";
2885
3221
  import { createHmac, timingSafeEqual } from "crypto";
2886
3222
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2887
3223
  import { randomBytes as randomBytes2 } from "crypto";
2888
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
3224
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
2889
3225
  import { join as join6, relative as relative2, sep as sep2 } from "path";
2890
3226
  import { pathToFileURL as pathToFileURL2 } from "url";
2891
3227
  import { AsyncLocalStorage } from "async_hooks";
2892
- import { existsSync as existsSync23 } from "fs";
2893
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2894
- import { dirname as dirname2, join as join24, relative as relative22, sep as sep22 } from "path";
3228
+ import { existsSync as existsSync24 } from "fs";
3229
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
3230
+ import { dirname as dirname3, join as join24, relative as relative22, sep as sep22 } from "path";
2895
3231
  var LiveError = class extends Error {
2896
3232
  name = "LiveError";
2897
3233
  kind = "Server";
@@ -3505,7 +3841,7 @@ function stripTags(html) {
3505
3841
  return html.replace(/<[^>]*>/g, "");
3506
3842
  }
3507
3843
  var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
3508
- var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
3844
+ var RAW_TEXT3 = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
3509
3845
  var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
3510
3846
  function rootTag(html, component) {
3511
3847
  const fail22 = () => {
@@ -3527,7 +3863,7 @@ function rootTag(html, component) {
3527
3863
  if (depth === 0) rootEnd = match.index + token2.length;
3528
3864
  continue;
3529
3865
  }
3530
- if (RAW_TEXT.has(tag)) {
3866
+ if (RAW_TEXT3.has(tag)) {
3531
3867
  const closer = new RegExp(`</${tag}`, "gi");
3532
3868
  closer.lastIndex = TOKEN.lastIndex;
3533
3869
  const end = closer.exec(source);
@@ -3785,7 +4121,7 @@ var fail = (message, actual, expected) => {
3785
4121
  var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
3786
4122
  var TestRequestScope = token("Testing.RequestScope");
3787
4123
  function requestScope2() {
3788
- const container2 = currentContainer() ?? Container.getInstance();
4124
+ const container2 = currentContainer() ?? Container2.getInstance();
3789
4125
  return container2?.bound(TestRequestScope) === true ? container2.make(TestRequestScope) : (run) => run();
3790
4126
  }
3791
4127
  function currentSession() {
@@ -4019,7 +4355,7 @@ function defaultName(cls) {
4019
4355
  }
4020
4356
  function componentFiles(directory) {
4021
4357
  if (!existsSync5(directory)) return [];
4022
- return readdirSync2(directory, { recursive: true, encoding: "utf8" }).filter((file) => /\.(tsx|ts|jsx|js|mts|mjs)$/.test(file) && !/\.(test|spec|d)\.[^.]+$/.test(file)).map((file) => file.split(sep2).join("/")).sort();
4358
+ return readdirSync3(directory, { recursive: true, encoding: "utf8" }).filter((file) => /\.(tsx|ts|jsx|js|mts|mjs)$/.test(file) && !/\.(test|spec|d)\.[^.]+$/.test(file)).map((file) => file.split(sep2).join("/")).sort();
4023
4359
  }
4024
4360
  async function discover(directory) {
4025
4361
  const found = [];
@@ -4565,7 +4901,9 @@ var LiveComponentManager = class {
4565
4901
  );
4566
4902
  await hook2(instance, "rendered", html);
4567
4903
  memo.children = context.children;
4568
- return { html: html.trim(), tag: rootTag(html, memo.name) };
4904
+ const container2 = this.container();
4905
+ const renamed = container2.bound(ClassNames) ? container2.make(ClassNames).html(html) : html;
4906
+ return { html: renamed.trim(), tag: rootTag(renamed, memo.name) };
4569
4907
  }
4570
4908
  /** The absolute URL of the page a memo path belongs to, on the current request's origin ('/' + path without one). */
4571
4909
  pageUrl(path) {
@@ -4625,7 +4963,7 @@ test('{{ name }} renders', async () => {
4625
4963
  `;
4626
4964
  var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
4627
4965
  function importPath(from, to) {
4628
- const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
4966
+ const path = relative22(dirname3(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
4629
4967
  return path.startsWith(".") ? path : `./${path}`;
4630
4968
  }
4631
4969
  function registerLiveCommands(kernel, app) {
@@ -4640,23 +4978,23 @@ function registerLiveCommands(kernel, app) {
4640
4978
  const directory = config?.get("live.class_path") ?? app.appPath("Live");
4641
4979
  const file = join24(directory, ...segments.slice(0, -1), `${className}.tsx`);
4642
4980
  const force = this.option("force") === true;
4643
- if (existsSync23(file) && !force) {
4981
+ if (existsSync24(file) && !force) {
4644
4982
  this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
4645
4983
  return 1;
4646
4984
  }
4647
- await mkdir2(dirname2(file), { recursive: true });
4648
- await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
4985
+ await mkdir3(dirname3(file), { recursive: true });
4986
+ await writeFile3(file, COMPONENT_STUB.replace("{{ class }}", () => className));
4649
4987
  this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
4650
4988
  if (this.option("test") === true) {
4651
4989
  const testFile = app.basePath(join24("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
4652
- if (existsSync23(testFile) && !force) {
4990
+ if (existsSync24(testFile) && !force) {
4653
4991
  this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
4654
4992
  return 1;
4655
4993
  }
4656
4994
  const name = segments.map((segment) => Str.kebab(segment)).join(".");
4657
4995
  const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join24("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
4658
- await mkdir2(dirname2(testFile), { recursive: true });
4659
- await writeFile2(testFile, test);
4996
+ await mkdir3(dirname3(testFile), { recursive: true });
4997
+ await writeFile3(testFile, test);
4660
4998
  this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
4661
4999
  }
4662
5000
  return 0;
@@ -4743,7 +5081,7 @@ import { randomUUID } from "crypto";
4743
5081
  import { randomUUID as randomUUID2 } from "crypto";
4744
5082
  import { randomUUID as randomUUID3 } from "crypto";
4745
5083
  import { randomBytes as randomBytes3 } from "crypto";
4746
- import { posix } from "path";
5084
+ import { posix as posix2 } from "path";
4747
5085
  import { deflateSync } from "zlib";
4748
5086
  var original;
4749
5087
  var testNow;
@@ -5552,7 +5890,7 @@ function decryptCookie(encrypter, name, value) {
5552
5890
  return void 0;
5553
5891
  }
5554
5892
  }
5555
- var decode = (text) => {
5893
+ var decode2 = (text) => {
5556
5894
  try {
5557
5895
  return decodeURIComponent(text);
5558
5896
  } catch {
@@ -5563,8 +5901,8 @@ function parseSetCookie(header) {
5563
5901
  const [pair = "", ...attributes] = header.split(";");
5564
5902
  const eq = pair.indexOf("=");
5565
5903
  const cookie = {
5566
- name: decode(pair.slice(0, eq).trim()),
5567
- value: decode(pair.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1")),
5904
+ name: decode2(pair.slice(0, eq).trim()),
5905
+ value: decode2(pair.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1")),
5568
5906
  httpOnly: false,
5569
5907
  secure: false
5570
5908
  };
@@ -6518,8 +6856,8 @@ var InteractsWithApplication = class {
6518
6856
  } finally {
6519
6857
  this.destroying = [];
6520
6858
  this.application = void 0;
6521
- const global = Container.getInstance();
6522
- if (global === app) Container.setInstance(void 0);
6859
+ const global = Container2.getInstance();
6860
+ if (global === app) Container2.setInstance(void 0);
6523
6861
  }
6524
6862
  }
6525
6863
  /**
@@ -7823,7 +8161,7 @@ var FakeDisk = class {
7823
8161
  async put(path, contents, options = {}) {
7824
8162
  const key2 = normalize(path);
7825
8163
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
7826
- this.write(key2, bytes, options.mimeType ?? diskType(posix.extname(key2)), options);
8164
+ this.write(key2, bytes, options.mimeType ?? diskType(posix2.extname(key2)), options);
7827
8165
  return true;
7828
8166
  }
7829
8167
  putFile(directory, file, options = {}) {
@@ -8268,7 +8606,9 @@ export {
8268
8606
  Config,
8269
8607
  Repository,
8270
8608
  env,
8609
+ RenameClasses,
8271
8610
  ApplicationBuilder,
8611
+ ClassNameMap,
8272
8612
  ProviderBootError,
8273
8613
  ProviderRegisterError,
8274
8614
  ViteError,
@@ -8329,10 +8669,13 @@ export {
8329
8669
  ExceptionsConfiguration,
8330
8670
  applyExceptionCallbacks,
8331
8671
  servePublicFile,
8672
+ ExportError,
8673
+ exportSite,
8332
8674
  bunRuntime,
8333
8675
  serveBun,
8334
8676
  toWebRequest,
8335
8677
  writeResponse,
8678
+ nodeRequestHandler,
8336
8679
  serveNode,
8337
8680
  detectRuntime,
8338
8681
  serve,
@@ -8430,4 +8773,4 @@ export {
8430
8773
  StorageFake,
8431
8774
  FakeFile
8432
8775
  };
8433
- //# sourceMappingURL=chunk-OXAATQA6.js.map
8776
+ //# sourceMappingURL=chunk-7NA4HALO.js.map