@abgov/nx-adsp 13.6.0 → 13.7.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.6.0",
3
+ "version": "13.7.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -31,7 +31,8 @@ executors read options from `project.json` and do not prompt.
31
31
  | File | Purpose |
32
32
  |------|---------|
33
33
  | `src/main.ts` | Entry — registers Pinia, Router, and Keycloak plugin |
34
- | `src/App.vue` | Shell — nav header with sign-in/out, hero banner, `<RouterView>` |
34
+ | `src/App.vue` | Shell — nav header with sign-in/out, hero banner, `<RouterView>` wrapped in `AppLayout` |
35
+ | `src/components/AppLayout.vue` | Shared content gutter (centered, max-width, token padding). Every view lands inside it — no per-view spacing to add. Width via route meta `layout` |
35
36
  | `src/router/index.ts` | Routes — `/protected` guarded with `requiresAuth` meta |
36
37
  | `src/views/HomeView.vue` | Public page — calls public and private APIs |
37
38
  | `src/views/ProtectedView.vue` | Authenticated page — shows user info from token |
@@ -157,12 +158,17 @@ across every Vue app in this workspace — fix or extend a wrapper once in
157
158
 
158
159
  ## Adding a view
159
160
 
160
- 1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC
161
+ 1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC. **Don't add your
162
+ own page margins/centering** — every view renders inside `AppLayout`'s gutter, so
163
+ any top-level tag (`<div>`, `<section>`, …) is already centered and padded.
161
164
  2. Add the route to `src/router/index.ts`:
162
165
  ```typescript
163
166
  { path: '/my-feature', component: () => import('../views/MyFeatureView.vue') }
164
167
  // For an authenticated route:
165
168
  { path: '/my-feature', component: () => import('../views/MyFeatureView.vue'), meta: { requiresAuth: true } }
169
+ // For a wider (data table) or narrower (form) view, set the layout width:
170
+ { path: '/queue', component: () => import('../views/QueueView.vue'), meta: { layout: 'wide' } }
171
+ { path: '/apply', component: () => import('../views/ApplyView.vue'), meta: { layout: 'form' } }
166
172
  ```
167
173
  3. Add a nav link to `src/App.vue` if needed
168
174
 
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue';
2
3
  import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
3
- import { RouterView } from 'vue-router';
4
+ import { RouterView, useRoute } from 'vue-router';
5
+ import AppLayout from './components/AppLayout.vue';
4
6
 
5
7
  // Keep the reactive instance — do NOT destructure. useKeycloak() returns a
6
8
  // readonly(reactive()); destructuring snapshots each field at setup time, so
@@ -8,6 +10,13 @@ import { RouterView } from 'vue-router';
8
10
  // `authenticated` would never flip. Access fields on `kc` to stay reactive.
9
11
  const kc = useKeycloak();
10
12
 
13
+ // Content width per view, from route meta (default 'page'). Views opt into a
14
+ // different width with `meta: { layout: 'wide' | 'form' }` in the router.
15
+ const route = useRoute();
16
+ const layout = computed(
17
+ () => (route.meta.layout as 'page' | 'wide' | 'form' | undefined) ?? 'page'
18
+ );
19
+
11
20
  function login() { kc.keycloak?.login(); }
12
21
  function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin }); }
13
22
  </script>
@@ -29,20 +38,11 @@ function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin })
29
38
  </goa-button-group>
30
39
  </goa-app-header>
31
40
  <goa-hero-banner heading="<%= projectName %>" backgroundurl="/assets/banner.jpg" />
32
- <main>
41
+ <AppLayout :variant="layout">
33
42
  <RouterView />
34
- </main>
43
+ </AppLayout>
35
44
  </template>
36
45
 
37
46
  <style>
38
47
  body { margin: 0; }
39
- main {
40
- display: grid;
41
- grid-template-columns: repeat(6, auto);
42
- }
43
- main > section {
44
- grid-column: 2 / span 2;
45
- margin-top: 40px;
46
- margin-bottom: 40px;
47
- }
48
48
  </style>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ // Shared page layout. App.vue wraps <RouterView> in this, so EVERY routed view
