@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
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
3
+ import { RouterView } from 'vue-router';
4
+
5
+ const { authenticated, fullName, ready, keycloak } = useKeycloak();
6
+
7
+ function login() { keycloak?.login(); }
8
+ function logout() { keycloak?.logout({ redirectUri: window.location.origin }); }
9
+ </script>
10
+
11
+ <template>
12
+ <goa-microsite-header type="alpha" />
13
+ <goa-app-header url="/" heading="<%= projectName %>">
14
+ <span v-if="ready && fullName">{{ fullName }}</span>
15
+ <goa-button v-if="!authenticated && ready" type="tertiary" @_click="login">
16
+ Sign in
17
+ </goa-button>
18
+ <goa-button v-if="authenticated" type="tertiary" @_click="logout">
19
+ Sign out
20
+ </goa-button>
21
+ </goa-app-header>
22
+ <goa-hero-banner heading="<%= projectName %>" backgroundurl="/assets/banner.jpg" />
23
+ <main>
24
+ <RouterView />
25
+ </main>
26
+ </template>
27
+
28
+ <style>
29
+ body { margin: 0; }
30
+ main {
31
+ display: grid;
32
+ grid-template-columns: repeat(6, auto);
33
+ }
34
+ main > section {
35
+ grid-column: 2 / span 2;
36
+ margin-top: 40px;
37
+ margin-bottom: 40px;
38
+ }
39
+ </style>
@@ -0,0 +1,11 @@
1
+ export const environment = {
2
+ production: false,
3
+ directory: {
4
+ url: '<%= directoryServiceUrl %>',
5
+ },
6
+ access: {
7
+ url: '<%= accessServiceUrl %>/auth',
8
+ realm: '<%= tenantRealm %>',
9
+ client_id: 'urn:ads:<%= tenant %>:<%= projectName %>',
10
+ },
11
+ };
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title><%= projectName %></title>
6
+ <base href="/" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <link rel="icon" type="image/x-icon" href="favicon.ico" />
9
+ <script
10
+ type="module"
11
+ src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
12
+ ></script>
13
+ <script
14
+ nomodule
15
+ src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
16
+ ></script>
17
+ </head>
18
+ <body>
19
+ <div id="app"></div>
20
+ <script type="module" src="/src/main.ts"></script>
21
+ </body>
22
+ </html>
@@ -0,0 +1,28 @@
1
+ import '@abgov/web-components';
2
+ import '@abgov/web-components/index.css';
3
+ import { createApp } from 'vue';
4
+ import { createPinia } from 'pinia';
5
+ import keycloakPlugin from '@dsb-norge/vue-keycloak-js';
6
+
7
+ import App from './App.vue';
8
+ import router from './router';
9
+ import { environment } from './environments/environment';
10
+
11
+ const app = createApp(App);
12
+
13
+ app.use(createPinia());
14
+ app.use(router);
15
+ app.use(keycloakPlugin, {
16
+ config: {
17
+ url: environment.access.url,
18
+ realm: environment.access.realm,
19
+ clientId: environment.access.client_id,
20
+ },
21
+ init: {
22
+ onLoad: 'check-sso',
23
+ pkceMethod: 'S256',
24
+ silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
25
+ },
26
+ });
27
+
28
+ app.mount('#app');
@@ -0,0 +1,29 @@
1
+ import { createRouter, createWebHistory } from 'vue-router';
2
+ import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
3
+ import HomeView from '../views/HomeView.vue';
4
+
5
+ const router = createRouter({
6
+ history: createWebHistory(import.meta.env.BASE_URL),
7
+ routes: [
8
+ { path: '/', component: HomeView },
9
+ {
10
+ path: '/protected',
11
+ component: () => import('../views/ProtectedView.vue'),
12
+ meta: { requiresAuth: true },
13
+ },
14
+ ],
15
+ });
16
+
17
+ router.beforeEach((to) => {
18
+ if (to.meta.requiresAuth) {
19
+ const { authenticated, ready, keycloak } = useKeycloak();
20
+ if (!ready) return true;
21
+ if (!authenticated) {
22
+ keycloak?.login({ redirectUri: window.location.origin + to.fullPath });
23
+ return false;
24
+ }
25
+ }
26
+ return true;
27
+ });
28
+
29
+ export default router;
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <script>parent.postMessage(location.href, location.origin);</script>
4
+ </body>
5
+ </html>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from 'vue';
3
+ import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
4
+
5
+ const { authenticated, keycloak } = useKeycloak();
6
+ const publicResource = ref('Not retrieved — is the backend service running?');
7
+ const privateResource = ref('Not retrieved — sign in first.');
8
+
9
+ onMounted(async () => {
10
+ try {
11
+ const res = await fetch('/api/v1/public');
12
+ const data = await res.json();
13
+ publicResource.value = data.message;
14
+ } catch {
15
+ publicResource.value = 'Error loading public resource.';
16
+ }
17
+
18
+ if (authenticated) {
19
+ try {
20
+ await keycloak?.updateToken(30);
21
+ const res = await fetch('/api/v1/private', {
22
+ headers: { Authorization: `Bearer ${keycloak?.token}` },
23
+ });
24
+ const data = await res.json();
25
+ privateResource.value = data.message;
26
+ } catch {
27
+ privateResource.value = 'Error loading private resource.';
28
+ }
29
+ }
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <section>
35
+ <h2>Welcome to <%= projectName %>!</h2>
36
+ <p>Don't panic. Start editing the project to build your digital service.</p>
37
+ <h3>A few things you might want to do next:</h3>
38
+ <ul>
39
+ <li>Public API: <strong>{{ publicResource }}</strong></li>
40
+ <li>Private API (sign in to access): <strong>{{ privateResource }}</strong></li>
41
+ <li>
42
+ Extend the API: add routes to <code>/<%= projectName %>/v1</code> in the backend service.
43
+ </li>
44
+ <li>
45
+ <router-link to="/protected">View the protected page</router-link>
46
+ </li>
47
+ </ul>
48
+ </section>
49
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
3
+
4
+ const { fullName, tokenParsed } = useKeycloak();
5
+ const userEmail = tokenParsed?.email ?? '';
6
+ </script>
7
+
8
+ <template>
9
+ <section>
10
+ <h2>Protected page</h2>
11
+ <p>You are signed in as <strong>{{ fullName }}</strong> ({{ userEmail }}).</p>
12
+ <router-link to="/">← Back to home</router-link>
13
+ </section>
14
+ </template>
@@ -0,0 +1,50 @@
1
+ /// <reference types='vitest' />
2
+ import { defineConfig } from 'vite';
3
+ import vue from '@vitejs/plugin-vue';
4
+
5
+ export default defineConfig({
6
+ root: __dirname,
7
+ cacheDir: '<%= offsetFromRoot %>node_modules/.vite/<%= projectName %>',
8
+
9
+ server: {
10
+ port: 4200,
11
+ host: 'localhost',
12
+ },
13
+
14
+ preview: {
15
+ port: 4300,
16
+ host: 'localhost',
17
+ },
18
+
19
+ plugins: [
20
+ vue({
21
+ template: {
22
+ compilerOptions: {
23
+ // Treat goa-* custom elements as web components, not Vue components.
24
+ isCustomElement: (tag) => tag.startsWith('goa-'),
25
+ },
26
+ },
27
+ }),
28
+ ],
29
+
30
+ test: {
31
+ watch: false,
32
+ globals: true,
33
+ environment: 'jsdom',
34
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue}'],
35
+ reporters: ['default'],
36
+ coverage: {
37
+ reportsDirectory: '<%= offsetFromRoot %>coverage/<%= projectRoot %>',
38
+ provider: 'v8',
39
+ },
40
+ },
41
+
42
+ build: {
43
+ outDir: '<%= offsetFromRoot %>dist/<%= projectRoot %>',
44
+ emptyOutDir: true,
45
+ reportCompressedSize: true,
46
+ commonjsOptions: {
47
+ transformMixedEsModules: true,
48
+ },
49
+ },
50
+ });
@@ -0,0 +1,22 @@
1
+ import { AdspConfiguration, EnvironmentName } from '@abgov/nx-oc';
2
+ import { NginxProxyConfiguration } from '../../utils/nginx';
3
+
4
+ export interface Schema {
5
+ name: string;
6
+ env: EnvironmentName;
7
+ accessToken?: string;
8
+ tenant?: string;
9
+ tenantRealm?: string;
10
+ serviceClientId?: string;
11
+ proxy?: NginxProxyConfiguration | NginxProxyConfiguration[];
12
+ /** When true, skip the agent interaction. Used by composite generators that run the agent themselves. */
13
+ skipAgent?: boolean;
14
+ }
15
+
16
+ export interface NormalizedSchema extends Schema {
17
+ projectName: string;
18
+ projectRoot: string;
19
+ openshiftDirectory: string;
20
+ adsp: AdspConfiguration;
21
+ nginxProxies: NginxProxyConfiguration[];
22
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "id": "NxAdspVueApp",
4
+ "title": "Vue ADSP Application",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "description": "Name of the application.",
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. Looks up the tenant realm and opens a single browser login, avoiding a separate interactive tenant selection.",
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
+ "serviceClientId": {
50
+ "type": "string",
51
+ "description": "Client ID of a paired backend service (e.g. urn:ads:my-tenant:my-svc). When provided with --tenant, configures audience mapping and example-role scope on the frontend client.",
52
+ "alias": "sc"
53
+ },
54
+ "proxy": {
55
+ "oneOf": [
56
+ {
57
+ "type": "array",
58
+ "items": {
59
+ "type": "object",
60
+ "properties": {
61
+ "location": { "type": "string" },
62
+ "proxyPass": { "type": "string" }
63
+ },
64
+ "required": ["location", "proxyPass"]
65
+ }
66
+ },
67
+ {
68
+ "type": "object",
69
+ "properties": {
70
+ "location": { "type": "string" },
71
+ "proxyPass": { "type": "string" }
72
+ },
73
+ "required": ["location", "proxyPass"]
74
+ }
75
+ ]
76
+ },
77
+ "skipAgent": {
78
+ "type": "boolean",
79
+ "description": "Skip the consultAgent interaction and generate base scaffolding only.",
80
+ "default": false
81
+ }
82
+ },
83
+ "required": ["name", "env"],
84
+ "additionalProperties": false
85
+ }
@@ -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,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const tslib_1 = require("tslib");
5
+ const nx_oc_1 = require("@abgov/nx-oc");
6
+ const agent_1 = require("../../utils/agent");
7
+ const keycloak_admin_1 = require("../../utils/keycloak-admin");
8
+ const plugin_version_1 = require("../../utils/plugin-version");
9
+ const quality_1 = require("../../utils/quality");
10
+ const devkit_1 = require("@nx/devkit");
11
+ const path = require("path");
12
+ function normalizeOptions(host, options) {
13
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
14
+ const projectName = (0, devkit_1.names)(options.name).fileName;
15
+ const projectRoot = `${(0, devkit_1.getWorkspaceLayout)(host).appsDir}/${projectName}`;
16
+ const openshiftDirectory = `.openshift/${projectName}`;
17
+ const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
18
+ const nginxProxies = Array.isArray(options.proxy)
19
+ ? [...options.proxy]
20
+ : options.proxy
21
+ ? [options.proxy]
22
+ : [];
23
+ return Object.assign(Object.assign({}, options), { projectName, projectRoot, openshiftDirectory, adsp, nginxProxies });
24
+ });
25
+ }
26
+ function addFiles(host, options) {
27
+ const templateOptions = Object.assign(Object.assign(Object.assign({}, options), options.adsp), { offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), tmpl: '' });
28
+ (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), options.projectRoot, templateOptions);
29
+ const addProxyConf = options.nginxProxies.length > 0;
30
+ if (addProxyConf) {
31
+ const devProxyConf = options.nginxProxies.reduce((proxyConf, nginxProxy) => {
32
+ const upstreamUrl = new URL(nginxProxy.proxyPass);
33
+ const proxy = {
34
+ target: `${upstreamUrl.protocol}//localhost${upstreamUrl.port ? ':' + upstreamUrl.port : ''}`,
35
+ secure: upstreamUrl.protocol === 'https:',
36
+ changeOrigin: false,
37
+ pathRewrite: {},
38
+ };
39
+ if (upstreamUrl.pathname.length > 1) {
40
+ proxy.pathRewrite = { [`^${nginxProxy.location}`]: upstreamUrl.pathname };
41
+ }
42
+ return Object.assign(Object.assign({}, proxyConf), { [nginxProxy.location]: proxy });
43
+ }, {});
44
+ (0, devkit_1.writeJson)(host, `${options.projectRoot}/vite.proxy.json`, devProxyConf);
45
+ }
46
+ return addProxyConf;
47
+ }
48
+ function default_1(host, options) {
49
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
50
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
51
+ const normalizedOptions = yield normalizeOptions(host, options);
52
+ const { applicationGenerator: initVue } = yield Promise.resolve().then(() => require('@nx/vue'));
53
+ yield initVue(host, {
54
+ name: options.name,
55
+ style: 'css',
56
+ skipFormat: true,
57
+ linter: 'eslint',
58
+ unitTestRunner: 'vitest',
59
+ e2eTestRunner: 'none',
60
+ routing: true,
61
+ directory: normalizedOptions.projectRoot,
62
+ });
63
+ (0, devkit_1.addDependenciesToPackageJson)(host, {
64
+ '@abgov/design-tokens': '1.8.0',
65
+ '@abgov/web-components': '1.39.3',
66
+ '@dsb-norge/vue-keycloak-js': '^3.0.0',
67
+ 'keycloak-js': '^23.0.7',
68
+ 'pinia': '^2.0.0',
69
+ 'vue-router': '^4.0.0',
70
+ }, {
71
+ 'eslint-plugin-security': '^3.0.0',
72
+ 'eslint-plugin-no-secrets': '^2.0.0',
73
+ });
74
+ // Remove Nx scaffold files replaced by our templates.
75
+ for (const f of [
76
+ 'src/App.vue',
77
+ 'src/components/HelloWorld.vue',
78
+ 'src/views/AboutView.vue',
79
+ ]) {
80
+ if (host.exists(`${normalizedOptions.projectRoot}/${f}`)) {
81
+ host.delete(`${normalizedOptions.projectRoot}/${f}`);
82
+ }
83
+ }
84
+ const addedProxy = addFiles(host, normalizedOptions);
85
+ (0, quality_1.addJestCoverageConfig)(host, normalizedOptions.projectRoot);
86
+ (0, quality_1.addVsCodeSettings)(host);
87
+ const config = (0, devkit_1.readProjectConfiguration)(host, options.name);
88
+ // Wire the vite dev-server proxy when nginx proxy locations are configured.
89
+ if (addedProxy && ((_a = config.targets.serve) === null || _a === void 0 ? void 0 : _a.options)) {
90
+ config.targets.serve.options = Object.assign(Object.assign({}, config.targets.serve.options), { proxyConfig: `${normalizedOptions.projectRoot}/vite.proxy.json` });
91
+ }
92
+ // Ensure silent-check-sso.html is served as a static asset.
93
+ if ((_b = config.targets.build) === null || _b === void 0 ? void 0 : _b.options) {
94
+ config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), { assets: [
95
+ ...((_c = config.targets.build.options.assets) !== null && _c !== void 0 ? _c : []),
96
+ {
97
+ glob: 'nginx.conf',
98
+ input: normalizedOptions.projectRoot,
99
+ output: './',
100
+ },
101
+ ] });
102
+ }
103
+ (0, devkit_1.updateProjectConfiguration)(host, options.name, config);
104
+ (0, quality_1.addSemgrepTarget)(host, options.name);
105
+ yield (0, devkit_1.formatFiles)(host);
106
+ if (normalizedOptions.adsp) {
107
+ const accessToken = (_d = normalizedOptions.adsp.accessToken) !== null && _d !== void 0 ? _d : options.accessToken;
108
+ const clientId = `urn:ads:${normalizedOptions.adsp.tenant}:${normalizedOptions.projectName}`;
109
+ yield (0, keycloak_admin_1.ensurePublicClient)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, clientId, accessToken);
110
+ if (options.serviceClientId) {
111
+ yield (0, keycloak_admin_1.ensureAudienceMapper)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, clientId, options.serviceClientId, accessToken);
112
+ yield (0, keycloak_admin_1.ensureClientRoleScope)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, clientId, options.serviceClientId, 'example-role', accessToken);
113
+ }
114
+ }
115
+ if (normalizedOptions.adsp && !options.skipAgent) {
116
+ const accessToken = (_e = normalizedOptions.adsp.accessToken) !== null && _e !== void 0 ? _e : options.accessToken;
117
+ const appVue = (_g = (_f = host.read(`${normalizedOptions.projectRoot}/src/App.vue`)) === null || _f === void 0 ? void 0 : _f.toString()) !== null && _g !== void 0 ? _g : '';
118
+ const mainTs = (_j = (_h = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _h === void 0 ? void 0 : _h.toString()) !== null && _j !== void 0 ? _j : '';
119
+ const routerTs = (_l = (_k = host.read(`${normalizedOptions.projectRoot}/src/router/index.ts`)) === null || _k === void 0 ? void 0 : _k.toString()) !== null && _l !== void 0 ? _l : '';
120
+ const environmentTs = (_o = (_m = host.read(`${normalizedOptions.projectRoot}/src/environments/environment.ts`)) === null || _m === void 0 ? void 0 : _m.toString()) !== null && _o !== void 0 ? _o : '';
121
+ yield (0, agent_1.confirmAfterAgentInterrupt)(yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
122
+ projectName: normalizedOptions.projectName,
123
+ projectType: 'vue-app',
124
+ tenant: normalizedOptions.adsp.tenant,
125
+ pluginVersion: plugin_version_1.PLUGIN_VERSION,
126
+ existingFiles: {
127
+ 'src/App.vue': appVue,
128
+ 'src/main.ts': mainTs,
129
+ 'src/router/index.ts': routerTs,
130
+ 'src/environments/environment.ts': environmentTs,
131
+ },
132
+ }, host, normalizedOptions.projectRoot));
133
+ }
134
+ yield (0, nx_oc_1.deploymentGenerator)(host, Object.assign(Object.assign({}, normalizedOptions), { appType: 'frontend', project: normalizedOptions.projectName }));
135
+ return () => {
136
+ (0, devkit_1.installPackagesTask)(host);
137
+ };
138
+ });
139
+ }
140
+ //# sourceMappingURL=vue-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":";;AA+DA,4BA4IC;;AA3MD,wCAAyE;AACzE,6CAA6E;AAC7E,+DAA6G;AAC7G,+DAA4D;AAC5D,iDAAiG;AACjG,uCAYoB;AACpB,6BAA6B;AAG7B,SAAe,gBAAgB,CAAC,IAAU,EAAE,OAAe;;QACzD,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QACjD,MAAM,WAAW,GAAG,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC,OAAO,IAAI,WAAW,EAAE,CAAC;QACzE,MAAM,kBAAkB,GAAG,cAAc,WAAW,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;YAC/C,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;YACpB,CAAC,CAAC,OAAO,CAAC,KAAK;gBACf,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjB,CAAC,CAAC,EAAE,CAAC;QACP,uCAAY,OAAO,KAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE,YAAY,IAAG;IAC1F,CAAC;CAAA;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;IACrD,MAAM,eAAe,iDAChB,OAAO,GACP,OAAO,CAAC,IAAI,KACf,cAAc,EAAE,IAAA,uBAAc,EAAC,OAAO,CAAC,WAAW,CAAC,EACnD,IAAI,EAAE,EAAE,GACT,CAAC;IACF,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IAEzF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IACrD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE;YACzE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG;gBACZ,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,cAAc,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC7F,MAAM,EAAE,WAAW,CAAC,QAAQ,KAAK,QAAQ;gBACzC,YAAY,EAAE,KAAK;gBACnB,WAAW,EAAE,EAAE;aAChB,CAAC;YACF,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC5E,CAAC;YACD,uCAAY,SAAS,KAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAG;QACxD,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,IAAA,kBAAS,EAAC,IAAI,EAAE,GAAG,OAAO,CAAC,WAAW,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;;QACxD,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEhE,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,GAAG,2CAAa,SAAS,EAAC,CAAC;QAClE,MAAM,OAAO,CAAC,IAAI,EAAE;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,QAAQ;YACxB,aAAa,EAAE,MAAM;YACrB,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,iBAAiB,CAAC,WAAW;SACzC,CAAC,CAAC;QAEH,IAAA,qCAA4B,EAC1B,IAAI,EACJ;YACE,sBAAsB,EAAE,OAAO;YAC/B,uBAAuB,EAAE,QAAQ;YACjC,4BAA4B,EAAE,QAAQ;YACtC,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,QAAQ;YACjB,YAAY,EAAE,QAAQ;SACvB,EACD;YACE,wBAAwB,EAAE,QAAQ;YAClC,0BAA0B,EAAE,QAAQ;SACrC,CACF,CAAC;QAEF,sDAAsD;QACtD,KAAK,MAAM,CAAC,IAAI;YACd,aAAa;YACb,+BAA+B;YAC/B,yBAAyB;SAC1B,EAAE,CAAC;YACF,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBACzD,IAAI,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAErD,IAAA,+BAAqB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3D,IAAA,2BAAiB,EAAC,IAAI,CAAC,CAAC;QAExB,MAAM,MAAM,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAE5D,4EAA4E;QAC5E,IAAI,UAAU,KAAI,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,0CAAE,OAAO,CAAA,EAAE,CAAC;YAChD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,WAAW,EAAE,GAAG,iBAAiB,CAAC,WAAW,kBAAkB,GAChE,CAAC;QACJ,CAAC;QAED,4DAA4D;QAC5D,IAAI,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,0CAAE,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,mCACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,KAC/B,MAAM,EAAE;oBACN,GAAG,CAAC,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,mCAAI,EAAE,CAAC;oBAC9C;wBACE,IAAI,EAAE,YAAY;wBAClB,KAAK,EAAE,iBAAiB,CAAC,WAAW;wBACpC,MAAM,EAAE,IAAI;qBACb;iBACF,GACF,CAAC;QACJ,CAAC;QAED,IAAA,mCAA0B,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAEvD,IAAA,0BAAgB,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,IAAI,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,WAAW,GAAG,MAAA,iBAAiB,CAAC,IAAI,CAAC,WAAW,mCAAI,OAAO,CAAC,WAAW,CAAC;YAC9E,MAAM,QAAQ,GAAG,WAAW,iBAAiB,CAAC,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC,WAAW,EAAE,CAAC;YAC7F,MAAM,IAAA,mCAAkB,EACtB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,WAAW,CACZ,CAAC;YACF,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;gBAC5B,MAAM,IAAA,qCAAoB,EACxB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,OAAO,CAAC,eAAe,EACvB,WAAW,CACZ,CAAC;gBACF,MAAM,IAAA,sCAAqB,EACzB,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,OAAO,CAAC,eAAe,EACvB,cAAc,EACd,WAAW,CACZ,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,iBAAiB,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YACjD,MAAM,WAAW,GAAG,MAAA,iBAAiB,CAAC,IAAI,CAAC,WAAW,mCAAI,OAAO,CAAC,WAAW,CAAC;YAC9E,MAAM,MAAM,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YAC3F,MAAM,MAAM,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YAC3F,MAAM,QAAQ,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,sBAAsB,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YACrG,MAAM,aAAa,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,kCAAkC,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YACtH,MAAM,IAAA,kCAA0B,EAAC,MAAM,IAAA,oBAAY,EACjD,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,EAC1C,WAAW,EACX;gBACE,WAAW,EAAE,iBAAiB,CAAC,WAAW;gBAC1C,WAAW,EAAE,SAAS;gBACtB,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM;gBACrC,aAAa,EAAE,+BAAc;gBAC7B,aAAa,EAAE;oBACb,aAAa,EAAE,MAAM;oBACrB,aAAa,EAAE,MAAM;oBACrB,qBAAqB,EAAE,QAAQ;oBAC/B,iCAAiC,EAAE,aAAa;iBACjD;aACF,EACD,IAAI,EACJ,iBAAiB,CAAC,WAAW,CAC9B,CAAC,CAAC;QACL,CAAC;QAED,MAAM,IAAA,2BAAmB,EAAC,IAAI,kCACzB,iBAAiB,KACpB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,iBAAiB,CAAC,WAAW,IACtC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
@@ -0,0 +1,93 @@
1
+ import { readProjectConfiguration, Tree } 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 './vue-app';
8
+
9
+ jest.mock('@nx/devkit', () => ({
10
+ ...jest.requireActual('@nx/devkit'),
11
+ formatFiles: jest.fn().mockResolvedValue(undefined),
12
+ }));
13
+ jest.mock('@abgov/nx-oc');
14
+ jest.mock('../../utils/agent', () => ({
15
+ consultAgent: jest.fn().mockResolvedValue(null),
16
+ confirmAfterAgentInterrupt: jest.fn().mockResolvedValue(undefined),
17
+ }));
18
+
19
+ const utilsMock = utils as jest.Mocked<typeof utils>;
20
+ utilsMock.getAdspConfiguration.mockResolvedValue({
21
+ tenant: 'test',
22
+ tenantRealm: 'test',
23
+ accessServiceUrl: environments.test.accessServiceUrl,
24
+ directoryServiceUrl: environments.test.directoryServiceUrl,
25
+ });
26
+
27
+ describe('Vue App Generator', () => {
28
+ let host: Tree;
29
+ const options: Schema = { name: 'test', env: 'dev' };
30
+
31
+ beforeEach(() => {
32
+ host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
33
+ });
34
+
35
+ it('can run', async () => {
36
+ await generator(host, options);
37
+ const config = readProjectConfiguration(host, 'test');
38
+ expect(config.root).toBe('apps/test');
39
+ expect(host.exists('apps/test/nginx.conf')).toBeTruthy();
40
+ expect(host.exists('apps/test/src/main.ts')).toBeTruthy();
41
+ expect(host.exists('apps/test/src/App.vue')).toBeTruthy();
42
+ expect(host.exists('apps/test/src/router/index.ts')).toBeTruthy();
43
+ expect(host.exists('apps/test/src/environments/environment.ts')).toBeTruthy();
44
+ expect(host.exists('apps/test/vite.config.ts')).toBeTruthy();
45
+ }, 30000);
46
+
47
+ it('vite.config.ts marks goa-* elements as custom elements', async () => {
48
+ await generator(host, options);
49
+ const viteConfig = host.read('apps/test/vite.config.ts').toString();
50
+ expect(viteConfig).toContain("isCustomElement: (tag) => tag.startsWith('goa-')");
51
+ }, 30000);
52
+
53
+ it('environment.ts is pre-populated with tenant config', async () => {
54
+ await generator(host, options);
55
+ const env = host.read('apps/test/src/environments/environment.ts').toString();
56
+ expect(env).toContain(environments.test.accessServiceUrl);
57
+ expect(env).toContain(environments.test.directoryServiceUrl);
58
+ expect(env).toContain('urn:ads:test:test');
59
+ }, 30000);
60
+
61
+ it('can add nginx proxy', async () => {
62
+ await generator(host, {
63
+ ...options,
64
+ proxy: { location: '/test/', proxyPass: 'http://test-service:3333/' },
65
+ });
66
+ const nginxConf = host.read('apps/test/nginx.conf').toString();
67
+ expect(nginxConf).toContain('http://test-service:3333/');
68
+ });
69
+
70
+ it('can add multiple nginx proxies', async () => {
71
+ await generator(host, {
72
+ ...options,
73
+ proxy: [
74
+ { location: '/test/', proxyPass: 'http://test-service:3333/' },
75
+ { location: '/test2/', proxyPass: 'http://test-service2:3333/' },
76
+ ],
77
+ });
78
+ const nginxConf = host.read('apps/test/nginx.conf').toString();
79
+ expect(nginxConf).toContain('http://test-service:3333/');
80
+ expect(nginxConf).toContain('http://test-service2:3333/');
81
+ });
82
+
83
+ it('writes vite dev proxy config when proxy is configured', async () => {
84
+ await generator(host, {
85
+ ...options,
86
+ proxy: { location: '/test/', proxyPass: 'http://test-service:3333/api/' },
87
+ });
88
+ expect(host.exists('apps/test/vite.proxy.json')).toBeTruthy();
89
+ const proxyConf = JSON.parse(host.read('apps/test/vite.proxy.json').toString());
90
+ expect(proxyConf['/test/'].target).toBe('http://localhost:3333');
91
+ expect(proxyConf['/test/'].pathRewrite['^/test/']).toBe('/api/');
92
+ });
93
+ });
@@ -41,7 +41,7 @@ export declare function confirmAfterAgentInterrupt(result: AgentResult | null):
41
41
  */
