Skip to content

Commit

Permalink
feat(contented): QOL cleanup for contented-pipeline-md (#541)
Browse files Browse the repository at this point in the history
<!--  Thanks for sending a pull request! -->

#### What this PR does / why we need it:

Clean up `contented-pipeline-md` so that downstream extending on top of
this pipeline have an easier time working with files.
  • Loading branch information
fuxingloh authored Aug 5, 2023
1 parent 112957b commit 946ff24
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 55 deletions.
12 changes: 6 additions & 6 deletions packages/contented-example/docs/01-index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Contented

[Contented](https://contented.dev) is a Markdown-based authoring workflow that encourage developer authoring within
its contextual Git repository. `npm i @contentedjs/contented`
[Contented](https://contented.dev) is a Markdown-based bundler for your documentation with pipeline driven
authoring-oriented workflow to encourage developer authoring within its contextual Git repository.

With a headless design of 1 config file `contented.config.mjs`, developers can start writing
their [markdown content](04-markdown) and preview it on their localhost `contented write`. Choosing convention over
configuration reduces HTML/UI clutter, allowing developers to focus on authoring.
their [markdown content](04-markdown) and preview it on their localhost `contented generate --watch`.
Choosing convention over configuration reduces HTML/UI clutter, allowing developers to focus on authoring.

Authored content can be continuously delivered (CD) into a hosted static site (e.g., GitHub Pages/Netlify/Vercel) for
preview `contented generate`. As code drift, so does documentation; this allows each pull request to have an
Expand All @@ -16,7 +16,7 @@ By encouraging authoring next to the source (in the same git repo), developers c
develop. All domain-specific changes will go into the `main` branch with one Git Pull Request.

With `contented build`, you can compile your markdown into sources `index.js` and `*.json`. That output
into `./dist`. `npm publish` them into any registry of your choice, for you can
into `./dist` to `npm publish` them into any registry of your choice, for you can
easily `npm i @your-scope/your-npm-package` and use the processed content on any of your downstream sites. Easily
pulling up-to-date content and prose from individual domain-specific repositories and re-presented. Think microservices,
but for your prose!
Expand Down Expand Up @@ -73,7 +73,7 @@ repo/
"files": ["dist"],
"main": "dist/index.js",
"scripts": {
"write": "contented write",
"write": "contented generate --watch",
"generate": "contented generate",
"build": "contented build"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/contented-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"scripts": {
"build": "contented build",
"generate": "contented generate",
"watch": "contented watch",
"write": "contented write"
"watch": "contented build --watch",
"write": "contented generate --watch"
},
"lint-staged": {
"*": [
Expand Down
17 changes: 10 additions & 7 deletions packages/contented-pipeline-jest-md/src/JestMarkdownPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import { join, ParsedPath } from 'node:path';

import { parse } from '@babel/parser';
import { File } from '@babel/types';
import { MarkdownPipeline } from '@contentedjs/contented-pipeline-md';
import { MarkdownPipeline, MarkdownVFile } from '@contentedjs/contented-pipeline-md';
import stripIndent from 'strip-indent';
import { VFile } from 'vfile';

export class JestMarkdownPipeline extends MarkdownPipeline {
protected override computePath(sections: string[], parsedPath: ParsedPath): string {
const path = super.computePath(sections, parsedPath);
return path.replaceAll(/-(unit|i9n|e2e|integration|test|tests|spec)$/g, '');
}

protected override async readVFile(rootPath: string, file: string): Promise<VFile | undefined> {
protected override async readVFile(rootPath: string, file: string): Promise<MarkdownVFile | undefined> {
const path = join(rootPath, file);
const contents = await readFile(path, { encoding: 'utf-8' });
const ast = await this.parseAST(contents);
Expand All @@ -22,10 +21,14 @@ export class JestMarkdownPipeline extends MarkdownPipeline {
const comments = this.collectComments(ast) ?? [];
const value = this.mergeCodeblock(lines, comments).join('\n\n');

return new VFile({
path,
value,
});
return new MarkdownVFile(
{
path,
value,
},
this,
file,
);
}

protected collectComments(ast: File): IndexedComment[] | undefined {
Expand Down
63 changes: 40 additions & 23 deletions packages/contented-pipeline-md/src/MarkdownPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,58 @@ export class MarkdownPipeline extends ContentedPipeline {
return [];
}

vFile.data = { contented: this.newUnifiedContented(file) };
const output = await this.processor.process(vFile);
const contented = output.data.contented as UnifiedContented;

if (contented.errors.length > 0) {
const message = contented.errors.map((value) => `${value.type}:${value.reason}`).join(',');
console.warn(`@contentedjs/contented-pipeline-md: ${file} - failed with errors: [${message}]`);
const fileContent = await this.processVFile(fileIndex, vFile);
if (fileContent === undefined) {
return [];
}

const content = this.newFileContent(fileIndex, output.value as string, contented);
return [content];
return [fileContent];
}

protected async readVFile(rootPath: string, file: string): Promise<VFile | undefined> {
return read(join(rootPath, file));
protected async readVFile(rootPath: string, filePath: string): Promise<MarkdownVFile | undefined> {
const vFile = await read(join(rootPath, filePath));
return new MarkdownVFile(vFile, this, filePath);
}

protected newUnifiedContented(filePath: string): UnifiedContented {
return {
contentedPipeline: this,
pipeline: this.pipeline,
headings: [],
fields: {},
errors: [],
filePath,
};
}
protected async processVFile(fileIndex: FileIndex, vFile: MarkdownVFile): Promise<FileContent | undefined> {
const output = await this.processor.process(vFile);
const contented = output.data.contented as UnifiedContented;

if (contented.errors.length > 0) {
const message = contented.errors.map((value) => `${value.type}:${value.reason}`).join(',');
console.warn(`@contentedjs/contented-pipeline-md: ${vFile.filePath} - failed with errors: [${message}]`);
return undefined;
}

protected newFileContent(fileIndex: FileIndex, html: string, contented: UnifiedContented) {
return {
...fileIndex,
html,
html: output.value as string,
headings: contented.headings,
fields: contented.fields,
};
}
}

export class MarkdownVFile extends VFile {
data: {
contented: UnifiedContented;
};

constructor(
value: ConstructorParameters<typeof VFile>[0],
contentedPipeline: MarkdownPipeline,
public readonly filePath: string,
) {
super(value);
this.data = {
contented: {
contentedPipeline,
pipeline: contentedPipeline.pipeline,
headings: [],
fields: {},
errors: [],
filePath,
},
};
}
}
9 changes: 7 additions & 2 deletions packages/contented-pipeline/src/Pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export interface Pipeline {
*/
export abstract class ContentedPipeline {
public constructor(
protected readonly rootPath: string,
protected readonly pipeline: Pipeline,
public readonly rootPath: string,
public readonly pipeline: Pipeline,
) {
// eslint-disable-next-line no-param-reassign
pipeline.fields = {
Expand Down Expand Up @@ -60,6 +60,11 @@ export abstract class ContentedPipeline {
return Promise.all(contents.map(this.pipeline.transform));
}

/**
* @param fileIndex is the pre-computed FileIndex of the file
* @param rootPath is the root path of the pipeline
* @param file is the file to process
*/
protected abstract processFileIndex(fileIndex: FileIndex, rootPath: string, file: string): Promise<FileContent[]>;

get type(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ export default function ContentNavigation({ sections, className }) {
const path = section.sections.join(' / ');
return (
<li key={path}>
{path && <h2 className="font-display mb-3 font-medium text-slate-900 dark:text-white">{path}</h2>}
{path && <h2 className="font-display mb-3 font-medium text-slate-900/40 dark:text-white/40">{path}</h2>}

<ul
role="list"
className={clsx('mb-3 space-y-3', {
'border-l-2 border-slate-100 border-slate-200 dark:border-slate-800': path,
'border-l-2 border-slate-200 dark:border-slate-800': path,
})}
>
{section.items?.map((link) => (
<li key={link.path} className="relative">
<Link
href={link.path}
className={clsx({
'block w-full cursor-pointer pl-3 before:pointer-events-none before:absolute before:inset-y-0 before:-left-1 before:w-1':
'block w-full cursor-pointer truncate pl-3 before:pointer-events-none before:absolute before:inset-y-0 before:-left-1 before:w-1':
path,
'font-medium': !path,
'text-primary-500 before:bg-primary-500 font-semibold': link.path === router.asPath,
Expand Down
4 changes: 2 additions & 2 deletions packages/contented-preview/src/pages/_components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Bars3Icon, DocumentTextIcon, XMarkIcon } from '@heroicons/react/24/solid';
import { Bars3Icon, DocumentTextIcon, XMarkIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import Link from 'next/link';

Expand Down Expand Up @@ -59,7 +59,7 @@ export default function Header() {

<div className="flex items-center space-x-4">
<div>
<ThemeButton className="h-6 w-auto" />
<ThemeButton />
</div>
<div>
<GitHubButton className="h-6 w-auto" />
Expand Down
10 changes: 5 additions & 5 deletions packages/contented-preview/src/pages/_components/ThemeButton.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MoonIcon, SunIcon } from '@heroicons/react/24/solid';
import { MoonIcon, SunIcon } from '@heroicons/react/24/outline';
import { useTheme } from './ThemeContext';

export default function ThemeButton(props) {
Expand All @@ -7,10 +7,10 @@ export default function ThemeButton(props) {
return (
<div {...props}>
<button className="flex items-center">
<SunIcon className="hidden h-6 w-6 [[data-theme=light]_&]:block" onClick={() => setTheme('dark')} />
<MoonIcon className="hidden h-6 w-6 [[data-theme=dark]_&]:block" onClick={() => setTheme('light')} />
<SunIcon className="hidden h-6 w-6 [:not(.dark)[data-theme=system]_&]:block" onClick={() => setTheme('dark')} />
<MoonIcon className="hidden h-6 w-6 [.dark[data-theme=system]_&]:block" onClick={() => setTheme('light')} />
<SunIcon className="hidden h-4 w-4 [[data-theme=light]_&]:block" onClick={() => setTheme('dark')} />
<MoonIcon className="hidden h-4 w-4 [[data-theme=dark]_&]:block" onClick={() => setTheme('light')} />
<SunIcon className="hidden h-4 w-4 [:not(.dark)[data-theme=system]_&]:block" onClick={() => setTheme('dark')} />
<MoonIcon className="hidden h-4 w-4 [.dark[data-theme=system]_&]:block" onClick={() => setTheme('light')} />
</button>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/contented-preview/src/styles/prose.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
.prose code:not(pre code):not(:where([class~='not-prose'] *)) {
padding: 0.2em 0.4em;
border-radius: 6px;
@apply font-semibold text-slate-800;
@apply font-semibold;
@apply rounded border;
@apply border-slate-200/70 bg-slate-100/70;
@apply dark:border-slate-700/70 dark:bg-slate-800/70;
@apply border-slate-200/70 bg-slate-100/70 text-slate-800;
@apply dark:border-slate-700/70 dark:bg-slate-800/70 dark:text-slate-200;
}
9 changes: 9 additions & 0 deletions packages/contented/src/commands/BuildCommand.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { ContentedProcessor } from '@contentedjs/contented-processor';
import { Option } from 'clipanion';

import { BaseCommand } from './BaseCommand.js';
import { ContentedWalker } from './contented/ContentedWalker.js';
import { ContentedWatcher } from './contented/ContentedWatcher.js';

/**
* `contented build` the dist
*/
export class BuildCommand extends BaseCommand {
static paths = [[`build`]];

watch = Option.Boolean(`--watch`, false);

async execute() {
const config = await this.loadConfig();
config.processor.outDir = config.processor.outDir ?? './dist';

const processor = new ContentedProcessor(config.processor);
const walker = new ContentedWalker(processor);
await walker.walk();

if (this.watch) {
const watcher = new ContentedWatcher(processor);
await watcher.watch();
}
}
}
17 changes: 16 additions & 1 deletion packages/contented/src/commands/GenerateCommand.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import { ContentedProcessor } from '@contentedjs/contented-processor';
import { Option } from 'clipanion';

import { BaseCommand } from './BaseCommand.js';
import { ContentedPreview } from './contented/ContentedPreview.js';
import { ContentedWalker } from './contented/ContentedWalker.js';
import { ContentedWatcher } from './contented/ContentedWatcher.js';

/**
* `contented generate` the preview website
*/
export class GenerateCommand extends BaseCommand {
static paths = [[`generate`]];

watch = Option.Boolean(`--watch`, false);

async execute() {
const config = await this.loadConfig();
config.processor.outDir = config.processor.outDir ?? '.contented';

const processor = new ContentedProcessor(config.processor);
const walker = new ContentedWalker(processor);
await walker.walk();

if (this.watch) {
const watcher = new ContentedWatcher(processor);
await watcher.watch();
}

const preview = new ContentedPreview(config.preview);
await preview.init();
await preview.install();
await preview.generate();

if (this.watch) {
await preview.write();
} else {
await preview.generate();
}
}
}
1 change: 1 addition & 0 deletions packages/contented/src/commands/WatchCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContentedWatcher } from './contented/ContentedWatcher.js';

/**
* `contented watch` files and automatically rebuild when changed into output directory `.contented`
* @deprecated use `contented build --watch` instead
*/
export class WatchCommand extends BaseCommand {
static paths = [[`watch`]];
Expand Down
16 changes: 15 additions & 1 deletion packages/contented/src/commands/contented/ContentedWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ export class ContentedWatcher extends ContentedWalker {
const dotContentedDir = join(relative(this.processor.rootPath, process.cwd()), '.contented');
const callback = (err: Error | null, events: Event[]) => this.listen(events);
await watcher.subscribe(this.processor.rootPath, callback, {
ignore: [dotContentedDir, '**/.contented', '**/node_modules', '**/.turbo', '.git', '.idea', '.vscode'],
ignore: [
dotContentedDir,
'**/.contented',
'**/node_modules',
'**/jspm_packages',
'**/.turbo',
'**/.next',
'**/.nuxt',
'**/.yarn',
'**/.cache',
'**/.npm',
'.git',
'.idea',
'.vscode',
],
});
}

Expand Down

0 comments on commit 946ff24

Please sign in to comment.