create-starlight-cms 0.9.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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/THIRD_PARTY_NOTICES.md +4 -0
  4. package/bin/index.js +147 -0
  5. package/package.json +46 -0
  6. package/template/.nvmrc +1 -0
  7. package/template/LICENSE +21 -0
  8. package/template/README.md +68 -0
  9. package/template/THIRD_PARTY_NOTICES.md +12 -0
  10. package/template/_gitignore +14 -0
  11. package/template/astro.config.mjs +26 -0
  12. package/template/migrations/0001_schema.sql +73 -0
  13. package/template/migrations/0002_publish_delivery.sql +16 -0
  14. package/template/package-lock.json.template +9123 -0
  15. package/template/package.json +89 -0
  16. package/template/scripts/build-admin.mjs +38 -0
  17. package/template/scripts/check-linux-bindings.mjs +18 -0
  18. package/template/scripts/dev.mjs +94 -0
  19. package/template/src/admin/admin.scss +182 -0
  20. package/template/src/admin/app.ts +130 -0
  21. package/template/src/admin/client.tsx +684 -0
  22. package/template/src/admin/components/tiptap-extension/node-background-extension.ts +150 -0
  23. package/template/src/admin/components/tiptap-icons/align-center-icon.tsx +38 -0
  24. package/template/src/admin/components/tiptap-icons/align-justify-icon.tsx +38 -0
  25. package/template/src/admin/components/tiptap-icons/align-left-icon.tsx +38 -0
  26. package/template/src/admin/components/tiptap-icons/align-right-icon.tsx +38 -0
  27. package/template/src/admin/components/tiptap-icons/arrow-left-icon.tsx +24 -0
  28. package/template/src/admin/components/tiptap-icons/arrow-right-icon.tsx +26 -0
  29. package/template/src/admin/components/tiptap-icons/ban-icon.tsx +26 -0
  30. package/template/src/admin/components/tiptap-icons/blockquote-icon.tsx +44 -0
  31. package/template/src/admin/components/tiptap-icons/bold-icon.tsx +26 -0
  32. package/template/src/admin/components/tiptap-icons/case-sensitive-icon.tsx +32 -0
  33. package/template/src/admin/components/tiptap-icons/check-icon.tsx +26 -0
  34. package/template/src/admin/components/tiptap-icons/chevron-down-icon.tsx +26 -0
  35. package/template/src/admin/components/tiptap-icons/chevron-up-icon.tsx +22 -0
  36. package/template/src/admin/components/tiptap-icons/close-icon.tsx +24 -0
  37. package/template/src/admin/components/tiptap-icons/code-block-icon.tsx +38 -0
  38. package/template/src/admin/components/tiptap-icons/code2-icon.tsx +32 -0
  39. package/template/src/admin/components/tiptap-icons/corner-down-left-icon.tsx +26 -0
  40. package/template/src/admin/components/tiptap-icons/external-link-icon.tsx +28 -0
  41. package/template/src/admin/components/tiptap-icons/heading-five-icon.tsx +28 -0
  42. package/template/src/admin/components/tiptap-icons/heading-four-icon.tsx +28 -0
  43. package/template/src/admin/components/tiptap-icons/heading-icon.tsx +24 -0
  44. package/template/src/admin/components/tiptap-icons/heading-one-icon.tsx +28 -0
  45. package/template/src/admin/components/tiptap-icons/heading-six-icon.tsx +30 -0
  46. package/template/src/admin/components/tiptap-icons/heading-three-icon.tsx +36 -0
  47. package/template/src/admin/components/tiptap-icons/heading-two-icon.tsx +28 -0
  48. package/template/src/admin/components/tiptap-icons/highlighter-icon.tsx +26 -0
  49. package/template/src/admin/components/tiptap-icons/image-plus-icon.tsx +26 -0
  50. package/template/src/admin/components/tiptap-icons/italic-icon.tsx +24 -0
  51. package/template/src/admin/components/tiptap-icons/link-icon.tsx +28 -0
  52. package/template/src/admin/components/tiptap-icons/list-icon.tsx +56 -0
  53. package/template/src/admin/components/tiptap-icons/list-ordered-icon.tsx +56 -0
  54. package/template/src/admin/components/tiptap-icons/list-todo-icon.tsx +50 -0
  55. package/template/src/admin/components/tiptap-icons/moon-star-icon.tsx +30 -0
  56. package/template/src/admin/components/tiptap-icons/redo2-icon.tsx +26 -0
  57. package/template/src/admin/components/tiptap-icons/search-icon.tsx +26 -0
  58. package/template/src/admin/components/tiptap-icons/strike-icon.tsx +28 -0
  59. package/template/src/admin/components/tiptap-icons/subscript-icon.tsx +38 -0
  60. package/template/src/admin/components/tiptap-icons/sun-icon.tsx +58 -0
  61. package/template/src/admin/components/tiptap-icons/superscript-icon.tsx +38 -0
  62. package/template/src/admin/components/tiptap-icons/trash-icon.tsx +26 -0
  63. package/template/src/admin/components/tiptap-icons/underline-icon.tsx +26 -0
  64. package/template/src/admin/components/tiptap-icons/undo2-icon.tsx +26 -0
  65. package/template/src/admin/components/tiptap-icons/whole-word-icon.tsx +36 -0
  66. package/template/src/admin/components/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
  67. package/template/src/admin/components/tiptap-node/code-block-node/code-block-node.scss +54 -0
  68. package/template/src/admin/components/tiptap-node/heading-node/heading-node.scss +45 -0
  69. package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +14 -0
  70. package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
  71. package/template/src/admin/components/tiptap-node/image-node/image-node.scss +35 -0
  72. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node-extension.ts +161 -0
  73. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
  74. package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.tsx +554 -0
  75. package/template/src/admin/components/tiptap-node/image-upload-node/index.tsx +1 -0
  76. package/template/src/admin/components/tiptap-node/list-node/list-node.scss +208 -0
  77. package/template/src/admin/components/tiptap-node/paragraph-node/paragraph-node.scss +217 -0
  78. package/template/src/admin/components/tiptap-templates/simple/NOTICE.md +14 -0
  79. package/template/src/admin/components/tiptap-templates/simple/data/content.json +477 -0
  80. package/template/src/admin/components/tiptap-templates/simple/simple-editor.scss +97 -0
  81. package/template/src/admin/components/tiptap-templates/simple/simple-editor.tsx +348 -0
  82. package/template/src/admin/components/tiptap-templates/simple/theme-toggle.tsx +44 -0
  83. package/template/src/admin/components/tiptap-ui/blockquote-button/blockquote-button.tsx +122 -0
  84. package/template/src/admin/components/tiptap-ui/blockquote-button/index.tsx +2 -0
  85. package/template/src/admin/components/tiptap-ui/blockquote-button/use-blockquote.ts +273 -0
  86. package/template/src/admin/components/tiptap-ui/code-block-button/code-block-button.tsx +122 -0
  87. package/template/src/admin/components/tiptap-ui/code-block-button/index.tsx +2 -0
  88. package/template/src/admin/components/tiptap-ui/code-block-button/use-code-block.ts +283 -0
  89. package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
  90. package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx +170 -0
  91. package/template/src/admin/components/tiptap-ui/color-highlight-button/index.tsx +2 -0
  92. package/template/src/admin/components/tiptap-ui/color-highlight-button/use-color-highlight.ts +380 -0
  93. package/template/src/admin/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +231 -0
  94. package/template/src/admin/components/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  95. package/template/src/admin/components/tiptap-ui/heading-button/heading-button.tsx +124 -0
  96. package/template/src/admin/components/tiptap-ui/heading-button/index.tsx +2 -0
  97. package/template/src/admin/components/tiptap-ui/heading-button/use-heading.ts +348 -0
  98. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +128 -0
  99. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
  100. package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +132 -0
  101. package/template/src/admin/components/tiptap-ui/image-upload-button/image-upload-button.tsx +130 -0
  102. package/template/src/admin/components/tiptap-ui/image-upload-button/index.tsx +2 -0
  103. package/template/src/admin/components/tiptap-ui/image-upload-button/use-image-upload.ts +197 -0
  104. package/template/src/admin/components/tiptap-ui/link-popover/index.tsx +2 -0
  105. package/template/src/admin/components/tiptap-ui/link-popover/link-popover.scss +16 -0
  106. package/template/src/admin/components/tiptap-ui/link-popover/link-popover.tsx +315 -0
  107. package/template/src/admin/components/tiptap-ui/link-popover/use-link-popover.ts +297 -0
  108. package/template/src/admin/components/tiptap-ui/list-button/index.tsx +2 -0
  109. package/template/src/admin/components/tiptap-ui/list-button/list-button.tsx +120 -0
  110. package/template/src/admin/components/tiptap-ui/list-button/use-list.ts +353 -0
  111. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/index.tsx +2 -0
  112. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +126 -0
  113. package/template/src/admin/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +220 -0
  114. package/template/src/admin/components/tiptap-ui/mark-button/index.tsx +2 -0
  115. package/template/src/admin/components/tiptap-ui/mark-button/mark-button.tsx +122 -0
  116. package/template/src/admin/components/tiptap-ui/mark-button/use-mark.ts +219 -0
  117. package/template/src/admin/components/tiptap-ui/search-and-replace/index.tsx +2 -0
  118. package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.scss +300 -0
  119. package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.tsx +718 -0
  120. package/template/src/admin/components/tiptap-ui/search-and-replace/use-search-and-replace.ts +528 -0
  121. package/template/src/admin/components/tiptap-ui/text-align-button/index.tsx +2 -0
  122. package/template/src/admin/components/tiptap-ui/text-align-button/text-align-button.tsx +144 -0
  123. package/template/src/admin/components/tiptap-ui/text-align-button/use-text-align.ts +237 -0
  124. package/template/src/admin/components/tiptap-ui/undo-redo-button/index.tsx +2 -0
  125. package/template/src/admin/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx +125 -0
  126. package/template/src/admin/components/tiptap-ui/undo-redo-button/use-undo-redo.ts +200 -0
  127. package/template/src/admin/components/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
  128. package/template/src/admin/components/tiptap-ui-primitive/badge/badge-group.scss +16 -0
  129. package/template/src/admin/components/tiptap-ui-primitive/badge/badge.scss +99 -0
  130. package/template/src/admin/components/tiptap-ui-primitive/badge/badge.tsx +44 -0
  131. package/template/src/admin/components/tiptap-ui-primitive/badge/index.tsx +1 -0
  132. package/template/src/admin/components/tiptap-ui-primitive/button/button-colors.scss +613 -0
  133. package/template/src/admin/components/tiptap-ui-primitive/button/button.scss +390 -0
  134. package/template/src/admin/components/tiptap-ui-primitive/button/button.tsx +158 -0
  135. package/template/src/admin/components/tiptap-ui-primitive/button/index.tsx +1 -0
  136. package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.scss +89 -0
  137. package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.tsx +72 -0
  138. package/template/src/admin/components/tiptap-ui-primitive/button-group/index.tsx +1 -0
  139. package/template/src/admin/components/tiptap-ui-primitive/card/card.scss +74 -0
  140. package/template/src/admin/components/tiptap-ui-primitive/card/card.tsx +79 -0
  141. package/template/src/admin/components/tiptap-ui-primitive/card/index.tsx +1 -0
  142. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +152 -0
  143. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +262 -0
  144. package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
  145. package/template/src/admin/components/tiptap-ui-primitive/input/index.tsx +1 -0
  146. package/template/src/admin/components/tiptap-ui-primitive/input/input.scss +41 -0
  147. package/template/src/admin/components/tiptap-ui-primitive/input/input.tsx +22 -0
  148. package/template/src/admin/components/tiptap-ui-primitive/input-group/index.tsx +1 -0
  149. package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.scss +225 -0
  150. package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.tsx +135 -0
  151. package/template/src/admin/components/tiptap-ui-primitive/popover/index.tsx +1 -0
  152. package/template/src/admin/components/tiptap-ui-primitive/popover/popover.scss +64 -0
  153. package/template/src/admin/components/tiptap-ui-primitive/popover/popover.tsx +35 -0
  154. package/template/src/admin/components/tiptap-ui-primitive/separator/index.tsx +1 -0
  155. package/template/src/admin/components/tiptap-ui-primitive/separator/separator.scss +23 -0
  156. package/template/src/admin/components/tiptap-ui-primitive/separator/separator.tsx +28 -0
  157. package/template/src/admin/components/tiptap-ui-primitive/spacer/index.tsx +1 -0
  158. package/template/src/admin/components/tiptap-ui-primitive/spacer/spacer.tsx +24 -0
  159. package/template/src/admin/components/tiptap-ui-primitive/switch/index.tsx +1 -0
  160. package/template/src/admin/components/tiptap-ui-primitive/switch/switch.scss +86 -0
  161. package/template/src/admin/components/tiptap-ui-primitive/switch/switch.tsx +29 -0
  162. package/template/src/admin/components/tiptap-ui-primitive/textarea/index.tsx +1 -0
  163. package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.scss +42 -0
  164. package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.tsx +16 -0
  165. package/template/src/admin/components/tiptap-ui-primitive/toolbar/index.tsx +1 -0
  166. package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
  167. package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.tsx +203 -0
  168. package/template/src/admin/components/tiptap-ui-primitive/tooltip/index.tsx +1 -0
  169. package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
  170. package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.tsx +241 -0
  171. package/template/src/admin/hooks/use-composed-ref.ts +46 -0
  172. package/template/src/admin/hooks/use-cursor-visibility.ts +69 -0
  173. package/template/src/admin/hooks/use-element-rect.ts +166 -0
  174. package/template/src/admin/hooks/use-is-breakpoint.ts +35 -0
  175. package/template/src/admin/hooks/use-menu-navigation.ts +204 -0
  176. package/template/src/admin/hooks/use-scrolling.ts +75 -0
  177. package/template/src/admin/hooks/use-throttled-callback.ts +48 -0
  178. package/template/src/admin/hooks/use-tiptap-editor.ts +69 -0
  179. package/template/src/admin/hooks/use-unmount.ts +21 -0
  180. package/template/src/admin/hooks/use-window-size.ts +93 -0
  181. package/template/src/admin/html.ts +17 -0
  182. package/template/src/admin/lib/tiptap-utils.ts +652 -0
  183. package/template/src/admin/navigation-tree.tsx +82 -0
  184. package/template/src/admin/preview.ts +73 -0
  185. package/template/src/admin/styles/_cms-theme.scss +58 -0
  186. package/template/src/admin/styles/_keyframe-animations.scss +91 -0
  187. package/template/src/admin/styles/_variables.scss +298 -0
  188. package/template/src/assets/favicon.svg +1 -0
  189. package/template/src/assets/logo.svg +1 -0
  190. package/template/src/astro-env.d.ts +1 -0
  191. package/template/src/content.config.ts +12 -0
  192. package/template/src/db/bootstrap.ts +36 -0
  193. package/template/src/db/client.ts +7 -0
  194. package/template/src/db/schema.ts +45 -0
  195. package/template/src/documents/service.ts +178 -0
  196. package/template/src/documents/validation.ts +9 -0
  197. package/template/src/env.ts +9 -0
  198. package/template/src/index.ts +10 -0
  199. package/template/src/locales.ts +10 -0
  200. package/template/src/media/service.ts +120 -0
  201. package/template/src/navigation/service.ts +171 -0
  202. package/template/src/pages/[locale]/cms-preview-shell.astro +12 -0
  203. package/template/src/pages/cms-preview-shell.astro +5 -0
  204. package/template/src/publish/deploy-hook.ts +42 -0
  205. package/template/src/publish/record.ts +20 -0
  206. package/template/src/publish/service.ts +135 -0
  207. package/template/src/scss.d.ts +5 -0
  208. package/template/src/site.config.ts +7 -0
  209. package/template/src/sql.d.ts +4 -0
  210. package/template/src/starlight/cms-origin.ts +4 -0
  211. package/template/src/starlight/loader.ts +21 -0
  212. package/template/src/starlight/preview-render.ts +162 -0
  213. package/template/src/starlight/preview-shell.astro +14 -0
  214. package/template/src/starlight/render.ts +59 -0
  215. package/template/src/starlight/schema.ts +34 -0
  216. package/template/src/starlight/sidebar.ts +35 -0
  217. package/template/src/starlight/snapshot.ts +47 -0
  218. package/template/src/starlight/source.ts +26 -0
  219. package/template/src/starlight/tiptap.ts +29 -0
  220. package/template/src/styles/starlight.css +12 -0
  221. package/template/test-files/content.test.template.ts +82 -0
  222. package/template/test-files/deploy-hook.test.template.ts +32 -0
  223. package/template/test-files/media.test.template.ts +39 -0
  224. package/template/test-files/publishing.integration.test.template.ts +156 -0
  225. package/template/tsconfig.admin.json +18 -0
  226. package/template/tsconfig.json +23 -0
  227. package/template/worker-configuration.d.ts +15389 -0
  228. package/template/wrangler.jsonc +26 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cloudflare-starlight-cms contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # create-starlight-cms
