nextly 0.0.1 → 0.0.2-alpha.0

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 (268) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +122 -0
  3. package/dist/_dts-chunks/collections-handler.d-DjgO74Wt.d.ts +20540 -0
  4. package/dist/_dts-chunks/config.d-DNwsDnjs.d.ts +2589 -0
  5. package/dist/_dts-chunks/define-component.d-BUgTHmt3.d.ts +1149 -0
  6. package/dist/_dts-chunks/image-processor.d-OO1PmMrv.d.ts +335 -0
  7. package/dist/_dts-chunks/index.d-axCAzZ7m.d.ts +17842 -0
  8. package/dist/_dts-chunks/media.d-DjDOZo4B.d.ts +117 -0
  9. package/dist/_dts-chunks/on-error.d-CHIKWNxd.d.ts +38 -0
  10. package/dist/_dts-chunks/storage.d-BUhQ2we_.d.ts +404 -0
  11. package/dist/actions/index.d.ts +239 -0
  12. package/dist/actions/index.mjs +281 -0
  13. package/dist/api/auth-state.d.ts +5 -0
  14. package/dist/api/auth-state.mjs +131 -0
  15. package/dist/api/collections-schema-detail.d.ts +56 -0
  16. package/dist/api/collections-schema-detail.mjs +244 -0
  17. package/dist/api/collections-schema-export.d.ts +56 -0
  18. package/dist/api/collections-schema-export.mjs +129 -0
  19. package/dist/api/collections-schema.d.ts +59 -0
  20. package/dist/api/collections-schema.mjs +207 -0
  21. package/dist/api/components-detail.d.ts +50 -0
  22. package/dist/api/components-detail.mjs +132 -0
  23. package/dist/api/components.d.ts +69 -0
  24. package/dist/api/components.mjs +144 -0
  25. package/dist/api/email-providers-default.d.ts +40 -0
  26. package/dist/api/email-providers-default.mjs +75 -0
  27. package/dist/api/email-providers-detail.d.ts +81 -0
  28. package/dist/api/email-providers-detail.mjs +109 -0
  29. package/dist/api/email-providers-test.d.ts +43 -0
  30. package/dist/api/email-providers-test.mjs +114 -0
  31. package/dist/api/email-providers.d.ts +69 -0
  32. package/dist/api/email-providers.mjs +110 -0
  33. package/dist/api/email-send-template.d.ts +41 -0
  34. package/dist/api/email-send-template.mjs +58 -0
  35. package/dist/api/email-send.d.ts +42 -0
  36. package/dist/api/email-send.mjs +58 -0
  37. package/dist/api/email-templates-detail.d.ts +74 -0
  38. package/dist/api/email-templates-detail.mjs +112 -0
  39. package/dist/api/email-templates-layout.d.ts +55 -0
  40. package/dist/api/email-templates-layout.mjs +92 -0
  41. package/dist/api/email-templates-preview.d.ts +48 -0
  42. package/dist/api/email-templates-preview.mjs +93 -0
  43. package/dist/api/email-templates.d.ts +61 -0
  44. package/dist/api/email-templates.mjs +118 -0
  45. package/dist/api/health.d.ts +68 -0
  46. package/dist/api/health.mjs +67 -0
  47. package/dist/api/index.d.ts +54 -0
  48. package/dist/api/index.mjs +16 -0
  49. package/dist/api/media-bulk.d.ts +74 -0
  50. package/dist/api/media-bulk.mjs +196 -0
  51. package/dist/api/media-folders.d.ts +112 -0
  52. package/dist/api/media-folders.mjs +187 -0
  53. package/dist/api/media-handlers.d.ts +102 -0
  54. package/dist/api/media-handlers.mjs +437 -0
  55. package/dist/api/media.d.ts +117 -0
  56. package/dist/api/media.mjs +242 -0
  57. package/dist/api/singles-detail.d.ts +87 -0
  58. package/dist/api/singles-detail.mjs +170 -0
  59. package/dist/api/singles-schema-detail.d.ts +54 -0
  60. package/dist/api/singles-schema-detail.mjs +182 -0
  61. package/dist/api/singles.d.ts +34 -0
  62. package/dist/api/singles.mjs +94 -0
  63. package/dist/api/storage-upload-url.d.ts +48 -0
  64. package/dist/api/storage-upload-url.mjs +202 -0
  65. package/dist/api/uploads.d.ts +109 -0
  66. package/dist/api/uploads.mjs +359 -0
  67. package/dist/auth/index.d.ts +425 -0
  68. package/dist/auth/index.mjs +199 -0
  69. package/dist/boot-apply-PQSYLDIN.mjs +7 -0
  70. package/dist/chunk-2OALJTK6.mjs +489 -0
  71. package/dist/chunk-2Q2SX2CS.mjs +365 -0
  72. package/dist/chunk-2TFX4ND3.mjs +13 -0
  73. package/dist/chunk-2TWPDSYD.mjs +87 -0
  74. package/dist/chunk-2W3DVD7S.mjs +647 -0
  75. package/dist/chunk-2ZFKXPQM.mjs +88 -0
  76. package/dist/chunk-3FA7FKAV.mjs +832 -0
  77. package/dist/chunk-3NZ2KMBL.mjs +58 -0
  78. package/dist/chunk-4MJLT6PZ.mjs +0 -0
  79. package/dist/chunk-56WO4WX7.mjs +0 -0
  80. package/dist/chunk-5APFUGAD.mjs +89 -0
  81. package/dist/chunk-5HMZ644B.mjs +108 -0
  82. package/dist/chunk-67GXH6PR.mjs +32 -0
  83. package/dist/chunk-6JNEPWRW.mjs +14368 -0
  84. package/dist/chunk-6NFHQIJD.mjs +45 -0
  85. package/dist/chunk-7P6ASYW6.mjs +9 -0
  86. package/dist/chunk-A3WPLSDT.mjs +1364 -0
  87. package/dist/chunk-AGJ6F2T3.mjs +144 -0
  88. package/dist/chunk-AK6Z23OX.mjs +1464 -0
  89. package/dist/chunk-APKKRD2G.mjs +102 -0
  90. package/dist/chunk-B2GV2BWH.mjs +73 -0
  91. package/dist/chunk-D5HQBNUB.mjs +74 -0
  92. package/dist/chunk-DNNG377Z.mjs +204 -0
  93. package/dist/chunk-DP3G27G5.mjs +135 -0
  94. package/dist/chunk-DV6WVX2Q.mjs +0 -0
  95. package/dist/chunk-DXGGXIUZ.mjs +57 -0
  96. package/dist/chunk-EGXBZCGC.mjs +943 -0
  97. package/dist/chunk-ERCNLX3V.mjs +176 -0
  98. package/dist/chunk-FQULBZ53.mjs +850 -0
  99. package/dist/chunk-G2AA4QLC.mjs +262 -0
  100. package/dist/chunk-GDBJ5JCU.mjs +488 -0
  101. package/dist/chunk-GJNSJU4S.mjs +19 -0
  102. package/dist/chunk-GZ6DCQKC.mjs +69 -0
  103. package/dist/chunk-H26B4FYG.mjs +167 -0
  104. package/dist/chunk-I4JMR3UR.mjs +21 -0
  105. package/dist/chunk-INV7QKLG.mjs +508 -0
  106. package/dist/chunk-IUDOC7N7.mjs +46 -0
  107. package/dist/chunk-IZWPRDC3.mjs +206 -0
  108. package/dist/chunk-KIMNCZGV.mjs +15 -0
  109. package/dist/chunk-L6HW2DA7.mjs +15 -0
  110. package/dist/chunk-LAZXX4HR.mjs +100 -0
  111. package/dist/chunk-LDKCUMHK.mjs +95 -0
  112. package/dist/chunk-LRXMECUA.mjs +0 -0
  113. package/dist/chunk-M52VMPGA.mjs +119 -0
  114. package/dist/chunk-MGUWEEI6.mjs +160 -0
  115. package/dist/chunk-NRUWQ5Z7.mjs +419 -0
  116. package/dist/chunk-NSEFNNU4.mjs +25360 -0
  117. package/dist/chunk-NTHVDFGO.mjs +138 -0
  118. package/dist/chunk-O3QHXMOX.mjs +3166 -0
  119. package/dist/chunk-P7NH2OSC.mjs +2605 -0
  120. package/dist/chunk-PKMABBB5.mjs +184 -0
  121. package/dist/chunk-PWS6XGJK.mjs +76 -0
  122. package/dist/chunk-R6JJQHFC.mjs +20 -0
  123. package/dist/chunk-RJLLGGPG.mjs +0 -0
  124. package/dist/chunk-SBACDPNX.mjs +689 -0
  125. package/dist/chunk-TO5AFLVQ.mjs +124 -0
  126. package/dist/chunk-TS7GHTG2.mjs +5436 -0
  127. package/dist/chunk-UJ2IMJ4W.mjs +133 -0
  128. package/dist/chunk-UOP63Q54.mjs +102 -0
  129. package/dist/chunk-UUOFWCM6.mjs +78 -0
  130. package/dist/chunk-V4EQTOA4.mjs +893 -0
  131. package/dist/chunk-VJ66NCL4.mjs +193 -0
  132. package/dist/chunk-VQJQHVEV.mjs +29 -0
  133. package/dist/chunk-VTJADRO3.mjs +141 -0
  134. package/dist/chunk-VWF3JO32.mjs +0 -0
  135. package/dist/chunk-W4MGXIRR.mjs +27 -0
  136. package/dist/chunk-W5KKPZT5.mjs +1204 -0
  137. package/dist/chunk-WD34YQ6T.mjs +381 -0
  138. package/dist/chunk-WZBYMYVW.mjs +14 -0
  139. package/dist/chunk-X23WKS3Z.mjs +50 -0
  140. package/dist/chunk-X7TXCYYN.mjs +6496 -0
  141. package/dist/chunk-XGI4EMS3.mjs +140 -0
  142. package/dist/chunk-XZKLBMN6.mjs +1153 -0
  143. package/dist/chunk-YB7INWPY.mjs +0 -0
  144. package/dist/chunk-YV4Y7SDL.mjs +83 -0
  145. package/dist/chunk-YZNBLFIW.mjs +1688 -0
  146. package/dist/chunk-YZZCTONM.mjs +263 -0
  147. package/dist/chunk-ZE6A3FYH.mjs +289 -0
  148. package/dist/cli/nextly.mjs +68 -0
  149. package/dist/cli/utils/index.d.ts +449 -0
  150. package/dist/cli/utils/index.mjs +49 -0
  151. package/dist/component-schema-service-5577KVW6.mjs +11 -0
  152. package/dist/config-loader-23YEMC3Z.mjs +23 -0
  153. package/dist/config.d.ts +44 -0
  154. package/dist/config.mjs +109 -0
  155. package/dist/container-ORGFGYSZ.mjs +9 -0
  156. package/dist/database/index.d.ts +12 -0
  157. package/dist/database/index.mjs +40 -0
  158. package/dist/database/seeders/index.d.ts +93 -0
  159. package/dist/database/seeders/index.mjs +47 -0
  160. package/dist/db-sync-demote-LJGKLB3S.mjs +117 -0
  161. package/dist/db-sync-promote-B26VSYQF.mjs +113 -0
  162. package/dist/dev-reload-broadcaster-B73IQ53V.mjs +25 -0
  163. package/dist/dist-M2NOU37V.mjs +19 -0
  164. package/dist/drizzle-kit-lazy-D2M2PXR2.mjs +13 -0
  165. package/dist/dynamic-collection-schema-service-IEXTPIZ7.mjs +8 -0
  166. package/dist/errors/index.d.ts +159 -0
  167. package/dist/errors/index.mjs +10 -0
  168. package/dist/factory-IWMBKUJM.mjs +15 -0
  169. package/dist/first-run-QIVKWJIF.mjs +63 -0
  170. package/dist/fresh-push-NR67DC3R.mjs +8 -0
  171. package/dist/index.d.ts +4175 -0
  172. package/dist/index.mjs +1336 -0
  173. package/dist/local-plugin-PTET4NAT.mjs +7 -0
  174. package/dist/logger-NU46DXNY.mjs +15 -0
  175. package/dist/logger-YE4TC7ZN.mjs +9 -0
  176. package/dist/migration-journal-EP532Y4L.mjs +139 -0
  177. package/dist/migrations/mysql/0000_eager_sentry.sql +174 -0
  178. package/dist/migrations/mysql/0001_soft_giant_girl.sql +27 -0
  179. package/dist/migrations/mysql/0002_media_table.sql +24 -0
  180. package/dist/migrations/mysql/0003_dynamic_singles.sql +37 -0
  181. package/dist/migrations/mysql/0004_dynamic_components.sql +35 -0
  182. package/dist/migrations/mysql/0005_user_management_tables.sql +92 -0
  183. package/dist/migrations/mysql/0006_api_keys.sql +36 -0
  184. package/dist/migrations/mysql/0007_general_settings.sql +20 -0
  185. package/dist/migrations/mysql/0008_site_settings_logo_url.sql +9 -0
  186. package/dist/migrations/mysql/0009_activity_log.sql +30 -0
  187. package/dist/migrations/mysql/0010_site_settings_sidebar.sql +13 -0
  188. package/dist/migrations/mysql/0011_missing_tables_and_columns.sql +54 -0
  189. package/dist/migrations/mysql/0012_image_sizes_and_focal_point.sql +30 -0
  190. package/dist/migrations/mysql/0012_media_folders.sql +43 -0
  191. package/dist/migrations/mysql/0013_user_brute_force_protection.sql +31 -0
  192. package/dist/migrations/mysql/0014_email_template_attachments.sql +12 -0
  193. package/dist/migrations/mysql/0015_media_uploaded_by_nullable.sql +15 -0
  194. package/dist/migrations/mysql/20260429_000000_000_initial_journal.sql +22 -0
  195. package/dist/migrations/mysql/20260501_000000_journal_batch.sql +17 -0
  196. package/dist/migrations/mysql/20260501_000001_audit_log.sql +24 -0
  197. package/dist/migrations/mysql/20260504_000000_nextly_meta.sql +21 -0
  198. package/dist/migrations/mysql/meta/0000_snapshot.json +1005 -0
  199. package/dist/migrations/mysql/meta/0001_snapshot.json +1099 -0
  200. package/dist/migrations/mysql/meta/_journal.json +41 -0
  201. package/dist/migrations/postgresql/0000_misty_king_bedlam.sql +169 -0
  202. package/dist/migrations/postgresql/0001_perpetual_captain_marvel.sql +8 -0
  203. package/dist/migrations/postgresql/0002_sad_spectrum.sql +16 -0
  204. package/dist/migrations/postgresql/0003_hesitant_ultron.sql +17 -0
  205. package/dist/migrations/postgresql/0004_media_table.sql +24 -0
  206. package/dist/migrations/postgresql/0005_media_folders.sql +36 -0
  207. package/dist/migrations/postgresql/0006_dynamic_collections_update.sql +50 -0
  208. package/dist/migrations/postgresql/0007_dynamic_singles.sql +38 -0
  209. package/dist/migrations/postgresql/0008_dynamic_components.sql +37 -0
  210. package/dist/migrations/postgresql/0009_user_management_tables.sql +95 -0
  211. package/dist/migrations/postgresql/0010_api_keys.sql +34 -0
  212. package/dist/migrations/postgresql/0011_general_settings.sql +20 -0
  213. package/dist/migrations/postgresql/0012_site_settings_logo_url.sql +9 -0
  214. package/dist/migrations/postgresql/0013_activity_log.sql +29 -0
  215. package/dist/migrations/postgresql/0014_image_sizes_and_focal_point.sql +33 -0
  216. package/dist/migrations/postgresql/0014_site_settings_sidebar.sql +13 -0
  217. package/dist/migrations/postgresql/0015_user_brute_force_protection.sql +29 -0
  218. package/dist/migrations/postgresql/0016_email_template_attachments.sql +12 -0
  219. package/dist/migrations/postgresql/0017_media_uploaded_by_nullable.sql +15 -0
  220. package/dist/migrations/postgresql/20260429_000000_000_initial_journal.sql +24 -0
  221. package/dist/migrations/postgresql/20260501_000000_journal_batch.sql +17 -0
  222. package/dist/migrations/postgresql/20260501_000001_audit_log.sql +24 -0
  223. package/dist/migrations/postgresql/20260504_000000_nextly_meta.sql +22 -0
  224. package/dist/migrations/postgresql/meta/0000_snapshot.json +1286 -0
  225. package/dist/migrations/postgresql/meta/0001_snapshot.json +1407 -0
  226. package/dist/migrations/postgresql/meta/0002_snapshot.json +1552 -0
  227. package/dist/migrations/postgresql/meta/0003_snapshot.json +1695 -0
  228. package/dist/migrations/postgresql/meta/0010_snapshot.json +2345 -0
  229. package/dist/migrations/postgresql/meta/_journal.json +90 -0
  230. package/dist/migrations/sqlite/0000_api_keys.sql +34 -0
  231. package/dist/migrations/sqlite/0001_general_settings.sql +20 -0
  232. package/dist/migrations/sqlite/0002_site_settings_logo_url.sql +9 -0
  233. package/dist/migrations/sqlite/0003_activity_log.sql +29 -0
  234. package/dist/migrations/sqlite/0004_image_sizes_and_focal_point.sql +29 -0
  235. package/dist/migrations/sqlite/0004_site_settings_sidebar.sql +11 -0
  236. package/dist/migrations/sqlite/0005_user_brute_force_protection.sql +29 -0
  237. package/dist/migrations/sqlite/0006_email_template_attachments.sql +12 -0
  238. package/dist/migrations/sqlite/0007_media_uploaded_by_nullable.sql +111 -0
  239. package/dist/migrations/sqlite/20260429_000000_000_initial_journal.sql +24 -0
  240. package/dist/migrations/sqlite/20260501_000000_journal_batch.sql +19 -0
  241. package/dist/migrations/sqlite/20260501_000001_audit_log.sql +24 -0
  242. package/dist/migrations/sqlite/20260504_000000_nextly_meta.sql +21 -0
  243. package/dist/migrations/sqlite/20260505_000000_user_management_tables.sql +77 -0
  244. package/dist/next.d.ts +57 -0
  245. package/dist/next.mjs +55 -0
  246. package/dist/observability/index.d.ts +87 -0
  247. package/dist/observability/index.mjs +57 -0
  248. package/dist/permissions-3DZZQZMI.mjs +39 -0
  249. package/dist/pipeline-YOML7SWF.mjs +29 -0
  250. package/dist/preview-ZZTR3QGS.mjs +9 -0
  251. package/dist/program-PW6UB2ZC.mjs +5934 -0
  252. package/dist/reconcile-single-tables-7ENVXJGB.mjs +7 -0
  253. package/dist/register-SF6E6FVU.mjs +49 -0
  254. package/dist/reload-config-HWQ4G5MM.mjs +23 -0
  255. package/dist/resolve-single-table-name-JSOMUB3R.mjs +7 -0
  256. package/dist/routeHandler-UNMMJIBM.mjs +77 -0
  257. package/dist/runtime-schema-generator-NRA6A6Z6.mjs +8 -0
  258. package/dist/runtime.d.ts +120 -0
  259. package/dist/runtime.mjs +73 -0
  260. package/dist/schema-hash-FMMG6VPJ.mjs +13 -0
  261. package/dist/schema-registry-EQ36FZDP.mjs +7 -0
  262. package/dist/scripts/load-env.mjs +42 -0
  263. package/dist/storage/index.d.ts +566 -0
  264. package/dist/storage/index.mjs +45 -0
  265. package/dist/super-admin-G5ZK5F4T.mjs +39 -0
  266. package/dist/system-table-service-WGSRVEGT.mjs +17 -0
  267. package/dist/users-7KELGRYJ.mjs +38 -0
  268. package/package.json +308 -9
