@abgov/nx-adsp 13.0.0-beta.1 → 13.0.0-beta.2
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/package.json +7 -1
- package/src/build-assets.spec.ts +62 -0
- package/src/generators/angular-app/files/AGENTS.md__tmpl__ +28 -2
- package/src/generators/express-service/express-service.js +27 -26
- package/src/generators/express-service/express-service.js.map +1 -1
- package/src/generators/express-service/express-service.spec.ts +21 -8
- package/src/generators/express-service/files/AGENTS.md__tmpl__ +56 -20
- package/src/generators/express-service/files/src/main.ts__tmpl__ +2 -2
- package/src/generators/express-service/files-postgres/drizzle.config.ts__tmpl__ +17 -0
- package/src/generators/express-service/files-postgres/scripts/dev-db.sh__tmpl__ +1 -1
- package/src/generators/express-service/files-postgres/src/database.ts__tmpl__ +13 -2
- package/src/generators/express-service/files-postgres/src/db/schema.ts__tmpl__ +17 -0
- package/src/generators/express-service/files-postgres/src/migrate.ts__tmpl__ +41 -0
- package/src/generators/express-service/files-postgres/webpack.config.js__tmpl__ +36 -0
- package/src/generators/express-service/schema.json +1 -1
- package/src/generators/mevn/mevn.spec.ts +3 -3
- package/src/generators/pean/pean.spec.ts +1 -1
- package/src/generators/pern/pern.spec.ts +1 -1
- package/src/generators/pevn/pevn.spec.ts +5 -3
- package/src/generators/react-app/files/AGENTS.md__tmpl__ +28 -2
- package/src/generators/react-app/react-app.js +3 -1
- package/src/generators/react-app/react-app.js.map +1 -1
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +28 -2
- package/src/generators/vue-app/files/public/favicon.ico +0 -0
- package/src/generators/vue-app/vue-app.js +37 -11
- package/src/generators/vue-app/vue-app.js.map +1 -1
- package/src/generators/vue-app/vue-app.spec.ts +36 -3
- package/src/generators/express-service/files-postgres/prisma/schema.prisma__tmpl__ +0 -14
- /package/src/generators/express-service/files-postgres/{prisma/migrations → drizzle}/.gitkeep +0 -0
- /package/src/generators/vue-app/files/{src/index.html__tmpl__ → index.html__tmpl__} +0 -0
- /package/src/generators/vue-app/files/{src → public}/assets/banner.jpg +0 -0
- /package/src/generators/vue-app/files/{nginx.conf__tmpl__ → public/nginx.conf__tmpl__} +0 -0
- /package/src/generators/vue-app/files/{src → public}/silent-check-sso.html__tmpl__ +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abgov/nx-adsp",
|
|
3
|
-
"version": "13.0.0-beta.
|
|
3
|
+
"version": "13.0.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"description": "Government of Alberta - Nx plugin for ADSP apps.",
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
"@nx/angular": {
|
|
28
28
|
"optional": true
|
|
29
29
|
},
|
|
30
|
+
"@nx/express": {
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
"@nx/react": {
|
|
34
|
+
"optional": true
|
|
35
|
+
},
|
|
30
36
|
"@nx/vue": {
|
|
31
37
|
"optional": true
|
|
32
38
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
// minimatch is always present (nx depends on it); used only in this test.
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
5
|
+
import minimatch = require('minimatch');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Guards the source -> published-package boundary.
|
|
9
|
+
*
|
|
10
|
+
* The build copies non-source files (generator templates, schemas, static
|
|
11
|
+
* assets) into the package via the build target's `assets` globs. Any such file
|
|
12
|
+
* NOT matched by a glob is silently dropped from the published package, so a
|
|
13
|
+
* consumer's generate produces output referencing a file that was never
|
|
14
|
+
* shipped. This is exactly how `Dockerfile__tmpl__` was lost: the rename from
|
|
15
|
+
* `Dockerfile.template` (dotted) to `Dockerfile__tmpl__` (dotless) stopped it
|
|
16
|
+
* matching `**\/*.!(ts)`.
|
|
17
|
+
*
|
|
18
|
+
* Unit tests of the generators can't catch this — they resolve templates from
|
|
19
|
+
* the source tree, never the packaged output. This asserts the invariant
|
|
20
|
+
* directly: every non-TypeScript file under src is covered by an asset glob.
|
|
21
|
+
*/
|
|
22
|
+
describe('build assets packaging', () => {
|
|
23
|
+
const srcRoot = __dirname;
|
|
24
|
+
const projectRoot = path.join(srcRoot, '..');
|
|
25
|
+
const repoRoot = path.join(projectRoot, '..', '..');
|
|
26
|
+
|
|
27
|
+
function listFiles(dir: string): string[] {
|
|
28
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
29
|
+
const full = path.join(dir, entry.name);
|
|
30
|
+
return entry.isDirectory() ? listFiles(full) : [full];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
it('matches every non-TypeScript src file with an asset glob', () => {
|
|
35
|
+
const project = JSON.parse(
|
|
36
|
+
fs.readFileSync(path.join(projectRoot, 'project.json'), 'utf-8')
|
|
37
|
+
);
|
|
38
|
+
const assets: unknown[] = project.targets.build.options.assets ?? [];
|
|
39
|
+
|
|
40
|
+
// Globs whose input is this package's src directory.
|
|
41
|
+
const srcGlobs = assets
|
|
42
|
+
.filter(
|
|
43
|
+
(a): a is { input: string; glob: string } =>
|
|
44
|
+
typeof a === 'object' &&
|
|
45
|
+
a !== null &&
|
|
46
|
+
'input' in a &&
|
|
47
|
+
path.resolve(repoRoot, (a as { input: string }).input) === srcRoot
|
|
48
|
+
)
|
|
49
|
+
.map((a) => a.glob);
|
|
50
|
+
|
|
51
|
+
// Every non-source file under src must ship (tsc only emits .ts -> .js).
|
|
52
|
+
const mustShip = listFiles(srcRoot)
|
|
53
|
+
.filter((f) => !/\.tsx?$/.test(f))
|
|
54
|
+
.map((f) => path.relative(srcRoot, f));
|
|
55
|
+
|
|
56
|
+
const unmatched = mustShip.filter(
|
|
57
|
+
(rel) => !srcGlobs.some((glob) => minimatch(rel, glob, { dot: true }))
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(unmatched).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -128,8 +128,8 @@ describe('MyFeatureComponent', () => {
|
|
|
128
128
|
## OpenShift targets
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
|
-
nx run <%= projectName %>:sandbox # build + push to
|
|
132
|
-
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources
|
|
131
|
+
nx run <%= projectName %>:sandbox # build locally (podman) + push to GHCR + deploy
|
|
132
|
+
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources + delete the GHCR image
|
|
133
133
|
nx run <%= projectName %>:apply-envs # apply manifests to all environments
|
|
134
134
|
nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
135
135
|
```
|
|
@@ -142,3 +142,29 @@ nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
|
142
142
|
silent SSO check on page load
|
|
143
143
|
- `environments/environment.ts` — access URL and realm are pre-configured for
|
|
144
144
|
the ADSP tenant; override at runtime via environment variables
|
|
145
|
+
|
|
146
|
+
## Sandbox deployment (local build)
|
|
147
|
+
|
|
148
|
+
The `sandbox` targets are added by the nx-oc sandbox generator — run it once per app first:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
nx g @abgov/nx-oc:sandbox <%= projectName %> --sandboxProject <your-namespace>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Then `nx run <%= projectName %>:sandbox` builds the image **locally with podman**, pushes it to GHCR, and imports it into your sandbox namespace — no git push or CI wait. Prerequisites (one-time):
|
|
155
|
+
|
|
156
|
+
- `podman` installed and running (`podman machine start` on macOS).
|
|
157
|
+
- `oc` logged in, targeting your (per-user) sandbox namespace.
|
|
158
|
+
- `gh auth login` as an account with **`write:packages`** on the registry org. That account must be the **active** `gh` account when you run the target — the sandbox reads `gh auth token` for both the image push and the pull secret (no PAT is stored). Check with `gh auth status`; switch with `gh auth switch -u <account>`.
|
|
159
|
+
|
|
160
|
+
The registry (`ghcr.io/<org>`) is derived from the git remote on first run and saved in `nx.json`. The image is `<registry>/<sandboxProject>-<app>:sandbox` (namespace-prefixed to avoid collisions). If a push or import fails, it's almost always the active `gh` account lacking `write:packages` or access to the org — re-run after `gh auth switch`.
|
|
161
|
+
|
|
162
|
+
### Verify & troubleshoot a sandbox deploy
|
|
163
|
+
|
|
164
|
+
The target ends with `oc rollout status`. To confirm and diagnose:
|
|
165
|
+
|
|
166
|
+
- **Health:** `oc get pods -n <namespace> -l name=<%= projectName %>` and `curl https://$(oc get route <%= projectName %> -n <namespace> -o jsonpath='{.spec.host}')/health`.
|
|
167
|
+
- **Push or import failed:** almost always the active `gh` account lacks `write:packages` or access to the org — check `gh auth status`, then `gh auth switch -u <account>` and re-run.
|
|
168
|
+
- **Pod CrashLoopBackOff / not ready:** `oc logs -n <namespace> <pod> -c <%= projectName %>`. For a service with a database, also check the migration init container: `oc logs -n <namespace> <pod> -c <%= projectName %>-migrate`.
|
|
169
|
+
- **`podman` errors:** ensure the machine is running — `podman machine start` (macOS).
|
|
170
|
+
- **Namespace missing:** create it with `oc new-project <namespace>`.
|
|
@@ -56,11 +56,13 @@ function addFiles(host, options) {
|
|
|
56
56
|
}
|
|
57
57
|
function default_1(host, options) {
|
|
58
58
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
59
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
59
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
60
60
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
61
|
-
const { applicationGenerator: initExpress } = yield Promise.resolve().then(() => require('@nx/express'))
|
|
61
|
+
const { applicationGenerator: initExpress } = yield Promise.resolve().then(() => require('@nx/express')).catch(() => {
|
|
62
|
+
throw new Error("The 'express-service' generator requires the '@nx/express' plugin. Install it and re-run:\n npm i -D @nx/express");
|
|
63
|
+
});
|
|
62
64
|
yield initExpress(host, Object.assign(Object.assign({}, options), { skipFormat: true, skipPackageJson: false, linter: eslint_1.Linter.EsLint, unitTestRunner: 'jest', js: false, directory: normalizedOptions.projectRoot }));
|
|
63
|
-
(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' ? { '
|
|
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' }));
|
|
64
66
|
addFiles(host, normalizedOptions);
|
|
65
67
|
(0, quality_1.addEslintQualityRules)(host, normalizedOptions.projectRoot, ['**/*.spec.ts', '**/*.test.ts']);
|
|
66
68
|
(0, quality_1.addJestCoverageConfig)(host, normalizedOptions.projectRoot);
|
|
@@ -81,33 +83,32 @@ function default_1(host, options) {
|
|
|
81
83
|
if (normalizedOptions.database === 'postgres') {
|
|
82
84
|
targets['db:generate'] = {
|
|
83
85
|
executor: 'nx:run-commands',
|
|
84
|
-
options: { command: 'npx
|
|
86
|
+
options: { command: 'npx drizzle-kit generate', cwd: '{projectRoot}' },
|
|
85
87
|
};
|
|
86
88
|
targets['db:migrate'] = {
|
|
87
89
|
executor: 'nx:run-commands',
|
|
88
|
-
options: { command: 'npx
|
|
90
|
+
options: { command: 'npx drizzle-kit migrate', cwd: '{projectRoot}' },
|
|
89
91
|
};
|
|
90
92
|
targets['db:migrate:deploy'] = {
|
|
91
93
|
executor: 'nx:run-commands',
|
|
92
|
-
options: { command: 'npx
|
|
94
|
+
options: { command: 'npx drizzle-kit migrate', cwd: '{projectRoot}' },
|
|
93
95
|
};
|
|
94
96
|
targets['db:studio'] = {
|
|
95
97
|
executor: 'nx:run-commands',
|
|
96
|
-
options: { command: 'npx
|
|
98
|
+
options: { command: 'npx drizzle-kit studio', cwd: '{projectRoot}' },
|
|
97
99
|
};
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
100
|
+
// Ship the SQL migrations (drizzle/) into the build output so the migrate.js
|
|
101
|
+
// init container can apply them at deploy time. Drizzle is pure TypeScript
|
|
102
|
+
// with no client codegen, so the build has no db:generate prerequisite.
|
|
103
|
+
if ((_b = targets['build']) === null || _b === void 0 ? void 0 : _b.options) {
|
|
104
|
+
targets['build'] = Object.assign(Object.assign({}, targets['build']), { options: Object.assign(Object.assign({}, targets['build'].options), { assets: [
|
|
105
|
+
...((_c = targets['build'].options.assets) !== null && _c !== void 0 ? _c : []),
|
|
106
|
+
{
|
|
107
|
+
input: `${normalizedOptions.projectRoot}/drizzle`,
|
|
108
|
+
glob: '**/*',
|
|
109
|
+
output: 'drizzle',
|
|
110
|
+
},
|
|
111
|
+
] }) });
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
@@ -116,7 +117,7 @@ function default_1(host, options) {
|
|
|
116
117
|
yield (0, devkit_1.formatFiles)(host);
|
|
117
118
|
if (normalizedOptions.adsp) {
|
|
118
119
|
const clientId = `urn:ads:${normalizedOptions.adsp.tenant}:${normalizedOptions.projectName}`;
|
|
119
|
-
const accessToken = (
|
|
120
|
+
const accessToken = (_d = normalizedOptions.accessToken) !== null && _d !== void 0 ? _d : normalizedOptions.adsp.accessToken;
|
|
120
121
|
const clientSecret = yield (0, keycloak_admin_1.ensureServiceClient)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm, clientId, accessToken);
|
|
121
122
|
if (clientSecret) {
|
|
122
123
|
const envPath = `${normalizedOptions.projectRoot}/.env`;
|
|
@@ -143,16 +144,16 @@ function default_1(host, options) {
|
|
|
143
144
|
// from the single realm login already performed during normalizeOptions.
|
|
144
145
|
// token from the single realm login. Fall back to a new login only when the
|
|
145
146
|
// full interactive flow was used and no token is available.
|
|
146
|
-
const accessToken = (
|
|
147
|
+
const accessToken = (_e = normalizedOptions.accessToken) !== null && _e !== void 0 ? _e : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
|
|
147
148
|
var _a;
|
|
148
149
|
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`);
|
|
149
150
|
return undefined;
|
|
150
151
|
}));
|
|
151
|
-
const mainTs = (
|
|
152
|
-
const environmentTs = (
|
|
153
|
-
const eventsTs = (
|
|
152
|
+
const mainTs = (_g = (_f = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _f === void 0 ? void 0 : _f.toString()) !== null && _g !== void 0 ? _g : '';
|
|
153
|
+
const environmentTs = (_j = (_h = host.read(`${normalizedOptions.projectRoot}/src/environment.ts`)) === null || _h === void 0 ? void 0 : _h.toString()) !== null && _j !== void 0 ? _j : '';
|
|
154
|
+
const eventsTs = (_l = (_k = host.read(`${normalizedOptions.projectRoot}/src/events.ts`)) === null || _k === void 0 ? void 0 : _k.toString()) !== null && _l !== void 0 ? _l : '';
|
|
154
155
|
const databaseTs = normalizedOptions.database !== 'none'
|
|
155
|
-
? (
|
|
156
|
+
? (_o = (_m = host.read(`${normalizedOptions.projectRoot}/src/database.ts`)) === null || _m === void 0 ? void 0 : _m.toString()) !== null && _o !== void 0 ? _o : ''
|
|
156
157
|
: undefined;
|
|
157
158
|
const agentResult = yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
|
|
158
159
|
projectName: normalizedOptions.projectName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":";;AAiGA,
|
|
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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"}
|
|
@@ -51,17 +51,20 @@ describe('Express Service Generator', () => {
|
|
|
51
51
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
52
52
|
await generator(host, { ...options, database: 'postgres' });
|
|
53
53
|
|
|
54
|
-
expect(host.exists('apps/test/
|
|
54
|
+
expect(host.exists('apps/test/src/db/schema.ts')).toBeTruthy();
|
|
55
55
|
expect(host.exists('apps/test/src/database.ts')).toBeTruthy();
|
|
56
|
+
expect(host.exists('apps/test/src/migrate.ts')).toBeTruthy();
|
|
57
|
+
expect(host.exists('apps/test/drizzle.config.ts')).toBeTruthy();
|
|
56
58
|
expect(host.exists('apps/test/scripts/dev-db.sh')).toBeTruthy();
|
|
57
59
|
expect(host.exists('apps/test/.env.example')).toBeTruthy();
|
|
58
60
|
|
|
59
|
-
const schema = host.read('apps/test/prisma/schema.prisma').toString();
|
|
60
|
-
expect(schema).toContain('postgresql');
|
|
61
|
-
expect(schema).toContain('src/generated/prisma');
|
|
62
|
-
|
|
63
61
|
const database = host.read('apps/test/src/database.ts').toString();
|
|
64
|
-
expect(database).toContain('
|
|
62
|
+
expect(database).toContain('drizzle-orm/node-postgres');
|
|
63
|
+
expect(database).toContain('closeDatabase');
|
|
64
|
+
|
|
65
|
+
// webpack emits a second bundle (migrate.js) for the deploy init container.
|
|
66
|
+
const webpackConfig = host.read('apps/test/webpack.config.js').toString();
|
|
67
|
+
expect(webpackConfig).toContain('migrate');
|
|
65
68
|
|
|
66
69
|
const config = readProjectConfiguration(host, 'test');
|
|
67
70
|
expect(config.targets['dev-db']).toBeTruthy();
|
|
@@ -70,7 +73,17 @@ describe('Express Service Generator', () => {
|
|
|
70
73
|
expect(config.targets['db:migrate:deploy']).toBeTruthy();
|
|
71
74
|
expect(config.targets['db:studio']).toBeTruthy();
|
|
72
75
|
expect(config.targets['serve'].dependsOn).toContain('dev-db');
|
|
73
|
-
|
|
76
|
+
|
|
77
|
+
// Drizzle has no client codegen, so build must NOT depend on db:generate.
|
|
78
|
+
expect(config.targets['build'].dependsOn ?? []).not.toContain('db:generate');
|
|
79
|
+
// The SQL migrations are shipped as a build asset.
|
|
80
|
+
const assets = config.targets['build'].options.assets ?? [];
|
|
81
|
+
expect(
|
|
82
|
+
assets.some(
|
|
83
|
+
(a: unknown) =>
|
|
84
|
+
typeof a === 'object' && (a as { output?: string }).output === 'drizzle'
|
|
85
|
+
)
|
|
86
|
+
).toBe(true);
|
|
74
87
|
}, 60000);
|
|
75
88
|
|
|
76
89
|
it('scaffolds mongo database files and targets', async () => {
|
|
@@ -94,7 +107,7 @@ describe('Express Service Generator', () => {
|
|
|
94
107
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
95
108
|
await generator(host, { ...options, database: 'none' });
|
|
96
109
|
|
|
97
|
-
expect(host.exists('apps/test/
|
|
110
|
+
expect(host.exists('apps/test/src/db/schema.ts')).toBeFalsy();
|
|
98
111
|
expect(host.exists('apps/test/src/database.ts')).toBeFalsy();
|
|
99
112
|
expect(host.exists('apps/test/scripts/dev-db.sh')).toBeFalsy();
|
|
100
113
|
|
|
@@ -18,7 +18,7 @@ When working on a feature that spans both projects, read `apps/<%= pairedProject
|
|
|
18
18
|
event publishing, configuration management, and service discovery
|
|
19
19
|
- **Tests**: Jest
|
|
20
20
|
<% if (database === 'postgres') { %>
|
|
21
|
-
- **Database**: PostgreSQL via
|
|
21
|
+
- **Database**: PostgreSQL via Drizzle ORM (pure TypeScript, node-postgres driver)
|
|
22
22
|
<% } else if (database === 'mongo') { %>
|
|
23
23
|
- **Database**: MongoDB via Mongoose
|
|
24
24
|
<% } %>
|
|
@@ -31,8 +31,10 @@ When working on a feature that spans both projects, read `apps/<%= pairedProject
|
|
|
31
31
|
| `src/environment.ts` | Validated env config with defaults pre-set from ADSP tenant |
|
|
32
32
|
| `src/events.ts` | Domain event definitions — register in `initializeService({ events })` |
|
|
33
33
|
<% if (database === 'postgres') { %>
|
|
34
|
-
| `src/database.ts` |
|
|
35
|
-
| `
|
|
34
|
+
| `src/database.ts` | Drizzle `db` instance — import `{ db }` in route handlers |
|
|
35
|
+
| `src/db/schema.ts` | Drizzle table definitions — edit to add tables, then run `nx db:generate <%= projectName %>` |
|
|
36
|
+
| `src/migrate.ts` | Standalone migration runner — bundled to `migrate.js`, run as the deploy init container |
|
|
37
|
+
| `drizzle.config.ts` | drizzle-kit config (schema path, migrations output, connection) |
|
|
36
38
|
<% } else if (database === 'mongo') { %>
|
|
37
39
|
| `src/database.ts` | Mongoose connection helpers — `connectDatabase()` and `disconnectDatabase()` |
|
|
38
40
|
<% } %>
|
|
@@ -144,35 +146,43 @@ podman machine init
|
|
|
144
146
|
podman machine start
|
|
145
147
|
```
|
|
146
148
|
|
|
147
|
-
## Adding a
|
|
149
|
+
## Adding a table
|
|
148
150
|
|
|
149
|
-
1. Edit `
|
|
150
|
-
2. Run `nx db:
|
|
151
|
-
3. Run `nx db:
|
|
152
|
-
4. Import `{
|
|
151
|
+
1. Edit `src/db/schema.ts` to add your table (Drizzle `pgTable`)
|
|
152
|
+
2. Run `nx db:generate <%= projectName %>` — writes a SQL migration to `drizzle/`
|
|
153
|
+
3. Run `nx db:migrate <%= projectName %>` — applies pending migrations to your dev DB
|
|
154
|
+
4. Import `{ db }` from `./database` and your table from `./db/schema` in a handler
|
|
153
155
|
|
|
154
156
|
```typescript
|
|
155
|
-
// Example route using
|
|
157
|
+
// Example route using Drizzle
|
|
158
|
+
import { db } from './database';
|
|
159
|
+
import { items } from './db/schema';
|
|
160
|
+
|
|
156
161
|
app.get('/<%= projectName %>/v1/items', async (_req, res) => {
|
|
157
|
-
const
|
|
158
|
-
res.json(
|
|
162
|
+
const rows = await db.select().from(items);
|
|
163
|
+
res.json(rows);
|
|
159
164
|
});
|
|
160
165
|
```
|
|
161
166
|
|
|
162
|
-
##
|
|
167
|
+
## Drizzle targets
|
|
163
168
|
|
|
164
169
|
| Target | Command | Use |
|
|
165
170
|
|--------|---------|-----|
|
|
166
|
-
| `nx db:
|
|
167
|
-
| `nx db:migrate
|
|
168
|
-
| `nx db:
|
|
169
|
-
| `nx db:studio <%= projectName %>` | `
|
|
171
|
+
| `nx db:generate <%= projectName %>` | `drizzle-kit generate` | Generate a SQL migration from schema changes |
|
|
172
|
+
| `nx db:migrate <%= projectName %>` | `drizzle-kit migrate` | Apply pending migrations to the dev DB |
|
|
173
|
+
| `nx db:migrate:deploy <%= projectName %>` | `drizzle-kit migrate` | Apply pending migrations in a dev/CI shell |
|
|
174
|
+
| `nx db:studio <%= projectName %>` | `drizzle-kit studio` | Open Drizzle Studio to browse data |
|
|
175
|
+
|
|
176
|
+
`drizzle-kit` is a dev-only dependency. In the container, migrations are applied
|
|
177
|
+
by `migrate.js` (bundled from `src/migrate.ts`), which uses only `drizzle-orm` +
|
|
178
|
+
`pg` — no CLI and no native engine — so it runs under OpenShift's arbitrary UID.
|
|
170
179
|
|
|
171
180
|
## OpenShift deployment — database
|
|
172
181
|
|
|
173
182
|
The deployment manifest references a Secret named `{appName}-database` for the
|
|
174
|
-
`DATABASE_URL` value, and runs `
|
|
175
|
-
|
|
183
|
+
`DATABASE_URL` value, and runs `node migrate.js` as an init container before the
|
|
184
|
+
application starts. The generated SQL migrations in `drizzle/` are shipped into
|
|
185
|
+
the build output as assets so the init container can apply them.
|
|
176
186
|
|
|
177
187
|
Create the Secret in each OpenShift namespace before first deploy:
|
|
178
188
|
|
|
@@ -308,8 +318,8 @@ nginx in production. All API routes live under `/<%= projectName %>/v1/`.
|
|
|
308
318
|
## OpenShift targets
|
|
309
319
|
|
|
310
320
|
```bash
|
|
311
|
-
nx run <%= projectName %>:sandbox # build + push to
|
|
312
|
-
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources
|
|
321
|
+
nx run <%= projectName %>:sandbox # build locally (podman) + push to GHCR + deploy
|
|
322
|
+
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources + delete the GHCR image
|
|
313
323
|
nx run <%= projectName %>:apply-envs # apply manifests to all environments
|
|
314
324
|
nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
315
325
|
```
|
|
@@ -322,3 +332,29 @@ nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
|
322
332
|
- `configurationHandler` on the API path — provides configuration service
|
|
323
333
|
integration; keep it applied before route handlers
|
|
324
334
|
- `createErrorHandler(logger)` — must remain the last `app.use()` call
|
|
335
|
+
|
|
336
|
+
## Sandbox deployment (local build)
|
|
337
|
+
|
|
338
|
+
The `sandbox` targets are added by the nx-oc sandbox generator — run it once per app first:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
nx g @abgov/nx-oc:sandbox <%= projectName %> --sandboxProject <your-namespace>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Then `nx run <%= projectName %>:sandbox` builds the image **locally with podman**, pushes it to GHCR, and imports it into your sandbox namespace — no git push or CI wait. Prerequisites (one-time):
|
|
345
|
+
|
|
346
|
+
- `podman` installed and running (`podman machine start` on macOS).
|
|
347
|
+
- `oc` logged in, targeting your (per-user) sandbox namespace.
|
|
348
|
+
- `gh auth login` as an account with **`write:packages`** on the registry org. That account must be the **active** `gh` account when you run the target — the sandbox reads `gh auth token` for both the image push and the pull secret (no PAT is stored). Check with `gh auth status`; switch with `gh auth switch -u <account>`.
|
|
349
|
+
|
|
350
|
+
The registry (`ghcr.io/<org>`) is derived from the git remote on first run and saved in `nx.json`. The image is `<registry>/<sandboxProject>-<app>:sandbox` (namespace-prefixed to avoid collisions). If a push or import fails, it's almost always the active `gh` account lacking `write:packages` or access to the org — re-run after `gh auth switch`.
|
|
351
|
+
|
|
352
|
+
### Verify & troubleshoot a sandbox deploy
|
|
353
|
+
|
|
354
|
+
The target ends with `oc rollout status`. To confirm and diagnose:
|
|
355
|
+
|
|
356
|
+
- **Health:** `oc get pods -n <namespace> -l name=<%= projectName %>` and `curl https://$(oc get route <%= projectName %> -n <namespace> -o jsonpath='{.spec.host}')/health`.
|
|
357
|
+
- **Push or import failed:** almost always the active `gh` account lacks `write:packages` or access to the org — check `gh auth status`, then `gh auth switch -u <account>` and re-run.
|
|
358
|
+
- **Pod CrashLoopBackOff / not ready:** `oc logs -n <namespace> <pod> -c <%= projectName %>`. For a service with a database, also check the migration init container: `oc logs -n <namespace> <pod> -c <%= projectName %>-migrate`.
|
|
359
|
+
- **`podman` errors:** ensure the machine is running — `podman machine start` (macOS).
|
|
360
|
+
- **Namespace missing:** create it with `oc new-project <namespace>`.
|
|
@@ -7,7 +7,7 @@ import passport from 'passport';
|
|
|
7
7
|
import { Strategy as AnonymousStrategy } from 'passport-anonymous';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
<% if (database === 'postgres') { %>
|
|
10
|
-
import {
|
|
10
|
+
import { closeDatabase } from './database';
|
|
11
11
|
<% } else if (database === 'mongo') { %>
|
|
12
12
|
import { connectDatabase, disconnectDatabase } from './database';
|
|
13
13
|
<% } %>
|
|
@@ -136,7 +136,7 @@ initializeApp().then(({ app, logger }) => {
|
|
|
136
136
|
|
|
137
137
|
const shutdown = async () => {
|
|
138
138
|
server.close();
|
|
139
|
-
await
|
|
139
|
+
await closeDatabase();
|
|
140
140
|
};
|
|
141
141
|
process.on('SIGTERM', shutdown);
|
|
142
142
|
process.on('SIGINT', shutdown);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { defineConfig } from 'drizzle-kit';
|
|
3
|
+
|
|
4
|
+
// drizzle-kit (generate/migrate/studio) is a dev-only CLI. Load the local
|
|
5
|
+
// DATABASE_URL the same way the app does: .env first, then .env.local (written
|
|
6
|
+
// by `nx dev-db`) taking precedence.
|
|
7
|
+
config({ path: '.env' });
|
|
8
|
+
config({ path: '.env.local', override: true });
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
schema: './src/db/schema.ts',
|
|
12
|
+
out: './drizzle',
|
|
13
|
+
dialect: 'postgresql',
|
|
14
|
+
dbCredentials: {
|
|
15
|
+
url: process.env.DATABASE_URL as string,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -37,7 +37,7 @@ 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
|
-
# Write DATABASE_URL to .env.local so
|
|
40
|
+
# Write DATABASE_URL to .env.local so Drizzle and the app pick it up without
|
|
41
41
|
# manual .env editing. Overridden by the SECRET in OpenShift at runtime.
|
|
42
42
|
echo "DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@localhost:${PORT}/${DB_NAME}" > .env.local
|
|
43
43
|
echo "DATABASE_URL written to .env.local"
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
2
|
+
import { Pool } from 'pg';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import { environment } from './environment';
|
|
5
|
+
import * as schema from './db/schema';
|
|
6
|
+
|
|
7
|
+
// Drizzle is pure TypeScript: the node-postgres (pg) driver owns the connection,
|
|
8
|
+
// so there is no native database engine to download, ship, or run — the
|
|
9
|
+
// container image needs nothing beyond the app's JS and node_modules.
|
|
10
|
+
const pool = new Pool({ connectionString: environment.DATABASE_URL });
|
|
11
|
+
|
|
12
|
+
export const db = drizzle(pool, { schema });
|
|
13
|
+
|
|
14
|
+
export const closeDatabase = (): Promise<void> => pool.end();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Drizzle schema — define your tables here.
|
|
2
|
+
//
|
|
3
|
+
// After editing this file:
|
|
4
|
+
// 1. nx db:generate <%= projectName %> — writes a SQL migration to ./drizzle
|
|
5
|
+
// 2. nx db:migrate <%= projectName %> — applies pending migrations to your dev DB
|
|
6
|
+
//
|
|
7
|
+
// Example (uncomment and adapt, then run the two commands above):
|
|
8
|
+
//
|
|
9
|
+
// import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
10
|
+
//
|
|
11
|
+
// export const items = pgTable('items', {
|
|
12
|
+
// id: serial('id').primaryKey(),
|
|
13
|
+
// name: text('name').notNull(),
|
|
14
|
+
// createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
15
|
+
// });
|
|
16
|
+
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
3
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
|
4
|
+
import { Pool } from 'pg';
|
|
5
|
+
|
|
6
|
+
const MIGRATIONS_FOLDER = 'drizzle';
|
|
7
|
+
|
|
8
|
+
// Standalone migration runner — the deployment runs this as an init container
|
|
9
|
+
// (`node migrate.js`) before the app starts. It uses only drizzle-orm + pg,
|
|
10
|
+
// both runtime dependencies, so it survives `npm prune --omit=dev` and needs no
|
|
11
|
+
// CLI or native engine in the image. The SQL files in ./drizzle are shipped as
|
|
12
|
+
// build assets alongside this bundle.
|
|
13
|
+
async function main() {
|
|
14
|
+
// A freshly generated service has no models yet, so there are no migrations
|
|
15
|
+
// to apply. Skip cleanly instead of failing the init container.
|
|
16
|
+
if (!existsSync(`${MIGRATIONS_FOLDER}/meta/_journal.json`)) {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.log('No migrations to apply.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const connectionString = process.env.DATABASE_URL;
|
|
23
|
+
if (!connectionString) {
|
|
24
|
+
throw new Error('DATABASE_URL is not set.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pool = new Pool({ connectionString });
|
|
28
|
+
try {
|
|
29
|
+
await migrate(drizzle(pool), { migrationsFolder: MIGRATIONS_FOLDER });
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log('Migrations applied.');
|
|
32
|
+
} finally {
|
|
33
|
+
await pool.end();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.error('Migration failed:', err);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { composePlugins, withNx } = require('@nx/webpack');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Nx plugins for webpack.
|
|
5
|
+
module.exports = composePlugins(
|
|
6
|
+
withNx({
|
|
7
|
+
target: 'node',
|
|
8
|
+
}),
|
|
9
|
+
(config) => {
|
|
10
|
+
// Emit a second bundle, migrate.js, from src/migrate.ts. The deployment runs
|
|
11
|
+
// it as an init container (`node migrate.js`) to apply Drizzle migrations
|
|
12
|
+
// before the app starts — using only runtime deps, no CLI, no native engine.
|
|
13
|
+
const mainEntry = config.entry.main;
|
|
14
|
+
const migrateEntry = path.resolve(__dirname, 'src/migrate.ts');
|
|
15
|
+
config.entry = {
|
|
16
|
+
...config.entry,
|
|
17
|
+
migrate:
|
|
18
|
+
mainEntry && typeof mainEntry === 'object' && !Array.isArray(mainEntry)
|
|
19
|
+
? { ...mainEntry, import: [migrateEntry] }
|
|
20
|
+
: [migrateEntry],
|
|
21
|
+
};
|
|
22
|
+
config.output = {
|
|
23
|
+
...config.output,
|
|
24
|
+
// Per-entry filenames so main.js and migrate.js don't collide.
|
|
25
|
+
filename: '[name].js',
|
|
26
|
+
...(process.env.NODE_ENV !== 'production' && {
|
|
27
|
+
clean: true,
|
|
28
|
+
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
config.devtool = 'source-map';
|
|
32
|
+
// Update the webpack config as needed here.
|
|
33
|
+
// e.g. `config.plugins.push(new MyPlugin())`
|
|
34
|
+
return config;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "list",
|
|
57
57
|
"items": [
|
|
58
58
|
{ "value": "none", "label": "None — add a database later" },
|
|
59
|
-
{ "value": "postgres", "label": "PostgreSQL (
|
|
59
|
+
{ "value": "postgres", "label": "PostgreSQL (Drizzle)" },
|
|
60
60
|
{ "value": "mongo", "label": "MongoDB (Mongoose)" }
|
|
61
61
|
]
|
|
62
62
|
}
|
|
@@ -37,11 +37,11 @@ describe('MEVN Generator', () => {
|
|
|
37
37
|
const serviceConfig = readProjectConfiguration(host, 'test-service');
|
|
38
38
|
expect(serviceConfig.root).toBe('apps/test-service');
|
|
39
39
|
|
|
40
|
-
expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
|
|
41
|
-
const nginxConf = host.read('apps/test-app/nginx.conf').toString();
|
|
40
|
+
expect(host.exists('apps/test-app/public/nginx.conf')).toBeTruthy();
|
|
41
|
+
const nginxConf = host.read('apps/test-app/public/nginx.conf').toString();
|
|
42
42
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
43
43
|
|
|
44
44
|
expect(host.exists('apps/test-app/src/App.vue')).toBeTruthy();
|
|
45
|
-
expect(host.exists('apps/test-service/
|
|
45
|
+
expect(host.exists('apps/test-service/src/db/schema.ts')).toBeFalsy();
|
|
46
46
|
}, 30000);
|
|
47
47
|
});
|
|
@@ -41,6 +41,6 @@ describe('PEAN Generator', () => {
|
|
|
41
41
|
const nginxConf = host.read('apps/test-app/nginx.conf').toString();
|
|
42
42
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
43
43
|
|
|
44
|
-
expect(host.exists('apps/test-service/
|
|
44
|
+
expect(host.exists('apps/test-service/src/db/schema.ts')).toBeTruthy();
|
|
45
45
|
}, 30000);
|
|
46
46
|
});
|
|
@@ -41,6 +41,6 @@ describe('PERN Generator', () => {
|
|
|
41
41
|
const nginxConf = host.read('apps/test-app/nginx.conf').toString();
|
|
42
42
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
43
43
|
|
|
44
|
-
expect(host.exists('apps/test-service/
|
|
44
|
+
expect(host.exists('apps/test-service/src/db/schema.ts')).toBeTruthy();
|
|
45
45
|
}, 30000);
|
|
46
46
|
});
|
|
@@ -33,15 +33,17 @@ describe('PEVN Generator', () => {
|
|
|
33
33
|
|
|
34
34
|
const appConfig = readProjectConfiguration(host, 'test-app');
|
|
35
35
|
expect(appConfig.root).toBe('apps/test-app');
|
|
36
|
+
// The paired backend (name + port) is recorded so the sandbox generator ensures its Service first.
|
|
37
|
+
expect(appConfig.tags).toContain('adsp:proxy-service:test-service:3333');
|
|
36
38
|
|
|
37
39
|
const serviceConfig = readProjectConfiguration(host, 'test-service');
|
|
38
40
|
expect(serviceConfig.root).toBe('apps/test-service');
|
|
39
41
|
|
|
40
|
-
expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
|
|
41
|
-
const nginxConf = host.read('apps/test-app/nginx.conf').toString();
|
|
42
|
+
expect(host.exists('apps/test-app/public/nginx.conf')).toBeTruthy();
|
|
43
|
+
const nginxConf = host.read('apps/test-app/public/nginx.conf').toString();
|
|
42
44
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
43
45
|
|
|
44
46
|
expect(host.exists('apps/test-app/src/App.vue')).toBeTruthy();
|
|
45
|
-
expect(host.exists('apps/test-service/
|
|
47
|
+
expect(host.exists('apps/test-service/src/db/schema.ts')).toBeTruthy();
|
|
46
48
|
}, 30000);
|
|
47
49
|
});
|
|
@@ -117,8 +117,8 @@ describe('MyFeature', () => {
|
|
|
117
117
|
## OpenShift targets
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
nx run <%= projectName %>:sandbox # build + push to
|
|
121
|
-
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources
|
|
120
|
+
nx run <%= projectName %>:sandbox # build locally (podman) + push to GHCR + deploy
|
|
121
|
+
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources + delete the GHCR image
|
|
122
122
|
nx run <%= projectName %>:apply-envs # apply manifests to all environments
|
|
123
123
|
nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
124
124
|
```
|
|
@@ -131,3 +131,29 @@ nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
|
131
131
|
the ADSP tenant; override at runtime via deployment config
|
|
132
132
|
- `silent-check-sso.html` — required for keycloak-js silent SSO; must be served
|
|
133
133
|
from the app root
|
|
134
|
+
|
|
135
|
+
## Sandbox deployment (local build)
|
|
136
|
+
|
|
137
|
+
The `sandbox` targets are added by the nx-oc sandbox generator — run it once per app first:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
nx g @abgov/nx-oc:sandbox <%= projectName %> --sandboxProject <your-namespace>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Then `nx run <%= projectName %>:sandbox` builds the image **locally with podman**, pushes it to GHCR, and imports it into your sandbox namespace — no git push or CI wait. Prerequisites (one-time):
|
|
144
|
+
|
|
145
|
+
- `podman` installed and running (`podman machine start` on macOS).
|
|
146
|
+
- `oc` logged in, targeting your (per-user) sandbox namespace.
|
|
147
|
+
- `gh auth login` as an account with **`write:packages`** on the registry org. That account must be the **active** `gh` account when you run the target — the sandbox reads `gh auth token` for both the image push and the pull secret (no PAT is stored). Check with `gh auth status`; switch with `gh auth switch -u <account>`.
|
|
148
|
+
|
|
149
|
+
The registry (`ghcr.io/<org>`) is derived from the git remote on first run and saved in `nx.json`. The image is `<registry>/<sandboxProject>-<app>:sandbox` (namespace-prefixed to avoid collisions). If a push or import fails, it's almost always the active `gh` account lacking `write:packages` or access to the org — re-run after `gh auth switch`.
|
|
150
|
+
|
|
151
|
+
### Verify & troubleshoot a sandbox deploy
|
|
152
|
+
|
|
153
|
+
The target ends with `oc rollout status`. To confirm and diagnose:
|
|
154
|
+
|
|
155
|
+
- **Health:** `oc get pods -n <namespace> -l name=<%= projectName %>` and `curl https://$(oc get route <%= projectName %> -n <namespace> -o jsonpath='{.spec.host}')/health`.
|
|
156
|
+
- **Push or import failed:** almost always the active `gh` account lacks `write:packages` or access to the org — check `gh auth status`, then `gh auth switch -u <account>` and re-run.
|
|
157
|
+
- **Pod CrashLoopBackOff / not ready:** `oc logs -n <namespace> <pod> -c <%= projectName %>`. For a service with a database, also check the migration init container: `oc logs -n <namespace> <pod> -c <%= projectName %>-migrate`.
|
|
158
|
+
- **`podman` errors:** ensure the machine is running — `podman machine start` (macOS).
|
|
159
|
+
- **Namespace missing:** create it with `oc new-project <namespace>`.
|
|
@@ -65,7 +65,9 @@ function default_1(host, options) {
|
|
|
65
65
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
66
66
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
67
67
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
68
|
-
const { applicationGenerator: initReact } = yield Promise.resolve().then(() => require('@nx/react'))
|
|
68
|
+
const { applicationGenerator: initReact } = yield Promise.resolve().then(() => require('@nx/react')).catch(() => {
|
|
69
|
+
throw new Error("The 'react-app' generator requires the '@nx/react' plugin. Install it and re-run:\n npm i -D @nx/react");
|
|
70
|
+
});
|
|
69
71
|
// Setting strict to false because of: https://github.com/nrwl/nx/issues/8180
|
|
70
72
|
yield initReact(host, {
|
|
71
73
|
name: options.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/react-app/react-app.ts"],"names":[],"mappings":";;AAwGA,
|
|
1
|
+
{"version":3,"file":"react-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/react-app/react-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -136,8 +136,8 @@ describe('MyComponent', () => {
|
|
|
136
136
|
## OpenShift targets
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
|
-
nx run <%= projectName %>:sandbox # build + push to
|
|
140
|
-
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources
|
|
139
|
+
nx run <%= projectName %>:sandbox # build locally (podman) + push to GHCR + deploy
|
|
140
|
+
nx run <%= projectName %>:sandbox-teardown # remove sandbox resources + delete the GHCR image
|
|
141
141
|
nx run <%= projectName %>:apply-envs # apply manifests to all environments
|
|
142
142
|
nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
143
143
|
```
|
|
@@ -148,3 +148,29 @@ nx run <%= projectName %>:teardown-dev # remove from dev environment
|
|
|
148
148
|
- `environments/environment.ts` — access URL and realm are pre-configured for the ADSP tenant
|
|
149
149
|
- `silent-check-sso.html` — required for keycloak-js silent SSO; must be served from the app root
|
|
150
150
|
- `vite.config.ts` — the `isCustomElement` predicate must stay to suppress Vue warnings for `goa-*` elements
|
|
151
|
+
|
|
152
|
+
## Sandbox deployment (local build)
|
|
153
|
+
|
|
154
|
+
The `sandbox` targets are added by the nx-oc sandbox generator — run it once per app first:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
nx g @abgov/nx-oc:sandbox <%= projectName %> --sandboxProject <your-namespace>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Then `nx run <%= projectName %>:sandbox` builds the image **locally with podman**, pushes it to GHCR, and imports it into your sandbox namespace — no git push or CI wait. Prerequisites (one-time):
|
|
161
|
+
|
|
162
|
+
- `podman` installed and running (`podman machine start` on macOS).
|
|
163
|
+
- `oc` logged in, targeting your (per-user) sandbox namespace.
|
|
164
|
+
- `gh auth login` as an account with **`write:packages`** on the registry org. That account must be the **active** `gh` account when you run the target — the sandbox reads `gh auth token` for both the image push and the pull secret (no PAT is stored). Check with `gh auth status`; switch with `gh auth switch -u <account>`.
|
|
165
|
+
|
|
166
|
+
The registry (`ghcr.io/<org>`) is derived from the git remote on first run and saved in `nx.json`. The image is `<registry>/<sandboxProject>-<app>:sandbox` (namespace-prefixed to avoid collisions). If a push or import fails, it's almost always the active `gh` account lacking `write:packages` or access to the org — re-run after `gh auth switch`.
|
|
167
|
+
|
|
168
|
+
### Verify & troubleshoot a sandbox deploy
|
|
169
|
+
|
|
170
|
+
The target ends with `oc rollout status`. To confirm and diagnose:
|
|
171
|
+
|
|
172
|
+
- **Health:** `oc get pods -n <namespace> -l name=<%= projectName %>` and `curl https://$(oc get route <%= projectName %> -n <namespace> -o jsonpath='{.spec.host}')/health`.
|
|
173
|
+
- **Push or import failed:** almost always the active `gh` account lacks `write:packages` or access to the org — check `gh auth status`, then `gh auth switch -u <account>` and re-run.
|
|
174
|
+
- **Pod CrashLoopBackOff / not ready:** `oc logs -n <namespace> <pod> -c <%= projectName %>`. For a service with a database, also check the migration init container: `oc logs -n <namespace> <pod> -c <%= projectName %>-migrate`.
|
|
175
|
+
- **`podman` errors:** ensure the machine is running — `podman machine start` (macOS).
|
|
176
|
+
- **Namespace missing:** create it with `oc new-project <namespace>`.
|
|
Binary file
|
|
@@ -50,7 +50,9 @@ function default_1(host, options) {
|
|
|
50
50
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
51
51
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
52
52
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
53
|
-
const { applicationGenerator: initVue } = yield Promise.resolve().then(() => require('@nx/vue'))
|
|
53
|
+
const { applicationGenerator: initVue } = yield Promise.resolve().then(() => require('@nx/vue')).catch(() => {
|
|
54
|
+
throw new Error("The 'vue-app' generator requires the '@nx/vue' plugin. Install it and re-run:\n npm i -D @nx/vue");
|
|
55
|
+
});
|
|
54
56
|
yield initVue(host, {
|
|
55
57
|
name: options.name,
|
|
56
58
|
style: 'css',
|
|
@@ -72,11 +74,14 @@ function default_1(host, options) {
|
|
|
72
74
|
'eslint-plugin-security': '^3.0.0',
|
|
73
75
|
'eslint-plugin-no-secrets': '^2.0.0',
|
|
74
76
|
});
|
|
75
|
-
// Remove Nx scaffold files replaced by our templates.
|
|
77
|
+
// Remove Nx scaffold files replaced by our templates. The @nx/vue generator
|
|
78
|
+
// emits vite.config.mts; we provide a GoA-aware vite.config.ts, so drop the
|
|
79
|
+
// duplicate to avoid two competing configs.
|
|
76
80
|
for (const f of [
|
|
77
81
|
'src/App.vue',
|
|
78
82
|
'src/components/HelloWorld.vue',
|
|
79
83
|
'src/views/AboutView.vue',
|
|
84
|
+
'vite.config.mts',
|
|
80
85
|
]) {
|
|
81
86
|
if (host.exists(`${normalizedOptions.projectRoot}/${f}`)) {
|
|
82
87
|
host.delete(`${normalizedOptions.projectRoot}/${f}`);
|
|
@@ -90,16 +95,37 @@ function default_1(host, options) {
|
|
|
90
95
|
if (addedProxy && ((_a = config.targets.serve) === null || _a === void 0 ? void 0 : _a.options)) {
|
|
91
96
|
config.targets.serve.options = Object.assign(Object.assign({}, config.targets.serve.options), { proxyConfig: `${normalizedOptions.projectRoot}/vite.proxy.json` });
|
|
92
97
|
}
|
|
93
|
-
//
|
|
98
|
+
// nginx.conf and silent-check-sso.html live in the Vite publicDir
|
|
99
|
+
// (<projectRoot>/public) so they are emitted to the build output root — the
|
|
100
|
+
// @nx/vite:build executor ignores webpack-style `assets`. Pin outputPath to
|
|
101
|
+
// the workspace-root dist so it matches the vite config's outDir and the
|
|
102
|
+
// generated Dockerfile's COPY path.
|
|
94
103
|
if ((_b = config.targets.build) === null || _b === void 0 ? void 0 : _b.options) {
|
|
95
|
-
config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), { outputPath: `dist/${normalizedOptions.projectRoot}` });
|
|
105
|
+
}
|
|
106
|
+
// Record the paired backend (name + port) so the sandbox generator can ensure
|
|
107
|
+
// its Service exists before this frontend's nginx starts — nginx resolves
|
|
108
|
+
// proxy_pass upstreams at startup, so a missing Service would crashloop the
|
|
109
|
+
// pod. Only the Service is needed (for DNS), not the backend's deployment.
|
|
110
|
+
if (normalizedOptions.pairedProject) {
|
|
111
|
+
const pairedService = (0, devkit_1.names)(normalizedOptions.pairedProject).fileName;
|
|
112
|
+
const proxy = normalizedOptions.nginxProxies.find((p) => {
|
|
113
|
+
try {
|
|
114
|
+
return new URL(p.proxyPass).hostname === pairedService;
|
|
115
|
+
}
|
|
116
|
+
catch (_a) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
let port = 3333;
|
|
121
|
+
if (proxy) {
|
|
122
|
+
const url = new URL(proxy.proxyPass);
|
|
123
|
+
port = Number(url.port) || (url.protocol === 'https:' ? 443 : 80);
|
|
124
|
+
}
|
|
125
|
+
config.tags = [
|
|
126
|
+
...((_c = config.tags) !== null && _c !== void 0 ? _c : []),
|
|
127
|
+
`adsp:proxy-service:${pairedService}:${port}`,
|
|
128
|
+
];
|
|
103
129
|
}
|
|
104
130
|
(0, devkit_1.updateProjectConfiguration)(host, options.name, config);
|
|
105
131
|
(0, quality_1.addSemgrepTarget)(host, options.name);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":";;AAgEA,
|
|
1
|
+
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,12 +36,45 @@ describe('Vue App Generator', () => {
|
|
|
36
36
|
await generator(host, options);
|
|
37
37
|
const config = readProjectConfiguration(host, 'test');
|
|
38
38
|
expect(config.root).toBe('apps/test');
|
|
39
|
-
|
|
39
|
+
// nginx.conf + silent-check-sso live in the Vite publicDir so they end up in the build output.
|
|
40
|
+
expect(host.exists('apps/test/public/nginx.conf')).toBeTruthy();
|
|
41
|
+
expect(host.exists('apps/test/public/silent-check-sso.html')).toBeTruthy();
|
|
40
42
|
expect(host.exists('apps/test/src/main.ts')).toBeTruthy();
|
|
41
43
|
expect(host.exists('apps/test/src/App.vue')).toBeTruthy();
|
|
42
44
|
expect(host.exists('apps/test/src/router/index.ts')).toBeTruthy();
|
|
43
45
|
expect(host.exists('apps/test/src/environments/environment.ts')).toBeTruthy();
|
|
44
46
|
expect(host.exists('apps/test/vite.config.ts')).toBeTruthy();
|
|
47
|
+
// The duplicate @nx/vue-generated config is removed.
|
|
48
|
+
expect(host.exists('apps/test/vite.config.mts')).toBeFalsy();
|
|
49
|
+
// build output mirrors the workspace layout under the root dist/.
|
|
50
|
+
expect(config.targets.build.options.outputPath).toBe('dist/apps/test');
|
|
51
|
+
}, 30000);
|
|
52
|
+
|
|
53
|
+
it('index.html is at the Vite entry root and its mount target matches main.ts', async () => {
|
|
54
|
+
await generator(host, options);
|
|
55
|
+
// Vite's entry is <projectRoot>/index.html, not src/index.html — a template
|
|
56
|
+
// shipped under src/ is ignored, leaving @nx/vue's #root div while main.ts
|
|
57
|
+
// mounts #app, so nothing renders. Guard both: correct path and matched id.
|
|
58
|
+
expect(host.exists('apps/test/index.html')).toBeTruthy();
|
|
59
|
+
expect(host.exists('apps/test/src/index.html')).toBeFalsy();
|
|
60
|
+
const indexHtml = host.read('apps/test/index.html').toString();
|
|
61
|
+
const mainTs = host.read('apps/test/src/main.ts').toString();
|
|
62
|
+
const mountId = mainTs.match(/\.mount\(['"]#([\w-]+)['"]\)/)?.[1];
|
|
63
|
+
expect(mountId).toBeTruthy();
|
|
64
|
+
expect(indexHtml).toContain(`id="${mountId}"`);
|
|
65
|
+
}, 30000);
|
|
66
|
+
|
|
67
|
+
it('static assets live in public/ so Vite serves them at the referenced URLs', async () => {
|
|
68
|
+
await generator(host, options);
|
|
69
|
+
// App.vue's <goa-hero-banner backgroundurl="/assets/banner.jpg"> and
|
|
70
|
+
// index.html's favicon.ico are absolute-URL string refs, so they must be in
|
|
71
|
+
// the Vite publicDir (public/) — a src/assets file is not served at /assets.
|
|
72
|
+
const appVue = host.read('apps/test/src/App.vue').toString();
|
|
73
|
+
const bannerUrl = appVue.match(/backgroundurl="([^"]+)"/)?.[1];
|
|
74
|
+
expect(bannerUrl).toBe('/assets/banner.jpg');
|
|
75
|
+
expect(host.exists('apps/test/public/assets/banner.jpg')).toBeTruthy();
|
|
76
|
+
expect(host.exists('apps/test/src/assets/banner.jpg')).toBeFalsy();
|
|
77
|
+
expect(host.exists('apps/test/public/favicon.ico')).toBeTruthy();
|
|
45
78
|
}, 30000);
|
|
46
79
|
|
|
47
80
|
it('vite.config.ts marks goa-* elements as custom elements', async () => {
|
|
@@ -63,7 +96,7 @@ describe('Vue App Generator', () => {
|
|
|
63
96
|
...options,
|
|
64
97
|
proxy: { location: '/test/', proxyPass: 'http://test-service:3333/' },
|
|
65
98
|
});
|
|
66
|
-
const nginxConf = host.read('apps/test/nginx.conf').toString();
|
|
99
|
+
const nginxConf = host.read('apps/test/public/nginx.conf').toString();
|
|
67
100
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
68
101
|
});
|
|
69
102
|
|
|
@@ -75,7 +108,7 @@ describe('Vue App Generator', () => {
|
|
|
75
108
|
{ location: '/test2/', proxyPass: 'http://test-service2:3333/' },
|
|
76
109
|
],
|
|
77
110
|
});
|
|
78
|
-
const nginxConf = host.read('apps/test/nginx.conf').toString();
|
|
111
|
+
const nginxConf = host.read('apps/test/public/nginx.conf').toString();
|
|
79
112
|
expect(nginxConf).toContain('http://test-service:3333/');
|
|
80
113
|
expect(nginxConf).toContain('http://test-service2:3333/');
|
|
81
114
|
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
generator client {
|
|
2
|
-
provider = "prisma-client-js"
|
|
3
|
-
// Scoped output prevents multiple postgres services in the same workspace from
|
|
4
|
-
// overwriting each other's generated client in the shared node_modules.
|
|
5
|
-
output = "../src/generated/prisma"
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
datasource db {
|
|
9
|
-
provider = "postgresql"
|
|
10
|
-
url = env("DATABASE_URL")
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Add your models here.
|
|
14
|
-
// After editing, run: nx db:migrate <%= projectName %>
|
/package/src/generators/express-service/files-postgres/{prisma/migrations → drizzle}/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|