@abgov/nx-adsp 13.31.0 → 13.31.2
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 +41 -32
- package/package.json +1 -1
- package/src/generators/angular-app/angular-app.js +2 -2
- package/src/generators/angular-app/files/AGENTS.md__tmpl__ +16 -0
- package/src/generators/express-service/express-service.spec.ts +26 -13
- package/src/generators/express-service/schema.json +1 -1
- package/src/generators/react-app/files/AGENTS.md__tmpl__ +16 -0
- package/src/generators/react-app/react-app.js +2 -2
- package/src/generators/vue-admin-crud/files/src/views/__editViewFileName__.vue__tmpl__ +3 -5
- package/src/generators/vue-admin-crud/files/src/views/__listViewFileName__.vue__tmpl__ +33 -4
- package/src/generators/vue-admin-crud/schema.d.ts +2 -0
- package/src/generators/vue-admin-crud/schema.json +7 -8
- package/src/generators/vue-admin-crud/vue-admin-crud.js +6 -1
- package/src/generators/vue-admin-crud/vue-admin-crud.js.map +1 -1
- package/src/generators/vue-admin-crud/vue-admin-crud.spec.ts +96 -13
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +56 -11
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +2 -1
- package/src/generators/vue-app/files/src/composables/useApi.spec.ts__tmpl__ +5 -3
- package/src/generators/vue-app/files/src/composables/useApi.ts__tmpl__ +14 -2
- package/src/generators/vue-app/vue-app.js +33 -6
- package/src/generators/vue-app/vue-app.js.map +1 -1
- package/src/generators/vue-app/vue-app.spec.ts +133 -13
- package/src/generators/vue-components/files/AGENTS.md__tmpl__ +36 -17
- package/src/generators/vue-components/files/src/index.ts__tmpl__ +3 -0
- package/src/generators/vue-components/files/src/lib/formatters.ts__tmpl__ +58 -0
- package/src/generators/vue-components/files/src/lib/patterns/AppSideMenu.vue__tmpl__ +14 -3
- package/src/generators/vue-components/files/src/lib/patterns/FilterBar.vue__tmpl__ +7 -1
- package/src/generators/vue-components/files/src/lib/patterns/WorkspaceTable.vue__tmpl__ +7 -1
- package/src/generators/vue-components/vue-components.d.ts +1 -0
- package/src/generators/vue-components/vue-components.js +18 -39
- package/src/generators/vue-components/vue-components.js.map +1 -1
- package/src/generators/vue-components/vue-components.spec.ts +94 -2
- package/src/generators/vue-detail-view/files/src/views/__viewFileName__.vue__tmpl__ +51 -2
- package/src/generators/vue-detail-view/schema.d.ts +18 -0
- package/src/generators/vue-detail-view/schema.json +2 -2
- package/src/generators/vue-detail-view/vue-detail-view.js +4 -1
- package/src/generators/vue-detail-view/vue-detail-view.js.map +1 -1
- package/src/generators/vue-detail-view/vue-detail-view.spec.ts +36 -6
- package/src/generators/vue-intake-view/files/shared/src/views/__confirmationViewFileName__.vue__tmpl__ +60 -6
- package/src/generators/vue-intake-view/files/shared/src/views/__reviewViewFileName__.vue__tmpl__ +37 -1
- package/src/generators/vue-intake-view/files/steps/src/views/__stepViewFileName__.vue__tmpl__ +1 -1
- package/src/generators/vue-intake-view/schema.d.ts +10 -0
- package/src/generators/vue-intake-view/schema.json +12 -9
- package/src/generators/vue-intake-view/vue-intake-view.js +9 -3
- package/src/generators/vue-intake-view/vue-intake-view.js.map +1 -1
- package/src/generators/vue-intake-view/vue-intake-view.spec.ts +113 -12
- package/src/generators/vue-workspace-view/files/src/views/__viewFileName__.vue__tmpl__ +49 -1
- package/src/generators/vue-workspace-view/schema.d.ts +22 -0
- package/src/generators/vue-workspace-view/schema.json +8 -9
- package/src/generators/vue-workspace-view/vue-workspace-view.js +7 -1
- package/src/generators/vue-workspace-view/vue-workspace-view.js.map +1 -1
- package/src/generators/vue-workspace-view/vue-workspace-view.spec.ts +73 -7
- package/src/utils/paired-project.d.ts +10 -0
- package/src/utils/paired-project.js +125 -18
- package/src/utils/paired-project.js.map +1 -1
- package/src/utils/paired-project.spec.ts +126 -0
- package/src/utils/quality.js +43 -9
- package/src/utils/quality.js.map +1 -1
- package/src/utils/quality.spec.ts +35 -2
- package/src/utils/vue-eslint.d.ts +2 -0
- package/src/utils/vue-eslint.js +48 -0
- package/src/utils/vue-eslint.js.map +1 -0
- package/src/utils/vue-side-menu.d.ts +13 -1
- package/src/utils/vue-side-menu.js +50 -5
- package/src/utils/vue-side-menu.js.map +1 -1
- package/src/utils/vue-side-menu.spec.ts +100 -0
- package/src/utils/vue-tsconfig.d.ts +26 -0
- package/src/utils/vue-tsconfig.js +49 -0
- package/src/utils/vue-tsconfig.js.map +1 -0
package/README.md
CHANGED
|
@@ -172,6 +172,12 @@ The generated Playwright e2e project includes an axe-core accessibility check (`
|
|
|
172
172
|
scoped to WCAG 2.1 A/AA, that runs automatically as part of the `e2e` target — no separate command
|
|
173
173
|
needed. `angular-app` and `vue-app` include the same check.
|
|
174
174
|
|
|
175
|
+
Its coverage is the `ROUTES` array inside that spec, which ships as `['/']` — the only route a
|
|
176
|
+
freshly generated app has. **Add to it as the app gains routes**, from a generator or by hand; no
|
|
177
|
+
generator maintains the list, and `/` is mostly app shell, so it exercises little of what the app
|
|
178
|
+
is actually built from. Expect genuine failures once expanded: some come from inside
|
|
179
|
+
`@abgov/web-components`' shadow DOM and need an upstream fix rather than an app-side change.
|
|
180
|
+
|
|
175
181
|
---
|
|
176
182
|
|
|
177
183
|
### `angular-app`
|
|
@@ -279,7 +285,7 @@ npx nx g @abgov/nx-adsp:vue-detail-view my-app --name=application-detail --resou
|
|
|
279
285
|
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
280
286
|
| `project` | Yes | The `vue-app` project to add the view to |
|
|
281
287
|
| `name` | Yes | View name, e.g. `application-detail` generates `src/views/ApplicationDetailView.vue` |
|
|
282
|
-
| `resource` | Yes | API resource path segment — the view fetches `/api/<resource>/:id`
|
|
288
|
+
| `resource` | Yes | API resource path segment — the view fetches `/api/v1/<resource>/:id` |
|
|
283
289
|
| `route` | Yes | Route path added to `router/index.ts`, e.g. `/applications/:id`. Must contain a `:id` param |
|
|
284
290
|
| `fields` | Yes | JSON array of fields rendered in the record's info list, in display order: `{ key, label, type?: "text"\|"date"\|"currency"\|"badge" }` — a JSON string, not a comma-separated CLI array (see note below) |
|
|
285
291
|
| `heading` | No | Page heading. Defaults to the view name, title-cased |
|
|
@@ -296,18 +302,19 @@ an existing `vue-app` project, built on the shared `WorkspaceTable` pattern comp
|
|
|
296
302
|
npx nx g @abgov/nx-adsp:vue-workspace-view my-app --name=applications --resource=applications --route=/applications --detailRoute=/applications --columns='[{"key":"status","label":"Status","type":"badge","sortable":true}]'
|
|
297
303
|
```
|
|
298
304
|
|
|
299
|
-
| Option | Required | Description
|
|
300
|
-
| -------------- | -------- |
|
|
301
|
-
| `project` | Yes | The `vue-app` project to add the view to
|
|
302
|
-
| `name` | Yes | View name, e.g. `applications` generates `src/views/ApplicationsListView.vue`
|
|
303
|
-
| `resource` | Yes | API resource path segment — fetches `/api/<resource>?page=&limit=&search=&sortBy=&sortDir=`
|
|
304
|
-
| `route` | Yes | Route path added to `router/index.ts`, e.g. `/applications`
|
|
305
|
-
| `columns` | Yes | JSON array of table columns, in display order: `{ key, label, type?: "text"\|"date"\|"currency"\|"badge", sortable
|
|
306
|
-
| `detailRoute` | No | If set, each row gets a "View" action linking to `` `${detailRoute}/${row.id}` `` — typically a `vue-detail-view`'s route with the `:id` segment dropped
|
|
307
|
-
| `filterable` | No | Whether to generate a debounced search input above the table. Defaults to `true`
|
|
308
|
-
| `heading` | No | Page heading. Defaults to the view name, title-cased
|
|
309
|
-
| `pageSize` | No | Rows per page. Defaults to `20`
|
|
310
|
-
| `
|
|
305
|
+
| Option | Required | Description |
|
|
306
|
+
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
307
|
+
| `project` | Yes | The `vue-app` project to add the view to |
|
|
308
|
+
| `name` | Yes | View name, e.g. `applications` generates `src/views/ApplicationsListView.vue` |
|
|
309
|
+
| `resource` | Yes | API resource path segment — fetches `/api/v1/<resource>?page=&limit=&search=&sortBy=&sortDir=` |
|
|
310
|
+
| `route` | Yes | Route path added to `router/index.ts`, e.g. `/applications` |
|
|
311
|
+
| `columns` | Yes | JSON array of table columns, in display order: `{ key, label, type?: "text"\|"date"\|"currency"\|"badge", sortable?, options?: [{value,label}], badgeMap?: {value: badgeType} }` — supply `options` for any column stored as a code (or the table shows the code), and `badgeMap` on a `badge` column to give each status its colour — a JSON string (see note below) |
|
|
312
|
+
| `detailRoute` | No | If set, each row gets a "View" action linking to `` `${detailRoute}/${row.id}` `` — typically a `vue-detail-view`'s route with the `:id` segment dropped |
|
|
313
|
+
| `filterable` | No | Whether to generate a debounced search input above the table. Defaults to `true` |
|
|
314
|
+
| `heading` | No | Page heading. Defaults to the view name, title-cased |
|
|
315
|
+
| `pageSize` | No | Rows per page. Defaults to `20` |
|
|
316
|
+
| `icon` | No | Ionicon name for the generated side-menu entry. Defaults to `list`. `goa-work-side-menu-item` renders a blank item without one |
|
|
317
|
+
| `requiresAuth` | No | Whether the generated route requires authentication. Defaults to `true` |
|
|
311
318
|
|
|
312
319
|
---
|
|
313
320
|
|
|
@@ -322,16 +329,17 @@ that).
|
|
|
322
329
|
npx nx g @abgov/nx-adsp:vue-admin-crud my-app --name=regions --resource=regions --route=/regions --fields='[{"key":"name","label":"Name"},{"key":"active","label":"Active","type":"checkbox"}]'
|
|
323
330
|
```
|
|
324
331
|
|
|
325
|
-
| Option | Required | Description
|
|
326
|
-
| --------------- | -------- |
|
|
327
|
-
| `project` | Yes | The `vue-app` project to add the views to
|
|
328
|
-
| `name` | Yes | View name, e.g. `regions` generates `src/views/RegionsListView.vue` and `src/views/RegionsEditView.vue`
|
|
329
|
-
| `resource` | Yes | API resource path segment — fetches `/api/<resource>` (list), `/api/<resource>/:id` (load one), `POST /api/<resource>` (create), `PUT /api/<resource>/:id` (update) |
|
|
330
|
-
| `route` | Yes | List route path added to `router/index.ts`, e.g. `/regions`. The edit/create route is added as `` `${route}/:id` `` (visiting `${route}/new` creates)
|
|
331
|
-
| `fields` | Yes | JSON array of fields, in display/form order: `{ key, label, type?: "text"\|"checkbox", required? }` — a JSON string (see note below)
|
|
332
|
-
| `heading` | No | List page heading. Defaults to the view name, title-cased
|
|
333
|
-
| `singularLabel` | No | Singular label used in "Create <label>"/"Edit <label>" headings and buttons. Defaults to `--heading` (override for irregular plurals)
|
|
334
|
-
| `
|
|
332
|
+
| Option | Required | Description |
|
|
333
|
+
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
334
|
+
| `project` | Yes | The `vue-app` project to add the views to |
|
|
335
|
+
| `name` | Yes | View name, e.g. `regions` generates `src/views/RegionsListView.vue` and `src/views/RegionsEditView.vue` |
|
|
336
|
+
| `resource` | Yes | API resource path segment — fetches `/api/v1/<resource>` (list), `/api/v1/<resource>/:id` (load one), `POST /api/v1/<resource>` (create), `PUT /api/v1/<resource>/:id` (update) |
|
|
337
|
+
| `route` | Yes | List route path added to `router/index.ts`, e.g. `/regions`. The edit/create route is added as `` `${route}/:id` `` (visiting `${route}/new` creates) |
|
|
338
|
+
| `fields` | Yes | JSON array of fields, in display/form order: `{ key, label, type?: "text"\|"checkbox", required? }` — a JSON string (see note below) |
|
|
339
|
+
| `heading` | No | List page heading. Defaults to the view name, title-cased |
|
|
340
|
+
| `singularLabel` | No | Singular label used in "Create <label>"/"Edit <label>" headings and buttons. Defaults to `--heading` (override for irregular plurals) |
|
|
341
|
+
| `icon` | No | Ionicon name for the generated side-menu entry. Defaults to `settings`. `goa-work-side-menu-item` renders a blank item without one |
|
|
342
|
+
| `requiresAuth` | No | Whether the generated routes require authentication. Defaults to `true` |
|
|
335
343
|
|
|
336
344
|
---
|
|
337
345
|
|
|
@@ -339,21 +347,22 @@ npx nx g @abgov/nx-adsp:vue-admin-crud my-app --name=regions --resource=regions
|
|
|
339
347
|
|
|
340
348
|
Adds a route-per-step intake wizard (`Stepper` + `StepErrorSummary`, a required read-only
|
|
341
349
|
review step, and a confirmation page) to an existing `vue-app` project. Cross-step state is
|
|
342
|
-
server-persisted — each step PUTs/POSTs to `/api/<resource>/:id` and refetches on mount, so
|
|
350
|
+
server-persisted — each step PUTs/POSTs to `/api/v1/<resource>/:id` and refetches on mount, so
|
|
343
351
|
there's no client-side draft caching. Every field is currently a plain text input.
|
|
344
352
|
|
|
345
353
|
```bash
|
|
346
354
|
npx nx g @abgov/nx-adsp:vue-intake-view my-app --name=application --resource=applications --route=/applications --steps='[{"key":"personal-info","label":"Personal information","fields":[{"key":"fullName","label":"Full name"}]}]'
|
|
347
355
|
```
|
|
348
356
|
|
|
349
|
-
| Option
|
|
350
|
-
|
|
|
351
|
-
| `project`
|
|
352
|
-
| `name`
|
|
353
|
-
| `resource`
|
|
354
|
-
| `route`
|
|
355
|
-
| `steps`
|
|
356
|
-
| `
|
|
357
|
+
| Option | Required | Description |
|
|
358
|
+
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
359
|
+
| `project` | Yes | The `vue-app` project to add the views to |
|
|
360
|
+
| `name` | Yes | Base name for the generated views, e.g. `application` generates `<Step>StepView.vue` per step plus `ApplicationReviewView.vue`/`ApplicationConfirmationView.vue` |
|
|
361
|
+
| `resource` | Yes | API resource path segment. Each step fetches/saves `/api/v1/<resource>/:id`; the review step's Submit posts `/api/v1/<resource>/:id/submit` |
|
|
362
|
+
| `route` | Yes | Base route, e.g. `/applications`. Steps become `/applications/:id/<step-key>`, plus `/review` and `/confirmation`. Start a new intake at `/applications/new/<first-step-key>` |
|
|
363
|
+
| `steps` | Yes | JSON array of steps, in order: `{ key, label, fields: [{ key, label, required? }] }` — a JSON string (see note below) |
|
|
364
|
+
| `referenceField` | No | Field on the submitted record holding the business reference shown on the confirmation page. Defaults to `reference`; falls back to the route id when absent |
|
|
365
|
+
| `requiresAuth` | No | Whether the generated routes require authentication. Defaults to `true` |
|
|
357
366
|
|
|
358
367
|
---
|
|
359
368
|
|
package/package.json
CHANGED
|
@@ -84,13 +84,13 @@ function default_1(host, options) {
|
|
|
84
84
|
(0, quality_1.addAxeAccessibilityCheck)(host, `${normalizedOptions.projectRoot}-e2e`);
|
|
85
85
|
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
86
86
|
'@abgov/angular-components': '5.3.0',
|
|
87
|
-
'@abgov/design-tokens': '2.12.
|
|
87
|
+
'@abgov/design-tokens': '2.12.8',
|
|
88
88
|
// Pin exact (not ^2.0.0): angular-components 5.3.0 imports symbols added in
|
|
89
89
|
// ui-components-common 2.3.0 (e.g. GoabWorkspaceLayoutScrollState), so a
|
|
90
90
|
// lower 2.x resolves and fails the build. Keep this in lockstep with the
|
|
91
91
|
// angular-components version above.
|
|
92
92
|
'@abgov/ui-components-common': '2.3.0',
|
|
93
|
-
'@abgov/web-components': '2.
|
|
93
|
+
'@abgov/web-components': '2.4.0',
|
|
94
94
|
'keycloak-angular': '^19.0.2',
|
|
95
95
|
'keycloak-js': '^23.0.7',
|
|
96
96
|
'zone.js': '~0.15.0',
|
|
@@ -146,6 +146,22 @@ describe('MyFeatureComponent', () => {
|
|
|
146
146
|
});
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
### Accessibility check — expand its route list
|
|
150
|
+
|
|
151
|
+
The generated e2e project ships `src/a11y.spec.ts`: an axe-core check scoped to WCAG 2.1
|
|
152
|
+
A/AA, run as part of the normal `e2e` target. **It only covers the routes named in its own
|
|
153
|
+
`ROUTES` array, which ships as `['/']`.**
|
|
154
|
+
|
|
155
|
+
Add an entry whenever you add a route — whether a generator added it or you hand-edited the
|
|
156
|
+
router. Nothing does this for you: no generator writes to that file (it is left alone once it
|
|
157
|
+
exists so your edits survive), so a router that has grown past `/` while `ROUTES` has not is
|
|
158
|
+
a check that has quietly stopped covering the app. `/` is mostly app shell, so it exercises
|
|
159
|
+
almost none of the elements the rest of the app is built from.
|
|
160
|
+
|
|
161
|
+
Expect real failures when you expand it, and triage before fixing: some violations originate
|
|
162
|
+
inside `@abgov/web-components`' own shadow DOM and cannot be addressed from app code. Those
|
|
163
|
+
need an upstream fix or a documented exclusion, not a workaround in your view.
|
|
164
|
+
|
|
149
165
|
## OpenShift targets
|
|
150
166
|
|
|
151
167
|
```bash
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
addProjectConfiguration,
|
|
3
|
+
readJson,
|
|
4
|
+
readProjectConfiguration,
|
|
5
|
+
writeJson,
|
|
6
|
+
} from '@nx/devkit';
|
|
2
7
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
3
8
|
|
|
4
9
|
import * as utils from '@abgov/nx-oc';
|
|
@@ -375,7 +380,7 @@ http {
|
|
|
375
380
|
}
|
|
376
381
|
`;
|
|
377
382
|
|
|
378
|
-
it('writes vite.proxy.
|
|
383
|
+
it('writes vite.proxy.cjs and patches serve target and nginx.conf for a Vue frontend', async () => {
|
|
379
384
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
380
385
|
addProjectConfiguration(host, 'my-vue-app', {
|
|
381
386
|
root: 'apps/my-vue-app',
|
|
@@ -393,23 +398,33 @@ http {
|
|
|
393
398
|
|
|
394
399
|
await generator(host, { ...options, pairedProject: 'my-vue-app' });
|
|
395
400
|
|
|
396
|
-
// Dev proxy
|
|
397
|
-
|
|
398
|
-
|
|
401
|
+
// Dev proxy module written at the Vue location -- a .cjs module, because
|
|
402
|
+
// Vite rewrites with a function and ignores JSON's `pathRewrite`.
|
|
403
|
+
expect(host.exists('apps/my-vue-app/vite.proxy.cjs')).toBeTruthy();
|
|
404
|
+
const source = host.read('apps/my-vue-app/vite.proxy.cjs').toString();
|
|
405
|
+
const proxyModule = { exports: {} };
|
|
406
|
+
new Function('module', 'exports', source)(
|
|
407
|
+
proxyModule,
|
|
408
|
+
proxyModule.exports,
|
|
409
|
+
);
|
|
410
|
+
const proxyConf = proxyModule.exports as Record<
|
|
411
|
+
string,
|
|
412
|
+
{ target: string; rewrite: (path: string) => string }
|
|
413
|
+
>;
|
|
399
414
|
expect(proxyConf['/api/']).toBeDefined();
|
|
400
415
|
expect(proxyConf['/api/'].target).toBe('http://localhost:3333');
|
|
401
|
-
expect(proxyConf['/api/'].
|
|
416
|
+
expect(proxyConf['/api/'].rewrite('/api/v1/things')).toBe(
|
|
417
|
+
'/test/v1/things',
|
|
418
|
+
);
|
|
402
419
|
|
|
403
420
|
// serve target updated with proxyConfig.
|
|
404
421
|
const frontendConfig = readProjectConfiguration(host, 'my-vue-app');
|
|
405
422
|
expect(frontendConfig.targets.serve.options.proxyConfig).toBe(
|
|
406
|
-
'apps/my-vue-app/vite.proxy.
|
|
423
|
+
'apps/my-vue-app/vite.proxy.cjs',
|
|
407
424
|
);
|
|
408
425
|
|
|
409
426
|
// nginx.conf updated with the proxy block.
|
|
410
|
-
const nginx = host
|
|
411
|
-
.read('apps/my-vue-app/public/nginx.conf')
|
|
412
|
-
.toString();
|
|
427
|
+
const nginx = host.read('apps/my-vue-app/public/nginx.conf').toString();
|
|
413
428
|
expect(nginx).toContain('location /api/');
|
|
414
429
|
expect(nginx).toContain('proxy_pass http://test:3333/test/');
|
|
415
430
|
|
|
@@ -482,9 +497,7 @@ http {
|
|
|
482
497
|
executor: '@angular-devkit/build-angular:browser',
|
|
483
498
|
options: {
|
|
484
499
|
outputPath: 'dist/apps/my-angular-app',
|
|
485
|
-
assets: [
|
|
486
|
-
`apps/my-angular-app/src/silent-check-sso.html`,
|
|
487
|
-
],
|
|
500
|
+
assets: [`apps/my-angular-app/src/silent-check-sso.html`],
|
|
488
501
|
},
|
|
489
502
|
},
|
|
490
503
|
},
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"pairedProject": {
|
|
72
72
|
"type": "string",
|
|
73
|
-
"description": "Name of a frontend app project (vue-app, react-app, or angular-app) to pair with this service. When that frontend already exists in the workspace, the generator automatically wires its nginx proxy config, dev-server proxy file (vite.proxy.
|
|
73
|
+
"description": "Name of a frontend app project (vue-app, react-app, or angular-app) to pair with this service. When that frontend already exists in the workspace, the generator automatically wires its nginx proxy config, dev-server proxy file (vite.proxy.cjs for Vue, proxy.conf.json for React/Angular), serve target proxyConfig, and adsp:proxy-service: tag. Set automatically by composite generators (pevn, pern, pean, mern, mean) — also valid standalone when the frontend was scaffolded first."
|
|
74
74
|
},
|
|
75
75
|
"cors": {
|
|
76
76
|
"type": "boolean",
|
|
@@ -129,6 +129,22 @@ describe('MyFeature', () => {
|
|
|
129
129
|
});
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
### Accessibility check — expand its route list
|
|
133
|
+
|
|
134
|
+
The generated e2e project ships `src/a11y.spec.ts`: an axe-core check scoped to WCAG 2.1
|
|
135
|
+
A/AA, run as part of the normal `e2e` target. **It only covers the routes named in its own
|
|
136
|
+
`ROUTES` array, which ships as `['/']`.**
|
|
137
|
+
|
|
138
|
+
Add an entry whenever you add a route — whether a generator added it or you hand-edited the
|
|
139
|
+
router. Nothing does this for you: no generator writes to that file (it is left alone once it
|
|
140
|
+
exists so your edits survive), so a router that has grown past `/` while `ROUTES` has not is
|
|
141
|
+
a check that has quietly stopped covering the app. `/` is mostly app shell, so it exercises
|
|
142
|
+
almost none of the elements the rest of the app is built from.
|
|
143
|
+
|
|
144
|
+
Expect real failures when you expand it, and triage before fixing: some violations originate
|
|
145
|
+
inside `@abgov/web-components`' own shadow DOM and cannot be addressed from app code. Those
|
|
146
|
+
need an upstream fix or a documented exclusion, not a workaround in your view.
|
|
147
|
+
|
|
132
148
|
## OpenShift targets
|
|
133
149
|
|
|
134
150
|
```bash
|
|
@@ -81,9 +81,9 @@ function default_1(host, options) {
|
|
|
81
81
|
(0, quality_1.guardPlaywrightWebServer)(host, `${normalizedOptions.projectRoot}-e2e`);
|
|
82
82
|
(0, quality_1.addAxeAccessibilityCheck)(host, `${normalizedOptions.projectRoot}-e2e`);
|
|
83
83
|
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
84
|
-
'@abgov/design-tokens': '2.12.
|
|
84
|
+
'@abgov/design-tokens': '2.12.8',
|
|
85
85
|
'@abgov/react-components': '7.3.0',
|
|
86
|
-
'@abgov/web-components': '2.
|
|
86
|
+
'@abgov/web-components': '2.4.0',
|
|
87
87
|
'@reduxjs/toolkit': '^2.5.1',
|
|
88
88
|
'keycloak-js': '^23.0.7',
|
|
89
89
|
'react-redux': '^9.2.0',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { reactive, ref, computed, watch, onUnmounted } from 'vue';
|
|
3
3
|
import { useRoute, useRouter } from 'vue-router';
|
|
4
|
-
import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
|
|
5
4
|
import {
|
|
6
5
|
<% if (fields.some((f) => !f.type || f.type === 'text' || f.type === 'number')) { -%>
|
|
7
6
|
GoabInput,
|
|
@@ -27,7 +26,6 @@ import { useApi } from '../composables/useApi';
|
|
|
27
26
|
|
|
28
27
|
const route = useRoute();
|
|
29
28
|
const router = useRouter();
|
|
30
|
-
const kc = useKeycloak();
|
|
31
29
|
const { get, save } = useApi();
|
|
32
30
|
|
|
33
31
|
const idParam = computed(() => String(route.params.id ?? ''));
|
|
@@ -207,11 +205,11 @@ onUnmounted(() => {
|
|
|
207
205
|
<% if (field.type === 'checkbox') { -%>
|
|
208
206
|
<!-- The checkbox carries its own label via `text`; goa-form-item must not
|
|
209
207
|
repeat it, or it renders twice. -->
|
|
210
|
-
<goa-form-item>
|
|
208
|
+
<goa-form-item mb="l">
|
|
211
209
|
<GoabCheckbox v-model="form.<%= field.key %>" name="<%= field.key %>" text="<%= field.label %>" />
|
|
212
210
|
</goa-form-item>
|
|
213
211
|
<% } else { -%>
|
|
214
|
-
<goa-form-item label="<%= field.label %>"<% if (field.required !== false) { %> requirement="required"<% } %> :error="errors.<%= field.key %>">
|
|
212
|
+
<goa-form-item mb="l" label="<%= field.label %>"<% if (field.required !== false) { %> requirement="required"<% } %> :error="errors.<%= field.key %>">
|
|
215
213
|
<% if (field.type === 'textarea') { -%>
|
|
216
214
|
<GoabTextarea v-model="form.<%= field.key %>" name="<%= field.key %>" />
|
|
217
215
|
<% } else if (field.type === 'number') { -%>
|
|
@@ -238,7 +236,7 @@ onUnmounted(() => {
|
|
|
238
236
|
<goa-spacer vspacing="l" />
|
|
239
237
|
|
|
240
238
|
<goa-button-group gap="relaxed">
|
|
241
|
-
<goa-button
|
|
239
|
+
<goa-button type="primary" :disabled="saving || undefined" @_click="onSubmit">
|
|
242
240
|
{{ isNew ? 'Create <%= singularLabel %>' : 'Save changes' }}
|
|
243
241
|
</goa-button>
|
|
244
242
|
<goa-button type="secondary" @_click="goBack">Cancel</goa-button>
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from 'vue';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
WorkspaceTable,
|
|
5
|
+
<% if (hasCodedFields) { -%>
|
|
6
|
+
optionLabel,
|
|
7
|
+
<% } -%>
|
|
8
|
+
<% if (hasDateFields) { -%>
|
|
9
|
+
formatDate,
|
|
10
|
+
<% } -%>
|
|
11
|
+
} from '<%= goaImportPath %>';
|
|
12
|
+
<% if (hasCodedFields) { -%>
|
|
13
|
+
import type { DisplayOption } from '<%= goaImportPath %>';
|
|
14
|
+
<% } -%>
|
|
5
15
|
import { useApi } from '../composables/useApi';
|
|
6
16
|
|
|
7
|
-
const kc = useKeycloak();
|
|
8
17
|
const { list } = useApi();
|
|
18
|
+
<% if (hasCodedFields) { -%>
|
|
19
|
+
|
|
20
|
+
// Value -> label for coded fields, so the table shows the same prose the edit
|
|
21
|
+
// form's dropdown does rather than the stored code.
|
|
22
|
+
const fieldOptions: Record<string, DisplayOption[]> = {
|
|
23
|
+
<% fields.forEach(function (field) { -%>
|
|
24
|
+
<% if (field.options && field.options.length) { -%>
|
|
25
|
+
'<%= field.key %>': <%- JSON.stringify(field.options) %>,
|
|
26
|
+
<% } -%>
|
|
27
|
+
<% }); -%>
|
|
28
|
+
};
|
|
29
|
+
<% } -%>
|
|
9
30
|
|
|
10
31
|
const columns = [
|
|
11
32
|
<% fields.forEach(function (field) { -%>
|
|
@@ -38,7 +59,7 @@ onMounted(load);
|
|
|
38
59
|
<div>
|
|
39
60
|
<div class="admin-crud-topbar">
|
|
40
61
|
<h1><%= heading %></h1>
|
|
41
|
-
<router-link
|
|
62
|
+
<router-link to="<%= route %>/new">
|
|
42
63
|
<goa-button type="primary">Create <%= singularLabel %></goa-button>
|
|
43
64
|
</router-link>
|
|
44
65
|
</div>
|
|
@@ -53,6 +74,14 @@ onMounted(load);
|
|
|
53
74
|
<template #cell-<%= field.key %>="{ row }">
|
|
54
75
|
<goa-badge :type="row['<%= field.key %>'] ? 'success' : 'midtone'" :content="row['<%= field.key %>'] ? 'Yes' : 'No'" />
|
|
55
76
|
</template>
|
|
77
|
+
<% } else if (field.options && field.options.length) { -%>
|
|
78
|
+
<template #cell-<%= field.key %>="{ row }">
|
|
79
|
+
{{ optionLabel(fieldOptions['<%= field.key %>'], row['<%= field.key %>']) }}
|
|
80
|
+
</template>
|
|
81
|
+
<% } else if (field.type === 'date') { -%>
|
|
82
|
+
<template #cell-<%= field.key %>="{ row }">
|
|
83
|
+
{{ formatDate(row['<%= field.key %>']) }}
|
|
84
|
+
</template>
|
|
56
85
|
<% } -%>
|
|
57
86
|
<% }); -%>
|
|
58
87
|
<template #actions="{ row }">
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"resource": {
|
|
27
27
|
"type": "string",
|
|
28
|
-
"description": "API resource path segment -- fetches /api/<resource> (list), /api/<resource>/:id (load one), POST /api/<resource> (create), PUT /api/<resource>/:id (update)."
|
|
28
|
+
"description": "API resource path segment -- fetches /api/v1/<resource> (list), /api/v1/<resource>/:id (load one), POST /api/v1/<resource> (create), PUT /api/v1/<resource>/:id (update)."
|
|
29
29
|
},
|
|
30
30
|
"route": {
|
|
31
31
|
"type": "string",
|
|
@@ -43,18 +43,17 @@
|
|
|
43
43
|
"type": "string",
|
|
44
44
|
"description": "Singular label used in \"Create <label>\"/\"Edit <label>\" headings and buttons. Defaults to --heading (override for irregular plurals, e.g. --heading=Regions --singularLabel=Region)."
|
|
45
45
|
},
|
|
46
|
+
"icon": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Ionicon name for the generated side-menu entry, e.g. `settings`. goa-work-side-menu-item renders a blank item without one.",
|
|
49
|
+
"default": "settings"
|
|
50
|
+
},
|
|
46
51
|
"requiresAuth": {
|
|
47
52
|
"type": "boolean",
|
|
48
53
|
"description": "Whether the generated routes require authentication.",
|
|
49
54
|
"default": true
|
|
50
55
|
}
|
|
51
56
|
},
|
|
52
|
-
"required": [
|
|
53
|
-
"project",
|
|
54
|
-
"name",
|
|
55
|
-
"resource",
|
|
56
|
-
"route",
|
|
57
|
-
"fields"
|
|
58
|
-
],
|
|
57
|
+
"required": ["project", "name", "resource", "route", "fields"],
|
|
59
58
|
"additionalProperties": false
|
|
60
59
|
}
|
|
@@ -54,11 +54,15 @@ function normalizeOptions(host, options) {
|
|
|
54
54
|
}
|
|
55
55
|
function default_1(host, options) {
|
|
56
56
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
var _a;
|
|
57
58
|
const normalizedOptions = normalizeOptions(host, options);
|
|
58
59
|
// Idempotent -- ensures WorkspaceTable exists even in a project scaffolded
|
|
59
60
|
// before vue-components carried it.
|
|
60
61
|
yield (0, vue_components_1.default)(host);
|
|
61
|
-
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
62
|
+
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
63
|
+
// Derived here, not tested inline: EJS throws a ReferenceError on a bare
|
|
64
|
+
// undefined variable, so a flag the template reads must always be defined.
|
|
65
|
+
hasCodedFields: normalizedOptions.fields.some((field) => { var _a; return (_a = field.options) === null || _a === void 0 ? void 0 : _a.length; }), hasDateFields: normalizedOptions.fields.some((field) => field.type === 'date'), tmpl: '' }));
|
|
62
66
|
// The edit route also serves create: visiting `${route}/new` matches the
|
|
63
67
|
// same `:id` param (id === 'new'), same convention the real reference
|
|
64
68
|
// implementation this was modeled on uses -- no separate create route.
|
|
@@ -78,6 +82,7 @@ function default_1(host, options) {
|
|
|
78
82
|
(0, vue_side_menu_1.insertSideMenuItem)(host, normalizedOptions.projectRoot, {
|
|
79
83
|
label: normalizedOptions.heading,
|
|
80
84
|
to: normalizedOptions.route,
|
|
85
|
+
icon: (_a = normalizedOptions.icon) !== null && _a !== void 0 ? _a : 'settings',
|
|
81
86
|
});
|
|
82
87
|
yield (0, devkit_1.formatFiles)(host);
|
|
83
88
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":"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"}
|