raktajs 0.1.2 → 0.1.4

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/rakta.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli/rakta.ts
5
- import { join as join17 } from "path";
5
+ import { join as join18 } from "path";
6
6
 
7
7
  // src/config/loadConfig.ts
8
8
  import { existsSync } from "fs";
@@ -474,18 +474,225 @@ function printInspectReport(report) {
474
474
  }
475
475
 
476
476
  // src/cli/build.ts
477
- import { isAbsolute, join as join5 } from "path";
477
+ import { isAbsolute, join as join6 } from "path";
478
+
479
+ // src/forge/build.ts
480
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
481
+ import { join as join5, resolve as resolve2 } from "path";
482
+
483
+ // src/forge/clientEntry.ts
484
+ import { existsSync as existsSync5, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
485
+ import { dirname, join as join4, relative as relative2 } from "path";
486
+ function toModuleSpecifier(fromFile, targetFile) {
487
+ const relativePath = relative2(dirname(fromFile), targetFile).replace(/\\/g, "/");
488
+ if (relativePath.startsWith(".")) {
489
+ return relativePath;
490
+ }
491
+ return `./${relativePath}`;
492
+ }
493
+ function toCssImportSpecifier(entryPath, projectRoot) {
494
+ const candidates = [
495
+ join4(projectRoot, "styles", "globals.css"),
496
+ join4(projectRoot, "styles", "globals.scss"),
497
+ join4(projectRoot, "styles", "globals.sass")
498
+ ];
499
+ const stylePath = candidates.find((candidate) => existsSync5(candidate));
500
+ if (!stylePath) {
501
+ return null;
502
+ }
503
+ return toModuleSpecifier(entryPath, stylePath);
504
+ }
505
+ function getPageRoutes(manifest) {
506
+ return manifest.routes.filter((route) => route.kind === "page");
507
+ }
508
+ function buildRouteImports(entryPath, appDir, pageRoutes) {
509
+ const routeEntries = pageRoutes.map((route) => {
510
+ const pagePath = join4(appDir, route.filePath);
511
+ return ` ${JSON.stringify(route.urlPattern)}: () => import("${toModuleSpecifier(entryPath, pagePath)}"),`;
512
+ }).join(`
513
+ `);
514
+ return `const routeModules = {
515
+ ${routeEntries}
516
+ } as const;`;
517
+ }
518
+ function buildRouteTable(pageRoutes) {
519
+ const routeEntries = pageRoutes.map((route) => ` ${JSON.stringify(route.urlPattern)}: routeModules[${JSON.stringify(route.urlPattern)}],`).join(`
520
+ `);
521
+ return `const routes = {
522
+ ${routeEntries}
523
+ } as const;`;
524
+ }
525
+ function findExistingModule(basePathWithoutExtension) {
526
+ const candidates = [".tsx", ".ts", ".jsx", ".js"].map((extension) => `${basePathWithoutExtension}${extension}`);
527
+ return candidates.find((candidate) => existsSync5(candidate));
528
+ }
529
+ function buildStarterGlobalLoaders(entryPath, appDir) {
530
+ const mascotPath = findExistingModule(join4(appDir, "components", "raktaShrimpMascot"));
531
+ const gamePath = findExistingModule(join4(appDir, "components", "shrimpRunGame"));
532
+ const loaders = [];
533
+ if (mascotPath !== undefined) {
534
+ loaders.push(` const mascotModule = await import("${toModuleSpecifier(entryPath, mascotPath)}");
535
+ (globalThis as typeof globalThis & Record<string, unknown>).RaktaShrimpMascot = mascotModule.default;`);
536
+ }
537
+ if (gamePath !== undefined) {
538
+ loaders.push(` const gameModule = await import("${toModuleSpecifier(entryPath, gamePath)}");
539
+ (globalThis as typeof globalThis & Record<string, unknown>).ShrimpRunGame = gameModule.default;`);
540
+ }
541
+ if (loaders.length === 0) {
542
+ return `async function loadRaktaGlobals(): Promise<void> {
543
+ return;
544
+ }`;
545
+ }
546
+ return `async function loadRaktaGlobals(): Promise<void> {
547
+ ${loaders.join(`
548
+
549
+ `)}
550
+ }`;
551
+ }
552
+ function buildClientEntrySource(options, entryPath) {
553
+ const pageRoutes = getPageRoutes(options.manifest);
554
+ const routeModules = buildRouteImports(entryPath, options.appDir, pageRoutes);
555
+ const routeTable = buildRouteTable(pageRoutes);
556
+ const cssImportSpecifier = toCssImportSpecifier(entryPath, options.projectRoot);
557
+ const cssImport = cssImportSpecifier !== null ? `import "${cssImportSpecifier}";
558
+ ` : "";
559
+ const starterGlobalLoaders = buildStarterGlobalLoaders(entryPath, options.appDir);
560
+ return `import React, { useEffect, useState } from "react";
561
+ import { createRoot } from "react-dom/client";
562
+ import * as ReactHooks from "react";
563
+ ${cssImport}
564
+ (globalThis as typeof globalThis & Record<string, unknown>).useCallback = ReactHooks.useCallback;
565
+ (globalThis as typeof globalThis & Record<string, unknown>).useEffect = ReactHooks.useEffect;
566
+ (globalThis as typeof globalThis & Record<string, unknown>).useRef = ReactHooks.useRef;
567
+ (globalThis as typeof globalThis & Record<string, unknown>).useState = ReactHooks.useState;
568
+
569
+ ${starterGlobalLoaders}
570
+
571
+ await loadRaktaGlobals();
572
+
573
+ ${routeModules}
574
+
575
+ ${routeTable}
576
+
577
+ type RoutePath = keyof typeof routes;
578
+ type PageModule = { default: React.ComponentType };
579
+
580
+ function normalizePathname(pathname: string): string {
581
+ if (pathname.length > 1 && pathname.endsWith("/")) {
582
+ return pathname.slice(0, -1);
583
+ }
584
+
585
+ return pathname;
586
+ }
587
+
588
+ function resolveRouteLoader(pathname: string): () => Promise<PageModule> {
589
+ const normalizedPathname = normalizePathname(pathname) as RoutePath;
590
+
591
+ return routes[normalizedPathname] ?? routes["/"];
592
+ }
593
+
594
+ function navigate(to: string): void {
595
+ window.history.pushState({ source: "rakta-click", to }, "", to);
596
+ window.dispatchEvent(new PopStateEvent("popstate", { state: { to } }));
597
+ }
598
+
599
+ function App(): React.ReactElement {
600
+ const [pathname, setPathname] = useState(() => window.location.pathname);
601
+ const [Page, setPage] = useState<React.ComponentType | null>(null);
602
+
603
+ useEffect(() => {
604
+ function handlePopState(): void {
605
+ setPathname(window.location.pathname);
606
+ }
607
+
608
+ function handleClick(event: MouseEvent): void {
609
+ const target = event.target;
610
+
611
+ if (!(target instanceof Element)) {
612
+ return;
613
+ }
614
+
615
+ const clickElement = target.closest("click");
616
+
617
+ if (!clickElement) {
618
+ return;
619
+ }
620
+
621
+ const to = clickElement.getAttribute("to");
622
+
623
+ if (!to || to.startsWith("http://") || to.startsWith("https://")) {
624
+ return;
625
+ }
626
+
627
+ event.preventDefault();
628
+ navigate(to);
629
+ setPathname(window.location.pathname);
630
+ }
631
+
632
+ window.addEventListener("popstate", handlePopState);
633
+ document.addEventListener("click", handleClick);
634
+
635
+ return () => {
636
+ window.removeEventListener("popstate", handlePopState);
637
+ document.removeEventListener("click", handleClick);
638
+ };
639
+ }, []);
640
+
641
+ useEffect(() => {
642
+ let isCurrent = true;
643
+
644
+ resolveRouteLoader(pathname)().then((pageModule) => {
645
+ if (isCurrent) {
646
+ setPage(() => pageModule.default);
647
+ }
648
+ });
649
+
650
+ return () => {
651
+ isCurrent = false;
652
+ };
653
+ }, [pathname]);
654
+
655
+ if (!Page) {
656
+ return React.createElement("main", {
657
+ style: {
658
+ minHeight: "100vh",
659
+ display: "grid",
660
+ placeItems: "center",
661
+ background: "#050505",
662
+ color: "#f8fafc",
663
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
664
+ },
665
+ }, "Loading Rakta.js...");
666
+ }
667
+
668
+ return React.createElement(Page);
669
+ }
670
+
671
+ const rootElement = document.getElementById("rakta-root");
672
+
673
+ if (!rootElement) {
674
+ throw new Error("Rakta.js root element #rakta-root was not found.");
675
+ }
676
+
677
+ createRoot(rootElement).render(React.createElement(App));
678
+ `;
679
+ }
680
+ function writeClientEntry(options) {
681
+ mkdirSync(options.workDir, { recursive: true });
682
+ const entryPath = join4(options.workDir, "client-entry.tsx");
683
+ const entrySource = buildClientEntrySource(options, entryPath);
684
+ writeFileSync2(entryPath, entrySource, "utf-8");
685
+ return entryPath;
686
+ }
478
687
 
479
688
  // src/forge/build.ts
480
- import { mkdirSync } from "fs";
481
- import { join as join4, resolve as resolve2 } from "path";
482
689
  async function buildProject(options) {
483
690
  const startMs = Date.now();
484
691
  const artifacts = [];
485
692
  const errors = [];
486
- mkdirSync(resolve2(options.outDir), { recursive: true });
693
+ mkdirSync2(resolve2(options.outDir), { recursive: true });
487
694
  const manifest = generateManifest(options.appDir);
488
- const manifestPath = join4(options.outDir, "route-manifest.json");
695
+ const manifestPath = join5(options.outDir, "route-manifest.json");
489
696
  writeManifest(manifest, manifestPath);
490
697
  const manifestContent = JSON.stringify(manifest);
491
698
  artifacts.push({
@@ -493,15 +700,21 @@ async function buildProject(options) {
493
700
  sizeBytes: new TextEncoder().encode(manifestContent).byteLength,
494
701
  kind: "manifest"
495
702
  });
703
+ const entryPoint = existsSync6(options.entryPoint) ? options.entryPoint : writeClientEntry({
704
+ projectRoot: options.projectRoot,
705
+ appDir: options.appDir,
706
+ workDir: join5(options.projectRoot, ".rakta"),
707
+ manifest
708
+ });
496
709
  const buildResult = await Bun.build({
497
- entrypoints: [options.entryPoint],
710
+ entrypoints: [entryPoint],
498
711
  outdir: options.outDir,
499
712
  target: options.target,
500
713
  minify: options.minify,
501
714
  sourcemap: options.sourcemap ? "external" : "none",
502
715
  splitting: options.splitting,
503
716
  naming: {
504
- entry: "[name].[ext]",
717
+ entry: "app.[ext]",
505
718
  chunk: "chunks/[name]-[hash].[ext]",
506
719
  asset: "assets/[name]-[hash].[ext]"
507
720
  }
@@ -539,7 +752,7 @@ function resolveProjectPath(cwd, pathValue) {
539
752
  if (isAbsolute(pathValue)) {
540
753
  return pathValue;
541
754
  }
542
- return join5(cwd, pathValue);
755
+ return join6(cwd, pathValue);
543
756
  }
544
757
  async function buildCommand(cwd = process.cwd()) {
545
758
  const projectConfig = await loadConfig(cwd);
@@ -547,16 +760,17 @@ async function buildCommand(cwd = process.cwd()) {
547
760
  const publicDirectory = projectConfig.publicDir ?? "public";
548
761
  const outputDirectory = projectConfig.build.outDir ?? "dist";
549
762
  const entryFile = projectConfig.build.entryPoint ?? "entry.client.tsx";
550
- const entryPoint = resolveProjectPath(join5(cwd, appDirectory), entryFile);
763
+ const entryPoint = resolveProjectPath(join6(cwd, appDirectory), entryFile);
551
764
  const outDir = resolveProjectPath(cwd, outputDirectory);
552
765
  console.log(`
553
766
  Building Rakta.js application...
554
767
  `);
555
768
  const buildResult = await buildProject({
769
+ projectRoot: cwd,
556
770
  entryPoint,
557
771
  outDir,
558
- appDir: join5(cwd, appDirectory),
559
- publicDir: join5(cwd, publicDirectory),
772
+ appDir: join6(cwd, appDirectory),
773
+ publicDir: join6(cwd, publicDirectory),
560
774
  appName: projectConfig.appName,
561
775
  sourcemap: projectConfig.build.sourcemap ?? false,
562
776
  minify: projectConfig.build.minify ?? true,
@@ -578,23 +792,26 @@ async function buildCommand(cwd = process.cwd()) {
578
792
  }
579
793
 
580
794
  // src/cli/dev.ts
581
- import { join as join7 } from "path";
795
+ import { join as join8 } from "path";
582
796
 
583
797
  // src/forge/devServer.ts
584
- import { existsSync as existsSync5, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
585
- import { join as join6 } from "path";
798
+ import { existsSync as existsSync7, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
799
+ import { join as join7 } from "path";
586
800
 
587
801
  // src/render/renderer.ts
588
802
  function buildHtmlShell(options) {
803
+ const title = options.title ?? options.appName;
804
+ const faviconPath = options.faviconPath ?? "/favicon.ico";
805
+ const descriptionMeta = options.description !== undefined ? `<meta name="description" content="${options.description}" />` : "";
589
806
  return `
590
807
  <!DOCTYPE html>
591
808
  <html lang="${options.lang}">
592
809
  <head>
593
810
  <meta charset="UTF-8" />
594
811
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
595
- <title>
596
- ${options.appName}
597
- </title>
812
+ <title>${title}</title>
813
+ ${descriptionMeta}
814
+ <link rel="icon" href="${faviconPath}" sizes="any" />
598
815
  <link rel="stylesheet" href="${options.cssPath}" />
599
816
  </head>
600
817
  <body>
@@ -700,18 +917,55 @@ function resolveDevPort(port) {
700
917
  return port > 0 ? port : DEFAULT_DEV_PORT;
701
918
  }
702
919
  function isReadableFile(filePath) {
703
- return existsSync5(filePath) && statSync3(filePath).isFile();
920
+ return existsSync7(filePath) && statSync3(filePath).isFile();
921
+ }
922
+ async function buildDevClientBundle(options, manifest) {
923
+ const workDir = join7(options.projectRoot, ".rakta");
924
+ const clientOutDir = join7(workDir, "dev");
925
+ const clientEntry = writeClientEntry({
926
+ projectRoot: options.projectRoot,
927
+ appDir: options.appDir,
928
+ workDir,
929
+ manifest
930
+ });
931
+ const buildResult = await Bun.build({
932
+ entrypoints: [clientEntry],
933
+ outdir: clientOutDir,
934
+ target: "browser",
935
+ sourcemap: "external",
936
+ naming: {
937
+ entry: "app.[ext]",
938
+ chunk: "chunks/[name]-[hash].[ext]",
939
+ asset: "assets/[name]-[hash].[ext]"
940
+ }
941
+ });
942
+ if (!buildResult.success) {
943
+ const buildErrors = buildResult.logs.map((buildLog) => buildLog.message).join(`
944
+ `);
945
+ throw new Error(`Failed to build Rakta.js client bundle.
946
+ ${buildErrors}`);
947
+ }
948
+ return clientOutDir;
704
949
  }
705
- function startDevServer(options) {
950
+ async function startDevServer(options) {
706
951
  const manifest = generateManifest(options.appDir);
707
952
  const resolvedPort = resolveDevPort(options.port);
953
+ const clientOutDir = await buildDevClientBundle(options, manifest);
708
954
  const server = Bun.serve({
709
955
  port: resolvedPort,
710
956
  hostname: options.host,
711
957
  async fetch(request) {
712
958
  const url = new URL(request.url);
713
959
  const { pathname } = url;
714
- const publicPath = join6(options.publicDir, pathname);
960
+ if (clientOutDir.length > 0) {
961
+ const clientBundlePath = join7(clientOutDir, pathname);
962
+ if (isReadableFile(clientBundlePath)) {
963
+ return new Response(readFileSync2(clientBundlePath), {
964
+ headers: { "Content-Type": resolveMime(clientBundlePath) }
965
+ });
966
+ }
967
+ }
968
+ const publicPath = join7(options.publicDir, pathname);
715
969
  if (isReadableFile(publicPath)) {
716
970
  return new Response(readFileSync2(publicPath), {
717
971
  headers: { "Content-Type": resolveMime(pathname) }
@@ -719,7 +973,7 @@ function startDevServer(options) {
719
973
  }
720
974
  const apiMatch = matchRoute(pathname, manifest.routes.filter((route) => route.kind === "api"));
721
975
  if (apiMatch) {
722
- const modulePath = join6(options.appDir, apiMatch.entry.filePath);
976
+ const modulePath = join7(options.appDir, apiMatch.entry.filePath);
723
977
  const routeModule = await import(modulePath);
724
978
  const method = request.method.toUpperCase();
725
979
  const handler = routeModule[method];
@@ -746,8 +1000,10 @@ function startDevServer(options) {
746
1000
  timestampMs: Date.now()
747
1001
  }, {
748
1002
  appName: options.appName,
1003
+ title: options.seo.defaultTitle,
1004
+ description: options.seo.defaultDescription,
749
1005
  scriptPath: "/app.js",
750
- cssPath: "/globals.css",
1006
+ cssPath: "/app.css",
751
1007
  lang: "en"
752
1008
  });
753
1009
  if (result.kind === "failure") {
@@ -774,19 +1030,23 @@ async function devCommand(cwd = process.cwd()) {
774
1030
  console.log(`
775
1031
  Starting Rakta.js dev server...
776
1032
  `);
777
- startDevServer({
1033
+ const server = await startDevServer({
1034
+ projectRoot: cwd,
778
1035
  port: config.port,
779
1036
  host: config.server.hostname ?? "0.0.0.0",
780
- appDir: join7(cwd, config.appDir),
781
- publicDir: join7(cwd, config.publicDir),
1037
+ appDir: join8(cwd, config.appDir),
1038
+ publicDir: join8(cwd, config.publicDir),
782
1039
  appName: config.appName,
1040
+ seo: config.seo,
783
1041
  renderConfig: config.render
784
1042
  });
1043
+ console.log(` Ready at ${server.url}
1044
+ `);
785
1045
  }
786
1046
 
787
1047
  // src/cli/doctor.ts
788
- import { existsSync as existsSync6 } from "fs";
789
- import { join as join8 } from "path";
1048
+ import { existsSync as existsSync8 } from "fs";
1049
+ import { join as join9 } from "path";
790
1050
  async function doctorCommand(cwd = process.cwd()) {
791
1051
  const config = await loadConfig(cwd);
792
1052
  const checks = [
@@ -797,31 +1057,31 @@ async function doctorCommand(cwd = process.cwd()) {
797
1057
  },
798
1058
  {
799
1059
  label: "rakta.config.ts",
800
- passed: existsSync6(join8(cwd, "rakta.config.ts")) || existsSync6(join8(cwd, "rakta.config.js"))
1060
+ passed: existsSync8(join9(cwd, "rakta.config.ts")) || existsSync8(join9(cwd, "rakta.config.js"))
801
1061
  },
802
1062
  {
803
1063
  label: `app/ directory (${config.appDir})`,
804
- passed: existsSync6(join8(cwd, config.appDir))
1064
+ passed: existsSync8(join9(cwd, config.appDir))
805
1065
  },
806
1066
  {
807
1067
  label: "app/page.tsx (root page)",
808
- passed: existsSync6(join8(cwd, config.appDir, "page.tsx"))
1068
+ passed: existsSync8(join9(cwd, config.appDir, "page.tsx"))
809
1069
  },
810
1070
  {
811
1071
  label: "app/layout.tsx (root layout)",
812
- passed: existsSync6(join8(cwd, config.appDir, "layout.tsx"))
1072
+ passed: existsSync8(join9(cwd, config.appDir, "layout.tsx"))
813
1073
  },
814
1074
  {
815
1075
  label: `public/ directory (${config.publicDir})`,
816
- passed: existsSync6(join8(cwd, config.publicDir))
1076
+ passed: existsSync8(join9(cwd, config.publicDir))
817
1077
  },
818
1078
  {
819
1079
  label: "package.json",
820
- passed: existsSync6(join8(cwd, "package.json"))
1080
+ passed: existsSync8(join9(cwd, "package.json"))
821
1081
  },
822
1082
  {
823
1083
  label: "tsconfig.json",
824
- passed: existsSync6(join8(cwd, "tsconfig.json"))
1084
+ passed: existsSync8(join9(cwd, "tsconfig.json"))
825
1085
  }
826
1086
  ];
827
1087
  const sep = "\u2500".repeat(52);
@@ -848,12 +1108,12 @@ async function doctorCommand(cwd = process.cwd()) {
848
1108
  }
849
1109
 
850
1110
  // src/auto-import/generator.ts
851
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
852
- import { join as join10 } from "path";
1111
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1112
+ import { join as join11 } from "path";
853
1113
 
854
1114
  // src/auto-import/scanner.ts
855
- import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
856
- import { basename, extname, join as join9, relative as relative2 } from "path";
1115
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
1116
+ import { basename, extname, join as join10, relative as relative3 } from "path";
857
1117
  var DEFAULT_EXTENSIONS = [
858
1118
  ".ts",
859
1119
  ".tsx",
@@ -884,12 +1144,12 @@ function toPascalCase(text) {
884
1144
  return text.replace(/[-_.]/g, " ").replace(/\s+(.)/g, (_match, char) => char.toUpperCase()).replace(/^(.)/, (_match, char) => char.toUpperCase());
885
1145
  }
886
1146
  function deriveExportName(filePath, dirPath) {
887
- const relativePath = relative2(dirPath, filePath).replace(/\\/g, "/");
1147
+ const relativePath = relative3(dirPath, filePath).replace(/\\/g, "/");
888
1148
  const withoutExtension = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "").replace(/\/index$/, "");
889
1149
  return toPascalCase(withoutExtension.replace(/\//g, "_"));
890
1150
  }
891
1151
  function walkDirectory(dirPath, extensions) {
892
- if (!existsSync7(dirPath)) {
1152
+ if (!existsSync9(dirPath)) {
893
1153
  return [];
894
1154
  }
895
1155
  const collected = [];
@@ -901,7 +1161,7 @@ function walkDirectory(dirPath, extensions) {
901
1161
  if (entry.name.startsWith(".") || entry.name === "node_modules") {
902
1162
  continue;
903
1163
  }
904
- const fullPath = join9(currentPath, entry.name);
1164
+ const fullPath = join10(currentPath, entry.name);
905
1165
  if (entry.isDirectory()) {
906
1166
  walk(fullPath);
907
1167
  continue;
@@ -917,16 +1177,16 @@ function walkDirectory(dirPath, extensions) {
917
1177
  function scanForExports(options) {
918
1178
  const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
919
1179
  const discovered = [];
920
- const outputAbs = join9(options.frontendRoot, options.outputDirectory);
1180
+ const outputAbs = join10(options.frontendRoot, options.outputDirectory);
921
1181
  for (const directory of options.directories) {
922
- const directoryAbs = join9(options.frontendRoot, directory);
1182
+ const directoryAbs = join10(options.frontendRoot, directory);
923
1183
  const files = walkDirectory(directoryAbs, extensions);
924
1184
  for (const filePath of files) {
925
- const relativeFromOutput = relative2(outputAbs, filePath).replace(/\\/g, "/").replace(/\.(ts|tsx)$/, "");
1185
+ const relativeFromOutput = relative3(outputAbs, filePath).replace(/\\/g, "/").replace(/\.(ts|tsx)$/, "");
926
1186
  const importPath = relativeFromOutput.startsWith(".") ? relativeFromOutput : `./${relativeFromOutput}`;
927
1187
  discovered.push({
928
1188
  name: deriveExportName(filePath, directoryAbs),
929
- filePath: relative2(options.frontendRoot, filePath).replace(/\\/g, "/"),
1189
+ filePath: relative3(options.frontendRoot, filePath).replace(/\\/g, "/"),
930
1190
  importPath,
931
1191
  kind: detectKind(filePath)
932
1192
  });
@@ -959,17 +1219,17 @@ function generateAutoImports(options) {
959
1219
  extensions: options.extensions
960
1220
  } : {}
961
1221
  });
962
- const outputDir = join10(options.frontendRoot, options.outputDirectory);
963
- mkdirSync2(outputDir, {
1222
+ const outputDir = join11(options.frontendRoot, options.outputDirectory);
1223
+ mkdirSync3(outputDir, {
964
1224
  recursive: true
965
1225
  });
966
1226
  const tsContent = `${GENERATED_HEADER}
967
1227
  ${buildImportLines(exports)}
968
1228
  `;
969
- writeFileSync2(join10(outputDir, "auto-imports.ts"), tsContent, "utf-8");
1229
+ writeFileSync3(join11(outputDir, "auto-imports.ts"), tsContent, "utf-8");
970
1230
  if (options.generateDts) {
971
1231
  const dtsContent = tsContent.replace("// This file is auto-generated by: bun rakta imports:generate", "// Type declarations \u2014 auto-generated by: bun rakta imports:generate");
972
- writeFileSync2(join10(outputDir, "auto-imports.d.ts"), dtsContent, "utf-8");
1232
+ writeFileSync3(join11(outputDir, "auto-imports.d.ts"), dtsContent, "utf-8");
973
1233
  }
974
1234
  return {
975
1235
  generatedAt: new Date().toISOString(),
@@ -1011,18 +1271,18 @@ async function importsGenerateCommand(cwd = process.cwd()) {
1011
1271
  }
1012
1272
 
1013
1273
  // src/cli/make.ts
1014
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1015
- import { dirname, join as join11 } from "path";
1274
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1275
+ import { dirname as dirname2, join as join12 } from "path";
1016
1276
  function toPascalCase2(name) {
1017
1277
  return name.split(/[-_/]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1018
1278
  }
1019
1279
  function writeIfNew(filePath, content) {
1020
- if (existsSync8(filePath)) {
1280
+ if (existsSync10(filePath)) {
1021
1281
  console.warn(` Already exists: ${filePath}`);
1022
1282
  return;
1023
1283
  }
1024
- mkdirSync3(dirname(filePath), { recursive: true });
1025
- writeFileSync3(filePath, content, "utf-8");
1284
+ mkdirSync4(dirname2(filePath), { recursive: true });
1285
+ writeFileSync4(filePath, content, "utf-8");
1026
1286
  console.log(` Created: ${filePath}`);
1027
1287
  }
1028
1288
  function getRelativeFilePath(filePath, cwd) {
@@ -1030,11 +1290,11 @@ function getRelativeFilePath(filePath, cwd) {
1030
1290
  }
1031
1291
  async function makeCommand(target, name, cwd = process.cwd()) {
1032
1292
  const config = await loadConfig(cwd);
1033
- const appDir = join11(cwd, config.appDir);
1293
+ const appDir = join12(cwd, config.appDir);
1034
1294
  const componentName = toPascalCase2(name);
1035
1295
  switch (target) {
1036
1296
  case "page": {
1037
- const filePath = join11(appDir, name, "page.tsx");
1297
+ const filePath = join12(appDir, name, "page.tsx");
1038
1298
  writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
1039
1299
  import React from "react";
1040
1300
  import type { Metadata } from "rakta/seo";
@@ -1054,7 +1314,7 @@ export default function ${componentName}Page() {
1054
1314
  break;
1055
1315
  }
1056
1316
  case "layout": {
1057
- const filePath = join11(appDir, name, "layout.tsx");
1317
+ const filePath = join12(appDir, name, "layout.tsx");
1058
1318
  writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
1059
1319
  import React from "react";
1060
1320
  import type { LayoutProps } from "rakta/router";
@@ -1066,7 +1326,7 @@ export default function ${componentName}Layout({ children }: LayoutProps) {
1066
1326
  break;
1067
1327
  }
1068
1328
  case "component": {
1069
- const filePath = join11(cwd, "components", "ui", `${componentName}.tsx`);
1329
+ const filePath = join12(cwd, "components", "ui", `${componentName}.tsx`);
1070
1330
  writeIfNew(filePath, `// components/ui/${componentName}.tsx
1071
1331
  import React from "react";
1072
1332
 
@@ -1084,7 +1344,7 @@ export default ${componentName};
1084
1344
  break;
1085
1345
  }
1086
1346
  case "api": {
1087
- const filePath = join11(appDir, "api", name, "route.ts");
1347
+ const filePath = join12(appDir, "api", name, "route.ts");
1088
1348
  writeIfNew(filePath, `// ${getRelativeFilePath(filePath, cwd)}
1089
1349
  import type { RouteContext } from "rakta/router";
1090
1350
 
@@ -1098,23 +1358,23 @@ export async function GET(_request: Request, context: RouteContext): Promise<Res
1098
1358
  }
1099
1359
 
1100
1360
  // src/cli/routes.ts
1101
- import { join as join12 } from "path";
1361
+ import { join as join13 } from "path";
1102
1362
  async function routesCommand(cwd = process.cwd()) {
1103
1363
  const config = await loadConfig(cwd);
1104
- const appDir = join12(cwd, config.appDir);
1364
+ const appDir = join13(cwd, config.appDir);
1105
1365
  const manifest = generateManifest(appDir);
1106
1366
  printManifest(manifest);
1107
1367
  }
1108
1368
 
1109
1369
  // src/cli/rpcTypes.ts
1110
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1111
- import { join as join13 } from "path";
1370
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
1371
+ import { join as join14 } from "path";
1112
1372
  async function rpcTypesCommand(cwd = process.cwd()) {
1113
- const sharedTypesDir = join13(cwd, "..", "shared", "types");
1114
- if (!existsSync9(sharedTypesDir)) {
1115
- mkdirSync4(sharedTypesDir, { recursive: true });
1373
+ const sharedTypesDir = join14(cwd, "..", "shared", "types");
1374
+ if (!existsSync11(sharedTypesDir)) {
1375
+ mkdirSync5(sharedTypesDir, { recursive: true });
1116
1376
  }
1117
- const outputPath = join13(sharedTypesDir, "rpc.ts");
1377
+ const outputPath = join14(sharedTypesDir, "rpc.ts");
1118
1378
  const content = `// shared/types/rpc.ts
1119
1379
  // Auto-generated by: bun rakta rpc:types
1120
1380
  // Re-exports AppRouter type from the backend for type-safe frontend RPC calls.
@@ -1122,7 +1382,7 @@ async function rpcTypesCommand(cwd = process.cwd()) {
1122
1382
 
1123
1383
  export type { AppRouter } from "../../backend/src/rpc/router.js";
1124
1384
  `;
1125
- writeFileSync4(outputPath, content, "utf-8");
1385
+ writeFileSync5(outputPath, content, "utf-8");
1126
1386
  console.log(`
1127
1387
  Generated: shared/types/rpc.ts
1128
1388
  `);
@@ -1132,16 +1392,16 @@ export type { AppRouter } from "../../backend/src/rpc/router.js";
1132
1392
  }
1133
1393
 
1134
1394
  // src/cli/seo.ts
1135
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
1136
- import { join as join14 } from "path";
1395
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
1396
+ import { join as join15 } from "path";
1137
1397
  async function seoGenerateCommand(cwd = process.cwd()) {
1138
1398
  const config = await loadConfig(cwd);
1139
- const appDir = join14(cwd, config.appDir);
1140
- const sitemapDir = join14(appDir, "api", "sitemap.xml");
1141
- const robotsDir = join14(appDir, "api", "robots.txt");
1142
- if (!existsSync10(sitemapDir)) {
1143
- mkdirSync5(sitemapDir, { recursive: true });
1144
- writeFileSync5(join14(sitemapDir, "route.ts"), `// app/api/sitemap.xml/route.ts \u2014 generated by: bun rakta seo:generate
1399
+ const appDir = join15(cwd, config.appDir);
1400
+ const sitemapDir = join15(appDir, "api", "sitemap.xml");
1401
+ const robotsDir = join15(appDir, "api", "robots.txt");
1402
+ if (!existsSync12(sitemapDir)) {
1403
+ mkdirSync6(sitemapDir, { recursive: true });
1404
+ writeFileSync6(join15(sitemapDir, "route.ts"), `// app/api/sitemap.xml/route.ts \u2014 generated by: bun rakta seo:generate
1145
1405
  import { createSitemapHandler } from "rakta/seo";
1146
1406
 
1147
1407
  export const GET = createSitemapHandler({
@@ -1156,9 +1416,9 @@ export const GET = createSitemapHandler({
1156
1416
  } else {
1157
1417
  console.warn(" Already exists: app/api/sitemap.xml/route.ts");
1158
1418
  }
1159
- if (!existsSync10(robotsDir)) {
1160
- mkdirSync5(robotsDir, { recursive: true });
1161
- writeFileSync5(join14(robotsDir, "route.ts"), `// app/api/robots.txt/route.ts \u2014 generated by: bun rakta seo:generate
1419
+ if (!existsSync12(robotsDir)) {
1420
+ mkdirSync6(robotsDir, { recursive: true });
1421
+ writeFileSync6(join15(robotsDir, "route.ts"), `// app/api/robots.txt/route.ts \u2014 generated by: bun rakta seo:generate
1162
1422
  import { createRobotsHandler } from "rakta/seo";
1163
1423
 
1164
1424
  export const GET = createRobotsHandler({
@@ -1182,11 +1442,11 @@ export const GET = createRobotsHandler({
1182
1442
  }
1183
1443
 
1184
1444
  // src/cli/start.ts
1185
- import { join as join16 } from "path";
1445
+ import { join as join17 } from "path";
1186
1446
 
1187
1447
  // src/tide/adapter.ts
1188
- import { existsSync as existsSync11, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
1189
- import { join as join15 } from "path";
1448
+ import { existsSync as existsSync13, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
1449
+ import { join as join16 } from "path";
1190
1450
  // src/tide/runtime.ts
1191
1451
  function createRuntimeContext(request, url, params, resolvedMode) {
1192
1452
  const searchParams = {};
@@ -1257,7 +1517,7 @@ function normalizeStaticPath(pathname) {
1257
1517
  return "index.html";
1258
1518
  }
1259
1519
  function isReadableFile2(filePath) {
1260
- return existsSync11(filePath) && statSync4(filePath).isFile();
1520
+ return existsSync13(filePath) && statSync4(filePath).isFile();
1261
1521
  }
1262
1522
  function isApiRouteExports(value) {
1263
1523
  if (typeof value !== "object" || value === null) {
@@ -1298,7 +1558,7 @@ function createBunAdapter(adapterConfig, renderConfig) {
1298
1558
  const { pathname } = url;
1299
1559
  const staticPathname = normalizeStaticPath(pathname);
1300
1560
  for (const searchDirectory of searchDirectories) {
1301
- const filePath = join15(searchDirectory, staticPathname);
1561
+ const filePath = join16(searchDirectory, staticPathname);
1302
1562
  if (isReadableFile2(filePath)) {
1303
1563
  return new Response(readFileSync3(filePath), {
1304
1564
  headers: {
@@ -1311,7 +1571,7 @@ function createBunAdapter(adapterConfig, renderConfig) {
1311
1571
  const apiRoutes = manifest.routes.filter((route) => route.kind === "api");
1312
1572
  const apiMatch = matchRoute(pathname, apiRoutes);
1313
1573
  if (apiMatch) {
1314
- const modulePath = join15(adapterConfig.appDir, apiMatch.entry.filePath);
1574
+ const modulePath = join16(adapterConfig.appDir, apiMatch.entry.filePath);
1315
1575
  const routeModule = await import(modulePath);
1316
1576
  if (!isApiRouteExports(routeModule)) {
1317
1577
  return new Response("Invalid API route module", {
@@ -1337,8 +1597,10 @@ function createBunAdapter(adapterConfig, renderConfig) {
1337
1597
  timestampMs: runtimeContext.timestampMs
1338
1598
  }, {
1339
1599
  appName: adapterConfig.appName,
1600
+ title: adapterConfig.seo.defaultTitle,
1601
+ description: adapterConfig.seo.defaultDescription,
1340
1602
  scriptPath: "/app.js",
1341
- cssPath: "/globals.css",
1603
+ cssPath: "/app.css",
1342
1604
  lang: "en"
1343
1605
  });
1344
1606
  if (renderResult.kind === "failure") {
@@ -1378,9 +1640,10 @@ async function startCommand(cwd = process.cwd()) {
1378
1640
  port: projectConfig.port,
1379
1641
  host: projectConfig.server.hostname ?? "0.0.0.0",
1380
1642
  appName: projectConfig.appName,
1381
- appDir: join16(cwd, projectConfig.appDir),
1382
- publicDir: join16(cwd, projectConfig.publicDir),
1383
- outDir: join16(cwd, outputDirectory)
1643
+ seo: projectConfig.seo,
1644
+ appDir: join17(cwd, projectConfig.appDir),
1645
+ publicDir: join17(cwd, projectConfig.publicDir),
1646
+ outDir: join17(cwd, outputDirectory)
1384
1647
  }, projectConfig.render);
1385
1648
  await serverAdapter.start();
1386
1649
  }
@@ -1446,7 +1709,7 @@ async function runForgeInspect() {
1446
1709
  const projectConfig = await loadConfig(cwd);
1447
1710
  const outputDirectory = projectConfig.build.outDir ?? "dist";
1448
1711
  const inspectReport = inspectBuild({
1449
- outDir: join17(cwd, outputDirectory),
1712
+ outDir: join18(cwd, outputDirectory),
1450
1713
  renderConfig: projectConfig.render
1451
1714
  });
1452
1715
  printInspectReport(inspectReport);
@@ -1514,4 +1777,4 @@ ${BOLD}${RED}Rakta.js error:${RESET} ${errorMessage}
1514
1777
  process.exit(1);
1515
1778
  });
1516
1779
 
1517
- //# debugId=E78251A4223C95A364756E2164756E21
1780
+ //# debugId=E8EBE1B7DAC0DE3364756E2164756E21