Inkstone Docs
Search... ⌘K
Reference

Extension Points#

Inkstone is built from small services connected by contracts and DTOs.

Commands should stay thin. Package behavior lives in services, parsers, transformers, renderers, and generators.

Pipeline Overview#

CompositeDocumentDiscoverer
    ├── FilesystemDocumentDiscoverer (Markdown files)
    └── ApiSpecDiscoverer (OpenAPI spec files)
         -> MarkdownParser (skipped for API documents)
         -> TransformerPipeline
             ├── HeadingAnchorTransformer
             ├── ExternalLinkTransformer
             ├── BaseUrlLinkTransformer
             ├── GitHubRelativeLinkTransformer
             ├── DemoBlockTransformer
             ├── SyntaxHighlightTransformer
             └── ApiHtmlTransformer (generates API page HTML)
         -> NavigationBuilder
         -> DocumentRenderer
         -> StaticSiteGenerator
             -> pages, search, robots metadata, and assets
             -> BuildExtensionPipeline
                 -> configured BuildExtension instances

Core DTOs#

DTO Purpose
Document Source path, relative path, slug, URL, Markdown, HTML, metadata, headings, AST
Heading Heading level, text, ID, and position
NavigationItem Sidebar title, URL, active state, order, children, headings
RenderedPage Rendered document, HTML, and output path
SearchEntry Static search index entry
BuildContext Readonly processed documents, rendered pages, and output path passed to post-build extensions
DemoBlock Parsed demo language, source, metadata, expected exceptions, void flag
DemoResult Demo execution result, stdout, exception, rendered value state

Core Contracts#

Contract Responsibility
DocumentDiscoverer Find documentation sources and create Document DTOs
MarkdownParser Parse frontmatter, headings, Markdown HTML, and AST
Transformer Transform a parsed Document
NavigationBuilder Build sidebar navigation for a document set
DocumentRenderer Render a page through the theme
StaticSiteGenerator Build the complete static site
SearchIndexer Produce SearchEntry DTOs
BuildExtension Run application-specific work after the core build is complete
DemoRuntime Execute or render demo blocks
DemoResultRenderer Render demo result values as HTML

Default Services#

Service Contract
CompositeDocumentDiscoverer DocumentDiscoverer (aggregates all discoverers)
FilesystemDocumentDiscoverer Markdown file discovery (child of composite)
ApiSpecDiscoverer OpenAPI spec file discovery (child of composite)
CommonMarkMarkdownParser MarkdownParser
NavigationBuilder NavigationBuilder
BladeDocumentRenderer DocumentRenderer
StaticDocumentationGenerator StaticSiteGenerator
JsonSearchIndexer SearchIndexer
BuildExtensionPipeline Resolve and invoke configured BuildExtension services
SitemapExtension BuildExtension
SimpleDemoRuntime DemoRuntime

Build Extensions#

Build extensions add post-build behavior without modifying the generator. Each extension implements one lifecycle method:

use Inkstone\Contracts\BuildExtension;
use Inkstone\DTOs\BuildContext;

final class BuildManifestExtension implements BuildExtension
{
    public function afterBuild(BuildContext $context): void
    {
        // Inspect $context->documents and $context->pages, then write an artifact
        // beneath $context->outputPath.
    }
}

BuildContext is readonly and exposes:

Property Type Description
documents list<Document> Fully processed Markdown and API documents
pages list<RenderedPage> Pages rendered and written by the build
outputPath string Configured generated-output directory

Extensions run once, in configuration order, after pages, search, robots metadata, and assets are complete. The container resolves each class only when the extension pipeline runs, so constructor injection is available.

StaticSiteGenerator::build() continues to return the rendered pages. Extensions receive the same page list through BuildContext.

Add An Application Extension#

For example, an application can add a small JSON build manifest:

namespace App\Inkstone;

use Illuminate\Filesystem\Filesystem;
use Inkstone\Contracts\BuildExtension;
use Inkstone\DTOs\BuildContext;

final readonly class BuildManifestExtension implements BuildExtension
{
    public function __construct(private Filesystem $files) {}

    public function afterBuild(BuildContext $context): void
    {
        $manifest = json_encode(
            ['pages' => count($context->pages)],
            JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR,
        );

        $this->files->put($context->outputPath.'/build-manifest.json', $manifest."\n");
    }
}

Register it after the bundled sitemap extension:

use App\Inkstone\BuildManifestExtension;
use Inkstone\Extensions\SitemapExtension;

'extensions' => [
    SitemapExtension::class,
    BuildManifestExtension::class,
],

The same configuration works in a standalone inkstone.php loaded with --config when Composer can autoload the extension class. An exception from any extension fails the build through the existing command error handling.

Bundled Sitemap Extension#

SitemapExtension is the first bundled extension and remains in the default configuration. It preserves the existing build.generate_sitemap switch and sitemap.xml output. Laravel applications can resolve it directly to obtain the public sitemap URL:

use Inkstone\Extensions\SitemapExtension;

$url = app(SitemapExtension::class)->url();

See Sitemaps for canonical URL resolution and parent sitemap-index integration.

Transformers#

Default transformers are configured in order:

'transformers' => [
    HeadingAnchorTransformer::class,
    ExternalLinkTransformer::class,
    BaseUrlLinkTransformer::class,
    GitHubRelativeLinkTransformer::class,
    DemoBlockTransformer::class,
    SyntaxHighlightTransformer::class,
    ApiHtmlTransformer::class,
],

Each transformer implements:

use Inkstone\Contracts\Transformer;
use Inkstone\DTOs\Document;

final class AlertTransformer implements Transformer
{
    public function transform(Document $document): Document
    {
        return $document->withHtml($document->html);
    }
}

Register it in the config:

'transformers' => [
    HeadingAnchorTransformer::class,
    AlertTransformer::class,
    SyntaxHighlightTransformer::class,
],

Replacing A Service#

Inside Laravel, bind your implementation in a service provider:

use Inkstone\Contracts\SearchIndexer;

$this->app->bind(SearchIndexer::class, CustomSearchIndexer::class);

Standalone usage can load custom classes through Composer autoloading and config.

Demo Result Renderers#

Inkstone includes individual renderers for exceptions, renderables, primitives, arrays, collections, models, and objects.

Custom result rendering should implement:

use Inkstone\Contracts\DemoResultRenderer;

final class MoneyResultRenderer implements DemoResultRenderer
{
    public function supports(mixed $value): bool
    {
        return $value instanceof Money;
    }

    public function render(mixed $value): string
    {
        return '<p>'.$value->format().'</p>';
    }
}

Keep renderers small and deterministic because they run during static builds.