Before You Begin
Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License Agreement (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.
If you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don’t need to do it again.
Visit cla.developers.google.com to see your current agreements or to sign a new one.
This project follows Google’s Open Source Community Guidelines.
Changesets (Required)
Every PR must include a changeset file. The CI policy check will fail without one.
Creating a Changeset
Create a file at .changeset/<descriptive-name>.md:
---
"@googleworkspace/cli": patch
---
Brief description of the change
Choosing the Change Type
| Type | When to Use | Examples |
|---|
patch | Fixes, chores, documentation | Bug fixes, dependency updates, typo fixes |
minor | New features, non-breaking additions | New commands, new flags, new APIs |
major | Breaking changes | Removed commands, changed output format, incompatible API changes |
Example Changesets
Patch (bug fix):
---
"@googleworkspace/cli": patch
---
Fix URL encoding for file IDs containing special characters
Minor (new feature):
---
"@googleworkspace/cli": minor
---
Add support for Gmail message sanitization with Model Armor
Major (breaking change):
---
"@googleworkspace/cli": major
---
Change default pagination limit from 10 to 1 page
Code Style Guidelines
Use rustfmt for consistent formatting:
The CI pipeline checks formatting automatically.
Linting
Run Clippy with strict warnings:
cargo clippy -- -D warnings
Fix all warnings before submitting a PR.
Naming Conventions
- Functions:
snake_case
- Types/Structs:
PascalCase
- Constants:
SCREAMING_SNAKE_CASE
- Modules:
snake_case
This CLI is designed to be invoked by AI/LLM agents, so all user-supplied inputs must be treated as potentially adversarial.
Validation Checklist
When adding a new feature or CLI command:
| Scenario | What to Use |
|---|
File path for writing (--output-dir) | validate::validate_safe_output_dir() |
File path for reading (--dir) | validate::validate_safe_dir_path() |
| URL path segment (file ID, resource ID) | helpers::encode_path_segment() |
| Query parameters | reqwest .query() builder |
Resource names (--project, --space) | helpers::validate_resource_name() |
Enum flags (--msg-format) | clap value_parser |
Path Safety Example
use crate::validate::validate_safe_output_dir;
if let Some(output_dir) = matches.get_one::<String>("output-dir") {
validate_safe_output_dir(output_dir)?;
builder.output_dir(Some(output_dir.clone()));
}
URL Encoding Example
use crate::helpers::encode_path_segment;
// CORRECT — encodes slashes, spaces, and special characters
let url = format!(
"https://www.googleapis.com/drive/v3/files/{}",
encode_path_segment(file_id),
);
// WRONG — raw user input in URL path
let url = format!("https://www.googleapis.com/drive/v3/files/{}", file_id);
Query Parameter Example
// CORRECT — reqwest encodes query values
client.get(url).query(&[("q", user_query)]).send().await?;
// WRONG — manual string interpolation
let url = format!("{}?q={}", base_url, user_query);
Resource Name Example
use crate::helpers::validate_resource_name;
let project = validate_resource_name(&project_id)?;
let url = format!(
"https://pubsub.googleapis.com/v1/projects/{}/topics/my-topic",
project
);
Testing Requirements
All validation logic must include both happy-path and error-path tests.
Test Coverage
- Run
cargo test locally before submitting
- Verify new branches are exercised
- Use
./scripts/coverage.sh to generate a coverage report
- The
codecov/patch check requires new or modified lines to be tested
Writing Tests
Extract testable helper functions:
// GOOD — testable helper function
pub fn validate_file_id(id: &str) -> Result<(), ValidationError> {
if id.contains("..") {
return Err(ValidationError::PathTraversal);
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_validate_file_id_rejects_traversal() {
assert!(validate_file_id("../../secret").is_err());
}
#[test]
fn test_validate_file_id_allows_valid() {
assert!(validate_file_id("abc123").is_ok());
}
}
// BAD — logic embedded in main/run (hard to test)
fn run() -> Result<()> {
let file_id = get_file_id();
if file_id.contains("..") {
return Err(...);
}
// ...
}
Serial Tests
Tests that modify the process CWD must use #[serial] from serial_test:
use serial_test::serial;
#[test]
#[serial]
fn test_changes_cwd() {
std::env::set_current_dir("/tmp").unwrap();
// ...
}
Tempdir Paths
Canonicalize tempdir paths to handle macOS /var → /private/var symlinks:
use tempfile::TempDir;
#[test]
fn test_with_tempdir() {
let temp_dir = TempDir::new().unwrap();
let canonical_path = temp_dir.path().canonicalize().unwrap();
// Use canonical_path in tests...
}
Pull Request Workflow
1. Fork and Clone
git clone https://github.com/YOUR_USERNAME/cli.git
cd cli
2. Create a Branch
git checkout -b feature/my-new-feature
3. Make Changes
- Write code following the style guidelines
- Add tests for new functionality
- Update documentation if needed
4. Create a Changeset
# Create .changeset/my-feature.md
cat > .changeset/my-feature.md << 'EOF'
---
"@googleworkspace/cli": minor
---
Add support for my new feature
EOF
5. Run Checks Locally
cargo fmt
cargo clippy -- -D warnings
cargo test
./scripts/coverage.sh
6. Commit and Push
git add .
git commit -m "Add my new feature"
git push origin feature/my-new-feature
7. Create Pull Request
- Go to github.com/googleworkspace/cli
- Click New Pull Request
- Select your branch
- Fill out the PR template
- Submit for review
Code Review Process
All submissions, including submissions by project members, require review:
- A maintainer will review your PR
- Address any requested changes
- Once approved, a maintainer will merge your PR
- Your changes will be included in the next release
Consult GitHub Help for more information on using pull requests.
Package Manager Note
Important: Use pnpm instead of npm for Node.js package management in this repository.
The project is configured with:
{
"packageManager": "pnpm@10.0.0"
}
Install dependencies with:
Additional Resources
Getting Help
If you have questions or need help:
- Check the documentation
- Search existing issues
- Open a new issue
- Join discussions in pull requests