create-gardener 1.1.13 → 2.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.
Files changed (46) hide show
  1. package/Readme.md +6 -2
  2. package/package.json +1 -1
  3. package/starter.js +5 -2
  4. package/template/.env +0 -0
  5. package/template/buildHelper.js +3 -0
  6. package/template/jsconfig.json +25 -0
  7. package/template/package.json +10 -12
  8. package/template/pnpm-lock.yaml +62 -0
  9. package/template/src/backend/controllers/gardener/addComponent.ts +27 -0
  10. package/template/src/backend/controllers/gardener/addPage.ts +60 -0
  11. package/template/src/backend/controllers/gardener/createStatic.ts +84 -0
  12. package/template/src/backend/controllers/gardener/imageOptimiser.ts +67 -0
  13. package/template/src/backend/controllers/gardener/index.ts +4 -0
  14. package/template/src/backend/libs/generateWebp.ts +1 -0
  15. package/template/src/backend/routes/gardener.route.ts +15 -5
  16. package/template/src/backend/server.ts +8 -10
  17. package/template/src/frontend/assets/favicon.png +0 -0
  18. package/template/src/frontend/frontendtemplate.ejs +6 -17
  19. package/template/src/frontend/static/cache/favicon_500x500.webp +0 -0
  20. package/template/src/frontend/static/cache/favicon_50x50.webp +0 -0
  21. package/template/src/frontend/static/cache/gardener_50x50.webp +0 -0
  22. package/template/src/frontend/static/components/copybtn.js +86 -0
  23. package/template/src/frontend/static/components/nonui/api.js +33 -16
  24. package/template/src/frontend/static/components/nonui/navigation.js +59 -0
  25. package/template/src/frontend/static/gardener.js +0 -388
  26. package/template/src/frontend/static/gardenerConfig.js +1 -0
  27. package/template/src/frontend/static/gardenerDev.js +408 -0
  28. package/template/src/frontend/static/global.js +2 -56
  29. package/template/src/frontend/static/pages/_.js +20 -0
  30. package/template/src/frontend/static/pages/_get-started.js +5 -0
  31. package/template/src/frontend/static/style.css +1 -1
  32. package/template/src/frontend/static/style2.css +1 -1
  33. package/template/src/frontend/static/zod.js +8 -0
  34. package/template/src/frontend/views/_.ejs +96 -216
  35. package/template/src/frontend/views/_get-started.ejs +25 -0
  36. package/template/src/frontend/views/partials/icons/clipboard.ejs +1 -0
  37. package/template/src/frontend/views/partials/icons/clipboardok.ejs +1 -0
  38. package/template/tsconfig.json +1 -1
  39. package/template/src/backend/controllers/gardener.controller.ts +0 -193
  40. package/template/src/frontend/gardenerST.js +0 -431
  41. package/template/src/frontend/static/components/emailsvg.js +0 -55
  42. package/template/src/frontend/static/components/eyeoff.js +0 -50
  43. package/template/src/frontend/static/components/eyeon.js +0 -43
  44. package/template/src/frontend/static/components/passwordBox.js +0 -105
  45. package/template/src/frontend/views/_login.ejs +0 -75
  46. package/template/src/frontend/views/partials/loader.ejs +0 -3
package/Readme.md CHANGED
@@ -1,5 +1,3 @@
1
- ---
2
-
3
1
  # 🌱 Gardener
4
2
 
5
3
  **Gardener** is a lightweight, DOM-first front-end library for building and manipulating HTML/SVG elements using a clean, declarative JavaScript object syntax.
@@ -13,6 +11,12 @@ Everything is explicit and inspectable directly in the browser.
13
11
 
14
12
  ---
15
13
 
14
+ ## Version: 2.0.0
15
+
16
+ * lsp support on frontend
17
+ * separate files for js logic inside /static/pages/(filename)
18
+ * bug fixes in application and scripts
19
+
16
20
  ## ✨ Philosophy
17
21
 
