alepha 0.19.4 → 0.19.5

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 (104) hide show
  1. package/dist/api/audits/index.d.ts +8 -8
  2. package/dist/api/issues/index.d.ts +810 -0
  3. package/dist/api/issues/index.d.ts.map +1 -0
  4. package/dist/api/issues/index.js +447 -0
  5. package/dist/api/issues/index.js.map +1 -0
  6. package/dist/api/keys/index.d.ts +5 -5
  7. package/dist/api/users/index.d.ts +6 -0
  8. package/dist/api/users/index.d.ts.map +1 -1
  9. package/dist/api/users/index.js +10 -3
  10. package/dist/api/users/index.js.map +1 -1
  11. package/dist/api/workflows/index.d.ts +3 -3
  12. package/dist/captcha/index.d.ts +142 -0
  13. package/dist/captcha/index.d.ts.map +1 -0
  14. package/dist/captcha/index.js +177 -0
  15. package/dist/captcha/index.js.map +1 -0
  16. package/dist/cli/core/index.d.ts +82 -2
  17. package/dist/cli/core/index.d.ts.map +1 -1
  18. package/dist/cli/core/index.js +90 -6
  19. package/dist/cli/core/index.js.map +1 -1
  20. package/dist/cli/platform/index.d.ts +84 -10
  21. package/dist/cli/platform/index.d.ts.map +1 -1
  22. package/dist/cli/platform/index.js +92 -4
  23. package/dist/cli/platform/index.js.map +1 -1
  24. package/dist/cli/vendor/index.d.ts +30 -3
  25. package/dist/cli/vendor/index.d.ts.map +1 -1
  26. package/dist/cli/vendor/index.js +98 -21
  27. package/dist/cli/vendor/index.js.map +1 -1
  28. package/dist/command/index.d.ts.map +1 -1
  29. package/dist/command/index.js +2 -3
  30. package/dist/command/index.js.map +1 -1
  31. package/dist/orm/core/index.bun.js +6 -6
  32. package/dist/orm/core/index.bun.js.map +1 -1
  33. package/dist/orm/core/index.d.ts.map +1 -1
  34. package/dist/orm/core/index.js +6 -6
  35. package/dist/orm/core/index.js.map +1 -1
  36. package/dist/react/i18n/index.d.ts +1 -0
  37. package/dist/react/i18n/index.d.ts.map +1 -1
  38. package/dist/react/i18n/index.js +8 -4
  39. package/dist/react/i18n/index.js.map +1 -1
  40. package/dist/security/index.d.ts.map +1 -1
  41. package/dist/security/index.js.map +1 -1
  42. package/dist/server/auth/index.d.ts +145 -2
  43. package/dist/server/auth/index.d.ts.map +1 -1
  44. package/dist/server/auth/index.js +364 -63
  45. package/dist/server/auth/index.js.map +1 -1
  46. package/dist/server/cookies/index.d.ts.map +1 -1
  47. package/dist/server/cookies/index.js.map +1 -1
  48. package/dist/websocket/index.d.ts.map +1 -1
  49. package/dist/websocket/index.js.map +1 -1
  50. package/package.json +11 -1
  51. package/src/api/issues/__tests__/IssueService.spec.ts +263 -0
  52. package/src/api/issues/controllers/AdminIssueController.ts +149 -0
  53. package/src/api/issues/controllers/IssueController.ts +44 -0
  54. package/src/api/issues/entities/issues.ts +49 -0
  55. package/src/api/issues/index.ts +53 -0
  56. package/src/api/issues/schemas/createIssueSchema.ts +13 -0
  57. package/src/api/issues/schemas/issueConfigAtom.ts +13 -0
  58. package/src/api/issues/schemas/issueQuerySchema.ts +18 -0
  59. package/src/api/issues/schemas/issueResourceSchema.ts +6 -0
  60. package/src/api/issues/schemas/myIssueQuerySchema.ts +10 -0
  61. package/src/api/issues/schemas/updateIssueSchema.ts +13 -0
  62. package/src/api/issues/services/IssueService.ts +264 -0
  63. package/src/api/users/primitives/$realm.ts +24 -0
  64. package/src/api/users/services/CredentialService.ts +6 -3
  65. package/src/api/users/services/RegistrationService.ts +15 -5
  66. package/src/api/users/services/SessionService.ts +2 -0
  67. package/src/captcha/__tests__/MemoryCaptchaProvider.spec.ts +74 -0
  68. package/src/captcha/index.ts +33 -0
  69. package/src/captcha/providers/CaptchaProvider.ts +17 -0
  70. package/src/captcha/providers/MemoryCaptchaProvider.ts +65 -0
  71. package/src/captcha/providers/TurnstileCaptchaProvider.ts +125 -0
  72. package/src/cli/core/atoms/buildOptions.ts +57 -0
  73. package/src/cli/core/commands/build.ts +2 -0
  74. package/src/cli/core/providers/ViteDevServerProvider.ts +1 -1
  75. package/src/cli/core/services/ViteUtils.ts +5 -2
  76. package/src/cli/core/tasks/BuildClientTask.ts +3 -1
  77. package/src/cli/core/tasks/BuildCloudflareTask.ts +4 -0
  78. package/src/cli/core/tasks/BuildPwaTask.ts +81 -0
  79. package/src/cli/platform/adapters/CloudflareAdapter.ts +24 -0
  80. package/src/cli/platform/atoms/platformOptions.ts +19 -3
  81. package/src/cli/platform/hooks/PlatformHook.ts +51 -0
  82. package/src/cli/platform/index.ts +1 -0
  83. package/src/cli/platform/services/CloudflareApi.ts +22 -1
  84. package/src/cli/platform/services/PlatformOrchestrator.ts +67 -2
  85. package/src/cli/vendor/__tests__/VendorService.spec.ts +40 -1
  86. package/src/cli/vendor/commands/VendorCommand.ts +41 -38
  87. package/src/cli/vendor/services/VendorService.ts +108 -4
  88. package/src/command/__tests__/CliProvider.spec.ts +45 -0
  89. package/src/command/providers/CliProvider.ts +3 -4
  90. package/src/orm/core/services/Repository.ts +20 -6
  91. package/src/react/i18n/__tests__/I18nProvider.spec.ts +83 -0
  92. package/src/react/i18n/providers/I18nProvider.ts +12 -10
  93. package/src/security/primitives/$issuer.ts +3 -1
  94. package/src/server/auth/index.ts +7 -0
  95. package/src/server/auth/primitives/$auth.ts +37 -3
  96. package/src/server/auth/primitives/$authApple.ts +114 -4
  97. package/src/server/auth/primitives/$authFacebook.ts +98 -0
  98. package/src/server/auth/primitives/$authFranceConnect.ts +105 -0
  99. package/src/server/auth/primitives/$authGithub.ts +22 -16
  100. package/src/server/auth/primitives/$authMicrosoft.ts +88 -0
  101. package/src/server/auth/providers/ServerAuthProvider.ts +197 -72
  102. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -0
  103. package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +1 -1
  104. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -1
