Skip to content

feat(engine): ComponentRegistry, EcsCore, signature 갱신 및 cleanup flow… #30

feat(engine): ComponentRegistry, EcsCore, signature 갱신 및 cleanup flow…

feat(engine): ComponentRegistry, EcsCore, signature 갱신 및 cleanup flow… #30

Workflow file for this run

name: PR Policy
on:
pull_request:
types:
- opened
- edited
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
pull-requests: read
jobs:
validate-pr:
name: Validate PR
runs-on: ubuntu-latest
steps:
- name: Check title and body
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title || "";
const body = pr.body || "";
const errors = [];
const titlePattern = /^\[(Engine|Domain|Application|Docs|Build|Analysis|Chore)\]\s.+$/;
if (!titlePattern.test(title)) {
errors.push("PR title must match `[Area] short summary`.");
}
const requiredHeadings = [
"## Summary",
"## Related Issue",
"## Area",
"## Architecture Check",
"## Verification",
];
for (const heading of requiredHeadings) {
if (!body.includes(heading)) {
errors.push(`Missing section: ${heading}`);
}
}
const section = (heading) => {
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`${escaped}\\s*([\\s\\S]*?)(?=\\n## |$)`, "i");
const match = body.match(pattern);
return match ? match[1] : "";
};
const relatedIssueSection = section("## Related Issue");
const areaSection = section("## Area");
const verificationSection = section("## Verification");
const selectedAreas = [...areaSection.matchAll(/- \[[xX]\] (.+)/g)].map((match) => match[1].trim());
const noIssuePattern = /\b(?:none|n\/a|no separate issue)\b/i;
const changedFiles = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const docsPolicyOnlyPatterns = [
/^docs\//,
/^uml\//,
/^\.github\/ISSUE_TEMPLATE\//,
/^\.github\/PULL_REQUEST_TEMPLATE\.md$/,
/^\.github\/workflows\/pr-policy\.yml$/,
/^CONTRIBUTING\.md$/,
/^README\.md$/,
];
const isDocsPolicyOnlyPr =
changedFiles.length > 0 &&
changedFiles.every((file) =>
docsPolicyOnlyPatterns.some((pattern) => pattern.test(file.filename))
);
const isDocsAreaOnly = selectedAreas.length === 1 && selectedAreas[0] === "Docs";
const issuePattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|refs?)\s+#\d+\b/i;
const hasIssueReference = issuePattern.test(relatedIssueSection);
const usesDocsOnlyException = noIssuePattern.test(relatedIssueSection);
if (!hasIssueReference) {
if (!(usesDocsOnlyException && isDocsAreaOnly && isDocsPolicyOnlyPr)) {
errors.push("`## Related Issue` must include an issue reference such as `Closes #12`, unless this is a Docs-only docs/policy PR and the section says `None (docs/policy-only PR)`.");
}
}
if (selectedAreas.length === 0) {
errors.push("Select at least one checkbox in `## Area`.");
}
if (!/- \[[xX]\] /.test(verificationSection)) {
errors.push("Select at least one checkbox in `## Verification`.");
}
if (errors.length > 0) {
core.setFailed(errors.join("\n"));
} else {
core.info("PR policy checks passed.");
}