create-feedbax 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +11 -1
  3. package/dist/cli.js +3283 -22
  4. package/dist/index.js +3394 -160
  5. package/package.json +40 -14
  6. package/template/.feedbax-template.sha256 +1 -0
  7. package/template/.gitignore.template +6 -0
  8. package/template/.npmignore.template +5 -0
  9. package/template/.prettierignore.template +4 -0
  10. package/template/AGENTS.md +13 -0
  11. package/template/CONNECTED.md +404 -0
  12. package/template/LICENSE +201 -0
  13. package/template/README.md +56 -5
  14. package/template/components.json +25 -0
  15. package/template/eslint.config.mjs +48 -0
  16. package/template/migrations/0001_connected.sql +6 -0
  17. package/template/migrations/0002_participation.sql +8 -0
  18. package/template/migrations/0003_images.sql +3 -0
  19. package/template/migrations/0004_followed_status.sql +9 -0
  20. package/template/package.json +74 -14
  21. package/template/public/feedbax-mark.svg +4 -0
  22. package/template/public/robots.txt +3 -0
  23. package/template/src/authorized-draft-post.test.tsx +212 -0
  24. package/template/src/authorized-draft-post.tsx +205 -0
  25. package/template/src/authorized-draft-query.ts +74 -0
  26. package/template/src/browser-post-state.test.ts +120 -0
  27. package/template/src/browser-post-state.ts +134 -0
  28. package/template/src/browser-vote-state.test.ts +14 -0
  29. package/template/src/browser-vote-state.ts +27 -0
  30. package/template/src/capability-authorization-error.ts +3 -0
  31. package/template/src/changelog-page.test.tsx +139 -0
  32. package/template/src/changelog-page.tsx +228 -0
  33. package/template/src/changelog-query.ts +13 -0
  34. package/template/src/changelog-runtime.ts +27 -0
  35. package/template/src/cloudflare-turnstile.ts +31 -0
  36. package/template/src/cloudflare-workers-test.ts +3 -0
  37. package/template/src/cloudflare-workers.d.ts +24 -0
  38. package/template/src/comment-creation.test.tsx +311 -0
  39. package/template/src/comment-profile.ts +5 -0
  40. package/template/src/comment-thread-page.ts +28 -0
  41. package/template/src/components/command-palette-context.tsx +71 -0
  42. package/template/src/components/command-palette.tsx +275 -0
  43. package/template/src/components/connected-identity-provider.tsx +108 -0
  44. package/template/src/components/create-post-trigger.tsx +44 -0
  45. package/template/src/components/device-profile-provider.tsx +324 -0
  46. package/template/src/components/follow-post-button.tsx +89 -0
  47. package/template/src/components/header.tsx +97 -0
  48. package/template/src/components/image-attachments.tsx +187 -0
  49. package/template/src/components/loader.tsx +53 -0
  50. package/template/src/components/notification-inbox.tsx +232 -0
  51. package/template/src/components/portal-footer.tsx +14 -0
  52. package/template/src/components/profile-menu.tsx +119 -0
  53. package/template/src/components/ui/components/alert-dialog.tsx +185 -0
  54. package/template/src/components/ui/components/attachment.tsx +207 -0
  55. package/template/src/components/ui/components/avatar.tsx +107 -0
  56. package/template/src/components/ui/components/badge.tsx +52 -0
  57. package/template/src/components/ui/components/bubble.tsx +128 -0
  58. package/template/src/components/ui/components/button-group.tsx +87 -0
  59. package/template/src/components/ui/components/button.tsx +56 -0
  60. package/template/src/components/ui/components/card.tsx +103 -0
  61. package/template/src/components/ui/components/checkbox.tsx +26 -0
  62. package/template/src/components/ui/components/command.tsx +195 -0
  63. package/template/src/components/ui/components/dialog.tsx +154 -0
  64. package/template/src/components/ui/components/drawer.tsx +229 -0
  65. package/template/src/components/ui/components/dropdown-menu.tsx +270 -0
  66. package/template/src/components/ui/components/empty.tsx +101 -0
  67. package/template/src/components/ui/components/field.tsx +236 -0
  68. package/template/src/components/ui/components/input-group.tsx +155 -0
  69. package/template/src/components/ui/components/input.tsx +20 -0
  70. package/template/src/components/ui/components/item.tsx +201 -0
  71. package/template/src/components/ui/components/kbd.tsx +26 -0
  72. package/template/src/components/ui/components/label.tsx +18 -0
  73. package/template/src/components/ui/components/marker.tsx +71 -0
  74. package/template/src/components/ui/components/message-scroller.tsx +128 -0
  75. package/template/src/components/ui/components/message.tsx +92 -0
  76. package/template/src/components/ui/components/native-select.tsx +65 -0
  77. package/template/src/components/ui/components/popover.tsx +88 -0
  78. package/template/src/components/ui/components/portal-primitives.test.tsx +85 -0
  79. package/template/src/components/ui/components/radio-group.tsx +36 -0
  80. package/template/src/components/ui/components/scroll-area.tsx +51 -0
  81. package/template/src/components/ui/components/secondary-form-actions.test.tsx +23 -0
  82. package/template/src/components/ui/components/select.tsx +199 -0
  83. package/template/src/components/ui/components/separator.tsx +23 -0
  84. package/template/src/components/ui/components/sheet.tsx +135 -0
  85. package/template/src/components/ui/components/skeleton.tsx +13 -0
  86. package/template/src/components/ui/components/sonner.tsx +43 -0
  87. package/template/src/components/ui/components/spinner.tsx +16 -0
  88. package/template/src/components/ui/components/switch.tsx +29 -0
  89. package/template/src/components/ui/components/tabs.tsx +80 -0
  90. package/template/src/components/ui/components/textarea.tsx +18 -0
  91. package/template/src/components/ui/components/tooltip.tsx +64 -0
  92. package/template/src/components/ui/hooks/.gitkeep +0 -0
  93. package/template/src/components/ui/lib/utils.ts +6 -0
  94. package/template/src/components/ui/styles/globals.css +151 -0
  95. package/template/src/connected-identity.test.ts +144 -0
  96. package/template/src/connected-identity.ts +122 -0
  97. package/template/src/connected-participation.test.tsx +67 -0
  98. package/template/src/connected-runtime.test.ts +241 -0
  99. package/template/src/connected-runtime.ts +176 -0
  100. package/template/src/connected-social.test.tsx +158 -0
  101. package/template/src/create-post-overlay.tsx +82 -0
  102. package/template/src/draft-post-list.tsx +39 -0
  103. package/template/src/feedback-cache-invalidation.test.ts +185 -0
  104. package/template/src/feedback-cache-invalidation.ts +98 -0
  105. package/template/src/feedback-runtime.ts +105 -0
  106. package/template/src/feedbax/changelog/index.test.ts +156 -0
  107. package/template/src/feedbax/changelog/index.ts +206 -0
  108. package/template/src/feedbax/changelog/notion-changelog.test.ts +395 -0
  109. package/template/src/feedbax/changelog/notion-changelog.ts +400 -0
  110. package/template/src/feedbax/config/index.test.ts +139 -0
  111. package/template/src/feedbax/config/index.ts +364 -0
  112. package/template/src/feedbax/feedback/connected-feedback.ts +206 -0
  113. package/template/src/feedbax/feedback/connected.test.ts +272 -0
  114. package/template/src/feedbax/feedback/connected.ts +215 -0
  115. package/template/src/feedbax/feedback/email-delivery.test.ts +65 -0
  116. package/template/src/feedbax/feedback/email-delivery.ts +66 -0
  117. package/template/src/feedbax/feedback/feedback-storage.ts +43 -0
  118. package/template/src/feedbax/feedback/identity-handoff.test.ts +81 -0
  119. package/template/src/feedbax/feedback/identity-handoff.ts +119 -0
  120. package/template/src/feedbax/feedback/image-feedback.ts +101 -0
  121. package/template/src/feedbax/feedback/images.test.ts +223 -0
  122. package/template/src/feedbax/feedback/images.ts +324 -0
  123. package/template/src/feedbax/feedback/index.test.ts +900 -0
  124. package/template/src/feedbax/feedback/index.ts +996 -0
  125. package/template/src/feedbax/feedback/notion-comments.test.ts +171 -0
  126. package/template/src/feedbax/feedback/notion-comments.ts +231 -0
  127. package/template/src/feedbax/feedback/notion-data-source.test.ts +364 -0
  128. package/template/src/feedbax/feedback/notion-data-source.ts +458 -0
  129. package/template/src/feedbax/feedback/notion-feedback.test.ts +998 -0
  130. package/template/src/feedbax/feedback/notion-feedback.ts +651 -0
  131. package/template/src/feedbax/feedback/notion-image-review.ts +55 -0
  132. package/template/src/feedbax/feedback/notion-request.ts +74 -0
  133. package/template/src/feedbax/feedback/participation.test.ts +653 -0
  134. package/template/src/feedbax/feedback/participation.ts +502 -0
  135. package/template/src/feedbax.test-fixture.ts +64 -0
  136. package/template/src/feedbax.ts +69 -0
  137. package/template/src/images-runtime.test.ts +133 -0
  138. package/template/src/images-runtime.ts +125 -0
  139. package/template/src/index.css +1795 -0
  140. package/template/src/lazy-create-post-overlay.tsx +6 -0
  141. package/template/src/lazy-post-detail-overlay.tsx +21 -0
  142. package/template/src/masked-post-link.tsx +27 -0
  143. package/template/src/overlay-post-detail.test.tsx +181 -0
  144. package/template/src/overlay-post-detail.tsx +43 -0
  145. package/template/src/participation-client.ts +63 -0
  146. package/template/src/participation-runtime.test.ts +35 -0
  147. package/template/src/participation-runtime.ts +255 -0
  148. package/template/src/portal-comments.test.ts +375 -0
  149. package/template/src/portal-comments.ts +403 -0
  150. package/template/src/portal-features.test.tsx +150 -0
  151. package/template/src/portal-feedback-form.tsx +530 -0
  152. package/template/src/portal-feedback-runtime.test.ts +32 -0
  153. package/template/src/portal-feedback-runtime.ts +58 -0
  154. package/template/src/portal-feedback-server-function.ts +96 -0
  155. package/template/src/portal-feedback-submission.test.ts +307 -0
  156. package/template/src/portal-feedback-submission.ts +224 -0
  157. package/template/src/portal-head.ts +58 -0
  158. package/template/src/portal-server-entry.ts +68 -0
  159. package/template/src/portal-voting.test.ts +106 -0
  160. package/template/src/portal-voting.ts +149 -0
  161. package/template/src/post-badges.tsx +27 -0
  162. package/template/src/post-creation-flow.test.tsx +230 -0
  163. package/template/src/post-creation-flow.tsx +88 -0
  164. package/template/src/post-detail-content.test.tsx +233 -0
  165. package/template/src/post-detail-content.tsx +162 -0
  166. package/template/src/post-detail-overlay.test.tsx +114 -0
  167. package/template/src/post-detail-overlay.tsx +67 -0
  168. package/template/src/post-detail-query.ts +34 -0
  169. package/template/src/post-feed-controls.test.tsx +239 -0
  170. package/template/src/post-feed-controls.tsx +298 -0
  171. package/template/src/post-presentation.ts +37 -0
  172. package/template/src/post-queries.test.ts +158 -0
  173. package/template/src/post-queries.ts +26 -0
  174. package/template/src/post-results.tsx +165 -0
  175. package/template/src/public-cache-policy.test.ts +148 -0
  176. package/template/src/public-cache-policy.ts +119 -0
  177. package/template/src/public-cache-tags.ts +7 -0
  178. package/template/src/public-changelog-page.ts +15 -0
  179. package/template/src/public-changelog-server-function.ts +27 -0
  180. package/template/src/public-date.ts +11 -0
  181. package/template/src/public-failure-state.test.tsx +69 -0
  182. package/template/src/public-feedback-index.tsx +94 -0
  183. package/template/src/public-feedback-page.test.tsx +355 -0
  184. package/template/src/public-feedback-page.ts +86 -0
  185. package/template/src/public-feedback-server-function.ts +22 -0
  186. package/template/src/public-portal-error.tsx +27 -0
  187. package/template/src/public-post-detail-skeleton.tsx +54 -0
  188. package/template/src/public-post-detail.tsx +772 -0
  189. package/template/src/public-post-page.test.tsx +173 -0
  190. package/template/src/public-post-page.ts +15 -0
  191. package/template/src/public-post-route.test.tsx +250 -0
  192. package/template/src/public-post-route.tsx +158 -0
  193. package/template/src/public-post-server-function.ts +48 -0
  194. package/template/src/public-post-unavailable.tsx +10 -0
  195. package/template/src/public-roadmap-page.test.tsx +139 -0
  196. package/template/src/public-roadmap-page.ts +24 -0
  197. package/template/src/public-roadmap-route.test.tsx +254 -0
  198. package/template/src/public-roadmap-route.tsx +50 -0
  199. package/template/src/public-roadmap-server-function.ts +44 -0
  200. package/template/src/public-roadmap-view.tsx +411 -0
  201. package/template/src/public-shell.test.tsx +544 -0
  202. package/template/src/roadmap-query.ts +21 -0
  203. package/template/src/root-branding.test.ts +53 -0
  204. package/template/src/routeTree.gen.ts +177 -0
  205. package/template/src/routed-post-creation-form.tsx +28 -0
  206. package/template/src/router.tsx +27 -0
  207. package/template/src/routes/__root.tsx +114 -0
  208. package/template/src/routes/changelog.tsx +39 -0
  209. package/template/src/routes/email-preferences.tsx +77 -0
  210. package/template/src/routes/index.tsx +61 -0
  211. package/template/src/routes/p.$slug.tsx +16 -0
  212. package/template/src/routes/roadmap.tsx +11 -0
  213. package/template/src/routes/submit.tsx +28 -0
  214. package/template/src/safe-public-failure.ts +47 -0
  215. package/template/src/server.ts +151 -0
  216. package/template/src/similar-posts.tsx +45 -0
  217. package/template/src/submit-route.test.tsx +134 -0
  218. package/template/src/trusted-feedback-handler.test.ts +425 -0
  219. package/template/src/trusted-feedback-handler.ts +181 -0
  220. package/template/src/trusted-feedback-runtime.ts +17 -0
  221. package/template/src/turnstile-configuration.test.ts +37 -0
  222. package/template/src/turnstile-configuration.ts +19 -0
  223. package/template/src/use-media-query.ts +17 -0
  224. package/template/src/vote-toggle.test.tsx +171 -0
  225. package/template/src/vote-toggle.tsx +244 -0
  226. package/template/tsconfig.json +45 -0
  227. package/template/vite.config.ts +20 -0
  228. package/template/vitest.config.ts +4 -0
  229. package/template/vitest.setup.ts +3 -0
  230. package/template/wrangler.jsonc +35 -0
  231. package/dist/cli.d.ts +0 -3
  232. package/dist/cli.d.ts.map +0 -1
  233. package/dist/cli.js.map +0 -1
  234. package/dist/index.d.ts +0 -21
  235. package/dist/index.d.ts.map +0 -1
  236. package/dist/index.js.map +0 -1
  237. package/template/.env.example +0 -4
  238. package/template/apps/portal/components.json +0 -20
  239. package/template/apps/portal/package.json +0 -31
  240. package/template/apps/portal/src/lib/utils.ts +0 -6
  241. package/template/apps/portal/src/routeTree.gen.ts +0 -59
  242. package/template/apps/portal/src/router.tsx +0 -10
  243. package/template/apps/portal/src/routes/__root.tsx +0 -31
  244. package/template/apps/portal/src/routes/index.tsx +0 -35
  245. package/template/apps/portal/src/styles.css +0 -91
  246. package/template/apps/portal/tsconfig.json +0 -15
  247. package/template/apps/portal/vite.config.ts +0 -13
  248. package/template/feedbax.config.ts +0 -13
  249. package/template/feedbax.jsonc +0 -11
  250. package/template/pnpm-workspace.yaml +0 -3
  251. package/template/turbo.json +0 -8