42
42
  export declare function consultAgent(directoryServiceUrl: string, accessToken: string, projectContext: {
43
43
  projectName: string;
44
- projectType: 'express-service' | 'react-app' | 'angular-app' | 'mern' | 'mean' | 'pern' | 'pean';
44
+ projectType: 'express-service' | 'react-app' | 'angular-app' | 'vue-app' | 'mern' | 'mean' | 'pern' | 'pean' | 'pevn' | 'mevn';
45
45
  tenant: string;
46
46
  pluginVersion: string;
47
47
  /** Content of key integration files for the agent to read and potentially modify. */
@@ -135,10 +135,12 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
135
135
  `The files (${fileNames}) have been uploaded to your workspace — please read them to understand the current structure before suggesting capabilities. ` +
136
136
  `Based on our service discussion, what ADSP frontend integrations would be most useful?`);
137
137
  }
138
- if (projectType === 'mern' || projectType === 'mean') {
139
- const frontendStack = projectType === 'mern'
138
+ if (projectType === 'mern' || projectType === 'mean' || projectType === 'pern' || projectType === 'pean' || projectType === 'pevn' || projectType === 'mevn') {
139
+ const frontendStack = projectType === 'mern' || projectType === 'pern'
140
140
  ? 'React frontend using Redux Toolkit slices and keycloak-js'
141
- : 'Angular frontend using standalone components and keycloak-angular';
141
+ : projectType === 'mean' || projectType === 'pean'
142
+ ? 'Angular frontend using standalone components and keycloak-angular'
143
+ : 'Vue 3 frontend using Pinia and @dsb-norge/vue-keycloak-js';
142
144
  const serviceFiles = Object.keys(projectContext.existingFiles)
143
145
  .filter((f) => f.startsWith('service/'))
144
146
  .join(', ');