@abgov/nx-adsp 13.30.0 → 13.31.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/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__ +1 -3
- 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 +74 -13
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +56 -11
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +18 -4
- 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 +152 -17
- package/src/generators/vue-components/files/AGENTS.md__tmpl__ +35 -16
- 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/AppLayout.spec.ts__tmpl__ +32 -0
- package/src/generators/vue-components/files/src/lib/patterns/AppLayout.vue__tmpl__ +28 -21
- 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 +95 -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__ +21 -0
- 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 +81 -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 ?? ''));
|
|
@@ -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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"}
|
|
@@ -64,7 +64,9 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
64
64
|
expect(view).toContain("await list('regions')");
|
|
65
65
|
// The envelope shape is the adapter's business, not the view's.
|
|
66
66
|
expect(view).not.toContain('data.results');
|
|
67
|
-
expect(view).toContain(
|
|
67
|
+
expect(view).toContain(
|
|
68
|
+
"import { WorkspaceTable } from '@proj/vue-components';",
|
|
69
|
+
);
|
|
68
70
|
expect(view).toContain('<WorkspaceTable');
|
|
69
71
|
// No pagination props bound -- this is the "reused without its pagination/
|
|
70
72
|
// filter props" case, unlike vue-workspace-view (the WorkspaceTable tag
|
|
@@ -76,7 +78,7 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
76
78
|
expect(view).toContain('Create Regions');
|
|
77
79
|
expect(view).toContain(':to="`/regions/${row.id}`"');
|
|
78
80
|
// Checkbox field renders as a Yes/No badge in the list.
|
|
79
|
-
expect(view).toContain(
|
|
81
|
+
expect(view).toContain('#cell-active="{ row }"');
|
|
80
82
|
expect(view).toContain("row['active'] ? 'Yes' : 'No'");
|
|
81
83
|
}, 30000);
|
|
82
84
|
|
|
@@ -87,8 +89,10 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
87
89
|
.read('apps/test/src/views/RegionsEditView.vue')
|
|
88
90
|
.toString();
|
|
89
91
|
expect(view).toContain("idParam.value === 'new' || idParam.value === ''");
|
|
90
|
-
expect(view).toContain(
|
|
91
|
-
|
|
92
|
+
expect(view).toContain(
|
|
93
|
+
"import { GoabInput, GoabCheckbox } from '@proj/vue-components';",
|
|
94
|
+
);
|
|
95
|
+
expect(view).toContain('name.trim()');
|
|
92
96
|
expect(view).toContain("errors.name = 'Name is required.';");
|
|
93
97
|
// Checkbox has no required-validation block.
|
|
94
98
|
expect(view).not.toContain('errors.active');
|
|
@@ -149,7 +153,9 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
149
153
|
expect(view).toContain('type="number"');
|
|
150
154
|
expect(view).toContain('<GoabDatePicker v-model="form.effective"');
|
|
151
155
|
expect(view).toContain('<GoabDropdown v-model="form.region"');
|
|
152
|
-
expect(view).toContain(
|
|
156
|
+
expect(view).toContain(
|
|
157
|
+
'<goa-dropdown-item value="north" label="North" />',
|
|
158
|
+
);
|
|
153
159
|
expect(view).toContain('<GoabCheckbox v-model="form.active"');
|
|
154
160
|
});
|
|
155
161
|
|
|
@@ -172,8 +178,12 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
172
178
|
const view = host
|
|
173
179
|
.read('apps/test/src/views/RegionsEditView.vue')
|
|
174
180
|
.toString();
|
|
175
|
-
expect(view).toContain(
|
|
176
|
-
|
|
181
|
+
expect(view).toContain(
|
|
182
|
+
"quota: form.quota === '' ? null : Number(form.quota)",
|
|
183
|
+
);
|
|
184
|
+
expect(view).toContain(
|
|
185
|
+
'effective: toIsoDateString(form.effective) || null',
|
|
186
|
+
);
|
|
177
187
|
// A stored YYYY-MM-DD comes back as a Date the picker can redisplay.
|
|
178
188
|
expect(view).toContain("fromIsoDateString(data['effective'])");
|
|
179
189
|
});
|
|
@@ -225,8 +235,12 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
225
235
|
heading: 'Regions',
|
|
226
236
|
singularLabel: 'Region',
|
|
227
237
|
});
|
|
228
|
-
const list = host
|
|
229
|
-
|
|
238
|
+
const list = host
|
|
239
|
+
.read('apps/test/src/views/RegionsListView.vue')
|
|
240
|
+
.toString();
|
|
241
|
+
const edit = host
|
|
242
|
+
.read('apps/test/src/views/RegionsEditView.vue')
|
|
243
|
+
.toString();
|
|
230
244
|
expect(list).toContain('<h1>Regions</h1>');
|
|
231
245
|
expect(list).toContain('Create Region');
|
|
232
246
|
expect(edit).toContain('Create Region');
|
|
@@ -238,7 +252,9 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
238
252
|
...baseOptions,
|
|
239
253
|
fields: [{ key: 'name', label: 'Name', required: false }],
|
|
240
254
|
});
|
|
241
|
-
const edit = host
|
|
255
|
+
const edit = host
|
|
256
|
+
.read('apps/test/src/views/RegionsEditView.vue')
|
|
257
|
+
.toString();
|
|
242
258
|
expect(edit).not.toContain("errors.name = 'Name is required.'");
|
|
243
259
|
expect(edit).not.toContain('requirement="required"');
|
|
244
260
|
}, 30000);
|
|
@@ -278,9 +294,7 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
278
294
|
expect(routerTs).toContain(
|
|
279
295
|
"component: () => import('../views/RegionsEditView.vue')",
|
|
280
296
|
);
|
|
281
|
-
expect(
|
|
282
|
-
routerTs.split('requiresAuth: true').length - 1,
|
|
283
|
-
).toBe(2);
|
|
297
|
+
expect(routerTs.split('requiresAuth: true').length - 1).toBe(2);
|
|
284
298
|
// The existing route is untouched, not replaced.
|
|
285
299
|
expect(routerTs).toContain("{ path: '/', component: HomeView }");
|
|
286
300
|
}, 30000);
|
|
@@ -304,4 +318,51 @@ describe('Vue Admin CRUD Generator', () => {
|
|
|
304
318
|
const after = readProjectConfiguration(host, 'test');
|
|
305
319
|
expect(after).toEqual(before);
|
|
306
320
|
}, 30000);
|
|
321
|
+
|
|
322
|
+
// Regression: both actions were gated on `kc.authenticated` unconditionally,
|
|
323
|
+
// ignoring --requiresAuth. With --requiresAuth=false a consumer got a fillable
|
|
324
|
+
// form whose only button was Cancel, and a list with no Create button, and no
|
|
325
|
+
// message explaining why. Access control belongs to the route guard (which
|
|
326
|
+
// already honours requiresAuth via route meta), not to markup in the view --
|
|
327
|
+
// and vue-intake-view never had this gate, so the two disagreed.
|
|
328
|
+
it('does not gate its actions on kc.authenticated', async () => {
|
|
329
|
+
await generator(host, { ...baseOptions, requiresAuth: false });
|
|
330
|
+
const list = host
|
|
331
|
+
.read('apps/test/src/views/RegionsListView.vue')
|
|
332
|
+
.toString();
|
|
333
|
+
const edit = host
|
|
334
|
+
.read('apps/test/src/views/RegionsEditView.vue')
|
|
335
|
+
.toString();
|
|
336
|
+
for (const view of [list, edit]) {
|
|
337
|
+
expect(view).not.toContain('kc.authenticated');
|
|
338
|
+
expect(view).not.toContain('useKeycloak');
|
|
339
|
+
}
|
|
340
|
+
// The actions themselves are still there, unconditionally. Matched loosely
|
|
341
|
+
// because formatFiles reflows the long button tag across lines.
|
|
342
|
+
expect(list).toContain('<router-link to="/regions/new">');
|
|
343
|
+
expect(edit).toMatch(/<goa-button[\s\S]{0,120}type="primary"/);
|
|
344
|
+
expect(edit).toContain('@_click="onSubmit"');
|
|
345
|
+
}, 30000);
|
|
346
|
+
|
|
347
|
+
it('renders a coded field using its label, not the stored code', async () => {
|
|
348
|
+
await generator(host, {
|
|
349
|
+
...baseOptions,
|
|
350
|
+
fields: [
|
|
351
|
+
{ key: 'name', label: 'Name' },
|
|
352
|
+
{
|
|
353
|
+
key: 'species',
|
|
354
|
+
label: 'Species',
|
|
355
|
+
type: 'select',
|
|
356
|
+
options: [{ value: 'cattle-mature', label: 'Cattle (mature)' }],
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
|
+
const list = host
|
|
361
|
+
.read('apps/test/src/views/RegionsListView.vue')
|
|
362
|
+
.toString();
|
|
363
|
+
expect(list).toContain(
|
|
364
|
+
"optionLabel(fieldOptions['species'], row['species'])",
|
|
365
|
+
);
|
|
366
|
+
expect(list).toContain('Cattle (mature)');
|
|
367
|
+
}, 30000);
|
|
307
368
|
});
|