@vira-ui/cli 1.1.2 → 2.0.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 +454 -1029
- package/dist/go/appYaml.js +30 -30
- package/dist/go/backendEnvExample.js +17 -17
- package/dist/go/backendReadme.js +14 -14
- package/dist/go/channelHelpers.js +25 -25
- package/dist/go/configGo.js +258 -258
- package/dist/go/dbGo.js +43 -43
- package/dist/go/dbYaml.js +7 -7
- package/dist/go/dockerCompose.js +48 -48
- package/dist/go/dockerComposeProd.js +78 -78
- package/dist/go/dockerfile.js +15 -15
- package/dist/go/eventHandlerTemplate.js +22 -22
- package/dist/go/eventsAPI.js +411 -411
- package/dist/go/goMod.js +16 -16
- package/dist/go/kafkaGo.js +67 -67
- package/dist/go/kafkaYaml.js +6 -6
- package/dist/go/kanbanHandlers.js +216 -216
- package/dist/go/mainGo.js +558 -558
- package/dist/go/readme.js +27 -27
- package/dist/go/redisGo.js +31 -31
- package/dist/go/redisYaml.js +4 -4
- package/dist/go/registryGo.js +38 -38
- package/dist/go/sqlcYaml.js +13 -13
- package/dist/go/stateStore.js +115 -115
- package/dist/go/typesGo.js +11 -11
- package/dist/index.js +472 -24
- package/dist/react/envExample.js +3 -3
- package/dist/react/envLocal.js +1 -1
- package/dist/react/indexCss.js +17 -17
- package/dist/react/indexHtml.js +12 -12
- package/dist/react/kanbanAppTsx.js +29 -29
- package/dist/react/kanbanBoard.js +58 -58
- package/dist/react/kanbanCard.js +60 -60
- package/dist/react/kanbanColumn.js +62 -62
- package/dist/react/kanbanModels.js +32 -32
- package/dist/react/mainTsx.js +12 -12
- package/dist/react/viteConfig.js +27 -27
- package/package.json +47 -45
- package/dist/go/useViraState.js +0 -160
- 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.
|
|
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
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
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].
|
|
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
|
-
|
|
1644
|
-
|
|
1645
|
-
const
|
|
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
|
-
|
|
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(
|
|
1652
|
-
|
|
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 ||
|
|
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
|
|
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(
|
|
1811
|
+
execSync(checkCommand, { stdio: "ignore" });
|
|
1812
|
+
gooseInstalled = true;
|
|
1677
1813
|
}
|
|
1678
1814
|
catch {
|
|
1679
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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);
|
package/dist/react/envExample.js
CHANGED
|
@@ -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
|
`;
|
package/dist/react/envLocal.js
CHANGED
package/dist/react/indexCss.js
CHANGED
|
@@ -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
|
`;
|