Compare commits
53 Commits
af7a960b77
...
76b516671c
| Author | SHA1 | Date |
|---|---|---|
|
|
76b516671c | 2 weeks ago |
|
|
7e2035260c | 2 weeks ago |
|
|
8eb50abc00 | 2 weeks ago |
|
|
7a7875caa2 | 2 weeks ago |
|
|
19602868cc | 2 weeks ago |
|
|
06173d4863 | 2 weeks ago |
|
|
9ae1ee0968 | 2 weeks ago |
|
|
68e14fc1b5 | 2 weeks ago |
|
|
50f505b1c5 | 2 weeks ago |
|
|
6247b8cdee | 2 weeks ago |
|
|
9cad1437e7 | 2 weeks ago |
|
|
ab276c5ac1 | 2 weeks ago |
|
|
87b0247bfd | 2 weeks ago |
|
|
2b32c800ed | 2 weeks ago |
|
|
0e18f68f0e | 3 weeks ago |
|
|
a3a39ae2f8 | 3 weeks ago |
|
|
eebf6fb19c | 3 weeks ago |
|
|
81e458a827 | 3 weeks ago |
|
|
84d0188835 | 3 weeks ago |
|
|
7fd432d668 | 3 weeks ago |
|
|
4ef28de66f | 3 weeks ago |
|
|
f28f684127 | 3 weeks ago |
|
|
8b9c55f9e9 | 3 weeks ago |
|
|
280742ef49 | 3 weeks ago |
|
|
b905e04561 | 3 weeks ago |
|
|
4a8a3126c7 | 3 weeks ago |
|
|
b7c3202746 | 3 weeks ago |
|
|
9a0a128540 | 3 weeks ago |
|
|
b7268a632a | 3 weeks ago |
|
|
1baee9b684 | 4 weeks ago |
|
|
a21666b129 | 4 weeks ago |
|
|
4c3a8a01b4 | 4 weeks ago |
|
|
30a18e0bbc | 4 weeks ago |
|
|
ae2c335802 | 4 weeks ago |
|
|
94d80e28b5 | 4 weeks ago |
|
|
06b1ae7fc5 | 4 weeks ago |
|
|
dc72b7a080 | 4 weeks ago |
|
|
63c24e9be6 | 4 weeks ago |
|
|
68c85b1fae | 4 weeks ago |
|
|
fa6cac5fee | 4 weeks ago |
|
|
4cb01fbc1f | 4 weeks ago |
|
|
539ede8586 | 4 weeks ago |
|
|
cb42a22f43 | 1 month ago |
|
|
d3623e2f2d | 1 month ago |
|
|
e0684f9773 | 1 month ago |
|
|
1ee008e2d3 | 1 month ago |
|
|
347d626792 | 1 month ago |
|
|
8e9ea094bf | 1 month ago |
|
|
8670d3892e | 1 month ago |
|
|
8ea8fcd7f3 | 1 month ago |
|
|
c1aec19626 | 1 month ago |
|
|
67224ed4a3 | 1 month ago |
|
|
e79a383586 | 1 month ago |
@ -0,0 +1,498 @@
|
|||||||
|
const repository = process.env.GITHUB_REPOSITORY || "CCOSTAN/Home-AssistantConfig";
|
||||||
|
const [owner, repo] = repository.split("/");
|
||||||
|
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
|
||||||
|
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
|
||||||
|
const token = process.env.GITHUB_TOKEN || "";
|
||||||
|
const dryRun = process.env.DIGEST_DRY_RUN === "1" || process.argv.includes("--dry-run");
|
||||||
|
|
||||||
|
if (!owner || !repo) {
|
||||||
|
throw new Error(`GITHUB_REPOSITORY must be owner/repo, got "${repository}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token && !dryRun) {
|
||||||
|
throw new Error("GITHUB_TOKEN is required unless DIGEST_DRY_RUN=1 is set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = process.env.DIGEST_NOW ? new Date(process.env.DIGEST_NOW) : new Date();
|
||||||
|
const end = process.env.DIGEST_END ? new Date(process.env.DIGEST_END) : now;
|
||||||
|
const start = process.env.DIGEST_START
|
||||||
|
? new Date(process.env.DIGEST_START)
|
||||||
|
: new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
for (const [name, date] of [["DIGEST_START", start], ["DIGEST_END", end]]) {
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
throw new Error(`${name} is not a valid date.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultHeaders = {
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
"User-Agent": "home-assistant-config-weekly-digest",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
defaultHeaders.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDigestDate(date) {
|
||||||
|
const day = new Intl.DateTimeFormat("en-US", { day: "numeric", timeZone: "UTC" }).format(date);
|
||||||
|
const month = new Intl.DateTimeFormat("en-US", { month: "long", timeZone: "UTC" }).format(date);
|
||||||
|
const year = new Intl.DateTimeFormat("en-US", { year: "numeric", timeZone: "UTC" }).format(date);
|
||||||
|
return `${day} ${month}, ${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCountVerb(count, singular, plural) {
|
||||||
|
return count === 1 ? singular : plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRange(field) {
|
||||||
|
return `${field}:${start.toISOString()}..${end.toISOString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWithinWindow(value) {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(value);
|
||||||
|
return !Number.isNaN(date.getTime()) && date >= start && date <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
function markdownLink(label, url) {
|
||||||
|
const safeLabel = String(label || "unknown").replace(/[[\]]/g, "\\$&");
|
||||||
|
return `[${safeLabel}](${url})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function userLink(user) {
|
||||||
|
if (!user || !user.login) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdownLink(user.login, user.html_url || `${serverUrl}/${user.login}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function issueLine(item, icon) {
|
||||||
|
return `${icon} #${item.number} ${markdownLink(item.title, item.html_url)}, by ${userLink(item.user)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitLine(commit) {
|
||||||
|
const title = commit.commit?.message?.split("\n")[0] || commit.sha;
|
||||||
|
const author = commit.author
|
||||||
|
? userLink(commit.author)
|
||||||
|
: commit.commit?.author?.name || "unknown";
|
||||||
|
return `:hammer_and_wrench: ${markdownLink(title, commit.html_url)} by ${author}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseLine(release) {
|
||||||
|
const label = release.name || release.tag_name;
|
||||||
|
return `:bookmark: ${markdownLink(label, release.html_url)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stargazerLine(star) {
|
||||||
|
return `:star: ${userLink(star.user)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sectionDivider(lines) {
|
||||||
|
lines.push("");
|
||||||
|
lines.push(" - - - ");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function githubRequest(method, path, options = {}) {
|
||||||
|
const query = options.query || {};
|
||||||
|
const url = new URL(`${apiUrl}${path}`);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(query)) {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
url.searchParams.set(key, String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
...defaultHeaders,
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
const data = text ? JSON.parse(text) : null;
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const message = data?.message || text || response.statusText;
|
||||||
|
throw new Error(`${method} ${url.pathname} failed: ${response.status} ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, headers: response.headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllPages(path, options = {}) {
|
||||||
|
const results = [];
|
||||||
|
let page = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { data } = await githubRequest("GET", path, {
|
||||||
|
...options,
|
||||||
|
query: {
|
||||||
|
...(options.query || {}),
|
||||||
|
per_page: 100,
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(...data);
|
||||||
|
|
||||||
|
if (data.length < 100) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchIssues(query) {
|
||||||
|
const results = [];
|
||||||
|
let page = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { data } = await githubRequest("GET", "/search/issues", {
|
||||||
|
query: {
|
||||||
|
q: query,
|
||||||
|
sort: "created",
|
||||||
|
order: "asc",
|
||||||
|
per_page: 100,
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = data.items || [];
|
||||||
|
results.push(...items);
|
||||||
|
|
||||||
|
if (items.length < 100 || results.length >= data.total_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLabel(item, labelName) {
|
||||||
|
return (item.labels || []).some((label) => label.name === labelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCreatedIssueItems() {
|
||||||
|
const items = await searchIssues(`repo:${owner}/${repo} ${searchRange("created")}`);
|
||||||
|
return items.filter((item) => !hasLabel(item, "weekly-digest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPullRequests() {
|
||||||
|
const updated = await searchIssues(`repo:${owner}/${repo} is:pr ${searchRange("updated")}`);
|
||||||
|
const merged = await searchIssues(`repo:${owner}/${repo} is:pr ${searchRange("merged")}`);
|
||||||
|
const byNumber = new Map();
|
||||||
|
|
||||||
|
for (const item of [...updated, ...merged]) {
|
||||||
|
byNumber.set(item.number, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pulls = [];
|
||||||
|
for (const item of byNumber.values()) {
|
||||||
|
const { data } = await githubRequest("GET", `/repos/${owner}/${repo}/pulls/${item.number}`);
|
||||||
|
pulls.push({
|
||||||
|
...item,
|
||||||
|
merged_at: data.merged_at,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pulls.sort((a, b) => a.number - b.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCommits() {
|
||||||
|
const commits = await getAllPages(`/repos/${owner}/${repo}/commits`, {
|
||||||
|
query: {
|
||||||
|
since: start.toISOString(),
|
||||||
|
until: end.toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return commits.sort((a, b) => new Date(a.commit.author.date) - new Date(b.commit.author.date));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getReleases() {
|
||||||
|
const releases = await getAllPages(`/repos/${owner}/${repo}/releases`);
|
||||||
|
return releases
|
||||||
|
.filter((release) => isWithinWindow(release.published_at || release.created_at))
|
||||||
|
.sort((a, b) => new Date(a.published_at || a.created_at) - new Date(b.published_at || b.created_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getStargazers() {
|
||||||
|
const stars = await getAllPages(`/repos/${owner}/${repo}/stargazers`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github.star+json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return stars
|
||||||
|
.filter((star) => isWithinWindow(star.starred_at))
|
||||||
|
.sort((a, b) => new Date(a.starred_at) - new Date(b.starred_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReactionTotal(item) {
|
||||||
|
const reactions = item.reactions || {};
|
||||||
|
return (reactions["+1"] || 0) + (reactions.smile || 0) + (reactions.tada || 0) + (reactions.heart || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendIssues(lines, createdItems) {
|
||||||
|
const openItems = createdItems.filter((item) => item.state === "open");
|
||||||
|
const closedItems = createdItems.filter((item) => item.state === "closed");
|
||||||
|
const likedItem = [...createdItems].sort((a, b) => getReactionTotal(b) - getReactionTotal(a))[0];
|
||||||
|
const noisyItem = [...createdItems].sort((a, b) => (b.comments || 0) - (a.comments || 0))[0];
|
||||||
|
|
||||||
|
lines.push("# ISSUES");
|
||||||
|
lines.push(`Last week ${createdItems.length} issues were created.`);
|
||||||
|
lines.push(`Of these, ${closedItems.length} issues have been closed and ${openItems.length} issues are still open.`);
|
||||||
|
|
||||||
|
if (openItems.length > 0) {
|
||||||
|
lines.push("## OPEN ISSUES");
|
||||||
|
lines.push(...openItems.map((item) => issueLine(item, ":green_heart:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closedItems.length > 0) {
|
||||||
|
lines.push("## CLOSED ISSUES");
|
||||||
|
lines.push(...closedItems.map((item) => issueLine(item, ":heart:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likedItem && getReactionTotal(likedItem) > 0) {
|
||||||
|
const reactions = likedItem.reactions || {};
|
||||||
|
lines.push("## LIKED ISSUE");
|
||||||
|
lines.push(issueLine(likedItem, ":+1:"));
|
||||||
|
lines.push(
|
||||||
|
`It received :+1: x${reactions["+1"] || 0}, :smile: x${reactions.smile || 0}, :tada: x${
|
||||||
|
reactions.tada || 0
|
||||||
|
} and :heart: x${reactions.heart || 0}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noisyItem && noisyItem.comments > 0) {
|
||||||
|
lines.push("## NOISY ISSUE");
|
||||||
|
lines.push(issueLine(noisyItem, ":speaker:"));
|
||||||
|
lines.push(`It received ${noisyItem.comments} comments.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendPullRequests(lines, pulls) {
|
||||||
|
const openPulls = pulls.filter((pull) => pull.state === "open");
|
||||||
|
const mergedPulls = pulls.filter((pull) => isWithinWindow(pull.merged_at));
|
||||||
|
const closedPulls = pulls.filter((pull) => pull.state === "closed" && !isWithinWindow(pull.merged_at));
|
||||||
|
|
||||||
|
lines.push("# PULL REQUESTS");
|
||||||
|
|
||||||
|
if (pulls.length === 0) {
|
||||||
|
lines.push("Last week, no pull requests were created, updated or merged.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`Last week, ${pulls.length} pull requests were created, updated or merged.`);
|
||||||
|
|
||||||
|
if (openPulls.length > 0) {
|
||||||
|
lines.push(`## OPEN PULL ${formatCountVerb(openPulls.length, "REQUEST", "REQUESTS")}`);
|
||||||
|
lines.push(...openPulls.map((item) => issueLine(item, ":green_heart:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closedPulls.length > 0) {
|
||||||
|
lines.push(`## CLOSED PULL ${formatCountVerb(closedPulls.length, "REQUEST", "REQUESTS")}`);
|
||||||
|
lines.push(...closedPulls.map((item) => issueLine(item, ":heart:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedPulls.length > 0) {
|
||||||
|
lines.push(`## MERGED PULL ${formatCountVerb(mergedPulls.length, "REQUEST", "REQUESTS")}`);
|
||||||
|
lines.push(...mergedPulls.map((item) => issueLine(item, ":purple_heart:")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendCommits(lines, commits) {
|
||||||
|
lines.push("# COMMITS");
|
||||||
|
|
||||||
|
if (commits.length === 0) {
|
||||||
|
lines.push("Last week there were no commits.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`Last week there were ${commits.length} commits.`);
|
||||||
|
lines.push(...commits.map(commitLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendContributors(lines, commits, createdItems, pulls) {
|
||||||
|
const contributors = new Map();
|
||||||
|
|
||||||
|
for (const commit of commits) {
|
||||||
|
if (commit.author?.login) {
|
||||||
|
contributors.set(commit.author.login, commit.author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contributors.size === 0) {
|
||||||
|
for (const item of [...createdItems, ...pulls]) {
|
||||||
|
if (item.user?.login) {
|
||||||
|
contributors.set(item.user.login, item.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("# CONTRIBUTORS");
|
||||||
|
lines.push(`Last week there ${formatCountVerb(contributors.size, "was", "were")} ${contributors.size} contributors.`);
|
||||||
|
lines.push(...[...contributors.values()].map((user) => `:bust_in_silhouette: ${userLink(user)}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendStargazers(lines, stargazers) {
|
||||||
|
lines.push("# STARGAZERS");
|
||||||
|
|
||||||
|
if (stargazers.length === 0) {
|
||||||
|
lines.push("Last week there were no stagazers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`Last week there were ${stargazers.length} stagazers.`);
|
||||||
|
lines.push(...stargazers.slice(0, 75).map(stargazerLine));
|
||||||
|
|
||||||
|
if (stargazers.length > 75) {
|
||||||
|
lines.push(`...and ${stargazers.length - 75} more.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("You all are the stars! :star2:");
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendReleases(lines, releases) {
|
||||||
|
lines.push("# RELEASES");
|
||||||
|
|
||||||
|
if (releases.length === 0) {
|
||||||
|
lines.push("Last week there were no releases.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`Last week there ${formatCountVerb(releases.length, "was", "were")} ${releases.length} releases.`);
|
||||||
|
lines.push(...releases.map(releaseLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDigestBody({ createdItems, pulls, commits, stargazers, releases }) {
|
||||||
|
const repoUrl = `${serverUrl}/${owner}/${repo}`;
|
||||||
|
const lines = [
|
||||||
|
`Here's the **Weekly Digest** for [*${owner}/${repo}*](${repoUrl}):`,
|
||||||
|
];
|
||||||
|
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendIssues(lines, createdItems);
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendPullRequests(lines, pulls);
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendCommits(lines, commits);
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendContributors(lines, commits, createdItems, pulls);
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendStargazers(lines, stargazers);
|
||||||
|
sectionDivider(lines);
|
||||||
|
appendReleases(lines, releases);
|
||||||
|
sectionDivider(lines);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(
|
||||||
|
`That's all for last week, please :eyes: **Watch** and :star: **Star** the repository [*${owner}/${repo}*](${repoUrl}) to receive next weekly updates. :smiley:`,
|
||||||
|
);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(
|
||||||
|
`*You can also [view all Weekly Digests by clicking here](${repoUrl}/issues?q=is:open+is:issue+label:weekly-digest).* `,
|
||||||
|
);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`> Your [**Weekly Digest**](${repoUrl}/actions/workflows/weekly-digest.yml) bot. :calendar:`);
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOpenDigestIssues() {
|
||||||
|
return getAllPages(`/repos/${owner}/${repo}/issues`, {
|
||||||
|
query: {
|
||||||
|
state: "open",
|
||||||
|
labels: "weekly-digest",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDigestIssue(title, body) {
|
||||||
|
const { data } = await githubRequest("POST", `/repos/${owner}/${repo}/issues`, {
|
||||||
|
body: {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
labels: ["weekly-digest"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closeOldDigestIssue(issue, newIssue) {
|
||||||
|
const labels = new Set((issue.labels || []).map((label) => label.name));
|
||||||
|
labels.add("oldnews");
|
||||||
|
|
||||||
|
await githubRequest("POST", `/repos/${owner}/${repo}/issues/${issue.number}/comments`, {
|
||||||
|
body: {
|
||||||
|
body: `A new weekly digest is available: #${newIssue.number}. Closing this older digest so only the latest one stays open.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await githubRequest("PATCH", `/repos/${owner}/${repo}/issues/${issue.number}`, {
|
||||||
|
body: {
|
||||||
|
state: "closed",
|
||||||
|
state_reason: "completed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await githubRequest("PATCH", `/repos/${owner}/${repo}/issues/${issue.number}`, {
|
||||||
|
body: {
|
||||||
|
labels: [...labels],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Could not add oldnews label to #${issue.number}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const title = `Weekly Digest (${formatDigestDate(start)} - ${formatDigestDate(end)})`;
|
||||||
|
const openDigestIssues = await getOpenDigestIssues();
|
||||||
|
const [createdItems, pulls, commits, stargazers, releases] = await Promise.all([
|
||||||
|
getCreatedIssueItems(),
|
||||||
|
getPullRequests(),
|
||||||
|
getCommits(),
|
||||||
|
getStargazers(),
|
||||||
|
getReleases(),
|
||||||
|
]);
|
||||||
|
const body = buildDigestBody({ createdItems, pulls, commits, stargazers, releases });
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log(`DRY RUN: would create "${title}"`);
|
||||||
|
console.log(body);
|
||||||
|
console.log(`DRY RUN: would close ${openDigestIssues.length} older digest issues.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIssue = await createDigestIssue(title, body);
|
||||||
|
const oldIssues = openDigestIssues.filter((issue) => issue.number !== newIssue.number);
|
||||||
|
|
||||||
|
for (const issue of oldIssues) {
|
||||||
|
await closeOldDigestIssue(issue, newIssue);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Created ${newIssue.html_url}`);
|
||||||
|
console.log(`Closed ${oldIssues.length} older digest issues.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
######################################################################
|
||||||
|
# @CCOSTAN - Follow Me on X
|
||||||
|
# For more info visit https://www.vcloudinfo.com/click-here
|
||||||
|
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Secret Scan Workflow - TruffleHog credential leak detection.
|
||||||
|
# Runs verified-only secret scanning on pull requests, master pushes,
|
||||||
|
# and manual dispatch without adding local commit-hook friction.
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
######################################################################
|
||||||
|
name: Secret Scan
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trufflehog:
|
||||||
|
name: TruffleHog
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Scan for verified secrets
|
||||||
|
uses: trufflesecurity/trufflehog@v3.95.3
|
||||||
|
with:
|
||||||
|
version: 3.95.3
|
||||||
|
extra_args: --results=verified --force-skip-binaries --force-skip-archives
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
######################################################################
|
||||||
|
# @CCOSTAN - Follow Me on X
|
||||||
|
# For more info visit https://www.vcloudinfo.com/click-here
|
||||||
|
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Weekly Digest Workflow - scheduled repository issue summary.
|
||||||
|
# Runs the GitHub Actions workflow for weekly digest reporting.
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
######################################################################
|
||||||
|
name: Weekly Digest
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "37 6 * * 1"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
weekly-digest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Create weekly digest issue
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||||
|
GITHUB_API_URL: ${{ github.api_url }}
|
||||||
|
run: node .github/scripts/weekly-digest.mjs
|
||||||
@ -1,4 +1,13 @@
|
|||||||
|
######################################################################
|
||||||
|
# @CCOSTAN - Follow Me on X
|
||||||
|
# For more info visit https://www.vcloudinfo.com/click-here
|
||||||
|
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Infrastructure Doc Sync Agent - Codex skill entry metadata.
|
||||||
|
# Defines the OpenAI-facing launcher text for this local skill.
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
######################################################################
|
||||||
interface:
|
interface:
|
||||||
display_name: "Infrastructure Doc Sync"
|
display_name: "Infrastructure Doc Sync"
|
||||||
short_description: "Sync infra docs with concise AGENTS"
|
short_description: "Sync infra docs with concise AGENTS"
|
||||||
default_prompt: "Use $infrastructure-doc-sync to keep AGENTS concise and scoped, move runbook content into dedicated docs when needed, and sync README/Dashy/infra_info overview.json after infra changes."
|
default_prompt: "Use $infrastructure-doc-sync to keep AGENTS concise and scoped, move runbook content into dedicated docs when needed, and sync README/Dashy/BearClaw infrastructure snapshot context after infra changes."
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
2026.4.4
|
2026.5.4
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 924 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 839 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |