Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
check-parity-inventory \
check-cli-metadata \
check-todo-args \
check-compatibility-dashboard \
upstream-compatibility

RUST_MANIFEST := cmd/devcontainer/Cargo.toml
RELEASE_BINARY := ./cmd/devcontainer/target/release/devcontainer

tests: rust-fmt rust-clippy rust-check rust-tests build-release standalone-artifact-smoke native-only-startup-contract command-matrix-drift-check schema-drift-check parity-harness no-node-runtime check-parity-inventory check-cli-metadata check-todo-args upstream-compatibility
tests: rust-fmt rust-clippy rust-check rust-tests build-release standalone-artifact-smoke native-only-startup-contract command-matrix-drift-check schema-drift-check parity-harness no-node-runtime check-parity-inventory check-cli-metadata check-todo-args check-compatibility-dashboard upstream-compatibility

rust-fmt:
cargo fmt --manifest-path $(RUST_MANIFEST) --all -- --check
Expand Down Expand Up @@ -62,5 +63,8 @@ check-cli-metadata:
check-todo-args:
node build/generate-todo-args.js --check

check-compatibility-dashboard:
node build/generate-compatibility-dashboard.js --check

upstream-compatibility:
node build/check-upstream-compatibility.js
10 changes: 0 additions & 10 deletions TODO_ARGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,3 @@ Unsupported CLI args for the current pinned upstream command surface.
- Upstream commit: `39685cf1aa58b5b11e90085bd32562fad61f4103`
- Source: `upstream/src/spec-node/devContainersSpecCLI.ts`

## `outdated`

- `--log-level`: Log level for the --terminal-log-file. When set to trace, the log level for --log-file will also be set to trace. [choices: "info", "debug", "trace"] [default: "info"]
- `--terminal-columns`: Number of columns to render the output for. This is required for some of the subprocesses to correctly render their output. [number]
- `--terminal-rows`: Number of rows to render the output for. This is required for some of the subprocesses to correctly render their output. [number]

## `upgrade`

- `--log-level`: Log level. [choices: "error", "info", "debug", "trace"] [default: "info"]

20 changes: 20 additions & 0 deletions build/generate-parity-inventory.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,26 @@ const OPTION_EVIDENCE_OVERRIDES = {
'cmd/devcontainer/src/runtime/lifecycle/selection.rs',
],
},
'outdated': {
'log-level': [
'cmd/devcontainer/src/commands/common/args.rs',
'cmd/devcontainer/src/commands/configuration/upgrade.rs',
],
'terminal-columns': [
'cmd/devcontainer/src/commands/common/args.rs',
'cmd/devcontainer/src/commands/configuration/upgrade.rs',
],
'terminal-rows': [
'cmd/devcontainer/src/commands/common/args.rs',
'cmd/devcontainer/src/commands/configuration/upgrade.rs',
],
},
'upgrade': {
'log-level': [
'cmd/devcontainer/src/commands/common/args.rs',
'cmd/devcontainer/src/commands/configuration/upgrade.rs',
],
},
};

function readFile(relativePath) {
Expand Down
27 changes: 12 additions & 15 deletions cmd/devcontainer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::OnceLock;

use serde::Deserialize;

use crate::output::{self, LogFormat};
use crate::output::{self, CommandLogLevel, LogFormat};

const CLI_METADATA_JSON: &str = include_str!("cli_metadata.json");
const UNSUPPORTED_MARKER: &str = " [not yet implemented in native Rust CLI]";
Expand Down Expand Up @@ -144,7 +144,10 @@ pub fn emit_log(log_format: &str, message: &str) {
"json" => LogFormat::Json,
_ => LogFormat::Text,
};
println!("{}", output::render_log(format, "info", message));
println!(
"{}",
output::render_log(format, CommandLogLevel::Info, message)
);
}

