@runa-ai/runa-cli 0.7.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-HQMSVN6N.js → build-P2A6345N.js} +2 -2
- package/dist/{check-PCSQPYDM.js → check-4TZHNOZU.js} +4 -4
- package/dist/{chunk-DRSUEMAK.js → chunk-B7C7CLW2.js} +2 -5
- package/dist/{chunk-B3POLMII.js → chunk-BQ336L5T.js} +1 -1
- package/dist/{chunk-6FAU4IGR.js → chunk-ELXXQIGW.js} +4 -1
- package/dist/{chunk-FWMGC5FP.js → chunk-EXR4J2JT.js} +289 -16
- package/dist/{chunk-AO554K3G.js → chunk-GKBE7EIE.js} +1 -1
- package/dist/{chunk-JMJP4A47.js → chunk-GT5DMS5R.js} +20 -2
- package/dist/{chunk-3JO6YP3T.js → chunk-IEKYTCYA.js} +1 -1
- package/dist/{chunk-WPMR7RQ4.js → chunk-IWVXI5O4.js} +2 -2
- package/dist/chunk-KUH3G522.js +72 -0
- package/dist/{chunk-VSH3IXDQ.js → chunk-MAFJAA2P.js} +1 -1
- package/dist/{chunk-CCKG5R4Y.js → chunk-MILCC3B6.js} +1 -1
- package/dist/{chunk-5NKWR4FF.js → chunk-OERS32LW.js} +1 -1
- package/dist/{chunk-2QX7T24B.js → chunk-QKGL6Q2S.js} +1 -1
- package/dist/{chunk-OBYZDT2E.js → chunk-URWDB7YL.js} +15 -78
- package/dist/{chunk-CKRLVEIO.js → chunk-WGRVAGSR.js} +16 -11
- package/dist/chunk-YRNQEJQW.js +9043 -0
- package/dist/chunk-ZWDWFMOX.js +1514 -0
- package/dist/{ci-Z4525QW6.js → ci-S5KSBECX.js} +1226 -1207
- package/dist/{cli-SVXOSMW6.js → cli-TJZCAMB2.js} +30 -30
- package/dist/commands/ci/commands/ci-prod-db-operations.d.ts +12 -17
- package/dist/commands/ci/commands/ci-prod-utils.d.ts +7 -0
- package/dist/commands/ci/commands/ci-resolvers.d.ts +1 -2
- package/dist/commands/ci/commands/layer4-discovery.d.ts +2 -0
- package/dist/commands/ci/machine/actors/db/production-preview.d.ts +4 -3
- package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +5 -1
- package/dist/commands/ci/machine/actors/setup/pr-common.d.ts +1 -1
- package/dist/commands/ci/machine/actors/test/capabilities.d.ts +2 -13
- package/dist/commands/ci/machine/actors/test/index.d.ts +1 -0
- package/dist/commands/ci/machine/actors/test/layer-content.d.ts +11 -0
- package/dist/commands/ci/machine/commands/ci-pr-internal-profile.d.ts +7 -0
- package/dist/commands/ci/machine/commands/ci-step-registry.d.ts +25 -0
- package/dist/commands/ci/machine/commands/step-telemetry.d.ts +1 -2
- package/dist/commands/ci/machine/contract.d.ts +9 -1
- package/dist/commands/ci/machine/guards.d.ts +19 -10
- package/dist/commands/ci/machine/helpers.d.ts +1 -1
- package/dist/commands/ci/machine/machine-execution-helpers.d.ts +5 -2
- package/dist/commands/ci/machine/machine.d.ts +34 -32
- package/dist/commands/ci/machine/selectors.d.ts +6 -0
- package/dist/commands/ci/machine/types.d.ts +3 -1
- package/dist/commands/ci/utils/ci-logging.d.ts +16 -0
- package/dist/commands/ci/utils/rls-verification.d.ts +3 -2
- package/dist/commands/db/apply/actors/pg-schema-diff-actors.d.ts +1 -0
- package/dist/commands/db/apply/actors/seed-actors.d.ts +1 -0
- package/dist/commands/db/apply/contract.d.ts +232 -0
- package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +3 -1
- package/dist/commands/db/apply/helpers/hazard-handler.d.ts +19 -8
- package/dist/commands/db/apply/helpers/index.d.ts +5 -2
- package/dist/commands/db/apply/helpers/no-change-plan.d.ts +2 -0
- package/dist/commands/db/apply/helpers/plan-ast-sql-helpers.d.ts +19 -0
- package/dist/commands/db/apply/helpers/plan-ast.d.ts +1 -2
- package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +11 -14
- package/dist/commands/db/apply/helpers/plan-validator.d.ts +34 -0
- package/dist/commands/db/apply/helpers/planner-artifact.d.ts +65 -0
- package/dist/commands/db/apply/helpers/retry-logic.d.ts +5 -0
- package/dist/commands/db/apply/machine.d.ts +100 -14
- package/dist/commands/db/commands/db-apply-error.d.ts +6 -1
- package/dist/commands/db/commands/db-apply.d.ts +5 -0
- package/dist/commands/db/commands/db-plan.d.ts +3 -0
- package/dist/commands/db/commands/db-preview-profile.d.ts +23 -0
- package/dist/commands/db/commands/db-preview.d.ts +3 -0
- package/dist/commands/db/sync/actors.d.ts +1 -0
- package/dist/commands/db/sync/contract.d.ts +16 -0
- package/dist/commands/db/sync/guardrail-orchestrator.d.ts +15 -0
- package/dist/commands/db/sync/guardrail-reporting.d.ts +12 -0
- package/dist/commands/db/sync/index.d.ts +4 -0
- package/dist/commands/db/sync/machine.d.ts +18 -13
- package/dist/commands/db/sync/schema-guardrail-config-test-support.d.ts +15 -0
- package/dist/commands/db/sync/schema-guardrail-config.d.ts +11 -0
- package/dist/commands/db/sync/schema-guardrail-ddl-order.d.ts +36 -0
- package/dist/commands/db/sync/schema-guardrail-graph-guidance.d.ts +15 -0
- package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +41 -0
- package/dist/commands/db/sync/schema-guardrail-graph-nodes.d.ts +61 -0
- package/dist/commands/db/sync/schema-guardrail-graph-sql-helpers.d.ts +31 -0
- package/dist/commands/db/sync/schema-guardrail-graph-types.d.ts +56 -0
- package/dist/commands/db/sync/schema-guardrail-graph.d.ts +20 -0
- package/dist/commands/db/sync/schema-guardrail-local-blockers.d.ts +7 -0
- package/dist/commands/db/sync/schema-guardrail-phases.d.ts +26 -0
- package/dist/commands/db/sync/schema-guardrail-production-check.d.ts +23 -0
- package/dist/commands/db/sync/schema-guardrail-rewrite.d.ts +46 -0
- package/dist/commands/db/sync/schema-guardrail-runtime.d.ts +5 -0
- package/dist/commands/db/sync/schema-guardrail-semantic-warnings.d.ts +9 -0
- package/dist/commands/db/sync/schema-guardrail-types.d.ts +243 -0
- package/dist/commands/db/sync/schema-guardrail.d.ts +10 -0
- package/dist/commands/db/utils/declarative-dependency-sql-utils.d.ts +1 -1
- package/dist/commands/db/utils/duplicate-function-ownership.d.ts +61 -0
- package/dist/commands/db/utils/plan-size-guard.d.ts +16 -0
- package/dist/commands/db/utils/policy-cross-schema-refs.d.ts +12 -0
- package/dist/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.d.ts +4 -0
- package/dist/commands/db/utils/sql-table-extractor.d.ts +6 -0
- package/dist/commands/test/commands/layer4-prereqs.d.ts +15 -0
- package/dist/{config-loader-GT3HAQ7U.js → config-loader-N5ODNMD5.js} +2 -2
- package/dist/db-D2OLJDYW.js +12757 -0
- package/dist/{dev-MLRKIP7F.js → dev-LGSMDFJN.js} +7 -6
- package/dist/{doctor-ROSWSMLH.js → doctor-GYX73IEW.js} +4 -4
- package/dist/{env-WNHJVLOT.js → env-KYR6Q7WO.js} +15 -10
- package/dist/{env-files-HRNUGZ5O.js → env-files-ONBC47I6.js} +3 -3
- package/dist/{hotfix-Z5EGVSMH.js → hotfix-RJIAPLAM.js} +4 -4
- package/dist/index.js +3 -3
- package/dist/{init-35JLDFHI.js → init-2O6ODG5Z.js} +2 -2
- package/dist/{inject-test-attrs-XN4I2AOR.js → inject-test-attrs-F5A346UV.js} +3 -3
- package/dist/{manifest-EGCAZ4TK.js → manifest-CI4BRWEB.js} +2 -2
- package/dist/{observability-CJA5UFIC.js → observability-WNSCJ5FV.js} +2 -2
- package/dist/pg-schema-diff-helpers-7377FS2D.js +7 -0
- package/dist/{sdk-XK6HQU7S.js → sdk-BTIVPEE5.js} +1 -1
- package/dist/{test-V4KQL574.js → test-QCPN6Z47.js} +74 -46
- package/dist/{upgrade-7L4JIE4K.js → upgrade-QZKEI3NJ.js} +2 -2
- package/dist/utils/db-url-utils.d.ts +4 -77
- package/dist/{vuln-check-D575VXIQ.js → vuln-check-5NUTETPW.js} +1 -1
- package/dist/{vuln-checker-QV6XODTJ.js → vuln-checker-UV342N66.js} +1 -1
- package/dist/{watch-AL4LCBRM.js → watch-RFVCEQLH.js} +3 -3
- package/dist/{workflow-UZIZ2JUS.js → workflow-UOG6ZZMH.js} +3 -3
- package/package.json +3 -3
- package/dist/chunk-4XHZQRRK.js +0 -215
- package/dist/db-S4V4ETDR.js +0 -17556
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
-
import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, detectAppSchemas, formatSchemasForSql } from './chunk-
|
|
4
|
-
import
|
|
3
|
+
import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, isIdempotentRoleHazard, detectAppSchemas, formatSchemasForSql, getDbPlanArtifactPath, runDbApply } from './chunk-YRNQEJQW.js';
|
|
4
|
+
import './chunk-WGRVAGSR.js';
|
|
5
|
+
import './chunk-ZWDWFMOX.js';
|
|
6
|
+
import './chunk-UHDAYPHH.js';
|
|
7
|
+
import './chunk-Y5ANTCKE.js';
|
|
8
|
+
import './chunk-IWVXI5O4.js';
|
|
9
|
+
import './chunk-GKBE7EIE.js';
|
|
10
|
+
import './chunk-B7C7CLW2.js';
|
|
5
11
|
import './chunk-QDF7QXBL.js';
|
|
6
12
|
import { getSnapshotStateName, getSnapshotStatePaths, isSnapshotComplete } from './chunk-XVNDDHAF.js';
|
|
7
|
-
import { writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput } from './chunk-
|
|
13
|
+
import { createInitialSummary, resolveMode, appendGithubStepSummary, buildCiProdApplyStepSummaryMarkdown, setSummaryErrorFromUnknown, writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput, requireCiAutoApprove, resolveProdApplyInputs, parseIntOr, classifyCiProdApplyError, addGithubMask } from './chunk-EXR4J2JT.js';
|
|
14
|
+
import './chunk-URWDB7YL.js';
|
|
8
15
|
import { parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, psqlSyncQuery } from './chunk-A6A7JIRD.js';
|
|
9
|
-
import { ensureRunaTmpDir, runLogged } from './chunk-
|
|
16
|
+
import { ensureRunaTmpDir, runLogged } from './chunk-ELXXQIGW.js';
|
|
10
17
|
import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
|
|
11
18
|
import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
|
|
12
19
|
import { init_constants, init_local_supabase, detectLocalSupabasePorts } from './chunk-QSEF4T3Y.js';
|
|
13
20
|
import { emitJsonSuccess } from './chunk-KE6QJBZG.js';
|
|
14
21
|
import './chunk-WJXC4MVY.js';
|
|
15
22
|
import { setOutputFormat } from './chunk-HKUWEGUX.js';
|
|
16
|
-
import
|
|
17
|
-
import
|
|
23
|
+
import './chunk-OERS32LW.js';
|
|
24
|
+
import './chunk-GT5DMS5R.js';
|
|
25
|
+
import { init_esm_shims } from './chunk-VRXHCR5K.js';
|
|
18
26
|
import { Command } from 'commander';
|
|
19
27
|
import { spawnSync, spawn, execFileSync } from 'child_process';
|
|
20
|
-
import { mkdir, writeFile,
|
|
28
|
+
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
21
29
|
import path4, { join } from 'path';
|
|
22
|
-
import { CommandOutcomeSchema, createCLILogger, CLIError, syncFromProduction,
|
|
30
|
+
import { CommandOutcomeSchema, createCLILogger, CLIError, syncFromProduction, GITHUB_API, getClassificationForProfile, formatDuration, buildCommandOutcomeSummary, deriveCommandExitMode, getStatusIcon, DATABASE_PACKAGE_CANDIDATES, detectDatabasePackage } from '@runa-ai/runa';
|
|
23
31
|
import { z } from 'zod';
|
|
24
32
|
import { existsSync, createWriteStream, readFileSync, readdirSync, statSync, promises, lstatSync } from 'fs';
|
|
25
33
|
import { resolve4 } from 'dns/promises';
|
|
@@ -28,6 +36,7 @@ import { fromPromise, setup, assign, createActor } from 'xstate';
|
|
|
28
36
|
import { randomUUID, createHash } from 'crypto';
|
|
29
37
|
import { execa } from 'execa';
|
|
30
38
|
import postgres from 'postgres';
|
|
39
|
+
import { resolveLayer4DiscoverySync } from '@runa-ai/runa/test-config';
|
|
31
40
|
import { glob } from 'glob';
|
|
32
41
|
|
|
33
42
|
createRequire(import.meta.url);
|
|
@@ -37,28 +46,6 @@ init_esm_shims();
|
|
|
37
46
|
|
|
38
47
|
// src/commands/ci/commands/ci-checks.ts
|
|
39
48
|
init_esm_shims();
|
|
40
|
-
|
|
41
|
-
// src/commands/ci/utils/github.ts
|
|
42
|
-
init_esm_shims();
|
|
43
|
-
async function appendGithubStepSummary(markdown) {
|
|
44
|
-
const file = process.env.GITHUB_STEP_SUMMARY;
|
|
45
|
-
if (!file) return;
|
|
46
|
-
await appendFile(file, `${markdown.trimEnd()}
|
|
47
|
-
|
|
48
|
-
`, "utf-8");
|
|
49
|
-
}
|
|
50
|
-
function addGithubMask(value) {
|
|
51
|
-
if (!value) return;
|
|
52
|
-
console.log(`::add-mask::${value}`);
|
|
53
|
-
}
|
|
54
|
-
z.object({
|
|
55
|
-
action: z.string().optional(),
|
|
56
|
-
pull_request: z.object({
|
|
57
|
-
number: z.number().int()
|
|
58
|
-
})
|
|
59
|
-
}).passthrough();
|
|
60
|
-
|
|
61
|
-
// src/commands/ci/commands/ci-checks.ts
|
|
62
49
|
async function runTool(params) {
|
|
63
50
|
const startTime = Date.now();
|
|
64
51
|
return new Promise((resolve) => {
|
|
@@ -316,29 +303,30 @@ async function loadCiConfig(params) {
|
|
|
316
303
|
|
|
317
304
|
// src/commands/ci/utils/ci-logging.ts
|
|
318
305
|
init_esm_shims();
|
|
306
|
+
var write = (msg) => process.stderr.write(msg + "\n");
|
|
319
307
|
function logSection(title) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
308
|
+
write("");
|
|
309
|
+
write("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
310
|
+
write(title);
|
|
311
|
+
write("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
312
|
+
write("");
|
|
325
313
|
}
|
|
326
314
|
function logKeyValueTable(params) {
|
|
327
315
|
logSection(params.title);
|
|
328
316
|
for (const [k, v] of params.rows) {
|
|
329
|
-
|
|
317
|
+
write(`${k}: ${v}`);
|
|
330
318
|
}
|
|
331
319
|
}
|
|
332
320
|
function logPlan(steps) {
|
|
333
321
|
logSection("Plan");
|
|
334
322
|
for (const step of steps) {
|
|
335
|
-
|
|
323
|
+
write(`- ${step.id}: ${step.description}`);
|
|
336
324
|
}
|
|
337
325
|
}
|
|
338
326
|
function logNextActions(items) {
|
|
339
327
|
logSection("Next");
|
|
340
328
|
for (const item of items) {
|
|
341
|
-
|
|
329
|
+
write(`- ${item}`);
|
|
342
330
|
}
|
|
343
331
|
}
|
|
344
332
|
|
|
@@ -506,7 +494,7 @@ async function githubRequest(params) {
|
|
|
506
494
|
url.searchParams.set(k, String(v));
|
|
507
495
|
}
|
|
508
496
|
const startTime = Date.now();
|
|
509
|
-
|
|
497
|
+
process.stderr.write(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
|
|
510
498
|
const controller = new AbortController();
|
|
511
499
|
const timeoutId = setTimeout(() => controller.abort(), GITHUB_API_TIMEOUT_MS);
|
|
512
500
|
try {
|
|
@@ -525,7 +513,7 @@ async function githubRequest(params) {
|
|
|
525
513
|
const duration = Date.now() - startTime;
|
|
526
514
|
if (!res.ok) {
|
|
527
515
|
const text = await res.text().catch(() => "");
|
|
528
|
-
|
|
516
|
+
process.stderr.write(
|
|
529
517
|
`[DEBUG] GitHub API: ${params.method} ${params.path} failed (${res.status}) after ${duration}ms`
|
|
530
518
|
);
|
|
531
519
|
throw new CLIError(
|
|
@@ -536,7 +524,7 @@ async function githubRequest(params) {
|
|
|
536
524
|
20
|
|
537
525
|
);
|
|
538
526
|
}
|
|
539
|
-
|
|
527
|
+
process.stderr.write(
|
|
540
528
|
`[DEBUG] GitHub API: ${params.method} ${params.path} succeeded (${res.status}) in ${duration}ms`
|
|
541
529
|
);
|
|
542
530
|
if (res.status === 204) return null;
|
|
@@ -546,7 +534,7 @@ async function githubRequest(params) {
|
|
|
546
534
|
clearTimeout(timeoutId);
|
|
547
535
|
const duration = Date.now() - startTime;
|
|
548
536
|
if (error instanceof Error && error.name === "AbortError") {
|
|
549
|
-
|
|
537
|
+
process.stderr.write(
|
|
550
538
|
`[DEBUG] GitHub API: ${params.method} ${params.path} timed out after ${duration}ms`
|
|
551
539
|
);
|
|
552
540
|
throw new CLIError(
|
|
@@ -557,7 +545,7 @@ async function githubRequest(params) {
|
|
|
557
545
|
21
|
|
558
546
|
);
|
|
559
547
|
}
|
|
560
|
-
|
|
548
|
+
process.stderr.write(
|
|
561
549
|
`[DEBUG] GitHub API: ${params.method} ${params.path} error after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`
|
|
562
550
|
);
|
|
563
551
|
throw error;
|
|
@@ -684,17 +672,6 @@ async function detectStack(repoRoot, tmpDir, productionDbUrlAdmin) {
|
|
|
684
672
|
});
|
|
685
673
|
return String(res.stdout ?? "").trim();
|
|
686
674
|
}
|
|
687
|
-
function runGitDiffQuiet(repoRoot, diffPaths) {
|
|
688
|
-
const result = spawnSync("git", ["diff", "--quiet", "origin/main", "HEAD", "--", ...diffPaths], {
|
|
689
|
-
cwd: repoRoot,
|
|
690
|
-
stdio: "ignore",
|
|
691
|
-
timeout: 3e4
|
|
692
|
-
});
|
|
693
|
-
if (result.error || result.signal === "SIGTERM") {
|
|
694
|
-
return null;
|
|
695
|
-
}
|
|
696
|
-
return result.status !== 0;
|
|
697
|
-
}
|
|
698
675
|
function getSqlSchemaDiffPaths(repoRoot) {
|
|
699
676
|
const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
|
|
700
677
|
const idempotentSqlPath = path4.join(repoRoot, "supabase/schemas/idempotent");
|
|
@@ -711,6 +688,38 @@ function getLegacySchemaDiffPath(repoRoot) {
|
|
|
711
688
|
const schemaPath = path4.join(detected, "src", "schema");
|
|
712
689
|
return existsSync(path4.join(repoRoot, schemaPath)) ? `${schemaPath}/` : null;
|
|
713
690
|
}
|
|
691
|
+
function resolveSchemaDiffTarget(repoRoot) {
|
|
692
|
+
const sqlDiffPaths = getSqlSchemaDiffPaths(repoRoot);
|
|
693
|
+
if (sqlDiffPaths.length > 0) {
|
|
694
|
+
return {
|
|
695
|
+
label: "git diff schema (supabase/schemas)",
|
|
696
|
+
paths: sqlDiffPaths
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const legacySchemaPath = getLegacySchemaDiffPath(repoRoot);
|
|
700
|
+
if (legacySchemaPath) {
|
|
701
|
+
return {
|
|
702
|
+
label: "git diff schema (legacy drizzle schema)",
|
|
703
|
+
paths: [legacySchemaPath]
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
|
|
707
|
+
const candidatePath = path4.join("packages", candidate, "src", "schema");
|
|
708
|
+
if (existsSync(path4.join(repoRoot, candidatePath))) {
|
|
709
|
+
return {
|
|
710
|
+
label: `git diff schema (packages/${candidate})`,
|
|
711
|
+
paths: [`${candidatePath}/`]
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (existsSync(path4.join(repoRoot, "src", "schema"))) {
|
|
716
|
+
return {
|
|
717
|
+
label: "git diff schema (src/schema)",
|
|
718
|
+
paths: ["src/schema/"]
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
714
723
|
function checkIfInitialDeployment(repoRoot, productionDbUrl) {
|
|
715
724
|
const schemasDir = path4.join(repoRoot, "supabase", "schemas", "declarative");
|
|
716
725
|
let schemas;
|
|
@@ -777,71 +786,32 @@ function checkIfInitialDeployment(repoRoot, productionDbUrl) {
|
|
|
777
786
|
};
|
|
778
787
|
}
|
|
779
788
|
}
|
|
780
|
-
function
|
|
781
|
-
const
|
|
782
|
-
if (
|
|
783
|
-
return
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
789
|
+
async function collectSchemaChangeSummary(repoRoot, tmpDir) {
|
|
790
|
+
const target = resolveSchemaDiffTarget(repoRoot);
|
|
791
|
+
if (!target) {
|
|
792
|
+
return {
|
|
793
|
+
hasChanges: null,
|
|
794
|
+
diffLogPath: null,
|
|
795
|
+
targetLabel: null,
|
|
796
|
+
diffPaths: []
|
|
797
|
+
};
|
|
788
798
|
}
|
|
789
|
-
return null;
|
|
790
|
-
}
|
|
791
|
-
async function showSchemaDiff(repoRoot, tmpDir) {
|
|
792
799
|
const diffLog = path4.join(tmpDir, "schema-diff.log");
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const schemaPath = path4.join(detected, "src", "schema");
|
|
809
|
-
if (existsSync(path4.join(repoRoot, schemaPath))) {
|
|
810
|
-
await runLogged({
|
|
811
|
-
cwd: repoRoot,
|
|
812
|
-
env: safeEnv,
|
|
813
|
-
label: `git diff schema (${detected})`,
|
|
814
|
-
command: "git",
|
|
815
|
-
args: ["diff", "origin/main", "HEAD", "--", `${schemaPath}/`],
|
|
816
|
-
logFile: diffLog
|
|
817
|
-
});
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
|
|
822
|
-
const candidatePath = path4.join("packages", candidate, "src", "schema");
|
|
823
|
-
if (existsSync(path4.join(repoRoot, candidatePath))) {
|
|
824
|
-
await runLogged({
|
|
825
|
-
cwd: repoRoot,
|
|
826
|
-
env: safeEnv,
|
|
827
|
-
label: `git diff schema (packages/${candidate})`,
|
|
828
|
-
command: "git",
|
|
829
|
-
args: ["diff", "origin/main", "HEAD", "--", `${candidatePath}/`],
|
|
830
|
-
logFile: diffLog
|
|
831
|
-
});
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (existsSync(path4.join(repoRoot, "src", "schema"))) {
|
|
836
|
-
await runLogged({
|
|
837
|
-
cwd: repoRoot,
|
|
838
|
-
env: safeEnv,
|
|
839
|
-
label: "git diff schema (src/schema)",
|
|
840
|
-
command: "git",
|
|
841
|
-
args: ["diff", "origin/main", "HEAD", "--", "src/schema/"],
|
|
842
|
-
logFile: diffLog
|
|
843
|
-
});
|
|
844
|
-
}
|
|
800
|
+
const diffResult = await runLogged({
|
|
801
|
+
cwd: repoRoot,
|
|
802
|
+
env: getSafeEnv(),
|
|
803
|
+
label: target.label,
|
|
804
|
+
command: "git",
|
|
805
|
+
args: ["diff", "origin/main", "HEAD", "--", ...target.paths],
|
|
806
|
+
logFile: diffLog
|
|
807
|
+
});
|
|
808
|
+
const rawDiff = String(diffResult.stdout ?? "");
|
|
809
|
+
return {
|
|
810
|
+
hasChanges: rawDiff.trim().length > 0,
|
|
811
|
+
diffLogPath: diffLog,
|
|
812
|
+
targetLabel: target.label,
|
|
813
|
+
diffPaths: target.paths
|
|
814
|
+
};
|
|
845
815
|
}
|
|
846
816
|
async function detectRisks(repoRoot, tmpDir, timeoutMs) {
|
|
847
817
|
const logFile = path4.join(tmpDir, "db-risks.log");
|
|
@@ -859,8 +829,8 @@ async function detectRisks(repoRoot, tmpDir, timeoutMs) {
|
|
|
859
829
|
} catch (error) {
|
|
860
830
|
let logContent = "";
|
|
861
831
|
try {
|
|
862
|
-
const { readFileSync:
|
|
863
|
-
logContent =
|
|
832
|
+
const { readFileSync: readFileSync4 } = await import('fs');
|
|
833
|
+
logContent = readFileSync4(logFile, "utf-8");
|
|
864
834
|
} catch {
|
|
865
835
|
}
|
|
866
836
|
const isInitialDeployment = logContent.includes("No common ancestor") || logContent.includes("INITIAL DEPLOYMENT");
|
|
@@ -909,15 +879,12 @@ async function snapshotRestoreLatest(repoRoot, tmpDir, productionDbUrlAdmin, tim
|
|
|
909
879
|
timeoutMs
|
|
910
880
|
});
|
|
911
881
|
}
|
|
912
|
-
function
|
|
913
|
-
const hazards = [];
|
|
882
|
+
function summarizePlanSql(sql) {
|
|
914
883
|
let statements = 0;
|
|
915
884
|
const changes = [];
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
const alterMatches = logContent.match(/ALTER\s+(TABLE|COLUMN|POLICY)/gi);
|
|
920
|
-
const dropMatches = logContent.match(/DROP\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
|
|
885
|
+
const createMatches = sql.match(/CREATE\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
|
|
886
|
+
const alterMatches = sql.match(/ALTER\s+(TABLE|COLUMN|POLICY)/gi);
|
|
887
|
+
const dropMatches = sql.match(/DROP\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
|
|
921
888
|
const creates = createMatches?.length ?? 0;
|
|
922
889
|
const alters = alterMatches?.length ?? 0;
|
|
923
890
|
const drops = dropMatches?.length ?? 0;
|
|
@@ -925,87 +892,123 @@ function parseApplyLog(logContent) {
|
|
|
925
892
|
if (creates > 0) changes.push(`${creates} CREATE`);
|
|
926
893
|
if (alters > 0) changes.push(`${alters} ALTER`);
|
|
927
894
|
if (drops > 0) changes.push(`${drops} DROP`);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
for (const match of hazardMatches) {
|
|
931
|
-
hazardSet.add(`${match[1]}: ${match[2]}`);
|
|
932
|
-
}
|
|
933
|
-
hazards.push(...hazardSet);
|
|
934
|
-
const retryMatch = logContent.match(/Retry (\d+)\/(\d+) after (\d+)ms/g);
|
|
935
|
-
if (retryMatch) {
|
|
936
|
-
retryAttempts = retryMatch.length;
|
|
937
|
-
const waitMatches = logContent.matchAll(/after (\d+)ms/g);
|
|
938
|
-
retryWaitMs = 0;
|
|
939
|
-
for (const m of waitMatches) {
|
|
940
|
-
retryWaitMs += Number.parseInt(m[1], 10);
|
|
941
|
-
}
|
|
895
|
+
if (!sql.trim()) {
|
|
896
|
+
return { statements: 0, summary: "No schema changes" };
|
|
942
897
|
}
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
898
|
+
const summary = changes.length > 0 ? changes.join(", ") : "Schema applied";
|
|
899
|
+
return { statements, summary };
|
|
900
|
+
}
|
|
901
|
+
function summarizeApplyResult(result) {
|
|
902
|
+
const sql = result.filteredPlanSql ?? result.planSql ?? "";
|
|
903
|
+
const summaryFromSql = summarizePlanSql(sql);
|
|
904
|
+
const statements = result.planSummary?.effectiveStatements ?? summaryFromSql.statements;
|
|
905
|
+
if (!sql.trim() || statements === 0 || result.planner?.hasChanges === false) {
|
|
906
|
+
return {
|
|
907
|
+
statements: 0,
|
|
908
|
+
summary: "No schema changes"
|
|
909
|
+
};
|
|
946
910
|
}
|
|
947
|
-
if (
|
|
948
|
-
return {
|
|
911
|
+
if (summaryFromSql.summary !== "Schema applied") {
|
|
912
|
+
return {
|
|
913
|
+
statements,
|
|
914
|
+
summary: summaryFromSql.summary
|
|
915
|
+
};
|
|
949
916
|
}
|
|
950
|
-
|
|
951
|
-
|
|
917
|
+
return {
|
|
918
|
+
statements,
|
|
919
|
+
summary: `${statements} structural statement${statements === 1 ? "" : "s"} applied`
|
|
920
|
+
};
|
|
952
921
|
}
|
|
953
|
-
async function
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
922
|
+
async function withTemporaryEnv(overrides, run) {
|
|
923
|
+
const previous = /* @__PURE__ */ new Map();
|
|
924
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
925
|
+
previous.set(key, process.env[key]);
|
|
926
|
+
if (value === void 0) {
|
|
927
|
+
delete process.env[key];
|
|
928
|
+
} else {
|
|
929
|
+
process.env[key] = value;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
return await run();
|
|
934
|
+
} finally {
|
|
935
|
+
for (const [key, value] of previous.entries()) {
|
|
936
|
+
if (value === void 0) {
|
|
937
|
+
delete process.env[key];
|
|
938
|
+
} else {
|
|
939
|
+
process.env[key] = value;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
966
942
|
}
|
|
967
|
-
|
|
968
|
-
|
|
943
|
+
}
|
|
944
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
945
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
946
|
+
return promise;
|
|
969
947
|
}
|
|
970
|
-
|
|
971
|
-
|
|
948
|
+
let timer;
|
|
949
|
+
try {
|
|
950
|
+
return await Promise.race([
|
|
951
|
+
promise,
|
|
952
|
+
new Promise((_, reject) => {
|
|
953
|
+
timer = setTimeout(() => {
|
|
954
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
955
|
+
}, timeoutMs);
|
|
956
|
+
timer.unref?.();
|
|
957
|
+
})
|
|
958
|
+
]);
|
|
959
|
+
} finally {
|
|
960
|
+
if (timer) {
|
|
961
|
+
clearTimeout(timer);
|
|
962
|
+
}
|
|
972
963
|
}
|
|
973
|
-
|
|
964
|
+
}
|
|
965
|
+
async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, productionDbUrl, options) {
|
|
966
|
+
const logPath = path4.join(tmpDir, "db-apply-production.json");
|
|
974
967
|
const startTime = Date.now();
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
968
|
+
const plannerArtifactPath = getDbPlanArtifactPath({
|
|
969
|
+
repoRoot,
|
|
970
|
+
environment: "production",
|
|
971
|
+
previewProfile: "full"
|
|
972
|
+
});
|
|
973
|
+
const result = await withTemporaryEnv(
|
|
974
|
+
{
|
|
978
975
|
...getFilteredEnv(),
|
|
979
|
-
// PRD Naming Convention:
|
|
980
|
-
// - GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff)
|
|
981
|
-
// - GH_DATABASE_URL = drizzle_app role (app runtime)
|
|
982
976
|
GH_DATABASE_URL_ADMIN: productionDbUrlAdmin,
|
|
983
977
|
GH_DATABASE_URL: productionDbUrl || productionDbUrlAdmin
|
|
984
978
|
},
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
979
|
+
() => withTimeout(
|
|
980
|
+
runDbApply("production", {
|
|
981
|
+
verbose: true,
|
|
982
|
+
seed: false,
|
|
983
|
+
autoApprove: true,
|
|
984
|
+
allowDataLoss: options?.allowDataLoss,
|
|
985
|
+
confirmAuthzUpdate: options?.confirmAuthzUpdate,
|
|
986
|
+
maxLockWaitMs: options?.maxLockWaitMs,
|
|
987
|
+
commandLabel: `runa db apply production --from-plan ${path4.relative(repoRoot, plannerArtifactPath)}`,
|
|
988
|
+
fromPlan: plannerArtifactPath
|
|
989
|
+
}),
|
|
990
|
+
options?.timeoutMs,
|
|
991
|
+
"db apply production"
|
|
992
|
+
)
|
|
993
|
+
);
|
|
994
|
+
await writeFile(logPath, `${JSON.stringify(result, null, 2)}
|
|
995
|
+
`, "utf-8");
|
|
996
|
+
if (result.error) {
|
|
997
|
+
throw new Error(result.error);
|
|
997
998
|
}
|
|
998
|
-
const
|
|
999
|
+
const totalMs = Date.now() - startTime;
|
|
1000
|
+
const parsed = summarizeApplyResult(result);
|
|
999
1001
|
return {
|
|
1000
1002
|
statementsExecuted: parsed.statements,
|
|
1001
|
-
hazards:
|
|
1003
|
+
hazards: result.hazards,
|
|
1002
1004
|
changeSummary: parsed.summary,
|
|
1003
1005
|
logPath,
|
|
1004
1006
|
metrics: {
|
|
1005
|
-
totalMs,
|
|
1006
|
-
retryAttempts:
|
|
1007
|
-
retryWaitMs:
|
|
1008
|
-
}
|
|
1007
|
+
totalMs: result.metrics?.totalMs ?? totalMs,
|
|
1008
|
+
retryAttempts: result.metrics?.retryAttempts,
|
|
1009
|
+
retryWaitMs: result.metrics?.retryWaitMs
|
|
1010
|
+
},
|
|
1011
|
+
planner: result.planner
|
|
1009
1012
|
};
|
|
1010
1013
|
}
|
|
1011
1014
|
async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
|
|
@@ -1019,30 +1022,17 @@ async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
|
|
|
1019
1022
|
logFile: path4.join(tmpDir, "git-commit-message.log")
|
|
1020
1023
|
});
|
|
1021
1024
|
const commitMsg = String(msg.stdout ?? "").trim();
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
schemaPaths.push("src/schema/");
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
const diff = await runLogged({
|
|
1038
|
-
cwd: repoRoot,
|
|
1039
|
-
env: safeEnv,
|
|
1040
|
-
label: "git diff (schema)",
|
|
1041
|
-
command: "git",
|
|
1042
|
-
args: ["diff", "origin/main", "HEAD", "--", ...schemaPaths],
|
|
1043
|
-
logFile: path4.join(tmpDir, "git-schema-diff.log")
|
|
1044
|
-
});
|
|
1045
|
-
const diffRaw = String(diff.stdout ?? "");
|
|
1025
|
+
const target = resolveSchemaDiffTarget(repoRoot);
|
|
1026
|
+
const diffRaw = target ? String(
|
|
1027
|
+
(await runLogged({
|
|
1028
|
+
cwd: repoRoot,
|
|
1029
|
+
env: safeEnv,
|
|
1030
|
+
label: target.label,
|
|
1031
|
+
command: "git",
|
|
1032
|
+
args: ["diff", "origin/main", "HEAD", "--", ...target.paths],
|
|
1033
|
+
logFile: path4.join(tmpDir, "git-schema-diff.log")
|
|
1034
|
+
})).stdout ?? ""
|
|
1035
|
+
) : "";
|
|
1046
1036
|
const diffLimited = diffRaw.length > 1e4 ? diffRaw.slice(0, 1e4) : diffRaw;
|
|
1047
1037
|
await runLogged({
|
|
1048
1038
|
cwd: repoRoot,
|
|
@@ -1102,6 +1092,24 @@ async function notifyDeployment(repoRoot, tmpDir, params) {
|
|
|
1102
1092
|
}
|
|
1103
1093
|
|
|
1104
1094
|
// src/commands/ci/commands/ci-prod-github.ts
|
|
1095
|
+
function formatPlannerSource(result) {
|
|
1096
|
+
const planner = result.planner;
|
|
1097
|
+
if (!planner) return null;
|
|
1098
|
+
if (planner.source === "artifact") {
|
|
1099
|
+
return "Reused checked full preview artifact";
|
|
1100
|
+
}
|
|
1101
|
+
if (planner.reuseAttempted) {
|
|
1102
|
+
return "Inline planning after checked artifact fallback";
|
|
1103
|
+
}
|
|
1104
|
+
return "Inline planning";
|
|
1105
|
+
}
|
|
1106
|
+
function formatPlannerFallback(result) {
|
|
1107
|
+
const planner = result.planner;
|
|
1108
|
+
if (!planner?.reuseAttempted || planner.source === "artifact" || !planner.reuseReason) {
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
return planner.reuseMessage ? `${planner.reuseReason}: ${planner.reuseMessage}` : planner.reuseReason;
|
|
1112
|
+
}
|
|
1105
1113
|
async function maybeNotifyExternal(params) {
|
|
1106
1114
|
if (params.skipNotify) return;
|
|
1107
1115
|
const runaAppUrl = process.env.RUNA_APP_URL?.trim() || "";
|
|
@@ -1142,6 +1150,17 @@ async function maybeAddGithubLabelAndComment(params) {
|
|
|
1142
1150
|
if (result.statementsExecuted > 0) {
|
|
1143
1151
|
changeSummaryLines.push(`- **Statements executed**: ${result.statementsExecuted}`);
|
|
1144
1152
|
}
|
|
1153
|
+
const plannerSource = formatPlannerSource(result);
|
|
1154
|
+
if (plannerSource) {
|
|
1155
|
+
changeSummaryLines.push(`- **Plan source**: ${plannerSource}`);
|
|
1156
|
+
}
|
|
1157
|
+
const plannerFallback = formatPlannerFallback(result);
|
|
1158
|
+
if (plannerFallback) {
|
|
1159
|
+
changeSummaryLines.push(`- **Plan reuse fallback**: ${plannerFallback}`);
|
|
1160
|
+
}
|
|
1161
|
+
if (result.planner?.hasChanges === false) {
|
|
1162
|
+
changeSummaryLines.push("- **Plan result**: No schema changes");
|
|
1163
|
+
}
|
|
1145
1164
|
if (result.hazards.length > 0) {
|
|
1146
1165
|
const hazardList = result.hazards.map((h) => ` - ${h}`).join("\n");
|
|
1147
1166
|
changeSummaryLines.push(`- **Hazards**:
|
|
@@ -1194,135 +1213,6 @@ async function maybePostFailureComment(params) {
|
|
|
1194
1213
|
}
|
|
1195
1214
|
}
|
|
1196
1215
|
|
|
1197
|
-
// src/commands/ci/commands/ci-prod-utils.ts
|
|
1198
|
-
init_esm_shims();
|
|
1199
|
-
function requireEnv(name) {
|
|
1200
|
-
const v = process.env[name];
|
|
1201
|
-
if (v && v.trim().length > 0) return v.trim();
|
|
1202
|
-
throw new CLIError(
|
|
1203
|
-
`Missing required environment variable: ${name}`,
|
|
1204
|
-
"CI_INPUT_MISSING",
|
|
1205
|
-
[`Set ${name} in GitHub Actions secrets/env`, "If unsure, run: runa check"],
|
|
1206
|
-
void 0,
|
|
1207
|
-
10
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
function resolveRepoKind() {
|
|
1211
|
-
const env = detectEnvironment(process.cwd());
|
|
1212
|
-
const result = env === "runa-repo" ? "monorepo" : env === "pj-repo" ? "pj-repo" : "unknown";
|
|
1213
|
-
if (process.env.RUNA_DEBUG === "true") {
|
|
1214
|
-
console.error("[DEBUG:resolveRepoKind]", {
|
|
1215
|
-
cwd: process.cwd(),
|
|
1216
|
-
detectedEnv: env,
|
|
1217
|
-
result
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
return result;
|
|
1221
|
-
}
|
|
1222
|
-
function createInitialSummary(params) {
|
|
1223
|
-
return {
|
|
1224
|
-
version: "1.0",
|
|
1225
|
-
mode: params.mode,
|
|
1226
|
-
command: "ci prod-apply",
|
|
1227
|
-
status: "failure",
|
|
1228
|
-
startedAt: params.startedAt.toISOString(),
|
|
1229
|
-
endedAt: params.startedAt.toISOString(),
|
|
1230
|
-
durationMs: 0,
|
|
1231
|
-
repoKind: resolveRepoKind(),
|
|
1232
|
-
detected: {},
|
|
1233
|
-
diagnostics: {},
|
|
1234
|
-
steps: {},
|
|
1235
|
-
layers: {},
|
|
1236
|
-
errors: []
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
function requireCiAutoApprove(params) {
|
|
1240
|
-
if (params.mode !== "github-actions") return;
|
|
1241
|
-
if (params.autoApprove === true) return;
|
|
1242
|
-
throw new CLIError(
|
|
1243
|
-
"Missing required flag: --auto-approve (required in CI mode)",
|
|
1244
|
-
"CI_AUTO_APPROVE_REQUIRED",
|
|
1245
|
-
["Re-run with: runa ci prod-apply --auto-approve", "Keep CI non-interactive and deterministic"],
|
|
1246
|
-
void 0,
|
|
1247
|
-
10
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
function resolveProdApplyInputs() {
|
|
1251
|
-
const productionDatabaseUrlAdmin = requireEnv("GH_DATABASE_URL_ADMIN");
|
|
1252
|
-
const productionDatabaseUrl = requireEnv("GH_DATABASE_URL");
|
|
1253
|
-
addGithubMask(productionDatabaseUrlAdmin);
|
|
1254
|
-
addGithubMask(productionDatabaseUrl);
|
|
1255
|
-
return {
|
|
1256
|
-
productionDatabaseUrlAdmin,
|
|
1257
|
-
productionDatabaseUrl,
|
|
1258
|
-
githubSha: process.env.GITHUB_SHA ?? "unknown",
|
|
1259
|
-
githubActor: process.env.GITHUB_ACTOR ?? "unknown",
|
|
1260
|
-
githubRepository: process.env.GITHUB_REPOSITORY ?? "unknown"
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
function setSummaryErrorFromUnknown(summary, error) {
|
|
1264
|
-
if (error instanceof CLIError) {
|
|
1265
|
-
summary.errors.push({
|
|
1266
|
-
code: error.code ?? "CI_ERROR",
|
|
1267
|
-
message: error.message,
|
|
1268
|
-
step: "ci prod-apply",
|
|
1269
|
-
details: error.cause instanceof Error ? error.cause.message : void 0
|
|
1270
|
-
});
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
if (error instanceof Error) {
|
|
1274
|
-
summary.errors.push({ code: "CI_ERROR", message: error.message, step: "ci prod-apply" });
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
summary.errors.push({ code: "CI_ERROR", message: String(error), step: "ci prod-apply" });
|
|
1278
|
-
}
|
|
1279
|
-
function buildCiProdApplyStepSummaryMarkdown(params) {
|
|
1280
|
-
const { summary } = params;
|
|
1281
|
-
const lines = [];
|
|
1282
|
-
const statusEmoji = summary.status === "success" ? "\u2705" : "\u274C";
|
|
1283
|
-
const duration = formatDuration(summary.durationMs);
|
|
1284
|
-
lines.push(
|
|
1285
|
-
`## ${statusEmoji} Production Deploy ${summary.status === "success" ? "Completed" : "Failed"}`
|
|
1286
|
-
);
|
|
1287
|
-
lines.push("");
|
|
1288
|
-
lines.push(`**Duration**: ${duration}`);
|
|
1289
|
-
lines.push("");
|
|
1290
|
-
if (summary.dbOutcome) {
|
|
1291
|
-
lines.push(`**Exit mode**: \`${summary.dbOutcome.exitMode}\``);
|
|
1292
|
-
const failedPhase = summary.dbOutcome.phases.find(
|
|
1293
|
-
(phase) => phase.status === "failed" || phase.status === "timeout"
|
|
1294
|
-
);
|
|
1295
|
-
if (failedPhase) {
|
|
1296
|
-
lines.push(`**Failed phase**: \`${failedPhase.id}\``);
|
|
1297
|
-
}
|
|
1298
|
-
if (summary.dbOutcome.summary.warnings > 0) {
|
|
1299
|
-
lines.push(`**Warnings**: ${summary.dbOutcome.summary.warnings}`);
|
|
1300
|
-
}
|
|
1301
|
-
lines.push("");
|
|
1302
|
-
}
|
|
1303
|
-
if (summary.errors.length > 0) {
|
|
1304
|
-
lines.push("### \u274C Errors");
|
|
1305
|
-
lines.push("");
|
|
1306
|
-
for (const e of summary.errors) {
|
|
1307
|
-
lines.push(`- **${e.code}**: ${e.message}`);
|
|
1308
|
-
}
|
|
1309
|
-
lines.push("");
|
|
1310
|
-
}
|
|
1311
|
-
lines.push("<details>");
|
|
1312
|
-
lines.push("<summary>\u{1F4CB} Technical Details</summary>");
|
|
1313
|
-
lines.push("");
|
|
1314
|
-
lines.push(`- Command: \`${summary.command}\``);
|
|
1315
|
-
lines.push(`- Mode: \`${summary.mode}\``);
|
|
1316
|
-
lines.push(`- Summary: \`${params.summaryPath}\``);
|
|
1317
|
-
if (summary.dbOutcome) {
|
|
1318
|
-
lines.push(`- DB exit mode: \`${summary.dbOutcome.exitMode}\``);
|
|
1319
|
-
}
|
|
1320
|
-
lines.push("");
|
|
1321
|
-
lines.push("</details>");
|
|
1322
|
-
lines.push("");
|
|
1323
|
-
return lines.join("\n");
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
1216
|
// src/commands/ci/commands/ci-prod-workflow.ts
|
|
1327
1217
|
init_esm_shims();
|
|
1328
1218
|
|
|
@@ -1523,8 +1413,8 @@ var CiProdApplyWorkflow = class {
|
|
|
1523
1413
|
id: "schema-diff",
|
|
1524
1414
|
description: "Show schema diff",
|
|
1525
1415
|
run: async (ctx) => {
|
|
1526
|
-
await
|
|
1527
|
-
ctx.hasSchemaChanges =
|
|
1416
|
+
ctx.schemaChangeSummary = await collectSchemaChangeSummary(ctx.repoRoot, ctx.tmpDir);
|
|
1417
|
+
ctx.hasSchemaChanges = ctx.schemaChangeSummary.hasChanges;
|
|
1528
1418
|
if (ctx.hasSchemaChanges === false) {
|
|
1529
1419
|
ctx.logger.info("No schema changes detected \u2014 schema apply steps will be skipped");
|
|
1530
1420
|
} else if (ctx.hasSchemaChanges === null) {
|
|
@@ -1621,6 +1511,17 @@ var CiProdApplyWorkflow = class {
|
|
|
1621
1511
|
timeoutMs: ctx.options.applyTimeoutMs
|
|
1622
1512
|
}
|
|
1623
1513
|
);
|
|
1514
|
+
if (ctx.schemaApplyResult.planner) {
|
|
1515
|
+
ctx.summary.detected.dbPlanSource = ctx.schemaApplyResult.planner.source;
|
|
1516
|
+
ctx.summary.detected.dbPlanArtifactPath = ctx.schemaApplyResult.planner.path;
|
|
1517
|
+
ctx.summary.detected.dbPlanReuseAttempted = ctx.schemaApplyResult.planner.reuseAttempted;
|
|
1518
|
+
ctx.summary.detected.dbPlanReuseReason = ctx.schemaApplyResult.planner.reuseReason;
|
|
1519
|
+
ctx.summary.detected.dbPlanReuseMessage = ctx.schemaApplyResult.planner.reuseMessage;
|
|
1520
|
+
ctx.summary.detected.dbPlanHasChanges = ctx.schemaApplyResult.planner.hasChanges;
|
|
1521
|
+
ctx.summary.detected.dbSourceFingerprint = ctx.schemaApplyResult.planner.sourceFingerprint;
|
|
1522
|
+
ctx.summary.detected.dbTargetFingerprint = ctx.schemaApplyResult.planner.targetFingerprint;
|
|
1523
|
+
ctx.summary.detected.dbTargetFingerprintMatched = ctx.schemaApplyResult.planner.targetFingerprintMatched;
|
|
1524
|
+
}
|
|
1624
1525
|
} catch (error) {
|
|
1625
1526
|
ctx.logger.error("Schema apply failed; attempting rollback from snapshot");
|
|
1626
1527
|
await snapshotRestoreLatest(
|
|
@@ -1712,18 +1613,18 @@ var CiProdApplyWorkflow = class {
|
|
|
1712
1613
|
console.log(`${prefix} done (${duration}ms)`);
|
|
1713
1614
|
} catch (error) {
|
|
1714
1615
|
const message = error instanceof Error ? error.message : String(error);
|
|
1715
|
-
const
|
|
1616
|
+
const classified = classifyCiProdApplyError(error);
|
|
1716
1617
|
phases.push({
|
|
1717
1618
|
id: step.id,
|
|
1718
1619
|
label: step.description,
|
|
1719
|
-
status:
|
|
1620
|
+
status: classified.retryable ? "timeout" : "failed",
|
|
1720
1621
|
startedAt: new Date(startedAt).toISOString(),
|
|
1721
1622
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1722
1623
|
durationMs: Date.now() - startedAt,
|
|
1723
1624
|
error: {
|
|
1724
|
-
code:
|
|
1625
|
+
code: classified.code === "CI_ERROR" ? "CI_PROD_APPLY_FAILED" : classified.code,
|
|
1725
1626
|
message,
|
|
1726
|
-
retryable:
|
|
1627
|
+
retryable: classified.retryable,
|
|
1727
1628
|
phase: step.id
|
|
1728
1629
|
}
|
|
1729
1630
|
});
|
|
@@ -1756,22 +1657,6 @@ var CiProdApplyWorkflow = class {
|
|
|
1756
1657
|
}
|
|
1757
1658
|
};
|
|
1758
1659
|
|
|
1759
|
-
// src/commands/ci/commands/ci-resolvers.ts
|
|
1760
|
-
init_esm_shims();
|
|
1761
|
-
|
|
1762
|
-
// src/commands/ci/utils/config-readers.ts
|
|
1763
|
-
init_esm_shims();
|
|
1764
|
-
|
|
1765
|
-
// src/commands/ci/commands/ci-resolvers.ts
|
|
1766
|
-
function resolveMode(modeRaw) {
|
|
1767
|
-
if (modeRaw === "github-actions" || modeRaw === "local") return modeRaw;
|
|
1768
|
-
return process.env.GITHUB_ACTIONS === "true" ? "github-actions" : "local";
|
|
1769
|
-
}
|
|
1770
|
-
function parseIntOr(value, fallback) {
|
|
1771
|
-
const n = Number.parseInt(String(value ?? ""), 10);
|
|
1772
|
-
return Number.isNaN(n) ? fallback : n;
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
1660
|
// src/commands/ci/utils/workflow-idempotency.ts
|
|
1776
1661
|
init_esm_shims();
|
|
1777
1662
|
var PROD_DEPLOY_LOCK_ID = 88889;
|
|
@@ -1878,7 +1763,7 @@ function guardProductionDeployment(dbUrl, commit) {
|
|
|
1878
1763
|
};
|
|
1879
1764
|
}
|
|
1880
1765
|
if (!idempotencyResult.checkSuccessful && idempotencyResult.error) {
|
|
1881
|
-
|
|
1766
|
+
process.stderr.write(
|
|
1882
1767
|
`[WARN] Idempotency check failed: ${idempotencyResult.error}. Proceeding with deployment.`
|
|
1883
1768
|
);
|
|
1884
1769
|
}
|
|
@@ -2029,7 +1914,7 @@ var ciProdApplyCommand = new Command("prod-apply").description("Apply production
|
|
|
2029
1914
|
console.log(`status: ${summary.status}`);
|
|
2030
1915
|
console.log(`summary: ${path4.relative(repoRoot, summaryPath)}`);
|
|
2031
1916
|
logNextActions([
|
|
2032
|
-
"Inspect .runa/tmp
|
|
1917
|
+
"Inspect .runa/tmp artifacts (db-apply-production.json, snapshot-*.log)",
|
|
2033
1918
|
"If rollback succeeded, database should be restored to pre-deploy snapshot"
|
|
2034
1919
|
]);
|
|
2035
1920
|
await appendGithubStepSummary(
|
|
@@ -4170,6 +4055,7 @@ function buildSkipResult(hasUrl) {
|
|
|
4170
4055
|
return {
|
|
4171
4056
|
preview: {
|
|
4172
4057
|
executed: false,
|
|
4058
|
+
profile: null,
|
|
4173
4059
|
planSql: null,
|
|
4174
4060
|
hazards: [],
|
|
4175
4061
|
hasChanges: false,
|
|
@@ -4256,41 +4142,6 @@ function extractSqlFromSchemaChanges(fullOutput) {
|
|
|
4256
4142
|
}
|
|
4257
4143
|
return null;
|
|
4258
4144
|
}
|
|
4259
|
-
function getIdempotentRoleNames(repoRoot) {
|
|
4260
|
-
const idempotentDir = path4.join(repoRoot, "supabase", "schemas", "idempotent");
|
|
4261
|
-
const roles = [];
|
|
4262
|
-
try {
|
|
4263
|
-
if (!existsSync(idempotentDir)) return [];
|
|
4264
|
-
const files = readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
|
|
4265
|
-
for (const file of files) {
|
|
4266
|
-
const content = readFileSync(path4.join(idempotentDir, file), "utf-8");
|
|
4267
|
-
const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
|
|
4268
|
-
for (const match of roleMatches) {
|
|
4269
|
-
if (match[1]) roles.push(match[1].toLowerCase());
|
|
4270
|
-
}
|
|
4271
|
-
const existsMatches = content.matchAll(/rolname\s*=\s*'(\w+)'/gi);
|
|
4272
|
-
for (const match of existsMatches) {
|
|
4273
|
-
if (match[1] && !roles.includes(match[1].toLowerCase())) {
|
|
4274
|
-
roles.push(match[1].toLowerCase());
|
|
4275
|
-
}
|
|
4276
|
-
}
|
|
4277
|
-
}
|
|
4278
|
-
} catch {
|
|
4279
|
-
}
|
|
4280
|
-
return [...new Set(roles)];
|
|
4281
|
-
}
|
|
4282
|
-
function isIdempotentRoleHazard(hazardType, hazardMessage, causingSql, idempotentRoles) {
|
|
4283
|
-
if (hazardType.toUpperCase() !== "AUTHZ_UPDATE") return false;
|
|
4284
|
-
if (idempotentRoles.length === 0) return false;
|
|
4285
|
-
const sql = (causingSql || "").trim().toLowerCase();
|
|
4286
|
-
const isGrantRevoke = /^\s*(?:grant|revoke)\b/i.test(sql);
|
|
4287
|
-
if (sql && !isGrantRevoke) return false;
|
|
4288
|
-
const textToCheck = sql || hazardMessage.toLowerCase();
|
|
4289
|
-
for (const role of idempotentRoles) {
|
|
4290
|
-
if (textToCheck.includes(role)) return true;
|
|
4291
|
-
}
|
|
4292
|
-
return false;
|
|
4293
|
-
}
|
|
4294
4145
|
function getHazardSeverity(type) {
|
|
4295
4146
|
switch (type.toUpperCase()) {
|
|
4296
4147
|
case "DELETES_DATA":
|
|
@@ -4332,20 +4183,26 @@ function createHazardDetail(type, message, causingSql) {
|
|
|
4332
4183
|
causingSql
|
|
4333
4184
|
};
|
|
4334
4185
|
}
|
|
4335
|
-
function collectCommentHazards(lines,
|
|
4186
|
+
function collectCommentHazards(lines, schemasDir) {
|
|
4336
4187
|
const hazards = [];
|
|
4337
4188
|
for (let i = 0; i < lines.length; i++) {
|
|
4338
4189
|
const parsed = parseHazardLine(lines[i] ?? "");
|
|
4339
4190
|
if (!parsed) continue;
|
|
4340
4191
|
const causingSql = findCausingSqlLine(lines, i);
|
|
4341
|
-
|
|
4192
|
+
const registryHazard = {
|
|
4193
|
+
type: parsed.type,
|
|
4194
|
+
message: parsed.message,
|
|
4195
|
+
fullMatch: lines[i] ?? "",
|
|
4196
|
+
causingSql
|
|
4197
|
+
};
|
|
4198
|
+
if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
|
|
4342
4199
|
continue;
|
|
4343
4200
|
}
|
|
4344
4201
|
hazards.push(createHazardDetail(parsed.type, parsed.message, causingSql));
|
|
4345
4202
|
}
|
|
4346
4203
|
return hazards;
|
|
4347
4204
|
}
|
|
4348
|
-
function appendEmojiHazards(fullOutput,
|
|
4205
|
+
function appendEmojiHazards(fullOutput, schemasDir, hazards) {
|
|
4349
4206
|
const hazardPrefixes = ["\u{1F534}", "\u{1F7E0}", "\u{1F7E1}", "\u26A0"];
|
|
4350
4207
|
for (const line of fullOutput.split("\n")) {
|
|
4351
4208
|
const trimmed = line.trim();
|
|
@@ -4356,7 +4213,12 @@ function appendEmojiHazards(fullOutput, idempotentRoles, hazards) {
|
|
|
4356
4213
|
if (!match?.[1] || !match[2]) continue;
|
|
4357
4214
|
const type = match[1].toUpperCase();
|
|
4358
4215
|
const message = match[2].trim();
|
|
4359
|
-
|
|
4216
|
+
const registryHazard = {
|
|
4217
|
+
type,
|
|
4218
|
+
message,
|
|
4219
|
+
fullMatch: trimmed
|
|
4220
|
+
};
|
|
4221
|
+
if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
|
|
4360
4222
|
continue;
|
|
4361
4223
|
}
|
|
4362
4224
|
const isDuplicate = hazards.some(
|
|
@@ -4369,9 +4231,9 @@ function appendEmojiHazards(fullOutput, idempotentRoles, hazards) {
|
|
|
4369
4231
|
}
|
|
4370
4232
|
function extractHazardsWithContext(fullOutput, repoRoot) {
|
|
4371
4233
|
const lines = fullOutput.split("\n");
|
|
4372
|
-
const
|
|
4373
|
-
const hazards = collectCommentHazards(lines,
|
|
4374
|
-
appendEmojiHazards(fullOutput,
|
|
4234
|
+
const schemasDir = repoRoot ? path4.join(repoRoot, "supabase", "schemas", "declarative") : void 0;
|
|
4235
|
+
const hazards = collectCommentHazards(lines, schemasDir);
|
|
4236
|
+
appendEmojiHazards(fullOutput, schemasDir, hazards);
|
|
4375
4237
|
return hazards;
|
|
4376
4238
|
}
|
|
4377
4239
|
function stripAnsi(text) {
|
|
@@ -4388,6 +4250,7 @@ var PreviewOutputSchema = z.object({
|
|
|
4388
4250
|
filteredPlanSql: z.string().optional(),
|
|
4389
4251
|
hazards: z.array(z.string()),
|
|
4390
4252
|
checkOnly: z.boolean().optional(),
|
|
4253
|
+
previewProfile: z.enum(["compare-only", "full"]).optional(),
|
|
4391
4254
|
metrics: z.object({
|
|
4392
4255
|
totalMs: z.number(),
|
|
4393
4256
|
idempotentMs: z.number().optional(),
|
|
@@ -4411,6 +4274,7 @@ function tryParseStructuredOutput(stdout) {
|
|
|
4411
4274
|
hazards: data.hazards,
|
|
4412
4275
|
hasChanges,
|
|
4413
4276
|
checkOnly: data.checkOnly ?? false,
|
|
4277
|
+
previewProfile: data.previewProfile ?? null,
|
|
4414
4278
|
metrics: data.metrics
|
|
4415
4279
|
};
|
|
4416
4280
|
} catch {
|
|
@@ -4526,6 +4390,7 @@ function buildErrorResult(error, databaseUrl) {
|
|
|
4526
4390
|
return {
|
|
4527
4391
|
preview: {
|
|
4528
4392
|
executed: true,
|
|
4393
|
+
profile: null,
|
|
4529
4394
|
planSql: null,
|
|
4530
4395
|
hazards: [],
|
|
4531
4396
|
hasChanges: false,
|
|
@@ -4541,6 +4406,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
|
|
|
4541
4406
|
hazards: structured.hazards,
|
|
4542
4407
|
hazardDetails: void 0,
|
|
4543
4408
|
hasChanges: structured.hasChanges,
|
|
4409
|
+
profile: structured.previewProfile,
|
|
4544
4410
|
error: null
|
|
4545
4411
|
};
|
|
4546
4412
|
}
|
|
@@ -4560,6 +4426,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
|
|
|
4560
4426
|
hazards,
|
|
4561
4427
|
hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
|
|
4562
4428
|
hasChanges,
|
|
4429
|
+
profile: null,
|
|
4563
4430
|
error: enhanceConnectionError(rawError, productionUrl)
|
|
4564
4431
|
};
|
|
4565
4432
|
}
|
|
@@ -4573,6 +4440,7 @@ function buildSuccessPreviewResult(exitCode, stdout, stderr, fullOutput, repoRoo
|
|
|
4573
4440
|
hazards: preview.hazards,
|
|
4574
4441
|
hazardDetails: preview.hazardDetails,
|
|
4575
4442
|
hasChanges: preview.hasChanges,
|
|
4443
|
+
profile: preview.profile ?? null,
|
|
4576
4444
|
error: isSuccess ? null : preview.error
|
|
4577
4445
|
}
|
|
4578
4446
|
};
|
|
@@ -4586,9 +4454,11 @@ var productionPreviewActor = fromPromise(
|
|
|
4586
4454
|
}
|
|
4587
4455
|
const logFile = path4.join(tmpDir, "ci-production-preview.log");
|
|
4588
4456
|
const timeoutMs = resolveProductionPreviewTimeoutMs();
|
|
4589
|
-
console.log("\u25B6 production preview (dry-run): runa db apply production --check --compare-only");
|
|
4590
4457
|
console.log(
|
|
4591
|
-
"\u25B6 production preview
|
|
4458
|
+
"\u25B6 production preview (dry-run): runa db preview production --profile compare-only"
|
|
4459
|
+
);
|
|
4460
|
+
console.log(
|
|
4461
|
+
"\u25B6 production preview details: db preview compare-only profile (plan + hazard extraction)"
|
|
4592
4462
|
);
|
|
4593
4463
|
console.log(`\u25B6 production preview timeout: ${formatDurationMs(timeoutMs)}`);
|
|
4594
4464
|
const heartbeat = startHeartbeat("production preview");
|
|
@@ -4598,7 +4468,7 @@ var productionPreviewActor = fromPromise(
|
|
|
4598
4468
|
try {
|
|
4599
4469
|
const child = execa(
|
|
4600
4470
|
"pnpm",
|
|
4601
|
-
["exec", "runa", "db", "
|
|
4471
|
+
["exec", "runa", "db", "preview", "production", "--profile", "compare-only"],
|
|
4602
4472
|
{
|
|
4603
4473
|
cwd: repoRoot,
|
|
4604
4474
|
env: {
|
|
@@ -4785,7 +4655,6 @@ function findBaseRef(repoRoot) {
|
|
|
4785
4655
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4786
4656
|
});
|
|
4787
4657
|
if (checkOriginMain.status === 0) {
|
|
4788
|
-
console.log("[DEBUG] getSchemaGitDiff: Using origin/main as base");
|
|
4789
4658
|
return "origin/main";
|
|
4790
4659
|
}
|
|
4791
4660
|
const baseRef = process.env.GITHUB_BASE_REF;
|
|
@@ -4796,9 +4665,6 @@ function findBaseRef(repoRoot) {
|
|
|
4796
4665
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4797
4666
|
});
|
|
4798
4667
|
if (checkBaseRef.status === 0) {
|
|
4799
|
-
console.log(
|
|
4800
|
-
`[DEBUG] getSchemaGitDiff: Using origin/${baseRef} as base (from GITHUB_BASE_REF)`
|
|
4801
|
-
);
|
|
4802
4668
|
return `origin/${baseRef}`;
|
|
4803
4669
|
}
|
|
4804
4670
|
}
|
|
@@ -4808,112 +4674,64 @@ function findBaseRef(repoRoot) {
|
|
|
4808
4674
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4809
4675
|
});
|
|
4810
4676
|
if (mergeBaseResult.status === 0 && mergeBaseResult.stdout?.trim()) {
|
|
4811
|
-
console.log("[DEBUG] getSchemaGitDiff: Using merge-base as base");
|
|
4812
4677
|
return mergeBaseResult.stdout.trim();
|
|
4813
4678
|
}
|
|
4814
|
-
console.log("[DEBUG] getSchemaGitDiff: No valid base reference found");
|
|
4815
4679
|
return null;
|
|
4816
4680
|
}
|
|
4681
|
+
function extractSchemaGitDiffSnapshot(diffOutput) {
|
|
4682
|
+
const filesChanged = [];
|
|
4683
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
4684
|
+
for (const match of diffOutput.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gm)) {
|
|
4685
|
+
const filePath = match[2] ?? match[1];
|
|
4686
|
+
if (!filePath || seenFiles.has(filePath)) {
|
|
4687
|
+
continue;
|
|
4688
|
+
}
|
|
4689
|
+
seenFiles.add(filePath);
|
|
4690
|
+
filesChanged.push(filePath);
|
|
4691
|
+
}
|
|
4692
|
+
if (filesChanged.length === 0) {
|
|
4693
|
+
return null;
|
|
4694
|
+
}
|
|
4695
|
+
let linesAdded = 0;
|
|
4696
|
+
let linesDeleted = 0;
|
|
4697
|
+
for (const line of diffOutput.split("\n")) {
|
|
4698
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
4699
|
+
linesAdded += 1;
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
4703
|
+
linesDeleted += 1;
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
const maxLines = 100;
|
|
4707
|
+
const diffLines = diffOutput.split("\n");
|
|
4708
|
+
const diffSummary = diffLines.length > maxLines ? `${diffLines.slice(0, maxLines).join("\n")}
|
|
4709
|
+
... (${diffLines.length - maxLines} more lines)` : diffOutput;
|
|
4710
|
+
return {
|
|
4711
|
+
filesChanged,
|
|
4712
|
+
linesAdded,
|
|
4713
|
+
linesDeleted,
|
|
4714
|
+
diffSummary
|
|
4715
|
+
};
|
|
4716
|
+
}
|
|
4817
4717
|
function getSchemaGitDiff(repoRoot) {
|
|
4818
4718
|
try {
|
|
4819
4719
|
const baseRef = findBaseRef(repoRoot);
|
|
4820
4720
|
if (!baseRef) {
|
|
4821
|
-
console.log("[DEBUG] getSchemaGitDiff: Cannot determine base reference, skipping diff");
|
|
4822
|
-
return null;
|
|
4823
|
-
}
|
|
4824
|
-
const filesResult = spawnSync(
|
|
4825
|
-
"git",
|
|
4826
|
-
["diff", "--name-only", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
|
|
4827
|
-
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4828
|
-
);
|
|
4829
|
-
if (filesResult.status !== 0) {
|
|
4830
|
-
console.log(
|
|
4831
|
-
`[DEBUG] getSchemaGitDiff: git diff failed: ${filesResult.stderr || "unknown error"}`
|
|
4832
|
-
);
|
|
4833
4721
|
return null;
|
|
4834
4722
|
}
|
|
4835
|
-
const filesChanged = (filesResult.stdout || "").split("\n").filter((f) => f.trim().length > 0);
|
|
4836
|
-
console.log(`[DEBUG] getSchemaGitDiff: Found ${filesChanged.length} changed schema file(s)`);
|
|
4837
|
-
if (filesChanged.length === 0) {
|
|
4838
|
-
return null;
|
|
4839
|
-
}
|
|
4840
|
-
const statResult = spawnSync(
|
|
4841
|
-
"git",
|
|
4842
|
-
["diff", "--stat", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
|
|
4843
|
-
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4844
|
-
);
|
|
4845
|
-
const statOutput = statResult.stdout || "";
|
|
4846
|
-
const statMatch = statOutput.match(/(\d+) insertions?\(\+\)/);
|
|
4847
|
-
const delMatch = statOutput.match(/(\d+) deletions?\(-\)/);
|
|
4848
|
-
const linesAdded = statMatch ? Number.parseInt(statMatch[1], 10) : 0;
|
|
4849
|
-
const linesDeleted = delMatch ? Number.parseInt(delMatch[1], 10) : 0;
|
|
4850
4723
|
const diffResult = spawnSync(
|
|
4851
4724
|
"git",
|
|
4852
4725
|
["diff", "--no-color", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
|
|
4853
4726
|
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4854
4727
|
);
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
const diffLines = fullDiff.split("\n");
|
|
4858
|
-
const diffSummary = diffLines.length > maxLines ? `${diffLines.slice(0, maxLines).join("\n")}
|
|
4859
|
-
... (${diffLines.length - maxLines} more lines)` : fullDiff;
|
|
4860
|
-
return {
|
|
4861
|
-
filesChanged,
|
|
4862
|
-
linesAdded,
|
|
4863
|
-
linesDeleted,
|
|
4864
|
-
diffSummary
|
|
4865
|
-
};
|
|
4866
|
-
} catch (error) {
|
|
4867
|
-
console.log(
|
|
4868
|
-
`[DEBUG] getSchemaGitDiff: Exception: ${error instanceof Error ? error.message : String(error)}`
|
|
4869
|
-
);
|
|
4870
|
-
return null;
|
|
4871
|
-
}
|
|
4872
|
-
}
|
|
4873
|
-
function extractRolesFromSql(content) {
|
|
4874
|
-
const roles = [];
|
|
4875
|
-
const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
|
|
4876
|
-
for (const match of roleMatches) {
|
|
4877
|
-
if (match[1]) roles.push(match[1].toLowerCase());
|
|
4878
|
-
}
|
|
4879
|
-
const existsMatches = content.matchAll(/rolname\s*=\s*'(\w+)'/gi);
|
|
4880
|
-
for (const match of existsMatches) {
|
|
4881
|
-
if (match[1] && !roles.includes(match[1].toLowerCase())) {
|
|
4882
|
-
roles.push(match[1].toLowerCase());
|
|
4883
|
-
}
|
|
4884
|
-
}
|
|
4885
|
-
return roles;
|
|
4886
|
-
}
|
|
4887
|
-
function getIdempotentRoleNames2(repoRoot) {
|
|
4888
|
-
const idempotentDir = path4.join(repoRoot, "supabase", "schemas", "idempotent");
|
|
4889
|
-
const roles = [];
|
|
4890
|
-
try {
|
|
4891
|
-
const fs2 = __require("fs");
|
|
4892
|
-
if (!fs2.existsSync(idempotentDir)) return [];
|
|
4893
|
-
const files = fs2.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
|
|
4894
|
-
for (const file of files) {
|
|
4895
|
-
const filePath = path4.join(idempotentDir, file);
|
|
4896
|
-
if (!isPathContained(idempotentDir, filePath)) {
|
|
4897
|
-
continue;
|
|
4898
|
-
}
|
|
4899
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
4900
|
-
roles.push(...extractRolesFromSql(content));
|
|
4728
|
+
if (diffResult.status !== 0) {
|
|
4729
|
+
return null;
|
|
4901
4730
|
}
|
|
4731
|
+
return extractSchemaGitDiffSnapshot(diffResult.stdout || "");
|
|
4902
4732
|
} catch {
|
|
4733
|
+
return null;
|
|
4903
4734
|
}
|
|
4904
|
-
return [...new Set(roles)];
|
|
4905
|
-
}
|
|
4906
|
-
function isIdempotentRoleHazard2(hazardType, hazardMessage, idempotentRoles, causingSql) {
|
|
4907
|
-
if (hazardType !== "AUTHZ_UPDATE") return false;
|
|
4908
|
-
if (idempotentRoles.length === 0) return false;
|
|
4909
|
-
const sql = ("").trim().toLowerCase();
|
|
4910
|
-
const isGrantRevoke = /^\s*(?:grant|revoke)\b/i.test(sql);
|
|
4911
|
-
if (sql && !isGrantRevoke) return false;
|
|
4912
|
-
const textToCheck = sql || hazardMessage.toLowerCase();
|
|
4913
|
-
for (const role of idempotentRoles) {
|
|
4914
|
-
if (textToCheck.includes(role)) return true;
|
|
4915
|
-
}
|
|
4916
|
-
return false;
|
|
4917
4735
|
}
|
|
4918
4736
|
function createEmptySchemaChangeStats() {
|
|
4919
4737
|
return {
|
|
@@ -4934,13 +4752,18 @@ function resolveSqlInput(sqlInput, logPath) {
|
|
|
4934
4752
|
return null;
|
|
4935
4753
|
}
|
|
4936
4754
|
}
|
|
4937
|
-
function parseHazards(sql,
|
|
4755
|
+
function parseHazards(sql, schemasDir) {
|
|
4938
4756
|
const hazards = [];
|
|
4939
4757
|
const hazardMatches = sql.matchAll(/-- Hazard (\w+): (.+)/g);
|
|
4940
4758
|
for (const match of hazardMatches) {
|
|
4941
4759
|
const hazardType = match[1];
|
|
4942
4760
|
const hazardMessage = match[2];
|
|
4943
|
-
|
|
4761
|
+
const registryHazard = {
|
|
4762
|
+
type: hazardType,
|
|
4763
|
+
message: hazardMessage,
|
|
4764
|
+
fullMatch: match[0]
|
|
4765
|
+
};
|
|
4766
|
+
if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
|
|
4944
4767
|
continue;
|
|
4945
4768
|
}
|
|
4946
4769
|
hazards.push(`${hazardType}: ${hazardMessage}`);
|
|
@@ -4960,8 +4783,8 @@ function parseSchemaChangeStats(sqlInput, logPath, repoRoot) {
|
|
|
4960
4783
|
stats.creates.other = countMatches(sql, /CREATE (TRIGGER|TYPE|SEQUENCE|VIEW|SCHEMA)/gi);
|
|
4961
4784
|
stats.alters = countMatches(sql, /ALTER (TABLE|COLUMN|INDEX|POLICY|FUNCTION)/gi);
|
|
4962
4785
|
stats.drops = countMatches(sql, /DROP (TABLE|COLUMN|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
|
|
4963
|
-
const
|
|
4964
|
-
stats.hazards = parseHazards(sql,
|
|
4786
|
+
const schemasDir = repoRoot ? path4.join(repoRoot, "supabase", "schemas", "declarative") : void 0;
|
|
4787
|
+
stats.hazards = parseHazards(sql, schemasDir);
|
|
4965
4788
|
return stats;
|
|
4966
4789
|
}
|
|
4967
4790
|
function isCheckSummaryOutput(output) {
|
|
@@ -5235,9 +5058,9 @@ function detectSupabaseContainers() {
|
|
|
5235
5058
|
async function checkSupabasePortConflicts(repoRoot) {
|
|
5236
5059
|
let dbPort = 54322;
|
|
5237
5060
|
try {
|
|
5238
|
-
const { readFileSync:
|
|
5061
|
+
const { readFileSync: readFileSync4 } = await import('fs');
|
|
5239
5062
|
const configPath = path4.join(repoRoot, "supabase", "config.toml");
|
|
5240
|
-
const content =
|
|
5063
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
5241
5064
|
const match = /\[db\][^[]*?port\s*=\s*(\d+)/s.exec(content);
|
|
5242
5065
|
if (match?.[1]) dbPort = Number.parseInt(match[1], 10);
|
|
5243
5066
|
} catch {
|
|
@@ -5481,7 +5304,7 @@ function shouldSkipSchemaPostCheck(context) {
|
|
|
5481
5304
|
return context.mode === "ci-pr-local" && context.executionEnv === "github-actions";
|
|
5482
5305
|
}
|
|
5483
5306
|
function shouldReuseCiReferenceStats(context) {
|
|
5484
|
-
return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.hasDrift === false;
|
|
5307
|
+
return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.checkExecuted === true && context.schemaDrift?.hasDrift === false;
|
|
5485
5308
|
}
|
|
5486
5309
|
function mergeLayerResults(coreResults, e2eResults) {
|
|
5487
5310
|
return { ...coreResults, ...e2eResults };
|
|
@@ -5617,6 +5440,11 @@ init_esm_shims();
|
|
|
5617
5440
|
|
|
5618
5441
|
// src/commands/ci/commands/ci-pr-capabilities.ts
|
|
5619
5442
|
init_esm_shims();
|
|
5443
|
+
|
|
5444
|
+
// src/commands/ci/commands/layer4-discovery.ts
|
|
5445
|
+
init_esm_shims();
|
|
5446
|
+
|
|
5447
|
+
// src/commands/ci/commands/ci-pr-capabilities.ts
|
|
5620
5448
|
async function pathExists(p) {
|
|
5621
5449
|
return promises.access(p).then(() => true).catch(() => false);
|
|
5622
5450
|
}
|
|
@@ -5654,116 +5482,96 @@ async function queryScalar(params) {
|
|
|
5654
5482
|
async function detectDbCapabilities(params) {
|
|
5655
5483
|
const caps = /* @__PURE__ */ new Set();
|
|
5656
5484
|
const diagnostics = {};
|
|
5657
|
-
const
|
|
5485
|
+
const batchedSql = `
|
|
5486
|
+
SELECT json_build_object(
|
|
5487
|
+
'isPostgres', (SELECT version() LIKE 'PostgreSQL%'),
|
|
5488
|
+
'tableCount', (
|
|
5489
|
+
SELECT COUNT(*)::int
|
|
5490
|
+
FROM information_schema.tables
|
|
5491
|
+
WHERE table_type = 'BASE TABLE'
|
|
5492
|
+
AND table_schema NOT LIKE 'pg_%'
|
|
5493
|
+
AND table_schema NOT IN ('information_schema')
|
|
5494
|
+
),
|
|
5495
|
+
'rlsCount', (
|
|
5496
|
+
SELECT COUNT(*)::int
|
|
5497
|
+
FROM pg_class c
|
|
5498
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
5499
|
+
WHERE c.relkind IN ('r', 'p')
|
|
5500
|
+
AND n.nspname NOT LIKE 'pg_%'
|
|
5501
|
+
AND n.nspname NOT IN ('information_schema')
|
|
5502
|
+
AND c.relrowsecurity
|
|
5503
|
+
),
|
|
5504
|
+
'canSelectAny', (
|
|
5505
|
+
SELECT COALESCE(bool_or(has_table_privilege(current_user, tbl, 'select')), false)
|
|
5506
|
+
FROM (
|
|
5507
|
+
SELECT quote_ident(table_schema) || '.' || quote_ident(table_name) AS tbl
|
|
5508
|
+
FROM information_schema.tables
|
|
5509
|
+
WHERE table_type = 'BASE TABLE'
|
|
5510
|
+
AND table_schema NOT LIKE 'pg_%'
|
|
5511
|
+
AND table_schema NOT IN ('information_schema')
|
|
5512
|
+
LIMIT 50 -- cap sampling to prevent slow scans on large databases
|
|
5513
|
+
) candidates
|
|
5514
|
+
),
|
|
5515
|
+
'authenticatedExists', (
|
|
5516
|
+
SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated')
|
|
5517
|
+
),
|
|
5518
|
+
'isMember', (
|
|
5519
|
+
SELECT CASE
|
|
5520
|
+
WHEN EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated')
|
|
5521
|
+
THEN pg_has_role(current_user, 'authenticated', 'member')
|
|
5522
|
+
ELSE false
|
|
5523
|
+
END
|
|
5524
|
+
)
|
|
5525
|
+
)::text;
|
|
5526
|
+
`.trim();
|
|
5527
|
+
const raw = await queryScalar({
|
|
5658
5528
|
repoRoot: params.repoRoot,
|
|
5659
5529
|
tmpDir: params.tmpDir,
|
|
5660
5530
|
databaseUrl: params.databaseUrlApp,
|
|
5661
|
-
sql:
|
|
5662
|
-
logName: "cap-
|
|
5531
|
+
sql: batchedSql,
|
|
5532
|
+
logName: "cap-db-batch"
|
|
5663
5533
|
});
|
|
5664
|
-
|
|
5665
|
-
|
|
5534
|
+
let parsed;
|
|
5535
|
+
try {
|
|
5536
|
+
parsed = JSON.parse(raw);
|
|
5537
|
+
} catch {
|
|
5538
|
+
diagnostics["db.postgres"] = `Failed to parse batched query result (raw=${raw.substring(0, 200)})`;
|
|
5539
|
+
return { capabilities: caps, diagnostics };
|
|
5540
|
+
}
|
|
5541
|
+
if (parsed.isPostgres) {
|
|
5666
5542
|
caps.add("db.postgres");
|
|
5667
5543
|
diagnostics["db.postgres"] = "PostgreSQL detected via SELECT version()";
|
|
5668
5544
|
} else {
|
|
5669
|
-
diagnostics["db.postgres"] = `Not PostgreSQL
|
|
5545
|
+
diagnostics["db.postgres"] = `Not PostgreSQL`;
|
|
5670
5546
|
return { capabilities: caps, diagnostics };
|
|
5671
5547
|
}
|
|
5672
|
-
|
|
5673
|
-
repoRoot: params.repoRoot,
|
|
5674
|
-
tmpDir: params.tmpDir,
|
|
5675
|
-
databaseUrl: params.databaseUrlApp,
|
|
5676
|
-
sql: `
|
|
5677
|
-
SELECT COUNT(*)::int
|
|
5678
|
-
FROM information_schema.tables
|
|
5679
|
-
WHERE table_type='BASE TABLE'
|
|
5680
|
-
AND table_schema NOT LIKE 'pg_%'
|
|
5681
|
-
AND table_schema NOT IN ('information_schema');
|
|
5682
|
-
`.trim(),
|
|
5683
|
-
logName: "cap-table-count"
|
|
5684
|
-
});
|
|
5685
|
-
const tableCount = Number.parseInt(tablesRaw, 10);
|
|
5686
|
-
if (Number.isFinite(tableCount) && tableCount > 0) {
|
|
5548
|
+
if (Number.isFinite(parsed.tableCount) && parsed.tableCount > 0) {
|
|
5687
5549
|
caps.add("db.schemaApplied");
|
|
5688
|
-
diagnostics["db.schemaApplied"] = `Non-system base tables detected: ${tableCount}`;
|
|
5550
|
+
diagnostics["db.schemaApplied"] = `Non-system base tables detected: ${parsed.tableCount}`;
|
|
5689
5551
|
} else {
|
|
5690
|
-
diagnostics["db.schemaApplied"] = `No non-system base tables detected (
|
|
5552
|
+
diagnostics["db.schemaApplied"] = `No non-system base tables detected (count=${parsed.tableCount})`;
|
|
5691
5553
|
}
|
|
5692
|
-
|
|
5693
|
-
repoRoot: params.repoRoot,
|
|
5694
|
-
tmpDir: params.tmpDir,
|
|
5695
|
-
databaseUrl: params.databaseUrlApp,
|
|
5696
|
-
sql: `
|
|
5697
|
-
SELECT COUNT(*)::int
|
|
5698
|
-
FROM pg_class c
|
|
5699
|
-
JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
5700
|
-
WHERE c.relkind IN ('r','p')
|
|
5701
|
-
AND n.nspname NOT LIKE 'pg_%'
|
|
5702
|
-
AND n.nspname NOT IN ('information_schema')
|
|
5703
|
-
AND c.relrowsecurity;
|
|
5704
|
-
`.trim(),
|
|
5705
|
-
logName: "cap-rls-count"
|
|
5706
|
-
});
|
|
5707
|
-
const rlsCount = Number.parseInt(rlsCountRaw, 10);
|
|
5708
|
-
if (Number.isFinite(rlsCount) && rlsCount > 0) {
|
|
5554
|
+
if (Number.isFinite(parsed.rlsCount) && parsed.rlsCount > 0) {
|
|
5709
5555
|
caps.add("db.rlsEnabled");
|
|
5710
|
-
diagnostics["db.rlsEnabled"] = `RLS-enabled tables detected: ${rlsCount}`;
|
|
5556
|
+
diagnostics["db.rlsEnabled"] = `RLS-enabled tables detected: ${parsed.rlsCount}`;
|
|
5711
5557
|
} else {
|
|
5712
|
-
diagnostics["db.rlsEnabled"] = `No RLS-enabled tables detected (
|
|
5558
|
+
diagnostics["db.rlsEnabled"] = `No RLS-enabled tables detected (count=${parsed.rlsCount})`;
|
|
5713
5559
|
}
|
|
5714
|
-
|
|
5715
|
-
repoRoot: params.repoRoot,
|
|
5716
|
-
tmpDir: params.tmpDir,
|
|
5717
|
-
databaseUrl: params.databaseUrlApp,
|
|
5718
|
-
sql: `
|
|
5719
|
-
WITH candidates AS (
|
|
5720
|
-
SELECT quote_ident(table_schema)||'.'||quote_ident(table_name) AS tbl
|
|
5721
|
-
FROM information_schema.tables
|
|
5722
|
-
WHERE table_type='BASE TABLE'
|
|
5723
|
-
AND table_schema NOT LIKE 'pg_%'
|
|
5724
|
-
AND table_schema NOT IN ('information_schema')
|
|
5725
|
-
LIMIT 50
|
|
5726
|
-
),
|
|
5727
|
-
checks AS (
|
|
5728
|
-
SELECT bool_or(has_table_privilege(current_user, tbl, 'select')) AS ok
|
|
5729
|
-
FROM candidates
|
|
5730
|
-
)
|
|
5731
|
-
SELECT COALESCE(ok,false) FROM checks;
|
|
5732
|
-
`.trim(),
|
|
5733
|
-
logName: "cap-select-any"
|
|
5734
|
-
});
|
|
5735
|
-
const canSelectAny = parseBoolish(canSelectAnyRaw);
|
|
5736
|
-
if (canSelectAny) {
|
|
5560
|
+
if (parsed.canSelectAny) {
|
|
5737
5561
|
caps.add("db.appCanSelectSomeTable");
|
|
5738
5562
|
diagnostics["db.appCanSelectSomeTable"] = "has_table_privilege(select) true for at least one table";
|
|
5739
5563
|
} else {
|
|
5740
|
-
diagnostics["db.appCanSelectSomeTable"] =
|
|
5564
|
+
diagnostics["db.appCanSelectSomeTable"] = "App role cannot SELECT any sampled table";
|
|
5741
5565
|
}
|
|
5742
|
-
|
|
5743
|
-
repoRoot: params.repoRoot,
|
|
5744
|
-
tmpDir: params.tmpDir,
|
|
5745
|
-
databaseUrl: params.databaseUrlApp,
|
|
5746
|
-
sql: "SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='authenticated');",
|
|
5747
|
-
logName: "cap-authenticated-role-exists"
|
|
5748
|
-
});
|
|
5749
|
-
const authenticatedExists = parseBoolish(authenticatedExistsRaw);
|
|
5750
|
-
if (!authenticatedExists) {
|
|
5566
|
+
if (!parsed.authenticatedExists) {
|
|
5751
5567
|
diagnostics["db.appIsAuthenticatedMember"] = "n/a (authenticated role does not exist)";
|
|
5752
5568
|
return { capabilities: caps, diagnostics };
|
|
5753
5569
|
}
|
|
5754
|
-
|
|
5755
|
-
repoRoot: params.repoRoot,
|
|
5756
|
-
tmpDir: params.tmpDir,
|
|
5757
|
-
databaseUrl: params.databaseUrlApp,
|
|
5758
|
-
sql: "SELECT pg_has_role(current_user, 'authenticated', 'member');",
|
|
5759
|
-
logName: "cap-app-is-authenticated-member"
|
|
5760
|
-
});
|
|
5761
|
-
const isMember = parseBoolish(isMemberRaw);
|
|
5762
|
-
if (isMember) {
|
|
5570
|
+
if (parsed.isMember) {
|
|
5763
5571
|
caps.add("db.appIsAuthenticatedMember");
|
|
5764
5572
|
diagnostics["db.appIsAuthenticatedMember"] = "pg_has_role(member, authenticated) is true";
|
|
5765
5573
|
} else {
|
|
5766
|
-
diagnostics["db.appIsAuthenticatedMember"] =
|
|
5574
|
+
diagnostics["db.appIsAuthenticatedMember"] = "pg_has_role(member, authenticated)=false";
|
|
5767
5575
|
}
|
|
5768
5576
|
return { capabilities: caps, diagnostics };
|
|
5769
5577
|
}
|
|
@@ -5775,12 +5583,22 @@ function isPlaywrightIgnored(filePath) {
|
|
|
5775
5583
|
async function detectGeneratedE2eCapabilities(params) {
|
|
5776
5584
|
const caps = /* @__PURE__ */ new Set();
|
|
5777
5585
|
const diagnostics = {};
|
|
5778
|
-
|
|
5779
|
-
|
|
5586
|
+
let layer4Dir;
|
|
5587
|
+
let tmpDir;
|
|
5588
|
+
try {
|
|
5589
|
+
const discovery = resolveLayer4DiscoverySync(params.repoRoot);
|
|
5590
|
+
layer4Dir = discovery.currentOutputDir;
|
|
5591
|
+
tmpDir = discovery.tmpDir;
|
|
5592
|
+
} catch (error) {
|
|
5593
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5594
|
+
diagnostics["e2e.generatedLayer4Present"] = `Layer 4 discovery failed: ${message}`;
|
|
5595
|
+
diagnostics["e2e.generatedLayer4HasUnknown"] = "n/a (Layer 4 discovery failed)";
|
|
5596
|
+
return { capabilities: caps, diagnostics };
|
|
5597
|
+
}
|
|
5780
5598
|
const hasLayer4Dir = await pathExists(layer4Dir);
|
|
5781
5599
|
const hasTmpDir = await pathExists(tmpDir);
|
|
5782
5600
|
if (!hasLayer4Dir && !hasTmpDir) {
|
|
5783
|
-
diagnostics["e2e.generatedLayer4Present"] =
|
|
5601
|
+
diagnostics["e2e.generatedLayer4Present"] = `Missing current Layer 4 artifact dirs (${layer4Dir}, ${tmpDir})`;
|
|
5784
5602
|
diagnostics["e2e.generatedLayer4HasUnknown"] = "n/a (layer4 tests not present)";
|
|
5785
5603
|
return { capabilities: caps, diagnostics };
|
|
5786
5604
|
}
|
|
@@ -5874,23 +5692,25 @@ async function detectUiContractReady(params) {
|
|
|
5874
5692
|
async function detectCiCapabilities(params) {
|
|
5875
5693
|
const caps = /* @__PURE__ */ new Set();
|
|
5876
5694
|
const diagnostics = {};
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
}
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5695
|
+
const mergeDiagnostics = (result) => {
|
|
5696
|
+
for (const c of result.capabilities) caps.add(c);
|
|
5697
|
+
for (const [k, v] of Object.entries(result.diagnostics))
|
|
5698
|
+
diagnostics[k] = String(v);
|
|
5699
|
+
};
|
|
5700
|
+
const [db, e2e, ui] = await Promise.all([
|
|
5701
|
+
detectDbCapabilities({
|
|
5702
|
+
repoRoot: params.repoRoot,
|
|
5703
|
+
tmpDir: params.tmpDir,
|
|
5704
|
+
databaseUrlApp: params.databaseUrlApp
|
|
5705
|
+
}),
|
|
5706
|
+
detectGeneratedE2eCapabilities({ repoRoot: params.repoRoot }),
|
|
5707
|
+
detectUiContractReady({ baseUrl: params.baseUrl })
|
|
5708
|
+
]);
|
|
5709
|
+
mergeDiagnostics(db);
|
|
5710
|
+
mergeDiagnostics(e2e);
|
|
5711
|
+
mergeDiagnostics(ui);
|
|
5887
5712
|
const preview = await detectPreviewReachable({ baseUrl: params.baseUrl });
|
|
5888
|
-
|
|
5889
|
-
for (const [k, v] of Object.entries(preview.diagnostics))
|
|
5890
|
-
diagnostics[k] = String(v);
|
|
5891
|
-
const ui = await detectUiContractReady({ baseUrl: params.baseUrl });
|
|
5892
|
-
for (const c of ui.capabilities) caps.add(c);
|
|
5893
|
-
for (const [k, v] of Object.entries(ui.diagnostics)) diagnostics[k] = String(v);
|
|
5713
|
+
mergeDiagnostics(preview);
|
|
5894
5714
|
return { capabilities: Array.from(caps), diagnostics };
|
|
5895
5715
|
}
|
|
5896
5716
|
function resolveRequiredCapabilities(params) {
|
|
@@ -5937,6 +5757,58 @@ function assertCapabilities(params) {
|
|
|
5937
5757
|
);
|
|
5938
5758
|
}
|
|
5939
5759
|
|
|
5760
|
+
// src/commands/ci/machine/actors/test/capabilities.ts
|
|
5761
|
+
var capabilitiesActor = fromPromise(
|
|
5762
|
+
async ({ input }) => {
|
|
5763
|
+
const { repoRoot, tmpDir, databaseUrlApp, baseUrl } = input;
|
|
5764
|
+
try {
|
|
5765
|
+
const detected = await detectCiCapabilities({
|
|
5766
|
+
repoRoot,
|
|
5767
|
+
tmpDir,
|
|
5768
|
+
databaseUrlApp,
|
|
5769
|
+
baseUrl
|
|
5770
|
+
});
|
|
5771
|
+
return { detected };
|
|
5772
|
+
} catch (error) {
|
|
5773
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5774
|
+
console.warn(`[Capabilities] Detection failed: ${errorMsg}`);
|
|
5775
|
+
return {
|
|
5776
|
+
detected: { capabilities: [], diagnostics: {} },
|
|
5777
|
+
error: errorMsg
|
|
5778
|
+
};
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
);
|
|
5782
|
+
fromPromise(
|
|
5783
|
+
async ({ input }) => {
|
|
5784
|
+
const { config, repoKind, layer, detected } = input;
|
|
5785
|
+
try {
|
|
5786
|
+
const required = resolveRequiredCapabilities({ config, repoKind, layer });
|
|
5787
|
+
const missing = required.filter((r) => !detected.capabilities.includes(r));
|
|
5788
|
+
if (missing.length > 0) {
|
|
5789
|
+
assertCapabilities({
|
|
5790
|
+
required,
|
|
5791
|
+
detected,
|
|
5792
|
+
label: `Layer ${layer} capability gate`
|
|
5793
|
+
});
|
|
5794
|
+
}
|
|
5795
|
+
return { passed: true, required, missing: [] };
|
|
5796
|
+
} catch (error) {
|
|
5797
|
+
const required = resolveRequiredCapabilities({ config, repoKind, layer });
|
|
5798
|
+
const missing = required.filter((r) => !detected.capabilities.includes(r));
|
|
5799
|
+
return {
|
|
5800
|
+
passed: false,
|
|
5801
|
+
required,
|
|
5802
|
+
missing,
|
|
5803
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5804
|
+
};
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
);
|
|
5808
|
+
|
|
5809
|
+
// src/commands/ci/machine/actors/test/layer-content.ts
|
|
5810
|
+
init_esm_shims();
|
|
5811
|
+
|
|
5940
5812
|
// src/commands/ci/commands/ci-layer-content.ts
|
|
5941
5813
|
init_esm_shims();
|
|
5942
5814
|
var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
@@ -6420,71 +6292,36 @@ async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
|
|
|
6420
6292
|
return { layers, skippedLayers, runnableLayers };
|
|
6421
6293
|
}
|
|
6422
6294
|
|
|
6423
|
-
// src/commands/ci/machine/actors/test/
|
|
6424
|
-
var
|
|
6295
|
+
// src/commands/ci/machine/actors/test/layer-content.ts
|
|
6296
|
+
var layerContentActor = fromPromise(
|
|
6425
6297
|
async ({ input }) => {
|
|
6426
|
-
const { repoRoot,
|
|
6298
|
+
const { repoRoot, selectedLayers } = input;
|
|
6427
6299
|
try {
|
|
6428
|
-
const detected = await detectCiCapabilities({
|
|
6429
|
-
repoRoot,
|
|
6430
|
-
tmpDir,
|
|
6431
|
-
databaseUrlApp,
|
|
6432
|
-
baseUrl
|
|
6433
|
-
});
|
|
6434
6300
|
const layerContent = await detectLayerContent(repoRoot, selectedLayers);
|
|
6435
|
-
return {
|
|
6301
|
+
return { layerContent };
|
|
6436
6302
|
} catch (error) {
|
|
6437
|
-
const
|
|
6438
|
-
console.warn(`[
|
|
6439
|
-
const fallbackLayers = {};
|
|
6440
|
-
for (const layer of selectedLayers) {
|
|
6441
|
-
fallbackLayers[layer] = {
|
|
6442
|
-
layer,
|
|
6443
|
-
hasContent: true,
|
|
6444
|
-
// Conservative: assume content exists
|
|
6445
|
-
contentType: "machines",
|
|
6446
|
-
// Placeholder
|
|
6447
|
-
count: 0,
|
|
6448
|
-
source: "filesystem",
|
|
6449
|
-
skipReason: `Detection failed: ${errorMsg}`
|
|
6450
|
-
// Track why we're using fallback
|
|
6451
|
-
};
|
|
6452
|
-
}
|
|
6303
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6304
|
+
console.warn(`[Layer Content] Detection failed: ${errorMessage}`);
|
|
6453
6305
|
const fallbackLayerContent = {
|
|
6454
|
-
layers:
|
|
6306
|
+
layers: Object.fromEntries(
|
|
6307
|
+
selectedLayers.map((layer) => [
|
|
6308
|
+
layer,
|
|
6309
|
+
{
|
|
6310
|
+
layer,
|
|
6311
|
+
hasContent: true,
|
|
6312
|
+
contentType: "machines",
|
|
6313
|
+
count: 0,
|
|
6314
|
+
source: "filesystem",
|
|
6315
|
+
skipReason: `Detection failed: ${errorMessage}`
|
|
6316
|
+
}
|
|
6317
|
+
])
|
|
6318
|
+
),
|
|
6455
6319
|
skippedLayers: [],
|
|
6456
6320
|
runnableLayers: selectedLayers
|
|
6457
6321
|
};
|
|
6458
6322
|
return {
|
|
6459
|
-
detected: { capabilities: [], diagnostics: {} },
|
|
6460
6323
|
layerContent: fallbackLayerContent,
|
|
6461
|
-
error:
|
|
6462
|
-
};
|
|
6463
|
-
}
|
|
6464
|
-
}
|
|
6465
|
-
);
|
|
6466
|
-
fromPromise(
|
|
6467
|
-
async ({ input }) => {
|
|
6468
|
-
const { config, repoKind, layer, detected } = input;
|
|
6469
|
-
try {
|
|
6470
|
-
const required = resolveRequiredCapabilities({ config, repoKind, layer });
|
|
6471
|
-
const missing = required.filter((r) => !detected.capabilities.includes(r));
|
|
6472
|
-
if (missing.length > 0) {
|
|
6473
|
-
assertCapabilities({
|
|
6474
|
-
required,
|
|
6475
|
-
detected,
|
|
6476
|
-
label: `Layer ${layer} capability gate`
|
|
6477
|
-
});
|
|
6478
|
-
}
|
|
6479
|
-
return { passed: true, required, missing: [] };
|
|
6480
|
-
} catch (error) {
|
|
6481
|
-
const required = resolveRequiredCapabilities({ config, repoKind, layer });
|
|
6482
|
-
const missing = required.filter((r) => !detected.capabilities.includes(r));
|
|
6483
|
-
return {
|
|
6484
|
-
passed: false,
|
|
6485
|
-
required,
|
|
6486
|
-
missing,
|
|
6487
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6324
|
+
error: errorMessage
|
|
6488
6325
|
};
|
|
6489
6326
|
}
|
|
6490
6327
|
}
|
|
@@ -6711,9 +6548,6 @@ var runLayersActor = fromPromise(
|
|
|
6711
6548
|
progressIntervalSeconds = 30
|
|
6712
6549
|
} = input;
|
|
6713
6550
|
try {
|
|
6714
|
-
console.log(
|
|
6715
|
-
`[DEBUG] runLayersActor: Starting with layers=${JSON.stringify(layers)}, failFast=${failFast}`
|
|
6716
|
-
);
|
|
6717
6551
|
const results = await runLayersInParallel({
|
|
6718
6552
|
repoRoot,
|
|
6719
6553
|
tmpDir,
|
|
@@ -6727,15 +6561,9 @@ var runLayersActor = fromPromise(
|
|
|
6727
6561
|
});
|
|
6728
6562
|
const failedLayers = results.filter((r) => !r.success).map((r) => r.layer);
|
|
6729
6563
|
const allPassed = failedLayers.length === 0;
|
|
6730
|
-
console.log(
|
|
6731
|
-
`[DEBUG] runLayersActor: Complete. results=${results.length}, failedLayers=${JSON.stringify(failedLayers)}, allPassed=${allPassed}`
|
|
6732
|
-
);
|
|
6733
6564
|
return { results, allPassed, failedLayers };
|
|
6734
6565
|
} catch (error) {
|
|
6735
6566
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6736
|
-
console.log(
|
|
6737
|
-
`[DEBUG] runLayersActor: Caught error: ${errorMessage}. Returning failedLayers=${JSON.stringify(layers)}`
|
|
6738
|
-
);
|
|
6739
6567
|
return {
|
|
6740
6568
|
results: [],
|
|
6741
6569
|
allPassed: false,
|
|
@@ -6761,9 +6589,6 @@ function getEffectiveMode(context) {
|
|
|
6761
6589
|
function isCiLocalMode(context) {
|
|
6762
6590
|
return getEffectiveMode(context) === "ci-local";
|
|
6763
6591
|
}
|
|
6764
|
-
function isCiPrLocalMode(context) {
|
|
6765
|
-
return getEffectiveMode(context) === "ci-pr-local";
|
|
6766
|
-
}
|
|
6767
6592
|
function isCiPrMode(context) {
|
|
6768
6593
|
return getEffectiveMode(context) === "ci-pr-local";
|
|
6769
6594
|
}
|
|
@@ -6785,7 +6610,9 @@ function shouldSkipStaticChecks(context) {
|
|
|
6785
6610
|
return shouldSkipStep(context, "staticChecks");
|
|
6786
6611
|
}
|
|
6787
6612
|
function shouldSkipBuild(context) {
|
|
6788
|
-
|
|
6613
|
+
if (shouldSkipStep(context, "build")) return true;
|
|
6614
|
+
const requiresBuild = context.selectedLayers.some((layer) => layer === 3 || layer === 4);
|
|
6615
|
+
return !requiresBuild;
|
|
6789
6616
|
}
|
|
6790
6617
|
function shouldSkipAppStart(context) {
|
|
6791
6618
|
return shouldSkipStep(context, "appStart");
|
|
@@ -6812,8 +6639,19 @@ function shouldPostGitHubComment(context) {
|
|
|
6812
6639
|
if (context.input.skipGithubComment === true) return false;
|
|
6813
6640
|
return true;
|
|
6814
6641
|
}
|
|
6642
|
+
function isObservabilityPhase(context) {
|
|
6643
|
+
return isCiPrMode(context) && context.phase === "observability";
|
|
6644
|
+
}
|
|
6815
6645
|
function isTestPhase(context) {
|
|
6816
|
-
return isCiPrMode(context) && context.
|
|
6646
|
+
return isCiPrMode(context) && context.phase === "test";
|
|
6647
|
+
}
|
|
6648
|
+
function shouldRunPrExecutionPhase(context) {
|
|
6649
|
+
if (!isCiPrMode(context)) return true;
|
|
6650
|
+
return !isObservabilityPhase(context);
|
|
6651
|
+
}
|
|
6652
|
+
function shouldRunPrObservabilityPhase(context) {
|
|
6653
|
+
if (!isCiPrMode(context)) return true;
|
|
6654
|
+
return !isTestPhase(context);
|
|
6817
6655
|
}
|
|
6818
6656
|
function hasError(context) {
|
|
6819
6657
|
return context.error !== null;
|
|
@@ -6868,7 +6706,7 @@ init_esm_shims();
|
|
|
6868
6706
|
|
|
6869
6707
|
// src/commands/ci/machine/formatters/sections/format-helpers.ts
|
|
6870
6708
|
init_esm_shims();
|
|
6871
|
-
function
|
|
6709
|
+
function formatDuration3(ms) {
|
|
6872
6710
|
if (ms < 1e3) return `${ms}ms`;
|
|
6873
6711
|
const seconds = Math.floor(ms / 1e3);
|
|
6874
6712
|
if (seconds < 60) return `${seconds}s`;
|
|
@@ -7560,9 +7398,10 @@ function truncateSql(sql, maxLength) {
|
|
|
7560
7398
|
}
|
|
7561
7399
|
function generatePreviewDetails(prodPreview) {
|
|
7562
7400
|
const truncatedSql = truncateSql(prodPreview.planSql || "", 2e3);
|
|
7401
|
+
const profileLabel = prodPreview.profile ?? "compare-only";
|
|
7563
7402
|
return [
|
|
7564
7403
|
"<details>",
|
|
7565
|
-
|
|
7404
|
+
`<summary>\u{1F4CB} \u672C\u756A\u30B9\u30AD\u30FC\u30DE\u30D7\u30EC\u30D3\u30E5\u30FC (${profileLabel})</summary>`,
|
|
7566
7405
|
"",
|
|
7567
7406
|
"**\u672C\u756A\u306B\u9069\u7528\u4E88\u5B9A\u306ESQL:**",
|
|
7568
7407
|
"",
|
|
@@ -7641,7 +7480,7 @@ function generatePreviewChangesDetectedSection(prodPreview, previewOnlyChanges)
|
|
|
7641
7480
|
];
|
|
7642
7481
|
if (previewOnlyChanges) {
|
|
7643
7482
|
lines.push(
|
|
7644
|
-
"> \u88DC\u8DB3: `Prod semantic diff` / count diff / index diff \u3067\u306F\u5DEE\u5206\u304C\u306A\u304F\u3001`
|
|
7483
|
+
"> \u88DC\u8DB3: `Prod semantic diff` / count diff / index diff \u3067\u306F\u5DEE\u5206\u304C\u306A\u304F\u3001`db preview --profile compare-only` \u306E\u307F\u304C\u5909\u66F4\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
|
|
7645
7484
|
"> \u3053\u308C\u306F ownership / session config / planner-only rewrite \u306A\u3069\u3001canonical diff \u3067\u306F\u8FFD\u308F\u306A\u3044\u5DEE\u5206\u3067\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
7646
7485
|
""
|
|
7647
7486
|
);
|
|
@@ -7684,7 +7523,7 @@ function generateComparisonMismatchSection(schemaStats, expectedDrift) {
|
|
|
7684
7523
|
const lines = [
|
|
7685
7524
|
"> \u{1F536} **\u672C\u756ADB\u3068\u306E\u5DEE\u5206\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
|
|
7686
7525
|
">",
|
|
7687
|
-
"> `runa db
|
|
7526
|
+
"> `runa db preview production --profile compare-only` \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",
|
|
7688
7527
|
"> \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",
|
|
7689
7528
|
""
|
|
7690
7529
|
];
|
|
@@ -7713,6 +7552,10 @@ function generateKnownDriftOnlySection(schemaStats, expectedDrift) {
|
|
|
7713
7552
|
}
|
|
7714
7553
|
function generateProductionPreviewSection(phase, prodPreview, schemaDrift, schemaStats, expectedDrift) {
|
|
7715
7554
|
const signals = getProductionSchemaSignals(prodPreview, schemaStats, expectedDrift);
|
|
7555
|
+
const deferredSection = getDeferredProductionPreviewSection(phase, prodPreview);
|
|
7556
|
+
if (deferredSection.length > 0) {
|
|
7557
|
+
return deferredSection;
|
|
7558
|
+
}
|
|
7716
7559
|
if (!prodPreview?.executed) {
|
|
7717
7560
|
if (schemaDrift?.gitDiff?.filesChanged?.length) {
|
|
7718
7561
|
return [
|
|
@@ -7736,19 +7579,34 @@ function generateProductionPreviewSection(phase, prodPreview, schemaDrift, schem
|
|
|
7736
7579
|
}
|
|
7737
7580
|
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", ""];
|
|
7738
7581
|
}
|
|
7739
|
-
function
|
|
7740
|
-
if (
|
|
7582
|
+
function getDeferredProductionPreviewSection(phase, prodPreview) {
|
|
7583
|
+
if (prodPreview?.executed) return [];
|
|
7584
|
+
switch (phase) {
|
|
7585
|
+
case "test":
|
|
7586
|
+
return [
|
|
7587
|
+
"**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u23ED\uFE0F phase=test \u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7",
|
|
7588
|
+
"",
|
|
7589
|
+
"> workflow-prepared DB \u3092\u518D\u5229\u7528\u3059\u308B\u8EFD\u91CF\u30D5\u30A7\u30FC\u30BA\u306E\u305F\u3081\u3001\u672C\u756A\u6BD4\u8F03\u306F\u5B9F\u884C\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
7590
|
+
""
|
|
7591
|
+
];
|
|
7592
|
+
default:
|
|
7593
|
+
return [];
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges) {
|
|
7597
|
+
if (hasSchemaChanges) return true;
|
|
7741
7598
|
if (productionPreview?.error) return true;
|
|
7742
7599
|
if (!productionPreview?.executed && schemaDrift?.gitDiff?.filesChanged?.length) return true;
|
|
7743
7600
|
return false;
|
|
7744
7601
|
}
|
|
7745
7602
|
function generateDeploySection(exitCode, layerResults, phase, gitBranchName, productionPreview, schemaDrift, schemaStats, expectedDrift, env) {
|
|
7746
7603
|
if (!checkIfDeployable(exitCode, layerResults)) return [];
|
|
7604
|
+
if (phase === "test") return [];
|
|
7747
7605
|
const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
|
|
7748
7606
|
const signals = getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift);
|
|
7749
|
-
const
|
|
7750
|
-
const headerEmoji =
|
|
7751
|
-
const headerText =
|
|
7607
|
+
const hasSchemaChanges = signals.requiresDeploy;
|
|
7608
|
+
const headerEmoji = hasSchemaChanges ? "\u{1F6A8}" : "\u{1F680}";
|
|
7609
|
+
const headerText = hasSchemaChanges ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
|
|
7752
7610
|
const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
|
|
7753
7611
|
if (exitCode !== 0)
|
|
7754
7612
|
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", "");
|
|
@@ -7761,9 +7619,9 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
|
|
|
7761
7619
|
expectedDrift
|
|
7762
7620
|
)
|
|
7763
7621
|
);
|
|
7764
|
-
const showButton = shouldShowDeployButton(productionPreview, schemaDrift,
|
|
7622
|
+
const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges);
|
|
7765
7623
|
if (deployWorkflowUrl && showButton) {
|
|
7766
|
-
const buttonText =
|
|
7624
|
+
const buttonText = hasSchemaChanges ? "\u26A1 \u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4" : "\u25B6\uFE0F \u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4";
|
|
7767
7625
|
lines.push(
|
|
7768
7626
|
`**\u30C7\u30D7\u30ED\u30A4\u30D6\u30E9\u30F3\u30C1**: \`${gitBranchName}\``,
|
|
7769
7627
|
"",
|
|
@@ -7778,23 +7636,32 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
|
|
|
7778
7636
|
function generateObservabilitySectionBody(input) {
|
|
7779
7637
|
const env = getGitHubEnv();
|
|
7780
7638
|
const gitBranchName = input.prContext?.headBranch ?? input.branchName ?? "unknown";
|
|
7639
|
+
const deploySection = generateDeploySection(
|
|
7640
|
+
input.exitCode,
|
|
7641
|
+
input.layerResults,
|
|
7642
|
+
input.phase,
|
|
7643
|
+
gitBranchName,
|
|
7644
|
+
input.productionPreview,
|
|
7645
|
+
input.schemaDrift,
|
|
7646
|
+
input.schemaStats ?? null,
|
|
7647
|
+
input.expectedDrift ?? [],
|
|
7648
|
+
env
|
|
7649
|
+
);
|
|
7650
|
+
const standalonePreviewLines = deploySection.length === 0 ? generateProductionPreviewSection(
|
|
7651
|
+
input.phase,
|
|
7652
|
+
input.productionPreview,
|
|
7653
|
+
input.schemaDrift,
|
|
7654
|
+
input.schemaStats ?? null,
|
|
7655
|
+
input.expectedDrift ?? []
|
|
7656
|
+
) : [];
|
|
7781
7657
|
const lines = [
|
|
7782
7658
|
...formatSchemaMatrix(
|
|
7783
7659
|
input.schemaStats,
|
|
7784
7660
|
input.schemaDrift?.gitDiff ?? null,
|
|
7785
7661
|
input.expectedDrift ?? []
|
|
7786
7662
|
),
|
|
7787
|
-
...
|
|
7788
|
-
|
|
7789
|
-
input.layerResults,
|
|
7790
|
-
input.phase,
|
|
7791
|
-
gitBranchName,
|
|
7792
|
-
input.productionPreview,
|
|
7793
|
-
input.schemaDrift,
|
|
7794
|
-
input.schemaStats ?? null,
|
|
7795
|
-
input.expectedDrift ?? [],
|
|
7796
|
-
env
|
|
7797
|
-
)
|
|
7663
|
+
...standalonePreviewLines,
|
|
7664
|
+
...deploySection
|
|
7798
7665
|
];
|
|
7799
7666
|
return lines.join("\n").trim();
|
|
7800
7667
|
}
|
|
@@ -7863,11 +7730,11 @@ function generateCommentBody(input) {
|
|
|
7863
7730
|
|
|
7864
7731
|
// src/commands/ci/machine/formatters/sections/progress-comment.ts
|
|
7865
7732
|
init_esm_shims();
|
|
7866
|
-
function calculateTotalElapsed(
|
|
7867
|
-
if (!
|
|
7868
|
-
return Object.values(
|
|
7733
|
+
function calculateTotalElapsed(stepTimings) {
|
|
7734
|
+
if (!stepTimings) return 0;
|
|
7735
|
+
return Object.values(stepTimings).reduce((sum, t) => sum + (t ?? 0), 0);
|
|
7869
7736
|
}
|
|
7870
|
-
function formatProgressBar(
|
|
7737
|
+
function formatProgressBar(_currentStep, completedSteps, failedStep, skippedSteps) {
|
|
7871
7738
|
const total = CI_STEPS.length;
|
|
7872
7739
|
const completedBase = completedSteps.length + skippedSteps.length;
|
|
7873
7740
|
const completed = failedStep ? completedBase : completedBase + 1;
|
|
@@ -8004,7 +7871,7 @@ function generateProgressHeader(currentStep, failedStep, completedSteps, skipped
|
|
|
8004
7871
|
);
|
|
8005
7872
|
}
|
|
8006
7873
|
if (totalElapsed > 0) {
|
|
8007
|
-
lines.push(`<sub>\u23F1\uFE0F \u7D4C\u904E\u6642\u9593: ${
|
|
7874
|
+
lines.push(`<sub>\u23F1\uFE0F \u7D4C\u904E\u6642\u9593: ${formatDuration3(totalElapsed)}</sub>`);
|
|
8008
7875
|
}
|
|
8009
7876
|
lines.push("");
|
|
8010
7877
|
return lines;
|
|
@@ -8018,13 +7885,13 @@ function generateInfoLine(branchName, supabaseUrl, runUrl) {
|
|
|
8018
7885
|
if (runUrl) items.push(`[\u{1F517} \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC](${runUrl})`);
|
|
8019
7886
|
return [items.join(" \xB7 "), ""];
|
|
8020
7887
|
}
|
|
8021
|
-
function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps,
|
|
7888
|
+
function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps, stepTimings, schemaDrift, layerResults, productionPreview) {
|
|
8022
7889
|
const lines = ["### \u9032\u6357", ""];
|
|
8023
7890
|
for (const { step, label, detail } of CI_STEPS) {
|
|
8024
7891
|
const status = getStepStatus(step, currentStep, completedSteps, failedStep, skippedSteps);
|
|
8025
7892
|
const icon = getStepIcon(status);
|
|
8026
|
-
const timing =
|
|
8027
|
-
const timingStr = timing !== void 0 ? ` \`${
|
|
7893
|
+
const timing = stepTimings?.[step];
|
|
7894
|
+
const timingStr = timing !== void 0 ? ` \`${formatDuration3(timing)}\`` : "";
|
|
8028
7895
|
const stepDetail = getStepDetail(step, status, schemaDrift, layerResults);
|
|
8029
7896
|
const runningDetail = getRunningStepDetail(step, detail, productionPreview);
|
|
8030
7897
|
if (status === "running" && runningDetail) {
|
|
@@ -8058,7 +7925,7 @@ function getFixSuggestions(failedStep) {
|
|
|
8058
7925
|
],
|
|
8059
7926
|
observability: [
|
|
8060
7927
|
"production preview / schema stats \u306E\u30ED\u30B0\u3092\u78BA\u8A8D",
|
|
8061
|
-
"\u672C\u756A DB \u63A5\u7D9A\u60C5\u5831\u3068 compare-only
|
|
7928
|
+
"\u672C\u756A DB \u63A5\u7D9A\u60C5\u5831\u3068 db preview compare-only \u306E\u51FA\u529B\u3092\u78BA\u8A8D",
|
|
8062
7929
|
"schema stats \u304C\u5FC5\u8981\u306A\u30B1\u30FC\u30B9\u304B expected drift \u8A2D\u5B9A\u3092\u78BA\u8A8D"
|
|
8063
7930
|
],
|
|
8064
7931
|
staticChecks: [
|
|
@@ -8125,6 +7992,11 @@ function generateTestResultsSection(layerResults) {
|
|
|
8125
7992
|
}
|
|
8126
7993
|
function generateProductionSchemaSection(productionPreview, phase, deployStatus, env) {
|
|
8127
7994
|
const lines = ["### \u{1F4CB} \u672C\u756A\u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1", ""];
|
|
7995
|
+
const deferredMessage = getDeferredProductionPreviewMessage(phase, productionPreview);
|
|
7996
|
+
if (!deployStatus?.deployed && deferredMessage) {
|
|
7997
|
+
lines.push(deferredMessage, "");
|
|
7998
|
+
return lines;
|
|
7999
|
+
}
|
|
8128
8000
|
const state = resolveProductionSchemaState(productionPreview, deployStatus);
|
|
8129
8001
|
switch (state) {
|
|
8130
8002
|
case "deployed":
|
|
@@ -8149,6 +8021,15 @@ function generateProductionSchemaSection(productionPreview, phase, deployStatus,
|
|
|
8149
8021
|
}
|
|
8150
8022
|
return lines;
|
|
8151
8023
|
}
|
|
8024
|
+
function getDeferredProductionPreviewMessage(phase, productionPreview) {
|
|
8025
|
+
if (productionPreview?.executed) return null;
|
|
8026
|
+
switch (phase) {
|
|
8027
|
+
case "test":
|
|
8028
|
+
return "\u23ED\uFE0F _phase=test \u306E\u305F\u3081\u3001\u672C\u756A\u6BD4\u8F03\u306F\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u3066\u3044\u307E\u3059_";
|
|
8029
|
+
default:
|
|
8030
|
+
return null;
|
|
8031
|
+
}
|
|
8032
|
+
}
|
|
8152
8033
|
function resolveProductionSchemaState(productionPreview, deployStatus) {
|
|
8153
8034
|
if (deployStatus?.deployed) return "deployed";
|
|
8154
8035
|
if (!productionPreview) return "checking";
|
|
@@ -8191,13 +8072,15 @@ function appendChangesPendingSection(lines, productionPreview, env) {
|
|
|
8191
8072
|
if (!productionPreview) return;
|
|
8192
8073
|
const hazardCount = productionPreview.hazards.length;
|
|
8193
8074
|
const hazardText = hazardCount > 0 ? ` (\u26A0\uFE0F ${hazardCount}\u4EF6\u306E\u30CF\u30B6\u30FC\u30C9)` : "";
|
|
8194
|
-
|
|
8075
|
+
const profileText = productionPreview.profile ? ` [${productionPreview.profile}]` : "";
|
|
8076
|
+
lines.push(`\u26A0\uFE0F **\u5909\u66F4\u5F85\u3061**${profileText}${hazardText}`, "");
|
|
8195
8077
|
if (productionPreview.planSql) {
|
|
8196
8078
|
appendPlanSqlPreview(lines, productionPreview.planSql);
|
|
8197
8079
|
}
|
|
8198
8080
|
appendDeployLink(lines, env);
|
|
8199
8081
|
}
|
|
8200
|
-
function generateDbDeploySection(layerResults, gitBranchName, productionPreview, env) {
|
|
8082
|
+
function generateDbDeploySection(phase, layerResults, gitBranchName, productionPreview, env) {
|
|
8083
|
+
if (phase === "test") return [];
|
|
8201
8084
|
if (!checkBlockingLayersPassed(layerResults)) return [];
|
|
8202
8085
|
if (productionPreview?.hasChanges) return [];
|
|
8203
8086
|
const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
|
|
@@ -8230,13 +8113,13 @@ function generateProgressCommentBody(input) {
|
|
|
8230
8113
|
productionPreview,
|
|
8231
8114
|
deployStatus,
|
|
8232
8115
|
error,
|
|
8233
|
-
stepTimings
|
|
8116
|
+
stepTimings
|
|
8234
8117
|
} = input;
|
|
8235
8118
|
const env = getGitHubEnv();
|
|
8236
8119
|
const runUrl = env.repository && env.runId ? `${env.serverUrl}/${env.repository}/actions/runs/${env.runId}` : null;
|
|
8237
8120
|
const effectiveBranchName = branchName ?? input.prContext?.headBranch ?? "unknown";
|
|
8238
8121
|
const supabaseUrl = supabase?.supabaseUrl ?? "";
|
|
8239
|
-
const totalElapsed = calculateTotalElapsed(
|
|
8122
|
+
const totalElapsed = calculateTotalElapsed(stepTimings);
|
|
8240
8123
|
const gitBranchName = input.prContext?.headBranch ?? branchName ?? "unknown";
|
|
8241
8124
|
const lines = [
|
|
8242
8125
|
...generateProgressHeader(
|
|
@@ -8252,7 +8135,7 @@ function generateProgressCommentBody(input) {
|
|
|
8252
8135
|
completedSteps,
|
|
8253
8136
|
failedStep,
|
|
8254
8137
|
input.skippedSteps ?? [],
|
|
8255
|
-
|
|
8138
|
+
stepTimings,
|
|
8256
8139
|
schemaDrift,
|
|
8257
8140
|
input.layerResults,
|
|
8258
8141
|
productionPreview
|
|
@@ -8260,7 +8143,13 @@ function generateProgressCommentBody(input) {
|
|
|
8260
8143
|
...generateErrorSection(error ?? void 0, failedStep),
|
|
8261
8144
|
...generateTestResultsSection(input.layerResults),
|
|
8262
8145
|
...generateProductionSchemaSection(productionPreview, input.phase, deployStatus, env),
|
|
8263
|
-
...generateDbDeploySection(
|
|
8146
|
+
...generateDbDeploySection(
|
|
8147
|
+
input.phase,
|
|
8148
|
+
input.layerResults,
|
|
8149
|
+
gitBranchName,
|
|
8150
|
+
productionPreview,
|
|
8151
|
+
env
|
|
8152
|
+
)
|
|
8264
8153
|
];
|
|
8265
8154
|
const jst = new Date(Date.now() + 9 * 60 * 60 * 1e3);
|
|
8266
8155
|
const now = jst.toISOString().replace("T", " ").substring(0, 19);
|
|
@@ -8269,13 +8158,25 @@ function generateProgressCommentBody(input) {
|
|
|
8269
8158
|
}
|
|
8270
8159
|
|
|
8271
8160
|
// src/commands/ci/machine/formatters/github-comment.ts
|
|
8161
|
+
function getSkippedStepsForPhase(phase) {
|
|
8162
|
+
switch (phase) {
|
|
8163
|
+
case "test":
|
|
8164
|
+
return ["syncSchema", "applySeeds", "observability"];
|
|
8165
|
+
case "blocking":
|
|
8166
|
+
return ["observability"];
|
|
8167
|
+
case "observability":
|
|
8168
|
+
return ["postSeedChecks", "staticChecks", "build", "runTests"];
|
|
8169
|
+
default:
|
|
8170
|
+
return [];
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8272
8173
|
function createCommentInput(context, options) {
|
|
8273
8174
|
const skippedLayerNumbers = Object.keys(context.layerSkipReasons || {}).map(Number);
|
|
8274
8175
|
const originalSelectedLayers = [
|
|
8275
8176
|
.../* @__PURE__ */ new Set([...skippedLayerNumbers, ...context.selectedLayers])
|
|
8276
8177
|
].sort((a, b) => a - b);
|
|
8277
8178
|
return {
|
|
8278
|
-
phase: context.
|
|
8179
|
+
phase: context.phase,
|
|
8279
8180
|
exitCode: context.exitCode,
|
|
8280
8181
|
branchName: context.branchName,
|
|
8281
8182
|
prContext: context.prContext,
|
|
@@ -8291,9 +8192,9 @@ function createCommentInput(context, options) {
|
|
|
8291
8192
|
originalSelectedLayers
|
|
8292
8193
|
};
|
|
8293
8194
|
}
|
|
8294
|
-
function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null,
|
|
8295
|
-
const phase = context.
|
|
8296
|
-
const skippedSteps = phase
|
|
8195
|
+
function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null, stepTimings) {
|
|
8196
|
+
const phase = context.phase;
|
|
8197
|
+
const skippedSteps = getSkippedStepsForPhase(phase);
|
|
8297
8198
|
return {
|
|
8298
8199
|
phase,
|
|
8299
8200
|
currentStep,
|
|
@@ -8307,7 +8208,7 @@ function createProgressCommentInput(context, currentStep, completedSteps, failed
|
|
|
8307
8208
|
layerResults: context.layerResults,
|
|
8308
8209
|
productionPreview: context.productionPreview,
|
|
8309
8210
|
error: context.error,
|
|
8310
|
-
stepTimings
|
|
8211
|
+
stepTimings
|
|
8311
8212
|
};
|
|
8312
8213
|
}
|
|
8313
8214
|
|
|
@@ -8497,7 +8398,12 @@ function createSummaryInput(context) {
|
|
|
8497
8398
|
endedAt: now,
|
|
8498
8399
|
durationMs: Date.now() - context.startTime,
|
|
8499
8400
|
repoKind: context.repoKind,
|
|
8500
|
-
detected: {
|
|
8401
|
+
detected: {
|
|
8402
|
+
ciProfile: context.input.internalProfile ?? "default",
|
|
8403
|
+
requestedLayers: context.input.layers ?? [1, 2, 3, 4],
|
|
8404
|
+
effectiveLayers: context.selectedLayers,
|
|
8405
|
+
phase: context.phase
|
|
8406
|
+
},
|
|
8501
8407
|
diagnostics: context.diagnostics,
|
|
8502
8408
|
steps: {},
|
|
8503
8409
|
layers: formatLayerSummary(context.layerResults),
|
|
@@ -8634,6 +8540,12 @@ function createStaticChecksInput(context) {
|
|
|
8634
8540
|
tmpDir: assertTmpDir(context)
|
|
8635
8541
|
};
|
|
8636
8542
|
}
|
|
8543
|
+
function createLayerContentInput(context) {
|
|
8544
|
+
return {
|
|
8545
|
+
repoRoot: assertRepoRoot(context),
|
|
8546
|
+
selectedLayers: context.selectedLayers
|
|
8547
|
+
};
|
|
8548
|
+
}
|
|
8637
8549
|
function createBuildAndPlaywrightInput(context) {
|
|
8638
8550
|
return {
|
|
8639
8551
|
repoRoot: assertRepoRoot(context),
|
|
@@ -8662,8 +8574,7 @@ function createCapabilitiesInput(context) {
|
|
|
8662
8574
|
repoRoot: assertRepoRoot(context),
|
|
8663
8575
|
tmpDir: assertTmpDir(context),
|
|
8664
8576
|
databaseUrlApp: getDatabaseUrlForRuntime(context),
|
|
8665
|
-
baseUrl: context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}
|
|
8666
|
-
selectedLayers: context.selectedLayers
|
|
8577
|
+
baseUrl: context.appStarted ? context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}` : null
|
|
8667
8578
|
};
|
|
8668
8579
|
}
|
|
8669
8580
|
function createRunCoreTestsInput(context) {
|
|
@@ -8687,19 +8598,106 @@ function deriveCoreLayerResults(output) {
|
|
|
8687
8598
|
return convertLayerResults(results);
|
|
8688
8599
|
}
|
|
8689
8600
|
|
|
8601
|
+
// src/commands/ci/machine/selectors.ts
|
|
8602
|
+
init_esm_shims();
|
|
8603
|
+
function getStateName(snapshot) {
|
|
8604
|
+
return getSnapshotStateName(snapshot);
|
|
8605
|
+
}
|
|
8606
|
+
function isComplete(snapshot) {
|
|
8607
|
+
return isSnapshotComplete(snapshot);
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8690
8610
|
// src/commands/ci/machine/machine.ts
|
|
8691
|
-
var
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8611
|
+
var FLAT_EXECUTION_META = {
|
|
8612
|
+
setupRoles: {
|
|
8613
|
+
e2e: {
|
|
8614
|
+
observable: "log",
|
|
8615
|
+
assertions: [{ type: "log", contains: "Setting up database roles" }]
|
|
8616
|
+
}
|
|
8617
|
+
},
|
|
8618
|
+
staticChecks: {
|
|
8619
|
+
e2e: {
|
|
8620
|
+
observable: "log",
|
|
8621
|
+
assertions: [{ type: "log", contains: "Running static checks" }]
|
|
8622
|
+
}
|
|
8623
|
+
},
|
|
8624
|
+
layerContent: {
|
|
8625
|
+
e2e: {
|
|
8626
|
+
observable: "log",
|
|
8627
|
+
assertions: [{ type: "log", contains: "Detecting runnable layers" }]
|
|
8628
|
+
}
|
|
8629
|
+
},
|
|
8630
|
+
buildAndPlaywright: {
|
|
8631
|
+
e2e: {
|
|
8632
|
+
observable: "log",
|
|
8633
|
+
assertions: [
|
|
8634
|
+
{ type: "log", contains: "Building app" },
|
|
8635
|
+
{ type: "log", contains: "Installing Playwright" }
|
|
8636
|
+
]
|
|
8637
|
+
}
|
|
8638
|
+
},
|
|
8639
|
+
appStart: {
|
|
8640
|
+
e2e: {
|
|
8641
|
+
observable: "log",
|
|
8642
|
+
assertions: [{ type: "log", contains: "Starting app" }]
|
|
8643
|
+
}
|
|
8644
|
+
},
|
|
8645
|
+
capabilities: {
|
|
8646
|
+
e2e: {
|
|
8647
|
+
observable: "log",
|
|
8648
|
+
assertions: [{ type: "log", contains: "Detecting capabilities" }]
|
|
8649
|
+
}
|
|
8650
|
+
},
|
|
8651
|
+
runCoreTests: {
|
|
8652
|
+
e2e: {
|
|
8653
|
+
observable: "log",
|
|
8654
|
+
assertions: [{ type: "log", contains: "Running core tests" }]
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
};
|
|
8658
|
+
function getSetupRolesApplied(output) {
|
|
8659
|
+
return output.skipped !== true;
|
|
8660
|
+
}
|
|
8661
|
+
function mergeSetupRolesStepOverrides(context, output, stepId) {
|
|
8662
|
+
if (output.skipped !== true) {
|
|
8663
|
+
return context.stepOverrides;
|
|
8664
|
+
}
|
|
8665
|
+
return {
|
|
8666
|
+
...context.stepOverrides,
|
|
8667
|
+
[stepId]: {
|
|
8668
|
+
status: "skipped",
|
|
8669
|
+
reason: output.skipReason ?? "Skipped because db:setup-roles is unavailable"
|
|
8670
|
+
}
|
|
8671
|
+
};
|
|
8672
|
+
}
|
|
8673
|
+
function getRunnableLayers(context, layerContent) {
|
|
8674
|
+
return filterRunnableLayers(context.selectedLayers, layerContent);
|
|
8675
|
+
}
|
|
8676
|
+
function getMergedLayerSkipReasons(context, layerContent) {
|
|
8677
|
+
return {
|
|
8678
|
+
...context.layerSkipReasons,
|
|
8679
|
+
...extractLayerSkipReasons(layerContent)
|
|
8680
|
+
};
|
|
8681
|
+
}
|
|
8682
|
+
function getDetectedCapabilities(output) {
|
|
8683
|
+
return output.detected?.capabilities ?? [];
|
|
8684
|
+
}
|
|
8685
|
+
function getCoreSuccessLayerResults(context, output, strategy) {
|
|
8686
|
+
const nextResults = convertLayerResults(output.results ?? []);
|
|
8687
|
+
return strategy === "merge" ? mergeLayerResults(context.layerResults, nextResults) : nextResults;
|
|
8688
|
+
}
|
|
8689
|
+
var ciMachine = setup({
|
|
8690
|
+
types: {},
|
|
8691
|
+
actors: {
|
|
8692
|
+
// Setup
|
|
8693
|
+
localSetup: localSetupActor,
|
|
8694
|
+
prLocalSetup: prLocalSetupActor,
|
|
8695
|
+
// DB
|
|
8696
|
+
resetDb: resetDbActor,
|
|
8697
|
+
pullProduction: pullProductionActor,
|
|
8698
|
+
syncSchema: syncSchemaActor,
|
|
8699
|
+
applySeeds: applySeedsActor,
|
|
8700
|
+
productionPreview: productionPreviewActor,
|
|
8703
8701
|
collectSchemaStats: collectSchemaStatsActor,
|
|
8704
8702
|
setupRoles: setupRolesActor,
|
|
8705
8703
|
installPgTap: installPgTapActor,
|
|
@@ -8710,6 +8708,7 @@ var ciMachine = setup({
|
|
|
8710
8708
|
playwrightInstall: playwrightInstallActor,
|
|
8711
8709
|
buildAndPlaywright: buildAndPlaywrightActor,
|
|
8712
8710
|
// Test
|
|
8711
|
+
layerContent: layerContentActor,
|
|
8713
8712
|
capabilities: capabilitiesActor,
|
|
8714
8713
|
runLayers: runLayersActor,
|
|
8715
8714
|
// Finalize
|
|
@@ -8717,7 +8716,7 @@ var ciMachine = setup({
|
|
|
8717
8716
|
},
|
|
8718
8717
|
guards: {
|
|
8719
8718
|
isCiLocalMode: ({ context }) => isCiLocalMode(context),
|
|
8720
|
-
isCiPrLocalMode: ({ context }) =>
|
|
8719
|
+
isCiPrLocalMode: ({ context }) => isCiPrMode(context),
|
|
8721
8720
|
isCiPrMode: ({ context }) => isCiPrMode(context),
|
|
8722
8721
|
shouldResetDb: ({ context }) => shouldResetDb(context),
|
|
8723
8722
|
shouldPullProduction: ({ context }) => shouldPullProduction(context),
|
|
@@ -8728,6 +8727,8 @@ var ciMachine = setup({
|
|
|
8728
8727
|
shouldInstallPgTap: ({ context }) => shouldInstallPgTap(context),
|
|
8729
8728
|
shouldSetupRoles: ({ context }) => shouldSetupRoles(context),
|
|
8730
8729
|
shouldPostGitHubComment: ({ context }) => shouldPostGitHubComment(context),
|
|
8730
|
+
shouldRunPrExecutionPhase: ({ context }) => shouldRunPrExecutionPhase(context),
|
|
8731
|
+
shouldRunPrObservabilityPhase: ({ context }) => shouldRunPrObservabilityPhase(context),
|
|
8731
8732
|
isDryRun: ({ context }) => isDryRun(context),
|
|
8732
8733
|
hasError: ({ context }) => hasError(context),
|
|
8733
8734
|
allTestsPassed: ({ context }) => allTestsPassed(context)
|
|
@@ -9062,8 +9063,14 @@ var ciMachine = setup({
|
|
|
9062
9063
|
},
|
|
9063
9064
|
states: {
|
|
9064
9065
|
execution: {
|
|
9065
|
-
initial: "
|
|
9066
|
+
initial: "gate",
|
|
9066
9067
|
states: {
|
|
9068
|
+
gate: {
|
|
9069
|
+
always: [
|
|
9070
|
+
{ guard: "shouldRunPrExecutionPhase", target: "setupRoles" },
|
|
9071
|
+
{ target: "done" }
|
|
9072
|
+
]
|
|
9073
|
+
},
|
|
9067
9074
|
setupRoles: {
|
|
9068
9075
|
invoke: {
|
|
9069
9076
|
src: "setupRoles",
|
|
@@ -9071,16 +9078,12 @@ var ciMachine = setup({
|
|
|
9071
9078
|
onDone: {
|
|
9072
9079
|
target: "staticChecks",
|
|
9073
9080
|
actions: assign({
|
|
9074
|
-
rolesSetup: ({ event }) => event.output
|
|
9075
|
-
stepOverrides: ({ context, event }) => (
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
reason: event.output.skipReason ?? "Skipped because db:setup-roles is unavailable"
|
|
9081
|
-
}
|
|
9082
|
-
} : {}
|
|
9083
|
-
}),
|
|
9081
|
+
rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
|
|
9082
|
+
stepOverrides: ({ context, event }) => mergeSetupRolesStepOverrides(
|
|
9083
|
+
context,
|
|
9084
|
+
event.output,
|
|
9085
|
+
"postSeedPr.execution.setupRoles"
|
|
9086
|
+
),
|
|
9084
9087
|
supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
|
|
9085
9088
|
})
|
|
9086
9089
|
},
|
|
@@ -9091,7 +9094,7 @@ var ciMachine = setup({
|
|
|
9091
9094
|
}
|
|
9092
9095
|
},
|
|
9093
9096
|
staticChecks: {
|
|
9094
|
-
always: [{ guard: "shouldSkipStaticChecks", target: "
|
|
9097
|
+
always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
|
|
9095
9098
|
invoke: {
|
|
9096
9099
|
src: "staticChecks",
|
|
9097
9100
|
input: ({ context }) => createStaticChecksInput(context),
|
|
@@ -9105,7 +9108,7 @@ var ciMachine = setup({
|
|
|
9105
9108
|
})
|
|
9106
9109
|
},
|
|
9107
9110
|
{
|
|
9108
|
-
target: "
|
|
9111
|
+
target: "layerContent",
|
|
9109
9112
|
actions: assign({ staticChecksPassed: true })
|
|
9110
9113
|
}
|
|
9111
9114
|
],
|
|
@@ -9118,6 +9121,22 @@ var ciMachine = setup({
|
|
|
9118
9121
|
}
|
|
9119
9122
|
}
|
|
9120
9123
|
},
|
|
9124
|
+
layerContent: {
|
|
9125
|
+
invoke: {
|
|
9126
|
+
src: "layerContent",
|
|
9127
|
+
input: ({ context }) => createLayerContentInput(context),
|
|
9128
|
+
onDone: {
|
|
9129
|
+
target: "buildAndPlaywright",
|
|
9130
|
+
actions: assign({
|
|
9131
|
+
selectedLayers: ({ context, event }) => getRunnableLayers(context, event.output.layerContent),
|
|
9132
|
+
layerSkipReasons: ({ context, event }) => getMergedLayerSkipReasons(context, event.output.layerContent)
|
|
9133
|
+
})
|
|
9134
|
+
},
|
|
9135
|
+
onError: {
|
|
9136
|
+
target: "buildAndPlaywright"
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
},
|
|
9121
9140
|
buildAndPlaywright: {
|
|
9122
9141
|
always: [{ guard: "shouldSkipBuild", target: "appStart" }],
|
|
9123
9142
|
invoke: {
|
|
@@ -9194,9 +9213,7 @@ var ciMachine = setup({
|
|
|
9194
9213
|
onDone: {
|
|
9195
9214
|
target: "runCoreTests",
|
|
9196
9215
|
actions: assign({
|
|
9197
|
-
capabilities: ({ event }) => event.output
|
|
9198
|
-
selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
|
|
9199
|
-
layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
|
|
9216
|
+
capabilities: ({ event }) => getDetectedCapabilities(event.output)
|
|
9200
9217
|
})
|
|
9201
9218
|
},
|
|
9202
9219
|
onError: {
|
|
@@ -9224,10 +9241,7 @@ var ciMachine = setup({
|
|
|
9224
9241
|
target: "coreTestsComplete",
|
|
9225
9242
|
actions: assign({
|
|
9226
9243
|
testsRun: true,
|
|
9227
|
-
layerResults: ({ context, event }) =>
|
|
9228
|
-
const nextResults = convertLayerResults(event.output.results);
|
|
9229
|
-
return mergeLayerResults(context.layerResults, nextResults);
|
|
9230
|
-
}
|
|
9244
|
+
layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "merge")
|
|
9231
9245
|
})
|
|
9232
9246
|
}
|
|
9233
9247
|
],
|
|
@@ -9357,8 +9371,14 @@ var ciMachine = setup({
|
|
|
9357
9371
|
}
|
|
9358
9372
|
},
|
|
9359
9373
|
observability: {
|
|
9360
|
-
initial: "
|
|
9374
|
+
initial: "gate",
|
|
9361
9375
|
states: {
|
|
9376
|
+
gate: {
|
|
9377
|
+
always: [
|
|
9378
|
+
{ guard: "shouldRunPrObservabilityPhase", target: "productionPreview" },
|
|
9379
|
+
{ target: "done" }
|
|
9380
|
+
]
|
|
9381
|
+
},
|
|
9362
9382
|
productionPreview: {
|
|
9363
9383
|
always: [
|
|
9364
9384
|
{
|
|
@@ -9558,19 +9578,14 @@ var ciMachine = setup({
|
|
|
9558
9578
|
// Setup Roles (ci-pr modes only)
|
|
9559
9579
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9560
9580
|
setupRoles: {
|
|
9561
|
-
meta:
|
|
9562
|
-
e2e: {
|
|
9563
|
-
observable: "log",
|
|
9564
|
-
assertions: [{ type: "log", contains: "Setting up database roles" }]
|
|
9565
|
-
}
|
|
9566
|
-
},
|
|
9581
|
+
meta: FLAT_EXECUTION_META.setupRoles,
|
|
9567
9582
|
invoke: {
|
|
9568
9583
|
src: "setupRoles",
|
|
9569
9584
|
input: ({ context }) => createSetupRolesInput(context),
|
|
9570
9585
|
onDone: {
|
|
9571
9586
|
target: "staticChecks",
|
|
9572
9587
|
actions: assign({
|
|
9573
|
-
rolesSetup:
|
|
9588
|
+
rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
|
|
9574
9589
|
supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
|
|
9575
9590
|
})
|
|
9576
9591
|
},
|
|
@@ -9585,13 +9600,8 @@ var ciMachine = setup({
|
|
|
9585
9600
|
// Static Checks (ci-pr modes only)
|
|
9586
9601
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9587
9602
|
staticChecks: {
|
|
9588
|
-
meta:
|
|
9589
|
-
|
|
9590
|
-
observable: "log",
|
|
9591
|
-
assertions: [{ type: "log", contains: "Running static checks" }]
|
|
9592
|
-
}
|
|
9593
|
-
},
|
|
9594
|
-
always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
|
|
9603
|
+
meta: FLAT_EXECUTION_META.staticChecks,
|
|
9604
|
+
always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
|
|
9595
9605
|
invoke: {
|
|
9596
9606
|
src: "staticChecks",
|
|
9597
9607
|
input: ({ context }) => createStaticChecksInput(context),
|
|
@@ -9605,7 +9615,7 @@ var ciMachine = setup({
|
|
|
9605
9615
|
})
|
|
9606
9616
|
},
|
|
9607
9617
|
{
|
|
9608
|
-
target: "
|
|
9618
|
+
target: "layerContent",
|
|
9609
9619
|
actions: assign({ staticChecksPassed: true })
|
|
9610
9620
|
}
|
|
9611
9621
|
],
|
|
@@ -9618,20 +9628,29 @@ var ciMachine = setup({
|
|
|
9618
9628
|
}
|
|
9619
9629
|
}
|
|
9620
9630
|
},
|
|
9631
|
+
layerContent: {
|
|
9632
|
+
meta: FLAT_EXECUTION_META.layerContent,
|
|
9633
|
+
invoke: {
|
|
9634
|
+
src: "layerContent",
|
|
9635
|
+
input: ({ context }) => createLayerContentInput(context),
|
|
9636
|
+
onDone: {
|
|
9637
|
+
target: "buildAndPlaywright",
|
|
9638
|
+
actions: assign({
|
|
9639
|
+
selectedLayers: ({ context, event }) => getRunnableLayers(context, event.output.layerContent),
|
|
9640
|
+
layerSkipReasons: ({ context, event }) => getMergedLayerSkipReasons(context, event.output.layerContent)
|
|
9641
|
+
})
|
|
9642
|
+
},
|
|
9643
|
+
onError: {
|
|
9644
|
+
target: "buildAndPlaywright"
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
},
|
|
9621
9648
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9622
9649
|
// Build and Playwright Install (ci-pr modes only, parallel)
|
|
9623
9650
|
// Optimization: Running build and playwright install concurrently saves ~30-60s
|
|
9624
9651
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9625
9652
|
buildAndPlaywright: {
|
|
9626
|
-
meta:
|
|
9627
|
-
e2e: {
|
|
9628
|
-
observable: "log",
|
|
9629
|
-
assertions: [
|
|
9630
|
-
{ type: "log", contains: "Building app" },
|
|
9631
|
-
{ type: "log", contains: "Installing Playwright" }
|
|
9632
|
-
]
|
|
9633
|
-
}
|
|
9634
|
-
},
|
|
9653
|
+
meta: FLAT_EXECUTION_META.buildAndPlaywright,
|
|
9635
9654
|
always: [{ guard: "shouldSkipBuild", target: "appStart" }],
|
|
9636
9655
|
invoke: {
|
|
9637
9656
|
src: "buildAndPlaywright",
|
|
@@ -9672,12 +9691,7 @@ var ciMachine = setup({
|
|
|
9672
9691
|
// App Start (ci-pr modes only)
|
|
9673
9692
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9674
9693
|
appStart: {
|
|
9675
|
-
meta:
|
|
9676
|
-
e2e: {
|
|
9677
|
-
observable: "log",
|
|
9678
|
-
assertions: [{ type: "log", contains: "Starting app" }]
|
|
9679
|
-
}
|
|
9680
|
-
},
|
|
9694
|
+
meta: FLAT_EXECUTION_META.appStart,
|
|
9681
9695
|
always: [{ guard: "shouldSkipAppStart", target: "capabilities" }],
|
|
9682
9696
|
invoke: {
|
|
9683
9697
|
src: "appStart",
|
|
@@ -9712,26 +9726,16 @@ var ciMachine = setup({
|
|
|
9712
9726
|
},
|
|
9713
9727
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9714
9728
|
// Capability Detection (ci-pr modes only)
|
|
9715
|
-
// Also detects layer content to filter layers without testable content
|
|
9716
9729
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9717
9730
|
capabilities: {
|
|
9718
|
-
meta:
|
|
9719
|
-
e2e: {
|
|
9720
|
-
observable: "log",
|
|
9721
|
-
assertions: [{ type: "log", contains: "Detecting capabilities" }]
|
|
9722
|
-
}
|
|
9723
|
-
},
|
|
9731
|
+
meta: FLAT_EXECUTION_META.capabilities,
|
|
9724
9732
|
invoke: {
|
|
9725
9733
|
src: "capabilities",
|
|
9726
9734
|
input: ({ context }) => createCapabilitiesInput(context),
|
|
9727
9735
|
onDone: {
|
|
9728
9736
|
target: "runCoreTests",
|
|
9729
9737
|
actions: assign({
|
|
9730
|
-
capabilities: ({ event }) => event.output
|
|
9731
|
-
// Filter to runnable layers only (those with content)
|
|
9732
|
-
selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
|
|
9733
|
-
// Store skip reasons for PR comment
|
|
9734
|
-
layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
|
|
9738
|
+
capabilities: ({ event }) => getDetectedCapabilities(event.output)
|
|
9735
9739
|
})
|
|
9736
9740
|
},
|
|
9737
9741
|
onError: {
|
|
@@ -9747,12 +9751,7 @@ var ciMachine = setup({
|
|
|
9747
9751
|
// ci-pr: Only core layers (1,2,3) - E2E runs in e2ePhase
|
|
9748
9752
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9749
9753
|
runCoreTests: {
|
|
9750
|
-
meta:
|
|
9751
|
-
e2e: {
|
|
9752
|
-
observable: "log",
|
|
9753
|
-
assertions: [{ type: "log", contains: "Running core tests" }]
|
|
9754
|
-
}
|
|
9755
|
-
},
|
|
9754
|
+
meta: FLAT_EXECUTION_META.runCoreTests,
|
|
9756
9755
|
invoke: {
|
|
9757
9756
|
src: "runLayers",
|
|
9758
9757
|
input: ({ context }) => createRunCoreTestsInput(context),
|
|
@@ -9774,7 +9773,7 @@ var ciMachine = setup({
|
|
|
9774
9773
|
target: "coreTestsComplete",
|
|
9775
9774
|
actions: assign({
|
|
9776
9775
|
testsRun: true,
|
|
9777
|
-
layerResults: ({ event }) =>
|
|
9776
|
+
layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "replace")
|
|
9778
9777
|
})
|
|
9779
9778
|
}
|
|
9780
9779
|
],
|
|
@@ -9997,18 +9996,94 @@ var ciMachine = setup({
|
|
|
9997
9996
|
},
|
|
9998
9997
|
output: ({ context }) => createOutput(context)
|
|
9999
9998
|
});
|
|
10000
|
-
function getStateName(snapshot) {
|
|
10001
|
-
return getSnapshotStateName(snapshot);
|
|
10002
|
-
}
|
|
10003
|
-
function isComplete(snapshot) {
|
|
10004
|
-
return isSnapshotComplete(snapshot);
|
|
10005
|
-
}
|
|
10006
9999
|
|
|
10007
10000
|
// src/commands/ci/machine/commands/machine-runner.ts
|
|
10008
10001
|
init_esm_shims();
|
|
10009
10002
|
|
|
10010
|
-
// src/commands/ci/machine/commands/step-
|
|
10003
|
+
// src/commands/ci/machine/commands/ci-step-registry.ts
|
|
10011
10004
|
init_esm_shims();
|
|
10005
|
+
var PROGRESS_STATE_TO_STEP = {
|
|
10006
|
+
setup: "setup",
|
|
10007
|
+
"setup.resolving": "setup",
|
|
10008
|
+
"setup.local": "setup",
|
|
10009
|
+
"setup.prLocal": "setup",
|
|
10010
|
+
"setup.complete": "setup",
|
|
10011
|
+
dbReset: "syncSchema",
|
|
10012
|
+
pullProduction: "syncSchema",
|
|
10013
|
+
syncSchema: "syncSchema",
|
|
10014
|
+
applySeeds: "applySeeds",
|
|
10015
|
+
productionPreview: "observability",
|
|
10016
|
+
collectSchemaStats: "observability",
|
|
10017
|
+
postSeedPr: "postSeedChecks",
|
|
10018
|
+
"postSeedPr.execution": "postSeedChecks",
|
|
10019
|
+
"postSeedPr.observability": "observability",
|
|
10020
|
+
"postSeedPr.execution.setupRoles": "postSeedChecks",
|
|
10021
|
+
"postSeedPr.observability.productionPreview": "observability",
|
|
10022
|
+
"postSeedPr.observability.collectSchemaStats": "observability",
|
|
10023
|
+
"postSeedPr.execution.staticChecks": "staticChecks",
|
|
10024
|
+
"postSeedPr.execution.layerContent": "build",
|
|
10025
|
+
"postSeedPr.execution.buildAndPlaywright": "build",
|
|
10026
|
+
"postSeedPr.execution.appStart": "build",
|
|
10027
|
+
"postSeedPr.execution.capabilities": "build",
|
|
10028
|
+
"postSeedPr.execution.runCoreTests": "runTests",
|
|
10029
|
+
"postSeedPr.execution.coreTestsComplete": "runTests",
|
|
10030
|
+
"postSeedPr.execution.coreTestsFailed": "runTests",
|
|
10031
|
+
"postSeedPr.execution.e2ePhase": "runTests",
|
|
10032
|
+
"postSeedPr.execution.done": "runTests",
|
|
10033
|
+
decidePath: "postSeedChecks",
|
|
10034
|
+
installPgTap: "applySeeds",
|
|
10035
|
+
setupRoles: "postSeedChecks",
|
|
10036
|
+
staticChecks: "staticChecks",
|
|
10037
|
+
layerContent: "build",
|
|
10038
|
+
buildAndPlaywright: "build",
|
|
10039
|
+
appStart: "build",
|
|
10040
|
+
capabilities: "build",
|
|
10041
|
+
runTests: "runTests",
|
|
10042
|
+
testsComplete: "runTests",
|
|
10043
|
+
testsFailed: "runTests",
|
|
10044
|
+
runCoreTests: "runTests",
|
|
10045
|
+
coreTestsComplete: "runTests",
|
|
10046
|
+
coreTestsFailed: "runTests",
|
|
10047
|
+
e2ePhase: "runTests",
|
|
10048
|
+
"e2ePhase.intermediateComment": "runTests",
|
|
10049
|
+
"e2ePhase.e2eTests": "runTests",
|
|
10050
|
+
finalize: "finalize",
|
|
10051
|
+
"finalize.writeSummary": "finalize",
|
|
10052
|
+
"finalize.postComment": "finalize",
|
|
10053
|
+
"finalize.complete": "finalize",
|
|
10054
|
+
done: "finalize",
|
|
10055
|
+
failed: "finalize"
|
|
10056
|
+
};
|
|
10057
|
+
var PROGRESS_STEP_ORDER = [
|
|
10058
|
+
"setup",
|
|
10059
|
+
"syncSchema",
|
|
10060
|
+
"applySeeds",
|
|
10061
|
+
"postSeedChecks",
|
|
10062
|
+
"observability",
|
|
10063
|
+
"staticChecks",
|
|
10064
|
+
"build",
|
|
10065
|
+
"runTests",
|
|
10066
|
+
"finalize"
|
|
10067
|
+
];
|
|
10068
|
+
var OBSERVABILITY_STEP_IDS = [
|
|
10069
|
+
"postSeedPr.observability.productionPreview",
|
|
10070
|
+
"postSeedPr.observability.collectSchemaStats"
|
|
10071
|
+
];
|
|
10072
|
+
var EXECUTION_STEP_IDS = [
|
|
10073
|
+
"postSeedPr.execution.setupRoles",
|
|
10074
|
+
"postSeedPr.execution.staticChecks",
|
|
10075
|
+
"postSeedPr.execution.layerContent",
|
|
10076
|
+
"postSeedPr.execution.buildAndPlaywright",
|
|
10077
|
+
"postSeedPr.execution.appStart",
|
|
10078
|
+
"postSeedPr.execution.capabilities",
|
|
10079
|
+
"postSeedPr.execution.runCoreTests",
|
|
10080
|
+
"postSeedPr.execution.e2ePhase"
|
|
10081
|
+
];
|
|
10082
|
+
var BUILD_PREPARATION_SUBSTEP_IDS = {
|
|
10083
|
+
build: "postSeedPr.execution.buildAndPlaywright.build",
|
|
10084
|
+
manifestGenerate: "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
|
|
10085
|
+
playwrightInstall: "postSeedPr.execution.buildAndPlaywright.playwrightInstall"
|
|
10086
|
+
};
|
|
10012
10087
|
var STEP_METADATA = {
|
|
10013
10088
|
"setup.local": {
|
|
10014
10089
|
order: 10,
|
|
@@ -10050,74 +10125,80 @@ var STEP_METADATA = {
|
|
|
10050
10125
|
optional: true,
|
|
10051
10126
|
parentStep: "applySeeds"
|
|
10052
10127
|
},
|
|
10053
|
-
|
|
10128
|
+
[OBSERVABILITY_STEP_IDS[0]]: {
|
|
10054
10129
|
order: 50,
|
|
10055
10130
|
title: "Run production preview",
|
|
10056
10131
|
phase: "observability",
|
|
10057
10132
|
optional: true,
|
|
10058
10133
|
parentStep: "applySeeds"
|
|
10059
10134
|
},
|
|
10060
|
-
|
|
10135
|
+
[OBSERVABILITY_STEP_IDS[1]]: {
|
|
10061
10136
|
order: 60,
|
|
10062
10137
|
title: "Collect schema statistics",
|
|
10063
10138
|
phase: "observability",
|
|
10064
10139
|
optional: true,
|
|
10065
10140
|
parentStep: "applySeeds"
|
|
10066
10141
|
},
|
|
10067
|
-
|
|
10142
|
+
[EXECUTION_STEP_IDS[0]]: {
|
|
10068
10143
|
order: 70,
|
|
10069
10144
|
title: "Setup database roles",
|
|
10070
10145
|
phase: "db",
|
|
10071
10146
|
optional: true,
|
|
10072
10147
|
parentStep: "applySeeds"
|
|
10073
10148
|
},
|
|
10074
|
-
|
|
10149
|
+
[EXECUTION_STEP_IDS[1]]: {
|
|
10075
10150
|
order: 80,
|
|
10076
10151
|
title: "Run static checks",
|
|
10077
10152
|
phase: "build"
|
|
10078
10153
|
},
|
|
10079
|
-
|
|
10154
|
+
[EXECUTION_STEP_IDS[2]]: {
|
|
10155
|
+
order: 85,
|
|
10156
|
+
title: "Detect runnable test layers",
|
|
10157
|
+
phase: "build",
|
|
10158
|
+
optional: true
|
|
10159
|
+
},
|
|
10160
|
+
[EXECUTION_STEP_IDS[3]]: {
|
|
10080
10161
|
order: 90,
|
|
10081
10162
|
title: "Build app and prepare Playwright",
|
|
10082
10163
|
phase: "build"
|
|
10083
10164
|
},
|
|
10084
|
-
|
|
10165
|
+
[BUILD_PREPARATION_SUBSTEP_IDS.build]: {
|
|
10085
10166
|
order: 91,
|
|
10086
10167
|
title: "Build application",
|
|
10087
10168
|
phase: "build",
|
|
10088
|
-
parentStep:
|
|
10169
|
+
parentStep: EXECUTION_STEP_IDS[3]
|
|
10089
10170
|
},
|
|
10090
|
-
|
|
10171
|
+
[BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate]: {
|
|
10091
10172
|
order: 92,
|
|
10092
10173
|
title: "Generate manifest",
|
|
10093
10174
|
phase: "build",
|
|
10094
10175
|
optional: true,
|
|
10095
|
-
parentStep:
|
|
10176
|
+
parentStep: EXECUTION_STEP_IDS[3]
|
|
10096
10177
|
},
|
|
10097
|
-
|
|
10178
|
+
[BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall]: {
|
|
10098
10179
|
order: 93,
|
|
10099
10180
|
title: "Install Playwright browsers",
|
|
10100
10181
|
phase: "build",
|
|
10101
10182
|
optional: true,
|
|
10102
|
-
parentStep:
|
|
10183
|
+
parentStep: EXECUTION_STEP_IDS[3]
|
|
10103
10184
|
},
|
|
10104
|
-
|
|
10185
|
+
[EXECUTION_STEP_IDS[4]]: {
|
|
10105
10186
|
order: 100,
|
|
10106
10187
|
title: "Start application",
|
|
10107
10188
|
phase: "build"
|
|
10108
10189
|
},
|
|
10109
|
-
|
|
10190
|
+
[EXECUTION_STEP_IDS[5]]: {
|
|
10110
10191
|
order: 110,
|
|
10111
10192
|
title: "Detect test capabilities",
|
|
10112
10193
|
phase: "test",
|
|
10113
10194
|
optional: true
|
|
10114
10195
|
},
|
|
10115
|
-
|
|
10196
|
+
[EXECUTION_STEP_IDS[6]]: {
|
|
10116
10197
|
order: 120,
|
|
10117
10198
|
title: "Run core test layers",
|
|
10118
10199
|
phase: "test"
|
|
10119
10200
|
},
|
|
10120
|
-
|
|
10201
|
+
[EXECUTION_STEP_IDS[7]]: {
|
|
10121
10202
|
order: 130,
|
|
10122
10203
|
title: "Run E2E phase",
|
|
10123
10204
|
phase: "test",
|
|
@@ -10136,10 +10217,7 @@ var STEP_METADATA = {
|
|
|
10136
10217
|
}
|
|
10137
10218
|
};
|
|
10138
10219
|
var CANONICAL_STEP_IDS = Object.keys(STEP_METADATA).sort((a, b) => b.length - a.length);
|
|
10139
|
-
function
|
|
10140
|
-
return new Date(timestampMs).toISOString();
|
|
10141
|
-
}
|
|
10142
|
-
function getMetadata(stepId) {
|
|
10220
|
+
function getStepMetadata(stepId) {
|
|
10143
10221
|
return STEP_METADATA[stepId];
|
|
10144
10222
|
}
|
|
10145
10223
|
function getCanonicalStepId(statePath) {
|
|
@@ -10155,6 +10233,12 @@ function getStepOrderFromStatePath(statePath) {
|
|
|
10155
10233
|
if (!canonical) return -1;
|
|
10156
10234
|
return STEP_METADATA[canonical]?.order ?? -1;
|
|
10157
10235
|
}
|
|
10236
|
+
|
|
10237
|
+
// src/commands/ci/machine/commands/step-telemetry.ts
|
|
10238
|
+
init_esm_shims();
|
|
10239
|
+
function toIsoString(timestampMs) {
|
|
10240
|
+
return new Date(timestampMs).toISOString();
|
|
10241
|
+
}
|
|
10158
10242
|
function pickPrimaryStatePath(statePaths, previousStatePath) {
|
|
10159
10243
|
if (statePaths.length === 0) return previousStatePath;
|
|
10160
10244
|
if (statePaths.length === 1 && (statePaths[0] === "done" || statePaths[0] === "failed")) {
|
|
@@ -10171,7 +10255,7 @@ function createSkippedSummary(stepId, reason) {
|
|
|
10171
10255
|
return createSyntheticSummary(stepId, "skipped", { reason });
|
|
10172
10256
|
}
|
|
10173
10257
|
function createSyntheticSummary(stepId, status, params = {}) {
|
|
10174
|
-
const metadata =
|
|
10258
|
+
const metadata = getStepMetadata(stepId);
|
|
10175
10259
|
if (!metadata) return null;
|
|
10176
10260
|
const durationMs = params.startedAtMs !== void 0 && params.endedAtMs !== void 0 ? Math.max(0, params.endedAtMs - params.startedAtMs) : void 0;
|
|
10177
10261
|
return [
|
|
@@ -10190,9 +10274,15 @@ function createSyntheticSummary(stepId, status, params = {}) {
|
|
|
10190
10274
|
];
|
|
10191
10275
|
}
|
|
10192
10276
|
function getBuildSkipReason(context) {
|
|
10193
|
-
|
|
10277
|
+
if (!shouldRunPrExecutionPhase(context)) {
|
|
10278
|
+
return getExecutionSkipReason(context);
|
|
10279
|
+
}
|
|
10280
|
+
return isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipBuild === true ? "Skipped by --skip-build" : !context.selectedLayers.some((layer) => layer === 3 || layer === 4) ? "Skipped because no build-dependent layers are selected" : "Skipped by step guard";
|
|
10194
10281
|
}
|
|
10195
10282
|
function getPlaywrightSkipReason(context) {
|
|
10283
|
+
if (!shouldRunPrExecutionPhase(context)) {
|
|
10284
|
+
return getExecutionSkipReason(context);
|
|
10285
|
+
}
|
|
10196
10286
|
if (isCiLocalMode(context)) return "Skipped in ci-local mode";
|
|
10197
10287
|
if (!context.selectedLayers.includes(4)) return "Skipped because Layer 4 is not selected";
|
|
10198
10288
|
if (shouldReusePreparedPlaywright(context)) {
|
|
@@ -10203,6 +10293,18 @@ function getPlaywrightSkipReason(context) {
|
|
|
10203
10293
|
}
|
|
10204
10294
|
return "Skipped by step guard";
|
|
10205
10295
|
}
|
|
10296
|
+
function getObservabilitySkipReason(context) {
|
|
10297
|
+
if (isTestPhase(context)) {
|
|
10298
|
+
return "Skipped in phase=test; observability DB checks are disabled";
|
|
10299
|
+
}
|
|
10300
|
+
return "Skipped by phase selection";
|
|
10301
|
+
}
|
|
10302
|
+
function getExecutionSkipReason(context) {
|
|
10303
|
+
if (isObservabilityPhase(context)) {
|
|
10304
|
+
return "Skipped in phase=observability; execution work is disabled";
|
|
10305
|
+
}
|
|
10306
|
+
return "Skipped by phase selection";
|
|
10307
|
+
}
|
|
10206
10308
|
function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs) {
|
|
10207
10309
|
const entries = [];
|
|
10208
10310
|
const pushEntry = (entry) => {
|
|
@@ -10210,9 +10312,15 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
|
|
|
10210
10312
|
if (trackedStepIds.has(entry[0])) return;
|
|
10211
10313
|
entries.push(entry);
|
|
10212
10314
|
};
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10315
|
+
if (!shouldRunPrExecutionPhase(context)) {
|
|
10316
|
+
const reason = getExecutionSkipReason(context);
|
|
10317
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, reason));
|
|
10318
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, reason));
|
|
10319
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, reason));
|
|
10320
|
+
return entries;
|
|
10321
|
+
}
|
|
10322
|
+
if (!shouldRunPrExecutionPhase(context)) {
|
|
10323
|
+
const reason = getExecutionSkipReason(context);
|
|
10216
10324
|
pushEntry(createSkippedSummary("postSeedPr.execution.buildAndPlaywright.build", reason));
|
|
10217
10325
|
pushEntry(
|
|
10218
10326
|
createSkippedSummary("postSeedPr.execution.buildAndPlaywright.manifestGenerate", reason)
|
|
@@ -10222,106 +10330,64 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
|
|
|
10222
10330
|
);
|
|
10223
10331
|
return entries;
|
|
10224
10332
|
}
|
|
10333
|
+
if (shouldSkipBuild(context)) {
|
|
10334
|
+
const reason = getBuildSkipReason(context);
|
|
10335
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, reason));
|
|
10336
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, reason));
|
|
10337
|
+
pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, reason));
|
|
10338
|
+
return entries;
|
|
10339
|
+
}
|
|
10225
10340
|
if (context.appBuildPassed === true) {
|
|
10226
|
-
pushEntry(
|
|
10227
|
-
createSyntheticSummary(
|
|
10228
|
-
"postSeedPr.execution.buildAndPlaywright.build",
|
|
10229
|
-
"passed",
|
|
10230
|
-
syntheticTimingParams
|
|
10231
|
-
)
|
|
10232
|
-
);
|
|
10341
|
+
pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "passed"));
|
|
10233
10342
|
} else if (context.appBuildPassed === false) {
|
|
10234
10343
|
pushEntry(
|
|
10235
|
-
createSyntheticSummary(
|
|
10236
|
-
...syntheticTimingParams,
|
|
10344
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "failed", {
|
|
10237
10345
|
reason: context.error ?? "App build failed"
|
|
10238
10346
|
})
|
|
10239
10347
|
);
|
|
10240
10348
|
}
|
|
10241
10349
|
if (context.appBuildPassed === true) {
|
|
10242
10350
|
if (context.manifestGenerated === true) {
|
|
10243
|
-
pushEntry(
|
|
10244
|
-
createSyntheticSummary(
|
|
10245
|
-
"postSeedPr.execution.buildAndPlaywright.manifestGenerate",
|
|
10246
|
-
"passed",
|
|
10247
|
-
syntheticTimingParams
|
|
10248
|
-
)
|
|
10249
|
-
);
|
|
10351
|
+
pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "passed"));
|
|
10250
10352
|
} else if (context.manifestGenerated === null) {
|
|
10251
10353
|
pushEntry(
|
|
10252
|
-
createSyntheticSummary(
|
|
10253
|
-
"
|
|
10254
|
-
|
|
10255
|
-
{
|
|
10256
|
-
...syntheticTimingParams,
|
|
10257
|
-
reason: "Skipped because manifest generation is optional for this repository"
|
|
10258
|
-
}
|
|
10259
|
-
)
|
|
10354
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
|
|
10355
|
+
reason: "Skipped because manifest generation is optional for this repository"
|
|
10356
|
+
})
|
|
10260
10357
|
);
|
|
10261
10358
|
} else if (context.manifestGenerated === false) {
|
|
10262
10359
|
pushEntry(
|
|
10263
|
-
createSyntheticSummary(
|
|
10264
|
-
"
|
|
10265
|
-
|
|
10266
|
-
{
|
|
10267
|
-
...syntheticTimingParams,
|
|
10268
|
-
reason: "Manifest generation failed but CI continued because it is optional"
|
|
10269
|
-
}
|
|
10270
|
-
)
|
|
10360
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "failed", {
|
|
10361
|
+
reason: "Manifest generation failed but CI continued because it is optional"
|
|
10362
|
+
})
|
|
10271
10363
|
);
|
|
10272
10364
|
}
|
|
10273
10365
|
} else if (context.appBuildPassed === false) {
|
|
10274
10366
|
pushEntry(
|
|
10275
|
-
createSyntheticSummary(
|
|
10276
|
-
"
|
|
10277
|
-
|
|
10278
|
-
{
|
|
10279
|
-
...syntheticTimingParams,
|
|
10280
|
-
reason: "Skipped because app build failed before manifest generation"
|
|
10281
|
-
}
|
|
10282
|
-
)
|
|
10367
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
|
|
10368
|
+
reason: "Skipped because app build failed before manifest generation"
|
|
10369
|
+
})
|
|
10283
10370
|
);
|
|
10284
10371
|
}
|
|
10285
10372
|
if (shouldReusePreparedPlaywright(context)) {
|
|
10286
10373
|
pushEntry(
|
|
10287
|
-
createSyntheticSummary(
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
{
|
|
10291
|
-
...syntheticTimingParams,
|
|
10292
|
-
reason: getPlaywrightSkipReason(context)
|
|
10293
|
-
}
|
|
10294
|
-
)
|
|
10374
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
|
|
10375
|
+
reason: getPlaywrightSkipReason(context)
|
|
10376
|
+
})
|
|
10295
10377
|
);
|
|
10296
10378
|
} else if (shouldSkipPlaywrightInstall(context)) {
|
|
10297
10379
|
pushEntry(
|
|
10298
|
-
createSyntheticSummary(
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
{
|
|
10302
|
-
...syntheticTimingParams,
|
|
10303
|
-
reason: getPlaywrightSkipReason(context)
|
|
10304
|
-
}
|
|
10305
|
-
)
|
|
10380
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
|
|
10381
|
+
reason: getPlaywrightSkipReason(context)
|
|
10382
|
+
})
|
|
10306
10383
|
);
|
|
10307
10384
|
} else if (context.playwrightInstalled === true) {
|
|
10308
|
-
pushEntry(
|
|
10309
|
-
createSyntheticSummary(
|
|
10310
|
-
"postSeedPr.execution.buildAndPlaywright.playwrightInstall",
|
|
10311
|
-
"passed",
|
|
10312
|
-
syntheticTimingParams
|
|
10313
|
-
)
|
|
10314
|
-
);
|
|
10385
|
+
pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "passed"));
|
|
10315
10386
|
} else if (context.playwrightInstalled === false) {
|
|
10316
10387
|
pushEntry(
|
|
10317
|
-
createSyntheticSummary(
|
|
10318
|
-
"
|
|
10319
|
-
|
|
10320
|
-
{
|
|
10321
|
-
...syntheticTimingParams,
|
|
10322
|
-
reason: "Playwright browser install failed"
|
|
10323
|
-
}
|
|
10324
|
-
)
|
|
10388
|
+
createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "failed", {
|
|
10389
|
+
reason: "Playwright browser install failed"
|
|
10390
|
+
})
|
|
10325
10391
|
);
|
|
10326
10392
|
}
|
|
10327
10393
|
return entries;
|
|
@@ -10333,38 +10399,43 @@ function getSkippedStepEntries(context, trackedStepIds) {
|
|
|
10333
10399
|
const entry = createSkippedSummary(stepId, reason);
|
|
10334
10400
|
if (entry) skipped.push(entry);
|
|
10335
10401
|
};
|
|
10402
|
+
if (isCiPrMode(context) && !shouldRunPrObservabilityPhase(context)) {
|
|
10403
|
+
const reason = getObservabilitySkipReason(context);
|
|
10404
|
+
for (const stepId of OBSERVABILITY_STEP_IDS) {
|
|
10405
|
+
pushSkipped(stepId, reason);
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10336
10408
|
if (isTestPhase(context)) {
|
|
10337
10409
|
pushSkipped("syncSchema", "Skipped in phase=test; reusing prepared database");
|
|
10338
10410
|
pushSkipped("applySeeds", "Skipped in phase=test; reusing prepared database");
|
|
10339
|
-
pushSkipped(
|
|
10340
|
-
"postSeedPr.observability.productionPreview",
|
|
10341
|
-
"Skipped in phase=test; observability DB checks are disabled"
|
|
10342
|
-
);
|
|
10343
|
-
pushSkipped(
|
|
10344
|
-
"postSeedPr.observability.collectSchemaStats",
|
|
10345
|
-
"Skipped in phase=test; observability DB checks are disabled"
|
|
10346
|
-
);
|
|
10347
10411
|
}
|
|
10348
10412
|
if (!shouldPostGitHubComment(context) || context.prContext?.prNumber === null) {
|
|
10349
10413
|
const commentReason = context.input.skipGithubComment === true ? "Skipped by --skip-github-comment" : context.executionEnv !== "github-actions" ? "Skipped outside GitHub Actions" : "Skipped because PR context is unavailable";
|
|
10350
10414
|
pushSkipped("initialComment", commentReason);
|
|
10351
10415
|
pushSkipped("finalize.postComment", commentReason);
|
|
10352
10416
|
}
|
|
10353
|
-
if (
|
|
10354
|
-
const reason =
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10417
|
+
if (isCiPrMode(context) && !shouldRunPrExecutionPhase(context)) {
|
|
10418
|
+
const reason = getExecutionSkipReason(context);
|
|
10419
|
+
for (const stepId of EXECUTION_STEP_IDS) {
|
|
10420
|
+
pushSkipped(stepId, reason);
|
|
10421
|
+
}
|
|
10422
|
+
} else {
|
|
10423
|
+
if (shouldSkipStaticChecks(context)) {
|
|
10424
|
+
const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipStaticChecks === true ? "Skipped by --skip-static-checks" : "Skipped by step guard";
|
|
10425
|
+
pushSkipped(EXECUTION_STEP_IDS[1], reason);
|
|
10426
|
+
}
|
|
10427
|
+
if (shouldSkipBuild(context)) {
|
|
10428
|
+
pushSkipped(EXECUTION_STEP_IDS[3], getBuildSkipReason(context));
|
|
10429
|
+
}
|
|
10430
|
+
if (shouldSkipAppStart(context)) {
|
|
10431
|
+
const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : !context.selectedLayers.includes(4) ? "Skipped because Layer 4 is not selected" : "Skipped by step guard";
|
|
10432
|
+
pushSkipped(EXECUTION_STEP_IDS[4], reason);
|
|
10433
|
+
}
|
|
10434
|
+
if (!context.selectedLayers.includes(4)) {
|
|
10435
|
+
pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because Layer 4 is not selected");
|
|
10436
|
+
} else if (Object.values(context.layerResults).some((result) => result.status === "failed")) {
|
|
10437
|
+
pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because blocking core tests failed");
|
|
10438
|
+
}
|
|
10368
10439
|
}
|
|
10369
10440
|
return skipped;
|
|
10370
10441
|
}
|
|
@@ -10372,7 +10443,7 @@ function createStepTelemetryTracker() {
|
|
|
10372
10443
|
const stepStates = /* @__PURE__ */ new Map();
|
|
10373
10444
|
let activeStepIds = /* @__PURE__ */ new Set();
|
|
10374
10445
|
const startStep = (stepId, nowMs) => {
|
|
10375
|
-
const metadata =
|
|
10446
|
+
const metadata = getStepMetadata(stepId);
|
|
10376
10447
|
if (!metadata) return;
|
|
10377
10448
|
if (stepStates.has(stepId)) return;
|
|
10378
10449
|
stepStates.set(stepId, {
|
|
@@ -10411,8 +10482,7 @@ function createStepTelemetryTracker() {
|
|
|
10411
10482
|
finishStep(stepId, "failed", nowMs);
|
|
10412
10483
|
continue;
|
|
10413
10484
|
}
|
|
10414
|
-
|
|
10415
|
-
finishStep(stepId, succeeded ? "passed" : metadata?.optional ? "killed" : "killed", nowMs);
|
|
10485
|
+
finishStep(stepId, succeeded ? "passed" : "killed", nowMs);
|
|
10416
10486
|
}
|
|
10417
10487
|
activeStepIds = /* @__PURE__ */ new Set();
|
|
10418
10488
|
const trackedEntries = [...stepStates.entries()].sort((a, b) => a[1].metadata.order - b[1].metadata.order).map(([stepId, state]) => {
|
|
@@ -10445,7 +10515,7 @@ function createStepTelemetryTracker() {
|
|
|
10445
10515
|
new Set([...trackedEntries, ...skippedEntries].map(([stepId]) => stepId)));
|
|
10446
10516
|
return Object.fromEntries(
|
|
10447
10517
|
[...trackedEntries, ...skippedEntries, ...syntheticBuildEntries].sort(
|
|
10448
|
-
([leftStepId], [rightStepId]) => (
|
|
10518
|
+
([leftStepId], [rightStepId]) => (getStepMetadata(leftStepId)?.order ?? Number.MAX_SAFE_INTEGER) - (getStepMetadata(rightStepId)?.order ?? Number.MAX_SAFE_INTEGER)
|
|
10449
10519
|
)
|
|
10450
10520
|
);
|
|
10451
10521
|
}
|
|
@@ -10454,17 +10524,22 @@ function createStepTelemetryTracker() {
|
|
|
10454
10524
|
|
|
10455
10525
|
// src/commands/ci/machine/commands/machine-runner.ts
|
|
10456
10526
|
var FLUSH_DELAY_MS = 100;
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10527
|
+
function createProgressState() {
|
|
10528
|
+
return {
|
|
10529
|
+
updateSequence: 0,
|
|
10530
|
+
pendingUpdate: null,
|
|
10531
|
+
heartbeatTimer: null,
|
|
10532
|
+
latestSnapshot: null,
|
|
10533
|
+
heartbeatStep: null,
|
|
10534
|
+
heartbeatFailedStep: null,
|
|
10535
|
+
updateCount: 0,
|
|
10536
|
+
stepStartTime: Date.now(),
|
|
10537
|
+
currentTrackedStep: null,
|
|
10538
|
+
stepTimings: {},
|
|
10539
|
+
previousActiveSteps: /* @__PURE__ */ new Set()
|
|
10540
|
+
};
|
|
10541
|
+
}
|
|
10542
|
+
var progress = createProgressState();
|
|
10468
10543
|
var DEFAULT_PROGRESS_HEARTBEAT_MS = 3e4;
|
|
10469
10544
|
var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
|
|
10470
10545
|
"syncSchema",
|
|
@@ -10475,95 +10550,15 @@ var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
|
|
|
10475
10550
|
"runTests"
|
|
10476
10551
|
]);
|
|
10477
10552
|
function resetProgressTracking() {
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
}
|
|
10484
|
-
latestSnapshot = null;
|
|
10485
|
-
heartbeatStep = null;
|
|
10486
|
-
heartbeatFailedStep = null;
|
|
10487
|
-
progressUpdateSuccessCount = 0;
|
|
10488
|
-
previousActiveSteps = /* @__PURE__ */ new Set();
|
|
10489
|
-
stepStartTime = Date.now();
|
|
10490
|
-
currentTrackedStep = null;
|
|
10491
|
-
for (const key of Object.keys(stepTimings)) {
|
|
10492
|
-
delete stepTimings[key];
|
|
10493
|
-
}
|
|
10494
|
-
}
|
|
10495
|
-
var STATE_TO_STEP = {
|
|
10496
|
-
// Setup phase
|
|
10497
|
-
setup: "setup",
|
|
10498
|
-
"setup.resolving": "setup",
|
|
10499
|
-
"setup.local": "setup",
|
|
10500
|
-
"setup.prLocal": "setup",
|
|
10501
|
-
"setup.complete": "setup",
|
|
10502
|
-
// DB phase
|
|
10503
|
-
dbReset: "syncSchema",
|
|
10504
|
-
pullProduction: "syncSchema",
|
|
10505
|
-
syncSchema: "syncSchema",
|
|
10506
|
-
applySeeds: "applySeeds",
|
|
10507
|
-
productionPreview: "observability",
|
|
10508
|
-
collectSchemaStats: "observability",
|
|
10509
|
-
postSeedPr: "postSeedChecks",
|
|
10510
|
-
"postSeedPr.execution": "postSeedChecks",
|
|
10511
|
-
"postSeedPr.observability": "observability",
|
|
10512
|
-
"postSeedPr.execution.setupRoles": "postSeedChecks",
|
|
10513
|
-
"postSeedPr.observability.productionPreview": "observability",
|
|
10514
|
-
"postSeedPr.observability.collectSchemaStats": "observability",
|
|
10515
|
-
"postSeedPr.execution.staticChecks": "staticChecks",
|
|
10516
|
-
"postSeedPr.execution.buildAndPlaywright": "build",
|
|
10517
|
-
"postSeedPr.execution.appStart": "build",
|
|
10518
|
-
"postSeedPr.execution.capabilities": "build",
|
|
10519
|
-
"postSeedPr.execution.runCoreTests": "runTests",
|
|
10520
|
-
"postSeedPr.execution.coreTestsComplete": "runTests",
|
|
10521
|
-
"postSeedPr.execution.coreTestsFailed": "runTests",
|
|
10522
|
-
"postSeedPr.execution.e2ePhase": "runTests",
|
|
10523
|
-
"postSeedPr.execution.done": "runTests",
|
|
10524
|
-
decidePath: "postSeedChecks",
|
|
10525
|
-
installPgTap: "applySeeds",
|
|
10526
|
-
setupRoles: "applySeeds",
|
|
10527
|
-
// Build phase
|
|
10528
|
-
staticChecks: "staticChecks",
|
|
10529
|
-
buildAndPlaywright: "build",
|
|
10530
|
-
appStart: "build",
|
|
10531
|
-
capabilities: "build",
|
|
10532
|
-
// Test phase (ci-local)
|
|
10533
|
-
runTests: "runTests",
|
|
10534
|
-
testsComplete: "runTests",
|
|
10535
|
-
testsFailed: "runTests",
|
|
10536
|
-
// Test phase (ci-pr: blocking/non-blocking split)
|
|
10537
|
-
runCoreTests: "runTests",
|
|
10538
|
-
coreTestsComplete: "runTests",
|
|
10539
|
-
coreTestsFailed: "runTests",
|
|
10540
|
-
e2ePhase: "runTests",
|
|
10541
|
-
"e2ePhase.intermediateComment": "runTests",
|
|
10542
|
-
"e2ePhase.e2eTests": "runTests",
|
|
10543
|
-
// Finalize phase
|
|
10544
|
-
finalize: "finalize",
|
|
10545
|
-
"finalize.writeSummary": "finalize",
|
|
10546
|
-
"finalize.postComment": "finalize",
|
|
10547
|
-
"finalize.complete": "finalize",
|
|
10548
|
-
// Final states
|
|
10549
|
-
done: "finalize",
|
|
10550
|
-
failed: "finalize"
|
|
10551
|
-
};
|
|
10552
|
-
var STEP_ORDER = [
|
|
10553
|
-
"setup",
|
|
10554
|
-
"syncSchema",
|
|
10555
|
-
"applySeeds",
|
|
10556
|
-
"postSeedChecks",
|
|
10557
|
-
"observability",
|
|
10558
|
-
"staticChecks",
|
|
10559
|
-
"build",
|
|
10560
|
-
"runTests",
|
|
10561
|
-
"finalize"
|
|
10562
|
-
];
|
|
10553
|
+
if (progress.heartbeatTimer) {
|
|
10554
|
+
clearInterval(progress.heartbeatTimer);
|
|
10555
|
+
}
|
|
10556
|
+
Object.assign(progress, createProgressState());
|
|
10557
|
+
}
|
|
10563
10558
|
function resolveStepForState(state) {
|
|
10564
10559
|
let cursor = state;
|
|
10565
10560
|
while (cursor.length > 0) {
|
|
10566
|
-
const step =
|
|
10561
|
+
const step = PROGRESS_STATE_TO_STEP[cursor];
|
|
10567
10562
|
if (step) return step;
|
|
10568
10563
|
const nextDot = cursor.lastIndexOf(".");
|
|
10569
10564
|
if (nextDot === -1) break;
|
|
@@ -10571,20 +10566,22 @@ function resolveStepForState(state) {
|
|
|
10571
10566
|
}
|
|
10572
10567
|
return void 0;
|
|
10573
10568
|
}
|
|
10574
|
-
function getCompletedSteps(currentStep,
|
|
10569
|
+
function getCompletedSteps(currentStep, stepTimings = {}) {
|
|
10575
10570
|
const completed = new Set(
|
|
10576
|
-
Object.keys(
|
|
10571
|
+
Object.keys(stepTimings).filter(
|
|
10572
|
+
(step) => PROGRESS_STEP_ORDER.includes(step)
|
|
10573
|
+
)
|
|
10577
10574
|
);
|
|
10578
10575
|
completed.delete(currentStep);
|
|
10579
|
-
return
|
|
10576
|
+
return PROGRESS_STEP_ORDER.filter((step) => completed.has(step));
|
|
10580
10577
|
}
|
|
10581
10578
|
function recordStepTiming(newStep) {
|
|
10582
10579
|
const now = Date.now();
|
|
10583
|
-
if (currentTrackedStep !== null) {
|
|
10584
|
-
stepTimings[currentTrackedStep] = now - stepStartTime;
|
|
10580
|
+
if (progress.currentTrackedStep !== null) {
|
|
10581
|
+
progress.stepTimings[progress.currentTrackedStep] = now - progress.stepStartTime;
|
|
10585
10582
|
}
|
|
10586
|
-
currentTrackedStep = newStep;
|
|
10587
|
-
stepStartTime = now;
|
|
10583
|
+
progress.currentTrackedStep = newStep;
|
|
10584
|
+
progress.stepStartTime = now;
|
|
10588
10585
|
}
|
|
10589
10586
|
function parseProgressHeartbeatMs(context) {
|
|
10590
10587
|
const rawValue = context.input.progressIntervalSeconds?.trim();
|
|
@@ -10596,24 +10593,28 @@ function parseProgressHeartbeatMs(context) {
|
|
|
10596
10593
|
return parsedSeconds * 1e3;
|
|
10597
10594
|
}
|
|
10598
10595
|
function stopProgressHeartbeat() {
|
|
10599
|
-
if (
|
|
10600
|
-
clearInterval(
|
|
10601
|
-
|
|
10596
|
+
if (progress.heartbeatTimer) {
|
|
10597
|
+
clearInterval(progress.heartbeatTimer);
|
|
10598
|
+
progress.heartbeatTimer = null;
|
|
10602
10599
|
}
|
|
10603
10600
|
}
|
|
10604
10601
|
function refreshProgressHeartbeat(currentStep, failedStep = null) {
|
|
10605
|
-
heartbeatStep = currentStep;
|
|
10606
|
-
heartbeatFailedStep = failedStep;
|
|
10602
|
+
progress.heartbeatStep = currentStep;
|
|
10603
|
+
progress.heartbeatFailedStep = failedStep;
|
|
10607
10604
|
stopProgressHeartbeat();
|
|
10608
10605
|
if (!HEARTBEAT_ELIGIBLE_STEPS.has(currentStep)) {
|
|
10609
10606
|
return;
|
|
10610
10607
|
}
|
|
10611
|
-
const intervalMs = latestSnapshot ? parseProgressHeartbeatMs(latestSnapshot.context) : DEFAULT_PROGRESS_HEARTBEAT_MS;
|
|
10612
|
-
|
|
10613
|
-
if (!latestSnapshot || !heartbeatStep) return;
|
|
10614
|
-
updateProgressComment(
|
|
10608
|
+
const intervalMs = progress.latestSnapshot ? parseProgressHeartbeatMs(progress.latestSnapshot.context) : DEFAULT_PROGRESS_HEARTBEAT_MS;
|
|
10609
|
+
progress.heartbeatTimer = setInterval(() => {
|
|
10610
|
+
if (!progress.latestSnapshot || !progress.heartbeatStep) return;
|
|
10611
|
+
updateProgressComment(
|
|
10612
|
+
progress.latestSnapshot.context,
|
|
10613
|
+
progress.heartbeatStep,
|
|
10614
|
+
progress.heartbeatFailedStep
|
|
10615
|
+
);
|
|
10615
10616
|
}, intervalMs);
|
|
10616
|
-
|
|
10617
|
+
progress.heartbeatTimer.unref?.();
|
|
10617
10618
|
}
|
|
10618
10619
|
function updateProgressComment(context, currentStep, failedStep = null) {
|
|
10619
10620
|
if (!shouldPostGitHubComment(context)) return;
|
|
@@ -10622,25 +10623,26 @@ function updateProgressComment(context, currentStep, failedStep = null) {
|
|
|
10622
10623
|
if (!token) return;
|
|
10623
10624
|
const repository = process.env.GITHUB_REPOSITORY?.trim();
|
|
10624
10625
|
if (!repository || !repository.includes("/")) return;
|
|
10625
|
-
const mySeq = ++
|
|
10626
|
+
const mySeq = ++progress.updateSequence;
|
|
10627
|
+
progress.updateCount++;
|
|
10626
10628
|
const doUpdate = async () => {
|
|
10627
|
-
if (
|
|
10629
|
+
if (progress.pendingUpdate) {
|
|
10628
10630
|
try {
|
|
10629
|
-
await
|
|
10631
|
+
await progress.pendingUpdate;
|
|
10630
10632
|
} catch {
|
|
10631
10633
|
}
|
|
10632
10634
|
}
|
|
10633
|
-
if (mySeq <
|
|
10635
|
+
if (mySeq < progress.updateSequence) {
|
|
10634
10636
|
return;
|
|
10635
10637
|
}
|
|
10636
|
-
const freshContext = latestSnapshot?.context ?? context;
|
|
10637
|
-
const completedSteps = getCompletedSteps(currentStep, stepTimings);
|
|
10638
|
+
const freshContext = progress.latestSnapshot?.context ?? context;
|
|
10639
|
+
const completedSteps = getCompletedSteps(currentStep, progress.stepTimings);
|
|
10638
10640
|
const progressInput = createProgressCommentInput(
|
|
10639
10641
|
freshContext,
|
|
10640
10642
|
currentStep,
|
|
10641
10643
|
completedSteps,
|
|
10642
10644
|
failedStep,
|
|
10643
|
-
{ ...stepTimings }
|
|
10645
|
+
{ ...progress.stepTimings }
|
|
10644
10646
|
);
|
|
10645
10647
|
const body = `<!-- runa-ci-report -->
|
|
10646
10648
|
${generateProgressCommentBody(progressInput)}`;
|
|
@@ -10651,11 +10653,10 @@ ${generateProgressCommentBody(progressInput)}`;
|
|
|
10651
10653
|
marker: "<!-- runa-ci-report -->",
|
|
10652
10654
|
body
|
|
10653
10655
|
});
|
|
10654
|
-
|
|
10655
|
-
} catch (error) {
|
|
10656
|
+
} catch {
|
|
10656
10657
|
}
|
|
10657
10658
|
};
|
|
10658
|
-
|
|
10659
|
+
progress.pendingUpdate = doUpdate();
|
|
10659
10660
|
}
|
|
10660
10661
|
function determineFailedStep(context) {
|
|
10661
10662
|
if (context.testsRun && Object.values(context.layerResults).some((r) => r.status === "failed"))
|
|
@@ -10681,8 +10682,8 @@ function handleProgressCommentUpdate(snapshot, prevState, currentState, activeSt
|
|
|
10681
10682
|
const currentStepSet = new Set(
|
|
10682
10683
|
activeStatePaths.map((p) => resolveStepForState(p)).filter((s) => s !== void 0)
|
|
10683
10684
|
);
|
|
10684
|
-
const parallelStepChanged = !setsEqual(previousActiveSteps, currentStepSet);
|
|
10685
|
-
previousActiveSteps = currentStepSet;
|
|
10685
|
+
const parallelStepChanged = !setsEqual(progress.previousActiveSteps, currentStepSet);
|
|
10686
|
+
progress.previousActiveSteps = currentStepSet;
|
|
10686
10687
|
if (!currentStep || !parallelStepChanged && currentStep === prevStep && !shouldRefreshWithinStep)
|
|
10687
10688
|
return;
|
|
10688
10689
|
if (!shouldRefreshWithinStep) {
|
|
@@ -10725,11 +10726,10 @@ async function runCiMachine(input, logger, onStateChange) {
|
|
|
10725
10726
|
let settled = false;
|
|
10726
10727
|
const stepTelemetry = createStepTelemetryTracker();
|
|
10727
10728
|
actor.subscribe((snapshot) => {
|
|
10728
|
-
latestSnapshot = snapshot;
|
|
10729
|
+
progress.latestSnapshot = snapshot;
|
|
10729
10730
|
const activeStatePaths = getSnapshotStatePaths(snapshot);
|
|
10730
10731
|
stepTelemetry.transition(activeStatePaths);
|
|
10731
10732
|
const currentState = pickPrimaryStatePath(activeStatePaths, previousState) ?? activeStatePaths[0] ?? previousState ?? "unknown";
|
|
10732
|
-
snapshot.status;
|
|
10733
10733
|
if (currentState && currentState !== "done" && currentState !== "failed" && !currentState.startsWith("finalize")) {
|
|
10734
10734
|
lastActionableState = currentState;
|
|
10735
10735
|
}
|
|
@@ -11006,7 +11006,7 @@ init_esm_shims();
|
|
|
11006
11006
|
init_esm_shims();
|
|
11007
11007
|
var CiModeSchema = z.enum(["ci-local", "ci-pr-local"]);
|
|
11008
11008
|
var CiExecutionEnvSchema = z.enum(["github-actions", "local"]);
|
|
11009
|
-
var CiPhaseSchema = z.enum(["all", "test"]);
|
|
11009
|
+
var CiPhaseSchema = z.enum(["all", "blocking", "observability", "test"]);
|
|
11010
11010
|
var CiDbModeSchema = z.enum(["auto", "local"]);
|
|
11011
11011
|
var RepoKindSchema = z.enum(["monorepo", "pj-repo", "unknown"]);
|
|
11012
11012
|
var StepStatusSchema = z.enum(["passed", "failed", "skipped", "killed", "timeout"]);
|
|
@@ -11096,7 +11096,9 @@ z.object({
|
|
|
11096
11096
|
/** Skip GitHub comment */
|
|
11097
11097
|
skipGithubComment: z.boolean().optional(),
|
|
11098
11098
|
/** Specific layers */
|
|
11099
|
-
layers: z.array(z.number().int()).optional()
|
|
11099
|
+
layers: z.array(z.number().int()).optional(),
|
|
11100
|
+
/** Internal workflow-only CI profile */
|
|
11101
|
+
internalProfile: z.enum(["schema-only"]).optional()
|
|
11100
11102
|
}).passthrough();
|
|
11101
11103
|
z.object({
|
|
11102
11104
|
passed: z.boolean(),
|
|
@@ -11235,7 +11237,6 @@ z.object({
|
|
|
11235
11237
|
});
|
|
11236
11238
|
z.object({
|
|
11237
11239
|
allowLocalFallback: z.boolean(),
|
|
11238
|
-
allowPartialPhases: z.boolean(),
|
|
11239
11240
|
source: z.enum(["config", "default"])
|
|
11240
11241
|
});
|
|
11241
11242
|
z.object({
|
|
@@ -11245,6 +11246,43 @@ z.object({
|
|
|
11245
11246
|
port: z.number().int()
|
|
11246
11247
|
});
|
|
11247
11248
|
|
|
11249
|
+
// src/commands/ci/machine/commands/ci-pr-internal-profile.ts
|
|
11250
|
+
init_esm_shims();
|
|
11251
|
+
function parseInternalLayers(raw) {
|
|
11252
|
+
const values = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
11253
|
+
if (values.length === 0) {
|
|
11254
|
+
throw new Error("RUNA_CI_INTERNAL_LAYERS must include at least one layer number");
|
|
11255
|
+
}
|
|
11256
|
+
const layers = values.map((token) => {
|
|
11257
|
+
const parsed = Number.parseInt(token, 10);
|
|
11258
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 4) {
|
|
11259
|
+
throw new Error(
|
|
11260
|
+
`Invalid RUNA_CI_INTERNAL_LAYERS value: "${raw}". Expected a comma-separated list of layers 1-4.`
|
|
11261
|
+
);
|
|
11262
|
+
}
|
|
11263
|
+
return parsed;
|
|
11264
|
+
});
|
|
11265
|
+
return Array.from(new Set(layers)).sort((a, b) => a - b);
|
|
11266
|
+
}
|
|
11267
|
+
function resolveInternalCiOverrides(env = process.env) {
|
|
11268
|
+
const profileRaw = env.RUNA_CI_INTERNAL_PROFILE?.trim();
|
|
11269
|
+
const layersRaw = env.RUNA_CI_INTERNAL_LAYERS?.trim();
|
|
11270
|
+
if (!profileRaw && !layersRaw) {
|
|
11271
|
+
return {};
|
|
11272
|
+
}
|
|
11273
|
+
let profile;
|
|
11274
|
+
if (profileRaw) {
|
|
11275
|
+
if (profileRaw !== "schema-only") {
|
|
11276
|
+
throw new Error(
|
|
11277
|
+
`Unsupported RUNA_CI_INTERNAL_PROFILE value: "${profileRaw}". Expected "schema-only".`
|
|
11278
|
+
);
|
|
11279
|
+
}
|
|
11280
|
+
profile = profileRaw;
|
|
11281
|
+
}
|
|
11282
|
+
const layers = layersRaw ? parseInternalLayers(layersRaw) : profile === "schema-only" ? [2] : void 0;
|
|
11283
|
+
return { profile, layers };
|
|
11284
|
+
}
|
|
11285
|
+
|
|
11248
11286
|
// src/commands/ci/machine/commands/ci-pr.ts
|
|
11249
11287
|
var cliOutputFormat = "human";
|
|
11250
11288
|
var isGitHubActionsMode2 = false;
|
|
@@ -11299,6 +11337,73 @@ function logSetupRolesStatus(ctx, logger) {
|
|
|
11299
11337
|
}
|
|
11300
11338
|
}
|
|
11301
11339
|
var CI_PR_UNKNOWN_STATE_LOG_SKIP = /* @__PURE__ */ new Set(["decidePath", "done", "failed"]);
|
|
11340
|
+
var logStaticChecks = (ctx, logger) => {
|
|
11341
|
+
logSetupRolesStatus(ctx, logger);
|
|
11342
|
+
logSection3("Static Checks: type-check + lint (parallel)");
|
|
11343
|
+
logger.info("Running: pnpm type-check || pnpm lint");
|
|
11344
|
+
};
|
|
11345
|
+
var logBuildAndPlaywright = (ctx, logger) => {
|
|
11346
|
+
if (ctx.staticChecksPassed) {
|
|
11347
|
+
logger.success("Static checks passed");
|
|
11348
|
+
}
|
|
11349
|
+
if (shouldReusePreparedPlaywright(ctx)) {
|
|
11350
|
+
logSection3("Build: App Build + Reuse Prepared Playwright");
|
|
11351
|
+
logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
|
|
11352
|
+
return;
|
|
11353
|
+
}
|
|
11354
|
+
logSection3("Build: App Build + Playwright Install (parallel)");
|
|
11355
|
+
logger.info("Running: pnpm build || pnpm exec playwright install chromium");
|
|
11356
|
+
};
|
|
11357
|
+
var logLayerContent = (ctx, logger) => {
|
|
11358
|
+
if (ctx.staticChecksPassed) {
|
|
11359
|
+
logger.success("Static checks passed");
|
|
11360
|
+
}
|
|
11361
|
+
logSection3("Detect Runnable Layers");
|
|
11362
|
+
logger.info(`Selected layers before filtering: ${ctx.selectedLayers.join(", ")}`);
|
|
11363
|
+
};
|
|
11364
|
+
var logAppStart = (ctx, logger) => {
|
|
11365
|
+
if (ctx.appBuildPassed) {
|
|
11366
|
+
logger.success("App build completed");
|
|
11367
|
+
}
|
|
11368
|
+
if (ctx.manifestGenerated) {
|
|
11369
|
+
logger.success("Manifest generated (Layer 3 ready)");
|
|
11370
|
+
} else if (ctx.manifestGenerated === null) {
|
|
11371
|
+
logger.info("Manifest generation skipped (optional)");
|
|
11372
|
+
} else if (ctx.manifestGenerated === false) {
|
|
11373
|
+
logger.warn("Manifest generation failed (Layer 3 may be skipped)");
|
|
11374
|
+
}
|
|
11375
|
+
if (ctx.playwrightInstalled) {
|
|
11376
|
+
logger.success("Playwright browsers installed");
|
|
11377
|
+
}
|
|
11378
|
+
logSection3("Start Application");
|
|
11379
|
+
logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
|
|
11380
|
+
};
|
|
11381
|
+
var logCapabilities = (ctx, logger) => {
|
|
11382
|
+
if (ctx.appStarted) {
|
|
11383
|
+
logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
|
|
11384
|
+
}
|
|
11385
|
+
logSection3("Detect Test Capabilities");
|
|
11386
|
+
};
|
|
11387
|
+
var logRunCoreTests = (ctx, logger) => {
|
|
11388
|
+
const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
|
|
11389
|
+
logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
|
|
11390
|
+
logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
11391
|
+
};
|
|
11392
|
+
var logCollectSchemaStats = (ctx, logger) => {
|
|
11393
|
+
if (ctx.productionPreview?.executed) {
|
|
11394
|
+
if (ctx.productionPreview.hasChanges) {
|
|
11395
|
+
logger.info("Production preview: schema changes detected");
|
|
11396
|
+
} else {
|
|
11397
|
+
logger.success("Production preview: no schema changes");
|
|
11398
|
+
}
|
|
11399
|
+
}
|
|
11400
|
+
logSection3("Database: Collect Schema Statistics");
|
|
11401
|
+
logger.info("Comparing Local/CI/Production schemas...");
|
|
11402
|
+
};
|
|
11403
|
+
var logE2ePhase = (_ctx, logger) => {
|
|
11404
|
+
logSection3("E2E Phase: Layer 4 + Intermediate Comment");
|
|
11405
|
+
logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
|
|
11406
|
+
};
|
|
11302
11407
|
var stateLogHandlers2 = {
|
|
11303
11408
|
// ─── Setup Phase ───────────────────────────────────────────
|
|
11304
11409
|
setup: (_ctx, _logger) => {
|
|
@@ -11319,9 +11424,12 @@ var stateLogHandlers2 = {
|
|
|
11319
11424
|
logger.info("Posting initial CI progress comment to PR...");
|
|
11320
11425
|
},
|
|
11321
11426
|
// ─── Database Phase ────────────────────────────────────────
|
|
11322
|
-
syncSchema: (
|
|
11323
|
-
|
|
11324
|
-
|
|
11427
|
+
syncSchema: (ctx, logger) => {
|
|
11428
|
+
const isCiLocalMode2 = ctx.mode === "ci-local";
|
|
11429
|
+
logSection3(`Database: Apply Schema (${isCiLocalMode2 ? "db sync" : "db apply --no-seed"})`);
|
|
11430
|
+
logger.info(
|
|
11431
|
+
isCiLocalMode2 ? "Running: pnpm exec runa db sync local --verbose" : "Running: pnpm exec runa db apply preview --no-seed --verbose"
|
|
11432
|
+
);
|
|
11325
11433
|
},
|
|
11326
11434
|
applySeeds: (ctx, logger) => {
|
|
11327
11435
|
if (ctx.schemaApplied) {
|
|
@@ -11330,6 +11438,7 @@ var stateLogHandlers2 = {
|
|
|
11330
11438
|
logSection3("Database: Apply Seeds (db seed)");
|
|
11331
11439
|
logger.info("Running: pnpm exec runa db seed ci --auto-approve");
|
|
11332
11440
|
},
|
|
11441
|
+
// Nested paths (postSeedPr.*) — differ from flat counterparts
|
|
11333
11442
|
"postSeedPr.execution.setupRoles": (ctx, logger) => {
|
|
11334
11443
|
if (ctx.seedsApplied) {
|
|
11335
11444
|
logger.success("Seeds applied successfully");
|
|
@@ -11342,68 +11451,18 @@ var stateLogHandlers2 = {
|
|
|
11342
11451
|
logger.success("Seeds applied successfully");
|
|
11343
11452
|
}
|
|
11344
11453
|
logSection3("Database: Production Preview (dry-run)");
|
|
11345
|
-
logger.info("Running: pnpm exec runa db
|
|
11346
|
-
},
|
|
11347
|
-
"postSeedPr.observability.collectSchemaStats": (ctx, logger) => {
|
|
11348
|
-
if (ctx.productionPreview?.executed) {
|
|
11349
|
-
if (ctx.productionPreview.hasChanges) {
|
|
11350
|
-
logger.info("Production preview: schema changes detected");
|
|
11351
|
-
} else {
|
|
11352
|
-
logger.success("Production preview: no schema changes");
|
|
11353
|
-
}
|
|
11354
|
-
}
|
|
11355
|
-
logSection3("Database: Collect Schema Statistics");
|
|
11356
|
-
logger.info("Comparing Local/CI/Production schemas...");
|
|
11357
|
-
},
|
|
11358
|
-
"postSeedPr.execution.staticChecks": (ctx, logger) => {
|
|
11359
|
-
logSetupRolesStatus(ctx, logger);
|
|
11360
|
-
logSection3("Static Checks: type-check + lint (parallel)");
|
|
11361
|
-
logger.info("Running: pnpm type-check || pnpm lint");
|
|
11362
|
-
},
|
|
11363
|
-
"postSeedPr.execution.buildAndPlaywright": (ctx, logger) => {
|
|
11364
|
-
if (ctx.staticChecksPassed) {
|
|
11365
|
-
logger.success("Static checks passed");
|
|
11366
|
-
}
|
|
11367
|
-
if (shouldReusePreparedPlaywright(ctx)) {
|
|
11368
|
-
logSection3("Build: App Build + Reuse Prepared Playwright");
|
|
11369
|
-
logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
|
|
11370
|
-
return;
|
|
11371
|
-
}
|
|
11372
|
-
logSection3("Build: App Build + Playwright Install (parallel)");
|
|
11373
|
-
logger.info("Running: pnpm build || pnpm exec playwright install chromium");
|
|
11374
|
-
},
|
|
11375
|
-
"postSeedPr.execution.appStart": (ctx, logger) => {
|
|
11376
|
-
if (ctx.appBuildPassed) {
|
|
11377
|
-
logger.success("App build completed");
|
|
11378
|
-
}
|
|
11379
|
-
if (ctx.manifestGenerated) {
|
|
11380
|
-
logger.success("Manifest generated (Layer 3 ready)");
|
|
11381
|
-
} else if (ctx.manifestGenerated === null) {
|
|
11382
|
-
logger.info("Manifest generation skipped (optional)");
|
|
11383
|
-
} else if (ctx.manifestGenerated === false) {
|
|
11384
|
-
logger.warn("Manifest generation failed (Layer 3 may be skipped)");
|
|
11385
|
-
}
|
|
11386
|
-
if (ctx.playwrightInstalled) {
|
|
11387
|
-
logger.success("Playwright browsers installed");
|
|
11388
|
-
}
|
|
11389
|
-
logSection3("Start Application");
|
|
11390
|
-
logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
|
|
11391
|
-
},
|
|
11392
|
-
"postSeedPr.execution.capabilities": (ctx, logger) => {
|
|
11393
|
-
if (ctx.appStarted) {
|
|
11394
|
-
logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
|
|
11395
|
-
}
|
|
11396
|
-
logSection3("Detect Test Capabilities");
|
|
11397
|
-
},
|
|
11398
|
-
"postSeedPr.execution.runCoreTests": (ctx, logger) => {
|
|
11399
|
-
const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
|
|
11400
|
-
logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
|
|
11401
|
-
logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
11402
|
-
},
|
|
11403
|
-
"postSeedPr.execution.e2ePhase": (_ctx, logger) => {
|
|
11404
|
-
logSection3("E2E Phase: Layer 4 + Intermediate Comment");
|
|
11405
|
-
logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
|
|
11454
|
+
logger.info("Running: pnpm exec runa db preview production --profile compare-only");
|
|
11406
11455
|
},
|
|
11456
|
+
// Shared handlers (nested paths alias to same handler as flat paths)
|
|
11457
|
+
"postSeedPr.observability.collectSchemaStats": logCollectSchemaStats,
|
|
11458
|
+
"postSeedPr.execution.staticChecks": logStaticChecks,
|
|
11459
|
+
"postSeedPr.execution.layerContent": logLayerContent,
|
|
11460
|
+
"postSeedPr.execution.buildAndPlaywright": logBuildAndPlaywright,
|
|
11461
|
+
"postSeedPr.execution.appStart": logAppStart,
|
|
11462
|
+
"postSeedPr.execution.capabilities": logCapabilities,
|
|
11463
|
+
"postSeedPr.execution.runCoreTests": logRunCoreTests,
|
|
11464
|
+
"postSeedPr.execution.e2ePhase": logE2ePhase,
|
|
11465
|
+
// Flat paths (differ from nested counterparts)
|
|
11407
11466
|
productionPreview: (ctx, logger) => {
|
|
11408
11467
|
if (ctx.seedsApplied) {
|
|
11409
11468
|
logger.success("Seeds applied successfully");
|
|
@@ -11411,19 +11470,9 @@ var stateLogHandlers2 = {
|
|
|
11411
11470
|
logger.info("Seeds skipped (no seed files or already seeded)");
|
|
11412
11471
|
}
|
|
11413
11472
|
logSection3("Database: Production Preview (dry-run)");
|
|
11414
|
-
logger.info("Running: pnpm exec runa db
|
|
11415
|
-
},
|
|
11416
|
-
collectSchemaStats: (ctx, logger) => {
|
|
11417
|
-
if (ctx.productionPreview?.executed) {
|
|
11418
|
-
if (ctx.productionPreview.hasChanges) {
|
|
11419
|
-
logger.info("Production preview: schema changes detected");
|
|
11420
|
-
} else {
|
|
11421
|
-
logger.success("Production preview: no schema changes");
|
|
11422
|
-
}
|
|
11423
|
-
}
|
|
11424
|
-
logSection3("Database: Collect Schema Statistics");
|
|
11425
|
-
logger.info("Comparing Local/CI/Production schemas...");
|
|
11473
|
+
logger.info("Running: pnpm exec runa db preview production --profile compare-only");
|
|
11426
11474
|
},
|
|
11475
|
+
collectSchemaStats: logCollectSchemaStats,
|
|
11427
11476
|
setupRoles: (ctx, logger) => {
|
|
11428
11477
|
if (ctx.schemaStats) {
|
|
11429
11478
|
logger.success("Schema statistics collected");
|
|
@@ -11432,57 +11481,18 @@ var stateLogHandlers2 = {
|
|
|
11432
11481
|
logger.info("Running: pnpm db:setup-roles");
|
|
11433
11482
|
},
|
|
11434
11483
|
// ─── Static Analysis Phase ─────────────────────────────────
|
|
11435
|
-
staticChecks:
|
|
11436
|
-
logSetupRolesStatus(ctx, logger);
|
|
11437
|
-
logSection3("Static Checks: type-check + lint (parallel)");
|
|
11438
|
-
logger.info("Running: pnpm type-check || pnpm lint");
|
|
11439
|
-
},
|
|
11484
|
+
staticChecks: logStaticChecks,
|
|
11440
11485
|
// ─── Build Phase ───────────────────────────────────────────
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
}
|
|
11445
|
-
if (shouldReusePreparedPlaywright(ctx)) {
|
|
11446
|
-
logSection3("Build: App Build + Reuse Prepared Playwright");
|
|
11447
|
-
logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
|
|
11448
|
-
return;
|
|
11449
|
-
}
|
|
11450
|
-
logSection3("Build: App Build + Playwright Install (parallel)");
|
|
11451
|
-
logger.info("Running: pnpm build || pnpm exec playwright install chromium");
|
|
11452
|
-
},
|
|
11453
|
-
appStart: (ctx, logger) => {
|
|
11454
|
-
if (ctx.appBuildPassed) {
|
|
11455
|
-
logger.success("App build completed");
|
|
11456
|
-
}
|
|
11457
|
-
if (ctx.manifestGenerated) {
|
|
11458
|
-
logger.success("Manifest generated (Layer 3 ready)");
|
|
11459
|
-
} else if (ctx.manifestGenerated === null) {
|
|
11460
|
-
logger.info("Manifest generation skipped (optional)");
|
|
11461
|
-
} else if (ctx.manifestGenerated === false) {
|
|
11462
|
-
logger.warn("Manifest generation failed (Layer 3 may be skipped)");
|
|
11463
|
-
}
|
|
11464
|
-
if (ctx.playwrightInstalled) {
|
|
11465
|
-
logger.success("Playwright browsers installed");
|
|
11466
|
-
}
|
|
11467
|
-
logSection3("Start Application");
|
|
11468
|
-
logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
|
|
11469
|
-
},
|
|
11486
|
+
layerContent: logLayerContent,
|
|
11487
|
+
buildAndPlaywright: logBuildAndPlaywright,
|
|
11488
|
+
appStart: logAppStart,
|
|
11470
11489
|
// ─── Test Phase ────────────────────────────────────────────
|
|
11471
|
-
capabilities:
|
|
11472
|
-
if (ctx.appStarted) {
|
|
11473
|
-
logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
|
|
11474
|
-
}
|
|
11475
|
-
logSection3("Detect Test Capabilities");
|
|
11476
|
-
},
|
|
11490
|
+
capabilities: logCapabilities,
|
|
11477
11491
|
runTests: (ctx, logger) => {
|
|
11478
11492
|
logSection3(`Run Tests: Layer ${ctx.selectedLayers.join(", ")}`);
|
|
11479
11493
|
logger.info(`Running layers: ${ctx.selectedLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
11480
11494
|
},
|
|
11481
|
-
runCoreTests:
|
|
11482
|
-
const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
|
|
11483
|
-
logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
|
|
11484
|
-
logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
|
|
11485
|
-
},
|
|
11495
|
+
runCoreTests: logRunCoreTests,
|
|
11486
11496
|
coreTestsComplete: (ctx, logger) => {
|
|
11487
11497
|
const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
|
|
11488
11498
|
const passedLayers = coreLayers.filter(
|
|
@@ -11496,10 +11506,7 @@ var stateLogHandlers2 = {
|
|
|
11496
11506
|
const failedLayers = Object.entries(ctx.layerResults).filter(([_, result]) => result.status === "failed").map(([layer]) => layer);
|
|
11497
11507
|
logger.error(`Core tests failed: Layer ${failedLayers.join(", ")}`);
|
|
11498
11508
|
},
|
|
11499
|
-
e2ePhase:
|
|
11500
|
-
logSection3("E2E Phase: Layer 4 + Intermediate Comment");
|
|
11501
|
-
logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
|
|
11502
|
-
},
|
|
11509
|
+
e2ePhase: logE2ePhase,
|
|
11503
11510
|
"e2ePhase.e2eTests": (_ctx, logger) => {
|
|
11504
11511
|
logger.info("Running Layer 4 E2E tests...");
|
|
11505
11512
|
},
|
|
@@ -11565,7 +11572,7 @@ function getGitHubEventAction() {
|
|
|
11565
11572
|
return void 0;
|
|
11566
11573
|
}
|
|
11567
11574
|
}
|
|
11568
|
-
function optionsToMachineInput(options) {
|
|
11575
|
+
function optionsToMachineInput(options, internalOverrides) {
|
|
11569
11576
|
return {
|
|
11570
11577
|
command: "pr",
|
|
11571
11578
|
// Explicit command for mode detection
|
|
@@ -11586,7 +11593,8 @@ function optionsToMachineInput(options) {
|
|
|
11586
11593
|
skipGithubComment: options.skipGithubComment,
|
|
11587
11594
|
verbose: options.verbose,
|
|
11588
11595
|
targetDir: process.cwd(),
|
|
11589
|
-
layers:
|
|
11596
|
+
layers: internalOverrides.layers,
|
|
11597
|
+
internalProfile: internalOverrides.profile,
|
|
11590
11598
|
// Environment capture (Environment Capture Pattern: capture at entry point, not in machine/guards)
|
|
11591
11599
|
databaseUrl: options.databaseUrl ?? process.env.DATABASE_URL,
|
|
11592
11600
|
// PRD: GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff dry-run)
|
|
@@ -11614,36 +11622,47 @@ async function runCiPrCommand(options) {
|
|
|
11614
11622
|
const skipStaticChecks = options.skipStaticChecks === true;
|
|
11615
11623
|
const skipBuild = options.skipBuild === true;
|
|
11616
11624
|
const phase = options.phase ?? "all";
|
|
11625
|
+
const blockingPhase = phase === "blocking";
|
|
11626
|
+
const observabilityPhase = phase === "observability";
|
|
11617
11627
|
const testOnlyPhase = phase === "test";
|
|
11628
|
+
const runExecutionPath = !observabilityPhase;
|
|
11618
11629
|
const preparedRuntime = options.skipLocalDbStart === true || options.assumeSupabaseReady === true;
|
|
11619
11630
|
const preparedPlaywright = options.skipPlaywrightInstall === true;
|
|
11631
|
+
const dbDescription = testOnlyPhase ? "Skip schema apply/seed and reuse the prepared database" : observabilityPhase ? "db apply \u2192 db seed \u2192 production preview \u2192 schema stats" : blockingPhase ? "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)" : "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)";
|
|
11620
11632
|
const planSteps = [
|
|
11621
11633
|
{
|
|
11622
11634
|
id: "setup",
|
|
11623
11635
|
description: testOnlyPhase ? "Resolve repo/app context (optionally reuse pre-started local Supabase)" : preparedRuntime ? "Reuse workflow-prepared local Supabase runtime" : "Start local Supabase instance"
|
|
11624
11636
|
},
|
|
11625
|
-
|
|
11637
|
+
{ id: "db", description: dbDescription },
|
|
11638
|
+
...!runExecutionPath || skipStaticChecks ? [] : [{ id: "static", description: "pnpm type-check + pnpm lint (parallel)" }],
|
|
11639
|
+
...!runExecutionPath || skipBuild ? [] : [
|
|
11626
11640
|
{
|
|
11627
|
-
id: "
|
|
11628
|
-
description: "
|
|
11641
|
+
id: "build",
|
|
11642
|
+
description: preparedPlaywright ? "Detect runnable layers \u2192 pnpm build \u2192 manifest:generate (reuse preinstalled Playwright)" : "Detect runnable layers \u2192 pnpm build \u2192 manifest:generate + playwright install"
|
|
11629
11643
|
}
|
|
11630
11644
|
],
|
|
11631
|
-
|
|
11632
|
-
...skipBuild ? [] : [
|
|
11645
|
+
...!runExecutionPath ? [] : [
|
|
11633
11646
|
{
|
|
11634
|
-
id: "
|
|
11635
|
-
description:
|
|
11647
|
+
id: "test",
|
|
11648
|
+
description: "Start app if Layer 4 is runnable \u2192 detect capabilities \u2192 run layers"
|
|
11636
11649
|
}
|
|
11637
11650
|
],
|
|
11638
|
-
{ id: "test", description: "Start app \u2192 detect capabilities \u2192 run layers 1-4" },
|
|
11639
11651
|
{
|
|
11640
11652
|
id: "finalize",
|
|
11641
11653
|
description: "Write ci-summary.json + post GitHub comment"
|
|
11642
11654
|
}
|
|
11643
11655
|
];
|
|
11644
11656
|
logPlan2(planSteps);
|
|
11645
|
-
const machineInput = optionsToMachineInput(options);
|
|
11646
11657
|
try {
|
|
11658
|
+
const internalOverrides = resolveInternalCiOverrides();
|
|
11659
|
+
if (internalOverrides.profile && outputFormat !== "json") {
|
|
11660
|
+
logger.info(`Internal CI profile: ${internalOverrides.profile}`);
|
|
11661
|
+
if (internalOverrides.layers) {
|
|
11662
|
+
logger.info(`Internal CI layers: ${internalOverrides.layers.join(", ")}`);
|
|
11663
|
+
}
|
|
11664
|
+
}
|
|
11665
|
+
const machineInput = optionsToMachineInput(options, internalOverrides);
|
|
11647
11666
|
const result = await runCiMachine(machineInput, logger, handleStateChange2);
|
|
11648
11667
|
endCurrentGroup2();
|
|
11649
11668
|
printSummary2(logger, result);
|