import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
const projects = fileURLToPath(new URL('../../', import.meta.url));
const roots = { pwa: resolve(projects, 'ridebus-pwa'), website: resolve(projects, 'ridebus-website/ridebus-website'), admin: resolve(projects, 'polotsk-transit-api/frontend') };
// The landing uses the TypeScript JS compiler API; the status project uses
// the native TypeScript CLI, which does not expose transpileModule.
const ts = createRequire(resolve(roots.website, 'package.json'))('typescript');
const source = readFileSync(new URL('../integrations/StatusWidget.tsx', import.meta.url), 'utf8');
function save(root, path, value) { const target = resolve(root, path); mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, value); }
function replace(root, path, before, after) {
const target = resolve(root, path), content = readFileSync(target, 'utf8');
if (content.includes(after)) return;
if (!content.includes(before)) throw new Error(`Integration anchor missing: ${target}`);
save(root, path, content.replace(before, after));
}
function append(root, path, value) {
const target = resolve(root, path), content = existsSync(target) ? readFileSync(target, 'utf8') : '';
if (!content.includes(value.trim())) save(root, path, `${content.trimEnd()}\n\n${value.trim()}\n`);
}
save(roots.pwa, 'src/components/StatusWidget.tsx', source);
replace(roots.pwa, 'src/App.tsx', "import { StopList, StopListItem } from './components/StopList';", "import { StopList, StopListItem } from './components/StopList';\nimport StatusWidget from './components/StatusWidget';");
replace(roots.pwa, 'src/App.tsx', '
', '');
append(roots.pwa, 'src/styles.scss', '.footer > div { flex-wrap: wrap; row-gap: 12px; max-width: 100%; }');
append(roots.pwa, '.env.example', '# Public status hosting URL, embedded in the production bundle.\n# VITE_STATUS_URL=https://your-status-domain.example');
save(roots.website, 'components/statusWidget/StatusWidget.tsx', source);
replace(roots.website, 'components/footer/footer.tsx', "import { CursorEffect } from '@/components/cursor';", "import { CursorEffect } from '@/components/cursor';\nimport StatusWidget from '@/components/statusWidget/StatusWidget';");
replace(roots.website, 'components/footer/footer.tsx', ' ', ' \n
');
append(roots.website, '.env.example', '# Public status hosting URL, embedded at build time.\n# NEXT_PUBLIC_STATUS_URL=https://your-status-domain.example');
const jsx = ts.transpileModule(source, { compilerOptions: { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.ESNext } }).outputText;
save(roots.admin, 'src/components/StatusWidget.jsx', jsx);
replace(roots.admin, 'src/components/AppLayout.jsx', "import { useCity } from '../contexts/CityContext';", "import { useCity } from '../contexts/CityContext';\nimport StatusWidget from './StatusWidget';");
replace(roots.admin, 'src/components/AppLayout.jsx', '
', '
\n ');
append(roots.admin, '.env.example', '# Public status hosting URL, embedded in the production bundle.\n# VITE_STATUS_URL=https://your-status-domain.example');
const docs = `## Виджет статуса RideBus\n\nВстроенный iframe показывает свежий статус этого сервиса и API. При недоступности мониторинга отображается отсутствие данных. Адрес хостинга задаётся публичной переменной сборки: ${'{ENV}'}. Если адрес не задан, production-виджет скрыт; в development используется http://127.0.0.1:5188. После изменения переменной нужна новая сборка.\n\nВиджет не получает сессию пользователя, API-ключи или данные приложения. Состояния, оформление, русская/английская локализация и обновления обслуживаются проектом ridebus-status.\n`;
append(roots.pwa, 'README.md', docs.replace('{ENV}', '`VITE_STATUS_URL`'));
append(roots.website, 'README.md', docs.replace('{ENV}', '`NEXT_PUBLIC_STATUS_URL`'));
append(roots.admin, 'README.md', docs.replace('{ENV}', '`VITE_STATUS_URL`'));
replace(roots.pwa, 'Dockerfile', 'COPY public ./public\nRUN npm run build', 'COPY public ./public\nARG VITE_STATUS_URL=\nENV VITE_STATUS_URL=$VITE_STATUS_URL\nRUN npm run build');
replace(roots.pwa, 'docker-compose.yml', ' dockerfile: Dockerfile\n restart:', ' dockerfile: Dockerfile\n args:\n VITE_STATUS_URL: ${VITE_STATUS_URL:-}\n restart:');
replace(roots.website, 'Dockerfile', 'COPY . .\nRUN yarn build', 'COPY . .\nARG NEXT_PUBLIC_STATUS_URL=\nENV NEXT_PUBLIC_STATUS_URL=$NEXT_PUBLIC_STATUS_URL\nRUN yarn build');
replace(roots.website, 'docker-compose.yml', ' build: .', ' build:\n context: .\n args:\n NEXT_PUBLIC_STATUS_URL: ${NEXT_PUBLIC_STATUS_URL:-}');
replace(roots.admin, 'Dockerfile', 'ENV VITE_API_URL=$VITE_API_URL', 'ENV VITE_API_URL=$VITE_API_URL\nARG VITE_STATUS_URL=\nENV VITE_STATUS_URL=$VITE_STATUS_URL');
replace(dirname(roots.admin), 'docker-compose.yml', ' VITE_API_URL: ${API_URL:-/api/v1}', ' VITE_API_URL: ${API_URL:-/api/v1}\n VITE_STATUS_URL: ${VITE_STATUS_URL:-}');
append(dirname(roots.admin), '.env.example', '# Public status hosting URL passed to the frontend build.\n# VITE_STATUS_URL=https://your-status-domain.example');
for (const root of Object.values(roots)) append(root, 'README.md', 'При запуске через Docker Compose укажите переменную URL в корневом `.env` проекта и пересоберите образ (`docker compose up -d --build`). URL передаётся как build argument; смена только runtime environment не обновляет уже собранный интерфейс.');
append(dirname(roots.admin), 'README.md', '## Виджет статуса\n\nАдминка включает виджет сервиса `admin` и API. Для Docker Compose задайте публичный `VITE_STATUS_URL` в корневом `.env` перед сборкой frontend. Для запуска без Docker — в `frontend/.env`. Подробности: [frontend/README.md](frontend/README.md).');
console.log('Status widget integrated into PWA, website and admin sources.');