18
22
  Gardener follows a **DOM-first, deterministic approach**:
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "https://github.com/ritishDas/gardener"
7
7
  },
8
- "version": "1.1.13",
8
+ "version": "2.0.1",
9
9
  "description": "A dom gardener converting dom elements into json and vice versa",
10
10
  "main": "index.js",
11
11
  "bin": {
package/starter.js CHANGED
@@ -50,8 +50,11 @@ console.log(`
50
50
 
51
51
  Next steps:
52
52
  cd ${projectName}
53
- pnpm install
54
- pnpm dev
53
+
54
+ pnpm install / npm install
55
+ pnpm dev / npm run dev
56
+
57
+
55
58
  `);
56
59
 
57
60
 
package/template/.env ADDED
File without changes
@@ -6,6 +6,9 @@ async function buildHelper() {
6
6
  const dest = path.resolve('build', 'frontend');
7
7
 
8
8
  await fs.cp(src, dest, { recursive: true });
9
+
10
+ await fs.writeFile(path.join(dest, 'static', 'gardenerConfig.js'), "export const mode = 'prod';", 'utf8');
11
+
9
12
  }
10
13
 
11
14
  buildHelper();// const path = require('path');
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "nodenext",
5
+ "moduleResolution": "Bundler",
6
+ "checkJs": true,
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "/static/*.js": ["src/frontend/static/*.js"]
10
+ },
11
+
12
+
13
+ "sourceMap": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+
17
+
18
+ "verbatimModuleSyntax": true,
19
+ "isolatedModules": true,
20
+ "noUncheckedSideEffectImports": true,
21
+ "moduleDetection": "force",
22
+ "skipLibCheck": true
23
+ },
24
+ "include": ["src/frontend/static/**/*.js"]
25
+ }
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "create-gardener",
3
- "version": "1.1.11",
3
+ "version": "2.0.0",
4
4
  "description": "A dom gardener converting dom elements into json and vice versa",
5
- "main": "src/build/server.js",
6
5
  "type": "module",
7
6
  "scripts": {
8
- "start": "node src/build/server.js",
9
- "test": "echo \"Error: no test specified\" && exit 1",
10
- "dev": "concurrently \"tsx watch src/backend/server.ts\" \"tailwindcss -w -i src/frontend/tailwind.css -o src/frontend/static/style.css\"",
11
- "build": "tailwindcss -i src/frontend/tailwind.css -o src/frontend/static/style.css --minify && tsc && node buildHelper.js"
7
+ "start": "cross-env NODE_ENV=production node build/backend/server.js",
8
+ "dev": "concurrently \"cross-env NODE_ENV=development tsx watch src/backend/server.ts\" \"tailwindcss -w -i src/frontend/tailwind.css -o src/frontend/static/style.css\"",
9
+ "build": "tailwindcss -i src/frontend/tailwind.css -o src/frontend/static/style.css --minify && tsc && node buildHelper.js",
10
+ "test": "echo \"Error: no test specified\" && exit 1"
12
11
  },
13
12
  "keywords": [],
14
13
  "author": "ritishDas",
@@ -18,17 +17,16 @@
18
17
  "ejs": "^3.1.10",
19
18
  "express": "^5.2.1",
20
19
  "sharp": "^0.34.5",
21
- "types": "^0.1.1",
22
20
  "zod": "^4.3.6"
23
21
  },
24
22
  "devDependencies": {
25
- "tailwindcss": "^4.1.18",
26
- "tsx": "^4.21.0",
27
23
  "@types/ejs": "^3.1.5",
28
24
  "@types/express": "^5.0.6",
29
25
  "@types/node": "^25.0.2",
30
- "typescript": "^5.9.3",
31
- "concurrently": "^9.2.1"
26
+ "concurrently": "^9.2.1",
27
+ "tailwindcss": "^4.1.18",
28
+ "cross-env": "^10.1.0",
29
+ "tsx": "^4.21.0",
30
+ "typescript": "^5.9.3"
32
31
  }
