@insforge/cli 0.1.85 → 0.1.87

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 CHANGED
@@ -654,6 +654,58 @@ Running `npx @insforge/cli link` creates a `.insforge/` directory in your projec
654
654
 
655
655
  Add `.insforge/` to your `.gitignore` — it contains your project API key.
656
656
 
657
+ ### Declarative project config — `insforge.toml`
658
+
659
+ Use `config export`, `config plan`, and `config apply` to manage project settings through `insforge.toml`:
660
+
661
+ ```bash
662
+ npx @insforge/cli config export --out insforge.toml
663
+ npx @insforge/cli config plan --file insforge.toml
664
+ npx @insforge/cli config apply --file insforge.toml --auto-approve
665
+ ```
666
+
667
+ Supported TOML sections include auth redirects and verification flags, password policy, SMTP, storage upload size, realtime/schedule retention, and cloud deployment subdomain:
668
+
669
+ ```toml
670
+ [auth]
671
+ allowed_redirect_urls = ["https://app.example.com"]
672
+ require_email_verification = true
673
+ verify_email_method = "link"
674
+ reset_password_method = "code"
675
+ disable_signup = false
676
+
677
+ [auth.password]
678
+ min_length = 12
679
+ require_number = true
680
+ require_lowercase = true
681
+ require_uppercase = false
682
+ require_special_char = true
683
+
684
+ [auth.smtp]
685
+ enabled = true
686
+ host = "smtp.example.com"
687
+ port = 587
688
+ username = "mailer@example.com"
689
+ password = "env(SMTP_PASSWORD)"
690
+ sender_email = "noreply@example.com"
691
+ sender_name = "Example"
692
+ min_interval_seconds = 60
693
+
694
+ [storage]
695
+ max_file_size_mb = 100
696
+
697
+ [realtime]
698
+ retention_days = 7
699
+
700
+ [schedules]
701
+ retention_days = 0 # 0 disables retention cleanup
702
+
703
+ [deployments]
704
+ subdomain = "my-app"
705
+ ```
706
+
707
+ `config apply` uses backend admin APIs and skips sections that the connected backend version does not expose. It does not manage external provider resources such as OAuth apps, storage bucket lifecycle, realtime channels, deployment environment variables, functions, or secrets.
708
+
657
709
  Global configuration is stored in `~/.insforge/`:
658
710
 
659
711
  ```
package/dist/index.js CHANGED
@@ -263,10 +263,11 @@ import * as clack4 from "@clack/prompts";
263
263
 
264
264
  // src/lib/errors.ts
265
265
  var CLIError = class extends Error {
266
- constructor(message, exitCode = 1, code) {
266
+ constructor(message, exitCode = 1, code, statusCode) {
267
267
  super(message);
268
268
  this.exitCode = exitCode;
269
269
  this.code = code;
270
+ this.statusCode = statusCode;
270
271
  this.name = "CLIError";
271
272
  }
272
273
  };
@@ -1172,7 +1173,7 @@ import * as clack5 from "@clack/prompts";
1172
1173
 
1173
1174
  // src/lib/analytics.ts
1174
1175
  import { PostHog } from "posthog-node";
1175
- var POSTHOG_API_KEY = "";
1176
+ var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
1176
1177
  var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
1177
1178
  var client = null;
1178
1179
  function getClient() {
@@ -2066,7 +2067,7 @@ ${err.nextActions}`;
2066
2067
  if (res.status === 404 && isRouteLevel404 && path6.startsWith("/api/ai")) {
2067
2068
  message = "AI Model Gateway setup is not available on this backend.\nUpgrade your InsForge project to a version with Model Gateway support, or keep using the legacy @insforge/sdk AI modules for projects that still rely on the older AI API surface.";
2068
2069
  }
2069
- throw new CLIError(message);
2070
+ throw new CLIError(message, 1, err.error, res.status);
2070
2071
  }
2071
2072
  return res;
2072
2073
  }
@@ -3033,6 +3034,9 @@ Browse available templates: https://insforge.dev/templates`
3033
3034
  json
3034
3035
  );
3035
3036
  if (downloaded) {
3037
+ captureEvent(orgId, "marketplace_template_downloaded", {
3038
+ template: opts.marketplace
3039
+ });
3036
3040
  void reportMarketplaceDownload(opts.marketplace);
3037
3041
  }
