diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..3a4f5ccf --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.ddev/ diff --git a/README.md b/README.md index e2f2a365..bdc632d5 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,6 @@ All commands are run from the root of the project, from a terminal: ### Local Development Setup -1. Run `cp .env.example .env` to create a `.env` file for environment variables. (Don’t check this in!) -2. Create a [classic GitHub access token](https://github.com/settings/tokens) with these scopes: `repo`, `read:org`, `read:user`, and `read:project`. -3. Paste the GitHub token after `.env`’s `GITHUB_TOKEN=`. - #### DDEV setup DDEV already has all the dependencies included. @@ -83,11 +79,22 @@ Check out the project in your favorite Node.js environment, ideally running [`nv To generate a static copy of the site, run `npm run build`. The contents of the `dist/` folder are exactly what get [deployed to Cloudflare Pages](#build--deployment). You can preview locally by running `npm run preview` or using a tool like [`serve`](https://www.npmjs.com/package/serve). - #### Switching from Without DDEV to with DDEV Make sure to delete your `node_modules/` directory and run `ddev npm install`. The change in architecture can create odd issues otherwise. +#### GitHub Token + +This step is not required if you just want to contribute a blog post to ddev.com. + +Contributors, sponsors, releases and more data about DDEV is retrieved dynamically from the GitHub API. To test this, please follow these steps: + +1. Run `cp .env.example .env` to create a `.env` file for environment variables. (Don’t check this in!) +2. Create a [classic GitHub access token](https://github.com/settings/tokens) with these scopes: `repo`, `read:org`, `read:user`, and `read:project`. +3. Paste the GitHub token after `.env`’s `GITHUB_TOKEN=`. + +There is a local `cache/` to reduce API calls. + ## Managing Content The site’s content lives in either `.astro` components that resemble souped-up HTML, or Markdown files organized into schema-validated [content collections](https://docs.astro.build/en/guides/content-collections/). diff --git a/src/components/RepoCard.astro b/src/components/RepoCard.astro index dc92670f..ae2075cb 100644 --- a/src/components/RepoCard.astro +++ b/src/components/RepoCard.astro @@ -28,7 +28,7 @@ const repoData = await getRepoDetails(name) className="w-4 h-4 inline-block relative" style={{ top: "-1px" }} />{" "} - {parseInt(repoData.stargazers_count).toLocaleString("en-US")} + {parseInt(repoData.stargazers_count || 0).toLocaleString("en-US")}
diff --git a/src/lib/api.ts b/src/lib/api.ts index 571d03d6..7c2d75ba 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -4,16 +4,33 @@ import dotenv from "dotenv" import fs2 from "fs" -import path from 'path' -import Slugger from 'github-slugger' -import { Octokit } from "octokit"; +import path from "path" +import Slugger from "github-slugger" +import { Octokit } from "octokit" import { GITHUB_REPO } from "./../const" dotenv.config() // Project-root-relative directory for temporary data used in local development -const DEVELOPMENT_CACHE_DIR = 'cache' -let octokitInstance: Octokit; +const DEVELOPMENT_CACHE_DIR = "cache" +let octokitInstance: Octokit + +// Define variable if GITHUB_TOKEN is set and not empty +const githubTokenIsSet: boolean = (() => { + if ( + process.env.hasOwnProperty("GITHUB_TOKEN") === false || + process.env.GITHUB_TOKEN === "" + ) { + // add warning for production builds + if (import.meta.env.MODE === "production") { + console.warn( + "GITHUB_TOKEN not set or empty. You can ignore this warning for local development." + ) + } + return false + } + return true +})() /** * Returns an instance of Octokit, which uses the `GITHUB_TOKEN` environment @@ -22,14 +39,14 @@ let octokitInstance: Octokit; */ const octokit = () => { if (octokitInstance) { - return octokitInstance; + return octokitInstance } octokitInstance = new Octokit({ auth: process.env.GITHUB_TOKEN, - }); - - return octokitInstance; + }) + + return octokitInstance } /** @@ -38,8 +55,8 @@ const octokit = () => { * @returns string */ export function getSlug(value: string) { - const slugger = new Slugger(); - return slugger.slug(value); + const slugger = new Slugger() + return slugger.slug(value) } /** @@ -56,11 +73,15 @@ export function getCategoryUrl(name: string) { * @returns response data */ export async function getSponsors() { - const cacheFilename = 'sponsors.json' - const cachedData = getCache(cacheFilename); + if (!githubTokenIsSet) { + return [] + } + + const cacheFilename = "sponsors.json" + const cachedData = getCache(cacheFilename) if (cachedData) { - return cachedData; + return cachedData } const response = await octokit().graphql(` @@ -106,11 +127,11 @@ export async function getSponsors() { } `) - const rfayData = response.user.sponsors.nodes; - const orgData = response.organization.sponsors.nodes; - const data = [ ...rfayData, ...orgData ]; + const rfayData = response.user.sponsors.nodes + const orgData = response.organization.sponsors.nodes + const data = [...rfayData, ...orgData] - putCache(cacheFilename, JSON.stringify(data)); + putCache(cacheFilename, JSON.stringify(data)) return data } @@ -121,27 +142,34 @@ export async function getSponsors() { * @returns response data */ export async function getContributors(includeAnonymous = false) { - const cacheFilename = 'contributors.json' - const cachedData = getCache(cacheFilename); + if (!githubTokenIsSet) { + return [] + } - let data; + const cacheFilename = "contributors.json" + const cachedData = getCache(cacheFilename) + + let data if (cachedData) { - data = cachedData; + data = cachedData } else { - const response = await octokit().paginate(`GET https://api.github.com/repos/${GITHUB_REPO}/contributors`, { - anon: 1, - per_page: 100, - }); - - data = response; - putCache(cacheFilename, JSON.stringify(data)); + const response = await octokit().paginate( + `GET https://api.github.com/repos/${GITHUB_REPO}/contributors`, + { + anon: 1, + per_page: 100, + } + ) + + data = response + putCache(cacheFilename, JSON.stringify(data)) } if (!includeAnonymous) { return data.filter((contributor) => { - return contributor.type !== 'Anonymous' - }); + return contributor.type !== "Anonymous" + }) } return data ?? [] @@ -150,25 +178,31 @@ export async function getContributors(includeAnonymous = false) { /** * Gets repository details from GitHub. * https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository - * + * * @param name The name of the repository, like `ddev/ddev`. * @returns response data */ export async function getRepoDetails(name: string) { - const slug = name.replace('/', '-') - const cacheFilename = `repository-${slug}.json`; - const cachedData = getCache(cacheFilename); + if (!githubTokenIsSet) { + return [] + } + + const slug = name.replace("/", "-") + const cacheFilename = `repository-${slug}.json` + const cachedData = getCache(cacheFilename) if (cachedData) { - return cachedData; + return cachedData } - const response = await octokit().request(`GET https://api.github.com/repos/${name}`) - const data = response.data; + const response = await octokit().request( + `GET https://api.github.com/repos/${name}` + ) + const data = response.data - putCache(cacheFilename, JSON.stringify(data)); + putCache(cacheFilename, JSON.stringify(data)) - return data; + return data } /** @@ -178,30 +212,41 @@ export async function getRepoDetails(name: string) { * @returns tag name */ export async function getLatestReleaseVersion(stable = true) { + if (!githubTokenIsSet) { + return [] + } + let data = await getReleases() if (stable) { data = data.filter((release) => { - return !release.draft && !release.prerelease; + return !release.draft && !release.prerelease }) } - return data[0].tag_name; + return data[0].tag_name } export async function getReleases() { - const cacheFilename = 'releases.json' - const cachedData = getCache(cacheFilename); + if (!githubTokenIsSet) { + return [] + } + + const cacheFilename = "releases.json" + const cachedData = getCache(cacheFilename) if (cachedData) { - return cachedData; + return cachedData } - const response = await octokit().paginate(`GET https://api.github.com/repos/${GITHUB_REPO}/releases`, { - per_page: 100, - }); + const response = await octokit().paginate( + `GET https://api.github.com/repos/${GITHUB_REPO}/releases`, + { + per_page: 100, + } + ) - putCache(cacheFilename, JSON.stringify(response)); + putCache(cacheFilename, JSON.stringify(response)) return response ?? [] } @@ -212,12 +257,12 @@ export async function getReleases() { * @returns file contents or null */ const getCache = (filename: string) => { - const dir = path.resolve('./' + DEVELOPMENT_CACHE_DIR) - const filePath = dir + '/' + filename - + const dir = path.resolve("./" + DEVELOPMENT_CACHE_DIR) + const filePath = dir + "/" + filename + if (fs2.existsSync(filePath)) { - const contents = fs2.readFileSync(filePath); - return JSON.parse(contents); + const contents = fs2.readFileSync(filePath) + return JSON.parse(contents) } return @@ -229,11 +274,11 @@ const getCache = (filename: string) => { * @param contents Contents of the file. */ const putCache = (filename: string, contents: string) => { - const dir = path.resolve('./' + DEVELOPMENT_CACHE_DIR) - const filePath = dir + '/' + filename + const dir = path.resolve("./" + DEVELOPMENT_CACHE_DIR) + const filePath = dir + "/" + filename if (!fs2.existsSync(dir)) { - fs2.mkdirSync(dir); + fs2.mkdirSync(dir) } fs2.writeFileSync(filePath, contents) @@ -249,15 +294,15 @@ const putCache = (filename: string, contents: string) => { export const formatDate = (date: string, customOptions?: object) => { const pubDate = new Date(date) const defaultOptions = { - timeZone: "UTC", - month: "short", - day: "numeric", - year: "numeric", + timeZone: "UTC", + month: "short", + day: "numeric", + year: "numeric", } const options = { ...defaultOptions, - ...customOptions + ...customOptions, } return new Intl.DateTimeFormat("en-US", options).format(pubDate) diff --git a/src/pages/about.astro b/src/pages/about.astro index 5b3f50fd..5a73a432 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -96,7 +96,7 @@ const releaseData = await getReleases()