@abgov/nx-adsp 13.11.0 → 13.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/generators/express-service/express-service.js +14 -1
- package/src/generators/express-service/express-service.js.map +1 -1
- package/src/generators/express-service/express-service.spec.ts +43 -0
- package/src/generators/express-service/files/AGENTS.md__tmpl__ +11 -0
- package/src/generators/express-service/files/src/main.ts__tmpl__ +29 -2
- package/src/generators/express-service/files-mongo/src/database.ts__tmpl__ +10 -0
- package/src/generators/express-service/files-postgres/src/database.ts__tmpl__ +8 -0
- package/src/utils/quality.d.ts +54 -0
- package/src/utils/quality.js +81 -0
- package/src/utils/quality.js.map +1 -1
- package/src/utils/quality.spec.ts +151 -1
package/README.md
CHANGED
|
@@ -265,7 +265,7 @@ have a tenant yet? That picker also offers a **+ Create a new tenant** choice
|
|
|
265
265
|
`tenant-service-admin`, and (unless `tenant-service-admin`) that doesn't already own a tenant (one
|
|
266
266
|
per admin email). Picking it prompts for a name and waits for the new realm to finish provisioning
|
|
267
267
|
before continuing the login as that tenant. Requires `@abgov/adsp-cli` ^1.4.0+ (this plugin pins
|
|
268
|
-
^1.
|
|
268
|
+
^1.6.0 or later).
|
|
269
269
|
|
|
270
270
|
## Agent consultation
|
|
271
271
|
|
package/package.json
CHANGED
|
@@ -11,6 +11,10 @@ const keycloak_admin_1 = require("../../utils/keycloak-admin");
|
|
|
11
11
|
const plugin_version_1 = require("../../utils/plugin-version");
|
|
12
12
|
const quality_1 = require("../../utils/quality");
|
|
13
13
|
const init_1 = require("../init/init");
|
|
14
|
+
// Must match environment.ts.__tmpl__'s own `PORT: num({ default: ... })` —
|
|
15
|
+
// there's no --port option to source this from yet, so it's a second literal
|
|
16
|
+
// kept in sync by convention rather than a single shared value.
|
|
17
|
+
const DEFAULT_PORT = 3333;
|
|
14
18
|
function normalizeOptions(host, options) {
|
|
15
19
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
16
20
|
var _a;
|
|
@@ -39,7 +43,16 @@ function default_1(host, options) {
|
|
|
39
43
|
throw new Error("The 'express-service' generator requires the '@nx/express' plugin. Install it and re-run:\n npm i -D @nx/express");
|
|
40
44
|
});
|
|
41
45
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
42
|
-
|
|
46
|
+
// @nx/express's own Schema type doesn't declare e2eTestRunner, even though
|
|
47
|
+
// its implementation forwards it straight through to @nx/node's own
|
|
48
|
+
// applicationGenerator (which does). Assigning to an inferred-type const
|
|
49
|
+
// first, rather than passing the object literal directly, sidesteps
|
|
50
|
+
// TypeScript's excess-property check on a type that's simply incomplete.
|
|
51
|
+
const initExpressOptions = Object.assign(Object.assign({}, options), { skipFormat: true, skipPackageJson: false, linter: eslint_1.Linter.EsLint, unitTestRunner: 'jest', e2eTestRunner: 'jest', js: false, directory: normalizedOptions.projectRoot });
|
|
52
|
+
yield initExpress(host, initExpressOptions);
|
|
53
|
+
// @nx/node:e2e-project's own generated output has two bugs of its own —
|
|
54
|
+
// see fixExpressServiceE2eProject's doc comment for how each was confirmed.
|
|
55
|
+
(0, quality_1.fixExpressServiceE2eProject)(host, `${normalizedOptions.projectRoot}-e2e`, DEFAULT_PORT);
|
|
43
56
|
(0, devkit_1.addDependenciesToPackageJson)(host, Object.assign(Object.assign({ '@abgov/adsp-service-sdk': '^2.23.0', '@asteasolutions/zod-to-openapi': '^7.3.4', 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'
|
|
44
57
|
? { 'drizzle-orm': '^0.44.0', pg: '^8.11.0' }
|
|
45
58
|
: {})), (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'
|
|
@@ -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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"}
|
|
@@ -99,6 +99,31 @@ describe('Express Service Generator', () => {
|
|
|
99
99
|
expect(agents).toContain('search_sdk_reference');
|
|
100
100
|
}, 60000);
|
|
101
101
|
|
|
102
|
+
it('scaffolds a Jest e2e project with the port default fixed to match environment.ts', async () => {
|
|
103
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
104
|
+
await generator(host, options);
|
|
105
|
+
|
|
106
|
+
const e2eRoot = 'apps/test-e2e';
|
|
107
|
+
expect(host.exists(`${e2eRoot}/jest.config.cts`)).toBeTruthy();
|
|
108
|
+
|
|
109
|
+
// @nx/node:e2e-project's own template hardcodes a fallback port of 3000;
|
|
110
|
+
// nx-adsp's own environment.ts.__tmpl__ defaults PORT to 3333. Which of
|
|
111
|
+
// @nx/node's two jest.config.cts shapes (ts-jest vs. @swc/jest — see
|
|
112
|
+
// fixExpressServiceE2eProject's own tests for that variance) this
|
|
113
|
+
// workspace produces isn't deterministic here, but the port fix applies
|
|
114
|
+
// to both identically.
|
|
115
|
+
for (const file of ['global-setup.ts', 'global-teardown.ts']) {
|
|
116
|
+
const content = host.read(`${e2eRoot}/src/support/${file}`).toString();
|
|
117
|
+
expect(content).not.toContain(': 3000');
|
|
118
|
+
expect(content).toContain(': 3333');
|
|
119
|
+
}
|
|
120
|
+
const testSetup = host
|
|
121
|
+
.read(`${e2eRoot}/src/support/test-setup.ts`)
|
|
122
|
+
.toString();
|
|
123
|
+
expect(testSetup).not.toContain("?? '3000'");
|
|
124
|
+
expect(testSetup).toContain("?? '3333'");
|
|
125
|
+
}, 60000);
|
|
126
|
+
|
|
102
127
|
it('merges .mcp.json without clobbering other servers or a customized entry', async () => {
|
|
103
128
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
104
129
|
writeJson(host, '.mcp.json', {
|
|
@@ -167,6 +192,13 @@ describe('Express Service Generator', () => {
|
|
|
167
192
|
const database = host.read('apps/test/src/database.ts').toString();
|
|
168
193
|
expect(database).toContain('drizzle-orm/node-postgres');
|
|
169
194
|
expect(database).toContain('closeDatabase');
|
|
195
|
+
expect(database).toContain('isDatabaseReady');
|
|
196
|
+
|
|
197
|
+
// Readiness (DB-checked) is wired up separately from liveness — a DB
|
|
198
|
+
// outage should hold traffic, not restart a pod that can't fix it.
|
|
199
|
+
const mainTs = host.read('apps/test/src/main.ts').toString();
|
|
200
|
+
expect(mainTs).toContain('isDatabaseReady');
|
|
201
|
+
expect(mainTs).toContain("app.get('/health/ready'");
|
|
170
202
|
|
|
171
203
|
// webpack emits a second bundle (migrate.js) for the deploy init container.
|
|
172
204
|
const webpackConfig = host.read('apps/test/webpack.config.js').toString();
|
|
@@ -208,6 +240,11 @@ describe('Express Service Generator', () => {
|
|
|
208
240
|
|
|
209
241
|
const database = host.read('apps/test/src/database.ts').toString();
|
|
210
242
|
expect(database).toContain('mongoose');
|
|
243
|
+
expect(database).toContain('isDatabaseReady');
|
|
244
|
+
|
|
245
|
+
const mainTs = host.read('apps/test/src/main.ts').toString();
|
|
246
|
+
expect(mainTs).toContain('isDatabaseReady');
|
|
247
|
+
expect(mainTs).toContain("app.get('/health/ready'");
|
|
211
248
|
|
|
212
249
|
const config = readProjectConfiguration(host, 'test');
|
|
213
250
|
expect(config.targets['dev-db']).toBeTruthy();
|
|
@@ -224,6 +261,12 @@ describe('Express Service Generator', () => {
|
|
|
224
261
|
expect(host.exists('apps/test/src/database.ts')).toBeFalsy();
|
|
225
262
|
expect(host.exists('apps/test/scripts/dev-db.sh')).toBeFalsy();
|
|
226
263
|
|
|
264
|
+
// No database to check readiness against — don't emit a route that
|
|
265
|
+
// would always report ready regardless of anything real.
|
|
266
|
+
const mainTs = host.read('apps/test/src/main.ts').toString();
|
|
267
|
+
expect(mainTs).not.toContain('/health/ready');
|
|
268
|
+
expect(mainTs).not.toContain('isDatabaseReady');
|
|
269
|
+
|
|
227
270
|
const config = readProjectConfiguration(host, 'test');
|
|
228
271
|
expect(config.targets['dev-db']).toBeFalsy();
|
|
229
272
|
expect(
|
|
@@ -68,6 +68,17 @@ need project-scoped MCP enabled. The server runs via `npx` and needs no credenti
|
|
|
68
68
|
| `src/database.ts` | Mongoose connection helpers — `connectDatabase()` and `disconnectDatabase()` |
|
|
69
69
|
<% } %>
|
|
70
70
|
|
|
71
|
+
## Health and readiness checks
|
|
72
|
+
|
|
73
|
+
`/health` and (<% if (database !== 'none') { %>already, since this service has a database<% } else { %>if this service later gains a database or another required dependency<% } %>) `/health/ready` are two different questions, mapped to the two different OpenShift probes for a reason — conflating them into one endpoint means an outage in a downstream dependency gets treated as "restart this pod," which does not fix the outage and just adds a restart loop on top of it:
|
|
74
|
+
|
|
75
|
+
- **`/health` — liveness: is the process itself up.** Stays dependency-free on purpose. Don't add a check here for anything this service depends on but doesn't own.
|
|
76
|
+
<% if (database !== 'none') { %>
|
|
77
|
+
- **`/health/ready` — readiness: can this pod actually serve requests right now.** Already checks the database (`isDatabaseReady()` in `src/database.ts`) — a real round-trip query, not just a pool-state flag. **Extend this handler, not `/health`, for any other resource this service can't function without** — another required downstream API, a message broker, a required ADSP capability beyond what `healthCheck()` already covers — following the same reasoning: a dependency being down should hold traffic (503, readiness), not trigger a pod restart (liveness) that can't fix a problem outside this pod.
|
|
78
|
+
<% } else { %>
|
|
79
|
+
- If this service later gains a database or another hard dependency, add a `/health/ready` route that checks it (see `@abgov/nx-adsp:express-service --database postgres|mongo` for the shipped pattern) and point the deployment's `readinessProbe` at it instead of `/health` — don't check dependencies from the liveness route.
|
|
80
|
+
<% } %>
|
|
81
|
+
|
|
71
82
|
## SDK capabilities
|
|
72
83
|
|
|
73
84
|
`initializeService()` returns `capabilities`:
|
|
@@ -7,9 +7,9 @@ import helmet from 'helmet';
|
|
|
7
7
|
import passport from 'passport';
|
|
8
8
|
import { Strategy as AnonymousStrategy } from 'passport-anonymous';
|
|
9
9
|
<% if (database === 'postgres') { %>
|
|
10
|
-
import { closeDatabase } from './database';
|
|
10
|
+
import { closeDatabase, isDatabaseReady } from './database';
|
|
11
11
|
<% } else if (database === 'mongo') { %>
|
|
12
|
-
import { connectDatabase, disconnectDatabase } from './database';
|
|
12
|
+
import { connectDatabase, disconnectDatabase, isDatabaseReady } from './database';
|
|
13
13
|
<% } %>
|
|
14
14
|
import { environment } from './environment';
|
|
15
15
|
import { exampleEventDefinition } from './events';
|
|
@@ -54,10 +54,34 @@ async function initializeApp() {
|
|
|
54
54
|
passport.use('tenant', tenantStrategy);
|
|
55
55
|
app.use(passport.initialize());
|
|
56
56
|
|
|
57
|
+
// Liveness — is the process itself up. Deliberately stays dependency-free
|
|
58
|
+
// (platform reachability, not this app's own resources) so an outage in a
|
|
59
|
+
// downstream dependency doesn't get treated as "restart this pod," which
|
|
60
|
+
// wouldn't fix the outage and would just cause a needless restart loop.
|
|
57
61
|
app.get('/health', async (_req, res) => {
|
|
58
62
|
const platform = await healthCheck();
|
|
59
63
|
res.json({ ...platform });
|
|
60
64
|
});
|
|
65
|
+
<% if (database !== 'none') { %>
|
|
66
|
+
// Readiness — can this pod actually serve requests right now. Checks the
|
|
67
|
+
// database because this service can't do anything useful without it; 503
|
|
68
|
+
// tells the platform to hold traffic rather than route it somewhere that
|
|
69
|
+
// will 500. Extend this handler (not /health above) for any other resource
|
|
70
|
+
// this service can't function without — another required downstream API,
|
|
71
|
+
// a message broker, etc. — following the same "hold traffic, don't
|
|
72
|
+
// restart" reasoning; see AGENTS.md's "Health and readiness checks".
|
|
73
|
+
app.get('/health/ready', async (_req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
await isDatabaseReady();
|
|
76
|
+
res.json({ ready: true });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
res.status(503).json({
|
|
79
|
+
ready: false,
|
|
80
|
+
error: err instanceof Error ? err.message : String(err),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
<% } %>
|
|
61
85
|
|
|
62
86
|
// Generated once from the routers' registry.registerPath() calls (see
|
|
63
87
|
// routes/example.ts) — every router imported above has already registered
|
|
@@ -85,6 +109,9 @@ async function initializeApp() {
|
|
|
85
109
|
_links: {
|
|
86
110
|
self: { href: new URL(req.originalUrl, rootUrl).href },
|
|
87
111
|
health: { href: new URL('/health', rootUrl).href },
|
|
112
|
+
<% if (database !== 'none') { %>
|
|
113
|
+
ready: { href: new URL('/health/ready', rootUrl).href },
|
|
114
|
+
<% } %>
|
|
88
115
|
api: { href: new URL('/<%= projectName %>/v1', rootUrl).href },
|
|
89
116
|
docs: { href: new URL('/swagger/docs/v1', rootUrl).href },
|
|
90
117
|
},
|
|
@@ -9,3 +9,13 @@ export async function connectDatabase(): Promise<void> {
|
|
|
9
9
|
export async function disconnectDatabase(): Promise<void> {
|
|
10
10
|
await mongoose.disconnect();
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
// A real ping, not just `readyState === 1` — the connection can report
|
|
14
|
+
// "connected" while actually stale. Used by /health/ready, not by `main.ts`
|
|
15
|
+
// directly.
|
|
16
|
+
export async function isDatabaseReady(): Promise<void> {
|
|
17
|
+
if (!mongoose.connection.db) {
|
|
18
|
+
throw new Error('Database connection not established.');
|
|
19
|
+
}
|
|
20
|
+
await mongoose.connection.db.admin().ping();
|
|
21
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
1
2
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
2
3
|
import { Pool } from 'pg';
|
|
3
4
|
|
|
@@ -12,3 +13,10 @@ const pool = new Pool({ connectionString: environment.DATABASE_URL });
|
|
|
12
13
|
export const db = drizzle(pool, { schema });
|
|
13
14
|
|
|
14
15
|
export const closeDatabase = (): Promise<void> => pool.end();
|
|
16
|
+
|
|
17
|
+
// A trivial round-trip query, not just a pool-state check — proves the
|
|
18
|
+
// database is actually reachable and answering, the same reason `pg_isready`
|
|
19
|
+
// exists. Used by /health/ready, not by `main.ts` directly.
|
|
20
|
+
export const isDatabaseReady = async (): Promise<void> => {
|
|
21
|
+
await db.execute(sql`SELECT 1`);
|
|
22
|
+
};
|
package/src/utils/quality.d.ts
CHANGED
|
@@ -49,6 +49,60 @@ export declare function addSemgrepTarget(host: Tree, projectName: string): void;
|
|
|
49
49
|
* Idempotent and a no-op if the config lacks a webServer or is already guarded.
|
|
50
50
|
*/
|
|
51
51
|
export declare function guardPlaywrightWebServer(host: Tree, e2eProjectRoot: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Fixes two bugs that surface when e2e testing is added to express-service,
|
|
54
|
+
* confirmed by actually running the generated `e2e` target rather than just
|
|
55
|
+
* inspecting the templates. The two have different owners — kept together
|
|
56
|
+
* here only because both need fixing at the same call site, not because
|
|
57
|
+
* they share a cause:
|
|
58
|
+
*
|
|
59
|
+
* 1. UPSTREAM BUG, not nx-adsp's — lives entirely inside `@nx/node`'s own
|
|
60
|
+
* `e2e-project` generator (triggered here via `e2eTestRunner: 'jest'` on
|
|
61
|
+
* the express application generator). Nothing about nx-adsp's own code
|
|
62
|
+
* causes it, and it would reproduce in a plain `@nx/node`/`@nx/express`
|
|
63
|
+
* workspace with no nx-adsp involved at all. Kept as a local workaround
|
|
64
|
+
* anyway since nx-adsp doesn't control Nx's release cadence and a safe
|
|
65
|
+
* no-op against already-correct output (see below) costs nothing if
|
|
66
|
+
* upstream fixes it later — worth reporting to `nrwl/nx` regardless.
|
|
67
|
+
*
|
|
68
|
+
* `@nx/node:e2e-project` writes one of two shapes for `jest.config.cts`
|
|
69
|
+
* depending on whether `@swc/jest` is present in the workspace — not
|
|
70
|
+
* necessarily for this project itself; a Vue/React app elsewhere in a
|
|
71
|
+
* multi-app workspace pulling in `@swc/core` for its own build is enough
|
|
72
|
+
* to flip it. The `@swc/jest` shape is `export default {...}` with a
|
|
73
|
+
* top-level `import` — ESM syntax in a `.cts` file, which Node/ts-jest/SWC
|
|
74
|
+
* always treat as CommonJS regardless of the workspace's own
|
|
75
|
+
* `package.json` "type". Jest's own config loader (unlike its `transform`
|
|
76
|
+
* pipeline, which only applies to *test* files) can't reconcile the two,
|
|
77
|
+
* so the config fails to parse before a single test runs — every target
|
|
78
|
+
* that reads it breaks outright with `SyntaxError: Cannot use import
|
|
79
|
+
* statement outside a module`. Rewritten to `const {...} = require(...)`
|
|
80
|
+
* / `module.exports = {...}`, matching both the pattern the service's own
|
|
81
|
+
* `jest.config.cts` already uses correctly and the ts-jest shape
|
|
82
|
+
* `@nx/node` writes when `@swc/jest` isn't present — which needs no fix,
|
|
83
|
+
* so the two replaces below are safe no-ops against it.
|
|
84
|
+
*
|
|
85
|
+
* (`src/support/*.ts` also import at the top level, but — verified by
|
|
86
|
+
* running the target — those are plain `.ts` files that DO go through
|
|
87
|
+
* the config's `transform` pipeline, so they don't need this fix; only
|
|
88
|
+
* the config file itself, read before that pipeline exists, does.)
|
|
89
|
+
*
|
|
90
|
+
* 2. OUR OWN BUG, not Nx's — a convention mismatch entirely on nx-adsp's
|
|
91
|
+
* side, not a defect in the upstream generator. `@nx/node:e2e-project`'s
|
|
92
|
+
* fallback port of `3000` is a reasonable generic default with no
|
|
93
|
+
* knowledge of any particular caller; it's only wrong here because
|
|
94
|
+
* nx-adsp's own `environment.ts.__tmpl__` defaults `PORT` to `3333`
|
|
95
|
+
* instead (matching `@nx/express`'s own scaffolded `main.ts` before
|
|
96
|
+
* nx-adsp replaces it), and until this fix nothing reconciled the two.
|
|
97
|
+
* There's no upstream fix to wait for — reconciling nx-adsp's own port
|
|
98
|
+
* convention with whatever generates the e2e project is squarely
|
|
99
|
+
* nx-adsp's job. Confirmed via a real run:
|
|
100
|
+
* the express app logs `Listening at http://localhost:3333`, while
|
|
101
|
+
* `global-setup.ts` waits on `3000` and never sees it open — the target
|
|
102
|
+
* hangs, then fails with `Jest: Got error running globalSetup ...
|
|
103
|
+
* reason: [AggregateError]`, no port number anywhere in the message.
|
|
104
|
+
*/
|
|
105
|
+
export declare function fixExpressServiceE2eProject(host: Tree, e2eProjectRoot: string, port: number): void;
|
|
52
106
|
export declare function addVsCodeSettings(host: Tree): void;
|
|
53
107
|
/**
|
|
54
108
|
* Registers the @abgov/adsp-sdk-mcp-server MCP server in the workspace-root
|
package/src/utils/quality.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.addEslintQualityRules = addEslintQualityRules;
|
|
|
4
4
|
exports.addJestCoverageConfig = addJestCoverageConfig;
|
|
5
5
|
exports.addSemgrepTarget = addSemgrepTarget;
|
|
6
6
|
exports.guardPlaywrightWebServer = guardPlaywrightWebServer;
|
|
7
|
+
exports.fixExpressServiceE2eProject = fixExpressServiceE2eProject;
|
|
7
8
|
exports.addVsCodeSettings = addVsCodeSettings;
|
|
8
9
|
exports.addAdspMcpServer = addAdspMcpServer;
|
|
9
10
|
const devkit_1 = require("@nx/devkit");
|
|
@@ -126,6 +127,86 @@ function guardPlaywrightWebServer(host, e2eProjectRoot) {
|
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Fixes two bugs that surface when e2e testing is added to express-service,
|
|
132
|
+
* confirmed by actually running the generated `e2e` target rather than just
|
|
133
|
+
* inspecting the templates. The two have different owners — kept together
|
|
134
|
+
* here only because both need fixing at the same call site, not because
|
|
135
|
+
* they share a cause:
|
|
136
|
+
*
|
|
137
|
+
* 1. UPSTREAM BUG, not nx-adsp's — lives entirely inside `@nx/node`'s own
|
|
138
|
+
* `e2e-project` generator (triggered here via `e2eTestRunner: 'jest'` on
|
|
139
|
+
* the express application generator). Nothing about nx-adsp's own code
|
|
140
|
+
* causes it, and it would reproduce in a plain `@nx/node`/`@nx/express`
|
|
141
|
+
* workspace with no nx-adsp involved at all. Kept as a local workaround
|
|
142
|
+
* anyway since nx-adsp doesn't control Nx's release cadence and a safe
|
|
143
|
+
* no-op against already-correct output (see below) costs nothing if
|
|
144
|
+
* upstream fixes it later — worth reporting to `nrwl/nx` regardless.
|
|
145
|
+
*
|
|
146
|
+
* `@nx/node:e2e-project` writes one of two shapes for `jest.config.cts`
|
|
147
|
+
* depending on whether `@swc/jest` is present in the workspace — not
|
|
148
|
+
* necessarily for this project itself; a Vue/React app elsewhere in a
|
|
149
|
+
* multi-app workspace pulling in `@swc/core` for its own build is enough
|
|
150
|
+
* to flip it. The `@swc/jest` shape is `export default {...}` with a
|
|
151
|
+
* top-level `import` — ESM syntax in a `.cts` file, which Node/ts-jest/SWC
|
|
152
|
+
* always treat as CommonJS regardless of the workspace's own
|
|
153
|
+
* `package.json` "type". Jest's own config loader (unlike its `transform`
|
|
154
|
+
* pipeline, which only applies to *test* files) can't reconcile the two,
|
|
155
|
+
* so the config fails to parse before a single test runs — every target
|
|
156
|
+
* that reads it breaks outright with `SyntaxError: Cannot use import
|
|
157
|
+
* statement outside a module`. Rewritten to `const {...} = require(...)`
|
|
158
|
+
* / `module.exports = {...}`, matching both the pattern the service's own
|
|
159
|
+
* `jest.config.cts` already uses correctly and the ts-jest shape
|
|
160
|
+
* `@nx/node` writes when `@swc/jest` isn't present — which needs no fix,
|
|
161
|
+
* so the two replaces below are safe no-ops against it.
|
|
162
|
+
*
|
|
163
|
+
* (`src/support/*.ts` also import at the top level, but — verified by
|
|
164
|
+
* running the target — those are plain `.ts` files that DO go through
|
|
165
|
+
* the config's `transform` pipeline, so they don't need this fix; only
|
|
166
|
+
* the config file itself, read before that pipeline exists, does.)
|
|
167
|
+
*
|
|
168
|
+
* 2. OUR OWN BUG, not Nx's — a convention mismatch entirely on nx-adsp's
|
|
169
|
+
* side, not a defect in the upstream generator. `@nx/node:e2e-project`'s
|
|
170
|
+
* fallback port of `3000` is a reasonable generic default with no
|
|
171
|
+
* knowledge of any particular caller; it's only wrong here because
|
|
172
|
+
* nx-adsp's own `environment.ts.__tmpl__` defaults `PORT` to `3333`
|
|
173
|
+
* instead (matching `@nx/express`'s own scaffolded `main.ts` before
|
|
174
|
+
* nx-adsp replaces it), and until this fix nothing reconciled the two.
|
|
175
|
+
* There's no upstream fix to wait for — reconciling nx-adsp's own port
|
|
176
|
+
* convention with whatever generates the e2e project is squarely
|
|
177
|
+
* nx-adsp's job. Confirmed via a real run:
|
|
178
|
+
* the express app logs `Listening at http://localhost:3333`, while
|
|
179
|
+
* `global-setup.ts` waits on `3000` and never sees it open — the target
|
|
180
|
+
* hangs, then fails with `Jest: Got error running globalSetup ...
|
|
181
|
+
* reason: [AggregateError]`, no port number anywhere in the message.
|
|
182
|
+
*/
|
|
183
|
+
function fixExpressServiceE2eProject(host, e2eProjectRoot, port) {
|
|
184
|
+
const jestConfigPath = `${e2eProjectRoot}/jest.config.cts`;
|
|
185
|
+
if (host.exists(jestConfigPath)) {
|
|
186
|
+
const cfg = host
|
|
187
|
+
.read(jestConfigPath)
|
|
188
|
+
.toString()
|
|
189
|
+
.replace(/^import \{([^}]+)\} from '([^']+)';$/m, "const {$1} = require('$2');")
|
|
190
|
+
.replace(/^export default \{/m, 'module.exports = {');
|
|
191
|
+
host.write(jestConfigPath, cfg);
|
|
192
|
+
}
|
|
193
|
+
for (const file of ['global-setup.ts', 'global-teardown.ts']) {
|
|
194
|
+
const path = `${e2eProjectRoot}/src/support/${file}`;
|
|
195
|
+
if (!host.exists(path))
|
|
196
|
+
continue;
|
|
197
|
+
host.write(path, host
|
|
198
|
+
.read(path)
|
|
199
|
+
.toString()
|
|
200
|
+
.replace(/process\.env\.PORT \? Number\(process\.env\.PORT\) : 3000/, `process.env.PORT ? Number(process.env.PORT) : ${port}`));
|
|
201
|
+
}
|
|
202
|
+
const testSetupPath = `${e2eProjectRoot}/src/support/test-setup.ts`;
|
|
203
|
+
if (host.exists(testSetupPath)) {
|
|
204
|
+
host.write(testSetupPath, host
|
|
205
|
+
.read(testSetupPath)
|
|
206
|
+
.toString()
|
|
207
|
+
.replace(/process\.env\.PORT \?\? '3000'/, `process.env.PORT ?? '${port}'`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
129
210
|
function addVsCodeSettings(host) {
|
|
130
211
|
const settingsPath = '.vscode/settings.json';
|
|
131
212
|
const settings = {
|
package/src/utils/quality.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/quality.ts"],"names":[],"mappings":";;AAgBA,sDA0CC;AAiBD,sDAgBC;AAOD,4CAeC;AAoBD,4DAmBC;AAED,8CAkBC;AAiBD,4CAqBC;
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/quality.ts"],"names":[],"mappings":";;AAgBA,sDA0CC;AAiBD,sDAgBC;AAOD,4CAeC;AAoBD,4DAmBC;AAuDD,kEA8CC;AAED,8CAkBC;AAiBD,4CAqBC;AAvTD,uCAMoB;AAEpB;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CACnC,IAAU,EACV,WAAmB,EACnB,aAAuB;;IAEvB,MAAM,UAAU,GAAG,GAAG,WAAW,oBAAoB,CAAC;IACtD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;QAAE,OAAO;IAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClD,MAAM,eAAe,GAAG,QAAQ,CAAC,KAAK,CACpC,6CAA6C,CAC9C,CAAC;IACF,MAAM,cAAc,GAAG,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAG,CAAC,CAAC,mCAAI,yBAAyB,CAAC;IACzE,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEnE,IAAI,CAAC,KAAK,CACR,UAAU,EACV,2BAA2B,cAAc;;;;;;;;;;;;;;;cAe/B,YAAY;;;;;;;;CAQzB,CACE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,qBAAqB,CAAC,IAAU,EAAE,WAAmB;IACnE,MAAM,QAAQ,GAAG,GAAG,WAAW,kBAAkB,CAAC;IAClD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO;IAEnC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChD,8EAA8E;IAC9E,iEAAiE;IACjE,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAC/B,uCAAuC,EACvC,gJAAgJ,CACjJ,CAAC;IACF,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,IAAU,EAAE,WAAmB;IAC9D,MAAM,MAAM,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,CAAC,OAAO,mCACT,MAAM,CAAC,OAAO,KACjB,OAAO,EAAE;YACP,QAAQ,EAAE,iBAAiB;YAC3B,MAAM,EAAE,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,IAAI;YACX,OAAO,EAAE;gBACP,OAAO,EAAE,iDAAiD;gBAC1D,GAAG,EAAE,eAAe;aACrB;SACF,GACF,CAAC;IACF,IAAA,mCAA0B,EAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,wBAAwB,CACtC,IAAU,EACV,cAAsB;IAEtB,KAAK,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,GAAG,cAAc,sBAAsB,GAAG,EAAE,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YAAE,SAAS;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7C,IAAI,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;YACvE,OAAO;QACT,IAAI,CAAC,KAAK,CACR,UAAU,EACV,GAAG,CAAC,OAAO,CACT,cAAc,EACd,iDAAiD,CAClD,CACF,CAAC;QACF,OAAO;IACT,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,SAAgB,2BAA2B,CACzC,IAAU,EACV,cAAsB,EACtB,IAAY;IAEZ,MAAM,cAAc,GAAG,GAAG,cAAc,kBAAkB,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,IAAI;aACb,IAAI,CAAC,cAAc,CAAC;aACpB,QAAQ,EAAE;aACV,OAAO,CACN,uCAAuC,EACvC,6BAA6B,CAC9B;aACA,OAAO,CAAC,qBAAqB,EAAE,oBAAoB,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,GAAG,cAAc,gBAAgB,IAAI,EAAE,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,SAAS;QACjC,IAAI,CAAC,KAAK,CACR,IAAI,EACJ,IAAI;aACD,IAAI,CAAC,IAAI,CAAC;aACV,QAAQ,EAAE;aACV,OAAO,CACN,2DAA2D,EAC3D,iDAAiD,IAAI,EAAE,CACxD,CACJ,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,cAAc,4BAA4B,CAAC;IACpE,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CACR,aAAa,EACb,IAAI;aACD,IAAI,CAAC,aAAa,CAAC;aACnB,QAAQ,EAAE;aACV,OAAO,CACN,gCAAgC,EAChC,wBAAwB,IAAI,GAAG,CAChC,CACJ,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,iBAAiB,CAAC,IAAU;IAC1C,MAAM,YAAY,GAAG,uBAAuB,CAAC;IAC7C,MAAM,QAAQ,GAAG;QACf,qBAAqB,EAAE,IAAI;QAC3B,yBAAyB,EAAE,wBAAwB;QACnD,0BAA0B,EAAE;YAC1B,sBAAsB,EAAE,UAAU;SACnC;KACF,CAAC;IAEF,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,IAAA,mBAAU,EAAC,IAAI,EAAE,YAAY,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,iCACxC,QAAQ,GACR,QAAQ,EACX,CAAC,CAAC;IACN,CAAC;SAAM,CAAC;QACN,IAAA,kBAAS,EAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB,CAAC,IAAU;IACzC,MAAM,OAAO,GAAG,WAAW,CAAC;IAC5B,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,4BAA4B,CAAC,EAAE,CAAC;IAE9E,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,IAAA,mBAAU,EAAC,IAAI,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;;YACrC,MAAM,UAAU,qBAAQ,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,UAAU,mCAAI,EAAE,CAAC,CAAE,CAAC;YACvD,4EAA4E;YAC5E,yEAAyE;YACzE,UAAU,CAAC,UAAU,CAAC,GAAG,MAAA,UAAU,CAAC,UAAU,CAAC,mCAAI,MAAM,CAAC;YAC1D,uCAAY,QAAQ,KAAE,UAAU,IAAG;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,IAAA,kBAAS,EAAC,IAAI,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,CAAC,GAAG,CACT,gEAAgE;QAC9D,iFAAiF;QACjF,+EAA+E,CAClF,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Tree } from '@nx/devkit';
|
|
2
2
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
3
|
-
import { addJestCoverageConfig } from './quality';
|
|
3
|
+
import { addJestCoverageConfig, fixExpressServiceE2eProject } from './quality';
|
|
4
4
|
|
|
5
5
|
function makeConfig(coverageDirectoryLine: string): string {
|
|
6
6
|
return [
|
|
@@ -69,3 +69,153 @@ describe('addJestCoverageConfig', () => {
|
|
|
69
69
|
expect(tree.exists('apps/none/jest.config.cts')).toBe(false);
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
describe('fixExpressServiceE2eProject', () => {
|
|
74
|
+
let tree: Tree;
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Byte-for-byte what @nx/node:e2e-project actually writes when the
|
|
80
|
+
// workspace has @swc/jest available — confirmed via a real `nx e2e` run,
|
|
81
|
+
// which failed to parse with exactly this shape.
|
|
82
|
+
const SWC_JEST_CONFIG = `/* eslint-disable */
|
|
83
|
+
import { readFileSync } from 'fs';
|
|
84
|
+
|
|
85
|
+
// Reading the SWC compilation config for the spec files
|
|
86
|
+
const swcJestConfig = JSON.parse(
|
|
87
|
+
readFileSync(\`\${__dirname}/.spec.swcrc\`, 'utf-8'),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves
|
|
91
|
+
swcJestConfig.swcrc = false;
|
|
92
|
+
|
|
93
|
+
export default {
|
|
94
|
+
displayName: '@org/test-service-e2e',
|
|
95
|
+
preset: '../jest.preset.js',
|
|
96
|
+
globalSetup: '<rootDir>/src/support/global-setup.ts',
|
|
97
|
+
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
|
|
98
|
+
setupFiles: ['<rootDir>/src/support/test-setup.ts'],
|
|
99
|
+
testEnvironment: 'node',
|
|
100
|
+
transform: {
|
|
101
|
+
'^.+\\\\.[tj]s$': ['@swc/jest', swcJestConfig],
|
|
102
|
+
},
|
|
103
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
104
|
+
coverageDirectory: 'test-output/jest/coverage',
|
|
105
|
+
};
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
// What @nx/node:e2e-project writes when the workspace uses ts-jest instead
|
|
109
|
+
// (this repo's own case) — already valid CommonJS, nothing to fix.
|
|
110
|
+
const TS_JEST_CONFIG = `module.exports = {
|
|
111
|
+
displayName: 'test-e2e',
|
|
112
|
+
preset: '../../jest.preset.js',
|
|
113
|
+
globalSetup: '<rootDir>/src/support/global-setup.ts',
|
|
114
|
+
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
|
|
115
|
+
setupFiles: ['<rootDir>/src/support/test-setup.ts'],
|
|
116
|
+
testEnvironment: 'node',
|
|
117
|
+
transform: {
|
|
118
|
+
'^.+\\\\.[tj]s$': ['ts-jest', {
|
|
119
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
120
|
+
}],
|
|
121
|
+
},
|
|
122
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
123
|
+
coverageDirectory: '../../coverage/test-e2e',
|
|
124
|
+
};
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const GLOBAL_SETUP = `import { waitForPortOpen } from '@nx/node/utils';
|
|
128
|
+
|
|
129
|
+
/* eslint-disable */
|
|
130
|
+
var __TEARDOWN_MESSAGE__: string;
|
|
131
|
+
|
|
132
|
+
module.exports = async function () {
|
|
133
|
+
console.log('\\nSetting up...\\n');
|
|
134
|
+
|
|
135
|
+
const host = process.env.HOST ?? 'localhost';
|
|
136
|
+
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
|
|
137
|
+
await waitForPortOpen(port, { host });
|
|
138
|
+
|
|
139
|
+
globalThis.__TEARDOWN_MESSAGE__ = '\\nTearing down...\\n';
|
|
140
|
+
};
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const GLOBAL_TEARDOWN = `import { killPort } from '@nx/node/utils';
|
|
144
|
+
/* eslint-disable */
|
|
145
|
+
|
|
146
|
+
module.exports = async function () {
|
|
147
|
+
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
|
|
148
|
+
await killPort(port);
|
|
149
|
+
console.log(globalThis.__TEARDOWN_MESSAGE__);
|
|
150
|
+
};
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const TEST_SETUP = `/* eslint-disable */
|
|
154
|
+
import axios from 'axios';
|
|
155
|
+
|
|
156
|
+
module.exports = async function () {
|
|
157
|
+
const host = process.env.HOST ?? 'localhost';
|
|
158
|
+
const port = process.env.PORT ?? '3000';
|
|
159
|
+
axios.defaults.baseURL = \`http://\${host}:\${port}\`;
|
|
160
|
+
};
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
function writeE2eProject(root: string, jestConfig: string): void {
|
|
164
|
+
tree.write(`${root}/jest.config.cts`, jestConfig);
|
|
165
|
+
tree.write(`${root}/src/support/global-setup.ts`, GLOBAL_SETUP);
|
|
166
|
+
tree.write(`${root}/src/support/global-teardown.ts`, GLOBAL_TEARDOWN);
|
|
167
|
+
tree.write(`${root}/src/support/test-setup.ts`, TEST_SETUP);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
it('rewrites the @swc/jest variant of jest.config.cts to CommonJS', () => {
|
|
171
|
+
writeE2eProject('apps/svc-e2e', SWC_JEST_CONFIG);
|
|
172
|
+
|
|
173
|
+
fixExpressServiceE2eProject(tree, 'apps/svc-e2e', 3333);
|
|
174
|
+
|
|
175
|
+
const out = tree.read('apps/svc-e2e/jest.config.cts').toString();
|
|
176
|
+
expect(out).not.toContain('export default');
|
|
177
|
+
expect(out).not.toMatch(/^import /m);
|
|
178
|
+
expect(out).toContain('module.exports = {');
|
|
179
|
+
expect(out).toContain("const { readFileSync } = require('fs');");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('leaves the ts-jest variant of jest.config.cts untouched (already valid CommonJS)', () => {
|
|
183
|
+
writeE2eProject('apps/svc-e2e', TS_JEST_CONFIG);
|
|
184
|
+
|
|
185
|
+
fixExpressServiceE2eProject(tree, 'apps/svc-e2e', 3333);
|
|
186
|
+
|
|
187
|
+
expect(tree.read('apps/svc-e2e/jest.config.cts').toString()).toBe(
|
|
188
|
+
TS_JEST_CONFIG,
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('fixes the hardcoded port default in all three support files', () => {
|
|
193
|
+
writeE2eProject('apps/svc-e2e', TS_JEST_CONFIG);
|
|
194
|
+
|
|
195
|
+
fixExpressServiceE2eProject(tree, 'apps/svc-e2e', 3333);
|
|
196
|
+
|
|
197
|
+
const setup = tree
|
|
198
|
+
.read('apps/svc-e2e/src/support/global-setup.ts')
|
|
199
|
+
.toString();
|
|
200
|
+
expect(setup).toContain(
|
|
201
|
+
'process.env.PORT ? Number(process.env.PORT) : 3333',
|
|
202
|
+
);
|
|
203
|
+
const teardown = tree
|
|
204
|
+
.read('apps/svc-e2e/src/support/global-teardown.ts')
|
|
205
|
+
.toString();
|
|
206
|
+
expect(teardown).toContain(
|
|
207
|
+
'process.env.PORT ? Number(process.env.PORT) : 3333',
|
|
208
|
+
);
|
|
209
|
+
const testSetup = tree
|
|
210
|
+
.read('apps/svc-e2e/src/support/test-setup.ts')
|
|
211
|
+
.toString();
|
|
212
|
+
expect(testSetup).toContain("process.env.PORT ?? '3333'");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('is a no-op when there is no e2e project at that root', () => {
|
|
216
|
+
expect(() =>
|
|
217
|
+
fixExpressServiceE2eProject(tree, 'apps/none-e2e', 3333),
|
|
218
|
+
).not.toThrow();
|
|
219
|
+
expect(tree.exists('apps/none-e2e/jest.config.cts')).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
});
|