@@ -0,0 +1,262 @@
1
+ // src/storage/adapters/local-adapter.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+
5
+ // src/storage/adapters/base-adapter.ts
6
+ var BaseStorageAdapter = class {
7
+ /**
8
+ * Get adapter info including capabilities.
9
+ *
10
+ * Default implementation that auto-detects capabilities by checking
11
+ * if getSignedUrl and getPresignedUploadUrl methods are implemented.
12
+ * Override in subclasses for more accurate capability reporting.
13
+ *
14
+ * @returns Adapter info with type, name, and capability flags
15
+ */
16
+ getInfo() {
17
+ const hasSignedUrls = "getSignedUrl" in this && typeof this.getSignedUrl === "function";
18
+ const hasClientUploads = "getPresignedUploadUrl" in this && typeof this.getPresignedUploadUrl === "function";
19
+ return {
20
+ type: this.getType(),
21
+ name: this.constructor.name,
22
+ supportsSignedUrls: hasSignedUrls,
23
+ supportsClientUploads: hasClientUploads
24
+ };
25
+ }
26
+ /**
27
+ * Sanitize filename to prevent directory traversal and storage issues.
28
+ *
29
+ * Security measures:
30
+ * - Remove path separators (/, \)
31
+ * - Keep only basename (no directories)
32
+ * - Replace problematic characters with hyphens
33
+ * - Preserve alphanumeric, dots, underscores, hyphens
34
+ *
35
+ * @param filename - Original filename to sanitize
36
+ * @returns Sanitized filename safe for storage
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * this.sanitizeFilename('../../../etc/passwd') // 'passwd'
41
+ * this.sanitizeFilename('my file (1).jpg') // 'my-file--1-.jpg'
42
+ * this.sanitizeFilename('photo.jpg') // 'photo.jpg'
43
+ * ```
44
+ */
45
+ sanitizeFilename(filename) {
46
+ const basename = filename.split(/[/\\]/).pop() || filename;
47
+ return basename.replace(/[^a-zA-Z0-9._-]/g, "-");
48
+ }
49
+ /**
50
+ * Generate a unique storage key with date-based prefix.
51
+ *
52
+ * Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}
53
+ * This provides:
54
+ * - Unique keys via UUID to prevent collisions
55
+ * - Date-based organization for easier management
56
+ * - Readable filenames for debugging
57
+ *
58
+ * @param filename - Original filename (will be sanitized)
59
+ * @param folder - Optional folder/prefix for organizing uploads
60
+ * @returns Generated storage key
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * this.generateKey('photo.jpg')
65
+ * // 'uploads/2026/01/abc-123-...-photo.jpg'
66
+ *
67
+ * this.generateKey('doc.pdf', 'documents')
68
+ * // 'documents/2026/01/abc-123-...-doc.pdf'
69
+ * ```
70
+ */
71
+ generateKey(filename, folder) {
72
+ const sanitized = this.sanitizeFilename(filename);
73
+ const uuid = crypto.randomUUID();
74
+ const date = /* @__PURE__ */ new Date();
75
+ const year = date.getFullYear();
76
+ const month = String(date.getMonth() + 1).padStart(2, "0");
77
+ const prefix = folder ? `${folder}/${year}/${month}` : `uploads/${year}/${month}`;
78
+ return `${prefix}/${uuid}-${sanitized}`;
79
+ }
80
+ };
81
+
82
+ // src/storage/adapters/local-adapter.ts
83
+ var gitignoreUpdated = false;
84
+ var LocalStorageAdapter = class extends BaseStorageAdapter {
85
+ basePath;
86
+ baseUrl;
87
+ constructor(config) {
88
+ super();
89
+ this.basePath = path.resolve(config.basePath);
90
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
91
+ }
92
+ /**
93
+ * Upload file to local disk.
94
+ * Creates directories as needed and writes the file buffer.
95
+ */
96
+ async upload(buffer, options) {
97
+ const key = this.generateKey(options.filename, options.folder);
98
+ const fullPath = this.resolveAndValidate(key);
99
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
100
+ await fs.writeFile(fullPath, buffer);
101
+ await this.ensureGitignore();
102
+ return {
103
+ url: this.getPublicUrl(key),
104
+ path: key
105
+ };
106
+ }
107
+ /**
108
+ * Delete file from local disk.
109
+ * Silently succeeds if the file doesn't exist.
110
+ */
111
+ async delete(filePath) {
112
+ let fullPath;
113
+ try {
114
+ fullPath = this.resolveAndValidate(filePath);
115
+ } catch {
116
+ return;
117
+ }
118
+ try {
119
+ await fs.unlink(fullPath);
120
+ } catch (err) {
121
+ if (err.code !== "ENOENT") {
122
+ throw err;
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Bulk delete files from local disk.
128
+ * Uses parallel unlinks with Promise.allSettled for best performance.
129
+ */
130
+ async bulkDelete(filePaths) {
131
+ const results = await Promise.allSettled(
132
+ filePaths.map(async (filePath) => {
133
+ await this.delete(filePath);
134
+ return filePath;
135
+ })
136
+ );
137
+ const successful = [];
138
+ const failed = [];
139
+ results.forEach((result, index) => {
140
+ if (result.status === "fulfilled") {
141
+ successful.push(filePaths[index]);
142
+ } else {
143
+ failed.push({
144
+ filePath: filePaths[index],
145
+ error: result.reason?.message || "Unknown error"
146
+ });
147
+ }
148
+ });
149
+ return { successful, failed };
150
+ }
151
+ /**
152
+ * Check if file exists on local disk.
153
+ */
154
+ async exists(filePath) {
155
+ try {
156
+ const fullPath = this.resolveAndValidate(filePath);
157
+ await fs.access(fullPath);
158
+ return true;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ /**
164
+ * Get public URL for a file.
165
+ * Returns baseUrl + relative path for Next.js static file serving.
166
+ */
167
+ getPublicUrl(filePath) {
168
+ const cleanPath = filePath.replace(/^\/+/, "");
169
+ return `${this.baseUrl}/${cleanPath}`;
170
+ }
171
+ /**
172
+ * Get storage type identifier.
173
+ */
174
+ getType() {
175
+ return "local";
176
+ }
177
+ /**
178
+ * Read file contents from local disk.
179
+ * Returns the file buffer, or null if file not found.
180
+ */
181
+ async read(filePath) {
182
+ try {
183
+ const fullPath = this.resolveAndValidate(filePath);
184
+ return await fs.readFile(fullPath);
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ // ============================================================
190
+ // Private Helpers
191
+ // ============================================================
192
+ /**
193
+ * Resolve a relative file path to an absolute path within basePath.
194
+ * Throws if the resolved path would escape basePath (path traversal attack).
195
+ */
196
+ resolveAndValidate(filePath) {
197
+ const sanitized = filePath.replace(/^[/\\]+/, "").replace(/\.\.[/\\]/g, "");
198
+ const fullPath = path.resolve(this.basePath, sanitized);
199
+ if (!fullPath.startsWith(this.basePath)) {
200
+ throw new Error(
201
+ `Path traversal detected: ${filePath} resolves outside of storage directory`
202
+ );
203
+ }
204
+ return fullPath;
205
+ }
206
+ /**
207
+ * Auto-add the uploads directory to .gitignore on first upload.
208
+ * Prevents accidentally committing uploaded files to git.
209
+ */
210
+ async ensureGitignore() {
211
+ if (gitignoreUpdated) return;
212
+ gitignoreUpdated = true;
213
+ try {
214
+ const projectRoot = path.resolve(this.basePath, "..", "..");
215
+ const gitignorePath = path.join(projectRoot, ".gitignore");
216
+ let content = "";
217
+ try {
218
+ content = await fs.readFile(gitignorePath, "utf-8");
219
+ } catch {
220
+ }
221
+ const uploadsDirRelative = path.relative(projectRoot, this.basePath);
222
+ const ignorePattern = uploadsDirRelative + "/";
223
+ if (!content.includes(ignorePattern)) {
224
+ const newEntry = `
225
+ # Nextly local uploads (auto-added)
226
+ ${ignorePattern}
227
+ `;
228
+ await fs.writeFile(gitignorePath, content + newEntry, "utf-8");
229
+ }
230
+ } catch {
231
+ }
232
+ }
233
+ };
234
+
235
+ // src/storage/adapters/local-plugin.ts
236
+ function localStorage(config) {
237
+ if (config.enabled === false) {
238
+ return {
239
+ name: "local-storage",
240
+ type: "local",
241
+ collections: {},
242
+ adapter: null
243
+ };
244
+ }
245
+ const adapter = new LocalStorageAdapter({
246
+ basePath: config.basePath ?? "./public/uploads",
247
+ baseUrl: config.baseUrl ?? "/uploads"
248
+ });
249
+ return {
250
+ name: "local-storage",
251
+ type: "local",
252
+ collections: config.collections,
253
+ adapter
254
+ // Local storage doesn't support presigned URLs or signed downloads
255
+ };
256
+ }
257
+
258
+ export {
259
+ BaseStorageAdapter,
260
+ LocalStorageAdapter,
261
+ localStorage
262
+ };