Merge pull request #178 from sambecker/git-meta
Add fork status to app configuration page
This commit is contained in:
commit
8c5edaf893
47
__tests__/github.test.ts
Normal file
47
__tests__/github.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { getGitHubMetaWithFallback, getGitHubPublicFork } from '@/admin/github';
|
||||
import { TEMPLATE_BASE_OWNER, TEMPLATE_BASE_REPO } from '@/site/config';
|
||||
|
||||
describe('GitHub', () => {
|
||||
it('fetches base repo meta', async () => {
|
||||
const meta = await getGitHubMetaWithFallback({
|
||||
owner: TEMPLATE_BASE_OWNER,
|
||||
repo: TEMPLATE_BASE_REPO,
|
||||
});
|
||||
expect(meta).toBeDefined();
|
||||
expect(meta.url).toBeDefined();
|
||||
expect(meta.isForkedFromBase).toEqual(false);
|
||||
expect(meta.label).toBeDefined();
|
||||
expect(meta.title).toBeDefined();
|
||||
expect(meta.isBehind).toEqual(false);
|
||||
expect(meta.isBaseRepo).toBe(true);
|
||||
});
|
||||
it('fetches fork meta', async () => {
|
||||
const fork = await getGitHubPublicFork();
|
||||
const metaFork = await getGitHubMetaWithFallback(fork);
|
||||
expect(metaFork.isForkedFromBase).toEqual(true);
|
||||
});
|
||||
it('handles nonexistent repos', async () => {
|
||||
const meta = await getGitHubMetaWithFallback({
|
||||
owner: 'nonexistent',
|
||||
repo: 'nonexistent',
|
||||
});
|
||||
expect(meta).toBeDefined();
|
||||
expect(meta.url).toBeDefined();
|
||||
expect(meta.isForkedFromBase).toEqual(false);
|
||||
expect(meta.label).toEqual('Unknown');
|
||||
expect(meta.title).toEqual('Unknown');
|
||||
expect(meta.isBehind).toBeUndefined();
|
||||
});
|
||||
it('handles fetch errors', async () => {
|
||||
const meta = await getGitHubMetaWithFallback({
|
||||
owner: 'gibberish / / *',
|
||||
repo: 'bad text for a url.com',
|
||||
});
|
||||
expect(meta).toBeDefined();
|
||||
expect(meta.url).toBeDefined();
|
||||
expect(meta.isForkedFromBase).toEqual(false);
|
||||
expect(meta.label).toEqual('Unknown');
|
||||
expect(meta.title).toEqual('Unknown');
|
||||
expect(meta.isBehind).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,7 @@
|
||||
import { htmlHasBrParagraphBreaks, safelyParseFormattedHtml } from '@/utility/html';
|
||||
import { parameterize } from '@/utility/string';
|
||||
import {
|
||||
htmlHasBrParagraphBreaks,
|
||||
safelyParseFormattedHtml,
|
||||
} from '@/utility/html';
|
||||
|
||||
describe('HTML', () => {
|
||||
it('safely parses', () => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/* eslint-disable max-len */
|
||||
import type { Config } from 'jest';
|
||||
import nextJest from 'next/jest.js';
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
@ -7,12 +8,10 @@ const createJestConfig = nextJest({
|
||||
});
|
||||
|
||||
// Add any custom config to be passed to Jest
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
// Add more setup options before each test is run
|
||||
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
const config: Config = {
|
||||
coverageProvider: 'v8',
|
||||
testEnvironment: 'jsdom',
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
};
|
||||
|
||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
||||
1
jest.setup.ts
Normal file
1
jest.setup.ts
Normal file
@ -0,0 +1 @@
|
||||
import 'cross-fetch/polyfill';
|
||||
@ -47,6 +47,7 @@
|
||||
"@next/bundle-analyzer": "15.1.6",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
@ -57,12 +58,14 @@
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"clsx": "^2.1.1",
|
||||
"cross-fetch": "^4.1.0",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-config-next": "15.1.6",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"postcss": "8.5.1",
|
||||
"tailwindcss": "3.4.17",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
209
pnpm-lock.yaml
generated
209
pnpm-lock.yaml
generated
@ -113,16 +113,19 @@ importers:
|
||||
version: 15.1.6
|
||||
'@tailwindcss/container-queries':
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1(tailwindcss@3.4.17)
|
||||
version: 0.1.1(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.10
|
||||
version: 0.5.10(tailwindcss@3.4.17)
|
||||
version: 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))
|
||||
'@testing-library/dom':
|
||||
specifier: ^10.4.0
|
||||
version: 10.4.0
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.6.3
|
||||
version: 6.6.3
|
||||
'@testing-library/react':
|
||||
specifier: ^16.2.0
|
||||
version: 16.2.0(@testing-library/dom@10.1.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@types/jest':
|
||||
specifier: ^29.5.14
|
||||
version: 29.5.14
|
||||
@ -147,6 +150,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
cross-fetch:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
eslint:
|
||||
specifier: 9.18.0
|
||||
version: 9.18.0(jiti@1.21.7)
|
||||
@ -155,7 +161,7 @@ importers:
|
||||
version: 15.1.6(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3)
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@22.10.7)
|
||||
version: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
jest-environment-jsdom:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
@ -164,7 +170,10 @@ importers:
|
||||
version: 8.5.1
|
||||
tailwindcss:
|
||||
specifier: 3.4.17
|
||||
version: 3.4.17
|
||||
version: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@22.10.7)(typescript@5.7.3)
|
||||
typescript:
|
||||
specifier: 5.7.3
|
||||
version: 5.7.3
|
||||
@ -566,6 +575,10 @@ packages:
|
||||
'@bcoe/v8-coverage@0.2.3':
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7':
|
||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@ -847,6 +860,9 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
|
||||
'@next/bundle-analyzer@15.1.6':
|
||||
resolution: {integrity: sha512-hGzQyDqJzFHcHNCyTqM3o05BpVq5tGnRODccZBVJDBf5Miv/26UJPMB0wh9L9j3ylgHC+0/v8BaBnBBek1rC6Q==}
|
||||
|
||||
@ -1487,8 +1503,8 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
|
||||
|
||||
'@testing-library/dom@10.1.0':
|
||||
resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==}
|
||||
'@testing-library/dom@10.4.0':
|
||||
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@testing-library/jest-dom@6.6.3':
|
||||
@ -1514,6 +1530,18 @@ packages:
|
||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@tsconfig/node10@1.0.11':
|
||||
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
|
||||
|
||||
'@tsconfig/node12@1.0.11':
|
||||
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
|
||||
|
||||
'@tsconfig/node14@1.0.3':
|
||||
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
|
||||
|
||||
'@tsconfig/node16@1.0.4':
|
||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
@ -1819,6 +1847,9 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
@ -2112,6 +2143,12 @@ packages:
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -2250,6 +2287,10 @@ packages:
|
||||
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
diff@4.0.2:
|
||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
@ -3217,6 +3258,9 @@ packages:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
makeerror@1.0.12:
|
||||
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
|
||||
|
||||
@ -3355,6 +3399,15 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-int64@0.4.0:
|
||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||
|
||||
@ -4098,6 +4151,9 @@ packages:
|
||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
tr46@3.0.0:
|
||||
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -4114,6 +4170,20 @@ packages:
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
ts-node@10.9.2:
|
||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
|
||||
@ -4233,6 +4303,9 @@ packages:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
hasBin: true
|
||||
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
v8-to-istanbul@9.2.0:
|
||||
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
@ -4255,6 +4328,9 @@ packages:
|
||||
walker@1.0.8:
|
||||
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
webidl-conversions@7.0.0:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
@ -4276,6 +4352,9 @@ packages:
|
||||
resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -4371,6 +4450,10 @@ packages:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@ -5121,6 +5204,10 @@ snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@0.2.3': {}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7': {}
|
||||
|
||||
'@emnapi/runtime@1.2.0':
|
||||
@ -5305,7 +5392,7 @@ snapshots:
|
||||
jest-util: 29.7.0
|
||||
slash: 3.0.0
|
||||
|
||||
'@jest/core@29.7.0':
|
||||
'@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))':
|
||||
dependencies:
|
||||
'@jest/console': 29.7.0
|
||||
'@jest/reporters': 29.7.0
|
||||
@ -5319,7 +5406,7 @@ snapshots:
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 29.7.0
|
||||
jest-config: 29.7.0(@types/node@22.10.7)
|
||||
jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
jest-haste-map: 29.7.0
|
||||
jest-message-util: 29.7.0
|
||||
jest-regex-util: 29.6.3
|
||||
@ -5475,6 +5562,11 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
'@next/bundle-analyzer@15.1.6':
|
||||
dependencies:
|
||||
webpack-bundle-analyzer: 4.10.1
|
||||
@ -6169,16 +6261,16 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.17)':
|
||||
'@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))':
|
||||
dependencies:
|
||||
tailwindcss: 3.4.17
|
||||
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
|
||||
'@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)':
|
||||
'@tailwindcss/forms@0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))':
|
||||
dependencies:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.4.17
|
||||
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
|
||||
'@testing-library/dom@10.1.0':
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.2
|
||||
'@babel/runtime': 7.24.5
|
||||
@ -6199,10 +6291,10 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/react@16.2.0(@testing-library/dom@10.1.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
'@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.5
|
||||
'@testing-library/dom': 10.1.0
|
||||
'@testing-library/dom': 10.4.0
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
optionalDependencies:
|
||||
@ -6211,6 +6303,14 @@ snapshots:
|
||||
|
||||
'@tootallnate/once@2.0.0': {}
|
||||
|
||||
'@tsconfig/node10@1.0.11': {}
|
||||
|
||||
'@tsconfig/node12@1.0.11': {}
|
||||
|
||||
'@tsconfig/node14@1.0.3': {}
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
@ -6552,6 +6652,8 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
@ -6902,13 +7004,13 @@ snapshots:
|
||||
|
||||
cookie@0.7.1: {}
|
||||
|
||||
create-jest@29.7.0(@types/node@22.10.7):
|
||||
create-jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-config: 29.7.0(@types/node@22.10.7)
|
||||
jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
jest-util: 29.7.0
|
||||
prompts: 2.4.2
|
||||
transitivePeerDependencies:
|
||||
@ -6917,6 +7019,14 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -7029,6 +7139,8 @@ snapshots:
|
||||
|
||||
diff-sequences@29.6.3: {}
|
||||
|
||||
diff@4.0.2: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
doctrine@2.1.0:
|
||||
@ -7926,16 +8038,16 @@ snapshots:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-cli@29.7.0(@types/node@22.10.7):
|
||||
jest-cli@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0(@types/node@22.10.7)
|
||||
create-jest: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
exit: 0.1.2
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.7.0(@types/node@22.10.7)
|
||||
jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
yargs: 17.7.2
|
||||
@ -7945,7 +8057,7 @@ snapshots:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest-config@29.7.0(@types/node@22.10.7):
|
||||
jest-config@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.24.5
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
@ -7971,6 +8083,7 @@ snapshots:
|
||||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 22.10.7
|
||||
ts-node: 10.9.2(@types/node@22.10.7)(typescript@5.7.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
@ -8205,12 +8318,12 @@ snapshots:
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
jest@29.7.0(@types/node@22.10.7):
|
||||
jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 29.7.0(@types/node@22.10.7)
|
||||
jest-cli: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
@ -8355,6 +8468,8 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.6.3
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
makeerror@1.0.12:
|
||||
dependencies:
|
||||
tmpl: 1.0.5
|
||||
@ -8465,6 +8580,10 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-int64@0.4.0: {}
|
||||
|
||||
node-releases@2.0.14: {}
|
||||
@ -8678,12 +8797,13 @@ snapshots:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.5.1
|
||||
|
||||
postcss-load-config@4.0.2(postcss@8.5.1):
|
||||
postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
yaml: 2.4.2
|
||||
optionalDependencies:
|
||||
postcss: 8.5.1
|
||||
ts-node: 10.9.2(@types/node@22.10.7)(typescript@5.7.3)
|
||||
|
||||
postcss-nested@6.2.0(postcss@8.5.1):
|
||||
dependencies:
|
||||
@ -9194,7 +9314,7 @@ snapshots:
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
tailwindcss@3.4.17:
|
||||
tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
arg: 5.0.2
|
||||
@ -9213,7 +9333,7 @@ snapshots:
|
||||
postcss: 8.5.1
|
||||
postcss-import: 15.1.0(postcss@8.5.1)
|
||||
postcss-js: 4.0.1(postcss@8.5.1)
|
||||
postcss-load-config: 4.0.2(postcss@8.5.1)
|
||||
postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))
|
||||
postcss-nested: 6.2.0(postcss@8.5.1)
|
||||
postcss-selector-parser: 6.1.2
|
||||
resolve: 1.22.8
|
||||
@ -9256,6 +9376,8 @@ snapshots:
|
||||
universalify: 0.2.0
|
||||
url-parse: 1.5.10
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
tr46@3.0.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
@ -9270,6 +9392,24 @@ snapshots:
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.11
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 22.10.7
|
||||
acorn: 8.14.0
|
||||
acorn-walk: 8.3.2
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.7.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
@ -9391,6 +9531,8 @@ snapshots:
|
||||
|
||||
uuid@9.0.1: {}
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
||||
v8-to-istanbul@9.2.0:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
@ -9418,6 +9560,8 @@ snapshots:
|
||||
dependencies:
|
||||
makeerror: 1.0.12
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
webpack-bundle-analyzer@4.10.1:
|
||||
@ -9450,6 +9594,11 @@ snapshots:
|
||||
tr46: 3.0.0
|
||||
webidl-conversions: 7.0.0
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
dependencies:
|
||||
is-bigint: 1.1.0
|
||||
@ -9543,6 +9692,8 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yn@3.1.1: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zod-to-json-schema@3.24.1(zod@3.23.8):
|
||||
|
||||
12
src/admin/github/GitHubForkStatusBadge.tsx
Normal file
12
src/admin/github/GitHubForkStatusBadge.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Suspense } from 'react';
|
||||
import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient';
|
||||
import GitHubForkStatusBadgeServer from './GitHubForkStatusBadgeServer';
|
||||
import { IS_DEVELOPMENT } from '@/site/config';
|
||||
|
||||
export default function GitHubForkStatusBadge() {
|
||||
return IS_DEVELOPMENT
|
||||
? <GitHubForkStatusBadgeClient label="Local" />
|
||||
: <Suspense>
|
||||
<GitHubForkStatusBadgeServer />
|
||||
</Suspense>;
|
||||
}
|
||||
75
src/admin/github/GitHubForkStatusBadgeClient.tsx
Normal file
75
src/admin/github/GitHubForkStatusBadgeClient.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import Spinner from '@/components/Spinner';
|
||||
import clsx from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { BiLogoGithub } from 'react-icons/bi';
|
||||
|
||||
export default function GitHubForkStatusBadgeClient({
|
||||
url,
|
||||
label,
|
||||
style = 'mono',
|
||||
title,
|
||||
}: {
|
||||
url?: string
|
||||
label?: string
|
||||
style?: 'success' | 'warning' | 'mono'
|
||||
title?: string
|
||||
}) {
|
||||
const classNameForStyle = () => {
|
||||
switch (style) {
|
||||
case 'success': return clsx(
|
||||
'text-green-700 hover:text-green-700',
|
||||
'dark:text-green-400 dark:hover:text-green-400',
|
||||
'bg-green-100/75 dark:bg-green-900/50',
|
||||
'border-green-300/25',
|
||||
);
|
||||
case 'warning': return clsx(
|
||||
'text-amber-700 hover:text-amber-700',
|
||||
'dark:text-amber-400 dark:hover:text-amber-400',
|
||||
'bg-amber-100/75 dark:bg-amber-900/50',
|
||||
'border-amber-300/25 dark:border-amber-900',
|
||||
);
|
||||
default: return clsx(
|
||||
'text-gray-700 hover:text-gray-700',
|
||||
'dark:text-gray-300 dark:hover:text-gray-300',
|
||||
'bg-white dark:bg-transparent',
|
||||
'border-main',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const className = clsx(
|
||||
'opacity-0 transition-opacity animate-fade-in',
|
||||
'inline-flex items-center gap-2',
|
||||
'border transition-colors',
|
||||
url ? 'hover:underline' : 'select-none',
|
||||
'pl-[4.5px] pr-2.5 py-[3px]',
|
||||
'rounded-full shadow-sm',
|
||||
classNameForStyle(),
|
||||
);
|
||||
|
||||
const content = <>
|
||||
{!label
|
||||
? <Spinner
|
||||
color="text"
|
||||
className="translate-x-[3px]"
|
||||
/>
|
||||
: <BiLogoGithub size={17} />}
|
||||
{label ?? 'Checking'}
|
||||
</>;
|
||||
|
||||
return url
|
||||
? <Link
|
||||
target="_blank"
|
||||
href={url}
|
||||
title={title}
|
||||
className={className}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
: <span
|
||||
title={title}
|
||||
className={className}
|
||||
>
|
||||
{content}
|
||||
</span>;
|
||||
}
|
||||
31
src/admin/github/GitHubForkStatusBadgeServer.tsx
Normal file
31
src/admin/github/GitHubForkStatusBadgeServer.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient';
|
||||
import {
|
||||
VERCEL_GIT_BRANCH,
|
||||
VERCEL_GIT_REPO_OWNER,
|
||||
VERCEL_GIT_REPO_SLUG,
|
||||
} from '@/site/config';
|
||||
import { getGitHubMetaWithFallback } from '.';
|
||||
|
||||
export default async function GitHubForkStatusBadgeServer() {
|
||||
const owner = VERCEL_GIT_REPO_OWNER;
|
||||
const repo = VERCEL_GIT_REPO_SLUG;
|
||||
const branch = VERCEL_GIT_BRANCH;
|
||||
|
||||
const {
|
||||
url,
|
||||
isForkedFromBase,
|
||||
isBaseRepo,
|
||||
label,
|
||||
title,
|
||||
isBehind,
|
||||
} = await getGitHubMetaWithFallback({ owner, repo, branch });
|
||||
|
||||
return isForkedFromBase || isBaseRepo
|
||||
? <GitHubForkStatusBadgeClient {...{
|
||||
url,
|
||||
label,
|
||||
title,
|
||||
style: isBehind === undefined || isBehind ? 'warning' : 'mono',
|
||||
}} />
|
||||
: null;
|
||||
}
|
||||
153
src/admin/github/index.ts
Normal file
153
src/admin/github/index.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {
|
||||
TEMPLATE_BASE_OWNER,
|
||||
TEMPLATE_BASE_REPO,
|
||||
TEMPLATE_BASE_BRANCH,
|
||||
} from '@/site/config';
|
||||
|
||||
const DEFAULT_BRANCH = 'main';
|
||||
|
||||
const FALLBACK_TEXT = 'Unknown';
|
||||
|
||||
// Cache all results for 2 minutes to avoid rate limiting
|
||||
// GitHub API requests limited to 60 requests per hour
|
||||
const FETCH_CONFIG: RequestInit = {
|
||||
next: { revalidate: 120 },
|
||||
};
|
||||
|
||||
interface RepoParams {
|
||||
owner?: string
|
||||
repo?: string
|
||||
branch?: string
|
||||
};
|
||||
|
||||
// Website urls
|
||||
|
||||
const getGitHubRepoUrl = ({
|
||||
owner = TEMPLATE_BASE_OWNER,
|
||||
repo = TEMPLATE_BASE_REPO,
|
||||
}: RepoParams = {}) =>
|
||||
`https://github.com/${owner}/${repo}`;
|
||||
|
||||
export const getGitHubCompareUrl = ({
|
||||
owner,
|
||||
repo,
|
||||
branch = DEFAULT_BRANCH,
|
||||
}: RepoParams = {}) =>
|
||||
// eslint-disable-next-line max-len
|
||||
`${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_BASE_OWNER}:${TEMPLATE_BASE_REPO}:${TEMPLATE_BASE_BRANCH}`;
|
||||
|
||||
// API urls
|
||||
|
||||
const getGitHubApiRepoUrl = ({
|
||||
owner = TEMPLATE_BASE_OWNER,
|
||||
repo = TEMPLATE_BASE_REPO,
|
||||
}: RepoParams = {}) =>
|
||||
`https://api.github.com/repos/${owner}/${repo}`;
|
||||
|
||||
const getGitHubApiCommitsUrl = (params?: RepoParams) =>
|
||||
`${getGitHubApiRepoUrl(params)}/commits/main`;
|
||||
|
||||
const getGitHubApiForksUrl = (params?: RepoParams) =>
|
||||
`${getGitHubApiRepoUrl(params)}/forks`;
|
||||
|
||||
const getGitHubApiCompareUrl = ({
|
||||
owner,
|
||||
repo,
|
||||
branch = 'main',
|
||||
}: RepoParams = {}) =>
|
||||
// eslint-disable-next-line max-len
|
||||
`${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${owner}:${repo}:${branch}`;
|
||||
|
||||
// Requests
|
||||
|
||||
export const getLatestBaseRepoCommitSha = async () => {
|
||||
const response = await fetch(getGitHubApiCommitsUrl(), FETCH_CONFIG);
|
||||
const data = await response.json();
|
||||
return data.sha ? data.sha.slice(0, 7) as string : undefined;
|
||||
};
|
||||
|
||||
const getIsRepoForkedFromBase = async (params: RepoParams) => {
|
||||
const response = await fetch(getGitHubApiRepoUrl(params), FETCH_CONFIG);
|
||||
const data = await response.json();
|
||||
return (
|
||||
Boolean(data.fork) &&
|
||||
data.source?.full_name === `${TEMPLATE_BASE_OWNER}/${TEMPLATE_BASE_REPO}`
|
||||
);
|
||||
};
|
||||
|
||||
const getGitHubCommitsBehind = async (params?: RepoParams) => {
|
||||
const response = await fetch(getGitHubApiCompareUrl(params), FETCH_CONFIG);
|
||||
const data = await response.json();
|
||||
return data.behind_by as number;
|
||||
};
|
||||
|
||||
const isRepoBaseRepo = async ({ owner, repo }: RepoParams) =>
|
||||
owner?.toLowerCase() === TEMPLATE_BASE_OWNER &&
|
||||
repo?.toLowerCase() === TEMPLATE_BASE_REPO;
|
||||
|
||||
export const getGitHubPublicFork = async (
|
||||
params?: RepoParams,
|
||||
): Promise<RepoParams> => {
|
||||
const response = await fetch(getGitHubApiForksUrl(params), FETCH_CONFIG);
|
||||
const fork = (await response.json())[0];
|
||||
return {
|
||||
owner: fork.owner.login,
|
||||
repo: fork.name,
|
||||
};
|
||||
};
|
||||
|
||||
const getGitHubMeta = async (params: RepoParams) => {
|
||||
const [
|
||||
url,
|
||||
isForkedFromBase,
|
||||
isBaseRepo,
|
||||
behindBy,
|
||||
] = await Promise.all([
|
||||
getGitHubRepoUrl(params),
|
||||
getIsRepoForkedFromBase(params),
|
||||
isRepoBaseRepo(params),
|
||||
getGitHubCommitsBehind(params),
|
||||
]);
|
||||
|
||||
const isBehind = behindBy === undefined
|
||||
? undefined
|
||||
: behindBy > 0;
|
||||
|
||||
const label = isBehind === undefined
|
||||
? FALLBACK_TEXT
|
||||
: isBehind
|
||||
? `${behindBy} Behind`
|
||||
: 'Synced';
|
||||
|
||||
const title = isBehind === undefined
|
||||
? FALLBACK_TEXT
|
||||
: isBehind
|
||||
// eslint-disable-next-line max-len
|
||||
? `This fork is ${behindBy} commit${behindBy === 1 ? '' : 's'} behind. Consider syncing on GitHub for the latest updates.`
|
||||
: 'This fork is up to date.';
|
||||
|
||||
return {
|
||||
url,
|
||||
isForkedFromBase,
|
||||
isBaseRepo,
|
||||
behindBy,
|
||||
isBehind,
|
||||
label,
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
export const getGitHubMetaWithFallback = (params: RepoParams) =>
|
||||
getGitHubMeta(params)
|
||||
.catch(e => {
|
||||
console.error('Error retrieving GitHub meta', { params, error: e });
|
||||
return {
|
||||
url: undefined,
|
||||
isForkedFromBase: false,
|
||||
isBaseRepo: undefined,
|
||||
behindBy: undefined,
|
||||
isBehind: undefined,
|
||||
label: FALLBACK_TEXT,
|
||||
title: FALLBACK_TEXT,
|
||||
};
|
||||
});
|
||||
@ -1,6 +1,8 @@
|
||||
import ClearCacheButton from '@/admin/ClearCacheButton';
|
||||
import GitHubForkStatusBadge from '@/admin/github/GitHubForkStatusBadge';
|
||||
import Container from '@/components/Container';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { IS_DEVELOPMENT, IS_VERCEL_GIT_PROVIDER_GITHUB } from '@/site/config';
|
||||
import SiteChecklist from '@/site/SiteChecklist';
|
||||
|
||||
export default async function AdminConfigurationPage() {
|
||||
@ -8,10 +10,12 @@ export default async function AdminConfigurationPage() {
|
||||
<SiteGrid
|
||||
contentMain={
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-grow">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="grow">
|
||||
App Configuration
|
||||
</div>
|
||||
{(IS_VERCEL_GIT_PROVIDER_GITHUB || IS_DEVELOPMENT) &&
|
||||
<GitHubForkStatusBadge />}
|
||||
<ClearCacheButton />
|
||||
</div>
|
||||
<Container spaceChildren={false}>
|
||||
|
||||
@ -11,7 +11,7 @@ export default function SiteChecklist({
|
||||
return (
|
||||
<Suspense fallback={<SiteChecklistClient {...{
|
||||
...CONFIG_CHECKLIST_STATUS,
|
||||
isTestingConnections: true,
|
||||
isAnalyzingConfiguration: true,
|
||||
simplifiedView,
|
||||
}} /> }>
|
||||
<SiteChecklistServer {...{ simplifiedView }} />
|
||||
|
||||
@ -98,11 +98,11 @@ export default function SiteChecklistClient({
|
||||
aiError,
|
||||
// Component props
|
||||
simplifiedView,
|
||||
isTestingConnections,
|
||||
isAnalyzingConfiguration,
|
||||
}: ConfigChecklistStatus &
|
||||
Partial<Awaited<ReturnType<typeof testConnectionsAction>>> & {
|
||||
simplifiedView?: boolean
|
||||
isTestingConnections?: boolean
|
||||
isAnalyzingConfiguration?: boolean
|
||||
}) {
|
||||
const renderLink = (href: string, text: string, external = true) =>
|
||||
<>
|
||||
@ -214,11 +214,11 @@ export default function SiteChecklistClient({
|
||||
icon={<BiData size={16} />}
|
||||
>
|
||||
<ChecklistRow
|
||||
title={hasDatabase && isTestingConnections
|
||||
title={hasDatabase && isAnalyzingConfiguration
|
||||
? 'Testing database connection'
|
||||
: 'Setup database'}
|
||||
status={hasDatabase}
|
||||
isPending={hasDatabase && isTestingConnections}
|
||||
isPending={hasDatabase && isAnalyzingConfiguration}
|
||||
>
|
||||
{databaseError && renderError({
|
||||
connection: { provider: 'Database', error: databaseError},
|
||||
@ -245,7 +245,7 @@ export default function SiteChecklistClient({
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title={
|
||||
hasStorageProvider && isTestingConnections
|
||||
hasStorageProvider && isAnalyzingConfiguration
|
||||
? 'Testing storage connection'
|
||||
: !hasStorageProvider
|
||||
? 'Setup storage (one of the following)'
|
||||
@ -254,7 +254,7 @@ export default function SiteChecklistClient({
|
||||
? `Setup storage (new uploads go to: ${labelForStorage(currentStorage)})`
|
||||
: 'Setup storage'}
|
||||
status={hasStorageProvider}
|
||||
isPending={hasStorageProvider && isTestingConnections}
|
||||
isPending={hasStorageProvider && isAnalyzingConfiguration}
|
||||
>
|
||||
{storageError && renderError({
|
||||
connection: { provider: 'Storage', error: storageError},
|
||||
@ -300,11 +300,11 @@ export default function SiteChecklistClient({
|
||||
icon={<BiLockAlt size={16} />}
|
||||
>
|
||||
<ChecklistRow
|
||||
title={!hasAuthSecret && isTestingConnections
|
||||
title={!hasAuthSecret && isAnalyzingConfiguration
|
||||
? 'Generating secret'
|
||||
: 'Setup auth'}
|
||||
status={hasAuthSecret}
|
||||
isPending={!hasAuthSecret && isTestingConnections}
|
||||
isPending={!hasAuthSecret && isAnalyzingConfiguration}
|
||||
>
|
||||
Store auth secret in environment variable:
|
||||
{!hasAuthSecret &&
|
||||
@ -376,11 +376,11 @@ export default function SiteChecklistClient({
|
||||
optional
|
||||
>
|
||||
<ChecklistRow
|
||||
title={isAiTextGenerationEnabled && isTestingConnections
|
||||
title={isAiTextGenerationEnabled && isAnalyzingConfiguration
|
||||
? 'Testing OpenAI connection'
|
||||
: 'Add OpenAI secret key'}
|
||||
status={isAiTextGenerationEnabled}
|
||||
isPending={isAiTextGenerationEnabled && isTestingConnections}
|
||||
isPending={isAiTextGenerationEnabled && isAnalyzingConfiguration}
|
||||
optional
|
||||
>
|
||||
{aiError && renderError({
|
||||
@ -392,11 +392,11 @@ export default function SiteChecklistClient({
|
||||
{renderEnvVars(['OPENAI_SECRET_KEY'])}
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title={hasVercelKv && isTestingConnections
|
||||
title={hasVercelKv && isAnalyzingConfiguration
|
||||
? 'Testing KV connection'
|
||||
: 'Enable rate limiting'}
|
||||
status={hasVercelKv}
|
||||
isPending={hasVercelKv && isTestingConnections}
|
||||
isPending={hasVercelKv && isAnalyzingConfiguration}
|
||||
optional
|
||||
>
|
||||
{kvError && renderError({
|
||||
|
||||
@ -8,6 +8,7 @@ export default async function SiteChecklistServer({
|
||||
simplifiedView?: boolean
|
||||
}) {
|
||||
const connectionErrors = await testConnectionsAction().catch(() => ({}));
|
||||
|
||||
return (
|
||||
<SiteChecklistClient {...{
|
||||
...CONFIG_CHECKLIST_STATUS,
|
||||
|
||||
@ -3,43 +3,46 @@ import type { StorageType } from '@/services/storage';
|
||||
import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
|
||||
|
||||
// HARD-CODED GLOBAL CONFIGURATION
|
||||
|
||||
export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined;
|
||||
export const SHOULD_DEBUG_SQL = false;
|
||||
|
||||
// META / SOURCE / DOMAINS
|
||||
|
||||
export const SITE_TITLE =
|
||||
process.env.NEXT_PUBLIC_SITE_TITLE ||
|
||||
'Photo Blog';
|
||||
|
||||
// SOURCE
|
||||
const VERCEL_GIT_PROVIDER =
|
||||
export const TEMPLATE_BASE_OWNER = 'sambecker';
|
||||
export const TEMPLATE_BASE_REPO = 'exif-photo-blog';
|
||||
export const TEMPLATE_BASE_BRANCH = 'main';
|
||||
|
||||
export const VERCEL_GIT_PROVIDER =
|
||||
process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER;
|
||||
const VERCEL_GIT_REPO_OWNER =
|
||||
export const VERCEL_GIT_REPO_OWNER =
|
||||
process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER;
|
||||
const VERCEL_GIT_REPO_SLUG =
|
||||
export const VERCEL_GIT_REPO_SLUG =
|
||||
process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG;
|
||||
const VERCEL_GIT_COMMIT_MESSAGE =
|
||||
export const VERCEL_GIT_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF;
|
||||
export const VERCEL_GIT_COMMIT_MESSAGE =
|
||||
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE;
|
||||
const VERCEL_GIT_COMMIT_SHA =
|
||||
export const VERCEL_GIT_COMMIT_SHA =
|
||||
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
|
||||
const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA
|
||||
export const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA
|
||||
? VERCEL_GIT_COMMIT_SHA.slice(0, 7)
|
||||
: undefined;
|
||||
const VERCEL_GIT_COMMIT_URL = VERCEL_GIT_PROVIDER === 'github'
|
||||
export const IS_VERCEL_GIT_PROVIDER_GITHUB = VERCEL_GIT_PROVIDER === 'github';
|
||||
export const VERCEL_GIT_COMMIT_URL = IS_VERCEL_GIT_PROVIDER_GITHUB
|
||||
// eslint-disable-next-line max-len
|
||||
? `https://github.com/${VERCEL_GIT_REPO_OWNER}/${VERCEL_GIT_REPO_SLUG}/commit/${VERCEL_GIT_COMMIT_SHA}`
|
||||
: undefined;
|
||||
|
||||
const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV;
|
||||
const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL;
|
||||
const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL;
|
||||
const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL;
|
||||
const VERCEL_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF;
|
||||
export const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV;
|
||||
export const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL;
|
||||
export const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL;
|
||||
export const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL;
|
||||
// Last resort: cannot be used reliably
|
||||
const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_BRANCH
|
||||
? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_BRANCH}-`)[0]}.vercel.app`
|
||||
export const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_GIT_BRANCH
|
||||
? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_GIT_BRANCH}-`)[0]}.vercel.app`
|
||||
: undefined;
|
||||
|
||||
export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && (
|
||||
@ -48,6 +51,7 @@ export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && (
|
||||
!VERCEL_ENV
|
||||
);
|
||||
|
||||
export const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
|
||||
export const IS_PREVIEW = VERCEL_ENV === 'preview';
|
||||
|
||||
export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass';
|
||||
|
||||
@ -155,6 +155,9 @@
|
||||
text-red-500 dark:text-red-400
|
||||
}
|
||||
/* Utilities: Border */
|
||||
.border-main {
|
||||
@apply border-gray-200 dark:border-gray-700
|
||||
}
|
||||
.border-subtle {
|
||||
@apply
|
||||
border border-gray-200 dark:border-gray-800
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
const GITHUB_API_URL =
|
||||
'https://api.github.com/repos/sambecker/exif-photo-blog/commits/main';
|
||||
|
||||
export const fetchLatestRepoCommit = async () => {
|
||||
const response = await fetch(GITHUB_API_URL);
|
||||
const data = await response.json();
|
||||
return data.sha.slice(0, 7);
|
||||
};
|
||||
@ -29,7 +29,7 @@ module.exports = {
|
||||
'rotate-pulse':
|
||||
'rotate-pulse 0.75s linear infinite normal both running',
|
||||
'fade-in':
|
||||
'fade-in 0.5s linear',
|
||||
'fade-in 0.5s linear both running',
|
||||
'hover-drift':
|
||||
'hover-drift 8s linear infinite',
|
||||
'hover-wobble':
|
||||
|
||||
Loading…
Reference in New Issue
Block a user