3
+ // gets the standard content gutter (centered, max-width, token-driven padding)
4
+ // regardless of its own top-level tag — there's no "wrap your view in <section>"
5
+ // convention to remember, and a plain <div> works fine.
6
+ //
7
+ // Width variant is chosen per-view via route meta `layout` (App.vue reads it):
8
+ // meta: { layout: 'form' } // single-column forms → 640px
9
+ // (default) // general content → 1000px
10
+ // meta: { layout: 'wide' } // data-heavy pages / tables → 1200px
11
+ withDefaults(defineProps<{ variant?: 'page' | 'wide' | 'form' }>(), {
12
+ variant: 'page',
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <main id="main-content">
18
+ <div :class="`${variant}-content`">
19
+ <slot />
20
+ </div>
21
+ </main>
22
+ </template>
23
+
24
+ <style scoped>
25
+ .page-content,
26
+ .wide-content,
27
+ .form-content {
28
+ margin-inline: auto;
29
+ box-sizing: border-box;
30
+ /* Design-token gutter; fallbacks apply only if tokens aren't loaded. */
31
+ padding: var(--goa-space-xl, 2.5rem) var(--goa-space-l, 1.5rem);
32
+ }
33
+
34
+ .form-content { max-width: 640px; }
35
+ .page-content { max-width: 1000px; }
36
+ .wide-content { max-width: 1200px; }
37
+
38
+ @media (max-width: 623.98px) {
39
+ .page-content,
40
+ .wide-content,
41
+ .form-content {
42
+ padding: var(--goa-space-m, 1rem);
43
+ }
44
+ }
45
+ </style>
@@ -72,6 +72,24 @@ describe('Vue App Generator', () => {
72
72
  expect(agents).not.toContain('ui-components.alberta.ca');
73
73
  }, 30000);
74
74
 
75
+ it('wraps views in a shared AppLayout gutter (not a bare tag selector)', async () => {
76
+ await generator(host, options);
77
+
78
+ // Shared layout component provides the content gutter for every view.
79
+ expect(host.exists('apps/test/src/components/AppLayout.vue')).toBeTruthy();
80
+ const layout = host.read('apps/test/src/components/AppLayout.vue').toString();
81
+ // Three named width variants, token-driven padding.
82
+ expect(layout).toContain('form-content');
83
+ expect(layout).toContain('wide-content');
84
+ expect(layout).toContain('--goa-space');
85
+
86
+ // App.vue uses AppLayout and no longer relies on the `main > section` gutter
87
+ // (which silently failed when a view's top-level tag wasn't <section>).
88
+ const app = host.read('apps/test/src/App.vue').toString();
89
+ expect(app).toContain('AppLayout');
90
+ expect(app).not.toContain('main > section');
91
+ });
92
+
75
93
  it('provisions the shared GoA wrapper library and points the app at it', async () => {
76
94
  await generator(host, options);
77
95
 
@@ -25,6 +25,7 @@ function onClose() {
25
25
  <template>
26
26
  <goa-modal :open="open" @_close="onClose">
27
27
  <slot />
28
+ <!-- `slot="actions"` is goa-modal's native web-component slot, not the Vue 2 slot syntax. The vue/no-deprecated-slot-attribute rule is turned off for this lib (see .eslintrc.json). -->
28
29
  <div v-if="slots.actions" slot="actions">
29
30
  <slot name="actions" />
30
31
  </div>
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as lib from './index';
3
+
4
+ // Smoke test: the barrel resolves and every wrapper SFC compiles under the test
5
+ // runner. It also gives the library a test target with real content, so
6
+ // `nx test vue-components` doesn't fail on "no test files found".
7
+ describe('vue-components', () => {
8
+ it('exports every GoA wrapper', () => {
9
+ for (const name of [
10
+ 'GoabInput',
11
+ 'GoabTextarea',
12
+ 'GoabDropdown',
13
+ 'GoabCheckbox',
14
+ 'GoabRadioGroup',
15
+ 'GoabButton',
16
+ 'GoabModal',
17
+ ]) {
18
+ expect(lib[name as keyof typeof lib]).toBeTruthy();
19
+ }
20
+ });
21
+ });
@@ -1,4 +1,5 @@
1
1
  import { Tree } from '@nx/devkit';
2
+ export declare function ensurePackageExports(host: Tree, libRoot: string): void;
2
3
  export declare const LIB_NAME = "vue-components";
3
4
  export declare function vueComponentsImportPath(host: Tree): string;
4
5
  export default function (host: Tree): Promise<void>;
@@ -1,11 +1,61 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIB_NAME = void 0;
4
+ exports.ensurePackageExports = ensurePackageExports;
4
5
  exports.vueComponentsImportPath = vueComponentsImportPath;
5
6
  exports.default = default_1;
6
7
  const tslib_1 = require("tslib");
7
8
  const devkit_1 = require("@nx/devkit");
8
9
  const path = require("path");
10
+ // Backfills package.json module-resolution fields for the library. In a TS-solution
11
+ // workspace @nx/vue's library resolves via package.json `exports`, but its
12
+ // programmatic default (useProjectJson skips writing them here) leaves them off, so
13
+ // the import path fails to resolve and the build breaks. Point them at the source
14
+ // entry (the lib is non-buildable — consumers compile it). A no-op in legacy
15
+ // path-alias workspaces (no lib package.json — resolution is via tsconfig paths),
16
+ // and idempotent (never clobbers fields @nx/vue did write).
17
+ function ensurePackageExports(host, libRoot) {
18
+ const pkgPath = `${libRoot}/package.json`;
19
+ if (!host.exists(pkgPath))
20
+ return;
21
+ const SRC = './src/index.ts';
22
+ (0, devkit_1.updateJson)(host, pkgPath, (pkg) => {
23
+ var _a, _b, _c, _d;
24
+ (_a = pkg.main) !== null && _a !== void 0 ? _a : (pkg.main = SRC);
25
+ (_b = pkg.module) !== null && _b !== void 0 ? _b : (pkg.module = SRC);
26
+ (_c = pkg.types) !== null && _c !== void 0 ? _c : (pkg.types = SRC);
27
+ (_d = pkg.exports) !== null && _d !== void 0 ? _d : (pkg.exports = {
28
+ './package.json': './package.json',
29
+ '.': { types: SRC, import: SRC, default: SRC },
30
+ });
31
+ return pkg;
32
+ });
33
+ }
34
+ // Turns off vue/no-deprecated-slot-attribute for the lib. GoabModal projects into
35
+ // goa-modal's native web-component slot via `slot="actions"` — legitimate custom-
36
+ // element usage, not the deprecated Vue 2 component-slot syntax the rule targets.
37
+ // The whole lib wraps web components, so it's scoped to the lib (consuming apps,
38
+ // which use `<template #actions>`, are unaffected).
39
+ function disableSlotAttributeRule(host, libRoot) {
40
+ const eslintrc = `${libRoot}/.eslintrc.json`;
41
+ if (!host.exists(eslintrc)) {
42
+ console.warn(`\n⚠ ${eslintrc} not found — could not disable vue/no-deprecated-slot-attribute.\n` +
43
+ ` GoabModal uses goa-modal's native \`slot\` attribute; if lint flags it, turn\n` +
44
+ ` that rule off for this library.\n`);
45
+ return;
46
+ }
47
+ (0, devkit_1.updateJson)(host, eslintrc, (json) => {
48
+ var _a, _b;
49
+ const overrides = ((_a = json.overrides) !== null && _a !== void 0 ? _a : (json.overrides = []));
50
+ let vueOverride = overrides.find((o) => { var _a; return (_a = o.files) === null || _a === void 0 ? void 0 : _a.some((f) => f.includes('vue')); });
51
+ if (!vueOverride) {
52
+ vueOverride = { files: ['*.vue'], rules: {} };
53
+ overrides.push(vueOverride);
54
+ }
55
+ ((_b = vueOverride.rules) !== null && _b !== void 0 ? _b : (vueOverride.rules = {}))['vue/no-deprecated-slot-attribute'] = 'off';
56
+ return json;
57
+ });
58
+ }
9
59
  exports.LIB_NAME = 'vue-components';
10
60
  // Import specifier for the shared wrapper lib, derived from the workspace npm
11
61
  // scope so it mirrors the eventual official `@abgov/vue-components` (same tail —
@@ -54,6 +104,8 @@ function default_1(host) {
54
104
  importPath: vueComponentsImportPath(host),
55
105
  skipFormat: true,
56
106
  });
107
+ disableSlotAttributeRule(host, libRoot);
108
+ ensurePackageExports(host, libRoot);
57
109
  }
58
110
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), libRoot, { tmpl: '' });
59
111
  });