3038
3042
  } else if (githubTemplates.includes(template)) {
@@ -7185,7 +7189,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
7185
7189
  const s = !json ? clack16.spinner() : null;
7186
7190
  s?.start("Collecting diagnostic data...");
7187
7191
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
7188
- const cliVersion = "0.1.85";
7192
+ const cliVersion = "0.1.87";
7189
7193
  s?.stop("Data collected");
7190
7194
  if (!json) {
7191
7195
  console.log(`
@@ -8947,9 +8951,47 @@ function validateConfig(input) {
8947
8951
  out.project_id = obj.project_id;
8948
8952
  }
8949
8953
  if ("auth" in obj) out.auth = validateAuth(obj.auth);
8954
+ if ("storage" in obj) out.storage = validateStorage(obj.storage);
8955
+ if ("realtime" in obj) out.realtime = validateRetentionSection("realtime", obj.realtime);
8956
+ if ("schedules" in obj) out.schedules = validateRetentionSection("schedules", obj.schedules);
8950
8957
  if ("deployments" in obj) out.deployments = validateDeployments(obj.deployments);
8951
8958
  return out;
8952
8959
  }
8960
+ function validateStorage(input) {
8961
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
8962
+ throw new ConfigValidationError("storage", "must be an object");
8963
+ }
8964
+ const obj = input;
8965
+ const out = {};
8966
+ if ("max_file_size_mb" in obj) {
8967
+ if (typeof obj.max_file_size_mb !== "number" || !Number.isInteger(obj.max_file_size_mb) || obj.max_file_size_mb < 1 || obj.max_file_size_mb > 200) {
8968
+ throw new ConfigValidationError(
8969
+ "storage.max_file_size_mb",
8970
+ "must be an integer between 1 and 200"
8971
+ );
8972
+ }
8973
+ out.max_file_size_mb = obj.max_file_size_mb;
8974
+ }
8975
+ return out;
8976
+ }
8977
+ function validateRetentionSection(path6, input) {
8978
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
8979
+ throw new ConfigValidationError(path6, "must be an object");
8980
+ }
8981
+ const obj = input;
8982
+ const out = {};
8983
+ if ("retention_days" in obj) {
8984
+ const v = obj.retention_days;
8985
+ if (v !== null && (typeof v !== "number" || !Number.isInteger(v) || v < 0)) {
8986
+ throw new ConfigValidationError(
8987
+ `${path6}.retention_days`,
8988
+ "must be a non-negative integer or null"
8989
+ );
8990
+ }
8991
+ out.retention_days = v;
8992
+ }
8993
+ return out;
8994
+ }
8953
8995
  function validateDeployments(input) {
8954
8996
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
8955
8997
  throw new ConfigValidationError("deployments", "must be an object");
@@ -9005,6 +9047,12 @@ function validateAuth(input) {
9005
9047
  obj.reset_password_method
9006
9048
  );
9007
9049
  }
9050
+ if ("disable_signup" in obj) {
9051
+ if (typeof obj.disable_signup !== "boolean") {
9052
+ throw new ConfigValidationError("auth.disable_signup", "must be a boolean");
9053
+ }
9054
+ out.disable_signup = obj.disable_signup;
9055
+ }
9008
9056
  if ("password" in obj) out.password = validatePassword(obj.password);
9009
9057
  if ("smtp" in obj) out.smtp = validateSmtp(obj.smtp);
9010
9058
  return out;
@@ -9140,6 +9188,21 @@ function stringifyConfigToml(config) {
9140
9188
  lines.push("");
9141
9189
  }
9142
9190
  }
9191
+ if (config.storage) {
9192
+ lines.push("[storage]");
9193
+ renderStorageFields(config.storage, lines);
9194
+ lines.push("");
9195
+ }
9196
+ if (config.realtime) {
9197
+ lines.push("[realtime]");
9198
+ renderRetentionFields(config.realtime, lines);
9199
+ lines.push("");
9200
+ }
9201
+ if (config.schedules) {
9202
+ lines.push("[schedules]");
9203
+ renderRetentionFields(config.schedules, lines);
9204
+ lines.push("");
9205
+ }
9143
9206
  if (config.deployments) {
9144
9207
  if (typeof config.deployments.subdomain === "string" && config.deployments.subdomain !== "") {
9145
9208
  lines.push("[deployments]");
@@ -9163,6 +9226,9 @@ function renderAuthFlatFields(auth, lines) {
9163
9226
  if (auth.reset_password_method !== void 0) {
9164
9227
  lines.push(`reset_password_method = ${JSON.stringify(auth.reset_password_method)}`);
9165
9228
  }
9229
+ if (auth.disable_signup !== void 0) {
9230
+ lines.push(`disable_signup = ${auth.disable_signup}`);
9231
+ }
9166
9232
  }
9167
9233
  function renderPasswordFields(pw, lines) {
9168
9234
  if (pw.min_length !== void 0) lines.push(`min_length = ${pw.min_length}`);
@@ -9199,9 +9265,19 @@ function renderSmtpFields(smtp, lines) {
9199
9265
  lines.push(`min_interval_seconds = ${smtp.min_interval_seconds}`);
9200
9266
  }
9201
9267
  }
9268
+ function renderStorageFields(storage, lines) {
9269
+ if (storage.max_file_size_mb !== void 0) {
9270
+ lines.push(`max_file_size_mb = ${storage.max_file_size_mb}`);
9271
+ }
9272
+ }
9273
+ function renderRetentionFields(config, lines) {
9274
+ if ("retention_days" in config) {
9275
+ lines.push(`retention_days = ${config.retention_days ?? 0}`);
9276
+ }
9277
+ }
9202
9278
 
9203
9279
  // src/lib/config-metadata.ts
9204
- function liveFromMetadata(raw) {
9280
+ function liveFromMetadata(raw, endpointConfig = {}) {
9205
9281
  const live = { auth: {} };
9206
9282
  const a = isPlainObject(raw.auth) ? raw.auth : void 0;
9207
9283
  if (a && "allowedRedirectUrls" in a) {
@@ -9216,6 +9292,9 @@ function liveFromMetadata(raw) {
9216
9292
  if (a && "resetPasswordMethod" in a && (a.resetPasswordMethod === "code" || a.resetPasswordMethod === "link")) {
9217
9293
  live.auth.reset_password_method = a.resetPasswordMethod;
9218
9294
  }
9295
+ if (a && "disableSignup" in a) {
9296
+ live.auth.disable_signup = a.disableSignup ?? false;
9297
+ }
9219
9298
  if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
9220
9299
  live.auth.password = {
9221
9300
  min_length: a.passwordMinLength ?? 8,
@@ -9244,6 +9323,18 @@ function liveFromMetadata(raw) {
9244
9323
  subdomain: typeof d.customSlug === "string" && d.customSlug ? d.customSlug : null
9245
9324
  };
9246
9325
  }
9326
+ const maxFileSizeMb = asNumber(endpointConfig.storageConfig?.maxFileSizeMb);
9327
+ if (maxFileSizeMb !== void 0) {
9328
+ live.storage = { max_file_size_mb: maxFileSizeMb };
9329
+ }
9330
+ const realtimeRetention = asRetentionDays(endpointConfig.realtimeConfig?.retentionDays);
9331
+ if (realtimeRetention !== void 0) {
9332
+ live.realtime = { retention_days: realtimeRetention };
9333
+ }
9334
+ const schedulesRetention = asRetentionDays(endpointConfig.schedulesConfig?.retentionDays);
9335
+ if (schedulesRetention !== void 0) {
9336
+ live.schedules = { retention_days: schedulesRetention };
9337
+ }
9247
9338
  return live;
9248
9339
  }
9249
9340
  function isPlainObject(v) {
@@ -9252,7 +9343,14 @@ function isPlainObject(v) {
9252
9343
  function asStringArray(v) {
9253
9344
  return Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null;
9254
9345
  }
9255
- function configFromMetadata(raw) {
9346
+ function asNumber(v) {
9347
+ return typeof v === "number" && Number.isInteger(v) ? v : void 0;
9348
+ }
9349
+ function asRetentionDays(v) {
9350
+ if (v === null) return null;
9351
+ return asNumber(v);
9352
+ }
9353
+ function configFromMetadata(raw, endpointConfig = {}) {
9256
9354
  const config = {};
9257
9355
  const skipped = [];
9258
9356
  const a = isPlainObject(raw.auth) ? raw.auth : void 0;
@@ -9280,6 +9378,12 @@ function configFromMetadata(raw) {
9280
9378
  } else {
9281
9379
  skipped.push("auth.reset_password_method");
9282
9380
  }
9381
+ if (a && "disableSignup" in a) {
9382
+ config.auth = config.auth ?? {};
9383
+ config.auth.disable_signup = a.disableSignup ?? false;
9384
+ } else {
9385
+ skipped.push("auth.disable_signup");
9386
+ }
9283
9387
  if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
9284
9388
  config.auth = config.auth ?? {};
9285
9389
  config.auth.password = {};
@@ -9323,6 +9427,24 @@ function configFromMetadata(raw) {
9323
9427
  } else {
9324
9428
  skipped.push("deployments.subdomain");
9325
9429
  }
9430
+ const maxFileSizeMb = asNumber(endpointConfig.storageConfig?.maxFileSizeMb);
9431
+ if (maxFileSizeMb !== void 0) {
9432
+ config.storage = { max_file_size_mb: maxFileSizeMb };
9433
+ } else {
9434
+ skipped.push("storage.max_file_size_mb");
9435
+ }
9436
+ const realtimeRetention = asRetentionDays(endpointConfig.realtimeConfig?.retentionDays);
9437
+ if (realtimeRetention !== void 0) {
9438
+ config.realtime = { retention_days: realtimeRetention };
9439
+ } else {
9440
+ skipped.push("realtime.retention_days");
9441
+ }
9442
+ const schedulesRetention = asRetentionDays(endpointConfig.schedulesConfig?.retentionDays);
9443
+ if (schedulesRetention !== void 0) {
9444
+ config.schedules = { retention_days: schedulesRetention };
9445
+ } else {
9446
+ skipped.push("schedules.retention_days");
9447
+ }
9326
9448
  return { config, skipped };
9327
9449
  }
9328
9450
 
@@ -9360,7 +9482,16 @@ function registerConfigExportCommand(cfg) {
9360
9482
  }
9361
9483
  const res = await ossFetch("/api/metadata");
9362
9484
  const raw = await res.json();
9363
- const { config, skipped } = configFromMetadata(raw);
9485
+ const [storageConfig, realtimeConfig, schedulesConfig] = await Promise.all([
9486
+ fetchOptionalConfig("/api/storage/config"),
9487
+ fetchOptionalConfig("/api/realtime/config"),
9488
+ fetchOptionalConfig("/api/schedules/config")
9489
+ ]);
9490
+ const { config, skipped } = configFromMetadata(raw, {
9491
+ storageConfig,
9492
+ realtimeConfig,
9493
+ schedulesConfig
9494
+ });
9364
9495
  const toml = stringifyConfigToml(config);
9365
9496
  writeFileSync8(target, toml, "utf8");
9366
9497
  if (json) {
@@ -9396,6 +9527,18 @@ function registerConfigExportCommand(cfg) {
9396
9527
  }
9397
9528
  });
9398
9529
  }
9530
+ async function fetchOptionalConfig(path6) {
9531
+ try {
9532
+ const res = await ossFetch(path6);
9533
+ return await res.json();
9534
+ } catch (err) {
9535
+ if (isMissingOptionalEndpoint(err)) return void 0;
9536
+ throw err;
9537
+ }
9538
+ }
9539
+ function isMissingOptionalEndpoint(err) {
9540
+ return err instanceof CLIError && err.statusCode === 404 && (err.code === void 0 || err.code === "NOT_FOUND");
9541
+ }
9399
9542
 
9400
9543
  // src/commands/config/plan.ts
9401
9544
  import { readFileSync as readFileSync9 } from "fs";
@@ -9459,6 +9602,19 @@ function diffConfig({ live, file }) {
9459
9602
  });
9460
9603
  }
9461
9604
  }
9605
+ if (fileAuth && "disable_signup" in fileAuth) {
9606
+ const fromV = liveAuth.disable_signup ?? false;
9607
+ const toV = fileAuth.disable_signup ?? false;
9608
+ if (fromV !== toV) {
9609
+ changes.push({
9610
+ section: "auth",
9611
+ op: "modify",
9612
+ key: "disable_signup",
9613
+ from: fromV,
9614
+ to: toV
9615
+ });
9616
+ }
9617
+ }
9462
9618
  if (fileAuth?.password) {
9463
9619
  diffPassword(liveAuth.password, fileAuth.password, changes);
9464
9620
  }
@@ -9466,6 +9622,15 @@ function diffConfig({ live, file }) {
9466
9622
  const smtpChange = diffSmtp(liveAuth.smtp, fileAuth.smtp);
9467
9623
  if (smtpChange) changes.push(smtpChange);
9468
9624
  }
9625
+ if (file.storage !== void 0) {
9626
+ diffStorage(live.storage, file.storage, changes);
9627
+ }
9628
+ if (file.realtime !== void 0) {
9629
+ diffRetention("realtime", live.realtime, file.realtime, changes);
9630
+ }
9631
+ if (file.schedules !== void 0) {
9632
+ diffRetention("schedules", live.schedules, file.schedules, changes);
9633
+ }
9469
9634
  const fileDeployments = file.deployments;
9470
9635
  const liveDeployments = live.deployments ?? {};
9471
9636
  if (fileDeployments && "subdomain" in fileDeployments) {
@@ -9514,6 +9679,36 @@ function diffPassword(live, file, changes) {
9514
9679
  }
9515
9680
  }
9516
9681
  }
9682
+ function diffStorage(live, file, changes) {
9683
+ if (file.max_file_size_mb === void 0) return;
9684
+ const fromV = live?.max_file_size_mb ?? EMPTY_STORAGE_CONFIG.max_file_size_mb;
9685
+ if (fromV !== file.max_file_size_mb) {
9686
+ changes.push({
9687
+ section: "storage",
9688
+ op: "modify",
9689
+ key: "max_file_size_mb",
9690
+ from: fromV,
9691
+ to: file.max_file_size_mb
9692
+ });
9693
+ }
9694
+ }
9695
+ function diffRetention(section, live, file, changes) {
9696
+ if (!("retention_days" in file)) return;
9697
+ const fromV = normalizeRetentionDays(live?.retention_days);
9698
+ const toV = normalizeRetentionDays(file.retention_days);
9699
+ if (fromV !== toV) {
9700
+ changes.push({
9701
+ section,
9702
+ op: "modify",
9703
+ key: "retention_days",
9704
+ from: fromV,
9705
+ to: toV
9706
+ });
9707
+ }
9708
+ }
9709
+ function normalizeRetentionDays(value) {
9710
+ return value === void 0 || value === null || value === 0 ? null : value;
9711
+ }
9517
9712
  function diffSmtp(live, fileSmtp) {
9518
9713
  const livedView = renderLiveSmtp(live);
9519
9714
  const tomlView = renderFileSmtp(fileSmtp);
@@ -9572,6 +9767,9 @@ var EMPTY_SMTP_VIEW = {
9572
9767
  sender_name: "",
9573
9768
  min_interval_seconds: 60
9574
9769
  };
9770
+ var EMPTY_STORAGE_CONFIG = {
9771
+ max_file_size_mb: 50
9772
+ };
9575
9773
  var EMPTY_PASSWORD_POLICY = {
9576
9774
  min_length: 8,
9577
9775
  require_number: false,
@@ -9652,7 +9850,7 @@ function formatChange(c) {
9652
9850
  }
9653
9851
 
9654
9852
  // src/lib/config-capabilities.ts
9655
- function metadataSupports(raw, change) {
9853
+ function metadataSupports(raw, change, endpointConfig = {}) {
9656
9854
  if (change.section === "auth" && change.key === "allowed_redirect_urls") {
9657
9855
  return hasAuthKey(raw, "allowedRedirectUrls");
9658
9856
  }
@@ -9665,12 +9863,24 @@ function metadataSupports(raw, change) {
9665
9863
  if (change.section === "auth" && change.key === "reset_password_method") {
9666
9864
  return hasAuthKey(raw, "resetPasswordMethod");
9667
9865
  }
9866
+ if (change.section === "auth" && change.key === "disable_signup") {
9867
+ return hasAuthKey(raw, "disableSignup");
9868
+ }
9668
9869
  if (change.section === "auth.password") {
9669
9870
  return hasAuthKey(raw, AUTH_PASSWORD_WIRE_KEY[change.key]);
9670
9871
  }
9671
9872
  if (change.section === "auth.smtp") {
9672
9873
  return hasAuthKey(raw, "smtpConfig");
9673
9874
  }
9875
+ if (change.section === "storage" && change.key === "max_file_size_mb") {
9876
+ return hasConfigKey(endpointConfig.storageConfig, "maxFileSizeMb");
9877
+ }
9878
+ if (change.section === "realtime" && change.key === "retention_days") {
9879
+ return hasConfigKey(endpointConfig.realtimeConfig, "retentionDays");
9880
+ }
9881
+ if (change.section === "schedules" && change.key === "retention_days") {
9882
+ return hasConfigKey(endpointConfig.schedulesConfig, "retentionDays");
9883
+ }
9674
9884
  if (change.section === "deployments" && change.key === "subdomain") {
9675
9885
  return raw?.deployments !== void 0 && raw.deployments !== null && typeof raw.deployments === "object";
9676
9886
  }
@@ -9682,6 +9892,9 @@ function hasAuthKey(raw, key) {
9682
9892
  const auth = raw?.auth;
9683
9893
  return auth !== void 0 && auth !== null && typeof auth === "object" && key in auth;
9684
9894
  }
9895
+ function hasConfigKey(slice, key) {
9896
+ return slice !== void 0 && slice !== null && typeof slice === "object" && key in slice;
9897
+ }
9685
9898
  var AUTH_PASSWORD_WIRE_KEY = {
9686
9899
  min_length: "passwordMinLength",
9687
9900
  require_number: "requireNumber",
@@ -9710,9 +9923,25 @@ function registerConfigPlanCommand(cfg) {
9710
9923
  const file = parseConfigToml(tomlSource);
9711
9924
  const res = await ossFetch("/api/metadata");
9712
9925
  const raw = await res.json();
9713
- const live = liveFromMetadata(raw);
9926
+ const endpointConfig = {};
9927
+ if (file.storage !== void 0) {
9928
+ endpointConfig.storageConfig = await fetchOptionalConfig2(
9929
+ "/api/storage/config"
9930
+ );
9931
+ }
9932
+ if (file.realtime !== void 0) {
9933
+ endpointConfig.realtimeConfig = await fetchOptionalConfig2(
9934
+ "/api/realtime/config"
9935
+ );
9936
+ }
9937
+ if (file.schedules !== void 0) {
9938
+ endpointConfig.schedulesConfig = await fetchOptionalConfig2(
9939
+ "/api/schedules/config"
9940
+ );
9941
+ }
9942
+ const live = liveFromMetadata(raw, endpointConfig);
9714
9943
  const result = diffConfig({ live, file });
9715
- const skipped = result.changes.filter((c) => !metadataSupports(raw, c)).map((c) => changePath(c));
9944
+ const skipped = result.changes.filter((c) => !metadataSupports(raw, c, endpointConfig)).map((c) => changePath(c));
9716
9945
  if (json) {
9717
9946
  console.log(JSON.stringify({ ...result, skipped }, null, 2));
9718
9947
  } else {
@@ -9748,6 +9977,18 @@ function registerConfigPlanCommand(cfg) {
9748
9977
  }
9749
9978
  });
9750
9979
  }
9980
+ async function fetchOptionalConfig2(path6) {
9981
+ try {
9982
+ const res = await ossFetch(path6);
9983
+ return await res.json();
9984
+ } catch (err) {
9985
+ if (isMissingOptionalEndpoint2(err)) return void 0;
9986
+ throw err;
9987
+ }
9988
+ }
9989
+ function isMissingOptionalEndpoint2(err) {
9990
+ return err instanceof CLIError && err.statusCode === 404 && (err.code === void 0 || err.code === "NOT_FOUND");
9991
+ }
9751
9992
 
9752
9993
  // src/commands/config/apply.ts
9753
9994
  import { readFileSync as readFileSync10 } from "fs";
@@ -9766,7 +10007,23 @@ function registerConfigApplyCommand(cfg) {
9766
10007
  const file = parseConfigToml(tomlSource);
9767
10008
  const res = await ossFetch("/api/metadata");
9768
10009
  const raw = await res.json();
9769
- const live = liveFromMetadata(raw);
10010
+ const endpointConfig = {};
10011
+ if (file.storage !== void 0) {
10012
+ endpointConfig.storageConfig = await fetchOptionalConfig3(
10013
+ "/api/storage/config"
10014
+ );
10015
+ }
10016
+ if (file.realtime !== void 0) {
10017
+ endpointConfig.realtimeConfig = await fetchOptionalConfig3(
10018
+ "/api/realtime/config"
10019
+ );
10020
+ }
10021
+ if (file.schedules !== void 0) {
10022
+ endpointConfig.schedulesConfig = await fetchOptionalConfig3(
10023
+ "/api/schedules/config"
10024
+ );
10025
+ }
10026
+ const live = liveFromMetadata(raw, endpointConfig);
9770
10027
  const result = diffConfig({ live, file });
9771
10028
  const approved = opts.autoApprove || yes;
9772
10029
  const sectionsChanged = Array.from(
@@ -9819,7 +10076,7 @@ function registerConfigApplyCommand(cfg) {
9819
10076
  const skipped = [];
9820
10077
  for (const change of result.changes) {
9821
10078
  const path6 = changePath(change);
9822
- if (!metadataSupports(raw, change)) {
10079
+ if (!metadataSupports(raw, change, endpointConfig)) {
9823
10080
  skipped.push({
9824
10081
  key: path6,
9825
10082
  reason: `your backend doesn't expose ${path6} \u2014 upgrade the project to apply this section`
@@ -9870,6 +10127,18 @@ function registerConfigApplyCommand(cfg) {
9870
10127
  }
9871
10128
  });
9872
10129
  }