package/dist/index.js CHANGED
@@ -1,173 +1,3407 @@
1
- import { cp, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
2
- import { spawn } from 'node:child_process';
3
- import { dirname, join, resolve } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- const valueFlags = new Set([
6
- '--connector',
7
- '--identity',
8
- '--storage',
9
- '--deploy',
10
- '--preset',
11
- '--package-manager',
12
- ]);
13
- const booleanFlags = new Set(['--yes', '--no-install', '--no-git']);
14
- const allowedDeployments = new Set(['cloudflare', 'vercel', 'node']);
15
- const allowedPackageManagers = new Set(['npm', 'pnpm']);
16
- const identityMode = (mode) => {
17
- if (mode === 'anonymous' || mode === 'email' || mode === 'handoff')
18
- return mode;
19
- throw new Error(`Identity mode "${mode}" is not supported in Feedbax 0.1.0.`);
20
- };
21
- export function resolveCreateOptions(argv) {
22
- const values = new Map();
23
- let directory;
24
- for (let index = 0; index < argv.length; index += 1) {
25
- const entry = argv[index];
26
- if (valueFlags.has(entry)) {
27
- const next = argv[index + 1];
28
- if (!next || next.startsWith('-'))
29
- throw new Error(`Option ${entry} requires a value.`);
30
- values.set(entry, next);
31
- index += 1;
1
+ // src/index.ts
2
+ import { createHash as createHash2, randomBytes } from "node:crypto";
3
+ import { readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "node:fs/promises";
4
+
5
+ // ../config/src/index.ts
6
+ var rootKeys = [
7
+ "connected",
8
+ "product",
9
+ "theme",
10
+ "notion",
11
+ "changelog",
12
+ "features",
13
+ "submissions",
14
+ "turnstile"
15
+ ];
16
+ var featureKeys = ["voting", "comments", "changelog"];
17
+ var feedbackPropertyKeys = [
18
+ "title",
19
+ "slug",
20
+ "description",
21
+ "type",
22
+ "status",
23
+ "published",
24
+ "voteCount",
25
+ "teamResponse",
26
+ "releaseNotesUrl",
27
+ "submitterName",
28
+ "submitterEmail",
29
+ "source",
30
+ "externalId",
31
+ "editTokenHash",
32
+ "createdAt",
33
+ "updatedAt"
34
+ ];
35
+ var changelogPropertyKeys = [
36
+ "title",
37
+ "slug",
38
+ "date",
39
+ "summary",
40
+ "body",
41
+ "labels",
42
+ "image",
43
+ "published",
44
+ "createdAt",
45
+ "updatedAt"
46
+ ];
47
+ function defineFeedbax(config) {
48
+ const value = object(config, "Feedbax configuration");
49
+ rejectUnknown(value, rootKeys, "Feedbax configuration");
50
+ const product = object(value.product, "product");
51
+ rejectUnknown(
52
+ product,
53
+ ["name", "description", "logo", "siteUrl", "supportUrl", "privacyUrl"],
54
+ "product"
55
+ );
56
+ const validatedProduct = {
57
+ name: text(product.name, "product.name"),
58
+ description: text(product.description, "product.description"),
59
+ logo: publicUrl(product.logo, "product.logo", true),
60
+ ...optionalPublicUrl(product, "siteUrl"),
61
+ ...optionalPublicUrl(product, "supportUrl"),
62
+ ...optionalPublicUrl(product, "privacyUrl")
63
+ };
64
+ let connected;
65
+ if (value.connected !== void 0) {
66
+ const input = object(value.connected, "connected");
67
+ rejectUnknown(
68
+ input,
69
+ ["issuer", "loginUrl", "email", "images", "processing"],
70
+ "connected"
71
+ );
72
+ const issuer = publicUrl(input.issuer, "connected.issuer", false);
73
+ const loginUrl = publicUrl(input.loginUrl, "connected.loginUrl", false);
74
+ if (!validatedProduct.siteUrl)
75
+ throw new Error("Connected Profile requires product.siteUrl.");
76
+ if (new URL(loginUrl).username || new URL(loginUrl).password || new URL(loginUrl).hash)
77
+ throw new Error(
78
+ "connected.loginUrl must not contain credentials or a fragment."
79
+ );
80
+ let email;
81
+ if (input.email !== void 0) {
82
+ const sender = object(input.email, "connected.email");
83
+ rejectUnknown(
84
+ sender,
85
+ ["provider", "from", "endpoint"],
86
+ "connected.email"
87
+ );
88
+ if (!["cloudflare", "resend", "custom"].includes(String(sender.provider)))
89
+ throw new Error(
90
+ "connected.email.provider must be cloudflare, resend, or custom."
91
+ );
92
+ const from = text(sender.from, "connected.email.from");
93
+ if (from.length > 254 || !/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(from))
94
+ throw new Error(
95
+ "connected.email.from must be a plain sender email address."
96
+ );
97
+ const endpoint = sender.endpoint === void 0 ? void 0 : publicUrl(sender.endpoint, "connected.email.endpoint", false);
98
+ if (sender.provider === "custom" && !endpoint)
99
+ throw new Error("Custom email requires connected.email.endpoint.");
100
+ if (sender.provider !== "custom" && endpoint)
101
+ throw new Error("Only custom email uses connected.email.endpoint.");
102
+ if (endpoint && (new URL(endpoint).username || new URL(endpoint).password || new URL(endpoint).hash))
103
+ throw new Error(
104
+ "Custom email endpoint must not contain credentials or a fragment."
105
+ );
106
+ email = {
107
+ provider: sender.provider,
108
+ from,
109
+ ...endpoint ? { endpoint } : {}
110
+ };
111
+ }
112
+ if (input.images !== void 0 && typeof input.images !== "boolean")
113
+ throw new Error("connected.images must be true or false.");
114
+ if (input.processing !== void 0 && input.processing !== "small" && input.processing !== "standard")
115
+ throw new Error("connected.processing must be small or standard.");
116
+ connected = {
117
+ issuer,
118
+ loginUrl,
119
+ ...input.processing !== void 0 ? { processing: input.processing } : {},
120
+ ...email ? { email } : {},
121
+ ...input.images !== void 0 ? { images: input.images } : {}
122
+ };
123
+ }
124
+ const theme = object(value.theme, "theme");
125
+ rejectUnknown(theme, ["preset"], "theme");
126
+ const notion = storage(
127
+ value.notion,
128
+ feedbackPropertyKeys,
129
+ "notion"
130
+ );
131
+ const features = object(value.features, "features");
132
+ rejectUnknown(features, featureKeys, "features");
133
+ const validatedFeatures = Object.fromEntries(
134
+ featureKeys.map((key) => {
135
+ if (typeof features[key] !== "boolean")
136
+ throw new Error(`features.${key} must be true or false.`);
137
+ return [key, features[key]];
138
+ })
139
+ );
140
+ const changelog = value.changelog === void 0 ? void 0 : storage(
141
+ value.changelog,
142
+ changelogPropertyKeys,
143
+ "changelog"
144
+ );
145
+ if (validatedFeatures.changelog && !changelog)
146
+ throw new Error(
147
+ "Changelog is enabled but changelog storage is not configured. Add changelog identifiers and property mappings, or set features.changelog to false."
148
+ );
149
+ const submissions = object(value.submissions, "submissions");
150
+ rejectUnknown(
151
+ submissions,
152
+ ["moderation", "maxTitleLength", "maxDescriptionLength"],
153
+ "submissions"
154
+ );
155
+ if (submissions.moderation !== "required")
156
+ throw new Error('submissions.moderation must be "required".');
157
+ const turnstile = object(value.turnstile, "turnstile");
158
+ rejectUnknown(turnstile, ["enabled", "siteKey"], "turnstile");
159
+ if (typeof turnstile.enabled !== "boolean")
160
+ throw new Error("turnstile.enabled must be true or false.");
161
+ const siteKey = turnstile.siteKey === void 0 ? void 0 : text(turnstile.siteKey, "turnstile.siteKey");
162
+ if (turnstile.enabled && !siteKey)
163
+ throw new Error("Turnstile is enabled but turnstile.siteKey is missing.");
164
+ return {
165
+ product: validatedProduct,
166
+ ...connected ? { connected } : {},
167
+ theme: { preset: text(theme.preset, "theme.preset") },
168
+ notion,
169
+ ...changelog ? { changelog } : {},
170
+ features: validatedFeatures,
171
+ submissions: {
172
+ moderation: "required",
173
+ maxTitleLength: positiveInteger(
174
+ submissions.maxTitleLength,
175
+ "submissions.maxTitleLength"
176
+ ),
177
+ maxDescriptionLength: positiveInteger(
178
+ submissions.maxDescriptionLength,
179
+ "submissions.maxDescriptionLength"
180
+ )
181
+ },
182
+ turnstile: { enabled: turnstile.enabled, ...siteKey ? { siteKey } : {} }
183
+ };
184
+ }
185
+ function storage(value, keys, label) {
186
+ const input = object(value, label);
187
+ rejectUnknown(input, ["databaseId", "dataSourceId", "properties"], label);
188
+ const properties = object(input.properties, `${label}.properties`);
189
+ rejectUnknown(properties, keys, `${label}.properties`);
190
+ for (const key of keys) text(properties[key], `${label}.properties.${key}`);
191
+ return {
192
+ databaseId: text(input.databaseId, `${label}.databaseId`),
193
+ dataSourceId: text(input.dataSourceId, `${label}.dataSourceId`),
194
+ properties: { ...properties }
195
+ };
196
+ }
197
+ function object(value, label) {
198
+ if (!value || typeof value !== "object" || Array.isArray(value))
199
+ throw new Error(`${label} must be an object.`);
200
+ return value;
201
+ }
202
+ function text(value, label) {
203
+ if (typeof value !== "string" || !value.trim())
204
+ throw new Error(`${label} must be a non-empty string.`);
205
+ return value.trim();
206
+ }
207
+ function positiveInteger(value, label) {
208
+ if (!Number.isInteger(value) || Number(value) <= 0)
209
+ throw new Error(`${label} must be a positive integer.`);
210
+ return Number(value);
211
+ }
212
+ function rejectUnknown(value, allowed, label) {
213
+ const key = Object.keys(value).find(
214
+ (candidate) => !allowed.includes(candidate)
215
+ );
216
+ if (key) throw new Error(`Unknown ${label} key "${key}".`);
217
+ }
218
+ function optionalPublicUrl(value, key) {
219
+ return value[key] === void 0 ? {} : { [key]: publicUrl(value[key], `product.${key}`, false) };
220
+ }
221
+ function publicUrl(value, label, allowRelative) {
222
+ const url = text(value, label);
223
+ if (allowRelative && url.startsWith("/") && !url.startsWith("//") && !url.includes("\\") && !Array.from(url).some((character) => character.charCodeAt(0) <= 32))
224
+ return url;
225
+ try {
226
+ if (new URL(url).protocol === "https:") return url;
227
+ } catch {
228
+ }
229
+ throw new Error(
230
+ `${label} must be ${allowRelative ? "root-relative or " : ""}an HTTPS URL.`
231
+ );
232
+ }
233
+
234
+ // src/index.ts
235
+ import { basename, join as join2 } from "node:path";
236
+
237
+ // src/template.ts
238
+ import { createHash } from "node:crypto";
239
+ import { parse } from "jsonc-parser";
240
+ import {
241
+ cp,
242
+ mkdir,
243
+ readFile,
244
+ readdir,
245
+ rm,
246
+ stat,
247
+ writeFile
248
+ } from "node:fs/promises";
249
+ import { dirname, join, relative, resolve } from "node:path";
250
+ import { fileURLToPath } from "node:url";
251
+ var ignored = ["node_modules", "dist", ".wrangler", ".dev.vars", ".env"];
252
+ var packagedTemplateDirectory = resolve(
253
+ dirname(fileURLToPath(import.meta.url)),
254
+ "../template"
255
+ );
256
+ async function assembleFlatTemplate(coreRoot, destination) {
257
+ const target = resolve(destination);
258
+ await rm(target, { recursive: true, force: true });
259
+ await mkdir(join(target, "src/feedbax"), { recursive: true });
260
+ await copy(join(coreRoot, "apps/portal/src"), join(target, "src"));
261
+ await copy(join(coreRoot, "apps/portal/public"), join(target, "public"));
262
+ await copy(
263
+ join(coreRoot, "apps/portal/migrations"),
264
+ join(target, "migrations")
265
+ );
266
+ await copy(
267
+ join(coreRoot, "docs/connected-profile.md"),
268
+ join(target, "CONNECTED.md")
269
+ );
270
+ await copy(
271
+ join(coreRoot, "packages/create-feedbax/docs/installation-readme.md"),
272
+ join(target, "README.md")
273
+ );
274
+ await copy(
275
+ join(coreRoot, "packages/create-feedbax/docs/installation-agents.md"),
276
+ join(target, "AGENTS.md")
277
+ );
278
+ await copy(
279
+ join(coreRoot, "packages/feedback/src"),
280
+ join(target, "src/feedbax/feedback")
281
+ );
282
+ await copy(
283
+ join(coreRoot, "packages/changelog/src"),
284
+ join(target, "src/feedbax/changelog")
285
+ );
286
+ await copy(
287
+ join(coreRoot, "packages/config/src"),
288
+ join(target, "src/feedbax/config")
289
+ );
290
+ await copy(
291
+ join(coreRoot, "packages/ui/src"),
292
+ join(target, "src/components/ui")
293
+ );
294
+ const stylesheet = join(target, "src/index.css");
295
+ await writeFile(
296
+ stylesheet,
297
+ (await readFile(stylesheet, "utf8")).replace(
298
+ '@import "@feedbax/ui/globals.css";',
299
+ '@import "./components/ui/styles/globals.css";'
300
+ )
301
+ );
302
+ const globals = join(target, "src/components/ui/styles/globals.css");
303
+ await writeFile(
304
+ globals,
305
+ (await readFile(globals, "utf8")).replace(
306
+ '@source "../../../../apps/**/*.{ts,tsx}";',
307
+ '@source "../../../**/*.{ts,tsx}";'
308
+ )
309
+ );
310
+ for (const name of ["vite.config.ts"])
311
+ await copy(join(coreRoot, "apps/portal", name), join(target, name));
312
+ const components = parse(
313
+ await readFile(join(coreRoot, "apps/portal/components.json"), "utf8")
314
+ );
315
+ components.tailwind.css = "src/index.css";
316
+ await writeFile(
317
+ join(target, "components.json"),
318
+ JSON.stringify(components, null, 2) + "\n"
319
+ );
320
+ const wrangler = parse(
321
+ await readFile(join(coreRoot, "apps/portal/wrangler.jsonc"), "utf8")
322
+ );
323
+ delete wrangler.routes;
324
+ delete wrangler.vars;
325
+ delete wrangler.account_id;
326
+ wrangler.name = "feedbax-installation";
327
+ await writeFile(
328
+ join(target, "wrangler.jsonc"),
329
+ JSON.stringify(wrangler, null, 2) + "\n"
330
+ );
331
+ await copy(join(coreRoot, "LICENSE"), join(target, "LICENSE"));
332
+ await copy(
333
+ join(coreRoot, "eslint.config.mjs"),
334
+ join(target, "eslint.config.mjs")
335
+ );
336
+ await writeFile(
337
+ join(target, "package.json"),
338
+ JSON.stringify(await flatManifest(coreRoot), null, 2) + "\n"
339
+ );
340
+ await writeFile(
341
+ join(target, "tsconfig.json"),
342
+ JSON.stringify(flatTsconfig, null, 2) + "\n"
343
+ );
344
+ await writeFile(
345
+ join(target, "vitest.config.ts"),
346
+ 'import { fileURLToPath } from "node:url";\nimport { defineConfig } from "vitest/config";\nconst src = fileURLToPath(new URL("./src/", import.meta.url));\nexport default defineConfig({ resolve: { alias: [{ find: "@feedbax/feedback", replacement: `${src}feedbax/feedback/index.ts` }, { find: "@feedbax/changelog", replacement: `${src}feedbax/changelog/index.ts` }, { find: "@feedbax/config", replacement: `${src}feedbax/config/index.ts` }, { find: "cloudflare:workers", replacement: `${src}cloudflare-workers-test.ts` }, { find: /^@feedbax\\/ui\\/(.*)$/, replacement: `${src}components/ui/$1` }] } });\n'
347
+ );
348
+ const feedbackKeys = [
349
+ "title",
350
+ "slug",
351
+ "description",
352
+ "type",
353
+ "status",
354
+ "published",
355
+ "voteCount",
356
+ "teamResponse",
357
+ "releaseNotesUrl",
358
+ "submitterName",
359
+ "submitterEmail",
360
+ "source",
361
+ "externalId",
362
+ "editTokenHash",
363
+ "createdAt",
364
+ "updatedAt"
365
+ ];
366
+ const changelogKeys = [
367
+ "title",
368
+ "slug",
369
+ "date",
370
+ "summary",
371
+ "body",
372
+ "labels",
373
+ "image",
374
+ "published",
375
+ "createdAt",
376
+ "updatedAt"
377
+ ];
378
+ const fixture = {
379
+ product: {
380
+ name: "Feedbax",
381
+ description: "Share feedback and follow product progress.",
382
+ logo: "/feedbax-mark.svg"
383
+ },
384
+ theme: { preset: "lyra-neutral" },
385
+ notion: {
386
+ databaseId: "test-database",
387
+ dataSourceId: "test-feedback",
388
+ properties: Object.fromEntries(
389
+ feedbackKeys.map((key) => [key, `test-${key}`])
390
+ )
391
+ },
392
+ changelog: {
393
+ databaseId: "test-database",
394
+ dataSourceId: "test-changelog",
395
+ properties: Object.fromEntries(
396
+ changelogKeys.map((key) => [key, `test-${key}`])
397
+ )
398
+ },
399
+ features: { voting: true, comments: true, changelog: true },
400
+ submissions: {
401
+ moderation: "required",
402
+ maxTitleLength: 120,
403
+ maxDescriptionLength: 5e3
404
+ },
405
+ turnstile: { enabled: false }
406
+ };
407
+ await writeFile(
408
+ join(target, "src/feedbax.test-fixture.ts"),
409
+ `import { defineFeedbax } from "@feedbax/config";
410
+
411
+ // prettier-ignore
412
+ export default defineFeedbax(${JSON.stringify(fixture, null, 2)});
413
+ `
414
+ );
415
+ await writeFile(
416
+ join(target, "vitest.setup.ts"),
417
+ 'import { vi } from "vitest";\n\nvi.mock("./src/feedbax", () => import("./src/feedbax.test-fixture"));\n'
418
+ );
419
+ const testConfigPath = join(target, "vitest.config.ts");
420
+ await writeFile(
421
+ testConfigPath,
422
+ (await readFile(testConfigPath, "utf8")).replace(
423
+ "defineConfig({ resolve:",
424
+ 'defineConfig({ test: { setupFiles: ["./vitest.setup.ts"] }, resolve:'
425
+ )
426
+ );
427
+ await writeFile(
428
+ join(target, ".gitignore.template"),
429
+ ".dev.vars\n.env*\ndist/\n.wrangler/\n*.log\nnode_modules/\n"
430
+ );
431
+ await writeFile(
432
+ join(target, ".npmignore.template"),
433
+ ".dev.vars\n.env*\ndist/\n.wrangler/\n*.log\n"
434
+ );
435
+ await writeFile(
436
+ join(target, ".prettierignore.template"),
437
+ "pnpm-lock.yaml\nsrc/routeTree.gen.ts\ntsconfig.json\nvitest.config.ts\n"
438
+ );
439
+ const checksum = await templateChecksum(target);
440
+ await writeFile(join(target, ".feedbax-template.sha256"), `${checksum}
441
+ `);
442
+ return checksum;
443
+ }
444
+ async function templateChecksum(root) {
445
+ const hash = createHash("sha256");
446
+ for (const file of await files(root)) {
447
+ const name = relative(root, file);
448
+ if (name === ".feedbax-template.sha256") continue;
449
+ hash.update(name).update("\0").update(await readFile(file)).update("\0");
450
+ }
451
+ return hash.digest("hex");
452
+ }
453
+ async function verifyPackagedTemplate(coreRoot, packaged = packagedTemplateDirectory) {
454
+ const temporary = `${packaged}.verify-${process.pid}`;
455
+ try {
456
+ const expected = await assembleFlatTemplate(coreRoot, temporary);
457
+ const actual = (await readFile(join(packaged, ".feedbax-template.sha256"), "utf8")).trim();
458
+ if (actual !== expected || await templateChecksum(packaged) !== actual)
459
+ throw new Error(
460
+ "Packaged create-feedbax template is out of sync. Run pnpm template:sync."
461
+ );
462
+ } finally {
463
+ await rm(temporary, { recursive: true, force: true });
464
+ }
465
+ }
466
+ async function copyPackagedTemplate(destination, packaged = packagedTemplateDirectory) {
467
+ const target = resolve(destination);
468
+ await mkdir(target, { recursive: true });
469
+ if ((await readdir(target)).length)
470
+ throw new Error(
471
+ `Refusing to generate into non-empty destination: ${target}`
472
+ );
473
+ await cp(packaged, target, { recursive: true });
474
+ await renameIgnoreTemplates(target);
475
+ }
476
+ async function renameIgnoreTemplates(target) {
477
+ const { rename } = await import("node:fs/promises");
478
+ for (const name of [".gitignore", ".npmignore", ".prettierignore"])
479
+ await rename(join(target, `${name}.template`), join(target, name));
480
+ }
481
+ async function flatManifest(coreRoot) {
482
+ const packageFiles = [
483
+ "apps/portal",
484
+ "packages/ui",
485
+ "packages/config",
486
+ "packages/feedback",
487
+ "packages/changelog"
488
+ ];
489
+ const manifests = await Promise.all(
490
+ packageFiles.map(
491
+ async (path) => JSON.parse(await readFile(join(coreRoot, path, "package.json"), "utf8"))
492
+ )
493
+ );
494
+ const workspace = await readFile(
495
+ join(coreRoot, "pnpm-workspace.yaml"),
496
+ "utf8"
497
+ );
498
+ const catalog = Object.fromEntries(
499
+ [...workspace.matchAll(/^ {2}("?[^"\s:]+"?): (.+)$/gm)].map((match) => [
500
+ match[1].replaceAll('"', ""),
501
+ match[2]
502
+ ])
503
+ );
504
+ const collect = (section) => Object.fromEntries(
505
+ manifests.flatMap((manifest) => Object.entries(manifest[section] ?? {})).filter(([name]) => !name.startsWith("@feedbax/")).map(([name, version2]) => [
506
+ name,
507
+ version2 === "catalog:" ? catalog[name] : version2
508
+ ])
509
+ );
510
+ return {
511
+ name: "feedbax-installation",
512
+ version: JSON.parse(
513
+ await readFile(join(coreRoot, "packages/feedbax/package.json"), "utf8")
514
+ ).version,
515
+ private: true,
516
+ type: "module",
517
+ scripts: {
518
+ dev: "vite dev",
519
+ build: "vite build",
520
+ test: "vitest run",
521
+ "type-check": "tsr generate && tsc --noEmit",
522
+ lint: "eslint .",
523
+ "format:check": "prettier --check .",
524
+ deploy: "feedbax deploy",
525
+ "deploy:dry-run": "wrangler deploy --dry-run"
526
+ },
527
+ dependencies: collect("dependencies"),
528
+ devDependencies: {
529
+ ...collect("devDependencies"),
530
+ "@eslint/js": "10.0.1",
531
+ "@shadcn/lint": "0.2.1",
532
+ "@types/node": "22.20.1",
533
+ feedbax: JSON.parse(
534
+ await readFile(join(coreRoot, "packages/feedbax/package.json"), "utf8")
535
+ ).version,
536
+ eslint: "10.8.1",
537
+ globals: "17.11.0",
538
+ prettier: "3.9.6",
539
+ "typescript-eslint": "8.67.0",
540
+ vitest: "4.1.11"
541
+ },
542
+ pnpm: { onlyBuiltDependencies: ["esbuild"] },
543
+ engines: { node: ">=22.14.0" },
544
+ packageManager: "pnpm@10.29.3",
545
+ license: "Apache-2.0"
546
+ };
547
+ }
548
+ var flatTsconfig = {
549
+ include: ["src/**/*.ts", "src/**/*.tsx"],
550
+ compilerOptions: {
551
+ target: "ES2022",
552
+ jsx: "react-jsx",
553
+ module: "ESNext",
554
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
555
+ types: ["vite/client", "node"],
556
+ moduleResolution: "bundler",
557
+ allowImportingTsExtensions: true,
558
+ noEmit: true,
559
+ skipLibCheck: true,
560
+ strict: true,
561
+ paths: {
562
+ "@/*": ["./src/*"],
563
+ "@feedbax/feedback": ["./src/feedbax/feedback/index.ts"],
564
+ "@feedbax/changelog": ["./src/feedbax/changelog/index.ts"],
565
+ "@feedbax/config": ["./src/feedbax/config/index.ts"],
566
+ "@feedbax/ui/*": ["./src/components/ui/*"],
567
+ "@feedbax/ui/globals.css": ["./src/components/ui/styles/globals.css"]
568
+ }
569
+ }
570
+ };
571
+ async function copy(from, to) {
572
+ await cp(from, to, {
573
+ recursive: true,
574
+ filter: (source) => !ignored.some((entry) => source.includes(`/${entry}`))
575
+ });
576
+ }
577
+ async function files(root) {
578
+ const entries = await readdir(root);
579
+ const result = [];
580
+ for (const entry of entries.sort()) {
581
+ const path = join(root, entry);
582
+ if ((await stat(path)).isDirectory()) result.push(...await files(path));
583
+ else result.push(path);
584
+ }
585
+ return result;
586
+ }
587
+
588
+ // ../feedback/src/notion-request.ts
589
+ var MAX_ATTEMPTS = 3;
590
+ var BASE_BACKOFF_MILLISECONDS = 250;
591
+ var MAX_BACKOFF_MILLISECONDS = 2e3;
592
+ async function requestNotion(input, init = {}, {
593
+ request,
594
+ retry = {},
595
+ operation
596
+ }) {
597
+ const sleep = retry.sleep ?? defaultSleep;
598
+ const random = retry.random ?? Math.random;
599
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
600
+ const response = await request(input, init);
601
+ if (!isRetryableNotionFailure(response.status, operation)) return response;
602
+ if (attempt === MAX_ATTEMPTS) {
603
+ throw new Error(
604
+ `Notion request failed after ${MAX_ATTEMPTS} attempts (${response.status}).`
605
+ );
606
+ }
607
+ await sleep(retryDelayMilliseconds(response, attempt, random));
608
+ }
609
+ throw new Error("Notion request retry loop ended unexpectedly.");
610
+ }
611
+ function isRetryableNotionFailure(status, operation) {
612
+ return status === 429 || status === 529 && operation === "idempotent";
613
+ }
614
+ function retryDelayMilliseconds(response, attempt, random) {
615
+ const retryAfter = response.headers.get("Retry-After");
616
+ if (retryAfter) {
617
+ const seconds = Number(retryAfter);
618
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
619
+ }
620
+ return exponentialDelayMilliseconds(attempt, random);
621
+ }
622
+ function exponentialDelayMilliseconds(attempt, random) {
623
+ const ceiling = Math.min(
624
+ BASE_BACKOFF_MILLISECONDS * 2 ** (attempt - 1),
625
+ MAX_BACKOFF_MILLISECONDS
626
+ );
627
+ return Math.round(ceiling / 2 + random() * (ceiling / 2));
628
+ }
629
+ async function defaultSleep(milliseconds) {
630
+ await new Promise((resolve2) => setTimeout(resolve2, milliseconds));
631
+ }
632
+
633
+ // ../feedback/src/notion-data-source.ts
634
+ var NOTION_API_URL = "https://api.notion.com/v1";
635
+ var NOTION_API_VERSION = "2026-03-11";
636
+ var canonicalProperties = {
637
+ title: { name: "Title", type: "title", schema: { title: {} } },
638
+ slug: { name: "Slug", type: "rich_text", schema: { rich_text: {} } },
639
+ description: {
640
+ name: "Description",
641
+ type: "rich_text",
642
+ schema: { rich_text: {} }
643
+ },
644
+ type: {
645
+ name: "Type",
646
+ type: "select",
647
+ schema: {
648
+ select: {
649
+ options: [
650
+ { name: "Feature Request" },
651
+ { name: "Bug Report" },
652
+ { name: "General Feedback" }
653
+ ]
654
+ }
655
+ }
656
+ },
657
+ status: {
658
+ name: "Status",
659
+ type: "select",
660
+ schema: {
661
+ select: {
662
+ options: [
663
+ { name: "New" },
664
+ { name: "Reviewing" },
665
+ { name: "Planned" },
666
+ { name: "In Progress" },
667
+ { name: "Shipped" },
668
+ { name: "Closed" }
669
+ ]
670
+ }
671
+ }
672
+ },
673
+ published: {
674
+ name: "Published",
675
+ type: "checkbox",
676
+ schema: { checkbox: {} }
677
+ },
678
+ voteCount: {
679
+ name: "Vote Count",
680
+ type: "number",
681
+ schema: { number: { format: "number" } }
682
+ },
683
+ teamResponse: {
684
+ name: "Team Response",
685
+ type: "rich_text",
686
+ schema: { rich_text: {} }
687
+ },
688
+ releaseNotesUrl: {
689
+ name: "Release Notes URL",
690
+ type: "url",
691
+ schema: { url: {} }
692
+ },
693
+ submitterName: {
694
+ name: "Submitter Name",
695
+ type: "rich_text",
696
+ schema: { rich_text: {} }
697
+ },
698
+ submitterEmail: {
699
+ name: "Submitter Email",
700
+ type: "email",
701
+ schema: { email: {} }
702
+ },
703
+ source: {
704
+ name: "Source",
705
+ type: "select",
706
+ schema: {
707
+ select: {
708
+ options: [{ name: "Portal" }, { name: "API" }, { name: "Team" }]
709
+ }
710
+ }
711
+ },
712
+ externalId: {
713
+ name: "External ID",
714
+ type: "rich_text",
715
+ schema: { rich_text: {} }
716
+ },
717
+ editTokenHash: {
718
+ name: "Edit Token Hash",
719
+ type: "rich_text",
720
+ schema: { rich_text: {} }
721
+ },
722
+ createdAt: {
723
+ name: "Created At",
724
+ type: "created_time",
725
+ schema: { created_time: {} }
726
+ },
727
+ updatedAt: {
728
+ name: "Updated At",
729
+ type: "last_edited_time",
730
+ schema: { last_edited_time: {} }
731
+ }
732
+ };
733
+ var feedbackWorkflowProperties = {
734
+ Owner: { people: {} },
735
+ Priority: {
736
+ select: {
737
+ options: [
738
+ { name: "Urgent" },
739
+ { name: "High" },
740
+ { name: "Medium" },
741
+ { name: "Low" }
742
+ ]
743
+ }
744
+ },
745
+ "Strategic Theme": { multi_select: { options: [] } },
746
+ "Participant Segment": { multi_select: { options: [] } },
747
+ Impact: {
748
+ select: {
749
+ options: [{ name: "High" }, { name: "Medium" }, { name: "Low" }]
750
+ }
751
+ },
752
+ Effort: {
753
+ select: {
754
+ options: [{ name: "Small" }, { name: "Medium" }, { name: "Large" }]
755
+ }
756
+ },
757
+ "Decision Note": { rich_text: {} },
758
+ "Needs Follow-up": { checkbox: {} }
759
+ };
760
+ async function createNotionFeedbackDataSource({
761
+ token,
762
+ parentPageId,
763
+ request = fetch,
764
+ retry
765
+ }) {
766
+ const response = await requestNotion(
767
+ `${NOTION_API_URL}/databases`,
768
+ {
769
+ method: "POST",
770
+ headers: notionHeaders(token),
771
+ body: JSON.stringify({
772
+ parent: { type: "page_id", page_id: parentPageId },
773
+ title: [{ type: "text", text: { content: "Feedback" } }],
774
+ initial_data_source: {
775
+ properties: {
776
+ ...Object.fromEntries(
777
+ Object.values(canonicalProperties).map(({ name, schema }) => [
778
+ name,
779
+ schema
780
+ ])
781
+ ),
782
+ ...feedbackWorkflowProperties
783
+ }
784
+ }
785
+ })
786
+ },
787
+ { request, retry, operation: "create" }
788
+ );
789
+ const database = await readNotionResponse(response);
790
+ if (typeof database.id !== "string") {
791
+ throw new Error("Notion did not return the created Feedback database.");
792
+ }
793
+ const dataSourceId = firstDataSourceId(database);
794
+ const dataSourceResponse = await requestNotion(
795
+ `${NOTION_API_URL}/data_sources/${dataSourceId}`,
796
+ { method: "GET", headers: notionHeaders(token) },
797
+ { request, retry, operation: "idempotent" }
798
+ );
799
+ const dataSource = await readNotionResponse(dataSourceResponse);
800
+ return configurationFromCanonicalNames(dataSource, database.id);
801
+ }
802
+ async function createFeedbackWorkflowViews({
803
+ token,
804
+ databaseId,
805
+ dataSourceId,
806
+ propertyIds,
807
+ request = fetch
808
+ }) {
809
+ const views = [
810
+ {
811
+ name: "Inbox",
812
+ type: "table",
813
+ filter: selectFilter(propertyIds.status, "New"),
814
+ sorts: [{ property: propertyIds.createdAt, direction: "descending" }]
815
+ },
816
+ {
817
+ name: "Reviewing",
818
+ type: "table",
819
+ filter: selectFilter(propertyIds.status, "Reviewing"),
820
+ sorts: [{ property: propertyIds.updatedAt, direction: "descending" }]
821
+ },
822
+ {
823
+ name: "Prioritization",
824
+ type: "table",
825
+ filter: {
826
+ or: [
827
+ selectFilter(propertyIds.status, "Reviewing"),
828
+ selectFilter(propertyIds.status, "Planned")
829
+ ]
830
+ },
831
+ sorts: [
832
+ { property: "Priority", direction: "ascending" },
833
+ { property: propertyIds.voteCount, direction: "descending" }
834
+ ]
835
+ },
836
+ {
837
+ name: "Follow-up",
838
+ type: "table",
839
+ filter: { property: "Needs Follow-up", checkbox: { equals: true } }
840
+ },
841
+ {
842
+ name: "Roadmap",
843
+ type: "board",
844
+ filter: {
845
+ or: ["Planned", "In Progress", "Shipped"].map(
846
+ (status) => selectFilter(propertyIds.status, status)
847
+ )
848
+ },
849
+ configuration: {
850
+ type: "board",
851
+ group_by: {
852
+ type: "select",
853
+ property_id: propertyIds.status,
854
+ group_by: "option",
855
+ sort: { type: "manual" }
856
+ }
857
+ }
858
+ },
859
+ {
860
+ name: "Shipped",
861
+ type: "table",
862
+ filter: selectFilter(propertyIds.status, "Shipped"),
863
+ sorts: [{ property: propertyIds.updatedAt, direction: "descending" }]
864
+ }
865
+ ];
866
+ for (const view of views) {
867
+ const response = await requestNotion(
868
+ `${NOTION_API_URL}/views`,
869
+ {
870
+ method: "POST",
871
+ headers: notionHeaders(token),
872
+ body: JSON.stringify({
873
+ database_id: databaseId,
874
+ data_source_id: dataSourceId,
875
+ ...view
876
+ })
877
+ },
878
+ { request, operation: "create" }
879
+ );
880
+ await readNotionResponse(response);
881
+ }
882
+ }
883
+ function selectFilter(property, value) {
884
+ return { property, select: { equals: value } };
885
+ }
886
+ async function validateNotionFeedbackDataSource({
887
+ token,
888
+ dataSourceId,
889
+ propertyIds,
890
+ request = fetch,
891
+ retry
892
+ }) {
893
+ const response = await requestNotion(
894
+ `${NOTION_API_URL}/data_sources/${dataSourceId}`,
895
+ {
896
+ method: "GET",
897
+ headers: notionHeaders(token)
898
+ },
899
+ { request, retry, operation: "idempotent" }
900
+ );
901
+ const dataSource = await readNotionResponse(response);
902
+ const properties = dataSource.properties;
903
+ if (typeof dataSource.id !== "string" || !isPropertyMap(properties)) {
904
+ throw new Error(
905
+ "Notion returned an invalid Feedback Data Source response."
906
+ );
907
+ }
908
+ const propertiesById = new Map(
909
+ Object.values(properties).filter(
910
+ (property) => Boolean(property) && typeof property.id === "string"
911
+ ).map((property) => [property.id, property])
912
+ );
913
+ const repairs = [];
914
+ for (const [key, { name, type }] of Object.entries(canonicalProperties)) {
915
+ const configuredId = propertyIds[key];
916
+ const property = propertiesById.get(configuredId);
917
+ if (!property) {
918
+ repairs.push(
919
+ `Restore the "${name}" property with ID "${configuredId}" and type "${type}", then update the configured property ID if Notion assigns a new one.`
920
+ );
921
+ } else if (property.type !== type) {
922
+ repairs.push(
923
+ `Change the property configured as "${name}" (ID "${configuredId}") from "${String(property.type)}" to "${type}", or restore a ${type} property and configure its ID.`
924
+ );
925
+ }
926
+ }
927
+ if (repairs.length > 0) {
928
+ throw new Error(repairs.join("\n"));
929
+ }
930
+ return {
931
+ databaseId: databaseIdFromDataSource(dataSource),
932
+ dataSourceId,
933
+ propertyIds: { ...propertyIds }
934
+ };
935
+ }
936
+ function notionHeaders(token) {
937
+ return {
938
+ Authorization: `Bearer ${token}`,
939
+ "Content-Type": "application/json",
940
+ "Notion-Version": NOTION_API_VERSION
941
+ };
942
+ }
943
+ async function readNotionResponse(response) {
944
+ const body = await response.json();
945
+ if (!response.ok) {
946
+ throw new Error(
947
+ `Notion rejected the Feedback Data Source request (${response.status}).`
948
+ );
949
+ }
950
+ if (!body || typeof body !== "object") {
951
+ throw new Error(
952
+ "Notion returned an invalid Feedback Data Source response."
953
+ );
954
+ }
955
+ return body;
956
+ }
957
+ function firstDataSourceId(database) {
958
+ const dataSources = Reflect.get(database, "data_sources");
959
+ const firstDataSource = Array.isArray(dataSources) ? dataSources[0] : void 0;
960
+ const id = firstDataSource && typeof firstDataSource === "object" ? Reflect.get(firstDataSource, "id") : void 0;
961
+ if (typeof id !== "string") {
962
+ throw new Error("Notion did not return the created Feedback Data Source.");
963
+ }
964
+ return id;
965
+ }
966
+ function configurationFromCanonicalNames(dataSource, databaseId) {
967
+ const properties = dataSource.properties;
968
+ if (typeof dataSource.id !== "string" || !isPropertyMap(properties)) {
969
+ throw new Error(
970
+ "Notion returned an invalid Feedback Data Source response."
971
+ );
972
+ }
973
+ return {
974
+ databaseId,
975
+ dataSourceId: dataSource.id,
976
+ propertyIds: Object.fromEntries(
977
+ Object.entries(canonicalProperties).map(([key, { name }]) => {
978
+ const property = properties[name];
979
+ if (!property || typeof property.id !== "string") {
980
+ throw new Error(
981
+ `Notion did not return the required ${name} property.`
982
+ );
32
983
  }
33
- else if (booleanFlags.has(entry))
34
- continue;
35
- else if (entry.startsWith('-'))
36
- throw new Error(`Unknown option ${entry}.`);
37
- else if (directory)
38
- throw new Error(`Unexpected argument ${entry}.`);
39
- else
40
- directory = entry;
41
- }
42
- const value = (name) => values.get(name);
43
- directory ??= 'my-feedback';
44
- const connector = value('--connector') ?? 'notion';
45
- const storage = value('--storage') ?? 'notion';
46
- if (connector !== 'notion')
47
- throw new Error(`Connector "${connector}" is not supported in Feedbax 0.1.0.`);
48
- if (storage !== 'notion')
49
- throw new Error(`Storage "${storage}" is deferred until after Feedbax 0.1.0.`);
50
- const identity = identityMode(value('--identity') ?? 'email');
51
- const deploy = (value('--deploy') ?? 'cloudflare');
52
- if (!allowedDeployments.has(deploy))
53
- throw new Error(`Deployment "${deploy}" is not supported.`);
54
- const packageManager = (value('--package-manager') ??
55
- 'pnpm');
56
- if (!allowedPackageManagers.has(packageManager))
57
- throw new Error(`Package manager "${packageManager}" is not supported.`);
58
- return {
59
- directory,
60
- identity,
61
- deploy,
62
- packageManager,
63
- preset: value('--preset') ?? 'feedbax-default',
64
- install: !argv.includes('--no-install'),
65
- git: !argv.includes('--no-git'),
984
+ return [key, property.id];
985
+ })
986
+ )
987
+ };
988
+ }
989
+ function databaseIdFromDataSource(dataSource) {
990
+ const parent = dataSource.parent;
991
+ const databaseId = parent && typeof parent === "object" ? Reflect.get(parent, "database_id") : void 0;
992
+ if (typeof databaseId !== "string") {
993
+ throw new Error(
994
+ "Notion did not return the Feedback Data Source parent database."
995
+ );
996
+ }
997
+ return databaseId;
998
+ }
999
+ function isPropertyMap(value) {
1000
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1001
+ }
1002
+
1003
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/entity.js
1004
+ var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
1005
+ function is(value, type) {
1006
+ if (!value || typeof value !== "object") {
1007
+ return false;
1008
+ }
1009
+ if (value instanceof type) {
1010
+ return true;
1011
+ }
1012
+ if (!Object.prototype.hasOwnProperty.call(type, entityKind)) {
1013
+ throw new Error(
1014
+ `Class "${type.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`
1015
+ );
1016
+ }
1017
+ let cls = Object.getPrototypeOf(value).constructor;
1018
+ if (cls) {
1019
+ while (cls) {
1020
+ if (entityKind in cls && cls[entityKind] === type[entityKind]) {
1021
+ return true;
1022
+ }
1023
+ cls = Object.getPrototypeOf(cls);
1024
+ }
1025
+ }
1026
+ return false;
1027
+ }
1028
+
1029
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/column.js
1030
+ var Column = class {
1031
+ constructor(table, config) {
1032
+ this.table = table;
1033
+ this.config = config;
1034
+ this.name = config.name;
1035
+ this.keyAsName = config.keyAsName;
1036
+ this.notNull = config.notNull;
1037
+ this.default = config.default;
1038
+ this.defaultFn = config.defaultFn;
1039
+ this.onUpdateFn = config.onUpdateFn;
1040
+ this.hasDefault = config.hasDefault;
1041
+ this.primary = config.primaryKey;
1042
+ this.isUnique = config.isUnique;
1043
+ this.uniqueName = config.uniqueName;
1044
+ this.uniqueType = config.uniqueType;
1045
+ this.dataType = config.dataType;
1046
+ this.columnType = config.columnType;
1047
+ this.generated = config.generated;
1048
+ this.generatedIdentity = config.generatedIdentity;
1049
+ }
1050
+ static [entityKind] = "Column";
1051
+ name;
1052
+ keyAsName;
1053
+ primary;
1054
+ notNull;
1055
+ default;
1056
+ defaultFn;
1057
+ onUpdateFn;
1058
+ hasDefault;
1059
+ isUnique;
1060
+ uniqueName;
1061
+ uniqueType;
1062
+ dataType;
1063
+ columnType;
1064
+ enumValues = void 0;
1065
+ generated = void 0;
1066
+ generatedIdentity = void 0;
1067
+ config;
1068
+ mapFromDriverValue(value) {
1069
+ return value;
1070
+ }
1071
+ mapToDriverValue(value) {
1072
+ return value;
1073
+ }
1074
+ // ** @internal */
1075
+ shouldDisableInsert() {
1076
+ return this.config.generated !== void 0 && this.config.generated.type !== "byDefault";
1077
+ }
1078
+ };
1079
+
1080
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/column-builder.js
1081
+ var ColumnBuilder = class {
1082
+ static [entityKind] = "ColumnBuilder";
1083
+ config;
1084
+ constructor(name, dataType, columnType) {
1085
+ this.config = {
1086
+ name,
1087
+ keyAsName: name === "",
1088
+ notNull: false,
1089
+ default: void 0,
1090
+ hasDefault: false,
1091
+ primaryKey: false,
1092
+ isUnique: false,
1093
+ uniqueName: void 0,
1094
+ uniqueType: void 0,
1095
+ dataType,
1096
+ columnType,
1097
+ generated: void 0
1098
+ };
1099
+ }
1100
+ /**
1101
+ * Changes the data type of the column. Commonly used with `json` columns. Also, useful for branded types.
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * const users = pgTable('users', {
1106
+ * id: integer('id').$type<UserId>().primaryKey(),
1107
+ * details: json('details').$type<UserDetails>().notNull(),
1108
+ * });
1109
+ * ```
1110
+ */
1111
+ $type() {
1112
+ return this;
1113
+ }
1114
+ /**
1115
+ * Adds a `not null` clause to the column definition.
1116
+ *
1117
+ * Affects the `select` model of the table - columns *without* `not null` will be nullable on select.
1118
+ */
1119
+ notNull() {
1120
+ this.config.notNull = true;
1121
+ return this;
1122
+ }
1123
+ /**
1124
+ * Adds a `default <value>` clause to the column definition.
1125
+ *
1126
+ * Affects the `insert` model of the table - columns *with* `default` are optional on insert.
1127
+ *
1128
+ * If you need to set a dynamic default value, use {@link $defaultFn} instead.
1129
+ */
1130
+ default(value) {
1131
+ this.config.default = value;
1132
+ this.config.hasDefault = true;
1133
+ return this;
1134
+ }
1135
+ /**
1136
+ * Adds a dynamic default value to the column.
1137
+ * The function will be called when the row is inserted, and the returned value will be used as the column value.
1138
+ *
1139
+ * **Note:** This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
1140
+ */
1141
+ $defaultFn(fn) {
1142
+ this.config.defaultFn = fn;
1143
+ this.config.hasDefault = true;
1144
+ return this;
1145
+ }
1146
+ /**
1147
+ * Alias for {@link $defaultFn}.
1148
+ */
1149
+ $default = this.$defaultFn;
1150
+ /**
1151
+ * Adds a dynamic update value to the column.
1152
+ * The function will be called when the row is updated, and the returned value will be used as the column value if none is provided.
1153
+ * If no `default` (or `$defaultFn`) value is provided, the function will be called when the row is inserted as well, and the returned value will be used as the column value.
1154
+ *
1155
+ * **Note:** This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
1156
+ */
1157
+ $onUpdateFn(fn) {
1158
+ this.config.onUpdateFn = fn;
1159
+ this.config.hasDefault = true;
1160
+ return this;
1161
+ }
1162
+ /**
1163
+ * Alias for {@link $onUpdateFn}.
1164
+ */
1165
+ $onUpdate = this.$onUpdateFn;
1166
+ /**
1167
+ * Adds a `primary key` clause to the column definition. This implicitly makes the column `not null`.
1168
+ *
1169
+ * In SQLite, `integer primary key` implicitly makes the column auto-incrementing.
1170
+ */
1171
+ primaryKey() {
1172
+ this.config.primaryKey = true;
1173
+ this.config.notNull = true;
1174
+ return this;
1175
+ }
1176
+ /** @internal Sets the name of the column to the key within the table definition if a name was not given. */
1177
+ setName(name) {
1178
+ if (this.config.name !== "") return;
1179
+ this.config.name = name;
1180
+ }
1181
+ };
1182
+
1183
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/table.utils.js
1184
+ var TableName = /* @__PURE__ */ Symbol.for("drizzle:Name");
1185
+
1186
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/foreign-keys.js
1187
+ var ForeignKeyBuilder = class {
1188
+ static [entityKind] = "PgForeignKeyBuilder";
1189
+ /** @internal */
1190
+ reference;
1191
+ /** @internal */
1192
+ _onUpdate = "no action";
1193
+ /** @internal */
1194
+ _onDelete = "no action";
1195
+ constructor(config, actions) {
1196
+ this.reference = () => {
1197
+ const { name, columns, foreignColumns } = config();
1198
+ return { name, columns, foreignTable: foreignColumns[0].table, foreignColumns };
66
1199
  };
1200
+ if (actions) {
1201
+ this._onUpdate = actions.onUpdate;
1202
+ this._onDelete = actions.onDelete;
1203
+ }
1204
+ }
1205
+ onUpdate(action) {
1206
+ this._onUpdate = action === void 0 ? "no action" : action;
1207
+ return this;
1208
+ }
1209
+ onDelete(action) {
1210
+ this._onDelete = action === void 0 ? "no action" : action;
1211
+ return this;
1212
+ }
1213
+ /** @internal */
1214
+ build(table) {
1215
+ return new ForeignKey(table, this);
1216
+ }
1217
+ };
1218
+ var ForeignKey = class {
1219
+ constructor(table, builder) {
1220
+ this.table = table;
1221
+ this.reference = builder.reference;
1222
+ this.onUpdate = builder._onUpdate;
1223
+ this.onDelete = builder._onDelete;
1224
+ }
1225
+ static [entityKind] = "PgForeignKey";
1226
+ reference;
1227
+ onUpdate;
1228
+ onDelete;
1229
+ getName() {
1230
+ const { name, columns, foreignColumns } = this.reference();
1231
+ const columnNames = columns.map((column) => column.name);
1232
+ const foreignColumnNames = foreignColumns.map((column) => column.name);
1233
+ const chunks = [
1234
+ this.table[TableName],
1235
+ ...columnNames,
1236
+ foreignColumns[0].table[TableName],
1237
+ ...foreignColumnNames
1238
+ ];
1239
+ return name ?? `${chunks.join("_")}_fk`;
1240
+ }
1241
+ };
1242
+
1243
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/tracing-utils.js
1244
+ function iife(fn, ...args) {
1245
+ return fn(...args);
1246
+ }
1247
+
1248
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/unique-constraint.js
1249
+ function uniqueKeyName(table, columns) {
1250
+ return `${table[TableName]}_${columns.join("_")}_unique`;
67
1251
  }
68
- export async function resolveInteractiveCreateOptions(argv, prompt) {
69
- if (argv.includes('--yes'))
70
- return resolveCreateOptions(argv);
71
- const prompted = [...argv];
72
- const ask = async (flag, question, defaultValue) => {
73
- if (argv.includes(flag))
74
- return;
75
- const answer = (await prompt(question, defaultValue)).trim() || defaultValue;
76
- prompted.push(flag, answer);
1252
+ var UniqueConstraintBuilder = class {
1253
+ constructor(columns, name) {
1254
+ this.name = name;
1255
+ this.columns = columns;
1256
+ }
1257
+ static [entityKind] = "PgUniqueConstraintBuilder";
1258
+ /** @internal */
1259
+ columns;
1260
+ /** @internal */
1261
+ nullsNotDistinctConfig = false;
1262
+ nullsNotDistinct() {
1263
+ this.nullsNotDistinctConfig = true;
1264
+ return this;
1265
+ }
1266
+ /** @internal */
1267
+ build(table) {
1268
+ return new UniqueConstraint(table, this.columns, this.nullsNotDistinctConfig, this.name);
1269
+ }
1270
+ };
1271
+ var UniqueOnConstraintBuilder = class {
1272
+ static [entityKind] = "PgUniqueOnConstraintBuilder";
1273
+ /** @internal */
1274
+ name;
1275
+ constructor(name) {
1276
+ this.name = name;
1277
+ }
1278
+ on(...columns) {
1279
+ return new UniqueConstraintBuilder(columns, this.name);
1280
+ }
1281
+ };
1282
+ var UniqueConstraint = class {
1283
+ constructor(table, columns, nullsNotDistinct, name) {
1284
+ this.table = table;
1285
+ this.columns = columns;
1286
+ this.name = name ?? uniqueKeyName(this.table, this.columns.map((column) => column.name));
1287
+ this.nullsNotDistinct = nullsNotDistinct;
1288
+ }
1289
+ static [entityKind] = "PgUniqueConstraint";
1290
+ columns;
1291
+ name;
1292
+ nullsNotDistinct = false;
1293
+ getName() {
1294
+ return this.name;
1295
+ }
1296
+ };
1297
+
1298
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/utils/array.js
1299
+ function parsePgArrayValue(arrayString, startFrom, inQuotes) {
1300
+ for (let i = startFrom; i < arrayString.length; i++) {
1301
+ const char = arrayString[i];
1302
+ if (char === "\\") {
1303
+ i++;
1304
+ continue;
1305
+ }
1306
+ if (char === '"') {
1307
+ return [arrayString.slice(startFrom, i).replace(/\\/g, ""), i + 1];
1308
+ }
1309
+ if (inQuotes) {
1310
+ continue;
1311
+ }
1312
+ if (char === "," || char === "}") {
1313
+ return [arrayString.slice(startFrom, i).replace(/\\/g, ""), i];
1314
+ }
1315
+ }
1316
+ return [arrayString.slice(startFrom).replace(/\\/g, ""), arrayString.length];
1317
+ }
1318
+ function parsePgNestedArray(arrayString, startFrom = 0) {
1319
+ const result = [];
1320
+ let i = startFrom;
1321
+ let lastCharIsComma = false;
1322
+ while (i < arrayString.length) {
1323
+ const char = arrayString[i];
1324
+ if (char === ",") {
1325
+ if (lastCharIsComma || i === startFrom) {
1326
+ result.push("");
1327
+ }
1328
+ lastCharIsComma = true;
1329
+ i++;
1330
+ continue;
1331
+ }
1332
+ lastCharIsComma = false;
1333
+ if (char === "\\") {
1334
+ i += 2;
1335
+ continue;
1336
+ }
1337
+ if (char === '"') {
1338
+ const [value2, startFrom2] = parsePgArrayValue(arrayString, i + 1, true);
1339
+ result.push(value2);
1340
+ i = startFrom2;
1341
+ continue;
1342
+ }
1343
+ if (char === "}") {
1344
+ return [result, i + 1];
1345
+ }
1346
+ if (char === "{") {
1347
+ const [value2, startFrom2] = parsePgNestedArray(arrayString, i + 1);
1348
+ result.push(value2);
1349
+ i = startFrom2;
1350
+ continue;
1351
+ }
1352
+ const [value, newStartFrom] = parsePgArrayValue(arrayString, i, false);
1353
+ result.push(value);
1354
+ i = newStartFrom;
1355
+ }
1356
+ return [result, i];
1357
+ }
1358
+ function parsePgArray(arrayString) {
1359
+ const [result] = parsePgNestedArray(arrayString, 1);
1360
+ return result;
1361
+ }
1362
+ function makePgArray(array) {
1363
+ return `{${array.map((item) => {
1364
+ if (Array.isArray(item)) {
1365
+ return makePgArray(item);
1366
+ }
1367
+ if (typeof item === "string") {
1368
+ return `"${item.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1369
+ }
1370
+ return `${item}`;
1371
+ }).join(",")}}`;
1372
+ }
1373
+
1374
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/common.js
1375
+ var PgColumnBuilder = class extends ColumnBuilder {
1376
+ foreignKeyConfigs = [];
1377
+ static [entityKind] = "PgColumnBuilder";
1378
+ array(size) {
1379
+ return new PgArrayBuilder(this.config.name, this, size);
1380
+ }
1381
+ references(ref, actions = {}) {
1382
+ this.foreignKeyConfigs.push({ ref, actions });
1383
+ return this;
1384
+ }
1385
+ unique(name, config) {
1386
+ this.config.isUnique = true;
1387
+ this.config.uniqueName = name;
1388
+ this.config.uniqueType = config?.nulls;
1389
+ return this;
1390
+ }
1391
+ generatedAlwaysAs(as) {
1392
+ this.config.generated = {
1393
+ as,
1394
+ type: "always",
1395
+ mode: "stored"
1396
+ };
1397
+ return this;
1398
+ }
1399
+ /** @internal */
1400
+ buildForeignKeys(column, table) {
1401
+ return this.foreignKeyConfigs.map(({ ref, actions }) => {
1402
+ return iife(
1403
+ (ref2, actions2) => {
1404
+ const builder = new ForeignKeyBuilder(() => {
1405
+ const foreignColumn = ref2();
1406
+ return { columns: [column], foreignColumns: [foreignColumn] };
1407
+ });
1408
+ if (actions2.onUpdate) {
1409
+ builder.onUpdate(actions2.onUpdate);
1410
+ }
1411
+ if (actions2.onDelete) {
1412
+ builder.onDelete(actions2.onDelete);
1413
+ }
1414
+ return builder.build(table);
1415
+ },
1416
+ ref,
1417
+ actions
1418
+ );
1419
+ });
1420
+ }
1421
+ /** @internal */
1422
+ buildExtraConfigColumn(table) {
1423
+ return new ExtraConfigColumn(table, this.config);
1424
+ }
1425
+ };
1426
+ var PgColumn = class extends Column {
1427
+ constructor(table, config) {
1428
+ if (!config.uniqueName) {
1429
+ config.uniqueName = uniqueKeyName(table, [config.name]);
1430
+ }
1431
+ super(table, config);
1432
+ this.table = table;
1433
+ }
1434
+ static [entityKind] = "PgColumn";
1435
+ };
1436
+ var ExtraConfigColumn = class extends PgColumn {
1437
+ static [entityKind] = "ExtraConfigColumn";
1438
+ getSQLType() {
1439
+ return this.getSQLType();
1440
+ }
1441
+ indexConfig = {
1442
+ order: this.config.order ?? "asc",
1443
+ nulls: this.config.nulls ?? "last",
1444
+ opClass: this.config.opClass
1445
+ };
1446
+ defaultConfig = {
1447
+ order: "asc",
1448
+ nulls: "last",
1449
+ opClass: void 0
1450
+ };
1451
+ asc() {
1452
+ this.indexConfig.order = "asc";
1453
+ return this;
1454
+ }
1455
+ desc() {
1456
+ this.indexConfig.order = "desc";
1457
+ return this;
1458
+ }
1459
+ nullsFirst() {
1460
+ this.indexConfig.nulls = "first";
1461
+ return this;
1462
+ }
1463
+ nullsLast() {
1464
+ this.indexConfig.nulls = "last";
1465
+ return this;
1466
+ }
1467
+ /**
1468
+ * ### PostgreSQL documentation quote
1469
+ *
1470
+ * > An operator class with optional parameters can be specified for each column of an index.
1471
+ * The operator class identifies the operators to be used by the index for that column.
1472
+ * For example, a B-tree index on four-byte integers would use the int4_ops class;
1473
+ * this operator class includes comparison functions for four-byte integers.
1474
+ * In practice the default operator class for the column's data type is usually sufficient.
1475
+ * The main point of having operator classes is that for some data types, there could be more than one meaningful ordering.
1476
+ * For example, we might want to sort a complex-number data type either by absolute value or by real part.
1477
+ * We could do this by defining two operator classes for the data type and then selecting the proper class when creating an index.
1478
+ * More information about operator classes check:
1479
+ *
1480
+ * ### Useful links
1481
+ * https://www.postgresql.org/docs/current/sql-createindex.html
1482
+ *
1483
+ * https://www.postgresql.org/docs/current/indexes-opclass.html
1484
+ *
1485
+ * https://www.postgresql.org/docs/current/xindex.html
1486
+ *
1487
+ * ### Additional types
1488
+ * If you have the `pg_vector` extension installed in your database, you can use the
1489
+ * `vector_l2_ops`, `vector_ip_ops`, `vector_cosine_ops`, `vector_l1_ops`, `bit_hamming_ops`, `bit_jaccard_ops`, `halfvec_l2_ops`, `sparsevec_l2_ops` options, which are predefined types.
1490
+ *
1491
+ * **You can always specify any string you want in the operator class, in case Drizzle doesn't have it natively in its types**
1492
+ *
1493
+ * @param opClass
1494
+ * @returns
1495
+ */
1496
+ op(opClass) {
1497
+ this.indexConfig.opClass = opClass;
1498
+ return this;
1499
+ }
1500
+ };
1501
+ var IndexedColumn = class {
1502
+ static [entityKind] = "IndexedColumn";
1503
+ constructor(name, keyAsName, type, indexConfig) {
1504
+ this.name = name;
1505
+ this.keyAsName = keyAsName;
1506
+ this.type = type;
1507
+ this.indexConfig = indexConfig;
1508
+ }
1509
+ name;
1510
+ keyAsName;
1511
+ type;
1512
+ indexConfig;
1513
+ };
1514
+ var PgArrayBuilder = class extends PgColumnBuilder {
1515
+ static [entityKind] = "PgArrayBuilder";
1516
+ constructor(name, baseBuilder, size) {
1517
+ super(name, "array", "PgArray");
1518
+ this.config.baseBuilder = baseBuilder;
1519
+ this.config.size = size;
1520
+ }
1521
+ /** @internal */
1522
+ build(table) {
1523
+ const baseColumn = this.config.baseBuilder.build(table);
1524
+ return new PgArray(
1525
+ table,
1526
+ this.config,
1527
+ baseColumn
1528
+ );
1529
+ }
1530
+ };
1531
+ var PgArray = class _PgArray extends PgColumn {
1532
+ constructor(table, config, baseColumn, range) {
1533
+ super(table, config);
1534
+ this.baseColumn = baseColumn;
1535
+ this.range = range;
1536
+ this.size = config.size;
1537
+ }
1538
+ size;
1539
+ static [entityKind] = "PgArray";
1540
+ getSQLType() {
1541
+ return `${this.baseColumn.getSQLType()}[${typeof this.size === "number" ? this.size : ""}]`;
1542
+ }
1543
+ mapFromDriverValue(value) {
1544
+ if (typeof value === "string") {
1545
+ value = parsePgArray(value);
1546
+ }
1547
+ return value.map((v) => this.baseColumn.mapFromDriverValue(v));
1548
+ }
1549
+ mapToDriverValue(value, isNestedArray = false) {
1550
+ const a = value.map(
1551
+ (v) => v === null ? null : is(this.baseColumn, _PgArray) ? this.baseColumn.mapToDriverValue(v, true) : this.baseColumn.mapToDriverValue(v)
1552
+ );
1553
+ if (isNestedArray) return a;
1554
+ return makePgArray(a);
1555
+ }
1556
+ };
1557
+
1558
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/enum.js
1559
+ var PgEnumObjectColumnBuilder = class extends PgColumnBuilder {
1560
+ static [entityKind] = "PgEnumObjectColumnBuilder";
1561
+ constructor(name, enumInstance) {
1562
+ super(name, "string", "PgEnumObjectColumn");
1563
+ this.config.enum = enumInstance;
1564
+ }
1565
+ /** @internal */
1566
+ build(table) {
1567
+ return new PgEnumObjectColumn(
1568
+ table,
1569
+ this.config
1570
+ );
1571
+ }
1572
+ };
1573
+ var PgEnumObjectColumn = class extends PgColumn {
1574
+ static [entityKind] = "PgEnumObjectColumn";
1575
+ enum;
1576
+ enumValues = this.config.enum.enumValues;
1577
+ constructor(table, config) {
1578
+ super(table, config);
1579
+ this.enum = config.enum;
1580
+ }
1581
+ getSQLType() {
1582
+ return this.enum.enumName;
1583
+ }
1584
+ };
1585
+ var isPgEnumSym = /* @__PURE__ */ Symbol.for("drizzle:isPgEnum");
1586
+ function isPgEnum(obj) {
1587
+ return !!obj && typeof obj === "function" && isPgEnumSym in obj && obj[isPgEnumSym] === true;
1588
+ }
1589
+ var PgEnumColumnBuilder = class extends PgColumnBuilder {
1590
+ static [entityKind] = "PgEnumColumnBuilder";
1591
+ constructor(name, enumInstance) {
1592
+ super(name, "string", "PgEnumColumn");
1593
+ this.config.enum = enumInstance;
1594
+ }
1595
+ /** @internal */
1596
+ build(table) {
1597
+ return new PgEnumColumn(
1598
+ table,
1599
+ this.config
1600
+ );
1601
+ }
1602
+ };
1603
+ var PgEnumColumn = class extends PgColumn {
1604
+ static [entityKind] = "PgEnumColumn";
1605
+ enum = this.config.enum;
1606
+ enumValues = this.config.enum.enumValues;
1607
+ constructor(table, config) {
1608
+ super(table, config);
1609
+ this.enum = config.enum;
1610
+ }
1611
+ getSQLType() {
1612
+ return this.enum.enumName;
1613
+ }
1614
+ };
1615
+
1616
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/subquery.js
1617
+ var Subquery = class {
1618
+ static [entityKind] = "Subquery";
1619
+ constructor(sql2, fields, alias, isWith = false, usedTables = []) {
1620
+ this._ = {
1621
+ brand: "Subquery",
1622
+ sql: sql2,
1623
+ selectedFields: fields,
1624
+ alias,
1625
+ isWith,
1626
+ usedTables
77
1627
  };
78
- await ask('--identity', 'Identity (anonymous, email, handoff)', 'email');
79
- await ask('--deploy', 'Deployment (cloudflare, vercel, node)', 'cloudflare');
80
- await ask('--package-manager', 'Package manager (pnpm, npm)', 'pnpm');
81
- await ask('--preset', 'shadcn preset', 'feedbax-default');
82
- return resolveCreateOptions(prompted);
83
- }
84
- async function assertEmpty(path) {
85
- try {
86
- if ((await readdir(path)).length > 0)
87
- throw new Error(`Target directory is not empty: ${path}`);
88
- }
89
- catch (error) {
90
- if (error.code !== 'ENOENT')
91
- throw error;
92
- }
93
- }
94
- async function replaceInTree(path, replacements) {
95
- for (const entry of await readdir(path)) {
96
- const target = join(path, entry);
97
- if ((await stat(target)).isDirectory())
98
- await replaceInTree(target, replacements);
99
- else {
100
- let content = await readFile(target, 'utf8');
101
- for (const [token, replacement] of Object.entries(replacements))
102
- content = content.replaceAll(token, replacement);
103
- await writeFile(target, content);
1628
+ }
1629
+ // getSQL(): SQL<unknown> {
1630
+ // return new SQL([this]);
1631
+ // }
1632
+ };
1633
+ var WithSubquery = class extends Subquery {
1634
+ static [entityKind] = "WithSubquery";
1635
+ };
1636
+
1637
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/version.js
1638
+ var version = "0.45.2";
1639
+
1640
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/tracing.js
1641
+ var otel;
1642
+ var rawTracer;
1643
+ var tracer = {
1644
+ startActiveSpan(name, fn) {
1645
+ if (!otel) {
1646
+ return fn();
1647
+ }
1648
+ if (!rawTracer) {
1649
+ rawTracer = otel.trace.getTracer("drizzle-orm", version);
1650
+ }
1651
+ return iife(
1652
+ (otel2, rawTracer2) => rawTracer2.startActiveSpan(
1653
+ name,
1654
+ (span) => {
1655
+ try {
1656
+ return fn(span);
1657
+ } catch (e) {
1658
+ span.setStatus({
1659
+ code: otel2.SpanStatusCode.ERROR,
1660
+ message: e instanceof Error ? e.message : "Unknown error"
1661
+ // eslint-disable-line no-instanceof/no-instanceof
1662
+ });
1663
+ throw e;
1664
+ } finally {
1665
+ span.end();
1666
+ }
104
1667
  }
1668
+ ),
1669
+ otel,
1670
+ rawTracer
1671
+ );
1672
+ }
1673
+ };
1674
+
1675
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/view-common.js
1676
+ var ViewBaseConfig = /* @__PURE__ */ Symbol.for("drizzle:ViewBaseConfig");
1677
+
1678
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/table.js
1679
+ var Schema = /* @__PURE__ */ Symbol.for("drizzle:Schema");
1680
+ var Columns = /* @__PURE__ */ Symbol.for("drizzle:Columns");
1681
+ var ExtraConfigColumns = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigColumns");
1682
+ var OriginalName = /* @__PURE__ */ Symbol.for("drizzle:OriginalName");
1683
+ var BaseName = /* @__PURE__ */ Symbol.for("drizzle:BaseName");
1684
+ var IsAlias = /* @__PURE__ */ Symbol.for("drizzle:IsAlias");
1685
+ var ExtraConfigBuilder = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigBuilder");
1686
+ var IsDrizzleTable = /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleTable");
1687
+ var Table = class {
1688
+ static [entityKind] = "Table";
1689
+ /** @internal */
1690
+ static Symbol = {
1691
+ Name: TableName,
1692
+ Schema,
1693
+ OriginalName,
1694
+ Columns,
1695
+ ExtraConfigColumns,
1696
+ BaseName,
1697
+ IsAlias,
1698
+ ExtraConfigBuilder
1699
+ };
1700
+ /**
1701
+ * @internal
1702
+ * Can be changed if the table is aliased.
1703
+ */
1704
+ [TableName];
1705
+ /**
1706
+ * @internal
1707
+ * Used to store the original name of the table, before any aliasing.
1708
+ */
1709
+ [OriginalName];
1710
+ /** @internal */
1711
+ [Schema];
1712
+ /** @internal */
1713
+ [Columns];
1714
+ /** @internal */
1715
+ [ExtraConfigColumns];
1716
+ /**
1717
+ * @internal
1718
+ * Used to store the table name before the transformation via the `tableCreator` functions.
1719
+ */
1720
+ [BaseName];
1721
+ /** @internal */
1722
+ [IsAlias] = false;
1723
+ /** @internal */
1724
+ [IsDrizzleTable] = true;
1725
+ /** @internal */
1726
+ [ExtraConfigBuilder] = void 0;
1727
+ constructor(name, schema, baseName) {
1728
+ this[TableName] = this[OriginalName] = name;
1729
+ this[Schema] = schema;
1730
+ this[BaseName] = baseName;
1731
+ }
1732
+ };
1733
+
1734
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sql/sql.js
1735
+ var FakePrimitiveParam = class {
1736
+ static [entityKind] = "FakePrimitiveParam";
1737
+ };
1738
+ function isSQLWrapper(value) {
1739
+ return value !== null && value !== void 0 && typeof value.getSQL === "function";
1740
+ }
1741
+ function mergeQueries(queries) {
1742
+ const result = { sql: "", params: [] };
1743
+ for (const query of queries) {
1744
+ result.sql += query.sql;
1745
+ result.params.push(...query.params);
1746
+ if (query.typings?.length) {
1747
+ if (!result.typings) {
1748
+ result.typings = [];
1749
+ }
1750
+ result.typings.push(...query.typings);
105
1751
  }
1752
+ }
1753
+ return result;
106
1754
  }
107
- const run = (command, args, cwd) => new Promise((resolvePromise, reject) => {
108
- const child = spawn(command, args, { cwd, stdio: 'inherit', shell: false });
109
- child.once('error', reject);
110
- child.once('exit', (code) => code === 0
111
- ? resolvePromise()
112
- : reject(new Error(`${command} ${args.join(' ')} failed with ${code}.`)));
113
- });
114
- export async function createProject(options) {
115
- const destination = resolve(options.directory);
116
- await assertEmpty(destination);
117
- await mkdir(dirname(destination), { recursive: true });
118
- const packageDirectory = dirname(fileURLToPath(import.meta.url));
119
- const candidates = [
120
- resolve(packageDirectory, '../template'),
121
- resolve(packageDirectory, '../../../templates/default'),
1755
+ var StringChunk = class {
1756
+ static [entityKind] = "StringChunk";
1757
+ value;
1758
+ constructor(value) {
1759
+ this.value = Array.isArray(value) ? value : [value];
1760
+ }
1761
+ getSQL() {
1762
+ return new SQL([this]);
1763
+ }
1764
+ };
1765
+ var SQL = class _SQL {
1766
+ constructor(queryChunks) {
1767
+ this.queryChunks = queryChunks;
1768
+ for (const chunk of queryChunks) {
1769
+ if (is(chunk, Table)) {
1770
+ const schemaName = chunk[Table.Symbol.Schema];
1771
+ this.usedTables.push(
1772
+ schemaName === void 0 ? chunk[Table.Symbol.Name] : schemaName + "." + chunk[Table.Symbol.Name]
1773
+ );
1774
+ }
1775
+ }
1776
+ }
1777
+ static [entityKind] = "SQL";
1778
+ /** @internal */
1779
+ decoder = noopDecoder;
1780
+ shouldInlineParams = false;
1781
+ /** @internal */
1782
+ usedTables = [];
1783
+ append(query) {
1784
+ this.queryChunks.push(...query.queryChunks);
1785
+ return this;
1786
+ }
1787
+ toQuery(config) {
1788
+ return tracer.startActiveSpan("drizzle.buildSQL", (span) => {
1789
+ const query = this.buildQueryFromSourceParams(this.queryChunks, config);
1790
+ span?.setAttributes({
1791
+ "drizzle.query.text": query.sql,
1792
+ "drizzle.query.params": JSON.stringify(query.params)
1793
+ });
1794
+ return query;
1795
+ });
1796
+ }
1797
+ buildQueryFromSourceParams(chunks, _config) {
1798
+ const config = Object.assign({}, _config, {
1799
+ inlineParams: _config.inlineParams || this.shouldInlineParams,
1800
+ paramStartIndex: _config.paramStartIndex || { value: 0 }
1801
+ });
1802
+ const {
1803
+ casing,
1804
+ escapeName,
1805
+ escapeParam,
1806
+ prepareTyping,
1807
+ inlineParams,
1808
+ paramStartIndex
1809
+ } = config;
1810
+ return mergeQueries(chunks.map((chunk) => {
1811
+ if (is(chunk, StringChunk)) {
1812
+ return { sql: chunk.value.join(""), params: [] };
1813
+ }
1814
+ if (is(chunk, Name)) {
1815
+ return { sql: escapeName(chunk.value), params: [] };
1816
+ }
1817
+ if (chunk === void 0) {
1818
+ return { sql: "", params: [] };
1819
+ }
1820
+ if (Array.isArray(chunk)) {
1821
+ const result = [new StringChunk("(")];
1822
+ for (const [i, p] of chunk.entries()) {
1823
+ result.push(p);
1824
+ if (i < chunk.length - 1) {
1825
+ result.push(new StringChunk(", "));
1826
+ }
1827
+ }
1828
+ result.push(new StringChunk(")"));
1829
+ return this.buildQueryFromSourceParams(result, config);
1830
+ }
1831
+ if (is(chunk, _SQL)) {
1832
+ return this.buildQueryFromSourceParams(chunk.queryChunks, {
1833
+ ...config,
1834
+ inlineParams: inlineParams || chunk.shouldInlineParams
1835
+ });
1836
+ }
1837
+ if (is(chunk, Table)) {
1838
+ const schemaName = chunk[Table.Symbol.Schema];
1839
+ const tableName = chunk[Table.Symbol.Name];
1840
+ return {
1841
+ sql: schemaName === void 0 || chunk[IsAlias] ? escapeName(tableName) : escapeName(schemaName) + "." + escapeName(tableName),
1842
+ params: []
1843
+ };
1844
+ }
1845
+ if (is(chunk, Column)) {
1846
+ const columnName = casing.getColumnCasing(chunk);
1847
+ if (_config.invokeSource === "indexes") {
1848
+ return { sql: escapeName(columnName), params: [] };
1849
+ }
1850
+ const schemaName = chunk.table[Table.Symbol.Schema];
1851
+ return {
1852
+ sql: chunk.table[IsAlias] || schemaName === void 0 ? escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName) : escapeName(schemaName) + "." + escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName),
1853
+ params: []
1854
+ };
1855
+ }
1856
+ if (is(chunk, View)) {
1857
+ const schemaName = chunk[ViewBaseConfig].schema;
1858
+ const viewName = chunk[ViewBaseConfig].name;
1859
+ return {
1860
+ sql: schemaName === void 0 || chunk[ViewBaseConfig].isAlias ? escapeName(viewName) : escapeName(schemaName) + "." + escapeName(viewName),
1861
+ params: []
1862
+ };
1863
+ }
1864
+ if (is(chunk, Param)) {
1865
+ if (is(chunk.value, Placeholder)) {
1866
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1867
+ }
1868
+ const mappedValue = chunk.value === null ? null : chunk.encoder.mapToDriverValue(chunk.value);
1869
+ if (is(mappedValue, _SQL)) {
1870
+ return this.buildQueryFromSourceParams([mappedValue], config);
1871
+ }
1872
+ if (inlineParams) {
1873
+ return { sql: this.mapInlineParam(mappedValue, config), params: [] };
1874
+ }
1875
+ let typings = ["none"];
1876
+ if (prepareTyping) {
1877
+ typings = [prepareTyping(chunk.encoder)];
1878
+ }
1879
+ return { sql: escapeParam(paramStartIndex.value++, mappedValue), params: [mappedValue], typings };
1880
+ }
1881
+ if (is(chunk, Placeholder)) {
1882
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1883
+ }
1884
+ if (is(chunk, _SQL.Aliased) && chunk.fieldAlias !== void 0) {
1885
+ return { sql: escapeName(chunk.fieldAlias), params: [] };
1886
+ }
1887
+ if (is(chunk, Subquery)) {
1888
+ if (chunk._.isWith) {
1889
+ return { sql: escapeName(chunk._.alias), params: [] };
1890
+ }
1891
+ return this.buildQueryFromSourceParams([
1892
+ new StringChunk("("),
1893
+ chunk._.sql,
1894
+ new StringChunk(") "),
1895
+ new Name(chunk._.alias)
1896
+ ], config);
1897
+ }
1898
+ if (isPgEnum(chunk)) {
1899
+ if (chunk.schema) {
1900
+ return { sql: escapeName(chunk.schema) + "." + escapeName(chunk.enumName), params: [] };
1901
+ }
1902
+ return { sql: escapeName(chunk.enumName), params: [] };
1903
+ }
1904
+ if (isSQLWrapper(chunk)) {
1905
+ if (chunk.shouldOmitSQLParens?.()) {
1906
+ return this.buildQueryFromSourceParams([chunk.getSQL()], config);
1907
+ }
1908
+ return this.buildQueryFromSourceParams([
1909
+ new StringChunk("("),
1910
+ chunk.getSQL(),
1911
+ new StringChunk(")")
1912
+ ], config);
1913
+ }
1914
+ if (inlineParams) {
1915
+ return { sql: this.mapInlineParam(chunk, config), params: [] };
1916
+ }
1917
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1918
+ }));
1919
+ }
1920
+ mapInlineParam(chunk, { escapeString }) {
1921
+ if (chunk === null) {
1922
+ return "null";
1923
+ }
1924
+ if (typeof chunk === "number" || typeof chunk === "boolean") {
1925
+ return chunk.toString();
1926
+ }
1927
+ if (typeof chunk === "string") {
1928
+ return escapeString(chunk);
1929
+ }
1930
+ if (typeof chunk === "object") {
1931
+ const mappedValueAsString = chunk.toString();
1932
+ if (mappedValueAsString === "[object Object]") {
1933
+ return escapeString(JSON.stringify(chunk));
1934
+ }
1935
+ return escapeString(mappedValueAsString);
1936
+ }
1937
+ throw new Error("Unexpected param value: " + chunk);
1938
+ }
1939
+ getSQL() {
1940
+ return this;
1941
+ }
1942
+ as(alias) {
1943
+ if (alias === void 0) {
1944
+ return this;
1945
+ }
1946
+ return new _SQL.Aliased(this, alias);
1947
+ }
1948
+ mapWith(decoder) {
1949
+ this.decoder = typeof decoder === "function" ? { mapFromDriverValue: decoder } : decoder;
1950
+ return this;
1951
+ }
1952
+ inlineParams() {
1953
+ this.shouldInlineParams = true;
1954
+ return this;
1955
+ }
1956
+ /**
1957
+ * This method is used to conditionally include a part of the query.
1958
+ *
1959
+ * @param condition - Condition to check
1960
+ * @returns itself if the condition is `true`, otherwise `undefined`
1961
+ */
1962
+ if(condition) {
1963
+ return condition ? this : void 0;
1964
+ }
1965
+ };
1966
+ var Name = class {
1967
+ constructor(value) {
1968
+ this.value = value;
1969
+ }
1970
+ static [entityKind] = "Name";
1971
+ brand;
1972
+ getSQL() {
1973
+ return new SQL([this]);
1974
+ }
1975
+ };
1976
+ var noopDecoder = {
1977
+ mapFromDriverValue: (value) => value
1978
+ };
1979
+ var noopEncoder = {
1980
+ mapToDriverValue: (value) => value
1981
+ };
1982
+ var noopMapper = {
1983
+ ...noopDecoder,
1984
+ ...noopEncoder
1985
+ };
1986
+ var Param = class {
1987
+ /**
1988
+ * @param value - Parameter value
1989
+ * @param encoder - Encoder to convert the value to a driver parameter
1990
+ */
1991
+ constructor(value, encoder = noopEncoder) {
1992
+ this.value = value;
1993
+ this.encoder = encoder;
1994
+ }
1995
+ static [entityKind] = "Param";
1996
+ brand;
1997
+ getSQL() {
1998
+ return new SQL([this]);
1999
+ }
2000
+ };
2001
+ function sql(strings, ...params) {
2002
+ const queryChunks = [];
2003
+ if (params.length > 0 || strings.length > 0 && strings[0] !== "") {
2004
+ queryChunks.push(new StringChunk(strings[0]));
2005
+ }
2006
+ for (const [paramIndex, param2] of params.entries()) {
2007
+ queryChunks.push(param2, new StringChunk(strings[paramIndex + 1]));
2008
+ }
2009
+ return new SQL(queryChunks);
2010
+ }
2011
+ ((sql2) => {
2012
+ function empty() {
2013
+ return new SQL([]);
2014
+ }
2015
+ sql2.empty = empty;
2016
+ function fromList(list) {
2017
+ return new SQL(list);
2018
+ }
2019
+ sql2.fromList = fromList;
2020
+ function raw(str) {
2021
+ return new SQL([new StringChunk(str)]);
2022
+ }
2023
+ sql2.raw = raw;
2024
+ function join3(chunks, separator) {
2025
+ const result = [];
2026
+ for (const [i, chunk] of chunks.entries()) {
2027
+ if (i > 0 && separator !== void 0) {
2028
+ result.push(separator);
2029
+ }
2030
+ result.push(chunk);
2031
+ }
2032
+ return new SQL(result);
2033
+ }
2034
+ sql2.join = join3;
2035
+ function identifier(value) {
2036
+ return new Name(value);
2037
+ }
2038
+ sql2.identifier = identifier;
2039
+ function placeholder2(name2) {
2040
+ return new Placeholder(name2);
2041
+ }
2042
+ sql2.placeholder = placeholder2;
2043
+ function param2(value, encoder) {
2044
+ return new Param(value, encoder);
2045
+ }
2046
+ sql2.param = param2;
2047
+ })(sql || (sql = {}));
2048
+ ((SQL2) => {
2049
+ class Aliased {
2050
+ constructor(sql2, fieldAlias) {
2051
+ this.sql = sql2;
2052
+ this.fieldAlias = fieldAlias;
2053
+ }
2054
+ static [entityKind] = "SQL.Aliased";
2055
+ /** @internal */
2056
+ isSelectionField = false;
2057
+ getSQL() {
2058
+ return this.sql;
2059
+ }
2060
+ /** @internal */
2061
+ clone() {
2062
+ return new Aliased(this.sql, this.fieldAlias);
2063
+ }
2064
+ }
2065
+ SQL2.Aliased = Aliased;
2066
+ })(SQL || (SQL = {}));
2067
+ var Placeholder = class {
2068
+ constructor(name2) {
2069
+ this.name = name2;
2070
+ }
2071
+ static [entityKind] = "Placeholder";
2072
+ getSQL() {
2073
+ return new SQL([this]);
2074
+ }
2075
+ };
2076
+ var IsDrizzleView = /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleView");
2077
+ var View = class {
2078
+ static [entityKind] = "View";
2079
+ /** @internal */
2080
+ [ViewBaseConfig];
2081
+ /** @internal */
2082
+ [IsDrizzleView] = true;
2083
+ constructor({ name: name2, schema, selectedFields, query }) {
2084
+ this[ViewBaseConfig] = {
2085
+ name: name2,
2086
+ originalName: name2,
2087
+ schema,
2088
+ selectedFields,
2089
+ query,
2090
+ isExisting: !query,
2091
+ isAlias: false
2092
+ };
2093
+ }
2094
+ getSQL() {
2095
+ return new SQL([this]);
2096
+ }
2097
+ };
2098
+ Column.prototype.getSQL = function() {
2099
+ return new SQL([this]);
2100
+ };
2101
+ Table.prototype.getSQL = function() {
2102
+ return new SQL([this]);
2103
+ };
2104
+ Subquery.prototype.getSQL = function() {
2105
+ return new SQL([this]);
2106
+ };
2107
+
2108
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/utils.js
2109
+ function getColumnNameAndConfig(a, b) {
2110
+ return {
2111
+ name: typeof a === "string" && a.length > 0 ? a : "",
2112
+ config: typeof a === "object" ? a : b
2113
+ };
2114
+ }
2115
+ var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder();
2116
+
2117
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
2118
+ var ForeignKeyBuilder2 = class {
2119
+ static [entityKind] = "SQLiteForeignKeyBuilder";
2120
+ /** @internal */
2121
+ reference;
2122
+ /** @internal */
2123
+ _onUpdate;
2124
+ /** @internal */
2125
+ _onDelete;
2126
+ constructor(config, actions) {
2127
+ this.reference = () => {
2128
+ const { name, columns, foreignColumns } = config();
2129
+ return { name, columns, foreignTable: foreignColumns[0].table, foreignColumns };
2130
+ };
2131
+ if (actions) {
2132
+ this._onUpdate = actions.onUpdate;
2133
+ this._onDelete = actions.onDelete;
2134
+ }
2135
+ }
2136
+ onUpdate(action) {
2137
+ this._onUpdate = action;
2138
+ return this;
2139
+ }
2140
+ onDelete(action) {
2141
+ this._onDelete = action;
2142
+ return this;
2143
+ }
2144
+ /** @internal */
2145
+ build(table) {
2146
+ return new ForeignKey2(table, this);
2147
+ }
2148
+ };
2149
+ var ForeignKey2 = class {
2150
+ constructor(table, builder) {
2151
+ this.table = table;
2152
+ this.reference = builder.reference;
2153
+ this.onUpdate = builder._onUpdate;
2154
+ this.onDelete = builder._onDelete;
2155
+ }
2156
+ static [entityKind] = "SQLiteForeignKey";
2157
+ reference;
2158
+ onUpdate;
2159
+ onDelete;
2160
+ getName() {
2161
+ const { name, columns, foreignColumns } = this.reference();
2162
+ const columnNames = columns.map((column) => column.name);
2163
+ const foreignColumnNames = foreignColumns.map((column) => column.name);
2164
+ const chunks = [
2165
+ this.table[TableName],
2166
+ ...columnNames,
2167
+ foreignColumns[0].table[TableName],
2168
+ ...foreignColumnNames
122
2169
  ];
123
- let template;
124
- for (const candidate of candidates) {
125
- try {
126
- if ((await stat(candidate)).isDirectory()) {
127
- template = candidate;
128
- break;
129
- }
2170
+ return name ?? `${chunks.join("_")}_fk`;
2171
+ }
2172
+ };
2173
+
2174
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
2175
+ function uniqueKeyName2(table, columns) {
2176
+ return `${table[TableName]}_${columns.join("_")}_unique`;
2177
+ }
2178
+ var UniqueConstraintBuilder2 = class {
2179
+ constructor(columns, name) {
2180
+ this.name = name;
2181
+ this.columns = columns;
2182
+ }
2183
+ static [entityKind] = "SQLiteUniqueConstraintBuilder";
2184
+ /** @internal */
2185
+ columns;
2186
+ /** @internal */
2187
+ build(table) {
2188
+ return new UniqueConstraint2(table, this.columns, this.name);
2189
+ }
2190
+ };
2191
+ var UniqueOnConstraintBuilder2 = class {
2192
+ static [entityKind] = "SQLiteUniqueOnConstraintBuilder";
2193
+ /** @internal */
2194
+ name;
2195
+ constructor(name) {
2196
+ this.name = name;
2197
+ }
2198
+ on(...columns) {
2199
+ return new UniqueConstraintBuilder2(columns, this.name);
2200
+ }
2201
+ };
2202
+ var UniqueConstraint2 = class {
2203
+ constructor(table, columns, name) {
2204
+ this.table = table;
2205
+ this.columns = columns;
2206
+ this.name = name ?? uniqueKeyName2(this.table, this.columns.map((column) => column.name));
2207
+ }
2208
+ static [entityKind] = "SQLiteUniqueConstraint";
2209
+ columns;
2210
+ name;
2211
+ getName() {
2212
+ return this.name;
2213
+ }
2214
+ };
2215
+
2216
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/common.js
2217
+ var SQLiteColumnBuilder = class extends ColumnBuilder {
2218
+ static [entityKind] = "SQLiteColumnBuilder";
2219
+ foreignKeyConfigs = [];
2220
+ references(ref, actions = {}) {
2221
+ this.foreignKeyConfigs.push({ ref, actions });
2222
+ return this;
2223
+ }
2224
+ unique(name) {
2225
+ this.config.isUnique = true;
2226
+ this.config.uniqueName = name;
2227
+ return this;
2228
+ }
2229
+ generatedAlwaysAs(as, config) {
2230
+ this.config.generated = {
2231
+ as,
2232
+ type: "always",
2233
+ mode: config?.mode ?? "virtual"
2234
+ };
2235
+ return this;
2236
+ }
2237
+ /** @internal */
2238
+ buildForeignKeys(column, table) {
2239
+ return this.foreignKeyConfigs.map(({ ref, actions }) => {
2240
+ return ((ref2, actions2) => {
2241
+ const builder = new ForeignKeyBuilder2(() => {
2242
+ const foreignColumn = ref2();
2243
+ return { columns: [column], foreignColumns: [foreignColumn] };
2244
+ });
2245
+ if (actions2.onUpdate) {
2246
+ builder.onUpdate(actions2.onUpdate);
130
2247
  }
131
- catch {
132
- continue;
2248
+ if (actions2.onDelete) {
2249
+ builder.onDelete(actions2.onDelete);
133
2250
  }
2251
+ return builder.build(table);
2252
+ })(ref, actions);
2253
+ });
2254
+ }
2255
+ };
2256
+ var SQLiteColumn = class extends Column {
2257
+ constructor(table, config) {
2258
+ if (!config.uniqueName) {
2259
+ config.uniqueName = uniqueKeyName2(table, [config.name]);
2260
+ }
2261
+ super(table, config);
2262
+ this.table = table;
2263
+ }
2264
+ static [entityKind] = "SQLiteColumn";
2265
+ };
2266
+
2267
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/blob.js
2268
+ var SQLiteBigIntBuilder = class extends SQLiteColumnBuilder {
2269
+ static [entityKind] = "SQLiteBigIntBuilder";
2270
+ constructor(name) {
2271
+ super(name, "bigint", "SQLiteBigInt");
2272
+ }
2273
+ /** @internal */
2274
+ build(table) {
2275
+ return new SQLiteBigInt(table, this.config);
2276
+ }
2277
+ };
2278
+ var SQLiteBigInt = class extends SQLiteColumn {
2279
+ static [entityKind] = "SQLiteBigInt";
2280
+ getSQLType() {
2281
+ return "blob";
2282
+ }
2283
+ mapFromDriverValue(value) {
2284
+ if (typeof Buffer !== "undefined" && Buffer.from) {
2285
+ const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
2286
+ return BigInt(buf.toString("utf8"));
2287
+ }
2288
+ return BigInt(textDecoder.decode(value));
2289
+ }
2290
+ mapToDriverValue(value) {
2291
+ return Buffer.from(value.toString());
2292
+ }
2293
+ };
2294
+ var SQLiteBlobJsonBuilder = class extends SQLiteColumnBuilder {
2295
+ static [entityKind] = "SQLiteBlobJsonBuilder";
2296
+ constructor(name) {
2297
+ super(name, "json", "SQLiteBlobJson");
2298
+ }
2299
+ /** @internal */
2300
+ build(table) {
2301
+ return new SQLiteBlobJson(
2302
+ table,
2303
+ this.config
2304
+ );
2305
+ }
2306
+ };
2307
+ var SQLiteBlobJson = class extends SQLiteColumn {
2308
+ static [entityKind] = "SQLiteBlobJson";
2309
+ getSQLType() {
2310
+ return "blob";
2311
+ }
2312
+ mapFromDriverValue(value) {
2313
+ if (typeof Buffer !== "undefined" && Buffer.from) {
2314
+ const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
2315
+ return JSON.parse(buf.toString("utf8"));
2316
+ }
2317
+ return JSON.parse(textDecoder.decode(value));
2318
+ }
2319
+ mapToDriverValue(value) {
2320
+ return Buffer.from(JSON.stringify(value));
2321
+ }
2322
+ };
2323
+ var SQLiteBlobBufferBuilder = class extends SQLiteColumnBuilder {
2324
+ static [entityKind] = "SQLiteBlobBufferBuilder";
2325
+ constructor(name) {
2326
+ super(name, "buffer", "SQLiteBlobBuffer");
2327
+ }
2328
+ /** @internal */
2329
+ build(table) {
2330
+ return new SQLiteBlobBuffer(table, this.config);
2331
+ }
2332
+ };
2333
+ var SQLiteBlobBuffer = class extends SQLiteColumn {
2334
+ static [entityKind] = "SQLiteBlobBuffer";
2335
+ mapFromDriverValue(value) {
2336
+ if (Buffer.isBuffer(value)) {
2337
+ return value;
2338
+ }
2339
+ return Buffer.from(value);
2340
+ }
2341
+ getSQLType() {
2342
+ return "blob";
2343
+ }
2344
+ };
2345
+ function blob(a, b) {
2346
+ const { name, config } = getColumnNameAndConfig(a, b);
2347
+ if (config?.mode === "json") {
2348
+ return new SQLiteBlobJsonBuilder(name);
2349
+ }
2350
+ if (config?.mode === "bigint") {
2351
+ return new SQLiteBigIntBuilder(name);
2352
+ }
2353
+ return new SQLiteBlobBufferBuilder(name);
2354
+ }
2355
+
2356
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/custom.js
2357
+ var SQLiteCustomColumnBuilder = class extends SQLiteColumnBuilder {
2358
+ static [entityKind] = "SQLiteCustomColumnBuilder";
2359
+ constructor(name, fieldConfig, customTypeParams) {
2360
+ super(name, "custom", "SQLiteCustomColumn");
2361
+ this.config.fieldConfig = fieldConfig;
2362
+ this.config.customTypeParams = customTypeParams;
2363
+ }
2364
+ /** @internal */
2365
+ build(table) {
2366
+ return new SQLiteCustomColumn(
2367
+ table,
2368
+ this.config
2369
+ );
2370
+ }
2371
+ };
2372
+ var SQLiteCustomColumn = class extends SQLiteColumn {
2373
+ static [entityKind] = "SQLiteCustomColumn";
2374
+ sqlName;
2375
+ mapTo;
2376
+ mapFrom;
2377
+ constructor(table, config) {
2378
+ super(table, config);
2379
+ this.sqlName = config.customTypeParams.dataType(config.fieldConfig);
2380
+ this.mapTo = config.customTypeParams.toDriver;
2381
+ this.mapFrom = config.customTypeParams.fromDriver;
2382
+ }
2383
+ getSQLType() {
2384
+ return this.sqlName;
2385
+ }
2386
+ mapFromDriverValue(value) {
2387
+ return typeof this.mapFrom === "function" ? this.mapFrom(value) : value;
2388
+ }
2389
+ mapToDriverValue(value) {
2390
+ return typeof this.mapTo === "function" ? this.mapTo(value) : value;
2391
+ }
2392
+ };
2393
+ function customType(customTypeParams) {
2394
+ return (a, b) => {
2395
+ const { name, config } = getColumnNameAndConfig(a, b);
2396
+ return new SQLiteCustomColumnBuilder(
2397
+ name,
2398
+ config,
2399
+ customTypeParams
2400
+ );
2401
+ };
2402
+ }
2403
+
2404
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/integer.js
2405
+ var SQLiteBaseIntegerBuilder = class extends SQLiteColumnBuilder {
2406
+ static [entityKind] = "SQLiteBaseIntegerBuilder";
2407
+ constructor(name, dataType, columnType) {
2408
+ super(name, dataType, columnType);
2409
+ this.config.autoIncrement = false;
2410
+ }
2411
+ primaryKey(config) {
2412
+ if (config?.autoIncrement) {
2413
+ this.config.autoIncrement = true;
2414
+ }
2415
+ this.config.hasDefault = true;
2416
+ return super.primaryKey();
2417
+ }
2418
+ };
2419
+ var SQLiteBaseInteger = class extends SQLiteColumn {
2420
+ static [entityKind] = "SQLiteBaseInteger";
2421
+ autoIncrement = this.config.autoIncrement;
2422
+ getSQLType() {
2423
+ return "integer";
2424
+ }
2425
+ };
2426
+ var SQLiteIntegerBuilder = class extends SQLiteBaseIntegerBuilder {
2427
+ static [entityKind] = "SQLiteIntegerBuilder";
2428
+ constructor(name) {
2429
+ super(name, "number", "SQLiteInteger");
2430
+ }
2431
+ build(table) {
2432
+ return new SQLiteInteger(
2433
+ table,
2434
+ this.config
2435
+ );
2436
+ }
2437
+ };
2438
+ var SQLiteInteger = class extends SQLiteBaseInteger {
2439
+ static [entityKind] = "SQLiteInteger";
2440
+ };
2441
+ var SQLiteTimestampBuilder = class extends SQLiteBaseIntegerBuilder {
2442
+ static [entityKind] = "SQLiteTimestampBuilder";
2443
+ constructor(name, mode) {
2444
+ super(name, "date", "SQLiteTimestamp");
2445
+ this.config.mode = mode;
2446
+ }
2447
+ /**
2448
+ * @deprecated Use `default()` with your own expression instead.
2449
+ *
2450
+ * Adds `DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer))` to the column, which is the current epoch timestamp in milliseconds.
2451
+ */
2452
+ defaultNow() {
2453
+ return this.default(sql`(cast((julianday('now') - 2440587.5)*86400000 as integer))`);
2454
+ }
2455
+ build(table) {
2456
+ return new SQLiteTimestamp(
2457
+ table,
2458
+ this.config
2459
+ );
2460
+ }
2461
+ };
2462
+ var SQLiteTimestamp = class extends SQLiteBaseInteger {
2463
+ static [entityKind] = "SQLiteTimestamp";
2464
+ mode = this.config.mode;
2465
+ mapFromDriverValue(value) {
2466
+ if (this.config.mode === "timestamp") {
2467
+ return new Date(value * 1e3);
2468
+ }
2469
+ return new Date(value);
2470
+ }
2471
+ mapToDriverValue(value) {
2472
+ const unix = value.getTime();
2473
+ if (this.config.mode === "timestamp") {
2474
+ return Math.floor(unix / 1e3);
2475
+ }
2476
+ return unix;
2477
+ }
2478
+ };
2479
+ var SQLiteBooleanBuilder = class extends SQLiteBaseIntegerBuilder {
2480
+ static [entityKind] = "SQLiteBooleanBuilder";
2481
+ constructor(name, mode) {
2482
+ super(name, "boolean", "SQLiteBoolean");
2483
+ this.config.mode = mode;
2484
+ }
2485
+ build(table) {
2486
+ return new SQLiteBoolean(
2487
+ table,
2488
+ this.config
2489
+ );
2490
+ }
2491
+ };
2492
+ var SQLiteBoolean = class extends SQLiteBaseInteger {
2493
+ static [entityKind] = "SQLiteBoolean";
2494
+ mode = this.config.mode;
2495
+ mapFromDriverValue(value) {
2496
+ return Number(value) === 1;
2497
+ }
2498
+ mapToDriverValue(value) {
2499
+ return value ? 1 : 0;
2500
+ }
2501
+ };
2502
+ function integer(a, b) {
2503
+ const { name, config } = getColumnNameAndConfig(a, b);
2504
+ if (config?.mode === "timestamp" || config?.mode === "timestamp_ms") {
2505
+ return new SQLiteTimestampBuilder(name, config.mode);
2506
+ }
2507
+ if (config?.mode === "boolean") {
2508
+ return new SQLiteBooleanBuilder(name, config.mode);
2509
+ }
2510
+ return new SQLiteIntegerBuilder(name);
2511
+ }
2512
+
2513
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
2514
+ var SQLiteNumericBuilder = class extends SQLiteColumnBuilder {
2515
+ static [entityKind] = "SQLiteNumericBuilder";
2516
+ constructor(name) {
2517
+ super(name, "string", "SQLiteNumeric");
2518
+ }
2519
+ /** @internal */
2520
+ build(table) {
2521
+ return new SQLiteNumeric(
2522
+ table,
2523
+ this.config
2524
+ );
2525
+ }
2526
+ };
2527
+ var SQLiteNumeric = class extends SQLiteColumn {
2528
+ static [entityKind] = "SQLiteNumeric";
2529
+ mapFromDriverValue(value) {
2530
+ if (typeof value === "string") return value;
2531
+ return String(value);
2532
+ }
2533
+ getSQLType() {
2534
+ return "numeric";
2535
+ }
2536
+ };
2537
+ var SQLiteNumericNumberBuilder = class extends SQLiteColumnBuilder {
2538
+ static [entityKind] = "SQLiteNumericNumberBuilder";
2539
+ constructor(name) {
2540
+ super(name, "number", "SQLiteNumericNumber");
2541
+ }
2542
+ /** @internal */
2543
+ build(table) {
2544
+ return new SQLiteNumericNumber(
2545
+ table,
2546
+ this.config
2547
+ );
2548
+ }
2549
+ };
2550
+ var SQLiteNumericNumber = class extends SQLiteColumn {
2551
+ static [entityKind] = "SQLiteNumericNumber";
2552
+ mapFromDriverValue(value) {
2553
+ if (typeof value === "number") return value;
2554
+ return Number(value);
2555
+ }
2556
+ mapToDriverValue = String;
2557
+ getSQLType() {
2558
+ return "numeric";
2559
+ }
2560
+ };
2561
+ var SQLiteNumericBigIntBuilder = class extends SQLiteColumnBuilder {
2562
+ static [entityKind] = "SQLiteNumericBigIntBuilder";
2563
+ constructor(name) {
2564
+ super(name, "bigint", "SQLiteNumericBigInt");
2565
+ }
2566
+ /** @internal */
2567
+ build(table) {
2568
+ return new SQLiteNumericBigInt(
2569
+ table,
2570
+ this.config
2571
+ );
2572
+ }
2573
+ };
2574
+ var SQLiteNumericBigInt = class extends SQLiteColumn {
2575
+ static [entityKind] = "SQLiteNumericBigInt";
2576
+ mapFromDriverValue = BigInt;
2577
+ mapToDriverValue = String;
2578
+ getSQLType() {
2579
+ return "numeric";
2580
+ }
2581
+ };
2582
+ function numeric(a, b) {
2583
+ const { name, config } = getColumnNameAndConfig(a, b);
2584
+ const mode = config?.mode;
2585
+ return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
2586
+ }
2587
+
2588
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/real.js
2589
+ var SQLiteRealBuilder = class extends SQLiteColumnBuilder {
2590
+ static [entityKind] = "SQLiteRealBuilder";
2591
+ constructor(name) {
2592
+ super(name, "number", "SQLiteReal");
2593
+ }
2594
+ /** @internal */
2595
+ build(table) {
2596
+ return new SQLiteReal(table, this.config);
2597
+ }
2598
+ };
2599
+ var SQLiteReal = class extends SQLiteColumn {
2600
+ static [entityKind] = "SQLiteReal";
2601
+ getSQLType() {
2602
+ return "real";
2603
+ }
2604
+ };
2605
+ function real(name) {
2606
+ return new SQLiteRealBuilder(name ?? "");
2607
+ }
2608
+
2609
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/text.js
2610
+ var SQLiteTextBuilder = class extends SQLiteColumnBuilder {
2611
+ static [entityKind] = "SQLiteTextBuilder";
2612
+ constructor(name, config) {
2613
+ super(name, "string", "SQLiteText");
2614
+ this.config.enumValues = config.enum;
2615
+ this.config.length = config.length;
2616
+ }
2617
+ /** @internal */
2618
+ build(table) {
2619
+ return new SQLiteText(
2620
+ table,
2621
+ this.config
2622
+ );
2623
+ }
2624
+ };
2625
+ var SQLiteText = class extends SQLiteColumn {
2626
+ static [entityKind] = "SQLiteText";
2627
+ enumValues = this.config.enumValues;
2628
+ length = this.config.length;
2629
+ constructor(table, config) {
2630
+ super(table, config);
2631
+ }
2632
+ getSQLType() {
2633
+ return `text${this.config.length ? `(${this.config.length})` : ""}`;
2634
+ }
2635
+ };
2636
+ var SQLiteTextJsonBuilder = class extends SQLiteColumnBuilder {
2637
+ static [entityKind] = "SQLiteTextJsonBuilder";
2638
+ constructor(name) {
2639
+ super(name, "json", "SQLiteTextJson");
2640
+ }
2641
+ /** @internal */
2642
+ build(table) {
2643
+ return new SQLiteTextJson(
2644
+ table,
2645
+ this.config
2646
+ );
2647
+ }
2648
+ };
2649
+ var SQLiteTextJson = class extends SQLiteColumn {
2650
+ static [entityKind] = "SQLiteTextJson";
2651
+ getSQLType() {
2652
+ return "text";
2653
+ }
2654
+ mapFromDriverValue(value) {
2655
+ return JSON.parse(value);
2656
+ }
2657
+ mapToDriverValue(value) {
2658
+ return JSON.stringify(value);
2659
+ }
2660
+ };
2661
+ function text2(a, b = {}) {
2662
+ const { name, config } = getColumnNameAndConfig(a, b);
2663
+ if (config.mode === "json") {
2664
+ return new SQLiteTextJsonBuilder(name);
2665
+ }
2666
+ return new SQLiteTextBuilder(name, config);
2667
+ }
2668
+
2669
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/all.js
2670
+ function getSQLiteColumnBuilders() {
2671
+ return {
2672
+ blob,
2673
+ customType,
2674
+ integer,
2675
+ numeric,
2676
+ real,
2677
+ text: text2
2678
+ };
2679
+ }
2680
+
2681
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/table.js
2682
+ var InlineForeignKeys = /* @__PURE__ */ Symbol.for("drizzle:SQLiteInlineForeignKeys");
2683
+ var SQLiteTable = class extends Table {
2684
+ static [entityKind] = "SQLiteTable";
2685
+ /** @internal */
2686
+ static Symbol = Object.assign({}, Table.Symbol, {
2687
+ InlineForeignKeys
2688
+ });
2689
+ /** @internal */
2690
+ [Table.Symbol.Columns];
2691
+ /** @internal */
2692
+ [InlineForeignKeys] = [];
2693
+ /** @internal */
2694
+ [Table.Symbol.ExtraConfigBuilder] = void 0;
2695
+ };
2696
+ function sqliteTableBase(name, columns, extraConfig, schema, baseName = name) {
2697
+ const rawTable = new SQLiteTable(name, schema, baseName);
2698
+ const parsedColumns = typeof columns === "function" ? columns(getSQLiteColumnBuilders()) : columns;
2699
+ const builtColumns = Object.fromEntries(
2700
+ Object.entries(parsedColumns).map(([name2, colBuilderBase]) => {
2701
+ const colBuilder = colBuilderBase;
2702
+ colBuilder.setName(name2);
2703
+ const column = colBuilder.build(rawTable);
2704
+ rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable));
2705
+ return [name2, column];
2706
+ })
2707
+ );
2708
+ const table = Object.assign(rawTable, builtColumns);
2709
+ table[Table.Symbol.Columns] = builtColumns;
2710
+ table[Table.Symbol.ExtraConfigColumns] = builtColumns;
2711
+ if (extraConfig) {
2712
+ table[SQLiteTable.Symbol.ExtraConfigBuilder] = extraConfig;
2713
+ }
2714
+ return table;
2715
+ }
2716
+ var sqliteTable = (name, columns, extraConfig) => {
2717
+ return sqliteTableBase(name, columns, extraConfig);
2718
+ };
2719
+
2720
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@5.20261014.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/primary-keys.js
2721
+ function primaryKey(...config) {
2722
+ if (config[0].columns) {
2723
+ return new PrimaryKeyBuilder(config[0].columns, config[0].name);
2724
+ }
2725
+ return new PrimaryKeyBuilder(config);
2726
+ }
2727
+ var PrimaryKeyBuilder = class {
2728
+ static [entityKind] = "SQLitePrimaryKeyBuilder";
2729
+ /** @internal */
2730
+ columns;
2731
+ /** @internal */
2732
+ name;
2733
+ constructor(columns, name) {
2734
+ this.columns = columns;
2735
+ this.name = name;
2736
+ }
2737
+ /** @internal */
2738
+ build(table) {
2739
+ return new PrimaryKey(table, this.columns, this.name);
2740
+ }
2741
+ };
2742
+ var PrimaryKey = class {
2743
+ constructor(table, columns, name) {
2744
+ this.table = table;
2745
+ this.columns = columns;
2746
+ this.name = name;
2747
+ }
2748
+ static [entityKind] = "SQLitePrimaryKey";
2749
+ columns;
2750
+ name;
2751
+ getName() {
2752
+ return this.name ?? `${this.table[SQLiteTable.Symbol.Name]}_${this.columns.map((column) => column.name).join("_")}_pk`;
2753
+ }
2754
+ };
2755
+
2756
+ // ../feedback/src/connected.ts
2757
+ var participants = sqliteTable("participants", {
2758
+ subject: text2("subject").primaryKey(),
2759
+ displayName: text2("display_name").notNull()
2760
+ });
2761
+ var contacts = sqliteTable("participant_contacts", {
2762
+ subject: text2("subject").primaryKey(),
2763
+ email: text2("email"),
2764
+ emailEnabled: integer("email_enabled").notNull().default(0)
2765
+ });
2766
+ var nonces = sqliteTable("identity_nonces", {
2767
+ nonce: text2("nonce").primaryKey(),
2768
+ expiresAt: integer("expires_at").notNull()
2769
+ });
2770
+ var sessions = sqliteTable("participant_sessions", {
2771
+ tokenHash: text2("token_hash").primaryKey(),
2772
+ subject: text2("subject").notNull().references(() => participants.subject),
2773
+ expiresAt: integer("expires_at").notNull()
2774
+ });
2775
+ var votes = sqliteTable(
2776
+ "participant_votes",
2777
+ {
2778
+ postSlug: text2("post_slug").notNull(),
2779
+ subject: text2("subject").notNull().references(() => participants.subject)
2780
+ },
2781
+ (t) => [primaryKey({ columns: [t.postSlug, t.subject] })]
2782
+ );
2783
+ var attribution = sqliteTable("comment_attribution", {
2784
+ commentId: text2("comment_id").primaryKey(),
2785
+ subject: text2("subject").notNull().references(() => participants.subject),
2786
+ displayName: text2("display_name").notNull()
2787
+ });
2788
+
2789
+ // ../feedback/src/images.ts
2790
+ var limit = 5 * 1024 * 1024;
2791
+
2792
+ // ../feedback/src/index.ts
2793
+ var postCreationQueue = Promise.resolve();
2794
+
2795
+ // ../changelog/src/notion-changelog.ts
2796
+ var NOTION_API_URL2 = "https://api.notion.com/v1";
2797
+ var NOTION_API_VERSION2 = "2026-03-11";
2798
+ var canonicalProperties2 = {
2799
+ title: { name: "Title", type: "title", schema: { title: {} } },
2800
+ slug: { name: "Slug", type: "rich_text", schema: { rich_text: {} } },
2801
+ date: { name: "Date", type: "date", schema: { date: {} } },
2802
+ summary: {
2803
+ name: "Summary",
2804
+ type: "rich_text",
2805
+ schema: { rich_text: {} }
2806
+ },
2807
+ body: { name: "Body", type: "rich_text", schema: { rich_text: {} } },
2808
+ labels: {
2809
+ name: "Labels",
2810
+ type: "multi_select",
2811
+ schema: { multi_select: { options: [] } }
2812
+ },
2813
+ image: { name: "Image", type: "files", schema: { files: {} } },
2814
+ published: {
2815
+ name: "Published",
2816
+ type: "checkbox",
2817
+ schema: { checkbox: {} }
2818
+ },
2819
+ createdAt: {
2820
+ name: "Created At",
2821
+ type: "created_time",
2822
+ schema: { created_time: {} }
2823
+ },
2824
+ updatedAt: {
2825
+ name: "Updated At",
2826
+ type: "last_edited_time",
2827
+ schema: { last_edited_time: {} }
2828
+ }
2829
+ };
2830
+ async function createNotionChangelogDataSource({
2831
+ token,
2832
+ databaseId,
2833
+ request = fetch
2834
+ }) {
2835
+ const response = await request(`${NOTION_API_URL2}/data_sources`, {
2836
+ method: "POST",
2837
+ headers: notionHeaders2(token),
2838
+ body: JSON.stringify({
2839
+ parent: { database_id: databaseId },
2840
+ title: [{ type: "text", text: { content: "Changelog" } }],
2841
+ properties: Object.fromEntries(
2842
+ Object.values(canonicalProperties2).map(({ name, schema }) => [
2843
+ name,
2844
+ schema
2845
+ ])
2846
+ )
2847
+ })
2848
+ });
2849
+ return configurationFromResponse(
2850
+ await readObject(response, "Changelog Data Source"),
2851
+ databaseId
2852
+ );
2853
+ }
2854
+ async function validateNotionChangelogDataSource({
2855
+ token,
2856
+ dataSourceId,
2857
+ propertyIds,
2858
+ request = fetch
2859
+ }) {
2860
+ const response = await request(
2861
+ `${NOTION_API_URL2}/data_sources/${dataSourceId}`,
2862
+ { headers: notionHeaders2(token) }
2863
+ );
2864
+ const dataSource = await readObject(response, "Changelog Data Source");
2865
+ const properties = recordAt(dataSource, "properties");
2866
+ const propertiesById = new Map(
2867
+ Object.values(properties).flatMap((property) => {
2868
+ if (!isRecord(property) || typeof property.id !== "string") return [];
2869
+ return [[property.id, property]];
2870
+ })
2871
+ );
2872
+ const repairs = [];
2873
+ for (const [key, { name, type }] of Object.entries(canonicalProperties2)) {
2874
+ const configuredId = propertyIds[key];
2875
+ const property = propertiesById.get(configuredId);
2876
+ if (!property) {
2877
+ repairs.push(
2878
+ `Restore the "${name}" property with ID "${configuredId}" and type "${type}", then update the configured property ID if Notion assigns a new one.`
2879
+ );
2880
+ } else if (property.type !== type) {
2881
+ repairs.push(
2882
+ `Change the property configured as "${name}" (ID "${configuredId}") from "${String(property.type)}" to "${type}", or restore a ${type} property and configure its ID.`
2883
+ );
134
2884
  }
135
- if (!template)
136
- throw new Error('The Feedbax project template is missing from this package.');
137
- await cp(template, destination, { recursive: true });
138
- await replaceInTree(destination, {
139
- __GENERATOR_VERSION__: '0.1.0',
140
- __IDENTITY__: options.identity,
141
- __DEPLOYMENT__: options.deploy,
142
- __PACKAGE_MANAGER__: options.packageManager,
143
- __PACKAGE_MANAGER_SPEC__: options.packageManager === 'pnpm' ? 'pnpm@10.29.3' : 'npm@11.4.2',
144
- __PRESET__: options.preset,
145
- __NITRO_PRESET__: options.deploy === 'vercel'
146
- ? 'vercel'
147
- : options.deploy === 'cloudflare'
148
- ? 'cloudflare-module'
149
- : 'node-server',
2885
+ }
2886
+ if (repairs.length) throw new Error(repairs.join("\n"));
2887
+ return {
2888
+ databaseId: databaseIdFromResponse(dataSource),
2889
+ dataSourceId,
2890
+ propertyIds: { ...propertyIds }
2891
+ };
2892
+ }
2893
+ function configurationFromResponse(dataSource, databaseId) {
2894
+ const properties = recordAt(dataSource, "properties");
2895
+ return {
2896
+ databaseId,
2897
+ dataSourceId: requiredString(dataSource.id),
2898
+ propertyIds: Object.fromEntries(
2899
+ Object.entries(canonicalProperties2).map(([key, { name }]) => [
2900
+ key,
2901
+ requiredString(recordAt(properties, name).id)
2902
+ ])
2903
+ )
2904
+ };
2905
+ }
2906
+ function databaseIdFromResponse(dataSource) {
2907
+ return requiredString(recordAt(dataSource, "parent").database_id);
2908
+ }
2909
+ function notionHeaders2(token) {
2910
+ return {
2911
+ Authorization: `Bearer ${token}`,
2912
+ "Content-Type": "application/json",
2913
+ "Notion-Version": NOTION_API_VERSION2
2914
+ };
2915
+ }
2916
+ async function readObject(response, subject) {
2917
+ const body = await response.json();
2918
+ if (!response.ok) {
2919
+ throw new Error(
2920
+ `Notion rejected the ${subject} request (${response.status}).`
2921
+ );
2922
+ }
2923
+ if (!isRecord(body))
2924
+ throw new Error(`Notion returned an invalid ${subject} response.`);
2925
+ return body;
2926
+ }
2927
+ function recordAt(value, key) {
2928
+ return record(value[key]);
2929
+ }
2930
+ function record(value) {
2931
+ if (!isRecord(value))
2932
+ throw new Error("Notion returned an invalid Changelog response.");
2933
+ return value;
2934
+ }
2935
+ function requiredString(value) {
2936
+ if (typeof value !== "string")
2937
+ throw new Error("Notion returned an invalid Changelog response.");
2938
+ return value;
2939
+ }
2940
+ function isRecord(value) {
2941
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2942
+ }
2943
+
2944
+ // src/index.ts
2945
+ function defaultProcessingQueueName(workerName) {
2946
+ return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${createHash2("sha256").update(workerName).digest("hex").slice(0, 8)}-updates`;
2947
+ }
2948
+ async function provisionGuidedInstallation({
2949
+ destination,
2950
+ product,
2951
+ themePreset,
2952
+ workerName,
2953
+ commentProbePageId,
2954
+ connected,
2955
+ features,
2956
+ feedbackSelection,
2957
+ changelogSelection,
2958
+ notionToken,
2959
+ turnstile,
2960
+ preview,
2961
+ request = fetch
2962
+ }) {
2963
+ try {
2964
+ if ((await readdir2(destination)).length)
2965
+ throw new Error(
2966
+ `Refusing to generate into non-empty destination: ${destination}`
2967
+ );
2968
+ } catch (error) {
2969
+ if (error.code !== "ENOENT") throw error;
2970
+ }
2971
+ if (product.siteUrl && new URL(product.siteUrl).protocol !== "https:")
2972
+ throw new Error("product.siteUrl must be an HTTPS URL.");
2973
+ if (connected) {
2974
+ if (!product.siteUrl || connected.handoffSecret.length < 32 || !/^[0-9a-f-]{36}$/i.test(connected.databaseId) || !connected.databaseName.trim())
2975
+ throw new Error(
2976
+ "Connected setup requires a public site URL, dedicated D1 identifiers, and a handoff secret of at least 32 characters."
2977
+ );
2978
+ for (const url of [connected.issuer, connected.loginUrl])
2979
+ if (new URL(url).protocol !== "https:" || new URL(url).username || new URL(url).password)
2980
+ throw new Error(
2981
+ "Connected identity URLs must use HTTPS without credentials."
2982
+ );
2983
+ }
2984
+ if (connected?.email) {
2985
+ const sender = connected.email;
2986
+ if (!["cloudflare", "resend", "custom"].includes(sender.provider) || !/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(sender.from))
2987
+ throw new Error(
2988
+ "Choose cloudflare, resend, or custom and a plain sender email address."
2989
+ );
2990
+ if (sender.provider !== "cloudflare" && !sender.secret?.trim())
2991
+ throw new Error(
2992
+ "Resend and custom delivery require a server-only email secret."
2993
+ );
2994
+ if (sender.provider === "custom" && (!sender.endpoint || new URL(sender.endpoint).protocol !== "https:" || new URL(sender.endpoint).username || new URL(sender.endpoint).password || new URL(sender.endpoint).hash))
2995
+ throw new Error(
2996
+ "Custom delivery requires an HTTPS endpoint without credentials or fragments."
2997
+ );
2998
+ }
2999
+ if (connected?.imageBucket && !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(connected.imageBucket))
3000
+ throw new Error(
3001
+ "Use a dedicated private R2 bucket name of 3\u201363 lowercase letters, digits, or hyphens."
3002
+ );
3003
+ const targetName = workerName ?? basename(destination).toLowerCase().replace(/[^a-z0-9-]/g, "-");
3004
+ if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(targetName))
3005
+ throw new Error(
3006
+ "Worker name must contain 1\u201363 lowercase letters, numbers, or hyphens."
3007
+ );
3008
+ const processingQueue = connected && connected.processing !== "standard" ? connected.queueName ?? defaultProcessingQueueName(targetName) : void 0;
3009
+ if (processingQueue && !/^[a-z0-9][a-z0-9-]{0,62}$/.test(processingQueue))
3010
+ throw new Error(
3011
+ "Processing queue name must contain 1\u201363 lowercase letters, numbers, or hyphens."
3012
+ );
3013
+ const approved = await preview(
3014
+ feedbackSelection.kind === "create" ? `Create the canonical Feedback Data Source inside Notion page ${feedbackSelection.parentPageId}.` : `Validate existing Feedback Data Source ${feedbackSelection.configuration.dataSourceId} without changing it.`
3015
+ );
3016
+ if (!approved)
3017
+ throw new Error(
3018
+ "Feedback setup was not approved. No local files were created."
3019
+ );
3020
+ const feedback = feedbackSelection.kind === "create" ? await createNotionFeedbackDataSource({
3021
+ token: notionToken,
3022
+ parentPageId: feedbackSelection.parentPageId,
3023
+ request
3024
+ }) : await validateNotionFeedbackDataSource({
3025
+ token: notionToken,
3026
+ ...feedbackSelection.configuration,
3027
+ request
3028
+ });
3029
+ if (feedbackSelection.kind === "create") {
3030
+ if (!await preview(
3031
+ "Create six Notion team views: Inbox, Reviewing, Prioritization, Follow-up, Roadmap, and Shipped."
3032
+ ))
3033
+ throw new Error("Workflow view creation was not approved.");
3034
+ await createFeedbackWorkflowViews({
3035
+ token: notionToken,
3036
+ ...feedback,
3037
+ request
150
3038
  });
151
- try {
152
- if (options.install) {
153
- await run(options.packageManager, ['install'], destination);
154
- await run(options.packageManager, ['run', 'type-check'], destination);
155
- }
156
- if (options.git)
157
- await run('git', ['init'], destination);
158
- }
159
- catch (error) {
160
- await writeFile(join(destination, '.feedbax-recovery'), `Generation stopped: ${error instanceof Error ? error.message : String(error)}\nRerun dependency installation and type checking after resolving the reported issue.\n`);
161
- throw error;
162
- }
163
- return {
164
- destination,
165
- nextSteps: [
166
- `cd ${options.directory}`,
167
- 'Copy .env.example to .env and configure Notion',
168
- `${options.packageManager === 'pnpm' ? 'pnpm' : 'npx'} feedbax doctor`,
169
- `${options.packageManager} run dev`,
170
- ],
171
- };
3039
+ }
3040
+ await verifySelectedCommentCapabilities(features, {
3041
+ token: notionToken,
3042
+ pageId: commentProbePageId ?? (feedbackSelection.kind === "create" ? feedbackSelection.parentPageId : feedback.databaseId),
3043
+ request,
3044
+ command: "setup"
3045
+ });
3046
+ const changelog = await configureChangelogStorage({
3047
+ features,
3048
+ selection: changelogSelection ?? (features.changelog ? { kind: "create", databaseId: feedback.databaseId } : void 0),
3049
+ token: notionToken,
3050
+ request,
3051
+ preview
3052
+ });
3053
+ await copyPackagedTemplate(destination);
3054
+ const wranglerPath = join2(destination, "wrangler.jsonc");
3055
+ const wrangler = JSON.parse(await readFile2(wranglerPath, "utf8"));
3056
+ await writeFile2(
3057
+ wranglerPath,
3058
+ JSON.stringify(
3059
+ {
3060
+ ...wrangler,
3061
+ name: targetName,
3062
+ ...connected ? {
3063
+ triggers: { crons: ["*/5 * * * *"] },
3064
+ ...processingQueue ? {
3065
+ queues: {
3066
+ producers: [
3067
+ { binding: "FEEDBAX_UPDATES", queue: processingQueue }
3068
+ ],
3069
+ consumers: [
3070
+ {
3071
+ queue: processingQueue,
3072
+ max_batch_size: 1,
3073
+ max_batch_timeout: 1,
3074
+ max_retries: 3,
3075
+ max_concurrency: 1,
3076
+ retry_delay: 60
3077
+ }
3078
+ ]
3079
+ }
3080
+ } : {},
3081
+ ...connected.email?.provider === "cloudflare" ? {
3082
+ send_email: [
3083
+ {
3084
+ name: "FEEDBAX_EMAIL",
3085
+ allowed_sender_addresses: [connected.email.from]
3086
+ }
3087
+ ]
3088
+ } : {},
3089
+ ...connected.imageBucket ? {
3090
+ r2_buckets: [
3091
+ {
3092
+ binding: "FEEDBAX_IMAGES",
3093
+ bucket_name: connected.imageBucket
3094
+ }
3095
+ ]
3096
+ } : {},
3097
+ d1_databases: [
3098
+ {
3099
+ binding: "FEEDBAX_DB",
3100
+ database_id: connected.databaseId,
3101
+ database_name: connected.databaseName,
3102
+ migrations_dir: "migrations"
3103
+ }
3104
+ ]
3105
+ } : {}
3106
+ },
3107
+ null,
3108
+ 2
3109
+ ) + "\n"
3110
+ );
3111
+ const apiKey = randomBytes(32).toString("base64url");
3112
+ const signingSecret = randomBytes(32).toString("base64url");
3113
+ const values = {
3114
+ NOTION_TOKEN: notionToken,
3115
+ NOTION_FEEDBACK_DATABASE_ID: feedback.databaseId,
3116
+ NOTION_FEEDBACK_DATA_SOURCE_ID: feedback.dataSourceId,
3117
+ ...prefixProperties("NOTION_FEEDBACK", feedback.propertyIds),
3118
+ FEEDBAX_API_KEY_HASH: createHash2("sha256").update(apiKey).digest("base64url"),
3119
+ PARTICIPATION_SIGNING_SECRET: signingSecret,
3120
+ ...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
3121
+ ...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
3122
+ FEEDBAX_VOTING_ENABLED: String(features.voting),
3123
+ FEEDBAX_COMMENTS_ENABLED: String(features.comments),
3124
+ FEEDBAX_CHANGELOG_ENABLED: String(features.changelog),
3125
+ ...turnstile.siteKey ? { VITE_TURNSTILE_SITE_KEY: turnstile.siteKey } : {},
3126
+ ...turnstile.secretKey ? { TURNSTILE_SECRET_KEY: turnstile.secretKey } : {},
3127
+ ...changelog ? {
3128
+ NOTION_CHANGELOG_DATABASE_ID: changelog.databaseId,
3129
+ NOTION_CHANGELOG_DATA_SOURCE_ID: changelog.dataSourceId,
3130
+ ...prefixProperties("NOTION_CHANGELOG", changelog.propertyIds)
3131
+ } : {}
3132
+ };
3133
+ await writeFile2(
3134
+ join2(destination, ".dev.vars"),
3135
+ Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
3136
+ { mode: 384 }
3137
+ );
3138
+ const configuration = defineFeedbax({
3139
+ product: { ...product, logo: "/feedbax-mark.svg" },
3140
+ theme: { preset: themePreset },
3141
+ ...connected ? {
3142
+ connected: {
3143
+ processing: connected.processing ?? "small",
3144
+ issuer: connected.issuer,
3145
+ loginUrl: connected.loginUrl,
3146
+ ...connected.email ? {
3147
+ email: {
3148
+ provider: connected.email.provider,
3149
+ from: connected.email.from,
3150
+ ...connected.email.endpoint ? { endpoint: connected.email.endpoint } : {}
3151
+ }
3152
+ } : {},
3153
+ ...connected.imageBucket ? { images: true } : {}
3154
+ }
3155
+ } : {},
3156
+ notion: {
3157
+ databaseId: feedback.databaseId,
3158
+ dataSourceId: feedback.dataSourceId,
3159
+ properties: feedback.propertyIds
3160
+ },
3161
+ ...changelog ? {
3162
+ changelog: {
3163
+ databaseId: changelog.databaseId,
3164
+ dataSourceId: changelog.dataSourceId,
3165
+ properties: changelog.propertyIds
3166
+ }
3167
+ } : {},
3168
+ features,
3169
+ submissions: {
3170
+ moderation: "required",
3171
+ maxTitleLength: 160,
3172
+ maxDescriptionLength: 5e3
3173
+ },
3174
+ turnstile: {
3175
+ enabled: Boolean(turnstile.siteKey),
3176
+ ...turnstile.siteKey ? { siteKey: turnstile.siteKey } : {}
3177
+ }
3178
+ });
3179
+ await writeFile2(
3180
+ join2(destination, "src/feedbax.ts"),
3181
+ `import { defineFeedbax } from "@feedbax/config";
3182
+
3183
+ export default defineFeedbax(${JSON.stringify(configuration, null, 2)});
3184
+ `
3185
+ );
3186
+ const probePage = commentProbePageId ?? (feedbackSelection.kind === "create" ? feedbackSelection.parentPageId : feedback.databaseId);
3187
+ await writeFile2(
3188
+ join2(destination, ".dev.vars"),
3189
+ `NOTION_COMMENT_CAPABILITY_PROBE_PAGE_ID=${JSON.stringify(probePage)}
3190
+ `,
3191
+ { flag: "a", mode: 384 }
3192
+ );
3193
+ return { apiKey, feedback, changelog, processingQueue };
172
3194
  }
