Skip to content

Commit ba58664

Browse files
committed
update
1 parent e510c34 commit ba58664

10 files changed

Lines changed: 399 additions & 62 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_modules
44
.terraform
55
*.backup
66
.DS_Store
7+
playwright-report
8+
test-results

MIGRATION_PLAN.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Plan: migration to Node 24, TypeScript, Playwright, modern lint
2+
3+
## Goals
4+
5+
- Upgrade runtime and infrastructure to Node.js 24 LTS.
6+
- Move application code to TypeScript without changing external behavior.
7+
- Add end-to-end tests with Playwright.
8+
- Update linting to a modern, maintained ESLint setup.
9+
- Keep backward compatibility for routes, startup flow, and environment variables.
10+
- Standardize dependency update workflow with `npm-check-updates`.
11+
12+
## Scope and compatibility constraints
13+
14+
- Keep route contract unchanged: `/` and `/error` must behave identically.
15+
- Keep env contract unchanged: `SERVER_MESSAGE`, `ROLLBAR_TOKEN`.
16+
- Keep startup compatibility: `npm start`, `make start`, Docker run flow from README.
17+
- Keep rendered content and core page assertions stable (existing tests + e2e).
18+
19+
## Step-by-step plan
20+
21+
### 1) Baseline and regression guardrails
22+
23+
- Freeze current behavior with existing unit tests (`jest`) and compatibility checklist.
24+
- Add a compact compatibility matrix (routes, env, startup, docker smoke).
25+
- Ensure current `test` and `lint` pass before migrations.
26+
27+
### 2) Node 24 LTS migration
28+
29+
- Update `package.json` engines to `24.x`.
30+
- Add `.nvmrc` with `24`.
31+
- Update `Dockerfile` base image to `node:24-slim`.
32+
- Validate local + container execution (`make start`, `make compose-test-ci`, `make compose-lint-ci`).
33+
34+
### 3) TypeScript migration (incremental, non-breaking)
35+
36+
- Add `typescript`, `@types/node`, and `tsconfig.json` (ESM / NodeNext setup).
37+
- Add scripts: `build`, `typecheck`.
38+
- Migrate server files to TS first: `server/plugin.*`, `server/routes.*`.
39+
- Preserve runtime behavior and public API/HTTP responses.
40+
41+
### 4) Linter modernization
42+
43+
- Move from legacy `.eslintrc.yml` to ESLint flat config (`eslint.config.js`).
44+
- Update ESLint stack to current supported versions.
45+
- Add TS-aware linting (`typescript-eslint`) + keep Jest/Node rules.
46+
- Port critical existing rules to avoid noisy non-functional churn.
47+
48+
### 5) Add Playwright e2e
49+
50+
- Add `@playwright/test`, `playwright.config.ts`, `e2e/` tests.
51+
- Cover critical flows:
52+
- main page without `SERVER_MESSAGE`
53+
- main page with `SERVER_MESSAGE`
54+
- `/error` page behavior
55+
- Add `test:e2e` script.
56+
- Add CI job for e2e with report artifact.
57+
58+
### 6) Add and formalize npm-check-updates usage
59+
60+
- Ensure `npm-check-updates` is present in dev dependencies (update to current).
61+
- Add scripts in `package.json`:
62+
- `deps:check` (show outdated deps)
63+
- `deps:update` (update package.json ranges)
64+
- `deps:update:interactive` (optional targeted workflow)
65+
- Add `.ncurc.json` policy:
66+
- default strategy for safe regular updates
67+
- optional reject list for risky majors (apply selectively)
68+
- Document dependency update workflow in README.
69+
70+
### 7) Dependency upgrade wave and stabilization
71+
72+
- Upgrade dependencies in controlled batches (tooling and runtime separately).
73+
- Treat Fastify ecosystem updates as high-risk and verify with full regression.
74+
- After each batch run: `lint`, `typecheck`, unit tests, e2e tests, docker smoke.
75+
- Update docs/CI configs to reflect final commands and toolchain.
76+
77+
## Recommended PR slicing
78+
79+
1. Node 24 + Docker/CI alignment.
80+
2. TypeScript scaffold + `typecheck`.
81+
3. Server TS migration.
82+
4. ESLint modern config.
83+
5. Playwright e2e + CI integration.
84+
6. `npm-check-updates` scripts + `.ncurc.json` + docs.
85+
7. Dependency major/minor upgrade batches + final regression.
86+
87+
## Definition of Done
88+
89+
- Node 24 is used in local dev, Docker, and CI.
90+
- `lint`, `typecheck`, unit and e2e test suites are green.
91+
- Route/env/startup contracts are unchanged and verified.
92+
- Dependency maintenance workflow with `npm-check-updates` is documented and usable.

