webspresso 0.0.99 → 0.0.100

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 (60) hide show
  1. package/.agents/AGENTS.md +2 -2
  2. package/.agents/AUTH.md +246 -0
  3. package/.agents/CLI.md +47 -0
  4. package/.agents/ERRORS.md +59 -0
  5. package/.agents/ORM.md +90 -0
  6. package/.agents/PLUGINS.md +44 -7
  7. package/.agents/QUEUE.md +18 -10
  8. package/.agents/ROUTING.md +33 -0
  9. package/.agents/STREAMING.md +35 -0
  10. package/AGENTS.md +1 -0
  11. package/README.md +145 -93
  12. package/bin/commands/add-docker.js +467 -0
  13. package/bin/commands/deploy-check.js +339 -0
  14. package/bin/webspresso.js +4 -0
  15. package/core/auth/index.js +63 -1
  16. package/core/auth/magic-link.js +170 -0
  17. package/core/auth/manager.js +343 -3
  18. package/core/auth/middleware.js +30 -5
  19. package/core/auth/session-manager.js +255 -0
  20. package/core/auth/sessions.js +351 -0
  21. package/core/auth/tokens.js +1 -0
  22. package/core/auth/totp.js +4 -1
  23. package/core/auth/user-agent.js +161 -0
  24. package/core/orm/index.js +2 -1
  25. package/core/orm/model.js +51 -1
  26. package/core/orm/repository.js +283 -13
  27. package/core/orm/scopes.js +3 -2
  28. package/core/orm/types.js +6 -1
  29. package/core/queue/cron.js +252 -39
  30. package/core/queue/manager.js +20 -2
  31. package/core/queue/scheduler.js +115 -59
  32. package/core/ssr/stream.js +62 -2
  33. package/index.d.ts +274 -14
  34. package/index.js +4 -0
  35. package/package.json +7 -5
  36. package/plugins/admin-panel/modules/admin-users.js +1 -1
  37. package/plugins/audit-log/admin-component.js +54 -54
  38. package/plugins/audit-log/index.js +2 -2
  39. package/plugins/content/client/inline-edit.js +2 -46
  40. package/plugins/cors.js +13 -2
  41. package/plugins/email/auth-bridge.js +66 -0
  42. package/plugins/email/bundled-templates.js +15 -0
  43. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  44. package/plugins/file-manager/admin-component.js +49 -49
  45. package/plugins/file-manager/index.js +4 -4
  46. package/plugins/index.js +2 -0
  47. package/plugins/webhooks/admin-component.js +608 -0
  48. package/plugins/webhooks/api-handlers.js +237 -0
  49. package/plugins/webhooks/crypto.js +144 -0
  50. package/plugins/webhooks/dispatcher.js +708 -0
  51. package/plugins/webhooks/index.js +263 -0
  52. package/plugins/webhooks/matcher.js +113 -0
  53. package/plugins/webhooks/migration-template.js +75 -0
  54. package/plugins/webhooks/models.js +98 -0
  55. package/plugins/webhooks/sender.js +176 -0
  56. package/src/bundle/index.js +2 -1
  57. package/src/file-router.js +173 -4
  58. package/src/helpers.js +7 -1
  59. package/src/njk-frontmatter.js +3 -0
  60. package/src/routing/cascading.js +112 -0
package/index.d.ts CHANGED
@@ -547,6 +547,19 @@ export interface CascadingLayoutInfo {
547
547
  configPath: string | null;
548
548
  }
549
549
 
550
+ export interface CascadingErrorBoundaryInfo {
551
+ dirSegment: string;
552
+ errorRelPath: string;
553
+ fullPath: string;
554
+ configPath: string | null;
555
+ }
556
+
557
+ export interface CascadingLoadingInfo {
558
+ dirSegment: string;
559
+ loadingRelPath: string;
560
+ fullPath: string;
561
+ }
562
+
550
563
  export namespace cascading {
551
564
  export function getDirectoryChain(relativeFilePath: string): string[];
552
565
  export function templateHasExtends(templateBody?: string | null, fullPath?: string): boolean;
@@ -568,6 +581,16 @@ export namespace cascading {
568
581
  renderContext: Record<string, unknown>,
569
582
  isDev?: boolean
570
583
  ): string;
584
+ export function findNearestErrorBoundary(
585
+ baseDir: string,
586
+ relativeFilePath: string,
587
+ isDev?: boolean
588
+ ): CascadingErrorBoundaryInfo | null;
589
+ export function findNearestLoadingTemplate(
590
+ baseDir: string,
591
+ relativeFilePath: string,
592
+ isDev?: boolean
593
+ ): CascadingLoadingInfo | null;
571
594
  export function clearCascadingCaches(): void;
572
595
  }