10130
+ async function fetchOptionalConfig3(path6) {
10131
+ try {
10132
+ const res = await ossFetch(path6);
10133
+ return await res.json();
10134
+ } catch (err) {
10135
+ if (isMissingOptionalEndpoint3(err)) return void 0;
10136
+ throw err;
10137
+ }
10138
+ }
10139
+ function isMissingOptionalEndpoint3(err) {
10140
+ return err instanceof CLIError && err.statusCode === 404 && (err.code === void 0 || err.code === "NOT_FOUND");
10141
+ }
9873
10142
  async function applyChange(change) {
9874
10143
  if (change.section === "auth" && change.key === "allowed_redirect_urls") {
9875
10144
  await ossFetch("/api/auth/config", {
@@ -9899,6 +10168,13 @@ async function applyChange(change) {
9899
10168
  });
9900
10169
  return;
9901
10170
  }
10171
+ if (change.section === "auth" && change.key === "disable_signup") {
10172
+ await ossFetch("/api/auth/config", {
10173
+ method: "PUT",
10174
+ body: JSON.stringify({ disableSignup: change.to })
10175
+ });
10176
+ return;
10177
+ }
9902
10178
  if (change.section === "auth.password") {
9903
10179
  const wireKey = authPasswordWireKey(change.key);
9904
10180
  await ossFetch("/api/auth/config", {
@@ -9931,6 +10207,27 @@ async function applyChange(change) {
9931
10207
  });
9932
10208
  return;
9933
10209
  }
10210
+ if (change.section === "storage" && change.key === "max_file_size_mb") {
10211
+ await ossFetch("/api/storage/config", {
10212
+ method: "PUT",
10213
+ body: JSON.stringify({ maxFileSizeMb: change.to })
10214
+ });
10215
+ return;
10216
+ }
10217
+ if (change.section === "realtime" && change.key === "retention_days") {
10218
+ await ossFetch("/api/realtime/config", {
10219
+ method: "PATCH",
10220
+ body: JSON.stringify({ retentionDays: change.to })
10221
+ });
10222
+ return;
10223
+ }
10224
+ if (change.section === "schedules" && change.key === "retention_days") {
10225
+ await ossFetch("/api/schedules/config", {
10226
+ method: "PATCH",
10227
+ body: JSON.stringify({ retentionDays: change.to })
10228
+ });
10229
+ return;
10230
+ }
9934
10231
  if (change.section === "deployments" && change.key === "subdomain") {
9935
10232
  await ossFetch("/api/deployments/slug", {
9936
10233
  method: "PUT",