@abgov/nx-adsp 12.16.1 → 12.18.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.
Files changed (35) hide show
  1. package/generators.json +17 -0
  2. package/package.json +5 -1
  3. package/src/generators/mevn/mevn.d.ts +3 -0
  4. package/src/generators/mevn/mevn.js +70 -0
  5. package/src/generators/mevn/mevn.js.map +1 -0
  6. package/src/generators/mevn/mevn.spec.ts +47 -0
  7. package/src/generators/mevn/schema.d.ts +14 -0
  8. package/src/generators/mevn/schema.json +60 -0
  9. package/src/generators/pevn/pevn.d.ts +3 -0
  10. package/src/generators/pevn/pevn.js +70 -0
  11. package/src/generators/pevn/pevn.js.map +1 -0
  12. package/src/generators/pevn/pevn.spec.ts +47 -0
  13. package/src/generators/pevn/schema.d.ts +14 -0
  14. package/src/generators/pevn/schema.json +60 -0
  15. package/src/generators/vue-app/files/AGENTS.md__tmpl__ +97 -0
  16. package/src/generators/vue-app/files/nginx.conf__tmpl__ +45 -0
  17. package/src/generators/vue-app/files/src/App.vue__tmpl__ +39 -0
  18. package/src/generators/vue-app/files/src/assets/banner.jpg +0 -0
  19. package/src/generators/vue-app/files/src/environments/environment.ts__tmpl__ +11 -0
  20. package/src/generators/vue-app/files/src/index.html__tmpl__ +22 -0
  21. package/src/generators/vue-app/files/src/main.ts__tmpl__ +28 -0
  22. package/src/generators/vue-app/files/src/router/index.ts__tmpl__ +29 -0
  23. package/src/generators/vue-app/files/src/silent-check-sso.html__tmpl__ +5 -0
  24. package/src/generators/vue-app/files/src/views/HomeView.vue__tmpl__ +49 -0
  25. package/src/generators/vue-app/files/src/views/ProtectedView.vue__tmpl__ +14 -0
  26. package/src/generators/vue-app/files/vite.config.ts__tmpl__ +50 -0
  27. package/src/generators/vue-app/schema.d.ts +22 -0
  28. package/src/generators/vue-app/schema.json +85 -0
  29. package/src/generators/vue-app/vue-app.d.ts +3 -0
  30. package/src/generators/vue-app/vue-app.js +140 -0
  31. package/src/generators/vue-app/vue-app.js.map +1 -0
  32. package/src/generators/vue-app/vue-app.spec.ts +93 -0
  33. package/src/utils/agent.d.ts +1 -1
  34. package/src/utils/agent.js +5 -3
  35. package/src/utils/agent.js.map +1 -1
package/generators.json CHANGED
@@ -44,6 +44,11 @@
44
44
  "schema": "./src/generators/angular-app/schema.json",
45
45
  "description": "Generator that creates a Angular based frontend application."
46
46
  },
47
+ "vue-app": {
48
+ "factory": "./src/generators/vue-app/vue-app",
49
+ "schema": "./src/generators/vue-app/schema.json",
50
+ "description": "Generator that creates a Vue 3 frontend application using GoA web components."
51
+ },
47
52
  "mean": {
48
53
  "factory": "./src/generators/mean/mean",
49
54
  "schema": "./src/generators/mean/schema.json",
@@ -61,6 +66,18 @@
61
66
  "schema": "./src/generators/pean/schema.json",
62
67
  "description": "Generator that creates a PEAN fullstack solution.",
63
68
  "hidden": true
69
+ },
70
+ "pevn": {
71
+ "factory": "./src/generators/pevn/pevn",
72
+ "schema": "./src/generators/pevn/schema.json",
73
+ "description": "Generator that creates a PEVN fullstack solution.",
74
+ "hidden": true
75
+ },
76
+ "mevn": {
77
+ "factory": "./src/generators/mevn/mevn",
78
+ "schema": "./src/generators/mevn/schema.json",
79
+ "description": "Generator that creates a MEVN fullstack solution.",
80
+ "hidden": true
64
81
  }
65
82
  }
66
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "12.16.1",
3
+ "version": "12.18.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -17,6 +17,7 @@
17
17
  "@nx/eslint": "^22.0.0",
