Search and open starred GitHub repos

Open search-starred-repos in Script Kit

This script allows searching and opening your (or anyone's) starred repositories from GitHub. It works by setting up a second, scheduled script that downloads and caches the repos in db() so searching is quick every time afterwards.

When running this script for the first time it checks if the cache script exists and otherwise creates it for you with the given schedule. On first run it doesn't have any cached repos yet and therefor tells you to run the cache-script manually once.

On consecutive runs the search script uses cached starred repositories to display choices and opens the selection in the browser.

// Name: Search starred GitHub repos
// Description: Search and filter starred GitHub repositories
// Author: Basti Buck
// Twitter: @bastibuck
import "@johnlindquist/kit";
import { Choices } from "@johnlindquist/kit";
const CACHE_SCRIPT_NAME = "cache-starred-repos.ts";
const { isValidCron }: typeof import("cron-validator") = await npm(
"cron-validator"
);
// setup caching script if not installed
const installedScripts = await readdir(home(kenvPath("/scripts")));
if (!installedScripts.includes(CACHE_SCRIPT_NAME)) {
const cronSchedule = await arg({
placeholder:
"Choose schedule or define your own schedule using CRON syntax",
strict: false,
choices: getCronChoices,
validate: validateCRON,
});
await appendFile(
home(kenvPath("/scripts"), CACHE_SCRIPT_NAME),
buildCacheScript({ cronSchedule })
);
await div(
`<div class='p-12 text-lg text-center'>
<h2 class='mb-8'>
Looks like this is your first run.
</h2>
<h3>I've setup a caching script for you.</h3>
<p class='mb-20'>
This script will run on the defined schedule and cache your starred repos for faster searching. If you need to run the caching manually you can do so from Kit.
</p>
<p>
To initialize your cache now, run <code class='text-yellow-400'>Cache starred GitHub repos</code>.
</p>
</div>`
);
exit(0);
}
// show filterable starred repos
let { cachedStars } = await db("starred-repos", { cachedStars: [] });
const star = await arg(
"Search starred repos...",
cachedStars.map((star) => {
return {
name: star.full_name,
value: star.html_url,
description: `${star.stargazers_count} ${
star.description ? " | " + star.description : ""
}`,
icon: star.owner.avatar_url,
};
})
);
browse(star);
// helper functions
function getCronChoices(): Choices<string> {
return [
{
name: "0 12 * * *",
value: "0 12 * * *",
description: "Daily at noon",
},
{
name: "0 8 * * *",
value: "0 8 * * *",
description: "Daily at 8:00 am",
},
{
name: "0 8 * * 1",
value: "0 8 * * 1",
description: "Weekly on Monday morning",
},
];
}
function validateCRON(cron: string) {
return isValidCron(cron)
? true
: "<span class='text-red-500'>Invalid CRON syntax. Check <a href='https://crontab.guru/'>crontab.guru</a> for help</span>";
}
function buildCacheScript({ cronSchedule }: { cronSchedule: string }) {
return `// Name: Cache starred GitHub repos
// Description: Cache starred GitHub repositories for future operations
// Author: Basti Buck
// Twitter: @bastibuck
\/\/ Schedule: ${cronSchedule}
import "@johnlindquist/kit";
const { z }: typeof import("zod") = await npm("zod");
const { Octokit }: typeof import("octokit") = await npm("octokit");
import { Endpoints } from "@octokit/types";
const AUTH_TOKEN = await env("GITHUB_AUTH_TOKEN", {
hint: md(
\`Create a [personal access token](https://github.com/settings/tokens) with the \\\`read:user\\\` scope.\`
),
ignoreBlur: true,
secret: true,
});
const USERNAME = await env("GITHUB_USERNAME", {
hint: md(\`Your GitHub username\`),
});
type Star = Endpoints["GET /user/starred"]["response"]["data"][number];
const cachedStarSchema = z.array(
z.object({
full_name: z.string(),
html_url: z.string(),
stargazers_count: z.number(),
description: z.string().optional().nullish(),
owner: z.object({
avatar_url: z.string(),
}),
})
);
const octokit = new Octokit({
auth: AUTH_TOKEN,
});
let { cachedStars, write } = await db("starred-repos", {
cachedStars: [],
});
const stars = await octokit.paginate<Star>(\`GET /users/\${USERNAME}/starred\`);
_.remove(cachedStars, () => true);
cachedStars.push(...cachedStarSchema.parse(stars));
await write();
`;
}

Any feedback appreciated 😀