@@ -1 +1 @@
1
- {"version":3,"file":"vue-components.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-components/vue-components.ts"],"names":[],"mappings":";;;AAcA,0DASC;AAMD,4BAgCC;;AA7DD,uCAKoB;AACpB,6BAA6B;AAEhB,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;QACL,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"}
1
+ {"version":3,"file":"vue-components.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-components/vue-components.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { readProjectConfiguration, Tree } from '@nx/devkit';
2
2
  import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
3
- import generator, { vueComponentsImportPath } from './vue-components';
3
+ import generator, { ensurePackageExports, vueComponentsImportPath } from './vue-components';
4
4
 
5
5
  jest.mock('@nx/devkit', () => ({
6
6
  ...jest.requireActual('@nx/devkit'),
@@ -31,6 +31,15 @@ describe('Vue Components Generator', () => {
31
31
  expect(index).toContain("export { default as GoabInput }");
32
32
  expect(index).toContain('@abgov/vue-components'); // interim marker
33
33
 
34
+ // Ships a spec so the vitest test target isn't empty (vitest exits non-zero
35
+ // on "no test files found").
36
+ expect(host.exists('libs/vue-components/src/vue-components.spec.ts')).toBeTruthy();
37
+
38
+ // GoabModal uses goa-modal's native `slot` attribute (web component, not the
39
+ // deprecated Vue 2 slot syntax) — the rule is turned off for this lib.
40
+ const eslintrc = host.read('libs/vue-components/.eslintrc.json').toString();
41
+ expect(eslintrc).toContain('"vue/no-deprecated-slot-attribute": "off"');
42
+
34
43
  // Ships agent direction for maintaining the (interim) lib, incl. a recipe
35
44
  // for wrapping additional components.
36
45
  expect(host.exists('libs/vue-components/AGENTS.md')).toBeTruthy();
@@ -50,4 +59,32 @@ describe('Vue Components Generator', () => {
50
59
  it('derives the import path from the workspace scope', () => {
51
60
  expect(vueComponentsImportPath(host)).toMatch(/\/vue-components$/);
52
61
  });
62
+
63
+ describe('ensurePackageExports (TS-solution resolution fix)', () => {
64
+ it('backfills exports/main/types when a lib package.json exists', () => {
65
+ host.write('libs/vue-components/package.json', JSON.stringify({ name: '@proj/vue-components' }));
66
+ ensurePackageExports(host, 'libs/vue-components');
67
+
68
+ const pkg = JSON.parse(host.read('libs/vue-components/package.json').toString());
69
+ expect(pkg.main).toBe('./src/index.ts');
70
+ expect(pkg.types).toBe('./src/index.ts');
71
+ expect(pkg.exports['.'].import).toBe('./src/index.ts');
72
+ });
73
+
74
+ it('does not clobber exports @nx/vue already wrote', () => {
75
+ host.write(
76
+ 'libs/vue-components/package.json',
77
+ JSON.stringify({ name: '@proj/vue-components', exports: { '.': './dist/index.js' } })
78
+ );
79
+ ensurePackageExports(host, 'libs/vue-components');
80
+
81
+ const pkg = JSON.parse(host.read('libs/vue-components/package.json').toString());
82
+ expect(pkg.exports['.']).toBe('./dist/index.js');
83
+ });
84
+
85
+ it('is a no-op for a legacy lib with no package.json', () => {
86
+ ensurePackageExports(host, 'libs/vue-components');
87
+ expect(host.exists('libs/vue-components/package.json')).toBeFalsy();
88
+ });
89
+ });
53
90
  });