@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/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, rmSync, readFileSync, readdirSync, mkdtempSync, writeFileSync, mkdirSync, copyFileSync, createWriteStream, statSync, realpathSync, promises, lstatSync, accessSync, constants, chmodSync, unlinkSync } from 'fs';
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.57";
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: readFileSync29 } = __require("fs");
7602
- const { join: join23, dirname: dirname5 } = __require("path");
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(join23(current, "turbo.json"))) {
7605
+ if (existsSync53(join24(current, "turbo.json"))) {
7606
7606
  return current;
7607
7607
  }
7608
- const pkgPath = join23(current, "package.json");
7608
+ const pkgPath = join24(current, "package.json");
7609
7609
  if (existsSync53(pkgPath)) {
7610
7610
  try {
7611
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
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: readFileSync29 } = __require("fs");
7911
- const raw = readFileSync29(pkgPath, "utf-8");
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?.["start"]);
8176
- const appHasDefaultScript = mode === "start" && Boolean(appScripts?.["start"]);
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: ["setup"]
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: "setup" }
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: readFileSync29 } = await import('fs');
10091
- logContent = readFileSync29(logFile, "utf-8");
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: readFileSync29 } = await import('fs');
10220
- logContent = readFileSync29(logPath, "utf-8");
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 (avoid duplicate application)
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='r'
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 executeApplyWithRetry(applyArgs, verbose, config) {
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 applyResult = spawnSync("pg-schema-diff", applyArgs, {
19661
- encoding: "utf-8",
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
- const errorOutput = stderr || stdout || "Unknown error";
19674
- if (isLockTimeoutError(errorOutput)) {
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
- logger5.error("pg-schema-diff apply failed:");
19680
- logger5.error(errorOutput);
19681
- const extDetection = detectMissingExtensionType(errorOutput);
19682
- if (extDetection.detected) {
19683
- logger5.error(formatExtensionErrorHint(extDetection));
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
- const applyArgs = [
19995
- "apply",
19996
- "--from-dsn",
19997
- dbUrl,
19998
- "--to-dir",
19999
- schemasDir,
20000
- "--disable-plan-validation",
20001
- "--skip-confirm-prompt",
20002
- "-l",
20003
- 'pattern=".*" timeout=50ms',
20004
- ...includeSchemas.flatMap((s) => ["--include-schema", s])
20005
- ];
20006
- const allowedHazards = buildAllowedHazards(input3);
20007
- if (allowedHazards.length > 0) {
20008
- applyArgs.push("--allow-hazards", allowedHazards.join(","));
20009
- }
20010
- const result = await executeApplyWithRetry(applyArgs, input3.verbose, {
20011
- maxDelayMs: input3.maxLockWaitMs
20012
- });
20013
- releaseAdvisoryLock(dbUrl, input3.verbose);
20014
- if (!result.success) {
20015
- throw result.error || new Error("Migration failed");
20016
- }
20017
- if (input3.verbose && result.attempts > 0) {
20018
- logger6.debug(`Retry metrics: ${result.attempts} attempts, ${result.totalWaitMs}ms total wait`);
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(dbUrl, schemasDir, includeSchemas, input3, planOutput, hazards);
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 }) => ({ input: context.input, targetDir: context.targetDir }),
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 }) => ({ input: context.input, targetDir: context.targetDir }),
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 = 'r'
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.56";
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: readFileSync29 } = __require("fs");
35699
- const { join: join23, dirname: dirname5 } = __require("path");
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(join23(current, "turbo.json"))) {
36034
+ if (existsSync53(join24(current, "turbo.json"))) {
35703
36035
  return current;
35704
36036
  }
35705
- const pkgPath = join23(current, "package.json");
36037
+ const pkgPath = join24(current, "package.json");
35706
36038
  if (existsSync53(pkgPath)) {
35707
36039
  try {
35708
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
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: join23 } = __require("path");
35776
- const templateDir = join23(repoRoot, "packages/runa-templates/templates");
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",