zyket 1.2.1 → 1.2.3

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 (22) hide show
  1. package/package.json +5 -2
  2. package/src/extensions/bullboard/index.js +3 -2
  3. package/src/services/vite/index.js +42 -0
  4. package/src/templates/default/frontend/main.jsx +12 -3
  5. package/src/templates/default/frontend/src/hooks/useAuth.jsx +51 -0
  6. package/src/templates/default/frontend/src/hooks/useLayout.jsx +18 -0
  7. package/src/templates/default/frontend/src/layouts/auth/index.jsx +45 -0
  8. package/src/templates/default/frontend/src/layouts/auth/routes.js +18 -0
  9. package/src/templates/default/frontend/src/layouts/landing/index.jsx +60 -0
  10. package/src/templates/default/frontend/src/layouts/landing/routes.js +10 -0
  11. package/src/templates/default/frontend/src/layouts/panel/index.jsx +116 -0
  12. package/src/templates/default/frontend/src/layouts/panel/routes.js +11 -0
  13. package/src/templates/default/frontend/src/middlewares/AdminMiddleware.jsx +17 -0
  14. package/src/templates/default/frontend/src/middlewares/LoggedMiddleware.jsx +21 -0
  15. package/src/templates/default/frontend/src/middlewares/NotLoggedMiddleware.jsx +15 -0
  16. package/src/templates/default/frontend/src/store/index.jsx +5 -0
  17. package/src/templates/default/frontend/src/store/storeAuth.jsx +14 -0
  18. package/src/templates/default/frontend/src/views/auth/index.jsx +5 -0
  19. package/src/templates/default/frontend/src/views/auth/register/index.jsx +5 -0
  20. package/src/templates/default/frontend/src/views/landing/index.jsx +5 -0
  21. package/src/templates/default/frontend/src/views/panel/dashboard/index.jsx +5 -0
  22. package/src/utils/EnvManager.js +21 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyket",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -31,8 +31,10 @@
31
31
  "node-dependency-injection": "^3.2.6",
32
32
  "pg": "^8.20.0",
33
33
  "prompts": "^2.4.2",
34
+ "prop-types": "^15.8.1",
34
35
  "react": "^19.2.4",
35
36
  "react-dom": "^19.2.4",
37
+ "react-router-dom": "^7.14.0",
36
38
  "redis": "^5.11.0",
37
39
  "sequelize": "^6.37.8",
38
40
  "socket.io": "^4.8.3",
@@ -41,6 +43,7 @@
41
43
  "swagger-ui-express": "^5.0.1",
42
44
  "tailwindcss": "^4.2.2",
43
45
  "umzug": "^3.8.2",
44
- "vite": "^8.0.2"
46
+ "vite": "^8.0.2",
47
+ "zustand": "^5.0.12"
45
48
  }
46
49
  }
