@byline/cli 0.1.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 (251) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +23 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +72 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/doctor.d.ts +2 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +36 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.d.ts +16 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +76 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/context.d.ts +38 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +37 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lib/pg-url.d.ts +11 -0
  24. package/dist/lib/pg-url.d.ts.map +1 -0
  25. package/dist/lib/pg-url.js +22 -0
  26. package/dist/lib/pg-url.js.map +1 -0
  27. package/dist/manifest/deps.d.ts +29 -0
  28. package/dist/manifest/deps.d.ts.map +1 -0
  29. package/dist/manifest/deps.js +97 -0
  30. package/dist/manifest/deps.js.map +1 -0
  31. package/dist/manifest/env.d.ts +18 -0
  32. package/dist/manifest/env.d.ts.map +1 -0
  33. package/dist/manifest/env.js +38 -0
  34. package/dist/manifest/env.js.map +1 -0
  35. package/dist/phases/db-init.d.ts +3 -0
  36. package/dist/phases/db-init.d.ts.map +1 -0
  37. package/dist/phases/db-init.js +163 -0
  38. package/dist/phases/db-init.js.map +1 -0
  39. package/dist/phases/db.d.ts +11 -0
  40. package/dist/phases/db.d.ts.map +1 -0
  41. package/dist/phases/db.js +93 -0
  42. package/dist/phases/db.js.map +1 -0
  43. package/dist/phases/deps.d.ts +3 -0
  44. package/dist/phases/deps.d.ts.map +1 -0
  45. package/dist/phases/deps.js +115 -0
  46. package/dist/phases/deps.js.map +1 -0
  47. package/dist/phases/env.d.ts +3 -0
  48. package/dist/phases/env.d.ts.map +1 -0
  49. package/dist/phases/env.js +172 -0
  50. package/dist/phases/env.js.map +1 -0
  51. package/dist/phases/host.d.ts +3 -0
  52. package/dist/phases/host.d.ts.map +1 -0
  53. package/dist/phases/host.js +99 -0
  54. package/dist/phases/host.js.map +1 -0
  55. package/dist/phases/index.d.ts +7 -0
  56. package/dist/phases/index.d.ts.map +1 -0
  57. package/dist/phases/index.js +40 -0
  58. package/dist/phases/index.js.map +1 -0
  59. package/dist/phases/preflight.d.ts +4 -0
  60. package/dist/phases/preflight.d.ts.map +1 -0
  61. package/dist/phases/preflight.js +81 -0
  62. package/dist/phases/preflight.js.map +1 -0
  63. package/dist/phases/routes.d.ts +3 -0
  64. package/dist/phases/routes.d.ts.map +1 -0
  65. package/dist/phases/routes.js +145 -0
  66. package/dist/phases/routes.js.map +1 -0
  67. package/dist/phases/scaffold.d.ts +3 -0
  68. package/dist/phases/scaffold.d.ts.map +1 -0
  69. package/dist/phases/scaffold.js +113 -0
  70. package/dist/phases/scaffold.js.map +1 -0
  71. package/dist/phases/stub.d.ts +3 -0
  72. package/dist/phases/stub.d.ts.map +1 -0
  73. package/dist/phases/stub.js +25 -0
  74. package/dist/phases/stub.js.map +1 -0
  75. package/dist/phases/ui.d.ts +3 -0
  76. package/dist/phases/ui.d.ts.map +1 -0
  77. package/dist/phases/ui.js +93 -0
  78. package/dist/phases/ui.js.map +1 -0
  79. package/dist/phases/wire/index.d.ts +3 -0
  80. package/dist/phases/wire/index.d.ts.map +1 -0
  81. package/dist/phases/wire/index.js +67 -0
  82. package/dist/phases/wire/index.js.map +1 -0
  83. package/dist/phases/wire/root-tsx.d.ts +3 -0
  84. package/dist/phases/wire/root-tsx.d.ts.map +1 -0
  85. package/dist/phases/wire/root-tsx.js +57 -0
  86. package/dist/phases/wire/root-tsx.js.map +1 -0
  87. package/dist/phases/wire/server-ts.d.ts +3 -0
  88. package/dist/phases/wire/server-ts.d.ts.map +1 -0
  89. package/dist/phases/wire/server-ts.js +54 -0
  90. package/dist/phases/wire/server-ts.js.map +1 -0
  91. package/dist/phases/wire/shared.d.ts +34 -0
  92. package/dist/phases/wire/shared.d.ts.map +1 -0
  93. package/dist/phases/wire/shared.js +2 -0
  94. package/dist/phases/wire/shared.js.map +1 -0
  95. package/dist/phases/wire/start-ts.d.ts +3 -0
  96. package/dist/phases/wire/start-ts.d.ts.map +1 -0
  97. package/dist/phases/wire/start-ts.js +149 -0
  98. package/dist/phases/wire/start-ts.js.map +1 -0
  99. package/dist/phases/wire/tsconfig.d.ts +3 -0
  100. package/dist/phases/wire/tsconfig.d.ts.map +1 -0
  101. package/dist/phases/wire/tsconfig.js +105 -0
  102. package/dist/phases/wire/tsconfig.js.map +1 -0
  103. package/dist/phases/wire/vite-config.d.ts +3 -0
  104. package/dist/phases/wire/vite-config.d.ts.map +1 -0
  105. package/dist/phases/wire/vite-config.js +46 -0
  106. package/dist/phases/wire/vite-config.js.map +1 -0
  107. package/dist/prompts.d.ts +34 -0
  108. package/dist/prompts.d.ts.map +1 -0
  109. package/dist/prompts.js +49 -0
  110. package/dist/prompts.js.map +1 -0
  111. package/dist/runner.d.ts +5 -0
  112. package/dist/runner.d.ts.map +1 -0
  113. package/dist/runner.js +91 -0
  114. package/dist/runner.js.map +1 -0
  115. package/dist/state.d.ts +18 -0
  116. package/dist/state.d.ts.map +1 -0
  117. package/dist/state.js +68 -0
  118. package/dist/state.js.map +1 -0
  119. package/dist/templates/byline/admin.config.ts +41 -0
  120. package/dist/templates/byline/i18n.ts +47 -0
  121. package/dist/templates/byline/routes.ts +28 -0
  122. package/dist/templates/byline/seed.ts +19 -0
  123. package/dist/templates/byline/seeds/admin.ts +62 -0
  124. package/dist/templates/byline/server.config.ts +92 -0
  125. package/dist/templates/byline-examples/admin.config.ts +74 -0
  126. package/dist/templates/byline-examples/blocks/photo-block.ts +59 -0
  127. package/dist/templates/byline-examples/blocks/richtext-block.ts +35 -0
  128. package/dist/templates/byline-examples/collections/doc-example-flat-locale-all.ts +373 -0
  129. package/dist/templates/byline-examples/collections/doc-example-flat-locale-en.ts +283 -0
  130. package/dist/templates/byline-examples/collections/doc-example-tree-locale-all.ts +278 -0
  131. package/dist/templates/byline-examples/collections/doc-example-tree-locale-en.ts +205 -0
  132. package/dist/templates/byline-examples/collections/docs/admin.tsx +204 -0
  133. package/dist/templates/byline-examples/collections/docs/components/.gitkeep +0 -0
  134. package/dist/templates/byline-examples/collections/docs/components/feature-formatter.tsx +10 -0
  135. package/dist/templates/byline-examples/collections/docs/hooks/.gitkeep +0 -0
  136. package/dist/templates/byline-examples/collections/docs/index.ts +10 -0
  137. package/dist/templates/byline-examples/collections/docs/schema.ts +209 -0
  138. package/dist/templates/byline-examples/collections/docs-categories/admin.tsx +78 -0
  139. package/dist/templates/byline-examples/collections/docs-categories/components/.gitkeep +0 -0
  140. package/dist/templates/byline-examples/collections/docs-categories/hooks/.gitkeep +0 -0
  141. package/dist/templates/byline-examples/collections/docs-categories/index.ts +10 -0
  142. package/dist/templates/byline-examples/collections/docs-categories/schema.ts +33 -0
  143. package/dist/templates/byline-examples/collections/media/admin.tsx +188 -0
  144. package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +330 -0
  145. package/dist/templates/byline-examples/collections/media/components/media-thumbnail.tsx +63 -0
  146. package/dist/templates/byline-examples/collections/media/hooks/.gitkeep +0 -0
  147. package/dist/templates/byline-examples/collections/media/index.ts +10 -0
  148. package/dist/templates/byline-examples/collections/media/schema.ts +157 -0
  149. package/dist/templates/byline-examples/collections/news/admin.tsx +192 -0
  150. package/dist/templates/byline-examples/collections/news/components/.gitkeep +0 -0
  151. package/dist/templates/byline-examples/collections/news/hooks/.gitkeep +0 -0
  152. package/dist/templates/byline-examples/collections/news/index.ts +10 -0
  153. package/dist/templates/byline-examples/collections/news/schema.ts +91 -0
  154. package/dist/templates/byline-examples/collections/news-categories/admin.tsx +78 -0
  155. package/dist/templates/byline-examples/collections/news-categories/components/.gitkeep +0 -0
  156. package/dist/templates/byline-examples/collections/news-categories/hooks/.gitkeep +0 -0
  157. package/dist/templates/byline-examples/collections/news-categories/index.ts +10 -0
  158. package/dist/templates/byline-examples/collections/news-categories/schema.ts +33 -0
  159. package/dist/templates/byline-examples/collections/pages/admin.tsx +183 -0
  160. package/dist/templates/byline-examples/collections/pages/components/.gitkeep +0 -0
  161. package/dist/templates/byline-examples/collections/pages/hooks/.gitkeep +0 -0
  162. package/dist/templates/byline-examples/collections/pages/index.ts +10 -0
  163. package/dist/templates/byline-examples/collections/pages/schema.ts +96 -0
  164. package/dist/templates/byline-examples/components/length-indicator.tsx +138 -0
  165. package/dist/templates/byline-examples/components/pill.tsx +38 -0
  166. package/dist/templates/byline-examples/components/summary-length.tsx +39 -0
  167. package/dist/templates/byline-examples/fields/available-languages-field.ts +90 -0
  168. package/dist/templates/byline-examples/fields/lexical-richtext-compact.ts +88 -0
  169. package/dist/templates/byline-examples/i18n.ts +47 -0
  170. package/dist/templates/byline-examples/routes.ts +28 -0
  171. package/dist/templates/byline-examples/scripts/regenerate-media.ts +275 -0
  172. package/dist/templates/byline-examples/seed.ts +25 -0
  173. package/dist/templates/byline-examples/seeds/admin.ts +62 -0
  174. package/dist/templates/byline-examples/seeds/doc-categories.ts +71 -0
  175. package/dist/templates/byline-examples/seeds/docs.ts +293 -0
  176. package/dist/templates/byline-examples/seeds/news-categories.ts +71 -0
  177. package/dist/templates/byline-examples/server.config.ts +179 -0
  178. package/dist/templates/host/vite.config.ts +41 -0
  179. package/dist/templates/migrations/0000_condemned_kronos.sql +324 -0
  180. package/dist/templates/migrations/0001_sudden_phantom_reporter.sql +1 -0
  181. package/dist/templates/migrations/meta/0000_snapshot.json +2793 -0
  182. package/dist/templates/migrations/meta/0001_snapshot.json +2799 -0
  183. package/dist/templates/migrations/meta/_journal.json +20 -0
  184. package/dist/templates/routes/(byline)/admin/account/index.tsx +11 -0
  185. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/api.tsx +16 -0
  186. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/history.tsx +19 -0
  187. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/index.tsx +16 -0
  188. package/dist/templates/routes/(byline)/admin/collections/$collection/create.tsx +11 -0
  189. package/dist/templates/routes/(byline)/admin/collections/$collection/index.tsx +11 -0
  190. package/dist/templates/routes/(byline)/admin/index.tsx +11 -0
  191. package/dist/templates/routes/(byline)/admin/permissions/index.tsx +11 -0
  192. package/dist/templates/routes/(byline)/admin/roles/$id/index.tsx +11 -0
  193. package/dist/templates/routes/(byline)/admin/roles/index.tsx +11 -0
  194. package/dist/templates/routes/(byline)/admin/route.tsx +11 -0
  195. package/dist/templates/routes/(byline)/admin/users/$id/index.tsx +11 -0
  196. package/dist/templates/routes/(byline)/admin/users/index.tsx +11 -0
  197. package/dist/templates/routes/(byline)/sign-in.tsx +11 -0
  198. package/dist/templates/ui-byline/blocks/photo-block/index.tsx +80 -0
  199. package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +46 -0
  200. package/dist/templates/ui-byline/components/admonition/index.tsx +40 -0
  201. package/dist/templates/ui-byline/components/code/code-serializer.tsx +20 -0
  202. package/dist/templates/ui-byline/components/code/code.tsx +50 -0
  203. package/dist/templates/ui-byline/components/code/index.module.scss +137 -0
  204. package/dist/templates/ui-byline/components/code/index.ts +2 -0
  205. package/dist/templates/ui-byline/components/code/types.ts +5 -0
  206. package/dist/templates/ui-byline/components/code/utils.ts +20 -0
  207. package/dist/templates/ui-byline/components/heading-anchor/heading-anchor.tsx +69 -0
  208. package/dist/templates/ui-byline/components/heading-anchor/index.ts +1 -0
  209. package/dist/templates/ui-byline/components/heading-anchor/utils.ts +15 -0
  210. package/dist/templates/ui-byline/components/inline-image/index.tsx +109 -0
  211. package/dist/templates/ui-byline/components/layout/index.tsx +63 -0
  212. package/dist/templates/ui-byline/components/link/lang-link.tsx +70 -0
  213. package/dist/templates/ui-byline/components/link/link-field.tsx +298 -0
  214. package/dist/templates/ui-byline/components/link/link-lexical.tsx +191 -0
  215. package/dist/templates/ui-byline/components/list/index.ts +2 -0
  216. package/dist/templates/ui-byline/components/list/list-item.tsx +32 -0
  217. package/dist/templates/ui-byline/components/list/list.tsx +17 -0
  218. package/dist/templates/ui-byline/components/responsive-image/index.tsx +205 -0
  219. package/dist/templates/ui-byline/components/richtext-lexical/index.tsx +31 -0
  220. package/dist/templates/ui-byline/components/richtext-lexical/serialize/index.tsx +249 -0
  221. package/dist/templates/ui-byline/components/richtext-lexical/serialize/richtext-node-formats.ts +66 -0
  222. package/dist/templates/ui-byline/components/richtext-lexical/serialize/types.ts +48 -0
  223. package/dist/templates/ui-byline/components/richtext-lexical/serialize/utils.ts +15 -0
  224. package/dist/templates/ui-byline/components/table-cell/index.tsx +36 -0
  225. package/dist/templates/ui-byline/components/vimeo/index.tsx +21 -0
  226. package/dist/templates/ui-byline/components/youtube/index.tsx +22 -0
  227. package/dist/templates/ui-byline/render-blocks.tsx +71 -0
  228. package/dist/templates/ui-byline/types/i18n.ts +14 -0
  229. package/dist/templates/ui-byline/utils/image-sources.ts +102 -0
  230. package/dist/templates/ui-byline/utils/to-kebab-case.ts +5 -0
  231. package/dist/types.d.ts +54 -0
  232. package/dist/types.d.ts.map +1 -0
  233. package/dist/types.js +2 -0
  234. package/dist/types.js.map +1 -0
  235. package/dist/ui/diff.d.ts +4 -0
  236. package/dist/ui/diff.d.ts.map +1 -0
  237. package/dist/ui/diff.js +23 -0
  238. package/dist/ui/diff.js.map +1 -0
  239. package/dist/ui/grid.d.ts +7 -0
  240. package/dist/ui/grid.d.ts.map +1 -0
  241. package/dist/ui/grid.js +24 -0
  242. package/dist/ui/grid.js.map +1 -0
  243. package/dist/ui/logger.d.ts +14 -0
  244. package/dist/ui/logger.d.ts.map +1 -0
  245. package/dist/ui/logger.js +30 -0
  246. package/dist/ui/logger.js.map +1 -0
  247. package/dist/ui/snippet.d.ts +2 -0
  248. package/dist/ui/snippet.d.ts.map +1 -0
  249. package/dist/ui/snippet.js +7 -0
  250. package/dist/ui/snippet.js.map +1 -0
  251. package/package.json +69 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Regenerate media — example Byline script.
