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.
- package/README.md +17 -5
- package/dist/cli.js +502 -209
- package/dist/index.js +251 -68
- package/package.json +7 -7
- package/template/.feedbax-template.sha256 +1 -1
- package/template/.gitignore.template +1 -0
- package/template/.npmignore.template +1 -0
- package/template/.nvmrc +1 -0
- package/template/AGENTS.md +1 -1
- package/template/CONNECTED.md +16 -2
- package/template/README.md +52 -13
- package/template/feedbax-vite.ts +79 -0
- package/template/migrations/0005_github.sql +4 -0
- package/template/migrations/0006_integration.sql +3 -0
- package/template/migrations/0007_ownership.sql +2 -0
- package/template/migrations/0008_integration_recovery.sql +5 -0
- package/template/migrations/0009_observed_history.sql +3 -0
- package/template/package.json +34 -33
- package/template/src/changelog-runtime.ts +2 -1
- package/template/src/cloudflare-workers.d.ts +3 -0
- package/template/src/comment-creation.test.tsx +53 -6
- package/template/src/components/connected-identity-provider.tsx +44 -17
- package/template/src/components/follow-post-button.tsx +2 -13
- package/template/src/components/post-history.tsx +101 -0
- package/template/src/components/profile-menu.tsx +7 -0
- package/template/src/configured-feedback.test.ts +82 -0
- package/template/src/configured-feedback.ts +61 -0
- package/template/src/connected-identity.test.ts +7 -3
- package/template/src/connected-identity.ts +6 -1
- package/template/src/connected-runtime.test.ts +5 -2
- package/template/src/feedback-cache-invalidation.ts +2 -0
- package/template/src/feedback-runtime.ts +61 -18
- package/template/src/feedbax/changelog/index.test.ts +35 -0
- package/template/src/feedbax/changelog/index.ts +5 -1
- package/template/src/feedbax/changelog/notion-changelog.test.ts +1 -1
- package/template/src/feedbax/changelog/notion-changelog.ts +1 -1
- package/template/src/feedbax/config/index.test.ts +45 -1
- package/template/src/feedbax/config/index.ts +112 -9
- package/template/src/feedbax/feedback/comment-storage.ts +38 -0
- package/template/src/feedbax/feedback/connected-feedback.ts +41 -12
- package/template/src/feedbax/feedback/connected.ts +15 -5
- package/template/src/feedbax/feedback/feedback-storage.ts +1 -0
- package/template/src/feedbax/feedback/github-comments.ts +390 -0
- package/template/src/feedbax/feedback/github-feedback.test.ts +1138 -0
- package/template/src/feedbax/feedback/github-feedback.ts +581 -0
- package/template/src/feedbax/feedback/github-request.ts +120 -0
- package/template/src/feedbax/feedback/github-service-client.ts +56 -0
- package/template/src/feedbax/feedback/image-feedback.ts +23 -10
- package/template/src/feedbax/feedback/images.ts +26 -0
- package/template/src/feedbax/feedback/index.ts +172 -16
- package/template/src/feedbax/feedback/integration-auth.ts +138 -0
- package/template/src/feedbax/feedback/notion-comments.ts +2 -35
- package/template/src/feedbax/feedback/observed-history.test.ts +150 -0
- package/template/src/feedbax/feedback/observed-history.ts +219 -0
- package/template/src/feedbax/feedback/ownership.ts +170 -0
- package/template/src/history-runtime.test.ts +80 -0
- package/template/src/history-runtime.ts +37 -0
- package/template/src/index.css +6 -0
- package/template/src/integration-runtime.test.ts +365 -0
- package/template/src/integration-runtime.ts +289 -0
- package/template/src/my-feedback-page.test.tsx +152 -0
- package/template/src/my-feedback-page.tsx +398 -0
- package/template/src/my-feedback-runtime.test.ts +93 -0
- package/template/src/my-feedback-runtime.ts +154 -0
- package/template/src/participation-runtime.ts +3 -3
- package/template/src/portal-comments.ts +11 -0
- package/template/src/portal-feedback-form.tsx +11 -3
- package/template/src/portal-feedback-submission.ts +9 -0
- package/template/src/portal-server-entry.ts +9 -0
- package/template/src/public-portal-error.tsx +1 -1
- package/template/src/public-post-detail.tsx +332 -256
- package/template/src/public-release-links.test.ts +120 -0
- package/template/src/public-release-links.ts +130 -0
- package/template/src/routeTree.gen.ts +21 -0
- package/template/src/routes/my-feedback.tsx +16 -0
- package/template/src/server.ts +46 -1
- package/template/src/submission-intent.ts +26 -0
- package/template/vite.config.ts +2 -0
package/dist/cli.js
CHANGED
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import * as prompt from "@clack/prompts";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { readFile as
|
|
6
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
7
7
|
import { basename as basename2, resolve as resolve2 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
11
|
-
import { readFile as
|
|
10
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
|
|
11
|
+
import { readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
12
12
|
|
|
13
13
|
// ../config/src/index.ts
|
|
14
14
|
var rootKeys = [
|
|
15
|
+
"history",
|
|
16
|
+
"provider",
|
|
17
|
+
"github",
|
|
15
18
|
"connected",
|
|
16
19
|
"product",
|
|
17
20
|
"theme",
|
|
@@ -55,6 +58,8 @@ var changelogPropertyKeys = [
|
|
|
55
58
|
function defineFeedbax(config) {
|
|
56
59
|
const value = object(config, "Feedbax configuration");
|
|
57
60
|
rejectUnknown(value, rootKeys, "Feedbax configuration");
|
|
61
|
+
if (value.history !== void 0 && typeof value.history !== "boolean")
|
|
62
|
+
throw new Error("history must be true or false.");
|
|
58
63
|
const product = object(value.product, "product");
|
|
59
64
|
rejectUnknown(
|
|
60
65
|
product,
|
|
@@ -131,11 +136,19 @@ function defineFeedbax(config) {
|
|
|
131
136
|
}
|
|
132
137
|
const theme = object(value.theme, "theme");
|
|
133
138
|
rejectUnknown(theme, ["preset"], "theme");
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
if (value.provider !== void 0 && !["notion", "github"].includes(String(value.provider)))
|
|
140
|
+
throw new Error("provider must be notion or github.");
|
|
141
|
+
const githubMode = value.provider === "github";
|
|
142
|
+
if (githubMode && value.notion !== void 0 || !githubMode && value.github !== void 0)
|
|
143
|
+
throw new Error("Select exactly one Content Provider: notion or github.");
|
|
144
|
+
const content = githubMode ? { provider: "github", github: githubConfiguration(value.github) } : {
|
|
145
|
+
...value.provider ? { provider: "notion" } : {},
|
|
146
|
+
notion: storage(
|
|
147
|
+
value.notion,
|
|
148
|
+
feedbackPropertyKeys,
|
|
149
|
+
"notion"
|
|
150
|
+
)
|
|
151
|
+
};
|
|
139
152
|
const features = object(value.features, "features");
|
|
140
153
|
rejectUnknown(features, featureKeys, "features");
|
|
141
154
|
const validatedFeatures = Object.fromEntries(
|
|
@@ -145,6 +158,10 @@ function defineFeedbax(config) {
|
|
|
145
158
|
return [key, features[key]];
|
|
146
159
|
})
|
|
147
160
|
);
|
|
161
|
+
if (githubMode && (validatedFeatures.changelog || value.changelog !== void 0))
|
|
162
|
+
throw new Error(
|
|
163
|
+
"GitHub Installations must disable Changelog (features.changelog: false) and remove Notion Changelog configuration."
|
|
164
|
+
);
|
|
148
165
|
const changelog = value.changelog === void 0 ? void 0 : storage(
|
|
149
166
|
value.changelog,
|
|
150
167
|
changelogPropertyKeys,
|
|
@@ -171,9 +188,10 @@ function defineFeedbax(config) {
|
|
|
171
188
|
throw new Error("Turnstile is enabled but turnstile.siteKey is missing.");
|
|
172
189
|
return {
|
|
173
190
|
product: validatedProduct,
|
|
191
|
+
...value.history !== void 0 ? { history: value.history } : {},
|
|
174
192
|
...connected ? { connected } : {},
|
|
175
193
|
theme: { preset: text(theme.preset, "theme.preset") },
|
|
176
|
-
|
|
194
|
+
...content,
|
|
177
195
|
...changelog ? { changelog } : {},
|
|
178
196
|
features: validatedFeatures,
|
|
179
197
|
submissions: {
|
|
@@ -238,9 +256,44 @@ function publicUrl(value, label, allowRelative) {
|
|
|
238
256
|
`${label} must be ${allowRelative ? "root-relative or " : ""}an HTTPS URL.`
|
|
239
257
|
);
|
|
240
258
|
}
|
|
259
|
+
function githubConfiguration(value) {
|
|
260
|
+
const input = object(value, "github");
|
|
261
|
+
rejectUnknown(
|
|
262
|
+
input,
|
|
263
|
+
["installationId", "serviceUrl", "repository", "botUserId"],
|
|
264
|
+
"github"
|
|
265
|
+
);
|
|
266
|
+
const installationId = text(input.installationId, "github.installationId");
|
|
267
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(installationId))
|
|
268
|
+
throw new Error(
|
|
269
|
+
"github.installationId must be a stable Installation identifier."
|
|
270
|
+
);
|
|
271
|
+
const serviceUrl = publicUrl(input.serviceUrl, "github.serviceUrl", false);
|
|
272
|
+
const url = new URL(serviceUrl);
|
|
273
|
+
if (url.origin !== serviceUrl || url.username || url.password)
|
|
274
|
+
throw new Error(
|
|
275
|
+
"github.serviceUrl must be an HTTPS origin without a path or credentials."
|
|
276
|
+
);
|
|
277
|
+
const repository = object(input.repository, "github.repository");
|
|
278
|
+
rejectUnknown(repository, ["id", "owner", "name"], "github.repository");
|
|
279
|
+
const owner = text(repository.owner, "github.repository.owner");
|
|
280
|
+
const name = text(repository.name, "github.repository.name");
|
|
281
|
+
if (!/^[A-Za-z0-9-]+$/.test(owner) || !/^[A-Za-z0-9_.-]+$/.test(name))
|
|
282
|
+
throw new Error("Use a valid GitHub repository owner and name.");
|
|
283
|
+
return {
|
|
284
|
+
installationId,
|
|
285
|
+
serviceUrl,
|
|
286
|
+
repository: {
|
|
287
|
+
id: positiveInteger(repository.id, "github.repository.id"),
|
|
288
|
+
owner,
|
|
289
|
+
name
|
|
290
|
+
},
|
|
291
|
+
botUserId: positiveInteger(input.botUserId, "github.botUserId")
|
|
292
|
+
};
|
|
293
|
+
}
|
|
241
294
|
|
|
242
295
|
// src/index.ts
|
|
243
|
-
import { basename, join as
|
|
296
|
+
import { basename, join as join3 } from "node:path";
|
|
244
297
|
|
|
245
298
|
// src/template.ts
|
|
246
299
|
import { parse } from "jsonc-parser";
|
|
@@ -690,7 +743,7 @@ function isPropertyMap(value) {
|
|
|
690
743
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
691
744
|
}
|
|
692
745
|
|
|
693
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
746
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/entity.js
|
|
694
747
|
var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
|
|
695
748
|
function is(value, type) {
|
|
696
749
|
if (!value || typeof value !== "object") {
|
|
@@ -716,7 +769,7 @@ function is(value, type) {
|
|
|
716
769
|
return false;
|
|
717
770
|
}
|
|
718
771
|
|
|
719
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
772
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/column.js
|
|
720
773
|
var Column = class {
|
|
721
774
|
constructor(table, config) {
|
|
722
775
|
this.table = table;
|
|
@@ -767,7 +820,7 @@ var Column = class {
|
|
|
767
820
|
}
|
|
768
821
|
};
|
|
769
822
|
|
|
770
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
823
|
+
// ../../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
|
|
771
824
|
var ColumnBuilder = class {
|
|
772
825
|
static [entityKind] = "ColumnBuilder";
|
|
773
826
|
config;
|
|
@@ -870,10 +923,10 @@ var ColumnBuilder = class {
|
|
|
870
923
|
}
|
|
871
924
|
};
|
|
872
925
|
|
|
873
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
926
|
+
// ../../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
|
|
874
927
|
var TableName = /* @__PURE__ */ Symbol.for("drizzle:Name");
|
|
875
928
|
|
|
876
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
929
|
+
// ../../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
|
|
877
930
|
var ForeignKeyBuilder = class {
|
|
878
931
|
static [entityKind] = "PgForeignKeyBuilder";
|
|
879
932
|
/** @internal */
|
|
@@ -930,12 +983,12 @@ var ForeignKey = class {
|
|
|
930
983
|
}
|
|
931
984
|
};
|
|
932
985
|
|
|
933
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
986
|
+
// ../../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
|
|
934
987
|
function iife(fn, ...args) {
|
|
935
988
|
return fn(...args);
|
|
936
989
|
}
|
|
937
990
|
|
|
938
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
991
|
+
// ../../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
|
|
939
992
|
function uniqueKeyName(table, columns) {
|
|
940
993
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
941
994
|
}
|
|
@@ -985,7 +1038,7 @@ var UniqueConstraint = class {
|
|
|
985
1038
|
}
|
|
986
1039
|
};
|
|
987
1040
|
|
|
988
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1041
|
+
// ../../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
|
|
989
1042
|
function parsePgArrayValue(arrayString, startFrom, inQuotes) {
|
|
990
1043
|
for (let i = startFrom; i < arrayString.length; i++) {
|
|
991
1044
|
const char = arrayString[i];
|
|
@@ -1061,7 +1114,7 @@ function makePgArray(array) {
|
|
|
1061
1114
|
}).join(",")}}`;
|
|
1062
1115
|
}
|
|
1063
1116
|
|
|
1064
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1117
|
+
// ../../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
|
|
1065
1118
|
var PgColumnBuilder = class extends ColumnBuilder {
|
|
1066
1119
|
foreignKeyConfigs = [];
|
|
1067
1120
|
static [entityKind] = "PgColumnBuilder";
|
|
@@ -1245,7 +1298,7 @@ var PgArray = class _PgArray extends PgColumn {
|
|
|
1245
1298
|
}
|
|
1246
1299
|
};
|
|
1247
1300
|
|
|
1248
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1301
|
+
// ../../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
|
|
1249
1302
|
var PgEnumObjectColumnBuilder = class extends PgColumnBuilder {
|
|
1250
1303
|
static [entityKind] = "PgEnumObjectColumnBuilder";
|
|
1251
1304
|
constructor(name, enumInstance) {
|
|
@@ -1303,7 +1356,7 @@ var PgEnumColumn = class extends PgColumn {
|
|
|
1303
1356
|
}
|
|
1304
1357
|
};
|
|
1305
1358
|
|
|
1306
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1359
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/subquery.js
|
|
1307
1360
|
var Subquery = class {
|
|
1308
1361
|
static [entityKind] = "Subquery";
|
|
1309
1362
|
constructor(sql2, fields, alias, isWith = false, usedTables = []) {
|
|
@@ -1324,10 +1377,10 @@ var WithSubquery = class extends Subquery {
|
|
|
1324
1377
|
static [entityKind] = "WithSubquery";
|
|
1325
1378
|
};
|
|
1326
1379
|
|
|
1327
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1328
|
-
var version = "0.45.
|
|
1380
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/version.js
|
|
1381
|
+
var version = "0.45.4";
|
|
1329
1382
|
|
|
1330
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1383
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/tracing.js
|
|
1331
1384
|
var otel;
|
|
1332
1385
|
var rawTracer;
|
|
1333
1386
|
var tracer = {
|
|
@@ -1362,10 +1415,10 @@ var tracer = {
|
|
|
1362
1415
|
}
|
|
1363
1416
|
};
|
|
1364
1417
|
|
|
1365
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1418
|
+
// ../../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
|
|
1366
1419
|
var ViewBaseConfig = /* @__PURE__ */ Symbol.for("drizzle:ViewBaseConfig");
|
|
1367
1420
|
|
|
1368
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1421
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/table.js
|
|
1369
1422
|
var Schema = /* @__PURE__ */ Symbol.for("drizzle:Schema");
|
|
1370
1423
|
var Columns = /* @__PURE__ */ Symbol.for("drizzle:Columns");
|
|
1371
1424
|
var ExtraConfigColumns = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigColumns");
|
|
@@ -1421,7 +1474,7 @@ var Table = class {
|
|
|
1421
1474
|
}
|
|
1422
1475
|
};
|
|
1423
1476
|
|
|
1424
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1477
|
+
// ../../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
|
|
1425
1478
|
var FakePrimitiveParam = class {
|
|
1426
1479
|
static [entityKind] = "FakePrimitiveParam";
|
|
1427
1480
|
};
|
|
@@ -1711,7 +1764,7 @@ function sql(strings, ...params) {
|
|
|
1711
1764
|
return new SQL([new StringChunk(str)]);
|
|
1712
1765
|
}
|
|
1713
1766
|
sql2.raw = raw;
|
|
1714
|
-
function
|
|
1767
|
+
function join4(chunks, separator) {
|
|
1715
1768
|
const result = [];
|
|
1716
1769
|
for (const [i, chunk] of chunks.entries()) {
|
|
1717
1770
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1721,7 +1774,7 @@ function sql(strings, ...params) {
|
|
|
1721
1774
|
}
|
|
1722
1775
|
return new SQL(result);
|
|
1723
1776
|
}
|
|
1724
|
-
sql2.join =
|
|
1777
|
+
sql2.join = join4;
|
|
1725
1778
|
function identifier(value) {
|
|
1726
1779
|
return new Name(value);
|
|
1727
1780
|
}
|
|
@@ -1795,7 +1848,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
1795
1848
|
return new SQL([this]);
|
|
1796
1849
|
};
|
|
1797
1850
|
|
|
1798
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1851
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/utils.js
|
|
1799
1852
|
function getColumnNameAndConfig(a, b) {
|
|
1800
1853
|
return {
|
|
1801
1854
|
name: typeof a === "string" && a.length > 0 ? a : "",
|
|
@@ -1804,7 +1857,7 @@ function getColumnNameAndConfig(a, b) {
|
|
|
1804
1857
|
}
|
|
1805
1858
|
var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder();
|
|
1806
1859
|
|
|
1807
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1860
|
+
// ../../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
|
|
1808
1861
|
var ForeignKeyBuilder2 = class {
|
|
1809
1862
|
static [entityKind] = "SQLiteForeignKeyBuilder";
|
|
1810
1863
|
/** @internal */
|
|
@@ -1861,7 +1914,7 @@ var ForeignKey2 = class {
|
|
|
1861
1914
|
}
|
|
1862
1915
|
};
|
|
1863
1916
|
|
|
1864
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1917
|
+
// ../../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
|
|
1865
1918
|
function uniqueKeyName2(table, columns) {
|
|
1866
1919
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
1867
1920
|
}
|
|
@@ -1903,7 +1956,7 @@ var UniqueConstraint2 = class {
|
|
|
1903
1956
|
}
|
|
1904
1957
|
};
|
|
1905
1958
|
|
|
1906
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
1959
|
+
// ../../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
|
|
1907
1960
|
var SQLiteColumnBuilder = class extends ColumnBuilder {
|
|
1908
1961
|
static [entityKind] = "SQLiteColumnBuilder";
|
|
1909
1962
|
foreignKeyConfigs = [];
|
|
@@ -1954,7 +2007,7 @@ var SQLiteColumn = class extends Column {
|
|
|
1954
2007
|
static [entityKind] = "SQLiteColumn";
|
|
1955
2008
|
};
|
|
1956
2009
|
|
|
1957
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2010
|
+
// ../../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
|
|
1958
2011
|
var SQLiteBigIntBuilder = class extends SQLiteColumnBuilder {
|
|
1959
2012
|
static [entityKind] = "SQLiteBigIntBuilder";
|
|
1960
2013
|
constructor(name) {
|
|
@@ -2043,7 +2096,7 @@ function blob(a, b) {
|
|
|
2043
2096
|
return new SQLiteBlobBufferBuilder(name);
|
|
2044
2097
|
}
|
|
2045
2098
|
|
|
2046
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2099
|
+
// ../../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
|
|
2047
2100
|
var SQLiteCustomColumnBuilder = class extends SQLiteColumnBuilder {
|
|
2048
2101
|
static [entityKind] = "SQLiteCustomColumnBuilder";
|
|
2049
2102
|
constructor(name, fieldConfig, customTypeParams) {
|
|
@@ -2091,7 +2144,7 @@ function customType(customTypeParams) {
|
|
|
2091
2144
|
};
|
|
2092
2145
|
}
|
|
2093
2146
|
|
|
2094
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2147
|
+
// ../../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
|
|
2095
2148
|
var SQLiteBaseIntegerBuilder = class extends SQLiteColumnBuilder {
|
|
2096
2149
|
static [entityKind] = "SQLiteBaseIntegerBuilder";
|
|
2097
2150
|
constructor(name, dataType, columnType) {
|
|
@@ -2200,7 +2253,7 @@ function integer(a, b) {
|
|
|
2200
2253
|
return new SQLiteIntegerBuilder(name);
|
|
2201
2254
|
}
|
|
2202
2255
|
|
|
2203
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2256
|
+
// ../../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
|
|
2204
2257
|
var SQLiteNumericBuilder = class extends SQLiteColumnBuilder {
|
|
2205
2258
|
static [entityKind] = "SQLiteNumericBuilder";
|
|
2206
2259
|
constructor(name) {
|
|
@@ -2275,7 +2328,7 @@ function numeric(a, b) {
|
|
|
2275
2328
|
return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
|
|
2276
2329
|
}
|
|
2277
2330
|
|
|
2278
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2331
|
+
// ../../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
|
|
2279
2332
|
var SQLiteRealBuilder = class extends SQLiteColumnBuilder {
|
|
2280
2333
|
static [entityKind] = "SQLiteRealBuilder";
|
|
2281
2334
|
constructor(name) {
|
|
@@ -2296,7 +2349,7 @@ function real(name) {
|
|
|
2296
2349
|
return new SQLiteRealBuilder(name ?? "");
|
|
2297
2350
|
}
|
|
2298
2351
|
|
|
2299
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2352
|
+
// ../../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
|
|
2300
2353
|
var SQLiteTextBuilder = class extends SQLiteColumnBuilder {
|
|
2301
2354
|
static [entityKind] = "SQLiteTextBuilder";
|
|
2302
2355
|
constructor(name, config) {
|
|
@@ -2356,7 +2409,7 @@ function text2(a, b = {}) {
|
|
|
2356
2409
|
return new SQLiteTextBuilder(name, config);
|
|
2357
2410
|
}
|
|
2358
2411
|
|
|
2359
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2412
|
+
// ../../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
|
|
2360
2413
|
function getSQLiteColumnBuilders() {
|
|
2361
2414
|
return {
|
|
2362
2415
|
blob,
|
|
@@ -2368,7 +2421,7 @@ function getSQLiteColumnBuilders() {
|
|
|
2368
2421
|
};
|
|
2369
2422
|
}
|
|
2370
2423
|
|
|
2371
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2424
|
+
// ../../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
|
|
2372
2425
|
var InlineForeignKeys = /* @__PURE__ */ Symbol.for("drizzle:SQLiteInlineForeignKeys");
|
|
2373
2426
|
var SQLiteTable = class extends Table {
|
|
2374
2427
|
static [entityKind] = "SQLiteTable";
|
|
@@ -2407,7 +2460,7 @@ var sqliteTable = (name, columns, extraConfig) => {
|
|
|
2407
2460
|
return sqliteTableBase(name, columns, extraConfig);
|
|
2408
2461
|
};
|
|
2409
2462
|
|
|
2410
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.
|
|
2463
|
+
// ../../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
|
|
2411
2464
|
function primaryKey(...config) {
|
|
2412
2465
|
if (config[0].columns) {
|
|
2413
2466
|
return new PrimaryKeyBuilder(config[0].columns, config[0].name);
|
|
@@ -2555,7 +2608,7 @@ async function validateNotionChangelogDataSource({
|
|
|
2555
2608
|
const properties = recordAt(dataSource, "properties");
|
|
2556
2609
|
const propertiesById = new Map(
|
|
2557
2610
|
Object.values(properties).flatMap((property) => {
|
|
2558
|
-
if (!
|
|
2611
|
+
if (!isRecord2(property) || typeof property.id !== "string") return [];
|
|
2559
2612
|
return [[property.id, property]];
|
|
2560
2613
|
})
|
|
2561
2614
|
);
|
|
@@ -2610,7 +2663,7 @@ async function readObject(response, subject) {
|
|
|
2610
2663
|
`Notion rejected the ${subject} request (${response.status}).`
|
|
2611
2664
|
);
|
|
2612
2665
|
}
|
|
2613
|
-
if (!
|
|
2666
|
+
if (!isRecord2(body))
|
|
2614
2667
|
throw new Error(`Notion returned an invalid ${subject} response.`);
|
|
2615
2668
|
return body;
|
|
2616
2669
|
}
|
|
@@ -2618,7 +2671,7 @@ function recordAt(value, key) {
|
|
|
2618
2671
|
return record(value[key]);
|
|
2619
2672
|
}
|
|
2620
2673
|
function record(value) {
|
|
2621
|
-
if (!
|
|
2674
|
+
if (!isRecord2(value))
|
|
2622
2675
|
throw new Error("Notion returned an invalid Changelog response.");
|
|
2623
2676
|
return value;
|
|
2624
2677
|
}
|
|
@@ -2627,13 +2680,141 @@ function requiredString(value) {
|
|
|
2627
2680
|
throw new Error("Notion returned an invalid Changelog response.");
|
|
2628
2681
|
return value;
|
|
2629
2682
|
}
|
|
2630
|
-
function
|
|
2683
|
+
function isRecord2(value) {
|
|
2631
2684
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2632
2685
|
}
|
|
2633
2686
|
|
|
2687
|
+
// src/github.ts
|
|
2688
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2689
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2690
|
+
import { join as join2 } from "node:path";
|
|
2691
|
+
async function provisionGitHubInstallation(options) {
|
|
2692
|
+
const { connected, github } = options;
|
|
2693
|
+
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())
|
|
2694
|
+
throw new Error(
|
|
2695
|
+
"GitHub setup requires a distinct Worker and an explicitly provisioned FEEDBAX_DB D1 database."
|
|
2696
|
+
);
|
|
2697
|
+
if (connected && (connected.databaseId !== options.database.id || connected.handoffSecret.length < 32 || connected.processing !== "standard" && !connected.queueName))
|
|
2698
|
+
throw new Error(
|
|
2699
|
+
"Connected setup requires the same D1 database, a handoff secret, and an existing processing queue for small processing."
|
|
2700
|
+
);
|
|
2701
|
+
if (connected?.imageBucket && !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(connected.imageBucket))
|
|
2702
|
+
throw new Error("Use a dedicated private R2 bucket for images.");
|
|
2703
|
+
const config = defineFeedbax({
|
|
2704
|
+
provider: "github",
|
|
2705
|
+
github,
|
|
2706
|
+
product: { ...options.product, logo: "/feedbax-mark.svg" },
|
|
2707
|
+
theme: { preset: "lyra-neutral" },
|
|
2708
|
+
features: options.features,
|
|
2709
|
+
...connected ? {
|
|
2710
|
+
connected: {
|
|
2711
|
+
processing: connected.processing ?? "small",
|
|
2712
|
+
issuer: connected.issuer,
|
|
2713
|
+
loginUrl: connected.loginUrl,
|
|
2714
|
+
...connected.imageBucket ? { images: true } : {},
|
|
2715
|
+
...connected.email ? {
|
|
2716
|
+
email: {
|
|
2717
|
+
provider: connected.email.provider,
|
|
2718
|
+
from: connected.email.from,
|
|
2719
|
+
...connected.email.endpoint ? { endpoint: connected.email.endpoint } : {}
|
|
2720
|
+
}
|
|
2721
|
+
} : {}
|
|
2722
|
+
}
|
|
2723
|
+
} : {},
|
|
2724
|
+
submissions: {
|
|
2725
|
+
moderation: "required",
|
|
2726
|
+
maxTitleLength: 160,
|
|
2727
|
+
maxDescriptionLength: 5e3
|
|
2728
|
+
},
|
|
2729
|
+
turnstile: {
|
|
2730
|
+
enabled: Boolean(options.turnstile?.siteKey),
|
|
2731
|
+
...options.turnstile?.siteKey ? { siteKey: options.turnstile.siteKey } : {}
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
if (!await options.preview(
|
|
2735
|
+
"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."
|
|
2736
|
+
))
|
|
2737
|
+
throw new Error("GitHub setup was not approved.");
|
|
2738
|
+
await copyPackagedTemplate(options.destination);
|
|
2739
|
+
const path = join2(options.destination, "wrangler.jsonc");
|
|
2740
|
+
const base = JSON.parse(await readFile2(path, "utf8"));
|
|
2741
|
+
const wrangler = {
|
|
2742
|
+
...base,
|
|
2743
|
+
name: options.workerName,
|
|
2744
|
+
compatibility_flags: [
|
|
2745
|
+
.../* @__PURE__ */ new Set([
|
|
2746
|
+
...base.compatibility_flags ?? [],
|
|
2747
|
+
"global_fetch_strictly_public"
|
|
2748
|
+
])
|
|
2749
|
+
],
|
|
2750
|
+
d1_databases: [
|
|
2751
|
+
{
|
|
2752
|
+
binding: "FEEDBAX_DB",
|
|
2753
|
+
database_id: options.database.id,
|
|
2754
|
+
database_name: options.database.name,
|
|
2755
|
+
migrations_dir: "migrations"
|
|
2756
|
+
}
|
|
2757
|
+
],
|
|
2758
|
+
triggers: { crons: ["*/5 * * * *"] },
|
|
2759
|
+
...connected?.queueName ? {
|
|
2760
|
+
queues: {
|
|
2761
|
+
producers: [
|
|
2762
|
+
{ binding: "FEEDBAX_UPDATES", queue: connected.queueName }
|
|
2763
|
+
],
|
|
2764
|
+
consumers: [
|
|
2765
|
+
{
|
|
2766
|
+
queue: connected.queueName,
|
|
2767
|
+
max_batch_size: 1,
|
|
2768
|
+
max_concurrency: 1,
|
|
2769
|
+
max_retries: 3,
|
|
2770
|
+
retry_delay: 60
|
|
2771
|
+
}
|
|
2772
|
+
]
|
|
2773
|
+
}
|
|
2774
|
+
} : {},
|
|
2775
|
+
...connected?.imageBucket ? {
|
|
2776
|
+
r2_buckets: [
|
|
2777
|
+
{ binding: "FEEDBAX_IMAGES", bucket_name: connected.imageBucket }
|
|
2778
|
+
]
|
|
2779
|
+
} : {},
|
|
2780
|
+
...connected?.email?.provider === "cloudflare" ? {
|
|
2781
|
+
send_email: [
|
|
2782
|
+
{
|
|
2783
|
+
name: "FEEDBAX_EMAIL",
|
|
2784
|
+
allowed_sender_addresses: [connected.email.from]
|
|
2785
|
+
}
|
|
2786
|
+
]
|
|
2787
|
+
} : {}
|
|
2788
|
+
};
|
|
2789
|
+
const apiKey = randomBytes(32).toString("base64url");
|
|
2790
|
+
const pairingSecret = randomBytes(32).toString("base64url");
|
|
2791
|
+
const secrets = {
|
|
2792
|
+
FEEDBAX_API_KEY_HASH: createHash("sha256").update(apiKey).digest("base64url"),
|
|
2793
|
+
PARTICIPATION_SIGNING_SECRET: randomBytes(32).toString("base64url"),
|
|
2794
|
+
GITHUB_PAIRING_SECRET: pairingSecret,
|
|
2795
|
+
...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
|
|
2796
|
+
...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
|
|
2797
|
+
...options.turnstile?.secretKey ? { TURNSTILE_SECRET_KEY: options.turnstile.secretKey } : {}
|
|
2798
|
+
};
|
|
2799
|
+
await writeFile2(path, JSON.stringify(wrangler, null, 2) + "\n");
|
|
2800
|
+
await writeFile2(
|
|
2801
|
+
join2(options.destination, "src/feedbax.ts"),
|
|
2802
|
+
`import { defineFeedbax } from "@feedbax/config";
|
|
2803
|
+
|
|
2804
|
+
export default defineFeedbax(${JSON.stringify(config, null, 2)});
|
|
2805
|
+
`
|
|
2806
|
+
);
|
|
2807
|
+
await writeFile2(
|
|
2808
|
+
join2(options.destination, ".dev.vars"),
|
|
2809
|
+
Object.entries(secrets).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
|
|
2810
|
+
{ mode: 384 }
|
|
2811
|
+
);
|
|
2812
|
+
return { apiKey, pairingSecret, configuration: config };
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2634
2815
|
// src/index.ts
|
|
2635
2816
|
function defaultProcessingQueueName(workerName) {
|
|
2636
|
-
return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${
|
|
2817
|
+
return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${createHash2("sha256").update(workerName).digest("hex").slice(0, 8)}-updates`;
|
|
2637
2818
|
}
|
|
2638
2819
|
async function provisionGuidedInstallation({
|
|
2639
2820
|
destination,
|
|
@@ -2741,9 +2922,9 @@ async function provisionGuidedInstallation({
|
|
|
2741
2922
|
preview
|
|
2742
2923
|
});
|
|
2743
2924
|
await copyPackagedTemplate(destination);
|
|
2744
|
-
const wranglerPath =
|
|
2745
|
-
const wrangler = JSON.parse(await
|
|
2746
|
-
await
|
|
2925
|
+
const wranglerPath = join3(destination, "wrangler.jsonc");
|
|
2926
|
+
const wrangler = JSON.parse(await readFile3(wranglerPath, "utf8"));
|
|
2927
|
+
await writeFile3(
|
|
2747
2928
|
wranglerPath,
|
|
2748
2929
|
JSON.stringify(
|
|
2749
2930
|
{
|
|
@@ -2798,14 +2979,14 @@ async function provisionGuidedInstallation({
|
|
|
2798
2979
|
2
|
|
2799
2980
|
) + "\n"
|
|
2800
2981
|
);
|
|
2801
|
-
const apiKey =
|
|
2802
|
-
const signingSecret =
|
|
2982
|
+
const apiKey = randomBytes2(32).toString("base64url");
|
|
2983
|
+
const signingSecret = randomBytes2(32).toString("base64url");
|
|
2803
2984
|
const values = {
|
|
2804
2985
|
NOTION_TOKEN: notionToken,
|
|
2805
2986
|
NOTION_FEEDBACK_DATABASE_ID: feedback.databaseId,
|
|
2806
2987
|
NOTION_FEEDBACK_DATA_SOURCE_ID: feedback.dataSourceId,
|
|
2807
2988
|
...prefixProperties("NOTION_FEEDBACK", feedback.propertyIds),
|
|
2808
|
-
FEEDBAX_API_KEY_HASH:
|
|
2989
|
+
FEEDBAX_API_KEY_HASH: createHash2("sha256").update(apiKey).digest("base64url"),
|
|
2809
2990
|
PARTICIPATION_SIGNING_SECRET: signingSecret,
|
|
2810
2991
|
...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
|
|
2811
2992
|
...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
|
|
@@ -2820,8 +3001,8 @@ async function provisionGuidedInstallation({
|
|
|
2820
3001
|
...prefixProperties("NOTION_CHANGELOG", changelog.propertyIds)
|
|
2821
3002
|
} : {}
|
|
2822
3003
|
};
|
|
2823
|
-
await
|
|
2824
|
-
|
|
3004
|
+
await writeFile3(
|
|
3005
|
+
join3(destination, ".dev.vars"),
|
|
2825
3006
|
Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
|
|
2826
3007
|
{ mode: 384 }
|
|
2827
3008
|
);
|
|
@@ -2866,16 +3047,16 @@ async function provisionGuidedInstallation({
|
|
|
2866
3047
|
...turnstile.siteKey ? { siteKey: turnstile.siteKey } : {}
|
|
2867
3048
|
}
|
|
2868
3049
|
});
|
|
2869
|
-
await
|
|
2870
|
-
|
|
3050
|
+
await writeFile3(
|
|
3051
|
+
join3(destination, "src/feedbax.ts"),
|
|
2871
3052
|
`import { defineFeedbax } from "@feedbax/config";
|
|
2872
3053
|
|
|
2873
3054
|
export default defineFeedbax(${JSON.stringify(configuration, null, 2)});
|
|
2874
3055
|
`
|
|
2875
3056
|
);
|
|
2876
3057
|
const probePage = commentProbePageId ?? (feedbackSelection.kind === "create" ? feedbackSelection.parentPageId : feedback.databaseId);
|
|
2877
|
-
await
|
|
2878
|
-
|
|
3058
|
+
await writeFile3(
|
|
3059
|
+
join3(destination, ".dev.vars"),
|
|
2879
3060
|
`NOTION_COMMENT_CAPABILITY_PROBE_PAGE_ID=${JSON.stringify(probePage)}
|
|
2880
3061
|
`,
|
|
2881
3062
|
{ flag: "a", mode: 384 }
|
|
@@ -3019,12 +3200,12 @@ try {
|
|
|
3019
3200
|
const args = process.argv.slice(2);
|
|
3020
3201
|
if (args.includes("--help") || args.includes("-h")) {
|
|
3021
3202
|
console.log(
|
|
3022
|
-
"Usage: create-feedbax [directory] [--template-only]\nGuided setup prompts for a destination, product, Notion connection, features, and deployment.\n--template-only copies source without external requests or installing dependencies."
|
|
3203
|
+
"Usage: create-feedbax [directory] [--template-only]\nGuided setup prompts for a destination, product, Notion or GitHub connection, features, and deployment.\n--template-only copies source without external requests or installing dependencies."
|
|
3023
3204
|
);
|
|
3024
3205
|
} else if (args.includes("--version")) {
|
|
3025
3206
|
console.log(
|
|
3026
3207
|
JSON.parse(
|
|
3027
|
-
await
|
|
3208
|
+
await readFile4(new URL("../package.json", import.meta.url), "utf8")
|
|
3028
3209
|
).version
|
|
3029
3210
|
);
|
|
3030
3211
|
} else {
|
|
@@ -3053,163 +3234,181 @@ try {
|
|
|
3053
3234
|
"Cloudflare Worker name (choose a distinct name for this product)",
|
|
3054
3235
|
basename2(destination).toLowerCase().replace(/[^a-z0-9-]/g, "-")
|
|
3055
3236
|
);
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
)
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3237
|
+
const provider = checked(
|
|
3238
|
+
await prompt.select({
|
|
3239
|
+
message: "Content Provider",
|
|
3240
|
+
options: [
|
|
3241
|
+
{ value: "notion", label: "Notion" },
|
|
3242
|
+
{ value: "github", label: "GitHub" }
|
|
3243
|
+
]
|
|
3244
|
+
})
|
|
3245
|
+
);
|
|
3246
|
+
if (provider === "github") {
|
|
3247
|
+
await githubSetup(destination, product, workerName);
|
|
3248
|
+
} else {
|
|
3249
|
+
const connected = await confirm2(
|
|
3250
|
+
"Enable Connected Profile using your application's sign-in, dedicated D1, and a processing queue? Small processing supports Workers Free with Resend/custom email.",
|
|
3251
|
+
false
|
|
3252
|
+
) ? {
|
|
3253
|
+
issuer: await text4("Application identity issuer (HTTPS URL)"),
|
|
3254
|
+
loginUrl: await text4(
|
|
3255
|
+
"Application signed-handoff login URL (HTTPS)"
|
|
3256
|
+
),
|
|
3257
|
+
databaseId: await text4("Dedicated Cloudflare D1 database ID"),
|
|
3258
|
+
databaseName: await text4("Dedicated Cloudflare D1 database name"),
|
|
3259
|
+
queueName: await text4(
|
|
3260
|
+
"Dedicated Cloudflare Queue name (create it with wrangler queues create first)",
|
|
3261
|
+
defaultProcessingQueueName(workerName)
|
|
3262
|
+
),
|
|
3263
|
+
handoffSecret: await secret(
|
|
3264
|
+
"Shared server-only identity handoff secret (at least 32 characters)"
|
|
3265
|
+
)
|
|
3266
|
+
} : void 0;
|
|
3267
|
+
let email;
|
|
3268
|
+
let imageBucket;
|
|
3269
|
+
if (connected) {
|
|
3270
|
+
const provider2 = await text4(
|
|
3271
|
+
"Email sender: cloudflare, resend, custom, or none",
|
|
3272
|
+
"none"
|
|
3273
|
+
);
|
|
3274
|
+
if (provider2 !== "none") {
|
|
3275
|
+
if (provider2 !== "cloudflare" && provider2 !== "resend" && provider2 !== "custom")
|
|
3276
|
+
throw new Error(
|
|
3277
|
+
"Email sender must be cloudflare, resend, custom, or none."
|
|
3278
|
+
);
|
|
3279
|
+
email = {
|
|
3280
|
+
provider: provider2,
|
|
3281
|
+
from: await text4("Verified sender email address"),
|
|
3282
|
+
...provider2 === "custom" ? { endpoint: await text4("Custom HTTPS delivery endpoint") } : {},
|
|
3283
|
+
...provider2 !== "cloudflare" ? { secret: await secret("Server-only email provider secret") } : {}
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
if (await confirm2(
|
|
3287
|
+
"Enable Participant image attachments using an existing private R2 bucket?",
|
|
3288
|
+
false
|
|
3289
|
+
))
|
|
3290
|
+
imageBucket = await text4(
|
|
3291
|
+
"Dedicated private R2 bucket name (create it in Cloudflare first)"
|
|
3085
3292
|
);
|
|
3086
|
-
email = {
|
|
3087
|
-
provider,
|
|
3088
|
-
from: await text4("Verified sender email address"),
|
|
3089
|
-
...provider === "custom" ? { endpoint: await text4("Custom HTTPS delivery endpoint") } : {},
|
|
3090
|
-
...provider !== "cloudflare" ? { secret: await secret("Server-only email provider secret") } : {}
|
|
3091
|
-
};
|
|
3092
3293
|
}
|
|
3093
|
-
|
|
3094
|
-
"
|
|
3294
|
+
const themePreset = await text4(
|
|
3295
|
+
"shadcn preset code, or lyra-neutral for the included preset",
|
|
3296
|
+
"lyra-neutral"
|
|
3297
|
+
);
|
|
3298
|
+
const notionToken = process.env.NOTION_TOKEN?.trim() || await secret("Notion internal integration token");
|
|
3299
|
+
const existing = await confirm2(
|
|
3300
|
+
"Use an existing compatible Feedback Data Source?",
|
|
3095
3301
|
false
|
|
3096
|
-
)
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
)
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
"
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
"
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
"
|
|
3136
|
-
"
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
feedbackSelection,
|
|
3159
|
-
changelogSelection,
|
|
3160
|
-
notionToken,
|
|
3161
|
-
turnstile,
|
|
3162
|
-
preview: (message) => confirm2(message, false)
|
|
3163
|
-
});
|
|
3164
|
-
console.log(
|
|
3165
|
-
`Created ${destination}.
|
|
3302
|
+
);
|
|
3303
|
+
const feedbackSelection = existing ? {
|
|
3304
|
+
kind: "existing",
|
|
3305
|
+
configuration: await existingFeedback()
|
|
3306
|
+
} : {
|
|
3307
|
+
kind: "create",
|
|
3308
|
+
parentPageId: await text4(
|
|
3309
|
+
"Notion parent page ID (share it with the integration)"
|
|
3310
|
+
)
|
|
3311
|
+
};
|
|
3312
|
+
const features = await collectFeatureSelection({
|
|
3313
|
+
confirm: ({ message, initialValue }) => confirm2(message, initialValue)
|
|
3314
|
+
});
|
|
3315
|
+
const commentProbePageId = existing && features.comments ? await text4(
|
|
3316
|
+
"Shared Notion page ID for checking Comment permissions"
|
|
3317
|
+
) : void 0;
|
|
3318
|
+
const changelogSelection = features.changelog && await confirm2(
|
|
3319
|
+
"Use an existing compatible Changelog Data Source?",
|
|
3320
|
+
false
|
|
3321
|
+
) ? {
|
|
3322
|
+
kind: "existing",
|
|
3323
|
+
dataSourceId: await text4("Changelog Data Source ID"),
|
|
3324
|
+
propertyIds: await propertyIds([
|
|
3325
|
+
"title",
|
|
3326
|
+
"slug",
|
|
3327
|
+
"date",
|
|
3328
|
+
"summary",
|
|
3329
|
+
"body",
|
|
3330
|
+
"labels",
|
|
3331
|
+
"image",
|
|
3332
|
+
"published",
|
|
3333
|
+
"createdAt",
|
|
3334
|
+
"updatedAt"
|
|
3335
|
+
])
|
|
3336
|
+
} : void 0;
|
|
3337
|
+
const turnstile = await confirm2(
|
|
3338
|
+
"Enable Cloudflare Turnstile? Recommended for production.",
|
|
3339
|
+
true
|
|
3340
|
+
) ? {
|
|
3341
|
+
siteKey: await text4("Turnstile site key"),
|
|
3342
|
+
secretKey: process.env.TURNSTILE_SECRET_KEY || await secret("Turnstile secret key")
|
|
3343
|
+
} : {};
|
|
3344
|
+
const result = await provisionGuidedInstallation({
|
|
3345
|
+
destination,
|
|
3346
|
+
product,
|
|
3347
|
+
themePreset,
|
|
3348
|
+
workerName,
|
|
3349
|
+
commentProbePageId,
|
|
3350
|
+
connected: connected ? {
|
|
3351
|
+
...connected,
|
|
3352
|
+
...email ? { email } : {},
|
|
3353
|
+
...imageBucket ? { imageBucket } : {}
|
|
3354
|
+
} : void 0,
|
|
3355
|
+
features,
|
|
3356
|
+
feedbackSelection,
|
|
3357
|
+
changelogSelection,
|
|
3358
|
+
notionToken,
|
|
3359
|
+
turnstile,
|
|
3360
|
+
preview: (message) => confirm2(message, false)
|
|
3361
|
+
});
|
|
3362
|
+
console.log(
|
|
3363
|
+
`Created ${destination}.
|
|
3166
3364
|
Trusted API key \u2014 copy it now; plaintext is not stored:
|
|
3167
3365
|
${result.apiKey}`
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
await runIn(destination, "pnpm", [
|
|
3181
|
-
"exec",
|
|
3182
|
-
"prettier",
|
|
3183
|
-
"--write",
|
|
3184
|
-
"src/feedbax.ts"
|
|
3185
|
-
]);
|
|
3186
|
-
if (connected) {
|
|
3187
|
-
if (!await confirm2(
|
|
3188
|
-
`Apply the included Connected schema migration to D1 ${connected.databaseName} (${connected.databaseId}) in your authenticated Cloudflare account?`,
|
|
3189
|
-
false
|
|
3190
|
-
))
|
|
3191
|
-
throw new Error(
|
|
3192
|
-
"Connected source is preserved. Apply the D1 migration described in CONNECTED.md before running doctor or deploying."
|
|
3193
|
-
);
|
|
3366
|
+
);
|
|
3367
|
+
await runIn(destination, "pnpm", ["install"]);
|
|
3368
|
+
if (themePreset !== "lyra-neutral")
|
|
3369
|
+
await runIn(destination, "pnpm", [
|
|
3370
|
+
"dlx",
|
|
3371
|
+
"shadcn@latest",
|
|
3372
|
+
"init",
|
|
3373
|
+
"--preset",
|
|
3374
|
+
themePreset,
|
|
3375
|
+
"--yes",
|
|
3376
|
+
"--force"
|
|
3377
|
+
]);
|
|
3194
3378
|
await runIn(destination, "pnpm", [
|
|
3195
3379
|
"exec",
|
|
3196
|
-
"
|
|
3197
|
-
"
|
|
3198
|
-
"
|
|
3199
|
-
"apply",
|
|
3200
|
-
"FEEDBAX_DB",
|
|
3201
|
-
"--remote"
|
|
3380
|
+
"prettier",
|
|
3381
|
+
"--write",
|
|
3382
|
+
"src/feedbax.ts"
|
|
3202
3383
|
]);
|
|
3384
|
+
if (connected) {
|
|
3385
|
+
if (!await confirm2(
|
|
3386
|
+
`Apply the included Connected schema migration to D1 ${connected.databaseName} (${connected.databaseId}) in your authenticated Cloudflare account?`,
|
|
3387
|
+
false
|
|
3388
|
+
))
|
|
3389
|
+
throw new Error(
|
|
3390
|
+
"Connected source is preserved. Apply the D1 migration described in CONNECTED.md before running doctor or deploying."
|
|
3391
|
+
);
|
|
3392
|
+
await runIn(destination, "pnpm", [
|
|
3393
|
+
"exec",
|
|
3394
|
+
"wrangler",
|
|
3395
|
+
"d1",
|
|
3396
|
+
"migrations",
|
|
3397
|
+
"apply",
|
|
3398
|
+
"FEEDBAX_DB",
|
|
3399
|
+
"--remote"
|
|
3400
|
+
]);
|
|
3401
|
+
}
|
|
3402
|
+
await runIn(destination, "pnpm", ["exec", "feedbax", "doctor"]);
|
|
3403
|
+
if (await confirm2(
|
|
3404
|
+
"Authenticate with Cloudflare and deploy this portal?",
|
|
3405
|
+
false
|
|
3406
|
+
)) {
|
|
3407
|
+
await runIn(destination, "pnpm", ["exec", "wrangler", "login"]);
|
|
3408
|
+
await runIn(destination, "pnpm", ["exec", "feedbax", "deploy"]);
|
|
3409
|
+
}
|
|
3410
|
+
prompt.outro(`Your portal source is ready in ${destination}.`);
|
|
3203
3411
|
}
|
|
3204
|
-
await runIn(destination, "pnpm", ["exec", "feedbax", "doctor"]);
|
|
3205
|
-
if (await confirm2(
|
|
3206
|
-
"Authenticate with Cloudflare and deploy this portal?",
|
|
3207
|
-
false
|
|
3208
|
-
)) {
|
|
3209
|
-
await runIn(destination, "pnpm", ["exec", "wrangler", "login"]);
|
|
3210
|
-
await runIn(destination, "pnpm", ["exec", "feedbax", "deploy"]);
|
|
3211
|
-
}
|
|
3212
|
-
prompt.outro(`Your portal source is ready in ${destination}.`);
|
|
3213
3412
|
}
|
|
3214
3413
|
}
|
|
3215
3414
|
} catch (error) {
|
|
@@ -3287,3 +3486,97 @@ function runIn(cwd, command, args) {
|
|
|
3287
3486
|
);
|
|
3288
3487
|
});
|
|
3289
3488
|
}
|
|
3489
|
+
async function githubSetup(destination, product, workerName) {
|
|
3490
|
+
const serviceUrl = await text4(
|
|
3491
|
+
"GitHub App service HTTPS origin (official or your self-hosted service)"
|
|
3492
|
+
);
|
|
3493
|
+
const infoResponse = await fetch(`${serviceUrl}/v1/info`, {
|
|
3494
|
+
redirect: "manual"
|
|
3495
|
+
});
|
|
3496
|
+
if (!infoResponse.ok)
|
|
3497
|
+
throw new Error(
|
|
3498
|
+
"The App service is unavailable. Configure it before portal setup."
|
|
3499
|
+
);
|
|
3500
|
+
const info = await infoResponse.json();
|
|
3501
|
+
if (!Number.isSafeInteger(info.botUserId))
|
|
3502
|
+
throw new Error("The service did not return a valid App identity.");
|
|
3503
|
+
const database = {
|
|
3504
|
+
id: await text4(
|
|
3505
|
+
"Dedicated Cloudflare D1 database ID (required independently of sign-in)"
|
|
3506
|
+
),
|
|
3507
|
+
name: await text4("Dedicated D1 database name")
|
|
3508
|
+
};
|
|
3509
|
+
const result = await provisionGitHubInstallation({
|
|
3510
|
+
destination,
|
|
3511
|
+
product,
|
|
3512
|
+
workerName,
|
|
3513
|
+
database,
|
|
3514
|
+
github: {
|
|
3515
|
+
serviceUrl,
|
|
3516
|
+
installationId: await text4("Stable Feedbax Installation ID", workerName),
|
|
3517
|
+
repository: {
|
|
3518
|
+
id: Number(
|
|
3519
|
+
await text4(
|
|
3520
|
+
"Feedback Repository numeric ID (gh api repos/OWNER/NAME --jq .id)"
|
|
3521
|
+
)
|
|
3522
|
+
),
|
|
3523
|
+
owner: await text4("Repository owner"),
|
|
3524
|
+
name: await text4("Repository name")
|
|
3525
|
+
},
|
|
3526
|
+
botUserId: info.botUserId
|
|
3527
|
+
},
|
|
3528
|
+
features: {
|
|
3529
|
+
voting: await confirm2("Enable best-effort browser voting?", true),
|
|
3530
|
+
comments: await confirm2("Enable portal Comments?", true),
|
|
3531
|
+
changelog: false
|
|
3532
|
+
},
|
|
3533
|
+
turnstile: await confirm2("Enable Cloudflare Turnstile?", true) ? {
|
|
3534
|
+
siteKey: await text4("Turnstile site key"),
|
|
3535
|
+
secretKey: await secret("Turnstile secret key")
|
|
3536
|
+
} : {},
|
|
3537
|
+
preview: (message) => confirm2(message, false)
|
|
3538
|
+
});
|
|
3539
|
+
console.log(
|
|
3540
|
+
`Created ${destination}. Trusted API key \u2014 copy it now; plaintext is not stored:
|
|
3541
|
+
${result.apiKey}`
|
|
3542
|
+
);
|
|
3543
|
+
await runIn(destination, "pnpm", ["install"]);
|
|
3544
|
+
await runIn(destination, "pnpm", [
|
|
3545
|
+
"exec",
|
|
3546
|
+
"prettier",
|
|
3547
|
+
"--write",
|
|
3548
|
+
"src/feedbax.ts"
|
|
3549
|
+
]);
|
|
3550
|
+
if (await confirm2(
|
|
3551
|
+
`Apply included additive migrations to D1 ${database.name}?`,
|
|
3552
|
+
false
|
|
3553
|
+
))
|
|
3554
|
+
await runIn(destination, "pnpm", [
|
|
3555
|
+
"exec",
|
|
3556
|
+
"wrangler",
|
|
3557
|
+
"d1",
|
|
3558
|
+
"migrations",
|
|
3559
|
+
"apply",
|
|
3560
|
+
"FEEDBAX_DB",
|
|
3561
|
+
"--remote"
|
|
3562
|
+
]);
|
|
3563
|
+
if (await confirm2("Deploy the unpaired portal for GitHub authorization?", false))
|
|
3564
|
+
await runIn(destination, "pnpm", [
|
|
3565
|
+
"exec",
|
|
3566
|
+
"feedbax",
|
|
3567
|
+
"deploy",
|
|
3568
|
+
"--pairing-bootstrap"
|
|
3569
|
+
]);
|
|
3570
|
+
const installationId = await text4(
|
|
3571
|
+
"GitHub App installation ID from the App installation page"
|
|
3572
|
+
);
|
|
3573
|
+
await runIn(destination, "pnpm", [
|
|
3574
|
+
"exec",
|
|
3575
|
+
"feedbax",
|
|
3576
|
+
"pair-github",
|
|
3577
|
+
installationId
|
|
3578
|
+
]);
|
|
3579
|
+
prompt.outro(
|
|
3580
|
+
"Finish the GitHub authorization, then run feedbax doctor. Connected identity can be configured independently using CONNECTED.md."
|
|
3581
|
+
);
|
|
3582
|
+
}
|