33
32
  }
34
-
@@ -8,6 +8,9 @@ importers:
8
8
 
9
9
  .:
10
10
  dependencies:
11
+ cross-env:
12
+ specifier: ^10.1.0
13
+ version: 10.1.0
11
14
  dotenv:
12
15
  specifier: ^17.2.3
13
16
  version: 17.3.1
@@ -54,6 +57,9 @@ packages:
54
57
  '@emnapi/runtime@1.8.1':
55
58
  resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
56
59
 
60
+ '@epic-web/invariant@1.0.0':
61
+ resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
62
+
57
63
  '@esbuild/aix-ppc64@0.27.3':
58
64
  resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
59
65
  engines: {node: '>=18'}
@@ -453,6 +459,15 @@ packages:
453
459
  resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
454
460
  engines: {node: '>= 0.6'}
455
461
 
462
+ cross-env@10.1.0:
463
+ resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
464
+ engines: {node: '>=20'}
465
+ hasBin: true
466
+
467
+ cross-spawn@7.0.6:
468
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
469
+ engines: {node: '>= 8'}
470
+
456
471
  debug@4.4.3:
457
472
  resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
458
473
  engines: {node: '>=6.0'}
@@ -601,6 +616,9 @@ packages:
601
616
  is-promise@4.0.0:
602
617
  resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
603
618
 
619
+ isexe@2.0.0:
620
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
621
+
604
622
  jake@10.9.4:
605
623
  resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
606
624
  engines: {node: '>=10'}
@@ -652,6 +670,10 @@ packages:
652
670
  resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
653
671
  engines: {node: '>= 0.8'}
654
672
 
673
+ path-key@3.1.1:
674
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
675
+ engines: {node: '>=8'}
676
+
655
677
  path-to-regexp@8.3.0:
656
678
  resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
657
679
 
@@ -711,6 +733,14 @@ packages:
711
733
  resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
712
734
  engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
713
735
 
736
+ shebang-command@2.0.0:
737
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
738
+ engines: {node: '>=8'}
739
+
740
+ shebang-regex@3.0.0:
741
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
742
+ engines: {node: '>=8'}
743
+
714
744
  shell-quote@1.8.3:
715
745
  resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
716
746
  engines: {node: '>= 0.4'}
@@ -794,6 +824,11 @@ packages:
794
824
  resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
795
825
  engines: {node: '>= 0.8'}
796
826
 
827
+ which@2.0.2:
828
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
829
+ engines: {node: '>= 8'}
830
+ hasBin: true
831
+
797
832
  wrap-ansi@7.0.0:
798
833
  resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
799
834
  engines: {node: '>=10'}
@@ -823,6 +858,8 @@ snapshots:
823
858
  tslib: 2.8.1
824
859
  optional: true
825
860
 
861
+ '@epic-web/invariant@1.0.0': {}
862
+
826
863
  '@esbuild/aix-ppc64@0.27.3':
827
864
  optional: true
828
865
 
@@ -1119,6 +1156,17 @@ snapshots:
1119
1156
 
1120
1157
  cookie@0.7.2: {}
1121
1158
 
1159
+ cross-env@10.1.0:
1160
+ dependencies:
1161
+ '@epic-web/invariant': 1.0.0
1162
+ cross-spawn: 7.0.6
1163
+
1164
+ cross-spawn@7.0.6:
1165
+ dependencies:
1166
+ path-key: 3.1.1
1167
+ shebang-command: 2.0.0
1168
+ which: 2.0.2
1169
+
1122
1170
  debug@4.4.3:
1123
1171
  dependencies:
1124
1172
  ms: 2.1.3
@@ -1299,6 +1347,8 @@ snapshots:
1299
1347
 
1300
1348
  is-promise@4.0.0: {}
1301
1349
 
1350
+ isexe@2.0.0: {}
1351
+
1302
1352
  jake@10.9.4:
1303
1353
  dependencies:
1304
1354
  async: 3.2.6
