@runa-ai/runa-cli 0.5.43 → 0.5.44

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,5 +6,6 @@
6
6
  * - Safety sequence: stack → risks → snapshot → apply → audit → notify
7
7
  */
8
8
  import { Command } from 'commander';
9
- export declare const ciProdApplyCommand: Command;
9
+ declare const ciProdApplyCommand: Command;
10
+ export { ciProdApplyCommand };
10
11
  //# sourceMappingURL=ci-prod-apply.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ci-prod-apply.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-prod-apply.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmUpC,eAAO,MAAM,kBAAkB,SA0G3B,CAAC"}
1
+ {"version":3,"file":"ci-prod-apply.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-prod-apply.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsHpC,QAAA,MAAM,kBAAkB,SA0GpB,CAAC;AAEL,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for ci prod-apply CLI options.
4
+ * Derived from commander option definitions in ci-prod-apply.ts.
5
+ */
6
+ export declare const CiProdApplyActionOptionsSchema: z.ZodObject<{
7
+ mode: z.ZodOptional<z.ZodEnum<{
8
+ local: "local";
9
+ "github-actions": "github-actions";
10
+ }>>;
11
+ output: z.ZodOptional<z.ZodEnum<{
12
+ json: "json";
13
+ human: "human";
14
+ }>>;
15
+ config: z.ZodOptional<z.ZodString>;
16
+ autoApprove: z.ZodOptional<z.ZodBoolean>;
17
+ skipNotify: z.ZodOptional<z.ZodBoolean>;
18
+ skipGithubLabel: z.ZodOptional<z.ZodBoolean>;
19
+ skipRisks: z.ZodOptional<z.ZodBoolean>;
20
+ initialDeployment: z.ZodOptional<z.ZodBoolean>;
21
+ allowDataLoss: z.ZodOptional<z.ZodBoolean>;
22
+ confirmAuthzUpdate: z.ZodOptional<z.ZodBoolean>;
23
+ maxLockWaitMs: z.ZodOptional<z.ZodNumber>;
24
+ }, z.core.$strict>;
25
+ export type CiProdApplyActionOptions = z.infer<typeof CiProdApplyActionOptionsSchema>;
26
+ //# sourceMappingURL=ci-prod-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-prod-types.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-prod-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;kBAchC,CAAC;AAEZ,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type createCLILogger } from '@runa-ai/runa';
2
+ import type { CiSummary } from '../utils/ci-summary.js';
3
+ import type { ProdApplyInputs } from './ci-prod-utils.js';
4
+ import type { CiProdApplyActionOptions } from './ci-prod-types.js';
5
+ export declare class CiProdApplyWorkflow {
6
+ private readonly steps;
7
+ private readonly ctx;
8
+ constructor(deps: {
9
+ options: CiProdApplyActionOptions;
10
+ logger: ReturnType<typeof createCLILogger>;
11
+ summary: CiSummary;
12
+ repoRoot: string;
13
+ tmpDir: string;
14
+ inputs: ProdApplyInputs;
15
+ });
16
+ run(): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=ci-prod-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-prod-workflow.d.ts","sourceRoot":"","sources":["../../../../src/commands/ci/commands/ci-prod-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAaxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAyBnE,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;gBAErC,IAAI,EAAE;QAChB,OAAO,EAAE,wBAAwB,CAAC;QAClC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;QAC3C,OAAO,EAAE,SAAS,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,eAAe,CAAC;KACzB;IAkMK,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAoB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/commands/db/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,aAAa,CA6DvE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;uBAKd,MAAM;;;;;;;;;;;;;;yBAuBF,MAAM;CA+ChB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;CAYhB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CA0BlD,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,cAAc;4CACM,MAAM,gBAAgB,MAAM;;;;;;;CAQnD,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ,CAOA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/commands/db/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,aAAa,CA6DvE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;uBASd,MAAM;;;;;;;;;;;;;;yBAuBF,MAAM;CA+ChB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;CAYhB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CA0BlD,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,cAAc;4CACM,MAAM,gBAAgB,MAAM;;;;;;;CAQnD,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ,CAOA"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-layer.d.ts","sourceRoot":"","sources":["../../../../src/commands/test/commands/test-layer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6KpC,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AAmDvD,eAAO,MAAM,gBAAgB,SAI5B,CAAC"}
1
+ {"version":3,"file":"test-layer.d.ts","sourceRoot":"","sources":["../../../../src/commands/test/commands/test-layer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmNpC,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AAmDvD,eAAO,MAAM,gBAAgB,SAI5B,CAAC"}
@@ -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.43";
23
+ export declare const COMPATIBLE_TEMPLATES_VERSION = "0.5.44";
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
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
6
6
  import { execSync, spawnSync, execFileSync, exec, spawn } from 'child_process';
7
7
  import * as fs5 from 'fs';
8
8
  import fs5__default, { existsSync, readFileSync, readdirSync, mkdtempSync, writeFileSync, mkdirSync, copyFileSync, createWriteStream, statSync, rmSync, realpathSync, promises, lstatSync, accessSync, constants, chmodSync, unlinkSync } from 'fs';
9
- import { createCLILogger, cacheClear, CacheClearOutputSchema, CLIError, cachePrune, CachePruneOutputSchema, cacheStats, CacheStatsOutputSchema, cacheList, CacheListOutputSchema, cacheInvalidate, CacheInvalidateOutputSchema, syncFromProduction, 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, loadRunaConfigOrThrow, recordSchemaAudit, RecordSchemaAuditOutputSchema, createBackup, CreateBackupOutputSchema, listBackups, ListBackupsOutputSchema, getBackupMetadata, restoreBackup, RestoreBackupOutputSchema, deleteBackup, DeleteBackupOutputSchema, detectSchemaNames, SUPABASE_SYSTEM_SCHEMAS, dbSeedApply, writeDbSeedStepSummary, DbSeedApplyOutputSchema, emitDbSeedFailureCapsule, syncEnvironment, EnvSyncOutputSchema, detectDatabasePackage, findProjectRoot as findProjectRoot$1, TelemetryEnableOutputSchema, TelemetryDisableOutputSchema, TelemetryStatusOutputSchema, workflowNotify, DevOpsSyncOutputSchema, WorkflowSyncOutputSchema, formatCLIError, DATABASE_PACKAGE_CANDIDATES, getStatusIcon as getStatusIcon$1, findWorkspaceRoot as findWorkspaceRoot$1, checkExtensionConfig, UpgradeTransaction, readRunaVersion, syncTemplates, SyncOutputSchema, ErrorEnvelopeSchema, preCheckSync, findConflictFiles, TestUnitGenOutputSchema, TestE2EGenerateOutputSchema, TestSecurityGenOutputSchema, TestApiGenOutputSchema, TestComponentGenOutputSchema } from '@runa-ai/runa';
9
+ import { createCLILogger, cacheClear, CacheClearOutputSchema, CLIError, cachePrune, CachePruneOutputSchema, cacheStats, CacheStatsOutputSchema, cacheList, CacheListOutputSchema, cacheInvalidate, CacheInvalidateOutputSchema, syncFromProduction, 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, loadRunaConfigOrThrow, recordSchemaAudit, RecordSchemaAuditOutputSchema, createBackup, CreateBackupOutputSchema, listBackups, ListBackupsOutputSchema, getBackupMetadata, restoreBackup, RestoreBackupOutputSchema, deleteBackup, DeleteBackupOutputSchema, detectSchemaNames, SUPABASE_SYSTEM_SCHEMAS, 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, 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';
11
11
  import fs9, { mkdir, writeFile, appendFile, readFile, rm, stat, realpath, cp, readdir, lstat } from 'fs/promises';
12
12
  import { promisify } from 'util';
@@ -147,6 +147,10 @@ var init_constants = __esm({
147
147
  DATABASE_DEFAULTS = {
148
148
  // Supabase Local defaults (for development)
149
149
  // Note: PORT is detected from running Supabase instance
150
+ // AI HINT: Local Supabase Default Credentials (Non-Production)
151
+ // postgres:postgres is the standard default for `supabase start`.
152
+ // This is a documented public value, not a secret.
153
+ // Production uses environment variables via .env.production.
150
154
  SUPABASE_LOCAL: {
151
155
  HOST: "127.0.0.1",
152
156
  get PORT() {
@@ -925,7 +929,7 @@ var CLI_VERSION, HAS_ADMIN_COMMAND;
925
929
  var init_version = __esm({
926
930
  "src/version.ts"() {
927
931
  init_esm_shims();
928
- CLI_VERSION = "0.5.43";
932
+ CLI_VERSION = "0.5.44";
929
933
  HAS_ADMIN_COMMAND = false;
930
934
  }
931
935
  });
@@ -9194,158 +9198,176 @@ async function writeCiSummary(params) {
9194
9198
  return filePath;
9195
9199
  }
9196
9200
 
9197
- // src/commands/ci/utils/timestamp-invariants.ts
9201
+ // src/commands/ci/commands/ci-prod-github.ts
9198
9202
  init_esm_shims();
9199
- async function resolveHostaddrV4(host) {
9200
- const ipVersion = isIP(host);
9201
- if (ipVersion === 4) return host;
9202
- if (ipVersion === 6) return void 0;
9203
+
9204
+ // src/commands/ci/utils/github-api.ts
9205
+ init_esm_shims();
9206
+ var RepoContextSchema = z.object({
9207
+ owner: z.string().min(1),
9208
+ repo: z.string().min(1)
9209
+ }).strict();
9210
+ function resolveGithubToken() {
9211
+ const token = process.env.GITHUB_TOKEN?.trim();
9212
+ if (token && token.length > 0) return token;
9213
+ throw new CLIError(
9214
+ "GITHUB_TOKEN is not set (required for GitHub API operations).",
9215
+ "GITHUB_TOKEN_MISSING",
9216
+ [
9217
+ "Ensure workflow permissions include issues: write / pull-requests: write",
9218
+ "Set GITHUB_TOKEN via env"
9219
+ ],
9220
+ void 0,
9221
+ 10
9222
+ );
9223
+ }
9224
+ function resolveRepoContextFromEnv() {
9225
+ const full = process.env.GITHUB_REPOSITORY?.trim() ?? "";
9226
+ const [owner, repo] = full.split("/");
9227
+ return RepoContextSchema.parse({ owner, repo });
9228
+ }
9229
+ var GITHUB_API_TIMEOUT_MS = 3e4;
9230
+ async function githubRequest(params) {
9231
+ const token = resolveGithubToken();
9232
+ const url = new URL(`${GITHUB_API.base}${params.path}`);
9233
+ for (const [k, v] of Object.entries(params.query ?? {})) {
9234
+ if (v === void 0) continue;
9235
+ url.searchParams.set(k, String(v));
9236
+ }
9237
+ const startTime = Date.now();
9238
+ console.log(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
9239
+ const controller = new AbortController();
9240
+ const timeoutId = setTimeout(() => controller.abort(), GITHUB_API_TIMEOUT_MS);
9203
9241
  try {
9204
- const out = execFileSync("getent", ["ahostsv4", host], {
9205
- stdio: ["ignore", "pipe", "pipe"],
9206
- shell: false,
9207
- encoding: "utf-8",
9208
- timeout: 5e3
9242
+ const res = await fetch(url.toString(), {
9243
+ method: params.method,
9244
+ headers: {
9245
+ Authorization: `Bearer ${token}`,
9246
+ Accept: "application/vnd.github+json",
9247
+ "X-GitHub-Api-Version": "2022-11-28",
9248
+ "Content-Type": "application/json"
9249
+ },
9250
+ body: params.body === void 0 ? void 0 : JSON.stringify(params.body),
9251
+ signal: controller.signal
9209
9252
  });
9210
- const firstLine = out.split("\n").find((l) => l.trim().length > 0);
9211
- if (!firstLine) return void 0;
9212
- const ip = firstLine.trim().split(/\s+/u)[0] ?? "";
9213
- return isIP(ip) === 4 ? ip : void 0;
9214
- } catch {
9215
- try {
9216
- const ips = await Promise.race([
9217
- resolve4(host),
9218
- new Promise(
9219
- (_, reject) => setTimeout(() => reject(new Error("resolve4 timeout")), 2e3)
9220
- )
9221
- ]);
9222
- const ip = ips[0];
9223
- return ip && isIP(ip) === 4 ? ip : void 0;
9224
- } catch {
9225
- return void 0;
9253
+ clearTimeout(timeoutId);
9254
+ const duration = Date.now() - startTime;
9255
+ if (!res.ok) {
9256
+ const text = await res.text().catch(() => "");
9257
+ console.error(
9258
+ `[DEBUG] GitHub API: ${params.method} ${params.path} failed (${res.status}) after ${duration}ms`
9259
+ );
9260
+ throw new CLIError(
9261
+ `GitHub API request failed: ${params.method} ${params.path} (${res.status})`,
9262
+ "GITHUB_API_FAILED",
9263
+ ["Check workflow permissions", "Check GITHUB_TOKEN availability"],
9264
+ new Error(text),
9265
+ 20
9266
+ );
9226
9267
  }
9268
+ console.log(
9269
+ `[DEBUG] GitHub API: ${params.method} ${params.path} succeeded (${res.status}) in ${duration}ms`
9270
+ );
9271
+ if (res.status === 204) return null;
9272
+ const json = await res.json().catch(() => null);
9273
+ return json;
9274
+ } catch (error) {
9275
+ clearTimeout(timeoutId);
9276
+ const duration = Date.now() - startTime;
9277
+ if (error instanceof Error && error.name === "AbortError") {
9278
+ console.error(
9279
+ `[DEBUG] GitHub API: ${params.method} ${params.path} timed out after ${duration}ms`
9280
+ );
9281
+ throw new CLIError(
9282
+ `GitHub API request timed out: ${params.method} ${params.path}`,
9283
+ "GITHUB_API_TIMEOUT",
9284
+ ["Request exceeded 30 second timeout", "GitHub API may be slow or unreachable"],
9285
+ error,
9286
+ 21
9287
+ );
9288
+ }
9289
+ console.error(
9290
+ `[DEBUG] GitHub API: ${params.method} ${params.path} error after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`
9291
+ );
9292
+ throw error;
9227
9293
  }
9228
9294
  }
9229
- async function buildPsqlParams(databaseUrl) {
9230
- const url = new URL(databaseUrl);
9231
- const username = decodeURIComponent(url.username);
9232
- const password = decodeURIComponent(url.password);
9233
- const host = url.hostname;
9234
- const port = url.port || "5432";
9235
- const database = url.pathname.replace(/^\//u, "");
9236
- const hostaddrV4 = process.env.CI === "true" ? await resolveHostaddrV4(host) : void 0;
9237
- const sslmode = url.searchParams.get("sslmode") ?? void 0;
9238
- const env2 = {
9239
- ...process.env,
9240
- PGPASSWORD: password,
9241
- ...sslmode ? { PGSSLMODE: sslmode } : {},
9242
- ...hostaddrV4 ? { PGHOSTADDR: hostaddrV4 } : {}
9243
- };
9244
- const hostForPsql = hostaddrV4 ?? host;
9245
- const args = ["-h", hostForPsql, "-p", port, "-U", username, "-d", database];
9246
- return { args, env: env2 };
9295
+ z.object({
9296
+ filename: z.string().min(1)
9297
+ }).passthrough();
9298
+ var IssueCommentSchema = z.object({
9299
+ id: z.number().int(),
9300
+ body: z.string().nullable()
9301
+ }).passthrough();
9302
+ async function upsertIssueComment(params) {
9303
+ let page = 1;
9304
+ let existingId = null;
9305
+ while (true) {
9306
+ const raw = await githubRequest({
9307
+ method: "GET",
9308
+ path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/comments`,
9309
+ query: { per_page: 100, page }
9310
+ });
9311
+ const arr = z.array(IssueCommentSchema).parse(raw);
9312
+ for (const c of arr) {
9313
+ const body = c.body ?? "";
9314
+ if (body.includes(params.marker)) {
9315
+ existingId = c.id;
9316
+ break;
9317
+ }
9318
+ }
9319
+ if (existingId) break;
9320
+ if (arr.length < 100) break;
9321
+ page += 1;
9322
+ if (page > 20) break;
9323
+ }
9324
+ if (existingId) {
9325
+ await githubRequest({
9326
+ method: "PATCH",
9327
+ path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/comments/${existingId}`,
9328
+ body: { body: params.body }
9329
+ });
9330
+ return;
9331
+ }
9332
+ await githubRequest({
9333
+ method: "POST",
9334
+ path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/comments`,
9335
+ body: { body: params.body }
9336
+ });
9247
9337
  }
9248
- async function repairTimestampInvariants(params) {
9249
- const sql = `
9250
- DO $$
9251
- DECLARE
9252
- v_total integer := 0;
9253
- v_count integer := 0;
9254
- v_repaired integer := 0;
9255
- v_pairs integer := 0;
9256
- r record;
9257
- BEGIN
9258
- -- Generic repair: derive (lhs >= rhs) invariants from CHECK constraints.
9259
- -- This avoids hardcoding schema/table/column names and works for pj-repos.
9260
- FOR r IN
9261
- WITH checks AS (
9262
- SELECT
9263
- n.nspname AS schema_name,
9264
- c.relname AS table_name,
9265
- pg_get_constraintdef(con.oid) AS constraint_def,
9266
- (regexp_matches(
9267
- pg_get_constraintdef(con.oid),
9268
- 'CHECK\\s*\\(\\s*\\(\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*>=\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*\\)\\s*\\)\\s*\\)'
9269
- ))[1] AS lhs_col,
9270
- (regexp_matches(
9271
- pg_get_constraintdef(con.oid),
9272
- 'CHECK\\s*\\(\\s*\\(\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*>=\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*\\)\\s*\\)\\s*\\)'
9273
- ))[2] AS rhs_col
9274
- FROM pg_constraint con
9275
- JOIN pg_class c ON c.oid = con.conrelid
9276
- JOIN pg_namespace n ON n.oid = c.relnamespace
9277
- WHERE con.contype = 'c'
9278
- AND pg_get_constraintdef(con.oid) ~ '>='
9279
- )
9280
- SELECT
9281
- schema_name,
9282
- table_name,
9283
- lhs_col,
9284
- rhs_col
9285
- FROM checks
9286
- WHERE lhs_col IS NOT NULL
9287
- AND rhs_col IS NOT NULL
9288
- -- Only apply to temporal column pairs (avoid touching unrelated invariants).
9289
- AND EXISTS (
9290
- SELECT 1
9291
- FROM pg_attribute a1
9292
- JOIN pg_type t1 ON t1.oid = a1.atttypid
9293
- WHERE a1.attrelid = to_regclass(format('%I.%I', schema_name, table_name))
9294
- AND a1.attname = lhs_col
9295
- AND t1.typname IN ('timestamptz', 'timestamp', 'date')
9296
- )
9297
- AND EXISTS (
9298
- SELECT 1
9299
- FROM pg_attribute a2
9300
- JOIN pg_type t2 ON t2.oid = a2.atttypid
9301
- WHERE a2.attrelid = to_regclass(format('%I.%I', schema_name, table_name))
9302
- AND a2.attname = rhs_col
9303
- AND t2.typname IN ('timestamptz', 'timestamp', 'date')
9304
- )
9305
- AND has_table_privilege(current_user, format('%I.%I', schema_name, table_name), 'UPDATE')
9306
- LOOP
9307
- v_pairs := v_pairs + 1;
9308
-
9309
- -- Repair violations (best-effort, idempotent).
9310
- EXECUTE format(
9311
- 'UPDATE %I.%I SET %I = %I WHERE %I < %I',
9312
- r.schema_name,
9313
- r.table_name,
9314
- r.lhs_col,
9315
- r.rhs_col,
9316
- r.lhs_col,
9317
- r.rhs_col
9318
- );
9319
- GET DIAGNOSTICS v_repaired = ROW_COUNT;
9320
-
9321
- -- Verify remaining violations.
9322
- EXECUTE format(
9323
- 'SELECT COUNT(*) FROM %I.%I WHERE %I < %I',
9324
- r.schema_name,
9325
- r.table_name,
9326
- r.lhs_col,
9327
- r.rhs_col
9328
- ) INTO v_count;
9329
- v_total := v_total + COALESCE(v_count, 0);
9330
- END LOOP;
9331
-
9332
- RAISE NOTICE 'Repaired check-based invariants: pairs=% , remaining_violations=%', v_pairs, v_total;
9333
- IF v_total <> 0 THEN
9334
- RAISE EXCEPTION 'Timestamp invariants still violated after repair: % rows', v_total;
9335
- END IF;
9336
- END
9337
- $$;
9338
- `.trim();
9339
- const psql = await buildPsqlParams(params.databaseUrl);
9340
- await runLogged({
9341
- cwd: params.repoRoot,
9342
- env: psql.env,
9343
- label: `repair timestamps (${params.labelPrefix})`,
9344
- command: "psql",
9345
- args: [...psql.args, "-v", "ON_ERROR_STOP=1", "-c", sql],
9346
- logFile: path10__default.join(params.tmpDir, `timestamp-repair-${params.labelPrefix}.log`)
9338
+ async function addIssueLabels(params) {
9339
+ await githubRequest({
9340
+ method: "POST",
9341
+ path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/labels`,
9342
+ body: { labels: params.labels }
9347
9343
  });
9348
9344
  }
9345
+ async function findPullRequestNumberForCommit(params) {
9346
+ const token = process.env.GITHUB_TOKEN;
9347
+ if (!token) return null;
9348
+ const res = await fetch(GITHUB_API.commitPulls(params.repo.owner, params.repo.repo, params.sha), {
9349
+ headers: {
9350
+ Accept: "application/vnd.github+json",
9351
+ Authorization: `Bearer ${token}`,
9352
+ "X-GitHub-Api-Version": "2022-11-28"
9353
+ }
9354
+ });
9355
+ if (!res.ok) return null;
9356
+ const json = await res.json();
9357
+ const arr = z.array(
9358
+ z.object({
9359
+ number: z.number().int().positive(),
9360
+ state: z.string().optional()
9361
+ }).passthrough()
9362
+ ).parse(json);
9363
+ const open = arr.find((p) => p.state === "open");
9364
+ return (open ?? arr[0] ?? null)?.number ?? null;
9365
+ }
9366
+ function buildWorkflowRunUrl() {
9367
+ const repository = process.env.GITHUB_REPOSITORY || "";
9368
+ const runId = process.env.GITHUB_RUN_ID || "";
9369
+ return `${GITHUB_API.serverUrl}/${repository}/actions/runs/${runId}`;
9370
+ }
9349
9371
 
9350
9372
  // src/commands/ci/commands/ci-prod-db-operations.ts
9351
9373
  init_esm_shims();
@@ -9798,177 +9820,6 @@ async function notifyDeployment(repoRoot, tmpDir, params) {
9798
9820
  });
9799
9821
  }
9800
9822
 
9801
- // src/commands/ci/commands/ci-prod-github.ts
9802
- init_esm_shims();
9803
-
9804
- // src/commands/ci/utils/github-api.ts
9805
- init_esm_shims();
9806
- var RepoContextSchema = z.object({
9807
- owner: z.string().min(1),
9808
- repo: z.string().min(1)
9809
- }).strict();
9810
- function resolveGithubToken() {
9811
- const token = process.env.GITHUB_TOKEN?.trim();
9812
- if (token && token.length > 0) return token;
9813
- throw new CLIError(
9814
- "GITHUB_TOKEN is not set (required for GitHub API operations).",
9815
- "GITHUB_TOKEN_MISSING",
9816
- [
9817
- "Ensure workflow permissions include issues: write / pull-requests: write",
9818
- "Set GITHUB_TOKEN via env"
9819
- ],
9820
- void 0,
9821
- 10
9822
- );
9823
- }
9824
- function resolveRepoContextFromEnv() {
9825
- const full = process.env.GITHUB_REPOSITORY?.trim() ?? "";
9826
- const [owner, repo] = full.split("/");
9827
- return RepoContextSchema.parse({ owner, repo });
9828
- }
9829
- var GITHUB_API_TIMEOUT_MS = 3e4;
9830
- async function githubRequest(params) {
9831
- const token = resolveGithubToken();
9832
- const url = new URL(`${GITHUB_API.base}${params.path}`);
9833
- for (const [k, v] of Object.entries(params.query ?? {})) {
9834
- if (v === void 0) continue;
9835
- url.searchParams.set(k, String(v));
9836
- }
9837
- const startTime = Date.now();
9838
- console.log(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
9839
- const controller = new AbortController();
9840
- const timeoutId = setTimeout(() => controller.abort(), GITHUB_API_TIMEOUT_MS);
9841
- try {
9842
- const res = await fetch(url.toString(), {
9843
- method: params.method,
9844
- headers: {
9845
- Authorization: `Bearer ${token}`,
9846
- Accept: "application/vnd.github+json",
9847
- "X-GitHub-Api-Version": "2022-11-28",
9848
- "Content-Type": "application/json"
9849
- },
9850
- body: params.body === void 0 ? void 0 : JSON.stringify(params.body),
9851
- signal: controller.signal
9852
- });
9853
- clearTimeout(timeoutId);
9854
- const duration = Date.now() - startTime;
9855
- if (!res.ok) {
9856
- const text = await res.text().catch(() => "");
9857
- console.error(
9858
- `[DEBUG] GitHub API: ${params.method} ${params.path} failed (${res.status}) after ${duration}ms`
9859
- );
9860
- throw new CLIError(
9861
- `GitHub API request failed: ${params.method} ${params.path} (${res.status})`,
9862
- "GITHUB_API_FAILED",
9863
- ["Check workflow permissions", "Check GITHUB_TOKEN availability"],
9864
- new Error(text),
9865
- 20
9866
- );
9867
- }
9868
- console.log(
9869
- `[DEBUG] GitHub API: ${params.method} ${params.path} succeeded (${res.status}) in ${duration}ms`
9870
- );
9871
- if (res.status === 204) return null;
9872
- const json = await res.json().catch(() => null);
9873
- return json;
9874
- } catch (error) {
9875
- clearTimeout(timeoutId);
9876
- const duration = Date.now() - startTime;
9877
- if (error instanceof Error && error.name === "AbortError") {
9878
- console.error(
9879
- `[DEBUG] GitHub API: ${params.method} ${params.path} timed out after ${duration}ms`
9880
- );
9881
- throw new CLIError(
9882
- `GitHub API request timed out: ${params.method} ${params.path}`,
9883
- "GITHUB_API_TIMEOUT",
9884
- ["Request exceeded 30 second timeout", "GitHub API may be slow or unreachable"],
9885
- error,
9886
- 21
9887
- );
9888
- }
9889
- console.error(
9890
- `[DEBUG] GitHub API: ${params.method} ${params.path} error after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`
9891
- );
9892
- throw error;
9893
- }
9894
- }
9895
- z.object({
9896
- filename: z.string().min(1)
9897
- }).passthrough();
9898
- var IssueCommentSchema = z.object({
9899
- id: z.number().int(),
9900
- body: z.string().nullable()
9901
- }).passthrough();
9902
- async function upsertIssueComment(params) {
9903
- let page = 1;
9904
- let existingId = null;
9905
- while (true) {
9906
- const raw = await githubRequest({
9907
- method: "GET",
9908
- path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/comments`,
9909
- query: { per_page: 100, page }
9910
- });
9911
- const arr = z.array(IssueCommentSchema).parse(raw);
9912
- for (const c of arr) {
9913
- const body = c.body ?? "";
9914
- if (body.includes(params.marker)) {
9915
- existingId = c.id;
9916
- break;
9917
- }
9918
- }
9919
- if (existingId) break;
9920
- if (arr.length < 100) break;
9921
- page += 1;
9922
- if (page > 20) break;
9923
- }
9924
- if (existingId) {
9925
- await githubRequest({
9926
- method: "PATCH",
9927
- path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/comments/${existingId}`,
9928
- body: { body: params.body }
9929
- });
9930
- return;
9931
- }
9932
- await githubRequest({
9933
- method: "POST",
9934
- path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/comments`,
9935
- body: { body: params.body }
9936
- });
9937
- }
9938
- async function addIssueLabels(params) {
9939
- await githubRequest({
9940
- method: "POST",
9941
- path: `/repos/${params.repo.owner}/${params.repo.repo}/issues/${params.issueNumber}/labels`,
9942
- body: { labels: params.labels }
9943
- });
9944
- }
9945
- async function findPullRequestNumberForCommit(params) {
9946
- const token = process.env.GITHUB_TOKEN;
9947
- if (!token) return null;
9948
- const res = await fetch(GITHUB_API.commitPulls(params.repo.owner, params.repo.repo, params.sha), {
9949
- headers: {
9950
- Accept: "application/vnd.github+json",
9951
- Authorization: `Bearer ${token}`,
9952
- "X-GitHub-Api-Version": "2022-11-28"
9953
- }
9954
- });
9955
- if (!res.ok) return null;
9956
- const json = await res.json();
9957
- const arr = z.array(
9958
- z.object({
9959
- number: z.number().int().positive(),
9960
- state: z.string().optional()
9961
- }).passthrough()
9962
- ).parse(json);
9963
- const open = arr.find((p) => p.state === "open");
9964
- return (open ?? arr[0] ?? null)?.number ?? null;
9965
- }
9966
- function buildWorkflowRunUrl() {
9967
- const repository = process.env.GITHUB_REPOSITORY || "";
9968
- const runId = process.env.GITHUB_RUN_ID || "";
9969
- return `${GITHUB_API.serverUrl}/${repository}/actions/runs/${runId}`;
9970
- }
9971
-
9972
9823
  // src/commands/ci/commands/ci-prod-github.ts
9973
9824
  async function maybeNotifyExternal(params) {
9974
9825
  if (params.skipNotify) return;
@@ -10175,6 +10026,362 @@ function buildCiProdApplyStepSummaryMarkdown(params) {
10175
10026
  return lines.join("\n");
10176
10027
  }
10177
10028
 
10029
+ // src/commands/ci/commands/ci-prod-workflow.ts
10030
+ init_esm_shims();
10031
+
10032
+ // src/commands/ci/utils/timestamp-invariants.ts
10033
+ init_esm_shims();
10034
+ async function resolveHostaddrV4(host) {
10035
+ const ipVersion = isIP(host);
10036
+ if (ipVersion === 4) return host;
10037
+ if (ipVersion === 6) return void 0;
10038
+ try {
10039
+ const out = execFileSync("getent", ["ahostsv4", host], {
10040
+ stdio: ["ignore", "pipe", "pipe"],
10041
+ shell: false,
10042
+ encoding: "utf-8",
10043
+ timeout: 5e3
10044
+ });
10045
+ const firstLine = out.split("\n").find((l) => l.trim().length > 0);
10046
+ if (!firstLine) return void 0;
10047
+ const ip = firstLine.trim().split(/\s+/u)[0] ?? "";
10048
+ return isIP(ip) === 4 ? ip : void 0;
10049
+ } catch {
10050
+ try {
10051
+ const ips = await Promise.race([
10052
+ resolve4(host),
10053
+ new Promise(
10054
+ (_, reject) => setTimeout(() => reject(new Error("resolve4 timeout")), 2e3)
10055
+ )
10056
+ ]);
10057
+ const ip = ips[0];
10058
+ return ip && isIP(ip) === 4 ? ip : void 0;
10059
+ } catch {
10060
+ return void 0;
10061
+ }
10062
+ }
10063
+ }
10064
+ async function buildPsqlParams(databaseUrl) {
10065
+ const url = new URL(databaseUrl);
10066
+ const username = decodeURIComponent(url.username);
10067
+ const password = decodeURIComponent(url.password);
10068
+ const host = url.hostname;
10069
+ const port = url.port || "5432";
10070
+ const database = url.pathname.replace(/^\//u, "");
10071
+ const hostaddrV4 = process.env.CI === "true" ? await resolveHostaddrV4(host) : void 0;
10072
+ const sslmode = url.searchParams.get("sslmode") ?? void 0;
10073
+ const env2 = {
10074
+ ...process.env,
10075
+ PGPASSWORD: password,
10076
+ ...sslmode ? { PGSSLMODE: sslmode } : {},
10077
+ ...hostaddrV4 ? { PGHOSTADDR: hostaddrV4 } : {}
10078
+ };
10079
+ const hostForPsql = hostaddrV4 ?? host;
10080
+ const args = ["-h", hostForPsql, "-p", port, "-U", username, "-d", database];
10081
+ return { args, env: env2 };
10082
+ }
10083
+ async function repairTimestampInvariants(params) {
10084
+ const sql = `
10085
+ DO $$
10086
+ DECLARE
10087
+ v_total integer := 0;
10088
+ v_count integer := 0;
10089
+ v_repaired integer := 0;
10090
+ v_pairs integer := 0;
10091
+ r record;
10092
+ BEGIN
10093
+ -- Generic repair: derive (lhs >= rhs) invariants from CHECK constraints.
10094
+ -- This avoids hardcoding schema/table/column names and works for pj-repos.
10095
+ FOR r IN
10096
+ WITH checks AS (
10097
+ SELECT
10098
+ n.nspname AS schema_name,
10099
+ c.relname AS table_name,
10100
+ pg_get_constraintdef(con.oid) AS constraint_def,
10101
+ (regexp_matches(
10102
+ pg_get_constraintdef(con.oid),
10103
+ 'CHECK\\s*\\(\\s*\\(\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*>=\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*\\)\\s*\\)\\s*\\)'
10104
+ ))[1] AS lhs_col,
10105
+ (regexp_matches(
10106
+ pg_get_constraintdef(con.oid),
10107
+ 'CHECK\\s*\\(\\s*\\(\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*>=\\s*"?([a-zA-Z_][a-zA-Z0-9_]*)"?\\s*\\)\\s*\\)\\s*\\)'
10108
+ ))[2] AS rhs_col
10109
+ FROM pg_constraint con
10110
+ JOIN pg_class c ON c.oid = con.conrelid
10111
+ JOIN pg_namespace n ON n.oid = c.relnamespace
10112
+ WHERE con.contype = 'c'
10113
+ AND pg_get_constraintdef(con.oid) ~ '>='
10114
+ )
10115
+ SELECT
10116
+ schema_name,
10117
+ table_name,
10118
+ lhs_col,
10119
+ rhs_col
10120
+ FROM checks
10121
+ WHERE lhs_col IS NOT NULL
10122
+ AND rhs_col IS NOT NULL
10123
+ -- Only apply to temporal column pairs (avoid touching unrelated invariants).
10124
+ AND EXISTS (
10125
+ SELECT 1
10126
+ FROM pg_attribute a1
10127
+ JOIN pg_type t1 ON t1.oid = a1.atttypid
10128
+ WHERE a1.attrelid = to_regclass(format('%I.%I', schema_name, table_name))
10129
+ AND a1.attname = lhs_col
10130
+ AND t1.typname IN ('timestamptz', 'timestamp', 'date')
10131
+ )
10132
+ AND EXISTS (
10133
+ SELECT 1
10134
+ FROM pg_attribute a2
10135
+ JOIN pg_type t2 ON t2.oid = a2.atttypid
10136
+ WHERE a2.attrelid = to_regclass(format('%I.%I', schema_name, table_name))
10137
+ AND a2.attname = rhs_col
10138
+ AND t2.typname IN ('timestamptz', 'timestamp', 'date')
10139
+ )
10140
+ AND has_table_privilege(current_user, format('%I.%I', schema_name, table_name), 'UPDATE')
10141
+ LOOP
10142
+ v_pairs := v_pairs + 1;
10143
+
10144
+ -- Repair violations (best-effort, idempotent).
10145
+ EXECUTE format(
10146
+ 'UPDATE %I.%I SET %I = %I WHERE %I < %I',
10147
+ r.schema_name,
10148
+ r.table_name,
10149
+ r.lhs_col,
10150
+ r.rhs_col,
10151
+ r.lhs_col,
10152
+ r.rhs_col
10153
+ );
10154
+ GET DIAGNOSTICS v_repaired = ROW_COUNT;
10155
+
10156
+ -- Verify remaining violations.
10157
+ EXECUTE format(
10158
+ 'SELECT COUNT(*) FROM %I.%I WHERE %I < %I',
10159
+ r.schema_name,
10160
+ r.table_name,
10161
+ r.lhs_col,
10162
+ r.rhs_col
10163
+ ) INTO v_count;
10164
+ v_total := v_total + COALESCE(v_count, 0);
10165
+ END LOOP;
10166
+
10167
+ RAISE NOTICE 'Repaired check-based invariants: pairs=% , remaining_violations=%', v_pairs, v_total;
10168
+ IF v_total <> 0 THEN
10169
+ RAISE EXCEPTION 'Timestamp invariants still violated after repair: % rows', v_total;
10170
+ END IF;
10171
+ END
10172
+ $$;
10173
+ `.trim();
10174
+ const psql = await buildPsqlParams(params.databaseUrl);
10175
+ await runLogged({
10176
+ cwd: params.repoRoot,
10177
+ env: psql.env,
10178
+ label: `repair timestamps (${params.labelPrefix})`,
10179
+ command: "psql",
10180
+ args: [...psql.args, "-v", "ON_ERROR_STOP=1", "-c", sql],
10181
+ logFile: path10__default.join(params.tmpDir, `timestamp-repair-${params.labelPrefix}.log`)
10182
+ });
10183
+ }
10184
+
10185
+ // src/commands/ci/commands/ci-prod-workflow.ts
10186
+ var CiProdApplyWorkflow = class {
10187
+ steps;
10188
+ ctx;
10189
+ constructor(deps) {
10190
+ this.ctx = {
10191
+ options: deps.options,
10192
+ logger: deps.logger,
10193
+ summary: deps.summary,
10194
+ repoRoot: deps.repoRoot,
10195
+ tmpDir: deps.tmpDir,
10196
+ inputs: deps.inputs
10197
+ };
10198
+ this.steps = [
10199
+ {
10200
+ id: "stack",
10201
+ description: "Validate stack and inputs",
10202
+ run: async (ctx) => {
10203
+ const stack = await detectStack(
10204
+ ctx.repoRoot,
10205
+ ctx.tmpDir,
10206
+ ctx.inputs.productionDatabaseUrlAdmin
10207
+ );
10208
+ ctx.summary.detected.stack = stack;
10209
+ const supportedStacks = ["drizzle", "supabase"];
10210
+ if (!stack || !supportedStacks.includes(stack)) {
10211
+ throw new CLIError(
10212
+ `Production apply requires a supported database stack (detected: ${stack || "<empty>"}).`,
10213
+ "CI_PROD_STACK_UNSUPPORTED",
10214
+ [
10215
+ "Supported stacks: drizzle, supabase",
10216
+ "Run: runa init (to set up database structure)",
10217
+ "See: packages/database or supabase/schemas/"
10218
+ ],
10219
+ void 0,
10220
+ 11
10221
+ );
10222
+ }
10223
+ }
10224
+ },
10225
+ {
10226
+ id: "schema-diff",
10227
+ description: "Show schema diff",
10228
+ run: async (ctx) => {
10229
+ await showSchemaDiff(ctx.repoRoot, ctx.tmpDir);
10230
+ }
10231
+ },
10232
+ {
10233
+ id: "initial-detect",
10234
+ description: "Detect initial deployment",
10235
+ run: async (ctx) => {
10236
+ const result = checkIfInitialDeployment(
10237
+ ctx.repoRoot,
10238
+ ctx.inputs.productionDatabaseUrlAdmin
10239
+ );
10240
+ ctx.initialDeploymentCheck = result;
10241
+ if (result.checkSuccessful) {
10242
+ if (result.isInitialDeployment) {
10243
+ ctx.logger.info(`Initial deployment detected: ${result.reason}`);
10244
+ } else {
10245
+ ctx.logger.info(`Existing deployment: ${result.tableCount} table(s) found`);
10246
+ }
10247
+ } else {
10248
+ ctx.logger.warn(
10249
+ `Initial deployment check failed: ${result.reason}. Proceeding with risk analysis.`
10250
+ );
10251
+ }
10252
+ const skipViaFlag = ctx.options.skipRisks === true ? "--skip-risks flag" : void 0;
10253
+ const skipViaInitialFlag = ctx.options.initialDeployment === true ? "--initial-deployment flag" : void 0;
10254
+ const skipViaEnv = process.env.RUNA_SKIP_SCHEMA_RISK === "1" || process.env.RUNA_SKIP_SCHEMA_RISK === "true" ? "RUNA_SKIP_SCHEMA_RISK env" : void 0;
10255
+ const skipViaAutoDetect = result.checkSuccessful && result.isInitialDeployment ? `auto-detected initial deployment (${result.reason})` : void 0;
10256
+ ctx.skipRisksReason = skipViaFlag ?? skipViaInitialFlag ?? skipViaEnv ?? skipViaAutoDetect ?? void 0;
10257
+ if (ctx.skipRisksReason) {
10258
+ ctx.logger.info(`Risk detection skipped: ${ctx.skipRisksReason}`);
10259
+ }
10260
+ }
10261
+ },
10262
+ {
10263
+ id: "detect-risks",
10264
+ description: "Detect risky schema changes",
10265
+ shouldSkip: (ctx) => ctx.skipRisksReason ? { reason: ctx.skipRisksReason } : null,
10266
+ run: async (ctx) => {
10267
+ await detectRisks(ctx.repoRoot, ctx.tmpDir);
10268
+ }
10269
+ },
10270
+ {
10271
+ id: "snapshot",
10272
+ description: "Create pre-deploy snapshot",
10273
+ run: async (ctx) => {
10274
+ await snapshotCreate(
10275
+ ctx.repoRoot,
10276
+ ctx.tmpDir,
10277
+ ctx.inputs.productionDatabaseUrlAdmin,
10278
+ ctx.inputs.githubSha
10279
+ );
10280
+ }
10281
+ },
10282
+ {
10283
+ id: "repair-timestamps",
10284
+ description: "Repair timestamp invariants",
10285
+ run: async (ctx) => {
10286
+ await repairTimestampInvariants({
10287
+ repoRoot: ctx.repoRoot,
10288
+ tmpDir: ctx.tmpDir,
10289
+ databaseUrl: ctx.inputs.productionDatabaseUrlAdmin,
10290
+ labelPrefix: "production"
10291
+ });
10292
+ }
10293
+ },
10294
+ {
10295
+ id: "apply-schema",
10296
+ description: "Apply production schema",
10297
+ run: async (ctx) => {
10298
+ try {
10299
+ ctx.schemaApplyResult = await applyProductionSchema(
10300
+ ctx.repoRoot,
10301
+ ctx.tmpDir,
10302
+ ctx.inputs.productionDatabaseUrlAdmin,
10303
+ ctx.inputs.productionDatabaseUrl,
10304
+ {
10305
+ allowDataLoss: ctx.options.allowDataLoss === true,
10306
+ confirmAuthzUpdate: ctx.options.confirmAuthzUpdate === true,
10307
+ maxLockWaitMs: typeof ctx.options.maxLockWaitMs === "number" ? ctx.options.maxLockWaitMs : void 0
10308
+ }
10309
+ );
10310
+ } catch (error) {
10311
+ ctx.logger.error("Schema apply failed; attempting rollback from snapshot");
10312
+ await snapshotRestoreLatest(
10313
+ ctx.repoRoot,
10314
+ ctx.tmpDir,
10315
+ ctx.inputs.productionDatabaseUrlAdmin
10316
+ );
10317
+ throw error;
10318
+ }
10319
+ }
10320
+ },
10321
+ {
10322
+ id: "audit",
10323
+ description: "Record audit trail",
10324
+ run: async (ctx) => {
10325
+ try {
10326
+ await auditRecord(ctx.repoRoot, ctx.tmpDir, ctx.inputs.productionDatabaseUrlAdmin, {
10327
+ commit: ctx.inputs.githubSha,
10328
+ author: ctx.inputs.githubActor
10329
+ });
10330
+ } catch (error) {
10331
+ ctx.logger.warn(
10332
+ error instanceof Error ? error.message : "Audit record failed (best-effort)"
10333
+ );
10334
+ }
10335
+ }
10336
+ },
10337
+ {
10338
+ id: "notify",
10339
+ description: "Notify external systems",
10340
+ run: async (ctx) => {
10341
+ await maybeNotifyExternal({
10342
+ logger: ctx.logger,
10343
+ repoRoot: ctx.repoRoot,
10344
+ tmpDir: ctx.tmpDir,
10345
+ githubRepository: ctx.inputs.githubRepository,
10346
+ githubSha: ctx.inputs.githubSha,
10347
+ skipNotify: ctx.options.skipNotify === true
10348
+ });
10349
+ }
10350
+ },
10351
+ {
10352
+ id: "github-label",
10353
+ description: "Add GitHub labels/comments",
10354
+ run: async (ctx) => {
10355
+ await maybeAddGithubLabelAndComment({
10356
+ mode: ctx.summary.mode,
10357
+ skipGithubLabel: ctx.options.skipGithubLabel === true,
10358
+ githubSha: ctx.inputs.githubSha,
10359
+ schemaApplyResult: ctx.schemaApplyResult
10360
+ });
10361
+ }
10362
+ }
10363
+ ];
10364
+ }
10365
+ async run() {
10366
+ logPlan(this.steps.map((step) => ({ id: step.id, description: step.description })));
10367
+ const total = this.steps.length;
10368
+ for (let i = 0; i < total; i++) {
10369
+ const step = this.steps[i];
10370
+ const prefix = `[DEBUG] Step ${i + 1}/${total}: ${step.description}`;
10371
+ const skipResult = step.shouldSkip?.(this.ctx);
10372
+ if (skipResult) {
10373
+ console.log(`${prefix} SKIPPED (${skipResult.reason})`);
10374
+ continue;
10375
+ }
10376
+ console.log(`${prefix}...`);
10377
+ const startedAt = Date.now();
10378
+ await step.run(this.ctx);
10379
+ const duration = Date.now() - startedAt;
10380
+ console.log(`${prefix} done (${duration}ms)`);
10381
+ }
10382
+ }
10383
+ };
10384
+
10178
10385
  // src/commands/ci/commands/ci-resolvers.ts
10179
10386
  init_esm_shims();
10180
10387
 
@@ -10398,18 +10605,6 @@ var CiProdApplyOutputSchema = z.object({
10398
10605
  }).strict();
10399
10606
  async function runCiProdApply(params) {
10400
10607
  console.log("[DEBUG] runCiProdApply: starting");
10401
- const steps = [
10402
- { id: "guard", description: "Acquire deployment lock and check idempotency" },
10403
- { id: "stack", description: "Validate stack and inputs" },
10404
- { id: "risks", description: "Detect risky changes" },
10405
- { id: "snapshot", description: "Create pre-deploy snapshot" },
10406
- { id: "apply", description: "Apply production schema" },
10407
- { id: "audit", description: "Record audit trail (best-effort)" },
10408
- { id: "notify", description: "Notify external system (best-effort)" },
10409
- { id: "summarize", description: "Write summary JSON" }
10410
- ];
10411
- console.log("[DEBUG] runCiProdApply: logging plan");
10412
- logPlan(steps);
10413
10608
  const cfg = await loadCiConfig({
10414
10609
  repoRoot: params.repoRoot,
10415
10610
  overridePath: params.options.config
@@ -10419,7 +10614,7 @@ async function runCiProdApply(params) {
10419
10614
  if (cfg.config) params.summary.detected.config = cfg.config;
10420
10615
  requireCiAutoApprove({ mode: params.summary.mode, autoApprove: params.options.autoApprove });
10421
10616
  const inputs = resolveProdApplyInputs();
10422
- console.log("[DEBUG] Step 0/8: guardProductionDeployment...");
10617
+ console.log("[DEBUG] Step 0: guardProductionDeployment...");
10423
10618
  const guardResult = guardProductionDeployment(
10424
10619
  inputs.productionDatabaseUrlAdmin,
10425
10620
  inputs.githubSha
@@ -10439,7 +10634,7 @@ async function runCiProdApply(params) {
10439
10634
  "If stuck, check for orphaned advisory locks in the database"
10440
10635
  ]);
10441
10636
  }
10442
- console.log("[DEBUG] Step 0/8: guardProductionDeployment done (lock acquired)");
10637
+ console.log("[DEBUG] Step 0: guardProductionDeployment done (lock acquired)");
10443
10638
  try {
10444
10639
  logKeyValueTable({
10445
10640
  title: "Inputs",
@@ -10452,140 +10647,15 @@ async function runCiProdApply(params) {
10452
10647
  ]
10453
10648
  });
10454
10649
  logSection("Execute");
10455
- console.log("[DEBUG] Step 1/7: detectStack...");
10456
- const stack = await detectStack(
10457
- params.repoRoot,
10458
- params.tmpDir,
10459
- inputs.productionDatabaseUrlAdmin
10460
- );
10461
- console.log(`[DEBUG] Step 1/7: detectStack done (stack=${stack})`);
10462
- params.summary.detected.stack = stack;
10463
- const supportedStacks = ["drizzle", "supabase"];
10464
- if (!stack || !supportedStacks.includes(stack)) {
10465
- throw new CLIError(
10466
- `Production apply requires a supported database stack (detected: ${stack || "<empty>"}).`,
10467
- "CI_PROD_STACK_UNSUPPORTED",
10468
- [
10469
- "Supported stacks: drizzle, supabase",
10470
- "Run: runa init (to set up database structure)",
10471
- "See: packages/database or supabase/schemas/"
10472
- ],
10473
- void 0,
10474
- 11
10475
- );
10476
- }
10477
- console.log("[DEBUG] Step 2/7: showSchemaDiff...");
10478
- await showSchemaDiff(params.repoRoot, params.tmpDir);
10479
- console.log("[DEBUG] Step 2/7: showSchemaDiff done");
10480
- console.log("[DEBUG] Step 2.5/7: checkIfInitialDeployment...");
10481
- const initialDeploymentCheck = checkIfInitialDeployment(
10482
- params.repoRoot,
10483
- inputs.productionDatabaseUrlAdmin
10484
- );
10485
- console.log(
10486
- `[DEBUG] Step 2.5/7: checkIfInitialDeployment done (isInitial=${initialDeploymentCheck.isInitialDeployment}, tables=${initialDeploymentCheck.tableCount})`
10487
- );
10488
- if (initialDeploymentCheck.checkSuccessful) {
10489
- if (initialDeploymentCheck.isInitialDeployment) {
10490
- params.logger.info(`Initial deployment detected: ${initialDeploymentCheck.reason}`);
10491
- } else {
10492
- params.logger.info(
10493
- `Existing deployment: ${initialDeploymentCheck.tableCount} table(s) found`
10494
- );
10495
- }
10496
- } else {
10497
- params.logger.warn(
10498
- `Initial deployment check failed: ${initialDeploymentCheck.reason}. Proceeding with risk analysis.`
10499
- );
10500
- }
10501
- const skipViaEnv = process.env.RUNA_SKIP_SCHEMA_RISK === "1" || process.env.RUNA_SKIP_SCHEMA_RISK === "true";
10502
- const skipViaAutoDetect = initialDeploymentCheck.checkSuccessful && initialDeploymentCheck.isInitialDeployment;
10503
- const shouldSkipRisks = params.options.skipRisks === true || params.options.initialDeployment === true || skipViaEnv || skipViaAutoDetect;
10504
- if (shouldSkipRisks) {
10505
- const skipReason = params.options.skipRisks ? "--skip-risks flag" : params.options.initialDeployment ? "--initial-deployment flag" : skipViaEnv ? "RUNA_SKIP_SCHEMA_RISK env" : `auto-detected initial deployment (${initialDeploymentCheck.reason})`;
10506
- console.log(`[DEBUG] Step 3/7: detectRisks SKIPPED (${skipReason})`);
10507
- params.logger.info(`Risk detection skipped: ${skipReason}`);
10508
- } else {
10509
- console.log("[DEBUG] Step 3/7: detectRisks...");
10510
- await detectRisks(params.repoRoot, params.tmpDir);
10511
- console.log("[DEBUG] Step 3/7: detectRisks done");
10512
- }
10513
- console.log("[DEBUG] Step 4/7: snapshotCreate...");
10514
- await snapshotCreate(
10515
- params.repoRoot,
10516
- params.tmpDir,
10517
- inputs.productionDatabaseUrlAdmin,
10518
- inputs.githubSha
10519
- );
10520
- console.log("[DEBUG] Step 4/7: snapshotCreate done");
10521
- console.log("[DEBUG] Step 5/7: repairTimestampInvariants...");
10522
- await repairTimestampInvariants({
10523
- repoRoot: params.repoRoot,
10524
- tmpDir: params.tmpDir,
10525
- databaseUrl: inputs.productionDatabaseUrlAdmin,
10526
- labelPrefix: "production"
10527
- });
10528
- console.log("[DEBUG] Step 5/7: repairTimestampInvariants done");
10529
- console.log("[DEBUG] Step 6/7: applyProductionSchema...");
10530
- let schemaApplyResult;
10531
- try {
10532
- schemaApplyResult = await applyProductionSchema(
10533
- params.repoRoot,
10534
- params.tmpDir,
10535
- inputs.productionDatabaseUrlAdmin,
10536
- // DDL operations (pg-schema-diff)
10537
- inputs.productionDatabaseUrl,
10538
- // App verification (drizzle_app)
10539
- {
10540
- allowDataLoss: params.options.allowDataLoss === true,
10541
- confirmAuthzUpdate: params.options.confirmAuthzUpdate === true,
10542
- maxLockWaitMs: typeof params.options.maxLockWaitMs === "number" ? params.options.maxLockWaitMs : void 0
10543
- }
10544
- );
10545
- console.log(
10546
- `[DEBUG] Step 6/7: applyProductionSchema done (${schemaApplyResult.changeSummary})`
10547
- );
10548
- } catch (applyError) {
10549
- params.logger.error("Schema apply failed; attempting rollback from snapshot");
10550
- await snapshotRestoreLatest(
10551
- params.repoRoot,
10552
- params.tmpDir,
10553
- inputs.productionDatabaseUrlAdmin
10554
- );
10555
- throw applyError;
10556
- }
10557
- console.log("[DEBUG] Step 7a/7: auditRecord...");
10558
- try {
10559
- await auditRecord(params.repoRoot, params.tmpDir, inputs.productionDatabaseUrlAdmin, {
10560
- commit: inputs.githubSha,
10561
- author: inputs.githubActor
10562
- });
10563
- console.log("[DEBUG] Step 7a/7: auditRecord done");
10564
- } catch (auditError) {
10565
- console.log("[DEBUG] Step 7a/7: auditRecord failed (non-blocking)");
10566
- params.logger.warn(
10567
- auditError instanceof Error ? auditError.message : "Audit record failed (best-effort)"
10568
- );
10569
- }
10570
- console.log("[DEBUG] Step 7b/7: maybeNotifyExternal...");
10571
- await maybeNotifyExternal({
10650
+ const workflow = new CiProdApplyWorkflow({
10651
+ options: params.options,
10572
10652
  logger: params.logger,
10653
+ summary: params.summary,
10573
10654
  repoRoot: params.repoRoot,
10574
10655
  tmpDir: params.tmpDir,
10575
- githubRepository: inputs.githubRepository,
10576
- githubSha: inputs.githubSha,
10577
- skipNotify: params.options.skipNotify === true
10656
+ inputs
10578
10657
  });
10579
- console.log("[DEBUG] Step 7b/7: maybeNotifyExternal done");
10580
- console.log("[DEBUG] Step 7c/7: maybeAddGithubLabelAndComment...");
10581
- await maybeAddGithubLabelAndComment({
10582
- mode: params.summary.mode,
10583
- skipGithubLabel: params.options.skipGithubLabel === true,
10584
- githubSha: inputs.githubSha,
10585
- schemaApplyResult
10586
- });
10587
- console.log("[DEBUG] Step 7c/7: maybeAddGithubLabelAndComment done");
10588
- console.log("[DEBUG] All steps complete, writing summary...");
10658
+ await workflow.run();
10589
10659
  params.summary.status = "success";
10590
10660
  params.summary.endedAt = (/* @__PURE__ */ new Date()).toISOString();
10591
10661
  params.summary.durationMs = new Date(params.summary.endedAt).getTime() - new Date(params.summary.startedAt).getTime();
@@ -27085,7 +27155,7 @@ function parseGitHubUrl(url) {
27085
27155
  }
27086
27156
  }
27087
27157
  const parts = cleanUrl.split("/");
27088
- if (parts.length === 2 && !cleanUrl.includes(".")) {
27158
+ if (parts.length === 2) {
27089
27159
  const owner = parts[0];
27090
27160
  const repo = parts[1];
27091
27161
  if (isValidGitHubOwner(owner) && isValidGitHubRepo(repo)) {
@@ -30025,7 +30095,7 @@ init_esm_shims();
30025
30095
 
30026
30096
  // src/constants/versions.ts
30027
30097
  init_esm_shims();
30028
- var COMPATIBLE_TEMPLATES_VERSION = "0.5.43";
30098
+ var COMPATIBLE_TEMPLATES_VERSION = "0.5.44";
30029
30099
  var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
30030
30100
  var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
30031
30101
 
@@ -34777,8 +34847,9 @@ var testIntegrationCommand = new Command("test:integration").description("Run La
34777
34847
 
34778
34848
  // src/commands/test/commands/test-layer.ts
34779
34849
  init_esm_shims();
34780
- function injectTestAttrsIfLayer4(layer, verbose) {
34850
+ function injectTestAttrsIfLayer4(layer, options) {
34781
34851
  if (layer !== 4) return;
34852
+ const { verbose, requireManifest } = options ?? {};
34782
34853
  try {
34783
34854
  if (verbose) {
34784
34855
  console.log("[Layer 4] Auto-injecting XState test attributes...");
@@ -34790,14 +34861,30 @@ function injectTestAttrsIfLayer4(layer, verbose) {
34790
34861
  if (verbose) {
34791
34862
  console.log("[Layer 4] Test attributes injected successfully");
34792
34863
  }
34793
- } catch {
34864
+ } catch (error) {
34865
+ if (requireManifest) {
34866
+ throw new CLIError(
34867
+ "XState test attribute injection failed",
34868
+ "INJECT_TEST_ATTRS_FAILED",
34869
+ [
34870
+ "Run `runa inject-test-attrs` manually to see detailed errors",
34871
+ "Ensure XState machines exist in your project",
34872
+ "Check that machines have valid meta.e2e definitions",
34873
+ "Or remove --require-manifest flag to continue with fallback behavior"
34874
+ ],
34875
+ error instanceof Error ? error : void 0
34876
+ );
34877
+ }
34794
34878
  if (verbose) {
34795
34879
  console.warn("[Layer 4] Test attribute injection failed, continuing with tests");
34796
34880
  }
34797
34881
  }
34798
34882
  }
34799
34883
  async function runSingleLayer(params) {
34800
- injectTestAttrsIfLayer4(params.layer, params.options.verbose);
34884
+ injectTestAttrsIfLayer4(params.layer, {
34885
+ verbose: params.options.verbose,
34886
+ requireManifest: params.options.requireManifest
34887
+ });
34801
34888
  try {
34802
34889
  const output3 = await runTest({
34803
34890
  layer: params.layer,
@@ -34813,6 +34900,7 @@ async function runSingleLayer(params) {
34813
34900
  reviewSnapshots: params.options.reviewSnapshots,
34814
34901
  forceRegenerate: params.options.force,
34815
34902
  skipGeneration: params.options.skipGeneration,
34903
+ requireManifest: params.options.requireManifest,
34816
34904
  invokedAs: `runa test:layer${params.layer}`
34817
34905
  });
34818
34906
  emitJsonSuccess(params.cmd, TestRunOutputSchema, output3);
@@ -34875,10 +34963,16 @@ function createLayerCommand(layer) {
34875
34963
  await runSingleLayer({ cmd, layer, options });
34876
34964
  });
34877
34965
  if (layer === 3) {
34878
- cmd.option("--skip-generation", "Skip auto-generation of API tests (run manual tests only)");
34966
+ cmd.option("--skip-generation", "Skip auto-generation of API tests (run manual tests only)").option(
34967
+ "--require-manifest",
34968
+ "Require manifest to be present and valid (fail if missing or stale)"
34969
+ );
34879
34970
  }
34880
34971
  if (layer >= 4) {
34881
- cmd.option("--generate", "Generate tests from XState before running (Layer 4)").option("--advanced", "Generate advanced tests (requires --generate)").option("--auto", "Zero-config auto-generation (implementation-driven)").option("--update-snapshots", "Update visual baselines (Layer 4)").option("--filter <pattern>", 'Filter tests by pattern (e.g., "checkout-*")').option("--review-snapshots", "Interactive review of changed visual snapshots (Layer 4)").option("--force", "Force regenerate all tests, bypassing cache");
34972
+ cmd.option("--generate", "Generate tests from XState before running (Layer 4)").option("--advanced", "Generate advanced tests (requires --generate)").option("--auto", "Zero-config auto-generation (implementation-driven)").option("--update-snapshots", "Update visual baselines (Layer 4)").option("--filter <pattern>", 'Filter tests by pattern (e.g., "checkout-*")').option("--review-snapshots", "Interactive review of changed visual snapshots (Layer 4)").option("--force", "Force regenerate all tests, bypassing cache").option(
34973
+ "--require-manifest",
34974
+ "Require manifest to be present and valid (fail if missing or stale)"
34975
+ );
34882
34976
  }
34883
34977
  return cmd;
34884
34978
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.5.43",
3
+ "version": "0.5.44",
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.25.0",
55
55
  "zod": "4.3.5",
56
- "@runa-ai/runa": "0.5.43",
56
+ "@runa-ai/runa": "0.5.44",
57
57
  "@runa-ai/runa-xstate-test-plugin": "0.5.35"
58
58
  },
59
59
  "engines": {