caracms 0.0.0-stage → 0.2.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 (281) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/bin/caracms.mjs +174 -0
  4. package/bin/gate.mjs +275 -0
  5. package/bin/init.mjs +712 -0
  6. package/bin/media.mjs +662 -0
  7. package/bin/site-runner.mjs +61 -0
  8. package/bin/templates/Page.astro.txt +31 -0
  9. package/bin/templates/TextBlock.astro.txt +13 -0
  10. package/bin/templates/caracms.config.ts.txt +22 -0
  11. package/bin/templates/catch-all.astro.txt +25 -0
  12. package/bin/templates/health.ts.txt +6 -0
  13. package/bin/templates/railway.ts.txt +147 -0
  14. package/package.json +66 -4
  15. package/skills/caracms-adopt/SKILL.md +508 -0
  16. package/skills/caracms-adopt/look-same.mjs +957 -0
  17. package/skills/caracms-blocks/SKILL.md +376 -0
  18. package/skills/caracms-content/SKILL.md +201 -0
  19. package/skills/caracms-tina/SKILL.md +427 -0
  20. package/skills/caracms-tina/field-kinds.md +88 -0
  21. package/skills/caracms-tina/tina-to-caracms.mjs +1011 -0
  22. package/src/block-runs.ts +84 -0
  23. package/src/cards.ts +158 -0
  24. package/src/cli/schemas.ts +122 -0
  25. package/src/cli/site.ts +66 -0
  26. package/src/cli/validate.ts +471 -0
  27. package/src/code-routes-disk.ts +67 -0
  28. package/src/code-routes.ts +134 -0
  29. package/src/columns.ts +39 -0
  30. package/src/components/Block.astro +46 -0
  31. package/src/components/BlockRunTree.astro +45 -0
  32. package/src/components/Blocks.astro +139 -0
  33. package/src/components/Buttons.astro +116 -0
  34. package/src/components/Fonts.astro +14 -0
  35. package/src/components/Form.astro +221 -0
  36. package/src/components/Head.astro +62 -0
  37. package/src/components/Link.astro +68 -0
  38. package/src/components/Lockup.astro +116 -0
  39. package/src/components/Media.astro +124 -0
  40. package/src/components/MediaPlayer.astro +102 -0
  41. package/src/components/RenderDocument.astro +64 -0
  42. package/src/components/RichText.astro +59 -0
  43. package/src/components/Text.astro +81 -0
  44. package/src/components/attributes.ts +33 -0
  45. package/src/components/descriptor.ts +45 -0
  46. package/src/components/form-client.ts +182 -0
  47. package/src/components/media-player.ts +46 -0
  48. package/src/components/media-props.ts +35 -0
  49. package/src/components/sitemap.xml.ts +36 -0
  50. package/src/config.ts +825 -0
  51. package/src/content-schema.ts +166 -0
  52. package/src/content.ts +422 -0
  53. package/src/document.ts +579 -0
  54. package/src/editor/README.md +405 -0
  55. package/src/editor/addresses.ts +61 -0
  56. package/src/editor/brand.ts +118 -0
  57. package/src/editor/client/block-list/BlockList.tsx +342 -0
  58. package/src/editor/client/block-list/Picker.tsx +105 -0
  59. package/src/editor/client/block-list/UnknownBlock.tsx +119 -0
  60. package/src/editor/client/block-list/insert-points.ts +56 -0
  61. package/src/editor/client/block-list/link.ts +71 -0
  62. package/src/editor/client/block-list/reorder.ts +105 -0
  63. package/src/editor/client/block-list/summary.ts +118 -0
  64. package/src/editor/client/collections/CollectionGrid.tsx +143 -0
  65. package/src/editor/client/collections/CollectionTable.tsx +406 -0
  66. package/src/editor/client/collections/table.ts +270 -0
  67. package/src/editor/client/context.tsx +27 -0
  68. package/src/editor/client/documents/DocMenu.tsx +154 -0
  69. package/src/editor/client/documents/actions.ts +192 -0
  70. package/src/editor/client/documents/dialogs.tsx +687 -0
  71. package/src/editor/client/editor.ts +995 -0
  72. package/src/editor/client/fields/ButtonsField.tsx +87 -0
  73. package/src/editor/client/fields/DateField.tsx +200 -0
  74. package/src/editor/client/fields/FieldControl.tsx +61 -0
  75. package/src/editor/client/fields/FieldFrame.tsx +109 -0
  76. package/src/editor/client/fields/GroupField.tsx +113 -0
  77. package/src/editor/client/fields/HiddenToggle.tsx +51 -0
  78. package/src/editor/client/fields/LinkField.tsx +307 -0
  79. package/src/editor/client/fields/ListField.tsx +642 -0
  80. package/src/editor/client/fields/LockupField.tsx +365 -0
  81. package/src/editor/client/fields/MediaField.tsx +823 -0
  82. package/src/editor/client/fields/PageFields.tsx +82 -0
  83. package/src/editor/client/fields/ReferenceField.tsx +209 -0
  84. package/src/editor/client/fields/RichTextField.tsx +306 -0
  85. package/src/editor/client/fields/SelectField.tsx +63 -0
  86. package/src/editor/client/fields/SeoGroup.tsx +282 -0
  87. package/src/editor/client/fields/SimpleFields.tsx +89 -0
  88. package/src/editor/client/fields/TextField.tsx +51 -0
  89. package/src/editor/client/fields/date.ts +73 -0
  90. package/src/editor/client/fields/host.tsx +172 -0
  91. package/src/editor/client/fields/list-summary.ts +85 -0
  92. package/src/editor/client/fields/lockup-rules.ts +181 -0
  93. package/src/editor/client/fields/parts.tsx +458 -0
  94. package/src/editor/client/fields/rich-text-commands.ts +183 -0
  95. package/src/editor/client/fields/rich-text-doc.ts +156 -0
  96. package/src/editor/client/fields/rich-text-schema.ts +224 -0
  97. package/src/editor/client/fields/stale.ts +58 -0
  98. package/src/editor/client/inbox/InboxStage.tsx +272 -0
  99. package/src/editor/client/inbox/store.ts +311 -0
  100. package/src/editor/client/live/format.ts +77 -0
  101. package/src/editor/client/live/marks.tsx +90 -0
  102. package/src/editor/client/live/notices.tsx +213 -0
  103. package/src/editor/client/live/publish.tsx +617 -0
  104. package/src/editor/client/live/session.tsx +162 -0
  105. package/src/editor/client/main.tsx +12 -0
  106. package/src/editor/client/media/LibraryStage.tsx +75 -0
  107. package/src/editor/client/media/ReplaceDialog.tsx +355 -0
  108. package/src/editor/client/media/store.ts +618 -0
  109. package/src/editor/client/nav/DocRow.tsx +122 -0
  110. package/src/editor/client/nav/search.ts +71 -0
  111. package/src/editor/client/nav/tree.ts +93 -0
  112. package/src/editor/client/overlays/OverlayMount.tsx +26 -0
  113. package/src/editor/client/overlays/editor-marks.ts +159 -0
  114. package/src/editor/client/overlays/index.ts +50 -0
  115. package/src/editor/client/overlays/layer.ts +437 -0
  116. package/src/editor/client/overlays/overlay-css.ts +177 -0
  117. package/src/editor/client/overlays/rich-text-bar.ts +717 -0
  118. package/src/editor/client/pane/Pane.tsx +91 -0
  119. package/src/editor/client/pane/levels/BlockLevel.tsx +123 -0
  120. package/src/editor/client/pane/levels/CollectionLevel.tsx +115 -0
  121. package/src/editor/client/pane/levels/DocumentLevel.tsx +93 -0
  122. package/src/editor/client/pane/levels/DocumentsLevel.tsx +390 -0
  123. package/src/editor/client/pane/levels/FileLevel.tsx +132 -0
  124. package/src/editor/client/pane/levels/FormLevel.tsx +550 -0
  125. package/src/editor/client/pane/levels/MediaLevel.tsx +657 -0
  126. package/src/editor/client/pane/levels/SettingsM6.tsx +299 -0
  127. package/src/editor/client/pane/levels/SubmissionsLevel.tsx +185 -0
  128. package/src/editor/client/pane/parts.tsx +179 -0
  129. package/src/editor/client/pane/switcher.tsx +165 -0
  130. package/src/editor/client/preview/driver.ts +640 -0
  131. package/src/editor/client/preview/field-text.ts +71 -0
  132. package/src/editor/client/preview/follow-links.ts +234 -0
  133. package/src/editor/client/preview/layout.ts +37 -0
  134. package/src/editor/client/preview/link-target.ts +50 -0
  135. package/src/editor/client/preview/markers.ts +86 -0
  136. package/src/editor/client/preview/page-edit.ts +632 -0
  137. package/src/editor/client/preview/page-editor-registry.ts +63 -0
  138. package/src/editor/client/preview/page-editor.ts +235 -0
  139. package/src/editor/client/preview/render-sequencer.ts +113 -0
  140. package/src/editor/client/preview/selection.ts +116 -0
  141. package/src/editor/client/preview/warnings.ts +61 -0
  142. package/src/editor/client/shell/App.tsx +101 -0
  143. package/src/editor/client/shell/Bar.tsx +197 -0
  144. package/src/editor/client/shell/Brand.tsx +33 -0
  145. package/src/editor/client/shell/Icons.tsx +87 -0
  146. package/src/editor/client/shell/NarrowNote.tsx +47 -0
  147. package/src/editor/client/shell/Popovers.tsx +47 -0
  148. package/src/editor/client/shell/Stage.tsx +132 -0
  149. package/src/editor/client/shell/Toast.tsx +61 -0
  150. package/src/editor/client/state/document-store.ts +465 -0
  151. package/src/editor/client/state/keeper.ts +713 -0
  152. package/src/editor/client/state/live.ts +257 -0
  153. package/src/editor/client/state/schema.ts +42 -0
  154. package/src/editor/client/state/store.ts +39 -0
  155. package/src/editor/fonts/OFL-GeistMono.txt +93 -0
  156. package/src/editor/fonts/OFL-InstrumentSans.txt +93 -0
  157. package/src/editor/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  158. package/src/editor/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
  159. package/src/editor/fonts/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
  160. package/src/editor/fonts/instrument-sans-latin-wght-normal.woff2 +0 -0
  161. package/src/editor/middleware.ts +62 -0
  162. package/src/editor/routes/_live.ts +26 -0
  163. package/src/editor/routes/admin.astro +80 -0
  164. package/src/editor/routes/create.ts +19 -0
  165. package/src/editor/routes/delete.ts +19 -0
  166. package/src/editor/routes/document.ts +39 -0
  167. package/src/editor/routes/documents.ts +30 -0
  168. package/src/editor/routes/github.ts +23 -0
  169. package/src/editor/routes/history.ts +9 -0
  170. package/src/editor/routes/lock.ts +9 -0
  171. package/src/editor/routes/login/assets.d.ts +10 -0
  172. package/src/editor/routes/login/login.ts +43 -0
  173. package/src/editor/routes/media.ts +16 -0
  174. package/src/editor/routes/move.ts +17 -0
  175. package/src/editor/routes/notice.ts +9 -0
  176. package/src/editor/routes/preview.astro +77 -0
  177. package/src/editor/routes/publish.ts +9 -0
  178. package/src/editor/routes/status.ts +9 -0
  179. package/src/editor/routes/submissions.ts +16 -0
  180. package/src/editor/routes/try-again.ts +9 -0
  181. package/src/editor/routes/write.ts +18 -0
  182. package/src/editor/server/admin-props.ts +151 -0
  183. package/src/editor/server/cards.ts +61 -0
  184. package/src/editor/server/create.ts +414 -0
  185. package/src/editor/server/drafts-store.ts +127 -0
  186. package/src/editor/server/media/alt.ts +107 -0
  187. package/src/editor/server/media/bucket.ts +295 -0
  188. package/src/editor/server/media/config.ts +78 -0
  189. package/src/editor/server/media/handler.ts +221 -0
  190. package/src/editor/server/media/library.ts +254 -0
  191. package/src/editor/server/media/memory.ts +184 -0
  192. package/src/editor/server/media/mux.ts +241 -0
  193. package/src/editor/server/media/sniff.ts +599 -0
  194. package/src/editor/server/media/upload.ts +175 -0
  195. package/src/editor/server/mode.ts +63 -0
  196. package/src/editor/server/move.ts +295 -0
  197. package/src/editor/server/render.tsx +9 -0
  198. package/src/editor/server/replace.ts +72 -0
  199. package/src/editor/server/settings.ts +35 -0
  200. package/src/editor/server/write.ts +366 -0
  201. package/src/editor/styles/editor.css +502 -0
  202. package/src/editor/styles/fonts.css +40 -0
  203. package/src/editor/styles/levels/README.md +4 -0
  204. package/src/editor/styles/levels/collections.css +99 -0
  205. package/src/editor/styles/levels/document-level.css +10 -0
  206. package/src/editor/styles/levels/documents.css +9 -0
  207. package/src/editor/styles/levels/fields.css +171 -0
  208. package/src/editor/styles/levels/files.css +95 -0
  209. package/src/editor/styles/levels/live.css +10 -0
  210. package/src/editor/styles/levels/m5a-documents.css +51 -0
  211. package/src/editor/styles/levels/m6.css +82 -0
  212. package/src/editor/styles/levels/media.css +175 -0
  213. package/src/editor/styles/levels/switcher.css +3 -0
  214. package/src/editor/styles/tokens.css +114 -0
  215. package/src/editor/tsconfig.json +16 -0
  216. package/src/editor/types.ts +881 -0
  217. package/src/editor/ui-state.ts +113 -0
  218. package/src/email.ts +67 -0
  219. package/src/fields.ts +1129 -0
  220. package/src/files-route.ts +201 -0
  221. package/src/files.ts +116 -0
  222. package/src/forms/email.ts +149 -0
  223. package/src/forms/endpoint.ts +347 -0
  224. package/src/forms/inbox.ts +273 -0
  225. package/src/forms/rate-limit.ts +130 -0
  226. package/src/forms/route.ts +24 -0
  227. package/src/forms/rules.ts +278 -0
  228. package/src/forms/store.ts +486 -0
  229. package/src/head.ts +125 -0
  230. package/src/index.ts +434 -0
  231. package/src/json-source.ts +116 -0
  232. package/src/links.ts +121 -0
  233. package/src/live/README.md +43 -0
  234. package/src/live/auth/allow-rules.ts +70 -0
  235. package/src/live/auth/allow.ts +128 -0
  236. package/src/live/auth/auth.ts +155 -0
  237. package/src/live/auth/email.ts +84 -0
  238. package/src/live/auth/guard.ts +111 -0
  239. package/src/live/auth/index.ts +68 -0
  240. package/src/live/auth/links.ts +101 -0
  241. package/src/live/auth/login.ts +245 -0
  242. package/src/live/auth/middleware.ts +53 -0
  243. package/src/live/auth/request.ts +56 -0
  244. package/src/live/auth/routes.ts +9 -0
  245. package/src/live/auth/session.ts +181 -0
  246. package/src/live/auth/views.ts +355 -0
  247. package/src/live/autosaves.ts +306 -0
  248. package/src/live/backend.ts +652 -0
  249. package/src/live/branches.ts +19 -0
  250. package/src/live/drafts-store.ts +591 -0
  251. package/src/live/editor.ts +465 -0
  252. package/src/live/git.ts +1021 -0
  253. package/src/live/github.ts +271 -0
  254. package/src/live/locals.ts +24 -0
  255. package/src/live/locks.ts +273 -0
  256. package/src/live/merge.ts +197 -0
  257. package/src/live/messages.ts +151 -0
  258. package/src/live/mode.ts +210 -0
  259. package/src/live/redis.ts +533 -0
  260. package/src/live/runtime.ts +164 -0
  261. package/src/live/status.ts +109 -0
  262. package/src/live/sweep.ts +228 -0
  263. package/src/live/timings.ts +47 -0
  264. package/src/live/webhook.ts +39 -0
  265. package/src/lockup-css.ts +58 -0
  266. package/src/measure.ts +145 -0
  267. package/src/media-value.ts +239 -0
  268. package/src/media.ts +255 -0
  269. package/src/read.ts +256 -0
  270. package/src/redirect-middleware.ts +49 -0
  271. package/src/redirects.ts +76 -0
  272. package/src/relink.ts +293 -0
  273. package/src/reserved.ts +24 -0
  274. package/src/rich-text.ts +690 -0
  275. package/src/schema-key.ts +63 -0
  276. package/src/seo.ts +124 -0
  277. package/src/serialize.ts +697 -0
  278. package/src/skipped.ts +35 -0
  279. package/src/slug.ts +121 -0
  280. package/src/text-styles.ts +62 -0
  281. package/src/virtual.d.ts +28 -0
