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
package/src/skipped.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The mark a preview render leaves where a block it skips would be (M3 Decision 17;
3
+ * Blocks.astro): an HTML comment, `<!--cms-skipped 3 type=carousel-->` (with
4
+ * ` shared=footer` for a shared-block entry), never an element. A site's sibling CSS
5
+ * (`+`, `:has(+ …)`) matches across a comment, so the preview's blocks join as the
6
+ * public page's do, where there's nothing for the block at all. The editor finds the
7
+ * comment (preview/markers.ts → skippedAnchor) and draws its mark in the overlay layer.
8
+ *
9
+ * The type and shared name are percent-encoded, so no `--` or `>` from a content
10
+ * file can end the comment early.
11
+ */
12
+
13
+ const PREFIX = 'cms-skipped ';
14
+
15
+ /** The comment for block `index`, skipped, as HTML. */
16
+ export function skippedMarker(index: number, skipped: { type: string; shared?: string }): string {
17
+ const shared = skipped.shared !== undefined ? ` shared=${encodeURIComponent(skipped.shared)}` : '';
18
+ return `<!--${PREFIX}${index} type=${encodeURIComponent(skipped.type)}${shared}-->`;
19
+ }
20
+
21
+ /** A comment's text read back, or null when it isn't a skipped block's. */
22
+ export function parseSkipped(text: string): { index: number; type: string; shared?: string } | null {
23
+ if (!text.startsWith(PREFIX)) return null;
24
+ const [index, ...rest] = text.slice(PREFIX.length).trim().split(' ');
25
+ if (!/^\d+$/.test(index ?? '')) return null;
26
+ const out: { index: number; type: string; shared?: string } = { index: Number(index), type: '' };
27
+ for (const part of rest) {
28
+ const eq = part.indexOf('=');
29
+ const key = part.slice(0, eq);
30
+ const value = decodeURIComponent(part.slice(eq + 1));
31
+ if (key === 'type') out.type = value;
32
+ else if (key === 'shared') out.shared = value;
33
+ }
34
+ return out;
35
+ }
package/src/slug.ts ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Addresses from names, and the rules a new document's address follows (M5a
3
+ * Decision 11). **Browser-safe** (its one import, code-routes.ts, is too): Phase 5's New page dialog shows the
4
+ * address before creating with `slugify`, and can check it with `addressProblem`;
5
+ * the create route (editor/server/create.ts) applies the same two, so the dialog and
6
+ * the server never disagree.
7
+ *
8
+ * - **`slugify(name)`**: lower case, accents folded (NFKD, marks dropped, plus the few
9
+ * letters that don't decompose: ß ss, æ ae, œ oe, ø o, ł l, đ/ð d, þ th, ı i), `&`
10
+ * as "and", every other run of characters that aren't letters or digits as one
11
+ * hyphen, hyphens trimmed from both ends, at most 60 characters (cut, then trimmed
12
+ * again). Letters and digits are Unicode's (`\p{L}`, `\p{N}`), as the decision words
13
+ * it, so a name in another script keeps its letters. A name that leaves nothing
14
+ * (only symbols) gives `''`, which the dialog and the route refuse.
15
+ * - **`isSlug(part)`**: a part of an address that `slugify` leaves as it is.
16
+ * - **`addressProblem(url, …)`**: why a new document can't have this public address,
17
+ * or null: under the editor's own `/admin` or `/_caracms`, or the library's files'
18
+ * `/files` (M6c Decision 3, served by files-route.ts); strictly below another
19
+ * collection's non-root `urlBase` (exactly at it is allowed: the Work page at
20
+ * `/work`); or another document's address already, in any collection.
21
+ */
22
+ import { codeRouteAt, codeRouteMessage, type CodeRoute } from './code-routes.ts';
23
+
24
+ /** The longest an address part may be (Decision 11). */
25
+ export const SLUG_MAX = 60;
26
+
27
+ /** `index` is Home's file name (M3 Decision 8), so no part of a new address can be it. */
28
+ export const RESERVED_SLUG = 'index';
29
+
30
+ const FOLD: Record<string, string> = { ß: 'ss', æ: 'ae', œ: 'oe', ø: 'o', ł: 'l', đ: 'd', ð: 'd', þ: 'th', ı: 'i' };
31
+
32
+ /** A name as an address part: `Kiln & Co.` → `kiln-and-co`, `Café Ölé` → `cafe-ole`. `''` when nothing is left. */
33
+ export function slugify(name: string): string {
34
+ let s = String(name).normalize('NFKD').replace(/\p{M}+/gu, '').toLowerCase();
35
+ s = s.replace(/[ßæœøłđðþı]/g, (c) => FOLD[c]!);
36
+ s = s.replace(/&/g, ' and ');
37
+ s = s.replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '');
38
+ s = s.normalize('NFC');
39
+ const chars = [...s];
40
+ if (chars.length > SLUG_MAX) s = chars.slice(0, SLUG_MAX).join('').replace(/-+$/, '');
41
+ return s;
42
+ }
43
+
44
+ /** Is this an address part `slugify` leaves unchanged (and not empty)? */
45
+ export function isSlug(part: string): boolean {
46
+ return typeof part === 'string' && part.length > 0 && slugify(part) === part;
47
+ }
48
+
49
+ /** The message for a name that leaves no address (only symbols). */
50
+ export const NO_ADDRESS_MESSAGE = 'That name has no letters or numbers to make an address from.';
51
+
52
+ /** The message for `index` as an address. */
53
+ export const INDEX_MESSAGE = '"index" is kept for Home, so it can\'t be an address. Choose another name.';
54
+
55
+ /** What `addressProblem` needs of a collection. */
56
+ export interface AddressCollection {
57
+ name: string;
58
+ label: string;
59
+ /** Starts and ends with `/`; null for collections with no pages (shared blocks, Site Settings). */
60
+ urlBase: string | null;
61
+ }
62
+
63
+ /** What `addressProblem` needs of a document. */
64
+ export interface AddressDocument {
65
+ collection: string;
66
+ path: string;
67
+ url: string | null;
68
+ }
69
+
70
+ export type AddressProblemCode = 'reserved-address' | 'below-collection' | 'address-taken';
71
+
72
+ export interface AddressProblem {
73
+ code: AddressProblemCode;
74
+ message: string;
75
+ }
76
+
77
+ /** An address compared with others: lower case, no trailing slash (`/` stays `/`). */
78
+ export const sameAddress = (url: string): string => {
79
+ const lower = url.toLowerCase();
80
+ return lower.length > 1 ? lower.replace(/\/+$/, '') : lower;
81
+ };
82
+
83
+ /**
84
+ * Why a new document in `collection` can't be at `url` (its public address), or null.
85
+ * `documents` is every document there is now, in every collection.
86
+ */
87
+ export function addressProblem(
88
+ url: string,
89
+ collection: string,
90
+ collections: readonly AddressCollection[],
91
+ documents: readonly AddressDocument[],
92
+ codeRoutes?: readonly CodeRoute[],
93
+ ): AddressProblem | null {
94
+ const at = sameAddress(url);
95
+ for (const reserved of ['/admin', '/_caracms']) {
96
+ if (at === reserved || at.startsWith(`${reserved}/`)) {
97
+ return { code: 'reserved-address', message: `${url} is the editor's own address, so a page can't go there.` };
98
+ }
99
+ }
100
+ const files = '/files';
101
+ if (at === files || at.startsWith(`${files}/`)) {
102
+ return { code: 'reserved-address', message: `${url} is where the library's files are served, so a page can't go there.` };
103
+ }
104
+ // M8 Decision 10: a URL the site's own code serves.
105
+ const route = codeRouteAt(url, codeRoutes);
106
+ if (route) return { code: 'reserved-address', message: codeRouteMessage(url, route) };
107
+ for (const other of collections) {
108
+ if (other.name === collection || other.urlBase === null) continue;
109
+ const base = sameAddress(other.urlBase);
110
+ if (base === '/') continue;
111
+ if (at.startsWith(`${base}/`)) {
112
+ return {
113
+ code: 'below-collection',
114
+ message: `${url} is under ${other.label} (${other.urlBase}), so it can't be a page here. Create it in ${other.label} instead.`,
115
+ };
116
+ }
117
+ }
118
+ const taken = documents.find((d) => d.url !== null && sameAddress(d.url) === at);
119
+ if (taken) return { code: 'address-taken', message: `There's already a page at ${url}.` };
120
+ return null;
121
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Text styles (Spec → Text: "defined once per site"). The same definition drives
3
+ * - the CSS (the Lockup component reads it into custom properties, and the
4
+ * integration registers its font with Astro's fonts API),
5
+ * - the measurement (measure.ts shapes text with these axes, tracking and transform),
6
+ * - and the editor's "Too long for mobile" warning (Phase 3), which compares the
7
+ * phone preview's computed size with `minSizePx` (exposed on the lockup in
8
+ * preview renders as `data-cms-min-size`).
9
+ *
10
+ * Plain data with no imports, so a site's config, components, tests and the editor
11
+ * can all import it.
12
+ */
13
+
14
+ export type TextTransform = 'none' | 'uppercase' | 'lowercase';
15
+
16
+ export interface TextStyle {
17
+ /** The CSS font-family name the fonts API registers. */
18
+ family: string;
19
+ /** The CSS variable Astro's fonts API defines for the family, e.g. `--font-display`. */
20
+ cssVariable: `--${string}`;
21
+ /** Font files, relative to the site's root. */
22
+ fontFile: {
23
+ /** The file measured at render (harfbuzzjs reads TTF/OTF, not WOFF2). */
24
+ measure: string;
25
+ /** The file the page serves, through Astro's fonts API. */
26
+ web: string;
27
+ };
28
+ /** The `@font-face` weight range of a variable web font, e.g. `"200 800"`. */
29
+ weightRange: string;
30
+ /**
31
+ * Every variable axis pinned to one value (M1 → E4). Set in CSS with
32
+ * `font-variation-settings` (plus `font-optical-sizing: none`) and used by the
33
+ * shaper, so the em width doesn't change with the font size. A static font: `{}`.
34
+ */
35
+ axes: Record<string, number>;
36
+ /** The design size in px. The fit never goes above it. */
37
+ sizePx: number;
38
+ /** Editorial minimum in px. Not a CSS floor (Spec → Text); the editor's warning reads it. */
39
+ minSizePx: number;
40
+ /** Letter-spacing in em, so it scales with the font size. */
41
+ trackingEm: number;
42
+ /** Line height, unitless. */
43
+ leading: number;
44
+ /** Applied before shaping, as the browser does. */
45
+ textTransform: TextTransform;
46
+ /** Fallback families after the web font. Default `['sans-serif']`. */
47
+ fallbacks?: string[];
48
+ }
49
+
50
+ /** A style plus its name in the site's config, and its font files resolved to absolute paths. */
51
+ export interface ResolvedTextStyle extends TextStyle {
52
+ name: string;
53
+ /** Absolute paths, resolved against the site's root by the integration. */
54
+ resolvedFontFile: { measure: string; web: string };
55
+ }
56
+
57
+ /** CSS `font-variation-settings` value for a style's pinned axes (`normal` if none). */
58
+ export function variationSettings(style: Pick<TextStyle, 'axes'>): string {
59
+ const entries = Object.entries(style.axes);
60
+ if (entries.length === 0) return 'normal';
61
+ return entries.map(([tag, value]) => `"${tag}" ${value}`).join(', ');
62
+ }
@@ -0,0 +1,28 @@
1
+ // Types for the integration's virtual modules (see index.ts). A site gets these
2
+ // through `.astro/types.d.ts` (the integration's injectTypes).
3
+
4
+ declare module 'virtual:caracms/config' {
5
+ export const site: import('./config.ts').ResolvedSite;
6
+ }
7
+
8
+ declare module 'virtual:caracms/public' {
9
+ /** M6: the site's address, Turnstile's site key and the build's kind, as the integration read them at build. */
10
+ export const build: import('./index.ts').PublicBuild;
11
+ }
12
+
13
+ declare module 'virtual:caracms/blocks' {
14
+ /** Block type → its Astro component. */
15
+ export const blocks: Record<string, (props: import('./document.ts').BlockProps<any>) => any>;
16
+ }
17
+
18
+ declare module 'virtual:caracms/page' {
19
+ /** The site's page component. */
20
+ export const Page: (props: import('./document.ts').PageProps) => any;
21
+ }
22
+
23
+ declare module 'virtual:caracms/collections' {
24
+ /** A list-of-fields collection's name → its Astro component (M5a Decision 7). */
25
+ export const components: Record<string, (props: import('./document.ts').FieldsDocumentProps<any>) => any>;
26
+ }
27
+
28
+ declare module 'virtual:caracms/lockup.css' {}