pub fn is_command_help_request(args: &[String]) -> bool {
Expand Down Expand Up @@ -250,28 +253,22 @@ mod tests {
}

#[test]
fn metadata_tracks_unsupported_flags() {
fn metadata_tracks_no_remaining_visible_unsupported_flags() {
let command = command_help("outdated").expect("outdated metadata");
assert!(command
.unsupported_options
.contains(&"log-level".to_string()));
assert!(command.unsupported_options.is_empty());

let up = command_help("up").expect("up metadata");
assert!(!up
.unsupported_options
.contains(&"omit-syntax-directive".to_string()));
let upgrade = command_help("upgrade").expect("upgrade metadata");
assert!(upgrade.unsupported_options.is_empty());
}

#[test]
fn detects_unsupported_command_options() {
fn supported_command_options_are_not_reported_as_unsupported() {
let error = unsupported_argument_error(
"outdated",
&["--log-level".to_string(), "trace".to_string()],
)
.expect("unsupported error");
);

assert!(error.contains("--log-level"));
assert!(error.contains("devcontainer outdated"));
assert!(error.is_none());
}

#[test]
Expand Down
10 changes: 2 additions & 8 deletions cmd/devcontainer/src/cli_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2256,11 +2256,7 @@
}
],
"positionals": [],
"unsupportedOptions": [
"log-level",
"terminal-columns",
"terminal-rows"
],
"unsupportedOptions": [],
"unsupportedPositionals": []
},
{
Expand Down Expand Up @@ -2417,9 +2413,7 @@
}
],
"positionals": [],
"unsupportedOptions": [
"log-level"
],
"unsupportedOptions": [],
"unsupportedPositionals": []
},
{
Expand Down
3 changes: 2 additions & 1 deletion cmd/devcontainer/src/commands/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ mod manifest;
pub(crate) use args::{
has_flag, parse_bool_option, parse_json_string_array_option, parse_option_value,
parse_option_values, remote_env_overrides, runtime_options, runtime_process_request,
secrets_env,
secrets_env, validate_choice_option, validate_number_option, validate_option_values,
validate_paired_options,
};
pub(crate) use config_resolution::{
load_resolved_config, resolve_override_config_path, resolve_read_configuration_path,
Expand Down
89 changes: 88 additions & 1 deletion cmd/devcontainer/src/commands/common/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,53 @@ pub(crate) fn validate_option_values(args: &[String], options: &[&str]) -> Resul
Ok(())
}

pub(crate) fn validate_choice_option(
args: &[String],
option: &str,
choices: &[&str],
) -> Result<(), String> {
validate_option_values(args, &[option])?;

for value in parse_option_values(args, option) {
if !choices.contains(&value.as_str()) {
return Err(format!(
"Invalid value for option {option}: {value}. Expected one of: {}",
choices.join(", ")
));
}
}

Ok(())
}

pub(crate) fn validate_number_option(args: &[String], option: &str) -> Result<(), String> {
validate_option_values(args, &[option])?;

for value in parse_option_values(args, option) {
if value.parse::<f64>().is_err() {
return Err(format!(
"Invalid value for option {option}: {value}. Expected a number."
));
}
}

Ok(())
}

pub(crate) fn validate_paired_options(
args: &[String],
left: &str,
right: &str,
) -> Result<(), String> {
let has_left = has_flag(args, left);
let has_right = has_flag(args, right);
if has_left != has_right {
return Err(format!("Options {left} and {right} must be used together."));
}

Ok(())
}

pub(crate) fn parse_option_values(args: &[String], option: &str) -> Vec<String> {
let mut values = Vec::new();
let mut index = 0;
Expand Down Expand Up @@ -211,7 +258,9 @@ mod tests {

use crate::process_runner::ProcessLogLevel;

use super::runtime_options;
use super::{
runtime_options, validate_choice_option, validate_number_option, validate_paired_options,
};

#[test]
fn runtime_options_collect_shared_runtime_flags() {
Expand Down Expand Up @@ -305,4 +354,42 @@ mod tests {
assert!(!options.skip_feature_auto_mapping);
assert!(!options.stop_for_personalization);
}

#[test]
fn choice_options_reject_unknown_values() {
let error = validate_choice_option(
&["--log-level".to_string(), "warning".to_string()],
"--log-level",
&["info", "debug", "trace"],
)
.expect_err("invalid choice");

assert!(error.contains("--log-level"));
assert!(error.contains("warning"));
}

#[test]
fn number_options_require_numeric_values() {
let error = validate_number_option(
&["--terminal-columns".to_string(), "wide".to_string()],
"--terminal-columns",
)
.expect_err("invalid number");

assert!(error.contains("--terminal-columns"));
assert!(error.contains("wide"));
}

#[test]
fn paired_options_require_both_flags() {
let error = validate_paired_options(
&["--terminal-columns".to_string(), "120".to_string()],
"--terminal-columns",
"--terminal-rows",
)
.expect_err("missing paired option");

assert!(error.contains("--terminal-columns"));
assert!(error.contains("--terminal-rows"));
}
}
Loading
Loading