@abgov/nx-adsp 13.6.1 → 13.7.1
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/README.md +26 -3
- package/package.json +1 -1
- package/src/generators/express-service/express-service.js +10 -10
- package/src/generators/express-service/express-service.js.map +1 -1
- package/src/generators/express-service/express-service.spec.ts +58 -0
- package/src/generators/express-service/files/src/environment.ts__tmpl__ +3 -3
- package/src/generators/express-service/files-mongo/.env.example__tmpl__ +8 -4
- package/src/generators/express-service/files-mongo/scripts/dev-db.sh__tmpl__ +8 -3
- package/src/generators/express-service/files-postgres/.env.example__tmpl__ +8 -4
- package/src/generators/express-service/files-postgres/scripts/dev-db.sh__tmpl__ +8 -3
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +8 -2
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +12 -12
- package/src/generators/vue-app/files/src/components/AppLayout.vue__tmpl__ +45 -0
- package/src/generators/vue-app/vue-app.spec.ts +18 -0
package/README.md
CHANGED
|
@@ -55,8 +55,9 @@ authenticated as an account with **`write:packages`** on your registry org.
|
|
|
55
55
|
> option (name, `--env`, `--sandboxProject`, etc.) supplied — otherwise Nx prompts
|
|
56
56
|
> and your session hangs waiting for input. Setting `CI=true` in the env has the
|
|
57
57
|
> same effect and also skips the Nx Cloud prompt. (`nx run <target>` executors
|
|
58
|
-
> don't prompt — this only applies to `nx g` generators.)
|
|
59
|
-
>
|
|
58
|
+
> don't prompt — this only applies to `nx g` generators.) Also pass `--skipAgent`
|
|
59
|
+
> — see [Agent consultation](#agent-consultation) for why you want to, not just
|
|
60
|
+
> how. The commands below already follow this.
|
|
60
61
|
|
|
61
62
|
```bash
|
|
62
63
|
# 1. Empty folder → Nx workspace (the plugins require Nx 23)
|
|
@@ -71,7 +72,7 @@ npm i -D @abgov/nx-oc @abgov/nx-adsp "@nx/express@$NXV" "@nx/vue@$NXV" "@nx/node
|
|
|
71
72
|
npx @abgov/adsp-cli login --env test --tenant "<Your Tenant>" --scope adsp-cli-admin
|
|
72
73
|
|
|
73
74
|
# 4. Scaffold a Postgres + Express + Vue + Node solution
|
|
74
|
-
npx nx g @abgov/nx-adsp:pevn acme --env=dev --tenant=my-tenant --no-interactive
|
|
75
|
+
npx nx g @abgov/nx-adsp:pevn acme --env=dev --tenant=my-tenant --no-interactive --skipAgent
|
|
75
76
|
|
|
76
77
|
# 5. Add sandbox targets (registry derives from the git remote, or pass --registry=ghcr.io/<org>;
|
|
77
78
|
# the database is auto-detected from the service — no --database needed)
|
|
@@ -225,6 +226,28 @@ Generator flags that steer which tenant/token is used:
|
|
|
225
226
|
|
|
226
227
|
With none of these, the generator lets `adsp login`'s interactive picker resolve the tenant.
|
|
227
228
|
|
|
229
|
+
## Agent consultation
|
|
230
|
+
|
|
231
|
+
`express-service`, `react-app`, `angular-app`, `vue-app`, and the fullstack composites connect to
|
|
232
|
+
ADSP's own `agent-service` after base scaffolding and hold an interactive, multi-turn conversation
|
|
233
|
+
with an ADSP-aware agent that can read and modify key integration files (`main.ts`,
|
|
234
|
+
`environment.ts`, `events.ts`, `database.ts`) based on a description you provide. The socket
|
|
235
|
+
connection and file upload start immediately, in parallel with prompting for that description.
|
|
236
|
+
|
|
237
|
+
This needs the same tenant/token as [Authentication](#authentication) above, plus a reachable
|
|
238
|
+
`agent-service` — it falls back silently to base scaffolding if either is missing, or if the run
|
|
239
|
+
is non-interactive (`--no-interactive`, no TTY, or `CI=true`). Skip it explicitly with
|
|
240
|
+
`--skipAgent`, regardless of interactivity.
|
|
241
|
+
|
|
242
|
+
> **Note for coding agents:** skip this. It exists to let a *human* describe what they want built
|
|
243
|
+
> to a separate, remote agent working from nothing but that description. You already have the
|
|
244
|
+
> actual requirements and the surrounding codebase context that conversation exists to gather —
|
|
245
|
+
> make any needed customizations directly, after scaffolding, rather than through an indirect
|
|
246
|
+
> round-trip to a second, less-informed agent. Pass `--skipAgent` explicitly rather than relying
|
|
247
|
+
> on `--no-interactive` alone: `--skipAgent` is a plain option check at the generator level, while
|
|
248
|
+
> `--no-interactive`'s detection is built on argv inspection this plugin's own code notes could go
|
|
249
|
+
> stale on an Nx upgrade — belt and suspenders, not a real behavior difference today.
|
|
250
|
+
|
|
228
251
|
## nx-oc integration
|
|
229
252
|
|
|
230
253
|
If `@abgov/nx-oc` is installed in the workspace, the quickstart generators (`express-service`, `react-app`, `angular-app`, `dotnet-service`, `react-dotnet`) automatically include OpenShift deployment YAML in their output. See the [@abgov/nx-oc README](../nx-oc/README.md) for details.
|
package/package.json
CHANGED
|
@@ -135,17 +135,17 @@ function default_1(host, options) {
|
|
|
135
135
|
const accessToken = (_f = normalizedOptions.accessToken) !== null && _f !== void 0 ? _f : normalizedOptions.adsp.accessToken;
|
|
136
136
|
const clientSecret = yield (0, keycloak_admin_1.ensureServiceClient)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, clientId, accessToken);
|
|
137
137
|
if (clientSecret) {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// .env.local, not .env: CLIENT_SECRET is a generated, local-only value — the
|
|
139
|
+
// same tier `nx dev-db` already uses for DATABASE_URL/MONGODB_URI — and
|
|
140
|
+
// already unconditionally gitignored by create-nx-workspace's default
|
|
141
|
+
// .gitignore (.env.local, .env.*.local), so no gitignore management is
|
|
142
|
+
// needed here at all. .env itself is left alone: it holds non-secret,
|
|
143
|
+
// developer-owned config too (KEYCLOAK_ROOT_URL, DIRECTORY_URL, etc.) and
|
|
144
|
+
// shouldn't be blanket-gitignored.
|
|
145
|
+
const envLocalPath = `${normalizedOptions.projectRoot}/.env.local`;
|
|
146
|
+
const existing = host.exists(envLocalPath) ? host.read(envLocalPath).toString() : '';
|
|
140
147
|
if (!existing.includes('CLIENT_SECRET=')) {
|
|
141
|
-
host.write(
|
|
142
|
-
}
|
|
143
|
-
const gitignorePath = '.gitignore';
|
|
144
|
-
if (host.exists(gitignorePath)) {
|
|
145
|
-
const gitignoreContent = host.read(gitignorePath).toString();
|
|
146
|
-
if (!gitignoreContent.includes('.env')) {
|
|
147
|
-
host.write(gitignorePath, `${gitignoreContent.trimEnd()}\n${normalizedOptions.projectRoot}/.env\n`);
|
|
148
|
-
}
|
|
148
|
+
host.write(envLocalPath, `${existing ? existing.trimEnd() + '\n' : ''}CLIENT_SECRET=${clientSecret}\n`);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":";;AAsGA,4BAkOC;;AAxUD,wCAA0I;AAC1I,uCAUoB;AACpB,uCAAoC;AACpC,6BAA6B;AAC7B,6CAAiD;AACjD,+DAAiE;AACjE,+DAA4D;AAC5D,iDAA0I;AAG1I,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAe;;;QAEf,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;QAEzE,IAAI,IAA8C,CAAC;QAEnD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,uFAAuF;YACvF,0DAA0D;YAC1D,MAAM,GAAG,GAAG,oBAAY,CAAC,MAAA,OAAO,CAAC,GAAG,mCAAI,MAAM,CAAC,CAAC;YAChD,MAAM,gBAAgB,GAAG,CAAC,MAAM,IAAA,sBAAc,EAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC;YAE5G,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,2CAAa,OAAO,EAAC,CAAC;YACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAC9B,IAAI,GAAG,CAAC,wBAAwB,EAAE,gBAAgB,CAAC,CAAC,IAAI,EACxD,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CACrC,CAAC;YAEF,MAAM,UAAU,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,0CAAG,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,CAAC,MAAM,kBAAkB,GAAG,CAAC,mBAAmB,GAAG,CAAC,CAAC;YACzF,CAAC;YAED,MAAM,WAAW,GAAG,MAAA,OAAO,CAAC,WAAW,mCAAI,UAAU,CAAC,KAAK,CAAC;YAE5D,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;gBACzB,OAAO,mCACF,OAAO,KACV,WAAW,EAAE,MAAM,IAAA,uBAAe,EAAC;wBACjC,GAAG,EAAE,MAAA,OAAO,CAAC,GAAG,mCAAI,MAAM;wBAC1B,KAAK,EAAE,WAAW;wBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,MAAM,EAAE,CAAC,wBAAgB,CAAC;qBAC3B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAC1B,CAAC;YACJ,CAAC;YAED,IAAI,GAAG;gBACL,MAAM,EAAE,UAAU,CAAC,IAAI;gBACvB,WAAW;gBACX,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;aAC7C,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QAED,uCACK,OAAO,KACV,WAAW;YACX,WAAW;YACX,IAAI,EACJ,QAAQ,EAAE,MAAA,OAAO,CAAC,QAAQ,mCAAI,MAAM,IACpC;IACJ,CAAC;CAAA;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;;IACrD,MAAM,eAAe,iDAChB,OAAO,GACP,OAAO,CAAC,IAAI,KACf,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,IAAI,EAC5C,IAAI,EAAE,EAAE,GACT,CAAC;IACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,WAAW,EACnB,eAAe,CAChB,CAAC;IACF,IAAI,OAAO,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACpE,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,OAAO,CAAC,QAAQ,EAAE,CAAC,EACjD,OAAO,CAAC,WAAW,EACnB,eAAe,CAChB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;;QACxD,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEhE,MAAM,EAAE,oBAAoB,EAAE,WAAW,EAAE,GAAG,MAAM,qCAAO,aAAa,GAAE,KAAK,CAC7E,GAAG,EAAE;YACH,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;QACJ,CAAC,CACF,CAAC;QACF,MAAM,WAAW,CAAC,IAAI,kCACjB,OAAO,KACV,UAAU,EAAE,IAAI,EAChB,eAAe,EAAE,KAAK,EACtB,MAAM,EAAE,eAAM,CAAC,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,KAAK,EACT,SAAS,EAAE,iBAAiB,CAAC,WAAW,IACxC,CAAC;QAEH,IAAA,qCAA4B,EAC1B,IAAI,gCAEF,yBAAyB,EAAE,SAAS,EACpC,WAAW,EAAE,QAAQ,EACrB,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,QAAQ,EAClB,oBAAoB,EAAE,QAAQ,EAC9B,GAAG,EAAE,QAAQ,IACV,CAAC,iBAAiB,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAC9F,CAAC,iBAAiB,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,iCAGzE,oBAAoB,EAAE,QAAQ,EAC9B,aAAa,EAAE,SAAS,EACxB,iBAAiB,EAAE,SAAS,EAC5B,2BAA2B,EAAE,QAAQ,EACrC,SAAS,EAAE,QAAQ,EACnB,kBAAkB,EAAE,QAAQ,IACzB,CAAC,iBAAiB,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC1G,wBAAwB,EAAE,QAAQ,EAClC,0BAA0B,EAAE,QAAQ,EACpC,oBAAoB,EAAE,SAAS,IAElC,CAAC;QAEF,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAElC,IAAA,+BAAqB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;QAC7F,IAAA,+BAAqB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3D,IAAA,2BAAiB,EAAC,IAAI,CAAC,CAAC;QACxB,2EAA2E;QAC3E,IAAA,0BAAgB,EAAC,IAAI,CAAC,CAAC;QAEvB,MAAM,aAAa,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACpF,MAAM,OAAO,qBAAQ,aAAa,CAAC,OAAO,CAAE,CAAC;QAE7C,IAAI,iBAAiB,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAE1C,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAClB,QAAQ,EAAE,iBAAiB;gBAC3B,OAAO,EAAE;oBACP,OAAO,EAAE,wBAAwB;oBACjC,GAAG,EAAE,eAAe;iBACrB;aACF,CAAC;YAEF,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrB,OAAO,CAAC,OAAO,CAAC,mCACX,OAAO,CAAC,OAAO,CAAC,KACnB,SAAS,EAAE,CAAC,GAAG,CAAC,MAAA,OAAO,CAAC,OAAO,CAAC,CAAC,SAAS,mCAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,GAC7D,CAAC;YACJ,CAAC;YAED,IAAI,iBAAiB,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAC9C,OAAO,CAAC,aAAa,CAAC,GAAG;oBACvB,QAAQ,EAAE,iBAAiB;oBAC3B,OAAO,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE,GAAG,EAAE,eAAe,EAAE;iBACvE,CAAC;gBACF,OAAO,CAAC,YAAY,CAAC,GAAG;oBACtB,QAAQ,EAAE,iBAAiB;oBAC3B,OAAO,EAAE,EAAE,OAAO,EAAE,yBAAyB,EAAE,GAAG,EAAE,eAAe,EAAE;iBACtE,CAAC;gBACF,OAAO,CAAC,mBAAmB,CAAC,GAAG;oBAC7B,QAAQ,EAAE,iBAAiB;oBAC3B,OAAO,EAAE,EAAE,OAAO,EAAE,yBAAyB,EAAE,GAAG,EAAE,eAAe,EAAE;iBACtE,CAAC;gBACF,OAAO,CAAC,WAAW,CAAC,GAAG;oBACrB,QAAQ,EAAE,iBAAiB;oBAC3B,OAAO,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,GAAG,EAAE,eAAe,EAAE;iBACrE,CAAC;gBAEF,6EAA6E;gBAC7E,2EAA2E;gBAC3E,wEAAwE;gBACxE,IAAI,MAAA,OAAO,CAAC,OAAO,CAAC,0CAAE,OAAO,EAAE,CAAC;oBAC9B,OAAO,CAAC,OAAO,CAAC,mCACX,OAAO,CAAC,OAAO,CAAC,KACnB,OAAO,kCACF,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,KAC3B,MAAM,EAAE;gCACN,GAAG,CAAC,MAAA,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,mCAAI,EAAE,CAAC;gCAC1C;oCACE,KAAK,EAAE,GAAG,iBAAiB,CAAC,WAAW,UAAU;oCACjD,IAAI,EAAE,MAAM;oCACZ,MAAM,EAAE,SAAS;iCAClB;6BACF,MAEJ,CAAC;gBACJ,CAAC;YACH,CAAC;QAEH,CAAC;QAED,0EAA0E;QAC1E,4EAA4E;QAC5E,MAAM,KAAK,GAAG,iBAAiB,iBAAiB,CAAC,QAAQ,EAAE,CAAC;QAC5D,MAAM,IAAI,GACR,iBAAiB,CAAC,QAAQ,KAAK,MAAM;YACrC,CAAC,CAAC,MAAA,aAAa,CAAC,IAAI,mCAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YACzC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAA,aAAa,CAAC,IAAI,mCAAI,EAAE,CAAC,EAAE,KAAK,CAAC;YACxC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC;QAEzB,IAAA,mCAA0B,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,kCACzD,aAAa,KAChB,OAAO;YACP,IAAI,IACJ,CAAC;QAEH,IAAA,0BAAgB,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACtD,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,IAAI,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,WAAW,iBAAiB,CAAC,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC,WAAW,EAAE,CAAC;YAC7F,MAAM,WAAW,GAAG,MAAA,iBAAiB,CAAC,WAAW,mCAAI,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC;YACxF,MAAM,YAAY,GAAG,MAAM,IAAA,oCAAmB,EAC5C,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAClC,QAAQ,EACR,WAAW,CACZ,CAAC;YACF,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,
|
|
1
|
+
{"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
|
3
3
|
|
|
4
4
|
import * as utils from '@abgov/nx-oc';
|
|
5
5
|
import { environments } from '@abgov/nx-oc';
|
|
6
|
+
import * as keycloakAdmin from '../../utils/keycloak-admin';
|
|
6
7
|
import { Schema } from './schema';
|
|
7
8
|
import generator from './express-service';
|
|
8
9
|
|
|
@@ -17,6 +18,9 @@ utilsMock.getAdspConfiguration.mockResolvedValue({
|
|
|
17
18
|
utilsMock.deploymentGenerator.mockResolvedValue(undefined);
|
|
18
19
|
utilsMock.ensureAdspToken.mockResolvedValue('test-token');
|
|
19
20
|
|
|
21
|
+
jest.mock('../../utils/keycloak-admin');
|
|
22
|
+
const keycloakAdminMock = keycloakAdmin as jest.Mocked<typeof keycloakAdmin>;
|
|
23
|
+
|
|
20
24
|
describe('Express Service Generator', () => {
|
|
21
25
|
const options: Schema = {
|
|
22
26
|
name: 'test',
|
|
@@ -168,4 +172,58 @@ describe('Express Service Generator', () => {
|
|
|
168
172
|
false
|
|
169
173
|
);
|
|
170
174
|
}, 60000);
|
|
175
|
+
|
|
176
|
+
it('writes a provisioned CLIENT_SECRET to .env.local, not .env', async () => {
|
|
177
|
+
keycloakAdminMock.ensureServiceClient.mockResolvedValueOnce('super-secret');
|
|
178
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
179
|
+
await generator(host, { ...options, accessToken: 'test-token' });
|
|
180
|
+
|
|
181
|
+
expect(host.read('apps/test/.env.local').toString()).toContain('CLIENT_SECRET=super-secret');
|
|
182
|
+
expect(host.exists('apps/test/.env')).toBeFalsy();
|
|
183
|
+
}, 60000);
|
|
184
|
+
|
|
185
|
+
it('does not modify .gitignore for the provisioned secret', async () => {
|
|
186
|
+
keycloakAdminMock.ensureServiceClient.mockResolvedValueOnce('super-secret');
|
|
187
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
188
|
+
const gitignoreBefore = host.read('.gitignore')?.toString() ?? '';
|
|
189
|
+
await generator(host, { ...options, accessToken: 'test-token' });
|
|
190
|
+
|
|
191
|
+
expect(host.read('.gitignore')?.toString() ?? '').toBe(gitignoreBefore);
|
|
192
|
+
}, 60000);
|
|
193
|
+
|
|
194
|
+
it('does not overwrite or duplicate an existing CLIENT_SECRET in .env.local', async () => {
|
|
195
|
+
keycloakAdminMock.ensureServiceClient.mockResolvedValueOnce('freshly-provisioned-secret');
|
|
196
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
197
|
+
// express-service is a one-shot scaffolder (re-running it against an
|
|
198
|
+
// existing project throws in @nx/express), so this exercises the guard
|
|
199
|
+
// directly rather than by calling the generator twice: a CLIENT_SECRET
|
|
200
|
+
// already present in .env.local (e.g. set by hand) must survive untouched.
|
|
201
|
+
host.write('apps/test/.env.local', 'CLIENT_SECRET=already-there\n');
|
|
202
|
+
await generator(host, { ...options, accessToken: 'test-token' });
|
|
203
|
+
|
|
204
|
+
const envLocal = host.read('apps/test/.env.local').toString();
|
|
205
|
+
expect(envLocal).toContain('CLIENT_SECRET=already-there');
|
|
206
|
+
expect(envLocal).not.toContain('freshly-provisioned-secret');
|
|
207
|
+
expect(envLocal.split('CLIENT_SECRET=').length - 1).toBe(1);
|
|
208
|
+
}, 60000);
|
|
209
|
+
|
|
210
|
+
it('preserves an existing .env.local value (e.g. a prior dev-db run) alongside CLIENT_SECRET', async () => {
|
|
211
|
+
keycloakAdminMock.ensureServiceClient.mockResolvedValueOnce('super-secret');
|
|
212
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
213
|
+
host.write('apps/test/.env.local', 'DATABASE_URL=postgresql://test:test@localhost:5432/test_dev\n');
|
|
214
|
+
await generator(host, { ...options, accessToken: 'test-token' });
|
|
215
|
+
|
|
216
|
+
const envLocal = host.read('apps/test/.env.local').toString();
|
|
217
|
+
expect(envLocal).toContain('DATABASE_URL=postgresql://test:test@localhost:5432/test_dev');
|
|
218
|
+
expect(envLocal).toContain('CLIENT_SECRET=super-secret');
|
|
219
|
+
}, 60000);
|
|
220
|
+
|
|
221
|
+
it('writes nothing when no secret is provisioned', async () => {
|
|
222
|
+
keycloakAdminMock.ensureServiceClient.mockResolvedValueOnce(null);
|
|
223
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
224
|
+
await generator(host, { ...options, accessToken: 'test-token' });
|
|
225
|
+
|
|
226
|
+
expect(host.exists('apps/test/.env.local')).toBeFalsy();
|
|
227
|
+
expect(host.exists('apps/test/.env')).toBeFalsy();
|
|
228
|
+
}, 60000);
|
|
171
229
|
});
|
|
@@ -5,13 +5,13 @@ import { resolve } from 'path';
|
|
|
5
5
|
config({
|
|
6
6
|
path: resolve(process.cwd(), '<%= projectRoot %>/.env'),
|
|
7
7
|
});
|
|
8
|
-
|
|
9
|
-
//
|
|
8
|
+
// .env.local holds generated, local-only values: CLIENT_SECRET (written once by
|
|
9
|
+
// this generator after Keycloak provisioning) and, when a database is
|
|
10
|
+
// configured, the connection string written by 'nx dev-db'.
|
|
10
11
|
config({
|
|
11
12
|
path: resolve(process.cwd(), '<%= projectRoot %>/.env.local'),
|
|
12
13
|
override: true,
|
|
13
14
|
});
|
|
14
|
-
<% } %>
|
|
15
15
|
|
|
16
16
|
export const environment = cleanEnv(process.env, {
|
|
17
17
|
KEYCLOAK_ROOT_URL: str({ default: '<%= accessServiceUrl %>' }),
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Every value below already has a matching default in src/environment.ts — you
|
|
2
|
+
# only need to copy this to .env if you want to override one (e.g. point at a
|
|
3
|
+
# different environment temporarily).
|
|
4
|
+
#
|
|
5
|
+
# CLIENT_SECRET is written automatically to .env.local after Keycloak
|
|
6
|
+
# provisioning (or set it there yourself, as CLIENT_SECRET=<value>, for manual
|
|
7
|
+
# setup). MONGODB_URI is written automatically to .env.local by
|
|
8
|
+
# 'nx dev-db <%= projectName %>'. Neither belongs here.
|
|
3
9
|
KEYCLOAK_ROOT_URL=<%= accessServiceUrl %>
|
|
4
10
|
DIRECTORY_URL=<%= directoryServiceUrl %>
|
|
5
11
|
TENANT_REALM=<%= tenantRealm %>
|
|
6
12
|
CLIENT_ID=urn:ads:<%= tenant %>:<%= projectName %>
|
|
7
|
-
CLIENT_SECRET=
|
|
8
|
-
MONGODB_URI=mongodb://localhost:27017/<%= projectName %>_dev
|
|
@@ -32,7 +32,12 @@ until podman exec "${CONTAINER}" mongosh --quiet --eval "quit()" &>/dev/null; do
|
|
|
32
32
|
done
|
|
33
33
|
echo "MongoDB is ready."
|
|
34
34
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
|
|
35
|
+
# Merge MONGODB_URI into .env.local rather than overwriting it wholesale —
|
|
36
|
+
# CLIENT_SECRET (written once by the generator after Keycloak provisioning) may
|
|
37
|
+
# already be there. Overridden by the Secret in OpenShift at runtime.
|
|
38
|
+
if [ -f .env.local ]; then
|
|
39
|
+
grep -v '^MONGODB_URI=' .env.local > .env.local.tmp || true
|
|
40
|
+
mv .env.local.tmp .env.local
|
|
41
|
+
fi
|
|
42
|
+
echo "MONGODB_URI=mongodb://localhost:${PORT}/${DB_NAME}" >> .env.local
|
|
38
43
|
echo "MONGODB_URI written to .env.local"
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Every value below already has a matching default in src/environment.ts — you
|
|
2
|
+
# only need to copy this to .env if you want to override one (e.g. point at a
|
|
3
|
+
# different environment temporarily).
|
|
4
|
+
#
|
|
5
|
+
# CLIENT_SECRET is written automatically to .env.local after Keycloak
|
|
6
|
+
# provisioning (or set it there yourself, as CLIENT_SECRET=<value>, for manual
|
|
7
|
+
# setup). DATABASE_URL is written automatically to .env.local by
|
|
8
|
+
# 'nx dev-db <%= projectName %>'. Neither belongs here.
|
|
3
9
|
KEYCLOAK_ROOT_URL=<%= accessServiceUrl %>
|
|
4
10
|
DIRECTORY_URL=<%= directoryServiceUrl %>
|
|
5
11
|
TENANT_REALM=<%= tenantRealm %>
|
|
6
12
|
CLIENT_ID=urn:ads:<%= tenant %>:<%= projectName %>
|
|
7
|
-
CLIENT_SECRET=
|
|
8
|
-
DATABASE_URL=postgresql://<%= projectName %>:<%= projectName %>@localhost:5432/<%= projectName %>_dev
|
|
@@ -37,7 +37,12 @@ until podman exec "${CONTAINER}" pg_isready -U "${DB_USER}" -d "${DB_NAME}" &>/d
|
|
|
37
37
|
done
|
|
38
38
|
echo "Postgres is ready."
|
|
39
39
|
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
40
|
+
# Merge DATABASE_URL into .env.local rather than overwriting it wholesale —
|
|
41
|
+
# CLIENT_SECRET (written once by the generator after Keycloak provisioning) may
|
|
42
|
+
# already be there. Overridden by the SECRET in OpenShift at runtime.
|
|
43
|
+
if [ -f .env.local ]; then
|
|
44
|
+
grep -v '^DATABASE_URL=' .env.local > .env.local.tmp || true
|
|
45
|
+
mv .env.local.tmp .env.local
|
|
46
|
+
fi
|
|
47
|
+
echo "DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@localhost:${PORT}/${DB_NAME}" >> .env.local
|
|
43
48
|
echo "DATABASE_URL written to .env.local"
|
|
@@ -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
|
-
<
|
|
41
|
+
<AppLayout :variant="layout">
|
|
33
42
|
<RouterView />
|
|
34
|
-
</
|
|
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
|
|