feat(cli): pic pull --group round-trips a group node
Exports a group as a [group] manifest: its scripts, [[routes]]/[[triggers.*]] TEMPLATES (with the group-only sealed/shared flags), a shared collections entry, [vars]/[secrets], extension_points, and [suppress]. Groups own no workflows. The group trigger/route reports were display-only (no ops, dispatch, retry, host_kind, cron tz, queue timeout) so a pulled manifest could not re-plan clean. Enriched TriggerTemplateInfo with the full internally-tagged TriggerDetails JSON + dispatch_mode + retry_max_attempts, and RouteTemplateInfo with raw manifest-shaped fields (method/host_kind/host/host_param_name/ path_kind/dispatch_mode); pic routes ls --group now applies the ANY/host munge client-side, consistent with the app pic routes ls. pull.rs factors the script-writing + trigger-decoding it shares with the app path. Pinned by a pull journey: a group with a sealed+shared kv trigger, a sealed route, a shared collection, and a var round-trips and the exported manifest re-applies clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -830,6 +830,19 @@ pub struct TriggerTemplateInfo {
|
||||
pub sealed: bool,
|
||||
/// §11.6: `true` for a shared-collection template.
|
||||
pub shared: bool,
|
||||
/// Full `TriggerDetails` as internally-tagged JSON (`{ "kind": …, … }`),
|
||||
/// so `pic pull --group` can round-trip a template's ops / schedule /
|
||||
/// timezone / queue name / visibility timeout, not just the display target.
|
||||
/// `#[serde(default)]` on the read side so an older server (which omits it)
|
||||
/// still deserializes; the display command reads `target`, not this.
|
||||
#[serde(default)]
|
||||
pub details: serde_json::Value,
|
||||
/// Wire dispatch mode (`sync`/`async`) — round-trip only.
|
||||
#[serde(default)]
|
||||
pub dispatch_mode: String,
|
||||
/// Per-trigger retry ceiling — round-trip only.
|
||||
#[serde(default)]
|
||||
pub retry_max_attempts: u32,
|
||||
}
|
||||
|
||||
/// One row of the read-only §11 tail route-template report (`pic routes ls
|
||||
@@ -838,12 +851,17 @@ pub struct TriggerTemplateInfo {
|
||||
/// rows, so the semantic bits are shown instead.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RouteTemplateInfo {
|
||||
pub method: String,
|
||||
/// `None` = any method. Carried raw (not the "ANY" display munge) so
|
||||
/// `pic pull --group` round-trips it; the display command applies the munge.
|
||||
pub method: Option<String>,
|
||||
pub host_kind: HostKind,
|
||||
/// Raw host (empty for `Any`, suffix for `Wildcard`), not the display munge.
|
||||
pub host: String,
|
||||
pub path_kind: String,
|
||||
pub host_param_name: Option<String>,
|
||||
pub path_kind: PathKind,
|
||||
pub path: String,
|
||||
pub script: String,
|
||||
pub dispatch: String,
|
||||
pub dispatch_mode: DispatchMode,
|
||||
pub enabled: bool,
|
||||
/// §11 tail: `true` for a sealed (non-suppressible) template.
|
||||
pub sealed: bool,
|
||||
@@ -3651,6 +3669,9 @@ impl ApplyService {
|
||||
enabled: t.enabled,
|
||||
sealed: t.sealed,
|
||||
shared: t.shared,
|
||||
details: serde_json::to_value(&t.details).unwrap_or(serde_json::Value::Null),
|
||||
dispatch_mode: t.dispatch_mode.as_str().to_string(),
|
||||
retry_max_attempts: t.retry_max_attempts,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
@@ -3681,16 +3702,14 @@ impl ApplyService {
|
||||
Ok(routes
|
||||
.into_iter()
|
||||
.map(|r| RouteTemplateInfo {
|
||||
method: r.method.clone().unwrap_or_else(|| "ANY".into()),
|
||||
host: match r.host_kind {
|
||||
HostKind::Any => "any".to_string(),
|
||||
HostKind::Strict => format!("strict:{}", r.host),
|
||||
HostKind::Wildcard => format!("*.{}", r.host),
|
||||
},
|
||||
path_kind: path_kind_str(r.path_kind).to_string(),
|
||||
method: r.method.clone(),
|
||||
host_kind: r.host_kind,
|
||||
host: r.host.clone(),
|
||||
host_param_name: r.host_param_name.clone(),
|
||||
path_kind: r.path_kind,
|
||||
path: r.path.clone(),
|
||||
script: name_by_id.get(&r.script_id).cloned().unwrap_or_default(),
|
||||
dispatch: r.dispatch_mode.as_str().to_string(),
|
||||
dispatch_mode: r.dispatch_mode,
|
||||
enabled: r.enabled,
|
||||
sealed: r.sealed,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user