11
+ *
12
+ * Iterates the `media` collection, re-uploads each original through the
13
+ * core upload service so Sharp regenerates the variant set declared in
14
+ * the current schema, then updates the document with the fresh
15
+ * `storedFile` via `@byline/client`.
16
+ *
17
+ * Run after changing the variant set (e.g. switching `format: 'webp'`
18
+ * to `'avif'` in `byline/collections/media/schema.ts`) to bring existing
19
+ * assets in line with the new pipeline:
20
+ *
21
+ * pnpm tsx --env-file=.env byline/scripts/regenerate-media.ts
22
+ *
23
+ * The script orchestrates the same two-step flow the admin UI uses
24
+ * for an existing document — upload (createDocument: false) followed by
25
+ * a document update — but as a single "one-shot" Client SDK pass per
26
+ * media item. Variants and the original written under fresh paths;
27
+ * the previous original + variant files are deleted once the document
28
+ * has been re-pointed at the new value.
29
+ *
30
+ * Bypass-MIME images (SVG / GIF) are passed through untouched — Sharp
31
+ * does not produce variants for them, so the only effect is a new
32
+ * storagePath on a re-stored copy.
33
+ *
34
+ * Status preservation: `updateDocument` always stamps new versions with
35
+ * the workflow's default status (`getDefaultStatus`, i.e. the first
36
+ * declared status — `'draft'` for media). After the update we step the
37
+ * new version forward through `changeStatus` until it lands in the same
38
+ * slot the original occupied. Skipping this leaves the previously-
39
+ * published version (with the old variant set) as what
40
+ * `current_published_documents` returns to public readers.
41
+ *
42
+ * Currently requires the local storage provider — pulls bytes back via
43
+ * the provider's `uploadDir`. An S3-capable variant would add a
44
+ * `download(storagePath)` to `IStorageProvider` and route through that.
45
+ */
46
+
47
+ import 'dotenv/config'
48
+ import '../server.config.js'
49
+
50
+ import fs from 'node:fs'
51
+ import path from 'node:path'
52
+
53
+ import { createSuperAdminContext } from '@byline/auth'
54
+ import { type CollectionHandle, createBylineClient } from '@byline/client'
55
+ import {
56
+ type FieldUploadContext,
57
+ getCollectionDefinition,
58
+ getServerConfig,
59
+ getWorkflowStatuses,
60
+ type StoredFileValue,
61
+ } from '@byline/core'
62
+ import { uploadField as coreUploadField } from '@byline/core/services'
63
+ import { extractImageMeta, generateImageVariants, isBypassMimeType } from '@byline/storage-local'
64
+
65
+ const COLLECTION_PATH = 'media'
66
+ const FIELD_NAME = 'image'
67
+
68
+ async function run(): Promise<void> {
69
+ const config = getServerConfig()
70
+ const definition = getCollectionDefinition(COLLECTION_PATH)
71
+ if (!definition) {
72
+ throw new Error(`Collection '${COLLECTION_PATH}' is not registered.`)
73
+ }
74
+
75
+ const storage = config.storage
76
+ if (!storage) {
77
+ throw new Error(
78
+ `regenerate-media: no storage provider configured on ServerConfig. ` +
79
+ `Set storage in byline/server.config.ts.`
80
+ )
81
+ }
82
+ // Local-only escape hatch — we read bytes back from disk via uploadDir.
83
+ // Lift this when IStorageProvider grows a download primitive.
84
+ const uploadDir = (storage as { uploadDir?: unknown }).uploadDir
85
+ if (typeof uploadDir !== 'string') {
86
+ throw new Error(
87
+ 'regenerate-media currently requires the local storage provider ' +
88
+ '(no remote download primitive on IStorageProvider yet).'
89
+ )
90
+ }
91
+
92
+ const requestContext = createSuperAdminContext({ id: 'regenerate-media-script' })
93
+ const client = createBylineClient({ config, requestContext })
94
+
95
+ const { id: collectionId, version: collectionVersion } =
96
+ await client.resolveCollectionRecord(COLLECTION_PATH)
97
+
98
+ const handle = client.collection(COLLECTION_PATH)
99
+
100
+ // Snapshot the full set up-front. Each update bumps `updated_at` and
101
+ // reorders the default sort, so paging through a moving window would
102
+ // either skip or re-visit rows. Capture `status` so we can restore
103
+ // each doc to its original workflow slot after the regenerated draft
104
+ // is written (see `restoreStatus` below).
105
+ const allDocs: {
106
+ id: string
107
+ path: string
108
+ status: string
109
+ fields: Record<string, any>
110
+ }[] = []
111
+ const pageSize = 100
112
+ for (let page = 1; ; page++) {
113
+ const result = await handle.find({
114
+ page,
115
+ pageSize,
116
+ status: 'any',
117
+ _bypassBeforeRead: true,
118
+ })
119
+ for (const d of result.docs) {
120
+ allDocs.push({
121
+ id: d.id,
122
+ path: d.path,
123
+ status: d.status,
124
+ fields: d.fields as Record<string, any>,
125
+ })
126
+ }
127
+ if (result.docs.length < pageSize) break
128
+ }
129
+
130
+ console.log(`regenerate-media: found ${allDocs.length} document(s) in '${COLLECTION_PATH}'.`)
131
+
132
+ const baseUploadCtx: Omit<FieldUploadContext, 'requestContext'> = {
133
+ db: config.db,
134
+ definition,
135
+ collectionId,
136
+ collectionVersion,
137
+ collectionPath: COLLECTION_PATH,
138
+ fieldName: FIELD_NAME,
139
+ storage,
140
+ logger: client.logger,
141
+ defaultLocale: config.i18n.content.defaultLocale,
142
+ slugifier: config.slugifier,
143
+ imageProcessor: {
144
+ extractMeta: extractImageMeta,
145
+ isBypassMimeType,
146
+ generateVariants: async ({ buffer, mimeType, storedFile, storage, upload, logger }) => {
147
+ const dir = (storage as { uploadDir?: unknown }).uploadDir as string
148
+ const absoluteOriginalPath = path.join(dir, storedFile.storagePath)
149
+ return generateImageVariants(
150
+ buffer,
151
+ mimeType,
152
+ absoluteOriginalPath,
153
+ dir,
154
+ upload.sizes ?? [],
155
+ logger
156
+ )
157
+ },
158
+ },
159
+ }
160
+
161
+ let processed = 0
162
+ let skipped = 0
163
+
164
+ for (const doc of allDocs) {
165
+ const image = doc.fields[FIELD_NAME] as StoredFileValue | undefined | null
166
+ if (image == null || !image.storagePath) {
167
+ console.log(` - skip ${doc.id} (${doc.path}) — no image value`)
168
+ skipped += 1
169
+ continue
170
+ }
171
+
172
+ const sourceAbsolutePath = path.join(uploadDir, image.storagePath)
173
+ let buffer: Buffer
174
+ try {
175
+ buffer = await fs.promises.readFile(sourceAbsolutePath)
176
+ } catch (err) {
177
+ console.error(
178
+ ` ! skip ${doc.id} (${doc.path}) — failed to read original at ${sourceAbsolutePath}:`,
179
+ err
180
+ )
181
+ skipped += 1
182
+ continue
183
+ }
184
+
185
+ const uploadResult = await coreUploadField(
186
+ { ...baseUploadCtx, requestContext },
187
+ {
188
+ buffer,
189
+ originalFilename: image.originalFilename || image.filename,
190
+ mimeType: image.mimeType,
191
+ fileSize: buffer.byteLength,
192
+ shouldCreateDocument: false,
193
+ }
194
+ )
195
+
196
+ const nextFields = { ...doc.fields, [FIELD_NAME]: uploadResult.storedFile }
197
+ await handle.update(doc.id, nextFields)
198
+
199
+ // `updateDocument` always stamps the new version with the workflow's
200
+ // default status (the first key — 'draft' for media), so without this
201
+ // step the public read path keeps returning the prior published
202
+ // version with the old variant set. Walk the new version forward to
203
+ // the slot the doc originally occupied.
204
+ await restoreStatus(handle, definition, doc.id, doc.status)
205
+
206
+ // Best-effort orphan cleanup. Failures are non-fatal — the new
207
+ // value is already persisted, the doc is consistent, the unused
208
+ // bytes are just dead weight on disk.
209
+ const orphans: string[] = [
210
+ image.storagePath,
211
+ ...(image.variants ?? []).map((v) => v.storagePath).filter((p): p is string => Boolean(p)),
212
+ ]
213
+ for (const orphan of orphans) {
214
+ try {
215
+ await storage.delete(orphan)
216
+ } catch (err) {
217
+ console.warn(` ! failed to delete orphan ${orphan}:`, err)
218
+ }
219
+ }
220
+
221
+ const variantSummary =
222
+ uploadResult.storedFile.variants?.map((v) => `${v.name}:${v.format ?? '?'}`).join(', ') ??
223
+ '(none)'
224
+ console.log(
225
+ ` - ${doc.id} (${doc.path}) → ${uploadResult.storedFile.storagePath} [${variantSummary}]`
226
+ )
227
+ processed += 1
228
+ }
229
+
230
+ console.log(
231
+ `regenerate-media: done. processed=${processed}, skipped=${skipped}, total=${allDocs.length}.`
232
+ )
233
+ }
234
+
235
+ /**
236
+ * Walk the workflow ladder forward from the new version's status (the
237
+ * workflow's first slot, set by `updateDocument`) to `targetStatus`.
238
+ *
239
+ * `validateStatusTransition` allows ±1-step moves and reset-to-first, so
240
+ * jumping straight from 'draft' to 'archived' is rejected — we step
241
+ * through 'published' on the way. A target equal to the current status
242
+ * is a no-op (transition validation accepts identical-status moves but
243
+ * we skip the work).
244
+ *
245
+ * Throws if `targetStatus` is not declared on the collection's workflow,
246
+ * since that signals a stale snapshot or a workflow definition that has
247
+ * been edited since the docs were created.
248
+ */
249
+ async function restoreStatus(
250
+ handle: CollectionHandle,
251
+ definition: Parameters<typeof getWorkflowStatuses>[0],
252
+ documentId: string,
253
+ targetStatus: string
254
+ ): Promise<void> {
255
+ const statuses = getWorkflowStatuses(definition).map((s) => s.name)
256
+ const targetIndex = statuses.indexOf(targetStatus)
257
+ if (targetIndex === -1) {
258
+ throw new Error(
259
+ `restoreStatus: status '${targetStatus}' is not declared on collection ` +
260
+ `'${definition.path}' (declared: ${statuses.join(', ')}).`
261
+ )
262
+ }
263
+ // The new version is always at index 0 (workflow default). Anything at
264
+ // index > 0 needs that many forward transitions.
265
+ for (let i = 1; i <= targetIndex; i++) {
266
+ await handle.changeStatus(documentId, statuses[i] as string)
267
+ }
268
+ }
269
+
270
+ run()
271
+ .then(() => process.exit(0))
272
+ .catch((err) => {
273
+ console.error('regenerate-media failed:', err)
274
+ process.exit(1)
275
+ })
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ // Initialize Byline config by importing the server config
10
+ import 'dotenv/config'
11
+ import './server.config.js'
12
+
13
+ import { seedAdmin } from './seeds/admin.js'
14
+ import { seedDocsCategories } from './seeds/doc-categories.js'
15
+ import { seedDocs } from './seeds/docs.js'
16
+ import { seedNewsCategories } from './seeds/news-categories.js'
17
+
18
+ async function run() {
19
+ await seedAdmin()
20
+ await seedDocsCategories()
21
+ await seedNewsCategories()
22
+ await seedDocs()
23
+ }
24
+
25
+ run()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Super-admin bootstrap seed.
11
+ *
12
+ * Idempotent. Reads credentials from env (`BYLINE_SUPERADMIN_EMAIL`,
13
+ * `BYLINE_SUPERADMIN_PASSWORD`) and calls the built-in
14
+ * `seedSuperAdmin` helper from `@byline/admin/admin-users` against an
15
+ * `AdminStore` built on the application's single connection pool.
16
+ *
17
+ * The admin account it produces:
18
+ * - `is_super_admin: true` — bypasses every ability check
19
+ * - `is_enabled: true` — ready to sign in immediately
20
+ * - `is_email_verified: true` — skip the verification gate for bootstrap
21
+ *
22
+ * In any non-dev deployment: change the credentials in env, run the seed
23
+ * once, then immediately change the password from inside the admin UI.
24
+ */
25
+
26
+ import type { AdminStore } from '@byline/admin'
27
+ import { seedSuperAdmin } from '@byline/admin/admin-users'
28
+ import { getBylineCore } from '@byline/core'
29
+
30
+ export async function seedAdmin() {
31
+ const email = process.env.BYLINE_SUPERADMIN_EMAIL
32
+ const password = process.env.BYLINE_SUPERADMIN_PASSWORD
33
+
34
+ if (!email || !password) {
35
+ console.warn(
36
+ 'Skipping admin seed: BYLINE_SUPERADMIN_EMAIL and BYLINE_SUPERADMIN_PASSWORD ' +
37
+ 'must both be set (see .env.example).'
38
+ )
39
+ return
40
+ }
41
+
42
+ const bylineCore = getBylineCore<AdminStore>()
43
+
44
+ if (!bylineCore.adminStore) {
45
+ throw new Error(
46
+ 'seedAdmin: bylineCore.adminStore is not configured. ' +
47
+ 'Pass adminStore to initBylineCore() in byline/server.config.ts.'
48
+ )
49
+ }
50
+
51
+ const result = await seedSuperAdmin(bylineCore.adminStore, { email, password })
52
+
53
+ const parts: string[] = []
54
+ if (result.created.role) parts.push('role')
55
+ if (result.created.user) parts.push('user')
56
+ if (result.created.assignment) parts.push('assignment')
57
+ if (parts.length === 0) {
58
+ console.log(`Super-admin already present (${email}) — no changes.`)
59
+ } else {
60
+ console.log(`Super-admin seed: created ${parts.join(', ')} for ${email}.`)
61
+ }
62
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { getCollectionDefinition, getDefaultStatus, getServerConfig, slugify } from '@byline/core'
10
+
11
+ const categories = [
12
+ {
13
+ name: { en: 'Whitepaper' },
14
+ description: { en: 'In-depth articles and research papers.' },
15
+ },
16
+ {
17
+ name: { en: 'Tutorial' },
18
+ description: { en: 'Step-by-step guides and how-tos.' },
19
+ },
20
+ {
21
+ name: { en: 'Framework' },
22
+ description: { en: 'Libraries, frameworks, and toolkits.' },
23
+ },
24
+ ]
25
+
26
+ export async function seedDocsCategories() {
27
+ const db = getServerConfig().db
28
+
29
+ const collectionDefinition = getCollectionDefinition('docs-categories')
30
+
31
+ if (!collectionDefinition) {
32
+ console.error('Collection definition not found for "docs-categories"')
33
+ return
34
+ }
35
+
36
+ // `initBylineCore` already registered the collection row via
37
+ // `ensureCollections()` when byline/server.config was imported, so we
38
+ // look the row up rather than re-inserting (which would violate the
39
+ // unique-path constraint).
40
+ const existing = await db.queries.collections.getCollectionByPath('docs-categories')
41
+ if (!existing) {
42
+ throw new Error(
43
+ "seedDocsCategories: expected the 'docs-categories' collection to be registered by initBylineCore()"
44
+ )
45
+ }
46
+
47
+ const categoriesCollection = {
48
+ id: existing.id as string,
49
+ name: existing.path as string,
50
+ version: (existing.version as number | undefined) ?? 1,
51
+ }
52
+
53
+ console.log(`Seeding into Docs Categories collection (${categoriesCollection.name})`)
54
+
55
+ for (const category of categories) {
56
+ const seedPath = slugify(category.name.en, {
57
+ locale: 'en',
58
+ collectionPath: 'docs-categories',
59
+ })
60
+ await db.commands.documents.createDocumentVersion({
61
+ collectionId: categoriesCollection.id,
62
+ collectionVersion: categoriesCollection.version,
63
+ collectionConfig: collectionDefinition,
64
+ action: 'create',
65
+ documentData: category,
66
+ path: seedPath,
67
+ status: getDefaultStatus(collectionDefinition),
68
+ })
69
+ console.log(` - seeded category: ${category.name.en} (${seedPath})`)
70
+ }
71
+ }