Public dry run dryrun-0b6a82183be2
Source commit: 0b6a82183be25780b308c75442a9b5f602f727d4
This commit is contained in:
parent
8c0c336ae9
commit
5386bb851f
16 changed files with 608 additions and 95 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"kind": "disasmer-filtered-public-tree",
|
"kind": "disasmer-filtered-public-tree",
|
||||||
"source_commit": "f1e9b7b784b4a4dd28799719681535e2a4ef44c9",
|
"source_commit": "0b6a8218d2d6d25f6025f62264e9394b90bd4dfd",
|
||||||
"release_name": "dryrun-f1e9b7b784b4",
|
"release_name": "dryrun-0b6a8218d2d6",
|
||||||
"filtered_out": [
|
"filtered_out": [
|
||||||
"private/**",
|
"private/**",
|
||||||
"experiments/**",
|
"experiments/**",
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,8 @@ enum BundleCommands {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser)]
|
#[derive(Clone, Debug, Parser)]
|
||||||
struct AgentEnrollArgs {
|
struct AgentEnrollArgs {
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
#[arg(long = "public-key")]
|
#[arg(long = "public-key")]
|
||||||
public_key: String,
|
public_key: String,
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +233,8 @@ struct ProjectSelectArgs {
|
||||||
struct BundleInspectArgs {
|
struct BundleInspectArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
project: Option<PathBuf>,
|
project: Option<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser)]
|
#[derive(Clone, Debug, Parser)]
|
||||||
|
|
@ -239,6 +243,8 @@ struct BuildArgs {
|
||||||
project: Option<PathBuf>,
|
project: Option<PathBuf>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser)]
|
#[derive(Clone, Debug, Parser)]
|
||||||
|
|
@ -250,6 +256,8 @@ struct RunArgs {
|
||||||
coordinator: Option<String>,
|
coordinator: Option<String>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
local: bool,
|
local: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Subcommand)]
|
#[derive(Clone, Debug, Subcommand)]
|
||||||
|
|
@ -277,6 +285,8 @@ struct AttachArgs {
|
||||||
enrollment_grant: Option<String>,
|
enrollment_grant: Option<String>,
|
||||||
#[arg(long = "public-key")]
|
#[arg(long = "public-key")]
|
||||||
public_key: Option<String>,
|
public_key: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser)]
|
#[derive(Clone, Debug, Parser)]
|
||||||
|
|
@ -414,6 +424,8 @@ struct ArtifactExportArgs {
|
||||||
struct DapArgs {
|
struct DapArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
plan: bool,
|
plan: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
#[arg(last = true)]
|
#[arg(last = true)]
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
@ -796,6 +808,7 @@ fn project_status_report(args: ProjectStatusArgs, cwd: PathBuf) -> Result<Value>
|
||||||
let inspection = bundle_inspection(
|
let inspection = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: Some(cwd.clone()),
|
project: Some(cwd.clone()),
|
||||||
|
json: true,
|
||||||
},
|
},
|
||||||
cwd.clone(),
|
cwd.clone(),
|
||||||
)
|
)
|
||||||
|
|
@ -864,6 +877,7 @@ fn build_report(args: BuildArgs, cwd: PathBuf) -> Result<Value> {
|
||||||
let inspection = bundle_inspection(
|
let inspection = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: args.project.clone(),
|
project: args.project.clone(),
|
||||||
|
json: true,
|
||||||
},
|
},
|
||||||
cwd,
|
cwd,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -1247,137 +1261,428 @@ fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_report<T: Serialize>(report: &T, json_output: bool) -> Result<()> {
|
||||||
|
let value = serde_json::to_value(report)?;
|
||||||
|
if json_output {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&value)?);
|
||||||
|
} else {
|
||||||
|
println!("{}", human_report(&value));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn human_report(value: &Value) -> String {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
let title = value
|
||||||
|
.get("command")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(|command| format!("Disasmer {command}"))
|
||||||
|
.or_else(|| {
|
||||||
|
if value.get("human_flow").is_some() {
|
||||||
|
Some("Disasmer login".to_owned())
|
||||||
|
} else if value.get("metadata").is_some()
|
||||||
|
&& value.get("source_provider_manifest").is_some()
|
||||||
|
{
|
||||||
|
Some("Disasmer bundle inspect".to_owned())
|
||||||
|
} else if value.get("entry").is_some() && value.get("session").is_some() {
|
||||||
|
Some("Disasmer run".to_owned())
|
||||||
|
} else if value.get("capabilities").is_some() && value.get("node").is_some() {
|
||||||
|
Some("Disasmer node attach".to_owned())
|
||||||
|
} else if value.get("public_key_fingerprint").is_some() {
|
||||||
|
Some("Disasmer agent enroll".to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "Disasmer report".to_owned());
|
||||||
|
lines.push(title);
|
||||||
|
|
||||||
|
push_string_field(&mut lines, value, "status", "status");
|
||||||
|
push_string_field(&mut lines, value, "coordinator", "coordinator");
|
||||||
|
push_string_field(&mut lines, value, "active_coordinator", "coordinator");
|
||||||
|
push_string_field(&mut lines, value, "tenant", "tenant");
|
||||||
|
push_string_field(&mut lines, value, "project", "project");
|
||||||
|
push_string_field(&mut lines, value, "process", "process");
|
||||||
|
push_string_field(&mut lines, value, "node", "node");
|
||||||
|
push_string_field(&mut lines, value, "artifact", "artifact");
|
||||||
|
push_string_field(&mut lines, value, "entry", "entry");
|
||||||
|
push_string_field(&mut lines, value, "config_file", "config");
|
||||||
|
push_string_field(&mut lines, value, "adapter", "adapter");
|
||||||
|
|
||||||
|
if let Some(project) = value.get("project").and_then(Value::as_str) {
|
||||||
|
if !lines
|
||||||
|
.iter()
|
||||||
|
.any(|line| line == &format!("project: {project}"))
|
||||||
|
{
|
||||||
|
lines.push(format!("project: {project}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(project_config) = value.get("project_config").or_else(|| value.get("project")) {
|
||||||
|
if project_config.is_object() {
|
||||||
|
push_nested_string_field(&mut lines, project_config, "tenant", "tenant");
|
||||||
|
push_nested_string_field(&mut lines, project_config, "project", "project");
|
||||||
|
push_nested_string_field(&mut lines, project_config, "coordinator", "coordinator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(metadata) = value.get("metadata") {
|
||||||
|
push_nested_string_field(&mut lines, metadata, "identity", "bundle");
|
||||||
|
if let Some(environments) = metadata.get("environments").and_then(Value::as_array) {
|
||||||
|
let names = environments
|
||||||
|
.iter()
|
||||||
|
.filter_map(|env| env.get("name").and_then(Value::as_str))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !names.is_empty() {
|
||||||
|
lines.push(format!("environments: {}", names.join(", ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(bundle) = value.get("bundle") {
|
||||||
|
if let Some(metadata) = bundle.get("metadata") {
|
||||||
|
push_nested_string_field(&mut lines, metadata, "identity", "bundle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(flow) = value.get("human_flow") {
|
||||||
|
if let Some(device) = flow.get("Device") {
|
||||||
|
lines.push("flow: device".to_owned());
|
||||||
|
push_nested_string_field(&mut lines, device, "verification_url", "verify");
|
||||||
|
push_nested_string_field(&mut lines, device, "user_code", "code");
|
||||||
|
} else if let Some(browser) = flow.get("Browser") {
|
||||||
|
lines.push("flow: browser".to_owned());
|
||||||
|
push_nested_string_field(&mut lines, browser, "authorization_url", "open");
|
||||||
|
push_nested_string_field(&mut lines, browser, "callback_path", "callback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(session) = value.get("session") {
|
||||||
|
lines.push(format!("session: {}", compact_json(session)));
|
||||||
|
}
|
||||||
|
if let Some(coordinator_selection) = value.get("coordinator") {
|
||||||
|
if coordinator_selection.is_object() {
|
||||||
|
lines.push(format!(
|
||||||
|
"coordinator: {}",
|
||||||
|
compact_json(coordinator_selection)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(response) = value
|
||||||
|
.get("response")
|
||||||
|
.or_else(|| value.get("coordinator_response"))
|
||||||
|
{
|
||||||
|
if let Some(response_type) = response.get("type").and_then(Value::as_str) {
|
||||||
|
lines.push(format!("coordinator response: {response_type}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(events) = value
|
||||||
|
.pointer("/events/response/events")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
{
|
||||||
|
lines.push(format!("events: {}", events.len()));
|
||||||
|
}
|
||||||
|
if let Some(events) = value
|
||||||
|
.pointer("/coordinator_response/response/events")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
{
|
||||||
|
lines.push(format!("events: {}", events.len()));
|
||||||
|
}
|
||||||
|
if let Some(requires_confirmation) = value.get("requires_confirmation").and_then(Value::as_bool)
|
||||||
|
{
|
||||||
|
lines.push(format!(
|
||||||
|
"confirmation: {}",
|
||||||
|
if requires_confirmation {
|
||||||
|
"required"
|
||||||
|
} else {
|
||||||
|
"confirmed"
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(flag) = value
|
||||||
|
.get("private_website_required")
|
||||||
|
.and_then(Value::as_bool)
|
||||||
|
{
|
||||||
|
lines.push(format!("private website required: {flag}"));
|
||||||
|
}
|
||||||
|
if let Some(next_actions) = value.get("next_actions").and_then(Value::as_array) {
|
||||||
|
let actions = next_actions
|
||||||
|
.iter()
|
||||||
|
.filter_map(Value::as_str)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !actions.is_empty() {
|
||||||
|
lines.push(format!("next: {}", actions.join("; ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(auth) = value.get("auth").or_else(|| value.get("session")) {
|
||||||
|
if let Some(kind) = auth.get("kind").and_then(Value::as_str) {
|
||||||
|
lines.push(format!("auth: {kind}"));
|
||||||
|
}
|
||||||
|
if let Some(authenticated) = auth.get("authenticated").and_then(Value::as_bool) {
|
||||||
|
lines.push(format!("authenticated: {authenticated}"));
|
||||||
|
}
|
||||||
|
if let Some(expires) = auth.get("expires_at").and_then(Value::as_str) {
|
||||||
|
lines.push(format!("token expiry: {expires}"));
|
||||||
|
} else if let Some(posture) = auth.get("token_expiry_posture").and_then(Value::as_str) {
|
||||||
|
lines.push(format!("token expiry: {posture}"));
|
||||||
|
} else if auth.get("authenticated").is_some() {
|
||||||
|
lines.push("token expiry: unavailable".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(dependencies) = value.get("dependencies").and_then(Value::as_object) {
|
||||||
|
let mut entries = dependencies
|
||||||
|
.iter()
|
||||||
|
.map(|(name, available)| {
|
||||||
|
format!(
|
||||||
|
"{name}={}",
|
||||||
|
if available.as_bool().unwrap_or(false) {
|
||||||
|
"ok"
|
||||||
|
} else {
|
||||||
|
"missing"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
entries.sort();
|
||||||
|
if !entries.is_empty() {
|
||||||
|
lines.push(format!("dependencies: {}", entries.join(", ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(readiness) = value.get("node_readiness") {
|
||||||
|
push_nested_string_field(&mut lines, readiness, "os", "node os");
|
||||||
|
push_nested_string_field(&mut lines, readiness, "arch", "node arch");
|
||||||
|
if let Some(capabilities) = readiness.get("capabilities").and_then(Value::as_array) {
|
||||||
|
let caps = capabilities
|
||||||
|
.iter()
|
||||||
|
.filter_map(Value::as_str)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !caps.is_empty() {
|
||||||
|
lines.push(format!("node capabilities: {}", caps.join(", ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.dedup();
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_string_field(lines: &mut Vec<String>, value: &Value, key: &str, label: &str) {
|
||||||
|
if let Some(text) = value.get(key).and_then(Value::as_str) {
|
||||||
|
lines.push(format!("{label}: {text}"));
|
||||||
|
} else if let Some(path) = value.get(key).and_then(|value| {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.and_then(|object| object.get("display"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
}) {
|
||||||
|
lines.push(format!("{label}: {path}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_nested_string_field(lines: &mut Vec<String>, value: &Value, key: &str, label: &str) {
|
||||||
|
if let Some(text) = value.get(key).and_then(Value::as_str) {
|
||||||
|
lines.push(format!("{label}: {text}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compact_json(value: &Value) -> String {
|
||||||
|
serde_json::to_string(value).unwrap_or_else(|_| "<unprintable>".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Doctor(args) => {
|
Commands::Doctor(args) => {
|
||||||
println!(
|
let json_output = args.scope.json;
|
||||||
"{}",
|
let report = doctor_report(args, std::env::current_dir()?)?;
|
||||||
serde_json::to_string_pretty(&doctor_report(args, std::env::current_dir()?)?)?
|
emit_report(&report, json_output)?;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Commands::Login(args) => {
|
Commands::Login(args) => {
|
||||||
|
let json_output = args.json;
|
||||||
if args.complete_browser_code.is_some() {
|
if args.complete_browser_code.is_some() {
|
||||||
let report = execute_browser_login_completion(args)?;
|
let report = execute_browser_login_completion(args)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, json_output)?;
|
||||||
} else if args.browser && !args.plan {
|
} else if args.browser && !args.plan {
|
||||||
let json_output = args.json;
|
|
||||||
let report = execute_interactive_browser_login(args)?;
|
let report = execute_interactive_browser_login(args)?;
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, true)?;
|
||||||
} else {
|
} else {
|
||||||
print_browser_login_success(&report);
|
print_browser_login_success(&report);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let plan = login_plan(args);
|
let plan = login_plan(args);
|
||||||
println!("{}", serde_json::to_string_pretty(&plan)?);
|
emit_report(&plan, json_output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Auth { command } => {
|
Commands::Auth { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
AuthCommands::Status(args) => auth_status_report(args, std::env::current_dir()?),
|
AuthCommands::Status(args) => {
|
||||||
AuthCommands::Logout(args) => auth_logout_report(args, std::env::current_dir()?),
|
let json_output = args.scope.json;
|
||||||
}?;
|
(
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
auth_status_report(args, std::env::current_dir()?)?,
|
||||||
|
json_output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AuthCommands::Logout(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(
|
||||||
|
auth_logout_report(args, std::env::current_dir()?)?,
|
||||||
|
json_output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Agent {
|
Commands::Agent {
|
||||||
command: AgentCommands::Enroll(args),
|
command: AgentCommands::Enroll(args),
|
||||||
} => {
|
} => {
|
||||||
|
let json_output = args.json;
|
||||||
let plan = agent_enrollment_plan(args);
|
let plan = agent_enrollment_plan(args);
|
||||||
println!("{}", serde_json::to_string_pretty(&plan)?);
|
emit_report(&plan, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Key { command } => {
|
Commands::Key { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
KeyCommands::Add(args) => key_add_report(args),
|
KeyCommands::Add(args) => {
|
||||||
KeyCommands::List(args) => key_list_report(args),
|
let json_output = args.scope.json;
|
||||||
KeyCommands::Revoke(args) => key_revoke_report(args),
|
(key_add_report(args)?, json_output)
|
||||||
}?;
|
}
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
KeyCommands::List(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(key_list_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
KeyCommands::Revoke(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(key_revoke_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Project { command } => {
|
Commands::Project { command } => {
|
||||||
let cwd = std::env::current_dir()?;
|
let cwd = std::env::current_dir()?;
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
ProjectCommands::Init(args) => project_init_report(args, cwd),
|
ProjectCommands::Init(args) => {
|
||||||
ProjectCommands::Status(args) => project_status_report(args, cwd),
|
let json_output = args.scope.json;
|
||||||
ProjectCommands::List(args) => project_list_report(args, cwd),
|
(project_init_report(args, cwd)?, json_output)
|
||||||
ProjectCommands::Select(args) => project_select_report(args, cwd),
|
}
|
||||||
}?;
|
ProjectCommands::Status(args) => {
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
let json_output = args.scope.json;
|
||||||
|
(project_status_report(args, cwd)?, json_output)
|
||||||
|
}
|
||||||
|
ProjectCommands::List(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(project_list_report(args, cwd)?, json_output)
|
||||||
|
}
|
||||||
|
ProjectCommands::Select(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(project_select_report(args, cwd)?, json_output)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Inspect(args) => {
|
Commands::Inspect(args) => {
|
||||||
|
let json_output = args.json;
|
||||||
let inspection = bundle_inspection(args, std::env::current_dir()?)?;
|
let inspection = bundle_inspection(args, std::env::current_dir()?)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&inspection)?);
|
emit_report(&inspection, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Build(args) => {
|
Commands::Build(args) => {
|
||||||
|
let json_output = args.json;
|
||||||
let report = build_report(args, std::env::current_dir()?)?;
|
let report = build_report(args, std::env::current_dir()?)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Bundle {
|
Commands::Bundle {
|
||||||
command: BundleCommands::Inspect(args),
|
command: BundleCommands::Inspect(args),
|
||||||
} => {
|
} => {
|
||||||
|
let json_output = args.json;
|
||||||
let inspection = bundle_inspection(args, std::env::current_dir()?)?;
|
let inspection = bundle_inspection(args, std::env::current_dir()?)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&inspection)?);
|
emit_report(&inspection, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Run(args) => {
|
Commands::Run(args) => {
|
||||||
|
let json_output = args.json;
|
||||||
let plan = run_plan(args, std::env::current_dir()?, session_from_env())?;
|
let plan = run_plan(args, std::env::current_dir()?, session_from_env())?;
|
||||||
if should_execute_local_node(&plan) {
|
if should_execute_local_node(&plan) {
|
||||||
let report = execute_local_node_run(plan)?;
|
let report = execute_local_node_run(plan)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, json_output)?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", serde_json::to_string_pretty(&plan)?);
|
emit_report(&plan, json_output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Node {
|
Commands::Node {
|
||||||
command: NodeCommands::Attach(args),
|
command: NodeCommands::Attach(args),
|
||||||
} => {
|
} => {
|
||||||
|
let json_output = args.json;
|
||||||
if args.coordinator.is_some() {
|
if args.coordinator.is_some() {
|
||||||
let report = execute_node_attach(args)?;
|
let report = execute_node_attach(args)?;
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, json_output)?;
|
||||||
} else {
|
} else {
|
||||||
let plan = attach_plan(args);
|
let plan = attach_plan(args);
|
||||||
println!("{}", serde_json::to_string_pretty(&plan)?);
|
emit_report(&plan, json_output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Node { command } => {
|
Commands::Node { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
NodeCommands::Enroll(args) => node_enroll_report(args),
|
NodeCommands::Enroll(args) => {
|
||||||
NodeCommands::List(args) => node_list_report(args),
|
let json_output = args.scope.json;
|
||||||
NodeCommands::Status(args) => node_status_report(args),
|
(node_enroll_report(args)?, json_output)
|
||||||
NodeCommands::Revoke(args) => node_revoke_report(args),
|
}
|
||||||
|
NodeCommands::List(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(node_list_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
NodeCommands::Status(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(node_status_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
NodeCommands::Revoke(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(node_revoke_report(args)?, json_output)
|
||||||
|
}
|
||||||
NodeCommands::Attach(_) => unreachable!("node attach is handled above"),
|
NodeCommands::Attach(_) => unreachable!("node attach is handled above"),
|
||||||
}?;
|
};
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Process { command } => {
|
Commands::Process { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
ProcessCommands::Status(args) => process_status_report(args),
|
ProcessCommands::Status(args) => {
|
||||||
ProcessCommands::Restart(args) => process_restart_report(args),
|
let json_output = args.scope.json;
|
||||||
ProcessCommands::Cancel(args) => process_cancel_report(args),
|
(process_status_report(args)?, json_output)
|
||||||
}?;
|
}
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
ProcessCommands::Restart(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(process_restart_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
ProcessCommands::Cancel(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(process_cancel_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Task {
|
Commands::Task {
|
||||||
command: TaskCommands::List(args),
|
command: TaskCommands::List(args),
|
||||||
} => {
|
} => {
|
||||||
println!(
|
let json_output = args.scope.json;
|
||||||
"{}",
|
emit_report(&task_list_report(args)?, json_output)?;
|
||||||
serde_json::to_string_pretty(&task_list_report(args)?)?
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Commands::Logs(args) => {
|
Commands::Logs(args) => {
|
||||||
println!("{}", serde_json::to_string_pretty(&logs_report(args)?)?);
|
let json_output = args.scope.json;
|
||||||
|
emit_report(&logs_report(args)?, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Artifact { command } => {
|
Commands::Artifact { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
ArtifactCommands::List(args) => artifact_list_report(args),
|
ArtifactCommands::List(args) => {
|
||||||
ArtifactCommands::Download(args) => artifact_download_report(args),
|
let json_output = args.scope.json;
|
||||||
ArtifactCommands::Export(args) => artifact_export_report(args),
|
(artifact_list_report(args)?, json_output)
|
||||||
}?;
|
}
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
ArtifactCommands::Download(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(artifact_download_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
ArtifactCommands::Export(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(artifact_export_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
Commands::Dap(args) => {
|
Commands::Dap(args) => {
|
||||||
|
let json_output = args.json;
|
||||||
if args.plan {
|
if args.plan {
|
||||||
println!("{}", serde_json::to_string_pretty(&dap_plan(args)?)?);
|
emit_report(&dap_plan(args)?, json_output)?;
|
||||||
} else {
|
} else {
|
||||||
return exec_dap(args);
|
return exec_dap(args);
|
||||||
}
|
}
|
||||||
|
|
@ -1385,30 +1690,42 @@ fn main() -> Result<()> {
|
||||||
Commands::Debug {
|
Commands::Debug {
|
||||||
command: DebugCommands::Attach(args),
|
command: DebugCommands::Attach(args),
|
||||||
} => {
|
} => {
|
||||||
println!(
|
let json_output = args.scope.json;
|
||||||
"{}",
|
emit_report(&debug_attach_report(args)?, json_output)?;
|
||||||
serde_json::to_string_pretty(&debug_attach_report(args)?)?
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Commands::Quota {
|
Commands::Quota {
|
||||||
command: QuotaCommands::Status(args),
|
command: QuotaCommands::Status(args),
|
||||||
} => {
|
} => {
|
||||||
println!(
|
let json_output = args.scope.json;
|
||||||
"{}",
|
emit_report("a_status_report(args)?, json_output)?;
|
||||||
serde_json::to_string_pretty("a_status_report(args)?)?
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Commands::Admin { command } => {
|
Commands::Admin { command } => {
|
||||||
let report = match command {
|
let (report, json_output) = match command {
|
||||||
AdminCommands::Status(args) => admin_status_report(args),
|
AdminCommands::Status(args) => {
|
||||||
AdminCommands::Bootstrap(args) => {
|
let json_output = args.scope.json;
|
||||||
admin_bootstrap_report(args, std::env::current_dir()?)
|
(admin_status_report(args)?, json_output)
|
||||||
}
|
}
|
||||||
AdminCommands::RevokeNode(args) => node_revoke_report(args),
|
AdminCommands::Bootstrap(args) => {
|
||||||
AdminCommands::StopProcess(args) => process_cancel_report(args),
|
let json_output = args.scope.json;
|
||||||
AdminCommands::SuspendTenant(args) => admin_suspend_tenant_report(args),
|
(
|
||||||
}?;
|
admin_bootstrap_report(args, std::env::current_dir()?)?,
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
json_output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AdminCommands::RevokeNode(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(node_revoke_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
AdminCommands::StopProcess(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(process_cancel_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
AdminCommands::SuspendTenant(args) => {
|
||||||
|
let json_output = args.scope.json;
|
||||||
|
(admin_suspend_tenant_report(args)?, json_output)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emit_report(&report, json_output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1450,13 +1767,19 @@ fn auth_state_value() -> Value {
|
||||||
"kind": "anonymous",
|
"kind": "anonymous",
|
||||||
"authenticated": false,
|
"authenticated": false,
|
||||||
"source": "environment",
|
"source": "environment",
|
||||||
|
"token_expiry_posture": "no_session",
|
||||||
}),
|
}),
|
||||||
CliSession::HumanSession => json!({
|
CliSession::HumanSession => {
|
||||||
"kind": "human",
|
let expires_at = std::env::var("DISASMER_TOKEN_EXPIRES_AT").ok();
|
||||||
"authenticated": true,
|
json!({
|
||||||
"source": "DISASMER_TOKEN",
|
"kind": "human",
|
||||||
"provider_tokens_exposed_to_nodes": false,
|
"authenticated": true,
|
||||||
}),
|
"source": "DISASMER_TOKEN",
|
||||||
|
"provider_tokens_exposed_to_nodes": false,
|
||||||
|
"expires_at": expires_at,
|
||||||
|
"token_expiry_posture": if expires_at.is_some() { "expires_at" } else { "unknown_env_token" },
|
||||||
|
})
|
||||||
|
}
|
||||||
CliSession::AgentPublicKey {
|
CliSession::AgentPublicKey {
|
||||||
public_key_fingerprint,
|
public_key_fingerprint,
|
||||||
browser_interaction_required,
|
browser_interaction_required,
|
||||||
|
|
@ -1466,6 +1789,7 @@ fn auth_state_value() -> Value {
|
||||||
"source": "DISASMER_AGENT_PUBLIC_KEY",
|
"source": "DISASMER_AGENT_PUBLIC_KEY",
|
||||||
"public_key_fingerprint": public_key_fingerprint,
|
"public_key_fingerprint": public_key_fingerprint,
|
||||||
"browser_interaction_required": browser_interaction_required,
|
"browser_interaction_required": browser_interaction_required,
|
||||||
|
"token_expiry_posture": "not_applicable_public_key",
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2549,6 +2873,7 @@ mod tests {
|
||||||
project: None,
|
project: None,
|
||||||
coordinator: None,
|
coordinator: None,
|
||||||
local: false,
|
local: false,
|
||||||
|
json: false,
|
||||||
};
|
};
|
||||||
let plan = run_plan(
|
let plan = run_plan(
|
||||||
args,
|
args,
|
||||||
|
|
@ -2747,6 +3072,7 @@ mod tests {
|
||||||
let first = bundle_inspection(
|
let first = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: Some(temp.path().to_path_buf()),
|
project: Some(temp.path().to_path_buf()),
|
||||||
|
json: false,
|
||||||
},
|
},
|
||||||
PathBuf::from("/unused"),
|
PathBuf::from("/unused"),
|
||||||
)
|
)
|
||||||
|
|
@ -2759,6 +3085,7 @@ mod tests {
|
||||||
let second = bundle_inspection(
|
let second = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: Some(temp.path().to_path_buf()),
|
project: Some(temp.path().to_path_buf()),
|
||||||
|
json: false,
|
||||||
},
|
},
|
||||||
PathBuf::from("/unused"),
|
PathBuf::from("/unused"),
|
||||||
)
|
)
|
||||||
|
|
@ -2777,6 +3104,7 @@ mod tests {
|
||||||
let first = bundle_inspection(
|
let first = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: Some(first.path().to_path_buf()),
|
project: Some(first.path().to_path_buf()),
|
||||||
|
json: false,
|
||||||
},
|
},
|
||||||
PathBuf::from("/unused"),
|
PathBuf::from("/unused"),
|
||||||
)
|
)
|
||||||
|
|
@ -2784,6 +3112,7 @@ mod tests {
|
||||||
let second = bundle_inspection(
|
let second = bundle_inspection(
|
||||||
BundleInspectArgs {
|
BundleInspectArgs {
|
||||||
project: Some(second.path().to_path_buf()),
|
project: Some(second.path().to_path_buf()),
|
||||||
|
json: false,
|
||||||
},
|
},
|
||||||
PathBuf::from("/unused"),
|
PathBuf::from("/unused"),
|
||||||
)
|
)
|
||||||
|
|
@ -2926,6 +3255,71 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_first_json_mode_parses_for_primary_commands() {
|
||||||
|
for args in [
|
||||||
|
&["disasmer", "doctor", "--json"][..],
|
||||||
|
&["disasmer", "login", "--json"],
|
||||||
|
&["disasmer", "auth", "status", "--json"],
|
||||||
|
&[
|
||||||
|
"disasmer",
|
||||||
|
"agent",
|
||||||
|
"enroll",
|
||||||
|
"--public-key",
|
||||||
|
"key",
|
||||||
|
"--json",
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
"disasmer",
|
||||||
|
"key",
|
||||||
|
"add",
|
||||||
|
"--agent",
|
||||||
|
"agent",
|
||||||
|
"--public-key",
|
||||||
|
"key",
|
||||||
|
"--json",
|
||||||
|
],
|
||||||
|
&["disasmer", "project", "init", "--yes", "--json"],
|
||||||
|
&["disasmer", "inspect", "--json"],
|
||||||
|
&["disasmer", "build", "--json"],
|
||||||
|
&["disasmer", "bundle", "inspect", "--json"],
|
||||||
|
&["disasmer", "run", "--json"],
|
||||||
|
&["disasmer", "node", "attach", "--json"],
|
||||||
|
&["disasmer", "node", "enroll", "--json"],
|
||||||
|
&["disasmer", "process", "status", "--json"],
|
||||||
|
&["disasmer", "task", "list", "--json"],
|
||||||
|
&["disasmer", "logs", "--json"],
|
||||||
|
&["disasmer", "artifact", "list", "--json"],
|
||||||
|
&["disasmer", "artifact", "download", "artifact", "--json"],
|
||||||
|
&[
|
||||||
|
"disasmer", "artifact", "export", "artifact", "--to", "/tmp/out", "--json",
|
||||||
|
],
|
||||||
|
&["disasmer", "dap", "--plan", "--json"],
|
||||||
|
&["disasmer", "debug", "attach", "--json"],
|
||||||
|
&["disasmer", "quota", "status", "--json"],
|
||||||
|
&["disasmer", "admin", "status", "--json"],
|
||||||
|
] {
|
||||||
|
let _ = parse(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn human_report_is_text_not_json() {
|
||||||
|
let report = json!({
|
||||||
|
"command": "doctor",
|
||||||
|
"status": "ok",
|
||||||
|
"coordinator": "127.0.0.1:9443",
|
||||||
|
"next_actions": ["disasmer login --browser", "disasmer project init"],
|
||||||
|
});
|
||||||
|
let human = human_report(&report);
|
||||||
|
|
||||||
|
assert!(!human.trim_start().starts_with('{'));
|
||||||
|
assert!(human.contains("Disasmer doctor"));
|
||||||
|
assert!(human.contains("status: ok"));
|
||||||
|
assert!(human.contains("coordinator: 127.0.0.1:9443"));
|
||||||
|
assert!(human.contains("disasmer login --browser"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_init_select_and_status_use_local_project_config() {
|
fn project_init_select_and_status_use_local_project_config() {
|
||||||
let temp = tempfile::tempdir().unwrap();
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -2984,6 +3378,7 @@ mod tests {
|
||||||
BuildArgs {
|
BuildArgs {
|
||||||
project: Some(temp.path().to_path_buf()),
|
project: Some(temp.path().to_path_buf()),
|
||||||
output: None,
|
output: None,
|
||||||
|
json: false,
|
||||||
},
|
},
|
||||||
PathBuf::from("/unused"),
|
PathBuf::from("/unused"),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ cargo fmt --all --check
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
cargo build --workspace --bins
|
cargo build --workspace --bins
|
||||||
node scripts/docs-smoke.js
|
node scripts/docs-smoke.js
|
||||||
|
node scripts/cli-output-mode-smoke.js
|
||||||
node scripts/cli-login-smoke.js
|
node scripts/cli-login-smoke.js
|
||||||
node scripts/cli-browser-login-flow-smoke.js
|
node scripts/cli-browser-login-flow-smoke.js
|
||||||
node scripts/cli-install-smoke.js
|
node scripts/cli-install-smoke.js
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ function expect(source, name, pattern) {
|
||||||
|
|
||||||
const criteria = read("cli_acceptance_criteria.md");
|
const criteria = read("cli_acceptance_criteria.md");
|
||||||
const cli = read("crates/disasmer-cli/src/main.rs");
|
const cli = read("crates/disasmer-cli/src/main.rs");
|
||||||
|
const outputModeSmoke = read("scripts/cli-output-mode-smoke.js");
|
||||||
|
|
||||||
expect(criteria, "header", /^# Disasmer CLI-First MVP Acceptance Criteria/m);
|
expect(criteria, "header", /^# Disasmer CLI-First MVP Acceptance Criteria/m);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -76,6 +77,8 @@ assert.deepStrictEqual(
|
||||||
|
|
||||||
for (const [name, pattern] of [
|
for (const [name, pattern] of [
|
||||||
["top-level version metadata", /#\[command\(name = "disasmer", version, arg_required_else_help = true\)\]/],
|
["top-level version metadata", /#\[command\(name = "disasmer", version, arg_required_else_help = true\)\]/],
|
||||||
|
["human report renderer", /fn human_report\(value: &Value\) -> String/],
|
||||||
|
["shared report emitter", /fn emit_report<T: Serialize>\(report: &T, json_output: bool\) -> Result<\(\)>/],
|
||||||
["doctor command", /Doctor\(DoctorArgs\)/],
|
["doctor command", /Doctor\(DoctorArgs\)/],
|
||||||
["auth status command", /enum AuthCommands[\s\S]*Status\(AuthStatusArgs\)/],
|
["auth status command", /enum AuthCommands[\s\S]*Status\(AuthStatusArgs\)/],
|
||||||
["auth logout command", /enum AuthCommands[\s\S]*Logout\(AuthLogoutArgs\)/],
|
["auth logout command", /enum AuthCommands[\s\S]*Logout\(AuthLogoutArgs\)/],
|
||||||
|
|
@ -99,6 +102,8 @@ for (const [name, pattern] of [
|
||||||
for (const [name, pattern] of [
|
for (const [name, pattern] of [
|
||||||
["CLI parse coverage", /fn cli_first_mvp_command_surface_parses\(\)/],
|
["CLI parse coverage", /fn cli_first_mvp_command_surface_parses\(\)/],
|
||||||
["CLI version coverage", /fn top_level_version_is_available\(\)/],
|
["CLI version coverage", /fn top_level_version_is_available\(\)/],
|
||||||
|
["CLI JSON parse coverage", /fn cli_first_json_mode_parses_for_primary_commands\(\)/],
|
||||||
|
["CLI human output coverage", /fn human_report_is_text_not_json\(\)/],
|
||||||
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
||||||
["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/],
|
["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/],
|
||||||
["safe coordinator-required plans", /fn node_enroll_and_process_commands_have_safe_plan_without_coordinator\(\)/],
|
["safe coordinator-required plans", /fn node_enroll_and_process_commands_have_safe_plan_without_coordinator\(\)/],
|
||||||
|
|
@ -106,4 +111,26 @@ for (const [name, pattern] of [
|
||||||
expect(cli, name, pattern);
|
expect(cli, name, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [name, pattern] of [
|
||||||
|
["agent --json flag", /struct AgentEnrollArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["bundle inspect --json flag", /struct BundleInspectArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["build --json flag", /struct BuildArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["run --json flag", /struct RunArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["node attach --json flag", /struct AttachArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["DAP plan --json flag", /struct DapArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
["shared scope --json flag", /struct CliScopeArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
]) {
|
||||||
|
expect(cli, name, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, pattern] of [
|
||||||
|
["human default assertion", /default output should be human-readable text, not JSON/],
|
||||||
|
["login JSON mode", /\["login", "--coordinator", "https:\/\/coord\.example\.test", "--json"\]/],
|
||||||
|
["doctor human mode", /\["doctor"\]/],
|
||||||
|
["bundle inspect JSON mode", /\["bundle", "inspect", "--project", project, "--json"\]/],
|
||||||
|
["auth expiry posture", /token_expiry_posture[\s\S]*expires_at/],
|
||||||
|
]) {
|
||||||
|
expect(outputModeSmoke, name, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("CLI-first contract smoke passed");
|
console.log("CLI-first contract smoke passed");
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ try {
|
||||||
const inspection = JSON.parse(
|
const inspection = JSON.parse(
|
||||||
cp.execFileSync(
|
cp.execFileSync(
|
||||||
installedBin,
|
installedBin,
|
||||||
["bundle", "inspect", "--project", project],
|
["bundle", "inspect", "--project", project, "--json"],
|
||||||
{ cwd: repo, encoding: "utf8" }
|
{ cwd: repo, encoding: "utf8" }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ function runCli(args, env = {}) {
|
||||||
assert.strictEqual((await send(addr, { type: "ping" })).type, "pong");
|
assert.strictEqual((await send(addr, { type: "ping" })).type, "pong");
|
||||||
|
|
||||||
const { pid: cliPid, report } = await runCli(
|
const { pid: cliPid, report } = await runCli(
|
||||||
["run", "--coordinator", `${addr.host}:${addr.port}`, "--project", project],
|
["run", "--coordinator", `${addr.host}:${addr.port}`, "--project", project, "--json"],
|
||||||
{ DISASMER_AGENT_PUBLIC_KEY: agentPublicKey }
|
{ DISASMER_AGENT_PUBLIC_KEY: agentPublicKey }
|
||||||
);
|
);
|
||||||
assert(Number.isInteger(cliPid));
|
assert(Number.isInteger(cliPid));
|
||||||
|
|
@ -164,7 +164,8 @@ function runCli(args, env = {}) {
|
||||||
"run",
|
"run",
|
||||||
"--local",
|
"--local",
|
||||||
"--project",
|
"--project",
|
||||||
project
|
project,
|
||||||
|
"--json",
|
||||||
]);
|
]);
|
||||||
assert(Number.isInteger(autoCliPid));
|
assert(Number.isInteger(autoCliPid));
|
||||||
assert.strictEqual(autoReport.plan.entry, "build");
|
assert.strictEqual(autoReport.plan.entry, "build");
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ function disasmer(args) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const device = disasmer(["login", "--coordinator", coordinator]);
|
const device = disasmer(["login", "--coordinator", coordinator, "--json"]);
|
||||||
assert.strictEqual(device.coordinator, coordinator);
|
assert.strictEqual(device.coordinator, coordinator);
|
||||||
assert(device.human_flow.Device, "default human login should use device flow");
|
assert(device.human_flow.Device, "default human login should use device flow");
|
||||||
assert.strictEqual(device.human_flow.Device.verification_url, `${coordinator}/auth/device`);
|
assert.strictEqual(device.human_flow.Device.verification_url, `${coordinator}/auth/device`);
|
||||||
|
|
@ -27,14 +27,14 @@ assert.match(device.human_flow.Device.device_code, /^sha256:[a-f0-9]{64}$/);
|
||||||
assert.strictEqual(device.human_flow.Device.expires_in_seconds, 900);
|
assert.strictEqual(device.human_flow.Device.expires_in_seconds, 900);
|
||||||
assert.strictEqual(device.human_flow.Device.yields_long_lived_secret_directly, false);
|
assert.strictEqual(device.human_flow.Device.yields_long_lived_secret_directly, false);
|
||||||
|
|
||||||
const defaultDevice = disasmer(["login"]);
|
const defaultDevice = disasmer(["login", "--json"]);
|
||||||
assert.strictEqual(defaultDevice.coordinator, defaultOperatorEndpoint);
|
assert.strictEqual(defaultDevice.coordinator, defaultOperatorEndpoint);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
defaultDevice.human_flow.Device.verification_url,
|
defaultDevice.human_flow.Device.verification_url,
|
||||||
`${defaultOperatorEndpoint}/auth/device`
|
`${defaultOperatorEndpoint}/auth/device`
|
||||||
);
|
);
|
||||||
|
|
||||||
const browser = disasmer(["login", "--browser", "--plan", "--coordinator", coordinator]);
|
const browser = disasmer(["login", "--browser", "--plan", "--coordinator", coordinator, "--json"]);
|
||||||
assert.strictEqual(browser.coordinator, coordinator);
|
assert.strictEqual(browser.coordinator, coordinator);
|
||||||
assert(browser.human_flow.Browser, "browser login should be available for human users");
|
assert(browser.human_flow.Browser, "browser login should be available for human users");
|
||||||
assert.match(
|
assert.match(
|
||||||
|
|
|
||||||
77
scripts/cli-output-mode-smoke.js
Normal file
77
scripts/cli-output-mode-smoke.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const assert = require("assert");
|
||||||
|
const cp = require("child_process");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const repo = path.resolve(__dirname, "..");
|
||||||
|
const project = path.join(repo, "examples/launch-build-demo");
|
||||||
|
|
||||||
|
function disasmer(args, env = {}) {
|
||||||
|
return cp.execFileSync(
|
||||||
|
"cargo",
|
||||||
|
["run", "-q", "-p", "disasmer-cli", "--bin", "disasmer", "--", ...args],
|
||||||
|
{
|
||||||
|
cwd: repo,
|
||||||
|
encoding: "utf8",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...env,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function json(args, env) {
|
||||||
|
return JSON.parse(disasmer(args, env));
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertHuman(name, output, requiredPatterns) {
|
||||||
|
assert(
|
||||||
|
!output.trimStart().startsWith("{"),
|
||||||
|
`${name} default output should be human-readable text, not JSON`
|
||||||
|
);
|
||||||
|
for (const pattern of requiredPatterns) {
|
||||||
|
assert.match(output, pattern, `${name} human output missing ${pattern}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginHuman = disasmer(["login", "--coordinator", "https://coord.example.test"]);
|
||||||
|
assertHuman("login", loginHuman, [
|
||||||
|
/Disasmer login/,
|
||||||
|
/flow: device/,
|
||||||
|
/verify: https:\/\/coord\.example\.test\/auth\/device/,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const loginJson = json(["login", "--coordinator", "https://coord.example.test", "--json"]);
|
||||||
|
assert.strictEqual(loginJson.coordinator, "https://coord.example.test");
|
||||||
|
assert(loginJson.human_flow.Device);
|
||||||
|
|
||||||
|
const doctorHuman = disasmer(["doctor"]);
|
||||||
|
assertHuman("doctor", doctorHuman, [
|
||||||
|
/Disasmer doctor/,
|
||||||
|
/dependencies:/,
|
||||||
|
/auth:/,
|
||||||
|
/node capabilities:/,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const authJson = json(["auth", "status", "--json"], {
|
||||||
|
DISASMER_TOKEN: "token",
|
||||||
|
DISASMER_TOKEN_EXPIRES_AT: "2026-07-04T00:00:00Z",
|
||||||
|
});
|
||||||
|
assert.strictEqual(authJson.session.kind, "human");
|
||||||
|
assert.strictEqual(authJson.session.token_expiry_posture, "expires_at");
|
||||||
|
assert.strictEqual(authJson.session.expires_at, "2026-07-04T00:00:00Z");
|
||||||
|
|
||||||
|
const inspectHuman = disasmer(["bundle", "inspect", "--project", project]);
|
||||||
|
assertHuman("bundle inspect", inspectHuman, [
|
||||||
|
/Disasmer bundle inspect/,
|
||||||
|
/bundle: sha256:/,
|
||||||
|
/environments:/,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const inspectJson = json(["bundle", "inspect", "--project", project, "--json"]);
|
||||||
|
assert.strictEqual(inspectJson.project, project);
|
||||||
|
assert.match(inspectJson.metadata.identity, /^sha256:/);
|
||||||
|
|
||||||
|
console.log("CLI output mode smoke passed");
|
||||||
|
|
@ -158,6 +158,10 @@ for (const script of [publicAcceptance, publicSplit]) {
|
||||||
script.includes("node scripts/cli-install-smoke.js"),
|
script.includes("node scripts/cli-install-smoke.js"),
|
||||||
"public acceptance gates must run cli-install-smoke.js"
|
"public acceptance gates must run cli-install-smoke.js"
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
script.includes("node scripts/cli-output-mode-smoke.js"),
|
||||||
|
"public acceptance gates must run cli-output-mode-smoke.js"
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
script.includes("node scripts/cli-login-smoke.js"),
|
script.includes("node scripts/cli-login-smoke.js"),
|
||||||
"public acceptance gates must run cli-login-smoke.js"
|
"public acceptance gates must run cli-login-smoke.js"
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ const inspection = JSON.parse(
|
||||||
"bundle",
|
"bundle",
|
||||||
"inspect",
|
"inspect",
|
||||||
"--project",
|
"--project",
|
||||||
project
|
project,
|
||||||
|
"--json"
|
||||||
],
|
],
|
||||||
{ cwd: repo, encoding: "utf8" }
|
{ cwd: repo, encoding: "utf8" }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ function runAttach(addr, grant) {
|
||||||
grant,
|
grant,
|
||||||
"--cap",
|
"--cap",
|
||||||
"quic-direct",
|
"quic-direct",
|
||||||
|
"--json",
|
||||||
],
|
],
|
||||||
{ cwd: repo }
|
{ cwd: repo }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -883,7 +883,7 @@ async function main() {
|
||||||
const disasmerNode = executable(installDir, "disasmer-node");
|
const disasmerNode = executable(installDir, "disasmer-node");
|
||||||
const disasmerDap = executable(installDir, "disasmer-debug-dap");
|
const disasmerDap = executable(installDir, "disasmer-debug-dap");
|
||||||
|
|
||||||
const defaultLoginPlan = runJson(disasmer, ["login"], { cwd: checkout });
|
const defaultLoginPlan = runJson(disasmer, ["login", "--json"], { cwd: checkout });
|
||||||
assert.strictEqual(defaultLoginPlan.coordinator, serviceEndpoint);
|
assert.strictEqual(defaultLoginPlan.coordinator, serviceEndpoint);
|
||||||
|
|
||||||
const suffix = String(Date.now());
|
const suffix = String(Date.now());
|
||||||
|
|
@ -902,6 +902,7 @@ async function main() {
|
||||||
[
|
[
|
||||||
"login",
|
"login",
|
||||||
"--browser",
|
"--browser",
|
||||||
|
"--json",
|
||||||
"--complete-browser-code",
|
"--complete-browser-code",
|
||||||
oidcCode,
|
oidcCode,
|
||||||
"--oidc-issuer-url",
|
"--oidc-issuer-url",
|
||||||
|
|
@ -959,6 +960,7 @@ async function main() {
|
||||||
`${cliNode}-public-key`,
|
`${cliNode}-public-key`,
|
||||||
"--enrollment-grant",
|
"--enrollment-grant",
|
||||||
cliGrant.grant.grant_id,
|
cliGrant.grant.grant_id,
|
||||||
|
"--json",
|
||||||
],
|
],
|
||||||
{ cwd: checkout }
|
{ cwd: checkout }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ if [[ -n "${DISASMER_FORGEJO_TOKEN:-}" ]]; then
|
||||||
(cd "$tmp_dir" && node scripts/publish-public-release-dryrun.js)
|
(cd "$tmp_dir" && node scripts/publish-public-release-dryrun.js)
|
||||||
fi
|
fi
|
||||||
(cd "$tmp_dir" && node scripts/docs-smoke.js)
|
(cd "$tmp_dir" && node scripts/docs-smoke.js)
|
||||||
|
(cd "$tmp_dir" && node scripts/cli-output-mode-smoke.js)
|
||||||
(cd "$tmp_dir" && node scripts/cli-login-smoke.js)
|
(cd "$tmp_dir" && node scripts/cli-login-smoke.js)
|
||||||
(cd "$tmp_dir" && node scripts/cli-browser-login-flow-smoke.js)
|
(cd "$tmp_dir" && node scripts/cli-browser-login-flow-smoke.js)
|
||||||
(cd "$tmp_dir" && node scripts/cli-install-smoke.js)
|
(cd "$tmp_dir" && node scripts/cli-install-smoke.js)
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ assert.deepStrictEqual(inspectCommand.args.slice(0, 8), [
|
||||||
assert(inspectCommand.args.includes("inspect"));
|
assert(inspectCommand.args.includes("inspect"));
|
||||||
assert(inspectCommand.args.includes("--project"));
|
assert(inspectCommand.args.includes("--project"));
|
||||||
assert(inspectCommand.args.includes(root));
|
assert(inspectCommand.args.includes(root));
|
||||||
|
assert(inspectCommand.args.includes("--json"));
|
||||||
|
|
||||||
const refreshed = extension.refreshBundleBeforeLaunch(root, "/repo", (command, args, options) => {
|
const refreshed = extension.refreshBundleBeforeLaunch(root, "/repo", (command, args, options) => {
|
||||||
assert.strictEqual(command, "cargo");
|
assert.strictEqual(command, "cargo");
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,8 @@ function runWindowsAttach(addr, grant) {
|
||||||
"--enrollment-grant",
|
"--enrollment-grant",
|
||||||
grant,
|
grant,
|
||||||
"--cap",
|
"--cap",
|
||||||
"windows-command-dev"
|
"windows-command-dev",
|
||||||
|
"--json"
|
||||||
],
|
],
|
||||||
{ cwd: repo }
|
{ cwd: repo }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ function bundleInspectCommand(projectRoot, adapterWorkspace = path.resolve(__dir
|
||||||
if (workspaceCli) {
|
if (workspaceCli) {
|
||||||
return {
|
return {
|
||||||
command: workspaceCli,
|
command: workspaceCli,
|
||||||
args: ["bundle", "inspect", "--project", projectRoot],
|
args: ["bundle", "inspect", "--project", projectRoot, "--json"],
|
||||||
options: {
|
options: {
|
||||||
cwd: projectRoot,
|
cwd: projectRoot,
|
||||||
encoding: "utf8"
|
encoding: "utf8"
|
||||||
|
|
@ -108,7 +108,8 @@ function bundleInspectCommand(projectRoot, adapterWorkspace = path.resolve(__dir
|
||||||
"bundle",
|
"bundle",
|
||||||
"inspect",
|
"inspect",
|
||||||
"--project",
|
"--project",
|
||||||
projectRoot
|
projectRoot,
|
||||||
|
"--json"
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
cwd: adapterWorkspace,
|
cwd: adapterWorkspace,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue