Skip to content

Commit e7ccade

Browse files
committed
fix: ensure action runs from GitHub workspace and handle TLS build on musl
1 parent f19f728 commit e7ccade

7 files changed

Lines changed: 157 additions & 11 deletions

File tree

.github/workflows/example.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: PR Check Example
22

33
on:
44
pull_request:
5-
types: [opened, synchronize, reopened]
5+
types: [opened, synchronize, reopened, labeled, unlabeled, edited]
66

77
jobs:
88
check:
@@ -11,7 +11,7 @@ jobs:
1111
- uses: actions/checkout@v4
1212

1313
- name: PR Checker
14-
uses: ./ # Use local action for testing
14+
uses: chenjjiaa/pr-checker@v0.1.0
1515
with:
1616
config: .github/pr-checker.yml
1717
env:

Dockerfile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,11 @@ RUN apk add --no-cache ca-certificates
5454
RUN addgroup -g 1000 appuser && \
5555
adduser -D -u 1000 -G appuser appuser
5656

57-
WORKDIR /app
58-
COPY --from=builder /app/target/release/pr-checker /app/pr-checker
57+
# GitHub Actions workspace is mounted at /github/workspace
58+
WORKDIR /github/workspace
59+
COPY --from=builder /app/target/release/pr-checker /usr/local/bin/pr-checker
5960

60-
# Set ownership
61-
RUN chown -R appuser:appuser /app
6261
# Switch to non-root user
6362
USER appuser
6463

65-
ENTRYPOINT ["/app/pr-checker"]
64+
ENTRYPOINT ["/usr/local/bin/pr-checker"]

pr-checker.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
title:
2+
# Regex pattern to match against PR title
3+
# Example: Conventional Commits format
4+
pattern: "^(feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert)(\\([^)]+\\))?:"
5+
6+
# Minimum length of the title
7+
min_length: 10
8+
9+
# Maximum length of the title (optional)
10+
# max_length: 100
11+
12+
# Label validation rules
13+
labels:
14+
# List of required labels
15+
# All labels in this list must be present on the PR
16+
17+
# required:
18+
# Type/Kind labels - Required: at least one type label
19+
# Common types: bug, feature, enhancement, documentation, refactor, test, chore
20+
# - "kind/bug" # Bug fixes
21+
# - "kind/feature" # New features
22+
# - "kind/docs" # Documentation changes
23+
# - "kind/enhancement" # Feature enhancements/improvements
24+
# - "kind/refactor" # Code refactoring
25+
# - "kind/test" # Adding or updating tests
26+
# - "kind/chore" # Maintenance tasks (deps, config, etc.)
27+
# - "kind/ci" # CI/CD changes
28+
# - "kind/performance" # Performance improvements
29+
# - "kind/build" # Build system changes
30+
# - "kind/security" # Security fixes
31+
# - "kind/dependencies" # Dependency updates
32+
33+
# Priority labels - Optional: uncomment to require priority classification
34+
# Common priorities: low, medium, high, critical
35+
# - "priority/medium" # Uncomment to require priority label
36+
37+
# Area/Component labels - Optional: uncomment to require component classification
38+
# Common areas: frontend, backend, api, database, infrastructure, tooling
39+
# - "area/backend" # Uncomment to require area label
40+
41+
# Size labels - Optional: uncomment to require size estimation
42+
# Common sizes: XS, S, M, L, XL (for effort estimation)
43+
# - "size/M" # Uncomment to require size label
44+
required: []

src/config/schema.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,13 @@ impl Config {
4242
let config: Config = serde_yaml::from_str(&content)?;
4343
Ok(config)
4444
}
45+
46+
/// Load built-in default config bundled at compile time.
47+
pub fn from_default() -> crate::error::Result<Self> {
48+
// The default config is stored at repository path `.github/pr-checker.yml`
49+
// and embedded via include_str! to make runtime fallback possible.
50+
const DEFAULT_CONFIG_STR: &str = include_str!("../../pr-checker.yml");
51+
let config: Config = serde_yaml::from_str(DEFAULT_CONFIG_STR)?;
52+
Ok(config)
53+
}
4554
}

src/engine.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use crate::config::Config;
1616
use crate::github::GitHubClient;
17+
use crate::rules::Violation;
1718
use crate::rules::{RuleResult, check_labels, check_title};
1819