@@ -0,0 +1,61 @@
1
+ // Loading the package's TypeScript (and through it the site's caracms.config.ts) from a
2
+ // plain `.mjs` command (M8 → Current state: "load it the way the site's own Astro does").
3
+ //
4
+ // Node 22.12 runs no TypeScript without flags, and the site's config imports `caracms/config`
5
+ // as TypeScript source. So a command that needs the config (`schemas`, `validate`) starts
6
+ // a Vite dev server in middleware mode from the site's own Astro's Vite, with caracms
7
+ // compiled (`ssr.noExternal`, as the integration sets it), loads one of the package's
8
+ // `src/cli/*.ts` modules through it, and closes it after. No port is opened, nothing is
9
+ // watched, and nothing is written.
10
+
11
+ import { existsSync } from 'node:fs';
12
+ import { createRequire } from 'node:module';
13
+ import { join } from 'node:path';
14
+ import { fileURLToPath, pathToFileURL } from 'node:url';
15
+
16
+ const packageRoot = fileURLToPath(new URL('..', import.meta.url));
17
+
18
+ /** The Vite the site's Astro uses, or null with a sentence why. */
19
+ export function siteVite(root) {
20
+ const fromSite = createRequire(join(root, 'package.json'));
21
+ let astro;
22
+ try {
23
+ astro = fromSite.resolve('astro/package.json');
24
+ } catch {
25
+ return { vite: null, problem: "there's no Astro installed here. Run this from the site's root, after installing its dependencies." };
26
+ }
27
+ try {
28
+ return { vite: createRequire(astro).resolve('vite'), problem: null };
29
+ } catch {
30
+ return { vite: null, problem: "the site's Astro has no Vite beside it. Reinstall the site's dependencies." };
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Run `fn(module)` with the package's `src/cli/<name>.ts` loaded through the site's Vite,
36
+ * then close the server. Throws an Error with a plain sentence when it can't be loaded.
37
+ */
38
+ export async function withPackageModule(root, name, fn) {
39
+ const { vite, problem } = siteVite(root);
40
+ if (!vite) throw new Error(problem);
41
+ const { createServer } = await import(pathToFileURL(vite).href);
42
+ const server = await createServer({
43
+ root,
44
+ configFile: false,
45
+ envFile: false,
46
+ logLevel: 'error',
47
+ clearScreen: false,
48
+ appType: 'custom',
49
+ server: { middlewareMode: true, hmr: false, ws: false, watch: null },
50
+ optimizeDeps: { noDiscovery: true, include: [] },
51
+ ssr: { noExternal: ['caracms'] },
52
+ });
53
+ try {
54
+ const entry = join(packageRoot, 'src/cli', `${name}.ts`);
55
+ if (!existsSync(entry)) throw new Error(`the package's ${entry} is missing. Reinstall caracms.`);
56
+ const module = await server.ssrLoadModule(entry);
57
+ return await fn(module);
58
+ } finally {
59
+ await server.close();
60
+ }
61
+ }
@@ -0,0 +1,31 @@
1
+ ---
2
+ /**
3
+ * The page component, written by `caracms init`: a document in, a whole page out. The
4
+ * public pages and the editor's preview both render through it. Give it the site's own
5
+ * layout (its head, header and footer) with `<Blocks>` where a page's content goes: the
6
+ * caracms-adopt skill says how.
7
+ */
8
+ import Blocks from 'caracms/components/Blocks.astro';
9
+ import Fonts from 'caracms/components/Fonts.astro';
10
+ import Head from 'caracms/components/Head.astro';
11
+ import type { PageProps } from 'caracms/document';
12
+
13
+ type Props = PageProps;
14
+ // `path` is the document's address (`/about`), the same on the public page and in the preview.
15
+ const { document, collection, path, preview = false, listPage } = Astro.props;
16
+ ---
17
+
18
+ <!doctype html>
19
+ <html lang="en">
20
+ <head>
21
+ <meta charset="utf-8" />
22
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
23
+ <Head document={document} collection={collection} path={path} preview={preview} listPage={listPage} />
24
+ <Fonts />
25
+ </head>
26
+ <body>
27
+ <main>
28
+ <Blocks document={document} collection={collection} preview={preview} listPage={listPage} />
29
+ </main>
30
+ </body>
31
+ </html>
@@ -0,0 +1,13 @@
1
+ ---
2
+ /** The plain text block `caracms init` adds: one text field, as a paragraph. */
3
+ import Block from 'caracms/components/Block.astro';
4
+ import Text from 'caracms/components/Text.astro';
5
+ import type { BlockProps } from 'caracms/document';
6
+
7
+ type Props = BlockProps<{ text: string }>;
8
+ const { block, cms } = Astro.props;
9
+ ---
10
+
11
+ <Block cms={cms} as="section">
12
+ <Text cms={cms} field="text" value={block.text} />
13
+ </Block>
@@ -0,0 +1,22 @@
1
+ import { defineSite, text } from 'caracms/config';
2
+
3
+ /**
4
+ * caracms's config for this site, written by `caracms init`: its collections, blocks and
5
+ * text styles. astro.config passes it to the integration. The caracms-adopt and
6
+ * caracms-blocks skills say how it grows as the site's pages come in.
7
+ */
8
+ export default defineSite({
9
+ collections: {
10
+ // content/pages/<path>.json is the page at /<path>; content/pages/index.json is Home.
11
+ pages: { kind: 'sections', folder: 'content/pages', urlBase: '/', nested: true },
12
+ },
13
+ blocks: {
14
+ text: {
15
+ label: 'Text',
16
+ component: 'src/components/caracms/TextBlock.astro',
17
+ fields: { text: text({ multiline: true }) },
18
+ },
19
+ },
20
+ textStyles: {},
21
+ page: 'src/components/caracms/Page.astro',
22
+ });
@@ -0,0 +1,25 @@
1
+ ---
2
+ /**
3
+ * Every document in the Pages collection (content/pages/), nested ones included: the
4
+ * file's path is the address. Written by `caracms init`. It serves documents only. The
5
+ * site's other routes in src/pages/ keep serving their own pages, and their addresses are
6
+ * reserved: a page is code or a document, never both.
7
+ */
8
+ import RenderDocument from 'caracms/components/RenderDocument.astro';
9
+ import { listDocuments, readDocument } from 'caracms/content';
10
+ import { site } from 'virtual:caracms/config';
11
+
12
+ export const prerender = true;
13
+
14
+ export async function getStaticPaths() {
15
+ // A file that can't be opened (invalid JSON, no title, a nested index.json) has no page,
16
+ // and nor has a hidden one (or one under it): visitors get the 404 page.
17
+ const entries = (await listDocuments(site, 'pages')).filter((e) => !e.warning && !e.hidden);
18
+ return entries.map((e) => ({ params: { path: e.path === 'index' ? undefined : e.path }, props: { path: e.path } }));
19
+ }
20
+
21
+ const { path } = Astro.props;
22
+ const { document } = await readDocument(site, 'pages', path);
23
+ ---
24
+
25
+ <RenderDocument document={document} collection="pages" />
@@ -0,0 +1,6 @@
1
+ import type { APIRoute } from 'astro';
2
+
3
+ // Railway's healthcheck: answered by the server, not prerendered.
4
+ export const prerender = false;
5
+
6
+ export const GET: APIRoute = () => new Response('ok');
@@ -0,0 +1,147 @@
1
+ import { bucket, defineRailway, github, image, preserve, project, redis, ref, service } from "railway/iac";
2
+
3
+ // A caracms site's Railway project, as code (written by `caracms init` from the
4
+ // caracms starter's). From the site's root: `railway config plan` shows
5
+ // what would change, `railway config apply` makes it so. Plan first, every time:
6
+ // Redis holds the editor's drafts and the media bucket every uploaded image.
7
+ //
8
+ // What this file doesn't do, set by hand on Railway: create the project, the
9
+ // domains (site's and imgproxy's, imgproxy's on port 8080),
10
+ // Redis's volume and start command (Railway's Redis template makes them), the
11
+ // secrets' values, and the optional secrets below until they're wanted.
12
+
13
+ // ---- The site's names: a new site edits this block and nothing else. ----
14
+ // Project name, bucket names and the S3 bucket references derive from SITE.
15
+ const SITE = "{{SITE}}";
16
+ // The repo the editor commits to and Railway builds from.
17
+ const GITHUB_REPO = "{{GITHUB_REPO}}";
18
+ // Railway's region for the services, and the buckets' region (a bucket region
19
+ // is one of sjc, iad, ams, sin; ams sits next to europe-west4).
20
+ const REGION = "europe-west4-drams3a";
21
+ const BUCKET_REGION = "ams";
22
+ // The site is the repo's root: no watch paths, so every push to main deploys.
23
+ const WATCH_PATHS: string[] = [];
24
+ const BUILD_COMMAND = "{{BUILD}}";
25
+ const START_COMMAND = "{{START}}";
26
+ // imgproxy's origin and the media bucket's S3 name. Null: they come as
27
+ // references to imgproxy's domain and the media bucket's BUCKET.
28
+ const MEDIA_URL: string | null = null;
29
+ const MEDIA_S3_NAME: string | null = null;
30
+ // -------------------------------------------------------------------------
31
+
32
+ // A Railway reference inside a longer string: ${{<resource>.<VARIABLE>}}.
33
+ // ref() is for a value that is one whole reference; this is for one wrapped in
34
+ // other text. ref() compiles to the same syntax from the resource's name.
35
+ const inline = (resource: { name: string }, variable: string) =>
36
+ "${{" + resource.name + "." + variable + "}}";
37
+
38
+ export default defineRailway(() => {
39
+ // Uploaded images, served publicly through imgproxy.
40
+ const media = bucket(`${SITE}-media`, { region: BUCKET_REGION });
41
+ // Form submissions: a second bucket, so imgproxy, which holds the media
42
+ // bucket's keys and serves it publicly, can never reach them. Only `site`
43
+ // gets its credentials (SUBMISSIONS_S3_*). Same region as media: the site
44
+ // uses media's endpoint and region (S3_ENDPOINT, S3_REGION) for both.
45
+ const submissions = bucket(`${SITE}-submissions`, { region: BUCKET_REGION });
46
+
47
+ // The editor's drafts and sessions.
48
+ const kv = redis("Redis", { region: REGION });
49
+ // Sleeps when idle. redis() takes only a region, so it's set on the node.
50
+ kv.deploy = { ...kv.deploy, sleepApplication: true };
51
+
52
+ const imgproxy = service("imgproxy", {
53
+ // Pinned to an exact release, no auto updates.
54
+ source: image("ghcr.io/imgproxy/imgproxy:v4.0.15"),
55
+ replicas: { [REGION]: 1 },
56
+ // Sleeps when idle.
57
+ deploy: { sleepApplication: true },
58
+ // Railway's edge caches the signed image URLs (immutable, a year).
59
+ cdn: { enabled: true },
60
+ env: {
61
+ // The domain is generated for this port, so it's fixed here.
62
+ PORT: "8080",
63
+ IMGPROXY_USE_S3: "true",
64
+ IMGPROXY_S3_ENDPOINT: ref(media, "ENDPOINT"),
65
+ IMGPROXY_S3_REGION: ref(media, "REGION"),
66
+ AWS_ACCESS_KEY_ID: ref(media, "ACCESS_KEY_ID"),
67
+ AWS_SECRET_ACCESS_KEY: ref(media, "SECRET_ACCESS_KEY"),
68
+ // Only the media bucket: BUCKET is the S3 name with Railway's suffix.
69
+ IMGPROXY_ALLOWED_SOURCES: `s3://${MEDIA_S3_NAME ?? inline(media, "BUCKET")}/`,
70
+ IMGPROXY_MAX_RESULT_DIMENSION: "4000",
71
+ IMGPROXY_MAX_SRC_RESOLUTION: "50",
72
+ IMGPROXY_TTL: "31536000",
73
+ IMGPROXY_USE_ETAG: "true",
74
+ IMGPROXY_WORKERS: "2",
75
+ IMGPROXY_MALLOC: "jemalloc",
76
+ MALLOC_ARENA_MAX: "2",
77
+ IMGPROXY_SKIP_PROCESSING_FORMATS: "gif,svg",
78
+ // Signing key and salt (64-byte hex, generated by hand). `site` refers
79
+ // to these, so the two can't drift apart.
80
+ IMGPROXY_KEY: preserve(),
81
+ IMGPROXY_SALT: preserve(),
82
+ },
83
+ });
84
+
85
+ const site = service("site", {
86
+ source: github(GITHUB_REPO, { branch: "main", checkSuites: false }),
87
+ build: { buildCommand: BUILD_COMMAND, watchPatterns: WATCH_PATHS },
88
+ start: START_COMMAND,
89
+ healthcheck: "/health",
90
+ replicas: { [REGION]: 1 },
91
+ deploy: { restartPolicyMaxRetries: 3 },
92
+ env: {
93
+ // ---- Written here: literals and Railway references ----
94
+ NODE_ENV: "production",
95
+ // The public address (canonical links, the sitemap, og:url). At launch it
96
+ // becomes the client's domain, written out as "https://<domain>".
97
+ SITE_URL: "https://${{RAILWAY_PUBLIC_DOMAIN}}",
98
+ REDIS_URL: ref(kv, "REDIS_URL"),
99
+ // Images: imgproxy's public origin (the custom image host at launch), and
100
+ // its key and salt, which sign every image URL.
101
+ CARACMS_MEDIA_URL: MEDIA_URL ?? `https://${inline(imgproxy, "RAILWAY_PUBLIC_DOMAIN")}`,
102
+ IMGPROXY_KEY: ref(imgproxy, "IMGPROXY_KEY"),
103
+ IMGPROXY_SALT: ref(imgproxy, "IMGPROXY_SALT"),
104
+ // The media bucket, for uploads from the editor.
105
+ S3_ENDPOINT: ref(media, "ENDPOINT"),
106
+ S3_REGION: ref(media, "REGION"),
107
+ S3_BUCKET: ref(media, "BUCKET"),
108
+ S3_ACCESS_KEY_ID: ref(media, "ACCESS_KEY_ID"),
109
+ S3_SECRET_ACCESS_KEY: ref(media, "SECRET_ACCESS_KEY"),
110
+ // The submissions bucket. Its endpoint and region are media's (S3_*).
111
+ SUBMISSIONS_S3_BUCKET: ref(submissions, "BUCKET"),
112
+ SUBMISSIONS_S3_ACCESS_KEY_ID: ref(submissions, "ACCESS_KEY_ID"),
113
+ SUBMISSIONS_S3_SECRET_ACCESS_KEY: ref(submissions, "SECRET_ACCESS_KEY"),
114
+
115
+ // The repo the editor commits to.
116
+ GITHUB_REPO,
117
+
118
+ // ---- Set by hand on Railway: names only, the values never live here ----
119
+ // On a fresh project these stay unset and the plan doesn't mention them.
120
+ // The editor's GitHub App and sign-in.
121
+ GITHUB_APP_ID: preserve(),
122
+ GITHUB_APP_PRIVATE_KEY: preserve(),
123
+ GITHUB_WEBHOOK_SECRET: preserve(),
124
+ CARACMS_SECRET: preserve(),
125
+ CARACMS_ALLOW: preserve(),
126
+ CARACMS_BACKUP_USER: preserve(),
127
+ CARACMS_BACKUP_PASSWORD: preserve(),
128
+ // Try again on a failed publish redeploys through this project token.
129
+ CARACMS_RAILWAY_TOKEN: preserve(),
130
+ MUX_TOKEN_ID: preserve(),
131
+ MUX_TOKEN_SECRET: preserve(),
132
+ // The form emails.
133
+ RESEND_API_KEY: preserve(),
134
+ FORMS_FROM: preserve(),
135
+ // Optional. The form's spam check: the site key is baked in at build, so
136
+ // a change needs a redeploy.
137
+ TURNSTILE_SITE_KEY: preserve(),
138
+ TURNSTILE_SECRET_KEY: preserve(),
139
+ // Optional. Pre-launch password ("user:password"); unset means public.
140
+ CARACMS_PRELAUNCH: preserve(),
141
+ },
142
+ });
143
+
144
+ return project(SITE, {
145
+ resources: [site, kv, imgproxy, media, submissions],
146
+ });
147
+ });
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "caracms",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "A visual CMS for Astro sites, with content in git.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.12.0"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.ts",
12
+ "./config": "./src/config.ts",
13
+ "./content": "./src/content.ts",
14
+ "./document": "./src/document.ts",
15
+ "./serialize": "./src/serialize.ts",
16
+ "./measure": "./src/measure.ts",
17
+ "./files": "./src/files.ts",
18
+ "./virtual": {
19
+ "types": "./src/virtual.d.ts"
20
+ },
21
+ "./components/*": "./src/components/*"
22
+ },
23
+ "bin": {
24
+ "caracms": "bin/caracms.mjs"
25
+ },
26
+ "files": [
27
+ "src",
28
+ "bin",
29
+ "skills",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "test": "pnpm run test:unit && pnpm run test:browser && pnpm run test:visual",
35
+ "test:unit": "node --test --experimental-strip-types $(find test -name '*.test.ts' ! -name '*.browser.test.ts' ! -name '*.visual.test.ts' ! -name '*.release.test.ts' | sort)",
36
+ "test:browser": "node --test --experimental-strip-types --test-concurrency=2 $(find test -name '*.browser.test.ts' | sort)",
37
+ "test:visual": "node --test --experimental-strip-types --test-concurrency=3 $(find test -name '*.visual.test.ts' | sort)",
38
+ "check": "tsc --noEmit -p tsconfig.json && astro check --root .",
39
+ "test:release": "node --test --experimental-strip-types $(find test -name '*.release.test.ts' | sort)"
40
+ },
41
+ "dependencies": {
42
+ "@mux/mux-player": "3.13.4",
43
+ "@mux/upchunk": "3.5.0",
44
+ "@tiptap/core": "3.31.4",
45
+ "@tiptap/pm": "3.31.4",
46
+ "aws4fetch": "1.0.20",
47
+ "harfbuzzjs": "1.6.3",
48
+ "idiomorph": "0.8.0",
49
+ "ioredis": "5.11.1",
50
+ "react": "19.3.0",
51
+ "react-day-picker": "^9.14.0",
52
+ "react-dom": "19.3.0"
53
+ },
54
+ "peerDependencies": {
55
+ "astro": "^7.3.8"
56
+ },
57
+ "devDependencies": {
58
+ "@astrojs/check": "^0.9.10",
59
+ "@astrojs/node": "^11.1.7",
60
+ "@types/node": "^22.20.5",
61
+ "@types/react": "^19.3.0",
62
+ "@types/react-dom": "^19.3.0",
63
+ "ajv": "^8.20.0",
64
+ "astro": "^7.3.8",
65
+ "playwright-core": "1.63.0",
66
+ "typescript": "^6.0.3"
67
+ }
68
+ }