@autobusal/utilities 1.3.0 → 1.4.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/CHANGELOG.md CHANGED
@@ -1,36 +1,45 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this package are documented here. This project follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
4
3
 
5
- ## 1.3.0
4
+ ## 1.4.1
6
5
 
7
- `getDayParts` — the same short date as `getFormattedShort`, but in its parts.
8
- One string cannot be shortened by CSS, and the seven-day strip needs to drop
9
- the month on a narrow screen so seven days fit without a scroller.
6
+ ### Changed
10
7
 
11
- ## [1.2.0] - 2026-07-25
8
+ - **Gender can be "not declared".** `getOptionsSex` gains it as the first
9
+ option and `getSex` reports it, instead of treating everything that is not
10
+ `1` as male — which is what three existing rows with no value were already
11
+ rendering as.
12
12
 
13
- ### Added
13
+ Carried by `null`: the `visitors`.`sex` column has always been nullable, so
14
+ nothing migrated. The option's id is `''`, which Laravel's
15
+ `ConvertEmptyStringsToNull` turns into a real null on the way in, so "not
16
+ declared" is the *absence* of a value at every layer rather than a magic
17
+ number each one has to learn.
14
18
 
15
- - `GetChannelStatus(t)`: shared disabled/dedicated/main select values for
16
- the Telegram and WhatsApp admin settings tabs (and any future per-label
17
- messaging channel).
19
+ Deliberately not shared with the checkout passenger forms — those build
20
+ their own field, and gender there is a per-route requirement a carrier can
21
+ insist on. A profile is the person; a passenger record is what a carrier
22
+ demands in order to board them.
18
23
 
19
- ## [1.1.2] - 2026-07-24
24
+ ## 1.4.0
20
25
 
21
26
  ### Added
22
27
 
23
- - New `sanitizeHtml(html)` helper (wraps DOMPurify) for sanitizing admin-authored CMS content (pages, articles, notifications, policies, affiliate terms, document previews) before rendering via `dangerouslySetInnerHTML`. Added `dompurify` as a dependency.
24
-
25
- ## [1.1.1] - 2026-07-19
26
-
27
- ### Added
28
+ - **`check-backticks.mjs`**, moved here from the autobusal repo root so the
29
+ apps can run it too.
28
30
 
29
- - `form.tsx`: the `Validate` helper now supports a cross-field `matches:<otherField>` rule (e.g. password confirmation). It accepts an optional third argument, react-hook-form's `getValues` (`UseFormGetValues<any>`), and emits a `validate` callback that compares the field against the named field, failing with the `errors.matches` message on mismatch. When `getValues` is not supplied the rule passes rather than blocking the form, so single-field rules keep working unchanged.
30
- - `users.tsx` / `index.ts`: new `GetEnvironment(t)` helper exposing a payment-gateway `sandbox` / `production` mode selector (labels `admin_label.manage.environment.*`), following the same shape as `GetStatus`. It is now exported from the package index.
31
+ A backtick used as quoting inside a styled-components template *terminates
32
+ the template*. The bundler then reports a syntax error somewhere further
33
+ down, pointing at a line that is perfectly fine — so the search starts in
34
+ the wrong place every time. Per the script's own history that cost five
35
+ build failures in one session before anyone connected the two.
31
36
 
32
- ### Fixed
37
+ It only ever guarded `packages`, and the trap is not confined there: it bit
38
+ `magus/src` the moment a public page grew its own styled-components, with
39
+ nothing watching. Each app's `prebuild` now runs it over its own `src`
40
+ (first, so a broken template stops the build before it spends minutes
41
+ copying files), and `publish.sh` still runs it over `packages`.
33
42
 
34
- - `users.tsx`: `GetStatus` now returns all four real statuses. Previously it listed only Available (`1`) and Unavailable (`0`), so records whose actual status was Suspended (`2`) or Pending (`3`) had no matching `<option>`; the admin select silently fell back to Available and an unrelated save could overwrite the real status with Available. Added `suspended` and `pending` options to keep it in sync with `DisplayStatus`.
43
+ One script with three callers, rather than a copy per repo that drifts.
35
44
 
