fulgur 0.0.1

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/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "fulgur",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "Vite-native fullstack framework powered by Bun, Vite and Elysia.",
6
+ "author": {
7
+ "name": "Pierre Houllière",
8
+ "email": "hi@houlliere.com",
9
+ "url": "https://github.com/wiizzl"
10
+ },
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "directory": "packages/core",
15
+ "url": "git+https://github.com/wiizzl/fulgur.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/wiizzl/fulgur/issues"
19
+ },
20
+ "keywords": [
21
+ "vite",
22
+ "elysia",
23
+ "bun",
24
+ "framework",
25
+ "rpc",
26
+ "fullstack"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "exports": {
32
+ "./server": {
33
+ "import": "./src/server.ts"
34
+ },
35
+ "./client": {
36
+ "import": "./src/client.ts"
37
+ },
38
+ "./vite": {
39
+ "import": "./src/plugin/index.ts"
40
+ }
41
+ },
42
+ "peerDependencies": {
43
+ "vite": "^8.0.14"
44
+ },
45
+ "dependencies": {
46
+ "@elysia/static": "^1.4.11",
47
+ "@elysia/eden": "^1.4.10",
48
+ "elysia": "^1.4.28"
49
+ },
50
+ "devDependencies": {
51
+ "@types/bun": "^1.3.14",
52
+ "typescript": "^6.0.3"
53
+ }
54
+ }
package/src/client.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@elysia/eden";
@@ -0,0 +1,100 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { Glob } from "bun";
5
+
6
+ const ensureDir = (path: string) => {
7
+ if (!existsSync(path)) {
8
+ mkdirSync(path, { recursive: true });
9
+ }
10
+ };
11
+
12
+ export function generateTsConfig(root: string) {
13
+ const pluginDir = resolve(root, ".fulgur");
14
+ ensureDir(pluginDir);
15
+
16
+ const tsconfigContent = {
17
+ compilerOptions: {
18
+ moduleResolution: "bundler",
19
+ module: "ESNext",
20
+ paths: {
21
+ "fulgur/api": ["./api.ts"],
22
+ },
23
+ },
24
+ };
25
+
26
+ writeFileSync(join(pluginDir, "tsconfig.paths.json"), JSON.stringify(tsconfigContent, null, 2));
27
+ }
28
+
29
+ export function generateClient(root: string) {
30
+ const pluginDir = resolve(root, ".fulgur");
31
+
32
+ ensureDir(pluginDir);
33
+ generateTsConfig(root);
34
+
35
+ const normalizePath = (filePath: string) => filePath.replaceAll("\\", "/");
36
+ const shouldIgnore = (filePath: string) => {
37
+ const normalized = normalizePath(filePath);
38
+ return (
39
+ normalized.startsWith("node_modules/") ||
40
+ normalized.includes("/node_modules/") ||
41
+ normalized.startsWith("dist/") ||
42
+ normalized.startsWith(".fulgur/")
43
+ );
44
+ };
45
+
46
+ const toSafeName = (filePath: string) => {
47
+ const normalized = normalizePath(filePath);
48
+ const base = normalized.replace(/[^\w]/g, "_").replace(/^_+|_+$/g, "");
49
+ const hash = createHash("sha1").update(normalized).digest("hex").slice(0, 8);
50
+ const candidate = base ? `${base}_${hash}` : `route_${hash}`;
51
+ return /^\d/.test(candidate) ? `_${candidate}` : candidate;
52
+ };
53
+
54
+ const glob = new Glob("**/*.server.ts");
55
+ const files = Array.from(glob.scanSync(root))
56
+ .map((file) => normalizePath(file))
57
+ .filter((file) => !shouldIgnore(file))
58
+ .sort((a, b) => a.localeCompare(b));
59
+
60
+ const routeFiles = files.filter((file) => {
61
+ try {
62
+ const absolutePath = join(root, file);
63
+ const content = readFileSync(absolutePath, "utf-8");
64
+
65
+ return /export\s+(const|let|var)\s+fulgur\s*=/.test(content);
66
+ } catch {
67
+ return false;
68
+ }
69
+ });
70
+
71
+ const importsCode = routeFiles
72
+ .map((file) => {
73
+ const safeName = toSafeName(file);
74
+ const importPath = file.replace(/\.ts$/, "");
75
+ return `import { fulgur as ${safeName} } from "../${importPath}";`;
76
+ })
77
+ .join("\n");
78
+
79
+ const useCode = routeFiles
80
+ .map((file) => {
81
+ const safeName = toSafeName(file);
82
+ return ` .use(${safeName})`;
83
+ })
84
+ .join("\n");
85
+
86
+ const routerTemplate = `import { Elysia } from "fulgur/server";
87
+ ${importsCode}
88
+ export const appRouter = new Elysia()
89
+ ${useCode};
90
+ export type AppRouter = typeof appRouter;
91
+ `;
92
+
93
+ const apiTemplate = `import { treaty } from "fulgur/client";
94
+ import type { AppRouter } from "./router";
95
+ export const api = treaty<AppRouter>(typeof window !== "undefined" ? \`\${window.location.origin}/api\` : "");
96
+ `;
97
+
98
+ writeFileSync(join(pluginDir, "router.ts"), routerTemplate);
99
+ writeFileSync(join(pluginDir, "api.ts"), apiTemplate);
100
+ }
@@ -0,0 +1,132 @@
1
+ import { spawn } from "node:child_process";
2
+ import { once } from "node:events";
3
+ import { type AddressInfo, createServer } from "node:net";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import type { Plugin, ResolvedConfig } from "vite";
7
+
8
+ import { generateClient } from "./generator";
9
+
10
+ const getAvailablePort = async (): Promise<number> => {
11
+ const server = createServer();
12
+
13
+ server.listen(0);
14
+
15
+ await once(server, "listening");
16
+
17
+ const { port } = server.address() as AddressInfo;
18
+
19
+ server.close();
20
+ await once(server, "close");
21
+
22
+ return port;
23
+ };
24
+
25
+ const runnerPath = fileURLToPath(import.meta.resolve("./runner.ts"));
26
+
27
+ export default function fulgur(): Plugin {
28
+ let backendProcess: ReturnType<typeof spawn> | null = null;
29
+ let viteConfig: ResolvedConfig;
30
+ let port: number;
31
+ let restartTimeout: ReturnType<typeof setTimeout> | null = null;
32
+
33
+ const killBackend = () => {
34
+ if (backendProcess && !backendProcess.killed) {
35
+ backendProcess.kill("SIGTERM");
36
+ backendProcess = null;
37
+ }
38
+ };
39
+
40
+ const startOrRestartBackend = () => {
41
+ killBackend();
42
+
43
+ backendProcess = spawn("bun", ["run", runnerPath], {
44
+ stdio: "pipe",
45
+ env: {
46
+ ...process.env,
47
+ FULGUR_PORT: port.toString(),
48
+ FULGUR_ROOT: viteConfig.root,
49
+ },
50
+ });
51
+
52
+ backendProcess.stdout?.on("data", (data) => {
53
+ process.stdout.write(`[Fulgur] ${data}`);
54
+ });
55
+
56
+ backendProcess.stderr?.on("data", (data) => {
57
+ process.stderr.write(`[Fulgur] ${data}`);
58
+ });
59
+ };
60
+
61
+ const debouncedRestart = () => {
62
+ if (restartTimeout) {
63
+ clearTimeout(restartTimeout);
64
+ }
65
+
66
+ restartTimeout = setTimeout(() => {
67
+ startOrRestartBackend();
68
+ }, 150);
69
+ };
70
+
71
+ return {
72
+ name: "vite-plugin-fulgur",
73
+ enforce: "pre",
74
+ load(id) {
75
+ if (id.includes(".server.ts")) {
76
+ throw new Error(`You can't import server code into the client in ${id}.`);
77
+ }
78
+ },
79
+ async config(userConfig) {
80
+ const root = userConfig.root || process.cwd();
81
+ port = await getAvailablePort();
82
+
83
+ return {
84
+ resolve: {
85
+ alias: {
86
+ "fulgur/api": resolve(root, ".fulgur/api.ts"),
87
+ },
88
+ },
89
+ server: {
90
+ proxy: {
91
+ "/api": {
92
+ target: `http://localhost:${port}`,
93
+ changeOrigin: true,
94
+ rewrite: (path) => path.replace(/^\/api/, ""),
95
+ },
96
+ },
97
+ },
98
+ };
99
+ },
100
+ configResolved(config) {
101
+ viteConfig = config;
102
+ },
103
+ buildStart() {
104
+ generateClient(viteConfig.root);
105
+ },
106
+ configureServer(server) {
107
+ startOrRestartBackend();
108
+
109
+ server.httpServer?.once("close", killBackend);
110
+ process.on("SIGINT", () => {
111
+ killBackend();
112
+ process.exit();
113
+ });
114
+ process.on("SIGTERM", () => {
115
+ killBackend();
116
+ process.exit();
117
+ });
118
+ process.on("exit", killBackend);
119
+
120
+ server.watcher.on("all", (_, path) => {
121
+ if (path.endsWith(".server.ts")) {
122
+ generateClient(viteConfig.root);
123
+ debouncedRestart();
124
+ }
125
+ });
126
+ },
127
+ configurePreviewServer() {
128
+ console.log("This command has been disabled by Fulgur.");
129
+ process.exit(1);
130
+ },
131
+ };
132
+ }
@@ -0,0 +1,51 @@
1
+ import { join } from "node:path";
2
+ import { Glob } from "bun";
3
+ import { Elysia } from "elysia";
4
+
5
+ async function startServer() {
6
+ const root = process.env.FULGUR_ROOT;
7
+ const port = process.env.FULGUR_PORT;
8
+
9
+ if (!root || !port) {
10
+ console.error("Some environment variables not set");
11
+ return;
12
+ }
13
+
14
+ const glob = new Glob("**/*.server.ts");
15
+ const api = new Elysia();
16
+
17
+ const normalizePath = (filePath: string) => filePath.replaceAll("\\", "/");
18
+ const shouldIgnore = (filePath: string) => {
19
+ const normalized = normalizePath(filePath);
20
+ return (
21
+ normalized.startsWith("node_modules/") ||
22
+ normalized.includes("/node_modules/") ||
23
+ normalized.startsWith("dist/") ||
24
+ normalized.startsWith(".fulgur/")
25
+ );
26
+ };
27
+
28
+ for await (const relativePath of glob.scan(root)) {
29
+ const normalized = normalizePath(relativePath);
30
+ if (shouldIgnore(normalized)) {
31
+ continue;
32
+ }
33
+
34
+ const absolutePath = join(root, normalized);
35
+
36
+ try {
37
+ const module = await import(absolutePath);
38
+
39
+ if (module.fulgur && module.fulgur instanceof Elysia) {
40
+ api.use(module.fulgur);
41
+ }
42
+ } catch (error) {
43
+ console.error(`Error in ${absolutePath}:`, error);
44
+ }
45
+ }
46
+
47
+ console.log(`Server running on http://localhost:${port}`);
48
+ api.listen(port);
49
+ }
50
+
51
+ startServer();
package/src/server.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { staticPlugin } from "@elysia/static";
2
+
3
+ export * from "elysia";
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "declaration": true
9
+ },
10
+ "include": ["src"]
11
+ }