@runa-ai/runa-cli 0.5.58 → 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.
@@ -6,6 +6,11 @@
6
6
  * Note:
7
7
  * - This module used to be named `ci-pr-supabase-local.ts`. It is now shared across CI commands.
8
8
  */
9
+ /**
10
+ * Pre-flight check for port conflicts before starting Supabase.
11
+ * Warns if DB port is already occupied by another Supabase instance.
12
+ */
13
+ export declare function checkSupabasePortConflicts(repoRoot: string): Promise<void>;
9
14
  export declare function startSupabaseLocal(params: {
10
15
  repoRoot: string;
11
16
  tmpDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ci-supabase-local.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-supabase-local.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CASpE;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,8JAC0H,CAAC;AAE9J,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqDpD;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DtC"}
1
+ {"version":3,"file":"ci-supabase-local.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-supabase-local.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6CH;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ChF;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAehB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CASpE;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,8JAC0H,CAAC;AAE9J,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqDpD;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../../../../../src/commands/ci/machine/actors/db/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,OAAO,KAAK,EAAE,MAAM,EAAqB,mBAAmB,EAAiB,MAAM,gBAAgB,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAmQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,qGAwG3B,CAAC"}
1
+ {"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../../../../../src/commands/ci/machine/actors/db/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,OAAO,KAAK,EAAE,MAAM,EAAqB,mBAAmB,EAAiB,MAAM,gBAAgB,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAmQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,qGA0G3B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import type { DbApplyInput } from './contract.js';
2
2
  export interface ApplyResult {
3
3
  filesApplied: number;
4
+ filesSkipped: number;
4
5
  rolePasswordsSet: number;
5
6
  }
6
7
  export interface PgSchemaDiffResult {
@@ -18,10 +19,13 @@ export interface PgSchemaDiffResult {
18
19
  * Flow:
19
20
  * 1. Apply idempotent/*.sql files (includes 15_rbac_roles.sql for ROLE creation)
20
21
  * 2. Set passwords for drizzle_app/drizzle_service if env vars present
22
+ *
23
+ * @param pass - 'pre' = 1st pass (graceful skip on error), 'post' = 2nd pass (strict)
21
24
  */
22
25
  export declare const applyIdempotentSchemas: import("xstate").PromiseActorLogic<ApplyResult, {
23
26
  input: DbApplyInput;
24
27
  targetDir: string;
28
+ pass: "pre" | "post";
25
29
  }, import("xstate").EventObject>;
26
30
  /**
27
31
  * Apply schema changes using pg-schema-diff (or direct psql for fresh DB).
@@ -1 +1 @@
1
- {"version":3,"file":"actors.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/actors.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA4ElD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAwwBD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;WAExB,YAAY;eAAa,MAAM;gCAgCxC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB;WAEnB,YAAY;eAAa,MAAM;gCA2GxC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU;aACV,OAAO;;WACT,YAAY;eAAa,MAAM;gCAqDxC,CAAC"}
1
+ {"version":3,"file":"actors.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/actors.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA4ElD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA2yBD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;WAExB,YAAY;eAAa,MAAM;UAAQ,KAAK,GAAG,MAAM;gCA+C9D,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB;WAEnB,YAAY;eAAa,MAAM;gCAmHxC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU;aACV,OAAO;;WACT,YAAY;eAAa,MAAM;gCAqDxC,CAAC"}
@@ -43,6 +43,7 @@ export type DbApplyMetrics = z.infer<typeof DbApplyMetricsSchema>;
43
43
  export declare const DbApplyOutputSchema: z.ZodObject<{
44
44
  success: z.ZodBoolean;
45
45
  idempotentSchemasApplied: z.ZodNumber;
46
+ idempotentSchemasSkipped: z.ZodOptional<z.ZodNumber>;
46
47
  rolePasswordsSet: z.ZodOptional<z.ZodNumber>;
47
48
  schemaChangesApplied: z.ZodBoolean;
48
49
  hazards: z.ZodArray<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;kBAoCpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;iBAe/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;iBAwB9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;kBAoCpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;iBAe/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;iBA6B9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -4,10 +4,12 @@
4
4
  * Purpose: Export all helper functions for db apply actors
5
5
  */
6
6
  export { acquireAdvisoryLock, MIGRATION_LOCK_ID, releaseAdvisoryLock, } from './advisory-lock.js';
7
+ export type { PlanHazard, PlanStatement, ValidatedPlan } from './plan-validator.js';
8
+ export { parsePlanOutput, validatePlanForExecution } from './plan-validator.js';
7
9
  export type { ParsedHazard, PgSchemaDiffPlanOptions } from './pg-schema-diff-helpers.js';
8
10
  export type { ShadowDbConfig, ShadowDbResult } from './shadow-db-manager.js';
9
11
  export { buildAllowedHazards, displayCheckModeResults, displayHazards, displayHazardsWithContext, executePgSchemaDiffPlan, filterFalsePositiveHazards, getIdempotentRoles, handleHazards, handleHazardsWithContext, handleProductionAuthzProtection, handleProductionDataProtection, isIdempotentRoleHazard, parseHazardsFromOutput, parseHazardsWithContext, resetIdempotentRolesCache, verifyDatabaseConnection, verifyPgSchemaDiffBinary, } from './pg-schema-diff-helpers.js';
10
- export type { RetryConfig, RetryResult } from './retry-logic.js';
11
- export { BASE_DELAY_MS, calculateBackoffDelay, DEFAULT_MAX_DELAY_MS, executeApplyWithRetry, isLockTimeoutError, MAX_RETRIES, sleep, } from './retry-logic.js';
12
+ export type { PlanSqlRetryConfig, RetryConfig, RetryResult } from './retry-logic.js';
13
+ export { BASE_DELAY_MS, calculateBackoffDelay, DEFAULT_MAX_DELAY_MS, executeApplyWithRetry, executePlanSqlWithRetry, isLockTimeoutError, MAX_RETRIES, sleep, } from './retry-logic.js';
12
14
  export { createShadowDbWithExtensions, needsShadowDb } from './shadow-db-manager.js';
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/commands/db/apply/helpers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACzF,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7E,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,+BAA+B,EAC/B,8BAA8B,EAC9B,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,KAAK,GACN,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,4BAA4B,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/commands/db/apply/helpers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACzF,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7E,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,+BAA+B,EAC/B,8BAA8B,EAC9B,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAErF,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,EACX,KAAK,GACN,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,4BAA4B,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AI HINT: pg-schema-diff Plan Output Validator
3
+ *
4
+ * Purpose: Parse pg-schema-diff plan output into structured statements and
5
+ * validate with Zod before psql execution (safety gate).
6
+ *
7
+ * Pattern: plan → Zod validation → psql execution
8
+ *
9
+ * Safety layers:
10
+ * 1. handleHazards() in actors.ts → blocks unapproved hazards (primary)
11
+ * 2. validatePlanForExecution() → defense-in-depth hazard check
12
+ * 3. Zod structural validation → ensures plan is well-formed
13
+ */
14
+ import { z } from 'zod';
15
+ export declare const PlanHazardSchema: z.ZodObject<{
16
+ type: z.ZodString;
17
+ message: z.ZodString;
18
+ }, z.core.$strip>;
19
+ export declare const PlanStatementSchema: z.ZodObject<{
20
+ index: z.ZodNumber;
21
+ sql: z.ZodString;
22
+ hazards: z.ZodArray<z.ZodObject<{
23
+ type: z.ZodString;
24
+ message: z.ZodString;
25
+ }, z.core.$strip>>;
26
+ }, z.core.$strip>;
27
+ export declare const ValidatedPlanSchema: z.ZodObject<{
28
+ statements: z.ZodArray<z.ZodObject<{
29
+ index: z.ZodNumber;
30
+ sql: z.ZodString;
31
+ hazards: z.ZodArray<z.ZodObject<{
32
+ type: z.ZodString;
33
+ message: z.ZodString;
34
+ }, z.core.$strip>>;
35
+ }, z.core.$strip>>;
36
+ totalStatements: z.ZodNumber;
37
+ rawSql: z.ZodString;
38
+ }, z.core.$strip>;
39
+ export type PlanHazard = z.infer<typeof PlanHazardSchema>;
40
+ export type PlanStatement = z.infer<typeof PlanStatementSchema>;
41
+ export type ValidatedPlan = z.infer<typeof ValidatedPlanSchema>;
42
+ /**
43
+ * Parse pg-schema-diff plan output into structured statements.
44
+ *
45
+ * Handles two formats:
46
+ * 1. With `-- Statement Idx. N` markers → split into individual statements
47
+ * 2. Without markers → treat as single statement
48
+ *
49
+ * @throws ZodError if parsed result fails structural validation
50
+ */
51
+ export declare function parsePlanOutput(planOutput: string): ValidatedPlan;
52
+ /**
53
+ * Validate that all hazards in the plan are in the allowed list.
54
+ *
55
+ * Defense-in-depth: handleHazards() in actors.ts is the primary check.
56
+ * This is a secondary safety gate right before psql execution.
57
+ *
58
+ * @throws Error if unresolved hazards are found
59
+ */
60
+ export declare function validatePlanForExecution(plan: ValidatedPlan, allowedHazardTypes: string[]): void;
61
+ //# sourceMappingURL=plan-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-validator.d.ts","sourceRoot":"","sources":["../../../../../src/commands/db/apply/helpers/plan-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;iBAI9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;iBAI9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAiFhE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAgBjE;AAMD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAG,IAAI,CAehG"}
@@ -48,4 +48,29 @@ export declare function isLockTimeoutError(errorOutput: string): boolean;
48
48
  * Enhanced with configurable options and metrics.
49
49
  */
50
50
  export declare function executeApplyWithRetry(applyArgs: string[], verbose: boolean, config?: RetryConfig): Promise<RetryResult>;
51
+ /**
52
+ * Options for plan+psql execution with retry.
53
+ */
54
+ export interface PlanSqlRetryConfig extends RetryConfig {
55
+ /**
56
+ * Re-generate plan SQL on retry to handle partial execution safely.
57
+ * Returns fresh plan SQL, or null if all changes are already applied.
58
+ */
59
+ rePlanFn?: () => string | null;
60
+ /**
61
+ * Allowed hazard types for defense-in-depth validation.
62
+ * Plan SQL is validated against this list before each psql execution.
63
+ * If omitted, hazard validation is skipped (primary check in actors.ts handles it).
64
+ */
65
+ allowedHazardTypes?: string[];
66
+ }
67
+ /**
68
+ * Execute plan SQL via psql with retry logic.
69
+ *
70
+ * Replaces pg-schema-diff apply to avoid creating temp DB on the production server,
71
+ * which exhausts connections on Supabase (max_connections=60).
72
+ *
73
+ * On retry: calls rePlanFn() to regenerate plan (handles partial execution safely).
74
+ */
75
+ export declare function executePlanSqlWithRetry(dbUrl: string, initialPlanSql: string, verbose: boolean, config?: PlanSqlRetryConfig): Promise<RetryResult>;
51
76
  //# sourceMappingURL=retry-logic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retry-logic.d.ts","sourceRoot":"","sources":["../../../../../src/commands/db/apply/helpers/retry-logic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAuB,GAAG,MAAM,CAIhG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAM/D;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAqEtB"}
1
+ {"version":3,"file":"retry-logic.d.ts","sourceRoot":"","sources":["../../../../../src/commands/db/apply/helpers/retry-logic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAuB,GAAG,MAAM,CAIhG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAM/D;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAqEtB;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CA0FtB"}
@@ -35,7 +35,9 @@ interface DbApplyContext {
35
35
  input: DbApplyInput;
36
36
  targetDir: string;
37
37
  idempotentPreApplied: number;
38
+ idempotentPreSkipped: number;
38
39
  idempotentPostApplied: number;
40
+ idempotentPostSkipped: number;
39
41
  rolePasswordsSet: number;
40
42
  schemaChangesApplied: boolean;
41
43
  hazards: string[];
@@ -67,6 +69,7 @@ export declare const dbApplyMachine: import("xstate").StateMachine<DbApplyContex
67
69
  }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<actors.ApplyResult, {
68
70
  input: DbApplyInput;
69
71
  targetDir: string;
72
+ pass: "pre" | "post";
70
73
  }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<actors.PgSchemaDiffResult, {
71
74
  input: DbApplyInput;
72
75
  targetDir: string;
@@ -85,6 +88,7 @@ export declare const dbApplyMachine: import("xstate").StateMachine<DbApplyContex
85
88
  logic: import("xstate").PromiseActorLogic<actors.ApplyResult, {
86
89
  input: DbApplyInput;
87
90
  targetDir: string;
91
+ pass: "pre" | "post";
88
92
  }, import("xstate").EventObject>;
89
93
  id: string | undefined;
90
94
  } | {
@@ -103,6 +107,7 @@ export declare const dbApplyMachine: import("xstate").StateMachine<DbApplyContex
103
107
  schemaChangesApplied: boolean;
104
108
  hazards: string[];
105
109
  seedsApplied: boolean;
110
+ idempotentSchemasSkipped?: number | undefined;
106
111
  rolePasswordsSet?: number | undefined;
107
112
  error?: string | undefined;
108
113
  planSql?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAU,KAAK,YAAY,EAAS,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,eAAe,CAAC;AA6FjF,UAAU,cAAc;IACtB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAElB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,eAAO,MAAM,cAAc;UAFC,OAAO;;UAAa,gBAAgB;WAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAI1D,YAAY;eAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6NjD,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC;AACnD,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC;AAGlE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAErE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAEpE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAExE"}
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/apply/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAU,KAAK,YAAY,EAAS,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,eAAe,CAAC;AA6FjF,UAAU,cAAc;IACtB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAElB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,eAAO,MAAM,cAAc;UAFC,OAAO;;UAAa,gBAAgB;WAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAI1D,YAAY;eAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2OjD,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC;AACnD,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC;AAGlE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAErE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAEpE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAExE"}
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * Sync strategy: Keep this in sync with packages/runa-templates/package.json version.
22
22
  */
23
- export declare const COMPATIBLE_TEMPLATES_VERSION = "0.5.56";
23
+ export declare const COMPATIBLE_TEMPLATES_VERSION = "0.5.60";
24
24
  /**
25
25
  * Templates package name on GitHub Packages.
26
26
  * Published to npm.pkg.github.com (requires NODE_AUTH_TOKEN).
package/dist/index.js CHANGED
@@ -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.58";
1164
+ CLI_VERSION = "0.5.60";
1165
1165
  HAS_ADMIN_COMMAND = false;
1166
1166
  }
1167
1167
  });
@@ -7599,13 +7599,13 @@ function printSummary(logger16, output3) {
7599
7599
  }
7600
7600
  function findRepoRoot(startDir) {
7601
7601
  const { existsSync: existsSync53, readFileSync: readFileSync30 } = __require("fs");
7602
- const { join: join23, dirname: dirname5 } = __require("path");
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
7611
  const pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
@@ -8176,8 +8176,8 @@ function determineAppCommand(mode, isMonorepo2, rootScripts, appScripts, repoRoo
8176
8176
  const nextCommand = mode === "dev" ? "dev" : "start";
8177
8177
  const rootHasCiScript = Boolean(rootScripts?.[ciScriptName]);
8178
8178
  const appHasCiScript = Boolean(appScripts?.[ciScriptName]);
8179
- const rootHasDefaultScript = mode === "start" && Boolean(rootScripts?.["start"]);
8180
- const appHasDefaultScript = mode === "start" && Boolean(appScripts?.["start"]);
8179
+ const rootHasDefaultScript = mode === "start" && Boolean(rootScripts?.start);
8180
+ const appHasDefaultScript = mode === "start" && Boolean(appScripts?.start);
8181
8181
  if (isMonorepo2 && (rootHasCiScript || rootHasDefaultScript)) {
8182
8182
  const scriptName = rootHasCiScript ? ciScriptName : "start";
8183
8183
  return { command: ["pnpm", scriptName], useRootScript: true };
@@ -8429,7 +8429,7 @@ var processCheckActor = fromPromise(async ({ input: input3 }) => {
8429
8429
  }
8430
8430
  if (pidFileContent) {
8431
8431
  const pid = parseInt(pidFileContent, 10);
8432
- if (!isNaN(pid) && isProcessAlive(pid)) {
8432
+ if (!Number.isNaN(pid) && isProcessAlive(pid)) {
8433
8433
  if (input3.replace) {
8434
8434
  await terminateAppProcessByPid({
8435
8435
  pid,
@@ -13099,7 +13099,7 @@ var syncSchemaActor = fromPromise(
13099
13099
  "--auto-approve",
13100
13100
  // Allow DELETES_DATA hazards in preview
13101
13101
  "--no-seed",
13102
- // Seeds applied separately by applySeedsActor (avoid duplicate application)
13102
+ // Seeds applied separately by applySeedsActor
13103
13103
  "--verbose"
13104
13104
  // Always verbose for full traceability
13105
13105
  ] : [
@@ -13109,6 +13109,8 @@ var syncSchemaActor = fromPromise(
13109
13109
  "sync",
13110
13110
  envArg,
13111
13111
  "--auto-approve",
13112
+ "--no-seed",
13113
+ // Seeds applied separately by applySeedsActor
13112
13114
  "--verbose",
13113
13115
  // Always verbose for full traceability
13114
13116
  ...skipCodegen ? ["--skip-codegen"] : []
@@ -13245,7 +13247,67 @@ init_esm_shims();
13245
13247
  // src/commands/ci/commands/ci-supabase-local.ts
13246
13248
  init_esm_shims();
13247
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
+ }
13248
13309
  async function startSupabaseLocal(params) {
13310
+ await checkSupabasePortConflicts(params.repoRoot);
13249
13311
  const exclude = process.env.RUNA_CI_SUPABASE_EXCLUDE ?? "studio,edge-runtime,storage-api,realtime,imgproxy,mailpit,logflare,vector,supavisor";
13250
13312
  await runLogged({
13251
13313
  cwd: params.repoRoot,
@@ -13465,7 +13527,7 @@ AND table_schema NOT IN ('information_schema');
13465
13527
  SELECT COUNT(*)::int
13466
13528
  FROM pg_class c
13467
13529
  JOIN pg_namespace n ON n.oid=c.relnamespace
13468
- WHERE c.relkind='r'
13530
+ WHERE c.relkind IN ('r','p')
13469
13531
  AND n.nspname NOT LIKE 'pg_%'
13470
13532
  AND n.nspname NOT IN ('information_schema')
13471
13533
  AND c.relrowsecurity;
@@ -19404,6 +19466,93 @@ function releaseAdvisoryLock(dbUrl, verbose) {
19404
19466
  }
19405
19467
  }
19406
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
+
19407
19556
  // src/commands/db/apply/helpers/pg-schema-diff-helpers.ts
19408
19557
  init_esm_shims();
19409
19558
  var logger4 = createCLILogger("db:apply");
@@ -19749,49 +19898,70 @@ function calculateBackoffDelay(attempt, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
19749
19898
  function isLockTimeoutError(errorOutput) {
19750
19899
  return errorOutput.includes("lock_timeout") || errorOutput.includes("canceling statement due to lock timeout") || errorOutput.includes("could not obtain lock");
19751
19900
  }
19752
- async function executeApplyWithRetry(applyArgs, verbose, config) {
19901
+ async function executePlanSqlWithRetry(dbUrl, initialPlanSql, verbose, config) {
19753
19902
  const maxRetries = config?.maxRetries ?? MAX_RETRIES;
19754
19903
  const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
19755
19904
  let lastError = null;
19756
19905
  let totalWaitMs = 0;
19906
+ let currentPlanSql = initialPlanSql;
19757
19907
  for (let attempt = 0; attempt < maxRetries; attempt++) {
19758
19908
  if (attempt > 0) {
19759
19909
  const delay = calculateBackoffDelay(attempt - 1, maxDelayMs);
19760
19910
  totalWaitMs += delay;
19761
19911
  logger5.info(`Retry ${attempt}/${maxRetries - 1} after ${Math.round(delay)}ms...`);
19762
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
+ }
19763
19920
  }
19764
- const applyResult = spawnSync("pg-schema-diff", applyArgs, {
19765
- encoding: "utf-8",
19766
- stdio: ["pipe", "pipe", "pipe"]
19767
- });
19768
- const stdout = applyResult.stdout || "";
19769
- const stderr = applyResult.stderr || "";
19770
- if (verbose) {
19771
- if (stdout) process.stdout.write(stdout);
19772
- if (stderr) process.stderr.write(stderr);
19773
- }
19774
- if (applyResult.status === 0) {
19921
+ const plan = parsePlanOutput(currentPlanSql);
19922
+ if (plan.totalStatements === 0) {
19775
19923
  return { success: true, attempts: attempt, totalWaitMs };
19776
19924
  }
19777
- const errorOutput = stderr || stdout || "Unknown error";
19778
- if (isLockTimeoutError(errorOutput)) {
19779
- logger5.warn(`Lock timeout on attempt ${attempt + 1}/${maxRetries}`);
19780
- lastError = new Error(`lock_timeout: ${errorOutput}`);
19781
- continue;
19925
+ if (config?.allowedHazardTypes) {
19926
+ validatePlanForExecution(plan, config.allowedHazardTypes);
19782
19927
  }
19783
- logger5.error("pg-schema-diff apply failed:");
19784
- logger5.error(errorOutput);
19785
- const extDetection = detectMissingExtensionType(errorOutput);
19786
- if (extDetection.detected) {
19787
- 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
+ }
19788
19964
  }
19789
- return {
19790
- success: false,
19791
- error: new Error(`pg-schema-diff apply failed: ${errorOutput}`),
19792
- attempts: attempt,
19793
- totalWaitMs
19794
- };
19795
19965
  }
19796
19966
  logger5.error(`Migration failed after ${maxRetries} attempts (total wait: ${totalWaitMs}ms)`);
19797
19967
  return {
@@ -20087,48 +20257,58 @@ function handleFreshDbCase(input3, dbUrl, targetDir) {
20087
20257
  return { sql: "", hazards: [], applied: true };
20088
20258
  }
20089
20259
  }
20090
- async function applyWithLockAndRetry(dbUrl, schemasDir, includeSchemas, input3, planOutput, hazards) {
20091
- logger6.step("Applying schema changes...");
20260
+ async function applyWithLockAndRetry(dbUrl, schemasDir, includeSchemas, input3, planOutput, hazards, tempDbDsn) {
20261
+ logger6.step("Applying schema changes (plan+psql)...");
20092
20262
  const lockAcquired = acquireAdvisoryLock(dbUrl, input3.verbose);
20093
20263
  if (!lockAcquired) {
20094
20264
  throw new Error(
20095
20265
  "Could not acquire migration lock. Another migration may be running. Wait for it to complete or manually release the lock."
20096
20266
  );
20097
20267
  }
20098
- const applyArgs = [
20099
- "apply",
20100
- "--from-dsn",
20101
- dbUrl,
20102
- "--to-dir",
20103
- schemasDir,
20104
- "--disable-plan-validation",
20105
- "--skip-confirm-prompt",
20106
- "-l",
20107
- 'pattern=".*" timeout=50ms',
20108
- ...includeSchemas.flatMap((s) => ["--include-schema", s])
20109
- ];
20110
- const allowedHazards = buildAllowedHazards(input3);
20111
- if (allowedHazards.length > 0) {
20112
- applyArgs.push("--allow-hazards", allowedHazards.join(","));
20113
- }
20114
- const result = await executeApplyWithRetry(applyArgs, input3.verbose, {
20115
- maxDelayMs: input3.maxLockWaitMs
20116
- });
20117
- releaseAdvisoryLock(dbUrl, input3.verbose);
20118
- if (!result.success) {
20119
- throw result.error || new Error("Migration failed");
20120
- }
20121
- if (input3.verbose && result.attempts > 0) {
20122
- 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
+ }
20123
20311
  }
20124
- logger6.success("Schema changes applied");
20125
- return {
20126
- sql: planOutput,
20127
- hazards,
20128
- applied: true,
20129
- retryAttempts: result.attempts,
20130
- retryWaitMs: result.totalWaitMs
20131
- };
20132
20312
  }
20133
20313
  var ROLE_PASSWORD_CONFIGS = [
20134
20314
  {
@@ -20239,45 +20419,66 @@ function shouldSkipInProduction(file, env2, verbose) {
20239
20419
  }
20240
20420
  return true;
20241
20421
  }
20242
- function applySingleIdempotentFile(dbUrl, schemasDir, file, verbose) {
20422
+ function applySingleIdempotentFile(dbUrl, schemasDir, file, verbose, pass) {
20243
20423
  const filePath = join(schemasDir, file);
20244
20424
  if (verbose) logger6.debug(`Applying ${file}...`);
20245
20425
  const result = psqlSyncFile({
20246
20426
  databaseUrl: dbUrl,
20247
20427
  filePath,
20248
20428
  onErrorStop: true
20249
- // Fail fast on idempotent schemas
20250
20429
  });
20251
20430
  if (verbose) {
20252
20431
  if (result.stdout) process.stdout.write(result.stdout);
20253
20432
  if (result.stderr) process.stderr.write(result.stderr);
20254
20433
  }
20255
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
+ }
20256
20441
  const errorMsg = result.stderr ? maskDbCredentials(result.stderr) : "";
20257
20442
  throw new Error(`Failed to apply idempotent schema: ${file}
20258
20443
  ${errorMsg}`);
20259
20444
  }
20445
+ return true;
20260
20446
  }
20261
- var applyIdempotentSchemas = fromPromise(async ({ input: { input: input3, targetDir } }) => {
20447
+ var applyIdempotentSchemas = fromPromise(async ({ input: { input: input3, targetDir, pass } }) => {
20262
20448
  checkPasswordSecurity();
20263
20449
  const schemasDir = join(targetDir, "supabase/schemas/idempotent");
20264
20450
  const dbUrl = getDbUrl(input3);
20265
20451
  let filesApplied = 0;
20452
+ let filesSkipped = 0;
20266
20453
  if (existsSync(schemasDir)) {
20267
20454
  const files = readdirSync(schemasDir).filter((f) => f.endsWith(".sql")).sort();
20268
20455
  if (files.length > 0) {
20269
20456
  for (const file of files) {
20270
20457
  if (shouldSkipInProduction(file, input3.env, input3.verbose)) continue;
20271
- 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)`);
20272
20475
  }
20273
- logger6.success(`Applied ${files.length} idempotent schema(s)`);
20274
- filesApplied = files.length;
20275
20476
  }
20276
20477
  } else {
20277
20478
  logger6.info("No idempotent schemas found");
20278
20479
  }
20279
20480
  const rolePasswordsSet = setRolePasswords(dbUrl, input3.verbose);
20280
- return { filesApplied, rolePasswordsSet };
20481
+ return { filesApplied, filesSkipped, rolePasswordsSet };
20281
20482
  });
20282
20483
  var applyPgSchemaDiff = fromPromise(async ({ input: { input: input3, targetDir } }) => {
20283
20484
  const schemasDir = join(targetDir, "supabase/schemas/declarative");
@@ -20346,7 +20547,15 @@ ${content}`;
20346
20547
  displayCheckModeResults(planOutput);
20347
20548
  return { sql: planOutput, hazards, applied: false };
20348
20549
  }
20349
- 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
+ );
20350
20559
  } finally {
20351
20560
  if (shadowDb) {
20352
20561
  try {
@@ -20458,6 +20667,11 @@ var DbApplyMetricsSchema = z.object({
20458
20667
  z.object({
20459
20668
  success: z.boolean(),
20460
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(),
20461
20675
  /**
20462
20676
  * Number of RBAC role passwords set (drizzle_app, drizzle_service).
20463
20677
  * Set when DRIZZLE_APP_PASSWORD or DRIZZLE_SERVICE_PASSWORD env vars are present.
@@ -20578,7 +20792,9 @@ var dbApplyMachine = setup({
20578
20792
  targetDir: input3.targetDir,
20579
20793
  // 2-pass idempotent
20580
20794
  idempotentPreApplied: 0,
20795
+ idempotentPreSkipped: 0,
20581
20796
  idempotentPostApplied: 0,
20797
+ idempotentPostSkipped: 0,
20582
20798
  rolePasswordsSet: 0,
20583
20799
  schemaChangesApplied: false,
20584
20800
  hazards: [],
@@ -20609,11 +20825,16 @@ var dbApplyMachine = setup({
20609
20825
  entry: assign({ idempotentPreStartTime: () => Date.now() }),
20610
20826
  invoke: {
20611
20827
  src: "applyIdempotentSchemas",
20612
- input: ({ context }) => ({ input: context.input, targetDir: context.targetDir }),
20828
+ input: ({ context }) => ({
20829
+ input: context.input,
20830
+ targetDir: context.targetDir,
20831
+ pass: "pre"
20832
+ }),
20613
20833
  onDone: {
20614
20834
  target: "applyingPgSchemaDiff",
20615
20835
  actions: assign({
20616
20836
  idempotentPreApplied: ({ event }) => event.output.filesApplied,
20837
+ idempotentPreSkipped: ({ event }) => event.output.filesSkipped,
20617
20838
  rolePasswordsSet: ({ event }) => event.output.rolePasswordsSet,
20618
20839
  idempotentPreEndTime: () => Date.now()
20619
20840
  })
@@ -20672,11 +20893,16 @@ var dbApplyMachine = setup({
20672
20893
  entry: assign({ idempotentPostStartTime: () => Date.now() }),
20673
20894
  invoke: {
20674
20895
  src: "applyIdempotentSchemas",
20675
- input: ({ context }) => ({ input: context.input, targetDir: context.targetDir }),
20896
+ input: ({ context }) => ({
20897
+ input: context.input,
20898
+ targetDir: context.targetDir,
20899
+ pass: "post"
20900
+ }),
20676
20901
  onDone: {
20677
20902
  target: "applyingSeeds",
20678
20903
  actions: assign({
20679
20904
  idempotentPostApplied: ({ event }) => event.output.filesApplied,
20905
+ idempotentPostSkipped: ({ event }) => event.output.filesSkipped,
20680
20906
  idempotentPostEndTime: () => Date.now()
20681
20907
  })
20682
20908
  },
@@ -20734,9 +20960,11 @@ var dbApplyMachine = setup({
20734
20960
  retryAttempts: context.retryAttempts > 0 ? context.retryAttempts : void 0
20735
20961
  };
20736
20962
  const totalIdempotentApplied = context.idempotentPreApplied + context.idempotentPostApplied;
20963
+ const totalIdempotentSkipped = context.idempotentPreSkipped + context.idempotentPostSkipped;
20737
20964
  return {
20738
20965
  success: !context.error,
20739
20966
  idempotentSchemasApplied: totalIdempotentApplied,
20967
+ idempotentSchemasSkipped: totalIdempotentSkipped > 0 ? totalIdempotentSkipped : void 0,
20740
20968
  rolePasswordsSet: context.rolePasswordsSet > 0 ? context.rolePasswordsSet : void 0,
20741
20969
  schemaChangesApplied: context.schemaChangesApplied,
20742
20970
  hazards: context.hazards,
@@ -26393,7 +26621,7 @@ function getTablesWithTimestamps(dbUrl, schemas) {
26393
26621
  JOIN pg_attribute a1 ON c.oid = a1.attrelid AND a1.attname = 'updated_at'
26394
26622
  JOIN pg_attribute a2 ON c.oid = a2.attrelid AND a2.attname = 'created_at'
26395
26623
  WHERE n.nspname IN (${schemasFilter})
26396
- AND c.relkind = 'r'
26624
+ AND c.relkind IN ('r', 'p')
26397
26625
  ORDER BY n.nspname, c.relname;
26398
26626
  `;
26399
26627
  const conn = parsePostgresUrl(dbUrl);
@@ -31209,7 +31437,7 @@ init_esm_shims();
31209
31437
 
31210
31438
  // src/constants/versions.ts
31211
31439
  init_esm_shims();
31212
- var COMPATIBLE_TEMPLATES_VERSION = "0.5.56";
31440
+ var COMPATIBLE_TEMPLATES_VERSION = "0.5.60";
31213
31441
  var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
31214
31442
  var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
31215
31443
 
@@ -35800,13 +36028,13 @@ function printActionsNeeded(logger16, actions) {
35800
36028
  }
35801
36029
  function findRepoRoot3(startDir) {
35802
36030
  const { existsSync: existsSync53, readFileSync: readFileSync30 } = __require("fs");
35803
- const { join: join23, dirname: dirname5 } = __require("path");
36031
+ const { join: join24, dirname: dirname5 } = __require("path");
35804
36032
  let current = startDir;
35805
36033
  while (current !== dirname5(current)) {
35806
- if (existsSync53(join23(current, "turbo.json"))) {
36034
+ if (existsSync53(join24(current, "turbo.json"))) {
35807
36035
  return current;
35808
36036
  }
35809
- const pkgPath = join23(current, "package.json");
36037
+ const pkgPath = join24(current, "package.json");
35810
36038
  if (existsSync53(pkgPath)) {
35811
36039
  try {
35812
36040
  const pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
@@ -35876,8 +36104,8 @@ function generateReportOutput(output3, isJsonMode) {
35876
36104
  }
35877
36105
  function validateRunaRepo(repoRoot) {
35878
36106
  const { existsSync: existsSync53 } = __require("fs");
35879
- const { join: join23 } = __require("path");
35880
- const templateDir = join23(repoRoot, "packages/runa-templates/templates");
36107
+ const { join: join24 } = __require("path");
36108
+ const templateDir = join24(repoRoot, "packages/runa-templates/templates");
35881
36109
  if (!existsSync53(templateDir)) {
35882
36110
  throw new CLIError("template-check is a runa-repo only command", "NOT_RUNA_REPO", [
35883
36111
  "This command compares runa-repo with pj-repo templates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.5.58",
3
+ "version": "0.5.60",
4
4
  "private": false,
5
5
  "description": "AI-powered DevOps CLI",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "typescript": "5.9.3",
54
54
  "xstate": "5.28.0",
55
55
  "zod": "4.3.6",
56
- "@runa-ai/runa": "0.5.58",
56
+ "@runa-ai/runa": "0.5.60",
57
57
  "@runa-ai/runa-xstate-test-plugin": "0.5.58"
58
58
  },
59
59
  "engines": {