feat(cli): UTExportedTypeDeclarations support for file associations - #25
feat(cli): UTExportedTypeDeclarations support for file associations#25tomerqodo wants to merge 2 commits into
Conversation
WalkthroughThis pull request introduces macOS file type definition support by adding two new file association properties: Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/tauri-schema-generator/schemas/config.schema.json (1)
2423-2445: Theextrequirement is unnecessarily restrictive, and a critical logic bug prevents proper extension handling.macOS CFBundleDocumentTypes natively supports content-type-only declarations via LSItemContentTypes without requiring CFBundleTypeExtensions. However, the schema marks
extas required, preventing this use case.Additionally, there is a logic bug in
crates/tauri-bundler/src/bundle/macos/app.rs:328where the condition is inverted:if association.ext.is_empty()should beif !association.ext.is_empty(). Currently, CFBundleTypeExtensions is only inserted when the array is empty, causing it to write nothing. This needs to be fixed regardless of the schema change.To enable content-type-only associations for macOS, consider:
- Making
extoptional in the schema (or useanyOfto require eitherextorcontentTypes/exportedType)- Fixing the inverted condition in the bundler
- Handling the
namefield default gracefully whenext[0]is unavailable
🤖 Fix all issues with AI agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs`:
- Around line 328-339: The current check uses if association.ext.is_empty()
which inserts CFBundleTypeExtensions when there are no extensions; invert the
condition to only call dict.insert for "CFBundleTypeExtensions" when
association.ext is non-empty (e.g., if !association.ext.is_empty()), leaving the
existing mapping logic (association.ext.iter().map(|ext|
ext.to_string().into()).collect()) unchanged so the actual extensions are
inserted into dict rather than an empty array.
- Around line 284-289: The code incorrectly uses association.content_types when
setting the "UTTypeConformsTo" plist key; instead use the
ExportedFileAssociation's conforms_to field. Update the dict.insert call that
creates the "UTTypeConformsTo" entry (currently mapping content_types ->
plist::Value::Array(...)) to read from association.conforms_to, mapping each
string into plist values as before; ensure you only set this key when
association.conforms_to.is_some() and preserve the same Array construction
logic.
🧹 Nitpick comments (2)
crates/tauri-cli/config.schema.json (1)
2436-2445: Consider using JSON field names in the description.The text references
Self::exported_type/ExportedFileAssociation::conforms_to; usingexportedType/conformsTowould be clearer for JSON config users.crates/tauri-bundler/src/bundle/macos/app.rs (1)
350-355: Consider returning an error instead of panicking.Using
.expect()will cause a panic at runtime ifnameisNone. Since this function already returnscrate::Result<()>, it would be more consistent and user-friendly to return a proper error.♻️ Suggested improvement
dict.insert( "CFBundleTypeName".into(), association .name - .as_ref() - .expect("File association must have a name") + .as_ref() + .ok_or_else(|| crate::Error::GenericError("File association must have a name".into()))? .to_string() .into(), );
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.changes/file-association-content-type.md.changes/file-association-exported-type-cli.md.changes/file-association-exported-type.mdcrates/tauri-bundler/src/bundle/macos/app.rscrates/tauri-cli/config.schema.jsoncrates/tauri-schema-generator/schemas/config.schema.jsoncrates/tauri-utils/src/config.rsexamples/file-associations/README.mdexamples/file-associations/src-tauri/Cargo.tomlexamples/file-associations/src-tauri/tauri.conf.json
🔇 Additional comments (17)
.changes/file-association-exported-type-cli.md (1)
1-6: LGTM.Changelog entry is clear and consistent with the new feature description.
examples/file-associations/src-tauri/Cargo.toml (1)
14-14: LGTM — feature flag matches example config..changes/file-association-exported-type.md (1)
1-5: Changelog entry looks good.examples/file-associations/README.md (2)
11-13: LGTM — build command updated appropriately.
15-19: Docs update is clear and aligned with config changes..changes/file-association-content-type.md (1)
1-6: Changelog entry looks good.examples/file-associations/src-tauri/tauri.conf.json (3)
2-2: LGTM — schema reference updated.
9-12: Looks good — asset protocol enabled in example config.
41-53: LGTM — exportedType associations are well-formed.crates/tauri-cli/config.schema.json (3)
2161-2169: LGTM — schema description update is fine.
2485-2494: LGTM — exportedType schema looks correct.
2576-2599: Looks good — ExportedFileAssociation definition is clear.crates/tauri-utils/src/config.rs (2)
1180-1185: LGTM!The
content_typesfield is well-documented, correctly typed asOption<Vec<String>>, and has the appropriate serde alias for kebab-case support.
1199-1217: LGTM!The
exported_typefield andExportedFileAssociationstruct are well-defined with clear documentation explaining the macOS UTExportedTypeDeclarations mapping. The struct correctly includesdeny_unknown_fieldsand proper serde aliases.crates/tauri-schema-generator/schemas/config.schema.json (3)
2162-2162: Clearer file association wording.
The updated description makes the intent more explicit for config authors.
2485-2495: Good optionalexportedTypehook.
Making it nullable preserves backward compatibility while enabling the new export declaration.
2576-2599: ExportedFileAssociation schema looks consistent.
Requiredidentifierplus optionalconformsTomirrors other config objects cleanly.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| if let Some(content_types) = &association.content_types { | ||
| dict.insert( | ||
| "UTTypeConformsTo".into(), | ||
| plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Bug: Wrong field used for UTTypeConformsTo.
association.content_types is meant for LSItemContentTypes in CFBundleDocumentTypes, not for UTTypeConformsTo in UTExportedTypeDeclarations. The ExportedFileAssociation struct has its own conforms_to field that should be used here.
🐛 Proposed fix
- if let Some(content_types) = &association.content_types {
+ if let Some(conforms_to) = &exported_type.conforms_to {
dict.insert(
"UTTypeConformsTo".into(),
- plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
+ plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
);
}🤖 Prompt for AI Agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs` around lines 284 - 289, The
code incorrectly uses association.content_types when setting the
"UTTypeConformsTo" plist key; instead use the ExportedFileAssociation's
conforms_to field. Update the dict.insert call that creates the
"UTTypeConformsTo" entry (currently mapping content_types ->
plist::Value::Array(...)) to read from association.conforms_to, mapping each
string into plist values as before; ensure you only set this key when
association.conforms_to.is_some() and preserve the same Array construction
logic.
| if association.ext.is_empty() { | ||
| dict.insert( | ||
| "CFBundleTypeExtensions".into(), | ||
| plist::Value::Array( | ||
| association | ||
| .ext | ||
| .iter() | ||
| .map(|ext| ext.to_string().into()) | ||
| .collect(), | ||
| ), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Bug: Inverted condition inserts empty array instead of actual extensions.
The condition if association.ext.is_empty() will insert CFBundleTypeExtensions only when there are no extensions, resulting in an empty array. The logic should be inverted to insert extensions when they exist.
🐛 Proposed fix
- if association.ext.is_empty() {
+ if !association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if association.ext.is_empty() { | |
| dict.insert( | |
| "CFBundleTypeExtensions".into(), | |
| plist::Value::Array( | |
| association | |
| .ext | |
| .iter() | |
| .map(|ext| ext.to_string().into()) | |
| .collect(), | |
| ), | |
| ); | |
| } | |
| if !association.ext.is_empty() { | |
| dict.insert( | |
| "CFBundleTypeExtensions".into(), | |
| plist::Value::Array( | |
| association | |
| .ext | |
| .iter() | |
| .map(|ext| ext.to_string().into()) | |
| .collect(), | |
| ), | |
| ); | |
| } |
🤖 Prompt for AI Agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs` around lines 328 - 339, The
current check uses if association.ext.is_empty() which inserts
CFBundleTypeExtensions when there are no extensions; invert the condition to
only call dict.insert for "CFBundleTypeExtensions" when association.ext is
non-empty (e.g., if !association.ext.is_empty()), leaving the existing mapping
logic (association.ext.iter().map(|ext| ext.to_string().into()).collect())
unchanged so the actual extensions are inserted into dict rather than an empty
array.
Benchmark PR from qodo-benchmark#166
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.