@@ -1337,6 +1387,8 @@ snapshots:
1337
1387
 
1338
1388
  parseurl@1.3.3: {}
1339
1389
 
1390
+ path-key@3.1.1: {}
1391
+
1340
1392
  path-to-regexp@8.3.0: {}
1341
1393
 
1342
1394
  picocolors@1.1.1: {}
@@ -1439,6 +1491,12 @@ snapshots:
1439
1491
  '@img/sharp-win32-ia32': 0.34.5
1440
1492
  '@img/sharp-win32-x64': 0.34.5
1441
1493
 
1494
+ shebang-command@2.0.0:
1495
+ dependencies:
1496
+ shebang-regex: 3.0.0
1497
+
1498
+ shebang-regex@3.0.0: {}
1499
+
1442
1500
  shell-quote@1.8.3: {}
1443
1501
 
1444
1502
  side-channel-list@1.0.0:
@@ -1520,6 +1578,10 @@ snapshots:
1520
1578
 
1521
1579
  vary@1.1.2: {}
1522
1580
 
1581
+ which@2.0.2:
1582
+ dependencies:
1583
+ isexe: 2.0.0
1584
+
1523
1585
  wrap-ansi@7.0.0:
1524
1586
  dependencies:
1525
1587
  ansi-styles: 4.3.0
