@wssto2/vue-core 0.14.0 → 0.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/CHANGELOG.md +21 -0
- package/README.md +2 -2
- package/bin/architecture/engine.mjs +11 -1
- package/bin/architecture/rules.mjs +95 -0
- package/bin/commands/check-architecture.mjs +8 -1
- package/bin/config.mjs +2 -0
- package/dist/{AccessGate.vue_vue_type_script_setup_true_lang-DEzXovsY.js → AccessGate.vue_vue_type_script_setup_true_lang-BH3f0hda.js} +1 -1
- package/dist/{Activity-C0HUEDUz.js → Activity-MOAIv6s4.js} +3 -3
- package/dist/{Changes-CSaHgVuz.js → Changes-Bu7D0dhY.js} +2 -2
- package/dist/{CollectionPage.vue_vue_type_script_setup_true_lang-dIrdS_Gn.js → CollectionPage.vue_vue_type_script_setup_true_lang-D5HsYIck.js} +2 -2
- package/dist/{DeadLetters-CHX4Ypgy.js → DeadLetters-DsujMnLG.js} +4 -4
- package/dist/FormGroup.vue_vue_type_script_setup_true_lang-CTVuhEbW.js +428 -0
- package/dist/{FormView.vue_vue_type_script_setup_true_lang-DvbPEiNQ.js → FormView.vue_vue_type_script_setup_true_lang-Cfvh7rF6.js} +1 -1
- package/dist/{General-v_24-zZw.js → General-Dswj1oXt.js} +6 -6
- package/dist/{IncidentRecord-BwSBNE37.js → IncidentRecord-DcmqpziF.js} +7 -7
- package/dist/{IncidentsList-BqzRU5cZ.js → IncidentsList-BRNqHZdU.js} +3 -3
- package/dist/{NewRolePage-Dt0O0VSI.js → NewRolePage-dFLznJGH.js} +2 -2
- package/dist/{PersonAccess.vue_vue_type_script_setup_true_lang-CWCt9_dM.js → PersonAccess.vue_vue_type_script_setup_true_lang-D3yp43o5.js} +4 -4
- package/dist/{ProfilePage-DApTxq8C.js → ProfilePage-BnHR3xGB.js} +8 -8
- package/dist/{RoleEditor.vue_vue_type_script_setup_true_lang-d_aaeXTT.js → RoleEditor.vue_vue_type_script_setup_true_lang-DNF9wzE9.js} +4 -4
- package/dist/{RolePage-noy-wmNc.js → RolePage-BtM2rFLm.js} +5 -5
- package/dist/{RolesPage-NRRainCw.js → RolesPage-MO1gAfdT.js} +3 -3
- package/dist/{SectionNavigator.vue_vue_type_script_setup_true_lang-D_08MS1-.js → SectionNavigator.vue_vue_type_script_setup_true_lang-TMdsmYAg.js} +1 -1
- package/dist/ServiceAccountDialog.vue_vue_type_script_setup_true_lang-Bo3phlqB.js +127 -0
- package/dist/{ServiceAccountRecord-D_HduQoK.js → ServiceAccountRecord-C1M-dcju.js} +6 -6
- package/dist/{ServiceAccountsList-CdSsqTGt.js → ServiceAccountsList-zfyS2WJB.js} +4 -4
- package/dist/{Sessions-CTjzMfNg.js → Sessions-BYBUSqHI.js} +3 -3
- package/dist/{SettingsPage-CpqASf_D.js → SettingsPage-C9Kp2ddX.js} +5 -5
- package/dist/{SignIn-D6tWsKf0.js → SignIn-HBYLSZ1M.js} +4 -4
- package/dist/{SignInPage-BCNGI871.js → SignInPage-CEoz3fWE.js} +3 -3
- package/dist/{SignIns-BxGGA99u.js → SignIns-BAz7V4vy.js} +2 -2
- package/dist/{UserRecord-B0kG0-Tm.js → UserRecord-DkyhGma-.js} +8 -8
- package/dist/{UsersList-KMgImERy.js → UsersList-CsW-jU0m.js} +3 -3
- package/dist/access/RestrictedPanel.vue.d.ts +20 -0
- package/dist/access/index.d.ts +1 -0
- package/dist/access/index.js +91 -31
- package/dist/app/application.d.ts +2 -2
- package/dist/app/current.d.ts +6 -0
- package/dist/app/index.d.ts +1 -1
- package/dist/app/index.js +17 -16
- package/dist/{application-CrXtiGdw.js → application-2gHiwAVy.js} +5 -5
- package/dist/client/client.d.ts +6 -0
- package/dist/client/index.js +1 -1
- package/dist/client-Do8v1I5l.js +193 -0
- package/dist/collection/index.js +4 -4
- package/dist/config/index.d.ts +21 -1
- package/dist/content/StatSummary.vue.d.ts +40 -0
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.js +245 -176
- package/dist/{contributions-obnyn03x.js → contributions-9CPT-Z5v.js} +1 -1
- package/dist/{current-B0lHKTWr.js → current-CslcEPXp.js} +8 -4
- package/dist/eslint/index.js +2 -2
- package/dist/form/index.js +8 -8
- package/dist/form/useForm.d.ts +1 -1
- package/dist/{guards-DGcSfTEh.js → guards-6fJxfUtk.js} +1 -1
- package/dist/{http-DOEfFCDA.js → http-D6qOS6xs.js} +1 -1
- package/dist/i18n/index.d.ts +5 -1
- package/dist/i18n/index.js +14 -13
- package/dist/i18n/serverMessages.d.ts +15 -1
- package/dist/identity/index.js +2 -2
- package/dist/incidents/index.js +1 -1
- package/dist/{index-D-2NNye6.js → index--AF_DNhE.js} +20 -20
- package/dist/{index-DNeIZpXo.js → index-DYva-K_4.js} +4 -4
- package/dist/{index-Crz-j_Hu.js → index-Dlu-PA4W.js} +105 -105
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{navigation-CctcxT51.js → navigation-DMJ6akat.js} +1 -1
- package/dist/{neighbors-Csfan8Ho.js → neighbors-DrEahe7B.js} +1 -1
- package/dist/notifications/index.js +1 -1
- package/dist/onboarding/index.js +1 -1
- package/dist/page/index.js +1 -1
- package/dist/phone/index.d.ts +1 -1
- package/dist/phone/index.js +153 -141
- package/dist/phone/phone.d.ts +6 -0
- package/dist/platform/index.js +3 -3
- package/dist/{platform-DtISOEt7.js → platform-ORzRR3Tw.js} +1 -1
- package/dist/resource/index.js +1 -1
- package/dist/router/index.js +3 -3
- package/dist/{sections-fkW_IJLc.js → sections-DI_JhgQG.js} +1 -1
- package/dist/serverMessages-Ce8-MK4e.js +30 -0
- package/dist/shell/index.js +4 -4
- package/dist/{state-CXMexi0l.js → state-QDaxntgR.js} +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{useCommand-Ch_nx1AQ.js → useCommand-BvcU3AlW.js} +1 -1
- package/dist/{useGroupSheet-CPjHydQV.js → useGroupSheet-BGIntPKD.js} +1 -1
- package/dist/{usePreferences-B8LIZp_f.js → usePreferences-BfMXVbSO.js} +1 -1
- package/dist/{useRecordDialog-DdkauXnU.js → useRecordDialog-q_BNXdb7.js} +1 -1
- package/dist/{useResourceForm-Dl6YFYyz.js → useResourceForm-C1S628UI.js} +1 -1
- package/package.json +1 -1
- package/dist/FormGroup.vue_vue_type_script_setup_true_lang-D6jYfNjW.js +0 -427
- package/dist/ServiceAccountDialog.vue_vue_type_script_setup_true_lang-DDcaju7L.js +0 -127
- package/dist/client-Dg303Pmc.js +0 -193
- package/dist/serverMessages-DnTqVS3h.js +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
What arv-next kept to itself and the library can say once: server field messages, a WhatsApp link, a stat card and the "no access" panel, two architecture rules and the request helpers every `api.ts` redefined. go-core is unchanged (`v1.19.0`). Breaking only in that `useForm` now words go-core's `validation_errors.*` keys itself (see Changed).
|
|
6
|
+
|
|
7
|
+
### New
|
|
8
|
+
- **i18n:** `useServerMessages({ prefixes })`. A field message that starts with one of the prefixes (default `["validation_errors"]`, go-core's) is a key: the application's text for it, else the library's `core.<key>`; a rule whose text needs a parameter the server does not send (`min`, `max`, `min_value`, `max_value`, `between`, `in`, `not_in`) reads as `<prefix>.out_of_range`. `fieldMessageWith(translator, options)` is the same outside setup. New text `core.validation_errors.out_of_range` in hr, en, sl, bs and sr-Latn.
|
|
9
|
+
- **phone:** `whatsAppLink(value, country)`: a `https://wa.me/` link for a number as typed; a local number gets the dial code of `country` (ISO code, any case), a number with `+` or `00` keeps its own, fewer than six digits give `null`.
|
|
10
|
+
- **content:** `StatSummary` (and `StatItem`, `StatTone`): one grouped card of numbers with tones, share bars, and header and footer rows.
|
|
11
|
+
- **access:** `RestrictedPanel`: the lock, `core.no_access` text, an optional `contact` address and a Back button (the application's history, else `home`). For `createApplication({ noAccess: () => h(RestrictedPanel, { contact }) })`. New texts `core.no_access.contact` and `core.no_access.back` in five languages.
|
|
12
|
+
- **client:** `RequestOptions.idempotencyKey`: sent as the `Idempotency-Key` header of that call (it wins over a header of the same name) and again on the renew-and-retry after a 401. The library's incident and service-account calls use it.
|
|
13
|
+
- **app:** `currentHttp()`: the current application's platform client, for an `api.ts`.
|
|
14
|
+
- **devkit:** `vue-core check-architecture` rule `no-hardcoded-copy` (`architecture.copy: { pattern, scope, properNames }`, allowlist `allow["no-hardcoded-copy"]`): no text in the authors' language in a template, in `form.ts` strings or, inside `scope`, in any string literal or copy attribute. It is arv-next's own heuristic, ported unchanged (checked against it on 951 files of its tree).
|
|
15
|
+
- **devkit:** rule `no-route-policy` (`architecture.routeAccess: { sessionField, exclude }`): no `policy:` / `requires:` in a `routes.ts`, and no `authStore.<sessionField>` / `state.<sessionField>`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **form:** a form with no `describeFieldError` of its own and none from the application now words go-core's `validation_errors.*` keys (it showed them as sent before). Other messages still show as sent.
|
|
19
|
+
- **eslint:** `vueCoreConfig` gives `tests/**/*.{ts,vue}`, as well as `**/*.test.ts`, the vitest globals and the stub-component allowances.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- Moved from arv-next's e2e: the saved-view save popover (Replace on a taken name), and the profile code step (resend countdown, tries left, expiry, the refusal sentences).
|
|
23
|
+
|
|
3
24
|
## 0.14.0 — 2026-10-19
|
|
4
25
|
|
|
5
26
|
An incident's occurrences for the staff, and Bosnian month names. Needs go-core v1.19.0 (`goCore`, `src/modules` synced).
|
package/README.md
CHANGED
|
@@ -11,12 +11,12 @@ Its plan and rules are in [PLAN.md](PLAN.md).
|
|
|
11
11
|
| Import | For |
|
|
12
12
|
|---|---|
|
|
13
13
|
| `@wssto2/vue-core/platform` | `readBootstrap`, `createPlatform` (HTTP client, session, access), `AccessGate`, typed contexts |
|
|
14
|
-
| `@wssto2/vue-core/app` | `createApplication`, `defineFeature`, `currentApplication` and `application.report` (outside setup, handled errors), shell outlets, effects |
|
|
14
|
+
| `@wssto2/vue-core/app` | `createApplication`, `defineFeature`, `currentApplication`, `currentHttp` and `application.report` (outside setup, handled errors), shell outlets, effects |
|
|
15
15
|
| `@wssto2/vue-core/shell` | `backofficeShell`, the shell's parts for a custom one |
|
|
16
16
|
| `@wssto2/vue-core/router` | `defineRoutes` (typed targets), guards, navigation |
|
|
17
17
|
| `@wssto2/vue-core/collection` | `defineCollection`, `useCollection`, `CollectionPage`, `DataTable` (a table over an array), record neighbors |
|
|
18
18
|
| `@wssto2/vue-core/resource`, `/page` | `useRouteResource`, `ResourcePage`, `SectionNavigator`, `AdaptivePageShell` |
|
|
19
|
-
| `@wssto2/vue-core/button`, `/controls`, `/content`, `/state`, `/overlay`, `/modal`, `/icon` | the primitives |
|
|
19
|
+
| `@wssto2/vue-core/button`, `/controls`, `/content`, `/state`, `/overlay`, `/modal`, `/icon` | the primitives (`/content` includes `Panel`, `KeyValueList`, `StatSummary`, the grouped card of numbers) |
|
|
20
20
|
| `@wssto2/vue-core/i18n`, `/format`, `/client` | messages, `useDescribeError`, formatting, `HttpClient` and `ApiError` |
|
|
21
21
|
| `@wssto2/vue-core/identity`, `/access` | go-core's module contracts (types, route tables) and, in `/identity`, `identityFeature` (sign-in page, language), `identityPlatform()` and `usersFeature` (users, "my profile") and `serviceAccountsFeature` (service accounts and their API keys): [sign-in](docs/recipes/sign-in.md), [users](docs/recipes/users.md), [service accounts](docs/recipes/service-accounts.md) |
|
|
22
22
|
| `@wssto2/vue-core/access` (screens) | `accessFeature({ catalogue })`: roles list and editor, compare, replace, a person's roles (`PersonAccess`) and what they can do: [roles and access](docs/recipes/access.md) |
|
|
@@ -4,6 +4,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import { PACKS } from "./packs.mjs";
|
|
7
|
+
import { builtInRules } from "./rules.mjs";
|
|
7
8
|
|
|
8
9
|
const SOURCE_EXTENSIONS = new Set([".ts", ".vue"]);
|
|
9
10
|
const toPosix = (value) => value.split(path.sep).join("/");
|
|
@@ -24,6 +25,7 @@ export function check({ root, config, configName }) {
|
|
|
24
25
|
const docRef = (key, join = " (") => (doc[key] ? `${join}${doc[key]}${join === " (" ? ")" : ""}` : "");
|
|
25
26
|
const packs = config.packs.map((name) => PACKS[name]);
|
|
26
27
|
const packRules = packs.flatMap((pack, index) => pack.rules.map((rule) => ({ ...rule, pack: packs[index], seen: new Set() })));
|
|
28
|
+
const builtIns = builtInRules(config);
|
|
27
29
|
const featuresRoot = path.join(root, featuresDir);
|
|
28
30
|
const violations = [];
|
|
29
31
|
const warnings = [];
|
|
@@ -99,6 +101,14 @@ export function check({ root, config, configName }) {
|
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
// the built-in rules with options (`copy`, `routeAccess`)
|
|
105
|
+
for (const rule of builtIns) {
|
|
106
|
+
const message = rule.check({ file, content, isVue, isTest });
|
|
107
|
+
if (!message) continue;
|
|
108
|
+
if (allowed(rule.id).has(file)) rule.seen.add(file);
|
|
109
|
+
else violations.push(`${file}: ${rule.id} — ${message}${docRef(rule.id)}.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
102
112
|
// a failed request is vue-core's `ApiError`; a second class makes `isApiError` miss the errors of the other client
|
|
103
113
|
if (/\bclass\s+\w*ApiError\b/.test(content)) {
|
|
104
114
|
violations.push(
|
|
@@ -124,7 +134,7 @@ export function check({ root, config, configName }) {
|
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
// a stale allowlist entry (the file no longer trips the rule) is a violation: the lists only shrink
|
|
127
|
-
for (const rule of packRules) {
|
|
137
|
+
for (const rule of [...packRules, ...builtIns]) {
|
|
128
138
|
for (const entry of allowed(rule.id)) {
|
|
129
139
|
if (!rule.seen.has(entry)) {
|
|
130
140
|
violations.push(`${entry}: stale ${rule.id} allowlist entry in ${configName} — the file no longer trips the rule; remove it from the allowlist.`);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// The built-in rules `vue-core check-architecture` runs when the config has their section (`copy`, `routeAccess`). Unlike the packs
|
|
2
|
+
// they take options, and like the packs they have an `id`, a per-rule allowlist (`allow[<id>]`, a ratchet in both directions) and
|
|
3
|
+
// a `doc` reference. `check(source)` returns the text that follows the id in the report, or null.
|
|
4
|
+
|
|
5
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
+
// `String.search` ignores a `g` flag's lastIndex, so a pattern given with one still gives the same answer on every line.
|
|
7
|
+
const matches = (pattern, text) => text.search(pattern) !== -1;
|
|
8
|
+
|
|
9
|
+
const literals = (code) => code.match(/(["'`])(?:\\.|(?!\1).)*\1/g) ?? [];
|
|
10
|
+
const codeWithoutComments = (code) =>
|
|
11
|
+
code
|
|
12
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
13
|
+
.split("\n")
|
|
14
|
+
.map((line) => line.replace(/(^|[^:"'`\\])\/\/.*$/, "$1"))
|
|
15
|
+
.join("\n");
|
|
16
|
+
const withoutComments = (code) => code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`])\/\/.*$/gm, "$1");
|
|
17
|
+
|
|
18
|
+
const COPY_ATTRIBUTE = /(?<![\w-])(:|v-bind:)?(placeholder|title|aria-label|label)="([^"]*)"/g;
|
|
19
|
+
|
|
20
|
+
/** The literals a user can read in a source file: a `.vue` file's `<script>` strings and its template's copy attributes (a bound one by its quoted parts). */
|
|
21
|
+
function copyLiterals(content, isVue) {
|
|
22
|
+
const scripts = isVue ? [...content.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)].map((m) => m[1]) : [content];
|
|
23
|
+
const found = scripts.flatMap((script) => literals(codeWithoutComments(script)));
|
|
24
|
+
if (isVue) {
|
|
25
|
+
const template = (content.match(/<template>([\s\S]*)<\/template>/)?.[1] ?? "").replace(/<!--[\s\S]*?-->/g, "");
|
|
26
|
+
for (const [, bound, , value] of template.matchAll(COPY_ATTRIBUTE)) found.push(...(bound ? literals(value) : [value]));
|
|
27
|
+
}
|
|
28
|
+
return found;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* no-hardcoded-copy: the first line of user-facing text that `pattern` (a language's letters or words) matches. Everywhere that is a
|
|
33
|
+
* `.vue` template's text nodes and the quoted strings of `form.ts`, `*Form.ts` and files under a `forms/` folder; inside `scope`
|
|
34
|
+
* (default the UI folders) also every string literal of a `.ts` or `<script>` and a template's placeholder / title / aria-label / label.
|
|
35
|
+
* `properNames` is cut out of what `scope` scans (brand names that use the language's letters). A test is never scanned.
|
|
36
|
+
*/
|
|
37
|
+
function noHardcodedCopy({ pattern, scope, properNames }) {
|
|
38
|
+
return ({ file, content, isVue, isTest }) => {
|
|
39
|
+
if (isTest) return null;
|
|
40
|
+
let scanText = null;
|
|
41
|
+
if (isVue) {
|
|
42
|
+
const tpl = content.match(/<template>([\s\S]*)<\/template>/);
|
|
43
|
+
if (tpl) scanText = tpl[1].replace(/<!--[\s\S]*?-->/g, "").replace(/\{\{[\s\S]*?\}\}/g, "").replace(/<[^>]+>/g, "\n");
|
|
44
|
+
} else if (file.endsWith(".ts")) {
|
|
45
|
+
const segments = file.split("/");
|
|
46
|
+
const base = segments[segments.length - 1];
|
|
47
|
+
if (base === "form.ts" || /Form\.ts$/.test(base) || segments.includes("forms")) {
|
|
48
|
+
const noComments = content
|
|
49
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
50
|
+
.split("\n")
|
|
51
|
+
.filter((line) => !/^\s*\/\//.test(line))
|
|
52
|
+
.join("\n");
|
|
53
|
+
scanText = literals(noComments).join("\n");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (matches(scope, file)) {
|
|
57
|
+
scanText = [scanText ?? "", ...copyLiterals(content, isVue)].join("\n");
|
|
58
|
+
if (properNames) scanText = scanText.replace(properNames, "");
|
|
59
|
+
}
|
|
60
|
+
const hit = scanText
|
|
61
|
+
?.split("\n")
|
|
62
|
+
.map((line) => line.trim())
|
|
63
|
+
.find((line) => line.length >= 3 && matches(pattern, line));
|
|
64
|
+
return hit ? `hardcoded copy in a user-facing string (“${hit.slice(0, 60)}”) — move it to an i18n key` : null;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* no-route-policy: a route names its permission in `meta.permission`; `policy:` / `requires:` in a `routes.ts` and the session's
|
|
70
|
+
* old list (`authStore.<sessionField>`, `state.<sessionField>`) are the model it replaced. Comments, tests and `exclude`d files are not scanned.
|
|
71
|
+
*/
|
|
72
|
+
function noRoutePolicy({ sessionField, exclude }) {
|
|
73
|
+
const oldList = sessionField ? new RegExp(`\\b(?:authStore|state)\\.${escape(sessionField)}\\b`) : null;
|
|
74
|
+
return ({ file, content, isTest }) => {
|
|
75
|
+
if (isTest || (exclude && matches(exclude, file))) return null;
|
|
76
|
+
const isRouteFile = /(^|\/)routes\.ts$/.test(file);
|
|
77
|
+
const hit = withoutComments(content)
|
|
78
|
+
.split("\n")
|
|
79
|
+
.find((line) => (isRouteFile && /^\s*(policy|requires)\s*:/.test(line)) || (oldList && oldList.test(line)));
|
|
80
|
+
return hit ? `“${hit.trim().slice(0, 80)}” — a route needs \`meta.permission\` (a catalogued permission, or an array of them)${sessionField ? `, and the session holds \`access\`, not a \`${sessionField}\` list` : ""}` : null;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const RULE_IDS = ["no-hardcoded-copy", "no-route-policy"];
|
|
85
|
+
|
|
86
|
+
/** The rules the config switches on, each `{ id, check, seen }` (`seen`: the allowlisted files that tripped it). */
|
|
87
|
+
export function builtInRules(config) {
|
|
88
|
+
const rules = [];
|
|
89
|
+
if (config.copy) {
|
|
90
|
+
const scope = config.copy.scope ?? new RegExp(`^(?:${config.uiDirs.map(escape).join("|")})/`);
|
|
91
|
+
rules.push({ id: "no-hardcoded-copy", check: noHardcodedCopy({ ...config.copy, scope }) });
|
|
92
|
+
}
|
|
93
|
+
if (config.routeAccess) rules.push({ id: "no-route-policy", check: noRoutePolicy(config.routeAccess) });
|
|
94
|
+
return rules.map((rule) => ({ ...rule, seen: new Set() }));
|
|
95
|
+
}
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
import { check } from "../architecture/engine.mjs";
|
|
8
8
|
import { PACKS } from "../architecture/packs.mjs";
|
|
9
|
+
import { RULE_IDS } from "../architecture/rules.mjs";
|
|
9
10
|
|
|
10
11
|
const ALLOW_RULES = ["deep-import", "raw-http", "feature-name", "feature-shape"];
|
|
11
12
|
const DOC_KEYS = ["shared-layer", "api-error"];
|
|
@@ -16,7 +17,13 @@ export default function run({ config, root, configPath, err }) {
|
|
|
16
17
|
|
|
17
18
|
const problems = [];
|
|
18
19
|
for (const name of settings.packs) if (!Object.hasOwn(PACKS, name)) problems.push(`packs: unknown pack "${name}"; the packs are ${Object.keys(PACKS).join(", ")}`);
|
|
19
|
-
const packRules =
|
|
20
|
+
const packRules = [
|
|
21
|
+
...settings.packs.filter((name) => Object.hasOwn(PACKS, name)).flatMap((name) => PACKS[name].rules.map((rule) => rule.id)),
|
|
22
|
+
...RULE_IDS.filter((id) => (id === "no-hardcoded-copy" ? settings.copy : settings.routeAccess)),
|
|
23
|
+
];
|
|
24
|
+
if (settings.copy && !(settings.copy.pattern instanceof RegExp)) problems.push("copy.pattern: a regular expression for the language's letters and words, e.g. /[čćžšđ]|\\b(Spremi|Odustani)\\b/");
|
|
25
|
+
for (const key of ["scope", "properNames"]) if (settings.copy?.[key] !== undefined && !(settings.copy[key] instanceof RegExp)) problems.push(`copy.${key}: must be a regular expression`);
|
|
26
|
+
if (settings.routeAccess?.exclude !== undefined && !(settings.routeAccess.exclude instanceof RegExp)) problems.push("routeAccess.exclude: must be a regular expression");
|
|
20
27
|
for (const rule of Object.keys(settings.allow)) {
|
|
21
28
|
if (!ALLOW_RULES.includes(rule) && !packRules.includes(rule)) problems.push(`allow.${rule}: no such rule; the allowlists are ${[...ALLOW_RULES, ...packRules].join(", ")}`);
|
|
22
29
|
}
|
package/bin/config.mjs
CHANGED
|
@@ -30,6 +30,8 @@ export const DEFAULTS = {
|
|
|
30
30
|
packs: [],
|
|
31
31
|
rules: [],
|
|
32
32
|
doc: {},
|
|
33
|
+
copy: null,
|
|
34
|
+
routeAccess: null,
|
|
33
35
|
},
|
|
34
36
|
generator: { template: join(dirname(fileURLToPath(import.meta.url)), "templates", "feature-crud"), out: "features" },
|
|
35
37
|
e2eTimings: { report: "e2e-timings/results.json" },
|
|
@@ -5,15 +5,15 @@ import { _ as T } from "./IconTile.vue_vue_type_script_setup_true_lang-BmxCuUZ9.
|
|
|
5
5
|
import { _ as z } from "./Tabs.vue_vue_type_script_setup_true_lang-tpkGp98F.js";
|
|
6
6
|
import { u as G } from "./format-Cp1h0vsC.js";
|
|
7
7
|
import { _ as B } from "./DateField.vue_vue_type_script_setup_true_lang-CKKG4JEg.js";
|
|
8
|
-
import { a as H } from "./FormGroup.vue_vue_type_script_setup_true_lang-
|
|
8
|
+
import { a as H } from "./FormGroup.vue_vue_type_script_setup_true_lang-CTVuhEbW.js";
|
|
9
9
|
import { g as J } from "./options-B48UiYsG.js";
|
|
10
10
|
import { i as M } from "./preferences-hYhrw8Uy.js";
|
|
11
|
-
import { u as Q } from "./platform-
|
|
11
|
+
import { u as Q } from "./platform-ORzRR3Tw.js";
|
|
12
12
|
import { b as W } from "./routeResource-DlTsGTg_.js";
|
|
13
13
|
import { _ as X } from "./AsyncSection.vue_vue_type_script_setup_true_lang-DGzQ-Snp.js";
|
|
14
14
|
import { _ as Y } from "./Badge.vue_vue_type_script_setup_true_lang-DJyZc0C7.js";
|
|
15
15
|
import { u as Z } from "./useLoad-M7V-AVwR.js";
|
|
16
|
-
import { P as tt, f as et, j as at } from "./index
|
|
16
|
+
import { P as tt, f as et, j as at } from "./index--AF_DNhE.js";
|
|
17
17
|
import { _ as st } from "./ActionRow.vue_vue_type_script_setup_true_lang-DybAIOHS.js";
|
|
18
18
|
const ot = {
|
|
19
19
|
class: "flex min-w-0 flex-col gap-group-gap",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineComponent as h, ref as m, watch as p, openBlock as l, createElementBlock as d, createVNode as c, unref as t, withCtx as y, toDisplayString as x, createBlock as f, createCommentVNode as k } from "vue";
|
|
2
2
|
import { useI18n as C } from "vue-i18n";
|
|
3
3
|
import { i as $ } from "./preferences-hYhrw8Uy.js";
|
|
4
|
-
import { u as b } from "./platform-
|
|
4
|
+
import { u as b } from "./platform-ORzRR3Tw.js";
|
|
5
5
|
import { b as V } from "./routeResource-DlTsGTg_.js";
|
|
6
6
|
import { _ as R } from "./AsyncSection.vue_vue_type_script_setup_true_lang-DGzQ-Snp.js";
|
|
7
7
|
import { u as E } from "./useLoad-M7V-AVwR.js";
|
|
8
|
-
import { P as N, f as B, k as P, j as S } from "./index
|
|
8
|
+
import { P as N, f as B, k as P, j as S } from "./index--AF_DNhE.js";
|
|
9
9
|
import { _ as U } from "./ActionRow.vue_vue_type_script_setup_true_lang-DybAIOHS.js";
|
|
10
10
|
import { _ as I } from "./ViewTabs.vue_vue_type_script_setup_true_lang-BjdDXE3T.js";
|
|
11
11
|
const W = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { hasInjectionContext as lt, inject as ot, shallowRef as xe, computed as k, watch as Ve, toValue as Se, ref as ie, getCurrentScope as nt, onScopeDispose as st, defineComponent as be, openBlock as u, createElementBlock as y, createVNode as O, unref as t, createElementVNode as i, toDisplayString as g, Fragment as ee, renderList as le, createCommentVNode as F, createBlock as W, mergeProps as ve, withCtx as q, createTextVNode as G, normalizeClass as ue, useTemplateRef as fe, withModifiers as Ke, withDirectives as Le, vModelText as Fe, nextTick as at, onBeforeUnmount as rt, onMounted as it, renderSlot as ge, useSlots as We, createSlots as Qe, normalizeProps as Xe, guardReactiveProps as Je } from "vue";
|
|
2
2
|
import { routerKey as ct } from "vue-router";
|
|
3
3
|
import { i as qe, a as ut } from "./error-CD7x0Jrz.js";
|
|
4
|
-
import { d as dt } from "./http-
|
|
5
|
-
import { d as Re, o as ft, L as Te, e as je, w as pt, r as vt } from "./state-
|
|
4
|
+
import { d as dt } from "./http-D6qOS6xs.js";
|
|
5
|
+
import { d as Re, o as ft, L as Te, e as je, w as pt, r as vt } from "./state-QDaxntgR.js";
|
|
6
6
|
import { _ as mt } from "./AdaptivePageShell.vue_vue_type_script_setup_true_lang-Bsv5j5Z8.js";
|
|
7
7
|
import { useI18n as he } from "vue-i18n";
|
|
8
8
|
import { _ as ce } from "./Button.vue_vue_type_script_setup_true_lang-C74hcb0N.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { defineComponent as B, useTemplateRef as C, computed as y, ref as v, openBlock as D, createBlock as z, unref as r, withCtx as m, createVNode as F, createElementVNode as b, normalizeClass as $, toDisplayString as h } from "vue";
|
|
2
2
|
import { useI18n as q } from "vue-i18n";
|
|
3
3
|
import { a as k } from "./error-CD7x0Jrz.js";
|
|
4
|
-
import { u as V, _ as N } from "./CollectionPage.vue_vue_type_script_setup_true_lang-
|
|
4
|
+
import { u as V, _ as N } from "./CollectionPage.vue_vue_type_script_setup_true_lang-D5HsYIck.js";
|
|
5
5
|
import { u as P } from "./describeError-B-NMa--w.js";
|
|
6
6
|
import { u as I } from "./format-Cp1h0vsC.js";
|
|
7
|
-
import { e as a, R as O } from "./index-
|
|
7
|
+
import { e as a, R as O } from "./index-DYva-K_4.js";
|
|
8
8
|
import { _ as W } from "./AlertDialog.vue_vue_type_script_setup_true_lang-CrFhR9rJ.js";
|
|
9
9
|
import { t as i } from "./toast-CqSDTWRX.js";
|
|
10
10
|
import { u as Y } from "./useLoad-M7V-AVwR.js";
|
|
11
|
-
import { u as j } from "./platform-
|
|
12
|
-
import { d as G, r as H } from "./http-
|
|
11
|
+
import { u as j } from "./platform-ORzRR3Tw.js";
|
|
12
|
+
import { d as G, r as H } from "./http-D6qOS6xs.js";
|
|
13
13
|
function J(u) {
|
|
14
14
|
return G({
|
|
15
15
|
id: "events.deadletters",
|