@vira-ui/cli 1.1.2 → 1.2.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.
Files changed (40) hide show
  1. package/README.md +454 -1029
  2. package/dist/go/appYaml.js +30 -30
  3. package/dist/go/backendEnvExample.js +17 -17
  4. package/dist/go/backendReadme.js +14 -14
  5. package/dist/go/channelHelpers.js +25 -25
  6. package/dist/go/configGo.js +258 -258
  7. package/dist/go/dbGo.js +43 -43
  8. package/dist/go/dbYaml.js +7 -7
  9. package/dist/go/dockerCompose.js +48 -48
  10. package/dist/go/dockerComposeProd.js +78 -78
  11. package/dist/go/dockerfile.js +15 -15
  12. package/dist/go/eventHandlerTemplate.js +22 -22
  13. package/dist/go/eventsAPI.js +411 -411
  14. package/dist/go/goMod.js +16 -16
  15. package/dist/go/kafkaGo.js +67 -67
  16. package/dist/go/kafkaYaml.js +6 -6
  17. package/dist/go/kanbanHandlers.js +216 -216
  18. package/dist/go/mainGo.js +558 -558
  19. package/dist/go/readme.js +27 -27
  20. package/dist/go/redisGo.js +31 -31
  21. package/dist/go/redisYaml.js +4 -4
  22. package/dist/go/registryGo.js +38 -38
  23. package/dist/go/sqlcYaml.js +13 -13
  24. package/dist/go/stateStore.js +115 -115
  25. package/dist/go/typesGo.js +11 -11
  26. package/dist/index.js +472 -24
  27. package/dist/react/envExample.js +3 -3
  28. package/dist/react/envLocal.js +1 -1
  29. package/dist/react/indexCss.js +17 -17
  30. package/dist/react/indexHtml.js +12 -12
  31. package/dist/react/kanbanAppTsx.js +29 -29
  32. package/dist/react/kanbanBoard.js +58 -58
  33. package/dist/react/kanbanCard.js +60 -60
  34. package/dist/react/kanbanColumn.js +62 -62
  35. package/dist/react/kanbanModels.js +32 -32
  36. package/dist/react/mainTsx.js +12 -12
  37. package/dist/react/viteConfig.js +27 -27
  38. package/package.json +47 -45
  39. package/dist/go/useViraState.js +0 -160
  40. package/dist/go/useViraStream.js +0 -167
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ const program = new commander_1.Command();
91
91
  program
92
92
  .name("vira")
93
93
  .description("ViraJS CLI - Create projects and generate code")
94
- .version("1.1.2");
94
+ .version("1.2.0");
95
95
  const SUPPORTED_TEMPLATES = ["frontend", "fullstack", "kanban"];
96
96
  /**
97
97
  * Инициализация проекта в текущей директории
@@ -325,6 +325,15 @@ dbCommand
325
325
  .action(async (options) => {
326
326
  await showMigrationStatus(options.dir || "migrations", options.dbUrl, options.driver || "postgres");
327
327
  });
328
+ dbCommand
329
+ .command("seed")
330
+ .description("Run database seeds")
331
+ .option("-d, --dir <directory>", "Seeds directory", "seeds")
332
+ .option("--db-url <url>", "Database connection URL (or use DATABASE_URL env var)")
333
+ .option("--driver <driver>", "Database driver (postgres, mysql, sqlite3)", "postgres")
334
+ .action(async (options) => {
335
+ await runSeeds(options.dir || "seeds", options.dbUrl, options.driver || "postgres");
336
+ });
328
337
  make
329
338
  .command("event")
330
339
  .description("Create Go event handler stub")
@@ -466,6 +475,8 @@ program
466
475
  .description("Sync artifacts between backend and frontend")
467
476
  .option("--types", "Sync TypeScript types from Go structs", true)
468
477
  .option("--backend <path>", "Path to Go types file", path.join("backend", "internal", "types", "types.go"))
478
+ .option("--from-models", "Generate TS types from Go models directory (backend/internal/models)", false)
479
+ .option("--models <path>", "Path to Go models directory", path.join("backend", "internal", "models"))
469
480
  .option("--frontend <path>", "Output TS types path (frontend)", path.join("frontend", "src", "vira-types.ts"))
470
481
  .option("--ui <path>", "Output TS types path (ui)", path.join("ui", "src", "vira-types.ts"))
471
482
  .option("-w, --watch", "Watch mode: automatically sync on file changes", false)
@@ -1362,9 +1373,31 @@ describe('${name}Page', () => {
1362
1373
  * Sync TypeScript types from Go structs (scaffold-level parser)
1363
1374
  */