@@ -0,0 +1,27 @@
1
+ import type { Request, Response } from "express";
2
+ import fsp from "fs/promises";
3
+ import path from 'path';
4
+
5
+ interface AddComponentBody {
6
+ path: string;
7
+ component: string;
8
+ }
9
+
10
+ import { fileURLToPath } from "url";
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ const frontendDir = path.resolve(__dirname, '..', '..', '..', 'frontend');
15
+
16
+ export async function addComponent(req: Request<{}, {}, AddComponentBody>, res: Response) {
17
+ try {
18
+ const { path: filePath, component } = req.body;
19
+ await fsp.mkdir(path.join(frontendDir, 'static', 'components'), { recursive: true });
20
+ await fsp.writeFile(path.join(frontendDir, `${filePath}`), component, "utf8");
21
+ res.json({ success: true });
22
+
23
+ } catch (err) {
24
+ const error = err as Error;
25
+ res.json({ success: false, msg: error.message });
26
+ }
27
+ }
@@ -0,0 +1,60 @@
1
+ import type { Request, Response } from "express";
2
+ import fsp from "fs/promises";
3
+ import path from 'path';
4
+
5
+ import { fileURLToPath } from "url";
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const frontendDir = path.resolve(__dirname, '..', '..', '..', 'frontend');
10
+
11
+ export async function addPage(req: Request, res: Response) {
12
+ try {
13
+ const pagename: string = req.body.page;
14
+ const name = pagename.replaceAll('/', '_');
15
+
16
+ const templatePath = path.join(frontendDir, 'frontendtemplate.ejs');
17
+ const viewPath = path.join(frontendDir, `views`, `${name}.ejs`);
18
+ const routePath = path.resolve(__dirname, '..', '..', 'routes', 'gardener.route.ts');
19
+ const jsDir = path.join(frontendDir, 'static/pages');
20
+ const jsFilePath = path.join(jsDir, `${name}.js`);
21
+
22
+ const templateContent = await fsp.readFile(templatePath, 'utf8');
23
+ await fsp.writeFile(viewPath, templateContent, "utf8");
24
+
25
+ await replaceLastOccurrence(viewPath, '<script', `<script src="/static/pages/${name}.js" type='module'></script>`);
26
+
27
+ const routeEntry = `router.route("${pagename}").get((req: Request, res: Response) => res.render("${name}"));\n`;
28
+ await fsp.appendFile(routePath, routeEntry, "utf8");
29
+
30
+ await fsp.mkdir(jsDir, { recursive: true });
31
+ const jsContent = 'import { gardener, fetchElement, replaceElement, appendElement } from "/static/gardener.js";\n import {log, parser, addEl, State} from "/static/gardenerDev.js"';
32
+ await fsp.writeFile(jsFilePath, jsContent, "utf8");
33
+
34
+ res.json({ success: true });
35
+ } catch (err) {
36
+ const error = err as Error;
37
+ res.json({ success: false, msg: error.message });
38
+ }
39
+ }
40
+
41
+ async function replaceLastOccurrence(filePath: string, searchPattern: string, replacementLine: string) {
42
+ const content = await fsp.readFile(filePath, 'utf8');
43
+ const lines = content.split('\n');
44
+ let found = false;
45
+
46
+ for (let i = lines.length - 1; i >= 0; i--) {
47
+
48
+ if (lines[i]!.includes(searchPattern)) {
49
+ lines[i] = `${replacementLine}\n${lines[i]}`;
50
+ found = true;
51
+ break;
52
+ }
53
+ }
54
+
55
+ if (found) {
56
+ await fsp.writeFile(filePath, lines.join('\n'), 'utf8');
57
+ } else {
58
+ console.warn(`Pattern "${searchPattern}" not found in ${filePath}`);
59
+ }
60
+ }
@@ -0,0 +1,84 @@
1
+ import type { Request, Response } from "express";
2
+ import path from "path";
3
+ import fsp from "fs/promises";
4
+ import ejs from "ejs";
5
+
6
+ import { fileURLToPath } from "url";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const frontendDir = path.resolve(__dirname, '..', '..', '..', 'frontend');
11
+
12
+ export async function createStatic(req: Request, res: Response) {
13
+ try {
14
+ const viewsDir = path.join(frontendDir, "views");
15
+ const outDir = path.resolve("src/tempfrontend");
16
+ const finalOut = path.resolve("src/frontendStatic");
17
+
18
+ await fsp.mkdir(outDir, { recursive: true });
19
+ await fsp.mkdir(finalOut, { recursive: true });
20
+
21
+ const entries = await fsp.readdir(viewsDir, { withFileTypes: true });
22
+
23
+ const rendered: string[] = [];
24
+
25
+
26
+ for (const entry of entries) {
27
+ // skip folders (partials, layouts, etc.)
28
+ if (!entry.isFile()) continue;
29
+ if (!entry.name.endsWith(".ejs")) continue;
30
+
31
+ const inputPath = path.join(viewsDir, entry.name);
32
+ const outputName = entry.name.replace(/\.ejs$/, ".html");
33
+ const outputPath = path.join(outDir, outputName);
34
+
35
+ const html = await ejs.renderFile(
36
+ inputPath,
37
+ {
38
+ },
39
+ {
40
+ // async: true,
41
+ views: [viewsDir], // needed for includes
42
+ }
43
+ );
44
+
45
+ await fsp.writeFile(outputPath, html, "utf8");
46
+ rendered.push(outputName);
47
+ }
48
+
49
+ const entries3 = await fsp.readdir(outDir, { withFileTypes: true });
50
+ for (const entry of entries3) {
51
+
52
+ // "_path1_path2_path3.html" -> ["path1", "path2", "path3"]
53
+ const parts = entry.name
54
+ .replace(/^_/, "")
55
+ .replace(/\.html$/, "")
56
+ .split("_");
57
+
58
+ const targetDir = path.join(finalOut, ...parts);
59
+ const targetFile = path.join(targetDir, "index.html");
60
+
61
+ // ensure directories exist
62
+ await fsp.mkdir(targetDir, { recursive: true });
63
+ console.log('done');
64
+ // copy file
65
+ await fsp.copyFile(path.join(outDir, entry.name), targetFile);
66
+
67
+ }
68
+ await fsp.rm(outDir, { recursive: true, force: true });
69
+ await fsp.cp(
70
+ path.join(frontendDir, "static"),
71
+ path.join(finalOut, 'static'),
72
+ { recursive: true }
73
+ );
74
+
75
+ return res.json({
76
+ success: true,
77
+ generated: rendered,
78
+ outDir,
79
+ });
80
+ } catch (err) {
81
+ console.error(err);
82
+ return res.status(500).json({ error: "Static build failed" });
83
+ }
84
+ }
@@ -0,0 +1,67 @@
1
+ import type { Request, Response } from "express";
2
+ import fsp from "fs/promises";
3
+ import path from "path";
4
+ import generateWebP from "../../libs/generateWebp.js";
5
+
6
+
7
+ import { fileURLToPath } from "url";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+
12
+ export async function imageOptimiser(req: Request, res: Response) {
13
+ try {
14
+ const { name } = req.params;
15
+
16
+ if (typeof name !== 'string') return res.status(400).json({ success: false, message: "invalid path" });
17
+ // name format: test_500x300.webp
18
+ const match = name.match(/^(.+?)_(\d+)x(\d+)\.webp$/);
19
+
20
+ if (!match) {
21
+ return res.status(400).json({ error: "Invalid image format" });
22
+ }
23
+
24
+ const [, baseName, widthStr, heightStr] = match;
25
+
26
+ if (!widthStr || !heightStr) return;
27
+ const width = parseInt(widthStr, 10);
28
+ const height = parseInt(heightStr, 10);
29
+
30
+ const cacheDir = path.join(__dirname, "..", "..", "..", "frontend", "static", "cache");
31
+ await fsp.mkdir(cacheDir, { recursive: true });
32
+
33
+ const outputPath = path.join(cacheDir, name);
34
+
35
+ try {
36
+ await fsp.access(outputPath);
37
+ return res.sendFile(path.basename(outputPath), {
38
+ root: path.dirname(outputPath),
39
+ });
40
+ } catch {
41
+ // not cached → continue
42
+ }
43
+
44
+ const assetsDir = path.resolve(__dirname, '..', '..', '..', "frontend", "assets");
45
+ const files = await fsp.readdir(assetsDir);
46
+
47
+ const sourceFile = files.find((file) => {
48
+ const parsed = path.parse(file);
49
+ return parsed.name === baseName;
50
+ });
51
+
52
+ if (!sourceFile) {
53
+ return res.status(404).json({ error: "Source image not found" });
54
+ }
55
+
56
+ const inputPath = path.join(assetsDir, sourceFile);
57
+
58
+ await generateWebP(inputPath, outputPath, width, height);
59
+
60
+ return res.sendFile(path.basename(outputPath), {
61
+ root: path.dirname(outputPath),
62
+ });
63
+ } catch (err) {
64
+ console.error(err);
65
+ return res.status(500).json({ error: "Image optimisation failed" });
66
+ }
67
+ }
@@ -0,0 +1,4 @@
1
+ export * from './imageOptimiser.js';
2
+ export * from './addPage.js';
3
+ export * from './createStatic.js';
4
+ export * from './addComponent.js';
@@ -24,3 +24,4 @@ export default async function generateWebP(
24
24
 
25
25
  console.log("✅ Image successfully generated");
26
26
  }
