@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
|
@@ -7,7 +7,7 @@ Generated by `nx g @abgov/nx-adsp:vue-app`.
|
|
|
7
7
|
|
|
8
8
|
This app is paired with **`<%= pairedProject %>`** — the Express backend it talks to.
|
|
9
9
|
API calls use the `/api/` prefix which the Vite dev proxy rewrites to `/<%= pairedProject %>/` on port 3333.
|
|
10
|
-
When working on a feature that spans both projects, read
|
|
10
|
+
When working on a feature that spans both projects, read `<%= pairedProjectRoot %>/AGENTS.md` for the service context.
|
|
11
11
|
<% } %>
|
|
12
12
|
## Running Nx commands (coding agents)
|
|
13
13
|
|
|
@@ -157,9 +157,9 @@ Extra props/events fall through to the underlying `goa-*` element, so anything
|
|
|
157
157
|
the design system supports still works. For components without a wrapper, use the
|
|
158
158
|
`goa-*` element directly with the manual wiring above. The library is shared
|
|
159
159
|
across every Vue app in this workspace — fix or extend a wrapper once in
|
|
160
|
-
|
|
160
|
+
`<%= goaLibRoot %>` (or run `nx g @abgov/nx-adsp:vue-components` to restore it).
|
|
161
161
|
|
|
162
|
-
> **Interim:** these wrappers (
|
|
162
|
+
> **Interim:** these wrappers (`<%= goaLibRoot %>/src/lib/primitives/`) are a
|
|
163
163
|
> stopgap until GoA DS ships an official `@abgov/vue-components`. When it lands,
|
|
164
164
|
> delete `primitives/` and repoint imports at that package — the names/props are
|
|
165
165
|
> intended to match, so it's a scope swap. This does **not** apply to the
|
|
@@ -176,7 +176,7 @@ from **`<%= goaImportPath %>`**, not hand-rolled markup:
|
|
|
176
176
|
|
|
177
177
|
| Component | Purpose |
|
|
178
178
|
|---|---|
|
|
179
|
-
| `AppSideMenu` | `goa-work-side-menu`. Takes `heading`/`primaryItems`/`secondaryItems`/`accountItems` props (each item: `{ label, to?, icon?, badge?, current? }`) and emits `itemClick` — it doesn't know about routing or auth, so `App.vue` computes `current`/handles the click itself. `App.vue`
|
|
179
|
+
| `AppSideMenu` | `goa-work-side-menu`. Takes `heading`/`primaryItems`/`secondaryItems`/`accountItems` props (each item: `{ label, to?, icon?, badge?, current? }`) and emits `itemClick` — it doesn't know about routing or auth, so `App.vue` computes `current`/handles the click itself. `App.vue` seeds `primaryItems` with Home and an `accountItems` Sign in/out item; the `vue-*-view` generators append their own entries, and you can add more by hand. **`icon` is effectively required** — `goa-work-side-menu-item` renders a blank item without it; use a GoA icon name (see [design.alberta.ca/components/icons](https://design.alberta.ca/components/icons)). Also owns the skip-to-main-content landmark for this layout — `AppLayout` deliberately doesn't duplicate it. Also takes an optional `#topbar` slot — a slim row above the routed content, for something like a notification bell; only renders when given content, unused by default |
|
|
180
180
|
| `AppLayout` | The content gutter every view renders inside (see the key files table) |
|
|
181
181
|
| `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits. Bound to the `useSessionStore()` Pinia store (`src/stores/session.ts`), which `main.ts`'s `onAuthRefreshError` hook flips when the refresh token itself has expired |
|
|
182
182
|
|
|
@@ -195,7 +195,7 @@ marketing chrome around a staff tool).
|
|
|
195
195
|
| `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits. Bound to the `useSessionStore()` Pinia store (`src/stores/session.ts`), which `main.ts`'s `onAuthRefreshError` hook flips when the refresh token itself has expired |
|
|
196
196
|
<% } %>
|
|
197
197
|
These are shared across every Vue app in this workspace — fix or extend one once
|
|
198
|
-
in
|
|
198
|
+
in `<%= goaLibRoot %>/src/lib/patterns/`, the same way as the `Goab*` wrappers.
|
|
199
199
|
|
|
200
200
|
A second, **`--layout=<%= layout === 'internal' ? 'header' : 'internal' %>`** frontend
|
|
201
201
|
can be scaffolded against the same backend with `nx g @abgov/nx-adsp:vue-app <name>
|
|
@@ -222,6 +222,22 @@ generator's own schema description for why). Only hand-author a view when none o
|
|
|
222
222
|
match, or once a generator's output needs customization it doesn't cover — its own comments
|
|
223
223
|
mark what's meant to be edited.
|
|
224
224
|
|
|
225
|
+
### Options that decide what a person sees
|
|
226
|
+
|
|
227
|
+
These are easy to omit and the result looks plausible until someone reads the screen, so
|
|
228
|
+
they are worth getting right on the first run:
|
|
229
|
+
|
|
230
|
+
| Pass | On | Or else |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `options: [{ value, label }]` on a field/column | any generator, for any value stored as a code | the view renders the raw code — `northwest`, `cattle-mature` — including on the check-your-answers page, whose whole purpose is that the person can read back what they entered |
|
|
233
|
+
| `badgeMap: { approved: 'success', declined: 'emergency' }` | a `badge` column/field | every status renders the same neutral blue, so a declined claim reads as routine |
|
|
234
|
+
| `--referenceField=<key>` | `vue-intake-view` | the confirmation page falls back to the route id, presenting a storage id as "your reference number" (defaults to `reference`) |
|
|
235
|
+
| `--icon=<name>` | `vue-workspace-view`, `vue-admin-crud` | defaults (`list`, `settings`) are used; pick one that suits the screen |
|
|
236
|
+
|
|
237
|
+
The lookup itself is shared — `optionLabel` and `badgeType` in `<%= goaLibRoot %>/src/lib/formatters.ts`
|
|
238
|
+
— so a hand-authored view should use them too rather than re-deriving a mapping. See that
|
|
239
|
+
lib's own AGENTS.md for the full component catalog.
|
|
240
|
+
|
|
225
241
|
1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC. **Don't add your
|
|
226
242
|
own page margins/centering** — every view renders inside `AppLayout`'s gutter, so
|
|
227
243
|
any top-level tag (`<div>`, `<section>`, …) is already centered and padded.
|
|
@@ -247,15 +263,17 @@ mark what's meant to be edited.
|
|
|
247
263
|
```typescript
|
|
248
264
|
const primaryItems = [
|
|
249
265
|
{ label: 'Home', to: '/', icon: 'home' },
|
|
250
|
-
{ label: 'Review queue', to: '/queue' }, // added by vue-workspace-view
|
|
266
|
+
{ label: 'Review queue', to: '/queue', icon: 'list' }, // added by vue-workspace-view
|
|
251
267
|
];
|
|
252
268
|
```
|
|
253
269
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
**`icon`** is effectively required — `goa-work-side-menu-item` renders a blank item without
|
|
271
|
+
one — so the generators supply it: `vue-workspace-view` defaults to `list` and
|
|
272
|
+
`vue-admin-crud` to `settings`. Pass `--icon=<name>` to choose a better one for the screen
|
|
273
|
+
(names: [design.alberta.ca/components/icons](https://design.alberta.ca/components/icons)).
|
|
274
|
+
|
|
275
|
+
Still worth adding by hand: **`current`** for active highlighting, e.g.
|
|
276
|
+
`current: route.path.startsWith('/queue')`, which needs the `route` already in scope.
|
|
259
277
|
|
|
260
278
|
The `header` layout has no side menu, so it has no `primaryItems` array and the generators
|
|
261
279
|
skip the step silently — use route-level breadcrumbs or a nav inside the view instead.
|
|
@@ -304,6 +322,17 @@ await save('my-resource', id, payload) // an id = update
|
|
|
304
322
|
await action('my-resource', id, 'submit') // POST /:id/submit
|
|
305
323
|
```
|
|
306
324
|
|
|
325
|
+
These build their URL through `apiConvention.path()`, which resolves to
|
|
326
|
+
`/api/v1/<resource>`. `/api/` is the dev-proxy and nginx location; `v1` is the version the
|
|
327
|
+
paired `express-service` mounts its routers under (`/<service>/v1`), and the proxy rewrites
|
|
328
|
+
`/api/` to `/<service>/`. If your API is versioned differently — or not at all — change
|
|
329
|
+
`apiConvention` and nothing else: the views only ever call through that seam, so no view
|
|
330
|
+
needs editing.
|
|
331
|
+
|
|
332
|
+
In dev the rewrite lives in `vite.proxy.cjs`. That file is a CommonJS module rather than
|
|
333
|
+
JSON on purpose: Vite rewrites a proxied path with a `rewrite` function, and a JSON config
|
|
334
|
+
cannot hold a function. Add a route by appending a row to its `routes` array.
|
|
335
|
+
|
|
307
336
|
Every wire-level detail these hide — the base path, whether paging is `page`/`limit` or
|
|
308
337
|
`limit`/`offset`, whether rows arrive bare or under `results`/`entries`, whether an update is PUT
|
|
309
338
|
or PATCH — lives in the **API convention adapter** at the top of
|
|
@@ -373,6 +402,22 @@ describe('MyComponent', () => {
|
|
|
373
402
|
});
|
|
374
403
|
```
|
|
375
404
|
|
|
405
|
+
### Accessibility check — expand its route list
|
|
406
|
+
|
|
407
|
+
The generated e2e project ships `src/a11y.spec.ts`: an axe-core check scoped to WCAG 2.1
|
|
408
|
+
A/AA, run as part of the normal `e2e` target. **It only covers the routes named in its own
|
|
409
|
+
`ROUTES` array, which ships as `['/']`.**
|
|
410
|
+
|
|
411
|
+
Add an entry whenever you add a route — whether a generator added it or you hand-edited the
|
|
412
|
+
router. Nothing does this for you: no generator writes to that file (it is left alone once it
|
|
413
|
+
exists so your edits survive), so a router that has grown past `/` while `ROUTES` has not is
|
|
414
|
+
a check that has quietly stopped covering the app. `/` is mostly app shell, so it exercises
|
|
415
|
+
almost none of the elements the rest of the app is built from.
|
|
416
|
+
|
|
417
|
+
Expect real failures when you expand it, and triage before fixing: some violations originate
|
|
418
|
+
inside `@abgov/web-components`' own shadow DOM and cannot be addressed from app code. Those
|
|
419
|
+
need an upstream fix or a documented exclusion, not a workaround in your view.
|
|
420
|
+
|
|
376
421
|
## OpenShift targets
|
|
377
422
|
|
|
378
423
|
```bash
|
|
@@ -37,7 +37,7 @@ function signInAgain() {
|
|
|
37
37
|
// prune -- a generated app is a starting point, not a fixed shell. Items are
|
|
38
38
|
// `{ label, to }`; add `icon` for a leading icon or `badge` for a count.
|
|
39
39
|
const primaryItems = [
|
|
40
|
-
{ label: 'Home', to: '/' },
|
|
40
|
+
{ label: 'Home', to: '/', icon: 'home' },
|
|
41
41
|
];
|
|
42
42
|
|
|
43
43
|
// AppSideMenu's items are presentational — it doesn't know about Keycloak or
|
|
@@ -64,6 +64,7 @@ function onItemClick(item: { to?: string }) {
|
|
|
64
64
|
<% if (layout === 'internal') { %>
|
|
65
65
|
<AppSideMenu
|
|
66
66
|
heading="<%= projectName %>"
|
|
67
|
+
:user-name="kc.fullName"
|
|
67
68
|
:primary-items="primaryItems"
|
|
68
69
|
:account-items="accountItems"
|
|
69
70
|
@item-click="onItemClick"
|
|
@@ -78,6 +79,13 @@ function onItemClick(item: { to?: string }) {
|
|
|
78
79
|
</AppLayout>
|
|
79
80
|
</AppSideMenu>
|
|
80
81
|
<% } else { %>
|
|
82
|
+
<!-- goa-one-column-layout owns the page's flex column, the sticky footer and
|
|
83
|
+
the <main> landmark. That <main> lives in the element's shadow DOM, so the
|
|
84
|
+
skip link targets an id'd wrapper in the slotted (light DOM) content
|
|
85
|
+
instead -- the wrapper is a plain div, so there is still exactly one main
|
|
86
|
+
landmark on the page. -->
|
|
87
|
+
<goa-one-column-layout>
|
|
88
|
+
<section slot="header">
|
|
81
89
|
<a class="skip-link" href="#main-content">Skip to main content</a>
|
|
82
90
|
<AppHeader heading="<%= projectName %>">
|
|
83
91
|
<template #utilities>
|
|
@@ -100,12 +108,18 @@ function onItemClick(item: { to?: string }) {
|
|
|
100
108
|
@sign-in="signInAgain"
|
|
101
109
|
@dismiss="session.dismiss"
|
|
102
110
|
/>
|
|
103
|
-
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<div id="main-content">
|
|
104
114
|
<AppLayout :variant="contentWidth">
|
|
105
115
|
<RouterView />
|
|
106
116
|
</AppLayout>
|
|
107
|
-
</
|
|
108
|
-
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<section slot="footer">
|
|
120
|
+
<AppFooter />
|
|
121
|
+
</section>
|
|
122
|
+
</goa-one-column-layout>
|
|
109
123
|
<% } %>
|
|
110
124
|
</template>
|
|
111
125
|
|
|
@@ -7,9 +7,11 @@ import { apiConvention } from './useApi'
|
|
|
7
7
|
// it -- a failure here means a generated view's assumptions moved.
|
|
8
8
|
describe('apiConvention', () => {
|
|
9
9
|
it('builds collection, record, and action paths', () => {
|
|
10
|
-
expect(apiConvention.path('widgets')).toBe('/api/widgets')
|
|
11
|
-
expect(apiConvention.path('widgets', 7)).toBe('/api/widgets/7')
|
|
12
|
-
expect(apiConvention.path('widgets', 7, 'submit')).toBe(
|
|
10
|
+
expect(apiConvention.path('widgets')).toBe('/api/v1/widgets')
|
|
11
|
+
expect(apiConvention.path('widgets', 7)).toBe('/api/v1/widgets/7')
|
|
12
|
+
expect(apiConvention.path('widgets', 7, 'submit')).toBe(
|
|
13
|
+
'/api/v1/widgets/7/submit',
|
|
14
|
+
)
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
it('maps a domain page/pageSize onto this API page/limit parameters', () => {
|
|
@@ -32,9 +32,21 @@ export interface ListResult<T = Record<string, unknown>> {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export const apiConvention = {
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Path to a collection, one record, or a named action on a record.
|
|
37
|
+
*
|
|
38
|
+
* `/api/` is the dev-proxy and nginx location; `v1` is the API version the
|
|
39
|
+
* paired express-service mounts its routers under (`/<service>/v1`), and the
|
|
40
|
+
* proxy rewrites `/api/` to `/<service>/`. Dropping the version segment here
|
|
41
|
+
* (an earlier version of this file did) makes every list/get/save 404 against
|
|
42
|
+
* a generated backend, while HomeView and AGENTS.md -- which spell out
|
|
43
|
+
* `/api/v1/...` by hand -- keep working, so the mismatch is easy to miss.
|
|
44
|
+
*
|
|
45
|
+
* This is the one place to change if your API is versioned differently, or not
|
|
46
|
+
* at all: the views only ever call through this seam.
|
|
47
|
+
*/
|
|
36
48
|
path(resource: string, id?: string | number, action?: string): string {
|
|
37
|
-
const base = `/api/${resource}`
|
|
49
|
+
const base = `/api/v1/${resource}`
|
|
38
50
|
if (id === undefined || id === null) return base
|
|
39
51
|
return action ? `${base}/${id}/${action}` : `${base}/${id}`
|
|
40
52
|
},
|
|
@@ -12,6 +12,8 @@ const devkit_1 = require("@nx/devkit");
|
|
|
12
12
|
const path = require("path");
|
|
13
13
|
const paired_project_1 = require("../../utils/paired-project");
|
|
14
14
|
const nginx_1 = require("../../utils/nginx");
|
|
15
|
+
const vue_eslint_1 = require("../../utils/vue-eslint");
|
|
16
|
+
const vue_tsconfig_1 = require("../../utils/vue-tsconfig");
|
|
15
17
|
const vue_components_1 = require("../vue-components/vue-components");
|
|
16
18
|
function normalizeOptions(host, options) {
|
|
17
19
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -45,12 +47,23 @@ function addFiles(host, options) {
|
|
|
45
47
|
var _a;
|
|
46
48
|
const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), { offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), pairedProject: (_a = options.pairedProject) !== null && _a !== void 0 ? _a : null,
|
|
47
49
|
// Import specifier for the shared GoA wrapper lib (AGENTS.md references it).
|
|
48
|
-
goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
50
|
+
goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
51
|
+
// Real workspace-relative roots, so AGENTS.md can point at files that exist.
|
|
52
|
+
// Hardcoding `libs/`/`apps/` made every path in the generated guidance wrong
|
|
53
|
+
// in a TS-solution workspace, where projects sit at the workspace root.
|
|
54
|
+
goaLibRoot: (0, vue_components_1.vueComponentsLibRoot)(host), pairedProjectRoot: options.pairedProject
|
|
55
|
+
? (0, devkit_1.readProjectConfiguration)(host, options.pairedProject).root
|
|
56
|
+
: null, tmpl: '' });
|
|
49
57
|
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), options.projectRoot, templateOptions);
|
|
50
|
-
host.write(`${options.projectRoot}/public/nginx.conf`, (0, nginx_1.generateNginxConf)({
|
|
58
|
+
host.write(`${options.projectRoot}/public/nginx.conf`, (0, nginx_1.generateNginxConf)({
|
|
59
|
+
proxyLocations: options.nginxProxies,
|
|
60
|
+
silentCheckSso: true,
|
|
61
|
+
}));
|
|
51
62
|
const addProxyConf = options.nginxProxies.length > 0;
|
|
52
63
|
if (addProxyConf) {
|
|
53
|
-
|
|
64
|
+
// A .cjs module, not JSON: Vite rewrites a proxied path with a `rewrite`
|
|
65
|
+
// function and silently ignores the `pathRewrite` key JSON would carry.
|
|
66
|
+
host.write(`${options.projectRoot}/${paired_project_1.VITE_PROXY_FILENAME}`, (0, paired_project_1.buildViteDevProxyModule)((0, paired_project_1.viteDevProxyRoutes)(options.nginxProxies)));
|
|
54
67
|
}
|
|
55
68
|
return addProxyConf;
|
|
56
69
|
}
|
|
@@ -85,9 +98,19 @@ function default_1(host, options) {
|
|
|
85
98
|
// real current alternative — and GoA's own design-system docs don't list Vue
|
|
86
99
|
// as a supported framework at all.
|
|
87
100
|
yield (0, vue_components_1.default)(host);
|
|
101
|
+
// The shell projects into goa-one-column-layout's native `header`/`footer`
|
|
102
|
+
// slots, and app authors composing goa-* elements need the same attribute
|
|
103
|
+
// (goa-modal's `actions`, goa-app-header's `utilities`). Applied to both
|
|
104
|
+
// layouts, not just the one whose template emits it today -- scoping this to
|
|
105
|
+
// the single known call site is what let `nx lint` start failing on
|
|
106
|
+
// unmodified output when the shell adopted goa-one-column-layout.
|
|
107
|
+
(0, vue_eslint_1.disableSlotAttributeRule)(host, normalizedOptions.projectRoot, "The app shell projects into goa-one-column-layout's native `slot`");
|
|
108
|
+
// @nx/vue can leave the app's tsconfig with a bundler moduleResolution and no
|
|
109
|
+
// `module`, which is invalid against a TS-solution base -- see the util.
|
|
110
|
+
(0, vue_tsconfig_1.ensureBundlerModuleOption)(host, `${normalizedOptions.projectRoot}/tsconfig.json`);
|
|
88
111
|
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
89
|
-
'@abgov/design-tokens': '2.12.
|
|
90
|
-
'@abgov/web-components': '2.
|
|
112
|
+
'@abgov/design-tokens': '2.12.8',
|
|
113
|
+
'@abgov/web-components': '2.4.0',
|
|
91
114
|
// keycloak-js is a transitive dependency of @dsb-norge/vue-keycloak-js;
|
|
92
115
|
// don't pin it directly or the versions diverge into two copies.
|
|
93
116
|
'@dsb-norge/vue-keycloak-js': '^3.0.0',
|
|
@@ -115,6 +138,10 @@ function default_1(host, options) {
|
|
|
115
138
|
'src/components/AppLayout.vue',
|
|
116
139
|
'src/views/AboutView.vue',
|
|
117
140
|
'vite.config.mts',
|
|
141
|
+
// Superseded by vite.proxy.cjs. The JSON form could not express Vite's
|
|
142
|
+
// `rewrite` function, so it was inert -- leaving it would strand a file
|
|
143
|
+
// that looks like live proxy config but isn't referenced.
|
|
144
|
+
'vite.proxy.json',
|
|
118
145
|
]) {
|
|
119
146
|
if (host.exists(`${normalizedOptions.projectRoot}/${f}`)) {
|
|
120
147
|
host.delete(`${normalizedOptions.projectRoot}/${f}`);
|
|
@@ -129,7 +156,7 @@ function default_1(host, options) {
|
|
|
129
156
|
const config = (0, devkit_1.readProjectConfiguration)(host, options.name);
|
|
130
157
|
// Wire the vite dev-server proxy when nginx proxy locations are configured.
|
|
131
158
|
if (addedProxy && ((_a = config.targets.serve) === null || _a === void 0 ? void 0 : _a.options)) {
|
|
132
|
-
config.targets.serve.options = Object.assign(Object.assign({}, config.targets.serve.options), { proxyConfig: `${normalizedOptions.projectRoot}
|
|
159
|
+
config.targets.serve.options = Object.assign(Object.assign({}, config.targets.serve.options), { proxyConfig: `${normalizedOptions.projectRoot}/${paired_project_1.VITE_PROXY_FILENAME}` });
|
|
133
160
|
}
|
|
134
161
|
// nginx.conf lives in the Vite publicDir
|
|
135
162
|
// (<projectRoot>/public) so it's emitted to the build output root — the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -120,10 +120,17 @@ describe('Vue App Generator', () => {
|
|
|
120
120
|
const layoutPath = 'libs/vue-components/src/lib/patterns/AppLayout.vue';
|
|
121
121
|
expect(host.exists(layoutPath)).toBeTruthy();
|
|
122
122
|
const layout = host.read(layoutPath).toString();
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
// The gutter is goa-page-block's job now: it supplies the centering, the
|
|
124
|
+
// max-width and a responsive horizontal gutter. The named variants stay the
|
|
125
|
+
// public API, mapped to the widths the element takes.
|
|
126
|
+
expect(layout).toContain('<goa-page-block');
|
|
127
|
+
expect(layout).toContain("form: '640px'");
|
|
128
|
+
expect(layout).toContain("page: '1000px'");
|
|
129
|
+
expect(layout).toContain("wide: '1200px'");
|
|
130
|
+
// Vertical padding is not part of goa-page-block, so this component keeps it
|
|
131
|
+
// -- the same thing GoA's own public-form reference does.
|
|
126
132
|
expect(layout).toContain('--goa-space');
|
|
133
|
+
expect(layout).toContain('padding-block');
|
|
127
134
|
// AppLayout must NOT own the skip-to-main-content landmark itself: it nests
|
|
128
135
|
// inside AppSideMenu for --layout=internal, which already provides one, and
|
|
129
136
|
// a second <main id="main-content"> would duplicate the landmark/id.
|
|
@@ -137,8 +144,16 @@ describe('Vue App Generator', () => {
|
|
|
137
144
|
const app = host.read('apps/test/src/App.vue').toString();
|
|
138
145
|
expect(app).toContain('AppLayout');
|
|
139
146
|
expect(app).not.toContain('main > section');
|
|
147
|
+
// The public shell is goa-one-column-layout, which owns the page's flex
|
|
148
|
+
// column, sticky footer and <main> landmark. That <main> is in its shadow
|
|
149
|
+
// DOM, so the skip link targets an id'd wrapper in the slotted content --
|
|
150
|
+
// a plain div, so the page still has exactly one main landmark.
|
|
151
|
+
expect(app).toContain('<goa-one-column-layout>');
|
|
152
|
+
expect(app).toContain('<section slot="header">');
|
|
153
|
+
expect(app).toContain('<section slot="footer">');
|
|
140
154
|
expect(app).toContain('href="#main-content"');
|
|
141
|
-
expect(app).toContain('id="main-content"');
|
|
155
|
+
expect(app).toContain('<div id="main-content">');
|
|
156
|
+
expect(app).not.toContain('<main id="main-content">');
|
|
142
157
|
});
|
|
143
158
|
|
|
144
159
|
it('provisions the shared GoA wrapper library and points the app at it', async () => {
|
|
@@ -217,7 +232,9 @@ describe('Vue App Generator', () => {
|
|
|
217
232
|
// nav order, grouping and labels are the owning team's decisions.
|
|
218
233
|
expect(app).toContain(':primary-items="primaryItems"');
|
|
219
234
|
expect(app).toContain('const primaryItems = [');
|
|
220
|
-
|
|
235
|
+
// The icon is not decoration: goa-work-side-menu-item renders a blank
|
|
236
|
+
// item without one.
|
|
237
|
+
expect(app).toContain("{ label: 'Home', to: '/', icon: 'home' },");
|
|
221
238
|
expect(app).toContain('@item-click="onItemClick"');
|
|
222
239
|
// The content gutter is shared regardless of shell choice.
|
|
223
240
|
expect(app).toContain('<AppLayout');
|
|
@@ -312,7 +329,12 @@ describe('Vue App Generator', () => {
|
|
|
312
329
|
// response-envelope keys get inlined into every generated view.
|
|
313
330
|
expect(useApi).toContain('apiConvention');
|
|
314
331
|
expect(useApi).toContain('THE GLUE LAYER');
|
|
315
|
-
for (const method of [
|
|
332
|
+
for (const method of [
|
|
333
|
+
'function list',
|
|
334
|
+
'function get',
|
|
335
|
+
'function save',
|
|
336
|
+
'function action',
|
|
337
|
+
]) {
|
|
316
338
|
expect(useApi).toContain(method);
|
|
317
339
|
}
|
|
318
340
|
// The documented escape hatch for a limit/offset backend.
|
|
@@ -428,7 +450,9 @@ describe('Vue App Generator', () => {
|
|
|
428
450
|
.read('libs/vue-components/src/lib/patterns/AppHeader.vue')
|
|
429
451
|
.toString();
|
|
430
452
|
expect(appHeader).toContain('version="2"');
|
|
431
|
-
expect(appHeader).toMatch(
|
|
453
|
+
expect(appHeader).toMatch(
|
|
454
|
+
/<div[^>]*slot="utilities"[^>]*>[\s\S]*<slot name="utilities"/,
|
|
455
|
+
);
|
|
432
456
|
|
|
433
457
|
const app = host.read('apps/test/src/App.vue').toString();
|
|
434
458
|
expect(app).toMatch(/<template #utilities>[\s\S]*<goa-button-group/);
|
|
@@ -486,17 +510,36 @@ describe('Vue App Generator', () => {
|
|
|
486
510
|
expect(nginxConf).toContain('http://test-service2:3333/');
|
|
487
511
|
});
|
|
488
512
|
|
|
489
|
-
|
|
513
|
+
// Regression: this was a JSON file carrying `pathRewrite`, which is
|
|
514
|
+
// webpack-dev-server syntax. Vite has no such key and ignores unknown ones
|
|
515
|
+
// silently, so the dev server proxied to the right host and forwarded the path
|
|
516
|
+
// unrewritten -- every generated API call 404'd, with no warning. The fix has
|
|
517
|
+
// to be a module because the thing Vite needs is a function.
|
|
518
|
+
it('writes an executable vite dev proxy module whose rewrite actually rewrites', async () => {
|
|
490
519
|
await generator(host, {
|
|
491
520
|
...options,
|
|
492
521
|
proxy: { location: '/test/', proxyPass: 'http://test-service:3333/api/' },
|
|
493
522
|
});
|
|
494
|
-
expect(host.exists('apps/test/vite.proxy.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
expect(
|
|
523
|
+
expect(host.exists('apps/test/vite.proxy.cjs')).toBeTruthy();
|
|
524
|
+
// The inert JSON form must not be left beside it.
|
|
525
|
+
expect(host.exists('apps/test/vite.proxy.json')).toBeFalsy();
|
|
526
|
+
|
|
527
|
+
const source = host.read('apps/test/vite.proxy.cjs').toString();
|
|
528
|
+
expect(source).not.toContain('pathRewrite');
|
|
529
|
+
|
|
530
|
+
// Evaluate it the way @nx/vite does (require) and exercise the function,
|
|
531
|
+
// rather than asserting on the text of a config that has to *behave*.
|
|
532
|
+
const module = { exports: {} };
|
|
533
|
+
new Function('module', 'exports', source)(module, module.exports);
|
|
534
|
+
const proxy = module.exports as Record<
|
|
535
|
+
string,
|
|
536
|
+
{ target: string; rewrite: (path: string) => string }
|
|
537
|
+
>;
|
|
538
|
+
expect(proxy['/test/'].target).toBe('http://localhost:3333');
|
|
539
|
+
expect(typeof proxy['/test/'].rewrite).toBe('function');
|
|
540
|
+
expect(proxy['/test/'].rewrite('/test/v1/things')).toBe('/api/v1/things');
|
|
541
|
+
// Only the leading location is replaced, not a later occurrence of it.
|
|
542
|
+
expect(proxy['/test/'].rewrite('/test/a/test/b')).toBe('/api/a/test/b');
|
|
500
543
|
});
|
|
501
544
|
|
|
502
545
|
it('derives the nginx/dev proxy and the sandbox tag from --pairedProject alone', async () => {
|
|
@@ -508,10 +551,19 @@ describe('Vue App Generator', () => {
|
|
|
508
551
|
const nginxConf = host.read('apps/test/public/nginx.conf').toString();
|
|
509
552
|
expect(nginxConf).toContain('http://test-service:3333/test-service/');
|
|
510
553
|
|
|
511
|
-
const
|
|
512
|
-
|
|
554
|
+
const source = host.read('apps/test/vite.proxy.cjs').toString();
|
|
555
|
+
const module = { exports: {} };
|
|
556
|
+
new Function('module', 'exports', source)(module, module.exports);
|
|
557
|
+
const proxy = module.exports as Record<
|
|
558
|
+
string,
|
|
559
|
+
{ target: string; rewrite: (path: string) => string }
|
|
560
|
+
>;
|
|
561
|
+
expect(proxy['/api/'].target).toBe('http://localhost:3333');
|
|
562
|
+
// /api/v1/... must reach the service's own mount point, which is what
|
|
563
|
+
// useApi's base path is built against.
|
|
564
|
+
expect(proxy['/api/'].rewrite('/api/v1/claims')).toBe(
|
|
565
|
+
'/test-service/v1/claims',
|
|
513
566
|
);
|
|
514
|
-
expect(proxyConf['/api/'].target).toBe('http://localhost:3333');
|
|
515
567
|
|
|
516
568
|
const config = readProjectConfiguration(host, 'test');
|
|
517
569
|
expect(config.tags).toContain('adsp:proxy-service:test-service:3333');
|
|
@@ -550,4 +602,87 @@ describe('Vue App Generator', () => {
|
|
|
550
602
|
expect(nginxConf).toContain('http://test-service:3333/test-service/');
|
|
551
603
|
expect(nginxConf).toContain('http://other-service:9000/');
|
|
552
604
|
});
|
|
605
|
+
|
|
606
|
+
// Regression: the shell projects into goa-one-column-layout's native `header`
|
|
607
|
+
// and `footer` slots. The rule was only turned off for the vue-components lib,
|
|
608
|
+
// so when the shell adopted the element `nx lint` began failing on unmodified
|
|
609
|
+
// generator output -- two `vue/no-deprecated-slot-attribute` errors in the
|
|
610
|
+
// generator's own App.vue. Asserted for both layouts: the attribute is
|
|
611
|
+
// legitimate custom-element usage in either shell, and scoping the override to
|
|
612
|
+
// the one template that happens to emit it today is what caused the bug.
|
|
613
|
+
it.each(['header', 'internal'] as const)(
|
|
614
|
+
'disables vue/no-deprecated-slot-attribute for a %s-layout app',
|
|
615
|
+
async (layout) => {
|
|
616
|
+
await generator(host, { ...options, layout });
|
|
617
|
+
// create-nx-workspace's current default is flat config, so that is what
|
|
618
|
+
// @nx/vue writes for the app; read whichever is actually in effect rather
|
|
619
|
+
// than assuming a filename.
|
|
620
|
+
const config =
|
|
621
|
+
host.read('apps/test/eslint.config.mjs')?.toString() ??
|
|
622
|
+
host.read('apps/test/.eslintrc.json')?.toString() ??
|
|
623
|
+
'';
|
|
624
|
+
// Quote style differs between the flat-config writer and the legacy JSON
|
|
625
|
+
// one, so match the rule and its value together rather than a literal.
|
|
626
|
+
expect(config).toMatch(
|
|
627
|
+
/vue\/no-deprecated-slot-attribute["']?\s*:\s*["']off["']/,
|
|
628
|
+
);
|
|
629
|
+
},
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
it('emits native slot attributes the disabled rule would otherwise flag', async () => {
|
|
633
|
+
await generator(host, { ...options, layout: 'header' });
|
|
634
|
+
const app = host.read('apps/test/src/App.vue')?.toString() ?? '';
|
|
635
|
+
expect(app).toContain('slot="header"');
|
|
636
|
+
expect(app).toContain('slot="footer"');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// Regression: the generated guidance hardcoded `apps/` and `libs/` prefixes. In a
|
|
640
|
+
// TS-solution workspace projects sit at the workspace root, so every path in the
|
|
641
|
+
// AGENTS.md an agent is told to read was wrong. Asserted against the roots actually
|
|
642
|
+
// in the tree rather than a literal, so this holds under either layout.
|
|
643
|
+
it('points its guidance at the real project roots, not hardcoded apps//libs/', async () => {
|
|
644
|
+
addProjectConfiguration(host, 'test-service', {
|
|
645
|
+
root: 'apps/test-service',
|
|
646
|
+
});
|
|
647
|
+
await generator(host, { ...options, pairedProject: 'test-service' });
|
|
648
|
+
|
|
649
|
+
const agents = host.read('apps/test/AGENTS.md')?.toString() ?? '';
|
|
650
|
+
const libRoot = readProjectConfiguration(host, 'vue-components').root;
|
|
651
|
+
const serviceRoot = readProjectConfiguration(host, 'test-service').root;
|
|
652
|
+
|
|
653
|
+
expect(agents).toContain(`${libRoot}/src/lib/primitives/`);
|
|
654
|
+
expect(agents).toContain(`${serviceRoot}/AGENTS.md`);
|
|
655
|
+
// No path may name the lib without going through its resolved root.
|
|
656
|
+
const strayLibPaths = agents
|
|
657
|
+
.split('\n')
|
|
658
|
+
.filter(
|
|
659
|
+
(line) =>
|
|
660
|
+
line.includes('vue-components/src/') && !line.includes(libRoot),
|
|
661
|
+
);
|
|
662
|
+
expect(strayLibPaths).toEqual([]);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('documents the options that decide what a person sees', async () => {
|
|
666
|
+
await generator(host, options);
|
|
667
|
+
const agents = host.read('apps/test/AGENTS.md')?.toString() ?? '';
|
|
668
|
+
// These live in the vue-components catalog too, but the "Adding a view"
|
|
669
|
+
// decision table is where a generator gets chosen -- an agent that never
|
|
670
|
+
// opens the lib's own docs still has to be told to pass them.
|
|
671
|
+
for (const option of [
|
|
672
|
+
'badgeMap',
|
|
673
|
+
'referenceField',
|
|
674
|
+
'--icon',
|
|
675
|
+
'optionLabel',
|
|
676
|
+
]) {
|
|
677
|
+
expect(agents).toContain(option);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('feeds the signed-in user into the side menu profile button', async () => {
|
|
682
|
+
await generator(host, { ...options, layout: 'internal' });
|
|
683
|
+
const app = host.read('apps/test/src/App.vue')?.toString() ?? '';
|
|
684
|
+
// AppSideMenu falls back to a generic name, but the real one has to reach it
|
|
685
|
+
// or an authenticated user's own menu never shows who they are.
|
|
686
|
+
expect(app).toContain(':user-name="kc.fullName"');
|
|
687
|
+
});
|
|
553
688
|
});
|