36
- Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel patch sets into canonical @autobusal source (eliminates per-repo patch-package divergence).
45
+ Takes the directory to scan as its argument.
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Refuse to publish a backtick used as quoting inside a styled-components
4
+ * template literal.
5
+ *
6
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
7
+ *
8
+ * A backtick inside a CSS comment TERMINATES the template string. What the
9
+ * bundler then reports is a syntax error somewhere further down, pointing at
10
+ * a line that is perfectly fine - so the search starts in the wrong place
11
+ * every time. It cost five build failures in a single session before anyone
12
+ * connected the two.
13
+ *
14
+ * TWO THINGS HERE ARE LEGITIMATE and must never be flagged, which is why
15
+ * this is not a plain grep:
16
+ *
17
+ * - nested css`...` interpolations, which this codebase uses everywhere
18
+ * - backticks in the JSDoc ABOVE a styled block, which are idiomatic and
19
+ * sit outside the template entirely
20
+ *
21
+ * The bug is only ever the intersection: a backtick inside a comment that
22
+ * sits BETWEEN styled.x` and its closing `;
23
+ *
24
+ * Edited: Ferjolt Ozuni - Date: 2026-08-06
25
+ * Moved into @autobusal/utilities from the repo root, so the APPS can run it
26
+ * too. It only ever guarded `packages`, and the trap is not confined there:
27
+ * it bit magus/src the moment a public page grew its own styled-components,
28
+ * with nothing watching. Each app's prebuild now runs it over its own src,
29
+ * and publish.sh still runs it over packages - one script, three callers,
30
+ * rather than a copy per repo that drifts.
31
+ *
32
+ * Takes the directory to scan as its argument.
33
+ */
34
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
35
+ import { join } from 'node:path';
36
+
37
+ const OPEN = /styled(?:\.\w+|\([^)]*\))(?:<[^>]*>)?`/g;
38
+
39
+ const walk = (dir) => readdirSync(dir).flatMap((entry) => {
40
+ const path = join(dir, entry);
41
+
42
+ if (entry === 'node_modules' || entry.startsWith('.')) {
43
+ return [];
44
+ }
45
+
46
+ if (statSync(path).isDirectory()) {
47
+ return walk(path);
48
+ }
49
+
50
+ return /\.tsx?$/.test(entry) ? [path] : [];
51
+ });
52
+
53
+ const found = [];
54
+
55
+ for (const path of walk(process.argv[2] ?? 'packages')) {
56
+ const text = readFileSync(path, 'utf8');
57
+
58
+ for (const match of text.matchAll(OPEN)) {
59
+ const start = match.index + match[0].length;
60
+ const end = text.indexOf('`;', start);
61
+
62
+ if (end === -1) {
63
+ continue;
64
+ }
65
+
66
+ const base = text.slice(0, start).split('\n').length;
67
+ let comment = false;
68
+
69
+ text.slice(start, end).split('\n').forEach((line, n) => {
70
+ if (line.includes('/*')) {
71
+ comment = true;
72
+ }
73
+
74
+ if (comment && line.includes('`')) {
75
+ found.push(`${ path }:${ base + n }: ${ line.trim().slice(0, 72) }`);
76
+ }
77
+
78
+ if (line.includes('*/')) {
79
+ comment = false;
80
+ }
81
+ });
82
+ }
83
+ }
84
+
85
+ if (found.length > 0) {
86
+ console.error('Backtick in a comment inside a styled template - this breaks the build:');
87
+ found.forEach((line) => console.error(` ${ line }`));
88
+ process.exit(1);
89
+ }
90
+
91
+ console.log('backticks: clean');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/utilities",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/passengers.ts CHANGED
@@ -1,15 +1,45 @@
1
1
  import { TFunction } from 'i18next';
2
2
 
3
- export const getSex = (value: number): string => (
4
- value === 1 ? 'female' : 'male'
5
- );
3
+ /**
4
+ * Edited: Ferjolt Ozuni - Date: 2026-08-06
5
+ *
6
+ * Gender is OPTIONAL on a profile, and "not declared" is a real answer
7
+ * rather than a missing one.
8
+ *
9
+ * `null` carries it - `visitors`.`sex` has always been nullable and already
10
+ * held rows with no value, so nothing needed migrating. What was missing was
11
+ * any way to SAY it: the select offered Male and Female, the profile form
12
+ * defaulted to Male, and anybody who never touched the field was recorded as
13
+ * male. Three such rows existed already and rendered as "male" here, because
14
+ * this treated everything that was not 1 as male.
15
+ *
16
+ * Deliberately NOT shared with the passenger forms at checkout: those build
17
+ * their own field (routes-order's Gender), and gender there is a per-route
18
+ * requirement a carrier can insist on. A profile is the person; a passenger
19
+ * record is what a carrier demands in order to board them, and those are
20
+ * different questions.
21
+ */
22
+ export const getSex = (value?: number | null): string => {
23
+ if (value === null || value === undefined) {
24
+ return 'undeclared';
25
+ }
26
+
27
+ return value === 1 ? 'female' : 'male';
28
+ };
6
29
 
7
30
  export const getOptionsSex = (t: TFunction<'common'>) => ([
8
31
  {
32
+ // Empty rather than a third numeric code: Laravel's
33
+ // ConvertEmptyStringsToNull turns it into a real null on the way in, so
34
+ // "not declared" is the ABSENCE of a value at every layer rather than a
35
+ // magic number each of them has to learn.
36
+ id: '',
37
+ name: t('data.gender.undeclared', { ns: 'common' })
38
+ }, {
9
39
  id: 0,
10
40
  name: t('data.gender.male', { ns: 'common' })
11
41
  }, {
12
42
  id: 1,
13
43
  name: t('data.gender.female', { ns: 'common' })
14
44
  }
15
- ]);
45
+ ]);