27
+
@@ -1,6 +1,6 @@
1
1
  import type { Request, Response } from 'express';
2
2
  import { Router } from "express";
3
- import { addComponent, addPage, createStatic, imageOptimiser } from "../controllers/gardener.controller.js";
3
+ import { addComponent, addPage, createStatic, imageOptimiser } from "../controllers/gardener/index.js";
4
4
 
5
5
  const router: Router = Router();
6
6
  export default router;
@@ -9,9 +9,12 @@ export default router;
9
9
 
10
10
 
11
11
  router.route("/static/cache/:name").get(imageOptimiser);
12
- router.route("/createstatic").get(createStatic);
13
- router.route('/addcomponent').post(addComponent);
14
- router.route('/addpage').post(addPage);
12
+
13
+ if (process.env.NODE_ENV !== 'production') {
14
+ router.route("/createstatic").get(createStatic);
15
+ router.route('/addcomponent').post(addComponent);
16
+ router.route('/addpage').post(addPage);
17
+ }
15
18
 
16
19
 
17
20
 
@@ -19,4 +22,11 @@ router.route('/addpage').post(addPage);
19
22
 
20
23
  router.route('/').get((req: Request, res: Response) => res.render('_'));
21
24
  router.route('/login').get((req: Request, res: Response) => res.render('_login'));
