@insforge/cli 0.1.86 → 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
  };
@@ -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
  }
@@ -7188,7 +7189,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
7188
7189
  const s = !json ? clack16.spinner() : null;
7189
7190
  s?.start("Collecting diagnostic data...");
7190
7191
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
7191
- const cliVersion = "0.1.86";
7192
+ const cliVersion = "0.1.87";
7192
7193
  s?.stop("Data collected");
7193
7194
  if (!json) {
7194
7195
  console.log(`
@@ -8950,9 +8951,47 @@ function validateConfig(input) {
8950
8951
  out.project_id = obj.project_id;
8951
8952
  }
8952
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);
8953
8957
  if ("deployments" in obj) out.deployments = validateDeployments(obj.deployments);
8954
8958
  return out;
8955
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
+ }
8956
8995
  function validateDeployments(input) {
8957
8996
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
8958
8997
  throw new ConfigValidationError("deployments", "must be an object");
@@ -9008,6 +9047,12 @@ function validateAuth(input) {
9008
9047
  obj.reset_password_method
9009
9048
  );
9010
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
+ }
9011
9056
  if ("password" in obj) out.password = validatePassword(obj.password);
9012
9057
  if ("smtp" in obj) out.smtp = validateSmtp(obj.smtp);
9013
9058
  return out;
@@ -9143,6 +9188,21 @@ function stringifyConfigToml(config) {
9143
9188
  lines.push("");
9144
9189
  }
9145
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
+ }
9146
9206
  if (config.deployments) {
9147
9207
  if (typeof config.deployments.subdomain === "string" && config.deployments.subdomain !== "") {
9148
9208
  lines.push("[deployments]");
@@ -9166,6 +9226,9 @@ function renderAuthFlatFields(auth, lines) {
9166
9226
  if (auth.reset_password_method !== void 0) {
9167
9227
  lines.push(`reset_password_method = ${JSON.stringify(auth.reset_password_method)}`);
9168
9228
  }
9229
+ if (auth.disable_signup !== void 0) {
9230
+ lines.push(`disable_signup = ${auth.disable_signup}`);
9231
+ }
9169
9232
  }
9170
9233
  function renderPasswordFields(pw, lines) {
9171
9234
  if (pw.min_length !== void 0) lines.push(`min_length = ${pw.min_length}`);
@@ -9202,9 +9265,19 @@ function renderSmtpFields(smtp, lines) {
9202
9265
  lines.push(`min_interval_seconds = ${smtp.min_interval_seconds}`);
9203
9266
  }
9204
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
+ }
9205
9278
 
9206
9279
  // src/lib/config-metadata.ts
9207
- function liveFromMetadata(raw) {
9280
+ function liveFromMetadata(raw, endpointConfig = {}) {
9208
9281
  const live = { auth: {} };
9209
9282
  const a = isPlainObject(raw.auth) ? raw.auth : void 0;
9210
9283
  if (a && "allowedRedirectUrls" in a) {
@@ -9219,6 +9292,9 @@ function liveFromMetadata(raw) {
9219
9292
  if (a && "resetPasswordMethod" in a && (a.resetPasswordMethod === "code" || a.resetPasswordMethod === "link")) {
9220
9293
  live.auth.reset_password_method = a.resetPasswordMethod;
9221
9294
  }
9295
+ if (a && "disableSignup" in a) {
9296
+ live.auth.disable_signup = a.disableSignup ?? false;
9297
+ }
9222
9298
  if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
9223
9299
  live.auth.password = {
9224
9300
  min_length: a.passwordMinLength ?? 8,
@@ -9247,6 +9323,18 @@ function liveFromMetadata(raw) {
9247
9323
  subdomain: typeof d.customSlug === "string" && d.customSlug ? d.customSlug : null
9248
9324
  };
9249
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
+ }
9250
9338
  return live;
9251
9339
  }
9252
9340
  function isPlainObject(v) {
@@ -9255,7 +9343,14 @@ function isPlainObject(v) {
9255
9343
  function asStringArray(v) {
9256
9344
  return Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null;
9257
9345
  }
9258
- 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 = {}) {
9259
9354
  const config = {};
9260
9355
  const skipped = [];
9261
9356
  const a = isPlainObject(raw.auth) ? raw.auth : void 0;
@@ -9283,6 +9378,12 @@ function configFromMetadata(raw) {
9283
9378
  } else {
9284
9379
  skipped.push("auth.reset_password_method");
9285
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
+ }
9286
9387
  if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
9287
9388
  config.auth = config.auth ?? {};
9288
9389
  config.auth.password = {};
@@ -9326,6 +9427,24 @@ function configFromMetadata(raw) {
9326
9427
  } else {
9327
9428
  skipped.push("deployments.subdomain");
9328
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
+ }
9329
9448
  return { config, skipped };
9330
9449
  }
9331
9450
 
@@ -9363,7 +9482,16 @@ function registerConfigExportCommand(cfg) {
9363
9482
  }
9364
9483
  const res = await ossFetch("/api/metadata");
9365
9484
  const raw = await res.json();
9366
- 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
+ });
9367
9495
  const toml = stringifyConfigToml(config);
9368
9496
  writeFileSync8(target, toml, "utf8");
9369
9497
  if (json) {
@@ -9399,6 +9527,18 @@ function registerConfigExportCommand(cfg) {
9399
9527
  }
9400
9528
  });
9401
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
+ }
9402
9542
 
9403
9543
  // src/commands/config/plan.ts
9404
9544
  import { readFileSync as readFileSync9 } from "fs";
@@ -9462,6 +9602,19 @@ function diffConfig({ live, file }) {
9462
9602
  });
9463
9603
  }
9464
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
+ }
9465
9618
  if (fileAuth?.password) {
9466
9619
  diffPassword(liveAuth.password, fileAuth.password, changes);
9467
9620
  }
@@ -9469,6 +9622,15 @@ function diffConfig({ live, file }) {
9469
9622
  const smtpChange = diffSmtp(liveAuth.smtp, fileAuth.smtp);
9470
9623
  if (smtpChange) changes.push(smtpChange);
9471
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
+ }
9472
9634
  const fileDeployments = file.deployments;
9473
9635
  const liveDeployments = live.deployments ?? {};
9474
9636
  if (fileDeployments && "subdomain" in fileDeployments) {
@@ -9517,6 +9679,36 @@ function diffPassword(live, file, changes) {
9517
9679
  }
9518
9680
  }
9519
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
+ }
9520
9712
  function diffSmtp(live, fileSmtp) {
9521
9713
  const livedView = renderLiveSmtp(live);
9522
9714
  const tomlView = renderFileSmtp(fileSmtp);
@@ -9575,6 +9767,9 @@ var EMPTY_SMTP_VIEW = {
9575
9767
  sender_name: "",
9576
9768
  min_interval_seconds: 60
9577
9769
  };
9770
+ var EMPTY_STORAGE_CONFIG = {
9771
+ max_file_size_mb: 50
9772
+ };
9578
9773
  var EMPTY_PASSWORD_POLICY = {
9579
9774
  min_length: 8,
9580
9775
  require_number: false,
@@ -9655,7 +9850,7 @@ function formatChange(c) {
9655
9850
  }
9656
9851
 
9657
9852
  // src/lib/config-capabilities.ts
9658
- function metadataSupports(raw, change) {
9853
+ function metadataSupports(raw, change, endpointConfig = {}) {
9659
9854
  if (change.section === "auth" && change.key === "allowed_redirect_urls") {
9660
9855
  return hasAuthKey(raw, "allowedRedirectUrls");
9661
9856
  }
@@ -9668,12 +9863,24 @@ function metadataSupports(raw, change) {
9668
9863
  if (change.section === "auth" && change.key === "reset_password_method") {
9669
9864
  return hasAuthKey(raw, "resetPasswordMethod");
9670
9865
  }
9866
+ if (change.section === "auth" && change.key === "disable_signup") {
9867
+ return hasAuthKey(raw, "disableSignup");
9868
+ }
9671
9869
  if (change.section === "auth.password") {
9672
9870
  return hasAuthKey(raw, AUTH_PASSWORD_WIRE_KEY[change.key]);
9673
9871
  }
9674
9872
  if (change.section === "auth.smtp") {
9675
9873
  return hasAuthKey(raw, "smtpConfig");
9676
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
+ }
9677
9884
  if (change.section === "deployments" && change.key === "subdomain") {
9678
9885
  return raw?.deployments !== void 0 && raw.deployments !== null && typeof raw.deployments === "object";
9679
9886
  }
@@ -9685,6 +9892,9 @@ function hasAuthKey(raw, key) {
9685
9892
  const auth = raw?.auth;
9686
9893
  return auth !== void 0 && auth !== null && typeof auth === "object" && key in auth;
9687
9894
  }
9895
+ function hasConfigKey(slice, key) {
9896
+ return slice !== void 0 && slice !== null && typeof slice === "object" && key in slice;
9897
+ }
9688
9898
  var AUTH_PASSWORD_WIRE_KEY = {
9689
9899
  min_length: "passwordMinLength",
9690
9900
  require_number: "requireNumber",
@@ -9713,9 +9923,25 @@ function registerConfigPlanCommand(cfg) {
9713
9923
  const file = parseConfigToml(tomlSource);
9714
9924
  const res = await ossFetch("/api/metadata");
9715
9925
  const raw = await res.json();
9716
- 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);
9717
9943
  const result = diffConfig({ live, file });
9718
- 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));
9719
9945
  if (json) {
9720
9946
  console.log(JSON.stringify({ ...result, skipped }, null, 2));
9721
9947
  } else {
@@ -9751,6 +9977,18 @@ function registerConfigPlanCommand(cfg) {
9751
9977
  }
9752
9978
  });
9753
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
+ }
9754
9992
 
9755
9993
  // src/commands/config/apply.ts
9756
9994
  import { readFileSync as readFileSync10 } from "fs";
@@ -9769,7 +10007,23 @@ function registerConfigApplyCommand(cfg) {
9769
10007
  const file = parseConfigToml(tomlSource);
9770
10008
  const res = await ossFetch("/api/metadata");
9771
10009
  const raw = await res.json();
9772
- 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);
9773
10027
  const result = diffConfig({ live, file });
9774
10028
  const approved = opts.autoApprove || yes;
9775
10029
  const sectionsChanged = Array.from(
@@ -9822,7 +10076,7 @@ function registerConfigApplyCommand(cfg) {
9822
10076
  const skipped = [];
9823
10077
  for (const change of result.changes) {
9824
10078
  const path6 = changePath(change);
9825
- if (!metadataSupports(raw, change)) {
10079
+ if (!metadataSupports(raw, change, endpointConfig)) {
9826
10080
  skipped.push({
9827
10081
  key: path6,
9828
10082
  reason: `your backend doesn't expose ${path6} \u2014 upgrade the project to apply this section`
@@ -9873,6 +10127,18 @@ function registerConfigApplyCommand(cfg) {
9873
10127
  }
9874
10128
  });
9875
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
+ }
9876
10142
  async function applyChange(change) {
9877
10143
  if (change.section === "auth" && change.key === "allowed_redirect_urls") {
9878
10144
  await ossFetch("/api/auth/config", {
@@ -9902,6 +10168,13 @@ async function applyChange(change) {
9902
10168
  });
9903
10169
  return;
9904
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
+ }
9905
10178
  if (change.section === "auth.password") {
9906
10179
  const wireKey = authPasswordWireKey(change.key);
9907
10180
  await ossFetch("/api/auth/config", {
@@ -9934,6 +10207,27 @@ async function applyChange(change) {
9934
10207
  });
9935
10208
  return;
9936
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
+ }
9937
10231
  if (change.section === "deployments" && change.key === "subdomain") {
9938
10232
  await ossFetch("/api/deployments/slug", {
9939
10233
  method: "PUT",