create-feedbax 0.2.1 → 0.2.2

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 (78) hide show
  1. package/README.md +17 -5
  2. package/dist/cli.js +502 -209
  3. package/dist/index.js +251 -68
  4. package/package.json +7 -7
  5. package/template/.feedbax-template.sha256 +1 -1
  6. package/template/.gitignore.template +1 -0
  7. package/template/.npmignore.template +1 -0
  8. package/template/.nvmrc +1 -0
  9. package/template/AGENTS.md +1 -1
  10. package/template/CONNECTED.md +16 -2
  11. package/template/README.md +52 -13
  12. package/template/feedbax-vite.ts +79 -0
  13. package/template/migrations/0005_github.sql +4 -0
  14. package/template/migrations/0006_integration.sql +3 -0
  15. package/template/migrations/0007_ownership.sql +2 -0
  16. package/template/migrations/0008_integration_recovery.sql +5 -0
  17. package/template/migrations/0009_observed_history.sql +3 -0
  18. package/template/package.json +34 -33
  19. package/template/src/changelog-runtime.ts +2 -1
  20. package/template/src/cloudflare-workers.d.ts +3 -0
  21. package/template/src/comment-creation.test.tsx +53 -6
  22. package/template/src/components/connected-identity-provider.tsx +44 -17
  23. package/template/src/components/follow-post-button.tsx +2 -13
  24. package/template/src/components/post-history.tsx +101 -0
  25. package/template/src/components/profile-menu.tsx +7 -0
  26. package/template/src/configured-feedback.test.ts +82 -0
  27. package/template/src/configured-feedback.ts +61 -0
  28. package/template/src/connected-identity.test.ts +7 -3
  29. package/template/src/connected-identity.ts +6 -1
  30. package/template/src/connected-runtime.test.ts +5 -2
  31. package/template/src/feedback-cache-invalidation.ts +2 -0
  32. package/template/src/feedback-runtime.ts +61 -18
  33. package/template/src/feedbax/changelog/index.test.ts +35 -0
  34. package/template/src/feedbax/changelog/index.ts +5 -1
  35. package/template/src/feedbax/changelog/notion-changelog.test.ts +1 -1
  36. package/template/src/feedbax/changelog/notion-changelog.ts +1 -1
  37. package/template/src/feedbax/config/index.test.ts +45 -1
  38. package/template/src/feedbax/config/index.ts +112 -9
  39. package/template/src/feedbax/feedback/comment-storage.ts +38 -0
  40. package/template/src/feedbax/feedback/connected-feedback.ts +41 -12
  41. package/template/src/feedbax/feedback/connected.ts +15 -5
  42. package/template/src/feedbax/feedback/feedback-storage.ts +1 -0
  43. package/template/src/feedbax/feedback/github-comments.ts +390 -0
  44. package/template/src/feedbax/feedback/github-feedback.test.ts +1138 -0
  45. package/template/src/feedbax/feedback/github-feedback.ts +581 -0
  46. package/template/src/feedbax/feedback/github-request.ts +120 -0
  47. package/template/src/feedbax/feedback/github-service-client.ts +56 -0
  48. package/template/src/feedbax/feedback/image-feedback.ts +23 -10
  49. package/template/src/feedbax/feedback/images.ts +26 -0
  50. package/template/src/feedbax/feedback/index.ts +172 -16
  51. package/template/src/feedbax/feedback/integration-auth.ts +138 -0
  52. package/template/src/feedbax/feedback/notion-comments.ts +2 -35
  53. package/template/src/feedbax/feedback/observed-history.test.ts +150 -0
  54. package/template/src/feedbax/feedback/observed-history.ts +219 -0
  55. package/template/src/feedbax/feedback/ownership.ts +170 -0
  56. package/template/src/history-runtime.test.ts +80 -0
  57. package/template/src/history-runtime.ts +37 -0
  58. package/template/src/index.css +6 -0
  59. package/template/src/integration-runtime.test.ts +365 -0
  60. package/template/src/integration-runtime.ts +289 -0
  61. package/template/src/my-feedback-page.test.tsx +152 -0
  62. package/template/src/my-feedback-page.tsx +398 -0
  63. package/template/src/my-feedback-runtime.test.ts +93 -0
  64. package/template/src/my-feedback-runtime.ts +154 -0
  65. package/template/src/participation-runtime.ts +3 -3
  66. package/template/src/portal-comments.ts +11 -0
  67. package/template/src/portal-feedback-form.tsx +11 -3
  68. package/template/src/portal-feedback-submission.ts +9 -0
  69. package/template/src/portal-server-entry.ts +9 -0
  70. package/template/src/public-portal-error.tsx +1 -1
  71. package/template/src/public-post-detail.tsx +332 -256
  72. package/template/src/public-release-links.test.ts +120 -0
  73. package/template/src/public-release-links.ts +130 -0
  74. package/template/src/routeTree.gen.ts +21 -0
  75. package/template/src/routes/my-feedback.tsx +16 -0
  76. package/template/src/server.ts +46 -1
  77. package/template/src/submission-intent.ts +26 -0
  78. package/template/vite.config.ts +2 -0
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
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";
2
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
3
+ import { readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "node:fs/promises";
4
4
 
5
5
  // ../config/src/index.ts
6
6
  var rootKeys = [
7
+ "history",
8
+ "provider",
9
+ "github",
7
10
  "connected",
8
11
  "product",
9
12
  "theme",
@@ -47,6 +50,8 @@ var changelogPropertyKeys = [
47
50
  function defineFeedbax(config) {
48
51
  const value = object(config, "Feedbax configuration");
49
52
  rejectUnknown(value, rootKeys, "Feedbax configuration");
53
+ if (value.history !== void 0 && typeof value.history !== "boolean")
54
+ throw new Error("history must be true or false.");
50
55
  const product = object(value.product, "product");
51
56
  rejectUnknown(
52
57
  product,
@@ -123,11 +128,19 @@ function defineFeedbax(config) {
123
128
  }
124
129
  const theme = object(value.theme, "theme");
125
130
  rejectUnknown(theme, ["preset"], "theme");
126
- const notion = storage(
127
- value.notion,
128
- feedbackPropertyKeys,
129
- "notion"
130
- );
131
+ if (value.provider !== void 0 && !["notion", "github"].includes(String(value.provider)))
132
+ throw new Error("provider must be notion or github.");
133
+ const githubMode = value.provider === "github";
134
+ if (githubMode && value.notion !== void 0 || !githubMode && value.github !== void 0)
135
+ throw new Error("Select exactly one Content Provider: notion or github.");
136
+ const content = githubMode ? { provider: "github", github: githubConfiguration(value.github) } : {
137
+ ...value.provider ? { provider: "notion" } : {},
138
+ notion: storage(
139
+ value.notion,
140
+ feedbackPropertyKeys,
141
+ "notion"
142
+ )
143
+ };
131
144
  const features = object(value.features, "features");
132
145
  rejectUnknown(features, featureKeys, "features");
133
146
  const validatedFeatures = Object.fromEntries(
@@ -137,6 +150,10 @@ function defineFeedbax(config) {
137
150
  return [key, features[key]];
138
151
  })
139
152
  );
153
+ if (githubMode && (validatedFeatures.changelog || value.changelog !== void 0))
154
+ throw new Error(
155
+ "GitHub Installations must disable Changelog (features.changelog: false) and remove Notion Changelog configuration."
156
+ );
140
157
  const changelog = value.changelog === void 0 ? void 0 : storage(
141
158
  value.changelog,
142
159
  changelogPropertyKeys,
@@ -163,9 +180,10 @@ function defineFeedbax(config) {
163
180
  throw new Error("Turnstile is enabled but turnstile.siteKey is missing.");
164
181
  return {
165
182
  product: validatedProduct,
183
+ ...value.history !== void 0 ? { history: value.history } : {},
166
184
  ...connected ? { connected } : {},
167
185
  theme: { preset: text(theme.preset, "theme.preset") },
168
- notion,
186
+ ...content,
169
187
  ...changelog ? { changelog } : {},
170
188
  features: validatedFeatures,
171
189
  submissions: {
@@ -230,9 +248,44 @@ function publicUrl(value, label, allowRelative) {
230
248
  `${label} must be ${allowRelative ? "root-relative or " : ""}an HTTPS URL.`
231
249
  );
232
250
  }
251
+ function githubConfiguration(value) {
252
+ const input = object(value, "github");
253
+ rejectUnknown(
254
+ input,
255
+ ["installationId", "serviceUrl", "repository", "botUserId"],
256
+ "github"
257
+ );
258
+ const installationId = text(input.installationId, "github.installationId");
259
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(installationId))
260
+ throw new Error(
261
+ "github.installationId must be a stable Installation identifier."
262
+ );
263
+ const serviceUrl = publicUrl(input.serviceUrl, "github.serviceUrl", false);
264
+ const url = new URL(serviceUrl);
265
+ if (url.origin !== serviceUrl || url.username || url.password)
266
+ throw new Error(
267
+ "github.serviceUrl must be an HTTPS origin without a path or credentials."
268
+ );
269
+ const repository = object(input.repository, "github.repository");
270
+ rejectUnknown(repository, ["id", "owner", "name"], "github.repository");
271
+ const owner = text(repository.owner, "github.repository.owner");
272
+ const name = text(repository.name, "github.repository.name");
273
+ if (!/^[A-Za-z0-9-]+$/.test(owner) || !/^[A-Za-z0-9_.-]+$/.test(name))
274
+ throw new Error("Use a valid GitHub repository owner and name.");
275
+ return {
276
+ installationId,
277
+ serviceUrl,
278
+ repository: {
279
+ id: positiveInteger(repository.id, "github.repository.id"),
280
+ owner,
281
+ name
282
+ },
283
+ botUserId: positiveInteger(input.botUserId, "github.botUserId")
284
+ };
285
+ }
233
286
 
234
287
  // src/index.ts
235
- import { basename, join as join2 } from "node:path";
288
+ import { basename, join as join3 } from "node:path";
236
289
 
237
290
  // src/template.ts
238
291
  import { createHash } from "node:crypto";
@@ -307,7 +360,7 @@ async function assembleFlatTemplate(coreRoot, destination) {
307
360
  '@source "../../../**/*.{ts,tsx}";'
308
361
  )
309
362
  );
310
- for (const name of ["vite.config.ts"])
363
+ for (const name of ["vite.config.ts", "feedbax-vite.ts"])
311
364
  await copy(join(coreRoot, "apps/portal", name), join(target, name));
312
365
  const components = parse(
313
366
  await readFile(join(coreRoot, "apps/portal/components.json"), "utf8")
@@ -329,6 +382,7 @@ async function assembleFlatTemplate(coreRoot, destination) {
329
382
  JSON.stringify(wrangler, null, 2) + "\n"
330
383
  );
331
384
  await copy(join(coreRoot, "LICENSE"), join(target, "LICENSE"));
385
+ await copy(join(coreRoot, ".nvmrc"), join(target, ".nvmrc"));
332
386
  await copy(
333
387
  join(coreRoot, "eslint.config.mjs"),
334
388
  join(target, "eslint.config.mjs")
@@ -426,11 +480,11 @@ export default defineFeedbax(${JSON.stringify(fixture, null, 2)});
426
480
  );
427
481
  await writeFile(
428
482
  join(target, ".gitignore.template"),
429
- ".dev.vars\n.env*\ndist/\n.wrangler/\n*.log\nnode_modules/\n"
483
+ ".dev.vars\n.env*\n.feedbax/\ndist/\n.wrangler/\n*.log\nnode_modules/\n"
430
484
  );
431
485
  await writeFile(
432
486
  join(target, ".npmignore.template"),
433
- ".dev.vars\n.env*\ndist/\n.wrangler/\n*.log\n"
487
+ ".dev.vars\n.env*\n.feedbax/\ndist/\n.wrangler/\n*.log\n"
434
488
  );
435
489
  await writeFile(
436
490
  join(target, ".prettierignore.template"),
@@ -529,18 +583,18 @@ async function flatManifest(coreRoot) {
529
583
  ...collect("devDependencies"),
530
584
  "@eslint/js": "10.0.1",
531
585
  "@shadcn/lint": "0.2.1",
532
- "@types/node": "22.20.1",
586
+ "@types/node": "24.19.2",
533
587
  feedbax: JSON.parse(
534
588
  await readFile(join(coreRoot, "packages/feedbax/package.json"), "utf8")
535
589
  ).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"
590
+ eslint: "10.12.0",
591
+ globals: "17.13.0",
592
+ prettier: "3.9.10",
593
+ "typescript-eslint": "8.71.2",
594
+ vitest: "5.0.4"
541
595
  },
542
596
  pnpm: { onlyBuiltDependencies: ["esbuild"] },
543
- engines: { node: ">=22.14.0" },
597
+ engines: { node: "^24.15.0 || >=26.0.0" },
544
598
  packageManager: "pnpm@10.29.3",
545
599
  license: "Apache-2.0"
546
600
  };
@@ -1000,7 +1054,7 @@ function isPropertyMap(value) {
1000
1054
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1001
1055
  }
1002
1056
 
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
1057
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/entity.js
1004
1058
  var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
1005
1059
  function is(value, type) {
1006
1060
  if (!value || typeof value !== "object") {
@@ -1026,7 +1080,7 @@ function is(value, type) {
1026
1080
  return false;
1027
1081
  }
1028
1082
 
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
1083
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/column.js
1030
1084
  var Column = class {
1031
1085
  constructor(table, config) {
1032
1086
  this.table = table;
@@ -1077,7 +1131,7 @@ var Column = class {
1077
1131
  }
1078
1132
  };
1079
1133
 
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
1134
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/column-builder.js
1081
1135
  var ColumnBuilder = class {
1082
1136
  static [entityKind] = "ColumnBuilder";
1083
1137
  config;
@@ -1180,10 +1234,10 @@ var ColumnBuilder = class {
1180
1234
  }
1181
1235
  };
1182
1236
 
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
1237
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/table.utils.js
1184
1238
  var TableName = /* @__PURE__ */ Symbol.for("drizzle:Name");
1185
1239
 
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
1240
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/foreign-keys.js
1187
1241
  var ForeignKeyBuilder = class {
1188
1242
  static [entityKind] = "PgForeignKeyBuilder";
1189
1243
  /** @internal */
@@ -1240,12 +1294,12 @@ var ForeignKey = class {
1240
1294
  }
1241
1295
  };
1242
1296
 
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
1297
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/tracing-utils.js
1244
1298
  function iife(fn, ...args) {
1245
1299
  return fn(...args);
1246
1300
  }
1247
1301
 
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
1302
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/unique-constraint.js
1249
1303
  function uniqueKeyName(table, columns) {
1250
1304
  return `${table[TableName]}_${columns.join("_")}_unique`;
1251
1305
  }
@@ -1295,7 +1349,7 @@ var UniqueConstraint = class {
1295
1349
  }
1296
1350
  };
1297
1351
 
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
1352
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/utils/array.js
1299
1353
  function parsePgArrayValue(arrayString, startFrom, inQuotes) {
1300
1354
  for (let i = startFrom; i < arrayString.length; i++) {
1301
1355
  const char = arrayString[i];
@@ -1371,7 +1425,7 @@ function makePgArray(array) {
1371
1425
  }).join(",")}}`;
1372
1426
  }
1373
1427
 
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
1428
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/common.js
1375
1429
  var PgColumnBuilder = class extends ColumnBuilder {
1376
1430
  foreignKeyConfigs = [];
1377
1431
  static [entityKind] = "PgColumnBuilder";
@@ -1555,7 +1609,7 @@ var PgArray = class _PgArray extends PgColumn {
1555
1609
  }
1556
1610
  };
1557
1611
 
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
1612
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/enum.js
1559
1613
  var PgEnumObjectColumnBuilder = class extends PgColumnBuilder {
1560
1614
  static [entityKind] = "PgEnumObjectColumnBuilder";
1561
1615
  constructor(name, enumInstance) {
@@ -1613,7 +1667,7 @@ var PgEnumColumn = class extends PgColumn {
1613
1667
  }
1614
1668
  };
1615
1669
 
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
1670
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/subquery.js
1617
1671
  var Subquery = class {
1618
1672
  static [entityKind] = "Subquery";
1619
1673
  constructor(sql2, fields, alias, isWith = false, usedTables = []) {
@@ -1634,10 +1688,10 @@ var WithSubquery = class extends Subquery {
1634
1688
  static [entityKind] = "WithSubquery";
1635
1689
  };
1636
1690
 
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";
1691
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/version.js
1692
+ var version = "0.45.4";
1639
1693
 
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
1694
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/tracing.js
1641
1695
  var otel;
1642
1696
  var rawTracer;
1643
1697
  var tracer = {
@@ -1672,10 +1726,10 @@ var tracer = {
1672
1726
  }
1673
1727
  };
1674
1728
 
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
1729
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/view-common.js
1676
1730
  var ViewBaseConfig = /* @__PURE__ */ Symbol.for("drizzle:ViewBaseConfig");
1677
1731
 
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
1732
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/table.js
1679
1733
  var Schema = /* @__PURE__ */ Symbol.for("drizzle:Schema");
1680
1734
  var Columns = /* @__PURE__ */ Symbol.for("drizzle:Columns");
1681
1735
  var ExtraConfigColumns = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigColumns");
@@ -1731,7 +1785,7 @@ var Table = class {
1731
1785
  }
1732
1786
  };
1733
1787
 
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
1788
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sql/sql.js
1735
1789
  var FakePrimitiveParam = class {
1736
1790
  static [entityKind] = "FakePrimitiveParam";
1737
1791
  };
@@ -2021,7 +2075,7 @@ function sql(strings, ...params) {
2021
2075
  return new SQL([new StringChunk(str)]);
2022
2076
  }
2023
2077
  sql2.raw = raw;
2024
- function join3(chunks, separator) {
2078
+ function join4(chunks, separator) {
2025
2079
  const result = [];
2026
2080
  for (const [i, chunk] of chunks.entries()) {
2027
2081
  if (i > 0 && separator !== void 0) {
@@ -2031,7 +2085,7 @@ function sql(strings, ...params) {
2031
2085
  }
2032
2086
  return new SQL(result);
2033
2087
  }
2034
- sql2.join = join3;
2088
+ sql2.join = join4;
2035
2089
  function identifier(value) {
2036
2090
  return new Name(value);
2037
2091
  }
@@ -2105,7 +2159,7 @@ Subquery.prototype.getSQL = function() {
2105
2159
  return new SQL([this]);
2106
2160
  };
2107
2161
 
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
2162
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/utils.js
2109
2163
  function getColumnNameAndConfig(a, b) {
2110
2164
  return {
2111
2165
  name: typeof a === "string" && a.length > 0 ? a : "",
@@ -2114,7 +2168,7 @@ function getColumnNameAndConfig(a, b) {
2114
2168
  }
2115
2169
  var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder();
2116
2170
 
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
2171
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
2118
2172
  var ForeignKeyBuilder2 = class {
2119
2173
  static [entityKind] = "SQLiteForeignKeyBuilder";
2120
2174
  /** @internal */
@@ -2171,7 +2225,7 @@ var ForeignKey2 = class {
2171
2225
  }
2172
2226
  };
2173
2227
 
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
2228
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
2175
2229
  function uniqueKeyName2(table, columns) {
2176
2230
  return `${table[TableName]}_${columns.join("_")}_unique`;
2177
2231
  }
@@ -2213,7 +2267,7 @@ var UniqueConstraint2 = class {
2213
2267
  }
2214
2268
  };
2215
2269
 
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
2270
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/common.js
2217
2271
  var SQLiteColumnBuilder = class extends ColumnBuilder {
2218
2272
  static [entityKind] = "SQLiteColumnBuilder";
2219
2273
  foreignKeyConfigs = [];
@@ -2264,7 +2318,7 @@ var SQLiteColumn = class extends Column {
2264
2318
  static [entityKind] = "SQLiteColumn";
2265
2319
  };
2266
2320
 
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
2321
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/blob.js
2268
2322
  var SQLiteBigIntBuilder = class extends SQLiteColumnBuilder {
2269
2323
  static [entityKind] = "SQLiteBigIntBuilder";
2270
2324
  constructor(name) {
@@ -2353,7 +2407,7 @@ function blob(a, b) {
2353
2407
  return new SQLiteBlobBufferBuilder(name);
2354
2408
  }
2355
2409
 
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
2410
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/custom.js
2357
2411
  var SQLiteCustomColumnBuilder = class extends SQLiteColumnBuilder {
2358
2412
  static [entityKind] = "SQLiteCustomColumnBuilder";
2359
2413
  constructor(name, fieldConfig, customTypeParams) {
@@ -2401,7 +2455,7 @@ function customType(customTypeParams) {
2401
2455
  };
2402
2456
  }
2403
2457
 
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
2458
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/integer.js
2405
2459
  var SQLiteBaseIntegerBuilder = class extends SQLiteColumnBuilder {
2406
2460
  static [entityKind] = "SQLiteBaseIntegerBuilder";
2407
2461
  constructor(name, dataType, columnType) {
@@ -2510,7 +2564,7 @@ function integer(a, b) {
2510
2564
  return new SQLiteIntegerBuilder(name);
2511
2565
  }
2512
2566
 
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
2567
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
2514
2568
  var SQLiteNumericBuilder = class extends SQLiteColumnBuilder {
2515
2569
  static [entityKind] = "SQLiteNumericBuilder";
2516
2570
  constructor(name) {
@@ -2585,7 +2639,7 @@ function numeric(a, b) {
2585
2639
  return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
2586
2640
  }
2587
2641
 
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
2642
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/real.js
2589
2643
  var SQLiteRealBuilder = class extends SQLiteColumnBuilder {
2590
2644
  static [entityKind] = "SQLiteRealBuilder";
2591
2645
  constructor(name) {
@@ -2606,7 +2660,7 @@ function real(name) {
2606
2660
  return new SQLiteRealBuilder(name ?? "");
2607
2661
  }
2608
2662
 
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
2663
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/text.js
2610
2664
  var SQLiteTextBuilder = class extends SQLiteColumnBuilder {
2611
2665
  static [entityKind] = "SQLiteTextBuilder";
2612
2666
  constructor(name, config) {
@@ -2666,7 +2720,7 @@ function text2(a, b = {}) {
2666
2720
  return new SQLiteTextBuilder(name, config);
2667
2721
  }
2668
2722
 
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
2723
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/all.js
2670
2724
  function getSQLiteColumnBuilders() {
2671
2725
  return {
2672
2726
  blob,
@@ -2678,7 +2732,7 @@ function getSQLiteColumnBuilders() {
2678
2732
  };
2679
2733
  }
2680
2734
 
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
2735
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/table.js
2682
2736
  var InlineForeignKeys = /* @__PURE__ */ Symbol.for("drizzle:SQLiteInlineForeignKeys");
2683
2737
  var SQLiteTable = class extends Table {
2684
2738
  static [entityKind] = "SQLiteTable";
@@ -2717,7 +2771,7 @@ var sqliteTable = (name, columns, extraConfig) => {
2717
2771
  return sqliteTableBase(name, columns, extraConfig);
2718
2772
  };
2719
2773
 
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
2774
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/primary-keys.js
2721
2775
  function primaryKey(...config) {
2722
2776
  if (config[0].columns) {
2723
2777
  return new PrimaryKeyBuilder(config[0].columns, config[0].name);
@@ -2865,7 +2919,7 @@ async function validateNotionChangelogDataSource({
2865
2919
  const properties = recordAt(dataSource, "properties");
2866
2920
  const propertiesById = new Map(
2867
2921
  Object.values(properties).flatMap((property) => {
2868
- if (!isRecord(property) || typeof property.id !== "string") return [];
2922
+ if (!isRecord2(property) || typeof property.id !== "string") return [];
2869
2923
  return [[property.id, property]];
2870
2924
  })
2871
2925
  );
@@ -2920,7 +2974,7 @@ async function readObject(response, subject) {
2920
2974
  `Notion rejected the ${subject} request (${response.status}).`
2921
2975
  );
2922
2976
  }
2923
- if (!isRecord(body))
2977
+ if (!isRecord2(body))
2924
2978
  throw new Error(`Notion returned an invalid ${subject} response.`);
2925
2979
  return body;
2926
2980
  }
@@ -2928,7 +2982,7 @@ function recordAt(value, key) {
2928
2982
  return record(value[key]);
2929
2983
  }
2930
2984
  function record(value) {
2931
- if (!isRecord(value))
2985
+ if (!isRecord2(value))
2932
2986
  throw new Error("Notion returned an invalid Changelog response.");
2933
2987
  return value;
2934
2988
  }
@@ -2937,13 +2991,141 @@ function requiredString(value) {
2937
2991
  throw new Error("Notion returned an invalid Changelog response.");
2938
2992
  return value;
2939
2993
  }
2940
- function isRecord(value) {
2994
+ function isRecord2(value) {
2941
2995
  return typeof value === "object" && value !== null && !Array.isArray(value);
2942
2996
  }
2943
2997
 
2998
+ // src/github.ts
2999
+ import { createHash as createHash2, randomBytes } from "node:crypto";
3000
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
3001
+ import { join as join2 } from "node:path";
3002
+ async function provisionGitHubInstallation(options) {
3003
+ const { connected, github } = options;
3004
+ if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(options.workerName) || !/^[0-9a-f-]{36}$/i.test(options.database.id) || !options.database.name.trim())
3005
+ throw new Error(
3006
+ "GitHub setup requires a distinct Worker and an explicitly provisioned FEEDBAX_DB D1 database."
3007
+ );
3008
+ if (connected && (connected.databaseId !== options.database.id || connected.handoffSecret.length < 32 || connected.processing !== "standard" && !connected.queueName))
3009
+ throw new Error(
3010
+ "Connected setup requires the same D1 database, a handoff secret, and an existing processing queue for small processing."
3011
+ );
3012
+ if (connected?.imageBucket && !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(connected.imageBucket))
3013
+ throw new Error("Use a dedicated private R2 bucket for images.");
3014
+ const config = defineFeedbax({
3015
+ provider: "github",
3016
+ github,
3017
+ product: { ...options.product, logo: "/feedbax-mark.svg" },
3018
+ theme: { preset: "lyra-neutral" },
3019
+ features: options.features,
3020
+ ...connected ? {
3021
+ connected: {
3022
+ processing: connected.processing ?? "small",
3023
+ issuer: connected.issuer,
3024
+ loginUrl: connected.loginUrl,
3025
+ ...connected.imageBucket ? { images: true } : {},
3026
+ ...connected.email ? {
3027
+ email: {
3028
+ provider: connected.email.provider,
3029
+ from: connected.email.from,
3030
+ ...connected.email.endpoint ? { endpoint: connected.email.endpoint } : {}
3031
+ }
3032
+ } : {}
3033
+ }
3034
+ } : {},
3035
+ submissions: {
3036
+ moderation: "required",
3037
+ maxTitleLength: 160,
3038
+ maxDescriptionLength: 5e3
3039
+ },
3040
+ turnstile: {
3041
+ enabled: Boolean(options.turnstile?.siteKey),
3042
+ ...options.turnstile?.siteKey ? { siteKey: options.turnstile.siteKey } : {}
3043
+ }
3044
+ });
3045
+ if (!await options.preview(
3046
+ "Create GitHub-backed portal source and private pairing secrets. GitHub requires D1 independently of sign-in. If the repository is public, issue titles/bodies are public immediately even while portal Posts await moderation. This does not create issues, pair the App, or deploy."
3047
+ ))
3048
+ throw new Error("GitHub setup was not approved.");
3049
+ await copyPackagedTemplate(options.destination);
3050
+ const path = join2(options.destination, "wrangler.jsonc");
3051
+ const base = JSON.parse(await readFile2(path, "utf8"));
3052
+ const wrangler = {
3053
+ ...base,
3054
+ name: options.workerName,
3055
+ compatibility_flags: [
3056
+ .../* @__PURE__ */ new Set([
3057
+ ...base.compatibility_flags ?? [],
3058
+ "global_fetch_strictly_public"
3059
+ ])
3060
+ ],
3061
+ d1_databases: [
3062
+ {
3063
+ binding: "FEEDBAX_DB",
3064
+ database_id: options.database.id,
3065
+ database_name: options.database.name,
3066
+ migrations_dir: "migrations"
3067
+ }
3068
+ ],
3069
+ triggers: { crons: ["*/5 * * * *"] },
3070
+ ...connected?.queueName ? {
3071
+ queues: {
3072
+ producers: [
3073
+ { binding: "FEEDBAX_UPDATES", queue: connected.queueName }
3074
+ ],
3075
+ consumers: [
3076
+ {
3077
+ queue: connected.queueName,
3078
+ max_batch_size: 1,
3079
+ max_concurrency: 1,
3080
+ max_retries: 3,
3081
+ retry_delay: 60
3082
+ }
3083
+ ]
3084
+ }
3085
+ } : {},
3086
+ ...connected?.imageBucket ? {
3087
+ r2_buckets: [
3088
+ { binding: "FEEDBAX_IMAGES", bucket_name: connected.imageBucket }
3089
+ ]
3090
+ } : {},
3091
+ ...connected?.email?.provider === "cloudflare" ? {
3092
+ send_email: [
3093
+ {
3094
+ name: "FEEDBAX_EMAIL",
3095
+ allowed_sender_addresses: [connected.email.from]
3096
+ }
3097
+ ]
3098
+ } : {}
3099
+ };
3100
+ const apiKey = randomBytes(32).toString("base64url");
3101
+ const pairingSecret = randomBytes(32).toString("base64url");
3102
+ const secrets = {
3103
+ FEEDBAX_API_KEY_HASH: createHash2("sha256").update(apiKey).digest("base64url"),
3104
+ PARTICIPATION_SIGNING_SECRET: randomBytes(32).toString("base64url"),
3105
+ GITHUB_PAIRING_SECRET: pairingSecret,
3106
+ ...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
3107
+ ...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
3108
+ ...options.turnstile?.secretKey ? { TURNSTILE_SECRET_KEY: options.turnstile.secretKey } : {}
3109
+ };
3110
+ await writeFile2(path, JSON.stringify(wrangler, null, 2) + "\n");
3111
+ await writeFile2(
3112
+ join2(options.destination, "src/feedbax.ts"),
3113
+ `import { defineFeedbax } from "@feedbax/config";
3114
+
3115
+ export default defineFeedbax(${JSON.stringify(config, null, 2)});
3116
+ `
3117
+ );
3118
+ await writeFile2(
3119
+ join2(options.destination, ".dev.vars"),
3120
+ Object.entries(secrets).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
3121
+ { mode: 384 }
3122
+ );
3123
+ return { apiKey, pairingSecret, configuration: config };
3124
+ }
3125
+
2944
3126
  // src/index.ts
2945
3127
  function defaultProcessingQueueName(workerName) {
2946
- return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${createHash2("sha256").update(workerName).digest("hex").slice(0, 8)}-updates`;
3128
+ return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${createHash3("sha256").update(workerName).digest("hex").slice(0, 8)}-updates`;
2947
3129
  }
2948
3130
  async function provisionGuidedInstallation({
2949
3131
  destination,
@@ -3051,9 +3233,9 @@ async function provisionGuidedInstallation({
3051
3233
  preview
3052
3234
  });
3053
3235
  await copyPackagedTemplate(destination);
3054
- const wranglerPath = join2(destination, "wrangler.jsonc");
3055
- const wrangler = JSON.parse(await readFile2(wranglerPath, "utf8"));
3056
- await writeFile2(
3236
+ const wranglerPath = join3(destination, "wrangler.jsonc");
3237
+ const wrangler = JSON.parse(await readFile3(wranglerPath, "utf8"));
3238
+ await writeFile3(
3057
3239
  wranglerPath,
3058
3240
  JSON.stringify(
3059
3241
  {
@@ -3108,14 +3290,14 @@ async function provisionGuidedInstallation({
3108
3290
  2
3109
3291
  ) + "\n"
3110
3292
  );
3111
- const apiKey = randomBytes(32).toString("base64url");
3112
- const signingSecret = randomBytes(32).toString("base64url");
3293
+ const apiKey = randomBytes2(32).toString("base64url");
3294
+ const signingSecret = randomBytes2(32).toString("base64url");
3113
3295
  const values = {
3114
3296
  NOTION_TOKEN: notionToken,
3115
3297
  NOTION_FEEDBACK_DATABASE_ID: feedback.databaseId,
3116
3298
  NOTION_FEEDBACK_DATA_SOURCE_ID: feedback.dataSourceId,
3117
3299
  ...prefixProperties("NOTION_FEEDBACK", feedback.propertyIds),
3118
- FEEDBAX_API_KEY_HASH: createHash2("sha256").update(apiKey).digest("base64url"),
3300
+ FEEDBAX_API_KEY_HASH: createHash3("sha256").update(apiKey).digest("base64url"),
3119
3301
  PARTICIPATION_SIGNING_SECRET: signingSecret,
3120
3302
  ...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
3121
3303
  ...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
@@ -3130,8 +3312,8 @@ async function provisionGuidedInstallation({
3130
3312
  ...prefixProperties("NOTION_CHANGELOG", changelog.propertyIds)
3131
3313
  } : {}
3132
3314
  };
3133
- await writeFile2(
3134
- join2(destination, ".dev.vars"),
3315
+ await writeFile3(
3316
+ join3(destination, ".dev.vars"),
3135
3317
  Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
3136
3318
  { mode: 384 }
3137
3319
  );
@@ -3176,16 +3358,16 @@ async function provisionGuidedInstallation({
3176
3358
  ...turnstile.siteKey ? { siteKey: turnstile.siteKey } : {}
3177
3359
  }
3178
3360
  });
3179
- await writeFile2(
3180
- join2(destination, "src/feedbax.ts"),
3361
+ await writeFile3(
3362
+ join3(destination, "src/feedbax.ts"),
3181
3363
  `import { defineFeedbax } from "@feedbax/config";
3182
3364
 
3183
3365
  export default defineFeedbax(${JSON.stringify(configuration, null, 2)});
3184
3366
  `
3185
3367
  );
3186
3368
  const probePage = commentProbePageId ?? (feedbackSelection.kind === "create" ? feedbackSelection.parentPageId : feedback.databaseId);
3187
- await writeFile2(
3188
- join2(destination, ".dev.vars"),
3369
+ await writeFile3(
3370
+ join3(destination, ".dev.vars"),
3189
3371
  `NOTION_COMMENT_CAPABILITY_PROBE_PAGE_ID=${JSON.stringify(probePage)}
3190
3372
  `,
3191
3373
  { flag: "a", mode: 384 }
@@ -3398,6 +3580,7 @@ export {
3398
3580
  doctorChangelogStorage,
3399
3581
  doctorInstallation,
3400
3582
  featureChoices,
3583
+ provisionGitHubInstallation,
3401
3584
  provisionGuidedInstallation,
3402
3585
  renderChangelogConfiguration,
3403
3586
  renderFeatureConfiguration,