create-prisma-php-app 4.4.4-beta → 4.4.4

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.
@@ -15,11 +15,12 @@ use PP\Request;
15
15
  use Exception;
16
16
  use InvalidArgumentException;
17
17
  use ArrayObject;
18
+ use PP\Env;
18
19
 
19
20
  class Auth
20
21
  {
21
22
  public const PAYLOAD_NAME = 'payload_name_8639D';
22
- public const ROLE_NAME = '';
23
+ public const ROLE_NAME = 'role';
23
24
  public const PAYLOAD_SESSION_KEY = 'payload_session_key_2183A';
24
25
 
25
26
  public static string $cookieName = '';
@@ -27,23 +28,14 @@ class Auth
27
28
  private static ?Auth $instance = null;
28
29
  private const PPAUTH = 'ppauth';
29
30
  private string $secretKey;
30
- private string $defaultTokenValidity = '1h'; // Default to 1 hour
31
+ private string $defaultTokenValidity = AuthConfig::DEFAULT_TOKEN_VALIDITY;
31
32
 
32
- /**
33
- * Private constructor to prevent direct instantiation.
34
- * Use Auth::getInstance() to get the singleton instance.
35
- */
36
33
  private function __construct()
37
34
  {
38
- $this->secretKey = $_ENV['AUTH_SECRET'] ?? 'CD24eEv4qbsC5LOzqeaWbcr58mBMSvA4Mkii8GjRiHkt';
35
+ $this->secretKey = Env::string('AUTH_SECRET', 'CD24eEv4qbsC5LOzqeaWbcr58mBMSvA4Mkii8GjRiHkt');
39
36
  self::$cookieName = self::getCookieName();
40
37
  }
41
38
 
42
- /**
43
- * Returns the singleton instance of the Auth class.
44
- *
45
- * @return Auth The singleton instance.
46
- */
47
39
  public static function getInstance(): Auth
48
40
  {
49
41
  if (self::$instance === null) {
@@ -53,33 +45,20 @@ class Auth
53
45
  }
54
46
 
55
47
  /**
56
- * Authenticates a user and generates a JWT (JSON Web Token) based on the specified user data
57
- * and token validity duration. The method first checks if the secret key is set, calculates
58
- * the token's expiration time, sets the necessary payload, and encodes it into a JWT.
59
- * If possible (HTTP headers not yet sent), it also sets cookies with the JWT for client-side storage.
48
+ * Authenticates a user and generates a JWT.
49
+ * Optionally redirects the user to a default or custom URL.
60
50
  *
61
- * @param mixed $data User data which can be a simple string or an instance of AuthRole.
62
- * If an instance of AuthRole is provided, its `value` property will be used as the role in the token.
63
- * @param string|null $tokenValidity Optional parameter specifying the duration the token is valid for (e.g., '10m', '1h').
64
- * If null, the default validity period set in the class property is used, which is 1 hour.
65
- * The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
66
- * 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
51
+ * @param mixed $data User data (string or AuthRole).
52
+ * @param string|null $tokenValidity Duration token is valid for (e.g., '1h'). Default is '1h'.
53
+ * @param bool|string $redirect
54
+ * - If `false` (default): No redirect occurs; returns the JWT.
55
+ * - If `true`: Redirects to `AuthConfig::DEFAULT_SIGNIN_REDIRECT`.
56
+ * - If `string`: Redirects to the specified URL (e.g., '/dashboard').
67
57
  *
68
58
  * @return string Returns the encoded JWT as a string.
69
- *
70
- * @throws InvalidArgumentException Thrown if the secret key is not set or if the duration format is invalid.
71
- *
72
- * Example:
73
- * $auth = Auth::getInstance();
74
- * $auth->setSecretKey('your_secret_key');
75
- * try {
76
- * $jwt = $auth->signIn('Admin', '1h');
77
- * echo "JWT: " . $jwt;
78
- * } catch (InvalidArgumentException $e) {
79
- * echo "Error: " . $e->getMessage();
80
- * }
59
+ * @throws InvalidArgumentException
81
60
  */
82
- public function signIn($data, ?string $tokenValidity = null): string
61
+ public function signIn($data, ?string $tokenValidity = null, bool|string $redirect = false): string
83
62
  {
84
63
  if (!$this->secretKey) {
85
64
  throw new InvalidArgumentException("Secret key is required for authentication.");
@@ -96,14 +75,20 @@ class Auth
96
75
  'exp' => $expirationTime,
97
76
  ];
98
77
 
99
- // Set the payload in the session
100
78
  $_SESSION[self::PAYLOAD_SESSION_KEY] = $payload;
101
79
 
102
- // Encode the JWT
103
80
  $jwt = JWT::encode($payload, $this->secretKey, 'HS256');
104
81
 
105
82
  if (!headers_sent()) {
106
83
  $this->setCookies($jwt, $expirationTime);
84
+
85
+ $this->rotateCsrfToken();
86
+ }
87
+
88
+ if ($redirect === true) {
89
+ Request::redirect(AuthConfig::DEFAULT_SIGNIN_REDIRECT);
90
+ } elseif (is_string($redirect) && !empty($redirect)) {
91
+ Request::redirect($redirect);
107
92
  }
108
93
 
109
94
  return $jwt;
@@ -184,21 +169,14 @@ class Auth
184
169
  public function verifyToken(?string $jwt): ?object
185
170
  {
186
171
  try {
187
- if (!$jwt) {
188
- return null;
189
- }
172
+ if (!$jwt) return null;
190
173
 
191
174
  $token = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
192
175
 
193
- if (empty($token->{Auth::PAYLOAD_NAME})) {
194
- return null;
195
- }
196
-
197
- if (isset($token->exp) && time() >= $token->exp) {
198
- return null;
199
- }
176
+ if (empty($token->{Auth::PAYLOAD_NAME})) return null;
177
+ if (isset($token->exp) && time() >= $token->exp) return null;
200
178
 
201
- return $token;
179
+ return $token->{Auth::PAYLOAD_NAME};
202
180
  } catch (Exception) {
203
181
  return null;
204
182
  }
@@ -211,9 +189,9 @@ class Auth
211
189
  *
212
190
  * @param string $jwt The JWT token to refresh.
213
191
  * @param string|null $tokenValidity Optional parameter specifying the duration the token is valid for (e.g., '10m', '1h').
214
- * If null, the default validity period set in the class property is used.
215
- * The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
216
- * 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
192
+ * If null, the default validity period set in the class property is used.
193
+ * The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
194
+ * 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
217
195
  *
218
196
  * @return string Returns the refreshed JWT as a string.
219
197
  *
@@ -221,16 +199,20 @@ class Auth
221
199
  */
222
200
  public function refreshToken(string $jwt, ?string $tokenValidity = null): string
223
201
  {
224
- $decodedToken = $this->verifyToken($jwt);
202
+ $decodedData = $this->verifyToken($jwt);
225
203
 
226
- if (!$decodedToken) {
204
+ if (!$decodedData) {
227
205
  throw new InvalidArgumentException("Invalid token.");
228
206
  }
229
207
 
230
208
  $expirationTime = $this->calculateExpirationTime($tokenValidity ?? $this->defaultTokenValidity);
231
209
 
232
- $decodedToken->exp = $expirationTime;
233
- $newJwt = JWT::encode((array)$decodedToken, $this->secretKey, 'HS256');
210
+ $payload = [
211
+ self::PAYLOAD_NAME => $decodedData,
212
+ 'exp' => $expirationTime,
213
+ ];
214
+
215
+ $newJwt = JWT::encode($payload, $this->secretKey, 'HS256');
234
216
 
235
217
  if (!headers_sent()) {
236
218
  $this->setCookies($newJwt, $expirationTime);
@@ -239,18 +221,62 @@ class Auth
239
221
  return $newJwt;
240
222
  }
241
223
 
224
+ /**
225
+ * Refreshes the current user's session if the provided User ID matches the currently authenticated user.
226
+ * This allows silent updates of permissions or profile data without requiring a logout.
227
+ * @param string $targetUserId The ID of the user being updated.
228
+ * @param mixed $newUserData The fresh user object (e.g. from Prisma) to replace the session payload.
229
+ * @return bool Returns true if the session was updated, false if the IDs did not match or no session exists.
230
+ */
231
+ public function refreshUserSession(string $targetUserId, mixed $newUserData): bool
232
+ {
233
+ $currentUser = $this->getPayload();
234
+
235
+ if ($currentUser && isset($currentUser->id) && $currentUser->id === $targetUserId) {
236
+ $this->signIn($newUserData, null, false);
237
+ return true;
238
+ }
239
+
240
+ return false;
241
+ }
242
+
242
243
  protected function setCookies(string $jwt, int $expirationTime)
243
244
  {
244
245
  if (!headers_sent()) {
245
246
  setcookie(self::$cookieName, $jwt, [
246
247
  'expires' => $expirationTime,
247
- 'path' => '/', // Set the path to '/' to make the cookie available site-wide
248
- 'domain' => '', // Specify your domain
249
- 'secure' => true, // Set to true if using HTTPS
250
- 'httponly' => true, // Prevent JavaScript access to the cookie
251
- 'samesite' => 'Lax', // or 'Strict' depending on your requirements
248
+ 'path' => '/',
249
+ 'domain' => '',
250
+ 'secure' => true,
251
+ 'httponly' => true,
252
+ 'samesite' => 'Lax',
253
+ ]);
254
+ }
255
+ }
256
+
257
+ public function rotateCsrfToken(): void
258
+ {
259
+ $secret = Env::string('FUNCTION_CALL_SECRET', '');
260
+
261
+ if (empty($secret)) {
262
+ return;
263
+ }
264
+
265
+ $nonce = bin2hex(random_bytes(16));
266
+ $signature = hash_hmac('sha256', $nonce, $secret);
267
+ $token = $nonce . '.' . $signature;
268
+
269
+ if (!headers_sent()) {
270
+ setcookie('prisma_php_csrf', $token, [
271
+ 'expires' => time() + 3600, // 1 hour validity
272
+ 'path' => '/',
273
+ 'secure' => true,
274
+ 'httponly' => false, // Must be FALSE so client JS can read it
275
+ 'samesite' => 'Lax',
252
276
  ]);
253
277
  }
278
+
279
+ $_COOKIE['prisma_php_csrf'] = $token;
254
280
  }
255
281
 
256
282
  /**
@@ -260,7 +286,7 @@ class Auth
260
286
  * @param string|null $redirect Optional parameter specifying the URL to redirect to after logging out.
261
287
  *
262
288
  * Example:
263
- * $auth = Auth::getInstance();
289
+ * $auth = Auth::getInstance();
264
290
  * $auth->signOut('/login');
265
291
  *
266
292
  * @return void
@@ -276,6 +302,8 @@ class Auth
276
302
  unset($_SESSION[self::PAYLOAD_SESSION_KEY]);
277
303
  }
278
304
 
305
+ $this->rotateCsrfToken();
306
+
279
307
  if ($redirect) {
280
308
  Request::redirect($redirect);
281
309
  }
@@ -342,8 +370,8 @@ class Auth
342
370
  * @param mixed ...$providers An array of provider objects such as GoogleProvider or GithubProvider.
343
371
  *
344
372
  * Example:
345
- * $auth = Auth::getInstance();
346
- * $auth->authProviders(new GoogleProvider('client_id', 'client_secret', 'redirect_uri'));
373
+ * $auth = Auth::getInstance();
374
+ * $auth->authProviders(new GoogleProvider('client_id', 'client_secret', 'redirect_uri'));
347
375
  */
348
376
  public function authProviders(...$providers)
349
377
  {
@@ -519,7 +547,7 @@ class Auth
519
547
 
520
548
  private static function getCookieName(): string
521
549
  {
522
- $authCookieName = $_ENV['AUTH_COOKIE_NAME'] ?? 'auth_cookie_name_d36e5';
550
+ $authCookieName = Env::string('AUTH_COOKIE_NAME', 'auth_cookie_name_d36e5');
523
551
  return strtolower(preg_replace('/\s+/', '_', trim($authCookieName)));
524
552
  }
525
553
  }
@@ -23,6 +23,7 @@ final class AuthConfig
23
23
  public const IS_ROLE_BASE = false;
24
24
  public const IS_TOKEN_AUTO_REFRESH = false;
25
25
  public const IS_ALL_ROUTES_PRIVATE = false;
26
+ public const DEFAULT_TOKEN_VALIDITY = "1h"; // Default to 1 hour
26
27
 
27
28
  /**
28
29
  * This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to false,
@@ -12,20 +12,21 @@ use Dotenv\Dotenv;
12
12
  use PhpMcp\Server\Server;
13
13
  use PhpMcp\Server\Transports\StreamableHttpServerTransport;
14
14
  use Throwable;
15
+ use PP\Env;
15
16
 
16
17
  // ── Load .env (optional) and timezone ──────────────────────────────────────────
17
18
  if (file_exists(DOCUMENT_PATH . '/.env')) {
18
19
  Dotenv::createImmutable(DOCUMENT_PATH)->safeLoad();
19
20
  }
20
- date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'UTC');
21
+ date_default_timezone_set(Env::string('APP_TIMEZONE', 'UTC'));
21
22
 
22
23
  // ── Resolve settings (with sane defaults) ─────────────────────────────────────
23
- $appName = $_ENV['MCP_NAME'] ?? 'prisma-php-mcp';
24
- $appVersion = $_ENV['MCP_VERSION'] ?? '0.0.1';
25
- $host = $_ENV['MCP_HOST'] ?? '127.0.0.1';
26
- $port = (int)($_ENV['MCP_PORT'] ?? 4000);
27
- $prefix = trim($_ENV['MCP_PATH_PREFIX'] ?? 'mcp', '/');
28
- $enableJson = filter_var($_ENV['MCP_JSON_RESPONSE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
24
+ $appName = Env::string('MCP_NAME', 'prisma-php-mcp');
25
+ $appVersion = Env::string('MCP_VERSION', '0.0.1');
26
+ $host = Env::string('MCP_HOST', '127.0.0.1');
27
+ $port = Env::int('MCP_PORT', 4000);
28
+ $prefix = trim(Env::string('MCP_PATH_PREFIX', 'mcp'), '/');
29
+ $enableJson = Env::bool('MCP_JSON_RESPONSE', false);
29
30
 
30
31
  // ── Build server and discover tools ───────────────────────────────────────────
31
32
  $server = Server::make()
@@ -7,6 +7,7 @@ namespace Lib\Middleware;
7
7
  use Lib\Auth\Auth;
8
8
  use Lib\Auth\AuthConfig;
9
9
  use PP\Request;
10
+ use Throwable;
10
11
 
11
12
  final class AuthMiddleware
12
13
  {
@@ -24,9 +25,18 @@ final class AuthMiddleware
24
25
  // Check if the user is authenticated and refresh the token if necessary
25
26
  if (AuthConfig::IS_TOKEN_AUTO_REFRESH) {
26
27
  $auth = Auth::getInstance();
27
- if (isset($_COOKIE[Auth::$cookieName])) {
28
- $jwt = $_COOKIE[Auth::$cookieName];
29
- $jwt = $auth->refreshToken($jwt);
28
+
29
+ $jwt = $_COOKIE[Auth::$cookieName] ?? Request::getBearerToken();
30
+
31
+ if ($jwt) {
32
+ try {
33
+ $newJwt = $auth->refreshToken($jwt);
34
+ if (!isset($_COOKIE[Auth::$cookieName])) {
35
+ header('Authorization: Bearer ' . $newJwt);
36
+ header('X-Refreshed-Token: ' . $newJwt);
37
+ }
38
+ } catch (Throwable) {
39
+ }
30
40
  }
31
41
  }
32
42
 
@@ -89,29 +99,35 @@ final class AuthMiddleware
89
99
  protected static function isAuthorized(): bool
90
100
  {
91
101
  $auth = Auth::getInstance();
92
- if (!isset($_COOKIE[Auth::$cookieName])) {
93
- unset($_SESSION[Auth::PAYLOAD_SESSION_KEY]);
102
+ $jwt = $_COOKIE[Auth::$cookieName] ?? Request::getBearerToken();
103
+
104
+ if (!$jwt) {
105
+ if (isset($_SESSION[Auth::PAYLOAD_SESSION_KEY])) {
106
+ unset($_SESSION[Auth::PAYLOAD_SESSION_KEY]);
107
+ }
94
108
  return false;
95
109
  }
96
110
 
97
- $jwt = $_COOKIE[Auth::$cookieName];
98
-
99
111
  if (AuthConfig::IS_TOKEN_AUTO_REFRESH) {
100
- $jwt = $auth->refreshToken($jwt);
101
- $verifyToken = $auth->verifyToken($jwt);
112
+ try {
113
+ $jwt = $auth->refreshToken($jwt);
114
+
115
+ if (!isset($_COOKIE[Auth::$cookieName]) && !headers_sent()) {
116
+ header('Authorization: Bearer ' . $jwt);
117
+ header('X-Refreshed-Token: ' . $jwt);
118
+ }
119
+ } catch (Throwable) {
120
+ return false;
121
+ }
102
122
  }
103
123
 
104
124
  $verifyToken = $auth->verifyToken($jwt);
125
+
105
126
  if ($verifyToken === false) {
106
127
  return false;
107
128
  }
108
129
 
109
- // Access the PAYLOAD_NAME property using the -> operator instead of array syntax
110
- if (isset($verifyToken->{Auth::PAYLOAD_NAME})) {
111
- return true;
112
- }
113
-
114
- return false;
130
+ return isset($verifyToken->{Auth::PAYLOAD_NAME});
115
131
  }
116
132
 
117
133
  protected static function hasRequiredRole(string $requestPathname): string
@@ -4,6 +4,8 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Lib\Middleware;
6
6
 
7
+ use PP\Env;
8
+
7
9
  final class CorsMiddleware
8
10
  {
9
11
  public static function handle(?array $overrides = null): void
@@ -58,14 +60,14 @@ final class CorsMiddleware
58
60
 
59
61
  private static function buildConfig(?array $overrides): array
60
62
  {
61
- $allowed = self::parseList($_ENV['CORS_ALLOWED_ORIGINS'] ?? '');
63
+ $allowed = self::parseList(Env::string('CORS_ALLOWED_ORIGINS', ''));
62
64
  $cfg = [
63
65
  'allowedOrigins' => $allowed,
64
- 'allowCredentials' => filter_var($_ENV['CORS_ALLOW_CREDENTIALS'] ?? 'false', FILTER_VALIDATE_BOOLEAN),
65
- 'allowedMethods' => $_ENV['CORS_ALLOWED_METHODS'] ?? 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
66
- 'allowedHeaders' => trim($_ENV['CORS_ALLOWED_HEADERS'] ?? ''),
67
- 'exposeHeaders' => trim($_ENV['CORS_EXPOSE_HEADERS'] ?? ''),
68
- 'maxAge' => (int)($_ENV['CORS_MAX_AGE'] ?? 86400),
66
+ 'allowCredentials' => Env::bool('CORS_ALLOW_CREDENTIALS', false),
67
+ 'allowedMethods' => Env::string('CORS_ALLOWED_METHODS', 'GET, POST, PUT, PATCH, DELETE, OPTIONS'),
68
+ 'allowedHeaders' => trim(Env::string('CORS_ALLOWED_HEADERS', '')),
69
+ 'exposeHeaders' => trim(Env::string('CORS_EXPOSE_HEADERS', '')),
70
+ 'maxAge' => Env::int('CORS_MAX_AGE', 86400),
69
71
  ];
70
72
 
71
73
  if (is_array($overrides)) {
@@ -15,12 +15,12 @@ class ConnectionManager implements MessageComponentInterface
15
15
 
16
16
  public function __construct()
17
17
  {
18
- $this->clients = new SplObjectStorage;
18
+ $this->clients = new SplObjectStorage();
19
19
  }
20
20
 
21
21
  public function onOpen(ConnectionInterface $conn): void
22
22
  {
23
- $this->clients->attach($conn);
23
+ $this->clients->offsetSet($conn, true);
24
24
  echo "New connection! ({$conn->resourceId})";
25
25
  }
26
26
 
@@ -35,7 +35,7 @@ class ConnectionManager implements MessageComponentInterface
35
35
 
36
36
  public function onClose(ConnectionInterface $conn): void
37
37
  {
38
- $this->clients->detach($conn);
38
+ $this->clients->offsetUnset($conn);
39
39
  echo "Connection {$conn->resourceId} has disconnected";
40
40
  }
41
41
 
@@ -15,14 +15,15 @@ use Ratchet\WebSocket\WsServer;
15
15
  use Lib\Websocket\ConnectionManager;
16
16
  use React\EventLoop\LoopInterface;
17
17
  use Throwable;
18
+ use PP\Env;
18
19
 
19
20
  // ── Load .env (optional) and timezone ─────────────────────────────────────────
20
21
  if (file_exists(DOCUMENT_PATH . '/.env')) {
21
22
  Dotenv::createImmutable(DOCUMENT_PATH)->safeLoad();
22
23
  }
23
- date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'UTC');
24
+ date_default_timezone_set(Env::string('APP_TIMEZONE', 'UTC'));
24
25
 
25
- // ── Tiny argv parser: allows --host=0.0.0.0 --port=8080 ──────────────────────
26
+ // ── Tiny argv parser: allows --host=0.0.0.0 --port=9001 ──────────────────────
26
27
  $cli = [];
27
28
  foreach ($argv ?? [] as $arg) {
28
29
  if (preg_match('/^--([^=]+)=(.*)$/', $arg, $m)) {
@@ -31,11 +32,11 @@ foreach ($argv ?? [] as $arg) {
31
32
  }
32
33
 
33
34
  // ── Resolve settings (env → cli defaults) ────────────────────────────────────
34
- $appName = $_ENV['WS_NAME'] ?? 'prisma-php-ws';
35
- $appVer = $_ENV['WS_VERSION'] ?? '0.0.1';
36
- $host = $cli['host'] ?? ($_ENV['WS_HOST'] ?? '127.0.0.1');
37
- $port = (int)($cli['port'] ?? ($_ENV['WS_PORT'] ?? 8080));
38
- $verbose = filter_var($cli['verbose'] ?? ($_ENV['WS_VERBOSE'] ?? 'true'), FILTER_VALIDATE_BOOLEAN);
35
+ $appName = Env::string('WS_NAME', 'prisma-php-ws');
36
+ $appVer = Env::string('WS_VERSION', '0.0.1');
37
+ $host = $cli['host'] ?? Env::string('WS_HOST', '127.0.0.1');
38
+ $port = (int)($cli['port'] ?? Env::int('WS_PORT', 9001));
39
+ $verbose = filter_var($cli['verbose'] ?? Env::bool('WS_VERBOSE', true), FILTER_VALIDATE_BOOLEAN);
39
40
 
40
41
  // ── Console helpers ──────────────────────────────────────────────────────────
41
42
  $color = static fn(string $t, string $c) => "\033[{$c}m{$t}\033[0m";
@@ -8,7 +8,7 @@
8
8
  <ol class="list-inside list-decimal text-sm/6 text-center sm:text-left">
9
9
  <li class="mb-2 tracking-[-.01em]">
10
10
  Get started by editing
11
- <code class="bg-text-foreground/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
11
+ <code class="bg-text-foreground/[.05] dark:bg-white/6 px-1 py-0.5 rounded font-semibold">
12
12
  src/app/index.php
13
13
  </code>
14
14
  .
@@ -46,7 +46,7 @@
46
46
  </div>
47
47
  </main>
48
48
 
49
- <footer class="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
49
+ <footer class="row-start-3 flex gap-6 flex-wrap items-center justify-center">
50
50
  <a
51
51
  class="flex items-center gap-2 hover:underline hover:underline-offset-4"
52
52
  href="https://prismaphp.tsnc.tech/docs?doc=learning-path"
@@ -15,7 +15,7 @@ MainLayout::$description = !empty(MainLayout::$description) ? MainLayout::$descr
15
15
  <!-- Dynamic Header Scripts -->
16
16
  </head>
17
17
 
18
- <body pp-spa="true">
18
+ <body pp-spa="true" style="opacity:0;pointer-events:none;user-select:none;transition:opacity .18s ease-out;">
19
19
  <?= MainLayout::$children; ?>
20
20
  <!-- Dynamic Footer Scripts -->
21
21
  </body>
@@ -1,7 +1,7 @@
1
- <div class="flex flex-col min-h-[100vh] items-center justify-center space-y-4 text-center">
1
+ <div class="flex flex-col min-h-screen items-center justify-center space-y-4 text-center">
2
2
  <div class="space-y-2">
3
3
  <h1 class="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl">404 Not Found</h1>
4
- <p class="max-w-[600px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400">
4
+ <p class="max-w-150 text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400">
5
5
  Sorry, we couldn't find the page you're looking for.
6
6
  </p>
7
7
  </div>
@@ -106,6 +106,6 @@
106
106
  // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
107
107
  "skipLibCheck": true /* Skip type checking all .d.ts files. */
108
108
  },
109
- "include": ["**/*.ts"],
109
+ "include": ["**/*.ts", ".pp/**/*.d.ts"],
110
110
  "exclude": ["node_modules", "vendor"]
111
111
  }
@@ -2,6 +2,7 @@ import { defineConfig, Plugin } from "vite";
2
2
  import path from "path";
3
3
  import fg from "fast-glob";
4
4
  import { writeFileSync } from "fs";
5
+ import { generateGlobalTypes } from "./settings/vite-plugins/generate-global-types.js";
5
6
 
6
7
  const entries = Object.fromEntries(
7
8
  fg.sync("ts/**/*.ts", { ignore: ["**/*.test.ts"] }).map((f) => {
@@ -10,6 +11,13 @@ const entries = Object.fromEntries(
10
11
  })
11
12
  );
12
13
 
14
+ const VITE_WATCH_EXCLUDE = [
15
+ "public/js/**",
16
+ "node_modules/**",
17
+ "vendor/**",
18
+ ".pp/**",
19
+ ];
20
+
13
21
  function browserSyncNotify(): Plugin {
14
22
  const flagFile = path.resolve(__dirname, ".pp", ".vite-build-complete");
15
23
 
@@ -28,9 +36,10 @@ export default defineConfig(({ command, mode }) => ({
28
36
  emptyOutDir: false,
29
37
  minify: "esbuild",
30
38
  sourcemap: false,
39
+ chunkSizeWarningLimit: 1000,
31
40
  watch:
32
41
  command === "build" && mode === "development"
33
- ? { exclude: ["public/**", "node_modules/**"] }
42
+ ? { exclude: VITE_WATCH_EXCLUDE }
34
43
  : undefined,
35
44
  rollupOptions: {
36
45
  input: entries,
@@ -39,11 +48,24 @@ export default defineConfig(({ command, mode }) => ({
39
48
  entryFileNames: "[name].js",
40
49
  chunkFileNames: "chunks/[name]-[hash].js",
41
50
  assetFileNames: "assets/[name]-[hash][extname]",
51
+ manualChunks(id) {
52
+ if (id.includes("node_modules")) {
53
+ return id
54
+ .toString()
55
+ .split("node_modules/")[1]
56
+ .split("/")[0]
57
+ .toString();
58
+ }
59
+ },
42
60
  },
43
61
  },
44
62
  },
45
- plugins:
46
- command === "build" && mode === "development" ? [browserSyncNotify()] : [],
63
+ plugins: [
64
+ generateGlobalTypes(),
65
+ ...(command === "build" && mode === "development"
66
+ ? [browserSyncNotify()]
67
+ : []),
68
+ ],
47
69
  esbuild: { legalComments: "none" },
48
70
  define: { "process.env.NODE_ENV": '"production"' },
49
71
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "4.4.4-beta",
3
+ "version": "4.4.4",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -32,15 +32,15 @@
32
32
  "author": "Jefferson Abraham Omier <thesteelninjacode@gmail.com>",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "chalk": "^5.3.0",
35
+ "chalk": "^5.6.2",
36
36
  "crypto-js": "^4.2.0",
37
37
  "prompts": "^2.4.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/crypto-js": "^4.2.2",
41
- "@types/node": "^20.11.7",
41
+ "@types/node": "^25.0.3",
42
42
  "@types/prompts": "^2.4.9",
43
43
  "ts-node": "^10.9.2",
44
- "typescript": "^5.3.3"
44
+ "typescript": "^5.9.3"
45
45
  }
46
46
  }