extension_cli: Populate grammars from `grammars` directory for legacy extension formats (#9650)

Marshall Bowers created

This PR makes the extension CLI populate the grammars in the manifest
from the contents of the `grammars` directory for legacy extensions
using the `extension.json` format (`schema_version == 0`).

This allows us to continue packaging these older extensions until they
can be migrated to the new schema version.

Release Notes:

- N/A

Change summary

crates/extension_cli/src/main.rs | 39 +++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+), 1 deletion(-)

Detailed changes

crates/extension_cli/src/main.rs 🔗

@@ -11,9 +11,10 @@ use anyhow::{anyhow, bail, Context, Result};
 use clap::Parser;
 use extension::{
     extension_builder::{CompileExtensionOptions, ExtensionBuilder},
-    ExtensionLibraryKind, ExtensionManifest, ExtensionStore,
+    ExtensionLibraryKind, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
 };
 use language::LanguageConfig;
+use serde::Deserialize;
 use theme::ThemeRegistry;
 use tree_sitter::{Language, Query, WasmStore};
 
@@ -143,6 +144,42 @@ fn populate_default_paths(manifest: &mut ExtensionManifest, extension_path: &Pat
         }
     }
 
+    // For legacy extensions on the v0 schema (aka, using `extension.json`), we want to populate the grammars in
+    // the manifest using the contents of the `grammars` directory.
+    if manifest.schema_version == 0 {
+        let grammars_dir = extension_path.join("grammars");
+        if grammars_dir.exists() {
+            for entry in fs::read_dir(&grammars_dir).context("failed to list grammars dir")? {
+                let entry = entry?;
+                let grammar_path = entry.path();
+                if grammar_path.extension() == Some("toml".as_ref()) {
+                    #[derive(Deserialize)]
+                    struct GrammarConfigToml {
+                        pub repository: String,
+                        pub commit: String,
+                    }
+
+                    let grammar_config = fs::read_to_string(&grammar_path)?;
+                    let grammar_config: GrammarConfigToml = toml::from_str(&grammar_config)?;
+
+                    let grammar_name = grammar_path
+                        .file_stem()
+                        .and_then(|stem| stem.to_str())
+                        .ok_or_else(|| anyhow!("no grammar name"))?;
+                    if !manifest.grammars.contains_key(grammar_name) {
+                        manifest.grammars.insert(
+                            grammar_name.into(),
+                            GrammarManifestEntry {
+                                repository: grammar_config.repository,
+                                rev: grammar_config.commit,
+                            },
+                        );
+                    }
+                }
+            }
+        }
+    }
+
     Ok(())
 }