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/README.md +104 -70
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/rakta.js +355 -92
- package/dist/cli/rakta.js.map +11 -10
- package/dist/cli/start.d.ts.map +1 -1
- package/dist/forge/build.d.ts.map +1 -1
- package/dist/forge/clientEntry.d.ts +9 -0
- package/dist/forge/clientEntry.d.ts.map +1 -0
- package/dist/forge/devServer.d.ts +1 -1
- package/dist/forge/devServer.d.ts.map +1 -1
- package/dist/forge/index.js +275 -22
- package/dist/forge/index.js.map +7 -6
- package/dist/forge/types.d.ts +4 -0
- package/dist/forge/types.d.ts.map +1 -1
- package/dist/index.js +283 -28
- package/dist/index.js.map +8 -7
- package/dist/render/index.js +7 -4
- package/dist/render/index.js.map +3 -3
- package/dist/render/renderer.d.ts +3 -0
- package/dist/render/renderer.d.ts.map +1 -1
- package/dist/tide/adapter.d.ts.map +1 -1
- package/dist/tide/index.js +10 -5
- package/dist/tide/index.js.map +4 -4
- package/dist/tide/types.d.ts +2 -0
- package/dist/tide/types.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
693
|
+
mkdirSync2(resolve2(options.outDir), { recursive: true });
|
|
487
694
|
const manifest = generateManifest(options.appDir);
|
|
488
|
-
const manifestPath =
|
|
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: [
|
|
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: "
|
|
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
|
|
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(
|
|
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:
|
|
559
|
-
publicDir:
|
|
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
|
|
795
|
+
import { join as join8 } from "path";
|
|
582
796
|
|
|
583
797
|
// src/forge/devServer.ts
|
|
584
|
-
import { existsSync as
|
|
585
|
-
import { join as
|
|
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
|
-
|
|
597
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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: "/
|
|
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:
|
|
781
|
-
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
|
|
789
|
-
import { join as
|
|
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:
|
|
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:
|
|
1064
|
+
passed: existsSync8(join9(cwd, config.appDir))
|
|
805
1065
|
},
|
|
806
1066
|
{
|
|
807
1067
|
label: "app/page.tsx (root page)",
|
|
808
|
-
passed:
|
|
1068
|
+
passed: existsSync8(join9(cwd, config.appDir, "page.tsx"))
|
|
809
1069
|
},
|
|
810
1070
|
{
|
|
811
1071
|
label: "app/layout.tsx (root layout)",
|
|
812
|
-
passed:
|
|
1072
|
+
passed: existsSync8(join9(cwd, config.appDir, "layout.tsx"))
|
|
813
1073
|
},
|
|
814
1074
|
{
|
|
815
1075
|
label: `public/ directory (${config.publicDir})`,
|
|
816
|
-
passed:
|
|
1076
|
+
passed: existsSync8(join9(cwd, config.publicDir))
|
|
817
1077
|
},
|
|
818
1078
|
{
|
|
819
1079
|
label: "package.json",
|
|
820
|
-
passed:
|
|
1080
|
+
passed: existsSync8(join9(cwd, "package.json"))
|
|
821
1081
|
},
|
|
822
1082
|
{
|
|
823
1083
|
label: "tsconfig.json",
|
|
824
|
-
passed:
|
|
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
|
|
852
|
-
import { join as
|
|
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
|
|
856
|
-
import { basename, extname, join as
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
1180
|
+
const outputAbs = join10(options.frontendRoot, options.outputDirectory);
|
|
921
1181
|
for (const directory of options.directories) {
|
|
922
|
-
const directoryAbs =
|
|
1182
|
+
const directoryAbs = join10(options.frontendRoot, directory);
|
|
923
1183
|
const files = walkDirectory(directoryAbs, extensions);
|
|
924
1184
|
for (const filePath of files) {
|
|
925
|
-
const relativeFromOutput =
|
|
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:
|
|
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 =
|
|
963
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1015
|
-
import { dirname, join as
|
|
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 (
|
|
1280
|
+
if (existsSync10(filePath)) {
|
|
1021
1281
|
console.warn(` Already exists: ${filePath}`);
|
|
1022
1282
|
return;
|
|
1023
1283
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
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 =
|
|
1293
|
+
const appDir = join12(cwd, config.appDir);
|
|
1034
1294
|
const componentName = toPascalCase2(name);
|
|
1035
1295
|
switch (target) {
|
|
1036
1296
|
case "page": {
|
|
1037
|
-
const filePath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1111
|
-
import { join as
|
|
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 =
|
|
1114
|
-
if (!
|
|
1115
|
-
|
|
1373
|
+
const sharedTypesDir = join14(cwd, "..", "shared", "types");
|
|
1374
|
+
if (!existsSync11(sharedTypesDir)) {
|
|
1375
|
+
mkdirSync5(sharedTypesDir, { recursive: true });
|
|
1116
1376
|
}
|
|
1117
|
-
const outputPath =
|
|
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
|
-
|
|
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
|
|
1136
|
-
import { join as
|
|
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 =
|
|
1140
|
-
const sitemapDir =
|
|
1141
|
-
const robotsDir =
|
|
1142
|
-
if (!
|
|
1143
|
-
|
|
1144
|
-
|
|
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 (!
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
|
1445
|
+
import { join as join17 } from "path";
|
|
1186
1446
|
|
|
1187
1447
|
// src/tide/adapter.ts
|
|
1188
|
-
import { existsSync as
|
|
1189
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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: "/
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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:
|
|
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=
|
|
1780
|
+
//# debugId=E8EBE1B7DAC0DE3364756E2164756E21
|