@abgov/nx-adsp 13.31.0 → 13.31.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -32
- package/package.json +1 -1
- package/src/generators/angular-app/angular-app.js +2 -2
- package/src/generators/angular-app/files/AGENTS.md__tmpl__ +16 -0
- package/src/generators/express-service/express-service.spec.ts +26 -13
- package/src/generators/express-service/schema.json +1 -1
- package/src/generators/react-app/files/AGENTS.md__tmpl__ +16 -0
- package/src/generators/react-app/react-app.js +2 -2
- package/src/generators/vue-admin-crud/files/src/views/__editViewFileName__.vue__tmpl__ +3 -5
- package/src/generators/vue-admin-crud/files/src/views/__listViewFileName__.vue__tmpl__ +33 -4
- package/src/generators/vue-admin-crud/schema.d.ts +2 -0
- package/src/generators/vue-admin-crud/schema.json +7 -8
- package/src/generators/vue-admin-crud/vue-admin-crud.js +6 -1
- package/src/generators/vue-admin-crud/vue-admin-crud.js.map +1 -1
- package/src/generators/vue-admin-crud/vue-admin-crud.spec.ts +96 -13
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +56 -11
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +2 -1
- package/src/generators/vue-app/files/src/composables/useApi.spec.ts__tmpl__ +5 -3
- package/src/generators/vue-app/files/src/composables/useApi.ts__tmpl__ +14 -2
- package/src/generators/vue-app/vue-app.js +33 -6
- package/src/generators/vue-app/vue-app.js.map +1 -1
- package/src/generators/vue-app/vue-app.spec.ts +133 -13
- package/src/generators/vue-components/files/AGENTS.md__tmpl__ +36 -17
- package/src/generators/vue-components/files/src/index.ts__tmpl__ +3 -0
- package/src/generators/vue-components/files/src/lib/formatters.ts__tmpl__ +58 -0
- package/src/generators/vue-components/files/src/lib/patterns/AppSideMenu.vue__tmpl__ +14 -3
- package/src/generators/vue-components/files/src/lib/patterns/FilterBar.vue__tmpl__ +7 -1
- package/src/generators/vue-components/files/src/lib/patterns/WorkspaceTable.vue__tmpl__ +7 -1
- package/src/generators/vue-components/vue-components.d.ts +1 -0
- package/src/generators/vue-components/vue-components.js +18 -39
- package/src/generators/vue-components/vue-components.js.map +1 -1
- package/src/generators/vue-components/vue-components.spec.ts +94 -2
- package/src/generators/vue-detail-view/files/src/views/__viewFileName__.vue__tmpl__ +51 -2
- package/src/generators/vue-detail-view/schema.d.ts +18 -0
- package/src/generators/vue-detail-view/schema.json +2 -2
- package/src/generators/vue-detail-view/vue-detail-view.js +4 -1
- package/src/generators/vue-detail-view/vue-detail-view.js.map +1 -1
- package/src/generators/vue-detail-view/vue-detail-view.spec.ts +36 -6
- package/src/generators/vue-intake-view/files/shared/src/views/__confirmationViewFileName__.vue__tmpl__ +60 -6
- package/src/generators/vue-intake-view/files/shared/src/views/__reviewViewFileName__.vue__tmpl__ +37 -1
- package/src/generators/vue-intake-view/files/steps/src/views/__stepViewFileName__.vue__tmpl__ +1 -1
- package/src/generators/vue-intake-view/schema.d.ts +10 -0
- package/src/generators/vue-intake-view/schema.json +12 -9
- package/src/generators/vue-intake-view/vue-intake-view.js +9 -3
- package/src/generators/vue-intake-view/vue-intake-view.js.map +1 -1
- package/src/generators/vue-intake-view/vue-intake-view.spec.ts +113 -12
- package/src/generators/vue-workspace-view/files/src/views/__viewFileName__.vue__tmpl__ +49 -1
- package/src/generators/vue-workspace-view/schema.d.ts +22 -0
- package/src/generators/vue-workspace-view/schema.json +8 -9
- package/src/generators/vue-workspace-view/vue-workspace-view.js +7 -1
- package/src/generators/vue-workspace-view/vue-workspace-view.js.map +1 -1
- package/src/generators/vue-workspace-view/vue-workspace-view.spec.ts +73 -7
- package/src/utils/paired-project.d.ts +10 -0
- package/src/utils/paired-project.js +125 -18
- package/src/utils/paired-project.js.map +1 -1
- package/src/utils/paired-project.spec.ts +126 -0
- package/src/utils/quality.js +43 -9
- package/src/utils/quality.js.map +1 -1
- package/src/utils/quality.spec.ts +35 -2
- package/src/utils/vue-eslint.d.ts +2 -0
- package/src/utils/vue-eslint.js +48 -0
- package/src/utils/vue-eslint.js.map +1 -0
- package/src/utils/vue-side-menu.d.ts +13 -1
- package/src/utils/vue-side-menu.js +50 -5
- package/src/utils/vue-side-menu.js.map +1 -1
- package/src/utils/vue-side-menu.spec.ts +100 -0
- package/src/utils/vue-tsconfig.d.ts +26 -0
- package/src/utils/vue-tsconfig.js +49 -0
- package/src/utils/vue-tsconfig.js.map +1 -0
|
@@ -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
|
package/src/utils/quality.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/quality.ts"],"names":[],"mappings":";;AAgBA,sDA0CC;AAiBD,sDAgBC;AAOD,4CAeC;AAoBD,4DAmBC;
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -239,8 +239,10 @@ describe('addAxeAccessibilityCheck', () => {
|
|
|
239
239
|
expect(spec).toContain("import AxeBuilder from '@axe-core/playwright'");
|
|
240
240
|
expect(spec).toContain("['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']");
|
|
241
241
|
expect(spec).toContain('.withTags(WCAG_TAGS).analyze()');
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
// Navigation and the failure message are asserted by the ROUTES tests below
|
|
243
|
+
// -- both are now per-route rather than a single hardcoded '/'.
|
|
244
|
+
expect(spec).toContain('await page.goto(route)');
|
|
245
|
+
expect(spec).toContain('summary.join');
|
|
244
246
|
});
|
|
245
247
|
|
|
246
248
|
it('is idempotent — a second call does not clobber a manually-edited file', () => {
|
|
@@ -252,4 +254,35 @@ describe('addAxeAccessibilityCheck', () => {
|
|
|
252
254
|
|
|
253
255
|
expect(tree.read(specPath).toString()).toBe('// manually edited\n');
|
|
254
256
|
});
|
|
257
|
+
|
|
258
|
+
// The spec is a seed, not a finished gate: its coverage is exactly its ROUTES
|
|
259
|
+
// array, and no generator maintains that list. It shipped with a hardcoded
|
|
260
|
+
// `page.goto('/')` and no hint of that, so a consumer reasonably read a green
|
|
261
|
+
// run as "accessibility is covered" while it checked one route -- the one route
|
|
262
|
+
// that exercises almost none of the goa-* elements the app is built from.
|
|
263
|
+
it('drives its coverage from an editable ROUTES list, seeded with /', () => {
|
|
264
|
+
addAxeAccessibilityCheck(tree, 'apps/my-app-e2e');
|
|
265
|
+
const spec = tree.read('apps/my-app-e2e/src/a11y.spec.ts').toString();
|
|
266
|
+
|
|
267
|
+
expect(spec).toContain("const ROUTES = ['/']");
|
|
268
|
+
// A route the list doesn't name is never checked, so the list has to be the
|
|
269
|
+
// thing that's iterated -- not one hardcoded navigation.
|
|
270
|
+
expect(spec).toContain('for (const route of ROUTES)');
|
|
271
|
+
expect(spec).toContain('await page.goto(route)');
|
|
272
|
+
expect(spec).not.toContain("page.goto('/')");
|
|
273
|
+
// The failure has to name the route, or a multi-route run can't be triaged.
|
|
274
|
+
expect(spec).toContain('violations: ${route}');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('tells the reader to expand the route list, and what to expect when they do', () => {
|
|
278
|
+
addAxeAccessibilityCheck(tree, 'apps/my-app-e2e');
|
|
279
|
+
const spec = tree.read('apps/my-app-e2e/src/a11y.spec.ts').toString();
|
|
280
|
+
|
|
281
|
+
expect(spec).toContain('ADD YOUR ROUTES HERE');
|
|
282
|
+
// Routes arrive by hand as well as from generators -- the reader has to know
|
|
283
|
+
// nothing keeps the list in sync.
|
|
284
|
+
expect(spec).toContain('hand-editing the router');
|
|
285
|
+
// And that a failure may not be theirs to fix.
|
|
286
|
+
expect(spec).toContain('shadow DOM');
|
|
287
|
+
});
|
|
255
288
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disableSlotAttributeRule = disableSlotAttributeRule;
|
|
4
|
+
const internal_1 = require("@nx/eslint/internal");
|
|
5
|
+
// Turns off vue/no-deprecated-slot-attribute for a generated Vue project.
|
|
6
|
+
//
|
|
7
|
+
// Every project this plugin generates projects content into a goa-* web
|
|
8
|
+
// component's native slot via the `slot` attribute -- `slot="actions"` on
|
|
9
|
+
// goa-modal in the shared wrapper lib, `slot="header"`/`slot="footer"` on
|
|
10
|
+
// goa-one-column-layout in the app shell. That is legitimate custom-element
|
|
11
|
+
// usage, not the deprecated Vue 2 component-slot syntax the rule targets, and
|
|
12
|
+
// there is no alternative: Vue's `v-slot`/`#name` shorthand only addresses slots
|
|
13
|
+
// declared by a Vue component, never a custom element's.
|
|
14
|
+
//
|
|
15
|
+
// Shared rather than per-generator on purpose. This hazard was originally solved
|
|
16
|
+
// only inside the vue-components generator, so when the app shell later adopted
|
|
17
|
+
// goa-one-column-layout its `slot` attributes tripped the rule and `nx lint`
|
|
18
|
+
// failed on unmodified generator output. Any generator emitting a native slot
|
|
19
|
+
// attribute must call this, so keep it in one place.
|
|
20
|
+
//
|
|
21
|
+
// Uses @nx/eslint's own override helpers rather than hand-editing a specific file
|
|
22
|
+
// format: create-nx-workspace's current default is flat config
|
|
23
|
+
// (eslint.config.mjs), a JS module exporting an array rather than JSON.
|
|
24
|
+
// addOverrideToLintConfig/lintConfigHasOverride detect flat vs legacy internally
|
|
25
|
+
// (via @nx/eslint's own useFlatConfig check) and edit whichever is in effect --
|
|
26
|
+
// AST-aware for flat config, not string splicing.
|
|
27
|
+
function disableSlotAttributeRule(host, projectRoot, reason) {
|
|
28
|
+
if (!(0, internal_1.isEslintConfigSupported)(host, projectRoot)) {
|
|
29
|
+
console.warn(`\n⚠ No ESLint config found for ${projectRoot} — could not disable vue/no-deprecated-slot-attribute.\n` +
|
|
30
|
+
` ${reason}; if lint flags it, turn that rule off for this project.\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// `files` is `string | string[]` per the Linter type — normalize before checking.
|
|
34
|
+
const isVueOverride = (o) => (Array.isArray(o.files) ? o.files : o.files ? [o.files] : []).some((f) => f.includes('vue'));
|
|
35
|
+
const alreadyDisabled = (0, internal_1.lintConfigHasOverride)(host, projectRoot, (o) => {
|
|
36
|
+
var _a;
|
|
37
|
+
return isVueOverride(o) &&
|
|
38
|
+
((_a = o.rules) === null || _a === void 0 ? void 0 : _a['vue/no-deprecated-slot-attribute']) === 'off';
|
|
39
|
+
});
|
|
40
|
+
if (alreadyDisabled) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
(0, internal_1.addOverrideToLintConfig)(host, projectRoot, {
|
|
44
|
+
files: ['*.vue'],
|
|
45
|
+
rules: { 'vue/no-deprecated-slot-attribute': 'off' },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=vue-eslint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-eslint.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/vue-eslint.ts"],"names":[],"mappings":";;AA8BA,4DAkCC;AA/DD,kDAI6B;AAG7B,0EAA0E;AAC1E,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,8EAA8E;AAC9E,iFAAiF;AACjF,yDAAyD;AACzD,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,qDAAqD;AACrD,EAAE;AACF,kFAAkF;AAClF,+DAA+D;AAC/D,wEAAwE;AACxE,iFAAiF;AACjF,gFAAgF;AAChF,kDAAkD;AAClD,SAAgB,wBAAwB,CACtC,IAAU,EACV,WAAmB,EACnB,MAAc;IAEd,IAAI,CAAC,IAAA,kCAAuB,EAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,CACV,mCAAmC,WAAW,0DAA0D;YACtG,MAAM,MAAM,4DAA4D,CAC3E,CAAC;QACF,OAAO;IACT,CAAC;IAED,kFAAkF;IAClF,MAAM,aAAa,GAAG,CAAC,CAA4C,EAAE,EAAE,CACrE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACvE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClB,CAAC;IAEJ,MAAM,eAAe,GAAG,IAAA,gCAAqB,EAC3C,IAAI,EACJ,WAAW,EACX,CAAC,CAAC,EAAE,EAAE;;QACJ,OAAA,aAAa,CAAC,CAAC,CAAC;YAChB,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAG,kCAAkC,CAAC,MAAK,KAAK,CAAA;KAAA,CAC1D,CAAC;IACF,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;IACT,CAAC;IAED,IAAA,kCAAuB,EAAC,IAAI,EAAE,WAAW,EAAE;QACzC,KAAK,EAAE,CAAC,OAAO,CAAC;QAChB,KAAK,EAAE,EAAE,kCAAkC,EAAE,KAAK,EAAE;KACrD,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -4,9 +4,21 @@ export interface SideMenuItemSpec {
|
|
|
4
4
|
label: string;
|
|
5
5
|
/** Route path the item navigates to, e.g. '/queue'. */
|
|
6
6
|
to: string;
|
|
7
|
+
/**
|
|
8
|
+
* Ionicon name, e.g. 'list'. Effectively required: goa-work-side-menu-item
|
|
9
|
+
* renders a blank item when it has no icon, so a generator that omits this
|
|
10
|
+
* produces nav that looks broken on arrival. Passed straight through to the
|
|
11
|
+
* element's <ion-icon>, so any valid ionicon name works.
|
|
12
|
+
*/
|
|
13
|
+
icon?: string;
|
|
7
14
|
}
|
|
8
15
|
/**
|
|
9
|
-
*
|
|
16
|
+
* Appends a nav item to a generated app's side menu.
|
|
17
|
+
*
|
|
18
|
+
* Appends rather than prepends so the shell's own 'Home' entry stays first and
|
|
19
|
+
* generated items accumulate in the order they were run. Inserting at the head
|
|
20
|
+
* (an earlier version of this function) pushed Home to the bottom of the nav
|
|
21
|
+
* after the first generator run, which no consuming app wants.
|
|
10
22
|
*
|
|
11
23
|
* A no-op when the app has no `primaryItems` array. That is the signal the app
|
|
12
24
|
* was generated with `--layout=header` (a public app has no side menu at all),
|