Skip to content
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
5 changes: 4 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
**/*/node_modules
**/*/content
**/*/test-content
pnpm-lock.yaml
pnpm-lock.yaml
**/*/editor/content
**/*/editor/test-content
**/*/export/out
69 changes: 3 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,16 @@ This monorepo hosts a group of packages that establish and re-use patterns to cr

The projects in [`packages`](packages) are libraries that could be published as reusable packages, and the ones in [`websites`](websites) are examples of concrete implementations using those packages.

Generally, a website made with these packages will span two projects: an editor and a website. The editor is a dynamic app that has serves as a custom-built CMS, and that CMS calls the website project to build a deployable static website with that content. Having this editor/website pattern combines the accessibility of a dynamic graphical CMS with the effortless hosting of a static website.
Generally, a website made with these packages will span two projects: the editor and the export. The editor is a dynamic app that has serves as a custom-built CMS, and that CMS calls the export project to build a static website with that content. Having this editor/website pattern combines the accessibility of a dynamic graphical CMS with the effortless hosting of a static website.

Reusable packages in this repo aim to be composable, allowing any implementation full control over the process of creating content. For example, a website can check in with any authentication service that's compatible with NextJS before calling the functions from `content-engine` to persist content to LMDB and the filesystem.
Reusable packages in this repo aim to be composable, allowing any implementation full control over the process of creating content. For example, a website can check in with any authentication service that's compatible with NextJS before calling the functions from `@discontent/cms` to persist content to LMDB and the filesystem.

One standout in this monorepo is [`next-static-image`](packages/next-static-image), which enables build-time optimization of dynamic images in a NextJS static export project with minimal API changes. This package may graduate into its own repo eventually, but the websites in this repo serve as its best test target currently.
One standout in this monorepo is [`@discontent/next-static-image`](packages/next-static-image), which enables build-time optimization of dynamic images in a NextJS static export project with minimal API changes. This package may graduate into its own repo eventually, but the websites in this repo serve as its best test target currently.

The [Recipe Website](websites/recipe-website) project is the most complete implementation of a real-world project using the code in this monorepo, but more are planned in the future.

# Running tests

## Unit Tests (Vitest)

This command builds a docker image with the code of this repository and runs the repository's unit tests:

```sh
./build_docker.sh content-engine
docker run -t content-engine ./run_tests.sh
```

## E2E Tests (Cypress)

End-to-end tests are available for the recipe editor. To run them:
Expand All @@ -38,57 +29,3 @@ pnpm e2e-dev:headless
```

See [cypress/README.md](websites/recipe-website/editor/cypress/README.md) for more details about the e2e test suite.

```
[+] Building 0.1s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 226B 0.0s
=> [internal] load metadata for docker.io/library/node:22.14.0-alpine3.21@sha256:9bef0ef1e268f60627da9ba7d76 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 154B 0.0s
=> [1/5] FROM docker.io/library/node:22.14.0-alpine3.21@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad0748 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 1.07kB 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package.json package-lock.json . 0.0s
=> CACHED [4/5] RUN npm install 0.0s
=> CACHED [5/5] COPY . . 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:80007dbaeba9813527f4a4e663e6d773256f6e42f1b3c3fdf713fe45b4897c2f 0.0s
=> => naming to docker.io/library/content-engine 0.0s


> my-react-app@0.0.0 test
> vitest


RUN v3.1.1 /app

✓ src/App.test.jsx (2 tests) 176ms
✓ test/basic.test.js (3 tests) 6ms
✓ test/suite.test.js (3 tests) 7ms

Test Files 3 passed (3)
Tests 8 passed (8)
Start at 22:08:27
Duration 3.74s (transform 93ms, setup 361ms, collect 282ms, tests 190ms, environment 1.95s, prepare 392ms)
```

# Running a specific test

This example runs all tests matching the name "basic":

```sh
./build_docker.sh content-engine
docker run -t content-engine ./run_tests.sh basic
```

# Running a vite dev server

Run this command to enable hot reloading via docker.

```sh
./build_docker.sh content-engine
docker run --network=host -v .:/app -it content-engine npm exec vite dev --host
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
},
"packageManager": "pnpm@10.24.0",
"dependencies": {
"component-library": "^1.0.0",
"content-engine": "^1.0.0",
"@discontent/component-library": "^1.0.0",
"@discontent/cms": "^1.0.0",
"execa": "^9.6.1",
"fdir": "^6.5.0",
"fs-extra": "^11.3.3",
Expand Down
10 changes: 5 additions & 5 deletions packages/component-library/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
"prefix": ""
},
"aliases": {
"components": "component-library/components",
"utils": "component-library/lib/utils",
"ui": "component-library/components/ui",
"lib": "component-library/lib",
"hooks": "component-library/hooks"
"components": "@discontent/component-library/components",
"utils": "@discontent/component-library/lib/utils",
"ui": "@discontent/component-library/components/ui",
"lib": "@discontent/component-library/lib",
"hooks": "@discontent/component-library/hooks"
},
"iconLibrary": "lucide"
}
2 changes: 1 addition & 1 deletion packages/component-library/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MouseEventHandler, ReactNode } from "react";
import {
buttonVariants,
Button as ShadcnButton,
} from "component-library/components/ui/button";
} from "@discontent/component-library/components/ui/button";

