@notionx/create-notionx-app 1.0.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 (239) hide show
  1. package/README.md +139 -0
  2. package/dist/answers.js +332 -0
  3. package/dist/answers.js.map +1 -0
  4. package/dist/cli-notionx.js +388 -0
  5. package/dist/cli-notionx.js.map +1 -0
  6. package/dist/cli-notionx.test.js +277 -0
  7. package/dist/cli-notionx.test.js.map +1 -0
  8. package/dist/diff.js +40 -0
  9. package/dist/diff.js.map +1 -0
  10. package/dist/diff.test.js +90 -0
  11. package/dist/diff.test.js.map +1 -0
  12. package/dist/index.js +99 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/locale-add/apply.js +39 -0
  15. package/dist/locale-add/apply.js.map +1 -0
  16. package/dist/locale-add/format.js +38 -0
  17. package/dist/locale-add/format.js.map +1 -0
  18. package/dist/locale-add/list.js +44 -0
  19. package/dist/locale-add/list.js.map +1 -0
  20. package/dist/locale-add/list.test.js +45 -0
  21. package/dist/locale-add/list.test.js.map +1 -0
  22. package/dist/locale-add/plan.js +128 -0
  23. package/dist/locale-add/plan.js.map +1 -0
  24. package/dist/locale-add/validate.js +46 -0
  25. package/dist/locale-add/validate.js.map +1 -0
  26. package/dist/metadata.js +41 -0
  27. package/dist/metadata.js.map +1 -0
  28. package/dist/notion-translation-sources/apply.js +61 -0
  29. package/dist/notion-translation-sources/apply.js.map +1 -0
  30. package/dist/notion-translation-sources/index.js +3 -0
  31. package/dist/notion-translation-sources/index.js.map +1 -0
  32. package/dist/notion-translation-sources/plan.js +33 -0
  33. package/dist/notion-translation-sources/plan.js.map +1 -0
  34. package/dist/notionx-source.js +142 -0
  35. package/dist/notionx-source.js.map +1 -0
  36. package/dist/notionx-source.test.js +144 -0
  37. package/dist/notionx-source.test.js.map +1 -0
  38. package/dist/password.js +18 -0
  39. package/dist/password.js.map +1 -0
  40. package/dist/presets.js +83 -0
  41. package/dist/presets.js.map +1 -0
  42. package/dist/presets.test.js +50 -0
  43. package/dist/presets.test.js.map +1 -0
  44. package/dist/prompt.js +218 -0
  45. package/dist/prompt.js.map +1 -0
  46. package/dist/provision/cloudflare.js +236 -0
  47. package/dist/provision/cloudflare.js.map +1 -0
  48. package/dist/provision/dependencies.js +219 -0
  49. package/dist/provision/dependencies.js.map +1 -0
  50. package/dist/provision/index.js +681 -0
  51. package/dist/provision/index.js.map +1 -0
  52. package/dist/provision/index.test.js +54 -0
  53. package/dist/provision/index.test.js.map +1 -0
  54. package/dist/provision/inspect.js +109 -0
  55. package/dist/provision/inspect.js.map +1 -0
  56. package/dist/provision/inspect.test.js +75 -0
  57. package/dist/provision/inspect.test.js.map +1 -0
  58. package/dist/provision/notion.js +1981 -0
  59. package/dist/provision/notion.js.map +1 -0
  60. package/dist/provision/notion.test.js +542 -0
  61. package/dist/provision/notion.test.js.map +1 -0
  62. package/dist/provision/ntn-credentials.js +198 -0
  63. package/dist/provision/ntn-credentials.js.map +1 -0
  64. package/dist/provision/options.js +15 -0
  65. package/dist/provision/options.js.map +1 -0
  66. package/dist/provision/password-hash.js +78 -0
  67. package/dist/provision/password-hash.js.map +1 -0
  68. package/dist/provision/prompts.js +115 -0
  69. package/dist/provision/prompts.js.map +1 -0
  70. package/dist/provision/repair.js +48 -0
  71. package/dist/provision/repair.js.map +1 -0
  72. package/dist/provision/repair.test.js +141 -0
  73. package/dist/provision/repair.test.js.map +1 -0
  74. package/dist/provision/shell.js +84 -0
  75. package/dist/provision/shell.js.map +1 -0
  76. package/dist/provision/wire.js +78 -0
  77. package/dist/provision/wire.js.map +1 -0
  78. package/dist/registry/doctor.js +181 -0
  79. package/dist/registry/doctor.js.map +1 -0
  80. package/dist/registry/doctor.test.js +180 -0
  81. package/dist/registry/doctor.test.js.map +1 -0
  82. package/dist/registry/install.js +217 -0
  83. package/dist/registry/install.js.map +1 -0
  84. package/dist/registry/install.test.js +168 -0
  85. package/dist/registry/install.test.js.map +1 -0
  86. package/dist/registry/load-registry.js +24 -0
  87. package/dist/registry/load-registry.js.map +1 -0
  88. package/dist/registry/load-registry.test.js +59 -0
  89. package/dist/registry/load-registry.test.js.map +1 -0
  90. package/dist/registry/migration-planner.js +204 -0
  91. package/dist/registry/migration-planner.js.map +1 -0
  92. package/dist/registry/migration-planner.test.js +340 -0
  93. package/dist/registry/migration-planner.test.js.map +1 -0
  94. package/dist/registry/migrations-store.js +125 -0
  95. package/dist/registry/migrations-store.js.map +1 -0
  96. package/dist/registry/migrations-store.test.js +163 -0
  97. package/dist/registry/migrations-store.test.js.map +1 -0
  98. package/dist/registry/migrations-types.js +25 -0
  99. package/dist/registry/migrations-types.js.map +1 -0
  100. package/dist/registry/project-meta.js +84 -0
  101. package/dist/registry/project-meta.js.map +1 -0
  102. package/dist/registry/registry-items.js +354 -0
  103. package/dist/registry/registry-items.js.map +1 -0
  104. package/dist/registry/registry-items.test.js +99 -0
  105. package/dist/registry/registry-items.test.js.map +1 -0
  106. package/dist/registry/registry-store.js +232 -0
  107. package/dist/registry/registry-store.js.map +1 -0
  108. package/dist/registry/registry-store.test.js +136 -0
  109. package/dist/registry/registry-store.test.js.map +1 -0
  110. package/dist/registry/registry-types.js +18 -0
  111. package/dist/registry/registry-types.js.map +1 -0
  112. package/dist/registry/registry-types.test.js +146 -0
  113. package/dist/registry/registry-types.test.js.map +1 -0
  114. package/dist/registry/render-content-source-files.js +158 -0
  115. package/dist/registry/render-content-source-files.js.map +1 -0
  116. package/dist/registry/render-multi-source.js +296 -0
  117. package/dist/registry/render-multi-source.js.map +1 -0
  118. package/dist/registry/render-multi-source.test.js +110 -0
  119. package/dist/registry/render-multi-source.test.js.map +1 -0
  120. package/dist/registry/text-utils.js +42 -0
  121. package/dist/registry/text-utils.js.map +1 -0
  122. package/dist/registry/uninstall.js +250 -0
  123. package/dist/registry/uninstall.js.map +1 -0
  124. package/dist/registry/uninstall.test.js +264 -0
  125. package/dist/registry/uninstall.test.js.map +1 -0
  126. package/dist/registry/update.js +280 -0
  127. package/dist/registry/update.js.map +1 -0
  128. package/dist/registry/update.test.js +229 -0
  129. package/dist/registry/update.test.js.map +1 -0
  130. package/dist/render.js +549 -0
  131. package/dist/render.js.map +1 -0
  132. package/dist/render.test.js +414 -0
  133. package/dist/render.test.js.map +1 -0
  134. package/dist/templates/.dev.vars.example.tmpl +32 -0
  135. package/dist/templates/.gitignore.tmpl +58 -0
  136. package/dist/templates/README.md.tmpl +417 -0
  137. package/dist/templates/app/[slug]/page.tsx.tmpl +55 -0
  138. package/dist/templates/app/admin/account/page.tsx.tmpl +18 -0
  139. package/dist/templates/app/admin/content-models/page.tsx.tmpl +6 -0
  140. package/dist/templates/app/admin/layout.tsx.tmpl +90 -0
  141. package/dist/templates/app/admin/loading.tsx.tmpl +6 -0
  142. package/dist/templates/app/admin/page.tsx.tmpl +17 -0
  143. package/dist/templates/app/api/auth/google/callback/route.ts.tmpl +3 -0
  144. package/dist/templates/app/api/auth/google/route.ts.tmpl +3 -0
  145. package/dist/templates/app/api/auth/verify-email/route.ts.tmpl +3 -0
  146. package/dist/templates/app/api/auth/viewer/route.ts.tmpl +3 -0
  147. package/dist/templates/app/api/health/route.ts.tmpl +3 -0
  148. package/dist/templates/app/api/{{contentSourceId}}/[slug]/route.ts.tmpl +27 -0
  149. package/dist/templates/app/api/{{contentSourceId}}/route.ts.tmpl +18 -0
  150. package/dist/templates/app/globals.css.tmpl +109 -0
  151. package/dist/templates/app/layout.tsx.tmpl +56 -0
  152. package/dist/templates/app/login/page.tsx.tmpl +154 -0
  153. package/dist/templates/app/page.fallback.tsx.tmpl +31 -0
  154. package/dist/templates/app/page.tsx.tmpl +42 -0
  155. package/dist/templates/app/register/page.tsx.tmpl +138 -0
  156. package/dist/templates/app/{{contentSourceListPath}}/[slug]/page.tsx.tmpl +113 -0
  157. package/dist/templates/app/{{contentSourceListPath}}/page.tsx.tmpl +74 -0
  158. package/dist/templates/components/content/post-card.tsx.tmpl +80 -0
  159. package/dist/templates/components/notion-blocks.tsx.tmpl +668 -0
  160. package/dist/templates/components/page-blocks/feature-grid-block.tsx.tmpl +68 -0
  161. package/dist/templates/components/page-blocks/hero-block.tsx.tmpl +73 -0
  162. package/dist/templates/components/page-blocks/latest-posts-block.tsx.tmpl +59 -0
  163. package/dist/templates/components/page-blocks/story-block.tsx.tmpl +70 -0
  164. package/dist/templates/components/page-blocks.fallback.tsx.tmpl +17 -0
  165. package/dist/templates/components/page-blocks.tsx.tmpl +32 -0
  166. package/dist/templates/components/search/search-dialog.tsx.tmpl +171 -0
  167. package/dist/templates/components/site/locale-switcher.tsx.tmpl +65 -0
  168. package/dist/templates/components/site/site-footer.tsx.tmpl +106 -0
  169. package/dist/templates/components/site/site-header.tsx.tmpl +80 -0
  170. package/dist/templates/components/site/site-shell.tsx.tmpl +20 -0
  171. package/dist/templates/components/site/theme-bootstrap.tsx.tmpl +51 -0
  172. package/dist/templates/components/theme-provider.tsx.tmpl +14 -0
  173. package/dist/templates/components/theme-toggle.tsx.tmpl +38 -0
  174. package/dist/templates/components/ui/accordion.tsx.tmpl +56 -0
  175. package/dist/templates/components/ui/alert.tsx.tmpl +59 -0
  176. package/dist/templates/components/ui/aspect-ratio.tsx.tmpl +8 -0
  177. package/dist/templates/components/ui/avatar.tsx.tmpl +44 -0
  178. package/dist/templates/components/ui/badge.tsx.tmpl +33 -0
  179. package/dist/templates/components/ui/button.tsx.tmpl +56 -0
  180. package/dist/templates/components/ui/card.tsx.tmpl +61 -0
  181. package/dist/templates/components/ui/checkbox.tsx.tmpl +28 -0
  182. package/dist/templates/components/ui/dialog.tsx.tmpl +104 -0
  183. package/dist/templates/components/ui/dropdown-menu.tsx.tmpl +183 -0
  184. package/dist/templates/components/ui/input.tsx.tmpl +21 -0
  185. package/dist/templates/components/ui/label.tsx.tmpl +25 -0
  186. package/dist/templates/components/ui/popover.tsx.tmpl +30 -0
  187. package/dist/templates/components/ui/radio-group.tsx.tmpl +44 -0
  188. package/dist/templates/components/ui/select.tsx.tmpl +150 -0
  189. package/dist/templates/components/ui/separator.tsx.tmpl +30 -0
  190. package/dist/templates/components/ui/sheet.tsx.tmpl +125 -0
  191. package/dist/templates/components/ui/skeleton.tsx.tmpl +15 -0
  192. package/dist/templates/components/ui/sonner.tsx.tmpl +30 -0
  193. package/dist/templates/components/ui/switch.tsx.tmpl +29 -0
  194. package/dist/templates/components/ui/table.tsx.tmpl +107 -0
  195. package/dist/templates/components/ui/tabs.tsx.tmpl +55 -0
  196. package/dist/templates/components/ui/textarea.tsx.tmpl +24 -0
  197. package/dist/templates/components/ui/tooltip.tsx.tmpl +30 -0
  198. package/dist/templates/components.json.tmpl +21 -0
  199. package/dist/templates/env.d.ts.tmpl +32 -0
  200. package/dist/templates/lib/admin/actions.ts.tmpl +43 -0
  201. package/dist/templates/lib/admin/context.tsx.tmpl +209 -0
  202. package/dist/templates/lib/admin/nav.ts.tmpl +23 -0
  203. package/dist/templates/lib/auth.config.fallback.ts.tmpl +10 -0
  204. package/dist/templates/lib/auth.config.ts.tmpl +45 -0
  205. package/dist/templates/lib/blocks/translations.ts.tmpl +44 -0
  206. package/dist/templates/lib/blog/translations.ts.tmpl +52 -0
  207. package/dist/templates/lib/content/models.ts.tmpl +53 -0
  208. package/dist/templates/lib/i18n/config.ts.tmpl +18 -0
  209. package/dist/templates/lib/i18n/index.ts.tmpl +1 -0
  210. package/dist/templates/lib/locale-contract/built-in.ts.tmpl +19 -0
  211. package/dist/templates/lib/locale-contract/index.ts.tmpl +3 -0
  212. package/dist/templates/lib/locale-contract/paths.ts.tmpl +29 -0
  213. package/dist/templates/lib/pages/model.ts.tmpl +16 -0
  214. package/dist/templates/lib/pages/source.ts.tmpl +566 -0
  215. package/dist/templates/lib/pages/translations.ts.tmpl +34 -0
  216. package/dist/templates/lib/search/config.fallback.ts.tmpl +11 -0
  217. package/dist/templates/lib/search/config.ts.tmpl +25 -0
  218. package/dist/templates/lib/site/config.ts.tmpl +120 -0
  219. package/dist/templates/lib/site/request-env.ts.tmpl +71 -0
  220. package/dist/templates/lib/site/settings.fallback.ts.tmpl +21 -0
  221. package/dist/templates/lib/site/settings.ts.tmpl +320 -0
  222. package/dist/templates/lib/site/translations.ts.tmpl +30 -0
  223. package/dist/templates/lib/utils.ts.tmpl +9 -0
  224. package/dist/templates/migrations/0001_init.sql.tmpl +57 -0
  225. package/dist/templates/migrations/0002_admin_seed.sql.tmpl +30 -0
  226. package/dist/templates/migrations/0003_search_index.sql.tmpl +29 -0
  227. package/dist/templates/next.config.ts.tmpl +18 -0
  228. package/dist/templates/package.json.tmpl +40 -0
  229. package/dist/templates/shims/cloudflare-workers-empty.mjs +4 -0
  230. package/dist/templates/shims/next-headers-empty.mjs +4 -0
  231. package/dist/templates/tests/smoke.test.ts.tmpl +83 -0
  232. package/dist/templates/tsconfig.json.tmpl +31 -0
  233. package/dist/templates/vite.config.ts.tmpl +53 -0
  234. package/dist/templates/vitest.config.ts.tmpl +13 -0
  235. package/dist/templates/worker/index.ts.tmpl +52 -0
  236. package/dist/templates/wrangler.jsonc.tmpl +44 -0
  237. package/dist/ui-presets.js +60 -0
  238. package/dist/ui-presets.js.map +1 -0
  239. package/package.json +60 -0