573
596
 
@@ -745,11 +768,20 @@ export interface ScopeContext {
745
768
 
746
769
  export type RelationType = 'belongsTo' | 'hasMany' | 'hasOne';
747
770
 
771
+ export type RelationCascadeOption =
772
+ | boolean
773
+ | 'delete'
774
+ | 'restore'
775
+ | 'all'
776
+ | ('delete' | 'restore' | 'all')[]
777
+ | { delete?: boolean; restore?: boolean };
778
+
748
779
  export interface RelationDefinition {
749
780
  type: RelationType;
750
- model: () => ModelDefinition;
781
+ model: string | (() => ModelDefinition<any> | string);
751
782
  foreignKey: string;
752
783
  localKey?: string;
784
+ cascade?: RelationCascadeOption;
753
785
  }
754
786
 
755
787
  export interface AdminPermissions {
@@ -955,6 +987,8 @@ export function hasModel(name: string): boolean;
955
987
 
956
988
  export function clearRegistry(): void;
957
989
 
990
+ export function isCascadeEnabled(relation: RelationDefinition, operation: 'delete' | 'restore'): boolean;
991
+
958
992
  // --- ORM: schema & columns ---
959
993
 
960
994
  /** zdb column helpers (id, string, timestamp, …) + `schema()` */
@@ -1040,9 +1074,9 @@ export interface Repository<T = Record<string, any>> {
1040
1074
  createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
1041
1075
  update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
1042
1076
  updateWhere(conditions: Partial<T> | Record<string, unknown>, data: Partial<T> | Record<string, unknown>): Promise<number>;
1043
- delete(id: string | number): Promise<boolean>;
1044
- forceDelete(id: string | number): Promise<boolean>;
1045
- restore(id: string | number): Promise<T | null>;
1077
+ delete(id: string | number, options?: { cascade?: boolean; deletedAt?: Date }): Promise<boolean>;
1078
+ forceDelete(id: string | number, options?: { cascade?: boolean }): Promise<boolean>;
1079
+ restore(id: string | number, options?: { cascade?: boolean; allChildren?: boolean }): Promise<T | null>;
1046
1080
  query(): QueryBuilder<T>;
1047
1081
  raw(sql: string, bindings?: unknown[]): Promise<unknown>;
1048
1082
  count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
@@ -1795,6 +1829,82 @@ export interface ApiKeyRouteOptions {
1795
1829
  export function apiKeyPlugin(options?: ApiKeyPluginOptions): WebspressoPlugin;
1796
1830
  export function createApiKeyMiddleware(options?: ApiKeyPluginOptions & ApiKeyRouteOptions): RequestHandler;
1797
1831
 
1832
+ export interface WebhookEndpoint {
1833
+ id?: number | string;
1834
+ name: string;
1835
+ url: string;
1836
+ secret?: string;
1837
+ events?: string[] | string;
1838
+ is_active?: boolean;
1839
+ description?: string | null;
1840
+ headers?: Record<string, string> | string | null;
1841
+ metadata?: Record<string, any> | string | null;
1842
+ created_at?: Date | string;
1843
+ updated_at?: Date | string;
1844
+ [key: string]: any;
1845
+ }
1846
+
1847
+ export interface WebhookDelivery {
1848
+ id?: number | string;
1849
+ endpoint_id: number | string;
1850
+ event: string;
1851
+ payload: string;
1852
+ status_code?: number | null;
1853
+ success: boolean;
1854
+ duration_ms?: number | null;
1855
+ error?: string | null;
1856
+ response_body?: string | null;
1857
+ attempt?: number;
1858
+ max_attempts?: number;
1859
+ next_retry_at?: Date | string | null;
1860
+ created_at?: Date | string;
1861
+ updated_at?: Date | string;
1862
+ [key: string]: any;
1863
+ }
1864
+
1865
+ export interface WebhooksPluginOptions {
1866
+ db?: any;
1867
+ knex?: any;
1868
+ endpointsTable?: string;
1869
+ deliveriesTable?: string;
1870
+ maxAttempts?: number;
1871
+ backoffBaseMs?: number;
1872
+ timeoutMs?: number;
1873
+ wrapPayload?: boolean;
1874
+ queue?: any;
1875
+ endpoints?: WebhookEndpoint[];
1876
+ apiPrefix?: string;
1877
+ includeDefaultPage?: boolean;
1878
+ pageTitle?: string;
1879
+ menuOrder?: number;
1880
+ }
1881
+
1882
+ export interface WebhookDispatchResult {
1883
+ dispatched: number;
1884
+ deliveries: WebhookDelivery[];
1885
+ results?: Array<{ success?: boolean; statusCode?: number; error?: string; [key: string]: any }>;
1886
+ }
1887
+
1888
+ export interface WebhookDispatcherInstance {
1889
+ dispatch(event: string, payload: any, options?: { wait?: boolean; endpointId?: number | string }): Promise<WebhookDispatchResult>;
1890
+ pingEndpoint(endpointId: number | string): Promise<any>;
1891
+ retryDelivery(deliveryId: number | string): Promise<any>;
1892
+ createEndpoint(data: Partial<WebhookEndpoint>): Promise<WebhookEndpoint>;
1893
+ getEndpoint(id: number | string): Promise<WebhookEndpoint | null>;
1894
+ updateEndpoint(id: number | string, data: Partial<WebhookEndpoint>): Promise<WebhookEndpoint | null>;
1895
+ deleteEndpoint(id: number | string): Promise<boolean>;
1896
+ listEndpoints(filters?: { active?: boolean; search?: string; limit?: number; offset?: number }): Promise<WebhookEndpoint[]>;
1897
+ listDeliveries(filters?: { endpointId?: number | string; event?: string; success?: boolean | string; page?: number; limit?: number }): Promise<{ items: WebhookDelivery[]; total: number; page: number; limit: number; pages: number }>;
1898
+ getDelivery(id: number | string): Promise<WebhookDelivery | null>;
1899
+ purgeDeliveries(options?: { days?: number }): Promise<number>;
1900
+ dispose(): void;
1901
+ on(event: string, listener: (...args: any[]) => void): this;
1902
+ }
1903
+
1904
+ export function webhooksPlugin(options?: WebhooksPluginOptions): WebspressoPlugin & {
1905
+ dispatcher: WebhookDispatcherInstance;
1906
+ };
1907
+
1798
1908
  export interface ContentPluginOptions {
1799
1909
  db: import('./index').Database;
1800
1910
  adminPath?: string;
@@ -2283,8 +2393,11 @@ export interface QueueRetentionConfig {
2283
2393
  export interface ScheduleOptions {
2284
2394
  id?: string;
2285
2395
  overlap?: boolean;
2396
+ preventOverlap?: boolean;
2286
2397
  maxRuns?: number;
2287
2398
  utc?: boolean;
2399
+ timezone?: string;
2400
+ tz?: string;
2288
2401
  runImmediately?: boolean;
2289
2402
  delay?: number | Date;
2290
2403
  attempts?: number;
@@ -2306,6 +2419,7 @@ export interface ScheduleInfo {
2306
2419
  nextRunAt: Date | null;
2307
2420
  isRunning: boolean;
2308
2421
  activeJobId: string | number | null;
2422
+ activeJobIds?: (string | number)[];
2309
2423
  registeredAt: Date;
2310
2424
  }
2311
2425
 
@@ -2313,21 +2427,22 @@ export class CronExpression {
2313
2427
  rawExpression: string;
2314
2428
  resolvedExpression: string;
2315
2429
  utc: boolean;
2430
+ timezone: string | null;
2316
2431
  minutes: Set<number>;
2317
2432
  hours: Set<number>;
2318
2433
  doms: Set<number>;
2319
2434
  months: Set<number>;
2320
2435
  dows: Set<number>;
2321
- constructor(expression: string, options?: { utc?: boolean });
2322
- match(date?: Date): boolean;
2323
- getNextDate(fromDate?: Date): Date | null;
2324
- getNextDates(count?: number, fromDate?: Date): Date[];
2325
- static isValid(expression: string): boolean;
2326
- static parse(expression: string, options?: { utc?: boolean }): CronExpression;
2436
+ constructor(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string });
2437
+ match(date?: Date | string | number): boolean;
2438
+ getNextDate(fromDate?: Date | string | number): Date | null;
2439
+ getNextDates(count?: number, fromDate?: Date | string | number): Date[];
2440
+ static isValid(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): boolean;
2441
+ static parse(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): CronExpression;
2327
2442
  }
2328
2443
 
2329
- export function parseCron(expression: string, options?: { utc?: boolean }): CronExpression;
2330
- export function isValidCron(expression: string): boolean;
2444
+ export function parseCron(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): CronExpression;
2445
+ export function isValidCron(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): boolean;
2331
2446
  export function resolveCronAlias(str: string): string;
2332
2447
  export const CRON_ALIASES: Record<string, string>;
2333
2448
 
@@ -2345,6 +2460,8 @@ export class TaskScheduler {
2345
2460
  trigger(idOrJobName: string): Promise<Job | null>;
2346
2461
  start(): this;
2347
2462
  stop(): this;
2463
+ pause(): this;
2464
+ resume(): this;
2348
2465
  clear(): void;
2349
2466
  destroy(): void;
2350
2467
  }
@@ -2379,6 +2496,8 @@ export class QueueManager extends EventEmitter {
2379
2496
  unschedule(idOrJobName: string): boolean;
2380
2497
  getSchedules(): ScheduleInfo[];
2381
2498
  getSchedule(idOrJobName: string): ScheduleInfo | null;
2499
+ triggerSchedule(idOrJobName: string): Promise<Job | null>;
2500
+ triggerCron(idOrJobName: string): Promise<Job | null>;
2382
2501
  start(): this;
2383
2502
  pause(): this;
2384
2503
  resume(): this;
@@ -2453,14 +2572,20 @@ export interface HtmlStreamOptions {
2453
2572
  env: import('nunjucks').Environment;
2454
2573
  templatePath: string;
2455
2574
  context?: Record<string, unknown>;
2456
- defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
2575
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string; loadingTemplate?: string }>;
2576
+ nonce?: string;
2577
+ loadingTemplate?: string | null;
2578
+ layouts?: CascadingLayoutInfo[];
2457
2579
  }
2458
2580
 
2459
2581
  export interface RenderStreamOptions {
2460
2582
  env?: import('nunjucks').Environment;
2461
- defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
2583
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string; loadingTemplate?: string }>;
2462
2584
  status?: number;
2463
2585
  flushImmediately?: boolean;
2586
+ nonce?: string;
2587
+ loadingTemplate?: string | null;
2588
+ layouts?: CascadingLayoutInfo[];
2464
2589
  }
2465
2590
 
2466
2591
  export function createHtmlStream(options: HtmlStreamOptions): import('stream').Readable;
@@ -3155,10 +3280,145 @@ export namespace totp {
3155
3280
  };
3156
3281
  }
3157
3282
 
3283
+ // --- Magic Link / Passwordless Authentication ---
3284
+
3285
+ export interface BuildMagicLinkUrlOptions {
3286
+ baseUrl?: string;
3287
+ path?: string;
3288
+ token: string;
3289
+ }
3290
+
3291
+ export interface FormatMagicLinkHtmlOptions {
3292
+ name?: string;
3293
+ magicLinkUrl: string;
3294
+ expiresMinutes?: number;
3295
+ }
3296
+
3297
+ export interface MagicLinkVerifyMiddlewareOptions {
3298
+ tokenParam?: string;
3299
+ redirectTo?: string;
3300
+ failureRedirect?: string;
3301
+ markEmailVerified?: boolean;
3302
+ jwt?: boolean;
3303
+ }
3158
3304
 
3305
+ export interface MagicLinkRequestOptions {
3306
+ baseUrl?: string;
3307
+ path?: string;
3308
+ tokenTtlMs?: number;
3309
+ hideUserNotFound?: boolean;
3310
+ buildMagicLinkUrl?: (token: string) => string;
3311
+ }
3159
3312
 
3313
+ export interface MagicLinkRequestResult {
3314
+ sent: boolean;
3315
+ userFound: boolean;
3316
+ expiresAt?: Date;
3317
+ token?: string;
3318
+ magicLinkUrl?: string;
3319
+ }
3160
3320
 
3321
+ export interface MagicLinkVerifyResult {
3322
+ ok: true;
3323
+ user: any;
3324
+ userId: string | number;
3325
+ accessToken?: string;
3326
+ refreshToken?: string;
3327
+ }
3161
3328
 
3329
+ export function buildMagicLinkUrl(options: BuildMagicLinkUrlOptions): string;
3330
+ export function formatMagicLinkHtml(options: FormatMagicLinkHtmlOptions): string;
3331
+ export function createMagicLinkVerifyMiddleware(authManager: any, options?: MagicLinkVerifyMiddlewareOptions): (req: any, res: any, next: any) => Promise<any>;
3332
+ export function createMagicLinkRequestHandler(authManager: any, options?: MagicLinkRequestOptions): (req: any, res: any) => Promise<any>;
3162
3333
 
3334
+ export namespace magicLink {
3335
+ export {
3336
+ buildMagicLinkUrl,
3337
+ formatMagicLinkHtml,
3338
+ createMagicLinkVerifyMiddleware,
3339
+ createMagicLinkRequestHandler,
3340
+ };
3341
+ }
3342
+
3343
+ // Active Devices & Session Management
3344
+ export interface DeviceInfo {
3345
+ browser: string;
3346
+ os: string;
3347
+ device: 'desktop' | 'mobile' | 'tablet' | 'bot' | 'unknown';
3348
+ isMobile: boolean;
3349
+ isTablet: boolean;
3350
+ isDesktop: boolean;
3351
+ isBot: boolean;
3352
+ }
3163
3353
 
3354
+ export interface UserSession {
3355
+ id?: number | string;
3356
+ session_id: string;
3357
+ user_id: number | string;
3358
+ ip_address?: string | null;
3359
+ user_agent?: string | null;
3360
+ browser?: string | null;
3361
+ os?: string | null;
3362
+ device?: string | null;
3363
+ last_active_at: Date;
3364
+ created_at?: Date;
3365
+ is_current?: boolean;
3366
+ }
3367
+
3368
+ export interface SessionTracker {
3369
+ track(sessionId: string, userId: string | number, metadata?: { ip?: string; userAgent?: string; browser?: string; os?: string; device?: string }): Promise<UserSession | null>;
3370
+ touch(sessionId: string): Promise<boolean>;
3371
+ get(sessionId: string): Promise<UserSession | null>;
3372
+ list(userId: string | number, currentSessionId?: string | null): Promise<UserSession[]>;
3373
+ isSessionValid(sessionId: string, userId?: string | number | null): Promise<boolean>;
3374
+ revoke(sessionId: string): Promise<boolean>;
3375
+ revokeOthers(userId: string | number, currentSessionId?: string | null): Promise<number>;
3376
+ revokeAll(userId: string | number): Promise<number>;
3377
+ purgeExpired(maxAgeMs: number): Promise<number>;
3378
+ }
3379
+
3380
+ export interface SessionManagerOptions {
3381
+ tracker?: SessionTracker;
3382
+ sessionStore?: any;
3383
+ touchThrottleMs?: number;
3384
+ emitter?: any;
3385
+ }
3386
+
3387
+ export class SessionManager {
3388
+ constructor(options?: SessionManagerOptions);
3389
+ tracker: SessionTracker;
3390
+ sessionStore?: any;
3391
+ touchThrottleMs: number;
3392
+ emitter?: any;
3393
+ track(sessionId: string, userId: string | number, req?: any): Promise<UserSession | null>;
3394
+ touch(sessionId: string): Promise<boolean>;
3395
+ isSessionValid(sessionId: string, userId?: string | number | null): Promise<boolean>;
3396
+ listSessions(userId: string | number, currentSessionId?: string | null): Promise<UserSession[]>;
3397
+ revokeSession(sessionId: string, options?: { userId?: string | number; revokedBy?: string }): Promise<boolean>;
3398
+ revokeOtherSessions(userId: string | number, currentSessionId: string): Promise<number>;
3399
+ revokeAllSessions(userId: string | number): Promise<number>;
3400
+ }
3401
+
3402
+ export function parseUserAgent(userAgent?: string): DeviceInfo;
3403
+ export function getClientIp(req?: any): string;
3404
+ export function createUserSessionsTable(knex: any): Promise<void>;
3405
+ export function dropUserSessionsTable(knex: any): Promise<void>;
3406
+ export function createKnexSessionTracker(db: any, options?: { tableName?: string }): SessionTracker;
3407
+ export function createMemorySessionTracker(): SessionTracker;
3408
+ export function createListSessionsHandler(authManager: any): (req: any, res: any) => Promise<any>;
3409
+ export function createRevokeSessionHandler(authManager: any): (req: any, res: any) => Promise<any>;
3410
+
3411
+ export namespace sessions {
3412
+ export {
3413
+ SessionManager,
3414
+ createUserSessionsTable,
3415
+ dropUserSessionsTable,
3416
+ createKnexSessionTracker,
3417
+ createMemorySessionTracker,
3418
+ getClientIp,
3419
+ parseUserAgent,
3420
+ createListSessionsHandler,
3421
+ createRevokeSessionHandler,
3422
+ };
3423
+ }
3164
3424
 
package/index.js CHANGED
@@ -272,5 +272,9 @@ Object.defineProperties(module.exports, {
272
272
  createDevProfilerMiddleware: { get: () => require('./src/dev').createDevProfilerMiddleware, enumerable: true, configurable: true },
273
273
  generateTypes: { get: () => require('./src/dev/typegen').generateTypes, enumerable: true, configurable: true },
274
274
  typegen: { get: () => require('./src/dev/typegen'), enumerable: true, configurable: true },
275
+
276
+ // Webhooks plugin
277
+ webhooks: { get: () => require('./plugins/webhooks'), enumerable: true, configurable: true },
278
+ webhooksPlugin: { get: () => require('./plugins/webhooks').webhooksPlugin, enumerable: true, configurable: true },
275
279
  });
276
280
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.99",
3
+ "version": "0.0.100",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -126,7 +126,7 @@
126
126
  "nodemailer": "^10.0.10",
127
127
  "nunjucks": "^3.2.4",
128
128
  "sanitize-html": "^2.17.7",
129
- "sharp": "^0.35.4",
129
+ "sharp": "^0.35.5",
130
130
  "swup": "^4.8.3",
131
131
  "yaml": "^2.8.3",
132
132
  "zod": "^3.23.0",
@@ -167,7 +167,7 @@
167
167
  "@types/node": "^20.14.0",
168
168
  "@vitest/coverage-v8": "^4.1.11",
169
169
  "better-sqlite3": "^11.10.0",
170
- "chokidar": "^3.5.3",
170
+ "chokidar": "^4.0.3",
171
171
  "docboot": "^0.3.0",
172
172
  "dotenv": "^16.3.1",
173
173
  "express-rate-limit": "^8.4.1",
@@ -181,9 +181,11 @@
181
181
  "node": ">=20.0.0"
182
182
  },
183
183
  "overrides": {
184
- "basic-ftp": "^5.3.0",
184
+ "basic-ftp": "^6.2.1",
185
+ "chokidar": "^4.0.3",
186
+ "katex": "^0.18.2",
185
187
  "tar": "^7.5.11",
186
- "undici": "^6.24.0",
188
+ "undici": "^6.28.1",
187
189
  "uuid": "^11.1.1",
188
190
  "js-yaml": "^5.4.2",
189
191
  "qs": "^6.16.0"
@@ -692,7 +692,7 @@ const AdminUsersPage = {
692
692
  m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-lg.bg-white.dark:bg-slate-900.text-gray-900.dark:text-slate-100.focus:ring-2.focus:ring-blue-500', {
693
693
  type: 'text',
694
694
  required: true,
695
- placeholder: 'e.g. Ahmet Yılmaz',
695
+ placeholder: 'e.g. Jane Doe',
696
696
  value: s.formName,
697
697
  oninput: (e) => { s.formName = e.target.value; },
698
698
  }),