feat(modules): no-provider plan check + read-only extension-points ls (§5.5 C4)
- apply: `check_extension_points_provided` (app nodes) — every EP visible on
the app's chain (its own + inherited) must have a provider: a same-apply
module, or one resolvable up-chain (override or default body). Else a clean
`pic plan` error instead of a runtime failure. Single-node only (the tree
path leans on the runtime backstop, like the dangling-import check).
- read-only surface: `extension_point_report` + `ExtensionPointInfo`;
`GET /{apps|groups}/{id}/extension-points` (AppRead / GroupScriptsRead);
`pic extension-points ls --app|--group` showing declared-vs-inherited + the
resolved provider (`app override` / `inherited default` / `unset`).
- `pic pull` round-trips an app's OWN declared EPs (filtered `declared_here`),
so pull→plan stays idempotent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
crates/picloud-cli/src/cmds/extension_points.rs
Normal file
48
crates/picloud-cli/src/cmds/extension_points.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! `pic extension-points ls --app|--group` — read-only view of a node's
|
||||
//! §5.5 extension points. For an app, each EP visible on its chain is shown
|
||||
//! with its resolved provider (`app override` / `inherited default` / `unset`);
|
||||
//! for a group, its own declared names. Authoring is declarative only (the
|
||||
//! manifest `extension_points = [...]`).
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::client::{Client, NodeKind};
|
||||
use crate::config;
|
||||
use crate::output::{OutputMode, Table};
|
||||
|
||||
pub async fn ls(app: Option<&str>, group: Option<&str>, mode: OutputMode) -> Result<()> {
|
||||
let (kind, ident) = match (app, group) {
|
||||
(Some(a), None) => (NodeKind::App, a),
|
||||
(None, Some(g)) => (NodeKind::Group, g),
|
||||
_ => bail!("`extension-points ls` requires exactly one of --app or --group"),
|
||||
};
|
||||
let creds = config::resolve()?;
|
||||
let client = Client::from_creds(&creds)?;
|
||||
let items = client.extension_points_list(kind, ident).await?;
|
||||
|
||||
match kind {
|
||||
NodeKind::App => {
|
||||
let mut table = Table::new(["name", "source", "provider"]);
|
||||
for ep in &items {
|
||||
table.row([
|
||||
ep.name.clone(),
|
||||
if ep.declared_here {
|
||||
"declared".into()
|
||||
} else {
|
||||
"inherited".into()
|
||||
},
|
||||
ep.provider.clone().unwrap_or_else(|| "unset".into()),
|
||||
]);
|
||||
}
|
||||
table.print(mode);
|
||||
}
|
||||
NodeKind::Group => {
|
||||
let mut table = Table::new(["name"]);
|
||||
for ep in &items {
|
||||
table.row([ep.name.clone()]);
|
||||
}
|
||||
table.print(mode);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user