Skip to content

Commit

Permalink
Merge pull request #49 from withmoons/manage-page-path-templates
Browse files Browse the repository at this point in the history
feat: manage path template with url-template spec
  • Loading branch information
emmanuelgautier authored Sep 10, 2023
2 parents 9d32085 + 98a3264 commit b6713c7
Show file tree
Hide file tree
Showing 18 changed files with 196 additions and 127 deletions.
1 change: 1 addition & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"moons"
],
"dependencies": {
"deepmerge": "4.3.1",
"zod": "3.22.2"
}
}
52 changes: 49 additions & 3 deletions packages/config/src/config.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import deepmerge from 'deepmerge';
import { existsSync, readFileSync } from 'fs';
import { z } from 'zod';

Expand All @@ -6,20 +7,46 @@ import {
type WebManifest,
} from './webmanifest.config.mjs';
import { join } from 'path';
import { pageDocumentTypes } from './consts.mjs';

const localesSchema = z.object({
default: z.string(),
available: z.array(z.string()),
});

const pageSchema = z.object({
path: z
.array(
z.object({
locale: z.string(),
path: z.string(),
})
)
.or(z.string()),
});

const moonsConfigFileSchema = z.object({
locales: localesSchema.optional(),
template: z.string(),
pages: z
.object(
Object.fromEntries(
Object.values(pageDocumentTypes).map((key) => [
key,
pageSchema.or(z.literal(false)).optional(),
])
)
)
.optional(),
});

const defaultConfigFileName = 'moons.config.json';

