fix: add manual project ID entry to setup project picker (#116) (#123)

When `gcloud projects list` times out (10s limit) for users with many
projects, the picker now includes a '⌨ Enter project ID manually'
option so they can type a known project ID instead of waiting or failing.

- Add '⌨ Enter project ID manually' item to project picker
- Handle the new item by prompting for input and calling set_gcloud_project
- Add EnterProjectId variant to SetupAction (tests)
- Add test_project_select_enter_manually unit test

Fixes #116

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
zerone0x
2026-03-05 14:54:41 +08:00
committed by GitHub
parent 364542b2c5
commit dbda001367
2 changed files with 67 additions and 8 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"@googleworkspace/cli": patch
---
Add "Enter project ID manually" option to project picker in `gws auth setup`.
Users with large numbers of GCP projects often hit the 10-second listing timeout.
The picker now includes a "⌨ Enter project ID manually" item so users can type a
known project ID directly without waiting for `gcloud projects list` to complete.
+58 -8
View File
@@ -1003,14 +1003,24 @@ fn stage_project(ctx: &mut SetupContext) -> Result<SetupStage, GwsError> {
}
let current = get_gcloud_project()?.unwrap_or_default();
let mut items: Vec<SelectItem> = vec![SelectItem {
label: " Create new project".to_string(),
description: "Create a new GCP project for gws".to_string(),
selected: false,
is_fixed: false,
is_template: false,
template_selects: vec![],
}];
let mut items: Vec<SelectItem> = vec![
SelectItem {
label: " Create new project".to_string(),
description: "Create a new GCP project for gws".to_string(),
selected: false,
is_fixed: false,
is_template: false,
template_selects: vec![],
},
SelectItem {
label: "⌨ Enter project ID manually".to_string(),
description: "Use an existing project ID you already know".to_string(),
selected: false,
is_fixed: false,
is_template: false,
template_selects: vec![],
},
];
items.extend(projects.iter().map(|(id, name)| SelectItem {
label: id.clone(),
description: name.clone(),
@@ -1075,6 +1085,30 @@ fn stage_project(ctx: &mut SetupContext) -> Result<SetupStage, GwsError> {
ctx.project_id = project_name;
Ok(SetupStage::EnableApis)
}
Some(item) if item.label.starts_with('⌨') => {
let project_id = match ctx
.wizard
.as_mut()
.unwrap()
.show_input(
"Enter GCP project ID",
"Type your existing project ID",
None,
)
.map_err(|e| GwsError::Validation(format!("TUI error: {e}")))?
{
crate::setup_tui::InputResult::Confirmed(v) if !v.is_empty() => v,
_ => {
return Err(GwsError::Validation(
"Project entry cancelled by user".to_string(),
))
}
};
set_gcloud_project(&project_id)?;
ctx.wiz(2, StepStatus::Done(project_id.clone()));
ctx.project_id = project_id;
Ok(SetupStage::EnableApis)
}
Some(item) => {
set_gcloud_project(&item.label)?;
ctx.wiz(2, StepStatus::Done(item.label.clone()));
@@ -1481,6 +1515,7 @@ mod tests {
LoginNewAccount,
SetProject(String),
CreateProject(String),
EnterProjectId,
EnableApis(Vec<String>),
NoSelection,
}
@@ -1498,6 +1533,7 @@ mod tests {
Some(item) if item.label.starts_with('') => {
SetupAction::CreateProject(String::new())
}
Some(item) if item.label.starts_with('⌨') => SetupAction::EnterProjectId,
Some(item) => SetupAction::SetProject(item.label.clone()),
None => SetupAction::NoSelection,
}
@@ -1692,6 +1728,20 @@ mod tests {
);
}
#[test]
fn test_project_select_enter_manually() {
let mut items = make_items(&[
" Create new project",
"⌨ Enter project ID manually",
"existing",
]);
items[1].selected = true;
assert_eq!(
resolve_project_selection(&items),
SetupAction::EnterProjectId
);
}
#[test]
fn test_project_select_none() {
let items = make_items(&[" Create new project", "proj-a"]);