@runa-ai/runa-cli 0.6.0 → 0.7.1
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/{build-BXUJKYHC.js → build-HUDIP6KU.js} +153 -164
- package/dist/{cache-H63JKFYH.js → cache-N7WNPEYF.js} +2 -3
- package/dist/check-LOMVIRHX.js +12 -0
- package/dist/{chunk-HPYJPB5Y.js → chunk-2APB25TT.js} +44 -10
- package/dist/chunk-3WDV32GA.js +33 -0
- package/dist/chunk-5FT3F36G.js +59 -0
- package/dist/{chunk-7QV7U6NI.js → chunk-6FAU4IGR.js} +2 -1
- package/dist/{chunk-CE3DEYFT.js → chunk-7B5C6U2K.js} +2 -208
- package/dist/{chunk-GOGRLQNP.js → chunk-AFY3TX4I.js} +1 -1
- package/dist/{chunk-KWX3JHCY.js → chunk-AKZAN4BC.js} +6 -1
- package/dist/{chunk-XJBQINSA.js → chunk-CCW3PLQY.js} +2 -2
- package/dist/{chunk-IBVVGH6X.js → chunk-EMB6IZFT.js} +17 -4
- package/dist/chunk-FHG3ILE4.js +2011 -0
- package/dist/{chunk-22CS6EMA.js → chunk-H2AHNI75.js} +1 -1
- package/dist/{chunk-UU55OH7P.js → chunk-KE6QJBZG.js} +2 -3
- package/dist/{check-6AB5NGWK.js → chunk-QM53IQHM.js} +14 -12
- package/dist/{chunk-RRGQCUKT.js → chunk-WJXC4MVY.js} +30 -3
- package/dist/chunk-XDCHRVE3.js +215 -0
- package/dist/{chunk-P7U52PBY.js → chunk-Z4Z5DNW4.js} +49 -2
- package/dist/{ci-V3PIG2GI.js → ci-XY6IKEDC.js} +1938 -238
- package/dist/cli/contract-output.d.ts +1 -0
- package/dist/{cli-GFRZCJQR.js → cli-UZA4RBNQ.js} +216 -173
- package/dist/commands/build/actors/validate.d.ts +2 -0
- package/dist/commands/check/commands/check.d.ts +8 -3
- package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts +12 -6
- package/dist/commands/ci/machine/actors/db/production-preview.d.ts +10 -0
- package/dist/commands/ci/machine/actors/db/schema-canonical-diff.d.ts +77 -0
- package/dist/commands/ci/machine/actors/db/schema-stats.d.ts +11 -0
- package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +9 -1
- package/dist/commands/ci/machine/commands/machine-runner.d.ts +2 -0
- package/dist/commands/ci/machine/formatters/sections/production-schema-status.d.ts +30 -0
- package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts +3 -3
- package/dist/commands/ci/machine/helpers.d.ts +8 -0
- package/dist/commands/ci/machine/machine.d.ts +57 -4
- package/dist/commands/ci/machine/types.d.ts +2 -0
- package/dist/commands/ci/utils/execa-helpers.d.ts +1 -0
- package/dist/commands/db/commands/db-sync/error-classifier.d.ts +9 -0
- package/dist/commands/dev/actors/index.d.ts +5 -0
- package/dist/commands/dev/actors/tables-manifest.d.ts +16 -0
- package/dist/commands/dev/contract.d.ts +1 -1
- package/dist/commands/dev/guards.d.ts +24 -0
- package/dist/commands/dev/machine.d.ts +22 -3
- package/dist/commands/dev/types.d.ts +2 -0
- package/dist/commands/doctor.d.ts +9 -0
- package/dist/commands/inject-test-attrs/defaults.d.ts +9 -0
- package/dist/commands/template-check/commands/template-check.d.ts +1 -0
- package/dist/commands/template-check/contract.d.ts +1 -0
- package/dist/commands/utils/machine-state-logging.d.ts +20 -0
- package/dist/commands/utils/repo-root.d.ts +2 -0
- package/dist/constants/versions.d.ts +1 -1
- package/dist/{db-HR7CREX2.js → db-Q3GF7JWP.js} +518 -2234
- package/dist/{dev-A7RW6XQV.js → dev-5YXNPTCJ.js} +168 -49
- package/dist/doctor-MZLOA53G.js +44 -0
- package/dist/{env-B47Z4747.js → env-GMB3THRG.js} +6 -7
- package/dist/{env-files-K2C7O7L5.js → env-files-2UIUYLLR.js} +2 -2
- package/dist/{error-handler-4EYSDOSE.js → error-handler-HEXBRNVV.js} +2 -2
- package/dist/{hotfix-CULKKMGS.js → hotfix-NDTPY2T4.js} +4 -4
- package/dist/index.js +4 -4
- package/dist/{init-ELK5QCWR.js → init-U4VCRHTD.js} +5 -6
- package/dist/{inject-test-attrs-Y5UD5P7Q.js → inject-test-attrs-P44BVTQS.js} +5 -18
- package/dist/{link-C43JRZWY.js → link-VSNDVZZD.js} +2 -3
- package/dist/manifest-TMFLESHW.js +19 -0
- package/dist/{risk-detector-BXUY2WKS.js → risk-detector-4U6ZJ2G5.js} +1 -1
- package/dist/{risk-detector-core-O7I7SPR7.js → risk-detector-core-TK4OAI3N.js} +2 -2
- package/dist/{risk-detector-plpgsql-SGMVKYJP.js → risk-detector-plpgsql-HWKS4OLR.js} +37 -7
- package/dist/{status-IJ4ZWHMX.js → status-UTKS63AB.js} +2 -3
- package/dist/{telemetry-FN7V727Y.js → telemetry-P56UBLZ2.js} +2 -3
- package/dist/{template-check-PNG5NQ5H.js → template-check-FFJVDLBF.js} +63 -35
- package/dist/{test-QYXE5UVW.js → test-V4KQL574.js} +34 -10
- package/dist/{test-gen-QPWOIEHU.js → test-gen-FS4CEY3P.js} +2 -3
- package/dist/{upgrade-3SLWVNAC.js → upgrade-7TWORWBV.js} +18 -6
- package/dist/{validate-SM4PXPS7.js → validate-CAAW4Y44.js} +2 -3
- package/dist/{vuln-check-TYQNEFS7.js → vuln-check-6CMNPSBR.js} +3 -4
- package/dist/{vuln-checker-2QXGN5YT.js → vuln-checker-EJJTNDNE.js} +413 -140
- package/dist/{watch-UCDVOQAH.js → watch-PNTKZYFB.js} +1 -1
- package/dist/{workflow-ZB5Q2PFY.js → workflow-H75N4BXX.js} +3 -4
- package/package.json +2 -2
- package/dist/chunk-JT5SUTWE.js +0 -9
- package/dist/chunk-M47WJJVS.js +0 -71
- package/dist/manifest-2NOQ2IMK.js +0 -32
- package/dist/{chunk-MNPMZERI.js → chunk-644FVGIQ.js} +1 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
-
import { normalizeDatabaseUrlForDdl,
|
|
4
|
-
import {
|
|
5
|
-
import { ensureRunaTmpDir, runLogged } from './chunk-7QV7U6NI.js';
|
|
3
|
+
import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, detectAppSchemas, formatSchemasForSql } from './chunk-XDCHRVE3.js';
|
|
4
|
+
import { isPathContained } from './chunk-DRSUEMAK.js';
|
|
6
5
|
import './chunk-QDF7QXBL.js';
|
|
7
|
-
import { getSnapshotStateName, isSnapshotComplete } from './chunk-
|
|
8
|
-
import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
|
|
6
|
+
import { getSnapshotStateName, isSnapshotComplete } from './chunk-EMB6IZFT.js';
|
|
9
7
|
import { writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput } from './chunk-HD74F6W2.js';
|
|
8
|
+
import { parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, psqlSyncQuery } from './chunk-7B5C6U2K.js';
|
|
9
|
+
import { ensureRunaTmpDir, runLogged } from './chunk-6FAU4IGR.js';
|
|
10
|
+
import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
|
|
11
|
+
import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
|
|
10
12
|
import { init_constants, detectSupabasePorts } from './chunk-VM3IWOT5.js';
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import './chunk-RRGQCUKT.js';
|
|
14
|
-
import './chunk-JT5SUTWE.js';
|
|
13
|
+
import { emitJsonSuccess } from './chunk-KE6QJBZG.js';
|
|
14
|
+
import './chunk-WJXC4MVY.js';
|
|
15
15
|
import { setOutputFormat } from './chunk-HKUWEGUX.js';
|
|
16
|
+
import { detectEnvironment } from './chunk-JMJP4A47.js';
|
|
16
17
|
import { init_esm_shims, __require } from './chunk-VRXHCR5K.js';
|
|
17
18
|
import { Command } from 'commander';
|
|
18
19
|
import { spawnSync, spawn, execFileSync } from 'child_process';
|
|
@@ -24,9 +25,9 @@ import { existsSync, readFileSync, readdirSync, promises, lstatSync, statSync }
|
|
|
24
25
|
import { resolve4 } from 'dns/promises';
|
|
25
26
|
import net, { isIP } from 'net';
|
|
26
27
|
import { fromPromise, setup, assign, createActor } from 'xstate';
|
|
27
|
-
import
|
|
28
|
+
import { randomUUID, createHash } from 'crypto';
|
|
28
29
|
import { execa } from 'execa';
|
|
29
|
-
import '
|
|
30
|
+
import postgres from 'postgres';
|
|
30
31
|
import { glob } from 'glob';
|
|
31
32
|
|
|
32
33
|
createRequire(import.meta.url);
|
|
@@ -1883,24 +1884,41 @@ var ciProdApplyCommand = new Command("prod-apply").description("Apply production
|
|
|
1883
1884
|
|
|
1884
1885
|
// src/commands/ci/commands/ci-static.ts
|
|
1885
1886
|
init_esm_shims();
|
|
1886
|
-
function
|
|
1887
|
-
|
|
1888
|
-
{
|
|
1887
|
+
function hasNativeActionlint() {
|
|
1888
|
+
try {
|
|
1889
|
+
const result = spawnSync("actionlint", ["-version"], {
|
|
1890
|
+
encoding: "utf-8",
|
|
1891
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1892
|
+
timeout: 5e3
|
|
1893
|
+
});
|
|
1894
|
+
return result.status === 0;
|
|
1895
|
+
} catch {
|
|
1896
|
+
return false;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
function createActionlintCheck(mode) {
|
|
1900
|
+
if (mode === "local" && hasNativeActionlint()) {
|
|
1901
|
+
return {
|
|
1889
1902
|
name: "actionlint",
|
|
1890
1903
|
label: "Workflow Lint",
|
|
1891
1904
|
description: "GitHub Actions syntax validation",
|
|
1892
|
-
command: "
|
|
1893
|
-
args: [
|
|
1894
|
-
"run",
|
|
1895
|
-
"--rm",
|
|
1896
|
-
"-v",
|
|
1897
|
-
`${process.cwd()}:/repo`,
|
|
1898
|
-
"-w",
|
|
1899
|
-
"/repo",
|
|
1900
|
-
"rhysd/actionlint:1.7.9"
|
|
1901
|
-
],
|
|
1905
|
+
command: "actionlint",
|
|
1906
|
+
args: ["-color"],
|
|
1902
1907
|
category: "workflow"
|
|
1903
|
-
}
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
name: "actionlint",
|
|
1912
|
+
label: "Workflow Lint",
|
|
1913
|
+
description: "GitHub Actions syntax validation",
|
|
1914
|
+
command: "docker",
|
|
1915
|
+
args: ["run", "--rm", "-v", `${process.cwd()}:/repo`, "-w", "/repo", "rhysd/actionlint:1.7.9"],
|
|
1916
|
+
category: "workflow"
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
function getChecks(mode) {
|
|
1920
|
+
return [
|
|
1921
|
+
createActionlintCheck(mode),
|
|
1904
1922
|
{
|
|
1905
1923
|
name: "type-check",
|
|
1906
1924
|
label: "Type Check",
|
|
@@ -2013,7 +2031,7 @@ function buildStepSummaryMarkdown2(summary) {
|
|
|
2013
2031
|
return lines.join("\n");
|
|
2014
2032
|
}
|
|
2015
2033
|
function getChecksToRun(options) {
|
|
2016
|
-
let checks = getChecks().filter((c) => {
|
|
2034
|
+
let checks = getChecks(options.mode).filter((c) => {
|
|
2017
2035
|
if (c.category === "workflow") return true;
|
|
2018
2036
|
if (c.category === "core") return true;
|
|
2019
2037
|
if (c.category === "build") return options.includeBuild;
|
|
@@ -2498,9 +2516,434 @@ var applySeedsActor = fromPromise(
|
|
|
2498
2516
|
// src/commands/ci/machine/actors/db/collect-schema-stats.ts
|
|
2499
2517
|
init_esm_shims();
|
|
2500
2518
|
|
|
2501
|
-
// src/commands/ci/machine/actors/db/schema-
|
|
2519
|
+
// src/commands/ci/machine/actors/db/schema-canonical-diff.ts
|
|
2502
2520
|
init_esm_shims();
|
|
2521
|
+
function createUnavailableCanonicalSnapshot(error) {
|
|
2522
|
+
return {
|
|
2523
|
+
available: false,
|
|
2524
|
+
objects: [],
|
|
2525
|
+
error
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2503
2528
|
var EXCLUDED_SCHEMAS = [
|
|
2529
|
+
"pg_catalog",
|
|
2530
|
+
"pg_toast",
|
|
2531
|
+
"information_schema",
|
|
2532
|
+
"auth",
|
|
2533
|
+
"storage",
|
|
2534
|
+
"realtime",
|
|
2535
|
+
"_realtime",
|
|
2536
|
+
"supabase_functions",
|
|
2537
|
+
"supabase_migrations",
|
|
2538
|
+
"vault",
|
|
2539
|
+
"pgsodium",
|
|
2540
|
+
"pgsodium_masks",
|
|
2541
|
+
"graphql",
|
|
2542
|
+
"graphql_public",
|
|
2543
|
+
"extensions",
|
|
2544
|
+
"net",
|
|
2545
|
+
"cron",
|
|
2546
|
+
"tests"
|
|
2547
|
+
];
|
|
2548
|
+
function normalizeWhitespace(value) {
|
|
2549
|
+
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
2550
|
+
}
|
|
2551
|
+
function stableHash(payload) {
|
|
2552
|
+
return createHash("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
|
|
2553
|
+
}
|
|
2554
|
+
function createCanonicalTableColumnPayload(row) {
|
|
2555
|
+
return {
|
|
2556
|
+
name: row.column_name,
|
|
2557
|
+
type: normalizeWhitespace(row.data_type),
|
|
2558
|
+
notNull: row.not_null,
|
|
2559
|
+
default: normalizeWhitespace(row.default_expr),
|
|
2560
|
+
identity: row.identity_kind ?? "",
|
|
2561
|
+
generated: row.generated_kind ?? ""
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function sortCanonicalTableColumns(columns) {
|
|
2565
|
+
return [...columns].sort((left, right) => left.name.localeCompare(right.name));
|
|
2566
|
+
}
|
|
2567
|
+
function createCanonicalObject(kind, schema, name, label, payload, parentName) {
|
|
2568
|
+
return {
|
|
2569
|
+
kind,
|
|
2570
|
+
schema,
|
|
2571
|
+
name,
|
|
2572
|
+
key: `${kind}:${schema}:${name}`,
|
|
2573
|
+
label,
|
|
2574
|
+
parentName,
|
|
2575
|
+
signature: stableHash(payload)
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
function buildSchemaListSql(schemaNames) {
|
|
2579
|
+
return schemaNames.map((schemaName) => `'${schemaName}'`).join(", ");
|
|
2580
|
+
}
|
|
2581
|
+
async function getUserSchemas(sql) {
|
|
2582
|
+
const excludeList = buildSchemaListSql(EXCLUDED_SCHEMAS);
|
|
2583
|
+
const rows = await sql`
|
|
2584
|
+
SELECT nspname
|
|
2585
|
+
FROM pg_namespace
|
|
2586
|
+
WHERE nspname NOT IN (${sql.unsafe(excludeList)})
|
|
2587
|
+
AND nspname NOT LIKE 'pg_%'
|
|
2588
|
+
ORDER BY nspname
|
|
2589
|
+
`;
|
|
2590
|
+
return rows.map((row) => row.nspname);
|
|
2591
|
+
}
|
|
2592
|
+
async function queryTableColumns(sql, schemaList) {
|
|
2593
|
+
return await sql`
|
|
2594
|
+
SELECT
|
|
2595
|
+
n.nspname AS schema_name,
|
|
2596
|
+
c.relname AS table_name,
|
|
2597
|
+
a.attnum::int AS attnum,
|
|
2598
|
+
a.attname AS column_name,
|
|
2599
|
+
format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
2600
|
+
a.attnotnull AS not_null,
|
|
2601
|
+
pg_get_expr(ad.adbin, ad.adrelid, true) AS default_expr,
|
|
2602
|
+
NULLIF(a.attidentity, '') AS identity_kind,
|
|
2603
|
+
NULLIF(a.attgenerated, '') AS generated_kind
|
|
2604
|
+
FROM pg_class c
|
|
2605
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2606
|
+
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
2607
|
+
LEFT JOIN pg_attrdef ad ON ad.adrelid = c.oid AND ad.adnum = a.attnum
|
|
2608
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2609
|
+
AND c.relkind IN ('r', 'p')
|
|
2610
|
+
AND a.attnum > 0
|
|
2611
|
+
AND NOT a.attisdropped
|
|
2612
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
2613
|
+
`;
|
|
2614
|
+
}
|
|
2615
|
+
async function queryTableFlags(sql, schemaList) {
|
|
2616
|
+
return await sql`
|
|
2617
|
+
SELECT
|
|
2618
|
+
n.nspname AS schema_name,
|
|
2619
|
+
c.relname AS table_name,
|
|
2620
|
+
c.relrowsecurity AS rls_enabled,
|
|
2621
|
+
c.relforcerowsecurity AS rls_forced
|
|
2622
|
+
FROM pg_class c
|
|
2623
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2624
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2625
|
+
AND c.relkind IN ('r', 'p')
|
|
2626
|
+
`;
|
|
2627
|
+
}
|
|
2628
|
+
async function queryTableConstraints(sql, schemaList) {
|
|
2629
|
+
return await sql`
|
|
2630
|
+
SELECT
|
|
2631
|
+
n.nspname AS schema_name,
|
|
2632
|
+
c.relname AS table_name,
|
|
2633
|
+
con.conname AS constraint_name,
|
|
2634
|
+
con.contype AS constraint_type,
|
|
2635
|
+
pg_get_constraintdef(con.oid, true) AS definition
|
|
2636
|
+
FROM pg_constraint con
|
|
2637
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
2638
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2639
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2640
|
+
AND c.relkind IN ('r', 'p')
|
|
2641
|
+
ORDER BY n.nspname, c.relname, con.conname
|
|
2642
|
+
`;
|
|
2643
|
+
}
|
|
2644
|
+
function buildTableObjects(columns, flags, constraints) {
|
|
2645
|
+
const tables = /* @__PURE__ */ new Map();
|
|
2646
|
+
for (const row of columns) {
|
|
2647
|
+
const key = `${row.schema_name}.${row.table_name}`;
|
|
2648
|
+
const entry = tables.get(key) ?? {
|
|
2649
|
+
schema: row.schema_name,
|
|
2650
|
+
table: row.table_name,
|
|
2651
|
+
columns: [],
|
|
2652
|
+
constraints: [],
|
|
2653
|
+
rlsEnabled: false,
|
|
2654
|
+
rlsForced: false
|
|
2655
|
+
};
|
|
2656
|
+
entry.columns.push(createCanonicalTableColumnPayload(row));
|
|
2657
|
+
tables.set(key, entry);
|
|
2658
|
+
}
|
|
2659
|
+
for (const row of flags) {
|
|
2660
|
+
const key = `${row.schema_name}.${row.table_name}`;
|
|
2661
|
+
const entry = tables.get(key) ?? {
|
|
2662
|
+
schema: row.schema_name,
|
|
2663
|
+
table: row.table_name,
|
|
2664
|
+
columns: [],
|
|
2665
|
+
constraints: [],
|
|
2666
|
+
rlsEnabled: false,
|
|
2667
|
+
rlsForced: false
|
|
2668
|
+
};
|
|
2669
|
+
entry.rlsEnabled = row.rls_enabled;
|
|
2670
|
+
entry.rlsForced = row.rls_forced;
|
|
2671
|
+
tables.set(key, entry);
|
|
2672
|
+
}
|
|
2673
|
+
for (const row of constraints) {
|
|
2674
|
+
const key = `${row.schema_name}.${row.table_name}`;
|
|
2675
|
+
const entry = tables.get(key) ?? {
|
|
2676
|
+
schema: row.schema_name,
|
|
2677
|
+
table: row.table_name,
|
|
2678
|
+
columns: [],
|
|
2679
|
+
constraints: [],
|
|
2680
|
+
rlsEnabled: false,
|
|
2681
|
+
rlsForced: false
|
|
2682
|
+
};
|
|
2683
|
+
entry.constraints.push({
|
|
2684
|
+
name: row.constraint_name,
|
|
2685
|
+
type: row.constraint_type,
|
|
2686
|
+
definition: normalizeWhitespace(row.definition)
|
|
2687
|
+
});
|
|
2688
|
+
tables.set(key, entry);
|
|
2689
|
+
}
|
|
2690
|
+
return Array.from(tables.values()).map(
|
|
2691
|
+
(table) => createCanonicalObject("table", table.schema, table.table, `${table.schema}.${table.table}`, {
|
|
2692
|
+
columns: sortCanonicalTableColumns(table.columns),
|
|
2693
|
+
constraints: table.constraints,
|
|
2694
|
+
rlsEnabled: table.rlsEnabled,
|
|
2695
|
+
rlsForced: table.rlsForced
|
|
2696
|
+
})
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
async function queryFunctions(sql, schemaList) {
|
|
2700
|
+
return await sql`
|
|
2701
|
+
SELECT
|
|
2702
|
+
n.nspname AS schema_name,
|
|
2703
|
+
p.proname AS object_name,
|
|
2704
|
+
format('%I.%I(%s)', n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)) AS object_key,
|
|
2705
|
+
format('%I.%I(%s)', n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)) AS object_label,
|
|
2706
|
+
pg_get_functiondef(p.oid) AS definition
|
|
2707
|
+
FROM pg_proc p
|
|
2708
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
2709
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2710
|
+
AND p.prokind = 'f'
|
|
2711
|
+
AND NOT EXISTS (
|
|
2712
|
+
SELECT 1 FROM pg_depend d
|
|
2713
|
+
WHERE d.objid = p.oid
|
|
2714
|
+
AND d.deptype = 'e'
|
|
2715
|
+
)
|
|
2716
|
+
AND p.proname NOT LIKE 'supabase_%'
|
|
2717
|
+
AND p.proname NOT LIKE 'postgrest_%'
|
|
2718
|
+
AND p.proname NOT LIKE 'pgrst_%'
|
|
2719
|
+
AND p.proname NOT LIKE '_pgrst_%'
|
|
2720
|
+
ORDER BY n.nspname, p.proname
|
|
2721
|
+
`;
|
|
2722
|
+
}
|
|
2723
|
+
async function queryPolicies(sql, schemaList) {
|
|
2724
|
+
return await sql`
|
|
2725
|
+
SELECT
|
|
2726
|
+
schemaname AS schema_name,
|
|
2727
|
+
policyname AS object_name,
|
|
2728
|
+
format('%I.%I.%I', schemaname, tablename, policyname) AS object_key,
|
|
2729
|
+
format('%I.%I policy %I', schemaname, tablename, policyname) AS object_label,
|
|
2730
|
+
tablename AS parent_name,
|
|
2731
|
+
jsonb_build_object(
|
|
2732
|
+
'table', tablename,
|
|
2733
|
+
'permissive', permissive,
|
|
2734
|
+
'roles', COALESCE(roles, ARRAY[]::text[]),
|
|
2735
|
+
'command', cmd,
|
|
2736
|
+
'using', COALESCE(qual, ''),
|
|
2737
|
+
'withCheck', COALESCE(with_check, '')
|
|
2738
|
+
)::text AS definition
|
|
2739
|
+
FROM pg_policies
|
|
2740
|
+
WHERE schemaname IN (${sql.unsafe(schemaList)})
|
|
2741
|
+
ORDER BY schemaname, tablename, policyname
|
|
2742
|
+
`;
|
|
2743
|
+
}
|
|
2744
|
+
async function queryIndexes(sql, schemaList) {
|
|
2745
|
+
return await sql`
|
|
2746
|
+
SELECT
|
|
2747
|
+
n.nspname AS schema_name,
|
|
2748
|
+
i.relname AS object_name,
|
|
2749
|
+
format('%I.%I', n.nspname, i.relname) AS object_key,
|
|
2750
|
+
format('%I.%I on %I', n.nspname, i.relname, t.relname) AS object_label,
|
|
2751
|
+
t.relname AS parent_name,
|
|
2752
|
+
jsonb_build_object(
|
|
2753
|
+
'table', t.relname,
|
|
2754
|
+
'definition',
|
|
2755
|
+
regexp_replace(pg_get_indexdef(i.oid), '^CREATE( UNIQUE)? INDEX [^ ]+ ON ', 'CREATE\\1 INDEX ON '),
|
|
2756
|
+
'predicate',
|
|
2757
|
+
COALESCE(pg_get_expr(idx.indpred, idx.indrelid, true), ''),
|
|
2758
|
+
'unique',
|
|
2759
|
+
idx.indisunique
|
|
2760
|
+
)::text AS definition
|
|
2761
|
+
FROM pg_index idx
|
|
2762
|
+
JOIN pg_class i ON i.oid = idx.indexrelid
|
|
2763
|
+
JOIN pg_class t ON t.oid = idx.indrelid
|
|
2764
|
+
JOIN pg_namespace n ON n.oid = i.relnamespace
|
|
2765
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2766
|
+
AND NOT EXISTS (
|
|
2767
|
+
SELECT 1 FROM pg_constraint c
|
|
2768
|
+
WHERE c.conindid = idx.indexrelid
|
|
2769
|
+
)
|
|
2770
|
+
ORDER BY n.nspname, t.relname, i.relname
|
|
2771
|
+
`;
|
|
2772
|
+
}
|
|
2773
|
+
async function queryTriggers(sql, schemaList) {
|
|
2774
|
+
return await sql`
|
|
2775
|
+
SELECT
|
|
2776
|
+
n.nspname AS schema_name,
|
|
2777
|
+
t.tgname AS object_name,
|
|
2778
|
+
format('%I.%I.%I', n.nspname, c.relname, t.tgname) AS object_key,
|
|
2779
|
+
format('%I.%I trigger %I', n.nspname, c.relname, t.tgname) AS object_label,
|
|
2780
|
+
c.relname AS parent_name,
|
|
2781
|
+
pg_get_triggerdef(t.oid, true) AS definition
|
|
2782
|
+
FROM pg_trigger t
|
|
2783
|
+
JOIN pg_class c ON t.tgrelid = c.oid
|
|
2784
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
2785
|
+
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2786
|
+
AND NOT t.tgisinternal
|
|
2787
|
+
ORDER BY n.nspname, c.relname, t.tgname
|
|
2788
|
+
`;
|
|
2789
|
+
}
|
|
2790
|
+
function rowsToObjects(kind, rows) {
|
|
2791
|
+
return rows.map(
|
|
2792
|
+
(row) => createCanonicalObject(
|
|
2793
|
+
kind,
|
|
2794
|
+
row.schema_name,
|
|
2795
|
+
row.object_key,
|
|
2796
|
+
row.object_label,
|
|
2797
|
+
normalizeWhitespace(row.definition),
|
|
2798
|
+
row.parent_name ?? void 0
|
|
2799
|
+
)
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
async function getCanonicalSchemaSnapshot(databaseUrl) {
|
|
2803
|
+
let sql = null;
|
|
2804
|
+
try {
|
|
2805
|
+
const isRemoteSupabase = databaseUrl.includes(".supabase.co");
|
|
2806
|
+
sql = postgres(databaseUrl, {
|
|
2807
|
+
max: 1,
|
|
2808
|
+
idle_timeout: 10,
|
|
2809
|
+
connect_timeout: 10,
|
|
2810
|
+
...isRemoteSupabase && { ssl: "require" }
|
|
2811
|
+
});
|
|
2812
|
+
const userSchemas = await getUserSchemas(sql);
|
|
2813
|
+
if (userSchemas.length === 0) {
|
|
2814
|
+
return { available: true, objects: [] };
|
|
2815
|
+
}
|
|
2816
|
+
const schemaList = buildSchemaListSql(userSchemas);
|
|
2817
|
+
const [columns, flags, constraints, functions, policies, indexes, triggers] = await Promise.all(
|
|
2818
|
+
[
|
|
2819
|
+
queryTableColumns(sql, schemaList),
|
|
2820
|
+
queryTableFlags(sql, schemaList),
|
|
2821
|
+
queryTableConstraints(sql, schemaList),
|
|
2822
|
+
queryFunctions(sql, schemaList),
|
|
2823
|
+
queryPolicies(sql, schemaList),
|
|
2824
|
+
queryIndexes(sql, schemaList),
|
|
2825
|
+
queryTriggers(sql, schemaList)
|
|
2826
|
+
]
|
|
2827
|
+
);
|
|
2828
|
+
return {
|
|
2829
|
+
available: true,
|
|
2830
|
+
objects: [
|
|
2831
|
+
...buildTableObjects(columns, flags, constraints),
|
|
2832
|
+
...rowsToObjects("function", functions),
|
|
2833
|
+
...rowsToObjects("policy", policies),
|
|
2834
|
+
...rowsToObjects("index", indexes),
|
|
2835
|
+
...rowsToObjects("trigger", triggers)
|
|
2836
|
+
].sort((a, b) => a.key.localeCompare(b.key))
|
|
2837
|
+
};
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2840
|
+
console.warn(`[schema-canonical-diff] Could not query canonical snapshot: ${message}`);
|
|
2841
|
+
return createUnavailableCanonicalSnapshot(message);
|
|
2842
|
+
} finally {
|
|
2843
|
+
if (sql) {
|
|
2844
|
+
await sql.end();
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
function buildObjectMap(snapshot) {
|
|
2849
|
+
return new Map(snapshot.objects.map((object) => [object.key, object]));
|
|
2850
|
+
}
|
|
2851
|
+
function buildSignatureBuckets(objects) {
|
|
2852
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2853
|
+
for (const object of objects) {
|
|
2854
|
+
const bucketKey = `${object.kind}:${object.schema}:${object.signature}`;
|
|
2855
|
+
const current = buckets.get(bucketKey) ?? [];
|
|
2856
|
+
current.push(object);
|
|
2857
|
+
buckets.set(bucketKey, current);
|
|
2858
|
+
}
|
|
2859
|
+
return buckets;
|
|
2860
|
+
}
|
|
2861
|
+
function compareCanonicalSnapshots(reference, target) {
|
|
2862
|
+
const missing = [];
|
|
2863
|
+
const extra = [];
|
|
2864
|
+
const changed = [];
|
|
2865
|
+
const renamed = [];
|
|
2866
|
+
const targetByKey = buildObjectMap(target);
|
|
2867
|
+
const targetSignatureBuckets = buildSignatureBuckets(target.objects);
|
|
2868
|
+
const matchedTargetKeys = /* @__PURE__ */ new Set();
|
|
2869
|
+
for (const referenceObject of reference.objects) {
|
|
2870
|
+
const matchedTarget = targetByKey.get(referenceObject.key);
|
|
2871
|
+
if (matchedTarget) {
|
|
2872
|
+
matchedTargetKeys.add(matchedTarget.key);
|
|
2873
|
+
if (matchedTarget.signature !== referenceObject.signature) {
|
|
2874
|
+
changed.push({ reference: referenceObject, target: matchedTarget });
|
|
2875
|
+
}
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
if (referenceObject.kind === "index") {
|
|
2879
|
+
const renameBucket = targetSignatureBuckets.get(
|
|
2880
|
+
`${referenceObject.kind}:${referenceObject.schema}:${referenceObject.signature}`
|
|
2881
|
+
) ?? [];
|
|
2882
|
+
const renamedTarget = renameBucket.find((candidate) => !matchedTargetKeys.has(candidate.key));
|
|
2883
|
+
if (renamedTarget) {
|
|
2884
|
+
matchedTargetKeys.add(renamedTarget.key);
|
|
2885
|
+
renamed.push({ reference: referenceObject, target: renamedTarget });
|
|
2886
|
+
continue;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
missing.push(referenceObject);
|
|
2890
|
+
}
|
|
2891
|
+
for (const targetObject of target.objects) {
|
|
2892
|
+
if (!matchedTargetKeys.has(targetObject.key) && !reference.objects.some((candidate) => candidate.key === targetObject.key)) {
|
|
2893
|
+
extra.push(targetObject);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
return { missing, extra, changed, renamed };
|
|
2897
|
+
}
|
|
2898
|
+
function addSummaryKind(summary, kind) {
|
|
2899
|
+
if (!summary.changedKinds.includes(kind)) {
|
|
2900
|
+
summary.changedKinds.push(kind);
|
|
2901
|
+
summary.changedKinds.sort();
|
|
2902
|
+
}
|
|
2903
|
+
summary.hasChanges = true;
|
|
2904
|
+
}
|
|
2905
|
+
function ensureSchemaSummary(summaries, schemaName) {
|
|
2906
|
+
const existing = summaries[schemaName];
|
|
2907
|
+
if (existing) return existing;
|
|
2908
|
+
const created = {
|
|
2909
|
+
hasChanges: false,
|
|
2910
|
+
changedKinds: [],
|
|
2911
|
+
counts: { missing: 0, extra: 0, changed: 0, renamed: 0 }
|
|
2912
|
+
};
|
|
2913
|
+
summaries[schemaName] = created;
|
|
2914
|
+
return created;
|
|
2915
|
+
}
|
|
2916
|
+
function summarizeCanonicalDiffBySchema(diff) {
|
|
2917
|
+
const summaries = {};
|
|
2918
|
+
for (const object of diff.missing) {
|
|
2919
|
+
const summary = ensureSchemaSummary(summaries, object.schema);
|
|
2920
|
+
summary.counts.missing += 1;
|
|
2921
|
+
addSummaryKind(summary, object.kind);
|
|
2922
|
+
}
|
|
2923
|
+
for (const object of diff.extra) {
|
|
2924
|
+
const summary = ensureSchemaSummary(summaries, object.schema);
|
|
2925
|
+
summary.counts.extra += 1;
|
|
2926
|
+
addSummaryKind(summary, object.kind);
|
|
2927
|
+
}
|
|
2928
|
+
for (const pair of diff.changed) {
|
|
2929
|
+
const summary = ensureSchemaSummary(summaries, pair.reference.schema);
|
|
2930
|
+
summary.counts.changed += 1;
|
|
2931
|
+
addSummaryKind(summary, pair.reference.kind);
|
|
2932
|
+
}
|
|
2933
|
+
for (const pair of diff.renamed) {
|
|
2934
|
+
const summary = ensureSchemaSummary(summaries, pair.reference.schema);
|
|
2935
|
+
summary.counts.renamed += 1;
|
|
2936
|
+
addSummaryKind(summary, pair.reference.kind);
|
|
2937
|
+
}
|
|
2938
|
+
return summaries;
|
|
2939
|
+
}
|
|
2940
|
+
function hasCanonicalChanges(diff) {
|
|
2941
|
+
return diff.missing.length > 0 || diff.extra.length > 0 || diff.changed.length > 0 || diff.renamed.length > 0;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// src/commands/ci/machine/actors/db/schema-stats.ts
|
|
2945
|
+
init_esm_shims();
|
|
2946
|
+
var EXCLUDED_SCHEMAS2 = [
|
|
2504
2947
|
// PostgreSQL system schemas
|
|
2505
2948
|
"pg_catalog",
|
|
2506
2949
|
"pg_toast",
|
|
@@ -2604,15 +3047,18 @@ async function getDetailedIndexList(sql, schemaList) {
|
|
|
2604
3047
|
t.relname as table,
|
|
2605
3048
|
idx.indisunique as is_unique,
|
|
2606
3049
|
idx.indpred IS NOT NULL as is_partial,
|
|
3050
|
+
am.amname as access_method,
|
|
2607
3051
|
CASE
|
|
2608
3052
|
WHEN idx.indpred IS NOT NULL
|
|
2609
3053
|
THEN pg_get_expr(idx.indpred, idx.indrelid, true)
|
|
2610
3054
|
ELSE NULL
|
|
2611
|
-
END as where_clause
|
|
3055
|
+
END as where_clause,
|
|
3056
|
+
pg_get_indexdef(i.oid) as definition
|
|
2612
3057
|
FROM pg_index idx
|
|
2613
3058
|
JOIN pg_class i ON i.oid = idx.indexrelid
|
|
2614
3059
|
JOIN pg_class t ON t.oid = idx.indrelid
|
|
2615
3060
|
JOIN pg_namespace n ON n.oid = i.relnamespace
|
|
3061
|
+
JOIN pg_am am ON am.oid = i.relam
|
|
2616
3062
|
WHERE n.nspname IN (${sql.unsafe(schemaList)})
|
|
2617
3063
|
AND NOT EXISTS (
|
|
2618
3064
|
SELECT 1 FROM pg_constraint c
|
|
@@ -2626,11 +3072,13 @@ async function getDetailedIndexList(sql, schemaList) {
|
|
|
2626
3072
|
table: row.table,
|
|
2627
3073
|
isUnique: row.is_unique,
|
|
2628
3074
|
isPartial: row.is_partial,
|
|
2629
|
-
whereClause: row.where_clause || void 0
|
|
3075
|
+
whereClause: row.where_clause || void 0,
|
|
3076
|
+
accessMethod: row.access_method || void 0,
|
|
3077
|
+
definition: normalizeIndexDefinition(row.definition || void 0)
|
|
2630
3078
|
}));
|
|
2631
3079
|
}
|
|
2632
|
-
async function
|
|
2633
|
-
const excludeList =
|
|
3080
|
+
async function getUserSchemas2(sql) {
|
|
3081
|
+
const excludeList = EXCLUDED_SCHEMAS2.map((s) => `'${s}'`).join(", ");
|
|
2634
3082
|
const result = await sql`
|
|
2635
3083
|
SELECT nspname
|
|
2636
3084
|
FROM pg_namespace
|
|
@@ -2653,7 +3101,7 @@ async function getDbSchemaStats(databaseUrl) {
|
|
|
2653
3101
|
// Supabase production requires SSL; local Docker does not
|
|
2654
3102
|
...isRemoteSupabase && { ssl: "require" }
|
|
2655
3103
|
});
|
|
2656
|
-
const userSchemas = await
|
|
3104
|
+
const userSchemas = await getUserSchemas2(sql);
|
|
2657
3105
|
if (userSchemas.length === 0) {
|
|
2658
3106
|
return { schemas, total };
|
|
2659
3107
|
}
|
|
@@ -2783,12 +3231,30 @@ function collectExpectedDriftReasons(schemaName, fieldDiffs, expectedDrift) {
|
|
|
2783
3231
|
function compareIndexLists(reference, target) {
|
|
2784
3232
|
const refIndexes = reference.indexList || [];
|
|
2785
3233
|
const targetIndexes = target.indexList || [];
|
|
2786
|
-
const
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
3234
|
+
const buildIndexKey = (index) => [
|
|
3235
|
+
index.schema,
|
|
3236
|
+
index.table,
|
|
3237
|
+
index.isUnique ? "unique" : "nonunique",
|
|
3238
|
+
index.isPartial ? "partial" : "full",
|
|
3239
|
+
normalizeDiffValue(index.whereClause),
|
|
3240
|
+
normalizeDiffValue(index.accessMethod),
|
|
3241
|
+
normalizeIndexDefinition(index.definition)
|
|
3242
|
+
].join(":");
|
|
3243
|
+
const refKeys = new Set(refIndexes.map(buildIndexKey));
|
|
3244
|
+
const targetKeys = new Set(targetIndexes.map(buildIndexKey));
|
|
3245
|
+
const missing = refIndexes.filter((index) => !targetKeys.has(buildIndexKey(index)));
|
|
3246
|
+
const extra = targetIndexes.filter((index) => !refKeys.has(buildIndexKey(index)));
|
|
2790
3247
|
return { missing, extra };
|
|
2791
3248
|
}
|
|
3249
|
+
function normalizeDiffValue(value) {
|
|
3250
|
+
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
3251
|
+
}
|
|
3252
|
+
function normalizeIndexDefinition(value) {
|
|
3253
|
+
return normalizeDiffValue(value).replace(
|
|
3254
|
+
/^create\s+(unique\s+)?index\s+\S+\s+on\s+/i,
|
|
3255
|
+
(_match, unique = "") => `create ${unique || ""}index on `
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
2792
3258
|
function formatIndexInfo(index) {
|
|
2793
3259
|
const qualifiedName = `${index.schema}.${index.name}`;
|
|
2794
3260
|
const tags = [];
|
|
@@ -2805,7 +3271,27 @@ function hasIndexDiff(diff) {
|
|
|
2805
3271
|
function emptyStats() {
|
|
2806
3272
|
return {
|
|
2807
3273
|
schemas: {},
|
|
2808
|
-
total: { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 }
|
|
3274
|
+
total: { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 },
|
|
3275
|
+
available: true
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
function unavailableStats(error) {
|
|
3279
|
+
return {
|
|
3280
|
+
...emptyStats(),
|
|
3281
|
+
available: false,
|
|
3282
|
+
error,
|
|
3283
|
+
canonicalSnapshot: createUnavailableCanonicalSnapshot(error)
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
function cloneEnvironmentStats(stats) {
|
|
3287
|
+
return {
|
|
3288
|
+
...stats,
|
|
3289
|
+
schemas: Object.fromEntries(
|
|
3290
|
+
Object.entries(stats.schemas).map(([schema, values]) => [schema, { ...values }])
|
|
3291
|
+
),
|
|
3292
|
+
total: { ...stats.total },
|
|
3293
|
+
indexList: stats.indexList?.map((index) => ({ ...index })),
|
|
3294
|
+
canonicalSnapshot: stats.canonicalSnapshot
|
|
2809
3295
|
};
|
|
2810
3296
|
}
|
|
2811
3297
|
function logSchemaStats(label, stats, includeSchemas = false) {
|
|
@@ -2817,43 +3303,158 @@ function logSchemaStats(label, stats, includeSchemas = false) {
|
|
|
2817
3303
|
const schemas = Object.keys(stats.schemas);
|
|
2818
3304
|
console.log(`${base} (schemas: ${schemas.join(", ")})`);
|
|
2819
3305
|
}
|
|
2820
|
-
function
|
|
3306
|
+
async function collectEnvironmentStats(dbUrl, label, includeSchemas = false) {
|
|
2821
3307
|
if (!dbUrl) {
|
|
2822
|
-
return
|
|
3308
|
+
return null;
|
|
2823
3309
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
3310
|
+
const stats = await getDbSchemaStats(dbUrl);
|
|
3311
|
+
stats.canonicalSnapshot = await getCanonicalSchemaSnapshot(dbUrl);
|
|
3312
|
+
stats.available = true;
|
|
3313
|
+
logSchemaStats(label, stats, includeSchemas);
|
|
3314
|
+
return stats;
|
|
2828
3315
|
}
|
|
2829
3316
|
function resolveSettledStats(result, failureLabel) {
|
|
2830
3317
|
if (result.status === "fulfilled" && result.value) {
|
|
2831
3318
|
return result.value;
|
|
2832
3319
|
}
|
|
3320
|
+
const reason = result.status === "rejected" ? String(result.reason) : `${failureLabel} database URL is not available for schema comparison`;
|
|
2833
3321
|
if (result.status === "rejected") {
|
|
2834
3322
|
console.warn(`[schema-stats] Failed to query ${failureLabel} database: ${result.reason}`);
|
|
2835
3323
|
}
|
|
2836
|
-
return
|
|
3324
|
+
return unavailableStats(reason);
|
|
2837
3325
|
}
|
|
2838
3326
|
function resolveSettledProductionStats(result) {
|
|
2839
3327
|
if (result.status === "fulfilled") {
|
|
2840
3328
|
return result.value;
|
|
2841
3329
|
}
|
|
2842
3330
|
console.warn(`[schema-stats] Failed to query production database: ${result.reason}`);
|
|
2843
|
-
return
|
|
3331
|
+
return unavailableStats(String(result.reason));
|
|
3332
|
+
}
|
|
3333
|
+
function buildAdminDsn(sourceDbUrl) {
|
|
3334
|
+
const conn = parsePostgresUrl(sourceDbUrl);
|
|
3335
|
+
const password = conn.password ? `:${encodeURIComponent(conn.password)}` : "";
|
|
3336
|
+
return `postgresql://${conn.user}${password}@${conn.host}:${conn.port}/postgres`;
|
|
3337
|
+
}
|
|
3338
|
+
function buildShadowDsn(sourceDbUrl, dbName) {
|
|
3339
|
+
const conn = parsePostgresUrl(sourceDbUrl);
|
|
3340
|
+
const password = conn.password ? `:${encodeURIComponent(conn.password)}` : "";
|
|
3341
|
+
return `postgresql://${conn.user}${password}@${conn.host}:${conn.port}/${dbName}`;
|
|
3342
|
+
}
|
|
3343
|
+
function createReferenceDb(sourceDbUrl) {
|
|
3344
|
+
const dbName = `ci_schema_ref_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
3345
|
+
const adminDsn = buildAdminDsn(sourceDbUrl);
|
|
3346
|
+
const createResult = psqlSyncQuery({
|
|
3347
|
+
databaseUrl: adminDsn,
|
|
3348
|
+
sql: `CREATE DATABASE ${dbName}`,
|
|
3349
|
+
timeout: 3e4
|
|
3350
|
+
});
|
|
3351
|
+
if (createResult.status !== 0) {
|
|
3352
|
+
throw new Error(
|
|
3353
|
+
`Failed to create reference database: ${createResult.stderr || createResult.stdout}`
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
return { dsn: buildShadowDsn(sourceDbUrl, dbName), dbName };
|
|
3357
|
+
}
|
|
3358
|
+
function dropReferenceDb(sourceDbUrl, dbName) {
|
|
3359
|
+
const adminDsn = buildAdminDsn(sourceDbUrl);
|
|
3360
|
+
psqlSyncQuery({
|
|
3361
|
+
databaseUrl: adminDsn,
|
|
3362
|
+
sql: `
|
|
3363
|
+
SELECT pg_terminate_backend(pid)
|
|
3364
|
+
FROM pg_stat_activity
|
|
3365
|
+
WHERE datname = '${dbName}' AND pid <> pg_backend_pid()
|
|
3366
|
+
`,
|
|
3367
|
+
timeout: 1e4
|
|
3368
|
+
});
|
|
3369
|
+
psqlSyncQuery({
|
|
3370
|
+
databaseUrl: adminDsn,
|
|
3371
|
+
sql: `DROP DATABASE IF EXISTS ${dbName}`,
|
|
3372
|
+
timeout: 3e4
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
async function buildReferenceStats(repoRoot, tmpDir, sourceDbUrl) {
|
|
3376
|
+
if (!sourceDbUrl) return null;
|
|
3377
|
+
const referenceDb = createReferenceDb(sourceDbUrl);
|
|
3378
|
+
const logFile = path4.join(tmpDir, "ci-reference-db-apply.log");
|
|
3379
|
+
try {
|
|
3380
|
+
const result = await execa(
|
|
3381
|
+
"pnpm",
|
|
3382
|
+
["exec", "runa", "db", "apply", "preview", "--auto-approve", "--no-seed", "--verbose"],
|
|
3383
|
+
{
|
|
3384
|
+
cwd: repoRoot,
|
|
3385
|
+
env: {
|
|
3386
|
+
...process.env,
|
|
3387
|
+
DATABASE_URL_ADMIN: referenceDb.dsn,
|
|
3388
|
+
DATABASE_URL: referenceDb.dsn
|
|
3389
|
+
},
|
|
3390
|
+
reject: false
|
|
3391
|
+
}
|
|
3392
|
+
);
|
|
3393
|
+
const fullOutput = `${result.stdout || ""}
|
|
3394
|
+
${result.stderr || ""}`;
|
|
3395
|
+
if (result.exitCode !== 0) {
|
|
3396
|
+
throw new Error(`Failed to build reference DB: ${fullOutput.substring(0, 1e3)}`);
|
|
3397
|
+
}
|
|
3398
|
+
await (await import('fs/promises')).writeFile(logFile, fullOutput);
|
|
3399
|
+
return collectEnvironmentStats(referenceDb.dsn, "Reference", true);
|
|
3400
|
+
} finally {
|
|
3401
|
+
dropReferenceDb(sourceDbUrl, referenceDb.dbName);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
async function resolveReferenceAndCiStats(params) {
|
|
3405
|
+
const { repoRoot, tmpDir, referenceDbUrl, ciDbUrl, referenceStrategy } = params;
|
|
3406
|
+
if (referenceStrategy === "reuse-ci" && ciDbUrl) {
|
|
3407
|
+
console.log(
|
|
3408
|
+
"[schema-stats] Reusing synced CI database as reference schema (skip temporary rebuild)"
|
|
3409
|
+
);
|
|
3410
|
+
const [ciResult2] = await Promise.allSettled([collectEnvironmentStats(ciDbUrl, "CI")]);
|
|
3411
|
+
const ci = resolveSettledStats(ciResult2, "CI");
|
|
3412
|
+
const reference = ci.available === false ? unavailableStats(ci.error ?? "CI database is unavailable for reference schema reuse") : cloneEnvironmentStats(ci);
|
|
3413
|
+
return [reference, ci];
|
|
3414
|
+
}
|
|
3415
|
+
const [referenceResult, ciResult] = await Promise.allSettled([
|
|
3416
|
+
buildReferenceStats(repoRoot, tmpDir, referenceDbUrl),
|
|
3417
|
+
collectEnvironmentStats(ciDbUrl, "CI")
|
|
3418
|
+
]);
|
|
3419
|
+
return [resolveSettledStats(referenceResult, "reference"), resolveSettledStats(ciResult, "CI")];
|
|
2844
3420
|
}
|
|
2845
3421
|
var collectSchemaStatsActor = fromPromise(
|
|
2846
3422
|
async ({ input }) => {
|
|
2847
|
-
const {
|
|
2848
|
-
|
|
3423
|
+
const {
|
|
3424
|
+
repoRoot,
|
|
3425
|
+
referenceDbUrl,
|
|
3426
|
+
ciDbUrl,
|
|
3427
|
+
referenceStrategy = "rebuild",
|
|
3428
|
+
queryProduction,
|
|
3429
|
+
tmpDir
|
|
3430
|
+
} = input;
|
|
3431
|
+
console.log(
|
|
3432
|
+
"[schema-stats] Collecting schema statistics (Reference/CI/Production, parallel)..."
|
|
3433
|
+
);
|
|
2849
3434
|
const productionUrl = process.env.GH_DATABASE_URL_ADMIN || process.env.GH_DATABASE_URL;
|
|
2850
|
-
const [
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3435
|
+
const [referenceResult, productionResult] = await Promise.allSettled([
|
|
3436
|
+
resolveReferenceAndCiStats({
|
|
3437
|
+
repoRoot,
|
|
3438
|
+
tmpDir,
|
|
3439
|
+
referenceDbUrl,
|
|
3440
|
+
ciDbUrl,
|
|
3441
|
+
referenceStrategy
|
|
3442
|
+
}),
|
|
3443
|
+
collectEnvironmentStats(queryProduction ? productionUrl ?? null : null, "Production")
|
|
2854
3444
|
]);
|
|
2855
|
-
|
|
2856
|
-
|
|
3445
|
+
let local;
|
|
3446
|
+
let ci;
|
|
3447
|
+
if (referenceResult.status === "fulfilled") {
|
|
3448
|
+
local = referenceResult.value[0];
|
|
3449
|
+
ci = referenceResult.value[1];
|
|
3450
|
+
} else {
|
|
3451
|
+
console.warn(
|
|
3452
|
+
`[schema-stats] Failed to collect reference/CI schema statistics: ${referenceResult.reason}`
|
|
3453
|
+
);
|
|
3454
|
+
const reason = String(referenceResult.reason);
|
|
3455
|
+
local = unavailableStats(reason);
|
|
3456
|
+
ci = unavailableStats(reason);
|
|
3457
|
+
}
|
|
2857
3458
|
const production = resolveSettledProductionStats(productionResult);
|
|
2858
3459
|
return {
|
|
2859
3460
|
schemaStats: {
|
|
@@ -3185,24 +3786,27 @@ function extractHazardsWithContext(fullOutput, repoRoot) {
|
|
|
3185
3786
|
appendEmojiHazards(fullOutput, idempotentRoles, hazards);
|
|
3186
3787
|
return hazards;
|
|
3187
3788
|
}
|
|
3188
|
-
function
|
|
3789
|
+
function detectSchemaChangeState(fullOutput) {
|
|
3189
3790
|
const lowerOutput = fullOutput.toLowerCase();
|
|
3791
|
+
const changesIndicators = [
|
|
3792
|
+
"check mode: schema changes detected (not applied)",
|
|
3793
|
+
"schema changes: detected"
|
|
3794
|
+
];
|
|
3795
|
+
if (changesIndicators.some((indicator) => lowerOutput.includes(indicator))) {
|
|
3796
|
+
return "changes";
|
|
3797
|
+
}
|
|
3190
3798
|
const noChangesIndicators = [
|
|
3799
|
+
"check mode: all changes are for idempotent-managed objects (nothing to apply)",
|
|
3800
|
+
"schema changes: none",
|
|
3191
3801
|
"no schema changes detected",
|
|
3192
|
-
// From actors.ts line 969
|
|
3193
|
-
"no schema changes",
|
|
3194
|
-
// Shorter variant
|
|
3195
3802
|
"already in sync",
|
|
3196
|
-
"nothing to apply",
|
|
3197
3803
|
"schemas are up to date",
|
|
3198
|
-
"
|
|
3199
|
-
// From db-apply.ts line 150
|
|
3200
|
-
"no changes",
|
|
3201
|
-
// From actors.ts line 578, 968
|
|
3202
|
-
"in sync"
|
|
3203
|
-
// From db-sync.ts line 80
|
|
3804
|
+
"nothing to apply"
|
|
3204
3805
|
];
|
|
3205
|
-
|
|
3806
|
+
if (noChangesIndicators.some((indicator) => lowerOutput.includes(indicator))) {
|
|
3807
|
+
return "no-changes";
|
|
3808
|
+
}
|
|
3809
|
+
return "unknown";
|
|
3206
3810
|
}
|
|
3207
3811
|
function isCommandSuccess(exitCode, fullOutput) {
|
|
3208
3812
|
return exitCode === 0 || fullOutput.includes("check complete") || fullOutput.includes("Check Summary");
|
|
@@ -3258,6 +3862,39 @@ function buildErrorResult(error, databaseUrl) {
|
|
|
3258
3862
|
}
|
|
3259
3863
|
};
|
|
3260
3864
|
}
|
|
3865
|
+
function buildPreviewFromOutput(fullOutput, repoRoot, productionUrl) {
|
|
3866
|
+
const lines = fullOutput.split("\n");
|
|
3867
|
+
const planSql = extractSqlFromLines(lines) || extractSqlFromSchemaChanges(fullOutput);
|
|
3868
|
+
const hazardDetails = extractHazardsWithContext(fullOutput, repoRoot);
|
|
3869
|
+
const hazards = hazardDetails.map((h) => `${h.type}: ${h.message}`);
|
|
3870
|
+
const schemaChangeState = detectSchemaChangeState(fullOutput);
|
|
3871
|
+
const hasSqlPlan = planSql !== null && planSql.trim().length > 0;
|
|
3872
|
+
const hasHazards = hazards.length > 0;
|
|
3873
|
+
const hasChanges = schemaChangeState === "changes" ? true : schemaChangeState === "no-changes" ? false : hasSqlPlan || hasHazards;
|
|
3874
|
+
const effectivePlanSql = hasChanges ? planSql?.substring(0, 5e3) || null : null;
|
|
3875
|
+
const rawError = extractErrorMessage(fullOutput);
|
|
3876
|
+
return {
|
|
3877
|
+
planSql: effectivePlanSql,
|
|
3878
|
+
hazards,
|
|
3879
|
+
hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
|
|
3880
|
+
hasChanges,
|
|
3881
|
+
error: enhanceConnectionError(rawError, productionUrl)
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
function buildSuccessPreviewResult(exitCode, fullOutput, repoRoot, productionUrl) {
|
|
3885
|
+
const isSuccess = isCommandSuccess(exitCode, fullOutput);
|
|
3886
|
+
const preview = buildPreviewFromOutput(fullOutput, repoRoot, productionUrl);
|
|
3887
|
+
return {
|
|
3888
|
+
preview: {
|
|
3889
|
+
executed: true,
|
|
3890
|
+
planSql: preview.planSql,
|
|
3891
|
+
hazards: preview.hazards,
|
|
3892
|
+
hazardDetails: preview.hazardDetails,
|
|
3893
|
+
hasChanges: preview.hasChanges,
|
|
3894
|
+
error: isSuccess ? null : preview.error
|
|
3895
|
+
}
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3261
3898
|
var productionPreviewActor = fromPromise(
|
|
3262
3899
|
async ({ input }) => {
|
|
3263
3900
|
const { repoRoot, tmpDir, shouldExecute } = input;
|
|
@@ -3282,28 +3919,7 @@ var productionPreviewActor = fromPromise(
|
|
|
3282
3919
|
const fullOutput = `${stdout}
|
|
3283
3920
|
${stderr}`;
|
|
3284
3921
|
await writeLogFile(logFile, fullOutput);
|
|
3285
|
-
|
|
3286
|
-
const planSql = extractSqlFromLines(lines) || extractSqlFromSchemaChanges(fullOutput);
|
|
3287
|
-
const hazardDetails = extractHazardsWithContext(fullOutput, repoRoot);
|
|
3288
|
-
const hazards = hazardDetails.map((h) => `${h.type}: ${h.message}`);
|
|
3289
|
-
const hasNoChangesIndicator = detectNoChanges(fullOutput);
|
|
3290
|
-
const hasSqlPlan = planSql !== null && planSql.trim().length > 0;
|
|
3291
|
-
const hasHazards = hazards.length > 0;
|
|
3292
|
-
const hasChanges = hasNoChangesIndicator ? false : hasSqlPlan || hasHazards;
|
|
3293
|
-
const effectivePlanSql = hasChanges ? planSql?.substring(0, 5e3) || null : null;
|
|
3294
|
-
const isSuccess = isCommandSuccess(result.exitCode, fullOutput);
|
|
3295
|
-
const rawError = !isSuccess ? extractErrorMessage(fullOutput) : null;
|
|
3296
|
-
const errorMessage = rawError ? enhanceConnectionError(rawError, productionUrl) : null;
|
|
3297
|
-
return {
|
|
3298
|
-
preview: {
|
|
3299
|
-
executed: true,
|
|
3300
|
-
planSql: effectivePlanSql,
|
|
3301
|
-
hazards,
|
|
3302
|
-
hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
|
|
3303
|
-
hasChanges,
|
|
3304
|
-
error: errorMessage
|
|
3305
|
-
}
|
|
3306
|
-
};
|
|
3922
|
+
return buildSuccessPreviewResult(result.exitCode, fullOutput, repoRoot, productionUrl);
|
|
3307
3923
|
} catch (error) {
|
|
3308
3924
|
return buildErrorResult(error, productionUrl);
|
|
3309
3925
|
}
|
|
@@ -3611,11 +4227,30 @@ function parseSchemaChangeStats(sqlInput, logPath, repoRoot) {
|
|
|
3611
4227
|
stats.hazards = parseHazards(sql, idempotentRoles);
|
|
3612
4228
|
return stats;
|
|
3613
4229
|
}
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
4230
|
+
function isCheckSummaryOutput(output) {
|
|
4231
|
+
return output.includes("check complete") || output.includes("Check Summary");
|
|
4232
|
+
}
|
|
4233
|
+
function isSchemaOutOfSyncOutput(output) {
|
|
4234
|
+
const lowerOutput = output.toLowerCase();
|
|
4235
|
+
return lowerOutput.includes("database schema is out of sync") || lowerOutput.includes("db_schema_out_of_sync");
|
|
4236
|
+
}
|
|
4237
|
+
function countResidualStatements(stats) {
|
|
4238
|
+
return stats.creates.tables + stats.creates.indexes + stats.creates.policies + stats.creates.functions + stats.creates.other + stats.alters + stats.drops;
|
|
4239
|
+
}
|
|
4240
|
+
function analyzePostCheckResult(params) {
|
|
4241
|
+
const hasSchemaOutOfSync = isSchemaOutOfSyncOutput(params.output);
|
|
4242
|
+
const hasDrift = hasSchemaOutOfSync || countResidualStatements(params.residualStats) > 0;
|
|
4243
|
+
const checkSucceeded = params.exitCode === 0 || isCheckSummaryOutput(params.output) || hasSchemaOutOfSync;
|
|
4244
|
+
return {
|
|
4245
|
+
hasDrift: hasDrift || params.residualStats.hazards.length > 0,
|
|
4246
|
+
commandFailed: !checkSucceeded
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
var syncSchemaActor = fromPromise(
|
|
4250
|
+
async ({ input }) => {
|
|
4251
|
+
const { repoRoot, tmpDir, databaseUrl, mode, skipCodegen } = input;
|
|
4252
|
+
const envArg = mode === "ci-local" ? "local" : "preview";
|
|
4253
|
+
const useDbApply = mode !== "ci-local";
|
|
3619
4254
|
const gitDiff = getSchemaGitDiff(repoRoot);
|
|
3620
4255
|
try {
|
|
3621
4256
|
const databaseUrlForRuntime = normalizeDatabaseUrlForDdl(databaseUrl);
|
|
@@ -3649,7 +4284,18 @@ var syncSchemaActor = fromPromise(
|
|
|
3649
4284
|
// Always verbose for full traceability
|
|
3650
4285
|
...skipCodegen ? ["--skip-codegen"] : []
|
|
3651
4286
|
];
|
|
4287
|
+
const checkArgs = useDbApply ? ["exec", "runa", "db", "apply", envArg, "--check", "--no-seed", "--verbose"] : [
|
|
4288
|
+
"exec",
|
|
4289
|
+
"runa",
|
|
4290
|
+
"db",
|
|
4291
|
+
"sync",
|
|
4292
|
+
envArg,
|
|
4293
|
+
"--check",
|
|
4294
|
+
"--verbose",
|
|
4295
|
+
...skipCodegen ? ["--skip-codegen"] : []
|
|
4296
|
+
];
|
|
3652
4297
|
const logFile = path4.join(tmpDir, `ci-db-${useDbApply ? "apply" : "sync"}-${envArg}.log`);
|
|
4298
|
+
const afterCheckLogFile = path4.join(tmpDir, `ci-db-check-${envArg}.log`);
|
|
3653
4299
|
await runLogged({
|
|
3654
4300
|
cwd: repoRoot,
|
|
3655
4301
|
env: baseEnv,
|
|
@@ -3658,23 +4304,54 @@ var syncSchemaActor = fromPromise(
|
|
|
3658
4304
|
args: syncArgs,
|
|
3659
4305
|
logFile
|
|
3660
4306
|
});
|
|
4307
|
+
const postCheckResult = await runLogged({
|
|
4308
|
+
cwd: repoRoot,
|
|
4309
|
+
env: baseEnv,
|
|
4310
|
+
label: `db ${useDbApply ? "apply" : "sync"} check (${envArg})`,
|
|
4311
|
+
command: "pnpm",
|
|
4312
|
+
args: checkArgs,
|
|
4313
|
+
logFile: afterCheckLogFile,
|
|
4314
|
+
reject: false
|
|
4315
|
+
});
|
|
3661
4316
|
let logContent = "";
|
|
4317
|
+
let afterCheckLogContent = "";
|
|
3662
4318
|
try {
|
|
3663
4319
|
logContent = readFileSync(logFile, "utf-8");
|
|
3664
4320
|
} catch {
|
|
3665
4321
|
}
|
|
4322
|
+
try {
|
|
4323
|
+
afterCheckLogContent = readFileSync(afterCheckLogFile, "utf-8");
|
|
4324
|
+
} catch {
|
|
4325
|
+
}
|
|
3666
4326
|
const changeStats = parseSchemaChangeStats(logContent, null, repoRoot);
|
|
4327
|
+
const residualStats = parseSchemaChangeStats(afterCheckLogContent, null, repoRoot);
|
|
4328
|
+
const fullPostCheckOutput = `${postCheckResult.stdout || ""}
|
|
4329
|
+
${postCheckResult.stderr || ""}`;
|
|
4330
|
+
const postCheckAnalysis = analyzePostCheckResult({
|
|
4331
|
+
exitCode: postCheckResult.exitCode ?? 1,
|
|
4332
|
+
output: fullPostCheckOutput,
|
|
4333
|
+
residualStats
|
|
4334
|
+
});
|
|
4335
|
+
if (postCheckAnalysis.commandFailed) {
|
|
4336
|
+
throw new Error(
|
|
4337
|
+
`Schema post-check failed (${postCheckResult.exitCode}): ${fullPostCheckOutput.substring(
|
|
4338
|
+
0,
|
|
4339
|
+
1e3
|
|
4340
|
+
)}`
|
|
4341
|
+
);
|
|
4342
|
+
}
|
|
3667
4343
|
const schemaDrift = {
|
|
3668
4344
|
beforeSql: logContent.substring(0, 5e3),
|
|
3669
4345
|
// Truncate for comment
|
|
3670
|
-
afterSql:
|
|
3671
|
-
|
|
4346
|
+
afterSql: postCheckAnalysis.hasDrift ? afterCheckLogContent.substring(0, 5e3) : "",
|
|
4347
|
+
checkExecuted: true,
|
|
4348
|
+
hasDrift: postCheckAnalysis.hasDrift,
|
|
3672
4349
|
changeStats,
|
|
3673
4350
|
gitDiff,
|
|
3674
4351
|
logs: {
|
|
3675
4352
|
beforeCheckLogPath: logFile,
|
|
3676
4353
|
applyLogPath: logFile,
|
|
3677
|
-
afterCheckLogPath:
|
|
4354
|
+
afterCheckLogPath: afterCheckLogFile
|
|
3678
4355
|
}
|
|
3679
4356
|
};
|
|
3680
4357
|
return { applied: true, schemaDrift };
|
|
@@ -3682,6 +4359,7 @@ var syncSchemaActor = fromPromise(
|
|
|
3682
4359
|
const schemaDrift = {
|
|
3683
4360
|
beforeSql: null,
|
|
3684
4361
|
afterSql: null,
|
|
4362
|
+
checkExecuted: false,
|
|
3685
4363
|
hasDrift: false,
|
|
3686
4364
|
changeStats: null,
|
|
3687
4365
|
gitDiff,
|
|
@@ -4345,6 +5023,213 @@ function safeReadFile(filePath) {
|
|
|
4345
5023
|
return null;
|
|
4346
5024
|
}
|
|
4347
5025
|
}
|
|
5026
|
+
function buildSanitizeState() {
|
|
5027
|
+
return {
|
|
5028
|
+
inSingle: false,
|
|
5029
|
+
inDouble: false,
|
|
5030
|
+
inTemplate: false,
|
|
5031
|
+
inLineComment: false,
|
|
5032
|
+
inBlockComment: false
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
function applySanitizeStep(state, stateUpdate) {
|
|
5036
|
+
return { ...state, ...stateUpdate };
|
|
5037
|
+
}
|
|
5038
|
+
function processLineCommentState(char) {
|
|
5039
|
+
return {
|
|
5040
|
+
output: char === "\n" ? "\n" : " ",
|
|
5041
|
+
nextIndex: 1,
|
|
5042
|
+
state: {
|
|
5043
|
+
inLineComment: char !== "\n"
|
|
5044
|
+
}
|
|
5045
|
+
};
|
|
5046
|
+
}
|
|
5047
|
+
function processBlockCommentState(char, next) {
|
|
5048
|
+
if (char === "*" && next === "/") {
|
|
5049
|
+
return {
|
|
5050
|
+
output: " ",
|
|
5051
|
+
nextIndex: 2,
|
|
5052
|
+
state: { inBlockComment: false }
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
return {
|
|
5056
|
+
output: char === "\n" ? "\n" : " ",
|
|
5057
|
+
nextIndex: 1,
|
|
5058
|
+
state: {}
|
|
5059
|
+
};
|
|
5060
|
+
}
|
|
5061
|
+
function processSingleQuoteState(content, i, stripStrings) {
|
|
5062
|
+
const char = content[i] ?? "";
|
|
5063
|
+
if (char === "\\") {
|
|
5064
|
+
return {
|
|
5065
|
+
output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
|
|
5066
|
+
nextIndex: 2,
|
|
5067
|
+
state: {}
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
if (char === "'") {
|
|
5071
|
+
return {
|
|
5072
|
+
output: char,
|
|
5073
|
+
nextIndex: 1,
|
|
5074
|
+
state: { inSingle: false }
|
|
5075
|
+
};
|
|
5076
|
+
}
|
|
5077
|
+
return {
|
|
5078
|
+
output: stripStrings ? " " : char,
|
|
5079
|
+
nextIndex: 1,
|
|
5080
|
+
state: {}
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
function processDoubleQuoteState(content, i, stripStrings) {
|
|
5084
|
+
const char = content[i] ?? "";
|
|
5085
|
+
if (char === "\\") {
|
|
5086
|
+
return {
|
|
5087
|
+
output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
|
|
5088
|
+
nextIndex: 2,
|
|
5089
|
+
state: {}
|
|
5090
|
+
};
|
|
5091
|
+
}
|
|
5092
|
+
if (char === '"') {
|
|
5093
|
+
return {
|
|
5094
|
+
output: char,
|
|
5095
|
+
nextIndex: 1,
|
|
5096
|
+
state: { inDouble: false }
|
|
5097
|
+
};
|
|
5098
|
+
}
|
|
5099
|
+
return {
|
|
5100
|
+
output: stripStrings ? " " : char,
|
|
5101
|
+
nextIndex: 1,
|
|
5102
|
+
state: {}
|
|
5103
|
+
};
|
|
5104
|
+
}
|
|
5105
|
+
function processTemplateQuoteState(content, i, stripStrings) {
|
|
5106
|
+
const char = content[i] ?? "";
|
|
5107
|
+
if (char === "\\") {
|
|
5108
|
+
return {
|
|
5109
|
+
output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
|
|
5110
|
+
nextIndex: 2,
|
|
5111
|
+
state: {}
|
|
5112
|
+
};
|
|
5113
|
+
}
|
|
5114
|
+
if (char === "`") {
|
|
5115
|
+
return {
|
|
5116
|
+
output: "`",
|
|
5117
|
+
nextIndex: 1,
|
|
5118
|
+
state: { inTemplate: false }
|
|
5119
|
+
};
|
|
5120
|
+
}
|
|
5121
|
+
return {
|
|
5122
|
+
output: stripStrings ? char === "\n" ? "\n" : " " : char,
|
|
5123
|
+
nextIndex: 1,
|
|
5124
|
+
state: {}
|
|
5125
|
+
};
|
|
5126
|
+
}
|
|
5127
|
+
function processQuoteState(content, i, stripStrings, quote) {
|
|
5128
|
+
if (quote === "'") return processSingleQuoteState(content, i, stripStrings);
|
|
5129
|
+
if (quote === '"') return processDoubleQuoteState(content, i, stripStrings);
|
|
5130
|
+
return processTemplateQuoteState(content, i, stripStrings);
|
|
5131
|
+
}
|
|
5132
|
+
function processDefaultState(char, next) {
|
|
5133
|
+
if (char === "/" && next === "/") {
|
|
5134
|
+
return {
|
|
5135
|
+
output: " ",
|
|
5136
|
+
nextIndex: 2,
|
|
5137
|
+
state: { inLineComment: true }
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
if (char === "/" && next === "*") {
|
|
5141
|
+
return {
|
|
5142
|
+
output: " ",
|
|
5143
|
+
nextIndex: 2,
|
|
5144
|
+
state: { inBlockComment: true }
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
if (char === "'") {
|
|
5148
|
+
return {
|
|
5149
|
+
output: char,
|
|
5150
|
+
nextIndex: 1,
|
|
5151
|
+
state: { inSingle: true }
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
if (char === '"') {
|
|
5155
|
+
return {
|
|
5156
|
+
output: char,
|
|
5157
|
+
nextIndex: 1,
|
|
5158
|
+
state: { inDouble: true }
|
|
5159
|
+
};
|
|
5160
|
+
}
|
|
5161
|
+
if (char === "`") {
|
|
5162
|
+
return {
|
|
5163
|
+
output: char,
|
|
5164
|
+
nextIndex: 1,
|
|
5165
|
+
state: { inTemplate: true }
|
|
5166
|
+
};
|
|
5167
|
+
}
|
|
5168
|
+
return { output: char, nextIndex: 1, state: {} };
|
|
5169
|
+
}
|
|
5170
|
+
function runSanitizeStep(step, state, result, currentIndex) {
|
|
5171
|
+
return {
|
|
5172
|
+
i: currentIndex + step.nextIndex,
|
|
5173
|
+
result: `${result}${step.output}`,
|
|
5174
|
+
state: applySanitizeStep(state, step.state)
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
function sanitizeTsContent(content, options = {}) {
|
|
5178
|
+
const { stripStrings = false } = options;
|
|
5179
|
+
let result = "";
|
|
5180
|
+
let i = 0;
|
|
5181
|
+
let state = buildSanitizeState();
|
|
5182
|
+
while (i < content.length) {
|
|
5183
|
+
const char = content[i] ?? "";
|
|
5184
|
+
const next = content[i + 1] ?? "";
|
|
5185
|
+
if (state.inLineComment) {
|
|
5186
|
+
const step2 = processLineCommentState(char);
|
|
5187
|
+
const nextState2 = runSanitizeStep(step2, state, result, i);
|
|
5188
|
+
i = nextState2.i;
|
|
5189
|
+
result = nextState2.result;
|
|
5190
|
+
state = nextState2.state;
|
|
5191
|
+
continue;
|
|
5192
|
+
}
|
|
5193
|
+
if (state.inBlockComment) {
|
|
5194
|
+
const step2 = processBlockCommentState(char, next);
|
|
5195
|
+
const nextState2 = runSanitizeStep(step2, state, result, i);
|
|
5196
|
+
i = nextState2.i;
|
|
5197
|
+
result = nextState2.result;
|
|
5198
|
+
state = nextState2.state;
|
|
5199
|
+
continue;
|
|
5200
|
+
}
|
|
5201
|
+
if (state.inSingle) {
|
|
5202
|
+
const step2 = processQuoteState(content, i, stripStrings, "'");
|
|
5203
|
+
const nextState2 = runSanitizeStep(step2, state, result, i);
|
|
5204
|
+
i = nextState2.i;
|
|
5205
|
+
result = nextState2.result;
|
|
5206
|
+
state = nextState2.state;
|
|
5207
|
+
continue;
|
|
5208
|
+
}
|
|
5209
|
+
if (state.inDouble) {
|
|
5210
|
+
const step2 = processQuoteState(content, i, stripStrings, '"');
|
|
5211
|
+
const nextState2 = runSanitizeStep(step2, state, result, i);
|
|
5212
|
+
i = nextState2.i;
|
|
5213
|
+
result = nextState2.result;
|
|
5214
|
+
state = nextState2.state;
|
|
5215
|
+
continue;
|
|
5216
|
+
}
|
|
5217
|
+
if (state.inTemplate) {
|
|
5218
|
+
const step2 = processQuoteState(content, i, stripStrings, "`");
|
|
5219
|
+
const nextState2 = runSanitizeStep(step2, state, result, i);
|
|
5220
|
+
i = nextState2.i;
|
|
5221
|
+
result = nextState2.result;
|
|
5222
|
+
state = nextState2.state;
|
|
5223
|
+
continue;
|
|
5224
|
+
}
|
|
5225
|
+
const step = processDefaultState(char, next);
|
|
5226
|
+
const nextState = runSanitizeStep(step, state, result, i);
|
|
5227
|
+
i = nextState.i;
|
|
5228
|
+
result = nextState.result;
|
|
5229
|
+
state = nextState.state;
|
|
5230
|
+
}
|
|
5231
|
+
return result;
|
|
5232
|
+
}
|
|
4348
5233
|
function readManifest(manifestPath) {
|
|
4349
5234
|
try {
|
|
4350
5235
|
if (!existsSync(manifestPath)) {
|
|
@@ -4389,11 +5274,12 @@ async function detectLayer1Content(repoRoot, manifest) {
|
|
|
4389
5274
|
const filePath = join(repoRoot, file);
|
|
4390
5275
|
const content = safeReadFile(filePath);
|
|
4391
5276
|
if (!content) continue;
|
|
5277
|
+
const codeOnly = sanitizeTsContent(content, { stripStrings: true });
|
|
4392
5278
|
const xstatePatterns = [
|
|
4393
5279
|
/(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*createMachine\s*\(/,
|
|
4394
5280
|
/(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*setup\s*\([^)]*\)\s*\.createMachine\s*\(/
|
|
4395
5281
|
];
|
|
4396
|
-
const hasXStateMachine = xstatePatterns.some((p) => p.test(
|
|
5282
|
+
const hasXStateMachine = xstatePatterns.some((p) => p.test(codeOnly));
|
|
4397
5283
|
if (hasXStateMachine) {
|
|
4398
5284
|
machineCount++;
|
|
4399
5285
|
}
|
|
@@ -4523,6 +5409,41 @@ async function detectLayer4Content(repoRoot, manifest) {
|
|
|
4523
5409
|
}
|
|
4524
5410
|
return createSkipStatus(base, "No user-facing pages found");
|
|
4525
5411
|
}
|
|
5412
|
+
var detectStaticLayer = async (_repoRoot, _manifest) => ({
|
|
5413
|
+
layer: 0,
|
|
5414
|
+
hasContent: true,
|
|
5415
|
+
contentType: "machines",
|
|
5416
|
+
// Not really, but L0 always runs
|
|
5417
|
+
count: 1,
|
|
5418
|
+
source: "filesystem"
|
|
5419
|
+
});
|
|
5420
|
+
var layerDetectors = {
|
|
5421
|
+
0: detectStaticLayer,
|
|
5422
|
+
1: detectLayer1Content,
|
|
5423
|
+
2: detectLayer2Content,
|
|
5424
|
+
3: detectLayer3Content,
|
|
5425
|
+
4: detectLayer4Content
|
|
5426
|
+
};
|
|
5427
|
+
function getUnknownLayerStatus(layer) {
|
|
5428
|
+
return {
|
|
5429
|
+
layer,
|
|
5430
|
+
hasContent: false,
|
|
5431
|
+
contentType: "machines",
|
|
5432
|
+
count: 0,
|
|
5433
|
+
source: "filesystem",
|
|
5434
|
+
skipReason: `Unknown layer ${layer}`
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5437
|
+
async function detectLayerStatus(layer, repoRoot, manifest) {
|
|
5438
|
+
const detector = layerDetectors[layer];
|
|
5439
|
+
if (!detector) {
|
|
5440
|
+
return getUnknownLayerStatus(layer);
|
|
5441
|
+
}
|
|
5442
|
+
if (layer === 0) {
|
|
5443
|
+
return detectStaticLayer();
|
|
5444
|
+
}
|
|
5445
|
+
return detector(repoRoot, manifest);
|
|
5446
|
+
}
|
|
4526
5447
|
async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
|
|
4527
5448
|
const effectiveManifestPath = join(repoRoot, ".runa/manifests/manifest.json");
|
|
4528
5449
|
const manifest = readManifest(effectiveManifestPath);
|
|
@@ -4530,40 +5451,7 @@ async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
|
|
|
4530
5451
|
const skippedLayers = [];
|
|
4531
5452
|
const runnableLayers = [];
|
|
4532
5453
|
for (const layer of selectedLayers) {
|
|
4533
|
-
|
|
4534
|
-
switch (layer) {
|
|
4535
|
-
case 0:
|
|
4536
|
-
status = {
|
|
4537
|
-
layer: 0,
|
|
4538
|
-
hasContent: true,
|
|
4539
|
-
contentType: "machines",
|
|
4540
|
-
// Not really, but L0 always runs
|
|
4541
|
-
count: 1,
|
|
4542
|
-
source: "filesystem"
|
|
4543
|
-
};
|
|
4544
|
-
break;
|
|
4545
|
-
case 1:
|
|
4546
|
-
status = await detectLayer1Content(repoRoot, manifest);
|
|
4547
|
-
break;
|
|
4548
|
-
case 2:
|
|
4549
|
-
status = await detectLayer2Content(repoRoot);
|
|
4550
|
-
break;
|
|
4551
|
-
case 3:
|
|
4552
|
-
status = await detectLayer3Content(repoRoot, manifest);
|
|
4553
|
-
break;
|
|
4554
|
-
case 4:
|
|
4555
|
-
status = await detectLayer4Content(repoRoot, manifest);
|
|
4556
|
-
break;
|
|
4557
|
-
default:
|
|
4558
|
-
status = {
|
|
4559
|
-
layer,
|
|
4560
|
-
hasContent: false,
|
|
4561
|
-
contentType: "machines",
|
|
4562
|
-
count: 0,
|
|
4563
|
-
source: "filesystem",
|
|
4564
|
-
skipReason: `Unknown layer ${layer}`
|
|
4565
|
-
};
|
|
4566
|
-
}
|
|
5454
|
+
const status = await detectLayerStatus(layer, repoRoot, manifest);
|
|
4567
5455
|
layers[layer] = status;
|
|
4568
5456
|
if (status.hasContent) {
|
|
4569
5457
|
runnableLayers.push(layer);
|
|
@@ -4983,6 +5871,9 @@ function getLayersForCorePhase(selectedLayers, mode) {
|
|
|
4983
5871
|
function hasE2ELayer(selectedLayers) {
|
|
4984
5872
|
return selectedLayers.includes(E2E_LAYER);
|
|
4985
5873
|
}
|
|
5874
|
+
function shouldReuseCiReferenceStats(context) {
|
|
5875
|
+
return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.hasDrift === false;
|
|
5876
|
+
}
|
|
4986
5877
|
function mergeLayerResults(coreResults, e2eResults) {
|
|
4987
5878
|
return { ...coreResults, ...e2eResults };
|
|
4988
5879
|
}
|
|
@@ -5079,7 +5970,7 @@ init_esm_shims();
|
|
|
5079
5970
|
var CI_STEPS = [
|
|
5080
5971
|
{ step: "setup", label: "\u74B0\u5883\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7", detail: "\u30EA\u30DD\u30B8\u30C8\u30EA\u691C\u51FA\u3001Supabase\u8D77\u52D5" },
|
|
5081
5972
|
{ step: "syncSchema", label: "\u30B9\u30AD\u30FC\u30DE\u540C\u671F", detail: "pg-schema-diff \u2192 CI DB" },
|
|
5082
|
-
{ step: "applySeeds", label: "\u30B7\u30FC\u30C9\u9069\u7528", detail: "ci.sql +
|
|
5973
|
+
{ step: "applySeeds", label: "\u30B7\u30FC\u30C9\u9069\u7528", detail: "ci.sql + prerequisite seeds" },
|
|
5083
5974
|
{ step: "staticChecks", label: "\u9759\u7684\u30C1\u30A7\u30C3\u30AF", detail: "\u578B\u30C1\u30A7\u30C3\u30AF \u2225 lint" },
|
|
5084
5975
|
{ step: "build", label: "\u30D3\u30EB\u30C9", detail: "build \u2225 playwright install" },
|
|
5085
5976
|
{ step: "runTests", label: "\u30C6\u30B9\u30C8\u5B9F\u884C", detail: "L0-L3 (\u30D6\u30ED\u30C3\u30AD\u30F3\u30B0) \u2192 L4 (E2E)" },
|
|
@@ -5243,43 +6134,187 @@ function formatSkippedLayers(layerSkipReasons, originalSelectedLayers) {
|
|
|
5243
6134
|
return lines;
|
|
5244
6135
|
}
|
|
5245
6136
|
|
|
5246
|
-
// src/commands/ci/machine/formatters/sections/schema-
|
|
6137
|
+
// src/commands/ci/machine/formatters/sections/production-schema-status.ts
|
|
5247
6138
|
init_esm_shims();
|
|
6139
|
+
var COUNT_FIELDS = [
|
|
6140
|
+
"tables",
|
|
6141
|
+
"functions",
|
|
6142
|
+
"policies",
|
|
6143
|
+
"indexes",
|
|
6144
|
+
"triggers"
|
|
6145
|
+
];
|
|
5248
6146
|
function emptyStats2() {
|
|
5249
6147
|
return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
5250
6148
|
}
|
|
5251
|
-
function
|
|
6149
|
+
function findExpectedCountDrift(schemaName, field, expectedDrift) {
|
|
6150
|
+
return expectedDrift.find(
|
|
6151
|
+
(entry) => entry.field === field && (entry.schemas === void 0 || entry.schemas.includes(schemaName))
|
|
6152
|
+
) ?? null;
|
|
6153
|
+
}
|
|
6154
|
+
function buildCountDiffs(schemaStats, expectedDrift) {
|
|
6155
|
+
if (!schemaStats?.local || !schemaStats.production) return [];
|
|
6156
|
+
if (schemaStats.local.available === false || schemaStats.production.available === false) {
|
|
6157
|
+
return [];
|
|
6158
|
+
}
|
|
6159
|
+
const schemaNames = /* @__PURE__ */ new Set([
|
|
6160
|
+
...Object.keys(schemaStats.local.schemas),
|
|
6161
|
+
...Object.keys(schemaStats.production.schemas)
|
|
6162
|
+
]);
|
|
6163
|
+
const diffs = [];
|
|
6164
|
+
for (const schemaName of Array.from(schemaNames).sort()) {
|
|
6165
|
+
const reference = schemaStats.local.schemas[schemaName] ?? emptyStats2();
|
|
6166
|
+
const production = schemaStats.production.schemas[schemaName] ?? emptyStats2();
|
|
6167
|
+
for (const field of COUNT_FIELDS) {
|
|
6168
|
+
if (reference[field] === production[field]) continue;
|
|
6169
|
+
const expected = findExpectedCountDrift(schemaName, field, expectedDrift);
|
|
6170
|
+
diffs.push({
|
|
6171
|
+
schema: schemaName,
|
|
6172
|
+
field,
|
|
6173
|
+
value: production[field],
|
|
6174
|
+
reference: reference[field],
|
|
6175
|
+
delta: production[field] - reference[field],
|
|
6176
|
+
expected: expected !== null,
|
|
6177
|
+
reason: expected?.reason
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
return diffs;
|
|
6182
|
+
}
|
|
6183
|
+
function areIndexDiffsCoveredByExpectedDrift(indexDiff, countDiffs) {
|
|
6184
|
+
if (!indexDiff || !hasIndexDiff(indexDiff)) return false;
|
|
6185
|
+
const touchedSchemas = /* @__PURE__ */ new Set([
|
|
6186
|
+
...indexDiff.missing.map((index) => index.schema),
|
|
6187
|
+
...indexDiff.extra.map((index) => index.schema)
|
|
6188
|
+
]);
|
|
6189
|
+
if (touchedSchemas.size === 0) return false;
|
|
6190
|
+
const expectedIndexSchemas = new Set(
|
|
6191
|
+
countDiffs.filter((diff) => diff.field === "indexes" && diff.expected).map((diff) => diff.schema)
|
|
6192
|
+
);
|
|
6193
|
+
const unexpectedIndexSchemas = new Set(
|
|
6194
|
+
countDiffs.filter((diff) => diff.field === "indexes" && !diff.expected).map((diff) => diff.schema)
|
|
6195
|
+
);
|
|
6196
|
+
for (const schema of touchedSchemas) {
|
|
6197
|
+
if (unexpectedIndexSchemas.has(schema)) return false;
|
|
6198
|
+
if (!expectedIndexSchemas.has(schema)) return false;
|
|
6199
|
+
}
|
|
6200
|
+
return true;
|
|
6201
|
+
}
|
|
6202
|
+
function buildIndexDiff(schemaStats) {
|
|
6203
|
+
if (!schemaStats?.local || !schemaStats.production) return null;
|
|
6204
|
+
if (schemaStats.local.available === false || schemaStats.production.available === false) {
|
|
6205
|
+
return null;
|
|
6206
|
+
}
|
|
6207
|
+
return compareIndexLists(schemaStats.local, schemaStats.production);
|
|
6208
|
+
}
|
|
6209
|
+
function buildSemanticDiff(schemaStats) {
|
|
6210
|
+
const referenceSnapshot = schemaStats?.local?.canonicalSnapshot;
|
|
6211
|
+
const productionSnapshot = schemaStats?.production?.canonicalSnapshot;
|
|
6212
|
+
if (!referenceSnapshot || !productionSnapshot) return null;
|
|
6213
|
+
if (referenceSnapshot.available !== true || productionSnapshot.available !== true) return null;
|
|
6214
|
+
return compareCanonicalSnapshots(referenceSnapshot, productionSnapshot);
|
|
6215
|
+
}
|
|
6216
|
+
function getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift = []) {
|
|
6217
|
+
const previewHasChanges = productionPreview?.hasChanges === true;
|
|
6218
|
+
const indexDiff = buildIndexDiff(schemaStats);
|
|
6219
|
+
const semanticDiff = buildSemanticDiff(schemaStats);
|
|
6220
|
+
const countDiffs = buildCountDiffs(schemaStats, expectedDrift);
|
|
6221
|
+
const hasIndexChanges = indexDiff ? hasIndexDiff(indexDiff) : false;
|
|
6222
|
+
const hasUnexpectedIndexChanges = hasIndexChanges && !areIndexDiffsCoveredByExpectedDrift(indexDiff, countDiffs);
|
|
6223
|
+
const hasSemanticChanges = semanticDiff ? hasCanonicalChanges(semanticDiff) : false;
|
|
6224
|
+
const hasCountChanges = countDiffs.length > 0;
|
|
6225
|
+
const hasUnexpectedCountChanges = countDiffs.some((diff) => !diff.expected);
|
|
6226
|
+
const knownDriftReasons = Array.from(
|
|
6227
|
+
new Set(countDiffs.filter((diff) => diff.expected).flatMap((diff) => diff.reason ?? []))
|
|
6228
|
+
);
|
|
6229
|
+
const hasKnownDriftOnly = hasCountChanges && !previewHasChanges && !hasUnexpectedCountChanges && !hasUnexpectedIndexChanges && !hasSemanticChanges;
|
|
6230
|
+
const requiresDeploy = previewHasChanges || hasUnexpectedIndexChanges || hasSemanticChanges || hasUnexpectedCountChanges;
|
|
6231
|
+
return {
|
|
6232
|
+
previewHasChanges,
|
|
6233
|
+
indexDiff,
|
|
6234
|
+
semanticDiff,
|
|
6235
|
+
countDiffs,
|
|
6236
|
+
hasIndexChanges,
|
|
6237
|
+
hasUnexpectedIndexChanges,
|
|
6238
|
+
hasSemanticChanges,
|
|
6239
|
+
hasCountChanges,
|
|
6240
|
+
hasUnexpectedCountChanges,
|
|
6241
|
+
hasKnownDriftOnly,
|
|
6242
|
+
knownDriftReasons,
|
|
6243
|
+
requiresDeploy,
|
|
6244
|
+
previewMissedChanges: !previewHasChanges && (hasUnexpectedIndexChanges || hasSemanticChanges || hasUnexpectedCountChanges),
|
|
6245
|
+
previewOnlyChanges: previewHasChanges && !hasUnexpectedIndexChanges && !hasSemanticChanges && !hasUnexpectedCountChanges
|
|
6246
|
+
};
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
// src/commands/ci/machine/formatters/sections/schema-matrix.ts
|
|
6250
|
+
init_esm_shims();
|
|
6251
|
+
function emptyStats3() {
|
|
6252
|
+
return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
|
|
6253
|
+
}
|
|
6254
|
+
function isStatsAvailable(stats) {
|
|
6255
|
+
return stats != null && stats.available !== false;
|
|
6256
|
+
}
|
|
6257
|
+
function isSemanticSnapshotAvailable(stats) {
|
|
6258
|
+
return stats != null && stats.available !== false && stats.canonicalSnapshot?.available === true;
|
|
6259
|
+
}
|
|
6260
|
+
function formatUnavailableCell(stats) {
|
|
6261
|
+
if (stats == null) return "n/a";
|
|
6262
|
+
return stats.error ? `\u26AA unavailable` : "n/a";
|
|
6263
|
+
}
|
|
6264
|
+
function formatReferenceCell(stats, isAvailable) {
|
|
6265
|
+
if (!isAvailable) return "\u26AA unavailable";
|
|
5252
6266
|
return formatSchemaStatsCompact(stats, true);
|
|
5253
6267
|
}
|
|
5254
|
-
function formatCiCellWithDiff(stats, reference) {
|
|
5255
|
-
if (
|
|
6268
|
+
function formatCiCellWithDiff(stats, reference, semanticSummary, isAvailable, referenceAvailable) {
|
|
6269
|
+
if (!isAvailable) return "\u26AA unavailable";
|
|
6270
|
+
if (!referenceAvailable) return formatSchemaStatsCompact(stats, true);
|
|
6271
|
+
if (hasDisplayedStatsDiff(stats, reference) || semanticSummary?.hasChanges) {
|
|
5256
6272
|
return `\u26A0\uFE0F ${formatSchemaStatsWithDiff(stats, reference)}`;
|
|
5257
6273
|
}
|
|
5258
6274
|
return formatSchemaStatsCompact(stats, true);
|
|
5259
6275
|
}
|
|
5260
|
-
function formatProdCellWithDiff(stats, reference, schemaName, expectedDrift) {
|
|
5261
|
-
if (!
|
|
6276
|
+
function formatProdCellWithDiff(stats, reference, schemaName, expectedDrift, semanticSummary, isAvailable, referenceAvailable) {
|
|
6277
|
+
if (!isAvailable) return "\u26AA unavailable";
|
|
6278
|
+
if (!referenceAvailable) return formatSchemaStatsCompact(stats, true);
|
|
6279
|
+
if (!hasDisplayedStatsDiff(stats, reference) && !semanticSummary?.hasChanges) {
|
|
5262
6280
|
return formatSchemaStatsCompact(stats, true);
|
|
5263
6281
|
}
|
|
5264
6282
|
const fieldDiffs = getFieldDiffs(stats, reference);
|
|
5265
|
-
const allExpected = expectedDrift.length > 0 && isAllDriftExpected(schemaName, fieldDiffs, expectedDrift);
|
|
6283
|
+
const allExpected = !semanticSummary?.hasChanges && expectedDrift.length > 0 && isAllDriftExpected(schemaName, fieldDiffs, expectedDrift);
|
|
5266
6284
|
const marker = allExpected ? "\u{1F4CB}" : "\u{1F4DD}";
|
|
5267
6285
|
return `${marker} ${formatSchemaStatsWithDiff(stats, reference)}`;
|
|
5268
6286
|
}
|
|
5269
|
-
function formatCiTotalCellWithDiff(stats, reference) {
|
|
6287
|
+
function formatCiTotalCellWithDiff(stats, reference, hasSemanticChanges, isAvailable, referenceAvailable) {
|
|
6288
|
+
if (!isAvailable) return "**\u26AA unavailable**";
|
|
6289
|
+
if (!referenceAvailable) return `**${formatSchemaStatsCompact(stats, true)}**`;
|
|
5270
6290
|
const formatted = formatSchemaStatsCompact(stats, true);
|
|
5271
|
-
if (hasDisplayedStatsDiff(stats, reference)) {
|
|
6291
|
+
if (hasDisplayedStatsDiff(stats, reference) || hasSemanticChanges) {
|
|
5272
6292
|
return `\u26A0\uFE0F **${formatted}**`;
|
|
5273
6293
|
}
|
|
5274
6294
|
return `**${formatted}**`;
|
|
5275
6295
|
}
|
|
5276
|
-
function formatProdTotalCellWithDiff(stats, reference) {
|
|
6296
|
+
function formatProdTotalCellWithDiff(stats, reference, hasSemanticChanges, isAvailable, referenceAvailable) {
|
|
6297
|
+
if (!isAvailable) return "**\u26AA unavailable**";
|
|
6298
|
+
if (!referenceAvailable) return `**${formatSchemaStatsCompact(stats, true)}**`;
|
|
5277
6299
|
const formatted = formatSchemaStatsCompact(stats, true);
|
|
5278
|
-
if (hasDisplayedStatsDiff(stats, reference)) {
|
|
6300
|
+
if (hasDisplayedStatsDiff(stats, reference) || hasSemanticChanges) {
|
|
5279
6301
|
return `\u{1F4DD} **${formatted}**`;
|
|
5280
6302
|
}
|
|
5281
6303
|
return `**${formatted}**`;
|
|
5282
6304
|
}
|
|
6305
|
+
function buildCanonicalDiffs(schemaStats) {
|
|
6306
|
+
const referenceSnapshot = schemaStats.local.canonicalSnapshot;
|
|
6307
|
+
const ciSnapshot = schemaStats.ci.canonicalSnapshot;
|
|
6308
|
+
const productionSnapshot = schemaStats.production?.canonicalSnapshot;
|
|
6309
|
+
const ciDiff = isSemanticSnapshotAvailable(schemaStats.local) && isSemanticSnapshotAvailable(schemaStats.ci) ? compareCanonicalSnapshots(referenceSnapshot, ciSnapshot) : null;
|
|
6310
|
+
const productionDiff = isSemanticSnapshotAvailable(schemaStats.local) && isSemanticSnapshotAvailable(schemaStats.production) ? compareCanonicalSnapshots(referenceSnapshot, productionSnapshot) : null;
|
|
6311
|
+
return {
|
|
6312
|
+
ci: ciDiff,
|
|
6313
|
+
production: productionDiff,
|
|
6314
|
+
ciBySchema: ciDiff ? summarizeCanonicalDiffBySchema(ciDiff) : {},
|
|
6315
|
+
productionBySchema: productionDiff ? summarizeCanonicalDiffBySchema(productionDiff) : {}
|
|
6316
|
+
};
|
|
6317
|
+
}
|
|
5283
6318
|
function getSortedActiveSchemas(schemaStats) {
|
|
5284
6319
|
const allSchemas = /* @__PURE__ */ new Set([
|
|
5285
6320
|
...getActiveSchemas(schemaStats.local),
|
|
@@ -5292,37 +6327,67 @@ function getSortedActiveSchemas(schemaStats) {
|
|
|
5292
6327
|
return a.localeCompare(b);
|
|
5293
6328
|
});
|
|
5294
6329
|
}
|
|
5295
|
-
function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift) {
|
|
6330
|
+
function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift, ciDiffBySchema, productionDiffBySchema) {
|
|
5296
6331
|
const lines = [];
|
|
5297
6332
|
for (const schemaName of sortedSchemas) {
|
|
5298
|
-
const localStats = schemaStats.local.schemas[schemaName] ??
|
|
5299
|
-
const ciStats = schemaStats.ci.schemas[schemaName] ??
|
|
6333
|
+
const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats3();
|
|
6334
|
+
const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats3();
|
|
5300
6335
|
const prodStats = schemaStats.production?.schemas[schemaName] ?? null;
|
|
5301
|
-
const localCell =
|
|
5302
|
-
const ciCell = formatCiCellWithDiff(
|
|
5303
|
-
|
|
6336
|
+
const localCell = formatReferenceCell(localStats, isStatsAvailable(schemaStats.local));
|
|
6337
|
+
const ciCell = formatCiCellWithDiff(
|
|
6338
|
+
ciStats,
|
|
6339
|
+
localStats,
|
|
6340
|
+
ciDiffBySchema[schemaName],
|
|
6341
|
+
isStatsAvailable(schemaStats.ci),
|
|
6342
|
+
isStatsAvailable(schemaStats.local)
|
|
6343
|
+
);
|
|
6344
|
+
const prodCell = prodStats !== null ? formatProdCellWithDiff(
|
|
6345
|
+
prodStats,
|
|
6346
|
+
localStats,
|
|
6347
|
+
schemaName,
|
|
6348
|
+
expectedDrift,
|
|
6349
|
+
productionDiffBySchema[schemaName],
|
|
6350
|
+
isStatsAvailable(schemaStats.production),
|
|
6351
|
+
isStatsAvailable(schemaStats.local)
|
|
6352
|
+
) : formatUnavailableCell(schemaStats.production);
|
|
5304
6353
|
lines.push(
|
|
5305
6354
|
hasProduction ? `| ${schemaName} | ${localCell} | ${ciCell} | ${prodCell ?? "n/a"} |` : `| ${schemaName} | ${localCell} | ${ciCell} |`
|
|
5306
6355
|
);
|
|
5307
6356
|
}
|
|
5308
6357
|
return lines;
|
|
5309
6358
|
}
|
|
5310
|
-
function generateSchemaMatrixSummary(localTotal, ciTotal, prodTotal) {
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
6359
|
+
function generateSchemaMatrixSummary(localTotal, ciTotal, prodTotal, hasCiSemanticDiff, hasProdSemanticDiff, localAvailable, ciAvailable, prodAvailable) {
|
|
6360
|
+
if (!localAvailable) {
|
|
6361
|
+
const parts2 = ["\u{1F4CA} **Reference:** unavailable"];
|
|
6362
|
+
if (ciAvailable) parts2.push(`| **CI:** ${formatTotalStatsCompact(ciTotal)}`);
|
|
6363
|
+
else parts2.push("| **CI:** unavailable");
|
|
6364
|
+
if (prodTotal !== null) {
|
|
6365
|
+
parts2.push(
|
|
6366
|
+
prodAvailable ? `| **Prod:** ${formatTotalStatsCompact(prodTotal)}` : "| **Prod:** unavailable"
|
|
6367
|
+
);
|
|
6368
|
+
}
|
|
6369
|
+
return parts2.join(" ");
|
|
6370
|
+
}
|
|
6371
|
+
const ciSynced = ciAvailable && !hasDisplayedStatsDiff(localTotal, ciTotal) && !hasCiSemanticDiff;
|
|
6372
|
+
const prodSynced = prodTotal === null || prodAvailable && !hasDisplayedStatsDiff(localTotal, prodTotal) && !hasProdSemanticDiff;
|
|
6373
|
+
if (ciSynced && prodSynced && ciAvailable && (prodTotal === null || prodAvailable)) {
|
|
6374
|
+
return `\u{1F4CA} **Reference:** ${formatTotalStatsCompact(localTotal)}`;
|
|
5315
6375
|
}
|
|
5316
|
-
const parts = [`\u{1F4CA} **
|
|
5317
|
-
if (!
|
|
6376
|
+
const parts = [`\u{1F4CA} **Reference:** ${formatTotalStatsCompact(localTotal)}`];
|
|
6377
|
+
if (!ciAvailable) parts.push("| **CI:** unavailable");
|
|
6378
|
+
else if (!ciSynced) parts.push(`| **CI:** ${formatTotalStatsCompact(ciTotal)}`);
|
|
5318
6379
|
if (prodTotal !== null && !prodSynced) {
|
|
5319
|
-
parts.push(
|
|
6380
|
+
parts.push(
|
|
6381
|
+
prodAvailable ? `| **Prod:** \u{1F4DD} ${formatTotalStatsCompact(prodTotal)}` : "| **Prod:** unavailable"
|
|
6382
|
+
);
|
|
5320
6383
|
}
|
|
5321
6384
|
return parts.join(" ");
|
|
5322
6385
|
}
|
|
5323
6386
|
function generateIndexDiffSection(schemaStats) {
|
|
5324
|
-
if (!schemaStats.production) return [];
|
|
5325
|
-
const
|
|
6387
|
+
if (!isStatsAvailable(schemaStats.local) || !isStatsAvailable(schemaStats.production)) return [];
|
|
6388
|
+
const productionStats = schemaStats.production;
|
|
6389
|
+
if (!productionStats) return [];
|
|
6390
|
+
const diff = compareIndexLists(schemaStats.local, productionStats);
|
|
5326
6391
|
if (!hasIndexDiff(diff)) return [];
|
|
5327
6392
|
const lines = [];
|
|
5328
6393
|
if (diff.missing.length > 0) {
|
|
@@ -5342,11 +6407,88 @@ function generateIndexDiffSection(schemaStats) {
|
|
|
5342
6407
|
}
|
|
5343
6408
|
return lines;
|
|
5344
6409
|
}
|
|
6410
|
+
function appendAvailabilityNote(lines, label, error, kind) {
|
|
6411
|
+
const scope = kind === "stats" ? "schema stats" : "semantic diff";
|
|
6412
|
+
lines.push(`> \u26AA **${label} ${scope} unavailable:** ${error ?? "unknown error"}`);
|
|
6413
|
+
}
|
|
6414
|
+
function generateAvailabilityNotes(schemaStats) {
|
|
6415
|
+
const lines = [];
|
|
6416
|
+
const appendNotesForEnv = (label, stats) => {
|
|
6417
|
+
if (stats == null) return;
|
|
6418
|
+
if (!isStatsAvailable(stats)) {
|
|
6419
|
+
appendAvailabilityNote(lines, label, stats.error, "stats");
|
|
6420
|
+
return;
|
|
6421
|
+
}
|
|
6422
|
+
if (stats.canonicalSnapshot?.available === false) {
|
|
6423
|
+
appendAvailabilityNote(lines, label, stats.canonicalSnapshot.error, "semantic");
|
|
6424
|
+
}
|
|
6425
|
+
};
|
|
6426
|
+
appendNotesForEnv("Reference", schemaStats.local);
|
|
6427
|
+
appendNotesForEnv("CI", schemaStats.ci);
|
|
6428
|
+
appendNotesForEnv("Prod", schemaStats.production);
|
|
6429
|
+
if (lines.length === 0) return [];
|
|
6430
|
+
return ["", ...lines, ">"];
|
|
6431
|
+
}
|
|
6432
|
+
function describeObject(object) {
|
|
6433
|
+
switch (object.kind) {
|
|
6434
|
+
case "index":
|
|
6435
|
+
return `index \`${object.label}\``;
|
|
6436
|
+
case "policy":
|
|
6437
|
+
return `policy \`${object.label}\``;
|
|
6438
|
+
case "trigger":
|
|
6439
|
+
return `trigger \`${object.label}\``;
|
|
6440
|
+
case "function":
|
|
6441
|
+
return `function \`${object.label}\``;
|
|
6442
|
+
case "table":
|
|
6443
|
+
return `table \`${object.label}\``;
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
function appendObjectLines(lines, prefix, objects, label) {
|
|
6447
|
+
if (objects.length === 0) return;
|
|
6448
|
+
lines.push(`> ${prefix} **${label}:**`);
|
|
6449
|
+
for (const object of objects.slice(0, 6)) {
|
|
6450
|
+
lines.push(`> - ${describeObject(object)}`);
|
|
6451
|
+
}
|
|
6452
|
+
if (objects.length > 6) {
|
|
6453
|
+
lines.push(`> - ... \u4ED6${objects.length - 6}\u4EF6`);
|
|
6454
|
+
}
|
|
6455
|
+
lines.push(">");
|
|
6456
|
+
}
|
|
6457
|
+
function appendChangedObjectLines(lines, prefix, pairs, label) {
|
|
6458
|
+
if (pairs.length === 0) return;
|
|
6459
|
+
lines.push(`> ${prefix} **${label}:**`);
|
|
6460
|
+
for (const pair of pairs.slice(0, 6)) {
|
|
6461
|
+
lines.push(`> - ${describeObject(pair.reference)}`);
|
|
6462
|
+
}
|
|
6463
|
+
if (pairs.length > 6) {
|
|
6464
|
+
lines.push(`> - ... \u4ED6${pairs.length - 6}\u4EF6`);
|
|
6465
|
+
}
|
|
6466
|
+
lines.push(">");
|
|
6467
|
+
}
|
|
6468
|
+
function generateSemanticDiffSection(title, prefix, diff) {
|
|
6469
|
+
if (!diff || !hasCanonicalChanges(diff)) return [];
|
|
6470
|
+
const lines = ["", `> ${prefix} **${title} semantic diff:**`, ">"];
|
|
6471
|
+
appendObjectLines(lines, prefix, diff.missing, "missing objects");
|
|
6472
|
+
appendObjectLines(lines, prefix, diff.extra, "extra objects");
|
|
6473
|
+
appendChangedObjectLines(lines, prefix, diff.changed, "changed objects");
|
|
6474
|
+
if (diff.renamed.length > 0) {
|
|
6475
|
+
lines.push(`> ${prefix} **renamed indexes:**`);
|
|
6476
|
+
for (const pair of diff.renamed.slice(0, 6)) {
|
|
6477
|
+
lines.push(`> - \`${pair.reference.label}\` -> \`${pair.target.label}\``);
|
|
6478
|
+
}
|
|
6479
|
+
if (diff.renamed.length > 6) {
|
|
6480
|
+
lines.push(`> - ... \u4ED6${diff.renamed.length - 6}\u4EF6`);
|
|
6481
|
+
}
|
|
6482
|
+
lines.push(">");
|
|
6483
|
+
}
|
|
6484
|
+
return lines;
|
|
6485
|
+
}
|
|
5345
6486
|
function generateExpectedDriftFootnotes(schemaStats, sortedSchemas, expectedDrift) {
|
|
5346
6487
|
if (expectedDrift.length === 0 || !schemaStats.production) return [];
|
|
6488
|
+
if (!isStatsAvailable(schemaStats.local) || !isStatsAvailable(schemaStats.production)) return [];
|
|
5347
6489
|
const allReasons = /* @__PURE__ */ new Set();
|
|
5348
6490
|
for (const schemaName of sortedSchemas) {
|
|
5349
|
-
const localStats = schemaStats.local.schemas[schemaName] ??
|
|
6491
|
+
const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats3();
|
|
5350
6492
|
const prodStats = schemaStats.production.schemas[schemaName];
|
|
5351
6493
|
if (!prodStats || !hasDisplayedStatsDiff(prodStats, localStats)) continue;
|
|
5352
6494
|
const fieldDiffs = getFieldDiffs(prodStats, localStats);
|
|
@@ -5370,33 +6512,69 @@ function formatSchemaMatrix(schemaStats, gitDiff, expectedDrift = []) {
|
|
|
5370
6512
|
const hasGitChanges = gitDiff && gitDiff.filesChanged.length > 0;
|
|
5371
6513
|
const header = hasGitChanges ? `### \u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1 (+${gitDiff.linesAdded}/-${gitDiff.linesDeleted} PR\u5185)` : "### \u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1";
|
|
5372
6514
|
const sortedSchemas = getSortedActiveSchemas(schemaStats);
|
|
6515
|
+
const availabilityNotes = generateAvailabilityNotes(schemaStats);
|
|
5373
6516
|
if (sortedSchemas.length === 0) {
|
|
5374
|
-
return [header, "", "_\u30B9\u30AD\u30FC\u30DE\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u3042\u308A\u307E\u305B\u3093_", ""];
|
|
6517
|
+
return [header, "", ...availabilityNotes, "_\u30B9\u30AD\u30FC\u30DE\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u3042\u308A\u307E\u305B\u3093_", ""];
|
|
5375
6518
|
}
|
|
5376
6519
|
const hasProduction = schemaStats.production !== null;
|
|
5377
|
-
const
|
|
5378
|
-
const
|
|
6520
|
+
const canonicalDiffs = buildCanonicalDiffs(schemaStats);
|
|
6521
|
+
const tableHeader = hasProduction ? ["| Schema | \u{1F4C4} Reference | \u{1F52C} CI | \u{1F3ED} Prod |", "|--------|:------------:|:-----:|:-------:|"] : ["| Schema | \u{1F4C4} Reference | \u{1F52C} CI |", "|--------|:------------:|:-----:|"];
|
|
6522
|
+
const rows = generateSchemaTableRows(
|
|
6523
|
+
schemaStats,
|
|
6524
|
+
sortedSchemas,
|
|
6525
|
+
hasProduction,
|
|
6526
|
+
expectedDrift,
|
|
6527
|
+
canonicalDiffs.ciBySchema,
|
|
6528
|
+
canonicalDiffs.productionBySchema
|
|
6529
|
+
);
|
|
5379
6530
|
const { local, ci, production } = schemaStats;
|
|
5380
|
-
const localTotalCell = `**${formatSchemaStatsCompact(local.total, true)}
|
|
5381
|
-
const ciTotalCell = formatCiTotalCellWithDiff(
|
|
5382
|
-
|
|
6531
|
+
const localTotalCell = isStatsAvailable(local) ? `**${formatSchemaStatsCompact(local.total, true)}**` : "**\u26AA unavailable**";
|
|
6532
|
+
const ciTotalCell = formatCiTotalCellWithDiff(
|
|
6533
|
+
ci.total,
|
|
6534
|
+
local.total,
|
|
6535
|
+
canonicalDiffs.ci ? hasCanonicalChanges(canonicalDiffs.ci) : false,
|
|
6536
|
+
isStatsAvailable(ci),
|
|
6537
|
+
isStatsAvailable(local)
|
|
6538
|
+
);
|
|
6539
|
+
const prodTotalCell = production ? formatProdTotalCellWithDiff(
|
|
6540
|
+
production.total,
|
|
6541
|
+
local.total,
|
|
6542
|
+
canonicalDiffs.production ? hasCanonicalChanges(canonicalDiffs.production) : false,
|
|
6543
|
+
isStatsAvailable(production),
|
|
6544
|
+
isStatsAvailable(local)
|
|
6545
|
+
) : null;
|
|
5383
6546
|
const totalRow = hasProduction ? `| **Total** | ${localTotalCell} | ${ciTotalCell} | ${prodTotalCell ?? "n/a"} |` : `| **Total** | ${localTotalCell} | ${ciTotalCell} |`;
|
|
5384
|
-
const summary = generateSchemaMatrixSummary(
|
|
6547
|
+
const summary = generateSchemaMatrixSummary(
|
|
6548
|
+
local.total,
|
|
6549
|
+
ci.total,
|
|
6550
|
+
production?.total ?? null,
|
|
6551
|
+
canonicalDiffs.ci ? hasCanonicalChanges(canonicalDiffs.ci) : false,
|
|
6552
|
+
canonicalDiffs.production ? hasCanonicalChanges(canonicalDiffs.production) : false,
|
|
6553
|
+
isStatsAvailable(local),
|
|
6554
|
+
isStatsAvailable(ci),
|
|
6555
|
+
isStatsAvailable(production)
|
|
6556
|
+
);
|
|
5385
6557
|
const indexDiffSection = generateIndexDiffSection(schemaStats);
|
|
5386
6558
|
const expectedDriftNotes = generateExpectedDriftFootnotes(
|
|
5387
6559
|
schemaStats,
|
|
5388
6560
|
sortedSchemas,
|
|
5389
6561
|
expectedDrift
|
|
5390
6562
|
);
|
|
6563
|
+
const semanticDiffSection = [
|
|
6564
|
+
...generateSemanticDiffSection("CI", "\u26A0\uFE0F", canonicalDiffs.ci),
|
|
6565
|
+
...generateSemanticDiffSection("Prod", "\u{1F4DD}", canonicalDiffs.production)
|
|
6566
|
+
];
|
|
5391
6567
|
return [
|
|
5392
6568
|
header,
|
|
5393
6569
|
"",
|
|
6570
|
+
...availabilityNotes,
|
|
5394
6571
|
...tableHeader,
|
|
5395
6572
|
...rows,
|
|
5396
6573
|
totalRow,
|
|
5397
6574
|
"",
|
|
5398
6575
|
summary,
|
|
5399
6576
|
...indexDiffSection,
|
|
6577
|
+
...semanticDiffSection,
|
|
5400
6578
|
...expectedDriftNotes,
|
|
5401
6579
|
"",
|
|
5402
6580
|
"<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>",
|
|
@@ -5406,8 +6584,8 @@ function formatSchemaMatrix(schemaStats, gitDiff, expectedDrift = []) {
|
|
|
5406
6584
|
|
|
5407
6585
|
// src/commands/ci/machine/formatters/sections/final-comment.ts
|
|
5408
6586
|
function formatDriftStatus(drift) {
|
|
5409
|
-
if (drift == null) {
|
|
5410
|
-
return "
|
|
6587
|
+
if (drift == null || !drift.checkExecuted) {
|
|
6588
|
+
return "_\u672A\u691C\u8A3C_";
|
|
5411
6589
|
}
|
|
5412
6590
|
return drift.hasDrift ? "**\u274C \u30C9\u30EA\u30D5\u30C8\u691C\u51FA (schemas \u2194 CI DB)**" : "**\u2705 \u30C9\u30EA\u30D5\u30C8\u306A\u3057 (schemas \u2194 CI DB)**";
|
|
5413
6591
|
}
|
|
@@ -5532,21 +6710,6 @@ function classifyPreviewError(error) {
|
|
|
5532
6710
|
}
|
|
5533
6711
|
return { label: "\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC\u5931\u6557", guidance: "" };
|
|
5534
6712
|
}
|
|
5535
|
-
function generateIndexDiffWarning(schemaStats) {
|
|
5536
|
-
if (!schemaStats?.local || !schemaStats?.production) return [];
|
|
5537
|
-
const indexDiff = compareIndexLists(schemaStats.local, schemaStats.production);
|
|
5538
|
-
if (!hasIndexDiff(indexDiff)) return [];
|
|
5539
|
-
const diffParts = [];
|
|
5540
|
-
if (indexDiff.missing.length > 0) diffParts.push(`${indexDiff.missing.length}\u4EF6\u4E0D\u8DB3`);
|
|
5541
|
-
if (indexDiff.extra.length > 0) diffParts.push(`${indexDiff.extra.length}\u4EF6\u4F59\u5206`);
|
|
5542
|
-
return [
|
|
5543
|
-
`> \u26A0\uFE0F **\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u5DEE\u5206\u691C\u51FA** (${diffParts.join(", ")})`,
|
|
5544
|
-
">",
|
|
5545
|
-
"> 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",
|
|
5546
|
-
"> \u30B9\u30AD\u30FC\u30DE\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9\u306E\u8A73\u7D30\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
5547
|
-
""
|
|
5548
|
-
];
|
|
5549
|
-
}
|
|
5550
6713
|
function generatePreviewError(error) {
|
|
5551
6714
|
const { label, guidance } = classifyPreviewError(error);
|
|
5552
6715
|
const lines = [`> \u26A0\uFE0F **${label}**`];
|
|
@@ -5567,7 +6730,82 @@ function generatePreviewError(error) {
|
|
|
5567
6730
|
);
|
|
5568
6731
|
return lines;
|
|
5569
6732
|
}
|
|
5570
|
-
function
|
|
6733
|
+
function generatePreviewChangesDetectedSection(prodPreview, previewOnlyChanges) {
|
|
6734
|
+
const lines = [
|
|
6735
|
+
"> \u{1F536} **\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
|
|
6736
|
+
">",
|
|
6737
|
+
"> \u3053\u306EPR\u3092\u30DE\u30FC\u30B8\u3057\u305F\u5F8C\u3001\u672C\u756A\u30C7\u30D7\u30ED\u30A4\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u5B9F\u884C\u3057\u3066\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u9069\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
6738
|
+
""
|
|
6739
|
+
];
|
|
6740
|
+
if (previewOnlyChanges) {
|
|
6741
|
+
lines.push(
|
|
6742
|
+
"> \u88DC\u8DB3: `Prod semantic diff` / count diff / index diff \u3067\u306F\u5DEE\u5206\u304C\u306A\u304F\u3001`production --check` \u306E\u307F\u304C\u5909\u66F4\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
|
|
6743
|
+
"> \u3053\u308C\u306F grants / ACL / ownership \u306A\u3069 canonical semantic diff \u306E\u5BFE\u8C61\u5916\u5DEE\u5206\u3067\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
6744
|
+
""
|
|
6745
|
+
);
|
|
6746
|
+
}
|
|
6747
|
+
if (prodPreview.planSql) {
|
|
6748
|
+
lines.push(...generatePreviewDetails(prodPreview));
|
|
6749
|
+
}
|
|
6750
|
+
return lines;
|
|
6751
|
+
}
|
|
6752
|
+
function buildComparisonMismatchReasons(schemaStats, expectedDrift) {
|
|
6753
|
+
const signals = getProductionSchemaSignals(null, schemaStats, expectedDrift);
|
|
6754
|
+
const reasons = [];
|
|
6755
|
+
if (signals.hasSemanticChanges && signals.semanticDiff) {
|
|
6756
|
+
const diff = signals.semanticDiff;
|
|
6757
|
+
reasons.push(
|
|
6758
|
+
`semantic diff: changed ${diff.changed.length}, missing ${diff.missing.length}, extra ${diff.extra.length}, renamed ${diff.renamed.length}`
|
|
6759
|
+
);
|
|
6760
|
+
}
|
|
6761
|
+
if (signals.hasIndexChanges && signals.indexDiff) {
|
|
6762
|
+
reasons.push(
|
|
6763
|
+
`index diff: missing ${signals.indexDiff.missing.length}, extra ${signals.indexDiff.extra.length}`
|
|
6764
|
+
);
|
|
6765
|
+
}
|
|
6766
|
+
if (signals.hasUnexpectedCountChanges) {
|
|
6767
|
+
const unexpectedDiffs = signals.countDiffs.filter((diff) => !diff.expected);
|
|
6768
|
+
const formatted = unexpectedDiffs.slice(0, 6).map(
|
|
6769
|
+
(diff) => `${diff.schema}.${diff.field} ${diff.reference} -> ${diff.value} (${diff.delta >= 0 ? "+" : ""}${diff.delta})`
|
|
6770
|
+
);
|
|
6771
|
+
reasons.push(`count diff: ${formatted.join(", ")}${unexpectedDiffs.length > 6 ? ", ..." : ""}`);
|
|
6772
|
+
}
|
|
6773
|
+
return reasons;
|
|
6774
|
+
}
|
|
6775
|
+
function generateComparisonMismatchSection(schemaStats, expectedDrift) {
|
|
6776
|
+
const reasons = buildComparisonMismatchReasons(schemaStats, expectedDrift);
|
|
6777
|
+
const lines = [
|
|
6778
|
+
"> \u{1F536} **\u672C\u756ADB\u3068\u306E\u5DEE\u5206\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
|
|
6779
|
+
">",
|
|
6780
|
+
"> `runa db apply production --check` \u306F\u5909\u66F4\u306A\u3057\u5224\u5B9A\u3067\u3057\u305F\u304C\u3001Reference \u2194 Prod \u306E\u6BD4\u8F03\u3067\u5DEE\u5206\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
|
|
6781
|
+
"> \u30B9\u30AD\u30FC\u30DE\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9\u306E `Prod semantic diff` \u3068\u8A73\u7D30\u30ED\u30B0\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
6782
|
+
""
|
|
6783
|
+
];
|
|
6784
|
+
for (const reason of reasons) {
|
|
6785
|
+
lines.push(`> - ${reason}`);
|
|
6786
|
+
}
|
|
6787
|
+
if (reasons.length > 0) {
|
|
6788
|
+
lines.push("");
|
|
6789
|
+
}
|
|
6790
|
+
return lines;
|
|
6791
|
+
}
|
|
6792
|
+
function generateKnownDriftOnlySection(schemaStats, expectedDrift) {
|
|
6793
|
+
const signals = getProductionSchemaSignals(null, schemaStats, expectedDrift);
|
|
6794
|
+
const lines = [
|
|
6795
|
+
"**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u2705 \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u306A\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093",
|
|
6796
|
+
"",
|
|
6797
|
+
"> \u{1F4CB} **\u65E2\u77E5\u306E\u5DEE\u5206\u306E\u307F\u691C\u51FA**",
|
|
6798
|
+
">",
|
|
6799
|
+
"> Reference \u2194 Prod \u306B\u306F `expectedDrift` \u3067\u8A31\u5BB9\u6E08\u307F\u306E\u5DEE\u5206\u306E\u307F\u304C\u3042\u308A\u307E\u3059\u3002\u8FFD\u52A0\u306E db apply/deploy \u306F\u4E0D\u8981\u3067\u3059\u3002"
|
|
6800
|
+
];
|
|
6801
|
+
for (const reason of signals.knownDriftReasons) {
|
|
6802
|
+
lines.push(`> - ${reason}`);
|
|
6803
|
+
}
|
|
6804
|
+
lines.push("");
|
|
6805
|
+
return lines;
|
|
6806
|
+
}
|
|
6807
|
+
function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats, expectedDrift) {
|
|
6808
|
+
const signals = getProductionSchemaSignals(prodPreview, schemaStats, expectedDrift);
|
|
5571
6809
|
if (!prodPreview?.executed) {
|
|
5572
6810
|
if (schemaDrift?.gitDiff?.filesChanged?.length) {
|
|
5573
6811
|
return [
|
|
@@ -5580,20 +6818,15 @@ function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats)
|
|
|
5580
6818
|
return [];
|
|
5581
6819
|
}
|
|
5582
6820
|
if (prodPreview.error) return generatePreviewError(prodPreview.error);
|
|
5583
|
-
if (
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
lines.push(...generatePreviewDetails(prodPreview));
|
|
5592
|
-
}
|
|
5593
|
-
return lines;
|
|
6821
|
+
if (signals.previewHasChanges) {
|
|
6822
|
+
return generatePreviewChangesDetectedSection(prodPreview, signals.previewOnlyChanges);
|
|
6823
|
+
}
|
|
6824
|
+
if (signals.previewMissedChanges) {
|
|
6825
|
+
return generateComparisonMismatchSection(schemaStats, expectedDrift);
|
|
6826
|
+
}
|
|
6827
|
+
if (signals.hasKnownDriftOnly) {
|
|
6828
|
+
return generateKnownDriftOnlySection(schemaStats, expectedDrift);
|
|
5594
6829
|
}
|
|
5595
|
-
const indexWarning = generateIndexDiffWarning(schemaStats);
|
|
5596
|
-
if (indexWarning.length > 0) return indexWarning;
|
|
5597
6830
|
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", ""];
|
|
5598
6831
|
}
|
|
5599
6832
|
function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2) {
|
|
@@ -5602,18 +6835,19 @@ function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges
|
|
|
5602
6835
|
if (!productionPreview?.executed && schemaDrift?.gitDiff?.filesChanged?.length) return true;
|
|
5603
6836
|
return false;
|
|
5604
6837
|
}
|
|
5605
|
-
function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, schemaStats, env) {
|
|
6838
|
+
function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, schemaStats, expectedDrift, env) {
|
|
5606
6839
|
if (!checkIfDeployable(exitCode, layerResults)) return [];
|
|
5607
6840
|
const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
|
|
5608
|
-
const
|
|
5609
|
-
const
|
|
5610
|
-
const hasSchemaChanges2 = hasPgSchemaDiffChanges || hasIndexChanges;
|
|
6841
|
+
const signals = getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift);
|
|
6842
|
+
const hasSchemaChanges2 = signals.requiresDeploy;
|
|
5611
6843
|
const headerEmoji = hasSchemaChanges2 ? "\u{1F6A8}" : "\u{1F680}";
|
|
5612
6844
|
const headerText = hasSchemaChanges2 ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
|
|
5613
6845
|
const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
|
|
5614
6846
|
if (exitCode !== 0)
|
|
5615
6847
|
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", "");
|
|
5616
|
-
lines.push(
|
|
6848
|
+
lines.push(
|
|
6849
|
+
...generateProductionPreviewSection(productionPreview, schemaDrift, schemaStats, expectedDrift)
|
|
6850
|
+
);
|
|
5617
6851
|
const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2);
|
|
5618
6852
|
if (deployWorkflowUrl && showButton) {
|
|
5619
6853
|
const buttonText = hasSchemaChanges2 ? "\u26A1 \u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4" : "\u25B6\uFE0F \u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4";
|
|
@@ -5653,6 +6887,7 @@ function generateIntermediateCommentBody(input) {
|
|
|
5653
6887
|
productionPreview,
|
|
5654
6888
|
schemaDrift,
|
|
5655
6889
|
input.schemaStats ?? null,
|
|
6890
|
+
input.expectedDrift ?? [],
|
|
5656
6891
|
env
|
|
5657
6892
|
),
|
|
5658
6893
|
"<sub>\u{1F916} RUNA CI \u751F\u6210 (\u4E2D\u9593\u66F4\u65B0)</sub>"
|
|
@@ -5688,6 +6923,7 @@ function generateCommentBody(input) {
|
|
|
5688
6923
|
input.productionPreview,
|
|
5689
6924
|
schemaDrift,
|
|
5690
6925
|
input.schemaStats ?? null,
|
|
6926
|
+
input.expectedDrift ?? [],
|
|
5691
6927
|
env
|
|
5692
6928
|
),
|
|
5693
6929
|
"<sub>\u{1F916} RUNA CI \u751F\u6210</sub>"
|
|
@@ -5737,7 +6973,7 @@ function formatSyncSchemaDetail(schemaDrift) {
|
|
|
5737
6973
|
if (!hasGitChanges) {
|
|
5738
6974
|
const hasSqlContent = schemaDrift.beforeSql && schemaDrift.beforeSql.trim().length > 0;
|
|
5739
6975
|
if (hasSqlContent) {
|
|
5740
|
-
return "ci.sql +
|
|
6976
|
+
return "ci.sql + prerequisite seeds";
|
|
5741
6977
|
}
|
|
5742
6978
|
return "SQL\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u306A\u3057";
|
|
5743
6979
|
}
|
|
@@ -5777,7 +7013,7 @@ function formatRunTestsDetail(layerResults) {
|
|
|
5777
7013
|
}
|
|
5778
7014
|
var stepDetailHandlers = {
|
|
5779
7015
|
syncSchema: (_status, schemaDrift) => formatSyncSchemaDetail(schemaDrift),
|
|
5780
|
-
applySeeds: () => "ci.sql +
|
|
7016
|
+
applySeeds: () => "ci.sql + prerequisite seeds",
|
|
5781
7017
|
staticChecks: (status) => status === "passed" ? "\u578B\u30C1\u30A7\u30C3\u30AF \u2713 lint \u2713" : "\u578B\u30C1\u30A7\u30C3\u30AF \u2717 \u307E\u305F\u306F lint \u2717",
|
|
5782
7018
|
build: (status) => status === "passed" ? "\u30A2\u30D7\u30EA\u30D3\u30EB\u30C9\u5B8C\u4E86, Playwright\u6E96\u5099\u5B8C\u4E86" : "\u30D3\u30EB\u30C9\u5931\u6557",
|
|
5783
7019
|
runTests: (_status, _schemaDrift, layerResults) => formatRunTestsDetail(layerResults)
|
|
@@ -6205,7 +7441,7 @@ function createInitialContext(input) {
|
|
|
6205
7441
|
function createOutput(context) {
|
|
6206
7442
|
return {
|
|
6207
7443
|
mode: context.mode,
|
|
6208
|
-
status: context.error ? "
|
|
7444
|
+
status: context.exitCode === 0 && !context.error ? "success" : "failure",
|
|
6209
7445
|
repoKind: context.repoKind,
|
|
6210
7446
|
steps: {},
|
|
6211
7447
|
// Populated from summary
|
|
@@ -6410,7 +7646,7 @@ function createBuildAndPlaywrightInput(context) {
|
|
|
6410
7646
|
enablePublicE2EFlag: true
|
|
6411
7647
|
}),
|
|
6412
7648
|
isCI: context.input.isCI ?? false,
|
|
6413
|
-
skipPlaywright: context.selectedLayers.includes(4)
|
|
7649
|
+
skipPlaywright: !context.selectedLayers.includes(4)
|
|
6414
7650
|
};
|
|
6415
7651
|
}
|
|
6416
7652
|
function createAppStartInput(context) {
|
|
@@ -6774,10 +8010,17 @@ var ciMachine = setup({
|
|
|
6774
8010
|
databaseUrl: context.supabase?.databaseUrlRaw ?? "",
|
|
6775
8011
|
mode: context.mode
|
|
6776
8012
|
}),
|
|
6777
|
-
onDone:
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
8013
|
+
onDone: [
|
|
8014
|
+
{
|
|
8015
|
+
guard: "isCiLocalMode",
|
|
8016
|
+
actions: assign({ seedsApplied: ({ event }) => event.output.applied }),
|
|
8017
|
+
target: "productionPreview"
|
|
8018
|
+
},
|
|
8019
|
+
{
|
|
8020
|
+
actions: assign({ seedsApplied: ({ event }) => event.output.applied }),
|
|
8021
|
+
target: "postSeedPr"
|
|
8022
|
+
}
|
|
8023
|
+
],
|
|
6781
8024
|
onError: {
|
|
6782
8025
|
// Seeds failure is CRITICAL - blocks CI (Layer 2/3 tests depend on seeds)
|
|
6783
8026
|
target: "failed",
|
|
@@ -6789,6 +8032,371 @@ var ciMachine = setup({
|
|
|
6789
8032
|
}
|
|
6790
8033
|
},
|
|
6791
8034
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
8035
|
+
// Post-Seed PR Phase
|
|
8036
|
+
// CI PR path only: run observability work in parallel with the critical build/test path
|
|
8037
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
8038
|
+
postSeedPr: {
|
|
8039
|
+
type: "parallel",
|
|
8040
|
+
meta: {
|
|
8041
|
+
e2e: {
|
|
8042
|
+
observable: "log",
|
|
8043
|
+
assertions: [{ type: "log", contains: "Database roles configured" }]
|
|
8044
|
+
}
|
|
8045
|
+
},
|
|
8046
|
+
states: {
|
|
8047
|
+
execution: {
|
|
8048
|
+
initial: "setupRoles",
|
|
8049
|
+
states: {
|
|
8050
|
+
setupRoles: {
|
|
8051
|
+
invoke: {
|
|
8052
|
+
src: "setupRoles",
|
|
8053
|
+
input: ({ context }) => createSetupRolesInput(context),
|
|
8054
|
+
onDone: {
|
|
8055
|
+
target: "staticChecks",
|
|
8056
|
+
actions: assign({
|
|
8057
|
+
rolesSetup: true,
|
|
8058
|
+
supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
|
|
8059
|
+
})
|
|
8060
|
+
},
|
|
8061
|
+
onError: {
|
|
8062
|
+
target: "staticChecks",
|
|
8063
|
+
actions: assign({ rolesSetup: false })
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
},
|
|
8067
|
+
staticChecks: {
|
|
8068
|
+
always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
|
|
8069
|
+
invoke: {
|
|
8070
|
+
src: "staticChecks",
|
|
8071
|
+
input: ({ context }) => createStaticChecksInput(context),
|
|
8072
|
+
onDone: [
|
|
8073
|
+
{
|
|
8074
|
+
guard: ({ event }) => !event.output.passed,
|
|
8075
|
+
target: "failed",
|
|
8076
|
+
actions: assign({
|
|
8077
|
+
staticChecksPassed: false,
|
|
8078
|
+
error: ({ event }) => event.output.error ?? "Static checks failed"
|
|
8079
|
+
})
|
|
8080
|
+
},
|
|
8081
|
+
{
|
|
8082
|
+
target: "buildAndPlaywright",
|
|
8083
|
+
actions: assign({ staticChecksPassed: true })
|
|
8084
|
+
}
|
|
8085
|
+
],
|
|
8086
|
+
onError: {
|
|
8087
|
+
target: "failed",
|
|
8088
|
+
actions: assign({
|
|
8089
|
+
staticChecksPassed: false,
|
|
8090
|
+
error: ({ event }) => extractErrorMessage2(event, "Static checks failed")
|
|
8091
|
+
})
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
},
|
|
8095
|
+
buildAndPlaywright: {
|
|
8096
|
+
always: [{ guard: "shouldSkipBuild", target: "appStart" }],
|
|
8097
|
+
invoke: {
|
|
8098
|
+
src: "buildAndPlaywright",
|
|
8099
|
+
input: ({ context }) => ({
|
|
8100
|
+
...createBuildAndPlaywrightInput(context),
|
|
8101
|
+
skipPlaywright: shouldSkipPlaywrightInstall(context)
|
|
8102
|
+
}),
|
|
8103
|
+
onDone: [
|
|
8104
|
+
{
|
|
8105
|
+
guard: ({ event }) => !event.output.buildPassed,
|
|
8106
|
+
target: "failed",
|
|
8107
|
+
actions: assign({
|
|
8108
|
+
appBuildPassed: false,
|
|
8109
|
+
playwrightInstalled: ({ event }) => event.output.playwrightInstalled,
|
|
8110
|
+
manifestGenerated: ({ event }) => event.output.manifestGenerated,
|
|
8111
|
+
error: ({ event }) => event.output.buildError ?? "App build failed"
|
|
8112
|
+
})
|
|
8113
|
+
},
|
|
8114
|
+
{
|
|
8115
|
+
target: "appStart",
|
|
8116
|
+
actions: assign({
|
|
8117
|
+
appBuildPassed: true,
|
|
8118
|
+
playwrightInstalled: ({ event }) => event.output.playwrightInstalled,
|
|
8119
|
+
manifestGenerated: ({ event }) => event.output.manifestGenerated
|
|
8120
|
+
})
|
|
8121
|
+
}
|
|
8122
|
+
],
|
|
8123
|
+
onError: {
|
|
8124
|
+
target: "failed",
|
|
8125
|
+
actions: assign({
|
|
8126
|
+
appBuildPassed: false,
|
|
8127
|
+
error: ({ event }) => extractErrorMessage2(event, "App build failed")
|
|
8128
|
+
})
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
},
|
|
8132
|
+
appStart: {
|
|
8133
|
+
always: [{ guard: "shouldSkipAppStart", target: "capabilities" }],
|
|
8134
|
+
invoke: {
|
|
8135
|
+
src: "appStart",
|
|
8136
|
+
input: ({ context }) => createAppStartInput(context),
|
|
8137
|
+
onDone: [
|
|
8138
|
+
{
|
|
8139
|
+
guard: ({ event }) => !event.output.started,
|
|
8140
|
+
target: "failed",
|
|
8141
|
+
actions: assign({
|
|
8142
|
+
appStarted: false,
|
|
8143
|
+
error: ({ event }) => event.output.error ?? "App start failed"
|
|
8144
|
+
})
|
|
8145
|
+
},
|
|
8146
|
+
{
|
|
8147
|
+
target: "capabilities",
|
|
8148
|
+
actions: assign({
|
|
8149
|
+
appStarted: true,
|
|
8150
|
+
appPid: ({ event }) => event.output.pid ?? null,
|
|
8151
|
+
baseUrl: ({ event }) => event.output.baseUrl ?? null
|
|
8152
|
+
})
|
|
8153
|
+
}
|
|
8154
|
+
],
|
|
8155
|
+
onError: {
|
|
8156
|
+
target: "failed",
|
|
8157
|
+
actions: assign({
|
|
8158
|
+
appStarted: false,
|
|
8159
|
+
error: ({ event }) => extractErrorMessage2(event, "App start failed")
|
|
8160
|
+
})
|
|
8161
|
+
}
|
|
8162
|
+
}
|
|
8163
|
+
},
|
|
8164
|
+
capabilities: {
|
|
8165
|
+
invoke: {
|
|
8166
|
+
src: "capabilities",
|
|
8167
|
+
input: ({ context }) => createCapabilitiesInput(context),
|
|
8168
|
+
onDone: {
|
|
8169
|
+
target: "runCoreTests",
|
|
8170
|
+
actions: assign({
|
|
8171
|
+
capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
|
|
8172
|
+
selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
|
|
8173
|
+
layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
|
|
8174
|
+
})
|
|
8175
|
+
},
|
|
8176
|
+
onError: {
|
|
8177
|
+
target: "runCoreTests"
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
},
|
|
8181
|
+
runCoreTests: {
|
|
8182
|
+
invoke: {
|
|
8183
|
+
src: "runLayers",
|
|
8184
|
+
input: ({ context }) => createRunCoreTestsInput(context),
|
|
8185
|
+
onDone: [
|
|
8186
|
+
{
|
|
8187
|
+
guard: ({ event }) => {
|
|
8188
|
+
const failedLayers = event.output.failedLayers ?? [];
|
|
8189
|
+
return failedLayers.length > 0;
|
|
8190
|
+
},
|
|
8191
|
+
target: "coreTestsFailed",
|
|
8192
|
+
actions: assign({
|
|
8193
|
+
testsRun: true,
|
|
8194
|
+
layerResults: ({ event }) => deriveCoreLayerResults(event.output)
|
|
8195
|
+
})
|
|
8196
|
+
},
|
|
8197
|
+
{
|
|
8198
|
+
target: "coreTestsComplete",
|
|
8199
|
+
actions: assign({
|
|
8200
|
+
testsRun: true,
|
|
8201
|
+
layerResults: ({ context, event }) => {
|
|
8202
|
+
const nextResults = convertLayerResults(event.output.results);
|
|
8203
|
+
return mergeLayerResults(context.layerResults, nextResults);
|
|
8204
|
+
}
|
|
8205
|
+
})
|
|
8206
|
+
}
|
|
8207
|
+
],
|
|
8208
|
+
onError: {
|
|
8209
|
+
target: "coreTestsFailed",
|
|
8210
|
+
actions: assign({
|
|
8211
|
+
testsRun: true,
|
|
8212
|
+
error: ({ event }) => extractErrorMessage2(event, "Core tests failed")
|
|
8213
|
+
})
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
8216
|
+
},
|
|
8217
|
+
coreTestsComplete: {
|
|
8218
|
+
always: [
|
|
8219
|
+
{ guard: ({ context }) => hasE2ELayer(context.selectedLayers), target: "e2ePhase" },
|
|
8220
|
+
{
|
|
8221
|
+
target: "done",
|
|
8222
|
+
actions: assign({
|
|
8223
|
+
exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
|
|
8224
|
+
})
|
|
8225
|
+
}
|
|
8226
|
+
]
|
|
8227
|
+
},
|
|
8228
|
+
coreTestsFailed: {
|
|
8229
|
+
entry: assign({
|
|
8230
|
+
exitCode: ({ context }) => {
|
|
8231
|
+
if (Object.keys(context.layerResults).length === 0) {
|
|
8232
|
+
return 1;
|
|
8233
|
+
}
|
|
8234
|
+
return computeExitCodeFromLayerResults(context.layerResults);
|
|
8235
|
+
}
|
|
8236
|
+
}),
|
|
8237
|
+
always: [{ target: "#ci.finalize" }]
|
|
8238
|
+
},
|
|
8239
|
+
e2ePhase: {
|
|
8240
|
+
type: "parallel",
|
|
8241
|
+
states: {
|
|
8242
|
+
intermediateComment: {
|
|
8243
|
+
initial: "checking",
|
|
8244
|
+
states: {
|
|
8245
|
+
checking: {
|
|
8246
|
+
always: [
|
|
8247
|
+
{
|
|
8248
|
+
guard: ({ context }) => !context.prContext?.prNumber,
|
|
8249
|
+
target: "done"
|
|
8250
|
+
},
|
|
8251
|
+
{
|
|
8252
|
+
guard: ({ context }) => !shouldPostGitHubComment(context),
|
|
8253
|
+
target: "done"
|
|
8254
|
+
},
|
|
8255
|
+
{ target: "posting" }
|
|
8256
|
+
]
|
|
8257
|
+
},
|
|
8258
|
+
posting: {
|
|
8259
|
+
invoke: {
|
|
8260
|
+
src: "upsertComment",
|
|
8261
|
+
input: ({ context }) => createIntermediateCommentRequest(context),
|
|
8262
|
+
onDone: "done",
|
|
8263
|
+
onError: "done"
|
|
8264
|
+
}
|
|
8265
|
+
},
|
|
8266
|
+
done: { type: "final" }
|
|
8267
|
+
}
|
|
8268
|
+
},
|
|
8269
|
+
e2eTests: {
|
|
8270
|
+
initial: "running",
|
|
8271
|
+
states: {
|
|
8272
|
+
running: {
|
|
8273
|
+
invoke: {
|
|
8274
|
+
src: "runLayers",
|
|
8275
|
+
input: ({ context }) => createE2ERunLayersInput(context),
|
|
8276
|
+
onDone: {
|
|
8277
|
+
target: "done",
|
|
8278
|
+
actions: assign({
|
|
8279
|
+
layerResults: ({ context, event }) => {
|
|
8280
|
+
const e2eResults = convertLayerResults(event.output.results);
|
|
8281
|
+
return mergeLayerResults(context.layerResults, e2eResults);
|
|
8282
|
+
}
|
|
8283
|
+
})
|
|
8284
|
+
},
|
|
8285
|
+
onError: {
|
|
8286
|
+
target: "done",
|
|
8287
|
+
actions: assign({
|
|
8288
|
+
layerResults: ({ context }) => ({
|
|
8289
|
+
...context.layerResults,
|
|
8290
|
+
[E2E_LAYER]: { status: "failed", exitCode: 1 }
|
|
8291
|
+
})
|
|
8292
|
+
})
|
|
8293
|
+
}
|
|
8294
|
+
}
|
|
8295
|
+
},
|
|
8296
|
+
done: { type: "final" }
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
},
|
|
8300
|
+
onDone: [
|
|
8301
|
+
{
|
|
8302
|
+
guard: ({ context }) => Object.values(context.layerResults).some(
|
|
8303
|
+
(result) => result.status === "failed"
|
|
8304
|
+
),
|
|
8305
|
+
target: "failed",
|
|
8306
|
+
actions: assign({
|
|
8307
|
+
exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
|
|
8308
|
+
})
|
|
8309
|
+
},
|
|
8310
|
+
{
|
|
8311
|
+
target: "done",
|
|
8312
|
+
actions: assign({
|
|
8313
|
+
exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
|
|
8314
|
+
})
|
|
8315
|
+
}
|
|
8316
|
+
]
|
|
8317
|
+
},
|
|
8318
|
+
failed: {
|
|
8319
|
+
always: [
|
|
8320
|
+
{
|
|
8321
|
+
target: "#ci.finalize",
|
|
8322
|
+
actions: assign({
|
|
8323
|
+
exitCode: ({ context }) => context.exitCode !== 0 ? context.exitCode : 1
|
|
8324
|
+
})
|
|
8325
|
+
}
|
|
8326
|
+
]
|
|
8327
|
+
},
|
|
8328
|
+
done: {
|
|
8329
|
+
type: "final"
|
|
8330
|
+
}
|
|
8331
|
+
}
|
|
8332
|
+
},
|
|
8333
|
+
observability: {
|
|
8334
|
+
initial: "productionPreview",
|
|
8335
|
+
states: {
|
|
8336
|
+
productionPreview: {
|
|
8337
|
+
invoke: {
|
|
8338
|
+
src: "productionPreview",
|
|
8339
|
+
input: ({ context }) => ({
|
|
8340
|
+
repoRoot: assertRepoRoot(context),
|
|
8341
|
+
tmpDir: assertTmpDir(context),
|
|
8342
|
+
shouldExecute: Boolean(context.input.productionDatabaseUrl?.trim())
|
|
8343
|
+
}),
|
|
8344
|
+
onDone: {
|
|
8345
|
+
target: "collectSchemaStats",
|
|
8346
|
+
actions: assign({
|
|
8347
|
+
productionPreview: ({ event }) => event.output.preview
|
|
8348
|
+
})
|
|
8349
|
+
},
|
|
8350
|
+
onError: {
|
|
8351
|
+
target: "collectSchemaStats",
|
|
8352
|
+
actions: assign({
|
|
8353
|
+
productionPreview: ({ event }) => ({
|
|
8354
|
+
executed: true,
|
|
8355
|
+
planSql: null,
|
|
8356
|
+
hazards: [],
|
|
8357
|
+
hasChanges: false,
|
|
8358
|
+
error: event.error instanceof Error ? event.error.message : "Production preview failed"
|
|
8359
|
+
})
|
|
8360
|
+
})
|
|
8361
|
+
}
|
|
8362
|
+
}
|
|
8363
|
+
},
|
|
8364
|
+
collectSchemaStats: {
|
|
8365
|
+
invoke: {
|
|
8366
|
+
src: "collectSchemaStats",
|
|
8367
|
+
input: ({ context }) => ({
|
|
8368
|
+
repoRoot: context.repoRoot ?? process.cwd(),
|
|
8369
|
+
referenceDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
8370
|
+
ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
8371
|
+
referenceStrategy: shouldReuseCiReferenceStats(context) ? "reuse-ci" : "rebuild",
|
|
8372
|
+
queryProduction: true,
|
|
8373
|
+
tmpDir: context.tmpDir ?? process.cwd()
|
|
8374
|
+
}),
|
|
8375
|
+
onDone: {
|
|
8376
|
+
target: "done",
|
|
8377
|
+
actions: assign({
|
|
8378
|
+
schemaStats: ({ event }) => event.output.schemaStats
|
|
8379
|
+
})
|
|
8380
|
+
},
|
|
8381
|
+
onError: {
|
|
8382
|
+
target: "done",
|
|
8383
|
+
actions: assign({
|
|
8384
|
+
schemaStats: () => null
|
|
8385
|
+
})
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
},
|
|
8389
|
+
done: {
|
|
8390
|
+
type: "final"
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
},
|
|
8395
|
+
onDone: {
|
|
8396
|
+
target: "finalize"
|
|
8397
|
+
}
|
|
8398
|
+
},
|
|
8399
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6792
8400
|
// Production Preview (ci-pr modes only, when schema changes exist)
|
|
6793
8401
|
// Runs dry-run against production to show what SQL would be applied
|
|
6794
8402
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
@@ -6845,11 +8453,12 @@ var ciMachine = setup({
|
|
|
6845
8453
|
invoke: {
|
|
6846
8454
|
src: "collectSchemaStats",
|
|
6847
8455
|
input: ({ context }) => ({
|
|
6848
|
-
|
|
6849
|
-
|
|
8456
|
+
repoRoot: context.repoRoot ?? process.cwd(),
|
|
8457
|
+
referenceDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
6850
8458
|
ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
|
|
6851
|
-
|
|
6852
|
-
queryProduction: context.mode !== "ci-local"
|
|
8459
|
+
referenceStrategy: shouldReuseCiReferenceStats(context) ? "reuse-ci" : "rebuild",
|
|
8460
|
+
queryProduction: context.mode !== "ci-local",
|
|
8461
|
+
tmpDir: context.tmpDir ?? process.cwd()
|
|
6853
8462
|
}),
|
|
6854
8463
|
onDone: {
|
|
6855
8464
|
target: "decidePath",
|
|
@@ -7383,6 +8992,19 @@ var STATE_TO_STEP = {
|
|
|
7383
8992
|
pullProduction: "syncSchema",
|
|
7384
8993
|
syncSchema: "syncSchema",
|
|
7385
8994
|
applySeeds: "applySeeds",
|
|
8995
|
+
postSeedPr: "applySeeds",
|
|
8996
|
+
"postSeedPr.execution.setupRoles": "applySeeds",
|
|
8997
|
+
"postSeedPr.execution.staticChecks": "staticChecks",
|
|
8998
|
+
"postSeedPr.execution.buildAndPlaywright": "build",
|
|
8999
|
+
"postSeedPr.execution.appStart": "build",
|
|
9000
|
+
"postSeedPr.execution.capabilities": "build",
|
|
9001
|
+
"postSeedPr.execution.runCoreTests": "runTests",
|
|
9002
|
+
"postSeedPr.execution.coreTestsComplete": "runTests",
|
|
9003
|
+
"postSeedPr.execution.coreTestsFailed": "runTests",
|
|
9004
|
+
"postSeedPr.execution.e2ePhase": "runTests",
|
|
9005
|
+
"postSeedPr.execution.done": "runTests",
|
|
9006
|
+
"postSeedPr.observability.productionPreview": "applySeeds",
|
|
9007
|
+
"postSeedPr.observability.collectSchemaStats": "applySeeds",
|
|
7386
9008
|
decidePath: "applySeeds",
|
|
7387
9009
|
installPgTap: "applySeeds",
|
|
7388
9010
|
setupRoles: "applySeeds",
|
|
@@ -7420,6 +9042,17 @@ var STEP_ORDER = [
|
|
|
7420
9042
|
"runTests",
|
|
7421
9043
|
"finalize"
|
|
7422
9044
|
];
|
|
9045
|
+
function resolveStepForState(state) {
|
|
9046
|
+
let cursor = state;
|
|
9047
|
+
while (cursor.length > 0) {
|
|
9048
|
+
const step = STATE_TO_STEP[cursor];
|
|
9049
|
+
if (step) return step;
|
|
9050
|
+
const nextDot = cursor.lastIndexOf(".");
|
|
9051
|
+
if (nextDot === -1) break;
|
|
9052
|
+
cursor = cursor.slice(0, nextDot);
|
|
9053
|
+
}
|
|
9054
|
+
return void 0;
|
|
9055
|
+
}
|
|
7423
9056
|
function getCompletedSteps(currentStep) {
|
|
7424
9057
|
const currentIndex = STEP_ORDER.indexOf(currentStep);
|
|
7425
9058
|
if (currentIndex <= 0) return [];
|
|
@@ -7508,8 +9141,8 @@ function determineFailedStep(context) {
|
|
|
7508
9141
|
function handleProgressCommentUpdate(snapshot, prevState) {
|
|
7509
9142
|
const currentState = getStateName(snapshot);
|
|
7510
9143
|
const context = snapshot.context;
|
|
7511
|
-
const currentStep =
|
|
7512
|
-
const prevStep =
|
|
9144
|
+
const currentStep = resolveStepForState(currentState);
|
|
9145
|
+
const prevStep = resolveStepForState(prevState);
|
|
7513
9146
|
if (!currentStep || currentStep === prevStep) return;
|
|
7514
9147
|
recordStepTiming(currentStep);
|
|
7515
9148
|
if (currentStep === "finalize") {
|
|
@@ -7704,16 +9337,13 @@ var stateLogHandlers = {
|
|
|
7704
9337
|
logger.info(`Running: ${ctx.selectedLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
7705
9338
|
}
|
|
7706
9339
|
};
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
handler(snapshot.context, logger);
|
|
7715
|
-
}
|
|
7716
|
-
}
|
|
9340
|
+
var handleStateChange = createMachineStateChangeLogger({
|
|
9341
|
+
getState: getStateName,
|
|
9342
|
+
getContext: (snapshot) => snapshot.context,
|
|
9343
|
+
handlers: stateLogHandlers,
|
|
9344
|
+
useParentState: true,
|
|
9345
|
+
handlerArgumentOrder: "context-first"
|
|
9346
|
+
});
|
|
7717
9347
|
function printSummary(logger, output) {
|
|
7718
9348
|
endCurrentGroup();
|
|
7719
9349
|
logSection2("Summary");
|
|
@@ -8052,6 +9682,7 @@ function logPlan2(steps) {
|
|
|
8052
9682
|
console.log("");
|
|
8053
9683
|
}
|
|
8054
9684
|
}
|
|
9685
|
+
var CI_PR_UNKNOWN_STATE_LOG_SKIP = /* @__PURE__ */ new Set(["decidePath", "done", "failed"]);
|
|
8055
9686
|
var stateLogHandlers2 = {
|
|
8056
9687
|
// ─── Setup Phase ───────────────────────────────────────────
|
|
8057
9688
|
setup: (_ctx, _logger) => {
|
|
@@ -8078,6 +9709,75 @@ var stateLogHandlers2 = {
|
|
|
8078
9709
|
logSection3("Database: Apply Seeds (db seed)");
|
|
8079
9710
|
logger.info("Running: pnpm exec runa db seed ci --auto-approve");
|
|
8080
9711
|
},
|
|
9712
|
+
"postSeedPr.execution.setupRoles": (ctx, logger) => {
|
|
9713
|
+
if (ctx.seedsApplied) {
|
|
9714
|
+
logger.success("Seeds applied successfully");
|
|
9715
|
+
}
|
|
9716
|
+
logSection3("Database: Setup Roles (db:setup-roles)");
|
|
9717
|
+
logger.info("Running: pnpm db:setup-roles");
|
|
9718
|
+
},
|
|
9719
|
+
"postSeedPr.observability.productionPreview": (ctx, logger) => {
|
|
9720
|
+
if (ctx.seedsApplied) {
|
|
9721
|
+
logger.success("Seeds applied successfully");
|
|
9722
|
+
}
|
|
9723
|
+
logSection3("Database: Production Preview (dry-run)");
|
|
9724
|
+
logger.info("Running: pnpm exec runa db apply production --check");
|
|
9725
|
+
},
|
|
9726
|
+
"postSeedPr.observability.collectSchemaStats": (ctx, logger) => {
|
|
9727
|
+
if (ctx.productionPreview?.executed) {
|
|
9728
|
+
if (ctx.productionPreview.hasChanges) {
|
|
9729
|
+
logger.info("Production preview: schema changes detected");
|
|
9730
|
+
} else {
|
|
9731
|
+
logger.success("Production preview: no schema changes");
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
logSection3("Database: Collect Schema Statistics");
|
|
9735
|
+
logger.info("Comparing Local/CI/Production schemas...");
|
|
9736
|
+
},
|
|
9737
|
+
"postSeedPr.execution.staticChecks": (ctx, logger) => {
|
|
9738
|
+
if (ctx.rolesSetup) {
|
|
9739
|
+
logger.success("Database roles configured");
|
|
9740
|
+
}
|
|
9741
|
+
logSection3("Static Checks: type-check + lint (parallel)");
|
|
9742
|
+
logger.info("Running: pnpm type-check || pnpm lint");
|
|
9743
|
+
},
|
|
9744
|
+
"postSeedPr.execution.buildAndPlaywright": (ctx, logger) => {
|
|
9745
|
+
if (ctx.staticChecksPassed) {
|
|
9746
|
+
logger.success("Static checks passed");
|
|
9747
|
+
}
|
|
9748
|
+
logSection3("Build: App Build + Playwright Install (parallel)");
|
|
9749
|
+
logger.info("Running: pnpm build || pnpm exec playwright install chromium");
|
|
9750
|
+
},
|
|
9751
|
+
"postSeedPr.execution.appStart": (ctx, logger) => {
|
|
9752
|
+
if (ctx.appBuildPassed) {
|
|
9753
|
+
logger.success("App build completed");
|
|
9754
|
+
}
|
|
9755
|
+
if (ctx.manifestGenerated) {
|
|
9756
|
+
logger.success("Manifest generated (Layer 3 ready)");
|
|
9757
|
+
} else if (ctx.manifestGenerated === false) {
|
|
9758
|
+
logger.warn("Manifest generation failed (Layer 3 may be skipped)");
|
|
9759
|
+
}
|
|
9760
|
+
if (ctx.playwrightInstalled) {
|
|
9761
|
+
logger.success("Playwright browsers installed");
|
|
9762
|
+
}
|
|
9763
|
+
logSection3("Start Application");
|
|
9764
|
+
logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
|
|
9765
|
+
},
|
|
9766
|
+
"postSeedPr.execution.capabilities": (ctx, logger) => {
|
|
9767
|
+
if (ctx.appStarted) {
|
|
9768
|
+
logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
|
|
9769
|
+
}
|
|
9770
|
+
logSection3("Detect Test Capabilities");
|
|
9771
|
+
},
|
|
9772
|
+
"postSeedPr.execution.runCoreTests": (ctx, logger) => {
|
|
9773
|
+
const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
|
|
9774
|
+
logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
|
|
9775
|
+
logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
9776
|
+
},
|
|
9777
|
+
"postSeedPr.execution.e2ePhase": (_ctx, logger) => {
|
|
9778
|
+
logSection3("E2E Phase: Layer 4 + Intermediate Comment");
|
|
9779
|
+
logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
|
|
9780
|
+
},
|
|
8081
9781
|
productionPreview: (ctx, logger) => {
|
|
8082
9782
|
if (ctx.seedsApplied) {
|
|
8083
9783
|
logger.success("Seeds applied successfully");
|
|
@@ -8186,18 +9886,18 @@ var stateLogHandlers2 = {
|
|
|
8186
9886
|
logSection3("Finalize: Post GitHub Comment");
|
|
8187
9887
|
}
|
|
8188
9888
|
};
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
9889
|
+
var handleStateChange2 = createMachineStateChangeLogger({
|
|
9890
|
+
getState: getStateName,
|
|
9891
|
+
getContext: (snapshot) => snapshot.context,
|
|
9892
|
+
handlers: stateLogHandlers2,
|
|
9893
|
+
useParentState: true,
|
|
9894
|
+
handlerArgumentOrder: "context-first",
|
|
9895
|
+
onUnknownState: (state, logger) => {
|
|
9896
|
+
if (cliOutputFormat !== "json" && !CI_PR_UNKNOWN_STATE_LOG_SKIP.has(state)) {
|
|
9897
|
+
logger.info(`State: ${state}`);
|
|
9898
|
+
}
|
|
8199
9899
|
}
|
|
8200
|
-
}
|
|
9900
|
+
});
|
|
8201
9901
|
function printSummary2(logger, output) {
|
|
8202
9902
|
logSection3("Summary");
|
|
8203
9903
|
logger.info(`Mode: ${output.mode}`);
|
|
@@ -8284,7 +9984,7 @@ async function runCiPrCommand(options) {
|
|
|
8284
9984
|
{ id: "setup", description: "Start local Supabase instance" },
|
|
8285
9985
|
{
|
|
8286
9986
|
id: "db",
|
|
8287
|
-
description: "db apply \u2192 db seed \u2192 production preview \
|
|
9987
|
+
description: "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)"
|
|
8288
9988
|
},
|
|
8289
9989
|
...skipStaticChecks ? [] : [{ id: "static", description: "pnpm type-check + pnpm lint (parallel)" }],
|
|
8290
9990
|
...skipBuild ? [] : [{ id: "build", description: "pnpm build \u2192 manifest:generate + playwright install" }],
|