@@ -0,0 +1,158 @@
1
+ // packages/create-notionx-app/src/registry/render-content-source-files.ts
2
+ //
3
+ // Render all files declared by a `RegistryItem` into the project.
4
+ //
5
+ // Each `RegistryFile` in the item's `files` array may carry a
6
+ // `template` path (relative to the templates directory). The
7
+ // renderer reads that template, substitutes tokens, and writes
8
+ // the result to the file's `path` (relative to the project root).
9
+ // Files without a `template` are skipped.
10
+ //
11
+ // The token set is the same multi-source token map used by
12
+ // `render.ts` at scaffold time, plus per-item overrides so the
13
+ // template sees *this* item's id/path/var-name (not the
14
+ // alphabetically-first installed source).
15
+ import { promises as fs } from "node:fs";
16
+ import path from "node:path";
17
+ import { buildMultiSourceTokenMap } from "./render-multi-source.js";
18
+ import { toCamel, titleCase } from "./text-utils.js";
19
+ /**
20
+ * Render all files declared by `input.item` that have a `template`
21
+ * path. Returns one `RenderedContentSourceFile` per rendered file.
22
+ *
23
+ * For `content-source` items, per-item tokens (contentSourceId,
24
+ * contentSourceListPath, etc.) are computed and injected. For
25
+ * `feature-module` items, only the shared multi-source tokens are
26
+ * injected — feature-module templates don't use content-source
27
+ * tokens.
28
+ *
29
+ * `internalSources` controls which internal singleton sources
30
+ * (siteSettingsSource, blocksSource) appear in the generated
31
+ * `models.ts` block. Callers should pass the manifest's
32
+ * `enableSiteSettings` / `enableBlocks` flags (adjusted for the
33
+ * item being installed if it's a feature-module).
34
+ */
35
+ export async function renderContentSourceFiles(input) {
36
+ const installedWithNew = dedupeInstalled([
37
+ ...input.installed,
38
+ {
39
+ id: input.item.id,
40
+ kind: input.item.kind,
41
+ version: input.item.version,
42
+ source: input.item.source,
43
+ params: input.item.params,
44
+ installedAt: new Date(0).toISOString(),
45
+ },
46
+ ]);
47
+ // Multi-source bookkeeping tokens (sources block, env vars, var
48
+ // names) — independent of which item is being rendered.
49
+ const tokens = buildMultiSourceTokenMap({
50
+ project: input.project,
51
+ installed: installedWithNew,
52
+ internalSources: input.internalSources,
53
+ });
54
+ const flat = {
55
+ contentSourceDeclarations: tokens.contentSourceDeclarations,
56
+ contentSourceSourcesVarNames: tokens.contentSourceSourcesVarNames,
57
+ internalSourceDeclarations: tokens.internalSourceDeclarations,
58
+ internalSourceVarNames: tokens.internalSourceVarNames,
59
+ projectName: input.project.projectName,
60
+ projectNameLower: input.project.projectName.toLowerCase(),
61
+ targetDir: input.project.targetDir,
62
+ defaultLocale: input.project.defaultLocale,
63
+ supportedLocales: input.project.supportedLocales.join(", "),
64
+ supportedLocalesJson: JSON.stringify(input.project.supportedLocales),
65
+ notionxSource: input.project.notionxSource,
66
+ };
67
+ // For content-source items, add per-item tokens so the template
68
+ // sees this item's id/path/var-name. Feature-module items don't
69
+ // use these tokens.
70
+ if (input.item.kind === "content-source") {
71
+ const perItem = computePerItemTokens(input.item);
72
+ Object.assign(flat, {
73
+ contentSourceId: perItem.contentSourceId,
74
+ contentSourceTitle: perItem.contentSourceTitle,
75
+ contentSourceListPath: perItem.contentSourceListPath,
76
+ contentSourceVarName: perItem.contentSourceVarName,
77
+ contentSourceConstName: perItem.contentSourceConstName,
78
+ contentSourceNavLabel: perItem.contentSourceNavLabel,
79
+ contentSourceListTitle: perItem.contentSourceListTitle,
80
+ contentSourceListDescription: perItem.contentSourceListDescription,
81
+ contentSourceEmptyState: perItem.contentSourceEmptyState,
82
+ });
83
+ }
84
+ const out = [];
85
+ for (const file of input.item.files) {
86
+ if (!file.template)
87
+ continue;
88
+ // The template path is a LITERAL path on disk — the
89
+ // `{{...}}` segments are real directory names (e.g.
90
+ // `app/{{contentSourceListPath}}/page.tsx.tmpl`). Token
91
+ // substitution only applies to the template *content*,
92
+ // not the path.
93
+ const tplAbs = path.join(input.templatesDir, file.template);
94
+ let tpl;
95
+ try {
96
+ tpl = await fs.readFile(tplAbs, "utf8");
97
+ }
98
+ catch {
99
+ // Template file doesn't exist — skip silently. The catalog
100
+ // may declare files whose templates ship in a later version.
101
+ continue;
102
+ }
103
+ const rendered = renderTemplate(tpl, flat);
104
+ const destAbs = path.join(input.projectDir, file.path);
105
+ const existed = await pathExists(destAbs);
106
+ out.push({
107
+ absolutePath: destAbs,
108
+ projectRelativePath: file.path,
109
+ ownership: file.ownership,
110
+ existed,
111
+ content: rendered,
112
+ });
113
+ }
114
+ return out;
115
+ }
116
+ function dedupeInstalled(items) {
117
+ const seen = new Map();
118
+ for (const item of items) {
119
+ if (!seen.has(item.id))
120
+ seen.set(item.id, item);
121
+ }
122
+ return [...seen.values()].sort((a, b) => a.id.localeCompare(b.id));
123
+ }
124
+ function computePerItemTokens(item) {
125
+ const id = item.id;
126
+ const basePath = item.params.basePath;
127
+ const listPath = basePath ?? `/${id}`;
128
+ const camel = toCamel(id);
129
+ return {
130
+ contentSourceId: id,
131
+ contentSourceTitle: titleCase(id),
132
+ contentSourceListPath: listPath,
133
+ contentSourceVarName: `${camel}Source`,
134
+ contentSourceConstName: `${camel}ContentModel`,
135
+ contentSourceNavLabel: titleCase(id),
136
+ contentSourceListTitle: titleCase(id),
137
+ contentSourceListDescription: `${titleCase(id)} entries backed by Notion.`,
138
+ contentSourceEmptyState: `No ${id} entries yet.`,
139
+ };
140
+ }
141
+ function renderTemplate(input, tokens) {
142
+ return input.replace(/\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*\}\}/g, (full, key) => {
143
+ const value = tokens[key];
144
+ if (typeof value === "string")
145
+ return value;
146
+ return full;
147
+ });
148
+ }
149
+ async function pathExists(p) {
150
+ try {
151
+ await fs.stat(p);
152
+ return true;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ //# sourceMappingURL=render-content-source-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-content-source-files.js","sourceRoot":"","sources":["../../src/registry/render-content-source-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,296 @@
1
+ // packages/create-notionx-app/src/registry/render-multi-source.ts
2
+ //
3
+ // Multi-source token-map builder. The v1 `render.ts` accepted a
4
+ // single `contentSource` answer and emitted one source's worth of
5
+ // tokens. With v2, a project can have *N* content sources installed
6
+ // (blog + docs + movies + …), and the templates need to render
7
+ // them as a single `contentSources` array.
8
+ //
9
+ // This module is the **only** place where multi-source text is
10
+ // generated. v1 callers go through the legacy single-source path in
11
+ // `render.ts`; v2 callers (`notionx add` / `notionx init`) build
12
+ // their token map here and pass it to a thin multi-source render
13
+ // function. The two paths share a `TokenMap` shape so the on-disk
14
+ // output is identical.
15
+ import { toCamel, titleCase } from "./text-utils.js";
16
+ /**
17
+ * Build a token map from a list of installed items.
18
+ *
19
+ * Sorting rule: content sources are alphabetised by `id` so the
20
+ * generated `models.ts` is deterministic across runs (the order of
21
+ * registration is observable via `getRegisteredSources()` and a
22
+ * non-deterministic order would produce noisy diffs in
23
+ * `notionx update` plans).
24
+ *
25
+ * Internal singleton sources (`siteSettingsSource`, `blocksSource`)
26
+ * are emitted in a fixed order (site-settings first, then blocks)
27
+ * so the generated block is also deterministic.
28
+ */
29
+ export function buildMultiSourceTokenMap(input) {
30
+ const contentSources = input.installed
31
+ .filter((i) => i.kind === "content-source")
32
+ .sort((a, b) => a.id.localeCompare(b.id));
33
+ if (contentSources.length === 0) {
34
+ throw new Error("Cannot build a multi-source token map without at least one content-source item installed.");
35
+ }
36
+ // Reject duplicate ids — they would shadow each other in the
37
+ // runtime registry and produce a broken `models.ts`.
38
+ const seen = new Set();
39
+ for (const source of contentSources) {
40
+ if (seen.has(source.id)) {
41
+ throw new Error(`Duplicate content-source id "${source.id}" in installed list. ` +
42
+ `Each content source must have a unique id.`);
43
+ }
44
+ seen.add(source.id);
45
+ }
46
+ const primary = contentSources[0];
47
+ const sourcesBlock = renderSourcesBlock(contentSources);
48
+ const internalFlags = {
49
+ siteSettings: input.internalSources?.siteSettings ?? true,
50
+ blocks: input.internalSources?.blocks ?? true,
51
+ };
52
+ const internalDecls = [];
53
+ const internalVars = [];
54
+ if (internalFlags.siteSettings) {
55
+ internalDecls.push(SITE_SETTINGS_SOURCE_DECLARATION);
56
+ internalVars.push("siteSettingsSource");
57
+ }
58
+ if (internalFlags.blocks) {
59
+ internalDecls.push(BLOCKS_SOURCE_DECLARATION);
60
+ internalVars.push("blocksSource");
61
+ }
62
+ return {
63
+ contentSourceId: primary.id,
64
+ contentSourceTitle: titleCase(primary.id),
65
+ contentSourceListPath: `/${primary.id}`,
66
+ contentSourceVarName: `${toCamel(primary.id)}Source`,
67
+ contentSourceConstName: `${toCamel(primary.id)}ContentModel`,
68
+ contentSourceNavLabel: titleCase(primary.id),
69
+ contentSourceListTitle: titleCase(primary.id),
70
+ contentSourceListDescription: `${titleCase(primary.id)} entries backed by Notion.`,
71
+ contentSourceEmptyState: `No ${primary.id} entries yet.`,
72
+ contentSourceDeclarations: sourcesBlock,
73
+ contentSourceSourcesVarNames: varNamesForSources(contentSources),
74
+ internalSourceDeclarations: internalDecls.join("\n\n"),
75
+ internalSourceVarNames: internalVars.length
76
+ ? "\n " + internalVars.join(",\n ") + ","
77
+ : "",
78
+ };
79
+ }
80
+ function varNamesForSources(sources) {
81
+ return sources
82
+ .map((s) => `${toCamel(s.id)}Source`)
83
+ .join(", ");
84
+ }
85
+ // ---- internals ----
86
+ /**
87
+ * Render the `defineContentSource({...})` calls for every installed
88
+ * content source, separated by `\n\n`. The output is meant to be
89
+ * dropped into `models.ts` between the import block and the
90
+ * `contentSources` array literal.
91
+ */
92
+ function renderSourcesBlock(sources) {
93
+ return sources
94
+ .map((source) => renderSingleSourceDeclaration(source))
95
+ .join("\n\n");
96
+ }
97
+ function renderSingleSourceDeclaration(source) {
98
+ const id = source.id;
99
+ const camelId = toCamel(id);
100
+ const varName = `${camelId}Source`;
101
+ const listPath = pathForSource(source, "list");
102
+ const detailPath = pathForSource(source, "detail");
103
+ const apiPath = pathForSource(source, "api");
104
+ const envVar = toEnvVarForSource(id);
105
+ const kind = id === "blog" ? "article" : "catalog";
106
+ const capabilities = defaultCapabilities(id);
107
+ // Indent every `key: "value"` line so it sits inside `fields: { ... }`
108
+ // of the generated call. Default fields: title + slug, which is the
109
+ // minimum any Notion data source needs. Users edit the file to
110
+ // match their actual property names.
111
+ return [
112
+ `export const ${varName}: ContentSource = defineContentSource({`,
113
+ ` id: "${id}",`,
114
+ ` kind: ${JSON.stringify(kind)},`,
115
+ ` visibility: {`,
116
+ ` public: true,`,
117
+ ` admin: true,`,
118
+ ` },`,
119
+ ` source: {`,
120
+ ` type: "notion",`,
121
+ ` tokenEnv: "NOTION_TOKEN",`,
122
+ ` dataSourceEnv: ${JSON.stringify(envVar)},`,
123
+ ` fields: {`,
124
+ ` title: "Name",`,
125
+ ` slug: "Slug",`,
126
+ ` },`,
127
+ ` query: {`,
128
+ ` pageSize: 50,`,
129
+ ` },`,
130
+ ` },`,
131
+ ` routes: {`,
132
+ ` listPath: ${JSON.stringify(listPath)},`,
133
+ ` detailPath: ${JSON.stringify(detailPath)},`,
134
+ ` detailParam: "slug",`,
135
+ ` publicApiPath: ${JSON.stringify(apiPath)},`,
136
+ ` },`,
137
+ ` ui: {`,
138
+ ` name: ${JSON.stringify(titleCase(id))},`,
139
+ ` pluralName: ${JSON.stringify(`${titleCase(id)}s`)},`,
140
+ ` navLabel: ${JSON.stringify(titleCase(id))},`,
141
+ ` listTitle: ${JSON.stringify(titleCase(id))},`,
142
+ ` listDescription: ${JSON.stringify(`${titleCase(id)} entries backed by Notion.`)},`,
143
+ ` emptyState: ${JSON.stringify(`No ${id} entries yet.`)},`,
144
+ ` },`,
145
+ ` capabilities: ${capabilities},`,
146
+ `});`,
147
+ ].join("\n");
148
+ }
149
+ function pathForSource(source, kind) {
150
+ // Sources can override the path via params (e.g. `basePath: "/docs"`).
151
+ // Falls back to the convention `/{id}` and `/api/{id}`.
152
+ const basePath = source.params.basePath;
153
+ if (basePath && kind !== "api") {
154
+ return kind === "list" ? basePath : `${basePath}/[slug]`;
155
+ }
156
+ switch (kind) {
157
+ case "list":
158
+ return `/${source.id}`;
159
+ case "detail":
160
+ return `/${source.id}/[slug]`;
161
+ case "api":
162
+ return `/api/${source.id}`;
163
+ }
164
+ }
165
+ function toEnvVarForSource(id) {
166
+ return `NOTION_${snakeUpper(id)}_DATA_SOURCE_ID`;
167
+ }
168
+ function snakeUpper(id) {
169
+ return id.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
170
+ }
171
+ function defaultCapabilities(id) {
172
+ // Blog ships with rich block rendering and cover images because
173
+ // that's what the v1 starter assumed. Other sources default to
174
+ // the leaner shape; users can edit the file to enable features.
175
+ if (id === "blog") {
176
+ return "{\n richBlocks: true,\n coverImages: true,\n gatedAssets: false,\n }";
177
+ }
178
+ return "{\n richBlocks: false,\n coverImages: true,\n gatedAssets: false,\n }";
179
+ }
180
+ // ---- internal singleton source declarations ----
181
+ //
182
+ // These two `defineContentSource({...})` blocks back
183
+ // `lib/site/settings.ts` and `components/page-blocks` respectively.
184
+ // They are emitted verbatim into `models.ts` between the
185
+ // `BEGIN generated-internal-sources` / `END generated-internal-sources`
186
+ // markers when the corresponding flag (`enableSiteSettings` /
187
+ // `enableBlocks`) is true.
188
+ //
189
+ // Keep these in sync with the field maps documented in
190
+ // `templates/README.md.tmpl` — operators edit the Notion database
191
+ // columns to match these names.
192
+ const SITE_SETTINGS_SOURCE_DECLARATION = `export const siteSettingsSource: ContentSource = defineContentSource({
193
+ id: "site-settings",
194
+ kind: "directory",
195
+ visibility: {
196
+ public: false,
197
+ admin: true,
198
+ },
199
+ source: {
200
+ type: "notion",
201
+ tokenEnv: "NOTION_TOKEN",
202
+ dataSourceEnv: "NOTION_SITE_SETTINGS_DATA_SOURCE_ID",
203
+ fields: {
204
+ title: "Site Name",
205
+ description: "Description",
206
+ cover: "Social Image",
207
+ },
208
+ query: {
209
+ pageSize: 1,
210
+ },
211
+ },
212
+ routes: {
213
+ listPath: "/__internal/site-settings",
214
+ detailPath: "/__internal/site-settings/[slug]",
215
+ detailParam: "slug",
216
+ publicApiPath: "/api/__internal/site-settings",
217
+ },
218
+ ui: {
219
+ name: "Site Settings",
220
+ pluralName: "Site Settings",
221
+ navLabel: "Site Settings",
222
+ listTitle: "Site Settings",
223
+ listDescription:
224
+ "Singleton row in Notion that powers the site name, tagline, description, default locale, and social image.",
225
+ emptyState: "No site settings row found — re-run provisioning.",
226
+ },
227
+ capabilities: {
228
+ richBlocks: false,
229
+ coverImages: true,
230
+ gatedAssets: false,
231
+ },
232
+ });`;
233
+ const BLOCKS_SOURCE_DECLARATION = `export const blocksSource: ContentSource = defineContentSource({
234
+ id: "blocks",
235
+ kind: "directory",
236
+ visibility: {
237
+ public: false,
238
+ admin: true,
239
+ },
240
+ source: {
241
+ type: "notion",
242
+ tokenEnv: "NOTION_TOKEN",
243
+ dataSourceEnv: "NOTION_BLOCKS_DATA_SOURCE_ID",
244
+ fields: {
245
+ title: "Name",
246
+ slug: "Slug",
247
+ type: "Type",
248
+ description: "Description",
249
+ pageKeys: "Page Keys",
250
+ order: "Order",
251
+ cover: "Cover",
252
+ published: "Status",
253
+ eyebrow: "Eyebrow",
254
+ headline: "Headline",
255
+ subheadline: "Subheadline",
256
+ primaryCtaLabel: "Primary CTA Label",
257
+ primaryCtaHref: "Primary CTA Href",
258
+ secondaryCtaLabel: "Secondary CTA Label",
259
+ secondaryCtaHref: "Secondary CTA Href",
260
+ alignment: "Alignment",
261
+ theme: "Theme",
262
+ columns: "Columns",
263
+ count: "Count",
264
+ items: "Items",
265
+ body: "Body",
266
+ quote: "Quote",
267
+ quoteAttribution: "Quote Attribution",
268
+ mediaUrl: "Media Url",
269
+ layout: "Layout",
270
+ },
271
+ query: {
272
+ pageSize: 50,
273
+ },
274
+ },
275
+ routes: {
276
+ listPath: "/__internal/blocks",
277
+ detailPath: "/__internal/blocks/[slug]",
278
+ detailParam: "slug",
279
+ publicApiPath: "/api/__internal/blocks",
280
+ },
281
+ ui: {
282
+ name: "Block",
283
+ pluralName: "Blocks",
284
+ navLabel: "Blocks",
285
+ listTitle: "Reusable Blocks",
286
+ listDescription:
287
+ "Reusable structured page sections stored in Notion and referenced from the Pages model.",
288
+ emptyState: "No reusable blocks found — re-run provisioning.",
289
+ },
290
+ capabilities: {
291
+ richBlocks: true,
292
+ coverImages: true,
293
+ gatedAssets: false,
294
+ },
295
+ });`;
296
+ //# sourceMappingURL=render-multi-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-multi-source.js","sourceRoot":"","sources":["../../src/registry/render-multi-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ // packages/create-notionx-app/src/registry/render-multi-source.test.ts
2
+ import { describe, expect, it } from "vitest";
3
+ import { buildMultiSourceTokenMap } from "./render-multi-source.js";
4
+ const OFFICIAL = {
5
+ kind: "official",
6
+ name: "@notionx/official",
7
+ };
8
+ function item(id, params = {}) {
9
+ return {
10
+ id,
11
+ kind: "content-source",
12
+ version: 1,
13
+ source: OFFICIAL,
14
+ params,
15
+ installedAt: "2026-06-10T00:00:00.000Z",
16
+ };
17
+ }
18
+ const DEFAULT_PROJECT = {
19
+ projectName: "demo",
20
+ targetDir: "./demo",
21
+ defaultLocale: "en",
22
+ supportedLocales: ["en"],
23
+ notionxSource: "^2.0.0",
24
+ adminEmail: "admin@example.com",
25
+ adminPassword: "Password123",
26
+ scaffoldVersion: "0.7.2",
27
+ };
28
+ describe("buildMultiSourceTokenMap", () => {
29
+ it("renders a single source (blog) with per-source and block tokens", () => {
30
+ const tokens = buildMultiSourceTokenMap({
31
+ project: DEFAULT_PROJECT,
32
+ installed: [item("blog", { contentSourceId: "blog" })],
33
+ });
34
+ // Per-source tokens (derived from the primary source).
35
+ expect(tokens.contentSourceId).toBe("blog");
36
+ expect(tokens.contentSourceVarName).toBe("blogSource");
37
+ expect(tokens.contentSourceListPath).toBe("/blog");
38
+ // The declarations block contains the same source.
39
+ expect(tokens.contentSourceDeclarations).toContain("blogSource");
40
+ expect(tokens.contentSourceDeclarations).toContain('id: "blog"');
41
+ });
42
+ it("renders two sources (blog + docs) and produces a unified declarations block", () => {
43
+ const tokens = buildMultiSourceTokenMap({
44
+ project: DEFAULT_PROJECT,
45
+ installed: [
46
+ item("blog", { contentSourceId: "blog" }),
47
+ item("docs", { contentSourceId: "docs", basePath: "/docs" }),
48
+ ],
49
+ });
50
+ // Both sources are referenced in the declarations block.
51
+ expect(tokens.contentSourceDeclarations).toContain("blogSource");
52
+ expect(tokens.contentSourceDeclarations).toContain("docsSource");
53
+ expect(tokens.contentSourceDeclarations).toContain('id: "blog"');
54
+ expect(tokens.contentSourceDeclarations).toContain('id: "docs"');
55
+ // The block is **ordered by id** for deterministic diffs
56
+ // (alphabetical when no explicit order is given).
57
+ const blogIdx = tokens.contentSourceDeclarations.indexOf("blogSource");
58
+ const docsIdx = tokens.contentSourceDeclarations.indexOf("docsSource");
59
+ expect(blogIdx).toBeGreaterThan(-1);
60
+ expect(docsIdx).toBeGreaterThan(blogIdx);
61
+ // The var-names token is a comma-joined list of `*Source` names.
62
+ expect(tokens.contentSourceSourcesVarNames).toBe("blogSource, docsSource");
63
+ });
64
+ it("honors a custom basePath param for content sources (docs at /docs)", () => {
65
+ const tokens = buildMultiSourceTokenMap({
66
+ project: DEFAULT_PROJECT,
67
+ installed: [item("docs", { contentSourceId: "docs", basePath: "/docs" })],
68
+ });
69
+ // The declarations block reflects the custom basePath.
70
+ expect(tokens.contentSourceDeclarations).toContain('listPath: "/docs"');
71
+ expect(tokens.contentSourceDeclarations).toContain('detailPath: "/docs/[slug]"');
72
+ });
73
+ it("treats feature-modules (e.g. search) as separate from content-source arrays", () => {
74
+ const tokens = buildMultiSourceTokenMap({
75
+ project: DEFAULT_PROJECT,
76
+ installed: [
77
+ item("docs", { contentSourceId: "docs" }),
78
+ {
79
+ id: "search",
80
+ kind: "feature-module",
81
+ version: 1,
82
+ source: OFFICIAL,
83
+ params: { scope: "docs" },
84
+ installedAt: "2026-06-10T00:00:00.000Z",
85
+ },
86
+ ],
87
+ });
88
+ // docs is a content-source → its source block is emitted
89
+ expect(tokens.contentSourceDeclarations).toContain("docsSource");
90
+ expect(tokens.contentSourceDeclarations).toContain('id: "docs"');
91
+ // search is a feature-module → not in the content-source array
92
+ expect(tokens.contentSourceDeclarations).not.toContain("searchSource");
93
+ });
94
+ it("throws if no content-source items are installed (must have at least one)", () => {
95
+ expect(() => buildMultiSourceTokenMap({
96
+ project: DEFAULT_PROJECT,
97
+ installed: [],
98
+ })).toThrow(/at least one content-source/);
99
+ });
100
+ it("throws on duplicate content-source ids", () => {
101
+ expect(() => buildMultiSourceTokenMap({
102
+ project: DEFAULT_PROJECT,
103
+ installed: [
104
+ item("blog", { contentSourceId: "blog" }),
105
+ item("blog", { contentSourceId: "blog" }),
106
+ ],
107
+ })).toThrow(/duplicate/i);
108
+ });
109
+ });
110
+ //# sourceMappingURL=render-multi-source.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-multi-source.test.js","sourceRoot":"","sources":["../../src/registry/render-multi-source.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ // packages/create-notionx-app/src/registry/text-utils.ts
2
+ //
3
+ // Shared string-casing helpers used by the render layer.
4
+ // Previously duplicated in render.ts, render-multi-source.ts, and
5
+ // render-content-source-files.ts.
6
+ /**
7
+ * Convert an arbitrary id (e.g. `blog`, `my-cool-source`) to
8
+ * `kebab-case` (lowercase, non-alphanumeric runs → `-`).
9
+ */
10
+ export function toKebab(input) {
11
+ return input
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, "-")
14
+ .replace(/(^-|-$)/g, "");
15
+ }
16
+ /**
17
+ * Convert an arbitrary id to `camelCase` (e.g. `blog` → `blog`,
18
+ * `my-cool-source` → `myCoolSource`).
19
+ */
20
+ export function toCamel(input) {
21
+ const parts = toKebab(input).split("-").filter(Boolean);
22
+ if (parts.length === 0)
23
+ return "field";
24
+ const [first, ...rest] = parts;
25
+ return first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
26
+ }
27
+ /**
28
+ * Convert an arbitrary id to `PascalCase` (e.g. `blog` → `Blog`,
29
+ * `my-cool-source` → `MyCoolSource`).
30
+ */
31
+ export function toPascal(input) {
32
+ const camel = toCamel(input);
33
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
34
+ }
35
+ /**
36
+ * Uppercase the first character, leave the rest untouched.
37
+ * (e.g. `blog` → `Blog`, `docs` → `Docs`).
38
+ */
39
+ export function titleCase(id) {
40
+ return id.charAt(0).toUpperCase() + id.slice(1);
41
+ }
42
+ //# sourceMappingURL=text-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-utils.js","sourceRoot":"","sources":["../../src/registry/text-utils.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,yDAAyD;AACzD,kEAAkE;AAClE,kCAAkC;AAElC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACvC,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,OAAO,KAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACnF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC"}