@abgov/nx-adsp 13.31.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.
Files changed (68) hide show
  1. package/README.md +41 -32
  2. package/package.json +1 -1
  3. package/src/generators/angular-app/angular-app.js +2 -2
  4. package/src/generators/angular-app/files/AGENTS.md__tmpl__ +16 -0
  5. package/src/generators/express-service/express-service.spec.ts +26 -13
  6. package/src/generators/express-service/schema.json +1 -1
  7. package/src/generators/react-app/files/AGENTS.md__tmpl__ +16 -0
  8. package/src/generators/react-app/react-app.js +2 -2
  9. package/src/generators/vue-admin-crud/files/src/views/__editViewFileName__.vue__tmpl__ +1 -3
  10. package/src/generators/vue-admin-crud/files/src/views/__listViewFileName__.vue__tmpl__ +33 -4
  11. package/src/generators/vue-admin-crud/schema.d.ts +2 -0
  12. package/src/generators/vue-admin-crud/schema.json +7 -8
  13. package/src/generators/vue-admin-crud/vue-admin-crud.js +6 -1
  14. package/src/generators/vue-admin-crud/vue-admin-crud.js.map +1 -1
  15. package/src/generators/vue-admin-crud/vue-admin-crud.spec.ts +74 -13
  16. package/src/generators/vue-app/files/AGENTS.md__tmpl__ +56 -11
  17. package/src/generators/vue-app/files/src/App.vue__tmpl__ +2 -1
  18. package/src/generators/vue-app/files/src/composables/useApi.spec.ts__tmpl__ +5 -3
  19. package/src/generators/vue-app/files/src/composables/useApi.ts__tmpl__ +14 -2
  20. package/src/generators/vue-app/vue-app.js +33 -6
  21. package/src/generators/vue-app/vue-app.js.map +1 -1
  22. package/src/generators/vue-app/vue-app.spec.ts +133 -13
  23. package/src/generators/vue-components/files/AGENTS.md__tmpl__ +35 -16
  24. package/src/generators/vue-components/files/src/index.ts__tmpl__ +3 -0
  25. package/src/generators/vue-components/files/src/lib/formatters.ts__tmpl__ +58 -0
  26. package/src/generators/vue-components/files/src/lib/patterns/AppSideMenu.vue__tmpl__ +14 -3
  27. package/src/generators/vue-components/files/src/lib/patterns/FilterBar.vue__tmpl__ +7 -1
  28. package/src/generators/vue-components/files/src/lib/patterns/WorkspaceTable.vue__tmpl__ +7 -1
  29. package/src/generators/vue-components/vue-components.d.ts +1 -0
  30. package/src/generators/vue-components/vue-components.js +18 -39
  31. package/src/generators/vue-components/vue-components.js.map +1 -1
  32. package/src/generators/vue-components/vue-components.spec.ts +94 -2
  33. package/src/generators/vue-detail-view/files/src/views/__viewFileName__.vue__tmpl__ +51 -2
  34. package/src/generators/vue-detail-view/schema.d.ts +18 -0
  35. package/src/generators/vue-detail-view/schema.json +2 -2
  36. package/src/generators/vue-detail-view/vue-detail-view.js +4 -1
  37. package/src/generators/vue-detail-view/vue-detail-view.js.map +1 -1
  38. package/src/generators/vue-detail-view/vue-detail-view.spec.ts +36 -6
  39. package/src/generators/vue-intake-view/files/shared/src/views/__confirmationViewFileName__.vue__tmpl__ +60 -6
  40. package/src/generators/vue-intake-view/files/shared/src/views/__reviewViewFileName__.vue__tmpl__ +21 -0
  41. package/src/generators/vue-intake-view/schema.d.ts +10 -0
  42. package/src/generators/vue-intake-view/schema.json +12 -9
  43. package/src/generators/vue-intake-view/vue-intake-view.js +9 -3
  44. package/src/generators/vue-intake-view/vue-intake-view.js.map +1 -1
  45. package/src/generators/vue-intake-view/vue-intake-view.spec.ts +81 -12
  46. package/src/generators/vue-workspace-view/files/src/views/__viewFileName__.vue__tmpl__ +49 -1
  47. package/src/generators/vue-workspace-view/schema.d.ts +22 -0
  48. package/src/generators/vue-workspace-view/schema.json +8 -9
  49. package/src/generators/vue-workspace-view/vue-workspace-view.js +7 -1
  50. package/src/generators/vue-workspace-view/vue-workspace-view.js.map +1 -1
  51. package/src/generators/vue-workspace-view/vue-workspace-view.spec.ts +73 -7
  52. package/src/utils/paired-project.d.ts +10 -0
  53. package/src/utils/paired-project.js +125 -18
  54. package/src/utils/paired-project.js.map +1 -1
  55. package/src/utils/paired-project.spec.ts +126 -0
  56. package/src/utils/quality.js +43 -9
  57. package/src/utils/quality.js.map +1 -1
  58. package/src/utils/quality.spec.ts +35 -2
  59. package/src/utils/vue-eslint.d.ts +2 -0
  60. package/src/utils/vue-eslint.js +48 -0
  61. package/src/utils/vue-eslint.js.map +1 -0
  62. package/src/utils/vue-side-menu.d.ts +13 -1
  63. package/src/utils/vue-side-menu.js +50 -5
  64. package/src/utils/vue-side-menu.js.map +1 -1
  65. package/src/utils/vue-side-menu.spec.ts +100 -0
  66. package/src/utils/vue-tsconfig.d.ts +26 -0
  67. package/src/utils/vue-tsconfig.js +49 -0
  68. 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? }` — a JSON string (see note below) |
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
- | `requiresAuth` | No | Whether the generated route requires authentication. Defaults to `true` |
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
- | `requiresAuth` | No | Whether the generated routes require authentication. Defaults to `true` |
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 | Required | Description |
350
- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
351
- | `project` | Yes | The `vue-app` project to add the views to |
352
- | `name` | Yes | Base name for the generated views, e.g. `application` generates `<Step>StepView.vue` per step plus `ApplicationReviewView.vue`/`ApplicationConfirmationView.vue` |
353
- | `resource` | Yes | API resource path segment. Each step fetches/saves `/api/<resource>/:id`; the review step's Submit posts `/api/<resource>/:id/submit` |
354
- | `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>` |
355
- | `steps` | Yes | JSON array of steps, in order: `{ key, label, fields: [{ key, label, required? }] }` — a JSON string (see note below) |
356
- | `requiresAuth` | No | Whether the generated routes require authentication. Defaults to `true` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.31.0",
3
+ "version": "13.31.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -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.0',
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.3.0',
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 { addProjectConfiguration, readJson, readProjectConfiguration, writeJson } from '@nx/devkit';
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.json and patches serve target and nginx.conf for a Vue frontend', async () => {
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 file written at the Vue location.
397
- expect(host.exists('apps/my-vue-app/vite.proxy.json')).toBeTruthy();
398
- const proxyConf = readJson(host, 'apps/my-vue-app/vite.proxy.json');
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/'].pathRewrite['^/api/']).toBe('/test/');
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.json',
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.json or proxy.conf.json), 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."
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.0',
84
+ '@abgov/design-tokens': '2.12.8',
85
85
  '@abgov/react-components': '7.3.0',
86
- '@abgov/web-components': '2.3.0',
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 v-if="kc.authenticated" type="primary" :disabled="saving || undefined" @_click="onSubmit">
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 { useKeycloak } from '@dsb-norge/vue-keycloak-js';
4
- import { WorkspaceTable } from '<%= goaImportPath %>';
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 v-if="kc.authenticated" to="<%= route %>/new">
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 }">
@@ -20,6 +20,8 @@ export interface Schema {
20
20
  fields: string | AdminCrudField[];
21
21
  heading?: string;
22
22
  singularLabel?: string;
23
+ /** Ionicon name for the side-menu entry. Defaults per generator. */
24
+ icon?: string;
23
25
  requiresAuth?: boolean;
24
26
  }
25
27
 
@@ -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), tmpl: '' }));
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":";;AA+EA,4BAyCC;;AAxHD,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,6DAA+D;AAC/D,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,iEAAiE;AACjE,SAAS,WAAW,CAAC,MAAwB;IAC3C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,WAAW,GAAG;IAClB,MAAM;IACN,UAAU;IACV,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,UAAU;CACF,CAAC;AAEX,SAAS,gBAAgB,CAAC,MAA2D;IACnF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAa,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CACb,kCAAkC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,GAAG,IAAI,CACpG,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CACb,wDAAwD,KAAK,CAAC,GAAG,6CAA6C,CAC/G,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IAChD,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,OAAO,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,uCACK,OAAO,KACV,WAAW,EACX,MAAM,EAAE,CAAC,GAAG,EAAE;YACZ,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC3C,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACzB,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,EAAE,EACJ,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,OAAO,EACP,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,OAAO,EAC/C,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI,IAC1C;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,WAAW,kCAExB,iBAAiB,KACpB,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,IAEX,CAAC;QAEF,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,MAAM;YACtC,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QACH,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,iBAAiB,CAAC,KAAK;YAC7B,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QAEH,qEAAqE;QACrE,IAAA,kCAAkB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE;YACtD,KAAK,EAAE,iBAAiB,CAAC,OAAO;YAChC,EAAE,EAAE,iBAAiB,CAAC,KAAK;SAC5B,CAAC,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
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("import { WorkspaceTable } from '@proj/vue-components';");
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("#cell-active=\"{ row }\"");
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("import { GoabInput, GoabCheckbox } from '@proj/vue-components';");
91
- expect(view).toContain("name.trim()");
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('<goa-dropdown-item value="north" label="North" />');
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("quota: form.quota === '' ? null : Number(form.quota)");
176
- expect(view).toContain('effective: toIsoDateString(form.effective) || null');
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.read('apps/test/src/views/RegionsListView.vue').toString();
229
- const edit = host.read('apps/test/src/views/RegionsEditView.vue').toString();
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.read('apps/test/src/views/RegionsEditView.vue').toString();
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
  });