@runa-ai/runa-cli 0.5.57 → 0.5.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/ci/commands/ci-supabase-local.d.ts +5 -0
- package/dist/commands/ci/commands/ci-supabase-local.d.ts.map +1 -1
- package/dist/commands/ci/machine/actors/db/sync-schema.d.ts.map +1 -1
- package/dist/commands/ci/utils/app-runtime.d.ts +2 -0
- package/dist/commands/ci/utils/app-runtime.d.ts.map +1 -1
- package/dist/commands/db/apply/actors.d.ts +4 -0
- package/dist/commands/db/apply/actors.d.ts.map +1 -1
- package/dist/commands/db/apply/contract.d.ts +1 -0
- package/dist/commands/db/apply/contract.d.ts.map +1 -1
- package/dist/commands/db/apply/helpers/index.d.ts +4 -2
- package/dist/commands/db/apply/helpers/index.d.ts.map +1 -1
- package/dist/commands/db/apply/helpers/plan-validator.d.ts +61 -0
- package/dist/commands/db/apply/helpers/plan-validator.d.ts.map +1 -0
- package/dist/commands/db/apply/helpers/retry-logic.d.ts +25 -0
- package/dist/commands/db/apply/helpers/retry-logic.d.ts.map +1 -1
- package/dist/commands/db/apply/machine.d.ts +5 -0
- package/dist/commands/db/apply/machine.d.ts.map +1 -1
- package/dist/commands/dev/commands/dev.d.ts +2 -0
- package/dist/commands/dev/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev/contract.d.ts +5 -0
- package/dist/commands/dev/contract.d.ts.map +1 -1
- package/dist/commands/dev/machine.d.ts +16 -3
- package/dist/commands/dev/machine.d.ts.map +1 -1
- package/dist/constants/versions.d.ts +1 -1
- package/dist/index.js +443 -111
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as path11 from 'path';
|
|
|
4
4
|
import path11__default, { join, dirname, resolve, isAbsolute, relative, sep, basename, normalize } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import * as fs5 from 'fs';
|
|
7
|
-
import fs5__default, { existsSync,
|
|
7
|
+
import fs5__default, { existsSync, readFileSync, unlinkSync, rmSync, readdirSync, mkdtempSync, writeFileSync, mkdirSync, copyFileSync, createWriteStream, statSync, realpathSync, promises, lstatSync, accessSync, constants, chmodSync } from 'fs';
|
|
8
8
|
import { execSync, spawnSync, execFileSync, exec, spawn } from 'child_process';
|
|
9
9
|
import { createCLILogger, cacheClear, CacheClearOutputSchema, CLIError, cachePrune, CachePruneOutputSchema, cacheStats, CacheStatsOutputSchema, cacheList, CacheListOutputSchema, cacheInvalidate, CacheInvalidateOutputSchema, syncFromProduction, SUPABASE_SYSTEM_SCHEMAS, dbGenerateDiagram, DbDiagramGenerateOutputSchema, createDbSnapshot, syncDatabase, emitDbPushFailureCapsule, emitDbAnnotations, writeDbPushStepSummary, exportDbReportJson, DbSyncOutputSchema, databasePaths, detectRequiredServices, formatDetectionResults, dbStart, DbLifecycleStartOutputSchema, dbStop, DbLifecycleStopOutputSchema, dbReset, DbLifecycleResetOutputSchema, dbValidateSchemas, DbSchemaValidateOutputSchema, DbSchemaRisksOutputSchema, dbDetectSchemaRisks, dbApplySchemas, DbSchemaApplyOutputSchema, dbGenerateTypes, DbSchemaGenerateOutputSchema, extractSchemaFilter, dbSeedInit, DbSeedInitOutputSchema, dbSeedValidate, DbSeedValidateOutputSchema, dbSeedGenerate, DbSeedGenerateOutputSchema, dbVerifySeeds, DbSeedVerifyOutputSchema, DbSnapshotCreateOutputSchema, restoreDbSnapshot, DbSnapshotRestoreOutputSchema, listDbSnapshots, DbSnapshotListOutputSchema, dbGeneratePgTapTests, DbTestGenOutputSchema, dbUpdateGoldenRecord, DbTestUpdateGoldenOutputSchema, repairRunaConfig, detectExistingInitConfig, initProject, validateInitResult, linkCliGlobally, LinkCliOutputSchema, unlinkCliGlobally, UnlinkCliOutputSchema, checkRepoStatus, CheckRepoStatusOutputSchema, enableTelemetry, disableTelemetry, getTelemetryStatus, uploadTelemetry, TelemetryUploadOutputSchema, runTest, TestRunOutputSchema, runTestService, TestServiceOutputSchema, runTestIntegration, TestIntegrationOutputSchema, runTestStatic, TestStaticOutputSchema, generateOwaspTop10Tests, TestOwaspGenerateOutputSchema, updateGoldenRecord, generateE2ETests, generateSecurityTests, generateUnitTests, generateApiTests, generateComponentTests, generateE2EScaffold, validateConfig, ValidateConfigOutputSchema, deploySchemaToProduction, WorkflowNotifyOutputSchema, devopsSync, workflowSync, validateInfrastructure, emitWorkflowValidateFailureCapsule, emitWorkflowAnnotations, writeWorkflowValidateStepSummary, exportWorkflowReportJson, WorkflowValidateInfrastructureOutputSchema, createSuccessEnvelopeSchema, CLI_CONTRACT_VERSION, runChecks, RunCheckOutputSchema, formatDuration as formatDuration$1, GITHUB_API, loadRunaConfig, getClassificationForProfile, BASE_PORTS, loadRunaConfigOrThrow, recordSchemaAudit, RecordSchemaAuditOutputSchema, createBackup, CreateBackupOutputSchema, listBackups, ListBackupsOutputSchema, getBackupMetadata, restoreBackup, RestoreBackupOutputSchema, deleteBackup, DeleteBackupOutputSchema, detectSchemaNames, resolveAvailablePorts, calculatePortOffset, dbSeedApply, writeDbSeedStepSummary, DbSeedApplyOutputSchema, emitDbSeedFailureCapsule, syncEnvironment, EnvSyncOutputSchema, detectDatabasePackage, findProjectRoot as findProjectRoot$1, TelemetryEnableOutputSchema, TelemetryDisableOutputSchema, TelemetryStatusOutputSchema, workflowNotify, DevOpsSyncOutputSchema, WorkflowSyncOutputSchema, formatCLIError, getStatusIcon as getStatusIcon$1, findWorkspaceRoot as findWorkspaceRoot$1, checkExtensionConfig, getPortsWithOffset, UpgradeTransaction, readRunaVersion, syncTemplates, SyncOutputSchema, DATABASE_PACKAGE_CANDIDATES, ErrorEnvelopeSchema, preCheckSync, findConflictFiles, TestUnitGenOutputSchema, TestE2EGenerateOutputSchema, TestSecurityGenOutputSchema, TestApiGenOutputSchema, TestComponentGenOutputSchema } from '@runa-ai/runa';
|
|
10
10
|
import { z } from 'zod';
|
|
@@ -17,12 +17,12 @@ import { minimatch } from 'minimatch';
|
|
|
17
17
|
import { Command } from 'commander';
|
|
18
18
|
import { fromPromise, setup, assign, createActor } from 'xstate';
|
|
19
19
|
import { execa } from 'execa';
|
|
20
|
+
import net, { isIP } from 'net';
|
|
20
21
|
import chalk from 'chalk';
|
|
21
22
|
import { config } from '@dotenvx/dotenvx';
|
|
22
23
|
import { parse } from 'dotenv';
|
|
23
24
|
import { expand } from 'dotenv-expand';
|
|
24
25
|
import { resolve4 } from 'dns/promises';
|
|
25
|
-
import { isIP } from 'net';
|
|
26
26
|
import postgres from 'postgres';
|
|
27
27
|
import crypto, { randomBytes } from 'crypto';
|
|
28
28
|
import os, { tmpdir } from 'os';
|
|
@@ -1161,7 +1161,7 @@ var CLI_VERSION, HAS_ADMIN_COMMAND;
|
|
|
1161
1161
|
var init_version = __esm({
|
|
1162
1162
|
"src/version.ts"() {
|
|
1163
1163
|
init_esm_shims();
|
|
1164
|
-
CLI_VERSION = "0.5.
|
|
1164
|
+
CLI_VERSION = "0.5.60";
|
|
1165
1165
|
HAS_ADMIN_COMMAND = false;
|
|
1166
1166
|
}
|
|
1167
1167
|
});
|
|
@@ -7598,17 +7598,17 @@ function printSummary(logger16, output3) {
|
|
|
7598
7598
|
}
|
|
7599
7599
|
}
|
|
7600
7600
|
function findRepoRoot(startDir) {
|
|
7601
|
-
const { existsSync: existsSync53, readFileSync:
|
|
7602
|
-
const { join:
|
|
7601
|
+
const { existsSync: existsSync53, readFileSync: readFileSync30 } = __require("fs");
|
|
7602
|
+
const { join: join24, dirname: dirname5 } = __require("path");
|
|
7603
7603
|
let current = startDir;
|
|
7604
7604
|
while (current !== dirname5(current)) {
|
|
7605
|
-
if (existsSync53(
|
|
7605
|
+
if (existsSync53(join24(current, "turbo.json"))) {
|
|
7606
7606
|
return current;
|
|
7607
7607
|
}
|
|
7608
|
-
const pkgPath =
|
|
7608
|
+
const pkgPath = join24(current, "package.json");
|
|
7609
7609
|
if (existsSync53(pkgPath)) {
|
|
7610
7610
|
try {
|
|
7611
|
-
const pkg = JSON.parse(
|
|
7611
|
+
const pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
|
|
7612
7612
|
if (pkg.workspaces) {
|
|
7613
7613
|
return current;
|
|
7614
7614
|
}
|
|
@@ -7809,7 +7809,11 @@ var DevInputSchema = z.object({
|
|
|
7809
7809
|
/** Stream app output to terminal (in addition to log file). Default: true */
|
|
7810
7810
|
stream: z.boolean().default(true),
|
|
7811
7811
|
/** Target directory (defaults to cwd) */
|
|
7812
|
-
targetDir: z.string().optional()
|
|
7812
|
+
targetDir: z.string().optional(),
|
|
7813
|
+
/** Replace existing runa dev process if already running */
|
|
7814
|
+
replace: z.boolean().default(false),
|
|
7815
|
+
/** Bundler to use for Next.js dev server */
|
|
7816
|
+
bundler: z.enum(["turbopack", "webpack"]).optional()
|
|
7813
7817
|
}).strict();
|
|
7814
7818
|
z.enum(["pending", "running", "passed", "failed", "skipped"]);
|
|
7815
7819
|
var DevOutputSchema = z.object({
|
|
@@ -7907,8 +7911,8 @@ function readPortFromScripts(appDir) {
|
|
|
7907
7911
|
const pkgPath = path11__default.join(appDir, "package.json");
|
|
7908
7912
|
if (!existsSync(pkgPath)) return 3e3;
|
|
7909
7913
|
try {
|
|
7910
|
-
const { readFileSync:
|
|
7911
|
-
const raw =
|
|
7914
|
+
const { readFileSync: readFileSync30 } = __require("fs");
|
|
7915
|
+
const raw = readFileSync30(pkgPath, "utf-8");
|
|
7912
7916
|
const parsed = JSON.parse(raw);
|
|
7913
7917
|
const scripts = parsed.scripts;
|
|
7914
7918
|
for (const key of ["start:ci", "start", "dev"]) {
|
|
@@ -8167,13 +8171,13 @@ async function readPackageScripts(pkgPath) {
|
|
|
8167
8171
|
return null;
|
|
8168
8172
|
}
|
|
8169
8173
|
}
|
|
8170
|
-
function determineAppCommand(mode, isMonorepo2, rootScripts, appScripts, repoRoot, appDir, port) {
|
|
8174
|
+
function determineAppCommand(mode, isMonorepo2, rootScripts, appScripts, repoRoot, appDir, port, bundler) {
|
|
8171
8175
|
const ciScriptName = mode === "dev" ? "dev:ci" : "start:ci";
|
|
8172
8176
|
const nextCommand = mode === "dev" ? "dev" : "start";
|
|
8173
8177
|
const rootHasCiScript = Boolean(rootScripts?.[ciScriptName]);
|
|
8174
8178
|
const appHasCiScript = Boolean(appScripts?.[ciScriptName]);
|
|
8175
|
-
const rootHasDefaultScript = mode === "start" && Boolean(rootScripts?.
|
|
8176
|
-
const appHasDefaultScript = mode === "start" && Boolean(appScripts?.
|
|
8179
|
+
const rootHasDefaultScript = mode === "start" && Boolean(rootScripts?.start);
|
|
8180
|
+
const appHasDefaultScript = mode === "start" && Boolean(appScripts?.start);
|
|
8177
8181
|
if (isMonorepo2 && (rootHasCiScript || rootHasDefaultScript)) {
|
|
8178
8182
|
const scriptName = rootHasCiScript ? ciScriptName : "start";
|
|
8179
8183
|
return { command: ["pnpm", scriptName], useRootScript: true };
|
|
@@ -8184,8 +8188,9 @@ function determineAppCommand(mode, isMonorepo2, rootScripts, appScripts, repoRoo
|
|
|
8184
8188
|
return { command: ["pnpm", ...dirArgs2, scriptName], useRootScript: false };
|
|
8185
8189
|
}
|
|
8186
8190
|
const dirArgs = isMonorepo2 ? ["-C", path11__default.relative(repoRoot, appDir)] : [];
|
|
8191
|
+
const bundlerArgs = bundler ? [`--${bundler}`] : [];
|
|
8187
8192
|
return {
|
|
8188
|
-
command: ["pnpm", ...dirArgs, "exec", "next", nextCommand, "-p", String(port)],
|
|
8193
|
+
command: ["pnpm", ...dirArgs, "exec", "next", nextCommand, ...bundlerArgs, "-p", String(port)],
|
|
8189
8194
|
useRootScript: false
|
|
8190
8195
|
};
|
|
8191
8196
|
}
|
|
@@ -8203,7 +8208,8 @@ async function startAppBackground(params) {
|
|
|
8203
8208
|
appScripts,
|
|
8204
8209
|
params.repoRoot,
|
|
8205
8210
|
params.appDir,
|
|
8206
|
-
params.port
|
|
8211
|
+
params.port,
|
|
8212
|
+
params.bundler
|
|
8207
8213
|
);
|
|
8208
8214
|
const appLog = path11__default.join(params.tmpDir, "app.log");
|
|
8209
8215
|
const out = createWriteStream(appLog, { flags: "a" });
|
|
@@ -8317,7 +8323,16 @@ var e2eMeta2 = {
|
|
|
8317
8323
|
log: "Starting dev"
|
|
8318
8324
|
},
|
|
8319
8325
|
assertions: ["expect(log).toContain('Starting dev')", "expect(state).toBe('idle')"],
|
|
8320
|
-
nextStates: ["
|
|
8326
|
+
nextStates: ["processCheck"]
|
|
8327
|
+
},
|
|
8328
|
+
processCheck: {
|
|
8329
|
+
description: "Check for existing runa dev process and port availability",
|
|
8330
|
+
severity: "blocking",
|
|
8331
|
+
observables: {
|
|
8332
|
+
log: "Checking for existing processes"
|
|
8333
|
+
},
|
|
8334
|
+
assertions: ["expect(log).toContain('Checking for existing processes')"],
|
|
8335
|
+
nextStates: ["setup", "failed"]
|
|
8321
8336
|
},
|
|
8322
8337
|
setup: {
|
|
8323
8338
|
description: "Setup phase: deps, env, Supabase",
|
|
@@ -8377,9 +8392,67 @@ var e2eMeta2 = {
|
|
|
8377
8392
|
nextStates: []
|
|
8378
8393
|
}
|
|
8379
8394
|
};
|
|
8395
|
+
function isProcessAlive(pid) {
|
|
8396
|
+
try {
|
|
8397
|
+
process.kill(pid, 0);
|
|
8398
|
+
return true;
|
|
8399
|
+
} catch {
|
|
8400
|
+
return false;
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8403
|
+
function checkPortAvailable(port) {
|
|
8404
|
+
return new Promise((resolve12, reject) => {
|
|
8405
|
+
const server = net.createServer();
|
|
8406
|
+
server.once("error", (err) => {
|
|
8407
|
+
if (err.code === "EADDRINUSE") {
|
|
8408
|
+
reject(
|
|
8409
|
+
new Error(
|
|
8410
|
+
`Port ${port} is already in use. Use --port <number> to specify a different port, or --replace to restart.`
|
|
8411
|
+
)
|
|
8412
|
+
);
|
|
8413
|
+
} else {
|
|
8414
|
+
reject(err);
|
|
8415
|
+
}
|
|
8416
|
+
});
|
|
8417
|
+
server.once("listening", () => {
|
|
8418
|
+
server.close(() => resolve12());
|
|
8419
|
+
});
|
|
8420
|
+
server.listen(port);
|
|
8421
|
+
});
|
|
8422
|
+
}
|
|
8423
|
+
var processCheckActor = fromPromise(async ({ input: input3 }) => {
|
|
8424
|
+
const pidFile = path11__default.join(input3.repoRoot, input3.tmpDir, "app.pid");
|
|
8425
|
+
let pidFileContent = null;
|
|
8426
|
+
try {
|
|
8427
|
+
pidFileContent = readFileSync(pidFile, "utf-8").trim();
|
|
8428
|
+
} catch {
|
|
8429
|
+
}
|
|
8430
|
+
if (pidFileContent) {
|
|
8431
|
+
const pid = parseInt(pidFileContent, 10);
|
|
8432
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) {
|
|
8433
|
+
if (input3.replace) {
|
|
8434
|
+
await terminateAppProcessByPid({
|
|
8435
|
+
pid,
|
|
8436
|
+
logger: { info: console.log, warn: console.warn }
|
|
8437
|
+
});
|
|
8438
|
+
} else {
|
|
8439
|
+
throw new Error(
|
|
8440
|
+
`runa dev is already running (PID: ${pid}). Use --replace to restart, or stop the existing process first.`
|
|
8441
|
+
);
|
|
8442
|
+
}
|
|
8443
|
+
}
|
|
8444
|
+
try {
|
|
8445
|
+
unlinkSync(pidFile);
|
|
8446
|
+
} catch {
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
if (!input3.skipApp) {
|
|
8450
|
+
await checkPortAvailable(input3.port);
|
|
8451
|
+
}
|
|
8452
|
+
});
|
|
8380
8453
|
var appStartActor = fromPromise(
|
|
8381
8454
|
async ({ input: input3 }) => {
|
|
8382
|
-
const { repoRoot, appDir, port, tmpDir, stream } = input3;
|
|
8455
|
+
const { repoRoot, appDir, port, tmpDir, stream, bundler } = input3;
|
|
8383
8456
|
const fullTmpDir = path11__default.join(repoRoot, tmpDir);
|
|
8384
8457
|
await mkdir(fullTmpDir, { recursive: true });
|
|
8385
8458
|
const nextDir = path11__default.join(appDir, ".next");
|
|
@@ -8394,7 +8467,8 @@ var appStartActor = fromPromise(
|
|
|
8394
8467
|
env: process.env,
|
|
8395
8468
|
tmpDir: fullTmpDir,
|
|
8396
8469
|
mode: "dev",
|
|
8397
|
-
stream
|
|
8470
|
+
stream,
|
|
8471
|
+
bundler
|
|
8398
8472
|
});
|
|
8399
8473
|
await waitForAppReady({
|
|
8400
8474
|
port,
|
|
@@ -8427,6 +8501,7 @@ function normalizeDevMachineInput(input3) {
|
|
|
8427
8501
|
var devMachine = setup({
|
|
8428
8502
|
types: {},
|
|
8429
8503
|
actors: {
|
|
8504
|
+
processCheck: processCheckActor,
|
|
8430
8505
|
depsInstall: depsInstallActor,
|
|
8431
8506
|
envCheck: envCheckActor,
|
|
8432
8507
|
supabaseStart: supabaseStartActor,
|
|
@@ -8461,7 +8536,30 @@ var devMachine = setup({
|
|
|
8461
8536
|
idle: {
|
|
8462
8537
|
meta: { e2e: e2eMeta2.idle },
|
|
8463
8538
|
on: {
|
|
8464
|
-
START: { target: "
|
|
8539
|
+
START: { target: "processCheck" }
|
|
8540
|
+
}
|
|
8541
|
+
},
|
|
8542
|
+
// ============================================================
|
|
8543
|
+
// Process Check Phase
|
|
8544
|
+
// ============================================================
|
|
8545
|
+
processCheck: {
|
|
8546
|
+
meta: { e2e: e2eMeta2.processCheck },
|
|
8547
|
+
invoke: {
|
|
8548
|
+
src: "processCheck",
|
|
8549
|
+
input: ({ context }) => ({
|
|
8550
|
+
repoRoot: context.repoRoot,
|
|
8551
|
+
tmpDir: context.tmpDir,
|
|
8552
|
+
port: context.input.port,
|
|
8553
|
+
replace: context.input.replace,
|
|
8554
|
+
skipApp: context.input.skipApp
|
|
8555
|
+
}),
|
|
8556
|
+
onDone: { target: "setup" },
|
|
8557
|
+
onError: {
|
|
8558
|
+
target: "failed",
|
|
8559
|
+
actions: assign({
|
|
8560
|
+
error: ({ event }) => event.error instanceof Error ? event.error.message : String(event.error)
|
|
8561
|
+
})
|
|
8562
|
+
}
|
|
8465
8563
|
}
|
|
8466
8564
|
},
|
|
8467
8565
|
// ============================================================
|
|
@@ -8537,7 +8635,8 @@ var devMachine = setup({
|
|
|
8537
8635
|
appDir: detected?.appDir ?? context.repoRoot,
|
|
8538
8636
|
port: context.input.port,
|
|
8539
8637
|
tmpDir: context.tmpDir,
|
|
8540
|
-
stream: context.input.stream
|
|
8638
|
+
stream: context.input.stream,
|
|
8639
|
+
bundler: context.input.bundler
|
|
8541
8640
|
};
|
|
8542
8641
|
},
|
|
8543
8642
|
onDone: {
|
|
@@ -8615,6 +8714,9 @@ var stateLogHandlers2 = {
|
|
|
8615
8714
|
logger16.section("Dev Server");
|
|
8616
8715
|
logger16.info("Starting development environment...");
|
|
8617
8716
|
},
|
|
8717
|
+
processCheck: (logger16) => {
|
|
8718
|
+
logger16.info("Checking for existing processes...");
|
|
8719
|
+
},
|
|
8618
8720
|
setup: (logger16) => {
|
|
8619
8721
|
logger16.section("Phase 0: Setup");
|
|
8620
8722
|
logger16.info("Checking environment prerequisites...");
|
|
@@ -8740,14 +8842,16 @@ async function runDevAction(options, cmd) {
|
|
|
8740
8842
|
);
|
|
8741
8843
|
}
|
|
8742
8844
|
}
|
|
8743
|
-
var devCommand = new Command("dev").description("Start development server (Supabase + Next.js)").option("--port <number>", "Port for Next.js dev server", (val) => Number.parseInt(val, 10), 3e3).option("--skip-db", "Skip Supabase start").option("--skip-app", "Start Supabase only (no app server)").option("--verbose", "Show detailed output").option("--no-stream", "Disable streaming app output to terminal (log to file only)").option("--target-dir <path>", "Target directory (defaults to repo root)").action(async (options, cmd) => {
|
|
8845
|
+
var devCommand = new Command("dev").description("Start development server (Supabase + Next.js)").option("--port <number>", "Port for Next.js dev server", (val) => Number.parseInt(val, 10), 3e3).option("--skip-db", "Skip Supabase start").option("--skip-app", "Start Supabase only (no app server)").option("--verbose", "Show detailed output").option("--no-stream", "Disable streaming app output to terminal (log to file only)").option("--target-dir <path>", "Target directory (defaults to repo root)").option("--replace", "Replace existing runa dev process if already running").option("--bundler <type>", "Bundler for Next.js dev server (turbopack or webpack)").action(async (options, cmd) => {
|
|
8744
8846
|
const input3 = {
|
|
8745
8847
|
port: options.port,
|
|
8746
8848
|
skipDb: options.skipDb ?? false,
|
|
8747
8849
|
skipApp: options.skipApp ?? false,
|
|
8748
8850
|
verbose: options.verbose ?? false,
|
|
8749
8851
|
stream: options.stream ?? true,
|
|
8750
|
-
targetDir: options.targetDir
|
|
8852
|
+
targetDir: options.targetDir,
|
|
8853
|
+
replace: options.replace ?? false,
|
|
8854
|
+
bundler: options.bundler
|
|
8751
8855
|
};
|
|
8752
8856
|
await runDevAction(input3, cmd);
|
|
8753
8857
|
});
|
|
@@ -10087,8 +10191,8 @@ async function detectRisks(repoRoot, tmpDir) {
|
|
|
10087
10191
|
} catch (error) {
|
|
10088
10192
|
let logContent = "";
|
|
10089
10193
|
try {
|
|
10090
|
-
const { readFileSync:
|
|
10091
|
-
logContent =
|
|
10194
|
+
const { readFileSync: readFileSync30 } = await import('fs');
|
|
10195
|
+
logContent = readFileSync30(logFile, "utf-8");
|
|
10092
10196
|
} catch {
|
|
10093
10197
|
}
|
|
10094
10198
|
const isInitialDeployment = logContent.includes("No common ancestor") || logContent.includes("INITIAL DEPLOYMENT");
|
|
@@ -10216,8 +10320,8 @@ async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, pro
|
|
|
10216
10320
|
const totalMs = Date.now() - startTime;
|
|
10217
10321
|
let logContent = "";
|
|
10218
10322
|
try {
|
|
10219
|
-
const { readFileSync:
|
|
10220
|
-
logContent =
|
|
10323
|
+
const { readFileSync: readFileSync30 } = await import('fs');
|
|
10324
|
+
logContent = readFileSync30(logPath, "utf-8");
|
|
10221
10325
|
} catch {
|
|
10222
10326
|
}
|
|
10223
10327
|
const parsed = parseApplyLog(logContent);
|
|
@@ -12995,7 +13099,7 @@ var syncSchemaActor = fromPromise(
|
|
|
12995
13099
|
"--auto-approve",
|
|
12996
13100
|
// Allow DELETES_DATA hazards in preview
|
|
12997
13101
|
"--no-seed",
|
|
12998
|
-
// Seeds applied separately by applySeedsActor
|
|
13102
|
+
// Seeds applied separately by applySeedsActor
|
|
12999
13103
|
"--verbose"
|
|
13000
13104
|
// Always verbose for full traceability
|
|
13001
13105
|
] : [
|
|
@@ -13005,6 +13109,8 @@ var syncSchemaActor = fromPromise(
|
|
|
13005
13109
|
"sync",
|
|
13006
13110
|
envArg,
|
|
13007
13111
|
"--auto-approve",
|
|
13112
|
+
"--no-seed",
|
|
13113
|
+
// Seeds applied separately by applySeedsActor
|
|
13008
13114
|
"--verbose",
|
|
13009
13115
|
// Always verbose for full traceability
|
|
13010
13116
|
...skipCodegen ? ["--skip-codegen"] : []
|
|
@@ -13141,7 +13247,67 @@ init_esm_shims();
|
|
|
13141
13247
|
// src/commands/ci/commands/ci-supabase-local.ts
|
|
13142
13248
|
init_esm_shims();
|
|
13143
13249
|
init_constants();
|
|
13250
|
+
function isPortAvailable(port) {
|
|
13251
|
+
return new Promise((resolve12) => {
|
|
13252
|
+
const server = net.createServer();
|
|
13253
|
+
server.once("error", () => resolve12(false));
|
|
13254
|
+
server.once("listening", () => {
|
|
13255
|
+
server.close(() => resolve12(true));
|
|
13256
|
+
});
|
|
13257
|
+
server.listen(port);
|
|
13258
|
+
});
|
|
13259
|
+
}
|
|
13260
|
+
function detectSupabaseContainers() {
|
|
13261
|
+
try {
|
|
13262
|
+
const result = spawnSync(
|
|
13263
|
+
"docker",
|
|
13264
|
+
["ps", "--format", "{{.Names}} {{.Ports}} {{.Status}}", "--filter", "name=supabase"],
|
|
13265
|
+
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
13266
|
+
);
|
|
13267
|
+
if (result.status !== 0 || !result.stdout?.trim()) return [];
|
|
13268
|
+
return result.stdout.trim().split("\n").filter(Boolean);
|
|
13269
|
+
} catch {
|
|
13270
|
+
return [];
|
|
13271
|
+
}
|
|
13272
|
+
}
|
|
13273
|
+
async function checkSupabasePortConflicts(repoRoot) {
|
|
13274
|
+
let dbPort = 54322;
|
|
13275
|
+
try {
|
|
13276
|
+
const { readFileSync: readFileSync30 } = await import('fs');
|
|
13277
|
+
const configPath = path11__default.join(repoRoot, "supabase", "config.toml");
|
|
13278
|
+
const content = readFileSync30(configPath, "utf-8");
|
|
13279
|
+
const match = /\[db\][^[]*?port\s*=\s*(\d+)/s.exec(content);
|
|
13280
|
+
if (match?.[1]) dbPort = Number.parseInt(match[1], 10);
|
|
13281
|
+
} catch {
|
|
13282
|
+
}
|
|
13283
|
+
const available = await isPortAvailable(dbPort);
|
|
13284
|
+
if (available) return;
|
|
13285
|
+
const containers = detectSupabaseContainers();
|
|
13286
|
+
const dbContainers = containers.filter((c) => c.includes(String(dbPort)) || c.includes("db-"));
|
|
13287
|
+
console.warn("");
|
|
13288
|
+
console.warn("\u26A0\uFE0F Port conflict detected: PostgreSQL port %d is already in use", dbPort);
|
|
13289
|
+
console.warn("");
|
|
13290
|
+
if (dbContainers.length > 0) {
|
|
13291
|
+
console.warn(" Running Supabase containers:");
|
|
13292
|
+
for (const c of dbContainers) {
|
|
13293
|
+
console.warn(` ${c}`);
|
|
13294
|
+
}
|
|
13295
|
+
} else if (containers.length > 0) {
|
|
13296
|
+
console.warn(" Running Supabase containers (no DB match):");
|
|
13297
|
+
for (const c of containers.slice(0, 5)) {
|
|
13298
|
+
console.warn(` ${c}`);
|
|
13299
|
+
}
|
|
13300
|
+
} else {
|
|
13301
|
+
console.warn(" No Supabase containers detected (port may be used by another process).");
|
|
13302
|
+
}
|
|
13303
|
+
console.warn("");
|
|
13304
|
+
console.warn(" To fix:");
|
|
13305
|
+
console.warn(" 1. Stop the other instance: supabase stop (in the other project)");
|
|
13306
|
+
console.warn(" 2. Or use a different port in supabase/config.toml: [db] port = 54323");
|
|
13307
|
+
console.warn("");
|
|
13308
|
+
}
|
|
13144
13309
|
async function startSupabaseLocal(params) {
|
|
13310
|
+
await checkSupabasePortConflicts(params.repoRoot);
|
|
13145
13311
|
const exclude = process.env.RUNA_CI_SUPABASE_EXCLUDE ?? "studio,edge-runtime,storage-api,realtime,imgproxy,mailpit,logflare,vector,supavisor";
|
|
13146
13312
|
await runLogged({
|
|
13147
13313
|
cwd: params.repoRoot,
|
|
@@ -13361,7 +13527,7 @@ AND table_schema NOT IN ('information_schema');
|
|
|
13361
13527
|
SELECT COUNT(*)::int
|
|
13362
13528
|
FROM pg_class c
|
|
13363
13529
|
JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
13364
|
-
WHERE c.relkind
|
|
13530
|
+
WHERE c.relkind IN ('r','p')
|
|
13365
13531
|
AND n.nspname NOT LIKE 'pg_%'
|
|
13366
13532
|
AND n.nspname NOT IN ('information_schema')
|
|
13367
13533
|
AND c.relrowsecurity;
|
|
@@ -19300,6 +19466,93 @@ function releaseAdvisoryLock(dbUrl, verbose) {
|
|
|
19300
19466
|
}
|
|
19301
19467
|
}
|
|
19302
19468
|
|
|
19469
|
+
// src/commands/db/apply/helpers/plan-validator.ts
|
|
19470
|
+
init_esm_shims();
|
|
19471
|
+
var PlanHazardSchema = z.object({
|
|
19472
|
+
type: z.string().min(1),
|
|
19473
|
+
message: z.string()
|
|
19474
|
+
});
|
|
19475
|
+
var PlanStatementSchema = z.object({
|
|
19476
|
+
index: z.number().int().nonnegative(),
|
|
19477
|
+
sql: z.string().min(1),
|
|
19478
|
+
hazards: z.array(PlanHazardSchema)
|
|
19479
|
+
});
|
|
19480
|
+
var ValidatedPlanSchema = z.object({
|
|
19481
|
+
statements: z.array(PlanStatementSchema),
|
|
19482
|
+
totalStatements: z.number().int().nonnegative(),
|
|
19483
|
+
rawSql: z.string().min(1)
|
|
19484
|
+
});
|
|
19485
|
+
function flushStatement(current, results) {
|
|
19486
|
+
if (!current) return;
|
|
19487
|
+
const sql = current.sqlLines.join("\n").trim();
|
|
19488
|
+
if (!sql) return;
|
|
19489
|
+
results.push({ index: current.index, sql, hazards: current.hazards });
|
|
19490
|
+
}
|
|
19491
|
+
function parseWithStatementMarkers(lines) {
|
|
19492
|
+
const results = [];
|
|
19493
|
+
let current = null;
|
|
19494
|
+
for (const line of lines) {
|
|
19495
|
+
const trimmed = line.trim();
|
|
19496
|
+
const idxMatch = trimmed.match(/^-- Statement Idx\.\s*(\d+)/);
|
|
19497
|
+
if (idxMatch) {
|
|
19498
|
+
flushStatement(current, results);
|
|
19499
|
+
current = { index: parseInt(idxMatch[1], 10), sqlLines: [], hazards: [] };
|
|
19500
|
+
continue;
|
|
19501
|
+
}
|
|
19502
|
+
const hazardMatch = trimmed.match(/^-- Hazard (\w+): (.+)/);
|
|
19503
|
+
if (hazardMatch && current) {
|
|
19504
|
+
current.hazards.push({ type: hazardMatch[1], message: hazardMatch[2] });
|
|
19505
|
+
continue;
|
|
19506
|
+
}
|
|
19507
|
+
if (current && trimmed && !trimmed.startsWith("--")) {
|
|
19508
|
+
current.sqlLines.push(line);
|
|
19509
|
+
}
|
|
19510
|
+
}
|
|
19511
|
+
flushStatement(current, results);
|
|
19512
|
+
return results;
|
|
19513
|
+
}
|
|
19514
|
+
function parseAsSingleStatement(lines) {
|
|
19515
|
+
const sqlLines = [];
|
|
19516
|
+
const hazards = [];
|
|
19517
|
+
for (const line of lines) {
|
|
19518
|
+
const trimmed = line.trim();
|
|
19519
|
+
const hazardMatch = trimmed.match(/^-- Hazard (\w+): (.+)/);
|
|
19520
|
+
if (hazardMatch) {
|
|
19521
|
+
hazards.push({ type: hazardMatch[1], message: hazardMatch[2] });
|
|
19522
|
+
continue;
|
|
19523
|
+
}
|
|
19524
|
+
if (trimmed && !trimmed.startsWith("--")) {
|
|
19525
|
+
sqlLines.push(line);
|
|
19526
|
+
}
|
|
19527
|
+
}
|
|
19528
|
+
const sql = sqlLines.join("\n").trim();
|
|
19529
|
+
if (!sql) return [];
|
|
19530
|
+
return [{ index: 0, sql, hazards }];
|
|
19531
|
+
}
|
|
19532
|
+
function parsePlanOutput(planOutput) {
|
|
19533
|
+
const lines = planOutput.split("\n");
|
|
19534
|
+
const hasStatementMarkers = lines.some((l) => /^-- Statement Idx\.\s*\d+/.test(l.trim()));
|
|
19535
|
+
const statements = hasStatementMarkers ? parseWithStatementMarkers(lines) : parseAsSingleStatement(lines);
|
|
19536
|
+
const plan = {
|
|
19537
|
+
statements,
|
|
19538
|
+
totalStatements: statements.length,
|
|
19539
|
+
rawSql: planOutput
|
|
19540
|
+
};
|
|
19541
|
+
return ValidatedPlanSchema.parse(plan);
|
|
19542
|
+
}
|
|
19543
|
+
function validatePlanForExecution(plan, allowedHazardTypes) {
|
|
19544
|
+
if (plan.totalStatements === 0) return;
|
|
19545
|
+
const allHazards = plan.statements.flatMap((s) => s.hazards);
|
|
19546
|
+
const allowedSet = new Set(allowedHazardTypes);
|
|
19547
|
+
const unresolved = allHazards.filter((h) => !allowedSet.has(h.type));
|
|
19548
|
+
if (unresolved.length > 0) {
|
|
19549
|
+
const types = [...new Set(unresolved.map((h) => h.type))].join(", ");
|
|
19550
|
+
throw new Error(
|
|
19551
|
+
`Plan contains unapproved hazards: ${types}. Approved: [${allowedHazardTypes.join(", ")}]. This is a safety check \u2014 hazards must be approved before execution.`
|
|
19552
|
+
);
|
|
19553
|
+
}
|
|
19554
|
+
}
|
|
19555
|
+
|
|
19303
19556
|
// src/commands/db/apply/helpers/pg-schema-diff-helpers.ts
|
|
19304
19557
|
init_esm_shims();
|
|
19305
19558
|
var logger4 = createCLILogger("db:apply");
|
|
@@ -19645,49 +19898,70 @@ function calculateBackoffDelay(attempt, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
|
|
|
19645
19898
|
function isLockTimeoutError(errorOutput) {
|
|
19646
19899
|
return errorOutput.includes("lock_timeout") || errorOutput.includes("canceling statement due to lock timeout") || errorOutput.includes("could not obtain lock");
|
|
19647
19900
|
}
|
|
19648
|
-
async function
|
|
19901
|
+
async function executePlanSqlWithRetry(dbUrl, initialPlanSql, verbose, config) {
|
|
19649
19902
|
const maxRetries = config?.maxRetries ?? MAX_RETRIES;
|
|
19650
19903
|
const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
19651
19904
|
let lastError = null;
|
|
19652
19905
|
let totalWaitMs = 0;
|
|
19906
|
+
let currentPlanSql = initialPlanSql;
|
|
19653
19907
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
19654
19908
|
if (attempt > 0) {
|
|
19655
19909
|
const delay = calculateBackoffDelay(attempt - 1, maxDelayMs);
|
|
19656
19910
|
totalWaitMs += delay;
|
|
19657
19911
|
logger5.info(`Retry ${attempt}/${maxRetries - 1} after ${Math.round(delay)}ms...`);
|
|
19658
19912
|
await sleep(delay);
|
|
19913
|
+
if (config?.rePlanFn) {
|
|
19914
|
+
const freshPlan = config.rePlanFn();
|
|
19915
|
+
if (freshPlan === null) {
|
|
19916
|
+
return { success: true, attempts: attempt, totalWaitMs };
|
|
19917
|
+
}
|
|
19918
|
+
currentPlanSql = freshPlan;
|
|
19919
|
+
}
|
|
19659
19920
|
}
|
|
19660
|
-
const
|
|
19661
|
-
|
|
19662
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
19663
|
-
});
|
|
19664
|
-
const stdout = applyResult.stdout || "";
|
|
19665
|
-
const stderr = applyResult.stderr || "";
|
|
19666
|
-
if (verbose) {
|
|
19667
|
-
if (stdout) process.stdout.write(stdout);
|
|
19668
|
-
if (stderr) process.stderr.write(stderr);
|
|
19669
|
-
}
|
|
19670
|
-
if (applyResult.status === 0) {
|
|
19921
|
+
const plan = parsePlanOutput(currentPlanSql);
|
|
19922
|
+
if (plan.totalStatements === 0) {
|
|
19671
19923
|
return { success: true, attempts: attempt, totalWaitMs };
|
|
19672
19924
|
}
|
|
19673
|
-
|
|
19674
|
-
|
|
19675
|
-
logger5.warn(`Lock timeout on attempt ${attempt + 1}/${maxRetries}`);
|
|
19676
|
-
lastError = new Error(`lock_timeout: ${errorOutput}`);
|
|
19677
|
-
continue;
|
|
19925
|
+
if (config?.allowedHazardTypes) {
|
|
19926
|
+
validatePlanForExecution(plan, config.allowedHazardTypes);
|
|
19678
19927
|
}
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19928
|
+
if (verbose) {
|
|
19929
|
+
logger5.debug(`Plan validated: ${plan.totalStatements} statement(s)`);
|
|
19930
|
+
}
|
|
19931
|
+
const planFile = join(tmpdir(), `runa-plan-${Date.now()}-${attempt}.sql`);
|
|
19932
|
+
const wrappedSql = `SET lock_timeout = '50ms';
|
|
19933
|
+
|
|
19934
|
+
${currentPlanSql}`;
|
|
19935
|
+
writeFileSync(planFile, wrappedSql, "utf-8");
|
|
19936
|
+
try {
|
|
19937
|
+
const result = psqlSyncFile({ databaseUrl: dbUrl, filePath: planFile, onErrorStop: true });
|
|
19938
|
+
if (verbose) {
|
|
19939
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
19940
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
19941
|
+
}
|
|
19942
|
+
if (result.status === 0) {
|
|
19943
|
+
return { success: true, attempts: attempt, totalWaitMs };
|
|
19944
|
+
}
|
|
19945
|
+
const errorOutput = result.stderr || result.stdout || "Unknown error";
|
|
19946
|
+
if (isLockTimeoutError(errorOutput)) {
|
|
19947
|
+
logger5.warn(`Lock timeout on attempt ${attempt + 1}/${maxRetries}`);
|
|
19948
|
+
lastError = new Error(`lock_timeout: ${errorOutput}`);
|
|
19949
|
+
continue;
|
|
19950
|
+
}
|
|
19951
|
+
logger5.error("Plan execution failed:");
|
|
19952
|
+
logger5.error(errorOutput);
|
|
19953
|
+
return {
|
|
19954
|
+
success: false,
|
|
19955
|
+
error: new Error(`Plan execution failed: ${errorOutput}`),
|
|
19956
|
+
attempts: attempt,
|
|
19957
|
+
totalWaitMs
|
|
19958
|
+
};
|
|
19959
|
+
} finally {
|
|
19960
|
+
try {
|
|
19961
|
+
unlinkSync(planFile);
|
|
19962
|
+
} catch {
|
|
19963
|
+
}
|
|
19684
19964
|
}
|
|
19685
|
-
return {
|
|
19686
|
-
success: false,
|
|
19687
|
-
error: new Error(`pg-schema-diff apply failed: ${errorOutput}`),
|
|
19688
|
-
attempts: attempt,
|
|
19689
|
-
totalWaitMs
|
|
19690
|
-
};
|
|
19691
19965
|
}
|
|
19692
19966
|
logger5.error(`Migration failed after ${maxRetries} attempts (total wait: ${totalWaitMs}ms)`);
|
|
19693
19967
|
return {
|
|
@@ -19983,48 +20257,58 @@ function handleFreshDbCase(input3, dbUrl, targetDir) {
|
|
|
19983
20257
|
return { sql: "", hazards: [], applied: true };
|
|
19984
20258
|
}
|
|
19985
20259
|
}
|
|
19986
|
-
async function applyWithLockAndRetry(dbUrl, schemasDir, includeSchemas, input3, planOutput, hazards) {
|
|
19987
|
-
logger6.step("Applying schema changes...");
|
|
20260
|
+
async function applyWithLockAndRetry(dbUrl, schemasDir, includeSchemas, input3, planOutput, hazards, tempDbDsn) {
|
|
20261
|
+
logger6.step("Applying schema changes (plan+psql)...");
|
|
19988
20262
|
const lockAcquired = acquireAdvisoryLock(dbUrl, input3.verbose);
|
|
19989
20263
|
if (!lockAcquired) {
|
|
19990
20264
|
throw new Error(
|
|
19991
20265
|
"Could not acquire migration lock. Another migration may be running. Wait for it to complete or manually release the lock."
|
|
19992
20266
|
);
|
|
19993
20267
|
}
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
|
|
20003
|
-
|
|
20004
|
-
|
|
20005
|
-
|
|
20006
|
-
|
|
20007
|
-
|
|
20008
|
-
|
|
20009
|
-
|
|
20010
|
-
|
|
20011
|
-
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
|
|
20016
|
-
|
|
20017
|
-
|
|
20018
|
-
|
|
20268
|
+
try {
|
|
20269
|
+
const allowedHazardTypes = buildAllowedHazards(input3);
|
|
20270
|
+
const result = await executePlanSqlWithRetry(dbUrl, planOutput, input3.verbose, {
|
|
20271
|
+
maxDelayMs: input3.maxLockWaitMs,
|
|
20272
|
+
allowedHazardTypes,
|
|
20273
|
+
rePlanFn: () => {
|
|
20274
|
+
const { planOutput: freshPlan } = executePgSchemaDiffPlan(
|
|
20275
|
+
dbUrl,
|
|
20276
|
+
schemasDir,
|
|
20277
|
+
includeSchemas,
|
|
20278
|
+
input3.verbose,
|
|
20279
|
+
{ tempDbDsn }
|
|
20280
|
+
);
|
|
20281
|
+
if (!freshPlan.trim() || freshPlan.includes("No changes")) {
|
|
20282
|
+
return null;
|
|
20283
|
+
}
|
|
20284
|
+
return freshPlan;
|
|
20285
|
+
}
|
|
20286
|
+
});
|
|
20287
|
+
if (!result.success) {
|
|
20288
|
+
throw result.error || new Error("Migration failed");
|
|
20289
|
+
}
|
|
20290
|
+
if (input3.verbose && result.attempts > 0) {
|
|
20291
|
+
logger6.debug(
|
|
20292
|
+
`Retry metrics: ${result.attempts} attempts, ${result.totalWaitMs}ms total wait`
|
|
20293
|
+
);
|
|
20294
|
+
}
|
|
20295
|
+
logger6.success("Schema changes applied");
|
|
20296
|
+
return {
|
|
20297
|
+
sql: planOutput,
|
|
20298
|
+
hazards,
|
|
20299
|
+
applied: true,
|
|
20300
|
+
retryAttempts: result.attempts,
|
|
20301
|
+
retryWaitMs: result.totalWaitMs
|
|
20302
|
+
};
|
|
20303
|
+
} finally {
|
|
20304
|
+
try {
|
|
20305
|
+
releaseAdvisoryLock(dbUrl, input3.verbose);
|
|
20306
|
+
} catch (lockError) {
|
|
20307
|
+
logger6.warn(
|
|
20308
|
+
`Failed to release advisory lock: ${lockError instanceof Error ? lockError.message : "Unknown error"}`
|
|
20309
|
+
);
|
|
20310
|
+
}
|
|
20019
20311
|
}
|
|
20020
|
-
logger6.success("Schema changes applied");
|
|
20021
|
-
return {
|
|
20022
|
-
sql: planOutput,
|
|
20023
|
-
hazards,
|
|
20024
|
-
applied: true,
|
|
20025
|
-
retryAttempts: result.attempts,
|
|
20026
|
-
retryWaitMs: result.totalWaitMs
|
|
20027
|
-
};
|
|
20028
20312
|
}
|
|
20029
20313
|
var ROLE_PASSWORD_CONFIGS = [
|
|
20030
20314
|
{
|
|
@@ -20135,45 +20419,66 @@ function shouldSkipInProduction(file, env2, verbose) {
|
|
|
20135
20419
|
}
|
|
20136
20420
|
return true;
|
|
20137
20421
|
}
|
|
20138
|
-
function applySingleIdempotentFile(dbUrl, schemasDir, file, verbose) {
|
|
20422
|
+
function applySingleIdempotentFile(dbUrl, schemasDir, file, verbose, pass) {
|
|
20139
20423
|
const filePath = join(schemasDir, file);
|
|
20140
20424
|
if (verbose) logger6.debug(`Applying ${file}...`);
|
|
20141
20425
|
const result = psqlSyncFile({
|
|
20142
20426
|
databaseUrl: dbUrl,
|
|
20143
20427
|
filePath,
|
|
20144
20428
|
onErrorStop: true
|
|
20145
|
-
// Fail fast on idempotent schemas
|
|
20146
20429
|
});
|
|
20147
20430
|
if (verbose) {
|
|
20148
20431
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
20149
20432
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
20150
20433
|
}
|
|
20151
20434
|
if (result.status !== 0) {
|
|
20435
|
+
if (pass === "pre") {
|
|
20436
|
+
if (verbose) {
|
|
20437
|
+
logger6.warn(`Skipped ${file} (will retry in 2nd pass)`);
|
|
20438
|
+
}
|
|
20439
|
+
return false;
|
|
20440
|
+
}
|
|
20152
20441
|
const errorMsg = result.stderr ? maskDbCredentials(result.stderr) : "";
|
|
20153
20442
|
throw new Error(`Failed to apply idempotent schema: ${file}
|
|
20154
20443
|
${errorMsg}`);
|
|
20155
20444
|
}
|
|
20445
|
+
return true;
|
|
20156
20446
|
}
|
|
20157
|
-
var applyIdempotentSchemas = fromPromise(async ({ input: { input: input3, targetDir } }) => {
|
|
20447
|
+
var applyIdempotentSchemas = fromPromise(async ({ input: { input: input3, targetDir, pass } }) => {
|
|
20158
20448
|
checkPasswordSecurity();
|
|
20159
20449
|
const schemasDir = join(targetDir, "supabase/schemas/idempotent");
|
|
20160
20450
|
const dbUrl = getDbUrl(input3);
|
|
20161
20451
|
let filesApplied = 0;
|
|
20452
|
+
let filesSkipped = 0;
|
|
20162
20453
|
if (existsSync(schemasDir)) {
|
|
20163
20454
|
const files = readdirSync(schemasDir).filter((f) => f.endsWith(".sql")).sort();
|
|
20164
20455
|
if (files.length > 0) {
|
|
20165
20456
|
for (const file of files) {
|
|
20166
20457
|
if (shouldSkipInProduction(file, input3.env, input3.verbose)) continue;
|
|
20167
|
-
applySingleIdempotentFile(dbUrl, schemasDir, file, input3.verbose);
|
|
20458
|
+
const applied = applySingleIdempotentFile(dbUrl, schemasDir, file, input3.verbose, pass);
|
|
20459
|
+
if (applied) {
|
|
20460
|
+
filesApplied++;
|
|
20461
|
+
} else {
|
|
20462
|
+
filesSkipped++;
|
|
20463
|
+
}
|
|
20464
|
+
}
|
|
20465
|
+
if (filesSkipped > 0) {
|
|
20466
|
+
logger6.success(
|
|
20467
|
+
`Applied ${filesApplied}/${filesApplied + filesSkipped} idempotent schema(s) (${filesSkipped} deferred to 2nd pass)`
|
|
20468
|
+
);
|
|
20469
|
+
if (filesSkipped === files.length) {
|
|
20470
|
+
logger6.warn("ALL idempotent files skipped in 1st pass.");
|
|
20471
|
+
logger6.warn("This may indicate a syntax error. Check logs with --verbose.");
|
|
20472
|
+
}
|
|
20473
|
+
} else {
|
|
20474
|
+
logger6.success(`Applied ${filesApplied} idempotent schema(s)`);
|
|
20168
20475
|
}
|
|
20169
|
-
logger6.success(`Applied ${files.length} idempotent schema(s)`);
|
|
20170
|
-
filesApplied = files.length;
|
|
20171
20476
|
}
|
|
20172
20477
|
} else {
|
|
20173
20478
|
logger6.info("No idempotent schemas found");
|
|
20174
20479
|
}
|
|
20175
20480
|
const rolePasswordsSet = setRolePasswords(dbUrl, input3.verbose);
|
|
20176
|
-
return { filesApplied, rolePasswordsSet };
|
|
20481
|
+
return { filesApplied, filesSkipped, rolePasswordsSet };
|
|
20177
20482
|
});
|
|
20178
20483
|
var applyPgSchemaDiff = fromPromise(async ({ input: { input: input3, targetDir } }) => {
|
|
20179
20484
|
const schemasDir = join(targetDir, "supabase/schemas/declarative");
|
|
@@ -20242,7 +20547,15 @@ ${content}`;
|
|
|
20242
20547
|
displayCheckModeResults(planOutput);
|
|
20243
20548
|
return { sql: planOutput, hazards, applied: false };
|
|
20244
20549
|
}
|
|
20245
|
-
return applyWithLockAndRetry(
|
|
20550
|
+
return applyWithLockAndRetry(
|
|
20551
|
+
dbUrl,
|
|
20552
|
+
schemasDir,
|
|
20553
|
+
includeSchemas,
|
|
20554
|
+
input3,
|
|
20555
|
+
planOutput,
|
|
20556
|
+
hazards,
|
|
20557
|
+
shadowDb?.dsn
|
|
20558
|
+
);
|
|
20246
20559
|
} finally {
|
|
20247
20560
|
if (shadowDb) {
|
|
20248
20561
|
try {
|
|
@@ -20354,6 +20667,11 @@ var DbApplyMetricsSchema = z.object({
|
|
|
20354
20667
|
z.object({
|
|
20355
20668
|
success: z.boolean(),
|
|
20356
20669
|
idempotentSchemasApplied: z.number(),
|
|
20670
|
+
/**
|
|
20671
|
+
* Number of idempotent schemas deferred during 1st pass (graceful skip).
|
|
20672
|
+
* These are retried in 2nd pass after declarative tables exist.
|
|
20673
|
+
*/
|
|
20674
|
+
idempotentSchemasSkipped: z.number().optional(),
|
|
20357
20675
|
/**
|
|
20358
20676
|
* Number of RBAC role passwords set (drizzle_app, drizzle_service).
|
|
20359
20677
|
* Set when DRIZZLE_APP_PASSWORD or DRIZZLE_SERVICE_PASSWORD env vars are present.
|
|
@@ -20474,7 +20792,9 @@ var dbApplyMachine = setup({
|
|
|
20474
20792
|
targetDir: input3.targetDir,
|
|
20475
20793
|
// 2-pass idempotent
|
|
20476
20794
|
idempotentPreApplied: 0,
|
|
20795
|
+
idempotentPreSkipped: 0,
|
|
20477
20796
|
idempotentPostApplied: 0,
|
|
20797
|
+
idempotentPostSkipped: 0,
|
|
20478
20798
|
rolePasswordsSet: 0,
|
|
20479
20799
|
schemaChangesApplied: false,
|
|
20480
20800
|
hazards: [],
|
|
@@ -20505,11 +20825,16 @@ var dbApplyMachine = setup({
|
|
|
20505
20825
|
entry: assign({ idempotentPreStartTime: () => Date.now() }),
|
|
20506
20826
|
invoke: {
|
|
20507
20827
|
src: "applyIdempotentSchemas",
|
|
20508
|
-
input: ({ context }) => ({
|
|
20828
|
+
input: ({ context }) => ({
|
|
20829
|
+
input: context.input,
|
|
20830
|
+
targetDir: context.targetDir,
|
|
20831
|
+
pass: "pre"
|
|
20832
|
+
}),
|
|
20509
20833
|
onDone: {
|
|
20510
20834
|
target: "applyingPgSchemaDiff",
|
|
20511
20835
|
actions: assign({
|
|
20512
20836
|
idempotentPreApplied: ({ event }) => event.output.filesApplied,
|
|
20837
|
+
idempotentPreSkipped: ({ event }) => event.output.filesSkipped,
|
|
20513
20838
|
rolePasswordsSet: ({ event }) => event.output.rolePasswordsSet,
|
|
20514
20839
|
idempotentPreEndTime: () => Date.now()
|
|
20515
20840
|
})
|
|
@@ -20568,11 +20893,16 @@ var dbApplyMachine = setup({
|
|
|
20568
20893
|
entry: assign({ idempotentPostStartTime: () => Date.now() }),
|
|
20569
20894
|
invoke: {
|
|
20570
20895
|
src: "applyIdempotentSchemas",
|
|
20571
|
-
input: ({ context }) => ({
|
|
20896
|
+
input: ({ context }) => ({
|
|
20897
|
+
input: context.input,
|
|
20898
|
+
targetDir: context.targetDir,
|
|
20899
|
+
pass: "post"
|
|
20900
|
+
}),
|
|
20572
20901
|
onDone: {
|
|
20573
20902
|
target: "applyingSeeds",
|
|
20574
20903
|
actions: assign({
|
|
20575
20904
|
idempotentPostApplied: ({ event }) => event.output.filesApplied,
|
|
20905
|
+
idempotentPostSkipped: ({ event }) => event.output.filesSkipped,
|
|
20576
20906
|
idempotentPostEndTime: () => Date.now()
|
|
20577
20907
|
})
|
|
20578
20908
|
},
|
|
@@ -20630,9 +20960,11 @@ var dbApplyMachine = setup({
|
|
|
20630
20960
|
retryAttempts: context.retryAttempts > 0 ? context.retryAttempts : void 0
|
|
20631
20961
|
};
|
|
20632
20962
|
const totalIdempotentApplied = context.idempotentPreApplied + context.idempotentPostApplied;
|
|
20963
|
+
const totalIdempotentSkipped = context.idempotentPreSkipped + context.idempotentPostSkipped;
|
|
20633
20964
|
return {
|
|
20634
20965
|
success: !context.error,
|
|
20635
20966
|
idempotentSchemasApplied: totalIdempotentApplied,
|
|
20967
|
+
idempotentSchemasSkipped: totalIdempotentSkipped > 0 ? totalIdempotentSkipped : void 0,
|
|
20636
20968
|
rolePasswordsSet: context.rolePasswordsSet > 0 ? context.rolePasswordsSet : void 0,
|
|
20637
20969
|
schemaChangesApplied: context.schemaChangesApplied,
|
|
20638
20970
|
hazards: context.hazards,
|
|
@@ -26289,7 +26621,7 @@ function getTablesWithTimestamps(dbUrl, schemas) {
|
|
|
26289
26621
|
JOIN pg_attribute a1 ON c.oid = a1.attrelid AND a1.attname = 'updated_at'
|
|
26290
26622
|
JOIN pg_attribute a2 ON c.oid = a2.attrelid AND a2.attname = 'created_at'
|
|
26291
26623
|
WHERE n.nspname IN (${schemasFilter})
|
|
26292
|
-
AND c.relkind
|
|
26624
|
+
AND c.relkind IN ('r', 'p')
|
|
26293
26625
|
ORDER BY n.nspname, c.relname;
|
|
26294
26626
|
`;
|
|
26295
26627
|
const conn = parsePostgresUrl(dbUrl);
|
|
@@ -31105,7 +31437,7 @@ init_esm_shims();
|
|
|
31105
31437
|
|
|
31106
31438
|
// src/constants/versions.ts
|
|
31107
31439
|
init_esm_shims();
|
|
31108
|
-
var COMPATIBLE_TEMPLATES_VERSION = "0.5.
|
|
31440
|
+
var COMPATIBLE_TEMPLATES_VERSION = "0.5.60";
|
|
31109
31441
|
var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
|
|
31110
31442
|
var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
|
|
31111
31443
|
|
|
@@ -35695,17 +36027,17 @@ function printActionsNeeded(logger16, actions) {
|
|
|
35695
36027
|
);
|
|
35696
36028
|
}
|
|
35697
36029
|
function findRepoRoot3(startDir) {
|
|
35698
|
-
const { existsSync: existsSync53, readFileSync:
|
|
35699
|
-
const { join:
|
|
36030
|
+
const { existsSync: existsSync53, readFileSync: readFileSync30 } = __require("fs");
|
|
36031
|
+
const { join: join24, dirname: dirname5 } = __require("path");
|
|
35700
36032
|
let current = startDir;
|
|
35701
36033
|
while (current !== dirname5(current)) {
|
|
35702
|
-
if (existsSync53(
|
|
36034
|
+
if (existsSync53(join24(current, "turbo.json"))) {
|
|
35703
36035
|
return current;
|
|
35704
36036
|
}
|
|
35705
|
-
const pkgPath =
|
|
36037
|
+
const pkgPath = join24(current, "package.json");
|
|
35706
36038
|
if (existsSync53(pkgPath)) {
|
|
35707
36039
|
try {
|
|
35708
|
-
const pkg = JSON.parse(
|
|
36040
|
+
const pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
|
|
35709
36041
|
if (pkg.workspaces) {
|
|
35710
36042
|
return current;
|
|
35711
36043
|
}
|
|
@@ -35772,8 +36104,8 @@ function generateReportOutput(output3, isJsonMode) {
|
|
|
35772
36104
|
}
|
|
35773
36105
|
function validateRunaRepo(repoRoot) {
|
|
35774
36106
|
const { existsSync: existsSync53 } = __require("fs");
|
|
35775
|
-
const { join:
|
|
35776
|
-
const templateDir =
|
|
36107
|
+
const { join: join24 } = __require("path");
|
|
36108
|
+
const templateDir = join24(repoRoot, "packages/runa-templates/templates");
|
|
35777
36109
|
if (!existsSync53(templateDir)) {
|
|
35778
36110
|
throw new CLIError("template-check is a runa-repo only command", "NOT_RUNA_REPO", [
|
|
35779
36111
|
"This command compares runa-repo with pj-repo templates",
|