From a184eb5bfeeff2e5618303d051340536b3a45dbc Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Fri, 11 Sep 2026 15:37:19 -0700 Subject: [PATCH 1/2] Localize dsc-lib tracing messages Move hard-coded tracing text into the en-us locale and render it through rust-i18n. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lib/dsc-lib/locales/en-us.toml | 28 +++++++++++++++++ lib/dsc-lib/src/configure/depends_on.rs | 2 +- lib/dsc-lib/src/configure/mod.rs | 12 ++++---- .../src/discovery/command_discovery.rs | 28 ++++++++--------- .../src/dscresources/command_resource.rs | 30 ++++++++++++------- lib/dsc-lib/src/dscresources/dscresource.rs | 6 ++-- lib/dsc-lib/src/extensions/discover.rs | 2 +- lib/dsc-lib/src/functions/max.rs | 2 +- lib/dsc-lib/src/functions/mod_function.rs | 2 +- 9 files changed, 75 insertions(+), 37 deletions(-) diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index cfde157b0..7ec10b494 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -33,6 +33,7 @@ dependencyTypeMismatch = "'dependsOn' resource type '%{resource_type}' does not resourceNotInOrder = "Resource not found in order" dependencyNotInOrder = "Dependency not found in order" circularDependency = "Circular dependency detected for resource named '%{resource}'" +gettingInvocationOrder = "Getting resource invocation order" invocationOrder = "Resource invocation order" [configure.export_filter] @@ -105,6 +106,10 @@ metadataRefreshEnvNoContext = "Resource returned '_refreshEnv' which indicates e copyDeprecated = "Copy for loop '%{name}' is deprecated and will be removed in a future release. See https://github.com/PowerShell/DSC/issues/1429 for more details." secureStringMustBeString = "Secure string parameter '%{name}' must be a string" secureObjectMustBeObject = "Secure object parameter '%{name}' must be an object" +prefixUserPath = "Prefixing user PATH '%{value}' to system PATH '%{system_path}'" +systemPathNotFound = "System PATH not found, using user PATH '%{value}' as PATH" +resourceType = "resource_type %{resource_type}" +validatingParameter = "Validating parameter '%{name}'" [configure.parameters] importingParametersFromComplexInput = "Importing parameters from complex input" @@ -153,6 +158,20 @@ conditionNotMet = "Condition '%{condition}' not met, skipping manifest at '%{pat adaptedResourcePathNotFound = "Adapted resource '%{resource}' path not found: %{path}" invalidManifestFileName = "Invalid manifest file name '%{path}'" failedLoadManifest = "Failed to load manifest: %{err}" +restrictedPath = "DSC_RESTRICTED_PATH: %{path}" +resourcePath = "DSC_RESOURCE_PATH: %{path}" +adapterNameFilter = "Using type name filter '%{filter}' as filter for adapter name" +adapterNameFilterForDiscovery = "Using type name filter '%{filter}' for adapter name" +resourceNameFilter = "Using type name filter '%{filter}' as filter for resource name" +adapterListFailed = "Adapter failed to list resources with exit code %{exit_code}: %{stderr}" +failedParseResource = "Failed to parse resource: %{line} -> %{err}" +adapterListedResources = "Adapter '%{adapter}' listed %{count} resources" +savingLookupTable = "Saving lookup table with %{count} items to %{path}" +failedWriteLookupTable = "Unable to write lookup_table file %{path}" +failedCreateLookupTableDirectory = "Unable to create parent directories of the lookup_table file %{path}" +failedGetLookupTableDirectory = "Unable to get directory of the lookup_table file %{path}" +failedSerializeLookupTable = "Unable to serialize lookup_table to json" +readLookupTable = "Read %{count} items into lookup table from %{path}" [dscresources.commandResource] invokeGet = "Invoking get for '%{resource}'" @@ -210,6 +229,9 @@ noAdaptedContent = "No adapted content available for resource '%{resource}'" invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}" exportFilteringNotSupported = "Resource '%{resource}' does not support export filtering" retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache" +writingStdin = "Writing to command STDIN: %{input}" +processTrace = "PID %{process_id}: %{message}" +processTraceWithTarget = "PID %{process_id}: %{target}: %{line_number}: %{message}" [dscresources.dscresource] invokeGet = "Invoking get for '%{resource}'" @@ -250,6 +272,9 @@ adapterDoesNotSupportDelete = "Adapter '%{adapter}' does not support delete oper validatingAgainstSchema = "Validating against resource schema" deprecationMessage = "Resource '%{resource}' is deprecated: %{message}" retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache" +exportResult = "Export result: %{result}" +json = "JSON: %{json}" +schema = "Schema: %{schema}" [dscresources.resource_manifest] resourceManifestSchemaTitle = "Resource manifest schema URI" @@ -272,6 +297,7 @@ secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines importProcessingOutput = "Processing output from extension '%{extension}'" deprecationMessage = "Extension '%{extension}' is deprecated: %{message}" failedLoadManifest = "Extension '%{extension}' failed to load manifest: %{err}" +manifestImported = "Manifest imported from extension %{extension}" [extensions.extension_manifest] extensionManifestSchemaTitle = "Extension manifest schema URI" @@ -575,6 +601,7 @@ syntax = "map( , )" [functions.max] description = "Returns the largest number from a list of numbers" +invoked = "max function" syntax = "max( , [number], ... )" emptyArray = "Array cannot be empty" integersOnly = "Array must contain only integers" @@ -590,6 +617,7 @@ noMin = "Unable to find min value" [functions.mod] description = "Divides the first number by the second and returns the remainder" +invoked = "mod function" syntax = "mod( , )" divideByZero = "Cannot divide by zero" diff --git a/lib/dsc-lib/src/configure/depends_on.rs b/lib/dsc-lib/src/configure/depends_on.rs index 90c1dc56f..8ed71c871 100644 --- a/lib/dsc-lib/src/configure/depends_on.rs +++ b/lib/dsc-lib/src/configure/depends_on.rs @@ -26,7 +26,7 @@ use tracing::{debug, warn}; /// /// * `DscError::Validation` - The configuration is invalid pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statement, context: &mut Context) -> Result, DscError> { - debug!("Getting resource invocation order"); + debug!("{}", t!("configure.dependsOn.gettingInvocationOrder")); let mut order: Vec = Vec::new(); for resource in &config.resources { // validate that the resource isn't specified more than once in the config diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 5a898be22..3c01965a2 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -433,10 +433,10 @@ fn get_metadata_from_result(mut context: Option<&mut Context>, properties: &mut for (key, value) in user_env_vars { if key == "PATH" { if let Some(system_path) = env_vars.get(&key) { - trace!("Prefixing user PATH '{value}' to system PATH '{system_path}'"); + trace!("{}", t!("configure.mod.prefixUserPath", value = value, system_path = system_path)); env_vars.insert(key, format!("{};{}", value, system_path)); } else { - trace!("System PATH not found, using user PATH '{value}' as PATH"); + trace!("{}", t!("configure.mod.systemPathNotFound", value = value)); env_vars.insert(key, value.to_string()); } } else { @@ -684,7 +684,7 @@ impl Configurator { let adapter = get_require_adapter_from_directive(&resource.directives); find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(&resource, &dsc_resource.kind)?; - debug!("resource_type {}", &resource.resource_type); + debug!("{}", t!("configure.mod.resourceType", resource_type = &resource.resource_type)); // see if the properties contains `_exist` and is false let exist = match &properties { Some(property_map) => { @@ -873,7 +873,7 @@ impl Configurator { let adapter = get_require_adapter_from_directive(&resource.directives); find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(&resource, &dsc_resource.kind)?; - debug!("resource_type {}", &resource.resource_type); + debug!("{}", t!("configure.mod.resourceType", resource_type = &resource.resource_type)); let expected = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.expectedState", state = expected)); let start_datetime = chrono::Local::now(); @@ -963,7 +963,7 @@ impl Configurator { let adapter = get_require_adapter_from_directive(&resource.directives); find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(resource, &dsc_resource.kind)?; - debug!("resource_type {}", &resource.resource_type); + debug!("{}", t!("configure.mod.resourceType", resource_type = &resource.resource_type)); let input = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.exportInput", input = input)); let export_result = match add_resource_export_results_to_configuration( @@ -1100,7 +1100,7 @@ impl Configurator { let input_parameters: HashMap = import_parameters(parameters_input)?; for (name, value) in input_parameters { if let Some(constraint) = parameters.get(&name) { - debug!("Validating parameter '{name}'"); + debug!("{}", t!("configure.mod.validatingParameter", name = name)); check_length(&name, &value, constraint)?; check_allowed_values(&name, &value, constraint)?; check_number_limits(&name, &value, constraint)?; diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 3dd8f872f..717fc0aa3 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -139,7 +139,7 @@ impl CommandDiscovery { let dsc_restricted_path = env::var_os("DSC_RESTRICTED_PATH"); let dsc_resource_path = env::var_os("DSC_RESOURCE_PATH"); if resource_path_setting.allow_env_override && let Some(restricted_path) = &dsc_restricted_path { - debug!("DSC_RESTRICTED_PATH: {:?}", restricted_path.to_string_lossy()); + debug!("{}", t!("discovery.commandDiscovery.restrictedPath", path = restricted_path.to_string_lossy() : {:?})); paths.append(&mut env::split_paths(&restricted_path).collect::>()); // when using restricted path, intent is to isolate the search of manifests and executables to the restricted path @@ -152,7 +152,7 @@ impl CommandDiscovery { return Err(DscError::Operation(t!("discovery.commandDiscovery.failedJoinRestrictedPath").to_string())); } } else if resource_path_setting.allow_env_override && let Some(resource_path) = &dsc_resource_path { - debug!("DSC_RESOURCE_PATH: {:?}", resource_path.to_string_lossy()); + debug!("{}", t!("discovery.commandDiscovery.resourcePath", path = resource_path.to_string_lossy() : {:?})); paths.append(&mut env::split_paths(&resource_path).collect::>()); // just add exe home to PATH env var if not already in PATH env var @@ -238,7 +238,7 @@ impl ResourceDiscovery for CommandDiscovery { info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter.to_string())); - debug!("Using type name filter '{filter}' for adapter name"); + debug!("{}", t!("discovery.commandDiscovery.adapterNameFilterForDiscovery", filter = filter)); let mut progress = ProgressBar::new(1, self.progress_format)?; match kind { DiscoveryKind::Resource => { @@ -376,9 +376,9 @@ impl ResourceDiscovery for CommandDiscovery { } let adapters = locked_clone!(ADAPTERS); - debug!("Using type name filter '{adapter_filter}' as filter for adapter name"); + debug!("{}", t!("discovery.commandDiscovery.adapterNameFilter", filter = adapter_filter)); - debug!("Using type name filter '{name_filter}' as filter for resource name"); + debug!("{}", t!("discovery.commandDiscovery.resourceNameFilter", filter = name_filter)); let mut progress = ProgressBar::new(adapters.len() as u64, self.progress_format)?; progress.write_activity("Searching for adapted resources"); @@ -416,7 +416,7 @@ impl ResourceDiscovery for CommandDiscovery { if exit_code != 0 { // in case of failure, log and continue - warn!("Adapter failed to list resources with exit code {exit_code}: {stderr}"); + warn!("{}", t!("discovery.commandDiscovery.adapterListFailed", exit_code = exit_code, stderr = stderr)); continue; } @@ -434,14 +434,14 @@ impl ResourceDiscovery for CommandDiscovery { } }, Result::Err(err) => { - warn!("Failed to parse resource: {line} -> {err}"); + warn!("{}", t!("discovery.commandDiscovery.failedParseResource", line = line, err = err)); } } } } adapter_progress.write_increment(1); - debug!("Adapter '{}' listed {} resources", adapter_name, adapter_resources_count); + debug!("{}", t!("discovery.commandDiscovery.adapterListedResources", adapter = adapter_name, count = adapter_resources_count)); } } @@ -1058,22 +1058,22 @@ fn save_adapted_resources_lookup_table(lookup_table: &HashMap) { if let Ok(lookup_table_json) = serde_json::to_string(&lookup_table) { let file_path = get_lookup_table_file_path(); - debug!("Saving lookup table with {} items to {:?}", lookup_table.len(), file_path); + debug!("{}", t!("discovery.commandDiscovery.savingLookupTable", count = lookup_table.len(), path = file_path : {:?})); let path = std::path::Path::new(&file_path); if let Some(prefix) = path.parent() { if create_dir_all(prefix).is_ok() { if write(file_path.clone(), lookup_table_json).is_err() { - info!("Unable to write lookup_table file {file_path:?}"); + info!("{}", t!("discovery.commandDiscovery.failedWriteLookupTable", path = file_path : {:?})); } } else { - info!("Unable to create parent directories of the lookup_table file {file_path:?}"); + info!("{}", t!("discovery.commandDiscovery.failedCreateLookupTableDirectory", path = file_path : {:?})); } } else { - info!("Unable to get directory of the lookup_table file {file_path:?}"); + info!("{}", t!("discovery.commandDiscovery.failedGetLookupTableDirectory", path = file_path : {:?})); } } else { - info!("Unable to serialize lookup_table to json"); + info!("{}", t!("discovery.commandDiscovery.failedSerializeLookupTable")); } } @@ -1086,7 +1086,7 @@ fn load_adapted_resources_lookup_table() -> HashMap Err(_) => { HashMap::new() } }; - debug!("Read {} items into lookup table from {:?}", lookup_table.len(), file_path); + debug!("{}", t!("discovery.commandDiscovery.readLookupTable", count = lookup_table.len(), path = file_path : {:?})); lookup_table } diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 8c6325a89..92bf673d9 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -865,7 +865,7 @@ async fn run_process_async(executable: &str, args: Option>, input: O let mut stderr_reader = BufReader::new(stderr).lines(); if let Some(input) = input { - trace!("Writing to command STDIN: {input}"); + trace!("{}", t!("dscresources.commandResource.writingStdin", input = input)); let Some(mut stdin) = child.stdin.take() else { return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStdin").to_string(), executable.to_string())); }; @@ -1286,9 +1286,19 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str 0 }; let trace_message = if include_target { - format!("PID {process_id}: {target}: {line_number}: {}", trace_object.fields.message) + t!( + "dscresources.commandResource.processTraceWithTarget", + process_id = process_id, + target = target, + line_number = line_number, + message = trace_object.fields.message + ).to_string() } else { - format!("PID {process_id}: {}", trace_object.fields.message) + t!( + "dscresources.commandResource.processTrace", + process_id = process_id, + message = trace_object.fields.message + ).to_string() }; match trace_object.level { TraceLevel::Error => { @@ -1310,23 +1320,23 @@ pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str } else if let Ok(json_obj) = serde_json::from_str::(trace_line) { if let Some(msg) = json_obj.get("error") { - error!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); + error!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = msg.as_str().unwrap_or_default())); } else if let Some(msg) = json_obj.get("warn") { - warn!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); + warn!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = msg.as_str().unwrap_or_default())); } else if let Some(msg) = json_obj.get("info") { - info!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); + info!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = msg.as_str().unwrap_or_default())); } else if let Some(msg) = json_obj.get("debug") { - debug!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); + debug!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = msg.as_str().unwrap_or_default())); } else if let Some(msg) = json_obj.get("trace") { - trace!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); + trace!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = msg.as_str().unwrap_or_default())); } else { // the line is a valid json, but not one of standard trace lines - return it as filtered stderr_line - trace!("PID {process_id}: {trace_line}"); + trace!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = trace_line)); return trace_line; } } else { // the line is not a valid json - return it as filtered stderr_line - trace!("PID {process_id}: {}", trace_line); + trace!("{}", t!("dscresources.commandResource.processTrace", process_id = process_id, message = trace_line)); return trace_line; } } diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index b01cccb33..9f8e8a34d 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -293,7 +293,7 @@ impl DscResource { let mut export_result = ExportResult { actual_state: Vec::new(), }; - debug!("Export result: {}", serde_json::to_string(&configuration)?); + debug!("{}", t!("dscresources.dscresource.exportResult", result = serde_json::to_string(&configuration)?)); for resource in configuration.resources { let Some(properties) = resource.properties else { return Err(DscError::Operation(t!("dscresources.dscresource.invokeExportReturnedNoResult", resource = self.type_name).to_string())); @@ -913,8 +913,8 @@ pub fn validate_properties(resource: &DscResource, properties: &Value) -> Result /// * `DscError` - The JSON is invalid pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), DscError> { debug!("{}: {source}", t!("dscresources.dscresource.validatingSchema")); - trace!("JSON: {json}"); - trace!("Schema: {schema}"); + trace!("{}", t!("dscresources.dscresource.json", json = json)); + trace!("{}", t!("dscresources.dscresource.schema", schema = schema)); let compiled_schema = match Validator::new(schema) { Ok(compiled_schema) => compiled_schema, Err(err) => { diff --git a/lib/dsc-lib/src/extensions/discover.rs b/lib/dsc-lib/src/extensions/discover.rs index 50adfd376..92bb6174f 100644 --- a/lib/dsc-lib/src/extensions/discover.rs +++ b/lib/dsc-lib/src/extensions/discover.rs @@ -114,7 +114,7 @@ impl DscExtension { match discover_result.path_or_content { ManifestKind::ManifestContent(manifest_value) => { let imported_manifests = load_manifest_content(&manifest_value)?; - info!("Manifest imported from extension {}", self.type_name); + info!("{}", t!("extensions.dscextension.manifestImported", extension = self.type_name)); for imported_manifest in imported_manifests { if let ImportedManifest::Resource(resource) = imported_manifest { resources.push(resource); diff --git a/lib/dsc-lib/src/functions/max.rs b/lib/dsc-lib/src/functions/max.rs index 9b106643d..d1200b3b0 100644 --- a/lib/dsc-lib/src/functions/max.rs +++ b/lib/dsc-lib/src/functions/max.rs @@ -28,7 +28,7 @@ impl Function for Max { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("max function"); + debug!("{}", t!("functions.max.invoked")); if args.len() == 1 { if let Some(array) = args[0].as_array() { find_max(array) diff --git a/lib/dsc-lib/src/functions/mod_function.rs b/lib/dsc-lib/src/functions/mod_function.rs index 1c5e60c12..b713d0cab 100644 --- a/lib/dsc-lib/src/functions/mod_function.rs +++ b/lib/dsc-lib/src/functions/mod_function.rs @@ -31,7 +31,7 @@ impl Function for Mod { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("mod function"); + debug!("{}", t!("functions.mod.invoked")); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { if arg2 == 0 { return Err(DscError::Parser(t!("functions.mod.divideByZero").to_string())); From 6720bd3797aa8a4160aae878663004a96c08d948 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Fri, 11 Sep 2026 15:37:19 -0700 Subject: [PATCH 2/2] Require build.ps1 for repository builds Add repository-wide Copilot guidance and align the build documentation to avoid direct Cargo build commands. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 8 ++++++++ .github/instructions/instructions.md | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..0df474e08 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# Repository instructions + +## Building + +- Always use `./build.ps1` from the repository root to build or compile the project. +- Do not invoke Cargo directly for build validation, including `cargo build` or `cargo check`. +- For targeted builds, use `./build.ps1 -Project ` instead of a package-scoped Cargo command. +- Use the relevant `build.ps1` switches for linting, testing, release builds, documentation, auditing, and other supported validation. diff --git a/.github/instructions/instructions.md b/.github/instructions/instructions.md index 3c0d951b2..071e813b9 100644 --- a/.github/instructions/instructions.md +++ b/.github/instructions/instructions.md @@ -72,7 +72,10 @@ For a quick build and test on your current platform: ## Building the Project -The main build script is `build.ps1`, which orchestrates the entire build process. +The main build script is `build.ps1`, which orchestrates the entire build process. Always use +`./build.ps1` from the repository root to build or compile the project. Do not invoke Cargo +directly for build validation, including `cargo build` or `cargo check`. For targeted builds, +use `./build.ps1 -Project `. ### Basic Build