@@ -7,16 +7,17 @@ const basicAuth = require('express-basic-auth')
7
7
  module.exports = class BullBoardExtension extends Extension {
8
8
  path;
9
9
 
10
- constructor({ path = '/bullboard' } = {}) {
10
+ constructor({ path = '/bullboard', basePath = '' } = {}) {
11
11
  super("BullBoardExtension");
12
12
  this.path = path || '/bullboard';
13
+ this.basePath = basePath;
13
14
  }
14
15
 
15
16
  load(container) {
16
17
  if (!container.get('bullmq')) return container.get('logger').warn('BullBoardExtension: bullmq service not found, skipping BullBoard setup');
17
18
  const bull = container.get('bullmq')
18
19
  const serverAdapter = new ExpressAdapter()
19
- serverAdapter.setBasePath(this.path)
20
+ serverAdapter.setBasePath(this.basePath + this.path)
20
21
 
21
22
  createBullBoard({
22
23
  queues: Object.values(bull.queues).map(queue => new BullMQAdapter(queue)),
@@ -1,6 +1,7 @@
1
1
  const Service = require("../Service");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
+ const EnvManager = require("../../utils/EnvManager");
4
5
 
5
6
  module.exports = class Vite extends Service {
6
7
  #container;
@@ -68,6 +69,47 @@ module.exports = class Vite extends Service {
68
69
  if (!fs.existsSync(path.join(viteRoot, "styles.css"))) {
69
70
  this.#container.get('template-manager').installFile('default/frontend/styles', path.join(process.cwd(), process.env.VITE_ROOT || "frontend", "styles.css"));
70
71
  }
72
+
73
+ // Install src directory if it doesn't exist
74
+ if (!fs.existsSync(path.join(viteRoot, "src"))) {
75
+ this.#installSrcFiles(viteRoot);
76
+ }
77
+ }
78
+
79
+ #installSrcFiles(viteRoot) {
80
+ const templateManager = this.#container.get('template-manager');
81
+ const srcFiles = Object.keys(templateManager.templates).filter(key =>
82
+ key.startsWith('default/frontend/src/')
83
+ );
84
+
85
+ for (const fileKey of srcFiles) {
86
+ const template = templateManager.templates[fileKey];
87
+ // Extract the path after 'default/frontend/'
88
+ const relativePath = template.route.replace(/^default\/frontend\//, '');
89
+ const targetPath = path.join(viteRoot, relativePath);
90
+
91
+ // Create directory if it doesn't exist
92
+ const targetDir = path.dirname(targetPath);
93
+ if (!fs.existsSync(targetDir)) {
94
+ fs.mkdirSync(targetDir, { recursive: true });
95
+ }
96
+
97
+ // Write the file
98
+ fs.writeFileSync(targetPath, template.content);
99
+ this.#container.get('logger').info(`Installed template file: ${relativePath}`);
100
+ }
101
+
102
+ // Add VITE_API_BASE to .env file
103
+ this.#addViteApiBaseToEnv();
104
+ }
105
+
106
+ #addViteApiBaseToEnv() {
107
+ const envPath = path.join(process.cwd(), '.env');
108
+ const added = EnvManager.addEnvVariable(envPath, 'VITE_API_BASE', 'http://localhost:3000');
109
+
110
+ if (added) {
111
+ this.#container.get('logger').info('Added VITE_API_BASE to .env file');
112
+ }
71
113
  }
72
114
 
73
115
  server() {
@@ -1,6 +1,15 @@
1
1
  import { createRoot } from 'react-dom/client'
2
+ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
3
+ import AuthLayout from './src/layouts/auth'
4
+ import LandingLayout from './src/layouts/landing'
2
5
  import './styles.css'
6
+ import PanelLayout from './src/layouts/panel'
3
7
 
4
- createRoot(document.getElementById('root')).render(<div>
5
- <p className='underline text-red-500 text-3xl m-4'>Test</p>
6
- </div>)
8
+ createRoot(document.getElementById('root')).render(<BrowserRouter>
9
+ <Routes>
10
+ <Route path="/" element={<LandingLayout />} />
11
+ <Route path="/auth/*" element={<AuthLayout />} />
12
+ <Route path="/panel/*" element={<PanelLayout />} />
13
+ <Route path="*" element={<Navigate to="/" replace />} />
14
+ </Routes>
15
+ </BrowserRouter>)
@@ -0,0 +1,51 @@
1
+ import { useStoreAuth } from "../store";
2
+
3
+ export default function useAuth() {
4
+ const context = useStoreAuth();
5
+ const sessionContext = context?.client?.useSession();
6
+
7
+ const { error: sessionError, isPending, isRefetching, refetch } = sessionContext || {};
8
+ const { user, session } = sessionContext?.data || {};
9
+
10
+ const login = async (email, password) => {
11
+ const { data, error } = await context.client.signIn.email({ email, password });
12
+ if (error) throw error;
13
+
14
+ return data;
15
+ };
16
+
17
+ const register = async ({ email, password, name, image }) => {
18
+ const payload = { email, password, name, isPublic: true };
19
+ if (typeof image === "string" && image.trim() !== "") payload.image = image;
20
+
21
+ const { data, error } = await context.client.signUp.email(payload);
22
+ if (error) throw error;
23
+
24
+ return data;
25
+ };
26
+
27
+ const logout = async () => {
28
+ await context.client.signOut({
29
+ fetchOptions: {
30
+ onSuccess: () => {
31
+ localStorage.removeItem('pn');
32
+ window.location.href = "/auth/login"
33
+ }
34
+ },
35
+ });
36
+ };
37
+
38
+
39
+ return {
40
+ ...context,
41
+ login,
42
+ register,
43
+ logout,
44
+ error: sessionError,
45
+ isPending,
46
+ isRefetching,
47
+ refetch,
48
+ user,
49
+ session,
50
+ };
51
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Route } from 'react-router-dom';
3
+
4
+ export default function useLayout(routeList) {
5
+
6
+ const routes = routesJSON.map((route, index) => {
7
+ const middlewares = route.middlewares ? [...route.middlewares].reverse() : [];
8
+ let element = <route.component />;
9
+ middlewares.forEach((middleware) => {
10
+ element = React.createElement(middleware, {}, element);
11
+ });
12
+ return <Route key={index} path={route.path} element={element} />;
13
+ });
14
+
15
+ return {
16
+ routes,
17
+ };
18
+ }
@@ -0,0 +1,45 @@
1
+ import { Navigate, Routes, Route } from "react-router-dom";
2
+ import useLayout from "../../hooks/useLayout";
3
+ import layoutRoutes from "./routes";
4
+
5
+ export default function AuthLayout() {
6
+ const { routes } = useLayout(layoutRoutes)
7
+
8
+ return (
9
+ <div className="min-h-screen bg-[#080808] text-white flex items-center justify-center relative overflow-hidden">
10
+ <div
11
+ className="absolute inset-0 pointer-events-none"
12
+ style={{
13
+ backgroundImage: `
14
+ linear-gradient(rgba(255,107,0,0.04) 1px, transparent 1px),
15
+ linear-gradient(90deg, rgba(255,107,0,0.04) 1px, transparent 1px)
16
+ `,
17
+ backgroundSize: "48px 48px",
18
+ }}
19
+ />
20
+
21
+ <div className="absolute inset-0 pointer-events-none">
22
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_50%,rgba(255,107,0,0.08),transparent_65%)]" />
23
+ </div>
24
+
25
+ <div className="relative z-10 w-full max-w-sm px-8 py-10 space-y-8">
26
+ <div className="flex flex-col items-center gap-4">
27
+ <div className="w-11 h-11 bg-orange-500 rounded-md flex items-center justify-center">
28
+ <span className="text-black font-black text-base tracking-tighter">PY</span>
29
+ </div>
30
+ <span className="text-white font-black text-2xl tracking-widest uppercase">Pnyise</span>
31
+ </div>
32
+
33
+ <Routes>
34
+ {routes}
35
+ <Route path="*" element={<Navigate to="/auth" replace />} />
36
+ </Routes>
37
+ </div>
38
+
39
+ <div className="absolute bottom-6 text-xs text-zinc-700">
40
+ © {new Date().getFullYear()} Zyket
41
+ </div>
42
+
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,18 @@
1
+ import AuthView from "../../views/auth";
2
+ import AuthRegisterView from "../../views/auth/register";
3
+ import NotLoggedMiddleware from "../../middlewares/NotLoggedMiddleware";
4
+
5
+ module.exports = [
6
+ {
7
+ name: "Auth",
8
+ path: "/auth",
9
+ component: AuthView,
10
+ middlewares: [NotLoggedMiddleware],
11
+ },
12
+ {
13
+ name: "Auth Register",
14
+ path: "/auth/register",
15
+ component: AuthRegisterView,
16
+ middlewares: [NotLoggedMiddleware],
17
+ }
18
+ ]
@@ -0,0 +1,60 @@
1
+ import { Navigate, Routes, Route, Link } from "react-router-dom";
2
+ import useLayout from "../../hooks/useLayout";
3
+
4
+ export default function LandingLayout() {
5
+ const { routes } = useLayout(layoutRoutes)
6
+
7
+
8
+ return (
9
+ <div className="min-h-screen bg-[#080808] text-white relative overflow-hidden">
10
+ <div
11
+ className="absolute inset-0 pointer-events-none"
12
+ style={{
13
+ backgroundImage: `
14
+ linear-gradient(rgba(255,107,0,0.04) 1px, transparent 1px),
15
+ linear-gradient(90deg, rgba(255,107,0,0.04) 1px, transparent 1px)
16
+ `,
17
+ backgroundSize: "48px 48px",
18
+ }}
19
+ />
20
+
21
+ <div className="absolute inset-0 pointer-events-none">
22
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_50%,rgba(255,107,0,0.08),transparent_65%)]" />
23
+ </div>
24
+
25
+ <div className="relative z-10">
26
+ <nav className="flex items-center justify-between px-8 py-6 max-w-7xl mx-auto">
27
+ <div className="flex items-center gap-3">
28
+ <div className="w-10 h-10 bg-orange-500 rounded-md flex items-center justify-center">
29
+ <span className="text-black font-black text-sm tracking-tighter">PY</span>
30
+ </div>
31
+ <span className="text-white font-black text-xl tracking-widest uppercase">Pnyise</span>
32
+ </div>
33
+
34
+ <div className="flex items-center gap-6">
35
+ <Link to="/auth" className="text-sm text-zinc-400 hover:text-white transition-colors">
36
+ Sign In
37
+ </Link>
38
+ <Link
39
+ to="/auth"
40
+ className="text-sm bg-orange-500 hover:bg-orange-600 text-black font-semibold px-4 py-2 rounded-md transition-colors"
41
+ >
42
+ Get Started
43
+ </Link>
44
+ </div>
45
+ </nav>
46
+
47
+ <main className="px-8 py-12">
48
+ <Routes>
49
+ {routes}
50
+ <Route path="*" element={<Navigate to="/" replace />} />
51
+ </Routes>
52
+ </main>
53
+
54
+ <footer className="absolute bottom-0 w-full py-6 text-center text-xs text-zinc-700 border-t border-zinc-900">
55
+ © {new Date().getFullYear()} Zyket. All rights reserved.
56
+ </footer>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,10 @@
1
+ import LandingView from "../../views/landing";
2
+
3
+ module.exports = [
4
+ {
5
+ name: "Landing",
6
+ path: "/",
7
+ component: LandingView,
8
+ middlewares: [],
9
+ },
10
+ ];
@@ -0,0 +1,116 @@
1
+ import { Navigate, Routes, Route, Link, useLocation } from "react-router-dom";
2
+ import useLayout from "../../hooks/useLayout";
3
+ import PanelDashboardView from "../../views/panel/dashboard";
4
+ import layoutRoutes from "./routes";
5
+
6
+ export default function PanelLayout() {
7
+ const location = useLocation();
8
+ const { routes } = useLayout();
9
+
10
+ const navItems = [
11
+ { name: "Dashboard", path: "/panel/dashboard", icon: "📊" },
12
+ { name: "Settings", path: "/panel/settings", icon: "⚙️" },
13
+ ];
14
+
15
+ const isActive = (path) => location.pathname === path;
16
+
17
+ return (
18
+ <div className="min-h-screen bg-[#080808] text-white flex">
19
+ {/* Sidebar */}
20
+ <aside className="w-64 bg-[#0a0a0a] border-r border-zinc-900 flex flex-col">
21
+ {/* Logo */}
22
+ <div className="px-6 py-6 border-b border-zinc-900">
23
+ <div className="flex items-center gap-3">
24
+ <div className="w-10 h-10 bg-orange-500 rounded-md flex items-center justify-center">
25
+ <span className="text-black font-black text-sm tracking-tighter">PY</span>
26
+ </div>
27
+ <span className="text-white font-black text-lg tracking-widest uppercase">Pnyise</span>
28
+ </div>
29
+ </div>
30
+
31
+ {/* Navigation */}
32
+ <nav className="flex-1 px-4 py-6 space-y-2">
33
+ {navItems.map((item) => (
34
+ <Link
35
+ key={item.path}
36
+ to={item.path}
37
+ className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
38
+ isActive(item.path)
39
+ ? "bg-orange-500 text-black font-semibold"
40
+ : "text-zinc-400 hover:text-white hover:bg-zinc-900"
41
+ }`}
42
+ >
43
+ <span className="text-lg">{item.icon}</span>
44
+ <span className="text-sm">{item.name}</span>
45
+ </Link>
46
+ ))}
47
+ </nav>
48
+
49
+ {/* User Menu */}
50
+ <div className="px-4 py-4 border-t border-zinc-900">
51
+ <div className="flex items-center gap-3 px-4 py-3">
52
+ <div className="w-9 h-9 bg-zinc-800 rounded-full flex items-center justify-center">
53
+ <span className="text-xs font-semibold">U</span>
54
+ </div>
55
+ <div className="flex-1 min-w-0">
56
+ <p className="text-sm font-medium truncate">User</p>
57
+ <p className="text-xs text-zinc-500 truncate">user@example.com</p>
58
+ </div>
59
+ </div>
60
+ <Link
61
+ to="/auth"
62
+ className="w-full mt-2 px-4 py-2 text-sm text-zinc-400 hover:text-white hover:bg-zinc-900 rounded-lg transition-colors flex items-center justify-center"
63
+ >
64
+ Logout
65
+ </Link>
66
+ </div>
67
+ </aside>
68
+
69
+ {/* Main Content */}
70
+ <div className="flex-1 flex flex-col min-w-0 relative overflow-hidden">
71
+ {/* Background Grid */}
72
+ <div
73
+ className="absolute inset-0 pointer-events-none"
74
+ style={{
75
+ backgroundImage: `
76
+ linear-gradient(rgba(255,107,0,0.04) 1px, transparent 1px),
77
+ linear-gradient(90deg, rgba(255,107,0,0.04) 1px, transparent 1px)
78
+ `,
79
+ backgroundSize: "48px 48px",
80
+ }}
81
+ />
82
+
83
+ <div className="absolute inset-0 pointer-events-none">
84
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_50%,rgba(255,107,0,0.08),transparent_65%)]" />
85
+ </div>
86
+
87
+ {/* Header */}
88
+ <header className="relative z-10 px-8 py-6 border-b border-zinc-900 bg-[#080808]/80 backdrop-blur-sm">
89
+ <div className="flex items-center justify-between">
90
+ <h1 className="text-2xl font-bold">Dashboard</h1>
91
+ <div className="flex items-center gap-4">
92
+ <button className="p-2 text-zinc-400 hover:text-white transition-colors">
93
+ 🔔
94
+ </button>
95
+ </div>
96
+ </div>
97
+ </header>
98
+
99
+ {/* Main Content Area */}
100
+ <main className="relative z-10 flex-1 overflow-auto">
101
+ <div className="p-8">
102
+ <Routes>
103
+ {routes}
104
+ <Route path="*" element={<Navigate to="/panel/dashboard" replace />} />
105
+ </Routes>
106
+ </div>
107
+ </main>
108
+
109
+ {/* Footer */}
110
+ <footer className="relative z-10 py-4 text-center text-xs text-zinc-700 border-t border-zinc-900">
111
+ © {new Date().getFullYear()} Zyket. All rights reserved.
112
+ </footer>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,11 @@
1
+ import PanelDashboardView from "../../views/panel/dashboard";
2
+ import LoggedMiddleware from "../../middlewares/LoggedMiddleware";
3
+
4
+ module.exports = [
5
+ {
6
+ name: "Panel Dashboard",
7
+ path: "/panel/dashboard",
8
+ component: PanelDashboardView,
9
+ middlewares: [LoggedMiddleware],
10
+ }
11
+ ]
@@ -0,0 +1,17 @@
1
+ import PropTypes from "prop-types";
2
+ import { Navigate } from "react-router-dom";
3
+ import useAuth from "../hooks/useAuth";
4
+
5
+ export default function AdminMiddleware({ children }) {
6
+ const { user, isPending } = useAuth();
7
+
8
+ if (isPending) return null;
9
+ if (!user) return <Navigate to="/auth/login" />; // Change this to your desired login route
10
+ if (user.role !== "admin") return <Navigate to="/" />; // Change this to your desired forbidden route
11
+
12
+ return children;
13
+ }
14
+
15
+ AdminMiddleware.propTypes = {
16
+ children: PropTypes.node.isRequired,
17
+ };
@@ -0,0 +1,21 @@
1
+ import PropTypes from "prop-types";
2
+ import { Navigate } from "react-router-dom";
3
+ import useAuth from "../hooks/useAuth";
4
+
5
+ export default function LoggedMiddleware({
6
+ children
7
+ }) {
8
+ const { user, isPending } = useAuth();
9
+
10
+ if (isPending) return null;
11
+ if (!user) return <Navigate to={`/auth/login`} />; // Change this to your desired login route
12
+
13
+ return children;
14
+ }
15
+
16
+ LoggedMiddleware.propTypes = {
17
+ children: PropTypes.node.isRequired,
18
+ roles: PropTypes.arrayOf(PropTypes.string),
19
+ loginRoute: PropTypes.string,
20
+ forbiddenRoute: PropTypes.string,
21
+ };
@@ -0,0 +1,15 @@
1
+ import PropTypes from 'prop-types';
2
+ import { Navigate } from 'react-router-dom';
3
+ import useAuth from '../hooks/useAuth';
4
+
5
+ export default function NotLoggedMiddleware ({ children }) {
6
+ const { user, isPending } = useAuth();
7
+
8
+ if(isPending) return null;
9
+ if(user) return <Navigate to={`/panel`} />; // Change this to your desired home route for logged in users
10
+ return children;
11
+ }
12
+
13
+ NotLoggedMiddleware.propTypes = {
14
+ children: PropTypes.node.isRequired
15
+ };
@@ -0,0 +1,5 @@
1
+ import { useStoreAuth } from './storeAuth';
2
+
3
+ export { useStoreAuth };
4
+
5
+
@@ -0,0 +1,14 @@
1
+ import { create } from "zustand";
2
+ import { createAuthClient } from "better-auth/react";
3
+ import { adminClient } from "better-auth/client/plugins"
4
+
5
+ export const useStoreAuth = create((set) => ({
6
+ client: createAuthClient({
7
+ plugins: [
8
+ adminClient()
9
+ ],
10
+ baseURL: `${import.meta.env.VITE_API_BASE}/api/auth/`,
11
+ }),
12
+
13
+ setClient: (client) => set({ client }),
14
+ }));
@@ -0,0 +1,5 @@
1
+ export default function AuthView() {
2
+ return (<div>
3
+ Auth View
4
+ </div>);
5
+ }
@@ -0,0 +1,5 @@
1
+ export default function AuthRegisterView() {
2
+ return (<div>
3
+ Auth Register View
4
+ </div>);
5
+ }
@@ -0,0 +1,5 @@
1
+ export default function LandingView() {
2
+ return (<div>
3
+ Landing View
4
+ </div>);
5
+ }
@@ -0,0 +1,5 @@
1
+ export default function PanelDashboardView() {
2
+ return (<div>
3
+ Panel dashboard View
4
+ </div>);
5
+ }
@@ -39,4 +39,25 @@ module.exports = class EnvManager {
39
39
  return `${acc}${key}=${value}\n`;
40
40
  }, "");
41
41
  }
42
+
43
+ static addEnvVariable(secretsPath, key, value) {
44
+ // Create env file if it doesn't exist
45
+ if (!fs.existsSync(secretsPath)) {
46
+ this.createEnvFile(secretsPath);
47
+ }
48
+
49
+ // Read existing content
50
+ let envContent = fs.readFileSync(secretsPath, 'utf-8');
51
+
52
+ // Check if the key already exists
53
+ const keyRegex = new RegExp(`^${key}=.*$`, 'm');
54
+ if (keyRegex.test(envContent)) {
55
+ return false; // Key already exists
56
+ }
57
+
58
+ // Add the new environment variable
59
+ envContent += `${key}=${value}\n`;
60
+ fs.writeFileSync(secretsPath, envContent);
61
+ return true; // Key added successfully
62
+ }
42
63
  }