e2e/global-setup.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { spawn, type ChildProcess } from 'node:child_process';
2+
3+
const BASE_URL = 'http://127.0.0.1:4300';
4+
const STARTUP_TIMEOUT_MS = 60_000;
5+
const POLL_INTERVAL_MS = 500;
6+
7+
const sleep = (ms: number) => new Promise((resolve) => {
8+
setTimeout(resolve, ms);
9+
});
10+
11+
const isAppReady = async () => {
12+
try {
13+
const response = await fetch(`${BASE_URL}/`);
14+
15+
if (!response.ok) {
16+
return false;
17+
}
18+
19+
const body = await response.text();
20+
return body.includes('Привет от Хекслета!');
21+
} catch {
22+
return false;
23+
}
24+
};
25+
26+
const waitForApp = async () => {
27+
const startTime = Date.now();
28+
29+
while ((Date.now() - startTime) < STARTUP_TIMEOUT_MS) {
30+
if (await isAppReady()) {
31+
return;
32+
}
33+
34+
await sleep(POLL_INTERVAL_MS);
35+
}
36+
37+
throw new Error(`App did not become ready in ${STARTUP_TIMEOUT_MS}ms`);
38+
};
39+
40+
export default async () => {
41+
const serverProcess: ChildProcess = spawn(
42+
process.execPath,
43+
['node_modules/fastify-cli/cli.js', 'start', 'server/plugin.js', '-a', '127.0.0.1', '-p', '4300', '-l', 'info'],
44+
{
45+
stdio: 'ignore',
46+
},
47+
);
48+
49+
serverProcess.unref();
50+
51+
try {
52+
await waitForApp();
53+
} catch (error) {
54+
serverProcess.kill('SIGTERM');
55+
throw error;
56+
}
57+
58+
return async () => {
59+
if (serverProcess.exitCode !== null || serverProcess.signalCode !== null) {
60+
return;
61+
}
62+
63+
await new Promise<void>((resolve) => {
64+
const timeout = setTimeout(() => {
65+
if (serverProcess.exitCode === null && serverProcess.signalCode === null) {
66+
serverProcess.kill('SIGKILL');
67+
}
68+
69+
resolve();
70+
}, 5_000);
71+
72+
serverProcess.once('exit', () => {
73+
clearTimeout(timeout);
74+
resolve();
75+
});
76+
77+
serverProcess.kill('SIGTERM');
78+
});
79+
};
80+
};

e2e/pages/error.page.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { expect, type Locator, type Page } from '@playwright/test';
2+
3+
export class ErrorPage {
4+
readonly page: Page;
5+
6+
readonly heading: Locator;
7+
8+
readonly errorText: Locator;
9+
10+
constructor(page: Page) {
11+
this.page = page;
12+
this.heading = page.getByText('Внимание, тут что-то не так!');
13+
this.errorText = page.getByText('Oops! Something went wrong!');
14+
}
15+
16+
async open() {
17+
await this.page.goto('/error');
18+
}
19+
20+
async expectLoaded() {
21+
await expect(this.heading).toBeVisible();
22+
await expect(this.errorText).toBeVisible();
23+
}
24+
}

e2e/pages/home.page.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { expect, type Locator, type Page } from '@playwright/test';
2+
3+
export class HomePage {
4+
readonly page: Page;
5+
6+
readonly heading: Locator;
7+
8+
readonly message: Locator;
9+
10+
constructor(page: Page) {
11+
this.page = page;
12+
this.heading = page.getByText('Привет от Хекслета!');
13+
this.message = page.locator('.lead');
14+
}
15+
16+
async open() {
17+
await this.page.goto('/');
18+
}
19+
20+
async expectLoaded() {
21+
await expect(this.heading).toBeVisible();
22+
await expect(this.message).toBeVisible();
23+
}
24+
}

e2e/tests/smoke.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { test } from '@playwright/test';
2+
3+
import { ErrorPage } from '../pages/error.page.js';
4+
import { HomePage } from '../pages/home.page.js';
5+
6+
test.describe('Application smoke tests', () => {
7+
test('home page renders without crash', async ({ page }) => {
8+
const homePage = new HomePage(page);
9+
10+
await homePage.open();
11+
await homePage.expectLoaded();
12+
});
13+
14+
test('error route renders fallback page', async ({ page }) => {
15+
const errorPage = new ErrorPage(page);
16+
17+
await errorPage.open();
18+
await errorPage.expectLoaded();
19+
});
20+
});

0 commit comments

Comments
 (0)