@@ -657,7 +657,7 @@ declare class WorkflowProvider {
657
657
  protected readonly stepLogs: _$alepha_orm0.Repository<_$alepha.TObject<{
658
658
  id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$alepha.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
659
659
  logs: _$alepha.TArray<_$alepha.TObject<{
660
- level: _$alepha.TUnsafe<"TRACE" | "SILENT" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
660
+ level: _$alepha.TUnsafe<"SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
661
661
  message: _$alepha.TString;
662
662
  service: _$alepha.TString;
663
663
  module: _$alepha.TString;
@@ -675,7 +675,7 @@ declare class WorkflowProvider {
675
675
  data?: any;
676
676
  context?: string | undefined;
677
677
  app?: string | undefined;
678
- level: "TRACE" | "SILENT" | "DEBUG" | "INFO" | "WARN" | "ERROR";
678
+ level: "SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR";
679
679
  message: string;
680
680
  service: string;
681
681
  module: string;
@@ -1412,7 +1412,7 @@ declare class AdminWorkflowController {
1412
1412
  declare const workflowStepLogs: _$alepha_orm0.EntityPrimitive<_$alepha.TObject<{
1413
1413
  id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$alepha.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
1414
1414
  logs: _$alepha.TArray<_$alepha.TObject<{
1415
- level: _$alepha.TUnsafe<"TRACE" | "SILENT" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
1415
+ level: _$alepha.TUnsafe<"SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
1416
1416
  message: _$alepha.TString;
1417
1417
  service: _$alepha.TString;
1418
1418
  module: _$alepha.TString;
@@ -0,0 +1,142 @@
1
+ import * as _$alepha from "alepha";
2
+ import * as _$alepha_logger0 from "alepha/logger";
3
+
4
+ //#region ../../src/captcha/providers/CaptchaProvider.d.ts
5
+ /**
6
+ * Captcha verification provider interface.
7
+ *
8
+ * Verifies that a user-submitted captcha token is valid. Implementations
9
+ * call the relevant captcha service (Turnstile, reCAPTCHA, hCaptcha, etc.)
10
+ * to validate the token server-side.
11
+ */
12
+ declare abstract class CaptchaProvider {
13
+ /**
14
+ * Verify a captcha token.
15
+ *
16
+ * @param token - The captcha response token submitted by the client.
17
+ * @param ip - Optional client IP address for additional validation.
18
+ * @returns Whether the token is valid.
19
+ */
20
+ abstract verify(token: string, ip?: string): Promise<boolean>;
21
+ }
22
+ //#endregion
23
+ //#region ../../src/captcha/providers/MemoryCaptchaProvider.d.ts
24
+ interface CaptchaRecord {
25
+ token: string;
26
+ ip?: string;
27
+ verifiedAt: Date;
28
+ }
29
+ /**
30
+ * In-memory captcha provider for testing.
31
+ *
32
+ * Accepts all tokens by default. Use `reject()` to make verification fail,
33
+ * and `accept()` to restore. All verification attempts are recorded for assertions.
34
+ */
35
+ declare class MemoryCaptchaProvider implements CaptchaProvider {
36
+ protected readonly log: _$alepha_logger0.Logger;
37
+ /**
38
+ * All verification attempts.
39
+ */
40
+ records: CaptchaRecord[];
41
+ protected shouldAccept: boolean;
42
+ verify(token: string, ip?: string): Promise<boolean>;
43
+ /**
44
+ * Make all subsequent verifications fail.
45
+ */
46
+ reject(): void;
47
+ /**
48
+ * Make all subsequent verifications pass (default behavior).
49
+ */
50
+ accept(): void;
51
+ /**
52
+ * Whether a token was verified.
53
+ */
54
+ wasVerified(token: string): boolean;
55
+ /**
56
+ * Get the last verification attempt.
57
+ */
58
+ get last(): CaptchaRecord | undefined;
59
+ }
60
+ //#endregion
61
+ //#region ../../src/captcha/providers/TurnstileCaptchaProvider.d.ts
62
+ /**
63
+ * Cloudflare Turnstile captcha verification provider.
64
+ *
65
+ * Validates captcha tokens against the Cloudflare Turnstile siteverify API.
66
+ * Free, privacy-friendly, and supports invisible mode.
67
+ *
68
+ * ## Setup
69
+ *
70
+ * 1. Create a Turnstile widget at https://dash.cloudflare.com/?to=/:account/turnstile
71
+ * 2. Copy the **Site Key** (public, for the client) and **Secret Key** (private, for the server)
72
+ * 3. Set `TURNSTILE_SECRET_KEY` in your environment
73
+ *
74
+ * ## Client-side integration
75
+ *
76
+ * Add the Turnstile script and widget to your form:
77
+ *
78
+ * ```html
79
+ * <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
80
+ * <form>
81
+ * <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
82
+ * <button type="submit">Submit</button>
83
+ * </form>
84
+ * ```
85
+ *
86
+ * The widget injects a hidden `cf-turnstile-response` input into the form.
87
+ * Send this value as the `captchaToken` in your registration request.
88
+ *
89
+ * For explicit rendering (React, SPA):
90
+ *
91
+ * ```ts
92
+ * turnstile.render("#container", {
93
+ * sitekey: "YOUR_SITE_KEY",
94
+ * callback: (token) => setCaptchaToken(token),
95
+ * });
96
+ * ```
97
+ *
98
+ * ## Server-side usage
99
+ *
100
+ * Register the provider in your app:
101
+ *
102
+ * ```ts
103
+ * import { CaptchaProvider } from "alepha/captcha";
104
+ * import { TurnstileCaptchaProvider } from "alepha/captcha";
105
+ *
106
+ * alepha.with({ provide: CaptchaProvider, use: TurnstileCaptchaProvider });
107
+ * ```
108
+ *
109
+ * ## Test keys (for development)
110
+ *
111
+ * - Always passes: site `1x00000000000000000000AA`, secret `1x0000000000000000000000000000000AA`
112
+ * - Always blocks: site `2x00000000000000000000AB`, secret `2x0000000000000000000000000000000AB`
113
+ * - Forces interactive: site `3x00000000000000000000FF`
114
+ *
115
+ * ## Environment Variables
116
+ *
117
+ * - `TURNSTILE_SECRET_KEY`: The secret key from the Cloudflare Turnstile dashboard.
118
+ *
119
+ * @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
120
+ */
121
+ declare class TurnstileCaptchaProvider implements CaptchaProvider {
122
+ protected readonly log: _$alepha_logger0.Logger;
123
+ protected readonly secretKey: string;
124
+ constructor();
125
+ verify(token: string, ip?: string): Promise<boolean>;
126
+ }
127
+ //#endregion
128
+ //#region ../../src/captcha/index.d.ts
129
+ /**
130
+ * Captcha verification for bot protection.
131
+ *
132
+ * **Features:**
133
+ * - Provider abstraction for captcha services
134
+ * - Cloudflare Turnstile support (free, privacy-friendly)
135
+ * - In-memory provider for testing
136
+ *
137
+ * @module alepha.captcha
138
+ */
139
+ declare const AlephaCaptcha: _$alepha.Service<_$alepha.Module>;
140
+ //#endregion
141
+ export { AlephaCaptcha, CaptchaProvider, CaptchaRecord, MemoryCaptchaProvider, TurnstileCaptchaProvider };
142
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/captcha/providers/CaptchaProvider.ts","../../src/captcha/providers/MemoryCaptchaProvider.ts","../../src/captcha/providers/TurnstileCaptchaProvider.ts","../../src/captcha/index.ts"],"mappings":";;;;;;;;AAOA;;;uBAAsB,eAAA;EAQJ;;;;;;;EAAA,SAAA,MAAA,CAAO,KAAA,UAAe,EAAA,YAAc,OAAA;AAAA;;;UCZrC,aAAA;EACf,KAAA;EACA,EAAA;EACA,UAAA,EAAY,IAAA;AAAA;;;;;;;cASD,qBAAA,YAAiC,eAAA;EAAA,mBACzB,GAAA,EADc,gBAAA,CACX,MAAA;;;;EAKf,OAAA,EAAS,aAAA;EAAA,UAEN,YAAA;EAEG,MAAA,CAAO,KAAA,UAAe,EAAA,YAAc,OAAA;EAnBjC;;;EAkCT,MAAA,CAAA;EAlCK;;;EAyCL,MAAA,CAAA;EAhC0B;;;EAuC1B,WAAA,CAAY,KAAA;EA7B8B;;;EAAA,IAoCtC,IAAA,CAAA,GAAQ,aAAA;AAAA;;;;;;ADtDrB;;;;;;;;;;;;;ACJA;;;;;;;;;;AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACgDA;;cAAa,wBAAA,YAAoC,eAAA;EAAA,mBAC5B,GAAA,EADiB,gBAAA,CACd,MAAA;EAAA,mBACH,SAAA;;EAiBN,MAAA,CAAO,KAAA,UAAe,EAAA,YAAc,OAAA;AAAA;;;;AF3EnD;;;;;;;;;cGgBa,aAAA,EAAa,QAAA,CAAA,OAAA,CASxB,QAAA,CATwB,MAAA"}
@@ -0,0 +1,177 @@
1
+ import { $context, $module, AlephaError, t } from "alepha";
2
+ import { $logger } from "alepha/logger";
3
+ //#region ../../src/captcha/providers/CaptchaProvider.ts
4
+ /**
5
+ * Captcha verification provider interface.
6
+ *
7
+ * Verifies that a user-submitted captcha token is valid. Implementations
8
+ * call the relevant captcha service (Turnstile, reCAPTCHA, hCaptcha, etc.)
9
+ * to validate the token server-side.
10
+ */
11
+ var CaptchaProvider = class {};
12
+ //#endregion
13
+ //#region ../../src/captcha/providers/MemoryCaptchaProvider.ts
14
+ /**
15
+ * In-memory captcha provider for testing.
16
+ *
17
+ * Accepts all tokens by default. Use `reject()` to make verification fail,
18
+ * and `accept()` to restore. All verification attempts are recorded for assertions.
19
+ */
20
+ var MemoryCaptchaProvider = class {
21
+ log = $logger();
22
+ /**
23
+ * All verification attempts.
24
+ */
25
+ records = [];
26
+ shouldAccept = true;
27
+ async verify(token, ip) {
28
+ this.log.debug("Verifying captcha in memory store", {
29
+ token,
30
+ ip
31
+ });
32
+ this.records.push({
33
+ token,
34
+ ip,
35
+ verifiedAt: /* @__PURE__ */ new Date()
36
+ });
37
+ return this.shouldAccept;
38
+ }
39
+ /**
40
+ * Make all subsequent verifications fail.
41
+ */
42
+ reject() {
43
+ this.shouldAccept = false;
44
+ }
45
+ /**
46
+ * Make all subsequent verifications pass (default behavior).
47
+ */
48
+ accept() {
49
+ this.shouldAccept = true;
50
+ }
51
+ /**
52
+ * Whether a token was verified.
53
+ */
54
+ wasVerified(token) {
55
+ return this.records.some((r) => r.token === token);
56
+ }
57
+ /**
58
+ * Get the last verification attempt.
59
+ */
60
+ get last() {
61
+ return this.records[this.records.length - 1];
62
+ }
63
+ };
64
+ //#endregion
65
+ //#region ../../src/captcha/providers/TurnstileCaptchaProvider.ts
66
+ /**
67
+ * Cloudflare Turnstile captcha verification provider.
68
+ *
69
+ * Validates captcha tokens against the Cloudflare Turnstile siteverify API.
70
+ * Free, privacy-friendly, and supports invisible mode.
71
+ *
72
+ * ## Setup
73
+ *
74
+ * 1. Create a Turnstile widget at https://dash.cloudflare.com/?to=/:account/turnstile
75
+ * 2. Copy the **Site Key** (public, for the client) and **Secret Key** (private, for the server)
76
+ * 3. Set `TURNSTILE_SECRET_KEY` in your environment
77
+ *
78
+ * ## Client-side integration
79
+ *
80
+ * Add the Turnstile script and widget to your form:
81
+ *
82
+ * ```html
83
+ * <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer><\/script>
84
+ * <form>
85
+ * <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
86
+ * <button type="submit">Submit</button>
87
+ * </form>
88
+ * ```
89
+ *
90
+ * The widget injects a hidden `cf-turnstile-response` input into the form.
91
+ * Send this value as the `captchaToken` in your registration request.
92
+ *
93
+ * For explicit rendering (React, SPA):
94
+ *
95
+ * ```ts
96
+ * turnstile.render("#container", {
97
+ * sitekey: "YOUR_SITE_KEY",
98
+ * callback: (token) => setCaptchaToken(token),
99
+ * });
100
+ * ```
101
+ *
102
+ * ## Server-side usage
103
+ *
104
+ * Register the provider in your app:
105
+ *
106
+ * ```ts
107
+ * import { CaptchaProvider } from "alepha/captcha";
108
+ * import { TurnstileCaptchaProvider } from "alepha/captcha";
109
+ *
110
+ * alepha.with({ provide: CaptchaProvider, use: TurnstileCaptchaProvider });
111
+ * ```
112
+ *
113
+ * ## Test keys (for development)
114
+ *
115
+ * - Always passes: site `1x00000000000000000000AA`, secret `1x0000000000000000000000000000000AA`
116
+ * - Always blocks: site `2x00000000000000000000AB`, secret `2x0000000000000000000000000000000AB`
117
+ * - Forces interactive: site `3x00000000000000000000FF`
118
+ *
119
+ * ## Environment Variables
120
+ *
121
+ * - `TURNSTILE_SECRET_KEY`: The secret key from the Cloudflare Turnstile dashboard.
122
+ *
123
+ * @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
124
+ */
125
+ var TurnstileCaptchaProvider = class {
126
+ log = $logger();
127
+ secretKey;
128
+ constructor() {
129
+ const { alepha } = $context();
130
+ this.secretKey = alepha.parseEnv(t.object({ TURNSTILE_SECRET_KEY: t.text({ description: "The secret key from the Cloudflare Turnstile dashboard." }) })).TURNSTILE_SECRET_KEY;
131
+ }
132
+ async verify(token, ip) {
133
+ const body = new URLSearchParams();
134
+ body.set("secret", this.secretKey);
135
+ body.set("response", token);
136
+ if (ip) body.set("remoteip", ip);
137
+ try {
138
+ const data = await (await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
139
+ method: "POST",
140
+ body
141
+ })).json();
142
+ if (!data.success) this.log.debug("Turnstile verification failed", { errorCodes: data["error-codes"] });
143
+ return data.success;
144
+ } catch (error) {
145
+ throw new AlephaError("Failed to verify Turnstile captcha token", { cause: error });
146
+ }
147
+ }
148
+ };
149
+ //#endregion
150
+ //#region ../../src/captcha/index.ts
151
+ /**
152
+ * Captcha verification for bot protection.
153
+ *
154
+ * **Features:**
155
+ * - Provider abstraction for captcha services
156
+ * - Cloudflare Turnstile support (free, privacy-friendly)
157
+ * - In-memory provider for testing
158
+ *
159
+ * @module alepha.captcha
160
+ */
161
+ const AlephaCaptcha = $module({
162
+ name: "alepha.captcha",
163
+ services: [
164
+ CaptchaProvider,
165
+ MemoryCaptchaProvider,
166
+ TurnstileCaptchaProvider
167
+ ],
168
+ register: (alepha) => alepha.with({
169
+ optional: true,
170
+ provide: CaptchaProvider,
171
+ use: MemoryCaptchaProvider
172
+ })
173
+ });
174
+ //#endregion
175
+ export { AlephaCaptcha, CaptchaProvider, MemoryCaptchaProvider, TurnstileCaptchaProvider };
176
+
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/captcha/providers/CaptchaProvider.ts","../../src/captcha/providers/MemoryCaptchaProvider.ts","../../src/captcha/providers/TurnstileCaptchaProvider.ts","../../src/captcha/index.ts"],"sourcesContent":["/**\n * Captcha verification provider interface.\n *\n * Verifies that a user-submitted captcha token is valid. Implementations\n * call the relevant captcha service (Turnstile, reCAPTCHA, hCaptcha, etc.)\n * to validate the token server-side.\n */\nexport abstract class CaptchaProvider {\n /**\n * Verify a captcha token.\n *\n * @param token - The captcha response token submitted by the client.\n * @param ip - Optional client IP address for additional validation.\n * @returns Whether the token is valid.\n */\n public abstract verify(token: string, ip?: string): Promise<boolean>;\n}\n","import { $logger } from \"alepha/logger\";\nimport type { CaptchaProvider } from \"./CaptchaProvider.ts\";\n\nexport interface CaptchaRecord {\n token: string;\n ip?: string;\n verifiedAt: Date;\n}\n\n/**\n * In-memory captcha provider for testing.\n *\n * Accepts all tokens by default. Use `reject()` to make verification fail,\n * and `accept()` to restore. All verification attempts are recorded for assertions.\n */\nexport class MemoryCaptchaProvider implements CaptchaProvider {\n protected readonly log = $logger();\n\n /**\n * All verification attempts.\n */\n public records: CaptchaRecord[] = [];\n\n protected shouldAccept = true;\n\n public async verify(token: string, ip?: string): Promise<boolean> {\n this.log.debug(\"Verifying captcha in memory store\", { token, ip });\n\n this.records.push({\n token,\n ip,\n verifiedAt: new Date(),\n });\n\n return this.shouldAccept;\n }\n\n /**\n * Make all subsequent verifications fail.\n */\n public reject(): void {\n this.shouldAccept = false;\n }\n\n /**\n * Make all subsequent verifications pass (default behavior).\n */\n public accept(): void {\n this.shouldAccept = true;\n }\n\n /**\n * Whether a token was verified.\n */\n public wasVerified(token: string): boolean {\n return this.records.some((r) => r.token === token);\n }\n\n /**\n * Get the last verification attempt.\n */\n public get last(): CaptchaRecord | undefined {\n return this.records[this.records.length - 1];\n }\n}\n","import { $context, AlephaError, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { CaptchaProvider } from \"./CaptchaProvider.ts\";\n\n/**\n * Cloudflare Turnstile captcha verification provider.\n *\n * Validates captcha tokens against the Cloudflare Turnstile siteverify API.\n * Free, privacy-friendly, and supports invisible mode.\n *\n * ## Setup\n *\n * 1. Create a Turnstile widget at https://dash.cloudflare.com/?to=/:account/turnstile\n * 2. Copy the **Site Key** (public, for the client) and **Secret Key** (private, for the server)\n * 3. Set `TURNSTILE_SECRET_KEY` in your environment\n *\n * ## Client-side integration\n *\n * Add the Turnstile script and widget to your form:\n *\n * ```html\n * <script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n * <form>\n * <div class=\"cf-turnstile\" data-sitekey=\"YOUR_SITE_KEY\"></div>\n * <button type=\"submit\">Submit</button>\n * </form>\n * ```\n *\n * The widget injects a hidden `cf-turnstile-response` input into the form.\n * Send this value as the `captchaToken` in your registration request.\n *\n * For explicit rendering (React, SPA):\n *\n * ```ts\n * turnstile.render(\"#container\", {\n * sitekey: \"YOUR_SITE_KEY\",\n * callback: (token) => setCaptchaToken(token),\n * });\n * ```\n *\n * ## Server-side usage\n *\n * Register the provider in your app:\n *\n * ```ts\n * import { CaptchaProvider } from \"alepha/captcha\";\n * import { TurnstileCaptchaProvider } from \"alepha/captcha\";\n *\n * alepha.with({ provide: CaptchaProvider, use: TurnstileCaptchaProvider });\n * ```\n *\n * ## Test keys (for development)\n *\n * - Always passes: site `1x00000000000000000000AA`, secret `1x0000000000000000000000000000000AA`\n * - Always blocks: site `2x00000000000000000000AB`, secret `2x0000000000000000000000000000000AB`\n * - Forces interactive: site `3x00000000000000000000FF`\n *\n * ## Environment Variables\n *\n * - `TURNSTILE_SECRET_KEY`: The secret key from the Cloudflare Turnstile dashboard.\n *\n * @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/\n */\nexport class TurnstileCaptchaProvider implements CaptchaProvider {\n protected readonly log = $logger();\n protected readonly secretKey: string;\n\n constructor() {\n const { alepha } = $context();\n\n const env = alepha.parseEnv(\n t.object({\n TURNSTILE_SECRET_KEY: t.text({\n description:\n \"The secret key from the Cloudflare Turnstile dashboard.\",\n }),\n }),\n );\n\n this.secretKey = env.TURNSTILE_SECRET_KEY;\n }\n\n public async verify(token: string, ip?: string): Promise<boolean> {\n const body = new URLSearchParams();\n body.set(\"secret\", this.secretKey);\n body.set(\"response\", token);\n\n if (ip) {\n body.set(\"remoteip\", ip);\n }\n\n try {\n const res = await fetch(\n \"https://challenges.cloudflare.com/turnstile/v0/siteverify\",\n {\n method: \"POST\",\n body,\n },\n );\n\n const data = (await res.json()) as TurnstileResponse;\n\n if (!data.success) {\n this.log.debug(\"Turnstile verification failed\", {\n errorCodes: data[\"error-codes\"],\n });\n }\n\n return data.success;\n } catch (error) {\n throw new AlephaError(\"Failed to verify Turnstile captcha token\", {\n cause: error,\n });\n }\n }\n}\n\ninterface TurnstileResponse {\n success: boolean;\n \"error-codes\"?: string[];\n challenge_ts?: string;\n hostname?: string;\n action?: string;\n cdata?: string;\n}\n","import { $module } from \"alepha\";\nimport { CaptchaProvider } from \"./providers/CaptchaProvider.ts\";\nimport { MemoryCaptchaProvider } from \"./providers/MemoryCaptchaProvider.ts\";\nimport { TurnstileCaptchaProvider } from \"./providers/TurnstileCaptchaProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/CaptchaProvider.ts\";\nexport * from \"./providers/MemoryCaptchaProvider.ts\";\nexport * from \"./providers/TurnstileCaptchaProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Captcha verification for bot protection.\n *\n * **Features:**\n * - Provider abstraction for captcha services\n * - Cloudflare Turnstile support (free, privacy-friendly)\n * - In-memory provider for testing\n *\n * @module alepha.captcha\n */\nexport const AlephaCaptcha = $module({\n name: \"alepha.captcha\",\n services: [CaptchaProvider, MemoryCaptchaProvider, TurnstileCaptchaProvider],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: CaptchaProvider,\n use: MemoryCaptchaProvider,\n }),\n});\n"],"mappings":";;;;;;;;;;AAOA,IAAsB,kBAAtB,MAAsC;;;;;;;;;ACQtC,IAAa,wBAAb,MAA8D;CAC5D,MAAyB,SAAS;;;;CAKlC,UAAkC,EAAE;CAEpC,eAAyB;CAEzB,MAAa,OAAO,OAAe,IAA+B;AAChE,OAAK,IAAI,MAAM,qCAAqC;GAAE;GAAO;GAAI,CAAC;AAElE,OAAK,QAAQ,KAAK;GAChB;GACA;GACA,4BAAY,IAAI,MAAM;GACvB,CAAC;AAEF,SAAO,KAAK;;;;;CAMd,SAAsB;AACpB,OAAK,eAAe;;;;;CAMtB,SAAsB;AACpB,OAAK,eAAe;;;;;CAMtB,YAAmB,OAAwB;AACzC,SAAO,KAAK,QAAQ,MAAM,MAAM,EAAE,UAAU,MAAM;;;;;CAMpD,IAAW,OAAkC;AAC3C,SAAO,KAAK,QAAQ,KAAK,QAAQ,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACC9C,IAAa,2BAAb,MAAiE;CAC/D,MAAyB,SAAS;CAClC;CAEA,cAAc;EACZ,MAAM,EAAE,WAAW,UAAU;AAW7B,OAAK,YATO,OAAO,SACjB,EAAE,OAAO,EACP,sBAAsB,EAAE,KAAK,EAC3B,aACE,2DACH,CAAC,EACH,CAAC,CACH,CAEoB;;CAGvB,MAAa,OAAO,OAAe,IAA+B;EAChE,MAAM,OAAO,IAAI,iBAAiB;AAClC,OAAK,IAAI,UAAU,KAAK,UAAU;AAClC,OAAK,IAAI,YAAY,MAAM;AAE3B,MAAI,GACF,MAAK,IAAI,YAAY,GAAG;AAG1B,MAAI;GASF,MAAM,OAAQ,OARF,MAAM,MAChB,6DACA;IACE,QAAQ;IACR;IACD,CACF,EAEuB,MAAM;AAE9B,OAAI,CAAC,KAAK,QACR,MAAK,IAAI,MAAM,iCAAiC,EAC9C,YAAY,KAAK,gBAClB,CAAC;AAGJ,UAAO,KAAK;WACL,OAAO;AACd,SAAM,IAAI,YAAY,4CAA4C,EAChE,OAAO,OACR,CAAC;;;;;;;;;;;;;;;;ACzFR,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,UAAU;EAAC;EAAiB;EAAuB;EAAyB;CAC5E,WAAW,WACT,OAAO,KAAK;EACV,UAAU;EACV,SAAS;EACT,KAAK;EACN,CAAC;CACL,CAAC"}
@@ -202,6 +202,53 @@ declare const buildOptions: _$alepha.Atom<_$alepha.TObject<{
202
202
  */
203
203
  domain: _$alepha.TOptional<_$alepha.TString>;
204
204
  }>>;
205
+ /**
206
+ * PWA (Progressive Web App) configuration.
207
+ *
208
+ * Generates a web app manifest and enables installability.
209
+ * Requires a client-side bundle (React).
210
+ */
211
+ pwa: _$alepha.TOptional<_$alepha.TObject<{
212
+ /**
213
+ * Full application name displayed on the splash screen
214
+ * and in the OS app switcher.
215
+ */
216
+ name: _$alepha.TString;
217
+ /**
218
+ * Short name displayed on the home screen icon.
219
+ * Falls back to `name` if omitted.
220
+ */
221
+ shortName: _$alepha.TOptional<_$alepha.TString>;
222
+ /**
223
+ * Theme color used for the browser toolbar and OS chrome.
224
+ *
225
+ * @default "#ffffff"
226
+ */
227
+ themeColor: _$alepha.TOptional<_$alepha.TString>;
228
+ /**
229
+ * Background color for the splash screen.
230
+ *
231
+ * @default "#ffffff"
232
+ */
233
+ backgroundColor: _$alepha.TOptional<_$alepha.TString>;
234
+ /**
235
+ * Display mode for the installed PWA.
236
+ *
237
+ * - `standalone` - Looks like a native app (default)
238
+ * - `fullscreen` - Uses entire screen (games, immersive)
239
+ * - `minimal-ui` - Like standalone with minimal browser UI
240
+ * - `browser` - Standard browser tab
241
+ *
242
+ * @default "standalone"
243
+ */
244
+ display: _$alepha.TOptional<_$alepha.TUnsafe<"browser" | "standalone" | "fullscreen" | "minimal-ui">>;
245
+ /**
246
+ * Enable offline support via service worker.
247
+ *
248
+ * TODO: Not yet implemented.
249
+ */
250
+ offline: _$alepha.TOptional<_$alepha.TBoolean>;
251
+ }>>;
205
252
  /**
206
253
  * Sitemap generation configuration.
207
254
  */
@@ -11802,7 +11849,9 @@ declare class ViteUtils {
11802
11849
  * and injects only the short key into $page definitions.
11803
11850
  */
11804
11851
  createSsrPreloadPlugin(): Plugin;
11805
- generateIndexHtml(entry: AppEntry): string;
11852
+ generateIndexHtml(entry: AppEntry, opts?: {
11853
+ pwa?: boolean;
11854
+ }): string;
11806
11855
  /**
11807
11856
  * We need to close the Vite dev server after build is done.
11808
11857
  */
@@ -12352,6 +12401,29 @@ declare class BuildPrerenderTask extends BuildTask {
12352
12401
  protected renderFile(page: any, options: any, dist: string): Promise<void>;
12353
12402
  }
12354
12403
  //#endregion
12404
+ //#region ../../src/cli/core/tasks/BuildPwaTask.d.ts
12405
+ /**
12406
+ * Generate PWA web app manifest.
12407
+ *
12408
+ * Produces a `manifest.webmanifest` in the public output directory
12409
+ * from the `pwa` section of build options. Detects icons from `public/`.
12410
+ */
12411
+ declare class BuildPwaTask extends BuildTask {
12412
+ protected readonly fs: FileSystemProvider;
12413
+ run(ctx: BuildTaskContext): Promise<void>;
12414
+ /**
12415
+ * Detect icon files in the public output directory.
12416
+ *
12417
+ * Looks for common icon filenames and generates
12418
+ * manifest icon entries with appropriate sizes and types.
12419
+ */
12420
+ protected detectIcons(publicDir: string): Promise<Array<{
12421
+ src: string;
12422
+ sizes: string;
12423
+ type: string;
12424
+ }>>;
12425
+ }
12426
+ //#endregion
12355
12427
  //#region ../../src/cli/core/tasks/BuildServerTask.d.ts
12356
12428
  /**
12357
12429
  * Build server-side SSR bundle with Vite.
@@ -12510,6 +12582,14 @@ declare class BuildCommand {
12510
12582
  dist?: string | undefined;
12511
12583
  public?: string | undefined;
12512
12584
  } | undefined;
12585
+ pwa?: {
12586
+ shortName?: string | undefined;
12587
+ themeColor?: string | undefined;
12588
+ backgroundColor?: string | undefined;
12589
+ display?: "browser" | "standalone" | "fullscreen" | "minimal-ui" | undefined;
12590
+ offline?: boolean | undefined;
12591
+ name: string;
12592
+ } | undefined;
12513
12593
  sitemap?: {
12514
12594
  hostname: string;
12515
12595
  } | undefined;
@@ -12519,7 +12599,7 @@ declare class BuildCommand {
12519
12599
  * Each task self-guards (checks target, hasClient, etc.).
12520
12600
  * Order matters — compress must be last.
12521
12601
  */
12522
- protected readonly pipeline: (BuildAssetsTask | BuildClientTask | BuildCloudflareTask | BuildCompressTask | BuildDockerTask | BuildPrerenderTask | BuildServerTask | BuildSitemapTask | BuildStaticTask | BuildVercelTask)[];
12602
+ protected readonly pipeline: (BuildAssetsTask | BuildClientTask | BuildCloudflareTask | BuildCompressTask | BuildDockerTask | BuildPrerenderTask | BuildServerTask | BuildSitemapTask | BuildStaticTask | BuildVercelTask | BuildPwaTask)[];
12523
12603
  /**
12524
12604
  * Resolve the effective runtime based on target and explicit runtime flag.
12525
12605
  *