Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/self-host-apps-discovery-catalogs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
--out scripts/self-host-apps/batches/${{ github.run_id }}-catalog-discovery.json
--write
- name: Classify exact duplicates and review matches
if: always()
run: npm run self-host-apps:dedupe-candidates -- --limit 30000
- name: Upload discovery report
if: always()
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/self-host-apps-refresh-github.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: Refresh self-host app GitHub facts
name: Refresh self-host and CLI app GitHub facts

on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
inputs:
limit:
description: "Maximum rows to refresh. Use 0 with all=true."
description: "Maximum unique repositories to refresh. Use 0 with all=true."
default: "500"
required: false
all:
description: "Refresh every matching row instead of using limit."
description: "Refresh every matching repository instead of using limit."
default: false
required: false
type: boolean
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/self-host-apps-refresh-source-hosts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ jobs:
- name: Refresh GitLab facts
run: npm run self-host-apps:refresh-gitlab -- --published-only --limit 500 --max-age-hours 24
- name: Refresh Codeberg facts
if: always()
run: npm run self-host-apps:refresh-codeberg -- --published-only --limit 500 --max-age-hours 24
3 changes: 3 additions & 0 deletions .github/workflows/self-host-apps-refresh-technical.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ jobs:
npm run self-host-apps:refresh-primary-technical-facts --
${{ (github.event_name == 'schedule' || inputs.refresh_all == true) && '--refresh-all' || '' }}
--concurrency 16
--max-failure-percent 5
--out scripts/self-host-apps/batches/${{ github.run_id }}-technical-refresh.json
- name: Inspect install destinations
# Keep dry-run: the builder infers registry links/support levels and replaces
# stored options; these changes require review before applying.
if: always()
run: npm run self-host-apps:refresh-install-options -- --limit 5000
- name: Run publication quality gate
Expand Down
12 changes: 1 addition & 11 deletions scripts/cli-apps/build-curated-batch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,7 @@ import path from "node:path";
import { parseArgs } from "./lib/args.mjs";
import { curated } from "./curated-map.mjs";

function maintenanceFrom(github, override) {
if (override) return override.toLowerCase();
if (github?.archived) return "archived";
if (!github?.pushed_at) return "unknown";
const pushedAt = new Date(github.pushed_at).getTime();
const ageDays = (Date.now() - pushedAt) / (1000 * 60 * 60 * 24);
if (Number.isNaN(ageDays)) return "unknown";
if (ageDays < 110) return "active";
if (ageDays < 395) return "slow";
return "stale";
}
import { maintenanceFrom } from "./lib/maintenance.mjs";

async function main() {
const args = parseArgs();
Expand Down
12 changes: 12 additions & 0 deletions scripts/cli-apps/lib/maintenance.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function maintenanceFrom(github, override, now = Date.now()) {
if (override) return override.toLowerCase();
if (github?.archived) return "archived";
if (!github?.pushed_at) return "unknown";
const pushedAt = new Date(github.pushed_at).getTime();
const ageDays = (now - pushedAt) / (1000 * 60 * 60 * 24);
if (Number.isNaN(ageDays)) return "unknown";
if (ageDays < 110) return "active";
if (ageDays < 395) return "slow";
return "stale";
}

10 changes: 6 additions & 4 deletions scripts/self-host-apps/discover-app-catalogs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
parseYunoHostAppsToml,
} from "./lib/catalog-adapters.mjs";
import { withClient } from "./lib/db.mjs";
import { pathToFileURL } from "node:url";

const catalogs = {
truenas: {
Expand All @@ -27,7 +28,7 @@ const catalogs = {
docker_hub: {
source: "docker_hub",
sourceUrl: "https://hub.docker.com/search",
searchUrl: "https://hub.docker.com/v2/search/repositories/",
searchUrl: "https://registry.hub.docker.com/v2/search/repositories/",
queries: ["self-hosted", "selfhosted", "homelab", "home-server", "nas", "media-server", "homeassistant"],
},
unraid: {
Expand Down Expand Up @@ -80,12 +81,13 @@ async function fetchResponse(url, options = {}) {
throw lastError ?? new Error(`Failed to fetch ${url}`);
}

async function fetchJson(url) {
export async function fetchJson(url) {
const isGithubApi = new URL(url).origin === "https://api.github.com";
const response = await fetchResponse(url, {
headers: {
Accept: "application/vnd.github+json, application/json",
"User-Agent": "stack127-self-host-app-catalog-discovery/1.0",
...(process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}),
...(isGithubApi && process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}),
},
});
return response.json();
Expand Down Expand Up @@ -346,7 +348,7 @@ async function main() {
if (Object.keys(report.errors).length > 0) process.exitCode = 1;
}

main().catch((error) => {
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main().catch((error) => {
console.error(error.stack ?? error.message);
process.exit(1);
});
14 changes: 14 additions & 0 deletions scripts/self-host-apps/lib/job-summary.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { appendFileSync } from "node:fs";

function cell(value) {
return String(value ?? "").replace(/[\r\n]+/g, " ").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\|/g, "&#124;");
}

export function jobSummary(title, counts, failures = []) {
if (!process.env.GITHUB_STEP_SUMMARY) return;
const lines = [`## ${cell(title)}`, "", ...Object.entries(counts).map(([name, count]) => `- ${cell(name)}: ${cell(count)}`)];
if (failures.length) {
lines.push("", "| Record | Failure |", "| --- | --- |", ...failures.map((failure) => `| ${cell(failure.repository ?? failure.slug)} | ${cell(failure.error)} |`));
}
appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${lines.join("\n")}\n\n`);
}
5 changes: 4 additions & 1 deletion scripts/self-host-apps/qa-gate.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/env node

import { writeFileSync, mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { parseArgs, numberArg } from "./lib/args.mjs";
import { withClient } from "./lib/db.mjs";
import { validateSummary } from "./lib/summary-rules.mjs";
import { isManagedMediaUrl } from "../stack127/media/storage.mjs";
import { hasCompleteResourceEstimate } from "./lib/resource-review.mjs";
import { jobSummary } from "./lib/job-summary.mjs";

const dockerHighRiskPattern =
/(^|\/)(\.github|\.ci|\.devcontainer|dev|vendor|test|tests|testing|ci|e2e(?:[-_][^/]*)?|fixtures?|examples?|docker-compose-examples|test-infrastructure|tests_integration)(\/|$)|devcontainer|dev-container|compose\.dev|docker-compose\.dev|docker-compose[-.]ci|compose[-.]ci|example|gitpod|\.ddev/i;
Expand Down Expand Up @@ -165,11 +167,12 @@ async function main() {
};

if (args.out) {
mkdirSync(path.dirname(args.out), { recursive: true });
mkdirSync(dirname(args.out), { recursive: true });
writeFileSync(args.out, `${JSON.stringify(report, null, 2)}\n`);
}

console.log(JSON.stringify(report, null, 2));
jobSummary("Publication QA", { Published: report.published, Blocked: report.blocked_count, "Excluded published": report.excluded_published_count });
}

main().catch((error) => {
Expand Down
Loading
Loading