create-nevela 0.2.1 → 0.4.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 (70) hide show
  1. package/README.md +17 -6
  2. package/index.mjs +89 -17
  3. package/package.json +2 -1
  4. package/project.mjs +9 -4
  5. package/selfupdate.mjs +13 -3
  6. package/template/laravel/README.md +1 -1
  7. package/template/laravel/composer.json +2 -1
  8. package/template/laravel/config/nevela.php +66 -1
  9. package/template/laravel/database/migrations/2026_10_05_000000_create_nevela_auth_tables.php +66 -0
  10. package/template/laravel/src/Auth/Account.php +140 -0
  11. package/template/laravel/src/Auth/AuthMail.php +77 -0
  12. package/template/laravel/src/Auth/Challenges.php +135 -0
  13. package/template/laravel/src/Auth/Passkeys.php +206 -0
  14. package/template/laravel/src/Auth/Totp.php +106 -0
  15. package/template/laravel/src/Auth/Web.php +75 -0
  16. package/template/laravel/src/Console/GenerateCommand.php +1 -0
  17. package/template/laravel/src/Console/StatusCommand.php +2 -2
  18. package/template/laravel/src/Console/UpdateCommand.php +67 -9
  19. package/template/laravel/src/Generator/ResourceGenerator.php +63 -2
  20. package/template/laravel/src/Http/Auth/AccountController.php +252 -0
  21. package/template/laravel/src/Http/Auth/Answers.php +33 -0
  22. package/template/laravel/src/Http/Auth/SecurityController.php +188 -0
  23. package/template/laravel/src/Http/Auth/SignInController.php +302 -0
  24. package/template/laravel/src/Http/TokenController.php +19 -37
  25. package/template/laravel/src/Http/UploadController.php +24 -12
  26. package/template/laravel/src/Models/Passkey.php +38 -0
  27. package/template/laravel/src/Models/TwoFactor.php +64 -0
  28. package/template/laravel/src/Nevela.php +1 -1
  29. package/template/laravel/src/NevelaServiceProvider.php +72 -4
  30. package/template/laravel/src/Support/Descriptor.php +3 -3
  31. package/template/laravel/src/Support/Field.php +29 -2
  32. package/template/web/app/api/[...path]/route.ts +5 -1
  33. package/template/web/app/api/auth/[...action]/route.ts +196 -0
  34. package/template/web/app/auth-actions.ts +4 -43
  35. package/template/web/app/dashboard/account/layout.tsx +29 -0
  36. package/template/web/app/dashboard/account/page.tsx +19 -0
  37. package/template/web/app/dashboard/account/password/page.tsx +10 -0
  38. package/template/web/app/dashboard/account/security/page.tsx +19 -0
  39. package/template/web/app/dashboard/account/sessions/page.tsx +9 -0
  40. package/template/web/app/dashboard/layout.tsx +2 -1
  41. package/template/web/app/forgot-password/page.tsx +22 -0
  42. package/template/web/app/reset-password/page.tsx +31 -0
  43. package/template/web/app/sign-in/page.tsx +34 -4
  44. package/template/web/app/sign-up/page.tsx +43 -0
  45. package/template/web/app/two-factor/page.tsx +34 -0
  46. package/template/web/app/verify-email/page.tsx +33 -0
  47. package/template/web/components/account/account-nav.tsx +39 -0
  48. package/template/web/components/account/sections.tsx +573 -0
  49. package/template/web/components/auth/otp-input.tsx +95 -0
  50. package/template/web/components/auth/password-field.tsx +116 -0
  51. package/template/web/components/auth/password-forms.tsx +76 -0
  52. package/template/web/components/auth/provider-icons.tsx +39 -0
  53. package/template/web/components/auth/sign-in-flow.tsx +307 -0
  54. package/template/web/components/auth/sign-up-form.tsx +93 -0
  55. package/template/web/components/auth/social-buttons.tsx +56 -0
  56. package/template/web/components/auth/two-factor-form.tsx +144 -0
  57. package/template/web/components/auth/use-resend.ts +44 -0
  58. package/template/web/components/auth/verify-email-form.tsx +147 -0
  59. package/template/web/components/dashboard/dashboard-sidebar.tsx +2 -2
  60. package/template/web/components/dashboard/user-menu.tsx +33 -1
  61. package/template/web/lib/auth-client.ts +279 -0
  62. package/template/web/lib/auth-config.ts +43 -0
  63. package/template/web/lib/laravel.ts +26 -1
  64. package/template/web/lib/password-rules.ts +167 -0
  65. package/template/web/lib/second-step.ts +35 -0
  66. package/template/web/lib/session.ts +12 -4
  67. package/template/web/package.json +3 -2
  68. package/template.mjs +1 -1
  69. package/tool.mjs +58 -0
  70. package/template/web/components/auth/sign-in-form.tsx +0 -50
