@runa-ai/runa-cli 0.5.40 → 0.5.42
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/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts +6 -5
- package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts.map +1 -1
- package/dist/commands/ci/machine/actors/db/index.d.ts +1 -1
- package/dist/commands/ci/machine/actors/db/index.d.ts.map +1 -1
- package/dist/commands/ci/machine/actors/db/schema-stats.d.ts +63 -23
- package/dist/commands/ci/machine/actors/db/schema-stats.d.ts.map +1 -1
- package/dist/commands/ci/machine/formatters/sections/final-comment.d.ts.map +1 -1
- package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts +1 -0
- package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts.map +1 -1
- package/dist/commands/ci/machine/machine.d.ts.map +1 -1
- package/dist/commands/db/commands/db-cleanup.d.ts.map +1 -1
- package/dist/commands/db/commands/db-sync.d.ts.map +1 -1
- package/dist/commands/db/sync/actors.d.ts.map +1 -1
- package/dist/commands/db/utils/preflight-check.d.ts.map +1 -1
- package/dist/commands/db/utils/schema-sync.d.ts +23 -0
- package/dist/commands/db/utils/schema-sync.d.ts.map +1 -1
- package/dist/commands/env/commands/setup/parsers.d.ts +1 -0
- package/dist/commands/env/commands/setup/parsers.d.ts.map +1 -1
- package/dist/commands/template-check/utils/path-mapping.d.ts.map +1 -1
- package/dist/constants/versions.d.ts +1 -1
- package/dist/index.js +451 -250
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,11 +4,11 @@ import * as path10 from 'path';
|
|
|
4
4
|
import path10__default, { join, dirname, resolve, relative, basename, sep, isAbsolute, normalize } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execSync, spawnSync, execFileSync, exec, spawn } from 'child_process';
|
|
7
|
-
import * as
|
|
8
|
-
import
|
|
7
|
+
import * as fs5 from 'fs';
|
|
8
|
+
import fs5__default, { existsSync, readFileSync, readdirSync, mkdtempSync, writeFileSync, mkdirSync, copyFileSync, createWriteStream, statSync, rmSync, realpathSync, promises, lstatSync, accessSync, constants, chmodSync, unlinkSync } from 'fs';
|
|
9
9
|
import { createCLILogger, cacheClear, CacheClearOutputSchema, CLIError, cachePrune, CachePruneOutputSchema, cacheStats, CacheStatsOutputSchema, cacheList, CacheListOutputSchema, cacheInvalidate, CacheInvalidateOutputSchema, syncFromProduction, dbGenerateDiagram, DbDiagramGenerateOutputSchema, createDbSnapshot, syncDatabase, emitDbPushFailureCapsule, emitDbAnnotations, writeDbPushStepSummary, exportDbReportJson, DbSyncOutputSchema, databasePaths, detectRequiredServices, formatDetectionResults, dbStart, DbLifecycleStartOutputSchema, dbStop, DbLifecycleStopOutputSchema, dbReset, DbLifecycleResetOutputSchema, dbValidateSchemas, DbSchemaValidateOutputSchema, DbSchemaRisksOutputSchema, dbDetectSchemaRisks, dbApplySchemas, DbSchemaApplyOutputSchema, dbGenerateTypes, DbSchemaGenerateOutputSchema, extractSchemaFilter, dbSeedInit, DbSeedInitOutputSchema, dbSeedValidate, DbSeedValidateOutputSchema, dbSeedGenerate, DbSeedGenerateOutputSchema, dbVerifySeeds, DbSeedVerifyOutputSchema, DbSnapshotCreateOutputSchema, restoreDbSnapshot, DbSnapshotRestoreOutputSchema, listDbSnapshots, DbSnapshotListOutputSchema, dbGeneratePgTapTests, DbTestGenOutputSchema, dbUpdateGoldenRecord, DbTestUpdateGoldenOutputSchema, repairRunaConfig, detectExistingInitConfig, initProject, validateInitResult, linkCliGlobally, LinkCliOutputSchema, unlinkCliGlobally, UnlinkCliOutputSchema, checkRepoStatus, CheckRepoStatusOutputSchema, enableTelemetry, disableTelemetry, getTelemetryStatus, uploadTelemetry, TelemetryUploadOutputSchema, runTest, TestRunOutputSchema, runTestService, TestServiceOutputSchema, runTestIntegration, TestIntegrationOutputSchema, runTestStatic, TestStaticOutputSchema, generateOwaspTop10Tests, TestOwaspGenerateOutputSchema, updateGoldenRecord, generateE2ETests, generateSecurityTests, generateUnitTests, generateApiTests, generateComponentTests, generateE2EScaffold, validateConfig, ValidateConfigOutputSchema, deploySchemaToProduction, WorkflowNotifyOutputSchema, devopsSync, workflowSync, validateInfrastructure, emitWorkflowValidateFailureCapsule, emitWorkflowAnnotations, writeWorkflowValidateStepSummary, exportWorkflowReportJson, WorkflowValidateInfrastructureOutputSchema, createSuccessEnvelopeSchema, CLI_CONTRACT_VERSION, runChecks, RunCheckOutputSchema, formatDuration as formatDuration$1, GITHUB_API, loadRunaConfig, getClassificationForProfile, loadRunaConfigOrThrow, recordSchemaAudit, RecordSchemaAuditOutputSchema, createBackup, CreateBackupOutputSchema, listBackups, ListBackupsOutputSchema, getBackupMetadata, restoreBackup, RestoreBackupOutputSchema, deleteBackup, DeleteBackupOutputSchema, detectSchemaNames, SUPABASE_SYSTEM_SCHEMAS, dbSeedApply, writeDbSeedStepSummary, DbSeedApplyOutputSchema, emitDbSeedFailureCapsule, syncEnvironment, EnvSyncOutputSchema, detectDatabasePackage, findProjectRoot as findProjectRoot$1, TelemetryEnableOutputSchema, TelemetryDisableOutputSchema, TelemetryStatusOutputSchema, workflowNotify, DevOpsSyncOutputSchema, WorkflowSyncOutputSchema, formatCLIError, DATABASE_PACKAGE_CANDIDATES, getStatusIcon as getStatusIcon$1, findWorkspaceRoot as findWorkspaceRoot$1, checkExtensionConfig, UpgradeTransaction, readRunaVersion, syncTemplates, SyncOutputSchema, ErrorEnvelopeSchema, preCheckSync, findConflictFiles, TestUnitGenOutputSchema, TestE2EGenerateOutputSchema, TestSecurityGenOutputSchema, TestApiGenOutputSchema, TestComponentGenOutputSchema } from '@runa-ai/runa';
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import
|
|
11
|
+
import fs9, { mkdir, writeFile, appendFile, readFile, rm, stat, realpath, cp, readdir, lstat } from 'fs/promises';
|
|
12
12
|
import { promisify } from 'util';
|
|
13
13
|
import { glob } from 'glob';
|
|
14
14
|
import { Project, Node, SyntaxKind } from 'ts-morph';
|
|
@@ -925,7 +925,7 @@ var CLI_VERSION, HAS_ADMIN_COMMAND;
|
|
|
925
925
|
var init_version = __esm({
|
|
926
926
|
"src/version.ts"() {
|
|
927
927
|
init_esm_shims();
|
|
928
|
-
CLI_VERSION = "0.5.
|
|
928
|
+
CLI_VERSION = "0.5.42";
|
|
929
929
|
HAS_ADMIN_COMMAND = false;
|
|
930
930
|
}
|
|
931
931
|
});
|
|
@@ -1323,7 +1323,7 @@ var init_dependency_analyzer = __esm({
|
|
|
1323
1323
|
async analyze(options) {
|
|
1324
1324
|
const packageJsonPath = path10__default.join(options.rootDir, "package.json");
|
|
1325
1325
|
try {
|
|
1326
|
-
await
|
|
1326
|
+
await fs9.access(packageJsonPath);
|
|
1327
1327
|
} catch {
|
|
1328
1328
|
return [];
|
|
1329
1329
|
}
|
|
@@ -1356,7 +1356,7 @@ var init_dependency_analyzer = __esm({
|
|
|
1356
1356
|
}
|
|
1357
1357
|
async checkTyposquatting(packageJsonPath) {
|
|
1358
1358
|
try {
|
|
1359
|
-
const content = await
|
|
1359
|
+
const content = await fs9.readFile(packageJsonPath, "utf-8");
|
|
1360
1360
|
const packageJson = JSON.parse(content);
|
|
1361
1361
|
const allDeps = {
|
|
1362
1362
|
...packageJson.dependencies || {},
|
|
@@ -1534,7 +1534,7 @@ function createPolicyFromMatch(match, content, filePath) {
|
|
|
1534
1534
|
};
|
|
1535
1535
|
}
|
|
1536
1536
|
async function parseSqlFileForPolicies(filePath) {
|
|
1537
|
-
const content = await
|
|
1537
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
1538
1538
|
const matches = findPolicyMatches(content);
|
|
1539
1539
|
return matches.map((match) => createPolicyFromMatch(match, content, filePath));
|
|
1540
1540
|
}
|
|
@@ -2019,11 +2019,11 @@ function scanLineForSecrets(line, lineNumber, filePath) {
|
|
|
2019
2019
|
return findings;
|
|
2020
2020
|
}
|
|
2021
2021
|
async function scanFileForSecrets(filePath) {
|
|
2022
|
-
const stats = await
|
|
2022
|
+
const stats = await fs9.stat(filePath);
|
|
2023
2023
|
if (stats.size > MAX_FILE_SIZE) {
|
|
2024
2024
|
return [];
|
|
2025
2025
|
}
|
|
2026
|
-
const content = await
|
|
2026
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
2027
2027
|
const lines = content.split("\n");
|
|
2028
2028
|
const findings = [];
|
|
2029
2029
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -3088,7 +3088,7 @@ var init_path_validation = __esm({
|
|
|
3088
3088
|
async function loadConfig(configPath, rootDir) {
|
|
3089
3089
|
const fullPath = resolvePathWithinBoundary(configPath, rootDir);
|
|
3090
3090
|
try {
|
|
3091
|
-
const content = await
|
|
3091
|
+
const content = await fs9.readFile(fullPath, "utf-8");
|
|
3092
3092
|
if (fullPath.endsWith(".yml") || fullPath.endsWith(".yaml")) {
|
|
3093
3093
|
return yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
3094
3094
|
}
|
|
@@ -3103,7 +3103,7 @@ async function loadConfig(configPath, rootDir) {
|
|
|
3103
3103
|
async function loadIgnores(ignorePath, rootDir) {
|
|
3104
3104
|
const fullPath = resolvePathWithinBoundary(ignorePath, rootDir);
|
|
3105
3105
|
try {
|
|
3106
|
-
const content = await
|
|
3106
|
+
const content = await fs9.readFile(fullPath, "utf-8");
|
|
3107
3107
|
let parsed;
|
|
3108
3108
|
if (fullPath.endsWith(".yml") || fullPath.endsWith(".yaml")) {
|
|
3109
3109
|
parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
@@ -3178,7 +3178,7 @@ function createAnnotationPatterns() {
|
|
|
3178
3178
|
async function parseInlineAnnotations(filePath) {
|
|
3179
3179
|
const annotations = [];
|
|
3180
3180
|
try {
|
|
3181
|
-
const content = await
|
|
3181
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
3182
3182
|
const lines = content.split("\n");
|
|
3183
3183
|
for (let i = 0; i < lines.length; i++) {
|
|
3184
3184
|
const line = lines[i];
|
|
@@ -7220,7 +7220,7 @@ function printSummary(logger16, output3) {
|
|
|
7220
7220
|
}
|
|
7221
7221
|
}
|
|
7222
7222
|
function findRepoRoot(startDir) {
|
|
7223
|
-
const { existsSync: existsSync50, readFileSync:
|
|
7223
|
+
const { existsSync: existsSync50, readFileSync: readFileSync28 } = __require("fs");
|
|
7224
7224
|
const { join: join22, dirname: dirname5 } = __require("path");
|
|
7225
7225
|
let current = startDir;
|
|
7226
7226
|
while (current !== dirname5(current)) {
|
|
@@ -7230,7 +7230,7 @@ function findRepoRoot(startDir) {
|
|
|
7230
7230
|
const pkgPath = join22(current, "package.json");
|
|
7231
7231
|
if (existsSync50(pkgPath)) {
|
|
7232
7232
|
try {
|
|
7233
|
-
const pkg = JSON.parse(
|
|
7233
|
+
const pkg = JSON.parse(readFileSync28(pkgPath, "utf-8"));
|
|
7234
7234
|
if (pkg.workspaces) {
|
|
7235
7235
|
return current;
|
|
7236
7236
|
}
|
|
@@ -7527,8 +7527,8 @@ function readPortFromScripts(appDir) {
|
|
|
7527
7527
|
const pkgPath = path10__default.join(appDir, "package.json");
|
|
7528
7528
|
if (!existsSync(pkgPath)) return 3e3;
|
|
7529
7529
|
try {
|
|
7530
|
-
const { readFileSync:
|
|
7531
|
-
const raw =
|
|
7530
|
+
const { readFileSync: readFileSync28 } = __require("fs");
|
|
7531
|
+
const raw = readFileSync28(pkgPath, "utf-8");
|
|
7532
7532
|
const parsed = JSON.parse(raw);
|
|
7533
7533
|
const scripts = parsed.scripts;
|
|
7534
7534
|
for (const key of ["start:ci", "start", "dev"]) {
|
|
@@ -7546,8 +7546,8 @@ function findWebAppUnderApps(repoRoot) {
|
|
|
7546
7546
|
const appsDir = path10__default.join(repoRoot, "apps");
|
|
7547
7547
|
if (!existsSync(appsDir)) return null;
|
|
7548
7548
|
try {
|
|
7549
|
-
const { readdirSync:
|
|
7550
|
-
const entries =
|
|
7549
|
+
const { readdirSync: readdirSync11 } = __require("fs");
|
|
7550
|
+
const entries = readdirSync11(appsDir, { withFileTypes: true });
|
|
7551
7551
|
const priority = ["web", "dashboard", "app", "frontend", "client"];
|
|
7552
7552
|
for (const name of priority) {
|
|
7553
7553
|
const candidate = path10__default.join(appsDir, name);
|
|
@@ -9559,8 +9559,8 @@ async function detectRisks(repoRoot, tmpDir) {
|
|
|
9559
9559
|
} catch (error) {
|
|
9560
9560
|
let logContent = "";
|
|
9561
9561
|
try {
|
|
9562
|
-
const { readFileSync:
|
|
9563
|
-
logContent =
|
|
9562
|
+
const { readFileSync: readFileSync28 } = await import('fs');
|
|
9563
|
+
logContent = readFileSync28(logFile, "utf-8");
|
|
9564
9564
|
} catch {
|
|
9565
9565
|
}
|
|
9566
9566
|
const isInitialDeployment = logContent.includes("No common ancestor") || logContent.includes("INITIAL DEPLOYMENT");
|
|
@@ -9688,8 +9688,8 @@ async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, pro
|
|
|
9688
9688
|
const totalMs = Date.now() - startTime;
|
|
9689
9689
|
let logContent = "";
|
|
9690
9690
|
try {
|
|
9691
|
-
const { readFileSync:
|
|
9692
|
-
logContent =
|
|
9691
|
+
const { readFileSync: readFileSync28 } = await import('fs');
|
|
9692
|
+
logContent = readFileSync28(logPath, "utf-8");
|
|
9693
9693
|
} catch {
|
|
9694
9694
|
}
|
|
9695
9695
|
const parsed = parseApplyLog(logContent);
|
|
@@ -11398,98 +11398,32 @@ init_esm_shims();
|
|
|
11398
11398
|
|
|
11399
11399
|
// src/commands/ci/machine/actors/db/schema-stats.ts
|
|
11400
11400
|
init_esm_shims();
|
|
11401
|
-
var
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
schemas[schemaName] = { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
11428
|
-
}
|
|
11429
|
-
}
|
|
11430
|
-
function parseLocalSqlFile(content, schemas, total) {
|
|
11431
|
-
const cleanContent = content.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
11432
|
-
const tableRegex = new RegExp(
|
|
11433
|
-
`CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?${SCHEMA_TABLE_PATTERN}`,
|
|
11434
|
-
"gi"
|
|
11435
|
-
);
|
|
11436
|
-
for (const match of cleanContent.matchAll(tableRegex)) {
|
|
11437
|
-
const schemaName = extractIdentifier(match[1], match[2]);
|
|
11438
|
-
ensureSchema(schemas, schemaName, EXCLUDED_SCHEMAS_FOR_LOCAL);
|
|
11439
|
-
if (schemas[schemaName]) {
|
|
11440
|
-
schemas[schemaName].tables++;
|
|
11441
|
-
total.tables++;
|
|
11442
|
-
}
|
|
11443
|
-
}
|
|
11444
|
-
const functionRegex = new RegExp(
|
|
11445
|
-
`CREATE\\s+(?:OR\\s+REPLACE\\s+)?FUNCTION\\s+${SCHEMA_TABLE_PATTERN}`,
|
|
11446
|
-
"gi"
|
|
11447
|
-
);
|
|
11448
|
-
for (const match of cleanContent.matchAll(functionRegex)) {
|
|
11449
|
-
const schemaName = extractIdentifier(match[1], match[2]);
|
|
11450
|
-
ensureSchema(schemas, schemaName, EXCLUDED_SCHEMAS_FOR_LOCAL);
|
|
11451
|
-
if (schemas[schemaName]) {
|
|
11452
|
-
schemas[schemaName].functions++;
|
|
11453
|
-
total.functions++;
|
|
11454
|
-
}
|
|
11455
|
-
}
|
|
11456
|
-
const policyRegex = new RegExp(
|
|
11457
|
-
`CREATE\\s+POLICY\\s+(?:"[^"]+"|'[^']+'|\\S+)\\s+ON\\s+${SCHEMA_TABLE_PATTERN}`,
|
|
11458
|
-
"gi"
|
|
11459
|
-
);
|
|
11460
|
-
for (const match of cleanContent.matchAll(policyRegex)) {
|
|
11461
|
-
const schemaName = extractIdentifier(match[1], match[2]);
|
|
11462
|
-
ensureSchema(schemas, schemaName, EXCLUDED_SCHEMAS_FOR_LOCAL);
|
|
11463
|
-
if (schemas[schemaName]) {
|
|
11464
|
-
schemas[schemaName].policies++;
|
|
11465
|
-
total.policies++;
|
|
11466
|
-
}
|
|
11467
|
-
}
|
|
11468
|
-
const indexRegex = new RegExp(
|
|
11469
|
-
`CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:CONCURRENTLY\\s+)?${IDENT_PATTERN}\\s+ON\\s+${SCHEMA_TABLE_PATTERN}`,
|
|
11470
|
-
"gi"
|
|
11471
|
-
);
|
|
11472
|
-
for (const match of cleanContent.matchAll(indexRegex)) {
|
|
11473
|
-
const schemaName = extractIdentifier(match[3], match[4]);
|
|
11474
|
-
ensureSchema(schemas, schemaName, EXCLUDED_SCHEMAS_FOR_LOCAL);
|
|
11475
|
-
if (schemas[schemaName]) {
|
|
11476
|
-
schemas[schemaName].indexes++;
|
|
11477
|
-
total.indexes++;
|
|
11478
|
-
}
|
|
11479
|
-
}
|
|
11480
|
-
const triggerRegex = new RegExp(
|
|
11481
|
-
`CREATE\\s+TRIGGER\\s+${IDENT_PATTERN}[\\s\\S]*?ON\\s+${SCHEMA_TABLE_PATTERN}`,
|
|
11482
|
-
"gi"
|
|
11483
|
-
);
|
|
11484
|
-
for (const match of cleanContent.matchAll(triggerRegex)) {
|
|
11485
|
-
const schemaName = extractIdentifier(match[3], match[4]);
|
|
11486
|
-
ensureSchema(schemas, schemaName, EXCLUDED_SCHEMAS_FOR_LOCAL);
|
|
11487
|
-
if (schemas[schemaName]) {
|
|
11488
|
-
schemas[schemaName].triggers++;
|
|
11489
|
-
total.triggers++;
|
|
11490
|
-
}
|
|
11491
|
-
}
|
|
11492
|
-
}
|
|
11401
|
+
var EXCLUDED_SCHEMAS2 = [
|
|
11402
|
+
// PostgreSQL system schemas
|
|
11403
|
+
"pg_catalog",
|
|
11404
|
+
"pg_toast",
|
|
11405
|
+
"information_schema",
|
|
11406
|
+
// Supabase managed schemas (vary by Supabase version)
|
|
11407
|
+
"auth",
|
|
11408
|
+
"storage",
|
|
11409
|
+
"realtime",
|
|
11410
|
+
"_realtime",
|
|
11411
|
+
// Supabase realtime internal schema
|
|
11412
|
+
"supabase_functions",
|
|
11413
|
+
"supabase_migrations",
|
|
11414
|
+
"vault",
|
|
11415
|
+
"pgsodium",
|
|
11416
|
+
"pgsodium_masks",
|
|
11417
|
+
"graphql",
|
|
11418
|
+
"graphql_public",
|
|
11419
|
+
"extensions",
|
|
11420
|
+
// PostgREST
|
|
11421
|
+
"net",
|
|
11422
|
+
// Cron
|
|
11423
|
+
"cron",
|
|
11424
|
+
// pgTAP test schema (test-only, should not exist in production)
|
|
11425
|
+
"tests"
|
|
11426
|
+
];
|
|
11493
11427
|
function processQueryResult(result, schemas, total, field) {
|
|
11494
11428
|
for (const row of result) {
|
|
11495
11429
|
const schemaName = row.schemaname;
|
|
@@ -11564,16 +11498,53 @@ async function executeSchemaQueries(sql, schemaList, schemas, total) {
|
|
|
11564
11498
|
`;
|
|
11565
11499
|
processQueryResult(triggersResult, schemas, total, "triggers");
|
|
11566
11500
|
}
|
|
11567
|
-
async function
|
|
11501
|
+
async function getDetailedIndexList(sql, schemaList) {
|
|
11502
|
+
const result = await sql`
|
|
11503
|
+
SELECT
|
|
11504
|
+
n.nspname as schema,
|
|
11505
|
+
i.relname as name,
|
|
11506
|
+
t.relname as table,
|
|
11507
|
+
idx.indisunique as is_unique,
|
|
11508
|
+
idx.indpred IS NOT NULL as is_partial,
|
|
11509
|
+
CASE
|
|
11510
|
+
WHEN idx.indpred IS NOT NULL
|
|
11511
|
+
THEN pg_get_expr(idx.indpred, idx.indrelid, true)
|
|
11512
|
+
ELSE NULL
|
|
11513
|
+
END as where_clause
|
|
11514
|
+
FROM pg_index idx
|
|
11515
|
+
JOIN pg_class i ON i.oid = idx.indexrelid
|
|
11516
|
+
JOIN pg_class t ON t.oid = idx.indrelid
|
|
11517
|
+
JOIN pg_namespace n ON n.oid = i.relnamespace
|
|
11518
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
11519
|
+
AND NOT EXISTS (
|
|
11520
|
+
SELECT 1 FROM pg_constraint c
|
|
11521
|
+
WHERE c.conindid = idx.indexrelid
|
|
11522
|
+
)
|
|
11523
|
+
ORDER BY n.nspname, t.relname, i.relname
|
|
11524
|
+
`;
|
|
11525
|
+
return result.map((row) => ({
|
|
11526
|
+
schema: row.schema,
|
|
11527
|
+
name: row.name,
|
|
11528
|
+
table: row.table,
|
|
11529
|
+
isUnique: row.is_unique,
|
|
11530
|
+
isPartial: row.is_partial,
|
|
11531
|
+
whereClause: row.where_clause || void 0
|
|
11532
|
+
}));
|
|
11533
|
+
}
|
|
11534
|
+
async function getUserSchemas(sql) {
|
|
11535
|
+
const excludeList = EXCLUDED_SCHEMAS2.map((s) => `'${s}'`).join(", ");
|
|
11536
|
+
const result = await sql`
|
|
11537
|
+
SELECT nspname
|
|
11538
|
+
FROM pg_namespace
|
|
11539
|
+
WHERE nspname NOT IN (${sql.unsafe(excludeList)})
|
|
11540
|
+
AND nspname NOT LIKE 'pg_%'
|
|
11541
|
+
ORDER BY nspname
|
|
11542
|
+
`;
|
|
11543
|
+
return result.map((row) => row.nspname);
|
|
11544
|
+
}
|
|
11545
|
+
async function getDbSchemaStats(databaseUrl) {
|
|
11568
11546
|
const schemas = {};
|
|
11569
11547
|
const total = { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
11570
|
-
if (targetSchemas.length === 0) {
|
|
11571
|
-
return { schemas, total };
|
|
11572
|
-
}
|
|
11573
|
-
for (const schemaName of targetSchemas) {
|
|
11574
|
-
schemas[schemaName] = { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
11575
|
-
}
|
|
11576
|
-
const schemaList = targetSchemas.map((s) => `'${s}'`).join(", ");
|
|
11577
11548
|
let sql = null;
|
|
11578
11549
|
const isRemoteSupabase = databaseUrl.includes(".supabase.co");
|
|
11579
11550
|
try {
|
|
@@ -11584,7 +11555,17 @@ async function getDbSchemaStats(databaseUrl, targetSchemas) {
|
|
|
11584
11555
|
// Supabase production requires SSL; local Docker does not
|
|
11585
11556
|
...isRemoteSupabase && { ssl: "require" }
|
|
11586
11557
|
});
|
|
11558
|
+
const userSchemas = await getUserSchemas(sql);
|
|
11559
|
+
if (userSchemas.length === 0) {
|
|
11560
|
+
return { schemas, total };
|
|
11561
|
+
}
|
|
11562
|
+
for (const schemaName of userSchemas) {
|
|
11563
|
+
schemas[schemaName] = { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
11564
|
+
}
|
|
11565
|
+
const schemaList = userSchemas.map((s) => `'${s}'`).join(", ");
|
|
11587
11566
|
await executeSchemaQueries(sql, schemaList, schemas, total);
|
|
11567
|
+
const indexList = await getDetailedIndexList(sql, schemaList);
|
|
11568
|
+
return { schemas, total, indexList };
|
|
11588
11569
|
} catch (error) {
|
|
11589
11570
|
console.warn(`[schema-stats] Could not query database: ${error}`);
|
|
11590
11571
|
} finally {
|
|
@@ -11637,39 +11618,73 @@ function hasDisplayedStatsDiff(a, b) {
|
|
|
11637
11618
|
function getActiveSchemas(stats) {
|
|
11638
11619
|
return Object.entries(stats.schemas).filter(([_, s]) => s.tables > 0 || s.functions > 0 || s.policies > 0).map(([name]) => name).sort();
|
|
11639
11620
|
}
|
|
11621
|
+
function compareIndexLists(reference, target) {
|
|
11622
|
+
const refIndexes = reference.indexList || [];
|
|
11623
|
+
const targetIndexes = target.indexList || [];
|
|
11624
|
+
const refKeys = new Set(refIndexes.map((i) => `${i.schema}.${i.name}`));
|
|
11625
|
+
const targetKeys = new Set(targetIndexes.map((i) => `${i.schema}.${i.name}`));
|
|
11626
|
+
const missing = refIndexes.filter((i) => !targetKeys.has(`${i.schema}.${i.name}`));
|
|
11627
|
+
const extra = targetIndexes.filter((i) => !refKeys.has(`${i.schema}.${i.name}`));
|
|
11628
|
+
return { missing, extra };
|
|
11629
|
+
}
|
|
11630
|
+
function formatIndexInfo(index) {
|
|
11631
|
+
const qualifiedName = `${index.schema}.${index.name}`;
|
|
11632
|
+
const tags = [];
|
|
11633
|
+
if (index.isUnique) tags.push("unique");
|
|
11634
|
+
if (index.isPartial) tags.push("partial");
|
|
11635
|
+
const tagStr = tags.length > 0 ? ` (${tags.join(", ")})` : "";
|
|
11636
|
+
return `${qualifiedName}${tagStr}`;
|
|
11637
|
+
}
|
|
11638
|
+
function hasIndexDiff(diff) {
|
|
11639
|
+
return diff.missing.length > 0 || diff.extra.length > 0;
|
|
11640
|
+
}
|
|
11640
11641
|
|
|
11641
11642
|
// src/commands/ci/machine/actors/db/collect-schema-stats.ts
|
|
11643
|
+
function emptyStats() {
|
|
11644
|
+
return {
|
|
11645
|
+
schemas: {},
|
|
11646
|
+
total: { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 }
|
|
11647
|
+
};
|
|
11648
|
+
}
|
|
11642
11649
|
var collectSchemaStatsActor = fromPromise(
|
|
11643
11650
|
async ({ input: input3 }) => {
|
|
11644
|
-
const {
|
|
11645
|
-
console.log("[schema-stats] Collecting schema statistics...");
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11651
|
+
const { localDbUrl, ciDbUrl, queryProduction } = input3;
|
|
11652
|
+
console.log("[schema-stats] Collecting schema statistics (DB query mode)...");
|
|
11653
|
+
let local = emptyStats();
|
|
11654
|
+
if (localDbUrl) {
|
|
11655
|
+
console.log("[schema-stats] Querying local database...");
|
|
11656
|
+
try {
|
|
11657
|
+
local = await getDbSchemaStats(localDbUrl);
|
|
11658
|
+
const schemas = Object.keys(local.schemas);
|
|
11659
|
+
console.log(
|
|
11660
|
+
`[schema-stats] Local: ${local.total.tables}T ${local.total.functions}F ${local.total.policies}P (schemas: ${schemas.join(", ")})`
|
|
11661
|
+
);
|
|
11662
|
+
} catch (error) {
|
|
11663
|
+
console.warn(`[schema-stats] Failed to query local database: ${error}`);
|
|
11664
|
+
}
|
|
11665
|
+
} else {
|
|
11666
|
+
console.log("[schema-stats] No local database URL, skipping local stats");
|
|
11667
|
+
}
|
|
11668
|
+
let ci = emptyStats();
|
|
11653
11669
|
if (ciDbUrl) {
|
|
11654
11670
|
console.log("[schema-stats] Querying CI database...");
|
|
11655
11671
|
try {
|
|
11656
|
-
ci = await getDbSchemaStats(ciDbUrl
|
|
11672
|
+
ci = await getDbSchemaStats(ciDbUrl);
|
|
11657
11673
|
console.log(
|
|
11658
11674
|
`[schema-stats] CI: ${ci.total.tables}T ${ci.total.functions}F ${ci.total.policies}P`
|
|
11659
11675
|
);
|
|
11660
11676
|
} catch (error) {
|
|
11661
11677
|
console.warn(`[schema-stats] Failed to query CI database: ${error}`);
|
|
11662
|
-
ci = local;
|
|
11663
11678
|
}
|
|
11664
11679
|
} else {
|
|
11665
|
-
console.log("[schema-stats] No CI database URL,
|
|
11680
|
+
console.log("[schema-stats] No CI database URL, skipping CI stats");
|
|
11666
11681
|
}
|
|
11667
11682
|
let production = null;
|
|
11668
11683
|
const productionUrl = process.env.GH_DATABASE_URL_ADMIN || process.env.GH_DATABASE_URL;
|
|
11669
11684
|
if (queryProduction && productionUrl) {
|
|
11670
11685
|
console.log("[schema-stats] Querying production database...");
|
|
11671
11686
|
try {
|
|
11672
|
-
production = await getDbSchemaStats(productionUrl
|
|
11687
|
+
production = await getDbSchemaStats(productionUrl);
|
|
11673
11688
|
console.log(
|
|
11674
11689
|
`[schema-stats] Production: ${production.total.tables}T ${production.total.functions}F ${production.total.policies}P`
|
|
11675
11690
|
);
|
|
@@ -11902,11 +11917,11 @@ function getIdempotentRoleNames(repoRoot) {
|
|
|
11902
11917
|
const idempotentDir = path10__default.join(repoRoot, "supabase", "schemas", "idempotent");
|
|
11903
11918
|
const roles = [];
|
|
11904
11919
|
try {
|
|
11905
|
-
const
|
|
11906
|
-
if (!
|
|
11907
|
-
const files =
|
|
11920
|
+
const fs14 = __require("fs");
|
|
11921
|
+
if (!fs14.existsSync(idempotentDir)) return [];
|
|
11922
|
+
const files = fs14.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
|
|
11908
11923
|
for (const file of files) {
|
|
11909
|
-
const content =
|
|
11924
|
+
const content = fs14.readFileSync(path10__default.join(idempotentDir, file), "utf-8");
|
|
11910
11925
|
const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
|
|
11911
11926
|
for (const match of roleMatches) {
|
|
11912
11927
|
if (match[1]) roles.push(match[1].toLowerCase());
|
|
@@ -12016,8 +12031,8 @@ function isCommandSuccess(exitCode, fullOutput) {
|
|
|
12016
12031
|
}
|
|
12017
12032
|
async function writeLogFile(logFile, content) {
|
|
12018
12033
|
try {
|
|
12019
|
-
const
|
|
12020
|
-
await
|
|
12034
|
+
const fs14 = await import('fs/promises');
|
|
12035
|
+
await fs14.writeFile(logFile, content);
|
|
12021
12036
|
console.log(`[DEBUG] productionPreviewActor: wrote log to ${logFile}`);
|
|
12022
12037
|
} catch {
|
|
12023
12038
|
}
|
|
@@ -12308,11 +12323,11 @@ function getIdempotentRoleNames2(repoRoot) {
|
|
|
12308
12323
|
const idempotentDir = path10__default.join(repoRoot, "supabase", "schemas", "idempotent");
|
|
12309
12324
|
const roles = [];
|
|
12310
12325
|
try {
|
|
12311
|
-
const
|
|
12312
|
-
if (!
|
|
12313
|
-
const files =
|
|
12326
|
+
const fs14 = __require("fs");
|
|
12327
|
+
if (!fs14.existsSync(idempotentDir)) return [];
|
|
12328
|
+
const files = fs14.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
|
|
12314
12329
|
for (const file of files) {
|
|
12315
|
-
const content =
|
|
12330
|
+
const content = fs14.readFileSync(path10__default.join(idempotentDir, file), "utf-8");
|
|
12316
12331
|
const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
|
|
12317
12332
|
for (const match of roleMatches) {
|
|
12318
12333
|
if (match[1]) roles.push(match[1].toLowerCase());
|
|
@@ -13755,7 +13770,7 @@ function formatSkippedLayers(layerSkipReasons, originalSelectedLayers) {
|
|
|
13755
13770
|
|
|
13756
13771
|
// src/commands/ci/machine/formatters/sections/schema-matrix.ts
|
|
13757
13772
|
init_esm_shims();
|
|
13758
|
-
function
|
|
13773
|
+
function emptyStats2() {
|
|
13759
13774
|
return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
13760
13775
|
}
|
|
13761
13776
|
function formatLocalCell(stats) {
|
|
@@ -13802,8 +13817,8 @@ function getSortedActiveSchemas(schemaStats) {
|
|
|
13802
13817
|
function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction) {
|
|
13803
13818
|
const lines = [];
|
|
13804
13819
|
for (const schemaName of sortedSchemas) {
|
|
13805
|
-
const localStats = schemaStats.local.schemas[schemaName] ??
|
|
13806
|
-
const ciStats = schemaStats.ci.schemas[schemaName] ??
|
|
13820
|
+
const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats2();
|
|
13821
|
+
const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats2();
|
|
13807
13822
|
const prodStats = schemaStats.production?.schemas[schemaName] ?? null;
|
|
13808
13823
|
const localCell = formatLocalCell(localStats);
|
|
13809
13824
|
const ciCell = formatCiCellWithDiff(ciStats, localStats);
|
|
@@ -13827,6 +13842,28 @@ function generateSchemaMatrixSummary(localTotal, ciTotal, prodTotal) {
|
|
|
13827
13842
|
}
|
|
13828
13843
|
return parts.join(" ");
|
|
13829
13844
|
}
|
|
13845
|
+
function generateIndexDiffSection(schemaStats) {
|
|
13846
|
+
if (!schemaStats.production) return [];
|
|
13847
|
+
const diff = compareIndexLists(schemaStats.local, schemaStats.production);
|
|
13848
|
+
if (!hasIndexDiff(diff)) return [];
|
|
13849
|
+
const lines = [];
|
|
13850
|
+
if (diff.missing.length > 0) {
|
|
13851
|
+
lines.push("");
|
|
13852
|
+
lines.push("> \u26A0\uFE0F **\u672C\u756A\u306B\u4E0D\u8DB3\u3057\u3066\u3044\u308B\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9:**");
|
|
13853
|
+
for (const idx of diff.missing) {
|
|
13854
|
+
const whereInfo = idx.whereClause ? ` WHERE ${idx.whereClause}` : "";
|
|
13855
|
+
lines.push(`> - \`${formatIndexInfo(idx)}\` on \`${idx.table}\`${whereInfo}`);
|
|
13856
|
+
}
|
|
13857
|
+
}
|
|
13858
|
+
if (diff.extra.length > 0) {
|
|
13859
|
+
lines.push("");
|
|
13860
|
+
lines.push("> \u{1F4DD} **\u672C\u756A\u306B\u4F59\u5206\u306A\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9:**");
|
|
13861
|
+
for (const idx of diff.extra) {
|
|
13862
|
+
lines.push(`> - \`${formatIndexInfo(idx)}\` on \`${idx.table}\``);
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
return lines;
|
|
13866
|
+
}
|
|
13830
13867
|
function formatSchemaMatrix(schemaStats, gitDiff) {
|
|
13831
13868
|
if (!schemaStats) {
|
|
13832
13869
|
return ["### \u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1", "", "_\u30B9\u30AD\u30FC\u30DE\u7D71\u8A08\u60C5\u5831\u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093_", ""];
|
|
@@ -13846,6 +13883,7 @@ function formatSchemaMatrix(schemaStats, gitDiff) {
|
|
|
13846
13883
|
const prodTotalCell = production ? formatProdTotalCellWithDiff(production.total, local.total) : null;
|
|
13847
13884
|
const totalRow = hasProduction ? `| **Total** | ${localTotalCell} | ${ciTotalCell} | ${prodTotalCell ?? "n/a"} |` : `| **Total** | ${localTotalCell} | ${ciTotalCell} |`;
|
|
13848
13885
|
const summary = generateSchemaMatrixSummary(local.total, ci.total, production?.total ?? null);
|
|
13886
|
+
const indexDiffSection = generateIndexDiffSection(schemaStats);
|
|
13849
13887
|
return [
|
|
13850
13888
|
header,
|
|
13851
13889
|
"",
|
|
@@ -13854,6 +13892,7 @@ function formatSchemaMatrix(schemaStats, gitDiff) {
|
|
|
13854
13892
|
totalRow,
|
|
13855
13893
|
"",
|
|
13856
13894
|
summary,
|
|
13895
|
+
...indexDiffSection,
|
|
13857
13896
|
"",
|
|
13858
13897
|
"<sub>T=\u30C6\u30FC\u30D6\u30EB F=\u95A2\u6570 P=\u30DD\u30EA\u30B7\u30FC I=\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 Tr=\u30C8\u30EA\u30AC\u30FC</sub>",
|
|
13859
13898
|
""
|
|
@@ -13951,7 +13990,7 @@ function generatePreviewDetails(prodPreview) {
|
|
|
13951
13990
|
""
|
|
13952
13991
|
];
|
|
13953
13992
|
}
|
|
13954
|
-
function generateProductionPreviewSection(prodPreview, schemaDrift) {
|
|
13993
|
+
function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats) {
|
|
13955
13994
|
if (!prodPreview?.executed) {
|
|
13956
13995
|
if (schemaDrift?.gitDiff?.filesChanged?.length) {
|
|
13957
13996
|
return [
|
|
@@ -13978,18 +14017,37 @@ function generateProductionPreviewSection(prodPreview, schemaDrift) {
|
|
|
13978
14017
|
}
|
|
13979
14018
|
return lines;
|
|
13980
14019
|
}
|
|
14020
|
+
if (schemaStats?.local && schemaStats?.production) {
|
|
14021
|
+
const indexDiff = compareIndexLists(schemaStats.local, schemaStats.production);
|
|
14022
|
+
if (hasIndexDiff(indexDiff)) {
|
|
14023
|
+
const missingCount = indexDiff.missing.length;
|
|
14024
|
+
const extraCount = indexDiff.extra.length;
|
|
14025
|
+
const diffParts = [];
|
|
14026
|
+
if (missingCount > 0) diffParts.push(`${missingCount}\u4EF6\u4E0D\u8DB3`);
|
|
14027
|
+
if (extraCount > 0) diffParts.push(`${extraCount}\u4EF6\u4F59\u5206`);
|
|
14028
|
+
return [
|
|
14029
|
+
`> \u26A0\uFE0F **\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u5DEE\u5206\u691C\u51FA** (${diffParts.join(", ")})`,
|
|
14030
|
+
">",
|
|
14031
|
+
"> pg-schema-diff \u3067\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u304C\u3001\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u5DEE\u5206\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
14032
|
+
"> \u30B9\u30AD\u30FC\u30DE\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9\u306E\u8A73\u7D30\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
14033
|
+
""
|
|
14034
|
+
];
|
|
14035
|
+
}
|
|
14036
|
+
}
|
|
13981
14037
|
return ["**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u2705 \u672C\u756A\u306B\u9069\u7528\u3059\u308B\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093", ""];
|
|
13982
14038
|
}
|
|
13983
|
-
function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, env2) {
|
|
14039
|
+
function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, schemaStats, env2) {
|
|
13984
14040
|
if (!checkIfDeployable(exitCode, layerResults)) return [];
|
|
13985
14041
|
const deployWorkflowUrl = env2.repository ? `${env2.serverUrl}/${env2.repository}/actions/workflows/deploy-db.yml` : null;
|
|
13986
|
-
const
|
|
14042
|
+
const hasPgSchemaDiffChanges = productionPreview?.hasChanges === true;
|
|
14043
|
+
const hasIndexChanges = schemaStats?.local && schemaStats?.production ? hasIndexDiff(compareIndexLists(schemaStats.local, schemaStats.production)) : false;
|
|
14044
|
+
const hasSchemaChanges = hasPgSchemaDiffChanges || hasIndexChanges;
|
|
13987
14045
|
const headerEmoji = hasSchemaChanges ? "\u{1F6A8}" : "\u{1F680}";
|
|
13988
14046
|
const headerText = hasSchemaChanges ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
|
|
13989
14047
|
const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
|
|
13990
14048
|
if (exitCode !== 0)
|
|
13991
14049
|
lines.push("> **\u6CE8**: Layer 4 (E2E) \u306F\u5931\u6557\u3057\u307E\u3057\u305F\u304C\u3001\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u30C7\u30D7\u30ED\u30A4\u53EF\u80FD\u3067\u3059\u3002", "");
|
|
13992
|
-
lines.push(...generateProductionPreviewSection(productionPreview, schemaDrift));
|
|
14050
|
+
lines.push(...generateProductionPreviewSection(productionPreview, schemaDrift, schemaStats));
|
|
13993
14051
|
if (deployWorkflowUrl) {
|
|
13994
14052
|
const buttonText = hasSchemaChanges ? "\u26A1 \u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4" : "\u25B6\uFE0F \u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4";
|
|
13995
14053
|
lines.push(
|
|
@@ -14034,6 +14092,7 @@ function generateIntermediateCommentBody(input3) {
|
|
|
14034
14092
|
gitBranchName,
|
|
14035
14093
|
productionPreview,
|
|
14036
14094
|
schemaDrift,
|
|
14095
|
+
input3.schemaStats ?? null,
|
|
14037
14096
|
env2
|
|
14038
14097
|
),
|
|
14039
14098
|
"<sub>\u{1F916} RUNA CI \u751F\u6210 (\u4E2D\u9593\u66F4\u65B0)</sub>"
|
|
@@ -14064,6 +14123,7 @@ function generateCommentBody(input3) {
|
|
|
14064
14123
|
gitBranchName,
|
|
14065
14124
|
input3.productionPreview,
|
|
14066
14125
|
schemaDrift,
|
|
14126
|
+
input3.schemaStats ?? null,
|
|
14067
14127
|
env2
|
|
14068
14128
|
),
|
|
14069
14129
|
"<sub>\u{1F916} RUNA CI \u751F\u6210</sub>"
|
|
@@ -15108,7 +15168,8 @@ ${generateProgressCommentBody(progressInput)}`;
|
|
|
15108
15168
|
invoke: {
|
|
15109
15169
|
src: "collectSchemaStats",
|
|
15110
15170
|
input: ({ context }) => ({
|
|
15111
|
-
|
|
15171
|
+
// In CI mode, local and CI both use the Docker Supabase
|
|
15172
|
+
localDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
15112
15173
|
ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
15113
15174
|
// Query production only in ci-pr modes
|
|
15114
15175
|
queryProduction: context.mode !== "ci-local"
|
|
@@ -16735,8 +16796,8 @@ function getGitHubEventAction() {
|
|
|
16735
16796
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
16736
16797
|
if (!eventPath) return void 0;
|
|
16737
16798
|
try {
|
|
16738
|
-
const
|
|
16739
|
-
const eventPayload = JSON.parse(
|
|
16799
|
+
const fs14 = __require("fs");
|
|
16800
|
+
const eventPayload = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
16740
16801
|
return eventPayload.action;
|
|
16741
16802
|
} catch {
|
|
16742
16803
|
return void 0;
|
|
@@ -19853,6 +19914,32 @@ function diffSchema(params) {
|
|
|
19853
19914
|
enumValueMismatches
|
|
19854
19915
|
};
|
|
19855
19916
|
}
|
|
19917
|
+
function extractTablesFromIdempotentSql(idempotentDir, projectRoot = process.cwd()) {
|
|
19918
|
+
const fullPath = path10__default.resolve(projectRoot, idempotentDir);
|
|
19919
|
+
if (!existsSync(fullPath)) {
|
|
19920
|
+
return [];
|
|
19921
|
+
}
|
|
19922
|
+
const tables = [];
|
|
19923
|
+
const createTablePattern = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"?([a-zA-Z_][a-zA-Z0-9_]*)"?\.)?(?:"?([a-zA-Z_][a-zA-Z0-9_]*)"?)/gi;
|
|
19924
|
+
try {
|
|
19925
|
+
const files = readdirSync(fullPath).filter((f) => f.endsWith(".sql"));
|
|
19926
|
+
for (const file of files) {
|
|
19927
|
+
const filePath = path10__default.join(fullPath, file);
|
|
19928
|
+
const content = readFileSync(filePath, "utf-8");
|
|
19929
|
+
const contentWithoutComments = content.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
19930
|
+
for (const match of contentWithoutComments.matchAll(createTablePattern)) {
|
|
19931
|
+
const schema = match[1] || "public";
|
|
19932
|
+
const tableName = match[2];
|
|
19933
|
+
if (tableName) {
|
|
19934
|
+
tables.push(`${schema}.${tableName}`);
|
|
19935
|
+
}
|
|
19936
|
+
}
|
|
19937
|
+
}
|
|
19938
|
+
} catch {
|
|
19939
|
+
return [];
|
|
19940
|
+
}
|
|
19941
|
+
return [...new Set(tables)].sort();
|
|
19942
|
+
}
|
|
19856
19943
|
|
|
19857
19944
|
// src/commands/db/commands/db-cleanup.ts
|
|
19858
19945
|
function quoteIdentifier(identifier) {
|
|
@@ -19966,18 +20053,30 @@ async function runCleanupAction(env2, options) {
|
|
|
19966
20053
|
const databaseUrl = resolveDatabaseUrl(runaEnv);
|
|
19967
20054
|
const { expectedTables, expectedEnums } = await extractSchemaTablesAndEnums(dbPackagePath);
|
|
19968
20055
|
const { dbTables, dbEnums } = await fetchDbTablesAndEnums(databaseUrl);
|
|
19969
|
-
let excludeFromOrphanDetection;
|
|
20056
|
+
let excludeFromOrphanDetection = [];
|
|
20057
|
+
let idempotentSqlDir = "supabase/schemas/idempotent";
|
|
19970
20058
|
try {
|
|
19971
20059
|
const config = loadRunaConfig2();
|
|
19972
|
-
|
|
20060
|
+
if (config.database?.pgSchemaDiff?.excludeFromOrphanDetection) {
|
|
20061
|
+
excludeFromOrphanDetection = [...config.database.pgSchemaDiff.excludeFromOrphanDetection];
|
|
20062
|
+
}
|
|
20063
|
+
if (config.database?.pgSchemaDiff?.idempotentSqlDir) {
|
|
20064
|
+
idempotentSqlDir = config.database.pgSchemaDiff.idempotentSqlDir;
|
|
20065
|
+
}
|
|
19973
20066
|
} catch {
|
|
19974
20067
|
}
|
|
20068
|
+
const idempotentTables = extractTablesFromIdempotentSql(idempotentSqlDir);
|
|
20069
|
+
if (idempotentTables.length > 0) {
|
|
20070
|
+
excludeFromOrphanDetection = [
|
|
20071
|
+
.../* @__PURE__ */ new Set([...excludeFromOrphanDetection, ...idempotentTables])
|
|
20072
|
+
];
|
|
20073
|
+
}
|
|
19975
20074
|
const diff = diffSchema({
|
|
19976
20075
|
expectedTables,
|
|
19977
20076
|
expectedEnums,
|
|
19978
20077
|
dbTables,
|
|
19979
20078
|
dbEnums,
|
|
19980
|
-
excludeFromOrphanDetection
|
|
20079
|
+
excludeFromOrphanDetection: excludeFromOrphanDetection.length > 0 ? excludeFromOrphanDetection : void 0
|
|
19981
20080
|
});
|
|
19982
20081
|
if (diff.orphanTables.length === 0 && diff.extraEnums.length === 0) {
|
|
19983
20082
|
logger16.success("\u2705 No orphan tables/enums to cleanup");
|
|
@@ -21105,6 +21204,10 @@ function logOrphanDetails(diff, result, logger16) {
|
|
|
21105
21204
|
logger16.info(" 2. Cleanup orphans: runa db cleanup local --force");
|
|
21106
21205
|
logger16.info(" 3. Auto-cleanup before sync: runa db sync --reconcile");
|
|
21107
21206
|
logger16.info("");
|
|
21207
|
+
logger16.info(" If these tables are managed by idempotent SQL (e.g., partitions, PostGIS):");
|
|
21208
|
+
logger16.info(" \u2022 Add to runa.config.ts: database.pgSchemaDiff.excludeFromOrphanDetection");
|
|
21209
|
+
logger16.info(" \u2022 Or set idempotentSqlDir to auto-detect CREATE TABLE statements");
|
|
21210
|
+
logger16.info("");
|
|
21108
21211
|
}
|
|
21109
21212
|
async function runOrphanCheck(env2, dbPackagePath, result, logger16, step) {
|
|
21110
21213
|
if (env2 !== "local" || !dbPackagePath) return;
|
|
@@ -21115,18 +21218,30 @@ async function runOrphanCheck(env2, dbPackagePath, result, logger16, step) {
|
|
|
21115
21218
|
const { dbTables, dbEnums } = await fetchDbTablesAndEnums(databaseUrl, {
|
|
21116
21219
|
schemaDir: path10__default.join(dbPackagePath, "src", "schema")
|
|
21117
21220
|
});
|
|
21118
|
-
let excludeFromOrphanDetection;
|
|
21221
|
+
let excludeFromOrphanDetection = [];
|
|
21222
|
+
let idempotentSqlDir = "supabase/schemas/idempotent";
|
|
21119
21223
|
try {
|
|
21120
21224
|
const config = loadRunaConfig2();
|
|
21121
|
-
|
|
21225
|
+
if (config.database?.pgSchemaDiff?.excludeFromOrphanDetection) {
|
|
21226
|
+
excludeFromOrphanDetection = [...config.database.pgSchemaDiff.excludeFromOrphanDetection];
|
|
21227
|
+
}
|
|
21228
|
+
if (config.database?.pgSchemaDiff?.idempotentSqlDir) {
|
|
21229
|
+
idempotentSqlDir = config.database.pgSchemaDiff.idempotentSqlDir;
|
|
21230
|
+
}
|
|
21122
21231
|
} catch {
|
|
21123
21232
|
}
|
|
21233
|
+
const idempotentTables = extractTablesFromIdempotentSql(idempotentSqlDir);
|
|
21234
|
+
if (idempotentTables.length > 0) {
|
|
21235
|
+
excludeFromOrphanDetection = [
|
|
21236
|
+
.../* @__PURE__ */ new Set([...excludeFromOrphanDetection, ...idempotentTables])
|
|
21237
|
+
];
|
|
21238
|
+
}
|
|
21124
21239
|
const diff = diffSchema({
|
|
21125
21240
|
expectedTables,
|
|
21126
21241
|
expectedEnums,
|
|
21127
21242
|
dbTables,
|
|
21128
21243
|
dbEnums,
|
|
21129
|
-
excludeFromOrphanDetection
|
|
21244
|
+
excludeFromOrphanDetection: excludeFromOrphanDetection.length > 0 ? excludeFromOrphanDetection : void 0
|
|
21130
21245
|
});
|
|
21131
21246
|
const hasOrphans = diff.orphanTables.length > 0 || diff.extraEnums.length > 0;
|
|
21132
21247
|
if (hasOrphans) {
|
|
@@ -21323,12 +21438,24 @@ var reconcile = fromPromise(
|
|
|
21323
21438
|
ctx.dbPackagePath
|
|
21324
21439
|
);
|
|
21325
21440
|
const { dbTables, dbEnums } = await fetchDbTablesAndEnums(ctx.databaseUrl);
|
|
21326
|
-
let excludeFromOrphanDetection;
|
|
21441
|
+
let excludeFromOrphanDetection = [];
|
|
21442
|
+
let idempotentSqlDir = "supabase/schemas/idempotent";
|
|
21327
21443
|
try {
|
|
21328
21444
|
const config = loadRunaConfig2();
|
|
21329
|
-
|
|
21445
|
+
if (config.database?.pgSchemaDiff?.excludeFromOrphanDetection) {
|
|
21446
|
+
excludeFromOrphanDetection = [...config.database.pgSchemaDiff.excludeFromOrphanDetection];
|
|
21447
|
+
}
|
|
21448
|
+
if (config.database?.pgSchemaDiff?.idempotentSqlDir) {
|
|
21449
|
+
idempotentSqlDir = config.database.pgSchemaDiff.idempotentSqlDir;
|
|
21450
|
+
}
|
|
21330
21451
|
} catch {
|
|
21331
21452
|
}
|
|
21453
|
+
const idempotentTables = extractTablesFromIdempotentSql(idempotentSqlDir);
|
|
21454
|
+
if (idempotentTables.length > 0) {
|
|
21455
|
+
excludeFromOrphanDetection = [
|
|
21456
|
+
.../* @__PURE__ */ new Set([...excludeFromOrphanDetection, ...idempotentTables])
|
|
21457
|
+
];
|
|
21458
|
+
}
|
|
21332
21459
|
const diff = diffSchema({
|
|
21333
21460
|
expectedTables,
|
|
21334
21461
|
expectedEnums,
|
|
@@ -24720,6 +24847,10 @@ Enum value mismatches (${diff.enumValueMismatches.length}):`);
|
|
|
24720
24847
|
" \u2022 If you only see orphan empty tables / unused enums: runa db cleanup <env> --empty-only --force"
|
|
24721
24848
|
);
|
|
24722
24849
|
logger16.info(" \u2022 Then re-run: runa db sync <env> --force");
|
|
24850
|
+
logger16.info("");
|
|
24851
|
+
logger16.info(" If orphan tables are managed by idempotent SQL (e.g., partitions, PostGIS):");
|
|
24852
|
+
logger16.info(" \u2022 Add to runa.config.ts: database.pgSchemaDiff.excludeFromOrphanDetection");
|
|
24853
|
+
logger16.info(" \u2022 Or set idempotentSqlDir to auto-detect CREATE TABLE statements");
|
|
24723
24854
|
}
|
|
24724
24855
|
async function runSyncAction(env2, options) {
|
|
24725
24856
|
const logger16 = createCLILogger("db:sync");
|
|
@@ -24731,18 +24862,30 @@ async function runSyncAction(env2, options) {
|
|
|
24731
24862
|
const databaseUrl = resolveDatabaseUrl(runaEnv);
|
|
24732
24863
|
const { expectedTables, expectedEnums } = await extractSchemaTablesAndEnums(dbPackagePath);
|
|
24733
24864
|
const { dbTables, dbEnums } = await fetchDbTablesAndEnums(databaseUrl);
|
|
24734
|
-
let excludeFromOrphanDetection;
|
|
24865
|
+
let excludeFromOrphanDetection = [];
|
|
24866
|
+
let idempotentSqlDir = "supabase/schemas/idempotent";
|
|
24735
24867
|
try {
|
|
24736
24868
|
const config = loadRunaConfig2();
|
|
24737
|
-
|
|
24869
|
+
if (config.database?.pgSchemaDiff?.excludeFromOrphanDetection) {
|
|
24870
|
+
excludeFromOrphanDetection = [...config.database.pgSchemaDiff.excludeFromOrphanDetection];
|
|
24871
|
+
}
|
|
24872
|
+
if (config.database?.pgSchemaDiff?.idempotentSqlDir) {
|
|
24873
|
+
idempotentSqlDir = config.database.pgSchemaDiff.idempotentSqlDir;
|
|
24874
|
+
}
|
|
24738
24875
|
} catch {
|
|
24739
24876
|
}
|
|
24877
|
+
const idempotentTables = extractTablesFromIdempotentSql(idempotentSqlDir);
|
|
24878
|
+
if (idempotentTables.length > 0) {
|
|
24879
|
+
excludeFromOrphanDetection = [
|
|
24880
|
+
.../* @__PURE__ */ new Set([...excludeFromOrphanDetection, ...idempotentTables])
|
|
24881
|
+
];
|
|
24882
|
+
}
|
|
24740
24883
|
const diff = diffSchema({
|
|
24741
24884
|
expectedTables,
|
|
24742
24885
|
expectedEnums,
|
|
24743
24886
|
dbTables,
|
|
24744
24887
|
dbEnums,
|
|
24745
|
-
excludeFromOrphanDetection
|
|
24888
|
+
excludeFromOrphanDetection: excludeFromOrphanDetection.length > 0 ? excludeFromOrphanDetection : void 0
|
|
24746
24889
|
});
|
|
24747
24890
|
if (options.json) {
|
|
24748
24891
|
process.stdout.write(
|
|
@@ -26713,18 +26856,42 @@ function parseVercelUrl(url) {
|
|
|
26713
26856
|
if (!match) return null;
|
|
26714
26857
|
return { team: match[1], project: match[2] };
|
|
26715
26858
|
}
|
|
26859
|
+
function isValidGitHubOwner(name) {
|
|
26860
|
+
if (!name || name.length > 39) return false;
|
|
26861
|
+
if (!/^[a-zA-Z0-9-]+$/.test(name)) return false;
|
|
26862
|
+
if (name.startsWith("-") || name.endsWith("-")) return false;
|
|
26863
|
+
return true;
|
|
26864
|
+
}
|
|
26865
|
+
function isValidGitHubRepo(name) {
|
|
26866
|
+
if (!name || name.length > 100) return false;
|
|
26867
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name)) return false;
|
|
26868
|
+
if (name.startsWith(".") && name !== ".github") return false;
|
|
26869
|
+
if (/\.\./.test(name)) return false;
|
|
26870
|
+
return true;
|
|
26871
|
+
}
|
|
26716
26872
|
function parseGitHubUrl(url) {
|
|
26717
26873
|
const cleanUrl = url.trim().replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/\/$/, "");
|
|
26718
|
-
const githubMatch = cleanUrl.match(/^github\.com\/([
|
|
26874
|
+
const githubMatch = cleanUrl.match(/^github\.com\/([^/]+)\/([^/]+)/);
|
|
26719
26875
|
if (githubMatch) {
|
|
26720
|
-
const
|
|
26721
|
-
|
|
26876
|
+
const owner = githubMatch[1];
|
|
26877
|
+
const repo = githubMatch[2];
|
|
26878
|
+
if (isValidGitHubOwner(owner) && isValidGitHubRepo(repo)) {
|
|
26879
|
+
return { owner, repo };
|
|
26880
|
+
}
|
|
26881
|
+
}
|
|
26882
|
+
const sshMatch = url.trim().match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
26883
|
+
if (sshMatch) {
|
|
26884
|
+
const owner = sshMatch[1];
|
|
26885
|
+
const repo = sshMatch[2];
|
|
26886
|
+
if (isValidGitHubOwner(owner) && isValidGitHubRepo(repo)) {
|
|
26887
|
+
return { owner, repo };
|
|
26888
|
+
}
|
|
26722
26889
|
}
|
|
26723
26890
|
const parts = cleanUrl.split("/");
|
|
26724
|
-
if (parts.length
|
|
26891
|
+
if (parts.length === 2 && !cleanUrl.includes(".")) {
|
|
26725
26892
|
const owner = parts[0];
|
|
26726
26893
|
const repo = parts[1];
|
|
26727
|
-
if (
|
|
26894
|
+
if (isValidGitHubOwner(owner) && isValidGitHubRepo(repo)) {
|
|
26728
26895
|
return { owner, repo };
|
|
26729
26896
|
}
|
|
26730
26897
|
}
|
|
@@ -27197,7 +27364,8 @@ async function setVercelEnvVar(linkedDir, key, value, overwrite, logger16) {
|
|
|
27197
27364
|
proc.stderr?.on("data", (data) => {
|
|
27198
27365
|
stderr += data.toString();
|
|
27199
27366
|
});
|
|
27200
|
-
proc.stdin?.write(value
|
|
27367
|
+
proc.stdin?.write(`${value}
|
|
27368
|
+
`);
|
|
27201
27369
|
proc.stdin?.end();
|
|
27202
27370
|
proc.on("close", (code) => {
|
|
27203
27371
|
if (code === 0) {
|
|
@@ -29660,7 +29828,7 @@ init_esm_shims();
|
|
|
29660
29828
|
|
|
29661
29829
|
// src/constants/versions.ts
|
|
29662
29830
|
init_esm_shims();
|
|
29663
|
-
var COMPATIBLE_TEMPLATES_VERSION = "0.5.
|
|
29831
|
+
var COMPATIBLE_TEMPLATES_VERSION = "0.5.42";
|
|
29664
29832
|
var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
|
|
29665
29833
|
var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
|
|
29666
29834
|
|
|
@@ -29713,7 +29881,7 @@ function getCacheDir(version) {
|
|
|
29713
29881
|
function isCached(version) {
|
|
29714
29882
|
const cacheDir = getCacheDir(version);
|
|
29715
29883
|
const templatesDir = path10__default.join(cacheDir, "templates");
|
|
29716
|
-
return
|
|
29884
|
+
return fs5__default.existsSync(templatesDir);
|
|
29717
29885
|
}
|
|
29718
29886
|
var scopedAuthToken = null;
|
|
29719
29887
|
var tokenWasAutoDetected = false;
|
|
@@ -29849,7 +30017,7 @@ async function verifyNoSymlinks(dir, baseDir) {
|
|
|
29849
30017
|
}
|
|
29850
30018
|
async function copyToCache(tempDir, cacheDir) {
|
|
29851
30019
|
const sourceTemplates = path10__default.join(tempDir, "node_modules", TEMPLATES_PACKAGE_NAME, "templates");
|
|
29852
|
-
if (!
|
|
30020
|
+
if (!fs5__default.existsSync(sourceTemplates)) {
|
|
29853
30021
|
throw new CLIError("Templates directory not found in package.", "TEMPLATES_DIR_NOT_FOUND", [
|
|
29854
30022
|
`Expected: ${sourceTemplates}`,
|
|
29855
30023
|
"The templates package may be corrupted.",
|
|
@@ -29893,7 +30061,7 @@ async function fetchTemplates(options = {}) {
|
|
|
29893
30061
|
}
|
|
29894
30062
|
const authToken = await getAuthToken();
|
|
29895
30063
|
try {
|
|
29896
|
-
if (fresh &&
|
|
30064
|
+
if (fresh && fs5__default.existsSync(cacheDir)) {
|
|
29897
30065
|
await rm(cacheDir, { recursive: true, force: true });
|
|
29898
30066
|
}
|
|
29899
30067
|
const tempDir = path10__default.join(os.tmpdir(), `runa-templates-${Date.now()}`);
|
|
@@ -29925,15 +30093,15 @@ async function fetchTemplates(options = {}) {
|
|
|
29925
30093
|
var MAX_WORKSPACE_TRAVERSAL_DEPTH = 10;
|
|
29926
30094
|
function isLegitimateWorkspaceRoot(workspaceRoot) {
|
|
29927
30095
|
const pnpmWorkspaceFile = path10__default.join(workspaceRoot, "pnpm-workspace.yaml");
|
|
29928
|
-
if (!
|
|
30096
|
+
if (!fs5__default.existsSync(pnpmWorkspaceFile)) {
|
|
29929
30097
|
return false;
|
|
29930
30098
|
}
|
|
29931
30099
|
const rootPackageFile = path10__default.join(workspaceRoot, "package.json");
|
|
29932
|
-
if (!
|
|
30100
|
+
if (!fs5__default.existsSync(rootPackageFile)) {
|
|
29933
30101
|
return false;
|
|
29934
30102
|
}
|
|
29935
30103
|
try {
|
|
29936
|
-
const rootPkg = JSON.parse(
|
|
30104
|
+
const rootPkg = JSON.parse(fs5__default.readFileSync(rootPackageFile, "utf-8"));
|
|
29937
30105
|
const hasWorkspaces = Array.isArray(rootPkg.workspaces) && rootPkg.workspaces.length > 0;
|
|
29938
30106
|
const hasExpectedName = rootPkg.name === "runa" || rootPkg.name === "@r06-dev/runa";
|
|
29939
30107
|
return hasWorkspaces || hasExpectedName;
|
|
@@ -29958,11 +30126,11 @@ function resolveWorkspaceTemplates() {
|
|
|
29958
30126
|
depth++;
|
|
29959
30127
|
continue;
|
|
29960
30128
|
}
|
|
29961
|
-
if (
|
|
30129
|
+
if (fs5__default.existsSync(normalizedTemplatesPath)) {
|
|
29962
30130
|
const markerFile = path10__default.join(current, "packages", "runa-templates", "package.json");
|
|
29963
|
-
if (
|
|
30131
|
+
if (fs5__default.existsSync(markerFile)) {
|
|
29964
30132
|
try {
|
|
29965
|
-
const pkg = JSON.parse(
|
|
30133
|
+
const pkg = JSON.parse(fs5__default.readFileSync(markerFile, "utf-8"));
|
|
29966
30134
|
if (pkg.name === "@r06-dev/runa-templates") {
|
|
29967
30135
|
return normalizedTemplatesPath;
|
|
29968
30136
|
}
|
|
@@ -29984,9 +30152,9 @@ function checkZodVersion(_logger) {
|
|
|
29984
30152
|
path10__default.join(process.env.HOME ?? "", "node_modules", "zod", "package.json")
|
|
29985
30153
|
];
|
|
29986
30154
|
for (const zodPath of zodPaths) {
|
|
29987
|
-
if (!
|
|
30155
|
+
if (!fs5__default.existsSync(zodPath)) continue;
|
|
29988
30156
|
try {
|
|
29989
|
-
const raw =
|
|
30157
|
+
const raw = fs5__default.readFileSync(zodPath, "utf-8");
|
|
29990
30158
|
const pkg = JSON.parse(raw);
|
|
29991
30159
|
const version = pkg.version ?? "";
|
|
29992
30160
|
if (version.startsWith("3.")) {
|
|
@@ -30524,15 +30692,15 @@ function emptyResult(filePath) {
|
|
|
30524
30692
|
};
|
|
30525
30693
|
}
|
|
30526
30694
|
function ensureDirectoryExists(dirPath) {
|
|
30527
|
-
if (!
|
|
30528
|
-
|
|
30695
|
+
if (!fs5.existsSync(dirPath)) {
|
|
30696
|
+
fs5.mkdirSync(dirPath, { recursive: true });
|
|
30529
30697
|
}
|
|
30530
30698
|
}
|
|
30531
30699
|
function getGeneratorVersion() {
|
|
30532
30700
|
try {
|
|
30533
30701
|
const pkgPath = path10.resolve(__dirname$1, "../../../package.json");
|
|
30534
|
-
if (
|
|
30535
|
-
const pkg = JSON.parse(
|
|
30702
|
+
if (fs5.existsSync(pkgPath)) {
|
|
30703
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
30536
30704
|
return pkg.version ?? "unknown";
|
|
30537
30705
|
}
|
|
30538
30706
|
} catch {
|
|
@@ -30549,21 +30717,21 @@ function detectPlatform(repoRoot) {
|
|
|
30549
30717
|
path10.join(repoRoot, "apps", "web", pattern),
|
|
30550
30718
|
path10.join(repoRoot, "apps", "dashboard", pattern)
|
|
30551
30719
|
];
|
|
30552
|
-
if (paths.some((p) =>
|
|
30720
|
+
if (paths.some((p) => fs5.existsSync(p))) {
|
|
30553
30721
|
return "nextjs";
|
|
30554
30722
|
}
|
|
30555
30723
|
}
|
|
30556
30724
|
const appJsonPath = path10.join(repoRoot, "app.json");
|
|
30557
|
-
if (
|
|
30725
|
+
if (fs5.existsSync(appJsonPath)) {
|
|
30558
30726
|
try {
|
|
30559
|
-
const appJson = JSON.parse(
|
|
30727
|
+
const appJson = JSON.parse(fs5.readFileSync(appJsonPath, "utf-8"));
|
|
30560
30728
|
if (appJson.expo) {
|
|
30561
30729
|
return "expo";
|
|
30562
30730
|
}
|
|
30563
30731
|
} catch {
|
|
30564
30732
|
}
|
|
30565
30733
|
}
|
|
30566
|
-
if (
|
|
30734
|
+
if (fs5.existsSync(path10.join(repoRoot, "expo.json"))) {
|
|
30567
30735
|
return "expo";
|
|
30568
30736
|
}
|
|
30569
30737
|
const electronPatterns = [
|
|
@@ -30572,7 +30740,7 @@ function detectPlatform(repoRoot) {
|
|
|
30572
30740
|
"electron.vite.config.ts",
|
|
30573
30741
|
"electron.vite.config.js"
|
|
30574
30742
|
];
|
|
30575
|
-
if (electronPatterns.some((p) =>
|
|
30743
|
+
if (electronPatterns.some((p) => fs5.existsSync(path10.join(repoRoot, p)))) {
|
|
30576
30744
|
return "electron";
|
|
30577
30745
|
}
|
|
30578
30746
|
return "unknown";
|
|
@@ -30580,14 +30748,14 @@ function detectPlatform(repoRoot) {
|
|
|
30580
30748
|
function findPossibleHonoDirs(repoRoot) {
|
|
30581
30749
|
const possibleDirs = [];
|
|
30582
30750
|
const appsDir = path10.join(repoRoot, "apps");
|
|
30583
|
-
if (
|
|
30751
|
+
if (fs5.existsSync(appsDir)) {
|
|
30584
30752
|
try {
|
|
30585
|
-
const apps =
|
|
30753
|
+
const apps = fs5.readdirSync(appsDir, { withFileTypes: true });
|
|
30586
30754
|
for (const app of apps) {
|
|
30587
30755
|
if (app.isDirectory()) {
|
|
30588
30756
|
const routesDir = path10.join(appsDir, app.name, "routes");
|
|
30589
|
-
if (
|
|
30590
|
-
const files =
|
|
30757
|
+
if (fs5.existsSync(routesDir)) {
|
|
30758
|
+
const files = fs5.readdirSync(routesDir);
|
|
30591
30759
|
const hasRouteFiles = files.some(
|
|
30592
30760
|
(f) => f.endsWith(".ts") && !f.endsWith(".test.ts") && !f.endsWith(".spec.ts")
|
|
30593
30761
|
);
|
|
@@ -30601,14 +30769,14 @@ function findPossibleHonoDirs(repoRoot) {
|
|
|
30601
30769
|
}
|
|
30602
30770
|
}
|
|
30603
30771
|
const packagesDir = path10.join(repoRoot, "packages");
|
|
30604
|
-
if (
|
|
30772
|
+
if (fs5.existsSync(packagesDir)) {
|
|
30605
30773
|
try {
|
|
30606
|
-
const packages =
|
|
30774
|
+
const packages = fs5.readdirSync(packagesDir, { withFileTypes: true });
|
|
30607
30775
|
for (const pkg of packages) {
|
|
30608
30776
|
if (pkg.isDirectory()) {
|
|
30609
30777
|
const routesDir = path10.join(packagesDir, pkg.name, "routes");
|
|
30610
|
-
if (
|
|
30611
|
-
const files =
|
|
30778
|
+
if (fs5.existsSync(routesDir)) {
|
|
30779
|
+
const files = fs5.readdirSync(routesDir);
|
|
30612
30780
|
const hasRouteFiles = files.some(
|
|
30613
30781
|
(f) => f.endsWith(".ts") && !f.endsWith(".test.ts") && !f.endsWith(".spec.ts")
|
|
30614
30782
|
);
|
|
@@ -30698,7 +30866,7 @@ function buildDefinitionMap(definitions) {
|
|
|
30698
30866
|
function buildEnhancedMachines(e2eManifest, definitionMap, machineDefinitions) {
|
|
30699
30867
|
const enhanced = {};
|
|
30700
30868
|
const normalizedIdToCanonical = /* @__PURE__ */ new Map();
|
|
30701
|
-
const isMachineFile = (
|
|
30869
|
+
const isMachineFile = (path63) => path63.includes("machine.ts") || path63.includes("machines/") || path63.includes(".machine.ts") || path63.includes("/machine/");
|
|
30702
30870
|
const addMachine = (id, sourceFile, entryBuilder) => {
|
|
30703
30871
|
const normalizedId = normalizeToCanonicalId(id).toLowerCase();
|
|
30704
30872
|
const existingCanonical = normalizedIdToCanonical.get(normalizedId);
|
|
@@ -30840,7 +31008,7 @@ async function generateManifestFiles(manifestDir, repoRoot, verbose, resolveSche
|
|
|
30840
31008
|
const manifestsDir = path10.join(absoluteManifestDir, "manifests");
|
|
30841
31009
|
ensureDirectoryExists(manifestsDir);
|
|
30842
31010
|
const tsPath = path10.join(generatedDir, "selectors.ts");
|
|
30843
|
-
await
|
|
31011
|
+
await fs5.promises.writeFile(tsPath, generateSelectorTypeScript(e2eManifest), "utf-8");
|
|
30844
31012
|
const unifiedRegistry = getUnifiedRegistry();
|
|
30845
31013
|
const machineLinks = buildMachineLinks();
|
|
30846
31014
|
const { apiContracts, schemasResolved } = await analyzeHonoRoutes(
|
|
@@ -30869,7 +31037,7 @@ async function generateManifestFiles(manifestDir, repoRoot, verbose, resolveSche
|
|
|
30869
31037
|
apiContracts
|
|
30870
31038
|
};
|
|
30871
31039
|
const manifestPath = path10.join(manifestsDir, "manifest.json");
|
|
30872
|
-
await
|
|
31040
|
+
await fs5.promises.writeFile(manifestPath, JSON.stringify(unifiedManifest, null, 2), "utf-8");
|
|
30873
31041
|
const machinesWithoutE2EMeta = machineDefinitions.filter(
|
|
30874
31042
|
(def) => !def.hasE2EMeta && typeof def.id === "string"
|
|
30875
31043
|
).map((def) => ({ id: def.id, sourceFile: def.sourceFile }));
|
|
@@ -30933,7 +31101,7 @@ function registerExistingInjections(machineIds, attrs, sourceFile) {
|
|
|
30933
31101
|
}
|
|
30934
31102
|
}
|
|
30935
31103
|
async function preprocessFile(filePath, repoRoot, options) {
|
|
30936
|
-
let code = await
|
|
31104
|
+
let code = await fs5.promises.readFile(filePath, "utf-8");
|
|
30937
31105
|
const relativePath = path10.relative(repoRoot, filePath);
|
|
30938
31106
|
collectRouteInfo(relativePath, code, options.verbose);
|
|
30939
31107
|
const hasMarker = code.includes(CLI_INJECTION_MARKER);
|
|
@@ -31035,7 +31203,7 @@ async function handleChangedFile(result, check, repoRoot, verbose, state2) {
|
|
|
31035
31203
|
});
|
|
31036
31204
|
state2.totalInjections += result.injectedCount;
|
|
31037
31205
|
if (check) return;
|
|
31038
|
-
await
|
|
31206
|
+
await fs5.promises.writeFile(result.filePath, result.transformedCode, "utf-8");
|
|
31039
31207
|
state2.filesToFormat.push(result.filePath);
|
|
31040
31208
|
if (verbose) {
|
|
31041
31209
|
const relativePath = path10.relative(repoRoot, result.filePath);
|
|
@@ -31058,7 +31226,7 @@ async function categorizeFiles(files) {
|
|
|
31058
31226
|
const withJsxOnly = [];
|
|
31059
31227
|
for (const filePath of files) {
|
|
31060
31228
|
try {
|
|
31061
|
-
const code = await
|
|
31229
|
+
const code = await fs5.promises.readFile(filePath, "utf-8");
|
|
31062
31230
|
if (mightContainMachineHooks(code)) {
|
|
31063
31231
|
withMachineHooks.push(filePath);
|
|
31064
31232
|
} else if (mightContainJsx(code)) {
|
|
@@ -31087,7 +31255,7 @@ async function processFiles(options) {
|
|
|
31087
31255
|
const processOpts = { verbose, force: options.force ?? false };
|
|
31088
31256
|
for (const filePath of withMachineDefinitions) {
|
|
31089
31257
|
try {
|
|
31090
|
-
const code = await
|
|
31258
|
+
const code = await fs5.promises.readFile(filePath, "utf-8");
|
|
31091
31259
|
const relativePath = path10.relative(repoRoot, filePath);
|
|
31092
31260
|
collectRouteInfo(relativePath, code, verbose);
|
|
31093
31261
|
} catch {
|
|
@@ -32816,8 +32984,8 @@ function formatLineDiff(diff) {
|
|
|
32816
32984
|
|
|
32817
32985
|
// src/commands/template-check/actors/compare.ts
|
|
32818
32986
|
function compareBothFiles(runaRelPath, templateRelPath, runaAbsPath, templateAbsPath, category, includeDiff) {
|
|
32819
|
-
const runaContent =
|
|
32820
|
-
const templateContent =
|
|
32987
|
+
const runaContent = fs5.readFileSync(runaAbsPath, "utf-8");
|
|
32988
|
+
const templateContent = fs5.readFileSync(templateAbsPath, "utf-8");
|
|
32821
32989
|
const comparison = compareFiles(runaContent, templateContent);
|
|
32822
32990
|
let status;
|
|
32823
32991
|
if (comparison.identical) {
|
|
@@ -33238,6 +33406,39 @@ var PATH_MAPPING_RULES = [
|
|
|
33238
33406
|
category: "other"
|
|
33239
33407
|
},
|
|
33240
33408
|
// ============================================================
|
|
33409
|
+
// .CODEX DIRECTORY (mirrors .claude policies for Codex)
|
|
33410
|
+
// ============================================================
|
|
33411
|
+
{
|
|
33412
|
+
runa: ".codex/README.md",
|
|
33413
|
+
template: "_codex/README.md",
|
|
33414
|
+
category: "config"
|
|
33415
|
+
},
|
|
33416
|
+
{
|
|
33417
|
+
runa: ".codex/AGENTS.md",
|
|
33418
|
+
template: "_codex/AGENTS.md",
|
|
33419
|
+
category: "config"
|
|
33420
|
+
},
|
|
33421
|
+
{
|
|
33422
|
+
runa: ".codex/CODEX.md",
|
|
33423
|
+
template: "_codex/CODEX.md",
|
|
33424
|
+
category: "config"
|
|
33425
|
+
},
|
|
33426
|
+
{
|
|
33427
|
+
runa: ".codex/config.toml",
|
|
33428
|
+
template: "_codex/config.toml",
|
|
33429
|
+
category: "config"
|
|
33430
|
+
},
|
|
33431
|
+
{
|
|
33432
|
+
runa: ".codex/rules/devops.md",
|
|
33433
|
+
template: "_codex/rules/devops.md",
|
|
33434
|
+
category: "rules"
|
|
33435
|
+
},
|
|
33436
|
+
{
|
|
33437
|
+
runa: ".codex/rules/permissions.toml",
|
|
33438
|
+
template: "_codex/rules/permissions.toml",
|
|
33439
|
+
category: "rules"
|
|
33440
|
+
},
|
|
33441
|
+
// ============================================================
|
|
33241
33442
|
// ROOT DOCUMENTATION (LOWEST PRIORITY)
|
|
33242
33443
|
// ============================================================
|
|
33243
33444
|
{
|
|
@@ -33261,9 +33462,9 @@ var PATH_MAPPING_RULES = [
|
|
|
33261
33462
|
category: "prompts"
|
|
33262
33463
|
}
|
|
33263
33464
|
];
|
|
33264
|
-
function getCategoryForPath(
|
|
33465
|
+
function getCategoryForPath(path63) {
|
|
33265
33466
|
for (const rule of PATH_MAPPING_RULES) {
|
|
33266
|
-
if (matchGlobPattern(
|
|
33467
|
+
if (matchGlobPattern(path63, rule.runa) || matchGlobPattern(path63, rule.template)) {
|
|
33267
33468
|
return rule.category;
|
|
33268
33469
|
}
|
|
33269
33470
|
}
|
|
@@ -33283,17 +33484,17 @@ function applyReverseRename(normalized, rule, isTemplate) {
|
|
|
33283
33484
|
}
|
|
33284
33485
|
return normalized;
|
|
33285
33486
|
}
|
|
33286
|
-
function generateComparisonKey(
|
|
33287
|
-
let normalized = isTemplate ? normalizeTemplatePath(
|
|
33487
|
+
function generateComparisonKey(path63, isTemplate) {
|
|
33488
|
+
let normalized = isTemplate ? normalizeTemplatePath(path63) : path63;
|
|
33288
33489
|
for (const rule of PATH_MAPPING_RULES) {
|
|
33289
33490
|
normalized = applyReverseRename(normalized, rule, isTemplate);
|
|
33290
33491
|
}
|
|
33291
33492
|
return normalized;
|
|
33292
33493
|
}
|
|
33293
|
-
function matchGlobPattern(
|
|
33494
|
+
function matchGlobPattern(path63, pattern) {
|
|
33294
33495
|
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<DOUBLE_STAR>>").replace(/\*/g, "([^/]*)").replace(/<<DOUBLE_STAR>>/g, "(.*)");
|
|
33295
33496
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
33296
|
-
const match =
|
|
33497
|
+
const match = path63.match(regex);
|
|
33297
33498
|
if (match) {
|
|
33298
33499
|
const subPath = match[1] ?? "";
|
|
33299
33500
|
return { matched: true, subPath };
|
|
@@ -33357,7 +33558,7 @@ async function globFiles(baseDir, pattern) {
|
|
|
33357
33558
|
return results;
|
|
33358
33559
|
}
|
|
33359
33560
|
function addFileResult(entryPath, fullPattern, results) {
|
|
33360
|
-
const stats =
|
|
33561
|
+
const stats = fs5.statSync(entryPath);
|
|
33361
33562
|
results.push({
|
|
33362
33563
|
absolutePath: entryPath,
|
|
33363
33564
|
size: stats.size,
|
|
@@ -33404,16 +33605,16 @@ async function matchBraceExpansion(ctx, entries, pattern, isLastPart) {
|
|
|
33404
33605
|
}
|
|
33405
33606
|
async function matchLiteral(ctx, pattern, isLastPart) {
|
|
33406
33607
|
const entryPath = path10.join(ctx.currentDir, pattern);
|
|
33407
|
-
if (!
|
|
33408
|
-
const stats =
|
|
33608
|
+
if (!fs5.existsSync(entryPath)) return;
|
|
33609
|
+
const stats = fs5.statSync(entryPath);
|
|
33409
33610
|
await processEntry(ctx, entryPath, stats.isFile(), stats.isDirectory(), isLastPart);
|
|
33410
33611
|
}
|
|
33411
33612
|
async function walkAndMatch(ctx) {
|
|
33412
33613
|
if (ctx.partIndex >= ctx.patternParts.length) return;
|
|
33413
|
-
if (!
|
|
33614
|
+
if (!fs5.existsSync(ctx.currentDir)) return;
|
|
33414
33615
|
const currentPattern = ctx.patternParts[ctx.partIndex];
|
|
33415
33616
|
const isLastPart = ctx.partIndex === ctx.patternParts.length - 1;
|
|
33416
|
-
const entries =
|
|
33617
|
+
const entries = fs5.readdirSync(ctx.currentDir, { withFileTypes: true });
|
|
33417
33618
|
if (currentPattern === "**") {
|
|
33418
33619
|
await matchDoubleWildcard(ctx, entries);
|
|
33419
33620
|
} else if (currentPattern.includes("*")) {
|
|
@@ -33928,7 +34129,7 @@ function printActionsNeeded(logger16, actions) {
|
|
|
33928
34129
|
);
|
|
33929
34130
|
}
|
|
33930
34131
|
function findRepoRoot3(startDir) {
|
|
33931
|
-
const { existsSync: existsSync50, readFileSync:
|
|
34132
|
+
const { existsSync: existsSync50, readFileSync: readFileSync28 } = __require("fs");
|
|
33932
34133
|
const { join: join22, dirname: dirname5 } = __require("path");
|
|
33933
34134
|
let current = startDir;
|
|
33934
34135
|
while (current !== dirname5(current)) {
|
|
@@ -33938,7 +34139,7 @@ function findRepoRoot3(startDir) {
|
|
|
33938
34139
|
const pkgPath = join22(current, "package.json");
|
|
33939
34140
|
if (existsSync50(pkgPath)) {
|
|
33940
34141
|
try {
|
|
33941
|
-
const pkg = JSON.parse(
|
|
34142
|
+
const pkg = JSON.parse(readFileSync28(pkgPath, "utf-8"));
|
|
33942
34143
|
if (pkg.workspaces) {
|
|
33943
34144
|
return current;
|
|
33944
34145
|
}
|
|
@@ -35240,8 +35441,8 @@ async function runVerification(logger16) {
|
|
|
35240
35441
|
return allPassed;
|
|
35241
35442
|
}
|
|
35242
35443
|
async function findWorkspacesWithSdkPackages(rootDir, packageNames) {
|
|
35243
|
-
const
|
|
35244
|
-
const
|
|
35444
|
+
const fs14 = await import('fs/promises');
|
|
35445
|
+
const path63 = await import('path');
|
|
35245
35446
|
const { glob: glob8 } = await import('glob');
|
|
35246
35447
|
const result = [];
|
|
35247
35448
|
const packageJsonPaths = await glob8("**/package.json", {
|
|
@@ -35250,9 +35451,9 @@ async function findWorkspacesWithSdkPackages(rootDir, packageNames) {
|
|
|
35250
35451
|
// Exclude root
|
|
35251
35452
|
});
|
|
35252
35453
|
for (const relPath of packageJsonPaths) {
|
|
35253
|
-
const fullPath =
|
|
35454
|
+
const fullPath = path63.join(rootDir, relPath);
|
|
35254
35455
|
try {
|
|
35255
|
-
const content = await
|
|
35456
|
+
const content = await fs14.readFile(fullPath, "utf-8");
|
|
35256
35457
|
const pkg = JSON.parse(content);
|
|
35257
35458
|
let hasAnySdkPackage = false;
|
|
35258
35459
|
for (const pkgName of packageNames) {
|
|
@@ -35263,7 +35464,7 @@ async function findWorkspacesWithSdkPackages(rootDir, packageNames) {
|
|
|
35263
35464
|
}
|
|
35264
35465
|
if (hasAnySdkPackage) {
|
|
35265
35466
|
result.push({
|
|
35266
|
-
dir:
|
|
35467
|
+
dir: path63.dirname(fullPath),
|
|
35267
35468
|
packages: packageNames
|
|
35268
35469
|
// All packages, not just found ones
|
|
35269
35470
|
});
|
|
@@ -35906,7 +36107,7 @@ var SchemaWatcher = class {
|
|
|
35906
36107
|
persistent: true,
|
|
35907
36108
|
ignoreInitial: true
|
|
35908
36109
|
});
|
|
35909
|
-
this.watcher.on("add", (
|
|
36110
|
+
this.watcher.on("add", (path63) => this.handleFileEvent("add", path63)).on("change", (path63) => this.handleFileEvent("change", path63)).on("unlink", (path63) => this.handleFileEvent("unlink", path63)).on("error", (error) => this.handleError(error));
|
|
35910
36111
|
this.logger.success("\u2705 Schema watcher started");
|
|
35911
36112
|
this.logger.info(chalk.dim(" Press Ctrl+C to stop\n"));
|
|
35912
36113
|
}
|
|
@@ -35927,23 +36128,23 @@ var SchemaWatcher = class {
|
|
|
35927
36128
|
/**
|
|
35928
36129
|
* Handle file system events with debouncing
|
|
35929
36130
|
*/
|
|
35930
|
-
handleFileEvent(type,
|
|
35931
|
-
const existingTimer = this.debounceTimers.get(
|
|
36131
|
+
handleFileEvent(type, path63) {
|
|
36132
|
+
const existingTimer = this.debounceTimers.get(path63);
|
|
35932
36133
|
if (existingTimer) {
|
|
35933
36134
|
clearTimeout(existingTimer);
|
|
35934
36135
|
}
|
|
35935
36136
|
const timer = setTimeout(() => {
|
|
35936
|
-
this.processFileEvent({ type, path:
|
|
35937
|
-
this.debounceTimers.delete(
|
|
36137
|
+
this.processFileEvent({ type, path: path63, timestamp: /* @__PURE__ */ new Date() });
|
|
36138
|
+
this.debounceTimers.delete(path63);
|
|
35938
36139
|
}, this.options.debounceMs);
|
|
35939
|
-
this.debounceTimers.set(
|
|
36140
|
+
this.debounceTimers.set(path63, timer);
|
|
35940
36141
|
}
|
|
35941
36142
|
/**
|
|
35942
36143
|
* Process file system event
|
|
35943
36144
|
*/
|
|
35944
36145
|
async processFileEvent(event) {
|
|
35945
|
-
const { type, path:
|
|
35946
|
-
const fileName =
|
|
36146
|
+
const { type, path: path63 } = event;
|
|
36147
|
+
const fileName = path63.split("/").pop() || path63;
|
|
35947
36148
|
switch (type) {
|
|
35948
36149
|
case "add":
|
|
35949
36150
|
this.logger.info(chalk.green(`\u2795 Added: ${fileName}`));
|
|
@@ -35956,19 +36157,19 @@ var SchemaWatcher = class {
|
|
|
35956
36157
|
return;
|
|
35957
36158
|
}
|
|
35958
36159
|
if (this.options.autoValidate) {
|
|
35959
|
-
await this.validateFile(
|
|
36160
|
+
await this.validateFile(path63);
|
|
35960
36161
|
}
|
|
35961
36162
|
}
|
|
35962
36163
|
/**
|
|
35963
36164
|
* Validate schema file
|
|
35964
36165
|
*/
|
|
35965
|
-
async validateFile(
|
|
36166
|
+
async validateFile(path63) {
|
|
35966
36167
|
try {
|
|
35967
36168
|
this.logger.info(chalk.dim(" Validating..."));
|
|
35968
|
-
const validationResult = await validateSchemaFile(
|
|
36169
|
+
const validationResult = await validateSchemaFile(path63);
|
|
35969
36170
|
if (validationResult.isValid) {
|
|
35970
36171
|
this.logger.success(chalk.green(" \u2713 Validation passed"));
|
|
35971
|
-
const risks = await detectSchemaRisks(
|
|
36172
|
+
const risks = await detectSchemaRisks(path63);
|
|
35972
36173
|
if (risks.length > 0) {
|
|
35973
36174
|
this.logger.warn(` \u26A0\uFE0F ${risks.length} risk(s) detected`);
|
|
35974
36175
|
for (const risk of risks) {
|
|
@@ -35983,7 +36184,7 @@ var SchemaWatcher = class {
|
|
|
35983
36184
|
if (this.options.notifyOnError) {
|
|
35984
36185
|
await notifyDesktop({
|
|
35985
36186
|
title: "Schema Validation Failed",
|
|
35986
|
-
message: `${validationResult.errors.length} error(s) in ${
|
|
36187
|
+
message: `${validationResult.errors.length} error(s) in ${path63.split("/").pop()}`,
|
|
35987
36188
|
type: "error"
|
|
35988
36189
|
});
|
|
35989
36190
|
}
|
|
@@ -36202,7 +36403,7 @@ var TestWatcher = class {
|
|
|
36202
36403
|
persistent: true,
|
|
36203
36404
|
ignoreInitial: true
|
|
36204
36405
|
});
|
|
36205
|
-
this.watcher.on("add", (
|
|
36406
|
+
this.watcher.on("add", (path63) => this.handleFileEvent("add", path63)).on("change", (path63) => this.handleFileEvent("change", path63)).on("unlink", (path63) => this.handleFileEvent("unlink", path63)).on("error", (error) => this.handleError(error));
|
|
36206
36407
|
this.logger.success("\u2705 Test watcher started");
|
|
36207
36408
|
this.logger.info(chalk.dim(" Press Ctrl+C to stop\n"));
|
|
36208
36409
|
}
|
|
@@ -36223,26 +36424,26 @@ var TestWatcher = class {
|
|
|
36223
36424
|
/**
|
|
36224
36425
|
* Handle file system events with debouncing
|
|
36225
36426
|
*/
|
|
36226
|
-
handleFileEvent(type,
|
|
36227
|
-
const existingTimer = this.debounceTimers.get(
|
|
36427
|
+
handleFileEvent(type, path63) {
|
|
36428
|
+
const existingTimer = this.debounceTimers.get(path63);
|
|
36228
36429
|
if (existingTimer) {
|
|
36229
36430
|
clearTimeout(existingTimer);
|
|
36230
36431
|
}
|
|
36231
36432
|
const timer = setTimeout(() => {
|
|
36232
|
-
this.processFileEvent(type,
|
|
36233
|
-
this.debounceTimers.delete(
|
|
36433
|
+
this.processFileEvent(type, path63);
|
|
36434
|
+
this.debounceTimers.delete(path63);
|
|
36234
36435
|
}, this.options.debounceMs);
|
|
36235
|
-
this.debounceTimers.set(
|
|
36436
|
+
this.debounceTimers.set(path63, timer);
|
|
36236
36437
|
}
|
|
36237
36438
|
/**
|
|
36238
36439
|
* Process file system event
|
|
36239
36440
|
*/
|
|
36240
|
-
async processFileEvent(type,
|
|
36441
|
+
async processFileEvent(type, path63) {
|
|
36241
36442
|
if (this.isRunning) {
|
|
36242
36443
|
this.logger.warn(chalk.yellow("\u23F3 Tests already running, skipping..."));
|
|
36243
36444
|
return;
|
|
36244
36445
|
}
|
|
36245
|
-
const fileName =
|
|
36446
|
+
const fileName = path63.split("/").pop() || path63;
|
|
36246
36447
|
switch (type) {
|
|
36247
36448
|
case "add":
|
|
36248
36449
|
this.logger.info(chalk.green(`\u2795 Test added: ${fileName}`));
|
|
@@ -36255,7 +36456,7 @@ var TestWatcher = class {
|
|
|
36255
36456
|
return;
|
|
36256
36457
|
}
|
|
36257
36458
|
if (this.options.autoRun) {
|
|
36258
|
-
await this.runTests(
|
|
36459
|
+
await this.runTests(path63);
|
|
36259
36460
|
}
|
|
36260
36461
|
}
|
|
36261
36462
|
/**
|