feat(cli): [project] manifest block, --takeover, groups ls owner column
Makes §7 ownership usable end to end from the CLI. - manifest: a `[project]` block (slug + optional name), independent of the [app]/[group] XOR; threaded onto every apply from the repo. --dir reads it from the tree ROOT's picloud.toml (a [project] elsewhere is ignored with a note). - client: apply_node/apply_tree carry project + takeover; the 409 branch now surfaces the server message verbatim (covers StateMoved AND OwnershipConflict, both self-contained). New groups_list_with_owner captures the owner slug. - pic apply --takeover flag; pic groups ls gains an `owner` column (the owning project's slug, or — when unclaimed). - server: /api/v1/admin/groups now returns each group flattened with its owner slug (via list_with_owner) — the shared Group deserialize ignores the extra field, so pic groups tree / the dashboard are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ pub async fn run(
|
||||
prune: bool,
|
||||
yes: bool,
|
||||
force: bool,
|
||||
takeover: bool,
|
||||
mode: OutputMode,
|
||||
) -> Result<()> {
|
||||
let creds = config::resolve()?;
|
||||
@@ -52,7 +53,15 @@ pub async fn run(
|
||||
};
|
||||
|
||||
let report = client
|
||||
.apply_node(kind, &slug, &bundle, prune, expected_token.as_deref())
|
||||
.apply_node(
|
||||
kind,
|
||||
&slug,
|
||||
&bundle,
|
||||
prune,
|
||||
manifest.project.as_ref(),
|
||||
takeover,
|
||||
expected_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
crate::linkstate::clear_plan(base_dir, &slug);
|
||||
|
||||
@@ -120,12 +129,13 @@ pub async fn run_tree(
|
||||
prune: bool,
|
||||
yes: bool,
|
||||
force: bool,
|
||||
takeover: bool,
|
||||
env: Option<&str>,
|
||||
mode: OutputMode,
|
||||
) -> Result<()> {
|
||||
let creds = config::resolve()?;
|
||||
let client = Client::from_creds(&creds)?;
|
||||
let (bundle, node_count) = crate::discover::build_tree(dir, env)?;
|
||||
let (bundle, node_count, project) = crate::discover::build_tree(dir, env)?;
|
||||
|
||||
if prune && !yes {
|
||||
confirm_prune(&format!("{node_count} node(s) under {}", dir.display()))?;
|
||||
@@ -141,7 +151,13 @@ pub async fn run_tree(
|
||||
};
|
||||
|
||||
let report = client
|
||||
.apply_tree(&bundle, prune, expected_token.as_deref())
|
||||
.apply_tree(
|
||||
&bundle,
|
||||
prune,
|
||||
project.as_ref(),
|
||||
takeover,
|
||||
expected_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
crate::linkstate::clear_plan(dir, token_key);
|
||||
|
||||
|
||||
@@ -16,19 +16,24 @@ use crate::output::{KvBlock, OutputMode, Table};
|
||||
pub async fn ls(mode: OutputMode) -> Result<()> {
|
||||
let creds = config::resolve()?;
|
||||
let client = Client::from_creds(&creds)?;
|
||||
let groups = client.groups_list().await?;
|
||||
let mut table = Table::new(["slug", "name", "parent", "created_at"]);
|
||||
let by_id: BTreeMap<_, _> = groups.iter().map(|g| (g.id, g.slug.clone())).collect();
|
||||
let groups = client.groups_list_with_owner().await?;
|
||||
let mut table = Table::new(["slug", "name", "parent", "owner", "created_at"]);
|
||||
let by_id: BTreeMap<_, _> = groups
|
||||
.iter()
|
||||
.map(|g| (g.group.id, g.group.slug.clone()))
|
||||
.collect();
|
||||
for g in &groups {
|
||||
let parent = g
|
||||
.group
|
||||
.parent_id
|
||||
.and_then(|p| by_id.get(&p).cloned())
|
||||
.unwrap_or_else(|| "-".into());
|
||||
table.row([
|
||||
g.slug.clone(),
|
||||
g.name.clone(),
|
||||
g.group.slug.clone(),
|
||||
g.group.name.clone(),
|
||||
parent,
|
||||
g.created_at.to_rfc3339(),
|
||||
g.owner.clone().unwrap_or_else(|| "—".into()),
|
||||
g.group.created_at.to_rfc3339(),
|
||||
]);
|
||||
}
|
||||
table.print(mode);
|
||||
|
||||
@@ -116,6 +116,7 @@ fn scaffold_manifest(slug: &str, name: &str) -> Manifest {
|
||||
extension_points: Vec::new(),
|
||||
}),
|
||||
group: None,
|
||||
project: None,
|
||||
scripts: vec![ManifestScript {
|
||||
name: "hello".into(),
|
||||
file: "scripts/hello.rhai".into(),
|
||||
|
||||
@@ -49,7 +49,7 @@ pub async fn run(manifest_path: &Path, env: Option<&str>, mode: OutputMode) -> R
|
||||
pub async fn run_tree(dir: &Path, env: Option<&str>, mode: OutputMode) -> Result<()> {
|
||||
let creds = config::resolve()?;
|
||||
let client = Client::from_creds(&creds)?;
|
||||
let (bundle, _count) = crate::discover::build_tree(dir, env)?;
|
||||
let (bundle, _count, _project) = crate::discover::build_tree(dir, env)?;
|
||||
let plan = client.plan_tree(&bundle).await?;
|
||||
if !plan.state_token.is_empty() {
|
||||
if let Err(e) = crate::linkstate::write_plan(dir, TREE_TOKEN_KEY, &plan.state_token) {
|
||||
|
||||
@@ -242,6 +242,7 @@ pub async fn run(app_ident: &str, dir: &Path, force: bool, mode: OutputMode) ->
|
||||
extension_points,
|
||||
}),
|
||||
group: None,
|
||||
project: None,
|
||||
scripts: manifest_scripts,
|
||||
routes,
|
||||
triggers: manifest_triggers,
|
||||
|
||||
Reference in New Issue
Block a user