22
-
25
+ router.route("/test").get((req: Request, res: Response) => res.render("_test"))
26
+ router.route("/rd").get((req: Request, res: Response) => res.render("_rd"));
27
+ router.route("/car").get((req: Request, res: Response) => res.render("_car"));
28
+ router.route("/onemore").get((req: Request, res: Response) => res.render("_onemore"));
29
+
30
+ router.route("/re").get((req: Request, res: Response) => res.render("_re"));
31
+ router.route("/playground").get((req: Request, res: Response) => res.render("_playground"));
32
+ router.route("/get-started").get((req: Request, res: Response) => res.render("_get-started"));
@@ -2,26 +2,24 @@
2
2
  import 'dotenv/config';
3
3
  import express from 'express';
4
4
  import frontendRoute from './routes/gardener.route.js'
5
+ import path from "path";
5
6
 
6
7
  const app = express();
7
8
 
8
9
 
10
+ import { fileURLToPath } from "url";
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
9
13
 
10
- app.set('views', './src/frontend/views');
14
+ const staticFiles = path.resolve(__dirname, '..', 'frontend')
15
+
16
+ app.set('views', path.join(staticFiles, 'views'));
11
17
  app.set("view engine", "ejs");
12
- app.use(express.static('./src/frontend'));
18
+ app.use(express.static(staticFiles));
13
19
  app.use(express.json());
14
20
  app.use(frontendRoute);
15
21
 
16
22
  const PORT = process.env.PORT || 3000;
17
- //
18
- // initDB().then(
19
- // () => {
20
- // app.listen(PORT, () => {
21
- // console.log("server listening 🚀🚀🚀 PORT:", PORT);
22
- // });
23
- // }
24
- // )
25
23
 
26
24
  app.listen(PORT, () => {
27
25
  console.log("server listening 🚀🚀🚀 PORT:", PORT);
@@ -3,33 +3,22 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" type="image/svg+xml" href="/static/cache/favicon_50x50.webp" />
6
7
  <link href="/static/style.css" rel="stylesheet"/>
7
8
  <link href="/static/style2.css" rel="stylesheet"/>
8
- <title>Redvent:Login</title>
9
+ <title>Gardener: The Mini Web Framework</title>
9
10
  </head>
10
11
  <body>
11
12
  <div class='notification'></div>
12
- <div class="bg-gray-200 w-screen h-screen flex justify-center items-center" id='body'>
13
- <!-- Remove this line when you are using hot reload as it will cause delay -->
14
- <%- include('partials/loader') %>
15
- <!-- Remove this line when you are using hot reload as it will cause delay -->
13
+ <div class='loader w-screen h-screen bg-white fixed z-2'> </div>
14
+ <div id='main'>
16
15
 
17
- <!-- Main Content-->
18
16
 
19
17
 
20
- <div class="hero flex justify-around items-center p-5 h-[90vh]">
21
- <p class='p-5'>
22
- Gardener is a front-end library for creating and manipulating DOM elements using a declarative JavaScript object syntax. It includes a development server with features like hot-reloading and on-the-fly component creation from existing HTML. The server also provides dynamic image resizing and caching.
23
- </p>
24
- <img src="/cache/w_500x500.webp" alt="logo" class="w-500">
25
- </div>
26
- </div>
27
- <script src='/static/global.js' type="module"></script>
28
- <script type="module">
29
18
 
30
- import { parser, fetchElement, replaceElement, appendElement } from "/static/gardener.js";
31
- </script>
19
+ </div>
32
20
  </body>
21
+ <script type='module' src='/static/global.js'> </script>
33
22
  </html>
34
23
 
35
24