mirror of
https://github.com/esphome/esphome.git
synced 2026-02-04 03:09:39 -07:00
Compare commits
7 Commits
json_web_s
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90407aa9f2 | ||
|
|
a167ea8269 | ||
|
|
354f60a9cf | ||
|
|
b8ac92c537 | ||
|
|
71390c075e | ||
|
|
0ad82b8bbf | ||
|
|
7863ac8b3a |
112
.github/workflows/auto-label-pr.yml
vendored
112
.github/workflows/auto-label-pr.yml
vendored
@@ -46,6 +46,7 @@ jobs:
|
|||||||
const BOT_COMMENT_MARKER = '<!-- auto-label-pr-bot -->';
|
const BOT_COMMENT_MARKER = '<!-- auto-label-pr-bot -->';
|
||||||
const CODEOWNERS_MARKER = '<!-- codeowners-request -->';
|
const CODEOWNERS_MARKER = '<!-- codeowners-request -->';
|
||||||
const TOO_BIG_MARKER = '<!-- too-big-request -->';
|
const TOO_BIG_MARKER = '<!-- too-big-request -->';
|
||||||
|
const DEPRECATED_COMPONENT_MARKER = '<!-- deprecated-component-request -->';
|
||||||
|
|
||||||
const MANAGED_LABELS = [
|
const MANAGED_LABELS = [
|
||||||
'new-component',
|
'new-component',
|
||||||
@@ -69,7 +70,8 @@ jobs:
|
|||||||
'new-feature',
|
'new-feature',
|
||||||
'breaking-change',
|
'breaking-change',
|
||||||
'developer-breaking-change',
|
'developer-breaking-change',
|
||||||
'code-quality'
|
'code-quality',
|
||||||
|
'deprecated_component'
|
||||||
];
|
];
|
||||||
|
|
||||||
const DOCS_PR_PATTERNS = [
|
const DOCS_PR_PATTERNS = [
|
||||||
@@ -382,6 +384,75 @@ jobs:
|
|||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strategy: Deprecated component detection
|
||||||
|
async function detectDeprecatedComponents() {
|
||||||
|
const labels = new Set();
|
||||||
|
const deprecatedInfo = [];
|
||||||
|
|
||||||
|
// Compile regex once for better performance
|
||||||
|
const componentFileRegex = /^esphome\/components\/([^\/]+)\/[^/]*\.py$/;
|
||||||
|
|
||||||
|
// Get files that are modified or added in components directory
|
||||||
|
const componentFiles = changedFiles.filter(file => componentFileRegex.test(file));
|
||||||
|
|
||||||
|
if (componentFiles.length === 0) {
|
||||||
|
return { labels, deprecatedInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract unique component names using the same regex
|
||||||
|
const components = new Set();
|
||||||
|
for (const file of componentFiles) {
|
||||||
|
const match = file.match(componentFileRegex);
|
||||||
|
if (match) {
|
||||||
|
components.add(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get PR head SHA to fetch files from the PR branch
|
||||||
|
const prHeadSha = context.payload.pull_request.head.sha;
|
||||||
|
|
||||||
|
// Check each component's __init__.py for DEPRECATED_COMPONENT constant
|
||||||
|
for (const component of components) {
|
||||||
|
const initFile = `esphome/components/${component}/__init__.py`;
|
||||||
|
try {
|
||||||
|
// Fetch file content from PR head using GitHub API
|
||||||
|
const { data: fileData } = await github.rest.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: initFile,
|
||||||
|
ref: prHeadSha
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decode base64 content
|
||||||
|
const content = Buffer.from(fileData.content, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
// Look for DEPRECATED_COMPONENT = "message" or DEPRECATED_COMPONENT = 'message'
|
||||||
|
// Support single quotes, double quotes, and triple quotes (for multiline)
|
||||||
|
const doubleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*"""([\s\S]*?)"""/s) ||
|
||||||
|
content.match(/DEPRECATED_COMPONENT\s*=\s*"((?:[^"\\]|\\.)*)"/);
|
||||||
|
const singleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*'''([\s\S]*?)'''/s) ||
|
||||||
|
content.match(/DEPRECATED_COMPONENT\s*=\s*'((?:[^'\\]|\\.)*)'/);
|
||||||
|
const deprecatedMatch = doubleQuoteMatch || singleQuoteMatch;
|
||||||
|
|
||||||
|
if (deprecatedMatch) {
|
||||||
|
labels.add('deprecated_component');
|
||||||
|
deprecatedInfo.push({
|
||||||
|
component: component,
|
||||||
|
message: deprecatedMatch[1]
|
||||||
|
});
|
||||||
|
console.log(`Found deprecated component: ${component}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Only log if it's not a simple "file not found" error (404)
|
||||||
|
if (error.status !== 404) {
|
||||||
|
console.log(`Error reading ${initFile}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { labels, deprecatedInfo };
|
||||||
|
}
|
||||||
|
|
||||||
// Strategy: Requirements detection
|
// Strategy: Requirements detection
|
||||||
async function detectRequirements(allLabels) {
|
async function detectRequirements(allLabels) {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
@@ -418,10 +489,26 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate review messages
|
// Generate review messages
|
||||||
function generateReviewMessages(finalLabels, originalLabelCount) {
|
function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo) {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
|
// Deprecated component message
|
||||||
|
if (finalLabels.includes('deprecated_component') && deprecatedInfo && deprecatedInfo.length > 0) {
|
||||||
|
let message = `${DEPRECATED_COMPONENT_MARKER}\n### ⚠️ Deprecated Component\n\n`;
|
||||||
|
message += `Hey there @${prAuthor},\n`;
|
||||||
|
message += `This PR modifies one or more deprecated components. Please be aware:\n\n`;
|
||||||
|
|
||||||
|
for (const info of deprecatedInfo) {
|
||||||
|
message += `#### Component: \`${info.component}\`\n`;
|
||||||
|
message += `${info.message}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `Consider migrating to the recommended alternative if applicable.`;
|
||||||
|
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
// Too big message
|
// Too big message
|
||||||
if (finalLabels.includes('too-big')) {
|
if (finalLabels.includes('too-big')) {
|
||||||
const testAdditions = prFiles
|
const testAdditions = prFiles
|
||||||
@@ -468,10 +555,10 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle reviews
|
// Handle reviews
|
||||||
async function handleReviews(finalLabels, originalLabelCount) {
|
async function handleReviews(finalLabels, originalLabelCount, deprecatedInfo) {
|
||||||
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
|
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo);
|
||||||
const hasReviewableLabels = finalLabels.some(label =>
|
const hasReviewableLabels = finalLabels.some(label =>
|
||||||
['too-big', 'needs-codeowners'].includes(label)
|
['too-big', 'needs-codeowners', 'deprecated_component'].includes(label)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||||
@@ -580,7 +667,8 @@ jobs:
|
|||||||
actionsLabels,
|
actionsLabels,
|
||||||
codeOwnerLabels,
|
codeOwnerLabels,
|
||||||
testLabels,
|
testLabels,
|
||||||
checkboxLabels
|
checkboxLabels,
|
||||||
|
deprecatedResult
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
detectMergeBranch(),
|
detectMergeBranch(),
|
||||||
detectComponentPlatforms(apiData),
|
detectComponentPlatforms(apiData),
|
||||||
@@ -592,9 +680,14 @@ jobs:
|
|||||||
detectGitHubActionsChanges(),
|
detectGitHubActionsChanges(),
|
||||||
detectCodeOwner(),
|
detectCodeOwner(),
|
||||||
detectTests(),
|
detectTests(),
|
||||||
detectPRTemplateCheckboxes()
|
detectPRTemplateCheckboxes(),
|
||||||
|
detectDeprecatedComponents()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Extract deprecated component info
|
||||||
|
const deprecatedLabels = deprecatedResult.labels;
|
||||||
|
const deprecatedInfo = deprecatedResult.deprecatedInfo;
|
||||||
|
|
||||||
// Combine all labels
|
// Combine all labels
|
||||||
const allLabels = new Set([
|
const allLabels = new Set([
|
||||||
...branchLabels,
|
...branchLabels,
|
||||||
@@ -607,7 +700,8 @@ jobs:
|
|||||||
...actionsLabels,
|
...actionsLabels,
|
||||||
...codeOwnerLabels,
|
...codeOwnerLabels,
|
||||||
...testLabels,
|
...testLabels,
|
||||||
...checkboxLabels
|
...checkboxLabels,
|
||||||
|
...deprecatedLabels
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Detect requirements based on all other labels
|
// Detect requirements based on all other labels
|
||||||
@@ -638,7 +732,7 @@ jobs:
|
|||||||
console.log('Computed labels:', finalLabels.join(', '));
|
console.log('Computed labels:', finalLabels.join(', '));
|
||||||
|
|
||||||
// Handle reviews
|
// Handle reviews
|
||||||
await handleReviews(finalLabels, originalLabelCount);
|
await handleReviews(finalLabels, originalLabelCount, deprecatedInfo);
|
||||||
|
|
||||||
// Apply labels
|
// Apply labels
|
||||||
if (finalLabels.length > 0) {
|
if (finalLabels.length > 0) {
|
||||||
|
|||||||
17
esphome/components/test_deprecated_example/README.md
Normal file
17
esphome/components/test_deprecated_example/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Test Deprecated Component
|
||||||
|
|
||||||
|
This is a test component to validate the `deprecated_component` label functionality in the auto-label workflow.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This component demonstrates how the `DEPRECATED_COMPONENT` constant should be used:
|
||||||
|
|
||||||
|
1. The component is still functional and usable
|
||||||
|
2. It's deprecated and not actively maintained
|
||||||
|
3. Users should migrate to an alternative when possible
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This is different from components that use `cv.invalid()`, which are completely unusable.
|
||||||
|
|
||||||
|
Components with `DEPRECATED_COMPONENT` are still functional but discouraged for new projects.
|
||||||
6
esphome/components/test_deprecated_example/__init__.py
Normal file
6
esphome/components/test_deprecated_example/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""Test deprecated component for validation."""
|
||||||
|
|
||||||
|
CODEOWNERS = ["@test"]
|
||||||
|
DEPRECATED_COMPONENT = "This component is deprecated and will be removed in a future release. Please use the newer_component instead."
|
||||||
|
|
||||||
|
# Component is still functional but deprecated
|
||||||
5
esphome/components/test_deprecated_example/sensor.py
Normal file
5
esphome/components/test_deprecated_example/sensor.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""Test sensor platform for deprecated component."""
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
# This is a simple placeholder - component still works
|
||||||
|
CONFIG_SCHEMA = cv.Schema({})
|
||||||
@@ -1 +1,2 @@
|
|||||||
CODEOWNERS = ["@clydebarrow"]
|
CODEOWNERS = ["@clydebarrow"]
|
||||||
|
DEPRECATED_COMPONENT = "The waveshare_epaper component is deprecated and no new models will be added. For new epaper displays use the epaper_spi component"
|
||||||
|
|||||||
Reference in New Issue
Block a user