2
+
3
+ Creates a self-hosted Astro Starlight documentation CMS for Cloudflare Workers.
4
+
5
+ ```sh
6
+ npx create-starlight-cms@latest my-docs
7
+ cd my-docs
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Use `.` to create a project in the current empty directory. The CLI never installs dependencies, initializes Git, deploys, or accesses a Cloudflare account.
13
+
14
+ The normalized project name also becomes the generated `wrangler.jsonc` Worker name. `DB` and `MEDIA` are binding-only definitions, so Cloudflare provisions the D1 database and R2 bucket on the first deployment.
15
+
16
+ The generated project is an independent copy of the CMS template. Updating this CLI affects new projects only.
17
+
18
+ Requires Node.js 22.19 or later. See the generated project's README for Cloudflare setup, including its one-time custom domain, R2 public URL, Access, and Deploy Hook configuration.
@@ -0,0 +1,4 @@
1
+ # Third-party notices
2
+
3
+ The generated project includes the cloudflare-starlight-cms source. Its third-party
4
+ notices are copied into the generated project's root directory.
package/bin/index.js ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { constants } from 'node:fs';
4
+ import { access, copyFile, lstat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
5
+ import { basename, dirname, join, relative, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9
+ const templateRoot = join(packageRoot, 'template');
10
+ const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));
11
+
12
+ const usage = `Usage: npx create-starlight-cms@latest <directory>\n npm create starlight-cms@latest <directory>\n\nCreate a self-hosted Astro Starlight CMS project. Use . for the current directory.\nThe destination must be new or empty (apart from .git and .DS_Store).\n\nOptions:\n --help Show this help message\n --version Show the CLI version\n`;
13
+
14
+ function projectNameFromDirectory(directory) {
15
+ const normalized = basename(directory)
16
+ .normalize('NFKD')
17
+ .replace(/[\u0300-\u036f]/g, '')
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9._-]+/g, '-')
20
+ .replace(/^[._-]+|[._-]+$/g, '');
21
+ return normalized && /^[a-z0-9]/.test(normalized) ? normalized : 'my-docs';
22
+ }
23
+
24
+ function workerNameFromProjectName(projectName) {
25
+ const workerName = projectName
26
+ .replace(/[._]/g, '-')
27
+ .replace(/-+/g, '-')
28
+ .replace(/^-+|-+$/g, '')
29
+ .slice(0, 63)
30
+ .replace(/-+$/g, '');
31
+ return workerName || 'my-docs';
32
+ }
33
+
34
+ async function exists(path) {
35
+ try {
36
+ await access(path, constants.F_OK);
37
+ return true;
38
+ } catch (error) {
39
+ if (error.code === 'ENOENT') return false;
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ async function assertDirectoryIsReady(destination) {
45
+ if (!await exists(destination)) return;
46
+ const stats = await lstat(destination);
47
+ if (stats.isSymbolicLink()) throw new Error('The destination cannot be a symbolic link.');
48
+ if (!stats.isDirectory()) throw new Error('The destination must be a directory.');
49
+
50
+ const unexpected = (await readdir(destination)).filter((entry) => entry !== '.git' && entry !== '.DS_Store');
51
+ if (unexpected.length > 0) throw new Error(`The destination is not empty: ${unexpected[0]}`);
52
+ }
53
+
54
+ async function assertNoSymlink(path) {
55
+ const stats = await lstat(path);
56
+ if (stats.isSymbolicLink()) throw new Error(`The packaged template contains a symbolic link: ${path}`);
57
+ if (!stats.isDirectory()) return;
58
+ for (const entry of await readdir(path)) await assertNoSymlink(join(path, entry));
59
+ }
60
+
61
+ async function assertDoesNotExist(path) {
62
+ if (await exists(path)) throw new Error(`Refusing to overwrite ${path}`);
63
+ }
64
+
65
+ async function copyDirectory(source, destination, isRoot = false) {
66
+ await mkdir(destination, { recursive: true });
67
+ for (const name of await readdir(source)) {
68
+ const restoredName = name.endsWith('.test.template.ts') ? name.replace('.test.template.ts', '.test.ts') : name;
69
+ const targetName = isRoot && restoredName === '_gitignore'
70
+ ? '.gitignore'
71
+ : isRoot && restoredName === 'package-lock.json.template'
72
+ ? 'package-lock.json'
73
+ : isRoot && restoredName === 'test-files'
74
+ ? 'tests'
75
+ : restoredName;
76
+ const sourcePath = join(source, name);
77
+ const destinationPath = join(destination, targetName);
78
+ const stats = await lstat(sourcePath);
79
+ if (stats.isSymbolicLink()) throw new Error(`The packaged template contains a symbolic link: ${sourcePath}`);
80
+ if (stats.isDirectory()) {
81
+ await assertDoesNotExist(destinationPath);
82
+ await copyDirectory(sourcePath, destinationPath);
83
+ } else {
84
+ await assertDoesNotExist(destinationPath);
85
+ await copyFile(sourcePath, destinationPath, constants.COPYFILE_EXCL);
86
+ }
87
+ }
88
+ }
89
+
90
+ async function configurePackage(destination, projectName) {
91
+ const packagePath = join(destination, 'package.json');
92
+ const projectPackage = JSON.parse(await readFile(packagePath, 'utf8'));
93
+ projectPackage.name = projectName;
94
+ await writeFile(packagePath, `${JSON.stringify(projectPackage, null, 2)}\n`);
95
+
96
+ const lockPath = join(destination, 'package-lock.json');
97
+ const lockfile = JSON.parse(await readFile(lockPath, 'utf8'));
98
+ lockfile.name = projectName;
99
+ if (lockfile.packages?.['']) lockfile.packages[''].name = projectName;
100
+ await writeFile(lockPath, `${JSON.stringify(lockfile, null, 2)}\n`);
101
+ }
102
+
103
+ async function configureWrangler(destination, workerName) {
104
+ const wranglerPath = join(destination, 'wrangler.jsonc');
105
+ const wranglerConfig = await readFile(wranglerPath, 'utf8');
106
+ const configured = wranglerConfig.replace(
107
+ /("name"\s*:\s*)"[^"]+"/,
108
+ `$1"${workerName}"`,
109
+ );
110
+ if (!/"name"\s*:\s*"[^"]+"/.test(wranglerConfig)) throw new Error('The packaged template has no Worker name to configure.');
111
+ await writeFile(wranglerPath, configured);
112
+ }
113
+
114
+ function printSuccess(destination) {
115
+ const relativeDestination = relative(process.cwd(), destination) || '.';
116
+ const directoryForCommand = relativeDestination === '.' || relativeDestination.startsWith('..')
117
+ ? destination
118
+ : relativeDestination;
119
+ const changeDirectory = relativeDestination === '.' ? '' : `\n cd ${directoryForCommand}`;
120
+ console.log(`\nCreated ${basename(destination)}.\n\nNext steps:${changeDirectory}\n npm install\n npm run dev\n\nThen edit src/site.config.ts and follow README.md for Cloudflare setup.\n`);
121
+ }
122
+
123
+ const [argument] = process.argv.slice(2);
124
+ if (argument === '--help' || argument === '-h') {
125
+ process.stdout.write(usage);
126
+ } else if (argument === '--version' || argument === '-v') {
127
+ console.log(packageJson.version);
128
+ } else if (!argument || argument.startsWith('-') || process.argv.length !== 3) {
129
+ process.stderr.write(usage);
130
+ process.exitCode = 1;
131
+ } else {
132
+ const destination = resolve(process.cwd(), argument);
133
+ try {
134
+ if (!await exists(templateRoot)) throw new Error('The packaged template is missing. Reinstall the CLI package.');
135
+ await assertNoSymlink(templateRoot);
136
+ await assertDirectoryIsReady(destination);
137
+ await mkdir(destination, { recursive: true });
138
+ await copyDirectory(templateRoot, destination, true);
139
+ const projectName = projectNameFromDirectory(destination);
140
+ await configurePackage(destination, projectName);
141
+ await configureWrangler(destination, workerNameFromProjectName(projectName));
142
+ printSuccess(destination);
143
+ } catch (error) {
144
+ console.error(`Could not create project: ${error.message}`);
145
+ process.exitCode = 1;
146
+ }
147
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "create-starlight-cms",
3
+ "version": "0.9.0",
4
+ "description": "Create a self-hosted Astro Starlight documentation CMS for Cloudflare Workers.",
5
+ "keywords": [
6
+ "astro",
7
+ "cloudflare",
8
+ "cms",
9
+ "docs",
10
+ "starlight",
11
+ "workers"
12
+ ],
13
+ "homepage": "https://github.com/KobayashiRui/cloudflare-starlight-cms#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/KobayashiRui/cloudflare-starlight-cms.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/KobayashiRui/cloudflare-starlight-cms/issues"
20
+ },
21
+ "license": "MIT",
22
+ "type": "module",
23
+ "bin": {
24
+ "create-starlight-cms": "bin/index.js"
25
+ },
26
+ "files": [
27
+ "bin",
28
+ "template",
29
+ "README.md",
30
+ "LICENSE",
31
+ "THIRD_PARTY_NOTICES.md"
32
+ ],
33
+ "scripts": {
34
+ "prepack": "node ../../scripts/prepare-create-template.mjs",
35
+ "release:check": "node ../../scripts/release-check.mjs",
36
+ "prepublishOnly": "npm run release:check",
37
+ "publish:public": "npm publish --access public",
38
+ "release:dry-run": "npm publish --dry-run --access public"
39
+ },
40
+ "engines": {
41
+ "node": ">=22.19.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
@@ -0,0 +1 @@
1
+ 22
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cloudflare-starlight-cms contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,68 @@
1
+ <p>
2
+ <img src="./src/assets/logo.svg" alt="Cloudflare Starlight CMS" width="300">
3
+ </p>
4
+
5
+ # My Docs
6
+
7
+ A documentation site created with cloudflare-starlight-cms.
8
+
9
+ Not an official Cloudflare or Astro project. Licensed under MIT.
10
+
11
+ ## Quick start
12
+
13
+ Install dependencies and start local development:
14
+
15
+ ```sh
16
+ npm install
17
+ npm run dev
18
+ ```
19
+
20
+ The local Admin is at `http://127.0.0.1:8787/admin/`; static Docs are at `http://127.0.0.1:4321/`. Local D1 and R2 use Miniflare and need no Cloudflare account.
21
+
22
+ The CLI writes the normalized project name to `wrangler.jsonc` as the Worker name. Its `DB` and `MEDIA` bindings have no resource IDs or names, so Cloudflare provisions their D1 database and R2 bucket during the first deploy.
23
+
24
+ ## Included
25
+
26
+ - React Admin with Tiptap Simple Editor, drafts, revisions, preview, navigation tree, and locale-aware documents
27
+ - D1 for content, R2 for PNG/JPEG/WebP/AVIF/MP4/WebM, and a media picker with safe unused-asset deletion
28
+ - Astro Starlight, Pagefind, and Workers Static Assets for fully static public Docs
29
+ - Cloudflare Access for `/admin/*`; no CMS users, passwords, or roles
30
+ - Publish → Workers Deploy Hook → Workers Builds
31
+
32
+ Replace [`src/assets/logo.svg`](src/assets/logo.svg) and [`src/assets/favicon.svg`](src/assets/favicon.svg) to brand a project. Edit Admin theme tokens in [`src/admin/styles/_cms-theme.scss`](src/admin/styles/_cms-theme.scss) and Starlight accents in [`src/styles/starlight.css`](src/styles/starlight.css).
33
+
34
+ ## Production setup
35
+
36
+ 1. Set the Workers Builds build command to `npm run build` and keep the default deploy command `npx wrangler deploy`. Disable non-production branch builds. With no public URL yet, the first build deploys an empty site and Admin Worker and provisions D1/R2. The first management API, export, or preview request applies the bundled initial schema automatically.
37
+ 2. Before attaching the custom domain to the Worker, create one Access Self-hosted Application for the planned hostname and `admin/*` path, for example `docs.example.com/admin/*`. Add the human `Allow` policy. The hostname must belong to an active Cloudflare zone, but it does not need to be attached to the Worker yet.
38
+ 3. Attach the custom domain to the Worker, then set [`src/site.config.ts`](src/site.config.ts)'s `url` to its origin, such as `https://docs.example.com`, and commit it. Admin routes are protected from their first custom-domain request. Builds derive both the Astro site URL and CMS snapshot endpoint from this source-controlled value.
39
+ 4. Add a Workers Builds `Service Auth` policy to that same Access Application. Add the Service Token's `CF_ACCESS_CLIENT_ID` and `CF_ACCESS_CLIENT_SECRET` to Workers Builds Build Variables and Secrets.
40
+ 5. After the bucket is provisioned, set `MEDIA_PUBLIC_URL` in [`wrangler.jsonc`](wrangler.jsonc) to the R2 public/custom-domain origin and commit it. It is public configuration, not a secret. Add the Workers Builds Deploy Hook URL as the production Worker runtime secret `WORKERS_DEPLOY_HOOK_URL` in **Worker → Settings → Variables and Secrets**. Do not add it to the Workers Builds variables: build variables are not available to the running CMS Worker. From a local authenticated terminal, the equivalent is:
41
+
42
+ ```sh
43
+ npx wrangler secret put WORKERS_DEPLOY_HOOK_URL
44
+ ```
45
+
46
+ Publishing updates the public revision and requests a build. A successful Hook request means the build was accepted, not that it has deployed. Without a Hook, local publishing still rebuilds the local static Docs.
47
+
48
+ Use a root-level document with the URL segment `index` as the homepage. It is a regular CMS document and publishes at `/`; its translations publish at their locale roots, such as `/ja/`.
49
+
50
+ For a custom landing page instead, do not create that CMS `index` document. Add `src/pages/index.astro` to the generated project: Astro owns `/`, while CMS documents remain available at their own paths such as `/getting-started/`.
51
+
52
+ Initialization records the two bundled CREATE migrations in `d1_migrations`, shared with Wrangler. Failed initialization returns 503 and can be retried. Future ALTER/backfill migrations require an explicit upgrade procedure; adding a SQL file does not automatically enable runtime execution. Keep production data when upgrading.
53
+
54
+ If provisioning reports an existing D1 after deletion, check Cloudflare Audit Logs for `DeleteDatabase` and subsequent `CreateDatabase` events and check overlapping builds. This error happens before schema initialization. Do not repeatedly delete databases or rename the Worker as a recovery procedure. Successful existing bindings are inherited by Wrangler; recovery of an unbound resource after a failed first deploy needs account-level verification.
55
+
56
+ ## Commands
57
+
58
+ ```sh
59
+ npm run dev
60
+ npm run check
61
+ npm test
62
+ npm run build:empty
63
+ npm run dry-run
64
+ ```
65
+
66
+ With an empty `siteConfig.url`, `build` creates the explicit empty site for an initial deploy. `build:empty` is available when an intentionally empty local build is needed. `dry-run` validates the Worker and Static Assets configuration without deploying.
67
+
68
+ Configure the site title, locales, and public URL in `src/site.config.ts`. CMS updates are not automatic; selectively bring the changes you need into this project.
@@ -0,0 +1,12 @@
1
+ # Third-party notices
2
+
3
+ ## Headless Tree comprehensive drag-and-drop example
4
+
5
+ The Navigation Tree markup and styling are adapted from the Headless Tree
6
+ Comprehensive Drag-and-Drop story:
7
+ https://github.com/lukasbach/headless-tree/tree/main/packages/sb-react/src/dnd/comprehensive.stories.tsx
8
+
9
+ Copyright (c) 2023 Lukas Bach. Licensed under the MIT License.
10
+
11
+ The MIT License is included with the Headless Tree package and at:
12
+ https://github.com/lukasbach/headless-tree/blob/main/LICENSE
@@ -0,0 +1,14 @@
1
+ node_modules/
2
+ dist/
3
+ .astro/
4
+ .wrangler/
5
+ .dev-assets/
6
+ packages/create-starlight-cms/template/
7
+ .env
8
+ .env.*
9
+ .dev.vars
10
+ .dev.vars.*
11
+ !.dev.vars.example
12
+ coverage/
13
+ *.tsbuildinfo
14
+ .DS_Store
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'astro/config';
2
+ import starlight from '@astrojs/starlight';
3
+ import { siteConfig } from './src/site.config.ts';
4
+ import { cmsOrigin } from './src/starlight/cms-origin.ts';
5
+ import { loadSnapshot } from './src/starlight/source.ts';
6
+ import { cmsSidebar } from './src/starlight/sidebar.ts';
7
+
8
+ export default defineConfig({
9
+ site: cmsOrigin || undefined,
10
+ output: 'static',
11
+ integrations: [starlight({
12
+ title: siteConfig.title,
13
+ logo: {
14
+ src: './src/assets/logo.svg',
15
+ alt: `${siteConfig.title} logo`,
16
+ replacesTitle: true,
17
+ },
18
+ favicon: '/favicon.svg',
19
+ customCss: ['./src/styles/starlight.css'],
20
+ defaultLocale: 'root',
21
+ locales: Object.fromEntries(siteConfig.locales.map(({ code, label }) => [
22
+ code === siteConfig.defaultLocale ? 'root' : code, { label, lang: code },
23
+ ])),
24
+ sidebar: process.argv.includes('preview') ? [] : cmsSidebar(await loadSnapshot()),
25
+ })],
26
+ });
@@ -0,0 +1,73 @@
1
+ CREATE TABLE IF NOT EXISTS folder (
2
+ id TEXT PRIMARY KEY,
3
+ parent_id TEXT REFERENCES folder(id) ON DELETE RESTRICT,
4
+ slug TEXT NOT NULL,
5
+ sort_order INTEGER NOT NULL DEFAULT 0,
6
+ created_at INTEGER NOT NULL,
7
+ updated_at INTEGER NOT NULL
8
+ );
9
+
10
+ CREATE TABLE IF NOT EXISTS folder_translation (
11
+ folder_id TEXT NOT NULL REFERENCES folder(id) ON DELETE CASCADE,
12
+ locale TEXT NOT NULL,
13
+ name TEXT NOT NULL,
14
+ created_at INTEGER NOT NULL,
15
+ updated_at INTEGER NOT NULL,
16
+ PRIMARY KEY (folder_id, locale)
17
+ );
18
+
19
+ CREATE TABLE IF NOT EXISTS document (
20
+ id TEXT PRIMARY KEY,
21
+ folder_id TEXT REFERENCES folder(id) ON DELETE RESTRICT,
22
+ slug TEXT NOT NULL,
23
+ sort_order INTEGER NOT NULL DEFAULT 0,
24
+ created_at INTEGER NOT NULL,
25
+ updated_at INTEGER NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS document_translation (
29
+ id TEXT PRIMARY KEY,
30
+ document_id TEXT NOT NULL REFERENCES document(id) ON DELETE CASCADE,
31
+ locale TEXT NOT NULL,
32
+ title TEXT NOT NULL,
33
+ sidebar_label TEXT,
34
+ description TEXT NOT NULL DEFAULT '',
35
+ content_json TEXT NOT NULL,
36
+ published_revision_id TEXT,
37
+ version INTEGER NOT NULL DEFAULT 1,
38
+ created_at INTEGER NOT NULL,
39
+ updated_at INTEGER NOT NULL,
40
+ published_at INTEGER,
41
+ UNIQUE (document_id, locale)
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS document_revision (
45
+ id TEXT PRIMARY KEY,
46
+ document_translation_id TEXT NOT NULL REFERENCES document_translation(id) ON DELETE CASCADE,
47
+ revision INTEGER NOT NULL,
48
+ title TEXT NOT NULL,
49
+ sidebar_label TEXT,
50
+ description TEXT NOT NULL DEFAULT '',
51
+ content_json TEXT NOT NULL,
52
+ created_at INTEGER NOT NULL,
53
+ UNIQUE(document_translation_id, revision)
54
+ );
55
+
56
+ CREATE TABLE IF NOT EXISTS media (
57
+ id TEXT PRIMARY KEY,
58
+ object_key TEXT NOT NULL UNIQUE,
59
+ file_name TEXT NOT NULL,
60
+ content_type TEXT NOT NULL,
61
+ size INTEGER NOT NULL,
62
+ created_at INTEGER NOT NULL
63
+ );
64
+
65
+ CREATE UNIQUE INDEX IF NOT EXISTS folder_root_slug_unique ON folder(slug) WHERE parent_id IS NULL;
66
+ CREATE UNIQUE INDEX IF NOT EXISTS folder_child_slug_unique ON folder(parent_id, slug) WHERE parent_id IS NOT NULL;
67
+ CREATE UNIQUE INDEX IF NOT EXISTS document_root_slug_unique ON document(slug) WHERE folder_id IS NULL;
68
+ CREATE UNIQUE INDEX IF NOT EXISTS document_folder_slug_unique ON document(folder_id, slug) WHERE folder_id IS NOT NULL;
69
+ CREATE INDEX IF NOT EXISTS folder_parent_order_idx ON folder(parent_id, sort_order);
70
+ CREATE INDEX IF NOT EXISTS document_folder_order_idx ON document(folder_id, sort_order);
71
+ CREATE INDEX IF NOT EXISTS folder_translation_locale_idx ON folder_translation(locale, name);
72
+ CREATE INDEX IF NOT EXISTS document_translation_locale_idx ON document_translation(locale, title);
73
+ CREATE INDEX IF NOT EXISTS document_revision_translation_idx ON document_revision(document_translation_id, revision DESC);
@@ -0,0 +1,16 @@
1
+ CREATE TABLE IF NOT EXISTS publish_delivery (
2
+ id TEXT PRIMARY KEY,
3
+ trigger_kind TEXT NOT NULL CHECK (trigger_kind IN ('document', 'site')),
4
+ document_translation_id TEXT REFERENCES document_translation(id) ON DELETE SET NULL,
5
+ status TEXT NOT NULL CHECK (status IN ('pending', 'accepted', 'failed', 'skipped')),
6
+ attempts INTEGER NOT NULL DEFAULT 0,
7
+ build_id TEXT,
8
+ already_exists INTEGER NOT NULL DEFAULT 0,
9
+ last_error TEXT,
10
+ requested_at INTEGER NOT NULL,
11
+ accepted_at INTEGER,
12
+ next_retry_at INTEGER
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS publish_delivery_recent_idx ON publish_delivery(requested_at DESC);
16
+ CREATE INDEX IF NOT EXISTS publish_delivery_retry_idx ON publish_delivery(status, next_retry_at);