@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.
- package/generators.json +17 -0
- package/package.json +5 -1
- package/src/generators/mevn/mevn.d.ts +3 -0
- package/src/generators/mevn/mevn.js +70 -0
- package/src/generators/mevn/mevn.js.map +1 -0
- package/src/generators/mevn/mevn.spec.ts +47 -0
- package/src/generators/mevn/schema.d.ts +14 -0
- package/src/generators/mevn/schema.json +60 -0
- package/src/generators/pevn/pevn.d.ts +3 -0
- package/src/generators/pevn/pevn.js +70 -0
- package/src/generators/pevn/pevn.js.map +1 -0
- package/src/generators/pevn/pevn.spec.ts +47 -0
- package/src/generators/pevn/schema.d.ts +14 -0
- package/src/generators/pevn/schema.json +60 -0
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +97 -0
- package/src/generators/vue-app/files/nginx.conf__tmpl__ +45 -0
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +39 -0
- package/src/generators/vue-app/files/src/assets/banner.jpg +0 -0
- package/src/generators/vue-app/files/src/environments/environment.ts__tmpl__ +11 -0
- package/src/generators/vue-app/files/src/index.html__tmpl__ +22 -0
- package/src/generators/vue-app/files/src/main.ts__tmpl__ +28 -0
- package/src/generators/vue-app/files/src/router/index.ts__tmpl__ +29 -0
- package/src/generators/vue-app/files/src/silent-check-sso.html__tmpl__ +5 -0
- package/src/generators/vue-app/files/src/views/HomeView.vue__tmpl__ +49 -0
- package/src/generators/vue-app/files/src/views/ProtectedView.vue__tmpl__ +14 -0
- package/src/generators/vue-app/files/vite.config.ts__tmpl__ +50 -0
- package/src/generators/vue-app/schema.d.ts +22 -0
- package/src/generators/vue-app/schema.json +85 -0
- package/src/generators/vue-app/vue-app.d.ts +3 -0
- package/src/generators/vue-app/vue-app.js +140 -0
- package/src/generators/vue-app/vue-app.js.map +1 -0
- package/src/generators/vue-app/vue-app.spec.ts +93 -0
- package/src/utils/agent.d.ts +1 -1
- package/src/utils/agent.js +5 -3
- 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.
|
|
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,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,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
|
+
}
|