@efa-one/sdk 1.14.0 → 1.15.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/README.md CHANGED
@@ -15,6 +15,7 @@ sub-paths — this keeps `express` out of the frontend and `react` out of the ba
15
15
  | `@efa-one/sdk/backend` | Node/Express | Auth + token exchange (`requireAuth`, `createExchangeRouter`, `requireInternalOrAuth` …), health router, service discovery + gateway client (`serviceClient`, `resolveService`), clients for audit/reporting/mail/notifications, permission resolution/registration (`getUserPermissions`, `registerPermissions`, `checkPermission`), capability registry (`registerApiMetadata`), backend i18n |
16
16
  | `@efa-one/sdk/frontend` | Browser/React | postMessage IPC (`registerAppInfo`, `sendAtStart`, `navigateToApp`, `notifyRouteChange` …), react-i18next factory (`initI18n`), `DevHeader` |
17
17
  | `@efa-one/sdk/frontend/ui` | Browser/React | The efa-one design-system kit: `Button`, `Input`, `Badge`, `Alert`, `Dialog`, `DropdownMenu`, `Tooltip`, `EmptyState`, `Skeleton`, `RecordDialog`, and the **`DataTable`** (sortable/filterable/groupable list with per-user persisted view; reflows to stacked cards below 640px). Also exports `useIsMobile` — the platform-wide 640px breakpoint hook. Ship its companion CSS once: `import '@efa-one/sdk/frontend/ui/styles.css'` |
18
+ | `@efa-one/sdk/frontend/format` | Browser | Platform-wide date/time display — `formatDate` (`01.09.2026`), `formatDateTime` (`01.09.2026, 11:05`), `formatTime`, `formatFileStamp`, `localeForLanguage`. Always use these instead of `toLocaleDateString`/`toLocaleString`: without an options object those render `1.9.2026`, and without a locale argument they follow the browser language (`9/1/2026`) |
18
19
  | `@efa-one/sdk/frontend/viewPreferences` | Browser/React | Persistence seam for `DataTable` — `createViewPreferencesClient()` (standard `/api/view-preferences/:listId` adapter) + `useViewPreferences()` |
19
20
 
20
21
  Every individual module is also reachable directly, e.g.
@@ -112,6 +113,33 @@ The flat output layout (`backend/` + `frontend/` at the package root) is a
112
113
  deliberate choice so that consumers using `moduleResolution: node` (node10) can
113
114
  resolve the subpath imports physically — without having to change their tsconfig.
114
115
 
116
+ ## Quality gates (CI)
117
+
118
+ Every push and pull request runs the full gate; the same gate also runs as a
119
+ prerequisite job of the release workflow, so a `v*` tag can never publish a state
120
+ that a PR would have rejected.
121
+
122
+ | Workflow | Runs on | What it enforces |
123
+ |---|---|---|
124
+ | `build-check.yml` | push (`main`, `development`), PR, called by `publish.yml` | `tsc` typecheck (sources **and** `test/` + `scripts/`), Vitest **with coverage thresholds**, ESLint, full `npm run build` (incl. the ESM specifier gate), plus `npm ci` **and** `npm run build` in both `template/` scaffolds |
125
+ | `security.yml` | push, PR, weekly (Mon 06:00 UTC) | `npm audit --audit-level=high` and a copyleft license gate (GPL/AGPL/LGPL) across all three npm directories, Trivy filesystem scan (HIGH/CRITICAL, **including devDependencies** — without that switch Trivy skips them and the scan would be structurally empty here), Trivy misconfig/secret scan (report-only) |
126
+ | `publish.yml` | `v*` tag | tag ↔ `package.json` version match, then npm publish via OIDC trusted publishing |
127
+ | `dependabot.yml` + `dependabot-automerge.yml` | weekly | grouped minor/patch dependency PRs for the SDK, both scaffolds, the scaffold's Docker base images and the GitHub Actions themselves. Auto-merge is present but dormant — see the header comment in the workflow for the three settings that arm it |
128
+
129
+ Run the blocking gates locally before pushing:
130
+
131
+ ```bash
132
+ ./scripts/ci-local.sh # mirrors build-check.yml + security.yml (Trivy via docker if not installed)
133
+ ```
134
+
135
+ Coverage uses a two-level ratchet (global floor + per-file gates on the modules that
136
+ are deliberately tested); see `vitest.config.ts`. When new tests raise coverage, raise
137
+ the thresholds — never lower them.
138
+
139
+ **TypeScript is pinned to exactly `6.0.3`.** That is the last JS-based line;
140
+ `typescript-eslint` does not support the native 7.0 compiler and aborts the entire lint
141
+ run under it. Dependabot is configured to never propose a TypeScript major.
142
+
115
143
  ## Versioning