18
18
  "@nx/express": "^22.0.0",
19
19
  "@nx/react": "^22.0.0",
20
+ "@nx/vue": "^22.0.0",
20
21
  "tslib": "^2.0.0"
21
22
  },
22
23
  "peerDependenciesMeta": {
@@ -25,6 +26,9 @@
25
26
  },
26
27
  "@nx/angular": {
27
28
  "optional": true
29
+ },
30
+ "@nx/vue": {
31
+ "optional": true
28
32
  }
29
33
  },
30
34
  "dependencies": {
@@ -0,0 +1,3 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { Schema } from './schema';
3
+ export default function (host: Tree, options: Schema): Promise<() => void>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const nx_oc_1 = require("@abgov/nx-oc");
7
+ const express_service_1 = require("../express-service/express-service");
8
+ const vue_app_1 = require("../vue-app/vue-app");
9
+ const agent_1 = require("../../utils/agent");
10
+ const keycloak_admin_1 = require("../../utils/keycloak-admin");
11
+ const plugin_version_1 = require("../../utils/plugin-version");
12
+ function normalizeOptions(host, options) {
13
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
14
+ var _a;
15
+ const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
16
+ return Object.assign(Object.assign({}, options), { accessToken: (_a = adsp.accessToken) !== null && _a !== void 0 ? _a : options.accessToken, adsp });
17
+ });
18
+ }
19
+ function default_1(host, options) {
20
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
21
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
22
+ const normalizedOptions = yield normalizeOptions(host, options);
23
+ const projectName = (0, devkit_1.names)(options.name).fileName;
24
+ const serviceName = `${projectName}-service`;
25
+ const appName = `${projectName}-app`;
26
+ const appsDir = (0, devkit_1.getWorkspaceLayout)(host).appsDir;
27
+ const serviceRoot = `${appsDir}/${serviceName}`;
28
+ const appRoot = `${appsDir}/${appName}`;
29
+ yield (0, express_service_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: serviceName, skipAgent: true, database: 'mongo' }));
30
+ yield (0, vue_app_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: appName, proxy: {
31
+ location: '/api/',
32
+ proxyPass: `http://${serviceName}:3333/${serviceName}/`,
33
+ }, skipAgent: true }));
34
+ if (normalizedOptions.adsp) {
35
+ const accessToken = (_a = normalizedOptions.adsp.accessToken) !== null && _a !== void 0 ? _a : normalizedOptions.accessToken;
36
+ const serviceClientId = `urn:ads:${normalizedOptions.adsp.tenant}:${serviceName}`;
37
+ const appClientId = `urn:ads:${normalizedOptions.adsp.tenant}:${appName}`;
38
+ yield (0, keycloak_admin_1.ensureAudienceMapper)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, appClientId, serviceClientId, accessToken);
39
+ yield (0, keycloak_admin_1.ensureClientRoleScope)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, appClientId, serviceClientId, 'example-role', accessToken);
40
+ }
41
+ if (normalizedOptions.adsp && !normalizedOptions.skipAgent) {
42
+ const accessToken = (_b = normalizedOptions.adsp.accessToken) !== null && _b !== void 0 ? _b : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
43
+ var _a;
44
+ process.stdout.write(`\n[nx-adsp] Agent sign-in failed (${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}) — skipping agent interaction.\n`);
45
+ return undefined;
46
+ }));
47
+ yield (0, agent_1.confirmAfterAgentInterrupt)(yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
48
+ projectName,
49
+ projectType: 'mevn',
50
+ tenant: normalizedOptions.adsp.tenant,
51
+ pluginVersion: plugin_version_1.PLUGIN_VERSION,
52
+ existingFiles: {
53
+ 'service/src/main.ts': (_d = (_c = host.read(`${serviceRoot}/src/main.ts`)) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '',
54
+ 'service/src/environment.ts': (_f = (_e = host.read(`${serviceRoot}/src/environment.ts`)) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _f !== void 0 ? _f : '',
55
+ 'service/src/database.ts': (_h = (_g = host.read(`${serviceRoot}/src/database.ts`)) === null || _g === void 0 ? void 0 : _g.toString()) !== null && _h !== void 0 ? _h : '',
56
+ 'service/src/events.ts': (_k = (_j = host.read(`${serviceRoot}/src/events.ts`)) === null || _j === void 0 ? void 0 : _j.toString()) !== null && _k !== void 0 ? _k : '',
57
+ 'app/src/App.vue': (_m = (_l = host.read(`${appRoot}/src/App.vue`)) === null || _l === void 0 ? void 0 : _l.toString()) !== null && _m !== void 0 ? _m : '',
58
+ 'app/src/main.ts': (_p = (_o = host.read(`${appRoot}/src/main.ts`)) === null || _o === void 0 ? void 0 : _o.toString()) !== null && _p !== void 0 ? _p : '',
59
+ 'app/src/router/index.ts': (_r = (_q = host.read(`${appRoot}/src/router/index.ts`)) === null || _q === void 0 ? void 0 : _q.toString()) !== null && _r !== void 0 ? _r : '',
60
+ 'app/src/environments/environment.ts': (_t = (_s = host.read(`${appRoot}/src/environments/environment.ts`)) === null || _s === void 0 ? void 0 : _s.toString()) !== null && _t !== void 0 ? _t : '',
61
+ },
62
+ }, host, serviceRoot, { additionalRoots: { 'app': appRoot } }));
63
+ }
64
+ yield (0, devkit_1.formatFiles)(host);
65
+ return () => {
66
+ (0, devkit_1.installPackagesTask)(host);
67
+ };
68
+ });
69
+ }
70
+ //# sourceMappingURL=mevn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mevn.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/mevn/mevn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { readProjectConfiguration } from '@nx/devkit';
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
3
+
4
+ import * as utils from '@abgov/nx-oc';
5
+ import { environments } from '@abgov/nx-oc';
6
+ import { Schema } from './schema';
7
+ import generator from './mevn';
8
+
9
+ jest.mock('@nx/devkit', () => ({ ...jest.requireActual('@nx/devkit'), formatFiles: jest.fn().mockResolvedValue(undefined) }));
10
+ jest.mock('@abgov/nx-oc');
11
+ jest.mock('../../utils/agent', () => ({
12
+ consultAgent: jest.fn().mockResolvedValue(null),
13
+ confirmAfterAgentInterrupt: jest.fn().mockResolvedValue(undefined),
14
+ }));
15
+ const utilsMock = utils as jest.Mocked<typeof utils>;
16
+ utilsMock.getAdspConfiguration.mockResolvedValue({
17
+ tenant: 'test',
18
+ tenantRealm: 'test',
19
+ accessServiceUrl: environments.test.accessServiceUrl,
20
+ directoryServiceUrl: environments.test.directoryServiceUrl,
21
+ });
22
+ utilsMock.realmLogin.mockResolvedValue('test-token');
23
+
24
+ describe('MEVN Generator', () => {
25
+ const options: Schema = {
26
+ name: 'test',
27
+ env: 'dev',
28
+ };
29
+
30
+ it('can run', async () => {
31
+ const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
32
+ await generator(host, options);
33
+
34
+ const appConfig = readProjectConfiguration(host, 'test-app');
35
+ expect(appConfig.root).toBe('apps/test-app');
36
+
37
+ const serviceConfig = readProjectConfiguration(host, 'test-service');
38
+ expect(serviceConfig.root).toBe('apps/test-service');
39
+
40
+ expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
41
+ const nginxConf = host.read('apps/test-app/nginx.conf').toString();
42
+ expect(nginxConf).toContain('http://test-service:3333/');
43
+
44
+ expect(host.exists('apps/test-app/src/App.vue')).toBeTruthy();
45
+ expect(host.exists('apps/test-service/prisma/schema.prisma')).toBeFalsy();
46
+ }, 30000);
47
+ });
@@ -0,0 +1,14 @@
1
+ import { AdspConfiguration, EnvironmentName } from '@abgov/nx-oc';
2
+
3
+ export interface Schema {
4
+ name: string;
5
+ env: EnvironmentName;
6
+ accessToken?: string;
7
+ tenant?: string;
8
+ tenantRealm?: string;
9
+ skipAgent?: boolean;
10
+ }
11
+
12
+ export interface NormalizedSchema extends Schema {
13
+ adsp: AdspConfiguration;
14
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "id": "NxAdspMevn",
4
+ "title": "MEVN Stack ADSP Solution",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "description": "Name of the MEVN stack solution.",
10
+ "$default": {
11
+ "$source": "argv",
12
+ "index": 0
13
+ },
14
+ "x-prompt": "What name would you like to use?"
15
+ },
16
+ "env": {
17
+ "type": "string",
18
+ "description": "Environment to target.",
19
+ "$default": {
20
+ "$source": "argv",
21
+ "index": 1
22
+ },
23
+ "alias": "e",
24
+ "x-prompt": {
25
+ "message": "Which ADSP environment do you want to target?",
26
+ "type": "list",
27
+ "items": [
28
+ "dev",
29
+ "test",
30
+ "prod"
31
+ ]
32
+ }
33
+ },
34
+ "tenant": {
35
+ "type": "string",
36
+ "description": "ADSP tenant name. Enables a single browser login for the full stack — service and frontend agent interactions both use the same tenant-realm token.",
37
+ "alias": "t"
38
+ },
39
+ "tenantRealm": {
40
+ "type": "string",
41
+ "description": "Keycloak realm UUID. Optional when --tenant is provided — overrides the realm looked up from the tenant service.",
42
+ "alias": "tr"
43
+ },
44
+ "accessToken": {
45
+ "type": "string",
46
+ "description": "Access token for retrieving configuration from ADSP APIs.",
47
+ "alias": "at"
48
+ },
49
+ "skipAgent": {
50
+ "type": "boolean",
51
+ "description": "Skip the consultAgent interaction and generate base scaffolding only.",
52
+ "default": false
53
+ }
54
+ },
55
+ "required": [
56
+ "name",
57
+ "env"
58
+ ],
59
+ "additionalProperties": false
60
+ }
@@ -0,0 +1,3 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { Schema } from './schema';
3
+ export default function (host: Tree, options: Schema): Promise<() => void>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const nx_oc_1 = require("@abgov/nx-oc");
7
+ const express_service_1 = require("../express-service/express-service");
8
+ const vue_app_1 = require("../vue-app/vue-app");
9
+ const agent_1 = require("../../utils/agent");
10
+ const keycloak_admin_1 = require("../../utils/keycloak-admin");
11
+ const plugin_version_1 = require("../../utils/plugin-version");
12
+ function normalizeOptions(host, options) {
13
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
14
+ var _a;
15
+ const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
16
+ return Object.assign(Object.assign({}, options), { accessToken: (_a = adsp.accessToken) !== null && _a !== void 0 ? _a : options.accessToken, adsp });
17
+ });
18
+ }
19
+ function default_1(host, options) {
20
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
21
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
22
+ const normalizedOptions = yield normalizeOptions(host, options);
23
+ const projectName = (0, devkit_1.names)(options.name).fileName;
24
+ const serviceName = `${projectName}-service`;
25
+ const appName = `${projectName}-app`;
26
+ const appsDir = (0, devkit_1.getWorkspaceLayout)(host).appsDir;
27
+ const serviceRoot = `${appsDir}/${serviceName}`;
28
+ const appRoot = `${appsDir}/${appName}`;
29
+ yield (0, express_service_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: serviceName, skipAgent: true, database: 'postgres' }));
30
+ yield (0, vue_app_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: appName, proxy: {
31
+ location: '/api/',
32
+ proxyPass: `http://${serviceName}:3333/${serviceName}/`,
33
+ }, skipAgent: true }));
34
+ if (normalizedOptions.adsp) {
35
+ const accessToken = (_a = normalizedOptions.adsp.accessToken) !== null && _a !== void 0 ? _a : normalizedOptions.accessToken;
36
+ const serviceClientId = `urn:ads:${normalizedOptions.adsp.tenant}:${serviceName}`;
37
+ const appClientId = `urn:ads:${normalizedOptions.adsp.tenant}:${appName}`;
38
+ yield (0, keycloak_admin_1.ensureAudienceMapper)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, appClientId, serviceClientId, accessToken);
39
+ yield (0, keycloak_admin_1.ensureClientRoleScope)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, appClientId, serviceClientId, 'example-role', accessToken);
40
+ }
41
+ if (normalizedOptions.adsp && !normalizedOptions.skipAgent) {
42
+ const accessToken = (_b = normalizedOptions.adsp.accessToken) !== null && _b !== void 0 ? _b : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
43
+ var _a;
44
+ process.stdout.write(`\n[nx-adsp] Agent sign-in failed (${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}) — skipping agent interaction.\n`);
45
+ return undefined;
46
+ }));
47
+ yield (0, agent_1.confirmAfterAgentInterrupt)(yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
48
+ projectName,
49
+ projectType: 'pevn',
50
+ tenant: normalizedOptions.adsp.tenant,
51
+ pluginVersion: plugin_version_1.PLUGIN_VERSION,
52
+ existingFiles: {
53
+ 'service/src/main.ts': (_d = (_c = host.read(`${serviceRoot}/src/main.ts`)) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '',
54
+ 'service/src/environment.ts': (_f = (_e = host.read(`${serviceRoot}/src/environment.ts`)) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _f !== void 0 ? _f : '',
55
+ 'service/src/database.ts': (_h = (_g = host.read(`${serviceRoot}/src/database.ts`)) === null || _g === void 0 ? void 0 : _g.toString()) !== null && _h !== void 0 ? _h : '',
56
+ 'service/src/events.ts': (_k = (_j = host.read(`${serviceRoot}/src/events.ts`)) === null || _j === void 0 ? void 0 : _j.toString()) !== null && _k !== void 0 ? _k : '',
57
+ 'app/src/App.vue': (_m = (_l = host.read(`${appRoot}/src/App.vue`)) === null || _l === void 0 ? void 0 : _l.toString()) !== null && _m !== void 0 ? _m : '',
58
+ 'app/src/main.ts': (_p = (_o = host.read(`${appRoot}/src/main.ts`)) === null || _o === void 0 ? void 0 : _o.toString()) !== null && _p !== void 0 ? _p : '',
59
+ 'app/src/router/index.ts': (_r = (_q = host.read(`${appRoot}/src/router/index.ts`)) === null || _q === void 0 ? void 0 : _q.toString()) !== null && _r !== void 0 ? _r : '',
60
+ 'app/src/environments/environment.ts': (_t = (_s = host.read(`${appRoot}/src/environments/environment.ts`)) === null || _s === void 0 ? void 0 : _s.toString()) !== null && _t !== void 0 ? _t : '',
61
+ },
62
+ }, host, serviceRoot, { additionalRoots: { 'app': appRoot } }));
63
+ }
64
+ yield (0, devkit_1.formatFiles)(host);
65
+ return () => {
66
+ (0, devkit_1.installPackagesTask)(host);
67
+ };
68
+ });
69
+ }
70
+ //# sourceMappingURL=pevn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pevn.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/pevn/pevn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { readProjectConfiguration } from '@nx/devkit';
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
3
+
4
+ import * as utils from '@abgov/nx-oc';
5
+ import { environments } from '@abgov/nx-oc';
6
+ import { Schema } from './schema';
7
+ import generator from './pevn';
8
+
9
+ jest.mock('@nx/devkit', () => ({ ...jest.requireActual('@nx/devkit'), formatFiles: jest.fn().mockResolvedValue(undefined) }));
10
+ jest.mock('@abgov/nx-oc');
11
+ jest.mock('../../utils/agent', () => ({
12
+ consultAgent: jest.fn().mockResolvedValue(null),
13
+ confirmAfterAgentInterrupt: jest.fn().mockResolvedValue(undefined),
14
+ }));
15
+ const utilsMock = utils as jest.Mocked<typeof utils>;
16
+ utilsMock.getAdspConfiguration.mockResolvedValue({
17
+ tenant: 'test',
18
+ tenantRealm: 'test',
19
+ accessServiceUrl: environments.test.accessServiceUrl,
20
+ directoryServiceUrl: environments.test.directoryServiceUrl,
21
+ });
22
+ utilsMock.realmLogin.mockResolvedValue('test-token');
23
+
24
+ describe('PEVN Generator', () => {
25
+ const options: Schema = {
26
+ name: 'test',
27
+ env: 'dev',
28
+ };
29
+
30
+ it('can run', async () => {
31
+ const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
32
+ await generator(host, options);
33
+
34
+ const appConfig = readProjectConfiguration(host, 'test-app');
35
+ expect(appConfig.root).toBe('apps/test-app');
36
+
37
+ const serviceConfig = readProjectConfiguration(host, 'test-service');
38
+ expect(serviceConfig.root).toBe('apps/test-service');
39
+
40
+ expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
41
+ const nginxConf = host.read('apps/test-app/nginx.conf').toString();
42
+ expect(nginxConf).toContain('http://test-service:3333/');
43
+
44
+ expect(host.exists('apps/test-app/src/App.vue')).toBeTruthy();
45
+ expect(host.exists('apps/test-service/prisma/schema.prisma')).toBeTruthy();
46
+ }, 30000);
47
+ });
@@ -0,0 +1,14 @@
1
+ import { AdspConfiguration, EnvironmentName } from '@abgov/nx-oc';
2
+
3
+ export interface Schema {
4
+ name: string;
5
+ env: EnvironmentName;
6
+ accessToken?: string;
7
+ tenant?: string;
8
+ tenantRealm?: string;
9
+ skipAgent?: boolean;
10
+ }
11
+
12
+ export interface NormalizedSchema extends Schema {
13
+ adsp: AdspConfiguration;
14
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "id": "NxAdspPevn",
4
+ "title": "PEVN Stack ADSP Solution",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "description": "Name of the PEVN stack solution.",
10
+ "$default": {
11
+ "$source": "argv",
12
+ "index": 0
13
+ },
14
+ "x-prompt": "What name would you like to use?"
15
+ },
16
+ "env": {
17
+ "type": "string",
18
+ "description": "Environment to target.",
19
+ "$default": {
20
+ "$source": "argv",
21
+ "index": 1
22
+ },
23
+ "alias": "e",
24
+ "x-prompt": {
25
+ "message": "Which ADSP environment do you want to target?",
26
+ "type": "list",
27
+ "items": [
28
+ "dev",
29
+ "test",
30
+ "prod"
31
+ ]
32
+ }
33
+ },
34
+ "tenant": {
35
+ "type": "string",
36
+ "description": "ADSP tenant name. Enables a single browser login for the full stack — service and frontend agent interactions both use the same tenant-realm token.",
37
+ "alias": "t"
38
+ },
39
+ "tenantRealm": {
40
+ "type": "string",
41
+ "description": "Keycloak realm UUID. Optional when --tenant is provided — overrides the realm looked up from the tenant service.",
42
+ "alias": "tr"
43
+ },
44
+ "accessToken": {
45
+ "type": "string",
46
+ "description": "Access token for retrieving configuration from ADSP APIs.",
47
+ "alias": "at"
48
+ },
49
+ "skipAgent": {
50
+ "type": "boolean",
51
+ "description": "Skip the consultAgent interaction and generate base scaffolding only.",
52
+ "default": false
53
+ }
54
+ },
55
+ "required": [
56
+ "name",
57
+ "env"
58
+ ],
59
+ "additionalProperties": false
60
+ }
@@ -0,0 +1,97 @@
1
+ # AGENTS.md — <%= projectName %>
2
+
3
+ Vue 3 frontend for the Alberta Digital Service Platform (ADSP).
4
+ Generated by `nx g @abgov/nx-adsp:vue-app`.
5
+
6
+ ## Stack
7
+
8
+ - **UI**: Vue 3 (Composition API) + GoA design system (`@abgov/web-components` — `goa-*` custom elements)
9
+ - **Auth**: `@dsb-norge/vue-keycloak-js` (wraps `keycloak-js`)
10
+ - **State**: Pinia
11
+ - **Router**: Vue Router v4
12
+
13
+ ## Key files
14
+
15
+ | File | Purpose |
16
+ |------|---------|
17
+ | `src/main.ts` | Entry — registers Pinia, Router, and Keycloak plugin |
18
+ | `src/App.vue` | Shell — nav header with sign-in/out, hero banner, `<RouterView>` |
19
+ | `src/router/index.ts` | Routes — `/protected` guarded with `requiresAuth` meta |
20
+ | `src/views/HomeView.vue` | Public page — calls public and private APIs |
21
+ | `src/views/ProtectedView.vue` | Authenticated page — shows user info from token |
22
+ | `src/environments/environment.ts` | Access URL, realm, client ID — pre-set from ADSP tenant |
23
+ | `vite.config.ts` | Vite config — `isCustomElement` marks `goa-*` as web components |
24
+
25
+ ## Auth pattern
26
+
27
+ ```typescript
28
+ import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
29
+
30
+ // useKeycloak() returns a DeepReadonly<VueKeycloakInstance>
31
+ const { authenticated, fullName, ready, token, tokenParsed, keycloak } = useKeycloak();
32
+
33
+ authenticated // Ref<boolean> — true after sign-in
34
+ fullName // Ref<string | undefined> — display name from token
35
+ ready // Ref<boolean> — true once Keycloak init has settled
36
+ token // Ref<string | undefined> — current access token
37
+
38
+ // Call Keycloak actions via the underlying keycloak-js instance:
39
+ keycloak?.login()
40
+ keycloak?.logout({ redirectUri: window.location.origin })
41
+
42
+ // Refresh token before an authenticated API call:
43
+ await keycloak?.updateToken(30);
44
+ const bearer = keycloak?.token;
45
+ ```
46
+
47
+ Route guard (in `router/index.ts`):
48
+
49
+ ```typescript
50
+ router.beforeEach((to) => {
51
+ if (to.meta.requiresAuth) {
52
+ const { authenticated, ready, keycloak } = useKeycloak();
53
+ if (!ready) return true; // let Keycloak init settle first
54
+ if (!authenticated) {
55
+ keycloak?.login({ redirectUri: window.location.origin + to.fullPath });
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ });
61
+ ```
62
+
63
+ ## GoA design system
64
+
65
+ Use `goa-*` web components directly in Vue templates. The vite.config.ts
66
+ `isCustomElement` option prevents Vue from warning about unknown elements.
67
+
68
+ **Event names**: GoA web component interactive elements (buttons, inputs, etc.)
69
+ emit a `_click` custom event (not `click`) to avoid conflicts with native browser
70
+ events. Use `@_click` in Vue templates:
71
+
72
+ ```html
73
+ <goa-button type="primary" @_click="handleSave">Save</goa-button>
74
+ <goa-input @_change="handleChange" />
75
+ ```
76
+
77
+ See [GoA web components docs](https://ui-components.alberta.ca) for the full
78
+ component and event reference.
79
+
80
+ ## Backend API calls (proxy setup)
81
+
82
+ Use relative `/api/` paths — they route through Vite's dev proxy and nginx in production:
83
+
84
+ ```typescript
85
+ // ✓ correct
86
+ const res = await fetch('/api/v1/my-resource');
87
+
88
+ // ✗ wrong — bypasses proxy, won't work in production
89
+ const res = await fetch('http://localhost:3333/my-service/v1/my-resource');
90
+ ```
91
+
92
+ ## What NOT to change
93
+
94
+ - `src/main.ts` — Keycloak plugin is registered once here; do not call `new Keycloak()` elsewhere
95
+ - `environments/environment.ts` — access URL and realm are pre-configured for the ADSP tenant
96
+ - `silent-check-sso.html` — required for keycloak-js silent SSO; must be served from the app root
97
+ - `vite.config.ts` — the `isCustomElement` predicate must stay to suppress Vue warnings for `goa-*` elements
@@ -0,0 +1,45 @@
1
+ events {
2
+ worker_connections 1024;
3
+ }
4
+
5
+ http {
6
+ sendfile on;
7
+ include mime.types;
8
+ default_type application/octet-stream;
9
+
10
+ gzip on;
11
+ gzip_types text/plain text/css application/javascript application/json image/svg+xml font/woff2;
12
+ gzip_min_length 1000;
13
+
14
+ server {
15
+ listen 8080;
16
+ root /opt/app-root/src;
17
+ index index.html;
18
+
19
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
20
+ add_header X-Content-Type-Options "nosniff" always;
21
+ add_header X-Frame-Options "SAMEORIGIN" always;
22
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
23
+
24
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
25
+ expires 30d;
26
+ add_header Cache-Control "public, no-transform";
27
+ }
28
+
29
+ location = /silent-check-sso.html {
30
+ add_header Cache-Control "no-store";
31
+ }
32
+
33
+ location / {
34
+ try_files $uri /index.html;
35
+ }
36
+ <% nginxProxies.forEach(function(nginxProxy){ %>
37
+ location <%= nginxProxy.location %> {
38
+ proxy_pass <%= nginxProxy.proxyPass %>;
39
+ proxy_set_header Host $host;
40
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
41
+ proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
42
+ }
43
+ <% }); %>
44
+ }
45
+ }