@mauroandre/velojs 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/bin/velojs.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const cliPath = resolve(__dirname, "../src/cli.ts");
9
+
10
+ const result = spawnSync("npx", ["tsx", cliPath, ...process.argv.slice(2)], {
11
+ stdio: "inherit",
12
+ cwd: process.cwd(),
13
+ });
14
+
15
+ process.exit(result.status ?? 0);
package/package.json ADDED
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "@mauroandre/velojs",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
6
+ "keywords": [
7
+ "framework",
8
+ "fullstack",
9
+ "hono",
10
+ "preact",
11
+ "vite",
12
+ "ssr",
13
+ "typescript"
14
+ ],
15
+ "author": "Mauro André Silva",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/mauro-andre/velojs.git"
20
+ },
21
+ "homepage": "https://github.com/mauro-andre/velojs#readme",
22
+ "bin": {
23
+ "velojs": "./bin/velojs.js"
24
+ },
25
+ "files": [
26
+ "src/",
27
+ "bin/",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "scripts": {
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest"
35
+ },
36
+ "main": "./src/index.ts",
37
+ "types": "./src/index.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./src/index.ts",
41
+ "import": "./src/index.ts"
42
+ },
43
+ "./server": {
44
+ "types": "./src/server.tsx",
45
+ "import": "./src/server.tsx"
46
+ },
47
+ "./client": {
48
+ "types": "./src/client.tsx",
49
+ "import": "./src/client.tsx"
50
+ },
51
+ "./hooks": {
52
+ "types": "./src/hooks.tsx",
53
+ "import": "./src/hooks.tsx"
54
+ },
55
+ "./cookie": {
56
+ "types": "./src/cookie.ts",
57
+ "import": "./src/cookie.ts"
58
+ },
59
+ "./factory": {
60
+ "types": "./src/factory.ts",
61
+ "import": "./src/factory.ts"
62
+ },
63
+ "./vite": {
64
+ "types": "./src/vite.ts",
65
+ "import": "./src/vite.ts"
66
+ },
67
+ "./config": {
68
+ "types": "./src/config.ts",
69
+ "import": "./src/config.ts"
70
+ }
71
+ },
72
+ "typesVersions": {
73
+ "*": {
74
+ "server": [
75
+ "./src/server.tsx"
76
+ ],
77
+ "client": [
78
+ "./src/client.tsx"
79
+ ],
80
+ "hooks": [
81
+ "./src/hooks.tsx"
82
+ ],
83
+ "cookie": [
84
+ "./src/cookie.ts"
85
+ ],
86
+ "factory": [
87
+ "./src/factory.ts"
88
+ ],
89
+ "vite": [
90
+ "./src/vite.ts"
91
+ ],
92
+ "config": [
93
+ "./src/config.ts"
94
+ ]
95
+ }
96
+ },
97
+ "dependencies": {
98
+ "@babel/generator": "^7.28.5",
99
+ "@babel/parser": "^7.28.5",
100
+ "@babel/traverse": "^7.28.5",
101
+ "@babel/types": "^7.28.5",
102
+ "@hono/node-server": "^1.19.0",
103
+ "@hono/vite-dev-server": "^0.25.0",
104
+ "@preact/preset-vite": "^2.10.0",
105
+ "@preact/signals": "^2.8.0",
106
+ "hono": "^4.12.0",
107
+ "preact": "^10.29.0",
108
+ "preact-render-to-string": "^6.6.0",
109
+ "tsx": "^4.19.0",
110
+ "vite": "^7.3.0",
111
+ "wouter-preact": "^3.9.0"
112
+ },
113
+ "devDependencies": {
114
+ "@types/babel__generator": "^7.27.0",
115
+ "@types/babel__traverse": "^7.28.0",
116
+ "@types/node": "^25.5.2",
117
+ "typescript": "^6.0.2",
118
+ "vitest": "^3.2.1"
119
+ }
120
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { resolve } from "node:path";
5
+
6
+ const args = process.argv.slice(2);
7
+ const command = args[0];
8
+
9
+ const runCommand = (cmd: string, cmdArgs: string[] = []) => {
10
+ const result = spawnSync(cmd, cmdArgs, {
11
+ stdio: "inherit",
12
+ cwd: process.cwd(),
13
+ });
14
+ process.exit(result.status ?? 0);
15
+ };
16
+
17
+ const runVite = () => {
18
+ runCommand("npx", ["vite", ...args.slice(1)]);
19
+ };
20
+
21
+ const runBuild = () => {
22
+ console.log("Building client...");
23
+ spawnSync("npx", ["vite", "build"], { stdio: "inherit", cwd: process.cwd() });
24
+
25
+ console.log("Building server...");
26
+ spawnSync("npx", ["vite", "build", "--mode", "server"], { stdio: "inherit", cwd: process.cwd() });
27
+
28
+ console.log("Build complete!");
29
+ };
30
+
31
+ const runStart = () => {
32
+ const serverPath = resolve(process.cwd(), "dist/server.js");
33
+ runCommand("node", [serverPath]);
34
+ };
35
+
36
+ const showHelp = () => {
37
+ console.log(`
38
+ VeloJS CLI
39
+
40
+ Usage:
41
+ velojs <command>
42
+
43
+ Commands:
44
+ init Create a new VeloJS project
45
+ dev Start development server
46
+ build Build for production (client + server)
47
+ start Start production server
48
+
49
+ Examples:
50
+ velojs init my-app
51
+ velojs dev
52
+ velojs build
53
+ velojs start
54
+ `);
55
+ };
56
+
57
+ switch (command) {
58
+ case "init": {
59
+ const { runInit } = await import("./init.js");
60
+ await runInit(args[1]);
61
+ break;
62
+ }
63
+ case "dev":
64
+ runVite();
65
+ break;
66
+ case "build":
67
+ runBuild();
68
+ break;
69
+ case "start":
70
+ runStart();
71
+ break;
72
+ case "help":
73
+ case "--help":
74
+ case "-h":
75
+ showHelp();
76
+ break;
77
+ default:
78
+ if (command) {
79
+ console.error(`Unknown command: ${command}`);
80
+ }
81
+ showHelp();
82
+ process.exit(1);
83
+ }
package/src/client.tsx ADDED
@@ -0,0 +1,79 @@
1
+ import { hydrate } from "preact";
2
+ import { Router, Route, Switch } from "wouter-preact";
3
+ import type { VNode } from "preact";
4
+ import type { RouteNode, AppRoutes } from "./types.js";
5
+
6
+ // ============================================
7
+ // CLIENT OPTIONS
8
+ // ============================================
9
+
10
+ export interface StartClientOptions {
11
+ routes: AppRoutes;
12
+ }
13
+
14
+ // ============================================
15
+ // BUILD ROUTES - Gera rotas do wouter recursivamente
16
+ // ============================================
17
+
18
+ const buildRoutes = (nodes: RouteNode[], nested: boolean = false): VNode[] => {
19
+ return nodes.map((node, index) => {
20
+ const Component = node.module.Component;
21
+
22
+ // Leaf node - rota final
23
+ if (!node.children) {
24
+ return (
25
+ <Route key={index} path={node.path || ""}>
26
+ <Component />
27
+ </Route>
28
+ );
29
+ }
30
+
31
+ // Node com children - os children são sempre relativos com nest
32
+ const childRoutes = buildRoutes(node.children, !!node.path);
33
+
34
+ // isRoot - pula o componente, renderiza só os children
35
+ if (node.isRoot) {
36
+ return <Switch key={index}>{childRoutes}</Switch>;
37
+ }
38
+
39
+ // Sem path - wrapper puro (sem Route)
40
+ if (!node.path) {
41
+ return (
42
+ <Component key={index}>
43
+ <Switch>{childRoutes}</Switch>
44
+ </Component>
45
+ );
46
+ }
47
+
48
+ // Layout com path + nest
49
+ return (
50
+ <Route key={index} path={node.path} nest>
51
+ <Component>
52
+ <Switch>{childRoutes}</Switch>
53
+ </Component>
54
+ </Route>
55
+ );
56
+ });
57
+ };
58
+
59
+ // ============================================
60
+ // CLIENT ROUTES - Componente de rotas
61
+ // ============================================
62
+
63
+ const ClientRoutes = ({ routes }: { routes: AppRoutes }) => {
64
+ const routeTree = buildRoutes(routes);
65
+ return <Router>{routeTree}</Router>;
66
+ };
67
+
68
+ // ============================================
69
+ // START CLIENT - Entry point principal
70
+ // ============================================
71
+
72
+ export const startClient = (options: StartClientOptions) => {
73
+ const { routes } = options;
74
+ const body = document.querySelector("body");
75
+
76
+ if (body) {
77
+ hydrate(<ClientRoutes routes={routes} />, body);
78
+ }
79
+ };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * VeloJS Components
3
+ * Components that can be used in the app for script/style injection
4
+ */
5
+
6
+ import { Link as WouterLink } from "wouter-preact";
7
+ import type { ComponentChildren } from "preact";
8
+
9
+ // ============================================
10
+ // SCRIPTS COMPONENT
11
+ // ============================================
12
+
13
+ interface ScriptsProps {
14
+ /**
15
+ * Base path for static assets in production
16
+ * @default ""
17
+ */
18
+ basePath?: string;
19
+
20
+ /**
21
+ * Path to the favicon file relative to the public directory
22
+ * Set to false to disable favicon injection
23
+ * @default "/favicon.ico"
24
+ */
25
+ favicon?: string | false;
26
+ }
27
+
28
+ /**
29
+ * Injects the necessary scripts and styles for VeloJS.
30
+ * In dev mode: injects Vite HMR client and velo client script
31
+ * In production: injects compiled CSS and JS
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <head>
36
+ * <Scripts />
37
+ * </head>
38
+ * ```
39
+ */
40
+ export function Scripts({ basePath, favicon = "/favicon.ico" }: ScriptsProps = {}) {
41
+ const isDev = import.meta.env.DEV;
42
+ basePath = basePath || process.env.STATIC_BASE_URL || "";
43
+
44
+ const faviconTag = favicon !== false && (
45
+ <link rel="icon" href={`${basePath}${favicon}`} type="image/x-icon" />
46
+ );
47
+
48
+ if (isDev) {
49
+ return (
50
+ <>
51
+ {faviconTag}
52
+ <script type="module" src="/@vite/client"></script>
53
+ <script type="module" src="/__velo_client.js"></script>
54
+ </>
55
+ );
56
+ }
57
+
58
+ return (
59
+ <>
60
+ {faviconTag}
61
+ <link rel="stylesheet" href={`${basePath}/client.css`} />
62
+ <script type="module" src={`${basePath}/client.js`}></script>
63
+ </>
64
+ );
65
+ }
66
+
67
+ // ============================================
68
+ // LINK COMPONENT
69
+ // ============================================
70
+
71
+ import type { ComponentProps } from "preact";
72
+ import type { RouteModule } from "./types.js";
73
+
74
+ // Props do Link do wouter, mas com "to" estendido
75
+ type WouterLinkProps = ComponentProps<typeof WouterLink>;
76
+ type LinkProps = Omit<WouterLinkProps, "to" | "href"> & {
77
+ /**
78
+ * Destination - can be a string path or a module with metadata
79
+ */
80
+ to: string | RouteModule;
81
+
82
+ /**
83
+ * URL parameters to substitute in the path
84
+ * e.g., { id: "123" } replaces :id with 123
85
+ */
86
+ params?: Record<string, string>;
87
+
88
+ /**
89
+ * Query string parameters appended to the URL
90
+ * e.g., { company: "abc" } appends ?company=abc
91
+ */
92
+ search?: Record<string, string> | undefined;
93
+
94
+ /**
95
+ * When true, ignores current URL params and uses fullPath as-is
96
+ * By default, params are extracted from current URL and substituted
97
+ * @default false
98
+ */
99
+ absolute?: boolean;
100
+ };
101
+
102
+ /**
103
+ * Substitutes :param placeholders in a path with actual values
104
+ */
105
+ export function substituteParams(
106
+ path: string,
107
+ params: Record<string, string>
108
+ ): string {
109
+ let result = path;
110
+ for (const [key, value] of Object.entries(params)) {
111
+ result = result.replace(`:${key}`, value);
112
+ }
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * Link component for navigation.
118
+ * Accepts either a string path or a route module.
119
+ *
120
+ * @example
121
+ * ```tsx
122
+ * // With string path
123
+ * <Link to="/login">Login</Link>
124
+ *
125
+ * // With route module (relative - uses path, works with nest)
126
+ * <Link to={McpPage}>MCP</Link>
127
+ *
128
+ * // With route module (absolute - uses fullPath)
129
+ * <Link to={LoginPage} absolute>Login</Link>
130
+ *
131
+ * // With explicit params
132
+ * <Link to={UserPage} params={{ id: "123" }}>View User</Link>
133
+ * ```
134
+ */
135
+ export function Link({ to, params, search, absolute, ...rest }: LinkProps) {
136
+ const isModule = typeof to !== "string";
137
+
138
+ // Default: path (relative), absolute: fullPath
139
+ const basePath = isModule
140
+ ? (absolute ? to.metadata?.fullPath : to.metadata?.path) ?? "/"
141
+ : to;
142
+
143
+ // Substitute params if provided
144
+ const finalPath = params ? substituteParams(basePath, params) : basePath;
145
+
146
+ // Absolute module paths: prefix with ~ for wouter absolute navigation
147
+ const routePath = isModule && absolute ? `~${finalPath}` : finalPath;
148
+
149
+ // Append query string if search params provided
150
+ const queryString = search
151
+ ? `?${new URLSearchParams(search).toString()}`
152
+ : "";
153
+
154
+ return <WouterLink to={`${routePath}${queryString}`} {...rest} />;
155
+ }
package/src/config.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface VeloConfig {
2
+ /**
3
+ * Diretório base da aplicação onde estão as rotas
4
+ * @default "./app"
5
+ */
6
+ appDirectory?: string;
7
+
8
+ /**
9
+ * Arquivo de rotas com export default AppRoutes
10
+ * @default "routes.tsx"
11
+ */
12
+ routesFile?: string;
13
+
14
+ /**
15
+ * Arquivo de inicialização do servidor (custom init, sem velojs imports)
16
+ * @default "server.tsx"
17
+ */
18
+ serverInit?: string;
19
+
20
+ /**
21
+ * Arquivo de inicialização do cliente (custom init, sem velojs imports)
22
+ * @default "client.tsx"
23
+ */
24
+ clientInit?: string;
25
+ }
26
+
27
+ export function defineConfig(config: VeloConfig): VeloConfig {
28
+ return config;
29
+ }
package/src/cookie.ts ADDED
@@ -0,0 +1,7 @@
1
+ export {
2
+ getCookie,
3
+ getSignedCookie,
4
+ setCookie,
5
+ setSignedCookie,
6
+ deleteCookie,
7
+ } from "hono/cookie";
package/src/factory.ts ADDED
@@ -0,0 +1 @@
1
+ export { createMiddleware, createFactory } from "hono/factory";