@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
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"resource": {
|
|
27
27
|
"type": "string",
|
|
28
|
-
"description": "API resource path segment -- the view fetches /api/<resource>?page=&limit=&search=&sortBy=&sortDir=."
|
|
28
|
+
"description": "API resource path segment -- the view fetches /api/v1/<resource>?page=&limit=&search=&sortBy=&sortDir=."
|
|
29
29
|
},
|
|
30
30
|
"route": {
|
|
31
31
|
"type": "string",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"columns": {
|
|
35
35
|
"type": "string",
|
|
36
|
-
"description": "JSON array of table columns, in display order -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"badge\",\"sortable\":true}]'. Each item: { key, label, type?: \"text\"|\"date\"|\"currency\"|\"badge\" (default \"text\"), sortable?: boolean }. A plain array is also accepted when this generator is invoked programmatically. Nx's CLI option coercion only supports comma-separated primitive lists for array-typed schema properties, not JSON -- a JSON string is the only CLI syntax that actually survives Nx's own arg parsing for a structured list like this."
|
|
36
|
+
"description": "JSON array of table columns, in display order -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"badge\",\"sortable\":true}]'. Each item: { key, label, type?: \"text\"|\"date\"|\"currency\"|\"badge\" (default \"text\"), sortable?: boolean, options?: [{value,label}], badgeMap?: {value: badgeType} }. Supply `options` for any column whose stored value is a code, or the table renders the raw code. Supply `badgeMap` on a `badge` column to give each status its colour (e.g. {\"approved\":\"success\",\"declined\":\"emergency\"}); unmapped values render neutral. A plain array is also accepted when this generator is invoked programmatically. Nx's CLI option coercion only supports comma-separated primitive lists for array-typed schema properties, not JSON -- a JSON string is the only CLI syntax that actually survives Nx's own arg parsing for a structured list like this."
|
|
37
37
|
},
|
|
38
38
|
"filters": {
|
|
39
39
|
"type": "string",
|
|
@@ -57,18 +57,17 @@
|
|
|
57
57
|
"description": "Whether to generate a debounced search input above the table.",
|
|
58
58
|
"default": true
|
|
59
59
|
},
|
|
60
|
+
"icon": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Ionicon name for the generated side-menu entry, e.g. `list`. goa-work-side-menu-item renders a blank item without one.",
|
|
63
|
+
"default": "list"
|
|
64
|
+
},
|
|
60
65
|
"requiresAuth": {
|
|
61
66
|
"type": "boolean",
|
|
62
67
|
"description": "Whether the generated route requires authentication.",
|
|
63
68
|
"default": true
|
|
64
69
|
}
|
|
65
70
|
},
|
|
66
|
-
"required": [
|
|
67
|
-
"project",
|
|
68
|
-
"name",
|
|
69
|
-
"resource",
|
|
70
|
-
"route",
|
|
71
|
-
"columns"
|
|
72
|
-
],
|
|
71
|
+
"required": ["project", "name", "resource", "route", "columns"],
|
|
73
72
|
"additionalProperties": false
|
|
74
73
|
}
|
|
@@ -51,11 +51,16 @@ function normalizeOptions(host, options) {
|
|
|
51
51
|
}
|
|
52
52
|
function default_1(host, options) {
|
|
53
53
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
var _a;
|
|
54
55
|
const normalizedOptions = normalizeOptions(host, options);
|
|
55
56
|
// Idempotent -- ensures WorkspaceTable exists even in a project scaffolded
|
|
56
57
|
// before vue-components carried it.
|
|
57
58
|
yield (0, vue_components_1.default)(host);
|
|
58
|
-
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
59
|
+
(0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
|
|
60
|
+
// Derived here rather than tested inline in the template: EJS throws a
|
|
61
|
+
// ReferenceError on a bare undefined variable, so a flag the template
|
|
62
|
+
// reads must always be defined.
|
|
63
|
+
hasCodedColumns: normalizedOptions.columns.some((column) => { var _a; return (_a = column.options) === null || _a === void 0 ? void 0 : _a.length; }), hasBadgeColumns: normalizedOptions.columns.some((column) => column.type === 'badge'), tmpl: '' }));
|
|
59
64
|
(0, vue_router_1.insertVueRoute)(host, normalizedOptions.projectRoot, normalizedOptions.project, {
|
|
60
65
|
path: normalizedOptions.route,
|
|
61
66
|
componentImportPath: `../views/${normalizedOptions.viewFileName}.vue`,
|
|
@@ -67,6 +72,7 @@ function default_1(host, options) {
|
|
|
67
72
|
(0, vue_side_menu_1.insertSideMenuItem)(host, normalizedOptions.projectRoot, {
|
|
68
73
|
label: normalizedOptions.heading,
|
|
69
74
|
to: normalizedOptions.route,
|
|
75
|
+
icon: (_a = normalizedOptions.icon) !== null && _a !== void 0 ? _a : 'list',
|
|
70
76
|
});
|
|
71
77
|
yield (0, devkit_1.formatFiles)(host);
|
|
72
78
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":";;AA+EA,
|
|
1
|
+
{"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":"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"}
|
|
@@ -125,7 +125,9 @@ describe('Vue Workspace View Generator', () => {
|
|
|
125
125
|
expect(view).toContain('let loadSequence = 0');
|
|
126
126
|
expect(view).toContain('const sequence = ++loadSequence');
|
|
127
127
|
expect(view).toContain('if (sequence !== loadSequence) return;');
|
|
128
|
-
expect(view).toContain(
|
|
128
|
+
expect(view).toContain(
|
|
129
|
+
'if (sequence === loadSequence) loading.value = false;',
|
|
130
|
+
);
|
|
129
131
|
|
|
130
132
|
// The debounce timer must not outlive the component.
|
|
131
133
|
expect(view).toContain('onUnmounted(');
|
|
@@ -138,7 +140,7 @@ describe('Vue Workspace View Generator', () => {
|
|
|
138
140
|
expect(view).not.toContain('Intl.');
|
|
139
141
|
expect(view).not.toContain('function formatCurrency');
|
|
140
142
|
expect(view).toContain(
|
|
141
|
-
"
|
|
143
|
+
"badgeType(columnBadgeTypes['status'], row['status'])",
|
|
142
144
|
);
|
|
143
145
|
// A type: 'date' column is a calendar date -- formatDate, not
|
|
144
146
|
// formatDateTime, which would show an invented midnight.
|
|
@@ -205,11 +207,11 @@ describe('Vue Workspace View Generator', () => {
|
|
|
205
207
|
await expect(
|
|
206
208
|
generator(host, {
|
|
207
209
|
...baseOptions,
|
|
208
|
-
filters: JSON.stringify([
|
|
209
|
-
{ key: 'x', label: 'X', type: 'checkbox' },
|
|
210
|
-
]),
|
|
210
|
+
filters: JSON.stringify([{ key: 'x', label: 'X', type: 'checkbox' }]),
|
|
211
211
|
}),
|
|
212
|
-
).rejects.toThrow(
|
|
212
|
+
).rejects.toThrow(
|
|
213
|
+
'--filters[].type must be "dropdown" or "date"; got "checkbox" for "x".',
|
|
214
|
+
);
|
|
213
215
|
});
|
|
214
216
|
|
|
215
217
|
it('accepts a real array, the form a programmatic caller passes', async () => {
|
|
@@ -265,7 +267,11 @@ describe('Vue Workspace View Generator', () => {
|
|
|
265
267
|
}, 30000);
|
|
266
268
|
|
|
267
269
|
it('respects an explicit --heading and --pageSize', async () => {
|
|
268
|
-
await generator(host, {
|
|
270
|
+
await generator(host, {
|
|
271
|
+
...baseOptions,
|
|
272
|
+
heading: 'Grant Applications',
|
|
273
|
+
pageSize: 50,
|
|
274
|
+
});
|
|
269
275
|
const view = host
|
|
270
276
|
.read('apps/test/src/views/ApplicationsListView.vue')
|
|
271
277
|
.toString();
|
|
@@ -318,4 +324,64 @@ describe('Vue Workspace View Generator', () => {
|
|
|
318
324
|
const after = readProjectConfiguration(host, 'test');
|
|
319
325
|
expect(after).toEqual(before);
|
|
320
326
|
}, 30000);
|
|
327
|
+
|
|
328
|
+
// Regression: every badge column was hardcoded type="information", so a
|
|
329
|
+
// declined claim rendered neutral blue -- a badge's colour is the fastest thing
|
|
330
|
+
// a reader takes in, so the wrong one actively misinforms.
|
|
331
|
+
it('maps badge values to semantic types and labels', async () => {
|
|
332
|
+
await generator(host, {
|
|
333
|
+
...baseOptions,
|
|
334
|
+
columns: [
|
|
335
|
+
{ key: 'reference', label: 'Reference' },
|
|
336
|
+
{
|
|
337
|
+
key: 'status',
|
|
338
|
+
label: 'Status',
|
|
339
|
+
type: 'badge',
|
|
340
|
+
options: [{ value: 'declined', label: 'Declined' }],
|
|
341
|
+
badgeMap: { approved: 'success', declined: 'emergency' },
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
const view = host
|
|
346
|
+
.read('apps/test/src/views/ApplicationsListView.vue')
|
|
347
|
+
.toString();
|
|
348
|
+
// Quote style depends on whether formatFiles ran, so match the pairs.
|
|
349
|
+
expect(view).toMatch(/["']?declined["']?\s*:\s*["']emergency["']/);
|
|
350
|
+
expect(view).toMatch(/["']?approved["']?\s*:\s*["']success["']/);
|
|
351
|
+
expect(view).toContain("badgeType(columnBadgeTypes['status']");
|
|
352
|
+
expect(view).toContain("optionLabel(columnOptions['status']");
|
|
353
|
+
}, 30000);
|
|
354
|
+
|
|
355
|
+
it('renders a coded non-badge column using its label', async () => {
|
|
356
|
+
await generator(host, {
|
|
357
|
+
...baseOptions,
|
|
358
|
+
columns: [
|
|
359
|
+
{
|
|
360
|
+
key: 'region',
|
|
361
|
+
label: 'Region',
|
|
362
|
+
options: [{ value: 'northwest', label: 'North west' }],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
});
|
|
366
|
+
const view = host
|
|
367
|
+
.read('apps/test/src/views/ApplicationsListView.vue')
|
|
368
|
+
.toString();
|
|
369
|
+
expect(view).toContain(
|
|
370
|
+
"optionLabel(columnOptions['region'], row['region'])",
|
|
371
|
+
);
|
|
372
|
+
expect(view).toContain('North west');
|
|
373
|
+
}, 30000);
|
|
374
|
+
|
|
375
|
+
it('omits the lookup maps entirely when no column needs them', async () => {
|
|
376
|
+
await generator(host, {
|
|
377
|
+
...baseOptions,
|
|
378
|
+
columns: [{ key: 'reference', label: 'Reference' }],
|
|
379
|
+
});
|
|
380
|
+
const view = host
|
|
381
|
+
.read('apps/test/src/views/ApplicationsListView.vue')
|
|
382
|
+
.toString();
|
|
383
|
+
expect(view).not.toContain('columnOptions');
|
|
384
|
+
expect(view).not.toContain('columnBadgeTypes');
|
|
385
|
+
expect(view).not.toContain('optionLabel');
|
|
386
|
+
}, 30000);
|
|
321
387
|
});
|
|
@@ -11,6 +11,16 @@ type DevProxyEntry = {
|
|
|
11
11
|
pathRewrite: Record<string, string>;
|
|
12
12
|
};
|
|
13
13
|
export declare function buildDevProxyConf(nginxProxies: NginxProxyConfiguration[]): Record<string, DevProxyEntry>;
|
|
14
|
+
export declare const VITE_PROXY_FILENAME = "vite.proxy.cjs";
|
|
15
|
+
export interface ViteProxyRoute {
|
|
16
|
+
location: string;
|
|
17
|
+
target: string;
|
|
18
|
+
secure: boolean;
|
|
19
|
+
rewriteTo?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseViteDevProxyRoutes(source: string): ViteProxyRoute[];
|
|
22
|
+
export declare function viteDevProxyRoutes(nginxProxies: NginxProxyConfiguration[]): ViteProxyRoute[];
|
|
23
|
+
export declare function buildViteDevProxyModule(routes: ViteProxyRoute[]): string;
|
|
14
24
|
export declare function resolvePairedProjectProxy(host: Tree, pairedProject: string | undefined): PairedProjectProxy | undefined;
|
|
15
25
|
export interface PairedFrontendApp {
|
|
16
26
|
tag: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VITE_PROXY_FILENAME = void 0;
|
|
3
4
|
exports.buildDevProxyConf = buildDevProxyConf;
|
|
5
|
+
exports.parseViteDevProxyRoutes = parseViteDevProxyRoutes;
|
|
6
|
+
exports.viteDevProxyRoutes = viteDevProxyRoutes;
|
|
7
|
+
exports.buildViteDevProxyModule = buildViteDevProxyModule;
|
|
4
8
|
exports.resolvePairedProjectProxy = resolvePairedProjectProxy;
|
|
5
9
|
exports.resolvePairedFrontendApp = resolvePairedFrontendApp;
|
|
6
10
|
exports.applyProxyToExistingFrontend = applyProxyToExistingFrontend;
|
|
@@ -8,9 +12,10 @@ const devkit_1 = require("@nx/devkit");
|
|
|
8
12
|
const path = require("path");
|
|
9
13
|
const express_service_port_1 = require("./express-service-port");
|
|
10
14
|
const frontend_app_port_1 = require("./frontend-app-port");
|
|
11
|
-
// Builds the dev-server proxy JSON (
|
|
12
|
-
//
|
|
13
|
-
//
|
|
15
|
+
// Builds the webpack-dev-server proxy JSON (proxy.conf.json) from a set of nginx proxy
|
|
16
|
+
// entries. React/Angular only -- their dev servers are webpack-based and do honour
|
|
17
|
+
// `pathRewrite`. Vue goes through buildViteDevProxyModule instead; see the note there for
|
|
18
|
+
// why a JSON file cannot work for Vite.
|
|
14
19
|
function buildDevProxyConf(nginxProxies) {
|
|
15
20
|
return nginxProxies.reduce((proxyConf, nginxProxy) => {
|
|
16
21
|
const upstreamUrl = new URL(nginxProxy.proxyPass);
|
|
@@ -25,6 +30,82 @@ function buildDevProxyConf(nginxProxies) {
|
|
|
25
30
|
return Object.assign(Object.assign({}, proxyConf), { [nginxProxy.location]: entry });
|
|
26
31
|
}, {});
|
|
27
32
|
}
|
|
33
|
+
// Vue's dev proxy filename. A module, not JSON -- see buildViteDevProxyModule.
|
|
34
|
+
exports.VITE_PROXY_FILENAME = 'vite.proxy.cjs';
|
|
35
|
+
// Extracts the routes out of a previously generated vite proxy module, so a second paired
|
|
36
|
+
// project merges into the file instead of replacing it. Regex rather than a parser because
|
|
37
|
+
// this module's shape is entirely generator-owned (see buildViteDevProxyModule) and never
|
|
38
|
+
// hand-authored -- and it returns [] on anything it doesn't recognise, so the caller can
|
|
39
|
+
// tell "no routes" from "not our format" by comparing against file existence.
|
|
40
|
+
//
|
|
41
|
+
// Tolerates the trailing comma and multi-line layout formatFiles introduces: the file on
|
|
42
|
+
// disk is the *formatted* output, not the string this module emitted, and a regex anchored
|
|
43
|
+
// on `}` immediately after the last field silently matches nothing there -- which would
|
|
44
|
+
// drop every existing route the next time a project is paired.
|
|
45
|
+
function parseViteDevProxyRoutes(source) {
|
|
46
|
+
const routes = [];
|
|
47
|
+
const entry = /\{\s*location:\s*'([^']+)',\s*target:\s*'([^']+)',\s*secure:\s*(true|false)(?:,\s*rewriteTo:\s*'([^']*)')?\s*,?\s*\}/g;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = entry.exec(source)) !== null) {
|
|
50
|
+
routes.push(Object.assign({ location: match[1], target: match[2], secure: match[3] === 'true' }, (match[4] ? { rewriteTo: match[4] } : {})));
|
|
51
|
+
}
|
|
52
|
+
return routes;
|
|
53
|
+
}
|
|
54
|
+
function viteDevProxyRoutes(nginxProxies) {
|
|
55
|
+
return nginxProxies.map((nginxProxy) => {
|
|
56
|
+
const upstreamUrl = new URL(nginxProxy.proxyPass);
|
|
57
|
+
return Object.assign({ location: nginxProxy.location, target: `${upstreamUrl.protocol}//localhost${upstreamUrl.port ? ':' + upstreamUrl.port : ''}`, secure: upstreamUrl.protocol === 'https:' }, (upstreamUrl.pathname.length > 1
|
|
58
|
+
? { rewriteTo: upstreamUrl.pathname }
|
|
59
|
+
: {}));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Builds the Vue dev-server proxy as a CommonJS module rather than JSON.
|
|
63
|
+
//
|
|
64
|
+
// Vite's proxy honours a `rewrite` FUNCTION and has nothing named `pathRewrite` -- that is
|
|
65
|
+
// http-proxy-middleware/webpack-dev-server syntax. Vite ignores unknown keys silently, so a
|
|
66
|
+
// JSON file carrying `pathRewrite` produces a dev server that proxies to the right host and
|
|
67
|
+
// then forwards the path unrewritten: every generated API call 404s, with no warning
|
|
68
|
+
// anywhere. A JSON file cannot express the fix, because the fix is a function.
|
|
69
|
+
//
|
|
70
|
+
// @nx/vite `require()`s whatever `proxyConfig` names (getViteServerProxyConfigPath only
|
|
71
|
+
// checks existsSync), so a module works and can hold the function. `.cjs`, not `.js`, so it
|
|
72
|
+
// stays CommonJS even if the consuming workspace sets `"type": "module"`.
|
|
73
|
+
//
|
|
74
|
+
// Emitted as data-plus-loop rather than a literal per entry so a second paired project can
|
|
75
|
+
// be merged by appending one row -- see parseViteDevProxyRoutes.
|
|
76
|
+
function buildViteDevProxyModule(routes) {
|
|
77
|
+
const rows = routes
|
|
78
|
+
.map(({ location, target, secure, rewriteTo }) => ` { location: '${location}', target: '${target}', secure: ${secure}` +
|
|
79
|
+
`${rewriteTo ? `, rewriteTo: '${rewriteTo}'` : ''} },`)
|
|
80
|
+
.join('\n');
|
|
81
|
+
return `// Vite dev-server proxy. Generated by @abgov/nx-adsp.
|
|
82
|
+
//
|
|
83
|
+
// A CommonJS module, not JSON, because Vite rewrites a proxied path with a \`rewrite\`
|
|
84
|
+
// function. The webpack-dev-server key for this is a different name that Vite does not
|
|
85
|
+
// implement, and it ignores unknown keys silently -- so a JSON proxy file forwards the
|
|
86
|
+
// path unrewritten and every API call 404s.
|
|
87
|
+
//
|
|
88
|
+
// Add a route by appending a row below; \`rewriteTo\` replaces the matched \`location\`
|
|
89
|
+
// prefix (so '/api/' -> '/my-service/' turns /api/v1/things into /my-service/v1/things).
|
|
90
|
+
const routes = [
|
|
91
|
+
${rows}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
module.exports = Object.fromEntries(
|
|
95
|
+
routes.map(({ location, target, secure, rewriteTo }) => [
|
|
96
|
+
location,
|
|
97
|
+
{
|
|
98
|
+
target,
|
|
99
|
+
secure,
|
|
100
|
+
changeOrigin: false,
|
|
101
|
+
...(rewriteTo
|
|
102
|
+
? { rewrite: (urlPath) => urlPath.replace(new RegExp('^' + location), rewriteTo) }
|
|
103
|
+
: {}),
|
|
104
|
+
},
|
|
105
|
+
]),
|
|
106
|
+
);
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
28
109
|
const PAIRED_PROJECT_LOCATION = '/api/';
|
|
29
110
|
// Resolves a frontend's --pairedProject into the nginx/dev-server proxy entry that routes API
|
|
30
111
|
// calls to it, plus the `adsp:proxy-service:<name>:<port>` tag the sandbox executor reads to
|
|
@@ -68,13 +149,13 @@ function resolvePairedFrontendApp(host, pairedProject) {
|
|
|
68
149
|
// frontend generator does when the frontend runs first. Safe to call when the frontend doesn't
|
|
69
150
|
// exist yet (composite generator ordering — backend is scaffolded before frontend); returns early.
|
|
70
151
|
function applyProxyToExistingFrontend(host, pairedFrontend, backendProjectName) {
|
|
71
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
152
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
72
153
|
const frontendName = (0, devkit_1.names)(pairedFrontend).fileName;
|
|
73
154
|
let frontendConfig;
|
|
74
155
|
try {
|
|
75
156
|
frontendConfig = (0, devkit_1.readProjectConfiguration)(host, frontendName);
|
|
76
157
|
}
|
|
77
|
-
catch (
|
|
158
|
+
catch (_m) {
|
|
78
159
|
// Frontend not scaffolded yet (composite generator ordering).
|
|
79
160
|
return;
|
|
80
161
|
}
|
|
@@ -118,33 +199,59 @@ function applyProxyToExistingFrontend(host, pairedFrontend, backendProjectName)
|
|
|
118
199
|
existingNginx.slice(serverCloseIdx));
|
|
119
200
|
}
|
|
120
201
|
}
|
|
121
|
-
// 2. Write (or merge into) the dev proxy config file.
|
|
122
|
-
|
|
202
|
+
// 2. Write (or merge into) the dev proxy config file. Vue gets a .cjs module
|
|
203
|
+
// (Vite needs a `rewrite` function, which JSON cannot hold); React/Angular
|
|
204
|
+
// keep the webpack-dev-server JSON, which does honour `pathRewrite`.
|
|
205
|
+
const devProxyFilename = isVue ? exports.VITE_PROXY_FILENAME : 'proxy.conf.json';
|
|
123
206
|
const devProxyPath = path.join(projectRoot, devProxyFilename);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
207
|
+
if (isVue) {
|
|
208
|
+
const existingSource = (_d = (_c = host.read(devProxyPath)) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '';
|
|
209
|
+
const existingRoutes = parseViteDevProxyRoutes(existingSource);
|
|
210
|
+
const alreadyRouted = existingRoutes.some((route) => route.location === nginxProxy.location);
|
|
211
|
+
// Rewrites the whole module from the union rather than splicing a row in:
|
|
212
|
+
// the format is generator-owned, so regenerating is safer than editing.
|
|
213
|
+
if (!alreadyRouted) {
|
|
214
|
+
host.write(devProxyPath, buildViteDevProxyModule([
|
|
215
|
+
...existingRoutes,
|
|
216
|
+
...viteDevProxyRoutes([nginxProxy]),
|
|
217
|
+
]));
|
|
218
|
+
}
|
|
219
|
+
// A pre-existing app may still carry the inert JSON form; drop it so there
|
|
220
|
+
// aren't two files that look like proxy config.
|
|
221
|
+
const staleJsonPath = path.join(projectRoot, 'vite.proxy.json');
|
|
222
|
+
if (host.exists(staleJsonPath)) {
|
|
223
|
+
host.delete(staleJsonPath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const existingProxy = host.exists(devProxyPath)
|
|
228
|
+
? (0, devkit_1.readJson)(host, devProxyPath)
|
|
229
|
+
: {};
|
|
230
|
+
if (!existingProxy[nginxProxy.location]) {
|
|
231
|
+
(0, devkit_1.writeJson)(host, devProxyPath, Object.assign(Object.assign({}, existingProxy), buildDevProxyConf([nginxProxy])));
|
|
232
|
+
}
|
|
129
233
|
}
|
|
130
234
|
// 3. Patch the frontend's project configuration.
|
|
131
235
|
const proxyTag = `adsp:proxy-service:${backendService}:${port}`;
|
|
132
|
-
if (!((
|
|
133
|
-
frontendConfig.tags = [...((
|
|
236
|
+
if (!((_e = frontendConfig.tags) !== null && _e !== void 0 ? _e : []).includes(proxyTag)) {
|
|
237
|
+
frontendConfig.tags = [...((_f = frontendConfig.tags) !== null && _f !== void 0 ? _f : []), proxyTag];
|
|
134
238
|
}
|
|
135
|
-
if (((
|
|
239
|
+
if (((_h = (_g = frontendConfig.targets) === null || _g === void 0 ? void 0 : _g.serve) === null || _h === void 0 ? void 0 : _h.options) &&
|
|
136
240
|
!frontendConfig.targets.serve.options.proxyConfig) {
|
|
137
241
|
frontendConfig.targets.serve.options = Object.assign(Object.assign({}, frontendConfig.targets.serve.options), { proxyConfig: devProxyPath });
|
|
138
242
|
}
|
|
139
243
|
// React/Angular only: add nginx.conf as a webpack build asset so it lands in the output root.
|
|
140
244
|
// Vue's @nx/vite:build copies public/ automatically — no asset glob needed.
|
|
141
|
-
if (isRootNginx && ((
|
|
142
|
-
const assets = (
|
|
245
|
+
if (isRootNginx && ((_k = (_j = frontendConfig.targets) === null || _j === void 0 ? void 0 : _j.build) === null || _k === void 0 ? void 0 : _k.options)) {
|
|
246
|
+
const assets = (_l = frontendConfig.targets.build.options.assets) !== null && _l !== void 0 ? _l : [];
|
|
143
247
|
const hasNginxAsset = assets.some((a) => typeof a === 'object' &&
|
|
144
248
|
a.glob === 'nginx.conf' &&
|
|
145
249
|
a.input === projectRoot);
|
|
146
250
|
if (!hasNginxAsset) {
|
|
147
|
-
frontendConfig.targets.build.options = Object.assign(Object.assign({}, frontendConfig.targets.build.options), { assets: [
|
|
251
|
+
frontendConfig.targets.build.options = Object.assign(Object.assign({}, frontendConfig.targets.build.options), { assets: [
|
|
252
|
+
...assets,
|
|
253
|
+
{ glob: 'nginx.conf', input: projectRoot, output: './' },
|
|
254
|
+
] });
|
|
148
255
|
}
|
|
149
256
|
}
|
|
150
257
|
(0, devkit_1.updateProjectConfiguration)(host, frontendName, frontendConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paired-project.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/paired-project.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paired-project.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/paired-project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildViteDevProxyModule,
|
|
3
|
+
parseViteDevProxyRoutes,
|
|
4
|
+
viteDevProxyRoutes,
|
|
5
|
+
ViteProxyRoute,
|
|
6
|
+
} from './paired-project';
|
|
7
|
+
|
|
8
|
+
// Evaluates the emitted module the way @nx/vite does (it `require()`s the file
|
|
9
|
+
// named by proxyConfig), so these assert what Vite will actually receive rather
|
|
10
|
+
// than the text of a config that has to behave.
|
|
11
|
+
function loadProxy(source: string) {
|
|
12
|
+
const module = { exports: {} };
|
|
13
|
+
new Function('module', 'exports', source)(module, module.exports);
|
|
14
|
+
return module.exports as Record<
|
|
15
|
+
string,
|
|
16
|
+
{ target: string; secure: boolean; rewrite?: (path: string) => string }
|
|
17
|
+
>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('vite dev proxy module', () => {
|
|
21
|
+
const route: ViteProxyRoute = {
|
|
22
|
+
location: '/api/',
|
|
23
|
+
target: 'http://localhost:3333',
|
|
24
|
+
secure: false,
|
|
25
|
+
rewriteTo: '/my-service/',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
it('rewrites the location prefix to the service mount point', () => {
|
|
29
|
+
const proxy = loadProxy(buildViteDevProxyModule([route]));
|
|
30
|
+
expect(proxy['/api/'].target).toBe('http://localhost:3333');
|
|
31
|
+
expect(proxy['/api/'].rewrite?.('/api/v1/things')).toBe(
|
|
32
|
+
'/my-service/v1/things',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('replaces only the leading occurrence of the location', () => {
|
|
37
|
+
const proxy = loadProxy(buildViteDevProxyModule([route]));
|
|
38
|
+
expect(proxy['/api/'].rewrite?.('/api/a/api/b')).toBe('/my-service/a/api/b');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('omits rewrite entirely when the upstream has no path prefix', () => {
|
|
42
|
+
const proxy = loadProxy(
|
|
43
|
+
buildViteDevProxyModule([
|
|
44
|
+
{ location: '/api/', target: 'http://localhost:3333', secure: false },
|
|
45
|
+
]),
|
|
46
|
+
);
|
|
47
|
+
expect(proxy['/api/'].rewrite).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('derives routes from an nginx proxy configuration', () => {
|
|
51
|
+
const [derived] = viteDevProxyRoutes([
|
|
52
|
+
{ location: '/api/', proxyPass: 'http://my-service:3333/my-service/' },
|
|
53
|
+
]);
|
|
54
|
+
expect(derived).toEqual({
|
|
55
|
+
location: '/api/',
|
|
56
|
+
target: 'http://localhost:3333',
|
|
57
|
+
secure: false,
|
|
58
|
+
rewriteTo: '/my-service/',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('parseViteDevProxyRoutes', () => {
|
|
63
|
+
it('round-trips what buildViteDevProxyModule emits', () => {
|
|
64
|
+
expect(parseViteDevProxyRoutes(buildViteDevProxyModule([route]))).toEqual([
|
|
65
|
+
route,
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// The file on disk is the *formatted* output, not the string the builder
|
|
70
|
+
// returned: formatFiles reflows each row across lines and adds a trailing
|
|
71
|
+
// comma. A regex anchored on `}` immediately after the last field matches
|
|
72
|
+
// nothing here, and the merge path would then drop every existing route --
|
|
73
|
+
// silently un-pairing an already-paired frontend.
|
|
74
|
+
it('parses the multi-line, trailing-comma layout formatFiles produces', () => {
|
|
75
|
+
const formatted = `const routes = [
|
|
76
|
+
{
|
|
77
|
+
location: '/api/',
|
|
78
|
+
target: 'http://localhost:3333',
|
|
79
|
+
secure: false,
|
|
80
|
+
rewriteTo: '/my-service/',
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
`;
|
|
84
|
+
expect(parseViteDevProxyRoutes(formatted)).toEqual([route]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('parses a formatted row that has no rewriteTo', () => {
|
|
88
|
+
const formatted = `const routes = [
|
|
89
|
+
{
|
|
90
|
+
location: '/api/',
|
|
91
|
+
target: 'http://localhost:3333',
|
|
92
|
+
secure: false,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
`;
|
|
96
|
+
expect(parseViteDevProxyRoutes(formatted)).toEqual([
|
|
97
|
+
{ location: '/api/', target: 'http://localhost:3333', secure: false },
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('preserves an existing route when a second is appended', () => {
|
|
102
|
+
const first = buildViteDevProxyModule([route]);
|
|
103
|
+
const merged = buildViteDevProxyModule([
|
|
104
|
+
...parseViteDevProxyRoutes(first),
|
|
105
|
+
{
|
|
106
|
+
location: '/other/',
|
|
107
|
+
target: 'http://localhost:4444',
|
|
108
|
+
secure: false,
|
|
109
|
+
rewriteTo: '/other-service/',
|
|
110
|
+
},
|
|
111
|
+
]);
|
|
112
|
+
const proxy = loadProxy(merged);
|
|
113
|
+
expect(Object.keys(proxy)).toEqual(['/api/', '/other/']);
|
|
114
|
+
expect(proxy['/api/'].rewrite?.('/api/v1/x')).toBe('/my-service/v1/x');
|
|
115
|
+
expect(proxy['/other/'].rewrite?.('/other/v1/y')).toBe(
|
|
116
|
+
'/other-service/v1/y',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns no routes for a file that is not this format', () => {
|
|
121
|
+
expect(parseViteDevProxyRoutes('{ "/api/": { "target": "x" } }')).toEqual(
|
|
122
|
+
[],
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
package/src/utils/quality.js
CHANGED
|
@@ -138,17 +138,51 @@ import AxeBuilder from '@axe-core/playwright';
|
|
|
138
138
|
// here means the WCAG 2.1 A/AA baseline, not full accessibility conformance.
|
|
139
139
|
const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// ADD YOUR ROUTES HERE. Coverage is exactly this list — a route that is not in
|
|
142
|
+
// it is never checked, so this array, not the spec, is what decides how much of
|
|
143
|
+
// the app is actually held to the WCAG baseline.
|
|
144
|
+
//
|
|
145
|
+
// It ships with only '/' because that is the sole route a freshly generated app
|
|
146
|
+
// has. Do not read a green run against it as a meaningful result: the landing
|
|
147
|
+
// page is mostly app shell, so it exercises almost none of the goa-* elements
|
|
148
|
+
// the rest of the app is built from — tables, dropdowns, date pickers,
|
|
149
|
+
// steppers, side-menu items, form steps. Those are where accessibility defects
|
|
150
|
+
// actually live.
|
|
151
|
+
//
|
|
152
|
+
// Nothing keeps this list in sync for you. Routes arrive both from the
|
|
153
|
+
// \`nx g @abgov/nx-adsp:vue-*-view\` generators and from hand-editing the router,
|
|
154
|
+
// and no generator writes to this file — it is deliberately left alone once it
|
|
155
|
+
// exists so your edits survive re-running them. Adding a route to the router and
|
|
156
|
+
// not adding it here is the normal way this check silently stops covering the
|
|
157
|
+
// app.
|
|
158
|
+
//
|
|
159
|
+
// Two things to watch when you add one:
|
|
160
|
+
//
|
|
161
|
+
// - Point at a path that renders real content. A detail or edit route needs an
|
|
162
|
+
// id that resolves against whatever the dev server talks to; otherwise the
|
|
163
|
+
// view renders its error state and the check passes against an empty screen.
|
|
164
|
+
// - Triage a failure before assuming it is yours. Some violations come from
|
|
165
|
+
// inside @abgov/web-components' own shadow DOM (a decorative icon with
|
|
166
|
+
// \`role="img"\` and an empty \`aria-label\`, a \`role="menuitem"\` with no menu
|
|
167
|
+
// parent) and cannot be fixed from app code. Those need an upstream fix or a
|
|
168
|
+
// documented exclusion — not a workaround in your view.
|
|
169
|
+
const ROUTES = ['/'];
|
|
143
170
|
|
|
144
|
-
|
|
171
|
+
for (const route of ROUTES) {
|
|
172
|
+
test(\`has no WCAG 2.1 A/AA accessibility violations: \${route}\`, async ({
|
|
173
|
+
page,
|
|
174
|
+
}) => {
|
|
175
|
+
await page.goto(route);
|
|
145
176
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
const results = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze();
|
|
178
|
+
|
|
179
|
+
const summary = results.violations.map(
|
|
180
|
+
(v) =>
|
|
181
|
+
\`[\${v.impact}] \${v.id}: \${v.help} (\${v.nodes.length} node(s)) \${v.helpUrl}\`,
|
|
182
|
+
);
|
|
183
|
+
expect(summary, \`\${route}\\n\${summary.join('\\n')}\`).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
152
186
|
`;
|
|
153
187
|
/**
|
|
154
188
|
* Writes a Playwright + axe-core accessibility spec into the generated e2e
|