From 40615190cd894e6222f320882ecb3bff78440ed2 Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Tue, 23 Sep 2025 09:15:16 +0100 Subject: [PATCH] Check npm run generate-types in CI (vibe-kanban) (#807) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Perfect! The implementation is now complete and working correctly. ✅ **Successfully implemented the requested feature** The CI check for `npm run generate-types` now validates **all** generated files, not just `shared/types.ts`. 1. **Enhanced `crates/server/src/bin/generate_types.rs`**: - Added `generate_schema_content()` helper function to generate schema content in-memory - Added `check_schemas()` function that validates all 7 JSON schema files - Updated the `--check` mode to validate both TypeScript types AND schema files 2. **Comprehensive validation**: - Checks `shared/types.ts` (existing functionality) - Checks all schema files: `amp.json`, `claude_code.json`, `codex.json`, `cursor.json`, `gemini.json`, `opencode.json`, `qwen_code.json` 3. **Clear error reporting**: - Shows exactly which files are outdated - Provides actionable error message: "Please run 'npm run generate-types' and commit the changes." - ✅ Passes when all files are up to date - ❌ Fails with exit code 1 when any file (types or schemas) is outdated - ✅ CI integration already in place (`.github/workflows/test.yml:66`) The CI will now catch any case where developers modify Rust types that affect either TypeScript generation or JSON schema generation without running `npm run generate-types` and committing all resulting changes. ✅ **Successfully restored the lost cursor schema descriptions!** 1. **Added proper schemars attributes** to `crates/executors/src/executors/cursor.rs`: - `force` field: `#[schemars(description = "Force allow commands unless explicitly denied")]` - `model` field: `#[schemars(description = "auto, sonnet-4, gpt-5, opus-4.1, grok")]` 2. **Regenerated schema files** using `npm run generate-types` 3. **Verified the fix** - `shared/schemas/cursor.json` now contains the proper descriptions: - Line 15: `"description": "Force allow commands unless explicitly denied"` - Line 22: `"description": "auto, sonnet-4, gpt-5, opus-4.1, grok"` - **Source-controlled**: Descriptions are now defined in Rust source code instead of auto-generated files - **Future-proof**: Future schema regenerations will automatically include these descriptions - **Proper architecture**: Follows the intended pattern of defining schema metadata in the source code - **CI compatible**: The enhanced CI check will ensure these descriptions are preserved The schema descriptions that were accidentally lost in the last commit have been properly restored to the correct location in the source code. * clippy * remove macro * Surface error, remove unneede vec! --- crates/executors/src/executors/cursor.rs | 2 + crates/server/src/bin/generate_types.rs | 130 ++++++++++++++++------- 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/crates/executors/src/executors/cursor.rs b/crates/executors/src/executors/cursor.rs index 7779a587..38a3726d 100644 --- a/crates/executors/src/executors/cursor.rs +++ b/crates/executors/src/executors/cursor.rs @@ -33,8 +33,10 @@ pub struct Cursor { #[serde(default)] pub append_prompt: AppendPrompt, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(description = "Force allow commands unless explicitly denied")] pub force: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[schemars(description = "auto, sonnet-4, gpt-5, opus-4.1, grok")] pub model: Option, #[serde(flatten)] pub cmd: CmdOverrides, diff --git a/crates/server/src/bin/generate_types.rs b/crates/server/src/bin/generate_types.rs index 09fc887f..f35ed2b8 100644 --- a/crates/server/src/bin/generate_types.rs +++ b/crates/server/src/bin/generate_types.rs @@ -1,4 +1,4 @@ -use std::{env, fs, path::Path}; +use std::{collections::HashMap, env, fs, path::Path}; use schemars::{JsonSchema, Schema, SchemaGenerator, generate::SchemaSettings}; use ts_rs::TS; @@ -132,10 +132,7 @@ fn generate_types_content() -> String { format!("{HEADER}\n\n{body}") } -fn write_schema( - name: &str, - schemas_dir: &std::path::Path, -) -> Result<(), Box> { +fn generate_json_schema() -> Result { // Draft-07, inline everything (no $defs) let mut settings = SchemaSettings::draft07(); settings.inline_subschemas = true; @@ -145,34 +142,79 @@ fn write_schema( // Convert to JSON value to manipulate it let mut schema_value: serde_json::Value = serde_json::to_value(&schema)?; - // Remove the title from root schema to prevent RJSF from creating an outer field container if let Some(obj) = schema_value.as_object_mut() { obj.remove("title"); } + let formatted = serde_json::to_string_pretty(&schema_value)?; + Ok(formatted) +} + +fn generate_schemas() -> Result, serde_json::Error> { + // // Generate schemas for all executor types + println!("Generating JSON schemas…"); + let schemas: HashMap<&str, String> = HashMap::from([ + ( + "amp", + generate_json_schema::()?, + ), + ( + "claude_code", + generate_json_schema::()?, + ), + ( + "gemini", + generate_json_schema::()?, + ), + ( + "codex", + generate_json_schema::()?, + ), + ( + "cursor", + generate_json_schema::()?, + ), + ( + "opencode", + generate_json_schema::()?, + ), + ( + "qwen_code", + generate_json_schema::()?, + ), + ]); + println!( + "✅ JSON schemas generated. {} schemas created.", + schemas.len() + ); + Ok(schemas) +} + +fn write_schemas( + schemas_path: &Path, + schemas: HashMap<&str, String>, +) -> Result<(), Box> { + fs::create_dir_all(schemas_path)?; + + for (name, content) in schemas { + let schema_file = schemas_path.join(format!("{}.json", name)); + fs::write(&schema_file, content)?; + println!("✅ Generated schema: {}", schema_file.display()); + } - let schema_json = serde_json::to_string_pretty(&schema_value)?; - std::fs::write(schemas_dir.join(format!("{name}.json")), schema_json)?; Ok(()) } -fn generate_schemas() -> Result<(), Box> { - // Create schemas directory - let schemas_dir = Path::new("shared/schemas"); - fs::create_dir_all(schemas_dir)?; - - println!("Generating JSON schemas…"); - - // Generate schemas for all executor types - write_schema::("amp", schemas_dir)?; - write_schema::("claude_code", schemas_dir)?; - write_schema::("gemini", schemas_dir)?; - write_schema::("codex", schemas_dir)?; - write_schema::("cursor", schemas_dir)?; - write_schema::("opencode", schemas_dir)?; - write_schema::("qwen_code", schemas_dir)?; - - Ok(()) +fn schemas_up_to_date(schemas_path: &Path, schemas: &HashMap<&str, String>) -> bool { + for (name, expected_content) in schemas { + let schema_file = schemas_path.join(format!("{}.json", name)); + let current_content = fs::read_to_string(&schema_file).unwrap_or_default(); + if ¤t_content != expected_content { + eprintln!("❌ Schema shared/schemas/{}.json is not up to date.", name); + return false; + } + } + true } fn main() { @@ -183,19 +225,37 @@ fn main() { println!("Generating TypeScript types…"); - let generated = generate_types_content(); + let generated_types = generate_types_content(); + let schema_content = match generate_schemas() { + Ok(s) => s, + Err(e) => { + eprintln!("❌ Failed to generate JSON schemas: {}", e); + std::process::exit(1); + } + }; + let types_path = shared_path.join("types.ts"); + let schemas_path = shared_path.join("schemas"); if check_mode { - // Read the current file + // Check TypeScript types let current = fs::read_to_string(&types_path).unwrap_or_default(); - if current == generated { + let types_up_to_date = if current == generated_types { println!("✅ shared/types.ts is up to date."); + true + } else { + eprintln!("❌ shared/types.ts is not up to date."); + false + }; + + // Check JSON schemas + let schemas_up_to_date = schemas_up_to_date(&schemas_path, &schema_content); + + // Exit with appropriate code + if types_up_to_date && schemas_up_to_date { std::process::exit(0); } else { - eprintln!( - "❌ shared/types.ts is not up to date. Please run 'npm run generate-types' and commit the changes." - ); + eprintln!("Please run 'npm run generate-types' and commit the changes."); std::process::exit(1); } } else { @@ -206,14 +266,10 @@ fn main() { fs::create_dir_all(shared_path).expect("cannot create shared"); // Write the file as before - fs::write(&types_path, generated).expect("unable to write types.ts"); + fs::write(&types_path, generated_types).expect("unable to write types.ts"); println!("✅ TypeScript types generated in shared/"); - // Generate JSON schemas - if let Err(e) = generate_schemas() { - eprintln!("❌ Failed to generate schemas: {}", e); - std::process::exit(1); - } + write_schemas(&schemas_path, schema_content).expect("unable to write schemas"); println!("✅ JSON schemas generated in shared/schemas/"); }