@abgov/nx-adsp 13.7.1 → 13.7.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/package.json +2 -1
- package/src/generators/express-service/files/AGENTS.md__tmpl__ +12 -3
- package/src/generators/express-service/files/src/routes/example.ts__tmpl__ +5 -1
- package/src/generators/vue-app/files/src/App.spec.ts__tmpl__ +17 -2
- package/src/generators/vue-components/vue-components.js +22 -13
- package/src/generators/vue-components/vue-components.js.map +1 -1
- package/src/generators/vue-components/vue-components.spec.ts +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abgov/nx-adsp",
|
|
3
|
-
"version": "13.7.
|
|
3
|
+
"version": "13.7.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"description": "Government of Alberta - Nx plugin for ADSP apps.",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@nx/express": "^23.0.0",
|
|
19
19
|
"@nx/react": "^23.0.0",
|
|
20
20
|
"@nx/vue": "^23.0.0",
|
|
21
|
+
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
|
21
22
|
"tslib": "^2.0.0"
|
|
22
23
|
},
|
|
23
24
|
"peerDependenciesMeta": {
|
|
@@ -157,7 +157,7 @@ import { authorize, createValidationHandler } from '@abgov/adsp-service-sdk';
|
|
|
157
157
|
import { z } from 'zod';
|
|
158
158
|
import * as items from '../services/items';
|
|
159
159
|
|
|
160
|
-
const CreateItem = z.object({ name: z.string().min(1) });
|
|
160
|
+
const CreateItem = z.object({ name: z.string().trim().min(1) });
|
|
161
161
|
|
|
162
162
|
export function itemsRouter(): Router {
|
|
163
163
|
const router = Router();
|
|
@@ -178,7 +178,13 @@ export function itemsRouter(): Router {
|
|
|
178
178
|
createValidationHandler(CreateItem),
|
|
179
179
|
async (req, res, next) => {
|
|
180
180
|
try {
|
|
181
|
-
|
|
181
|
+
// createValidationHandler validates req.body against the schema but
|
|
182
|
+
// doesn't replace it with the parsed result — req.body is still the
|
|
183
|
+
// raw body afterward. Invisible for a plain string, but any
|
|
184
|
+
// transform (.trim() here) or coercion (z.coerce.date(), a default)
|
|
185
|
+
// is silently lost if you read req.body directly instead of
|
|
186
|
+
// re-parsing. Parse again, here, to get the actual parsed value.
|
|
187
|
+
const { name } = CreateItem.parse(req.body);
|
|
182
188
|
res.status(201).json(await items.create(name));
|
|
183
189
|
} catch (err) {
|
|
184
190
|
next(err);
|
|
@@ -201,7 +207,10 @@ app.use('/<%= projectName %>/v1/items', itemsRouter());
|
|
|
201
207
|
|
|
202
208
|
`authorize(role)` → 403 if the user lacks the role; `createValidationHandler(schema)`
|
|
203
209
|
→ 400 on a bad body. Both forward errors to `next()`, which `createErrorHandler`
|
|
204
|
-
(mounted last in `main.ts`) turns into structured HTTP responses.
|
|
210
|
+
(mounted last in `main.ts`) turns into structured HTTP responses. Don't read
|
|
211
|
+
`req.body` directly in the handler and assert its type (`req.body as
|
|
212
|
+
z.infer<typeof Schema>`) — that's the raw, unparsed body, not the validated
|
|
213
|
+
one; parse it again with the same schema instead, as above.
|
|
205
214
|
|
|
206
215
|
## Recipe: add a resource end to end
|
|
207
216
|
|
|
@@ -46,7 +46,11 @@ export function exampleRouter(eventService: EventService): Router {
|
|
|
46
46
|
createValidationHandler(ExampleRequestSchema),
|
|
47
47
|
async (req, res, next) => {
|
|
48
48
|
try {
|
|
49
|
-
|
|
49
|
+
// createValidationHandler validates req.body but doesn't replace it
|
|
50
|
+
// with the parsed result — parse again here rather than asserting
|
|
51
|
+
// req.body's type, so any transform/coercion your schema adds later
|
|
52
|
+
// actually takes effect (see AGENTS.md's "Adding routes").
|
|
53
|
+
const { id } = ExampleRequestSchema.parse(req.body);
|
|
50
54
|
eventService.send(createExampleEvent(id));
|
|
51
55
|
res.json({ id });
|
|
52
56
|
} catch (err) {
|
|
@@ -16,11 +16,26 @@ vi.mock('@dsb-norge/vue-keycloak-js', async () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
|
|
19
|
+
import { createMemoryHistory, createRouter } from 'vue-router';
|
|
19
20
|
import App from './App.vue';
|
|
20
21
|
|
|
22
|
+
// App.vue reads useRoute() for the layout-width feature (route.meta.layout,
|
|
23
|
+
// default 'page') — that needs a real router installed via `global.plugins`,
|
|
24
|
+
// not just a RouterView stub. Stubbing RouterView only replaces what it
|
|
25
|
+
// renders; it doesn't provide the injection context useRoute() reads from, so
|
|
26
|
+
// route.meta would throw on undefined without this. Installing the router is
|
|
27
|
+
// enough on its own — its route ref is already a real object (Vue Router's
|
|
28
|
+
// START_LOCATION_NORMALIZED, meta: {}) before any navigation resolves, so no
|
|
29
|
+
// router.isReady()/await is needed, and calling it here would hang: with no
|
|
30
|
+
// app mounted yet, no navigation has been triggered for it to wait on.
|
|
31
|
+
const router = createRouter({
|
|
32
|
+
history: createMemoryHistory(),
|
|
33
|
+
routes: [{ path: '/', component: { template: '<div />' } }],
|
|
34
|
+
});
|
|
35
|
+
|
|
21
36
|
describe('App shell header', () => {
|
|
22
37
|
it('shows Sign in when unauthenticated', () => {
|
|
23
|
-
const wrapper = mount(App, { global: { stubs: { RouterView: true } } });
|
|
38
|
+
const wrapper = mount(App, { global: { plugins: [router], stubs: { RouterView: true } } });
|
|
24
39
|
const group = wrapper.find('goa-button-group');
|
|
25
40
|
expect(group.exists()).toBe(true);
|
|
26
41
|
expect(group.html()).toContain('Sign in');
|
|
@@ -30,7 +45,7 @@ describe('App shell header', () => {
|
|
|
30
45
|
// The bug: destructuring useKeycloak() froze `keycloak` at undefined, so
|
|
31
46
|
// login() was a permanent no-op. Reactive access must reach the instance.
|
|
32
47
|
const kc = useKeycloak();
|
|
33
|
-
const wrapper = mount(App, { global: { stubs: { RouterView: true } } });
|
|
48
|
+
const wrapper = mount(App, { global: { plugins: [router], stubs: { RouterView: true } } });
|
|
34
49
|
const btn = wrapper.findAll('goa-button').find((b) => b.text().includes('Sign in'));
|
|
35
50
|
await btn?.trigger('_click');
|
|
36
51
|
expect(kc.keycloak?.login).toHaveBeenCalled();
|
|
@@ -6,6 +6,7 @@ exports.vueComponentsImportPath = vueComponentsImportPath;
|
|
|
6
6
|
exports.default = default_1;
|
|
7
7
|
const tslib_1 = require("tslib");
|
|
8
8
|
const devkit_1 = require("@nx/devkit");
|
|
9
|
+
const internal_1 = require("@nx/eslint/internal");
|
|
9
10
|
const path = require("path");
|
|
10
11
|
// Backfills package.json module-resolution fields for the library. In a TS-solution
|
|
11
12
|
// workspace @nx/vue's library resolves via package.json `exports`, but its
|
|
@@ -36,24 +37,32 @@ function ensurePackageExports(host, libRoot) {
|
|
|
36
37
|
// element usage, not the deprecated Vue 2 component-slot syntax the rule targets.
|
|
37
38
|
// The whole lib wraps web components, so it's scoped to the lib (consuming apps,
|
|
38
39
|
// which use `<template #actions>`, are unaffected).
|
|
40
|
+
//
|
|
41
|
+
// Uses @nx/eslint's own override helpers rather than hand-editing a specific
|
|
42
|
+
// file format: create-nx-workspace's current default is flat config
|
|
43
|
+
// (eslint.config.mjs), which is a JS module exporting an array, not JSON — an
|
|
44
|
+
// earlier version of this function only handled legacy .eslintrc.json, so it
|
|
45
|
+
// silently no-op'd (just a console warning) on flat-config workspaces, and
|
|
46
|
+
// `nx lint vue-components` failed for real on unmodified generator output.
|
|
47
|
+
// addOverrideToLintConfig/lintConfigHasOverride detect flat vs legacy
|
|
48
|
+
// internally (via @nx/eslint's own useFlatConfig check) and edit whichever is
|
|
49
|
+
// actually in effect — AST-aware for flat config, not string splicing.
|
|
39
50
|
function disableSlotAttributeRule(host, libRoot) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.warn(`\n⚠ ${eslintrc} not found — could not disable vue/no-deprecated-slot-attribute.\n` +
|
|
51
|
+
if (!(0, internal_1.isEslintConfigSupported)(host, libRoot)) {
|
|
52
|
+
console.warn(`\n⚠ No ESLint config found for ${libRoot} — could not disable vue/no-deprecated-slot-attribute.\n` +
|
|
43
53
|
` GoabModal uses goa-modal's native \`slot\` attribute; if lint flags it, turn\n` +
|
|
44
54
|
` that rule off for this library.\n`);
|
|
45
55
|
return;
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return json;
|
|
57
|
+
// `files` is `string | string[]` per the Linter type — normalize before checking.
|
|
58
|
+
const isVueOverride = (o) => (Array.isArray(o.files) ? o.files : o.files ? [o.files] : []).some((f) => f.includes('vue'));
|
|
59
|
+
const alreadyDisabled = (0, internal_1.lintConfigHasOverride)(host, libRoot, (o) => { var _a; return isVueOverride(o) && ((_a = o.rules) === null || _a === void 0 ? void 0 : _a['vue/no-deprecated-slot-attribute']) === 'off'; });
|
|
60
|
+
if (alreadyDisabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
(0, internal_1.addOverrideToLintConfig)(host, libRoot, {
|
|
64
|
+
files: ['*.vue'],
|
|
65
|
+
rules: { 'vue/no-deprecated-slot-attribute': 'off' },
|
|
57
66
|
});
|
|
58
67
|
}
|
|
59
68
|
exports.LIB_NAME = 'vue-components';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-components.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-components/vue-components.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"vue-components.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-components/vue-components.ts"],"names":[],"mappings":";;;AAkBA,oDAcC;AAoDD,0DASC;AAMD,4BAkCC;;AArID,uCAMoB;AACpB,kDAA8G;AAE9G,6BAA6B;AAE7B,oFAAoF;AACpF,2EAA2E;AAC3E,oFAAoF;AACpF,kFAAkF;AAClF,6EAA6E;AAC7E,kFAAkF;AAClF,4DAA4D;AAC5D,SAAgB,oBAAoB,CAAC,IAAU,EAAE,OAAe;IAC9D,MAAM,OAAO,GAAG,GAAG,OAAO,eAAe,CAAC;IAC1C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO;IAClC,MAAM,GAAG,GAAG,gBAAgB,CAAC;IAC7B,IAAA,mBAAU,EAAmD,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;;QAClF,MAAA,GAAG,CAAC,IAAI,oCAAR,GAAG,CAAC,IAAI,GAAK,GAAG,EAAC;QACjB,MAAA,GAAG,CAAC,MAAM,oCAAV,GAAG,CAAC,MAAM,GAAK,GAAG,EAAC;QACnB,MAAA,GAAG,CAAC,KAAK,oCAAT,GAAG,CAAC,KAAK,GAAK,GAAG,EAAC;QAClB,MAAA,GAAG,CAAC,OAAO,oCAAX,GAAG,CAAC,OAAO,GAAK;YACd,gBAAgB,EAAE,gBAAgB;YAClC,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;SAC/C,EAAC;QACF,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC;AAED,kFAAkF;AAClF,kFAAkF;AAClF,kFAAkF;AAClF,iFAAiF;AACjF,oDAAoD;AACpD,EAAE;AACF,6EAA6E;AAC7E,oEAAoE;AACpE,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,sEAAsE;AACtE,8EAA8E;AAC9E,uEAAuE;AACvE,SAAS,wBAAwB,CAAC,IAAU,EAAE,OAAe;IAC3D,IAAI,CAAC,IAAA,kCAAuB,EAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAC5C,OAAO,CAAC,IAAI,CACV,mCAAmC,OAAO,0DAA0D;YACpG,mFAAmF;YACnF,sCAAsC,CACvC,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,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/F,MAAM,eAAe,GAAG,IAAA,gCAAqB,EAC3C,IAAI,EACJ,OAAO,EACP,CAAC,CAAC,EAAE,EAAE,WAAC,OAAA,aAAa,CAAC,CAAC,CAAC,IAAI,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAG,kCAAkC,CAAC,MAAK,KAAK,CAAA,EAAA,CACnF,CAAC;IACF,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;IACT,CAAC;IAED,IAAA,kCAAuB,EAAC,IAAI,EAAE,OAAO,EAAE;QACrC,KAAK,EAAE,CAAC,OAAO,CAAC;QAChB,KAAK,EAAE,EAAE,kCAAkC,EAAE,KAAK,EAAE;KACrD,CAAC,CAAC;AACL,CAAC;AAEY,QAAA,QAAQ,GAAG,gBAAgB,CAAC;AAEzC,8EAA8E;AAC9E,iFAAiF;AACjF,+EAA+E;AAC/E,gDAAgD;AAChD,SAAgB,uBAAuB,CAAC,IAAU;;IAChD,IAAI,IAAI,GAAG,WAAW,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCAAI,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC;IAChF,CAAC;IAAC,WAAM,CAAC;QACP,mBAAmB;IACrB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IACrE,OAAO,GAAG,KAAK,IAAI,gBAAQ,EAAE,CAAC;AAChC,CAAC;AAED,kFAAkF;AAClF,kFAAkF;AAClF,4EAA4E;AAC5E,oFAAoF;AACpF,mBAA+B,IAAU;;QACvC,MAAM,OAAO,GAAG,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC,OAAO,IAAI,gBAAQ,EAAE,CAAC;QAElE,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC;YACH,IAAA,iCAAwB,EAAC,IAAI,EAAE,gBAAQ,CAAC,CAAC;QAC3C,CAAC;QAAC,WAAM,CAAC;YACP,MAAM,GAAG,KAAK,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,qCAAO,SAAS,GAAE,KAAK,CAAC,GAAG,EAAE;gBAC9D,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,MAAM,gBAAgB,CAAC,IAAI,EAAE;gBAC3B,SAAS,EAAE,OAAO;gBAClB,IAAI,EAAE,gBAAQ;gBACd,MAAM,EAAE,QAAQ;gBAChB,cAAc,EAAE,QAAQ;gBACxB,0EAA0E;gBAC1E,2EAA2E;gBAC3E,8DAA8D;gBAC9D,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE,MAAM;gBACf,UAAU,EAAE,uBAAuB,CAAC,IAAI,CAAC;gBACzC,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QAED,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5E,CAAC;CAAA"}
|
|
@@ -50,12 +50,34 @@ describe('Vue Components Generator', () => {
|
|
|
50
50
|
expect(agents).toContain('defineModel<boolean>');
|
|
51
51
|
}, 30000);
|
|
52
52
|
|
|
53
|
+
it('disables vue/no-deprecated-slot-attribute in flat config too, not just .eslintrc.json', async () => {
|
|
54
|
+
// useFlatConfig() (from @nx/eslint) treats a root flat-config file's
|
|
55
|
+
// presence as authoritative, regardless of the installed ESLint version —
|
|
56
|
+
// this is what create-nx-workspace's current default actually looks like.
|
|
57
|
+
host.write('eslint.config.mjs', 'export default [];\n');
|
|
58
|
+
await generator(host);
|
|
59
|
+
|
|
60
|
+
expect(host.exists('libs/vue-components/eslint.config.mjs')).toBeTruthy();
|
|
61
|
+
expect(host.exists('libs/vue-components/.eslintrc.json')).toBeFalsy();
|
|
62
|
+
const flatConfig = host.read('libs/vue-components/eslint.config.mjs').toString();
|
|
63
|
+
expect(flatConfig).toContain('"vue/no-deprecated-slot-attribute": "off"');
|
|
64
|
+
}, 30000);
|
|
65
|
+
|
|
53
66
|
it('is idempotent — a second run does not throw and keeps the wrappers', async () => {
|
|
54
67
|
await generator(host);
|
|
55
68
|
await expect(generator(host)).resolves.not.toThrow();
|
|
56
69
|
expect(host.exists('libs/vue-components/src/lib/GoabInput.vue')).toBeTruthy();
|
|
57
70
|
}, 30000);
|
|
58
71
|
|
|
72
|
+
it('does not duplicate the ESLint override on a second run (legacy or flat)', async () => {
|
|
73
|
+
host.write('eslint.config.mjs', 'export default [];\n');
|
|
74
|
+
await generator(host);
|
|
75
|
+
await generator(host);
|
|
76
|
+
|
|
77
|
+
const flatConfig = host.read('libs/vue-components/eslint.config.mjs').toString();
|
|
78
|
+
expect(flatConfig.split('vue/no-deprecated-slot-attribute').length - 1).toBe(1);
|
|
79
|
+
}, 30000);
|
|
80
|
+
|
|
59
81
|
it('derives the import path from the workspace scope', () => {
|
|
60
82
|
expect(vueComponentsImportPath(host)).toMatch(/\/vue-components$/);
|
|
61
83
|
});
|