@meith/settings 0.28.1 → 0.29.1

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": "@meith/settings",
3
- "version": "0.28.1",
3
+ "version": "0.29.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "zod": "^4.4.3",
23
- "@meith/core": "0.28.1",
24
- "@meith/i18n": "0.28.1"
23
+ "@meith/core": "0.29.1",
24
+ "@meith/i18n": "0.29.1"
25
25
  }
26
26
  }
@@ -3,6 +3,7 @@ import { z } from 'zod'
3
3
  import { normaliseLocale, SOURCE_LOCALE } from '@meith/i18n'
4
4
 
5
5
  import { DEFAULT_PRIVACY_POLICY, DEFAULT_TERMS_OF_SERVICE } from './legal'
6
+ import { mailEndpointProblem } from './mail'
6
7
  import { isUsableFeedUrl, isUsableIssuer, isUsableOrigin } from './origin'
7
8
 
8
9
  export type SettingGroup =
@@ -249,6 +250,20 @@ export const SETTING_DEFINITIONS = [
249
250
  ui: { min: 5, max: 100 },
250
251
  }),
251
252
 
253
+ define({
254
+ key: 'display.max_displayed_groups',
255
+ group: 'display',
256
+ label: 'Maximum displayed groups',
257
+ description:
258
+ "How many of a member's groups are shown with their name, in the postbit " +
259
+ 'and on their profile. The display group comes first and the rest follow ' +
260
+ 'in display order; 1 shows the display group alone.',
261
+ schema: z.number().int().min(1).max(10),
262
+ default: 3,
263
+ invalidates: ['settings'],
264
+ ui: { min: 1, max: 10 },
265
+ }),
266
+
252
267
  define({
253
268
  key: 'display.default_locale',
254
269
  group: 'display',
@@ -403,14 +418,16 @@ export const SETTING_DEFINITIONS = [
403
418
  'Only for the provider-API transport. The board posts Resend’s exact ' +
404
419
  'field names with a Bearer token, so this works for Resend and for ' +
405
420
  'anything that copies it — and not for Postmark or Mailgun, whose SMTP ' +
406
- 'hosts are the way in.',
421
+ 'hosts are the way in. It must be an https:// address with no embedded ' +
422
+ 'credentials, and the board refuses to reach one that resolves to a ' +
423
+ 'private or internal address.',
407
424
  schema: z
408
425
  .string()
409
426
  .trim()
410
- .refine(
411
- (value) => value === '' || z.string().url().safeParse(value).success,
412
- 'That is not a URL.',
413
- ),
427
+ .superRefine((value, ctx) => {
428
+ const problem = mailEndpointProblem(value)
429
+ if (problem !== null) ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem })
430
+ }),
414
431
  default: '',
415
432
  }),
416
433
  define({
package/src/index.ts CHANGED
@@ -48,6 +48,7 @@ export {
48
48
  mailConfigFromEnvironment,
49
49
  mailConfigFromSettings,
50
50
  mailConfigProblems,
51
+ mailEndpointProblem,
51
52
  NO_MAIL,
52
53
  resolveMailConfig,
53
54
  type SmtpMailConfig,
package/src/mail.ts CHANGED
@@ -175,6 +175,24 @@ function hostOf(endpoint: string): string {
175
175
  }
176
176
  }
177
177
 
178
+ export function mailEndpointProblem(value: string): string | null {
179
+ const trimmed = value.trim()
180
+ if (trimmed === '') return null
181
+
182
+ let url: URL
183
+ try {
184
+ url = new URL(trimmed)
185
+ } catch {
186
+ return 'That is not a URL.'
187
+ }
188
+
189
+ if (url.protocol !== 'https:') return 'The endpoint must be an https:// URL.'
190
+ if (url.username !== '' || url.password !== '') {
191
+ return 'The endpoint must not carry a username or password.'
192
+ }
193
+ return null
194
+ }
195
+
178
196
  export interface MailPreset {
179
197
  readonly id: string
180
198
  readonly label: string