export type MoonsConfig = z.infer<typeof moonsConfigFileSchema> & {
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
export type MoonsConfig = WithRequired<
z.infer<typeof moonsConfigFileSchema>,
'pages'
> & {
name?: string;
content: {
root: string;
Expand All @@ -28,6 +55,24 @@ export type MoonsConfig = z.infer<typeof moonsConfigFileSchema> & {
webManifest: WebManifest;
};

const defaultPages: MoonsConfig['pages'] = {
articles: {
path: '/{+isPartOf}/{/identifier}',
},

pages: {
path: '/{+isPartOf}{/identifier}',
},

people: {
path: '/authors/{/identifier}',
},

organizations: {
path: '/organizations/{/identifier}',
},
};

let _config: MoonsConfig;

const readConfigFile = (path: string): MoonsConfig => {
Expand All @@ -39,14 +84,15 @@ const readConfigFile = (path: string): MoonsConfig => {
const configFileContent = readFileSync(configPath, 'utf8');
const configFile = moonsConfigFileSchema.parse(JSON.parse(configFileContent));

const config: MoonsConfig = {
...configFile,
const defaultConfig = {
pages: defaultPages,
content: {
root: path,
generated: join(path, '.contentlayer/generated/index.mjs'),
},
webManifest: readWebManifestFromPath(path),
};
const config: MoonsConfig = deepmerge(defaultConfig, configFile);

return config;
};
Expand Down
14 changes: 14 additions & 0 deletions packages/config/src/consts.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export enum pageDocumentTypes {
Article = 'articles',
Organization = 'organizations',
Page = 'pages',
Person = 'people',
}
export enum documentTypes {
Article = pageDocumentTypes.Article,
Organization = pageDocumentTypes.Organization,
Page = pageDocumentTypes.Page,
Person = pageDocumentTypes.Person,
WebPageElement = 'webPageElements',
Website = 'websites',
}
1 change: 1 addition & 0 deletions packages/config/src/index.mts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './config.mjs';
export * from './consts.mjs';
export * from './webmanifest.config.mjs';
1 change: 1 addition & 0 deletions packages/moons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"moons"
],
"dependencies": {
"@std-uritemplate/std-uritemplate": "0.0.40",
"@withmoons/config": "*",
"esbuild": "0.19.2",
"mdx-bundler": "9.2.1",
Expand Down
68 changes: 10 additions & 58 deletions packages/moons/src/core/content/compute.mts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ import {
} from './urls.mjs';
import {
documentByIdentifierAndLanguageSelector,
documentsByAuthorSelector,
isInLanguage,
usedLanguagesInDocumentsSelector,
} from './selectors.mjs';
import type {
Content,
Expand All @@ -33,22 +31,19 @@ import type {
export type ComputeDTO<T> = {
documents: T[];
websites: ContentlayerWebsite[];
persons: ContentlayerPerson[];
people: ContentlayerPerson[];
};

function createPage<T>(
identifier: string,
path: string,
document: Partial<ContentlayerWebPageDocument>
) {
return {
_id: identifier,
identifier,
path,
slug: identifier,
description: '',
type: 'Page',
collection: 'pages',
name: identifier,
...document,
body: document.body?.raw
Expand All @@ -63,12 +58,10 @@ function createPage<T>(

const createListingPage = (
identifier: string,
path: string,
document: Partial<ContentlayerWebPageDocument> = {}
) =>
createPage<ContentlayerDocumentWithRender<ContentlayerWebPageDocument>>(
identifier,
path,
{
listingPage: true,
...document,
Expand Down Expand Up @@ -112,9 +105,7 @@ const computeRemainingListingPages = async (
(_a) => _a.identifier === isPartOf && isInLanguage(_a, inLanguage)
) === false
) {
acc = acc.concat(
createListingPage(isPartOf, `/${isPartOf}`, templateDocument)
);
acc = acc.concat(createListingPage(isPartOf, templateDocument));
}

// Create all keywords pages not existing yet
Expand All @@ -124,9 +115,7 @@ const computeRemainingListingPages = async (
(_a) => _a.identifier === 'tags' && isInLanguage(_a, inLanguage)
) === false
) {
acc = acc.concat(
createListingPage('tags', '/tags', templateDocument)
);
acc = acc.concat(createListingPage('tags', templateDocument));
}

acc = acc.concat(
Expand All @@ -138,7 +127,7 @@ const computeRemainingListingPages = async (
) === false
)
.map((_k) =>
createListingPage(_k, `/${_k}`, {
createListingPage(_k, {
...templateDocument,
isPartOf: 'tags',
})
Expand All @@ -151,44 +140,8 @@ const computeRemainingListingPages = async (
documents
);

const computePersonPages =
(persons: ContentlayerPerson[]) =>
async (documents: ContentlayerWebPageDocumentWithRender[]) => {
const selectUsedLanguages = usedLanguagesInDocumentsSelector();
const selectDocumentByAuthor = documentsByAuthorSelector(documents);

if (Array.isArray(persons) && persons.length > 0) {
const usedLanguages = selectUsedLanguages(documents);
documents = documents.concat(
...usedLanguages.map((language) =>
createListingPage('author', '/author', { inLanguage: language })
)
);
}

return persons.reduce((acc, person) => {
if (!person.body) {
return acc;
}

const usedLanguages = selectUsedLanguages(
selectDocumentByAuthor(person.identifier)
);
return acc.concat(
...usedLanguages.map<ContentlayerWebPageDocumentWithRender>(
(language) =>
createPage(person.identifier, person.identifier, {
body: person.body,
isPartOf: 'author',
inLanguage: language,
})
)
);
}, documents);
};

const computeMissingFields =
(persons: ContentlayerPerson[]) =>
(people: ContentlayerPerson[]) =>
async (
documents: Array<
ContentlayerDocumentWithURL & ContentlayerWebPageDocumentWithRender
Expand All @@ -197,7 +150,7 @@ const computeMissingFields =
const buildBreadcrumb = breadcrumbBuilder(documents);
const buildAlternates = alternatesHeaderBuilder(documents);
const selectPersonByIdentifierAndLanguage =
documentByIdentifierAndLanguageSelector(persons);
documentByIdentifierAndLanguageSelector(people);

const getAuthor = (
identifier?: string,
Expand All @@ -206,8 +159,8 @@ const computeMissingFields =
let author;
if (identifier) {
author = selectPersonByIdentifierAndLanguage(identifier, inLanguage);
} else if (persons.length === 1) {
author = persons[0];
} else if (people.length === 1) {
author = people[0];
}

return (
Expand Down Expand Up @@ -252,12 +205,11 @@ const computeMissingFields =

export const computeDocuments = async ({
documents,
persons,
people,
websites,
}: ComputeDTO<ContentlayerWebPageDocument>): Promise<Content[]> =>
Promise.resolve(documents)
.then(hydratePagesWithRender)
.then(computePersonPages(persons))
.then(computeRemainingListingPages)
.then(computeDocumentsUrl(websites))
.then(computeMissingFields(persons));
.then(computeMissingFields(people));
7 changes: 0 additions & 7 deletions packages/moons/src/core/content/consts.mts
Original file line number Diff line number Diff line change
@@ -1,9 +1,2 @@
export const homeIdentifier = 'index';
export const pageDepthLimit = 10;

export enum collectionName {
article = 'articles',
page = 'pages',
person = 'persons',
organization = 'organizations',
}
1 change: 0 additions & 1 deletion packages/moons/src/core/content/index.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { collectionName } from './consts.mjs';
export * from './repository.mjs';
export * from './types/index.mjs';
4 changes: 3 additions & 1 deletion packages/moons/src/core/content/metadata/breadcrumb.mts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export const breadcrumbBuilder =
inLanguage
);
if (!page) {
throw new Error('No page found during breadcrumb computation');
throw new Error(
`No page ${documentIdentifier} in ${inLanguage} found during breadcrumb computation`
);
}

itemList = itemList.concat({
Expand Down
3 changes: 1 addition & 2 deletions packages/moons/src/core/content/metadata/ogp/og.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { collectionName } from '../../consts.mjs';
import type { Content, MetadataHeaders } from '../../types/index.mjs';

import { getArticle } from './article.mjs';
Expand All @@ -7,7 +6,7 @@ import { getWebsite } from './website.mjs';
export const getOpenGraphObjects = (
document: Content
): MetadataHeaders['openGraph'] => {
if (document.collection === collectionName.article) {
if (document.type === 'Article') {
return getArticle(document);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { Thing, WithContext } from 'schema-dts';

import { collectionName } from '../../consts.mjs';
import type { Content } from '../../types/index.mjs';
import { getArticle } from './article.mjs';
import { getBreadcrumb } from './breadcrumb.mjs';

export const getStructuredDataSchemas = (document: Content) => {
let schemas: WithContext<Thing>[] = [getBreadcrumb(document)];

if (document.collection === collectionName.article) {
if (document.type === 'Article') {
schemas = schemas.concat(getArticle(document));
}

Expand Down
9 changes: 3 additions & 6 deletions packages/moons/src/core/content/repository.mts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ const getWebPageDocuments = async (): Promise<Content[]> => {
websites: await getWebsites(),
documents: new Array<ContentlayerWebPageDocument>()
.concat(generated.allPages)
.concat(generated.allArticles),
persons: await getPersons(),
.concat(generated.allArticles)
.concat(generated.allPeople),
people: generated.allPeople,
});

return _documents;
Expand Down Expand Up @@ -102,10 +103,6 @@ export const getPageBySlug = async (
filters?: RepositoryFilters
) => (await getPages(filters)).find((doc) => doc.slug === slug);

export const getPersons = async () => (await getGenerated()).allPeople;
export const getPersonByIdentifier = async (identifier: string) =>
documentByIdentifierSelector(await getPersons())(identifier);

export const getOrganizations = async () =>
(await getGenerated()).allOrganizations;
export const getOrganizationByIdentifier = async (identifier: string) =>
Expand Down
6 changes: 2 additions & 4 deletions packages/moons/src/core/content/types/_schemas.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { documentTypes } from '@withmoons/config';
import { z } from 'zod';
import { collectionName } from '../consts.mjs';

const metadataHeaders = z
.object({
Expand Down Expand Up @@ -38,9 +38,7 @@ const metadataHeaders = z

const moonsSchema = z
.object({
collection: z
.enum(Object.values(collectionName) as [string, ...string[]])
.or(z.string()),
type: z.enum(Object.keys(documentTypes) as [keyof typeof documentTypes]),
tags: z.array(z.string()).optional(),
listingPage: z.boolean().default(false),
path: z.string(),
Expand Down
Loading

0 comments on commit b6713c7

Please sign in to comment.