1920
pub struct Engine {
@@ -43,6 +44,75 @@ impl Engine {
4344
all_violations.extend(violations);
4445
}
4546

47+
// Ensure title type aligns with kind/* label (best-effort)
48+
if let Some(expected) = expected_label_for_title(&pr.title) {
49+
if !has_label(&pr, expected) {
50+
all_violations.push(Violation {
51+
message: format!(
52+
"Title type '{}' requires label '{}', current labels: [{}], title: '{}'",
53+
title_type(&pr.title),
54+
expected,
55+
format_labels(&pr),
56+
pr.title
57+
),
58+
});
59+
}
60+
}
61+
62+
// Prepend a context line with title and labels if there are violations
63+
if !all_violations.is_empty() {
64+
all_violations.insert(
65+
0,
66+
Violation {
67+
message: format!(
68+
"Context -> title: '{}'; labels: [{}]",
69+
pr.title,
70+
format_labels(&pr)
71+
),
72+
},
73+
);
74+
}
75+
4676
Ok(all_violations)
4777
}
4878
}
79+
80+
fn has_label(pr: &crate::github::PullRequest, name: &str) -> bool {
81+
pr.labels.iter().any(|l| l.name == name)
82+
}
83+
84+
fn format_labels(pr: &crate::github::PullRequest) -> String {
85+
if pr.labels.is_empty() {
86+
"none".to_string()
87+
} else {
88+
pr.labels
89+
.iter()
90+
.map(|l| l.name.clone())
91+
.collect::<Vec<_>>()
92+
.join(", ")
93+
}
94+
}
95+
96+
fn title_type(title: &str) -> String {
97+
let prefix = title.split(':').next().unwrap_or_default().trim();
98+
// Support optional component scope, e.g., feat(api-server): ...
99+
let type_only = prefix.split('(').next().unwrap_or(prefix).trim();
100+
type_only.to_lowercase()
101+
}
102+
103+
fn expected_label_for_title(title: &str) -> Option<&'static str> {
104+
match title_type(title).as_str() {
105+
"feat" => Some("kind/feature"),
106+
"fix" => Some("kind/bug"),
107+
"docs" => Some("kind/docs"),
108+
"chore" => Some("kind/chore"),
109+
"refactor" => Some("kind/refactor"),
110+
"test" => Some("kind/test"),
111+
"perf" => Some("kind/performance"),
112+
"ci" => Some("kind/ci"),
113+
"build" => Some("kind/build"),
114+
"security" => Some("kind/security"),
115+
"dependencies" => Some("kind/dependencies"),
116+
_ => None,
117+
}
118+
}

src/main.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ mod github;
1919
mod rules;
2020

2121
use clap::Parser;
22-
use tracing::{error, info};
22+
use std::io::ErrorKind;
23+
use tracing::{error, info, warn};
2324

2425
#[derive(Parser)]
2526
#[command(name = "pr-checker")]
@@ -59,6 +60,15 @@ async fn main() {
5960
)
6061
.init();
6162

63+
// Ensure we operate in the GitHub workspace when running as an Action
64+
if let Ok(ws) = std::env::var("GITHUB_WORKSPACE") {
65+
if let Err(e) = std::env::set_current_dir(&ws) {
66+
warn!("Failed to set current dir to GITHUB_WORKSPACE={ws}: {e}");
67+
} else {
68+
info!("Working directory set to GITHUB_WORKSPACE: {}", ws);
69+
}
70+
}
71+
6272
let args = Args::parse();
6373

6474
// Get config path from args or GitHub Actions input or default
@@ -111,9 +121,23 @@ async fn run(config_path: &str) -> error::Result<Vec<rules::Violation>> {
111121
info!("Starting PR checker...");
112122
info!("Config path: {}", config_path);
113123

114-
// Load configuration
115-
let config = config::Config::from_file(config_path)?;
116-
info!("Configuration loaded successfully");
124+
// Load configuration, fallback to built-in default if file missing
125+
let config = match config::Config::from_file(config_path) {
126+
Ok(c) => {
127+
info!("Configuration loaded successfully");
128+
c
129+
}
130+
Err(error::Error::Io(ref e)) if e.kind() == ErrorKind::NotFound => {
131+
warn!(
132+
"Config file not found at '{}', falling back to built-in default",
133+
config_path
134+
);
135+
let cfg = config::Config::from_default()?;
136+
info!("Loaded built-in default configuration");
137+
cfg
138+
}
139+
Err(e) => return Err(e),
140+
};
117141

118142
// Initialize GitHub client from environment
119143
let client = github::GitHubClient::from_env()?;

0 commit comments

Comments
 (0)