htmv 0.0.34 → 0.0.36

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.
@@ -18,8 +18,11 @@ export function render(node, context) {
18
18
  return String(resolvePropertyPath(node.value));
19
19
  }
20
20
  if (node.type === "isset") {
21
- if (context[node.itemName] !== undefined &&
22
- context[node.itemName] !== null) {
21
+ const isNegated = node.itemName.startsWith("!");
22
+ const propName = isNegated ? node.itemName.slice(1) : node.itemName;
23
+ const prop = resolvePropertyPath(propName);
24
+ const exists = isset(prop);
25
+ if (isNegated ? !exists : exists) {
23
26
  return node.children.map((node) => render(node, context)).join("");
24
27
  }
25
28
  return "";
@@ -39,3 +42,9 @@ export function render(node, context) {
39
42
  return result;
40
43
  }
41
44
  }
45
+ function isset(prop) {
46
+ if (Array.isArray(prop)) {
47
+ return prop.length > 0;
48
+ }
49
+ return prop !== undefined && prop !== null && prop !== false;
50
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "htmv",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.34",
5
+ "version": "0.0.36",
6
6
  "devDependencies": {
7
7
  "@biomejs/biome": "2.3.3",
8
8
  "@types/bun": "latest"
@@ -26,5 +26,16 @@
26
26
  "repository": {
27
27
  "type": "git",
28
28
  "url": "https://github.com/Fabrisdev/htmv"
29
- }
29
+ },
30
+ "keywords": [
31
+ "framework",
32
+ "web-framework",
33
+ "web",
34
+ "server",
35
+ "backend",
36
+ "frontend",
37
+ "cli"
38
+ ],
39
+ "license": "MIT",
40
+ "description": "A simple yet fast web framework"
30
41
  }
@@ -1,27 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
-
8
- jobs:
9
- build:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - uses: actions/checkout@v4
14
-
15
- - name: Setup Bun
16
- uses: oven-sh/setup-bun@v1
17
- with:
18
- bun-version: latest
19
-
20
- - name: Install dependencies
21
- run: bun install
22
-
23
- - name: Lint
24
- run: npm run lint
25
-
26
- - name: Run TypeScript build
27
- run: npm run build
@@ -1,42 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
-
8
- permissions:
9
- id-token: write # Required for OIDC
10
- contents: read
11
-
12
- jobs:
13
- publish:
14
- runs-on: ubuntu-latest
15
-
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - uses: actions/setup-node@v4
20
- with:
21
- node-version: '20'
22
- registry-url: 'https://registry.npmjs.org'
23
-
24
- - name: Update npm
25
- run: npm install -g npm@latest
26
-
27
- - name: Setup Bun
28
- uses: oven-sh/setup-bun@v1
29
- with:
30
- bun-version: latest
31
-
32
- - name: Install dependencies
33
- run: bun install
34
-
35
- - name: Lint
36
- run: npm run lint
37
-
38
- - name: Build (tsc)
39
- run: npm run build
40
-
41
- - name: Publish to npm
42
- run: npm publish --provenance
package/bun.lock DELETED
@@ -1,98 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 0,
4
- "workspaces": {
5
- "": {
6
- "name": "htmv",
7
- "dependencies": {
8
- "@elysiajs/node": "^1.4.2",
9
- "@elysiajs/static": "^1.4.6",
10
- "elysia": "^1.4.18",
11
- },
12
- "devDependencies": {
13
- "@biomejs/biome": "2.3.3",
14
- "@types/bun": "latest",
15
- },
16
- "peerDependencies": {
17
- "typescript": "^5",
18
- },
19
- },
20
- },
21
- "packages": {
22
- "@biomejs/biome": ["@biomejs/biome@2.3.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.3", "@biomejs/cli-darwin-x64": "2.3.3", "@biomejs/cli-linux-arm64": "2.3.3", "@biomejs/cli-linux-arm64-musl": "2.3.3", "@biomejs/cli-linux-x64": "2.3.3", "@biomejs/cli-linux-x64-musl": "2.3.3", "@biomejs/cli-win32-arm64": "2.3.3", "@biomejs/cli-win32-x64": "2.3.3" }, "bin": { "biome": "bin/biome" } }, "sha512-zn/P1pRBCpDdhi+VNSMnpczOz9DnqzOA2c48K8xgxjDODvi5O8gs3a2H233rck/5HXpkFj6TmyoqVvxirZUnvg=="],
23
-
24
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5+JtW6RKmjqL9un0UtHV0ezOslAyYBzyl5ZhYiu7GHesX2x8NCDl6tXYrenv9m7e1RLbkO5E5Kh04kseMtz6lw=="],
25
-
26
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-UPmKRalkHicvIpeccuKqq+/gA2HYV8FUnAEDJnqYBlGlycKqe6xrovWqvWTE4TTNpIFf4UQyuaDzLkN6Kz6tbA=="],
27
-
28
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-zeiKwALNB/hax7+LLhCYqhqzlWdTfgE9BGkX2Z8S4VmCYnGFrf2fON/ec6KCos7mra5MDm6fYICsEWN2+HKZhw=="],
29
-
30
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-KhCDMV+V7Yu72v40ssGJTHuv/j0n7JQ6l0s/c+EMcX5zPYLMLr4XpmI+WXhp4Vfkz0T5Xnh5wbrTBI3f2UTpjQ=="],
31
-
32
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-05CjPLbvVVU8J6eaO6iSEoA0FXKy2l6ddL+1h/VpiosCmIp3HxRKLOa1hhC1n+D13Z8g9b1DtnglGtM5U3sTag=="],
33
-
34
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-IyqQ+jYzU5MVy9CK5NV0U+NnUMPUAhYMrB/x4QgL/Dl1MqzBVc61bHeyhLnKM6DSEk73/TQYrk/8/QmVHudLdQ=="],
35
-
36
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-NtlLs3pdFqFAQYZjlEHKOwJEn3GEaz7rtR2oCrzaLT2Xt3Cfd55/VvodQ5V+X+KepLa956QJagckJrNL+DmumQ=="],
37
-
38
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-klJKPPQvUk9Rlp0Dd56gQw/+Wt6uUprHdHWtbDC93f3Iv+knA2tLWpcYoOZJgPV+9s+RBmYv0DGy4mUlr20esg=="],
39
-
40
- "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
41
-
42
- "@elysiajs/node": ["@elysiajs/node@1.4.2", "", { "dependencies": { "crossws": "^0.4.1", "srvx": "^0.9.4" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-zqeBAV4/faCcmIEjCp3g6jRwsbaWsd5HqmlEf3CirD9HkTWQNo4T+GN/qGZi7zgd84D3Kzxsny7ZTMXEfrDSXQ=="],
43
-
44
- "@elysiajs/static": ["@elysiajs/static@1.4.6", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-cd61aY/DHOVhlnBjzTBX8E1XANIrsCH8MwEGHeLMaZzNrz0gD4Q8Qsde2dFMzu81I7ZDaaZ2Rim9blSLtUrYBg=="],
45
-
46
- "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
47
-
48
- "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
49
-
50
- "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
51
-
52
- "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
53
-
54
- "@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
55
-
56
- "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
57
-
58
- "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
59
-
60
- "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
61
-
62
- "crossws": ["crossws@0.4.1", "", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-E7WKBcHVhAVrY6JYD5kteNqVq1GSZxqGrdSiwXR9at+XHi43HJoCQKXcCczR5LBnBquFZPsB3o7HklulKoBU5w=="],
63
-
64
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
65
-
66
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
67
-
68
- "elysia": ["elysia@1.4.18", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-A6BhlipmSvgCy69SBgWADYZSdDIj3fT2gk8/9iMAC8iD+aGcnCr0fitziX0xr36MFDs/fsvVp8dWqxeq1VCgKg=="],
69
-
70
- "exact-mirror": ["exact-mirror@0.2.5", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-u8Wu2lO8nio5lKSJubOydsdNtQmH8ENba5m0nbQYmTvsjksXKYIS1nSShdDlO8Uem+kbo+N6eD5I03cpZ+QsRQ=="],
71
-
72
- "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
73
-
74
- "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
75
-
76
- "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="],
77
-
78
- "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
79
-
80
- "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
81
-
82
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
83
-
84
- "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
85
-
86
- "srvx": ["srvx@0.9.5", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-nQsA2c8q3XwbSn6kTxVQjz0zS096rV+Be2pzJwrYEAdtnYszLw4MTy8JWJjz1XEGBZwP0qW51SUIX3WdjdRemQ=="],
87
-
88
- "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
89
-
90
- "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],
91
-
92
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
93
-
94
- "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
95
-
96
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
97
- }
98
- }
package/src/app.ts DELETED
@@ -1,10 +0,0 @@
1
- import staticPlugin from "@elysiajs/static";
2
- import { Elysia } from "elysia";
3
-
4
- export function createApp(publicPath: string) {
5
- return new Elysia().use(
6
- staticPlugin({
7
- assets: publicPath,
8
- }),
9
- );
10
- }
package/src/cli/cli.ts DELETED
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- import gen from "./commands/gen.js";
3
- import help from "./commands/help.js";
4
- import newCommand from "./commands/new.js";
5
- import { AVAILABLE_COMMANDS } from "./consts.js";
6
-
7
- const args = process.argv.slice(2);
8
- const command = args[0];
9
- if (command === undefined) {
10
- console.error("No command specified. Available commands are:");
11
- console.error(AVAILABLE_COMMANDS);
12
- process.exit(1);
13
- }
14
-
15
- const commandArgs = args.slice(1);
16
- const commands = {
17
- help,
18
- new: () => newCommand(commandArgs),
19
- gen: () => gen(commandArgs),
20
- };
21
-
22
- if (command in commands) {
23
- await commands[command as keyof typeof commands]();
24
- process.exit(0);
25
- }
26
- console.error("Unknown command. Available commands are:");
27
- console.error(AVAILABLE_COMMANDS);
28
- process.exit(1);
@@ -1,87 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { exists } from "../utils.js";
4
-
5
- export default async (args: string[]) => {
6
- const [type, name, ...options] = args;
7
- if (type === undefined || name === undefined)
8
- return console.error(
9
- "You must specify both a type and name of file to generate.\nCorrect usage: htmv gen {TYPE} {NAME} {OPTIONS}",
10
- );
11
- if (!["view", "route"].includes(type.toLowerCase()))
12
- return console.error("Invalid type inputted. Valid types are: view, route");
13
- if (type.toLowerCase() === "view") {
14
- const viewsFolderPath = await validateOptions("views", ...options);
15
- const viewsContents = `<!DOCTYPE html>
16
- <html lang="en">
17
-
18
- <head>
19
- <title>${name} view</title>
20
- </head>
21
-
22
- <body>
23
- <h1>This view was quickly generated with htmv gen.</h1>
24
- </body>
25
- </html>`;
26
- const fileName = `${name}.html`;
27
- const filePath = path.join(viewsFolderPath, fileName);
28
- const fileAlreadyExists = await exists(filePath);
29
- if (fileAlreadyExists)
30
- return console.error(`File ${fileName} already exists.`);
31
- await generateFile(filePath, viewsContents);
32
- }
33
- if (type.toLowerCase() === "route") {
34
- const routesFolderPath = await validateOptions("routes", ...options);
35
- const routeContents = `import { type RouteParams } from "htmv";
36
-
37
- export default (_params: RouteParams) => {
38
- return "Just a simple ALL method.";
39
- };
40
-
41
- export function POST (_params: RouteParams) {
42
- return "Searching for something more specific? How about a POST method route?"
43
- };
44
- `;
45
- const fileName = `${name}.ts`;
46
- const filePath = path.join(routesFolderPath, fileName);
47
- const fileAlreadyExists = await exists(filePath);
48
-
49
- if (fileAlreadyExists)
50
- return console.error(`File ${fileName} already exists.`);
51
- await generateFile(filePath, routeContents);
52
- }
53
- console.log(`${type} ${name} generated succesfully.`);
54
- };
55
-
56
- async function generateFile(_path: string, contents: string) {
57
- await fs.writeFile(_path, contents);
58
- }
59
-
60
- async function validateOptions(
61
- folderName: string,
62
- optionName?: string,
63
- optionValue?: string,
64
- ) {
65
- let folderPath = path.resolve(folderName);
66
- if (optionName) {
67
- if (optionName !== "--path") {
68
- console.error("Invalid option provided. Valid options are: --path.");
69
- process.exit(1);
70
- }
71
-
72
- if (optionValue === undefined) {
73
- console.error("No option value provided.");
74
- process.exit(1);
75
- }
76
-
77
- folderPath = path.resolve(optionValue);
78
- }
79
- const viewsFolderExists = await exists(folderPath);
80
- if (!viewsFolderExists) {
81
- console.error(
82
- `Unable to find ${folderName} folder. Did you change your ${folderName} folder's name? If so, did you pass the option --path with the corresponding path to it?`,
83
- );
84
- process.exit(1);
85
- }
86
- return folderPath;
87
- }
@@ -1,5 +0,0 @@
1
- import { AVAILABLE_COMMANDS } from "../consts.js";
2
-
3
- export default () => {
4
- console.log(AVAILABLE_COMMANDS);
5
- };
@@ -1,83 +0,0 @@
1
- import childProcess from "node:child_process";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { exists } from "../utils.js";
5
-
6
- export default async (args: string[]) => {
7
- const name = args[0];
8
- if (name === undefined)
9
- return console.error("No name supplied. Project creation cancelled");
10
- console.log("1. Starting project creation...");
11
- const folderAlreadyExists = await exists(name);
12
- if (folderAlreadyExists)
13
- return console.error("There already exists a folder with that name.");
14
- const fullPath = path.resolve(name);
15
- await fs.mkdir(name);
16
- console.log("2. Creating a new bun project...");
17
- await runCommand("bun init -y", {
18
- cwd: fullPath,
19
- });
20
- console.log("3. Installing dependencies...");
21
- await runCommand("bun add htmv", {
22
- cwd: fullPath,
23
- });
24
- console.log("4. Scaffolding project structure...");
25
- await fs.mkdir(path.join(fullPath, "public"));
26
- await fs.mkdir(path.join(fullPath, "routes"));
27
- await fs.mkdir(path.join(fullPath, "views"));
28
- const indexContent = `import path from "node:path";
29
- import { fileURLToPath } from "node:url";
30
- import { setup } from "htmv";
31
-
32
- const __filename = fileURLToPath(import.meta.url);
33
- const dirPath = path.dirname(__filename);
34
- setup({
35
- routes: path.join(dirPath, "routes"),
36
- views: path.join(dirPath, "views"),
37
- public: path.join(dirPath, "public"),
38
- port: 3000,
39
- });`;
40
- await fs.writeFile(path.join(fullPath, "index.ts"), indexContent);
41
- const routeContent = `import { type RouteParams, view } from "htmv";
42
-
43
- export default async (_params: RouteParams) => {
44
- return await view("example", {
45
- title: "Welcome to HTMV!",
46
- });
47
- };
48
- `;
49
- await fs.writeFile(path.join(fullPath, "routes", "index.ts"), routeContent);
50
- const viewContent = `<!DOCTYPE html>
51
- <html lang="en">
52
-
53
- <head>
54
- <title>{title}</title>
55
- </head>
56
-
57
- <body>
58
- <h1>{title}</h1>
59
- </body>
60
- </html>`;
61
- await fs.writeFile(path.join(fullPath, "views", "example.html"), viewContent);
62
- console.log("5. Creating run scripts...");
63
- await runCommand(`npm pkg set scripts.dev="bun --watch ."`, {
64
- cwd: fullPath,
65
- });
66
- await runCommand(`npm pkg set scripts.start="bun index.ts"`, {
67
- cwd: fullPath,
68
- });
69
- console.log(`All done! Project ${name} created.`);
70
- console.log(`Now run cd ${name} and start building your next big project!`);
71
- };
72
-
73
- async function runCommand(
74
- command: string,
75
- options?: childProcess.ExecOptionsWithStringEncoding,
76
- ) {
77
- const process = childProcess.exec(command, options);
78
- return new Promise((resolve) => {
79
- process.on("exit", () => {
80
- resolve(1);
81
- });
82
- });
83
- }
package/src/cli/consts.ts DELETED
@@ -1,2 +0,0 @@
1
- export const AVAILABLE_COMMANDS =
2
- "1. htmv new {project_name} (creates a new HTMV project)\n2. htmv help (prints this page)\n3. htmv gen {TYPE} {NAME} {OPTIONS} (generates files for you)";
package/src/cli/utils.ts DELETED
@@ -1,10 +0,0 @@
1
- import fs from "node:fs/promises";
2
-
3
- export async function exists(path: string) {
4
- try {
5
- await fs.access(path);
6
- return true;
7
- } catch {
8
- return false;
9
- }
10
- }
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- import { createApp } from "./app";
2
- import { registerRoutes } from "./routing";
3
- import type { Paths } from "./types";
4
- import { setViewsPath } from "./views";
5
-
6
- export type { RouteParams } from "./types";
7
- export { view } from "./views";
8
-
9
- export async function setup(paths: Paths) {
10
- setViewsPath(paths.views);
11
- const app = createApp(paths.public);
12
- await registerRoutes(app, paths.routes);
13
- app.listen(paths.port);
14
- console.log("");
15
- console.log(`HTMV running on port ${paths.port}! 🎉`);
16
- console.log(`http://localhost:${paths.port}`);
17
- }
@@ -1,113 +0,0 @@
1
- import type { Node, RootNode } from "./renderer";
2
- import type { Token } from "./tokenizer";
3
-
4
- export function parse(tokens: Token[]) {
5
- let i = 0;
6
-
7
- const root: RootNode = {
8
- type: "root",
9
- children: parseChildren(),
10
- };
11
-
12
- return root;
13
-
14
- function parseChildren(untilTag?: string): Node[] {
15
- const nodes: Node[] = [];
16
- while (i < tokens.length) {
17
- const token = tokens[i];
18
- if (
19
- untilTag &&
20
- token?.type === "close" &&
21
- token.tag.toLocaleLowerCase() === untilTag
22
- ) {
23
- i++;
24
- break;
25
- }
26
- if (token?.type === "text") {
27
- nodes.push({
28
- type: "text",
29
- text: token.text,
30
- });
31
- i++;
32
- continue;
33
- }
34
- if (token?.type === "interpolation") {
35
- nodes.push({
36
- type: "interpolation",
37
- value: token.value,
38
- });
39
- i++;
40
- continue;
41
- }
42
- if (token?.type === "open") {
43
- const tag = token.tag.toLowerCase();
44
- i++;
45
- if (tag === "for") {
46
- const nextToken = tokens[i]; //should be arguments token
47
- i++;
48
- if (nextToken?.type !== "arguments")
49
- throw new Error("Missing arguments in for");
50
- const [itemName, _in, listName] = nextToken.value;
51
- if (
52
- itemName === undefined ||
53
- _in === undefined ||
54
- listName === undefined
55
- )
56
- throw new Error(
57
- "Incorrect amount of arguments in for. Expected 3 arguments",
58
- );
59
- if (_in !== "in") throw new Error("Expected reserved word in.");
60
-
61
- const children = parseChildren(tag);
62
- nodes.push({
63
- type: "for",
64
- children,
65
- itemName,
66
- listName,
67
- });
68
- continue;
69
- }
70
- if (tag === "isset") {
71
- const nextToken = tokens[i]; //should be arguments token
72
- i++;
73
- if (nextToken?.type !== "arguments")
74
- throw new Error("Missing arguments in isset");
75
- const [itemName] = nextToken.value;
76
- if (itemName === undefined) throw new Error("Expected item name");
77
- const children = parseChildren(tag);
78
- nodes.push({
79
- type: "isset",
80
- children,
81
- itemName,
82
- });
83
- continue;
84
- }
85
- const nextToken = tokens[i]; //may be arguments token
86
- if (nextToken?.type === "arguments") {
87
- const args = nextToken.value.join(" ");
88
- nodes.push({
89
- type: "text",
90
- text: `<${tag} ${args}>`,
91
- });
92
- i++;
93
- continue;
94
- }
95
- nodes.push({
96
- type: "text",
97
- text: `<${tag}>`,
98
- });
99
- continue;
100
- }
101
- if (token?.type === "close") {
102
- if (token.tag === "for" || token.tag === "isset") continue;
103
- const tag = token.tag;
104
- nodes.push({
105
- type: "text",
106
- text: `</${tag}>`,
107
- });
108
- i++;
109
- }
110
- }
111
- return nodes;
112
- }
113
- }
@@ -1,81 +0,0 @@
1
- export type Node =
2
- | RootNode
3
- | TextNode
4
- | InterpolationNode
5
- | IssetNode
6
- | ForNode;
7
-
8
- export interface RootNode {
9
- type: "root";
10
- children: Node[];
11
- }
12
-
13
- interface TextNode {
14
- type: "text";
15
- text: string;
16
- }
17
-
18
- interface InterpolationNode {
19
- type: "interpolation";
20
- value: string;
21
- }
22
-
23
- interface IssetNode {
24
- type: "isset";
25
- itemName: string;
26
- children: Node[];
27
- }
28
-
29
- interface ForNode {
30
- type: "for";
31
- listName: string;
32
- itemName: string;
33
- children: Node[];
34
- }
35
-
36
- export function render(node: Node, context: Record<string, unknown>): string {
37
- if (node.type === "text") {
38
- return node.text;
39
- }
40
- if (node.type === "for") {
41
- const list = context[node.listName];
42
- if (Array.isArray(list)) {
43
- const output = list.map((item) => {
44
- return node.children
45
- .map((childrenNode) =>
46
- render(childrenNode, { ...context, [node.itemName]: item }),
47
- )
48
- .join("");
49
- });
50
- return output.join("");
51
- }
52
- throw new Error("La lista pasada no es un array");
53
- }
54
- if (node.type === "interpolation") {
55
- return String(resolvePropertyPath(node.value));
56
- }
57
- if (node.type === "isset") {
58
- if (
59
- context[node.itemName] !== undefined &&
60
- context[node.itemName] !== null
61
- ) {
62
- return node.children.map((node) => render(node, context)).join("");
63
- }
64
- return "";
65
- }
66
- const output = node.children.map((node) => render(node, context));
67
- return output.join("");
68
-
69
- function resolvePropertyPath(path: string) {
70
- const [variable, ...properties] = path.split(".");
71
- if (variable === undefined)
72
- throw new Error("Missing variable name on interpolation");
73
- let result = context[variable];
74
- for (const property of properties) {
75
- if (typeof result !== "object" || result === null)
76
- throw new Error("Property access attempt on non-object.");
77
- result = (result as Record<string, unknown>)[property];
78
- }
79
- return result;
80
- }
81
- }
@@ -1,123 +0,0 @@
1
- export type Token =
2
- | TextToken
3
- | InterpolationToken
4
- | OpenToken
5
- | CloseToken
6
- | ArgumentsToken;
7
-
8
- type TextToken = {
9
- type: "text";
10
- text: string;
11
- };
12
-
13
- type InterpolationToken = {
14
- type: "interpolation";
15
- value: string;
16
- };
17
-
18
- type OpenToken = {
19
- type: "open";
20
- tag: string;
21
- };
22
-
23
- type CloseToken = {
24
- type: "close";
25
- tag: string;
26
- };
27
-
28
- type ArgumentsToken = {
29
- type: "arguments";
30
- value: string[];
31
- };
32
-
33
- export function tokenize(input: string): Token[] {
34
- const tokens: Token[] = [];
35
- let textBuffer = "";
36
- for (let i = 0; i < input.length; i++) {
37
- const char = input[i];
38
- if (char === "{") {
39
- pushTextBuffer();
40
- const read = readTill("}", i, input);
41
- if (!read.success) throw new Error("Unable to find closing }");
42
- const { value, finishingPosition } = read;
43
- tokens.push({
44
- type: "interpolation",
45
- value,
46
- });
47
- i = finishingPosition;
48
- continue;
49
- }
50
-
51
- if (char === "<") {
52
- pushTextBuffer();
53
- const read = readTill(">", i, input);
54
- if (!read.success) throw new Error("Unable to find closing '>'");
55
- const { value, finishingPosition } = read;
56
- const values = value.trim().split(/\s+/);
57
- const tag = values[0];
58
- if (tag === undefined) throw new Error("Unable to find tag name");
59
- if (tag[0] === "/") {
60
- const tagName = tag.substring(1);
61
- tokens.push({
62
- type: "close",
63
- tag: tagName,
64
- });
65
- i = finishingPosition;
66
- continue;
67
- }
68
- const args = values.slice(1);
69
- tokens.push({
70
- type: "open",
71
- tag,
72
- });
73
- if (args.length > 0) {
74
- tokens.push({
75
- type: "arguments",
76
- value: args,
77
- });
78
- }
79
-
80
- i = finishingPosition;
81
- continue;
82
- }
83
-
84
- textBuffer += char;
85
- }
86
- pushTextBuffer();
87
- return tokens;
88
-
89
- function pushTextBuffer() {
90
- if (textBuffer.length > 0) {
91
- tokens.push({
92
- type: "text",
93
- text: textBuffer,
94
- });
95
- textBuffer = "";
96
- }
97
- }
98
- }
99
-
100
- function readTill(
101
- charToSearchFor: string,
102
- startingPosition: number,
103
- text: string,
104
- ):
105
- | {
106
- success: true;
107
- value: string;
108
- finishingPosition: number;
109
- }
110
- | {
111
- success: false;
112
- } {
113
- const finishingPosition = text.indexOf(charToSearchFor, startingPosition + 1);
114
- if (finishingPosition === -1) {
115
- return { success: false };
116
- }
117
- const substring = text.substring(startingPosition + 1, finishingPosition);
118
- return {
119
- success: true,
120
- value: substring,
121
- finishingPosition,
122
- };
123
- }
package/src/routing.ts DELETED
@@ -1,43 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type Elysia from "elysia";
4
- import type { RouteFn } from "./types";
5
-
6
- export async function registerRoutes(
7
- app: Elysia,
8
- baseDir: string,
9
- prefix = "/",
10
- ) {
11
- const entries = await fs.readdir(baseDir, { withFileTypes: true });
12
- for (const entry of entries) {
13
- const fullPath = path.join(baseDir, entry.name);
14
- if (entry.isDirectory()) {
15
- await registerRoutes(app, fullPath, path.join(prefix, entry.name));
16
- continue;
17
- }
18
- if (entry.name !== "index.ts") continue;
19
- const module = (await import(fullPath)) as Record<string, unknown>;
20
- const defaultFn = module.default;
21
- if (defaultFn && typeof defaultFn === "function") {
22
- app.all(prefix, async ({ request, query, params }) => {
23
- const result = await defaultFn({ request, query, params });
24
- return result;
25
- });
26
- console.log(`Registered ${fullPath} on ${prefix} route with method all`);
27
- }
28
- for (const propName in module) {
29
- const prop = module[propName];
30
- if (typeof prop !== "function") continue;
31
- const fn = prop as RouteFn;
32
- const name = fn.name.toLowerCase();
33
- if (!["get", "post", "put", "patch", "delete"].includes(name)) continue;
34
- app[name as "get"](prefix, async ({ request, query, params }) => {
35
- const result = await fn({ request, query, params });
36
- return result;
37
- });
38
- console.log(
39
- `Registered ${fullPath} on ${prefix} route with method ${name}`,
40
- );
41
- }
42
- }
43
- }
package/src/types.ts DELETED
@@ -1,16 +0,0 @@
1
- export type RouteParams = {
2
- query: Record<string, string>;
3
- request: Request;
4
- params: Record<string, string>;
5
- };
6
-
7
- export type Paths = {
8
- routes: string;
9
- views: string;
10
- public: string;
11
- port: number;
12
- };
13
-
14
- export type RouteFn = (
15
- _: RouteParams,
16
- ) => Promise<Response> | Response | Promise<string> | string;
package/src/views.ts DELETED
@@ -1,26 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { parse } from "./next-parser/parser";
4
- import { render } from "./next-parser/renderer";
5
- import { tokenize } from "./next-parser/tokenizer";
6
-
7
- let viewsPath = "";
8
-
9
- export function setViewsPath(path: string) {
10
- viewsPath = path;
11
- }
12
-
13
- export async function view(view: string, props: Record<string, unknown>) {
14
- if (viewsPath === "")
15
- throw new Error(
16
- "Views folder path not yet configured. Use `Htmv.setup` before rendering a view.",
17
- );
18
- const filePath = path.join(viewsPath, `${view}.html`);
19
- const code = await fs.readFile(filePath, "utf-8");
20
- const tokens = tokenize(code);
21
- const root = parse(tokens);
22
- const rendered = render(root, props);
23
- return new Response(rendered, {
24
- headers: { "Content-Type": "text/html; charset=utf-8" },
25
- });
26
- }
package/tsconfig.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "allowJs": true,
9
-
10
- // Bundler mode
11
- "moduleResolution": "bundler",
12
- "verbatimModuleSyntax": true,
13
-
14
- // Best practices
15
- "strict": true,
16
- "skipLibCheck": true,
17
- "noFallthroughCasesInSwitch": true,
18
- "noUncheckedIndexedAccess": true,
19
- "noImplicitOverride": true,
20
-
21
- // Some stricter flags (disabled by default)
22
- "noUnusedLocals": false,
23
- "noUnusedParameters": false,
24
- "noPropertyAccessFromIndexSignature": false,
25
-
26
- // Config for npm
27
- "noEmit": false, // Changed to false to allow it to compile
28
- "outDir": "./dist",
29
- "declaration": true //generates .d.ts
30
- },
31
- "include": ["src"]
32
- }