@autobusal/utilities 1.2.0 → 1.4.0

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,30 +1,24 @@
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
-
5
- ## [1.2.0] - 2026-07-25
6
-
7
- ### Added
8
-
9
- - `GetChannelStatus(t)`: shared disabled/dedicated/main select values for
10
- the Telegram and WhatsApp admin settings tabs (and any future per-label
11
- messaging channel).
12
-
13
- ## [1.1.2] - 2026-07-24
3
+ ## 1.4.0
14
4
 
15
5
  ### Added
16
6
 
17
- - 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.
18
-
19
- ## [1.1.1] - 2026-07-19
20
-
21
- ### Added
7
+ - **`check-backticks.mjs`**, moved here from the autobusal repo root so the
8
+ apps can run it too.
22
9
 
23
- - `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.
24
- - `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.
10
+ A backtick used as quoting inside a styled-components template *terminates
11
+ the template*. The bundler then reports a syntax error somewhere further
12
+ down, pointing at a line that is perfectly fine — so the search starts in
13
+ the wrong place every time. Per the script's own history that cost five
14
+ build failures in one session before anyone connected the two.
25
15
 
26
- ### Fixed
16
+ It only ever guarded `packages`, and the trap is not confined there: it bit
17
+ `magus/src` the moment a public page grew its own styled-components, with
18
+ nothing watching. Each app's `prebuild` now runs it over its own `src`
19
+ (first, so a broken template stops the build before it spends minutes
20
+ copying files), and `publish.sh` still runs it over `packages`.
27
21
 
28
- - `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`.
22
+ One script with three callers, rather than a copy per repo that drifts.
29
23
 
30
- Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel patch sets into canonical @autobusal source (eliminates per-repo patch-package divergence).
24
+ 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/date.tsx CHANGED
@@ -27,6 +27,28 @@ export const getFormattedShort = (date: Date, t: TFunction<'generate'>): string
27
27
  return `${ t(display.weekday, { ns: 'common' }) }, ${ display.day } ${ t(display.month, { ns: 'common' }).substring(0, 3) }`;
28
28
  };
29
29
 
30
+ /**
31
+ * The same short date, but in parts.
32
+ *
33
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
34
+ *
35
+ * getFormattedShort returns one string, so a caller that needs to drop the
36
+ * month on a narrow screen cannot - CSS can hide an element but it cannot
37
+ * shorten a sentence. The seven-day strip needs exactly that: "Sat, 01 Aug"
38
+ * seven times does not fit a phone, "Sat 01" does.
39
+ */
40
+ export const getDayParts = (date: Date, t: TFunction<'generate'>): { weekday: string, day: string, month: string } => {
41
+ const day = date.getDate();
42
+ const month = date.toLocaleString('default', { month: 'long' });
43
+ const weekday = date.toLocaleString('default', { weekday: 'short' });
44
+
45
+ return {
46
+ weekday: t(`data.days.${ weekday.toLowerCase() }`, { ns: 'common' }),
47
+ day: day < 10 ? `0${ day }` : String(day),
48
+ month: t(`data.months.${ month.toLowerCase() }`, { ns: 'common' }).substring(0, 3)
49
+ };
50
+ };
51
+
30
52
  export const differenceYears = (date: Date): number => {
31
53
  const milliseconds = Date.now() - date.getTime();
32
54
 
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createDate, prepareDate, getFormattedShort, differenceYears, futureDate } from './date';
1
+ import { createDate, prepareDate, getFormattedShort, getDayParts, differenceYears, futureDate } from './date';
2
2
  import { Validate, Display } from './form';
3
3
  import { error, notify, success } from './notify';
4
4
  import { getAllowed, getLanguages, getPayments } from './options';
@@ -18,6 +18,7 @@ export {
18
18
  createDate,
19
19
  prepareDate,
20
20
  getFormattedShort,
21
+ getDayParts,
21
22
  differenceYears,
22
23
  DisplayStatus,
23
24
  futureDate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/utilities",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",