@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/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 fs6 from 'fs';
8
- import fs6__default, { existsSync, readFileSync, readdirSync, mkdtempSync, writeFileSync, mkdirSync, copyFileSync, createWriteStream, statSync, rmSync, realpathSync, promises, lstatSync, accessSync, constants, chmodSync, unlinkSync } from 'fs';
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 fs10, { mkdir, writeFile, appendFile, readFile, rm, stat, realpath, cp, readdir, lstat } from 'fs/promises';
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.40";
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 fs10.access(packageJsonPath);
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 fs10.readFile(packageJsonPath, "utf-8");
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 fs10.readFile(filePath, "utf-8");
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 fs10.stat(filePath);
2022
+ const stats = await fs9.stat(filePath);
2023
2023
  if (stats.size > MAX_FILE_SIZE) {
2024
2024
  return [];
2025
2025
  }
2026
- const content = await fs10.readFile(filePath, "utf-8");
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 fs10.readFile(fullPath, "utf-8");
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 fs10.readFile(fullPath, "utf-8");
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 fs10.readFile(filePath, "utf-8");
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: readFileSync27 } = __require("fs");
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(readFileSync27(pkgPath, "utf-8"));
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: readFileSync27 } = __require("fs");
7531
- const raw = readFileSync27(pkgPath, "utf-8");
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: readdirSync10 } = __require("fs");
7550
- const entries = readdirSync10(appsDir, { withFileTypes: true });
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: readFileSync27 } = await import('fs');
9563
- logContent = readFileSync27(logFile, "utf-8");
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: readFileSync27 } = await import('fs');
9692
- logContent = readFileSync27(logPath, "utf-8");
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 EXCLUDED_SCHEMAS_FOR_LOCAL = ["pg_catalog", "information_schema"];
11402
- var IDENT_PATTERN = `(?:"([^"]+)"|(\\w+))`;
11403
- var SCHEMA_TABLE_PATTERN = `${IDENT_PATTERN}\\.${IDENT_PATTERN}`;
11404
- function extractIdentifier(quotedGroup, unquotedGroup) {
11405
- const raw = quotedGroup ?? unquotedGroup ?? "";
11406
- return raw.toLowerCase();
11407
- }
11408
- async function getLocalSchemaStats(repoRoot) {
11409
- const schemasDir = path10__default.join(repoRoot, "supabase", "schemas", "declarative");
11410
- const schemas = {};
11411
- const total = { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
11412
- try {
11413
- const files = await fs10.readdir(schemasDir);
11414
- const sqlFiles = files.filter((f) => f.endsWith(".sql")).sort();
11415
- for (const file of sqlFiles) {
11416
- const filePath = path10__default.join(schemasDir, file);
11417
- const content = await fs10.readFile(filePath, "utf-8");
11418
- parseLocalSqlFile(content, schemas, total);
11419
- }
11420
- } catch (error) {
11421
- console.warn(`[schema-stats] Could not read SQL files: ${error}`);
11422
- }
11423
- return { schemas, total };
11424
- }
11425
- function ensureSchema(schemas, schemaName, excluded) {
11426
- if (!schemas[schemaName] && !excluded.includes(schemaName)) {
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 getDbSchemaStats(databaseUrl, targetSchemas) {
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 { repoRoot, ciDbUrl, queryProduction } = input3;
11645
- console.log("[schema-stats] Collecting schema statistics...");
11646
- console.log("[schema-stats] Parsing local SQL files...");
11647
- const local = await getLocalSchemaStats(repoRoot);
11648
- const targetSchemas = Object.keys(local.schemas);
11649
- console.log(
11650
- `[schema-stats] Local: ${local.total.tables}T ${local.total.functions}F ${local.total.policies}P (schemas: ${targetSchemas.join(", ")})`
11651
- );
11652
- let ci = local;
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, targetSchemas);
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, using local stats for CI");
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, targetSchemas);
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 fs15 = __require("fs");
11906
- if (!fs15.existsSync(idempotentDir)) return [];
11907
- const files = fs15.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
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 = fs15.readFileSync(path10__default.join(idempotentDir, file), "utf-8");
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 fs15 = await import('fs/promises');
12020
- await fs15.writeFile(logFile, content);
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 fs15 = __require("fs");
12312
- if (!fs15.existsSync(idempotentDir)) return [];
12313
- const files = fs15.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
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 = fs15.readFileSync(path10__default.join(idempotentDir, file), "utf-8");
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 emptyStats() {
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] ?? emptyStats();
13806
- const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats();
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 hasSchemaChanges = productionPreview?.hasChanges === true;
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
- repoRoot: assertRepoRoot(context),
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 fs15 = __require("fs");
16739
- const eventPayload = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
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
- excludeFromOrphanDetection = config.database?.pgSchemaDiff?.excludeFromOrphanDetection;
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
- excludeFromOrphanDetection = config.database?.pgSchemaDiff?.excludeFromOrphanDetection;
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
- excludeFromOrphanDetection = config.database?.pgSchemaDiff?.excludeFromOrphanDetection;
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
- excludeFromOrphanDetection = config.database?.pgSchemaDiff?.excludeFromOrphanDetection;
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\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9._-]+)/);
26874
+ const githubMatch = cleanUrl.match(/^github\.com\/([^/]+)\/([^/]+)/);
26719
26875
  if (githubMatch) {
26720
- const repo = githubMatch[2].split("/")[0];
26721
- return { owner: githubMatch[1], repo };
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 >= 2 && !cleanUrl.includes(".")) {
26891
+ if (parts.length === 2 && !cleanUrl.includes(".")) {
26725
26892
  const owner = parts[0];
26726
26893
  const repo = parts[1];
26727
- if (/^[a-zA-Z0-9_-]+$/.test(owner) && /^[a-zA-Z0-9._-]+$/.test(repo)) {
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.40";
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 fs6__default.existsSync(templatesDir);
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 (!fs6__default.existsSync(sourceTemplates)) {
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 && fs6__default.existsSync(cacheDir)) {
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 (!fs6__default.existsSync(pnpmWorkspaceFile)) {
30096
+ if (!fs5__default.existsSync(pnpmWorkspaceFile)) {
29929
30097
  return false;
29930
30098
  }
29931
30099
  const rootPackageFile = path10__default.join(workspaceRoot, "package.json");
29932
- if (!fs6__default.existsSync(rootPackageFile)) {
30100
+ if (!fs5__default.existsSync(rootPackageFile)) {
29933
30101
  return false;
29934
30102
  }
29935
30103
  try {
29936
- const rootPkg = JSON.parse(fs6__default.readFileSync(rootPackageFile, "utf-8"));
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 (fs6__default.existsSync(normalizedTemplatesPath)) {
30129
+ if (fs5__default.existsSync(normalizedTemplatesPath)) {
29962
30130
  const markerFile = path10__default.join(current, "packages", "runa-templates", "package.json");
29963
- if (fs6__default.existsSync(markerFile)) {
30131
+ if (fs5__default.existsSync(markerFile)) {
29964
30132
  try {
29965
- const pkg = JSON.parse(fs6__default.readFileSync(markerFile, "utf-8"));
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 (!fs6__default.existsSync(zodPath)) continue;
30155
+ if (!fs5__default.existsSync(zodPath)) continue;
29988
30156
  try {
29989
- const raw = fs6__default.readFileSync(zodPath, "utf-8");
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 (!fs6.existsSync(dirPath)) {
30528
- fs6.mkdirSync(dirPath, { recursive: true });
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 (fs6.existsSync(pkgPath)) {
30535
- const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
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) => fs6.existsSync(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 (fs6.existsSync(appJsonPath)) {
30725
+ if (fs5.existsSync(appJsonPath)) {
30558
30726
  try {
30559
- const appJson = JSON.parse(fs6.readFileSync(appJsonPath, "utf-8"));
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 (fs6.existsSync(path10.join(repoRoot, "expo.json"))) {
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) => fs6.existsSync(path10.join(repoRoot, 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 (fs6.existsSync(appsDir)) {
30751
+ if (fs5.existsSync(appsDir)) {
30584
30752
  try {
30585
- const apps = fs6.readdirSync(appsDir, { withFileTypes: true });
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 (fs6.existsSync(routesDir)) {
30590
- const files = fs6.readdirSync(routesDir);
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 (fs6.existsSync(packagesDir)) {
30772
+ if (fs5.existsSync(packagesDir)) {
30605
30773
  try {
30606
- const packages = fs6.readdirSync(packagesDir, { withFileTypes: true });
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 (fs6.existsSync(routesDir)) {
30611
- const files = fs6.readdirSync(routesDir);
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 = (path64) => path64.includes("machine.ts") || path64.includes("machines/") || path64.includes(".machine.ts") || path64.includes("/machine/");
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 fs6.promises.writeFile(tsPath, generateSelectorTypeScript(e2eManifest), "utf-8");
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 fs6.promises.writeFile(manifestPath, JSON.stringify(unifiedManifest, null, 2), "utf-8");
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 fs6.promises.readFile(filePath, "utf-8");
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 fs6.promises.writeFile(result.filePath, result.transformedCode, "utf-8");
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 fs6.promises.readFile(filePath, "utf-8");
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 fs6.promises.readFile(filePath, "utf-8");
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 = fs6.readFileSync(runaAbsPath, "utf-8");
32820
- const templateContent = fs6.readFileSync(templateAbsPath, "utf-8");
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(path64) {
33465
+ function getCategoryForPath(path63) {
33265
33466
  for (const rule of PATH_MAPPING_RULES) {
33266
- if (matchGlobPattern(path64, rule.runa) || matchGlobPattern(path64, rule.template)) {
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(path64, isTemplate) {
33287
- let normalized = isTemplate ? normalizeTemplatePath(path64) : path64;
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(path64, pattern) {
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 = path64.match(regex);
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 = fs6.statSync(entryPath);
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 (!fs6.existsSync(entryPath)) return;
33408
- const stats = fs6.statSync(entryPath);
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 (!fs6.existsSync(ctx.currentDir)) return;
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 = fs6.readdirSync(ctx.currentDir, { withFileTypes: true });
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: readFileSync27 } = __require("fs");
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(readFileSync27(pkgPath, "utf-8"));
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 fs15 = await import('fs/promises');
35244
- const path64 = await import('path');
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 = path64.join(rootDir, relPath);
35454
+ const fullPath = path63.join(rootDir, relPath);
35254
35455
  try {
35255
- const content = await fs15.readFile(fullPath, "utf-8");
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: path64.dirname(fullPath),
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", (path64) => this.handleFileEvent("add", path64)).on("change", (path64) => this.handleFileEvent("change", path64)).on("unlink", (path64) => this.handleFileEvent("unlink", path64)).on("error", (error) => this.handleError(error));
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, path64) {
35931
- const existingTimer = this.debounceTimers.get(path64);
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: path64, timestamp: /* @__PURE__ */ new Date() });
35937
- this.debounceTimers.delete(path64);
36137
+ this.processFileEvent({ type, path: path63, timestamp: /* @__PURE__ */ new Date() });
36138
+ this.debounceTimers.delete(path63);
35938
36139
  }, this.options.debounceMs);
35939
- this.debounceTimers.set(path64, timer);
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: path64 } = event;
35946
- const fileName = path64.split("/").pop() || path64;
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(path64);
36160
+ await this.validateFile(path63);
35960
36161
  }
35961
36162
  }
35962
36163
  /**
35963
36164
  * Validate schema file
35964
36165
  */
35965
- async validateFile(path64) {
36166
+ async validateFile(path63) {
35966
36167
  try {
35967
36168
  this.logger.info(chalk.dim(" Validating..."));
35968
- const validationResult = await validateSchemaFile(path64);
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(path64);
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 ${path64.split("/").pop()}`,
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", (path64) => this.handleFileEvent("add", path64)).on("change", (path64) => this.handleFileEvent("change", path64)).on("unlink", (path64) => this.handleFileEvent("unlink", path64)).on("error", (error) => this.handleError(error));
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, path64) {
36227
- const existingTimer = this.debounceTimers.get(path64);
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, path64);
36233
- this.debounceTimers.delete(path64);
36433
+ this.processFileEvent(type, path63);
36434
+ this.debounceTimers.delete(path63);
36234
36435
  }, this.options.debounceMs);
36235
- this.debounceTimers.set(path64, timer);
36436
+ this.debounceTimers.set(path63, timer);
36236
36437
  }
36237
36438
  /**
36238
36439
  * Process file system event
36239
36440
  */
36240
- async processFileEvent(type, path64) {
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 = path64.split("/").pop() || path64;
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(path64);
36459
+ await this.runTests(path63);
36259
36460
  }
36260
36461
  }
36261
36462
  /**