@selvajs/cli 4.20.0-beta.24 → 4.20.0-beta.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvajs/cli",
3
- "version": "4.20.0-beta.24",
3
+ "version": "4.20.0-beta.26",
4
4
  "description": "Scaffold and operate a Selva white-label deployment. `npx @selvajs/cli <dir>` to bootstrap, `selva <cmd>` to manage.",
5
5
  "license": "MIT",
6
6
  "author": "VektorNode",
package/src/caddyfile.js CHANGED
@@ -45,7 +45,7 @@ export function renderCaddyfile({ domain, acmeEmail, port = 3000 }) {
45
45
  @api path /api/*
46
46
  header @api Cache-Control "no-cache, no-store, must-revalidate"
47
47
 
48
- # Must clear the app's own caps (COMPUTE_REQUEST_MAX_BYTES / BODY_SIZE_LIMIT,
48
+ # Must clear the app's own caps (the solve request cap and BODY_SIZE_LIMIT,
49
49
  # both 256 MiB) — Caddy rejects first, so a lower value here silently caps
50
50
  # every upload no matter what .env says. Write MiB: Caddy's "mb" is 10^6 bytes.
51
51
  request_body {
@@ -10,7 +10,7 @@
10
10
  // it. `__tests__/doctor-config-parity.test.js` asserts both against one fixture
11
11
  // table, so a rule changed here and not there fails CI.
12
12
 
13
- import { RENAMED_ENV_VARS, REPLACED_ENV_VARS } from '../env.js';
13
+ import { RENAMED_ENV_VARS, RETIRED_ENV_VARS } from '../env.js';
14
14
  import { keyRotationStatus } from '../generated/config.js';
15
15
  import { green, red, yellow } from './result.js';
16
16
 
@@ -221,10 +221,10 @@ export function parseBodySizeLimit(raw) {
221
221
  return bytes;
222
222
  }
223
223
 
224
- // The app's own default for COMPUTE_REQUEST_MAX_BYTES (packages/server
224
+ // The app's solve request cap (COMPUTE_REQUEST_MAX_BYTES in packages/server
225
225
  // compute/limits.ts). Duplicated rather than imported: the CLI installs the
226
226
  // runtime and cannot import from it.
227
- const COMPUTE_REQUEST_DEFAULT_BYTES = 256 * 1024 * 1024;
227
+ const COMPUTE_REQUEST_MAX_BYTES = 256 * 1024 * 1024;
228
228
 
229
229
  /**
230
230
  * BODY_SIZE_LIMIT has to clear the compute request cap, or adapter-node's global
@@ -234,7 +234,7 @@ const COMPUTE_REQUEST_DEFAULT_BYTES = 256 * 1024 * 1024;
234
234
  */
235
235
  export function checkBodySizeLimit(env) {
236
236
  const raw = env.BODY_SIZE_LIMIT;
237
- const requestCap = Number(env.COMPUTE_REQUEST_MAX_BYTES) || COMPUTE_REQUEST_DEFAULT_BYTES;
237
+ const requestCap = COMPUTE_REQUEST_MAX_BYTES;
238
238
  const asMb = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
239
239
 
240
240
  if (raw == null || raw === '') {
@@ -255,9 +255,9 @@ export function checkBodySizeLimit(env) {
255
255
 
256
256
  if (bytes < requestCap) {
257
257
  return yellow(
258
- `BODY_SIZE_LIMIT=${raw} (${asMb(bytes)}) is below COMPUTE_REQUEST_MAX_BYTES ` +
259
- `(${asMb(requestCap)}) — the global cap rejects first, making the compute cap ` +
260
- `dead config. Raise it to at least ${asMb(requestCap)}.`
258
+ `BODY_SIZE_LIMIT=${raw} (${asMb(bytes)}) is below the ` +
259
+ `${asMb(requestCap)} a solve may send — larger solves fail with 413 before Selva ` +
260
+ `sees them. Raise it to at least ${asMb(requestCap)}.`
261
261
  );
262
262
  }
263
263
 
@@ -331,9 +331,7 @@ export function checkClientAddress(env) {
331
331
  return green(`ADDRESS_HEADER=${header}${depth ? ` (XFF_DEPTH=${depth})` : ''}`);
332
332
  }
333
333
 
334
- // A rename is auto-fixable (`selva migrate` rewrites the key). A replacement
335
- // encodes a value in the new name, so migrate leaves it alone rather than
336
- // guessing — it's reported here and nowhere else.
334
+ // Both are auto-fixable: `selva migrate` renames old names and removes retired keys.
337
335
  export function checkDeprecatedEnv(env) {
338
336
  const checks = [];
339
337
 
@@ -346,9 +344,9 @@ export function checkDeprecatedEnv(env) {
346
344
  );
347
345
  }
348
346
 
349
- for (const [oldName, replacement] of Object.entries(REPLACED_ENV_VARS)) {
350
- if (env[oldName] === undefined) continue;
351
- checks.push(yellow(`${oldName} is deprecated — replace it with ${replacement}`));
347
+ for (const [key, now] of Object.entries(RETIRED_ENV_VARS)) {
348
+ if (env[key] === undefined) continue;
349
+ checks.push(yellow(`${key} is no longer read (${now}) — \`selva migrate\` removes it`));
352
350
  }
353
351
 
354
352
  return checks;
@@ -1,6 +1,6 @@
1
1
  // Plan the .env rewrite `selva migrate` applies: old names renamed, values a
2
- // looser reader used to accept rewritten to what they meant, no-op lines
3
- // removed, shipped template prose stripped. Unknown keys are kept.
2
+ // looser reader used to accept rewritten to what they meant, no-op and retired
3
+ // lines removed, shipped template prose stripped. Unknown keys are kept.
4
4
  //
5
5
  // `mismatches` is the proof: every declared key resolved from the old file
6
6
  // (with the old readers' semantics) and from the new one. Anything but empty
@@ -10,6 +10,7 @@ import {
10
10
  countEnvCommentLines,
11
11
  parseEnv,
12
12
  RENAMED_ENV_VARS as RENAMES,
13
+ RETIRED_ENV_VARS as RETIRED,
13
14
  renameEnvKeys,
14
15
  setEnvValues,
15
16
  stripEnvComments
@@ -34,8 +35,7 @@ function computeDebugMode(env) {
34
35
  /** Keys whose effective value depends on another key. */
35
36
  const EFFECTIVE = {
36
37
  HEADER_AUTH_DATA_DIR: (env) => env.HEADER_AUTH_DATA_DIR || env.DATA_PATH,
37
- SELVA_FLAG_COMPUTE_DEBUG: computeDebugMode,
38
- SELVA_FLAG_COMPUTE_DEBUG_VERBOSE: () => undefined
38
+ SELVA_FLAG_COMPUTE_DEBUG: computeDebugMode
39
39
  };
40
40
 
41
41
  function canonical(value) {
@@ -114,6 +114,13 @@ export function planEnvMigration(input) {
114
114
  changes.push({ key: 'HEADER_AUTH_DATA_DIR', action: 'removed', reason: 'same as DATA_PATH' });
115
115
  }
116
116
 
117
+ // The app ignores these whatever their value, so removing the line changes nothing it does.
118
+ for (const [key, now] of Object.entries(RETIRED)) {
119
+ if (env[key] === undefined || key in updates) continue;
120
+ updates[key] = undefined;
121
+ changes.push({ key, action: 'removed', reason: `no longer read: ${now}` });
122
+ }
123
+
117
124
  if (Object.keys(updates).length > 0) text = setEnvValues(text, updates);
118
125
 
119
126
  // Read back with Node's parser, the one the app boots with, so a writer bug
@@ -127,7 +134,7 @@ export function planEnvMigration(input) {
127
134
  if (JSON.stringify(was) !== JSON.stringify(is)) mismatches.push({ key: spec.key, was, is });
128
135
  }
129
136
  for (const key of Object.keys(before)) {
130
- if (!SPECS.has(key) && !(key in RENAMES) && after[key] !== before[key]) {
137
+ if (!SPECS.has(key) && !(key in RENAMES) && !(key in RETIRED) && after[key] !== before[key]) {
131
138
  mismatches.push({ key, was: before[key], is: after[key] });
132
139
  }
133
140
  }
package/src/env.js CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs';
6
6
  import { parseEnv as nodeParseEnv } from 'node:util';
7
- import { ALL_SETTINGS } from './generated/config.js';
7
+ import { ALL_SETTINGS, RETIRED_KEYS } from './generated/config.js';
8
8
 
9
9
  // Old env name → its replacement, for keys renamed without changing meaning.
10
10
  // From the manifest's `aliases`, the same list the server reads old names by.
@@ -12,11 +12,8 @@ export const RENAMED_ENV_VARS = Object.fromEntries(
12
12
  ALL_SETTINGS.flatMap((s) => (s.aliases ?? []).map((alias) => [alias, s.key]))
13
13
  );
14
14
 
15
- // Deprecated vars whose replacement changes the VALUE too, so a simple key
16
- // rename can't migrate them — `selva migrate` leaves these alone rather than guessing.
17
- export const REPLACED_ENV_VARS = {
18
- SELVA_FLAG_COMPUTE_DEBUG_VERBOSE: 'SELVA_FLAG_COMPUTE_DEBUG=verbose'
19
- };
15
+ // Keys the app no longer reads → what took their place. `selva migrate` removes them.
16
+ export const RETIRED_ENV_VARS = RETIRED_KEYS;
20
17
 
21
18
  // Header written above the settings in a stripped .env. The pointer matters:
22
19
  // without it the operator has no way back to what a var means, and the reflex
@@ -682,68 +682,15 @@ var ENGINE_SETTINGS = composeManifest([
682
682
  description: 'Override the TLS mode inferred from the port.',
683
683
  legacy: { bool: 'exact-true' }
684
684
  }),
685
- tunableInt('COMPUTE_SOLVE_DEADLINE_MS', 1e5, 'Solve time limit', 'Longest one solve may run.', {
686
- unit: 'ms',
687
- aliases: ['MAX_SOLVE_DURATION_MS'],
688
- impact: 'Past the reverse proxy read timeout, solves fail there with a 502 instead.'
689
- }),
690
- tunableInt(
691
- 'COMPUTE_RATE_LIMIT_WINDOW_MS',
692
- 1e5,
693
- 'Solve rate limit window',
694
- 'Compute rate-limit window.',
695
- { unit: 'ms' }
696
- ),
697
685
  tunableInt(
698
686
  'COMPUTE_RATE_LIMIT_MAX',
699
687
  120,
700
688
  'Solves per window',
701
- 'Solves per user or share link per window.'
689
+ 'Solves per user or share link per 100-second window.'
702
690
  ),
703
691
  tunableInt('MAX_DEFINITION_FILE_SIZE_BYTES', 50 * MB, 'Max .gh upload', 'Largest .gh upload.', {
704
692
  unit: 'bytes'
705
693
  }),
706
- tunableInt(
707
- 'MAX_IMAGE_FILE_SIZE_BYTES',
708
- 10 * MB,
709
- 'Max image upload',
710
- 'Largest cover image upload.',
711
- { unit: 'bytes' }
712
- ),
713
- tunableInt(
714
- 'COMPUTE_REQUEST_MAX_BYTES',
715
- 256 * MB,
716
- 'Max solve request',
717
- 'Largest solve request body.',
718
- {
719
- unit: 'bytes',
720
- impact: 'Must stay at or below BODY_SIZE_LIMIT, or that limit rejects first.'
721
- }
722
- ),
723
- tunableInt(
724
- 'COMPUTE_RESPONSE_MAX_BYTES',
725
- 300 * MB,
726
- 'Max solve response',
727
- 'Largest solve response body.',
728
- { unit: 'bytes' }
729
- ),
730
- tunableInt(
731
- 'REMOTE_DEFINITION_FETCH_TIMEOUT_MS',
732
- 3e4,
733
- 'Remote .gh fetch timeout',
734
- 'Deadline for fetching a remote .gh.',
735
- { unit: 'ms' }
736
- ),
737
- tunableInt(
738
- 'REMOTE_DEFINITION_CACHE_TTL_MS',
739
- 3e5,
740
- 'Remote .gh cache time',
741
- 'How long remote .gh bytes stay cached.',
742
- {
743
- unit: 'ms',
744
- aliases: ['DEFINITION_CACHE_TTL_MS']
745
- }
746
- ),
747
694
  tunableInt(
748
695
  'COMPUTE_MAX_QUEUE_DEPTH',
749
696
  0,
@@ -789,17 +736,6 @@ var ENGINE_SETTINGS = composeManifest([
789
736
  aliases: ['COMPUTE_RESPONSE_CACHE_MB']
790
737
  }
791
738
  ),
792
- defineSetting({
793
- key: 'COMPUTE_REUSE_DEFINITION_CACHE',
794
- label: 'Reuse compute server definition cache',
795
- type: 'bool',
796
- default: true,
797
- onInvalid: 'default',
798
- class: 'tunable',
799
- description: 'Reuse the compute server’s cached definition instead of re-uploading it.',
800
- impact:
801
- 'Leave on only with a compute server that reports a stale pointer (the VektorNode fork).'
802
- }),
803
739
  defineSetting({
804
740
  key: 'COMPUTE_SERVER_CACHESOLVE',
805
741
  label: 'Compute server solve cache',
@@ -809,18 +745,11 @@ var ENGINE_SETTINGS = composeManifest([
809
745
  class: 'tunable',
810
746
  description: 'Ask Rhino.Compute to cache solve results server-side.'
811
747
  }),
812
- tunableInt(
813
- 'API_TOKEN_RATE_LIMIT_WINDOW_MS',
814
- DEFAULT_API_RATE_LIMIT.windowMs,
815
- 'API rate limit window',
816
- 'API token rate-limit window.',
817
- { unit: 'ms' }
818
- ),
819
748
  tunableInt(
820
749
  'API_TOKEN_RATE_LIMIT_MAX',
821
750
  DEFAULT_API_RATE_LIMIT.maxPerWindow,
822
- 'API requests per window',
823
- 'Requests per API token per window. 0 is off.',
751
+ 'API requests per minute',
752
+ 'Requests per API token per minute. 0 is off.',
824
753
  {
825
754
  min: 0,
826
755
  zero: 'Off'
@@ -946,29 +875,6 @@ var SUPABASE_SETTINGS = composeManifest([
946
875
  class: 'setting',
947
876
  description: 'OAuth buttons on the login page (google, github, azure, gitlab).'
948
877
  }),
949
- defineSetting({
950
- key: 'SUPABASE_TOKEN_VERIFICATION',
951
- label: 'Session check mode',
952
- type: 'enum',
953
- values: ['hybrid', 'strict'],
954
- default: 'hybrid',
955
- onInvalid: 'fail',
956
- class: 'tunable',
957
- description:
958
- 'hybrid verifies JWTs locally and rechecks periodically; strict asks Supabase every request.',
959
- legacy: { enumFallback: 'hybrid' }
960
- }),
961
- defineSetting({
962
- key: 'SUPABASE_REVALIDATE_MS',
963
- label: 'Session recheck interval',
964
- type: 'int',
965
- min: 1,
966
- default: 6e4,
967
- unit: 'ms',
968
- onInvalid: 'default',
969
- class: 'tunable',
970
- description: 'Hybrid recheck window: the longest a revoked session keeps working.'
971
- }),
972
878
  defineSetting({
973
879
  key: 'SUPABASE_PUBLIC_BUCKET',
974
880
  label: 'Public files bucket',
@@ -986,15 +892,6 @@ var SUPABASE_SETTINGS = composeManifest([
986
892
  onInvalid: 'fail',
987
893
  class: 'tunable',
988
894
  description: 'Storage bucket for private files.'
989
- }),
990
- defineSetting({
991
- key: 'SUPABASE_PRIVATE_URL_PREFIX',
992
- label: 'Private file URL prefix',
993
- type: 'string',
994
- default: '/api/files',
995
- onInvalid: 'fail',
996
- class: 'tunable',
997
- description: 'URL prefix the app serves private files at.'
998
895
  })
999
896
  ]);
1000
897
  // src/lib/server/config/settings.ts
@@ -1147,14 +1044,6 @@ var APP_SETTINGS = composeManifest([
1147
1044
  'off, on (cache and timing lines per solve) or verbose (adds Compute’s raw error bodies).',
1148
1045
  impact: 'verbose can write what was solved to the log, which erasure cannot reach.'
1149
1046
  }),
1150
- defineSetting({
1151
- key: 'SELVA_FLAG_COMPUTE_DEBUG_VERBOSE',
1152
- label: 'Compute debug logging (old name)',
1153
- type: 'bool',
1154
- onInvalid: 'default',
1155
- class: 'setting',
1156
- description: 'Replaced by SELVA_FLAG_COMPUTE_DEBUG=verbose.'
1157
- }),
1158
1047
  defineSetting({
1159
1048
  key: 'SENTRY_DSN',
1160
1049
  label: 'Sentry error reporting',
@@ -1162,7 +1051,18 @@ var APP_SETTINGS = composeManifest([
1162
1051
  onInvalid: 'fail',
1163
1052
  class: 'secret',
1164
1053
  sensitive: true,
1165
- description: 'Ship unexpected server errors to Sentry.'
1054
+ description:
1055
+ 'Ship unexpected server errors to Sentry, scrubbed of cookies, auth headers, bodies and query strings.'
1056
+ }),
1057
+ defineSetting({
1058
+ key: 'SENTRY_TRACES_SAMPLE_RATE',
1059
+ label: 'Sentry trace sampling',
1060
+ type: 'string',
1061
+ default: '0',
1062
+ onInvalid: 'default',
1063
+ class: 'tunable',
1064
+ description:
1065
+ 'Share of requests traced to Sentry, 0–1 (out of range is clamped). 0 sends errors only.'
1166
1066
  }),
1167
1067
  defineSetting({
1168
1068
  key: 'SELVA_RELEASE',
@@ -1188,6 +1088,27 @@ var WATCHED_PREFIXES = [
1188
1088
  ];
1189
1089
  /** Read by tooling, not by the app: not unknown, not reported. */
1190
1090
  var EXTERNAL_KEYS = ['SELVA_REQUIRE_SUPABASE'];
1091
+ /**
1092
+ * Keys no release reads any more, with what took their place. Boot and doctor
1093
+ * name them instead of calling them typos; `selva migrate` removes them.
1094
+ */
1095
+ var RETIRED_KEYS = {
1096
+ SELVA_FLAG_COMPUTE_DEBUG_VERBOSE: 'use SELVA_FLAG_COMPUTE_DEBUG=verbose',
1097
+ COMPUTE_RATE_LIMIT_WINDOW_MS: 'fixed at 100 s',
1098
+ API_TOKEN_RATE_LIMIT_WINDOW_MS: 'fixed at 60 s',
1099
+ MAX_IMAGE_FILE_SIZE_BYTES: 'fixed at 10 MB',
1100
+ COMPUTE_REQUEST_MAX_BYTES: 'fixed at 256 MB',
1101
+ COMPUTE_RESPONSE_MAX_BYTES: 'fixed at 300 MB',
1102
+ REMOTE_DEFINITION_FETCH_TIMEOUT_MS: 'fixed at 30 s',
1103
+ REMOTE_DEFINITION_CACHE_TTL_MS: 'fixed at 5 min',
1104
+ DEFINITION_CACHE_TTL_MS: 'fixed at 5 min',
1105
+ SUPABASE_TOKEN_VERIFICATION: 'always hybrid',
1106
+ SUPABASE_REVALIDATE_MS: 'fixed at 60 s',
1107
+ SUPABASE_PRIVATE_URL_PREFIX: 'fixed at /api/files',
1108
+ COMPUTE_SOLVE_DEADLINE_MS: 'set --timeout (RHINO_COMPUTE_TIMEOUT) on the compute server',
1109
+ MAX_SOLVE_DURATION_MS: 'set --timeout (RHINO_COMPUTE_TIMEOUT) on the compute server',
1110
+ COMPUTE_REUSE_DEFINITION_CACHE: 'on whenever the compute server reports /limits'
1111
+ };
1191
1112
  /**
1192
1113
  * The manifest for this deployment: engine and app keys, plus the slices of
1193
1114
  * the providers it selects. A provider's required keys only apply when it is
@@ -1224,7 +1145,7 @@ function deploymentReport(env) {
1224
1145
  );
1225
1146
  return buildConfigReport(manifest, env, {
1226
1147
  prefixes: WATCHED_PREFIXES,
1227
- external: [...EXTERNAL_KEYS, ...inactive]
1148
+ external: [...EXTERNAL_KEYS, ...Object.keys(RETIRED_KEYS), ...inactive]
1228
1149
  });
1229
1150
  }
1230
1151
  var NOT_FROM_ENV_FILE = /* @__PURE__ */ new Set(['NODE_ENV']);
@@ -1251,6 +1172,7 @@ function keyRotationStatus(env, now = Date.now()) {
1251
1172
  export {
1252
1173
  ALL_SETTINGS,
1253
1174
  PREVIOUS_KEY_DAYS,
1175
+ RETIRED_KEYS,
1254
1176
  buildConfigReport,
1255
1177
  deploymentManifest,
1256
1178
  deploymentReport,