package/tool.mjs ADDED
@@ -0,0 +1,58 @@
1
+ // `nevela update`: the nevela command updating itself, from any folder.
2
+ //
3
+ // Two different things get newer, and they have two different commands, as in Grit:
4
+ //
5
+ // nevela update the `nevela` command on this computer
6
+ // nevela upgrade the app you are in: its Nevela package and its dashboard
7
+ //
8
+ // Updating the command means asking the package manager that installed it to install the
9
+ // latest. Which one that was is read from where this file is running from.
10
+
11
+ import fs from 'node:fs';
12
+
13
+ /** The command that installs the latest create-nevela globally, for each package manager. */
14
+ const INSTALL = {
15
+ npm: ['npm', 'install', '-g', 'create-nevela@latest'],
16
+ pnpm: ['pnpm', 'add', '-g', 'create-nevela@latest'],
17
+ yarn: ['yarn', 'global', 'add', 'create-nevela@latest'],
18
+ bun: ['bun', 'add', '-g', 'create-nevela@latest'],
19
+ };
20
+
21
+ /**
22
+ * How the running copy was installed, from the folder it is in.
23
+ *
24
+ * Returns the package manager and the command that updates it, or null when this copy
25
+ * isn't the installed command: it was fetched for a single run (npx, pnpm dlx, a
26
+ * self-update), it is the repository itself, or it is a dependency in some project's own
27
+ * node_modules. Updating the global command would not change the copy that is running in
28
+ * any of those cases.
29
+ *
30
+ * @param {string} dir The folder this package is running from
31
+ * @param {(file: string) => boolean} exists Whether a file exists; replaceable in tests
32
+ */
33
+ export function globalInstall(dir, exists = fs.existsSync) {
34
+ const plain = dir.replace(/\\/g, '/').replace(/\/+$/, '');
35
+ const where = plain.toLowerCase();
36
+
37
+ // Fetched for one run and thrown away afterwards.
38
+ if (/\/_npx\/|\/dlx[-/]|\/\.?bunx-|\/xfs-[0-9a-f]+\/|\/create-nevela-\d[^/]*\/package$/.test(where)) return null;
39
+ // Installed, as opposed to checked out: it sits in somebody's node_modules.
40
+ if (!/\/node_modules\/create-nevela$/.test(where)) return null;
41
+
42
+ // pnpm, yarn and bun each keep global packages in a folder of their own.
43
+ if (/\/pnpm\/global\//.test(where)) return { manager: 'pnpm', command: INSTALL.pnpm };
44
+ if (/\/\.bun\/install\/global\//.test(where)) return { manager: 'bun', command: INSTALL.bun };
45
+ if (/\/yarn\/(data\/)?global\/|\/\.config\/yarn\/global\//.test(where)) return { manager: 'yarn', command: INSTALL.yarn };
46
+
47
+ // npm's global folder is a plain node_modules, like a project's. Two things tell them
48
+ // apart: a project has a package.json beside its node_modules, and a dependency of a
49
+ // dependency (or pnpm's local store) sits in a node_modules inside another.
50
+ if (where.split('/node_modules/').length !== 2) return null;
51
+ const owner = plain.slice(0, where.lastIndexOf('/node_modules/'));
52
+ if (exists(`${owner}/package.json`)) return null;
53
+
54
+ return { manager: 'npm', command: INSTALL.npm };
55
+ }
56
+
57
+ /** What to type to get the `nevela` command, for someone who doesn't have it yet. */
58
+ export const INSTALL_COMMAND = INSTALL.npm.join(' ').replace('@latest', '');
@@ -1,50 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { signInAction } from "@/app/auth-actions";
5
- import { AuthInput, AuthLabel, FormMessage, PrimaryButton } from "./ui";
6
-
7
- /** Email and password, checked by Laravel. */
8
- export function SignInForm({ next, initialError }: { next: string; initialError?: string | null }) {
9
- const [email, setEmail] = useState("");
10
- const [password, setPassword] = useState("");
11
- const [error, setError] = useState<string | null>(initialError ?? null);
12
- const [pending, setPending] = useState(false);
13
-
14
- const submit = async (event: React.FormEvent) => {
15
- event.preventDefault();
16
- setPending(true);
17
- setError(null);
18
- const result = await signInAction(email, password);
19
- if (result.ok) {
20
- window.location.href = next;
21
- return;
22
- }
23
- setError(result.error);
24
- setPending(false);
25
- };
26
-
27
- return (
28
- <form onSubmit={submit} className="flex flex-col gap-4">
29
- {error && <FormMessage tone="error">{error}</FormMessage>}
30
- <div className="flex flex-col gap-1.5">
31
- <AuthLabel htmlFor="email">Email</AuthLabel>
32
- <AuthInput id="email" type="email" autoComplete="email" required autoFocus value={email} onChange={(event) => setEmail(event.target.value)} />
33
- </div>
34
- <div className="flex flex-col gap-1.5">
35
- <AuthLabel htmlFor="password">Password</AuthLabel>
36
- <AuthInput
37
- id="password"
38
- type="password"
39
- autoComplete="current-password"
40
- required
41
- value={password}
42
- onChange={(event) => setPassword(event.target.value)}
43
- />
44
- </div>
45
- <PrimaryButton type="submit" pending={pending}>
46
- Sign in
47
- </PrimaryButton>
48
- </form>
49
- );
50
- }