mirror of
https://github.com/esphome/esphome.git
synced 2025-08-01 07:57:47 +00:00
Merge branch 'integration' into memory_api
This commit is contained in:
commit
9cd1c7a355
177
.github/workflows/auto-label-pr.yml
vendored
177
.github/workflows/auto-label-pr.yml
vendored
@ -11,49 +11,6 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TARGET_PLATFORMS: |
|
|
||||||
esp32
|
|
||||||
esp8266
|
|
||||||
rp2040
|
|
||||||
libretiny
|
|
||||||
bk72xx
|
|
||||||
rtl87xx
|
|
||||||
ln882x
|
|
||||||
nrf52
|
|
||||||
host
|
|
||||||
PLATFORM_COMPONENTS: |
|
|
||||||
alarm_control_panel
|
|
||||||
audio_adc
|
|
||||||
audio_dac
|
|
||||||
binary_sensor
|
|
||||||
button
|
|
||||||
canbus
|
|
||||||
climate
|
|
||||||
cover
|
|
||||||
datetime
|
|
||||||
display
|
|
||||||
event
|
|
||||||
fan
|
|
||||||
light
|
|
||||||
lock
|
|
||||||
media_player
|
|
||||||
microphone
|
|
||||||
number
|
|
||||||
one_wire
|
|
||||||
ota
|
|
||||||
output
|
|
||||||
packet_transport
|
|
||||||
select
|
|
||||||
sensor
|
|
||||||
speaker
|
|
||||||
stepper
|
|
||||||
switch
|
|
||||||
text
|
|
||||||
text_sensor
|
|
||||||
time
|
|
||||||
touchscreen
|
|
||||||
update
|
|
||||||
valve
|
|
||||||
SMALL_PR_THRESHOLD: 30
|
SMALL_PR_THRESHOLD: 30
|
||||||
MAX_LABELS: 15
|
MAX_LABELS: 15
|
||||||
TOO_BIG_THRESHOLD: 1000
|
TOO_BIG_THRESHOLD: 1000
|
||||||
@ -101,6 +58,9 @@ jobs:
|
|||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
const pr_number = context.issue.number;
|
const pr_number = context.issue.number;
|
||||||
|
|
||||||
|
// Hidden marker to identify bot comments from this workflow
|
||||||
|
const BOT_COMMENT_MARKER = '<!-- auto-label-pr-bot -->';
|
||||||
|
|
||||||
// Get current labels
|
// Get current labels
|
||||||
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
||||||
owner,
|
owner,
|
||||||
@ -143,9 +103,25 @@ jobs:
|
|||||||
|
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
|
||||||
|
// Fetch TARGET_PLATFORMS and PLATFORM_COMPONENTS from API
|
||||||
|
let targetPlatforms = [];
|
||||||
|
let platformComponents = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://data.esphome.io/components.json');
|
||||||
|
const componentsData = await response.json();
|
||||||
|
|
||||||
|
// Extract target platforms and platform components directly from API
|
||||||
|
targetPlatforms = componentsData.target_platforms || [];
|
||||||
|
platformComponents = componentsData.platform_components || [];
|
||||||
|
|
||||||
|
console.log('Target platforms from API:', targetPlatforms.length, targetPlatforms);
|
||||||
|
console.log('Platform components from API:', platformComponents.length, platformComponents);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to fetch components data from API:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Get environment variables
|
// Get environment variables
|
||||||
const targetPlatforms = `${{ env.TARGET_PLATFORMS }}`.split('\n').filter(p => p.trim().length > 0).map(p => p.trim());
|
|
||||||
const platformComponents = `${{ env.PLATFORM_COMPONENTS }}`.split('\n').filter(p => p.trim().length > 0).map(p => p.trim());
|
|
||||||
const smallPrThreshold = parseInt('${{ env.SMALL_PR_THRESHOLD }}');
|
const smallPrThreshold = parseInt('${{ env.SMALL_PR_THRESHOLD }}');
|
||||||
const maxLabels = parseInt('${{ env.MAX_LABELS }}');
|
const maxLabels = parseInt('${{ env.MAX_LABELS }}');
|
||||||
const tooBigThreshold = parseInt('${{ env.TOO_BIG_THRESHOLD }}');
|
const tooBigThreshold = parseInt('${{ env.TOO_BIG_THRESHOLD }}');
|
||||||
@ -225,6 +201,14 @@ jobs:
|
|||||||
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
||||||
|
|
||||||
|
// Calculate changes excluding root tests directory for too-big calculation
|
||||||
|
const testChanges = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
|
|
||||||
|
const nonTestChanges = totalChanges - testChanges;
|
||||||
|
console.log(`Test changes: ${testChanges}, Non-test changes: ${nonTestChanges}`);
|
||||||
|
|
||||||
// Strategy: New Component detection
|
// Strategy: New Component detection
|
||||||
for (const file of addedFiles) {
|
for (const file of addedFiles) {
|
||||||
// Check for new component files: esphome/components/{component}/__init__.py
|
// Check for new component files: esphome/components/{component}/__init__.py
|
||||||
@ -404,16 +388,30 @@ jobs:
|
|||||||
|
|
||||||
console.log('Computed labels:', finalLabels.join(', '));
|
console.log('Computed labels:', finalLabels.join(', '));
|
||||||
|
|
||||||
// Check if PR is allowed to be too big
|
// Check if PR has mega-pr label
|
||||||
const allowedTooBig = currentLabels.includes('mega-pr');
|
const isMegaPR = currentLabels.includes('mega-pr');
|
||||||
|
|
||||||
// Check if PR is too big (either too many labels or too many line changes)
|
// Check if PR is too big (either too many labels or too many line changes)
|
||||||
const tooManyLabels = finalLabels.length > maxLabels;
|
const tooManyLabels = finalLabels.length > maxLabels;
|
||||||
const tooManyChanges = totalChanges > tooBigThreshold;
|
const tooManyChanges = nonTestChanges > tooBigThreshold;
|
||||||
|
|
||||||
if ((tooManyLabels || tooManyChanges) && !allowedTooBig) {
|
if ((tooManyLabels || tooManyChanges) && !isMegaPR) {
|
||||||
const originalLength = finalLabels.length;
|
const originalLength = finalLabels.length;
|
||||||
console.log(`PR is too big - Labels: ${originalLength}, Changes: ${totalChanges}`);
|
console.log(`PR is too big - Labels: ${originalLength}, Changes: ${totalChanges} (non-test: ${nonTestChanges})`);
|
||||||
|
|
||||||
|
// Get all reviews on this PR to check for existing bot reviews
|
||||||
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there's already an active bot review requesting changes
|
||||||
|
const existingBotReview = reviews.find(review =>
|
||||||
|
review.user.type === 'Bot' &&
|
||||||
|
review.state === 'CHANGES_REQUESTED' &&
|
||||||
|
review.body && review.body.includes(BOT_COMMENT_MARKER)
|
||||||
|
);
|
||||||
|
|
||||||
// If too big due to line changes only, keep original labels and add too-big
|
// If too big due to line changes only, keep original labels and add too-big
|
||||||
// If too big due to too many labels, replace with just too-big
|
// If too big due to too many labels, replace with just too-big
|
||||||
@ -423,24 +421,69 @@ jobs:
|
|||||||
finalLabels = ['too-big'];
|
finalLabels = ['too-big'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create appropriate review message
|
// Only create a new review if there isn't already an active bot review
|
||||||
let reviewBody;
|
if (!existingBotReview) {
|
||||||
if (tooManyLabels && tooManyChanges) {
|
// Create appropriate review message
|
||||||
reviewBody = `This PR is too large with ${totalChanges} line changes and affects ${originalLength} different components/areas. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.`;
|
let reviewBody;
|
||||||
} else if (tooManyLabels) {
|
if (tooManyLabels && tooManyChanges) {
|
||||||
reviewBody = `This PR affects ${originalLength} different components/areas. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.`;
|
reviewBody = `${BOT_COMMENT_MARKER}\nThis PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLength} different components/areas. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.\n\nFor guidance on breaking down large PRs, see: https://developers.esphome.io/contributing/submitting-your-work/#but-howwww-looonnnggg`;
|
||||||
} else {
|
} else if (tooManyLabels) {
|
||||||
reviewBody = `This PR is too large with ${totalChanges} line changes. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.`;
|
reviewBody = `${BOT_COMMENT_MARKER}\nThis PR affects ${originalLength} different components/areas. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.\n\nFor guidance on breaking down large PRs, see: https://developers.esphome.io/contributing/submitting-your-work/#but-howwww-looonnnggg`;
|
||||||
}
|
} else {
|
||||||
|
reviewBody = `${BOT_COMMENT_MARKER}\nThis PR is too large with ${nonTestChanges} line changes (excluding tests). Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.\n\nFor guidance on breaking down large PRs, see: https://developers.esphome.io/contributing/submitting-your-work/#but-howwww-looonnnggg`;
|
||||||
|
}
|
||||||
|
|
||||||
// Request changes on the PR
|
// Request changes on the PR
|
||||||
await github.rest.pulls.createReview({
|
await github.rest.pulls.createReview({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
pull_number: pr_number,
|
pull_number: pr_number,
|
||||||
body: reviewBody,
|
body: reviewBody,
|
||||||
event: 'REQUEST_CHANGES'
|
event: 'REQUEST_CHANGES'
|
||||||
});
|
});
|
||||||
|
console.log('Created new "too big" review requesting changes');
|
||||||
|
} else {
|
||||||
|
console.log('Skipping review creation - existing bot review already requesting changes');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if PR was previously too big but is now acceptable
|
||||||
|
const wasPreviouslyTooBig = currentLabels.includes('too-big');
|
||||||
|
|
||||||
|
if (wasPreviouslyTooBig || isMegaPR) {
|
||||||
|
console.log('PR is no longer too big or has mega-pr label - dismissing bot reviews');
|
||||||
|
|
||||||
|
// Get all reviews on this PR to find reviews to dismiss
|
||||||
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find bot reviews that requested changes
|
||||||
|
const botReviews = reviews.filter(review =>
|
||||||
|
review.user.type === 'Bot' &&
|
||||||
|
review.state === 'CHANGES_REQUESTED' &&
|
||||||
|
review.body && review.body.includes(BOT_COMMENT_MARKER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dismiss bot reviews
|
||||||
|
for (const review of botReviews) {
|
||||||
|
try {
|
||||||
|
await github.rest.pulls.dismissReview({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number,
|
||||||
|
review_id: review.id,
|
||||||
|
message: isMegaPR ?
|
||||||
|
'Review dismissed: mega-pr label was added' :
|
||||||
|
'Review dismissed: PR size is now acceptable'
|
||||||
|
});
|
||||||
|
console.log(`Dismissed review ${review.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to dismiss review ${review.id}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new labels
|
// Add new labels
|
||||||
|
11
.github/workflows/codeowner-review-request.yml
vendored
11
.github/workflows/codeowner-review-request.yml
vendored
@ -34,6 +34,9 @@ jobs:
|
|||||||
|
|
||||||
console.log(`Processing PR #${pr_number} for codeowner review requests`);
|
console.log(`Processing PR #${pr_number} for codeowner review requests`);
|
||||||
|
|
||||||
|
// Hidden marker to identify bot comments from this workflow
|
||||||
|
const BOT_COMMENT_MARKER = '<!-- codeowner-review-request-bot -->';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the list of changed files in this PR
|
// Get the list of changed files in this PR
|
||||||
const { data: files } = await github.rest.pulls.listFiles({
|
const { data: files } = await github.rest.pulls.listFiles({
|
||||||
@ -84,9 +87,9 @@ jobs:
|
|||||||
const allMentions = [...reviewerMentions, ...teamMentions].join(', ');
|
const allMentions = [...reviewerMentions, ...teamMentions].join(', ');
|
||||||
|
|
||||||
if (isSuccessful) {
|
if (isSuccessful) {
|
||||||
return `👋 Hi there! I've automatically requested reviews from codeowners based on the files changed in this PR.\n\n${allMentions} - You've been requested to review this PR as codeowner(s) of ${matchedFileCount} file(s) that were modified. Thanks for your time! 🙏`;
|
return `${BOT_COMMENT_MARKER}\n👋 Hi there! I've automatically requested reviews from codeowners based on the files changed in this PR.\n\n${allMentions} - You've been requested to review this PR as codeowner(s) of ${matchedFileCount} file(s) that were modified. Thanks for your time! 🙏`;
|
||||||
} else {
|
} else {
|
||||||
return `👋 Hi there! This PR modifies ${matchedFileCount} file(s) with codeowners.\n\n${allMentions} - As codeowner(s) of the affected files, your review would be appreciated! 🙏\n\n_Note: Automatic review request may have failed, but you're still welcome to review._`;
|
return `${BOT_COMMENT_MARKER}\n👋 Hi there! This PR modifies ${matchedFileCount} file(s) with codeowners.\n\n${allMentions} - As codeowner(s) of the affected files, your review would be appreciated! 🙏\n\n_Note: Automatic review request may have failed, but you're still welcome to review._`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,11 +191,11 @@ jobs:
|
|||||||
const previouslyPingedUsers = new Set();
|
const previouslyPingedUsers = new Set();
|
||||||
const previouslyPingedTeams = new Set();
|
const previouslyPingedTeams = new Set();
|
||||||
|
|
||||||
// Look for comments from github-actions bot that contain codeowner pings
|
// Look for comments from github-actions bot that contain our bot marker
|
||||||
const workflowComments = comments.filter(comment =>
|
const workflowComments = comments.filter(comment =>
|
||||||
comment.user.type === 'Bot' &&
|
comment.user.type === 'Bot' &&
|
||||||
comment.user.login === 'github-actions[bot]' &&
|
comment.user.login === 'github-actions[bot]' &&
|
||||||
comment.body.includes("I've automatically requested reviews from codeowners")
|
comment.body.includes(BOT_COMMENT_MARKER)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Extract previously mentioned users and teams from workflow comments
|
// Extract previously mentioned users and teams from workflow comments
|
||||||
|
@ -203,7 +203,7 @@ message DeviceInfoResponse {
|
|||||||
option (id) = 10;
|
option (id) = 10;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
|
|
||||||
bool uses_password = 1;
|
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"];
|
||||||
|
|
||||||
// The name of the node, given by "App.set_name()"
|
// The name of the node, given by "App.set_name()"
|
||||||
string name = 2;
|
string name = 2;
|
||||||
|
@ -1432,8 +1432,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||||||
DeviceInfoResponse resp{};
|
DeviceInfoResponse resp{};
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
resp.uses_password = true;
|
resp.uses_password = true;
|
||||||
#else
|
|
||||||
resp.uses_password = false;
|
|
||||||
#endif
|
#endif
|
||||||
resp.name = App.get_name();
|
resp.name = App.get_name();
|
||||||
resp.friendly_name = App.get_friendly_name();
|
resp.friendly_name = App.get_friendly_name();
|
||||||
|
@ -80,7 +80,9 @@ void DeviceInfo::calculate_size(uint32_t &total_size) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
buffer.encode_bool(1, this->uses_password);
|
buffer.encode_bool(1, this->uses_password);
|
||||||
|
#endif
|
||||||
buffer.encode_string(2, this->name);
|
buffer.encode_string(2, this->name);
|
||||||
buffer.encode_string(3, this->mac_address);
|
buffer.encode_string(3, this->mac_address);
|
||||||
buffer.encode_string(4, this->esphome_version);
|
buffer.encode_string(4, this->esphome_version);
|
||||||
@ -130,7 +132,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
ProtoSize::add_bool_field(total_size, 1, this->uses_password);
|
ProtoSize::add_bool_field(total_size, 1, this->uses_password);
|
||||||
|
#endif
|
||||||
ProtoSize::add_string_field(total_size, 1, this->name);
|
ProtoSize::add_string_field(total_size, 1, this->name);
|
||||||
ProtoSize::add_string_field(total_size, 1, this->mac_address);
|
ProtoSize::add_string_field(total_size, 1, this->mac_address);
|
||||||
ProtoSize::add_string_field(total_size, 1, this->esphome_version);
|
ProtoSize::add_string_field(total_size, 1, this->esphome_version);
|
||||||
|
@ -474,7 +474,9 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "device_info_response"; }
|
const char *message_name() const override { return "device_info_response"; }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
bool uses_password{false};
|
bool uses_password{false};
|
||||||
|
#endif
|
||||||
std::string name{};
|
std::string name{};
|
||||||
std::string mac_address{};
|
std::string mac_address{};
|
||||||
std::string esphome_version{};
|
std::string esphome_version{};
|
||||||
|
@ -647,10 +647,12 @@ void DeviceInfo::dump_to(std::string &out) const {
|
|||||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||||
__attribute__((unused)) char buffer[64];
|
__attribute__((unused)) char buffer[64];
|
||||||
out.append("DeviceInfoResponse {\n");
|
out.append("DeviceInfoResponse {\n");
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
out.append(" uses_password: ");
|
out.append(" uses_password: ");
|
||||||
out.append(YESNO(this->uses_password));
|
out.append(YESNO(this->uses_password));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
#endif
|
||||||
out.append(" name: ");
|
out.append(" name: ");
|
||||||
out.append("'").append(this->name).append("'");
|
out.append("'").append(this->name).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
|||||||
esptool==4.9.0
|
esptool==4.9.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250514.0
|
esphome-dashboard==20250514.0
|
||||||
aioesphomeapi==37.0.2
|
aioesphomeapi==37.0.3
|
||||||
zeroconf==0.147.0
|
zeroconf==0.147.0
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.18.14 # dashboard_import
|
ruamel.yaml==0.18.14 # dashboard_import
|
||||||
|
Loading…
x
Reference in New Issue
Block a user