@abgov/nx-adsp 13.2.0 → 13.3.0-beta.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 +34 -13
- package/package.json +1 -1
- package/src/generators/angular-app/angular-app.js +8 -4
- package/src/generators/angular-app/angular-app.js.map +1 -1
- package/src/generators/angular-app/angular-app.spec.ts +8 -0
- package/src/generators/angular-app/files/AGENTS.md__tmpl__ +14 -2
- package/src/generators/express-service/express-service.js +13 -14
- package/src/generators/express-service/express-service.js.map +1 -1
- package/src/generators/express-service/express-service.spec.ts +19 -6
- package/src/generators/express-service/files/AGENTS.md__tmpl__ +5 -3
- package/src/generators/express-service/files/src/main.ts__tmpl__ +11 -53
- package/src/generators/express-service/files/src/routes/example.spec.ts__tmpl__ +26 -0
- package/src/generators/express-service/files/src/routes/example.ts__tmpl__ +59 -0
- package/src/generators/mean/mean.js +14 -16
- package/src/generators/mean/mean.js.map +1 -1
- package/src/generators/mean/mean.spec.ts +1 -1
- package/src/generators/mern/mern.js +14 -16
- package/src/generators/mern/mern.js.map +1 -1
- package/src/generators/mern/mern.spec.ts +1 -1
- package/src/generators/mevn/mevn.js +12 -14
- package/src/generators/mevn/mevn.js.map +1 -1
- package/src/generators/mevn/mevn.spec.ts +1 -1
- package/src/generators/pean/pean.js +14 -16
- package/src/generators/pean/pean.js.map +1 -1
- package/src/generators/pean/pean.spec.ts +1 -1
- package/src/generators/pern/pern.js +14 -16
- package/src/generators/pern/pern.js.map +1 -1
- package/src/generators/pern/pern.spec.ts +1 -1
- package/src/generators/pevn/pevn.js +12 -14
- package/src/generators/pevn/pevn.js.map +1 -1
- package/src/generators/pevn/pevn.spec.ts +1 -1
- package/src/generators/react-app/files/AGENTS.md__tmpl__ +9 -4
- package/src/generators/react-app/react-app.js +3 -3
- package/src/generators/react-app/react-app.js.map +1 -1
- package/src/generators/react-app/react-app.spec.ts +9 -0
- package/src/generators/react-form/react-form.js +4 -8
- package/src/generators/react-form/react-form.js.map +1 -1
- package/src/generators/react-form/react-forms.spec.ts +1 -2
- package/src/generators/react-task-list/react-task-list.js +4 -8
- package/src/generators/react-task-list/react-task-list.js.map +1 -1
- package/src/generators/react-task-list/react-task-list.spec.ts +1 -2
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +8 -6
- package/src/generators/vue-app/vue-app.js +2 -2
- package/src/generators/vue-app/vue-app.js.map +1 -1
- package/src/generators/vue-app/vue-app.spec.ts +8 -0
package/README.md
CHANGED
|
@@ -67,21 +67,24 @@ cd my-solution
|
|
|
67
67
|
NXV=$(node -p "require('./node_modules/nx/package.json').version")
|
|
68
68
|
npm i -D @abgov/nx-oc @abgov/nx-adsp "@nx/express@$NXV" "@nx/vue@$NXV" "@nx/node@$NXV" "@nx/js@$NXV" "@nx/eslint@$NXV"
|
|
69
69
|
|
|
70
|
-
# 3.
|
|
70
|
+
# 3. Sign in to ADSP once (opens a browser; token is cached for later generator runs)
|
|
71
|
+
npx @abgov/adsp-cli login --env test --tenant "<Your Tenant>" --scope adsp-cli-admin
|
|
72
|
+
|
|
73
|
+
# 4. Scaffold a Postgres + Express + Vue + Node solution
|
|
71
74
|
npx nx g @abgov/nx-adsp:pevn acme --env=dev --tenant=my-tenant --no-interactive
|
|
72
75
|
|
|
73
|
-
#
|
|
76
|
+
# 5. Add sandbox targets (registry derives from the git remote, or pass --registry=ghcr.io/<org>;
|
|
74
77
|
# the database is auto-detected from the service — no --database needed)
|
|
75
78
|
npx nx g @abgov/nx-oc:sandbox acme-service --sandboxProject=<namespace> --registry=ghcr.io/<org> --tenant=my-tenant --env=dev --no-interactive
|
|
76
79
|
npx nx g @abgov/nx-oc:sandbox acme-app --sandboxProject=<namespace> --tenant=my-tenant --env=dev --no-interactive
|
|
77
80
|
|
|
78
|
-
#
|
|
81
|
+
# 6. Deploy — backend first so the frontend's /api proxy resolves
|
|
79
82
|
npx nx run acme-service:sandbox
|
|
80
83
|
npx nx run acme-app:sandbox # or: --deployBackend to bring the backend up in the same run
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
Each generated app has an `AGENTS.md` (framework + ADSP context) and, once
|
|
84
|
-
step
|
|
87
|
+
step 5 runs, a `.openshift/<app>/SANDBOX.md` deploy/troubleshooting runbook.
|
|
85
88
|
See [`@abgov/nx-oc`](https://www.npmjs.com/package/@abgov/nx-oc) for the sandbox
|
|
86
89
|
deploy details.
|
|
87
90
|
|
|
@@ -99,7 +102,7 @@ npx nx g @abgov/nx-adsp:express-service my-service --env dev --tenant my-tenant
|
|
|
99
102
|
|--------|-------|----------|-------------|
|
|
100
103
|
| `name` | — | Yes | Name of the service |
|
|
101
104
|
| `env` | `-e` | No | ADSP environment / access service. Defaults to `test` = access-uat.alberta.ca (UAT — use for dev and pre-prod); `prod` = access.alberta.ca; `dev` = ADSP platform dev (restricted) |
|
|
102
|
-
| `tenant` | `-t` | No | ADSP tenant name;
|
|
105
|
+
| `tenant` | `-t` | No | ADSP tenant name; resolves the Keycloak realm by name (sign in once with `@abgov/adsp-cli` — see Authentication) |
|
|
103
106
|
| `tenantRealm` | `-tr` | No | Keycloak realm UUID; overrides the realm resolved from `--tenant` |
|
|
104
107
|
| `accessToken` | `-at` | No | Access token for non-interactive retrieval of ADSP configuration |
|
|
105
108
|
|
|
@@ -117,7 +120,7 @@ npx nx g @abgov/nx-adsp:react-app my-app --env dev --tenant my-tenant
|
|
|
117
120
|
|--------|-------|----------|-------------|
|
|
118
121
|
| `name` | — | Yes | Name of the application |
|
|
119
122
|
| `env` | `-e` | No | ADSP environment / access service. Defaults to `test` = access-uat.alberta.ca (UAT — use for dev and pre-prod); `prod` = access.alberta.ca; `dev` = ADSP platform dev (restricted) |
|
|
120
|
-
| `tenant` | `-t` | No | ADSP tenant name;
|
|
123
|
+
| `tenant` | `-t` | No | ADSP tenant name; resolves the Keycloak realm by name (sign in once with `@abgov/adsp-cli` — see Authentication) |
|
|
121
124
|
| `tenantRealm` | `-tr` | No | Keycloak realm UUID; overrides the realm resolved from `--tenant` |
|
|
122
125
|
| `accessToken` | `-at` | No | Access token for non-interactive retrieval of ADSP configuration |
|
|
123
126
|
| `proxy` | — | No | Nginx proxy rule(s) as `{ location, proxyPass }` or an array of such objects |
|
|
@@ -194,15 +197,33 @@ Accepts the same options as `react-form`.
|
|
|
194
197
|
|
|
195
198
|
## Authentication
|
|
196
199
|
|
|
197
|
-
Most generators call ADSP APIs
|
|
200
|
+
Most generators call ADSP APIs during generation, which needs an ADSP access
|
|
201
|
+
token. Sign-in is delegated to **[`@abgov/adsp-cli`](https://www.npmjs.com/package/@abgov/adsp-cli)** —
|
|
202
|
+
log in once (interactive browser) and its cached token is reused across generator
|
|
203
|
+
runs. There's no separate login built into this plugin.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Sign in once. --scope adsp-cli-admin grants the Keycloak-admin capability some
|
|
207
|
+
# generators use to provision a service's OAuth client (safe to request without it).
|
|
208
|
+
npx @abgov/adsp-cli login --env <dev|test|prod> --tenant "<Your Tenant>" --scope adsp-cli-admin
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
When you run a generator:
|
|
212
|
+
|
|
213
|
+
- if a valid cached token exists, generation proceeds with no prompt;
|
|
214
|
+
- if not, an **interactive** run launches `adsp login` for you (browser); a
|
|
215
|
+
**non-interactive** run (`--no-interactive` / CI) fails with the exact
|
|
216
|
+
`adsp login` command to run first.
|
|
217
|
+
|
|
218
|
+
Generator flags that steer which tenant/token is used:
|
|
198
219
|
|
|
199
|
-
|
|
|
200
|
-
|
|
201
|
-
| `--tenant <name>` |
|
|
202
|
-
| `--tenantRealm <uuid>` | Use
|
|
203
|
-
| `--accessToken <token>` |
|
|
220
|
+
| Flag | Effect |
|
|
221
|
+
|------|--------|
|
|
222
|
+
| `--tenant <name>` | Resolves the Keycloak realm by tenant name (anonymous lookup) and targets it |
|
|
223
|
+
| `--tenantRealm <uuid>` | Use the realm UUID directly; combine with `--tenant` to override the resolved realm |
|
|
224
|
+
| `--accessToken <token>` | Supply a pre-obtained token directly (CI/CD), bypassing the CLI |
|
|
204
225
|
|
|
205
|
-
|
|
226
|
+
With none of these, the generator lets `adsp login`'s interactive picker resolve the tenant.
|
|
206
227
|
|
|
207
228
|
## nx-oc integration
|
|
208
229
|
|
package/package.json
CHANGED
|
@@ -68,10 +68,14 @@ function default_1(host, options) {
|
|
|
68
68
|
skipFormat: true,
|
|
69
69
|
});
|
|
70
70
|
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
71
|
-
'@abgov/angular-components': '5.
|
|
72
|
-
'@abgov/design-tokens': '
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
'@abgov/angular-components': '5.3.0',
|
|
72
|
+
'@abgov/design-tokens': '2.9.0',
|
|
73
|
+
// Pin exact (not ^2.0.0): angular-components 5.3.0 imports symbols added in
|
|
74
|
+
// ui-components-common 2.3.0 (e.g. GoabWorkspaceLayoutScrollState), so a
|
|
75
|
+
// lower 2.x resolves and fails the build. Keep this in lockstep with the
|
|
76
|
+
// angular-components version above.
|
|
77
|
+
'@abgov/ui-components-common': '2.3.0',
|
|
78
|
+
'@abgov/web-components': '2.3.0',
|
|
75
79
|
'keycloak-angular': '^19.0.2',
|
|
76
80
|
'keycloak-js': '^23.0.7',
|
|
77
81
|
'zone.js': '~0.15.0',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":";;AAmGA,
|
|
1
|
+
{"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,14 @@ describe('angular app generator', () => {
|
|
|
39
39
|
expect(appTree.exists('apps/test/nginx.conf')).toBeTruthy();
|
|
40
40
|
}, 120000);
|
|
41
41
|
|
|
42
|
+
it('AGENTS.md points at the current design system docs', async () => {
|
|
43
|
+
await angularApp(appTree, options);
|
|
44
|
+
const agents = appTree.read('apps/test/AGENTS.md').toString();
|
|
45
|
+
expect(agents).toContain('design.alberta.ca/components');
|
|
46
|
+
// guard against the retired ui-components.alberta.ca URL creeping back in
|
|
47
|
+
expect(agents).not.toContain('ui-components.alberta.ca');
|
|
48
|
+
}, 120000);
|
|
49
|
+
|
|
42
50
|
it('can add nginx proxy', async () => {
|
|
43
51
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
44
52
|
await angularApp(host, {
|
|
@@ -62,8 +62,12 @@ const authGuard = createAuthGuard(async (_route, _state, { authenticated, keyclo
|
|
|
62
62
|
|
|
63
63
|
## GoA design system
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
**Find a component:** browse the gallery at
|
|
66
|
+
[design.alberta.ca/components](https://design.alberta.ca/components/) — press ⌘K to
|
|
67
|
+
search components and examples. Each component's page documents its properties,
|
|
68
|
+
events, and usage; treat it as the source of truth. Angular uses the `Goab*`
|
|
69
|
+
wrappers from `@abgov/angular-components`, imported into each standalone component's
|
|
70
|
+
`imports` array:
|
|
67
71
|
|
|
68
72
|
```typescript
|
|
69
73
|
import { GoabButton, GoabAppHeader } from '@abgov/angular-components';
|
|
@@ -71,6 +75,14 @@ import { GoabButton, GoabAppHeader } from '@abgov/angular-components';
|
|
|
71
75
|
@Component({ imports: [GoabButton, GoabAppHeader], ... })
|
|
72
76
|
```
|
|
73
77
|
|
|
78
|
+
**Events**: the Angular wrappers expose events as outputs — bind with `(onClick)`,
|
|
79
|
+
`(onChange)`, etc. on the `goab-*` element:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<goab-button type="tertiary" (onClick)="login()">Sign in</goab-button>
|
|
83
|
+
<goab-dropdown (onChange)="handleSelect($event)"></goab-dropdown>
|
|
84
|
+
```
|
|
85
|
+
|
|
74
86
|
**Known limitation:** `GoabHeroBanner.backgroundUrl` does not bind reliably via
|
|
75
87
|
Angular's template binding due to an `@if(isReady)` timing issue inside the
|
|
76
88
|
component wrapper. Use `MutationObserver` to set the property directly after
|
|
@@ -12,13 +12,13 @@ const plugin_version_1 = require("../../utils/plugin-version");
|
|
|
12
12
|
const quality_1 = require("../../utils/quality");
|
|
13
13
|
function normalizeOptions(host, options) {
|
|
14
14
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
15
|
-
var _a, _b, _c, _d;
|
|
15
|
+
var _a, _b, _c, _d, _e;
|
|
16
16
|
const projectName = (0, devkit_1.names)(options.name).fileName;
|
|
17
17
|
const projectRoot = `${(0, devkit_1.getWorkspaceLayout)(host).appsDir}/${projectName}`;
|
|
18
18
|
let adsp;
|
|
19
19
|
if (options.tenant) {
|
|
20
20
|
// Resolve realm from tenant name via the public tenant service API (no auth required),
|
|
21
|
-
// then
|
|
21
|
+
// then obtain a token for that realm via @abgov/adsp-cli.
|
|
22
22
|
const env = nx_oc_1.environments[(_a = options.env) !== null && _a !== void 0 ? _a : 'prod'];
|
|
23
23
|
const tenantServiceUrl = (yield (0, nx_oc_1.getServiceUrls)(env.directoryServiceUrl))['urn:ads:platform:tenant-service'];
|
|
24
24
|
const { default: axios } = yield Promise.resolve().then(() => require('axios'));
|
|
@@ -29,7 +29,12 @@ function normalizeOptions(host, options) {
|
|
|
29
29
|
}
|
|
30
30
|
const tenantRealm = (_c = options.tenantRealm) !== null && _c !== void 0 ? _c : tenantInfo.realm;
|
|
31
31
|
if (!options.accessToken) {
|
|
32
|
-
options = Object.assign(Object.assign({}, options), { accessToken: yield (0, nx_oc_1.
|
|
32
|
+
options = Object.assign(Object.assign({}, options), { accessToken: yield (0, nx_oc_1.ensureAdspToken)({
|
|
33
|
+
env: (_d = options.env) !== null && _d !== void 0 ? _d : 'prod',
|
|
34
|
+
realm: tenantRealm,
|
|
35
|
+
tenant: options.tenant,
|
|
36
|
+
scopes: [nx_oc_1.ADSP_ADMIN_SCOPE],
|
|
37
|
+
}).catch(() => undefined) });
|
|
33
38
|
}
|
|
34
39
|
adsp = {
|
|
35
40
|
tenant: tenantInfo.name,
|
|
@@ -43,7 +48,7 @@ function normalizeOptions(host, options) {
|
|
|
43
48
|
}
|
|
44
49
|
return Object.assign(Object.assign({}, options), { projectName,
|
|
45
50
|
projectRoot,
|
|
46
|
-
adsp, database: (
|
|
51
|
+
adsp, database: (_e = options.database) !== null && _e !== void 0 ? _e : 'none' });
|
|
47
52
|
});
|
|
48
53
|
}
|
|
49
54
|
function addFiles(host, options) {
|
|
@@ -62,7 +67,7 @@ function default_1(host, options) {
|
|
|
62
67
|
throw new Error("The 'express-service' generator requires the '@nx/express' plugin. Install it and re-run:\n npm i -D @nx/express");
|
|
63
68
|
});
|
|
64
69
|
yield initExpress(host, Object.assign(Object.assign({}, options), { skipFormat: true, skipPackageJson: false, linter: eslint_1.Linter.EsLint, unitTestRunner: 'jest', js: false, directory: normalizedOptions.projectRoot }));
|
|
65
|
-
(0, devkit_1.addDependenciesToPackageJson)(host, Object.assign(Object.assign({ '@abgov/adsp-service-sdk': '^2.23.0', compression: '^1.8.1', cors: '^2.8.5', dotenv: '^16.4.7', envalid: '^8.0.0', helmet: '^8.0.0', passport: '^0.7.0', 'passport-anonymous': '^1.0.1', zod: '^3.0.0' }, (normalizedOptions.database === 'postgres' ? { 'drizzle-orm': '^0.44.0', pg: '^8.11.0' } : {})), (normalizedOptions.database === 'mongo' ? { mongoose: '^8.0.0' } : {})), Object.assign(Object.assign({ '@types/compression': '^1.7.5', '@types/cors': '^2.8.17', '@types/passport': '^1.0.16', '@types/passport-anonymous': '^1.0.3' }, (normalizedOptions.database === 'postgres' ? { 'drizzle-kit': '^0.31.0', '@types/pg': '^8.11.0' } : {})), { 'eslint-plugin-security': '^3.0.0', 'eslint-plugin-no-secrets': '^2.0.0', 'eslint-plugin-jest': '^28.0.0' }));
|
|
70
|
+
(0, devkit_1.addDependenciesToPackageJson)(host, Object.assign(Object.assign({ '@abgov/adsp-service-sdk': '^2.23.0', compression: '^1.8.1', cors: '^2.8.5', dotenv: '^16.4.7', envalid: '^8.0.0', helmet: '^8.0.0', passport: '^0.7.0', 'passport-anonymous': '^1.0.1', zod: '^3.0.0' }, (normalizedOptions.database === 'postgres' ? { 'drizzle-orm': '^0.44.0', pg: '^8.11.0' } : {})), (normalizedOptions.database === 'mongo' ? { mongoose: '^8.0.0' } : {})), Object.assign(Object.assign({ '@types/compression': '^1.7.5', '@types/cors': '^2.8.17', '@types/passport': '^1.0.16', '@types/passport-anonymous': '^1.0.3', supertest: '^7.0.0', '@types/supertest': '^6.0.0' }, (normalizedOptions.database === 'postgres' ? { 'drizzle-kit': '^0.31.0', '@types/pg': '^8.11.0' } : {})), { 'eslint-plugin-security': '^3.0.0', 'eslint-plugin-no-secrets': '^2.0.0', 'eslint-plugin-jest': '^28.0.0' }));
|
|
66
71
|
addFiles(host, normalizedOptions);
|
|
67
72
|
(0, quality_1.addEslintQualityRules)(host, normalizedOptions.projectRoot, ['**/*.spec.ts', '**/*.test.ts']);
|
|
68
73
|
(0, quality_1.addJestCoverageConfig)(host, normalizedOptions.projectRoot);
|
|
@@ -150,15 +155,9 @@ function default_1(host, options) {
|
|
|
150
155
|
// which are applied directly to the Nx Tree.
|
|
151
156
|
// Falls back silently if agent-service is unreachable or no accessToken.
|
|
152
157
|
if (normalizedOptions.adsp && !options.skipAgent) {
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
// full interactive flow was used and no token is available.
|
|
157
|
-
const accessToken = (_g = normalizedOptions.accessToken) !== null && _g !== void 0 ? _g : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
|
|
158
|
-
var _a;
|
|
159
|
-
process.stdout.write(`Agent sign-in failed (${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}) — skipping agent interaction.\n`);
|
|
160
|
-
return undefined;
|
|
161
|
-
}));
|
|
158
|
+
// Both normalizeOptions paths resolve a tenant-realm token via @abgov/adsp-cli,
|
|
159
|
+
// so it's already on normalizedOptions.accessToken (--tenant) or adsp.accessToken.
|
|
160
|
+
const accessToken = (_g = normalizedOptions.accessToken) !== null && _g !== void 0 ? _g : normalizedOptions.adsp.accessToken;
|
|
162
161
|
const mainTs = (_j = (_h = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _h === void 0 ? void 0 : _h.toString()) !== null && _j !== void 0 ? _j : '';
|
|
163
162
|
const environmentTs = (_l = (_k = host.read(`${normalizedOptions.projectRoot}/src/environment.ts`)) === null || _k === void 0 ? void 0 : _k.toString()) !== null && _l !== void 0 ? _l : '';
|
|
164
163
|
const eventsTs = (_o = (_m = host.read(`${normalizedOptions.projectRoot}/src/events.ts`)) === null || _m === void 0 ? void 0 : _m.toString()) !== null && _o !== void 0 ? _o : '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":";;
|
|
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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"}
|
|
@@ -15,7 +15,7 @@ utilsMock.getAdspConfiguration.mockResolvedValue({
|
|
|
15
15
|
directoryServiceUrl: environments.test.directoryServiceUrl,
|
|
16
16
|
});
|
|
17
17
|
utilsMock.deploymentGenerator.mockResolvedValue(undefined);
|
|
18
|
-
utilsMock.
|
|
18
|
+
utilsMock.ensureAdspToken.mockResolvedValue('test-token');
|
|
19
19
|
|
|
20
20
|
describe('Express Service Generator', () => {
|
|
21
21
|
const options: Schema = {
|
|
@@ -69,16 +69,29 @@ describe('Express Service Generator', () => {
|
|
|
69
69
|
expect(mcp.mcpServers['adsp-sdk']).toEqual({ command: 'node', args: ['/local/build/main.js'] });
|
|
70
70
|
}, 60000);
|
|
71
71
|
|
|
72
|
-
it('
|
|
72
|
+
it('factors routes into a router module mounted in main.ts (not inlined)', async () => {
|
|
73
73
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
74
74
|
await generator(host, options);
|
|
75
75
|
|
|
76
|
+
// main.ts holds infra + the mount, not route handlers.
|
|
76
77
|
const mainTs = host.read('apps/test/src/main.ts').toString();
|
|
77
|
-
expect(mainTs).toContain('authorize');
|
|
78
|
-
expect(mainTs).toContain('createValidationHandler');
|
|
79
78
|
expect(mainTs).toContain('createErrorHandler');
|
|
80
|
-
expect(mainTs).toContain('/
|
|
81
|
-
expect(mainTs).toContain('eventService
|
|
79
|
+
expect(mainTs).toContain("import { exampleRouter } from './routes/example'");
|
|
80
|
+
expect(mainTs).toContain('exampleRouter(eventService)');
|
|
81
|
+
// The handler internals moved out of main.ts.
|
|
82
|
+
expect(mainTs).not.toContain('authorize');
|
|
83
|
+
expect(mainTs).not.toContain('/v1/example');
|
|
84
|
+
|
|
85
|
+
// The router module carries the handlers, capabilities passed in as args.
|
|
86
|
+
expect(host.exists('apps/test/src/routes/example.ts')).toBeTruthy();
|
|
87
|
+
const routerTs = host.read('apps/test/src/routes/example.ts').toString();
|
|
88
|
+
expect(routerTs).toContain('export function exampleRouter(eventService: EventService)');
|
|
89
|
+
expect(routerTs).toContain('authorize');
|
|
90
|
+
expect(routerTs).toContain('createValidationHandler');
|
|
91
|
+
expect(routerTs).toContain('eventService.send');
|
|
92
|
+
|
|
93
|
+
// Shipped with a supertest test demonstrating the router-testing pattern.
|
|
94
|
+
expect(host.exists('apps/test/src/routes/example.spec.ts')).toBeTruthy();
|
|
82
95
|
}, 60000);
|
|
83
96
|
|
|
84
97
|
it('scaffolds postgres database files and targets', async () => {
|
|
@@ -55,7 +55,8 @@ need project-scoped MCP enabled. The server runs via `npx` and needs no credenti
|
|
|
55
55
|
|
|
56
56
|
| File | Purpose |
|
|
57
57
|
|------|---------|
|
|
58
|
-
| `src/main.ts` |
|
|
58
|
+
| `src/main.ts` | Composition root — SDK init, middleware, mounts routers, server start |
|
|
59
|
+
| `src/routes/example.ts` | Example `express.Router()` — starter resource; copy it per resource |
|
|
59
60
|
| `src/environment.ts` | Validated env config with defaults pre-set from ADSP tenant |
|
|
60
61
|
| `src/events.ts` | Domain event definitions — register in `initializeService({ events })` |
|
|
61
62
|
<% if (database === 'postgres') { %>
|
|
@@ -109,8 +110,9 @@ src/
|
|
|
109
110
|
Rules of thumb:
|
|
110
111
|
|
|
111
112
|
- **Do not inline route handlers in `main.ts`.** Add an `express.Router()` per
|
|
112
|
-
resource under `routes/` and mount it.
|
|
113
|
-
|
|
113
|
+
resource under `routes/` and mount it. `src/routes/example.ts` is the shipped
|
|
114
|
+
starter (with a `example.spec.ts` supertest test) — copy its shape per resource
|
|
115
|
+
and replace it as you build real features.
|
|
114
116
|
- **Keep handlers thin** — validate input, call a service, shape the response.
|
|
115
117
|
Put non-trivial logic in `services/` so it can be tested without HTTP.
|
|
116
118
|
- **One responsibility per module** — routers do HTTP, services do logic, the db
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { AdspId,
|
|
1
|
+
import { AdspId, createErrorHandler, initializeService } from '@abgov/adsp-service-sdk';
|
|
2
2
|
import compression from 'compression';
|
|
3
3
|
import cors from 'cors';
|
|
4
4
|
import express from 'express';
|
|
5
5
|
import helmet from 'helmet';
|
|
6
6
|
import passport from 'passport';
|
|
7
7
|
import { Strategy as AnonymousStrategy } from 'passport-anonymous';
|
|
8
|
-
import { z } from 'zod';
|
|
9
8
|
<% if (database === 'postgres') { %>
|
|
10
9
|
import { closeDatabase } from './database';
|
|
11
10
|
<% } else if (database === 'mongo') { %>
|
|
12
11
|
import { connectDatabase, disconnectDatabase } from './database';
|
|
13
12
|
<% } %>
|
|
14
13
|
import { environment } from './environment';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
const ExampleRequestSchema = z.object({
|
|
18
|
-
id: z.string().min(1),
|
|
19
|
-
});
|
|
14
|
+
import { exampleEventDefinition } from './events';
|
|
15
|
+
import { exampleRouter } from './routes/example';
|
|
20
16
|
|
|
21
17
|
async function initializeApp() {
|
|
22
18
|
const serviceId = AdspId.parse(environment.CLIENT_ID);
|
|
@@ -56,12 +52,6 @@ async function initializeApp() {
|
|
|
56
52
|
passport.use('tenant', tenantStrategy);
|
|
57
53
|
app.use(passport.initialize());
|
|
58
54
|
|
|
59
|
-
app.use(
|
|
60
|
-
'/<%= projectName %>/v1',
|
|
61
|
-
passport.authenticate(['tenant', 'anonymous'], { session: false }),
|
|
62
|
-
configurationHandler
|
|
63
|
-
);
|
|
64
|
-
|
|
65
55
|
app.get('/health', async (_req, res) => {
|
|
66
56
|
const platform = await healthCheck();
|
|
67
57
|
res.json({ ...platform });
|
|
@@ -78,46 +68,14 @@ async function initializeApp() {
|
|
|
78
68
|
});
|
|
79
69
|
});
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Public endpoint — no authentication required.
|
|
92
|
-
app.get('/<%= projectName %>/v1/public', (_req, res) => {
|
|
93
|
-
res.json({ message: 'Hello from the public <%= projectName %> API.' });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Private endpoint — requires an authenticated tenant user.
|
|
97
|
-
app.get(
|
|
98
|
-
'/<%= projectName %>/v1/private',
|
|
99
|
-
passport.authenticate(['tenant'], { session: false }),
|
|
100
|
-
(req, res) => {
|
|
101
|
-
res.json({ message: `Hello, ${req.user!.name}.` });
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// Example: protected route demonstrating authorize, input validation, and a domain event.
|
|
106
|
-
// Require 'example-role' — replace with a role relevant to your service.
|
|
107
|
-
// Remove or replace this route once you have real business logic.
|
|
108
|
-
app.post(
|
|
109
|
-
'/<%= projectName %>/v1/example',
|
|
110
|
-
authorize('example-role'),
|
|
111
|
-
createValidationHandler(ExampleRequestSchema),
|
|
112
|
-
async (req, res, next) => {
|
|
113
|
-
try {
|
|
114
|
-
const { id } = req.body as z.infer<typeof ExampleRequestSchema>;
|
|
115
|
-
eventService.send(createExampleEvent(id));
|
|
116
|
-
res.json({ id });
|
|
117
|
-
} catch (err) {
|
|
118
|
-
next(err);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
71
|
+
// v1 API: authenticate (tenant or anonymous) and resolve tenant configuration
|
|
72
|
+
// for the whole subtree, then mount the resource routers. Add one router per
|
|
73
|
+
// resource under routes/ and mount it here — keep handlers out of main.ts.
|
|
74
|
+
app.use(
|
|
75
|
+
'/<%= projectName %>/v1',
|
|
76
|
+
passport.authenticate(['tenant', 'anonymous'], { session: false }),
|
|
77
|
+
configurationHandler,
|
|
78
|
+
exampleRouter(eventService)
|
|
121
79
|
);
|
|
122
80
|
|
|
123
81
|
// Mount error handler last — after all routes and middleware.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { EventService } from '@abgov/adsp-service-sdk';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { exampleRouter } from './example';
|
|
5
|
+
|
|
6
|
+
describe('exampleRouter', () => {
|
|
7
|
+
// The router only needs eventService.send — mock it, so the test needs no DB
|
|
8
|
+
// and no real auth. This is the pattern for testing any resource router.
|
|
9
|
+
const eventService = { send: jest.fn() } as unknown as EventService;
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
app.use('/<%= projectName %>/v1', exampleRouter(eventService));
|
|
14
|
+
|
|
15
|
+
it('serves the public endpoint without authentication', async () => {
|
|
16
|
+
const res = await request(app).get('/<%= projectName %>/v1/public');
|
|
17
|
+
expect(res.status).toBe(200);
|
|
18
|
+
expect(res.body.message).toContain('public');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('exposes resource links at the index', async () => {
|
|
22
|
+
const res = await request(app).get('/<%= projectName %>/v1');
|
|
23
|
+
expect(res.status).toBe(200);
|
|
24
|
+
expect(res.body._links.public.href).toBe('/<%= projectName %>/v1/public');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { authorize, createValidationHandler, type EventService } from '@abgov/adsp-service-sdk';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import passport from 'passport';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createExampleEvent } from '../events';
|
|
6
|
+
|
|
7
|
+
const ExampleRequestSchema = z.object({
|
|
8
|
+
id: z.string().min(1),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Thin HTTP handlers for the example resource, grouped in an express.Router().
|
|
12
|
+
// Capabilities from initializeService() are passed in (not imported) so the router
|
|
13
|
+
// stays testable — mount it with supertest and pass a mock eventService (see the
|
|
14
|
+
// spec). This is a starter: add one router per resource under routes/ and replace
|
|
15
|
+
// this once you have real business logic.
|
|
16
|
+
export function exampleRouter(eventService: EventService): Router {
|
|
17
|
+
const router = Router();
|
|
18
|
+
|
|
19
|
+
// Resource index — hypermedia links to this resource's routes. req.baseUrl is
|
|
20
|
+
// the mount path, so the router doesn't hard-code where it's mounted.
|
|
21
|
+
router.get('/', (req, res) => {
|
|
22
|
+
res.json({
|
|
23
|
+
_links: {
|
|
24
|
+
self: { href: req.baseUrl },
|
|
25
|
+
public: { href: `${req.baseUrl}/public` },
|
|
26
|
+
private: { href: `${req.baseUrl}/private` },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Public endpoint — no authentication required.
|
|
32
|
+
router.get('/public', (_req, res) => {
|
|
33
|
+
res.json({ message: 'Hello from the public <%= projectName %> API.' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Private endpoint — requires an authenticated tenant user.
|
|
37
|
+
router.get('/private', passport.authenticate(['tenant'], { session: false }), (req, res) => {
|
|
38
|
+
res.json({ message: `Hello, ${req.user!.name}.` });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Protected route demonstrating authorize, input validation, and a domain event.
|
|
42
|
+
// Require 'example-role' — replace with a role relevant to your service.
|
|
43
|
+
router.post(
|
|
44
|
+
'/example',
|
|
45
|
+
authorize('example-role'),
|
|
46
|
+
createValidationHandler(ExampleRequestSchema),
|
|
47
|
+
async (req, res, next) => {
|
|
48
|
+
try {
|
|
49
|
+
const { id } = req.body as z.infer<typeof ExampleRequestSchema>;
|
|
50
|
+
eventService.send(createExampleEvent(id));
|
|
51
|
+
res.json({ id });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
next(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return router;
|
|
59
|
+
}
|