Skip to content

Commit 0aa186a

Browse files
jamesadevineCopilot
andcommitted
fix(compile): address PR review feedback for job/stage targets
- Fix path corruption: use Path::with_extension instead of string replace to derive .lock.yml paths in header comments (avoids corrupting directory names containing '.md') - Fix repos header docs: only check front_matter.repositories (populated by resolve_repos() before compile) instead of also checking front_matter.repos (raw input, may be empty) - Fix Unicode in stage prefix: use is_ascii_alphanumeric() to split on non-ASCII characters, ensuring ADO-valid identifiers (ADO requires [A-Za-z0-9_] for job/stage names) - Add test for Unicode stripping in generate_stage_prefix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d178081 commit 0aa186a

3 files changed

Lines changed: 18 additions & 9 deletions

File tree

src/compile/common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,9 +1008,10 @@ pub const DEFAULT_POOL: &str = "AZS-1ES-L-MMS-ubuntu-22.04";
10081008
/// - `"my-agent-123"` → `"MyAgent123"`
10091009
/// - `""` → `"Agent"` (fallback)
10101010
/// - `"123start"` → `"_123start"` (prefix underscore for leading digit)
1011+
/// - `"über-agent"` → `"BerAgent"` (non-ASCII stripped; ADO requires `[A-Za-z0-9_]`)
10111012
pub fn generate_stage_prefix(name: &str) -> String {
10121013
let pascal: String = name
1013-
.split(|c: char| !c.is_alphanumeric())
1014+
.split(|c: char| !c.is_ascii_alphanumeric())
10141015
.filter(|s| !s.is_empty())
10151016
.map(|word| {
10161017
let mut chars = word.chars();

src/compile/job.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,27 +115,28 @@ impl Compiler for JobCompiler {
115115
/// Generate the header comment block for job-level templates.
116116
fn generate_job_header(input_path: &Path, front_matter: &FrontMatter) -> String {
117117
let base_header = generate_header_comment(input_path);
118-
let source_path = input_path
118+
let lock_path = input_path
119+
.with_extension("lock.yml")
119120
.to_string_lossy()
120121
.replace('\\', "/");
121122

122123
let mut header = base_header;
123124
header.push_str("#\n");
124125
header.push_str("# Job-level ADO template. Include in your pipeline:\n");
125126
header.push_str("#\n");
126-
header.push_str(&format!("# jobs:\n"));
127-
header.push_str(&format!("# - template: {}\n", source_path.replace(".md", ".lock.yml")));
127+
header.push_str("# jobs:\n");
128+
header.push_str(&format!("# - template: {}\n", lock_path));
128129
header.push_str("#\n");
129130
header.push_str("# Or inside a stage in a multi-stage pipeline:\n");
130131
header.push_str("#\n");
131132
header.push_str("# stages:\n");
132133
header.push_str("# - stage: AgenticReview\n");
133134
header.push_str("# dependsOn: Build\n");
134135
header.push_str("# jobs:\n");
135-
header.push_str(&format!("# - template: {}\n", source_path.replace(".md", ".lock.yml")));
136+
header.push_str(&format!("# - template: {}\n", lock_path));
136137

137138
// Document required resources if agent uses repos
138-
if !front_matter.repos.is_empty() || !front_matter.repositories.is_empty() {
139+
if !front_matter.repositories.is_empty() {
139140
header.push_str("#\n");
140141
header.push_str("# Add these repositories to your pipeline's resources: block:\n");
141142
header.push_str("#\n");
@@ -186,4 +187,10 @@ mod tests {
186187
fn test_generate_stage_prefix_underscores() {
187188
assert_eq!(generate_stage_prefix("code_review_agent"), "CodeReviewAgent");
188189
}
190+
191+
#[test]
192+
fn test_generate_stage_prefix_unicode_stripped() {
193+
// ADO identifiers require [A-Za-z0-9_]; non-ASCII chars are split points
194+
assert_eq!(generate_stage_prefix("über-agent"), "BerAgent");
195+
}
189196
}

src/compile/stage.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ impl Compiler for StageCompiler {
123123
/// Generate the header comment block for stage-level templates.
124124
fn generate_stage_header(input_path: &Path, front_matter: &FrontMatter) -> String {
125125
let base_header = generate_header_comment(input_path);
126-
let source_path = input_path
126+
let lock_path = input_path
127+
.with_extension("lock.yml")
127128
.to_string_lossy()
128129
.replace('\\', "/");
129130

@@ -132,12 +133,12 @@ fn generate_stage_header(input_path: &Path, front_matter: &FrontMatter) -> Strin
132133
header.push_str("# Stage-level ADO template. Include in your pipeline:\n");
133134
header.push_str("#\n");
134135
header.push_str("# stages:\n");
135-
header.push_str(&format!("# - template: {}\n", source_path.replace(".md", ".lock.yml")));
136+
header.push_str(&format!("# - template: {}\n", lock_path));
136137
header.push_str("# dependsOn: Build\n");
137138
header.push_str("# condition: succeeded()\n");
138139

139140
// Document required resources if agent uses repos
140-
if !front_matter.repos.is_empty() || !front_matter.repositories.is_empty() {
141+
if !front_matter.repositories.is_empty() {
141142
header.push_str("#\n");
142143
header.push_str("# Add these repositories to your pipeline's resources: block:\n");
143144
header.push_str("#\n");

0 commit comments

Comments
 (0)