116
144
 
117
145
  SemVer. Consuming apps pull new versions with `npm update @efa-one/sdk`.
@@ -0,0 +1,14 @@
1
+ export type DateInput = Date | string | number | null | undefined;
2
+ export interface DateFormatOptions {
3
+ locale?: string;
4
+ timeZone?: string;
5
+ fallback?: string;
6
+ withSeconds?: boolean;
7
+ }
8
+ export declare const DEFAULT_DATE_LOCALE = "de-DE";
9
+ export declare const DEFAULT_DATE_FALLBACK = "\u2014";
10
+ export declare function localeForLanguage(language?: string | null): string;
11
+ export declare function formatDate(value: DateInput, opts?: DateFormatOptions): string;
12
+ export declare function formatTime(value: DateInput, opts?: DateFormatOptions): string;
13
+ export declare function formatDateTime(value: DateInput, opts?: DateFormatOptions): string;
14
+ export declare function formatFileStamp(value: DateInput, opts?: Pick<DateFormatOptions, 'timeZone'>): string;
@@ -0,0 +1,64 @@
1
+ export const DEFAULT_DATE_LOCALE = 'de-DE';
2
+ export const DEFAULT_DATE_FALLBACK = '—';
3
+ export function localeForLanguage(language) {
4
+ const lang = (language ?? '').trim().toLowerCase();
5
+ if (!lang)
6
+ return DEFAULT_DATE_LOCALE;
7
+ if (lang === 'de' || lang.startsWith('de-'))
8
+ return DEFAULT_DATE_LOCALE;
9
+ if (lang === 'en' || lang.startsWith('en-'))
10
+ return 'en-US';
11
+ return language;
12
+ }
13
+ function toDate(value) {
14
+ if (value === null || value === undefined || value === '')
15
+ return null;
16
+ const d = value instanceof Date ? value : new Date(value);
17
+ return Number.isNaN(d.getTime()) ? null : d;
18
+ }
19
+ function base(opts) {
20
+ return opts?.timeZone ? { timeZone: opts.timeZone } : {};
21
+ }
22
+ function run(value, opts, fmt) {
23
+ const d = toDate(value);
24
+ if (!d)
25
+ return opts?.fallback ?? DEFAULT_DATE_FALLBACK;
26
+ return new Intl.DateTimeFormat(opts?.locale ?? DEFAULT_DATE_LOCALE, { ...base(opts), ...fmt }).format(d);
27
+ }
28
+ export function formatDate(value, opts) {
29
+ return run(value, opts, { day: '2-digit', month: '2-digit', year: 'numeric' });
30
+ }
31
+ export function formatTime(value, opts) {
32
+ return run(value, opts, {
33
+ hour: '2-digit',
34
+ minute: '2-digit',
35
+ ...(opts?.withSeconds ? { second: '2-digit' } : {}),
36
+ hourCycle: 'h23',
37
+ });
38
+ }
39
+ export function formatDateTime(value, opts) {
40
+ return run(value, opts, {
41
+ day: '2-digit',
42
+ month: '2-digit',
43
+ year: 'numeric',
44
+ hour: '2-digit',
45
+ minute: '2-digit',
46
+ ...(opts?.withSeconds ? { second: '2-digit' } : {}),
47
+ hourCycle: 'h23',
48
+ });
49
+ }
50
+ export function formatFileStamp(value, opts) {
51
+ const d = toDate(value) ?? new Date();
52
+ const parts = new Intl.DateTimeFormat('en-CA', {
53
+ ...(opts?.timeZone ? { timeZone: opts.timeZone } : {}),
54
+ year: 'numeric',
55
+ month: '2-digit',
56
+ day: '2-digit',
57
+ hour: '2-digit',
58
+ minute: '2-digit',
59
+ second: '2-digit',
60
+ hourCycle: 'h23',
61
+ }).formatToParts(d);
62
+ const p = (type) => parts.find((x) => x.type === type)?.value ?? '';
63
+ return `${p('year')}-${p('month')}-${p('day')}_${p('hour')}-${p('minute')}-${p('second')}`;
64
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './ipc.js';
2
+ export * from './format.js';
2
3
  export { useIsMobile, MOBILE_QUERY } from './useIsMobile.js';
3
4
  export * from './i18n.js';
4
5
  export { default as DevHeader } from './DevHeader.js';
package/frontend/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './ipc.js';
2
+ export * from './format.js';
2
3
  export { useIsMobile, MOBILE_QUERY } from './useIsMobile.js';
3
4
  export * from './i18n.js';
4
5
  export { default as DevHeader } from './DevHeader.js';
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
3
  import * as DropdownMenu from './DropdownMenu.js';
4
4
  import { ChevronDown, ChevronUp, ChevronRight, Settings, EyeOff, Filter, RotateCcw, ArrowUp, ArrowDown, Layers, X } from 'lucide-react';
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Pencil } from 'lucide-react';
4
4
  import { Dialog, DialogClose } from './Dialog.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efa-one/sdk",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",