export function Button({
children,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { FileInput } from "../File";
import { StaticImageProps } from "next-static-image/src";
import { StaticImageProps } from "@discontent/next-static-image/src";
import { CheckboxInput } from "../Checkbox";

export function ImageInput({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DefaultControls, FormatButton, MarkdownInputProps } from "../common";
import clsx from "clsx";
import { MouseEventHandler, useState } from "react";
import { Errors, FieldWrapper, baseInputStyle } from "../../..";
import StyledMarkdown from "component-library/components/Markdown";
import StyledMarkdown from "@discontent/component-library/components/Markdown";

export function InlineMarkdownInput({
name,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MouseEventHandler, ReactNode } from "react";
import { Button } from "component-library/components/Button";
import { Button } from "@discontent/component-library/components/Button";
import { MarkdownToJSX } from "markdown-to-jsx/react";

export interface MarkdownControlsProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from "react";
import { Errors, FieldWrapper, baseInputStyle } from "../..";
import clsx from "clsx";
import StyledMarkdown from "component-library/components/Markdown";
import { Button } from "component-library/components/Button";
import StyledMarkdown from "@discontent/component-library/components/Markdown";
import { Button } from "@discontent/component-library/components/Button";
import { DefaultControls, MarkdownInputProps } from "./common";

export function MarkdownInput({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { VideoPlayer } from "component-library/components/VideoPlayer";
import { VideoPlayer } from "@discontent/component-library/components/VideoPlayer";
import { ReactNode, useState } from "react";
import clsx from "clsx";

Expand Down
2 changes: 1 addition & 1 deletion packages/component-library/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "component-library/lib/utils";
import { cn } from "@discontent/component-library/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
Expand Down
2 changes: 1 addition & 1 deletion packages/component-library/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";

import { cn } from "component-library/lib/utils";
import { cn } from "@discontent/component-library/lib/utils";

const DialogRoot = DialogPrimitive.Root;

Expand Down
6 changes: 3 additions & 3 deletions packages/component-library/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "component-library",
"name": "@discontent/component-library",
"version": "1.0.0",
"description": "",
"main": "index.js",
Expand All @@ -14,11 +14,11 @@
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"content-engine": "^1.0.0",
"@discontent/cms": "^1.0.0",
"github-markdown-css": "^5.8.1",
"lucide-react": "^0.563.0",
"markdown-to-jsx": "^9.6.1",
"next-static-image": "^0.0.1",
"@discontent/next-static-image": "^0.0.1",
"react-markdown": "^10.1.0",
"react-player": "^3.4.0",
"tailwind-merge": "^3.4.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/component-library/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
}
],
"paths": {
"component-library/*": ["./*"]
"@discontent/component-library/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
Expand Down
8 changes: 3 additions & 5 deletions packages/content-engine/content/deleteContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ import type { ContentTypeConfig, DeleteContentOptions } from "./types";
* });
* ```
*/
export async function deleteContent<
TData,
TIndexValue,
TKey extends Key,
>(options: DeleteContentOptions<TData, TIndexValue, TKey>): Promise<void> {
export async function deleteContent<TData, TIndexValue, TKey extends Key>(
options: DeleteContentOptions<TData, TIndexValue, TKey>,
): Promise<void> {
const {
config,
slug,
Expand Down
14 changes: 12 additions & 2 deletions packages/content-engine/content/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,23 @@ export async function processUploadChanges(
existingFile !== undefined &&
(uploadData === undefined || uploadData.file || uploadData.fileImportUrl)
) {
paths.push(relative(baseDir, getUploadFilePath(config, slug, existingFile, contentDirectory)));
paths.push(
relative(
baseDir,
getUploadFilePath(config, slug, existingFile, contentDirectory),
),
);
await removeUploadFile(config, slug, existingFile, contentDirectory);
}

// Write new file if provided
if (uploadData) {
paths.push(relative(baseDir, getUploadFilePath(config, slug, uploadData.fileName, contentDirectory)));
paths.push(
relative(
baseDir,
getUploadFilePath(config, slug, uploadData.fileName, contentDirectory),
),
);
await writeUploadFile(config, slug, uploadData, contentDirectory);
}

Expand Down
8 changes: 3 additions & 5 deletions packages/content-engine/content/rebuildIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ import type { ContentTypeConfig, RebuildIndexOptions } from "./types";
* });
* ```
*/
export async function rebuildIndex<
TData,
TIndexValue,
TKey extends Key,
>(options: RebuildIndexOptions<TData, TIndexValue, TKey>): Promise<void> {
export async function rebuildIndex<TData, TIndexValue, TKey extends Key>(
options: RebuildIndexOptions<TData, TIndexValue, TKey>,
): Promise<void> {
const { config, contentDirectory: providedContentDirectory } = options;

const contentDirectory = providedContentDirectory || getContentDirectory();
Expand Down
8 changes: 3 additions & 5 deletions packages/content-engine/content/updateContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,9 @@ export async function defaultUpdateUploadsProcessor(
* });
* ```
*/
export async function updateContent<
TData,
TIndexValue,
TKey extends Key,
>(options: UpdateContentOptions<TData, TIndexValue, TKey>): Promise<void> {
export async function updateContent<TData, TIndexValue, TKey extends Key>(
options: UpdateContentOptions<TData, TIndexValue, TKey>,
): Promise<void> {
const {
config,
slug,
Expand Down
12 changes: 10 additions & 2 deletions packages/content-engine/content/updateReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,11 @@ async function updateReferencesViaIndex<

result.updatedCount++;
result.updatedSlugs.push(slug);
const filePath = getContentFilePath(config as ContentTypeConfig, slug, contentDirectory);
const filePath = getContentFilePath(
config as ContentTypeConfig,
slug,
contentDirectory,
);
result.updatedPaths.push(relative(contentDirectory, filePath));
} catch (error) {
result.errors.push({
Expand Down Expand Up @@ -262,7 +266,11 @@ async function updateReferencesViaFileScan<

result.updatedCount++;
result.updatedSlugs.push(slug);
const filePath = getContentFilePath(config as ContentTypeConfig, slug, contentDirectory);
const filePath = getContentFilePath(
config as ContentTypeConfig,
slug,
contentDirectory,
);
result.updatedPaths.push(relative(contentDirectory, filePath));
}
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions packages/content-engine/demo/app/bookmarks/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from "next/link";
import { notFound } from "next/navigation";
import { readContentFile } from "content-engine/content/readContentFile";
import { getContentDirectory } from "content-engine/fs/getContentDirectory";
import { readContentFile } from "@discontent/cms/content/readContentFile";
import { getContentDirectory } from "@discontent/cms/fs/getContentDirectory";
import {
bookmarkConfig,
BookmarkIndexKey,
Expand Down
10 changes: 7 additions & 3 deletions packages/content-engine/demo/app/bookmarks/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import { createContent } from "content-engine/content/createContent";
import { getContentDirectory } from "content-engine/fs/getContentDirectory";
import { createContent } from "@discontent/cms/content/createContent";
import { getContentDirectory } from "@discontent/cms/fs/getContentDirectory";
import {
bookmarkConfig,
bookmarkFormSchema,
Expand Down Expand Up @@ -52,7 +52,11 @@ export default async function NewBookmarkPage({ searchParams }: PageProps) {
<div>
<h2>Create New Bookmark</h2>
<form action={createBookmark}>
<BookmarkForm submitLabel="Create Bookmark" cancelHref="/" note={note} />
<BookmarkForm
submitLabel="Create Bookmark"
cancelHref="/"
note={note}
/>
</form>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/content-engine/demo/app/notes/[slug]/delete/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { redirect, notFound } from "next/navigation";
import Link from "next/link";
import { readContentFile } from "content-engine/content/readContentFile";
import { deleteContent } from "content-engine/content/deleteContent";
import { getContentDirectory } from "content-engine/fs/getContentDirectory";
import { readContentFile } from "@discontent/cms/content/readContentFile";
import { deleteContent } from "@discontent/cms/content/deleteContent";
import { getContentDirectory } from "@discontent/cms/fs/getContentDirectory";
import {
noteConfig,
NoteIndexValue,
Expand Down
6 changes: 3 additions & 3 deletions packages/content-engine/demo/app/notes/[slug]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { redirect, notFound } from "next/navigation";
import { readContentFile } from "content-engine/content/readContentFile";
import { updateContent } from "content-engine/content/updateContent";
import { getContentDirectory } from "content-engine/fs/getContentDirectory";
import { readContentFile } from "@discontent/cms/content/readContentFile";
import { updateContent } from "@discontent/cms/content/updateContent";
import { getContentDirectory } from "@discontent/cms/fs/getContentDirectory";
import {
noteConfig,
noteFormSchema,
Expand Down
4 changes: 2 additions & 2 deletions packages/content-engine/demo/app/notes/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from "next/link";
import { notFound } from "next/navigation";
import { readContentFile } from "content-engine/content/readContentFile";
import { getContentDirectory } from "content-engine/fs/getContentDirectory";
import { readContentFile } from "@discontent/cms/content/readContentFile";
import { getContentDirectory } from "@discontent/cms/fs/getContentDirectory";
import {
noteConfig,
NoteIndexKey,
Expand Down
2 changes: 1 addition & 1 deletion packages/content-engine/demo/app/notes/form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useCurrentTimezone } from "content-engine/hooks/useCurrentTimezone";
import { useCurrentTimezone } from "@discontent/cms/hooks/useCurrentTimezone";
import type { Note } from "@/lib/notes";

interface NoteFormProps {
Expand Down
Loading
Loading