@crvouga/mockingbird-service-formbricks 3.6.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Changelog — @crvouga/mockingbird-service-formbricks
2
2
 
3
- ## 3.6.0 (2026-10-17)
3
+ ## 3.8.0 (2026-10-21)
4
+
5
+ ### Features
6
+
7
+ - raise the serve and fleet body limit to the S3 maximum ([86b9e2b](https://github.com/crvouga/mockingbird/commit/86b9e2b80bcaaaef57f52edbac50b27da0b42f33))
8
+
9
+ ## 3.7.0 (2026-10-18)
10
+
11
+ ### Features
12
+
13
+ - resolve the agent-reported issue queue in one batch ([25c52b6](https://github.com/crvouga/mockingbird/commit/25c52b668a53ddb80b968419601a20abf33f956b))
14
+
15
+ ## 3.6.0 (2026-10-16)
4
16
 
5
17
  ### Features
6
18
 
package/README.md CHANGED
@@ -36,6 +36,25 @@ npx mockingbird-formbricks serve --port 8813 \
36
36
  --webhook-secret "whsec_…"
37
37
  ```
38
38
 
39
+ Boot it with your own settings and surveys, without a wrapper process or a `PUT` after boot:
40
+
41
+ ```bash
42
+ npx mockingbird-formbricks serve \
43
+ --settings '{"workspaces": ["cworkspace000000000000002"], "legacyEnvironmentIds": {"cenvironment0000000000002": "cworkspace000000000000002"}}' \
44
+ --surveys ./surveys.json
45
+ ```
46
+
47
+ `--settings <json|file>` (or `MOCKINGBIRD_SETTINGS`, or a fleet entry's `settings`) takes the
48
+ fields of `GET|PUT /__admin/settings`, checked with their types (a misspelled key is an error). They
49
+ are every namespace's defaults, a reset returns to them, and a field you give replaces the
50
+ built-in value whole (give `legacyEnvironmentIds` and the default environment alias is gone).
51
+ `--workspace-id` adds to the booted `workspaces`. `--surveys <file>` (a list, or `{surveys: [...]}`)
52
+ adds your surveys after the corpus ones for every namespace, and they survive a reset. A survey
53
+ whose id the corpus (or an earlier row) already has is skipped, a survey with no `workspaceId` is served by
54
+ every configured workspace, and `type`, `status` and a legacy `environmentId` are handled as
55
+ `PUT /__admin/surveys` handles them. `--surveys-mode replace` uses the file as the whole corpus.
56
+ In a fleet, set `options: {"surveys": "surveys.json"}`.
57
+
39
58
  ```ts
40
59
  import { WORKSPACE_ID } from "@crvouga/mockingbird-service-formbricks"
41
60
  import { createServer } from "@crvouga/mockingbird-service-formbricks/server"
@@ -135,7 +154,7 @@ string), `management_unauthorized`, `connection_drop`, `duplicate` (the next web
135
154
  | Export | Kind | Description |
136
155
  | --- | --- | --- |
137
156
  | `FormbricksAPI` | class | The in-process emulator: `fetch(request)`, `reset()`, `responses()`, `render(record)`, `wireSurvey(survey)`, `pipeline(event, record, survey)`, `state`. Options: `sqlite`, `now`, `namespace`, `surveys`, `settings`, `onWebhook`. |
138
- | `createRuntime` | function | The emulator with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {url, secret?, events?, retryDelaysMs?, fetch?}`, `surveys`, `settings`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
157
+ | `createRuntime` | function | The emulator with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {url, secret?, events?, retryDelaysMs?, fetch?}`, `surveys` (replaces the corpus; `serve --surveys` appends), `settings`, `clock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
139
158
  | `FORMBRICKS_PRESETS` | object | Every named fault preset. |
140
159
  | `FORMBRICKS_NAMESPACE` | string | The service name, `"formbricks"`. |
141
160
  | `WEBHOOK_PATH` | string | A conventional receiver path for examples, `/webhooks/formbricks`. |
@@ -1,20 +1,38 @@
1
1
  import {
2
+ CORPUS_SURVEYS,
2
3
  DEFAULT_SETTINGS,
3
4
  createClock,
4
5
  createRuntime,
5
6
  normalizeBootControls,
6
7
  normalizeControls,
8
+ normalizeIdBase,
7
9
  ownedTransport,
8
10
  parseDuration,
9
11
  parseFaultHeader,
10
12
  resolveAdminPrefix,
11
13
  restoreClock
12
- } from "./chunk-THESW6F4.js";
14
+ } from "./chunk-XJB56QGS.js";
15
+
16
+ // src/server.ts
17
+ import { readFileSync } from "node:fs";
13
18
 
14
19
  // ../../adapters/node/dist/cli.js
15
20
  import { readFile as readFile5 } from "node:fs/promises";
16
21
  import { parseArgs } from "node:util";
17
22
 
23
+ // ../../adapters/node/dist/boot-settings.js
24
+ var checkSettings = (target, settings, label) => {
25
+ if (!target.parseSettings)
26
+ throw new Error(`${label}: ${target.name} takes no boot settings`);
27
+ try {
28
+ return target.parseSettings(settings);
29
+ } catch (error) {
30
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
31
+ cause: error
32
+ });
33
+ }
34
+ };
35
+
18
36
  // ../../adapters/node/dist/fleet.js
19
37
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
20
38
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
@@ -2664,6 +2682,9 @@ var fleet_config_schema_default = {
2664
2682
  adminControls: {
2665
2683
  $ref: "#/$defs/controls"
2666
2684
  },
2685
+ idBase: {
2686
+ $ref: "#/$defs/idBase"
2687
+ },
2667
2688
  namespace: {
2668
2689
  type: "string",
2669
2690
  pattern: "^[A-Za-z0-9_.-]{1,64}$"
@@ -2753,6 +2774,12 @@ var fleet_config_schema_default = {
2753
2774
  adminControls: {
2754
2775
  $ref: "#/$defs/controls"
2755
2776
  },
2777
+ idBase: {
2778
+ $ref: "#/$defs/idBase"
2779
+ },
2780
+ settings: {
2781
+ type: "object"
2782
+ },
2756
2783
  fixtures: {
2757
2784
  type: "object"
2758
2785
  },
@@ -2799,6 +2826,18 @@ var fleet_config_schema_default = {
2799
2826
  }
2800
2827
  }
2801
2828
  },
2829
+ idBase: {
2830
+ anyOf: [
2831
+ {
2832
+ type: "integer",
2833
+ minimum: 0
2834
+ },
2835
+ {
2836
+ type: "string",
2837
+ pattern: "^(boot|[0-9]+)$"
2838
+ }
2839
+ ]
2840
+ },
2802
2841
  controls: {
2803
2842
  type: "object",
2804
2843
  additionalProperties: false,
@@ -2948,6 +2987,8 @@ async function preflightFleet(config, options) {
2948
2987
  throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
2949
2988
  resolveAdminPrefix(config.adminPrefix);
2950
2989
  normalizeBootControls(config.adminControls);
2990
+ if (config.idBase !== void 0)
2991
+ normalizeIdBase(config.idBase, "idBase");
2951
2992
  const targets = /* @__PURE__ */ new Map();
2952
2993
  const ports = /* @__PURE__ */ new Set();
2953
2994
  for (const [name, entry] of Object.entries(config.services)) {
@@ -2979,6 +3020,13 @@ async function preflightFleet(config, options) {
2979
3020
  throw new Error("invalid protocol namespace selector");
2980
3021
  }
2981
3022
  }
3023
+ if (entry.idBase !== void 0)
3024
+ normalizeIdBase(entry.idBase, "idBase");
3025
+ if (entry.settings !== void 0) {
3026
+ if (protocol !== "http" || !("create" in target))
3027
+ throw new Error("settings: only HTTP services take boot settings");
3028
+ checkSettings(target, entry.settings, "settings");
3029
+ }
2982
3030
  const port = entry.port ?? target.defaultPort;
2983
3031
  if (port !== 0) {
2984
3032
  const key = `${entry.host ?? "127.0.0.1"}:${port}`;
@@ -3014,7 +3062,7 @@ async function preflightFleet(config, options) {
3014
3062
  break;
3015
3063
  }
3016
3064
  }
3017
- targets.set(name, { ...target, packageVersion: packageVersion ?? "3.6.0" });
3065
+ targets.set(name, { ...target, packageVersion: packageVersion ?? "3.8.0" });
3018
3066
  } catch (error) {
3019
3067
  throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3020
3068
  }
@@ -3240,7 +3288,11 @@ var serve = async (api, options = {}) => {
3240
3288
  // ../../adapters/node/dist/listen.js
3241
3289
  var listen = async (api, options = {}) => {
3242
3290
  const host = options.host ?? "127.0.0.1";
3243
- const server = await serve(api, { port: options.port ?? 0, host });
3291
+ const server = await serve(api, {
3292
+ port: options.port ?? 0,
3293
+ host,
3294
+ ...options.maxBodyBytes !== void 0 ? { maxBodyBytes: options.maxBodyBytes } : {}
3295
+ });
3244
3296
  const address = server.address();
3245
3297
  const port = typeof address === "object" && address !== null ? address.port : options.port ?? 0;
3246
3298
  const shown = host.includes(":") ? `[${host}]` : host;
@@ -3334,6 +3386,7 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3334
3386
  options.onLog?.(entry2);
3335
3387
  },
3336
3388
  ...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
3389
+ ...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
3337
3390
  ...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
3338
3391
  ...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
3339
3392
  });
@@ -3352,6 +3405,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3352
3405
  ].some((method) => typeof created[method] !== "function"))
3353
3406
  throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
3354
3407
  const runtime = created;
3408
+ if (entry.idBase !== void 0)
3409
+ runtime.setIdBase?.(entry.idBase);
3355
3410
  const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
3356
3411
  if (options.outbound && runtime.webhooks)
3357
3412
  runtime.webhooks.setTransportPolicy(ownedTransport(options.outbound));
@@ -3436,7 +3491,8 @@ var httpChild = async (target, entry, options, admission, generation, privateAcc
3436
3491
  try {
3437
3492
  server = await listen(api, {
3438
3493
  port: entry.port ?? target.defaultPort,
3439
- host: entry.host ?? "127.0.0.1"
3494
+ host: entry.host ?? "127.0.0.1",
3495
+ ...target.maxBodyBytes !== void 0 ? { maxBodyBytes: target.maxBodyBytes } : {}
3440
3496
  });
3441
3497
  target.listening?.(server);
3442
3498
  } catch (error) {
@@ -3950,6 +4006,7 @@ async function startFleet(config, options) {
3950
4006
  ...entry,
3951
4007
  adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
3952
4008
  ...adminKey !== void 0 ? { adminKey } : {},
4009
+ ...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
3953
4010
  // Fleet-wide controls, with the service's own keys replacing them.
3954
4011
  ...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
3955
4012
  }, options, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
@@ -4634,6 +4691,16 @@ var COMMON_SERVE_OPTIONS = {
4634
4691
  value: "<seed>",
4635
4692
  description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
4636
4693
  },
4694
+ "id-base": {
4695
+ type: "string",
4696
+ value: "<n|boot>",
4697
+ description: "Start generated ids at this base so a restart does not reissue the last boot's ids; boot derives one from the clock (env MOCKINGBIRD_ID_BASE; default 0, the same ids every boot)"
4698
+ },
4699
+ settings: {
4700
+ type: "string",
4701
+ value: "<json|file>",
4702
+ description: "Service settings to boot with, as JSON or a JSON file; every namespace starts from them (env MOCKINGBIRD_SETTINGS; only for services that declare settings)"
4703
+ },
4637
4704
  "admin-controls": {
4638
4705
  type: "string",
4639
4706
  value: "<json|file>",
@@ -4728,19 +4795,40 @@ var readJsonFile = async (path, label) => {
4728
4795
  }
4729
4796
  };
4730
4797
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4798
+ var readJsonOption = async (source, label) => {
4799
+ const trimmed = source.trim();
4800
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4801
+ try {
4802
+ return JSON.parse(trimmed);
4803
+ } catch {
4804
+ throw new Error(`${label}: not valid JSON`);
4805
+ }
4806
+ }
4807
+ return readJsonFile(source, label);
4808
+ };
4809
+ var resolveServeIdBase = (values, env = process.env) => {
4810
+ const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
4811
+ if (source === void 0 || source.trim() === "")
4812
+ return void 0;
4813
+ normalizeIdBase(source.trim(), "--id-base");
4814
+ return source.trim();
4815
+ };
4816
+ var resolveServeSettings = async (target, values, env = process.env) => {
4817
+ const flag = asString(values.settings);
4818
+ const source = flag ?? env.MOCKINGBIRD_SETTINGS;
4819
+ if (source === void 0 || source.trim() === "")
4820
+ return void 0;
4821
+ const label = flag !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
4822
+ const parsed = await readJsonOption(source, label);
4823
+ if (!isObject2(parsed))
4824
+ throw new Error(`${label}: expected a JSON object`);
4825
+ return checkSettings(target, parsed, label);
4826
+ };
4731
4827
  var resolveServeControls = async (values, env = process.env, base) => {
4732
4828
  const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
4733
4829
  let document = isObject2(base) ? { ...base } : {};
4734
4830
  if (source !== void 0 && source.trim() !== "") {
4735
- let parsed;
4736
- if (source.trim().startsWith("{")) {
4737
- try {
4738
- parsed = JSON.parse(source);
4739
- } catch {
4740
- throw new Error("--admin-controls: not valid JSON");
4741
- }
4742
- } else
4743
- parsed = await readJsonFile(source, "--admin-controls");
4831
+ const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
4744
4832
  if (!isObject2(parsed))
4745
4833
  throw new Error("--admin-controls: expected a JSON object");
4746
4834
  document = { ...document, ...parsed };
@@ -4790,9 +4878,16 @@ var start = async (target, values, config) => {
4790
4878
  adminKey: config.adminKey,
4791
4879
  seed: config.seed,
4792
4880
  onLog: formatLog(config.log),
4881
+ ...config.settings !== void 0 ? { settings: config.settings } : {},
4793
4882
  ...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
4794
4883
  });
4795
- const listening = await listen(runtime, { port: config.port, host: config.host });
4884
+ if (config.idBase !== void 0)
4885
+ runtime.setIdBase?.(config.idBase);
4886
+ const listening = await listen(runtime, {
4887
+ port: config.port,
4888
+ host: config.host,
4889
+ ...target.maxBodyBytes !== void 0 ? { maxBodyBytes: target.maxBodyBytes } : {}
4890
+ });
4796
4891
  target.listening?.(listening);
4797
4892
  console.log(`${target.name} emulator listening on ${listening.url}`);
4798
4893
  console.log(`${target.name} health: GET ${listening.url}${adminPrefix}/health`);
@@ -4837,6 +4932,11 @@ var serveCommand = (target) => ({
4837
4932
  const adminControls = await resolveServeControls(values, process.env, config.adminControls);
4838
4933
  if (adminControls !== void 0)
4839
4934
  config.adminControls = adminControls;
4935
+ if (values.settings !== void 0)
4936
+ throw new Error("--settings serves a single service; give each service its own `settings` in the config");
4937
+ const idBase = config.idBase ?? resolveServeIdBase(values);
4938
+ if (idBase !== void 0)
4939
+ config.idBase = idBase;
4840
4940
  const fleet = await startFleet(config, {
4841
4941
  load: (name) => loadTarget(name, target),
4842
4942
  onLog: formatLog(config.log ?? log),
@@ -4866,8 +4966,12 @@ var serveCommand = (target) => ({
4866
4966
  let listening;
4867
4967
  try {
4868
4968
  const adminControls = await resolveServeControls(values);
4969
+ const idBase = resolveServeIdBase(values);
4970
+ const settings = await resolveServeSettings(target, values);
4869
4971
  listening = await start(target, values, {
4870
4972
  ...adminControls !== void 0 ? { adminControls } : {},
4973
+ ...idBase !== void 0 ? { idBase } : {},
4974
+ ...settings !== void 0 ? { settings } : {},
4871
4975
  adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
4872
4976
  port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
4873
4977
  host: asString(values.host) ?? "127.0.0.1",
@@ -4883,6 +4987,84 @@ var serveCommand = (target) => ({
4883
4987
  }
4884
4988
  });
4885
4989
 
4990
+ // src/boot.ts
4991
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4992
+ var strings = (value, where) => {
4993
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
4994
+ throw new Error(`${where} must be an array of strings`);
4995
+ return value;
4996
+ };
4997
+ var KEYS = [
4998
+ "workspaces",
4999
+ "legacyEnvironmentIds",
5000
+ "apiKeys",
5001
+ "webhookId",
5002
+ "contactsEnabled",
5003
+ "validation"
5004
+ ];
5005
+ var parseBootSettings = (input) => {
5006
+ for (const key of Object.keys(input))
5007
+ if (!KEYS.includes(key)) throw new Error(`unknown setting ${JSON.stringify(key)}`);
5008
+ const out = {};
5009
+ if (input.workspaces !== void 0) out.workspaces = strings(input.workspaces, "workspaces");
5010
+ if (input.legacyEnvironmentIds !== void 0) {
5011
+ const aliases = input.legacyEnvironmentIds;
5012
+ if (!isRecord(aliases) || Object.values(aliases).some((id) => typeof id !== "string"))
5013
+ throw new Error("legacyEnvironmentIds must be an object of workspace ids by environment id");
5014
+ out.legacyEnvironmentIds = aliases;
5015
+ }
5016
+ if (input.apiKeys !== void 0) out.apiKeys = strings(input.apiKeys, "apiKeys");
5017
+ if (input.webhookId !== void 0) {
5018
+ if (typeof input.webhookId !== "string") throw new Error("webhookId must be a string");
5019
+ out.webhookId = input.webhookId;
5020
+ }
5021
+ if (input.contactsEnabled !== void 0) {
5022
+ if (typeof input.contactsEnabled !== "boolean")
5023
+ throw new Error("contactsEnabled must be a boolean");
5024
+ out.contactsEnabled = input.contactsEnabled;
5025
+ }
5026
+ if (input.validation !== void 0) {
5027
+ if (input.validation !== "present-only" && input.validation !== "finished-validates-all")
5028
+ throw new Error('validation must be "present-only" or "finished-validates-all"');
5029
+ out.validation = input.validation;
5030
+ }
5031
+ return out;
5032
+ };
5033
+ var parseSurveysDocument = (input, legacyEnvironmentIds = {}) => {
5034
+ const list = Array.isArray(input) ? input : isRecord(input) ? input.surveys : void 0;
5035
+ if (!Array.isArray(list)) throw new Error("expected [survey] or {surveys: [survey]}");
5036
+ return list.map((entry, index) => {
5037
+ const where = `surveys[${index}]`;
5038
+ if (!isRecord(entry)) throw new Error(`${where} must be an object`);
5039
+ if (typeof entry.id !== "string" || entry.id === "")
5040
+ throw new Error(`${where}.id must be a non-empty string`);
5041
+ if (typeof entry.name !== "string") throw new Error(`${where}.name must be a string`);
5042
+ const { environmentId, ...rest } = entry;
5043
+ for (const key of ["workspaceId", "environmentId", "type", "status"])
5044
+ if (entry[key] !== void 0 && entry[key] !== null && typeof entry[key] !== "string")
5045
+ throw new Error(`${where}.${key} must be a string`);
5046
+ const owner = entry.workspaceId ?? environmentId;
5047
+ return {
5048
+ ...rest,
5049
+ id: entry.id,
5050
+ name: entry.name,
5051
+ type: entry.type ?? "app",
5052
+ status: entry.status ?? "inProgress",
5053
+ workspaceId: owner ? legacyEnvironmentIds[owner] ?? owner : null
5054
+ };
5055
+ });
5056
+ };
5057
+ var appendSurveys = (base, extra) => {
5058
+ const seen = new Set(base.map((survey) => survey.id));
5059
+ const out = [...base];
5060
+ for (const survey of extra) {
5061
+ if (seen.has(survey.id)) continue;
5062
+ seen.add(survey.id);
5063
+ out.push(survey);
5064
+ }
5065
+ return out;
5066
+ };
5067
+
4886
5068
  // src/server.ts
4887
5069
  var DEFAULT_PORT = 8813;
4888
5070
  var createServer2 = async (options = {}) => {
@@ -4895,9 +5077,24 @@ var createServer2 = async (options = {}) => {
4895
5077
  return { ...listening, runtime };
4896
5078
  };
4897
5079
  var text = (value) => typeof value === "string" ? value : void 0;
5080
+ var loadSurveysFile = (path, mode = "append", legacyEnvironmentIds = DEFAULT_SETTINGS.legacyEnvironmentIds) => {
5081
+ let raw;
5082
+ try {
5083
+ raw = readFileSync(path, "utf8");
5084
+ } catch {
5085
+ throw new Error(`formbricks surveys not found: ${path}`);
5086
+ }
5087
+ try {
5088
+ const surveys = parseSurveysDocument(JSON.parse(raw), legacyEnvironmentIds);
5089
+ return appendSurveys(mode === "replace" ? [] : CORPUS_SURVEYS, surveys);
5090
+ } catch (error) {
5091
+ throw new Error(`formbricks surveys ${path}: ${error.message}`);
5092
+ }
5093
+ };
4898
5094
  var serveTarget = {
4899
5095
  name: "formbricks",
4900
5096
  defaultPort: DEFAULT_PORT,
5097
+ parseSettings: (settings) => parseBootSettings(settings),
4901
5098
  options: {
4902
5099
  "webhook-url": {
4903
5100
  type: "string",
@@ -4918,6 +5115,16 @@ var serveTarget = {
4918
5115
  type: "string",
4919
5116
  value: "<id>",
4920
5117
  description: "An extra workspace id that serves the survey corpus"
5118
+ },
5119
+ surveys: {
5120
+ type: "string",
5121
+ value: "<file.json>",
5122
+ description: "Surveys every namespace starts with, as a list or {surveys: [...]}, added after the corpus surveys (a repeated id is skipped)"
5123
+ },
5124
+ "surveys-mode": {
5125
+ type: "string",
5126
+ value: "<append|replace>",
5127
+ description: "With --surveys: add to the corpus (default) or use the file as the whole corpus"
4921
5128
  }
4922
5129
  },
4923
5130
  create: (values, common) => {
@@ -4925,10 +5132,28 @@ var serveTarget = {
4925
5132
  const token = text(values["webhook-token"]);
4926
5133
  const secret = text(values["webhook-secret"]);
4927
5134
  const workspace = text(values["workspace-id"]);
5135
+ const surveysPath = text(values.surveys);
5136
+ const mode = text(values["surveys-mode"]);
5137
+ if (mode !== void 0 && mode !== "append" && mode !== "replace")
5138
+ throw new Error('--surveys-mode must be "append" or "replace"');
5139
+ if (mode !== void 0 && surveysPath === void 0)
5140
+ throw new Error("--surveys-mode needs --surveys");
5141
+ const boot = common.settings;
5142
+ const settings = {
5143
+ ...boot,
5144
+ ...workspace ? { workspaces: [workspace, ...boot?.workspaces ?? DEFAULT_SETTINGS.workspaces] } : {}
5145
+ };
4928
5146
  const target = url && token ? `${url}${url.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}` : url;
4929
5147
  return createRuntime({
4930
5148
  ...target ? { webhooks: { url: target, ...secret ? { secret } : {} } } : {},
4931
- ...workspace ? { settings: { workspaces: [workspace, ...DEFAULT_SETTINGS.workspaces] } } : {},
5149
+ ...Object.keys(settings).length > 0 ? { settings } : {},
5150
+ ...surveysPath ? {
5151
+ surveys: loadSurveysFile(
5152
+ surveysPath,
5153
+ mode,
5154
+ settings.legacyEnvironmentIds ?? DEFAULT_SETTINGS.legacyEnvironmentIds
5155
+ )
5156
+ } : {},
4932
5157
  ...common.adminPrefix !== void 0 ? { adminPrefix: common.adminPrefix } : {},
4933
5158
  ...common.adminKey !== void 0 ? { adminKey: common.adminKey } : {},
4934
5159
  ...common.adminControls !== void 0 ? { adminControls: common.adminControls } : {},
@@ -4947,6 +5172,7 @@ export {
4947
5172
  serveCommand,
4948
5173
  DEFAULT_PORT,
4949
5174
  createServer2 as createServer,
5175
+ loadSurveysFile,
4950
5176
  serveTarget
4951
5177
  };
4952
- //# sourceMappingURL=chunk-VZ55MOOA.js.map
5178
+ //# sourceMappingURL=chunk-5ZAU7WOP.js.map