1364
1375
  async function syncTypes(options) {
1365
- const backendPath = path.resolve(process.cwd(), options.backend);
1366
- const goSource = await fs.readFile(backendPath, "utf8");
1367
- const structs = parseGoStructs(goSource);
1376
+ let structs = [];
1377
+ if (options.fromModels) {
1378
+ const modelsDir = path.resolve(process.cwd(), options.models || path.join("backend", "internal", "models"));
1379
+ structs = await parseGoStructsFromDir(modelsDir);
1380
+ }
1381
+ else {
1382
+ const backendPath = path.resolve(process.cwd(), options.backend);
1383
+ const exists = await fs.pathExists(backendPath);
1384
+ if (!exists) {
1385
+ // Friendly fallback: if types.go isn't present (many projects don't need it anymore),
1386
+ // but models exist — generate from models.
1387
+ const modelsDir = path.resolve(process.cwd(), options.models || path.join("backend", "internal", "models"));
1388
+ if (await fs.pathExists(modelsDir)) {
1389
+ structs = await parseGoStructsFromDir(modelsDir);
1390
+ }
1391
+ else {
1392
+ throw new Error(`Go types file not found: ${backendPath}. ` +
1393
+ `Either create it, or run "vira sync --types --from-models" (and ensure ${modelsDir} exists).`);
1394
+ }
1395
+ }
1396
+ else {
1397
+ const goSource = await fs.readFile(backendPath, "utf8");
1398
+ structs = parseGoStructs(goSource);
1399
+ }
1400
+ }
1368
1401
  const tsContent = renderTsTypes(structs);
1369
1402
  const targets = [
1370
1403
  path.resolve(process.cwd(), options.frontend),
@@ -1376,6 +1409,39 @@ async function syncTypes(options) {
1376
1409
  }
1377
1410
  console.log(chalk_1.default.green(`✓ Synced ${structs.length} type(s) to ${options.frontend} and ${options.ui}`));
1378
1411
  }
1412
+ async function parseGoStructsFromDir(modelsDir) {
1413
+ const out = [];
1414
+ const exists = await fs.pathExists(modelsDir);
1415
+ if (!exists) {
1416
+ throw new Error(`Models directory not found: ${modelsDir}`);
1417
+ }
1418
+ const stat = await fs.stat(modelsDir);
1419
+ if (!stat.isDirectory()) {
1420
+ throw new Error(`Models path is not a directory: ${modelsDir}`);
1421
+ }
1422
+ const files = (await fs.readdir(modelsDir))
1423
+ .filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
1424
+ for (const file of files) {
1425
+ const full = path.join(modelsDir, file);
1426
+ const src = await fs.readFile(full, "utf8");
1427
+ out.push(...parseGoStructs(src));
1428
+ }
1429
+ // De-dupe by struct name (first wins)
1430
+ const seen = new Set();
1431
+ const deduped = [];
1432
+ for (const s of out) {
1433
+ if (!s?.name)
1434
+ continue;
1435
+ // Only exported types (to avoid internal helpers)
1436
+ if (s.name[0] !== s.name[0].toUpperCase())
1437
+ continue;
1438
+ if (seen.has(s.name))
1439
+ continue;
1440
+ seen.add(s.name);
1441
+ deduped.push(s);
1442
+ }
1443
+ return deduped;
1444
+ }
1379
1445
  function parseGoStructs(source) {
1380
1446
  const structs = [];
1381
1447
  const structRegex = /type\s+(\w+)\s+struct\s*\{([^}]*)\}/gm;
@@ -1396,9 +1462,13 @@ function parseGoStructs(source) {
1396
1462
  const fieldName = tokens[0];
1397
1463
  const fieldType = tokens[1];
1398
1464
  let jsonTag;
1399
- const tagMatch = line.match(/`json:"([^"]+)"/);
1465
+ // json tag can appear anywhere inside the struct tags: `db:"..." json:"..."`
1466
+ const tagMatch = line.match(/json:"([^"]+)"/);
1400
1467
  if (tagMatch) {
1401
- jsonTag = tagMatch[1].replace(",omitempty", "");
1468
+ jsonTag = tagMatch[1].split(",")[0];
1469
+ if (jsonTag === "-" || jsonTag === "") {
1470
+ continue; // skip non-exposed fields
1471
+ }
1402
1472
  }
1403
1473
  fields.push({ name: fieldName, type: fieldType, json: jsonTag });
1404
1474
  }
@@ -1514,6 +1584,9 @@ function goTypeToTs(goType) {
1514
1584
  function toCamel(name) {
1515
1585
  if (!name)
1516
1586
  return name;
1587
+ // "ID" -> "id", "URL" -> "url" (common Go acronym fields)
1588
+ if (/^[A-Z0-9]+$/.test(name))
1589
+ return name.toLowerCase();
1517
1590
  return name.charAt(0).toLowerCase() + name.slice(1);
1518
1591
  }
1519
1592
  /**
@@ -1640,16 +1713,74 @@ async function generateMigration(name, dir) {
1640
1713
  const baseName = `${timestamp}_${name}`;
1641
1714
  const targetDir = path.join(process.cwd(), dir);
1642
1715
  await fs.ensureDir(targetDir);
1643
- const upPath = path.join(targetDir, `${baseName}.up.sql`);
1644
- const downPath = path.join(targetDir, `${baseName}.down.sql`);
1645
- const upTemplate = `-- +goose Up
1716
+ // Goose v3 имеет проблему с отдельными .up.sql/.down.sql файлами (дубликат версии)
1717
+ // Используем один файл .sql с обеими директивами (работает надежнее)
1718
+ const migrationPath = path.join(targetDir, `${baseName}.sql`);
1719
+ const migrationTemplate = `-- +goose Up
1720
+ -- +goose StatementBegin
1646
1721
  -- TODO: add migration SQL here
1647
- `;
1648
- const downTemplate = `-- +goose Down
1722
+ -- +goose StatementEnd
1723
+
1724
+ -- +goose Down
1725
+ -- +goose StatementBegin
1649
1726
  -- TODO: rollback SQL here
1727
+ -- +goose StatementEnd
1650
1728
  `;
1651
- await fs.writeFile(upPath, upTemplate);
1652
- await fs.writeFile(downPath, downTemplate);
1729
+ await fs.writeFile(migrationPath, migrationTemplate);
1730
+ }
1731
+ /**
1732
+ * Получить DATABASE_URL из переменных окружения или .env файлов
1733
+ */
1734
+ async function getDatabaseUrl() {
1735
+ // Сначала проверяем переменную окружения
1736
+ if (process.env.DATABASE_URL) {
1737
+ return process.env.DATABASE_URL;
1738
+ }
1739
+ // Пытаемся прочитать .env файлы
1740
+ const possibleEnvPaths = [
1741
+ path.join(process.cwd(), ".env"),
1742
+ path.join(process.cwd(), "env"),
1743
+ path.join(process.cwd(), "backend", ".env"),
1744
+ path.join(process.cwd(), ".env.local"),
1745
+ ];
1746
+ for (const envPath of possibleEnvPaths) {
1747
+ if (await fs.pathExists(envPath)) {
1748
+ try {
1749
+ const envContent = await fs.readFile(envPath, "utf8");
1750
+ const lines = envContent.split("\n");
1751
+ for (const line of lines) {
1752
+ // Пропускаем комментарии и пустые строки
1753
+ const trimmed = line.trim();
1754
+ if (!trimmed || trimmed.startsWith("#"))
1755
+ continue;
1756
+ // Парсим DATABASE_URL=value
1757
+ const match = trimmed.match(/^DATABASE_URL\s*=\s*(.+)$/);
1758
+ if (match) {
1759
+ // Убираем кавычки если есть
1760
+ let value = match[1].trim();
1761
+ if ((value.startsWith('"') && value.endsWith('"')) ||
1762
+ (value.startsWith("'") && value.endsWith("'"))) {
1763
+ value = value.slice(1, -1);
1764
+ }
1765
+ return value;
1766
+ }
1767
+ }
1768
+ }
1769
+ catch (error) {
1770
+ // Игнорируем ошибки чтения .env файла
1771
+ }
1772
+ }
1773
+ }
1774
+ // Пытаемся собрать URL из отдельных переменных DB_*
1775
+ const dbHost = process.env.DB_HOST || process.env.POSTGRES_HOST || "localhost";
1776
+ const dbPort = process.env.DB_PORT || process.env.POSTGRES_PORT || "5432";
1777
+ const dbUser = process.env.DB_USER || process.env.POSTGRES_USER || "postgres";
1778
+ const dbPassword = process.env.DB_PASSWORD || process.env.POSTGRES_PASSWORD || "";
1779
+ const dbName = process.env.DB_NAME || process.env.POSTGRES_DB || process.env.DB_DATABASE || "";
1780
+ if (dbName) {
1781
+ return `postgres://${dbUser}${dbPassword ? `:${dbPassword}` : ""}@${dbHost}:${dbPort}/${dbName}?sslmode=disable`;
1782
+ }
1783
+ return undefined;
1653
1784
  }
1654
1785
  /**
1655
1786
  * Выполнение миграций через goose
@@ -1663,26 +1794,41 @@ async function runMigrations(migrationsDir, dbUrl, driver, direction) {
1663
1794
  console.log(chalk_1.default.yellow(` Create migrations with: npx vira make migration <name>`));
1664
1795
  process.exit(1);
1665
1796
  }
1666
- // Получаем URL базы данных
1667
- const databaseUrl = dbUrl || process.env.DATABASE_URL;
1797
+ // Получаем URL базы данных (проверяем .env файлы)
1798
+ const databaseUrl = dbUrl || await getDatabaseUrl();
1668
1799
  if (!databaseUrl) {
1669
1800
  console.error(chalk_1.default.red("✗ Database URL not provided"));
1670
- console.log(chalk_1.default.yellow(" Use --db-url option or set DATABASE_URL environment variable"));
1801
+ console.log(chalk_1.default.yellow(" Use --db-url option, set DATABASE_URL environment variable, or create .env file"));
1671
1802
  console.log(chalk_1.default.yellow(" Example: DATABASE_URL=postgres://user:pass@localhost/dbname?sslmode=disable"));
1803
+ console.log(chalk_1.default.yellow(" Or create .env in project root with: DATABASE_URL=..."));
1672
1804
  process.exit(1);
1673
1805
  }
1674
- // Проверяем наличие goose
1806
+ // Проверяем наличие goose (кроссплатформенная проверка)
1807
+ const isWindows = process.platform === "win32";
1808
+ const checkCommand = isWindows ? "where goose" : "which goose";
1809
+ let gooseInstalled = false;
1675
1810
  try {
1676
- execSync("which goose", { stdio: "ignore" });
1811
+ execSync(checkCommand, { stdio: "ignore" });
1812
+ gooseInstalled = true;
1677
1813
  }
1678
1814
  catch {
1679
- console.log(chalk_1.default.blue("Installing goose..."));
1815
+ // goose не найден
1816
+ }
1817
+ if (!gooseInstalled) {
1818
+ console.log(chalk_1.default.blue("goose not found. Installing..."));
1819
+ console.log(chalk_1.default.gray(" This will download goose and its dependencies (this is normal)"));
1680
1820
  try {
1681
- execSync("go install github.com/pressly/goose/v3/cmd/goose@latest", { stdio: "inherit" });
1821
+ // Скрываем вывод загрузки зависимостей, показываем только прогресс
1822
+ execSync("go install github.com/pressly/goose/v3/cmd/goose@latest", {
1823
+ stdio: "inherit",
1824
+ env: { ...process.env, GOFLAGS: "-mod=mod" }
1825
+ });
1826
+ console.log(chalk_1.default.green("✓ goose installed successfully"));
1682
1827
  }
1683
1828
  catch (error) {
1684
1829
  console.error(chalk_1.default.red("✗ Failed to install goose"));
1685
1830
  console.log(chalk_1.default.yellow(" Install manually: go install github.com/pressly/goose/v3/cmd/goose@latest"));
1831
+ console.log(chalk_1.default.yellow(" Or download from: https://github.com/pressly/goose/releases"));
1686
1832
  process.exit(1);
1687
1833
  }
1688
1834
  }
@@ -1690,7 +1836,17 @@ async function runMigrations(migrationsDir, dbUrl, driver, direction) {
1690
1836
  try {
1691
1837
  const command = direction === "up" ? "up" : "down";
1692
1838
  console.log(chalk_1.default.blue(`Running migrations ${direction}...`));
1693
- execSync(`goose -dir "${migrationsPath}" ${driver} "${databaseUrl}" ${command}`, { stdio: "inherit", cwd: process.cwd() });
1839
+ // Используем переменные окружения для goose (более надежно на Windows)
1840
+ const env = {
1841
+ ...process.env,
1842
+ GOOSE_DRIVER: driver,
1843
+ GOOSE_DBSTRING: databaseUrl,
1844
+ };
1845
+ execSync(`goose -dir "${migrationsPath}" ${command}`, {
1846
+ stdio: "inherit",
1847
+ cwd: process.cwd(),
1848
+ env: env
1849
+ });
1694
1850
  console.log(chalk_1.default.green(`✓ Migrations ${direction} completed successfully`));
1695
1851
  }
1696
1852
  catch (error) {
@@ -1708,21 +1864,313 @@ async function showMigrationStatus(migrationsDir, dbUrl, driver) {
1708
1864
  console.error(chalk_1.default.red(`✗ Migrations directory not found: ${migrationsPath}`));
1709
1865
  process.exit(1);
1710
1866
  }
1711
- const databaseUrl = dbUrl || process.env.DATABASE_URL;
1867
+ const databaseUrl = dbUrl || await getDatabaseUrl();
1712
1868
  if (!databaseUrl) {
1713
1869
  console.error(chalk_1.default.red("✗ Database URL not provided"));
1714
- console.log(chalk_1.default.yellow(" Use --db-url option or set DATABASE_URL environment variable"));
1870
+ console.log(chalk_1.default.yellow(" Use --db-url option, set DATABASE_URL environment variable, or create .env file"));
1715
1871
  process.exit(1);
1716
1872
  }
1717
1873
  try {
1718
1874
  console.log(chalk_1.default.blue("Migration status:"));
1719
- execSync(`goose -dir "${migrationsPath}" ${driver} "${databaseUrl}" status`, { stdio: "inherit", cwd: process.cwd() });
1875
+ // Используем переменные окружения для goose (более надежно на Windows)
1876
+ const env = {
1877
+ ...process.env,
1878
+ GOOSE_DRIVER: driver,
1879
+ GOOSE_DBSTRING: databaseUrl,
1880
+ };
1881
+ execSync(`goose -dir "${migrationsPath}" status`, {
1882
+ stdio: "inherit",
1883
+ cwd: process.cwd(),
1884
+ env: env
1885
+ });
1720
1886
  }
1721
1887
  catch (error) {
1722
1888
  console.error(chalk_1.default.red(`✗ Failed to get migration status: ${error.message}`));
1723
1889
  process.exit(1);
1724
1890
  }
1725
1891
  }
1892
+ /**
1893
+ * Выполнение сидеров (seeds)
1894
+ */
1895
+ async function runSeeds(seedsDir, dbUrl, driver) {
1896
+ const { execSync } = require("child_process");
1897
+ const seedsPath = path.resolve(process.cwd(), seedsDir);
1898
+ // Проверяем наличие директории сидеров
1899
+ if (!(await fs.pathExists(seedsPath))) {
1900
+ console.error(chalk_1.default.red(`✗ Seeds directory not found: ${seedsPath}`));
1901
+ console.log(chalk_1.default.yellow(` Create seeds directory and add SQL files`));
1902
+ process.exit(1);
1903
+ }
1904
+ // Получаем URL базы данных
1905
+ const databaseUrl = dbUrl || await getDatabaseUrl();
1906
+ if (!databaseUrl) {
1907
+ console.error(chalk_1.default.red("✗ Database URL not provided"));
1908
+ console.log(chalk_1.default.yellow(" Use --db-url option, set DATABASE_URL environment variable, or create .env file"));
1909
+ process.exit(1);
1910
+ }
1911
+ // Получаем список SQL файлов из папки seeds
1912
+ const files = await fs.readdir(seedsPath);
1913
+ const sqlFiles = files
1914
+ .filter((f) => f.endsWith(".sql"))
1915
+ .sort(); // Сортируем по имени для последовательного выполнения
1916
+ if (sqlFiles.length === 0) {
1917
+ console.log(chalk_1.default.yellow(`⚠ No SQL files found in ${seedsPath}`));
1918
+ return;
1919
+ }
1920
+ console.log(chalk_1.default.blue(`Found ${sqlFiles.length} seed file(s):`));
1921
+ sqlFiles.forEach((file) => {
1922
+ console.log(chalk_1.default.gray(` - ${file}`));
1923
+ });
1924
+ // Выполняем каждый SQL файл
1925
+ try {
1926
+ console.log(chalk_1.default.blue(`\nRunning seeds...`));
1927
+ if (driver === "postgres") {
1928
+ // Для PostgreSQL используем psql
1929
+ const isWindows = process.platform === "win32";
1930
+ // Парсим DATABASE_URL используя встроенный URL класс
1931
+ let parsedUrl;
1932
+ try {
1933
+ parsedUrl = new URL(databaseUrl);
1934
+ }
1935
+ catch (error) {
1936
+ console.error(chalk_1.default.red("✗ Invalid DATABASE_URL format for PostgreSQL"));
1937
+ console.log(chalk_1.default.yellow(" Expected format: postgres://user:password@host:port/dbname"));
1938
+ console.log(chalk_1.default.gray(` Error: ${error instanceof Error ? error.message : String(error)}`));
1939
+ process.exit(1);
1940
+ }
1941
+ // Проверяем протокол
1942
+ if (parsedUrl.protocol !== "postgres:" && parsedUrl.protocol !== "postgresql:") {
1943
+ console.error(chalk_1.default.red("✗ Invalid database protocol"));
1944
+ console.log(chalk_1.default.yellow(" Expected protocol: postgres:// or postgresql://"));
1945
+ process.exit(1);
1946
+ }
1947
+ const user = parsedUrl.username || "postgres";
1948
+ const password = parsedUrl.password || "";
1949
+ const host = parsedUrl.hostname || "localhost";
1950
+ const port = parsedUrl.port || "5432";
1951
+ const dbname = parsedUrl.pathname ? parsedUrl.pathname.slice(1) : ""; // Убираем ведущий /
1952
+ if (!dbname) {
1953
+ console.error(chalk_1.default.red("✗ Database name not specified in DATABASE_URL"));
1954
+ process.exit(1);
1955
+ }
1956
+ // Проверяем наличие psql локально
1957
+ let useDocker = false;
1958
+ let dockerContainer = "";
1959
+ try {
1960
+ execSync(isWindows ? "where psql" : "which psql", { stdio: "ignore" });
1961
+ }
1962
+ catch {
1963
+ // psql не найден локально, пробуем Docker
1964
+ useDocker = true;
1965
+ console.log(chalk_1.default.blue(" psql not found locally, trying Docker..."));
1966
+ // Пробуем найти контейнер PostgreSQL
1967
+ try {
1968
+ // Список запущенных контейнеров
1969
+ const containersOutput = execSync("docker ps --format {{.Names}}", { encoding: "utf8" });
1970
+ const containers = containersOutput.trim().split("\n")
1971
+ .filter((c) => c.trim())
1972
+ .map((c) => c.trim().replace(/['"]/g, "")); // Убираем кавычки
1973
+ // Ищем контейнер с postgres в имени
1974
+ const postgresContainer = containers.find((c) => c.toLowerCase().includes("postgres") ||
1975
+ c.toLowerCase().includes("db") ||
1976
+ c.toLowerCase().includes("database"));
1977
+ if (postgresContainer) {
1978
+ dockerContainer = postgresContainer;
1979
+ console.log(chalk_1.default.gray(` Found Docker container: ${dockerContainer}`));
1980
+ // Проверяем, что контейнер действительно запущен
1981
+ try {
1982
+ const checkOutput = execSync(`docker ps --filter "name=^${dockerContainer}$" --format {{.Names}}`, { encoding: "utf8", stdio: "pipe" });
1983
+ if (!checkOutput.trim()) {
1984
+ console.error(chalk_1.default.red(`✗ Container ${dockerContainer} is not running`));
1985
+ process.exit(1);
1986
+ }
1987
+ }
1988
+ catch {
1989
+ console.error(chalk_1.default.red(`✗ Container ${dockerContainer} is not running`));
1990
+ process.exit(1);
1991
+ }
1992
+ }
1993
+ else {
1994
+ // Пробуем стандартные имена
1995
+ const standardNames = ["postgres", "postgresql", "db", "database", "vira-db", "vira_db"];
1996
+ for (const name of standardNames) {
1997
+ try {
1998
+ const checkOutput = execSync(`docker ps --filter "name=^${name}$" --format {{.Names}}`, { encoding: "utf8", stdio: "pipe" });
1999
+ if (checkOutput.trim()) {
2000
+ dockerContainer = name;
2001
+ console.log(chalk_1.default.gray(` Using Docker container: ${dockerContainer}`));
2002
+ break;
2003
+ }
2004
+ }
2005
+ catch {
2006
+ // Продолжаем поиск
2007
+ }
2008
+ }
2009
+ }
2010
+ if (!dockerContainer) {
2011
+ console.error(chalk_1.default.red("✗ psql not found and no PostgreSQL Docker container detected"));
2012
+ console.log(chalk_1.default.yellow(" Please install PostgreSQL client tools or ensure Docker container is running"));
2013
+ console.log(chalk_1.default.yellow(" Container name should contain 'postgres', 'db', or 'database'"));
2014
+ process.exit(1);
2015
+ }
2016
+ }
2017
+ catch (dockerError) {
2018
+ console.error(chalk_1.default.red("✗ Docker not available or PostgreSQL container not found"));
2019
+ console.log(chalk_1.default.yellow(" Please install PostgreSQL client tools (psql) or ensure Docker is running"));
2020
+ process.exit(1);
2021
+ }
2022
+ }
2023
+ // Устанавливаем переменную окружения PGPASSWORD для psql
2024
+ const env = {
2025
+ ...process.env,
2026
+ PGPASSWORD: password,
2027
+ };
2028
+ // Функция для выполнения SQL запроса
2029
+ const executeSQL = async (sql) => {
2030
+ const tempFile = path.join(seedsPath, `.temp_${Date.now()}.sql`);
2031
+ await fs.writeFile(tempFile, sql);
2032
+ try {
2033
+ if (useDocker && dockerContainer) {
2034
+ const containerPath = `/tmp/temp_${Date.now()}.sql`;
2035
+ execSync(`docker cp "${tempFile}" "${dockerContainer}":${containerPath}`, { stdio: "ignore" });
2036
+ const escapedPassword = password.replace(/'/g, "'\\''");
2037
+ const output = execSync(`docker exec -e PGPASSWORD='${escapedPassword}' "${dockerContainer}" psql -U ${user} -d ${dbname} -t -A -f ${containerPath}`, { encoding: "utf8", stdio: "pipe" });
2038
+ execSync(`docker exec "${dockerContainer}" rm ${containerPath}`, { stdio: "ignore" });
2039
+ return output.trim();
2040
+ }
2041
+ else {
2042
+ const output = execSync(`psql -h ${host} -p ${port} -U ${user} -d ${dbname} -t -A -f "${tempFile}"`, { encoding: "utf8", stdio: "pipe", env: env });
2043
+ return output.trim();
2044
+ }
2045
+ }
2046
+ finally {
2047
+ await fs.remove(tempFile);
2048
+ }
2049
+ };
2050
+ // Получаем список уже выполненных seed-файлов
2051
+ let executedSeeds = new Set();
2052
+ try {
2053
+ const checkTableSQL = `
2054
+ SELECT EXISTS (
2055
+ SELECT FROM information_schema.tables
2056
+ WHERE table_schema = 'public'
2057
+ AND table_name = 'seed_history'
2058
+ );
2059
+ `;
2060
+ const tableExists = await executeSQL(checkTableSQL);
2061
+ if (tableExists === "t") {
2062
+ const getExecutedSQL = `SELECT seed_file FROM seed_history WHERE success = true;`;
2063
+ const executedOutput = await executeSQL(getExecutedSQL);
2064
+ if (executedOutput) {
2065
+ executedSeeds = new Set(executedOutput.split("\n").filter((f) => f.trim()));
2066
+ }
2067
+ }
2068
+ }
2069
+ catch (error) {
2070
+ // Игнорируем ошибки при проверке - возможно таблица еще не создана
2071
+ console.log(chalk_1.default.yellow(" ⚠ Could not check seed history, will execute all seeds"));
2072
+ }
2073
+ // Фильтруем уже выполненные seed-файлы
2074
+ const pendingSeeds = sqlFiles.filter((file) => !executedSeeds.has(file));
2075
+ if (pendingSeeds.length === 0) {
2076
+ console.log(chalk_1.default.green(`\n✓ All seeds have already been executed`));
2077
+ return;
2078
+ }
2079
+ if (pendingSeeds.length < sqlFiles.length) {
2080
+ console.log(chalk_1.default.blue(`\n ${sqlFiles.length - pendingSeeds.length} seed(s) already executed, ${pendingSeeds.length} pending`));
2081
+ }
2082
+ for (const file of pendingSeeds) {
2083
+ const filePath = path.join(seedsPath, file);
2084
+ const startTime = Date.now();
2085
+ console.log(chalk_1.default.blue(` Running ${file}...`));
2086
+ let success = false;
2087
+ let errorMsg = "";
2088
+ try {
2089
+ if (useDocker && dockerContainer) {
2090
+ // Копируем файл в контейнер и выполняем
2091
+ const fileName = path.basename(filePath);
2092
+ const containerPath = `/tmp/${fileName}`;
2093
+ // Копируем файл в контейнер (используем кавычки для имени контейнера на случай пробелов)
2094
+ execSync(`docker cp "${filePath}" "${dockerContainer}":${containerPath}`, { stdio: "inherit" });
2095
+ // Выполняем SQL в контейнере (экранируем пароль и используем кавычки)
2096
+ const escapedPassword = password.replace(/'/g, "'\\''");
2097
+ execSync(`docker exec -e PGPASSWORD='${escapedPassword}' "${dockerContainer}" psql -U ${user} -d ${dbname} -f ${containerPath}`, {
2098
+ stdio: "inherit",
2099
+ cwd: process.cwd(),
2100
+ });
2101
+ // Удаляем временный файл из контейнера
2102
+ try {
2103
+ execSync(`docker exec "${dockerContainer}" rm ${containerPath}`, { stdio: "ignore" });
2104
+ }
2105
+ catch {
2106
+ // Игнорируем ошибки удаления
2107
+ }
2108
+ }
2109
+ else {
2110
+ // Используем локальный psql
2111
+ execSync(`psql -h ${host} -p ${port} -U ${user} -d ${dbname} -f "${filePath}"`, {
2112
+ stdio: "inherit",
2113
+ cwd: process.cwd(),
2114
+ env: env,
2115
+ });
2116
+ }
2117
+ const executionTime = Date.now() - startTime;
2118
+ success = true;
2119
+ console.log(chalk_1.default.green(` ✓ ${file} completed (${executionTime}ms)`));
2120
+ // Записываем успешное выполнение в seed_history
2121
+ try {
2122
+ const insertSQL = `
2123
+ INSERT INTO seed_history (seed_file, execution_time_ms, success)
2124
+ VALUES ('${file.replace(/'/g, "''")}', ${executionTime}, true)
2125
+ ON CONFLICT (seed_file) DO UPDATE SET
2126
+ executed_at = CURRENT_TIMESTAMP,
2127
+ execution_time_ms = ${executionTime},
2128
+ success = true,
2129
+ error_message = NULL;
2130
+ `;
2131
+ await executeSQL(insertSQL);
2132
+ }
2133
+ catch (historyError) {
2134
+ // Игнорируем ошибки записи истории
2135
+ console.log(chalk_1.default.yellow(` ⚠ Could not record seed history: ${historyError instanceof Error ? historyError.message : String(historyError)}`));
2136
+ }
2137
+ }
2138
+ catch (error) {
2139
+ const executionTime = Date.now() - startTime;
2140
+ errorMsg = error.message || String(error);
2141
+ console.error(chalk_1.default.red(` ✗ ${file} failed: ${errorMsg}`));
2142
+ // Записываем неудачное выполнение в seed_history
2143
+ try {
2144
+ const insertSQL = `
2145
+ INSERT INTO seed_history (seed_file, execution_time_ms, success, error_message)
2146
+ VALUES ('${file.replace(/'/g, "''")}', ${executionTime}, false, '${errorMsg.replace(/'/g, "''")}')
2147
+ ON CONFLICT (seed_file) DO UPDATE SET
2148
+ executed_at = CURRENT_TIMESTAMP,
2149
+ execution_time_ms = ${executionTime},
2150
+ success = false,
2151
+ error_message = '${errorMsg.replace(/'/g, "''")}';
2152
+ `;
2153
+ await executeSQL(insertSQL);
2154
+ }
2155
+ catch (historyError) {
2156
+ // Игнорируем ошибки записи истории
2157
+ }
2158
+ // Продолжаем выполнение остальных файлов
2159
+ }
2160
+ }
2161
+ }
2162
+ else {
2163
+ // Для других драйверов можно добавить поддержку позже
2164
+ console.error(chalk_1.default.red(`✗ Seeds are currently only supported for PostgreSQL`));
2165
+ process.exit(1);
2166
+ }
2167
+ console.log(chalk_1.default.green(`\n✓ Seeds completed successfully`));
2168
+ }
2169
+ catch (error) {
2170
+ console.error(chalk_1.default.red(`✗ Seeds failed: ${error.message}`));
2171
+ process.exit(1);
2172
+ }
2173
+ }
1726
2174
  async function generateEventHandler(name, dir) {
1727
2175
  const targetDir = path.join(process.cwd(), dir);
1728
2176
  await fs.ensureDir(targetDir);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.envExample = void 0;
4
- exports.envExample = `# Frontend environment example
5
- VITE_API_URL=http://localhost:8080
6
- VITE_AUTH_TOKEN=
4
+ exports.envExample = `# Frontend environment example
5
+ VITE_API_URL=http://localhost:8080
6
+ VITE_AUTH_TOKEN=
7
7
  `;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.envLocal = void 0;
4
- exports.envLocal = `VITE_API_URL=http://localhost:8080
4
+ exports.envLocal = `VITE_API_URL=http://localhost:8080
5
5
  `;
@@ -2,21 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.indexCss = void 0;
4
4
  // src/index.css
5
- exports.indexCss = `* {
6
- margin: 0;
7
- padding: 0;
8
- box-sizing: border-box;
9
- }
10
-
11
- body {
12
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
13
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
14
- sans-serif;
15
- -webkit-font-smoothing: antialiased;
16
- -moz-osx-font-smoothing: grayscale;
17
- }
18
-
19
- #root {
20
- min-height: 100vh;
21
- }
5
+ exports.indexCss = `* {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
13
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
14
+ sans-serif;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ }
18
+
19
+ #root {
20
+ min-height: 100vh;
21
+ }
22
22
  `;