Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid blank pages, omit desc tab instead #45

Merged
merged 5 commits into from
Sep 8, 2024
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
4 changes: 2 additions & 2 deletions api/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ export const PackageArray = new t.Type<Array<Package>>(
validation,
either.fold(
() => pipe(
`\n[>>>>] warn - PackageArray decode failure: ignoring the package
`[>>>>] warn - Package decode failure: ignoring
${JSON.stringify((input as Array<unknown>)[i])}
because
${PathReporter.report(validation).join('\n')}\n`,
${PathReporter.report(validation).join('\n')}`,
console.error,
() => option.none
),
Expand Down
105 changes: 105 additions & 0 deletions api/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { array, either, nonEmptyArray, option, record } from "fp-ts";
import { flow, pipe } from "fp-ts/lib/function";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import remarkGfm from "remark-gfm";
import {
GroupedByVersion,
OrderedByVersion,
Package,
PackageRepo,
PackagesIndex,
} from "./domain";
import { fetchPackageRepo } from "./packageRepo";

export type PackageDetails = {
packageRepo:
| (PackageRepo & {
serializedReadme: MDXRemoteSerializeResult<
Record<string, unknown>
> | null;
})
| null;
versions: Array<string>;
};

export async function resolvePackage(
packagesIndex: PackagesIndex,
packageName?: string,
version?: string
): Promise<PackageDetails> {
const packages = packagesIndex.packages.filter((p) => p.name === packageName);
const orderedPackages = pipe(
OrderedByVersion.decode(packages),
either.fold(
() => {
throw new Error("Failed to order packages by version");
},
(packages) => packages
)
);

const orderedVersions = orderedPackages.map((p) => p.version);

const currentVersion = pipe(
orderedPackages,
version ? array.findFirst((p) => p.version === version) : array.head,
option.toNullable
);

if (!currentVersion) {
// TODO: Omit entire package details page if version not found
throw new Error("Version not found");
}

const packageRepoEither = await fetchPackageRepo(currentVersion)();
const packageRepo = pipe(
packageRepoEither,
either.mapLeft(console.error),
option.fromEither,
option.toNullable
);

if (!packageRepo) {
return {
packageRepo: null,
versions: orderedVersions,
};
}

// See https://github.com/espanso/hub-frontend/issues/6
const fixLiteralHTML = (html: string) =>
html
.replaceAll("<br>", "<br/>")
.replaceAll("<kbd>", "`")
.replaceAll("</kbd>", "`");

const serializedReadme = await serialize(fixLiteralHTML(packageRepo.readme), {
mdxOptions: {
remarkPlugins: [remarkGfm],
},
}).catch((e) => {
console.error(`>>>
Failed to serialize readme for package "${currentVersion.name}@${currentVersion.version}":\n${e}
>>>`);
return null;
});

return {
packageRepo: {
...packageRepo,
serializedReadme,
},
versions: orderedVersions,
};
}

export const groupByVersion = (packages: Package[]) =>
pipe(
packages,
GroupedByVersion.decode,
either.map<GroupedByVersion, Package[]>(
flow(record.map(nonEmptyArray.head), Object.values)
),
option.fromEither
);
77 changes: 36 additions & 41 deletions api/packagesIndex.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,46 @@
import flatCache from "flat-cache";
import { array, either, option, taskEither } from "fp-ts";
import { flow, pipe } from "fp-ts/function";
import { TaskEither } from "fp-ts/TaskEither";
import { array, either } from "fp-ts";
import { pipe } from "fp-ts/function";
import { PackagesIndex } from "./domain";

const PACKAGE_INDEX_URL = process.env.PACKAGE_INDEX_URL || "";

const PACKAGE_INDEX_CACHE_ID = "packagesIndex";
const CACHE_DIR = process.env.PACKAGE_INDEX_CACHE_DIR || undefined;

const fetchPackagesIndexInternal = (cache: flatCache.Cache) =>
pipe(
cache.getKey(PACKAGE_INDEX_URL),
option.fromNullable,
option.fold(
() =>
pipe(
taskEither.tryCatch(() => fetch(PACKAGE_INDEX_URL), either.toError),
taskEither.chain((response) =>
taskEither.tryCatch(() => response.json(), either.toError)
),
taskEither.chain(
flow(
PackagesIndex.decode,
either.mapLeft(either.toError),
taskEither.fromEither
)
),
taskEither.map((packagesIndex) => {
const noDummyPackage: PackagesIndex = {
...packagesIndex,
packages: pipe(
packagesIndex.packages,
array.filter((p) => p.name !== "dummy-package")
),
};
cache.setKey(PACKAGE_INDEX_URL, noDummyPackage);
cache.save();
return cache.getKey(PACKAGE_INDEX_URL);
})
),
taskEither.of
),
taskEither.mapLeft(x => { console.error(x); return x }),
async function fetchPackagesIndex(url: string): Promise<PackagesIndex> {
const response = await fetch(url);
const json = await response.json();

return pipe(
PackagesIndex.decode(json),
either.map((x) => ({
...x,
packages: pipe(
x.packages,
array.filter((p) => p.name !== "dummy-package")
),
})),
either.fold(
(e) => Promise.reject(e),
(x) => Promise.resolve(x)
)
);
}

export async function getPackagesIndex(): Promise<PackagesIndex> {
const cache = flatCache.load(PACKAGE_INDEX_CACHE_ID, CACHE_DIR);
const cachedPackagesIndex = cache.getKey(PACKAGE_INDEX_URL);
if (cachedPackagesIndex) {
return cachedPackagesIndex;
}

const packagesIndex = await fetchPackagesIndex(PACKAGE_INDEX_URL);

cache.setKey(PACKAGE_INDEX_URL, packagesIndex);
cache.save();
return cache.getKey(PACKAGE_INDEX_URL);
}

export const fetchPackagesIndex: TaskEither<Error, PackagesIndex> = pipe(
flatCache.load(PACKAGE_INDEX_CACHE_ID, CACHE_DIR),
fetchPackagesIndexInternal
);
// export const getPackagesIndex: TaskEither<Error, PackagesIndex> =
// taskEither.tryCatch(fetchPackagesIndexOrCache, either.toError);
33 changes: 0 additions & 33 deletions api/serializeReadme.ts

This file was deleted.

5 changes: 4 additions & 1 deletion components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ export const Footer = (props: Props) => {
<Text color={props.color}>Made with</Text>
<HeartIcon color={props.color as Color} />
<Text color={props.color}>by</Text>
<NextjsLink href="https://www.matteopellegrino.me/" target="_blank">
<NextjsLink
href="https://www.matteopellegrino.dev/"
target="_blank"
>
<Text color={props.color} className="link-pelle">
Matteo Pellegrino
</Text>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
"monocle-ts": "^2.3.11",
"newtype-ts": "^0.3.4",
"next": "^12.0.4",
"next-mdx-remote": "^4.0.1",
"next-mdx-remote": "^5.0.0",
"node-file-cache": "^1.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-is": "^18.0.0",
"react-scripts": "5.0.1",
"remark-gfm": "^3.0.1",
"remark-gfm": "^4.0.0",
"unzipit": "^1.3.6",
"uuid": "^9.0.1",
"yaml": "^2.3.4"
Expand Down
101 changes: 25 additions & 76 deletions pages/[packageName].tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,33 @@
import { array, either, nonEmptyArray, option, task, taskEither } from "fp-ts";
import { constant, flow, pipe } from "fp-ts/function";
import { sequenceS } from "fp-ts/lib/Apply";
import { GetStaticPropsContext } from "next";
import { OrderedByVersion } from "../api/domain";
import { fetchPackageRepo } from "../api/packageRepo";
import { fetchPackagesIndex } from "../api/packagesIndex";
import { serializeReadme } from "../api/serializeReadme";
import { resolvePackage } from "../api/package";
import { getPackagesIndex } from "../api/packagesIndex";
import VersionedPackagePage, { Props } from "./[packageName]/v/[version]";

export const getStaticProps = (context: GetStaticPropsContext) =>
pipe(
fetchPackagesIndex,
taskEither.chain((packagesIndex) =>
pipe(
packagesIndex.packages,
array.filter(
(p) =>
context.params !== undefined &&
p.name === context.params.packageName
),
OrderedByVersion.decode,
either.mapLeft(
flow(
array.reduce("", (acc, curr) => `${acc}, ${curr.message}`),
either.toError
)
),
taskEither.fromEither
)
),
taskEither.chain((packages) =>
sequenceS(taskEither.ApplyPar)({
packageRepo: pipe(
packages,
array.head,
option.map(fetchPackageRepo),
taskEither.fromOption(
() => new Error(`Version ${context.params?.version} not found`)
),
taskEither.flatten,
serializeReadme
),
versions: pipe(
packages,
nonEmptyArray.map((p) => p.version),
taskEither.right
),
})
),
task.map((props) => ({
props: pipe(
props,
either.foldW(
() => ({
packageRepo: option.none,
versions: [],
}),
(v) => ({
packageRepo: option.some(v.packageRepo),
versions: v.versions,
})
)
),
}))
)();
export const getStaticProps = async (context: GetStaticPropsContext) => {
const packagesIndex = await getPackagesIndex();
const packageName = context.params?.packageName;
const version = context.params?.version;

export const getStaticPaths = pipe(
fetchPackagesIndex,
task.map(
flow(
either.map((d) => d.packages),
either.map(array.map((p) => ({ params: { packageName: p.name } }))),
either.getOrElseW(constant([]))
)
),
task.map((paths) => ({
paths,
const props = await resolvePackage(
packagesIndex,
Array.isArray(packageName) ? packageName[0] : packageName,
Array.isArray(version) ? version[0] : version
);

return {
props,
};
};

export const getStaticPaths = async () => {
const packagesIndex = await getPackagesIndex();
return {
paths: packagesIndex.packages.map((p) => ({
params: { packageName: p.name },
})),
fallback: false,
}))
);
};
};

const LatestPackagePage = (props: Props) => <VersionedPackagePage {...props} />;

Expand Down
Loading
Loading