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.
- package/Readme.md +6 -2
- package/package.json +1 -1
- package/starter.js +5 -2
- package/template/.env +0 -0
- package/template/buildHelper.js +3 -0
- package/template/jsconfig.json +25 -0
- package/template/package.json +10 -12
- package/template/pnpm-lock.yaml +62 -0
- package/template/src/backend/controllers/gardener/addComponent.ts +27 -0
- package/template/src/backend/controllers/gardener/addPage.ts +60 -0
- package/template/src/backend/controllers/gardener/createStatic.ts +84 -0
- package/template/src/backend/controllers/gardener/imageOptimiser.ts +67 -0
- package/template/src/backend/controllers/gardener/index.ts +4 -0
- package/template/src/backend/libs/generateWebp.ts +1 -0
- package/template/src/backend/routes/gardener.route.ts +15 -5
- package/template/src/backend/server.ts +8 -10
- package/template/src/frontend/assets/favicon.png +0 -0
- package/template/src/frontend/frontendtemplate.ejs +6 -17
- package/template/src/frontend/static/cache/favicon_500x500.webp +0 -0
- package/template/src/frontend/static/cache/favicon_50x50.webp +0 -0
- package/template/src/frontend/static/cache/gardener_50x50.webp +0 -0
- package/template/src/frontend/static/components/copybtn.js +86 -0
- package/template/src/frontend/static/components/nonui/api.js +33 -16
- package/template/src/frontend/static/components/nonui/navigation.js +59 -0
- package/template/src/frontend/static/gardener.js +0 -388
- package/template/src/frontend/static/gardenerConfig.js +1 -0
- package/template/src/frontend/static/gardenerDev.js +408 -0
- package/template/src/frontend/static/global.js +2 -56
- package/template/src/frontend/static/pages/_.js +20 -0
- package/template/src/frontend/static/pages/_get-started.js +5 -0
- package/template/src/frontend/static/style.css +1 -1
- package/template/src/frontend/static/style2.css +1 -1
- package/template/src/frontend/static/zod.js +8 -0
- package/template/src/frontend/views/_.ejs +96 -216
- package/template/src/frontend/views/_get-started.ejs +25 -0
- package/template/src/frontend/views/partials/icons/clipboard.ejs +1 -0
- package/template/src/frontend/views/partials/icons/clipboardok.ejs +1 -0
- package/template/tsconfig.json +1 -1
- package/template/src/backend/controllers/gardener.controller.ts +0 -193
- package/template/src/frontend/gardenerST.js +0 -431
- package/template/src/frontend/static/components/emailsvg.js +0 -55
- package/template/src/frontend/static/components/eyeoff.js +0 -50
- package/template/src/frontend/static/components/eyeon.js +0 -43
- package/template/src/frontend/static/components/passwordBox.js +0 -105
- package/template/src/frontend/views/_login.ejs +0 -75
- 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
package/starter.js
CHANGED
package/template/.env
ADDED
|
File without changes
|
package/template/buildHelper.js
CHANGED
|
@@ -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
|
+
}
|
package/template/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-gardener",
|
|
3
|
-
"version": "
|
|
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
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
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
|
-
"
|
|
31
|
-
"
|
|
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
|
-
|
package/template/pnpm-lock.yaml
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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.
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
router.route(
|
|
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
|
-
|
|
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(
|
|
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);
|
|
Binary file
|
|
@@ -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>
|
|
9
|
+
<title>Gardener: The Mini Web Framework</title>
|
|
9
10
|
</head>
|
|
10
11
|
<body>
|
|
11
12
|
<div class='notification'></div>
|
|
12
|
-
<div class=
|
|
13
|
-
|
|
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
|
-
|
|
31
|
-
</script>
|
|
19
|
+
</div>
|
|
32
20
|
</body>
|
|
21
|
+
<script type='module' src='/static/global.js'> </script>
|
|
33
22
|
</html>
|
|
34
23
|
|
|
35
24
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|