@@ -43,8 +43,10 @@
43
43
  "build:backend": "tsc -p tsconfig.backend.json",
44
44
  "build:frontend": "tsc -p tsconfig.frontend.json && node scripts/postbuild-frontend.mjs",
45
45
  "build": "npm run clean && npm run build:backend && npm run build:frontend",
46
- "typecheck": "tsc -p tsconfig.backend.json --noEmit && tsc -p tsconfig.frontend.json --noEmit",
46
+ "typecheck": "tsc -p tsconfig.backend.json --noEmit && tsc -p tsconfig.frontend.json --noEmit && tsc -p tsconfig.test.json --noEmit",
47
+ "lint": "eslint src test scripts",
47
48
  "test": "vitest run",
49
+ "test:coverage": "vitest run --coverage",
48
50
  "prepublishOnly": "npm run build"
49
51
  },
50
52
  "publishConfig": {
@@ -103,28 +105,33 @@
103
105
  }
104
106
  },
105
107
  "devDependencies": {
108
+ "@eslint/js": "^9.39.5",
106
109
  "@radix-ui/react-dialog": "^1.1.4",
107
110
  "@radix-ui/react-dropdown-menu": "^2.1.4",
108
111
  "@radix-ui/react-tooltip": "^1.1.6",
109
112
  "@types/express": "^4.17.21",
110
113
  "@types/jsonwebtoken": "^9.0.6",
111
- "cookie-parser": "^1.4.6",
112
- "lucide-react": "^0.468.0",
113
114
  "@types/node": "^20.14.0",
114
115
  "@types/pg": "^8.11.0",
115
116
  "@types/react": "^18.2.46",
117
+ "@types/react-dom": "^18.2.0",
116
118
  "@types/supertest": "^7.2.0",
119
+ "@vitest/coverage-v8": "^4.1.11",
120
+ "cookie-parser": "^1.4.6",
121
+ "eslint": "^9.39.5",
122
+ "eslint-plugin-react-hooks": "^7.1.1",
117
123
  "express": "^4.19.2",
118
124
  "i18next": "^23.11.0",
119
125
  "i18next-http-backend": "^2.7.3",
120
126
  "jsonwebtoken": "^9.0.2",
127
+ "lucide-react": "^0.577.0",
121
128
  "pg": "^8.12.0",
122
129
  "react": "^18.2.0",
123
130
  "react-dom": "^18.2.0",
124
- "@types/react-dom": "^18.2.0",
125
131
  "react-i18next": "^13.5.0",
126
132
  "supertest": "^7.2.2",
127
- "typescript": "7.0.2",
133
+ "typescript": "6.0.3",
134
+ "typescript-eslint": "^8.67.0",
128
135
  "vitest": "^4.1.4"
129
136
  },
130
137
  "engines": {