173
- //# sourceMappingURL=index.js.map
3195
+ function prefixProperties(prefix, properties) {
3196
+ return Object.fromEntries(
3197
+ Object.entries(properties).map(([key, value]) => [
3198
+ `${prefix}_${key.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase()}_PROPERTY_ID`,
3199
+ value
3200
+ ])
3201
+ );
3202
+ }
3203
+ var featureChoices = [
3204
+ { key: "voting", label: "Voting", initialValue: true },
3205
+ { key: "comments", label: "Comments", initialValue: true },
3206
+ { key: "changelog", label: "Changelog", initialValue: true }
3207
+ ];
3208
+ async function collectFeatureSelection(prompter) {
3209
+ const selections = [];
3210
+ for (const { key, label, initialValue } of featureChoices) {
3211
+ selections.push([
3212
+ key,
3213
+ await prompter.confirm({
3214
+ message: `Enable ${label}?`,
3215
+ initialValue
3216
+ })
3217
+ ]);
3218
+ }
3219
+ return Object.fromEntries(selections);
3220
+ }
3221
+ function renderFeatureConfiguration(features) {
3222
+ return `features: {
3223
+ voting: ${features.voting},
3224
+ comments: ${features.comments},
3225
+ changelog: ${features.changelog},
3226
+ },`;
3227
+ }
3228
+ function renderChangelogConfiguration(configuration) {
3229
+ const properties = Object.entries(configuration.propertyIds).map(([key, value]) => ` ${key}: ${JSON.stringify(value)},`).join("\n");
3230
+ return `changelog: {
3231
+ databaseId: ${JSON.stringify(configuration.databaseId)},
3232
+ dataSourceId: ${JSON.stringify(configuration.dataSourceId)},
3233
+ propertyIds: {
3234
+ ${properties}
3235
+ },
3236
+ },`;
3237
+ }
3238
+ async function verifySelectedCommentCapabilities(features, {
3239
+ token,
3240
+ pageId,
3241
+ request = fetch,
3242
+ command = "setup"
3243
+ }) {
3244
+ if (!features.comments) return;
3245
+ const headers = {
3246
+ Authorization: `Bearer ${token}`,
3247
+ "Notion-Version": "2026-03-11"
3248
+ };
3249
+ const readResponse = await request(
3250
+ `https://api.notion.com/v1/comments?block_id=${encodeURIComponent(pageId)}&page_size=1`,
3251
+ { headers }
3252
+ );
3253
+ if (!readResponse.ok) {
3254
+ if (readResponse.status === 403)
3255
+ assertEnabledCommentCapabilities({
3256
+ commentsEnabled: true,
3257
+ capabilities: { readComments: false, insertComments: true },
3258
+ command
3259
+ });
3260
+ throw new Error(
3261
+ `Notion Comment read check failed (HTTP ${readResponse.status}). Verify a shared page ID and connection access.`
3262
+ );
3263
+ }
3264
+ if (command === "doctor") {
3265
+ assertEnabledCommentCapabilities({
3266
+ commentsEnabled: true,
3267
+ capabilities: {
3268
+ readComments: readResponse.status !== 403,
3269
+ // Setup proves Insert comments before enabling the capability. Doctor
3270
+ // deliberately avoids a write-method probe and verifies the live read path.
3271
+ insertComments: true
3272
+ },
3273
+ command
3274
+ });
3275
+ return;
3276
+ }
3277
+ const insertResponse = await request("https://api.notion.com/v1/comments", {
3278
+ method: "POST",
3279
+ headers: { ...headers, "Content-Type": "application/json" },
3280
+ body: "{}"
3281
+ });
3282
+ if (insertResponse.status !== 400 && insertResponse.status !== 403)
3283
+ throw new Error(
3284
+ `Notion Comment insert capability check failed (HTTP ${insertResponse.status}).`
3285
+ );
3286
+ const capabilities = {
3287
+ readComments: readResponse.status !== 403,
3288
+ insertComments: insertResponse.status !== 403
3289
+ };
3290
+ assertEnabledCommentCapabilities({
3291
+ commentsEnabled: true,
3292
+ capabilities,
3293
+ command
3294
+ });
3295
+ }
3296
+ function assertEnabledCommentCapabilities({
3297
+ commentsEnabled,
3298
+ capabilities,
3299
+ command = "setup"
3300
+ }) {
3301
+ if (!commentsEnabled) return;
3302
+ const missing = [
3303
+ ...capabilities.readComments ? [] : ["Read comments"],
3304
+ ...capabilities.insertComments ? [] : ["Insert comments"]
3305
+ ];
3306
+ if (missing.length === 0) return;
3307
+ const retry = command === "doctor" ? "rerun doctor. No changes were made." : "retry setup.";
3308
+ throw new Error(
3309
+ `Enable ${missing.join(" and ")} in the Notion connection settings, then ${retry}`
3310
+ );
3311
+ }
3312
+ async function configureChangelogStorage({
3313
+ features,
3314
+ selection,
3315
+ token,
3316
+ request = fetch,
3317
+ preview
3318
+ }) {
3319
+ if (!features.changelog) return;
3320
+ if (!selection) {
3321
+ throw new Error(
3322
+ "Choose an existing compatible Changelog Data Source or create one beside Feedback."
3323
+ );
3324
+ }
3325
+ if (selection.kind === "existing") {
3326
+ return validateNotionChangelogDataSource({
3327
+ token,
3328
+ dataSourceId: selection.dataSourceId,
3329
+ propertyIds: selection.propertyIds,
3330
+ request
3331
+ });
3332
+ }
3333
+ const approved = await preview(
3334
+ `Create an empty Changelog Data Source beside Feedback in Feedbax Database ${selection.databaseId}. No sample entries will be published.`
3335
+ );
3336
+ if (!approved) throw new Error("Changelog creation was not approved.");
3337
+ return createNotionChangelogDataSource({
3338
+ token,
3339
+ databaseId: selection.databaseId,
3340
+ request
3341
+ });
3342
+ }
3343
+ async function doctorChangelogStorage({
3344
+ enabled,
3345
+ configuration,
3346
+ token,
3347
+ request = fetch
3348
+ }) {
3349
+ if (!enabled) return;
3350
+ if (!configuration) {
3351
+ throw new Error(
3352
+ "Changelog is enabled without identifiers and property mappings. Create or select a compatible Changelog Data Source, configure it, then rerun doctor. No changes were made."
3353
+ );
3354
+ }
3355
+ await validateNotionChangelogDataSource({
3356
+ token,
3357
+ dataSourceId: configuration.dataSourceId,
3358
+ propertyIds: configuration.propertyIds,
3359
+ request
3360
+ });
3361
+ }
3362
+ async function doctorInstallation({
3363
+ features,
3364
+ feedback,
3365
+ changelog,
3366
+ commentProbePageId,
3367
+ token,
3368
+ request = fetch
3369
+ }) {
3370
+ await validateNotionFeedbackDataSource({ token, ...feedback, request });
3371
+ if (features.comments) {
3372
+ if (!commentProbePageId) {
3373
+ throw new Error(
3374
+ "Comments are enabled but no Comment capability probe page is configured. Configure one, then rerun doctor. No changes were made."
3375
+ );
3376
+ }
3377
+ await verifySelectedCommentCapabilities(features, {
3378
+ token,
3379
+ pageId: commentProbePageId,
3380
+ request,
3381
+ command: "doctor"
3382
+ });
3383
+ }
3384
+ await doctorChangelogStorage({
3385
+ enabled: features.changelog,
3386
+ configuration: changelog,
3387
+ token,
3388
+ request
3389
+ });
3390
+ }
3391
+ export {
3392
+ assembleFlatTemplate,
3393
+ assertEnabledCommentCapabilities,
3394
+ collectFeatureSelection,
3395
+ configureChangelogStorage,
3396
+ copyPackagedTemplate,
3397
+ defaultProcessingQueueName,
3398
+ doctorChangelogStorage,
3399
+ doctorInstallation,
3400
+ featureChoices,
3401
+ provisionGuidedInstallation,
3402
+ renderChangelogConfiguration,
3403
+ renderFeatureConfiguration,
3404
+ templateChecksum,
3405
+ verifyPackagedTemplate,
3406
+ verifySelectedCommentCapabilities
3407
+ };