Aller au contenu

omnibase/docs

The documentation, read from Markdown files: a manual in the back office, and public manuals - one per package, read in its repository, one version per branch - with a search (Typesense, or the pages' own index).

Étage
Structure préparée · Omnibase
Version
2.0
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnibase/docs:2.0.x-dev

The documentation of a site, read from Markdown files. A page is a file: it is written, reviewed and versioned with the code it describes, and no editor, no table and no migration stands between the file and the reader.

Where For whom
The back office's manual /docs, inside omnibase/admin the people who run the site
Public manuals /docs/<package>/<version>/<page> everyone: one manual per package, one version per branch
Search a palette on every page, /docs/_search Typesense, or the pages' own index

Then: how a page is written and how the pages look; without Symfony, the configuration reference, the reference (routes, commands, services) and testing.

What is on by default

Only the back office's manual. The public manuals (wikidoc.public.enabled), their discovery (wikidoc.manuals, wikidoc.discover) and the engine (wikidoc.search.typesense.enabled) are off until a site sets them, and their controller lives outside the folder the back office's routes are imported from: a site that had the bundle before them gains no route, no service it uses, no change in what its manual renders.

Requirements

PHP 8.1, omnisense/omnibase, league/commonmark. Under Symfony, the framework comes through omnibridge/symfony. The pages, the manuals, their Markdown and the search are plain PHP and load without the framework (Base\Wikidoc\Documentation, Manual, Search; the bundle, its controllers and commands are under src/Bridge/Symfony): see Without Symfony. Optional:

Package Adds
omnibase/admin the back office's manual
glitchr/typesense-bundle the search in Typesense
tempest/highlight (PHP 8.4) code blocks coloured on the server
symfony/process, and the git binary wikidoc:sync: the versions that are not checked out

Proposer une modification docs/index.md

The back office's manual

With omnibase/admin installed, /docs shows the documentation inside the back office: the page tree and the page's headings in a rail beside the text, and a search palette (Ctrl K) that matches in the browser.

# config/routes.yaml
wikidoc_controller:
    resource: "@WikidocBundle/src/Controller"
    type: attribute
    prefix: /
# config/packages/wikidoc.yaml
wikidoc:
    # Documentation roots, LOWEST priority first.
    roots:
        base:
            path: '%base.package_dir%/docs'
            label: 'Base'
        app:
            path: '%kernel.project_dir%/docs'
            label: 'Application'

Roots override each other by path

A page is identified by its path inside a root. When two roots hold install/requirements.md, the later one wins and the manual shows a badge on it: the rule Symfony uses for a bundle's templates. A root that does not exist on disk is skipped - shipping the configuration before writing any page is harmless.

The hierarchy is the folder tree. A folder is a section; its index.md, if it has one, is the section's own page. The root's index.md is the manual's home. Titles and order: see Writing pages.

Routes

Route Address
backoffice_manual /docs/{path} a page; without a path, the home or the first page
backoffice_manual_search /docs/_search the search records, as JSON, behind the same firewall

Raw HTML in a page is escaped: these files are rendered on an authenticated page, and markup that Markdown cannot write is refused rather than run.

In code

use Base\Wikidoc\Documentation\DocumentationRegistry;

$registry->getTree();                 // DocPage[]: the top-level pages and sections
$registry->get('install/requirements');
$registry->getDefault();              // the home page, else the first page
$registry->getSequence();             // every page in reading order
$registry->getNeighbours($page);      // [previous, next]
$registry->getAncestors($page);       // its breadcrumb

Proposer une modification docs/back-office.md

Without Symfony

The documentation itself loads without the framework: the bundle's root namespace - Base\Wikidoc\Documentation (the roots, their pages, the Markdown), Base\Wikidoc\Manual (the public manuals, their versions, the export of a branch) and Base\Wikidoc\Search (the search, its index, the Typesense client's interface) - needs PHP, league/commonmark, Symfony's standalone Finder and Filesystem (and Process for an export), a PSR-6 pool if one is given, nothing else. tests/BareTest.php loads its classes (tests/Fondation.php) with Composer's autoloader alone and fails if a class or a file of the framework comes with them.

The services by hand

use Base\Wikidoc\Documentation\DocumentationRegistry;
use Base\Wikidoc\Documentation\MarkdownRenderer;
use Base\Wikidoc\Manual\ManualMarkdownRenderer;
use Base\Wikidoc\Manual\ManualRegistry;
use Base\Wikidoc\Manual\PageRenderer;
use Base\Wikidoc\Search\LocalSearch;
use Base\Wikidoc\Search\ManualIndex;
use Base\Wikidoc\Search\ManualSearch;

// The back office's pages of a root, and one of them in HTML.
$pages = new DocumentationRegistry(['app' => ['path' => __DIR__.'/docs', 'label' => 'Application']]);
$html = (new MarkdownRenderer())->renderFile($pages->getDefault()->file);

// The public manuals found under a folder of repositories, a page, the search.
$manuals = new ManualRegistry([], ['/srv/repos/*/*'], ['export_dir' => '/srv/exports']);
$manual = $manuals->get('glitchr/omnitrade');
$version = $manual->getVersion(null);
$page = (new PageRenderer($manuals, new ManualMarkdownRenderer()))->render(
    $manual, $version, $manuals->pages($manual, $version)->get('installation'),
    fn (string $target, ?string $anchor = null) => '/docs/'.$manual->urlPath($version->name, $target).($anchor ? '#'.$anchor : ''),
    fn (string $file) => '/docs/_file/'.$manual->urlPath($version->name, $file),
);
$hits = (new ManualSearch($manuals, new LocalSearch(new ManualIndex($manuals, new MarkdownRenderer()))))->search('webhook');

ManualRegistry's three arguments are what wikidoc.manuals, wikidoc.discover and the other options give it under Symfony (Configuration reference): the manuals named, the glob patterns to discover, and export_dir, branches, docs, readme, remote, cache_ttl, exclude. PageRenderer::render() takes two closures, the address of a page (with its anchor) and the address of a file of the repository: the host's routes.

Run in the core's harness

tests/bare/docs-without-framework.php does the same over the tests' fixtures (tests/Fixtures/repos/*/*, three repositories). Run with the harness's autoloader and this checkout's classes (tests/bare/autoload.php), it returns:

array (
  'manuals' =>
  array (
    0 => 'acme/plain',
    1 => '@acme/script',
    2 => 'acme/widget',
  ),
  'link' => true,
  'picture' => true,
  'engine' => 'local',
  'hit' =>
  array (
    0 => '20-guides/webhooks',
    1 => 'Webhooks',
    2 => 'Signature',
    3 => 'signature',
  ),
  'pages' =>
  array (
    0 => 'installation',
    1 => '20-guides/events',
    2 => '20-guides/webhooks',
  ),
)

The three manuals found (an npm package keeps its scope), a relative link and a picture of a page rewritten to the closures' addresses, a search answered by the local index with its section and anchor, the back office's pages of a root in reading order.

Under Symfony

What needs Symfony is under src/Bridge/Symfony: the bundle, its configuration and extension, the two controllers, the two commands and the Typesense client of glitchr/typesense-bundle (Reference). The former names still answer: Base\Wikidoc\WikidocBundle in config/bundles.php, the folders @WikidocBundle/src/Controller and @WikidocBundle/src/Site/Controller in the routes, Base\Wikidoc\DependencyInjection\*, Base\Wikidoc\Command\*.

Proposer une modification docs/without-symfony.md

Public manuals

One manual per package, read where the package is written: the docs/ folder and the README of its repository. One version per branch. The pages are rendered on the public site, the way symfony.com/doc renders its own.

Switching them on

# config/packages/wikidoc.yaml
wikidoc:
    public:
        enabled: true
        path: docs            # the address they are published under
    manuals:
        glitchr/omnitrade:
            path: '%env(OMNI_REPOS)%/omnitrade/core'    # the repository's checkout
    # or: every package found under these folders
    discover:
        - '%env(OMNI_REPOS)%/*/*'
# config/routes.yaml
wikidoc_manuals:
    resource: "@WikidocBundle/src/Site/Controller"
    type: attribute
    prefix: /

The public controller is in src/Site/Controller, outside src/Controller: a site that imports the latter for the back office's manual does not get the public routes with it.

What a manual is made of

In the repository In the manual
README.md the home page - unless docs/index.md exists, which is then the home, the README staying readable as the first page
docs/*.md a page each
docs/<folder>/ a section; docs/<folder>/index.md is its own page
composer.json or package.json the manual's name (glitchr/omnitrade, @glitchr/stickyjs) and description
.git/config, remote origin the repository's web address, for "Edit this page"

A package without a docs/ folder and with a README is a manual of one page; a package with neither is not a manual.

Naming them, or finding them

wikidoc.manuals names manuals one by one, keyed by the package's name:

Key Default
path (required) the repository's checkout
label the name what the pages call it
description the package's
docs wikidoc.docs (docs) the Markdown folder inside the repository
readme wikidoc.readme (README.md) false: no README
repository the origin remote, else the package's support.source / repository its web address
default_version the branch checked out the version an address without one opens
versions (see below) versions with a checkout of their own

wikidoc.discover takes glob patterns: every folder matched that holds a composer.json or a package.json and something to read is a manual named after its package. wikidoc.discover_exclude leaves packages out, by name or by folder (fnmatch patterns). A manual named in wikidoc.manuals wins over one discovered under the same name. A folder that is not there - not mounted, not cloned yet - is skipped.

The folders are looked at when a page is asked for, not when the container is compiled: a repository cloned later is simply there. wikidoc.cache_ttl (seconds, 0 by default) keeps the list of discovered folders, and the local search records, for a while.

Versions

A version is a branch of the repository whose name matches wikidoc.branches (by default 1.x, 2.0, main, master...).

  • The branch checked out is read where it is: edit a file, reload the page. It is the manual's default version.
  • The other branches are copied by a command, with git (ls-tree, show: the repository is only read, a read-only mount is enough):
bin/console wikidoc:sync                    # every manual
bin/console wikidoc:sync glitchr/omnibus    # one
bin/console wikidoc:sync --remote=github    # read another remote's branches beside the local ones

It writes each branch's docs/ and README (Markdown and pictures) under wikidoc.export_dir (var/wikidoc/<package>/<branch>/), where the next request finds it as a version, and removes the copy of a branch that is gone. Local branches come first, then the remote's (origin): a branch that was fetched but never checked out is a version too. Without git or symfony/process the command says so, and each manual keeps the one version of its working tree.

Run it after a deployment or a fetch, or from cron.

Versions with a checkout of their own, when a site prefers worktrees to copies:

wikidoc:
    manuals:
        glitchr/omnibus:
            path: '/srv/repos/omnibus/core'
            versions:
                '2.x': '/srv/repos/omnibus/core'
                '1.x': { path: '/srv/worktrees/omnibus-1.x', branch: '1.x' }

Versions are listed newest first (main and master ahead, then 3.x, 2.x, 1.x). A folder that is not a git repository has one version, named current.

Addresses

Address
/docs every manual (wikidoc_index)
/docs/glitchr/omnitrade redirects to the default version
/docs/glitchr/omnitrade/1.x the version's home
/docs/glitchr/omnitrade/1.x/webhooks a page (wikidoc_manual, path: glitchr/omnitrade/1.x/webhooks)
/docs/glitchr/omnitrade/current/webhooks redirects to the page in the default version
/docs/_search?q=webhook the search (wikidoc_search): see Search
/docs/_file/glitchr/omnitrade/1.x/docs/img/flow.png a picture of the manual (wikidoc_asset)

A manual's part of an address is its package name, lower case, without the @ of an npm scope: glitchr/stickyjs for @glitchr/stickyjs. One address per page: an address without a version redirects to the one that spells it out. A folder without an index.md redirects to its first page.

<a href="{{ path('wikidoc_manual', {path: manual.urlPath()}) }}">{{ manual.label }}</a>
<a href="{{ path('wikidoc_manual', {path: manual.urlPath('1.x', 'webhooks')}) }}">Webhooks, 1.x</a>

What a page shows

  • the manual's contents, the current branch open;
  • a breadcrumb: Documentation / the manual / the version / the sections / the page;
  • the version menu, each entry going to the same page in that version (to its home when that version has no such page), and a notice on a page of a version that is not the default one;
  • the page, its headings carrying anchors;
  • "On this page": the page's second and third level headings, the one being read marked;
  • "Edit this page", to the file on its forge, and the path of the source file;
  • previous and next, in reading order.

"Edit this page" is {repository}/edit/{branch}/{file} (GitLab: {repository}/-/edit/...); wikidoc.edit_url sets another pattern with the same three placeholders. A manual whose repository is not known has no such link. The source stays the Markdown in the repository: there is no editor here.

In code

use Base\Wikidoc\Manual\ManualRegistry;

$manuals->all();                               // array<string, Manual>, keyed "glitchr/omnitrade"
$manual = $manuals->get('glitchr/omnitrade');  // or null
$manual->versions;                             // array<string, ManualVersion>, newest first
$manual->getDefaultVersion();
$manual->urlPath('1.x', 'webhooks');           // "glitchr/omnitrade/1.x/webhooks"
$manuals->pages($manual, $version);            // a DocumentationRegistry: tree, pages, reading order
$manuals->resolve('glitchr/omnitrade/1.x/webhooks');   // ManualLocation: manual, version, path

ManualRegistry and ManualSearch are registered whether the public controller is or not: a site may read manuals from controllers of its own.

Proposer une modification docs/manuals.md

Writing pages

A page is a Markdown file (CommonMark with GitHub's extensions: tables, strikethrough, task lists, autolinks). It should read well on its forge too: nothing here needs a syntax GitHub does not render.

Title and order

---
title: Installation
order: 10
---

# Installing the widget

The title in the contents is the front matter's title, else the first # heading, else the file's name. The order is the front matter's order, else a number the file's name starts with (10-getting-started.md, 20-architecture/), else 500; pages of the same order are sorted by title. The front matter is not printed.

A page without a first-level heading gets its title printed above it.

Write links for the repository; the manual follows them:

Written in docs/installation.md Goes to
[webhooks](webhooks.md), [guides](guides/) that page, in the same manual and version
[signature](webhooks.md#signature) that page's anchor
[overview](../README.md) the manual's home
[the bridge](../src/Bridge/Symfony) the folder on the forge, at the version's branch
![flow](img/flow.png) the picture, served by the site
[anchor](#configuration), https://... unchanged

A link is relative to the file it is written in, wherever that file is: a README links to docs/installation.md. A link that climbs out of the repository is left as written. Pictures served: png, jpg, gif, svg, webp, avif.

Callouts

Written as GitHub writes them - a quote whose first line is a marker:

> [!NOTE]
> The widget needs PHP 8.2.

> [!WARNING]
> Never commit the signing secret.

[!NOTE], [!TIP], [!IMPORTANT], [!WARNING], [!CAUTION]. Their titles are translated (wikidoc.callout.*: "Astuce", "Attention"...). The back office's manual renders them as plain quotes.

Code

```php
$widget = new Widget(secret: 'not-a-real-secret');
```

A fenced block is framed, named after its language, and has a "copy" button. With tempest/highlight installed it is coloured on the server - php, yaml, twig, sh / bash, js, json, html, css, scss, sql, xml, ini, dotenv, nginx, dockerfile, diff, and what else the library knows; a language it does not know is shown plain. Without the library every block is plain.

Raw HTML is escaped, in every page.

Headings

Second to fourth level headings get an anchor (## Signature is #signature), linked from "On this page" (second and third level) and from the search results.

Proposer une modification docs/writing.md

Every page of the public manuals has a search field; Ctrl K (or /) opens it as a palette that asks the server as one types, in the manual being read first, in every manual on demand. Without JavaScript the field is a form that goes to the results page.

/docs/_search?q=webhook                                   every manual, each in its default version
/docs/_search?q=webhook&manual=glitchr/omnitrade          one manual, its default version
/docs/_search?q=webhook&manual=glitchr/omnibus&version=1.x
/docs/_search?q=webhook&format=json                       what the palette reads

A result is a page, or a section of a page (it links to the heading). The answer says who gave it: engine is typesense or local.

The local index

With nothing configured, the search reads the pages' own index: one record per page and one per heading - title, section, text (code included: a class name or a command is what people search for) - built by SearchIndexBuilder, the index of the back office's palette, and matched in PHP. Every word of the query must be found; a title outranks a heading, a heading the text. No typo tolerance.

Typesense

composer require glitchr/typesense-bundle
# config/packages/typesense.yaml: the connection (no entity mapping is needed)
typesense:
    default_connection: default
    connections:
        default:
            secret: '%env(TYPESENSE_SECRET)%'
            scheme: '%env(TYPESENSE_SCHEME)%'
            host: '%env(resolve:TYPESENSE_HOST)%'
            port: '%env(int:TYPESENSE_PORT)%'
            options:
                connection_timeout_seconds: 1

# config/packages/wikidoc.yaml
wikidoc:
    search:
        typesense:
            enabled: true
            connection: ~        # typesense-bundle's connection; null: its default one
            prefix: wikidoc      # the collections are <prefix>_<manual>_<version>
bin/console wikidoc:index                      # every manual, every version
bin/console wikidoc:index glitchr/omnitrade    # one

One collection per manual and version (wikidoc_glitchr-omnitrade_1-x), dropped and written again at each run, with the same records as the local index:

Field
manual, version facets
path, anchor where the result links to (not indexed)
title, section, text searched, weighted 4, 3, 1, prefixes and two typos allowed
rank 0 for a page, 1 for a section: the page first, for the same match

Run without a manual's name, the command also drops the collections of manuals and versions that are no longer published. Run it after wikidoc:sync, after a deployment, or from cron.

A search over every manual is one multi_search call, one search per manual (40 per call).

When the engine does not answer

The local index answers, and the result says local:

  • glitchr/typesense-bundle is not installed, or enabled is false;
  • the server is down or times out (keep connection_timeout_seconds short);
  • a manual that is searched has no collection yet (wikidoc:index has not run since it appeared).

Nothing is shown to the visitor but the results; a notice goes to the log. wikidoc:index itself exits with an error when the engine is switched on and does not answer, and says so when it is switched off.

In code

use Base\Wikidoc\Search\ManualSearch;

$result = $search->search('webhook');                                // every manual
$result = $search->search('webhook', 'glitchr/omnitrade', '1.x', limit: 10);

$result->engine;     // "typesense" or "local"
$result->found;
foreach ($result->hits as $hit) {
    $hit->manual; $hit->version; $hit->path;      // "glitchr/omnitrade", "1.x", "webhooks"
    $hit->title; $hit->section; $hit->anchor;     // "Webhooks", "Signature", "signature"
    $hit->snippet;
}

Base\Wikidoc\Search\TypesenseClientInterface is the handful of calls made to the engine; a test replaces it with its own (see tests/Search/FakeTypesense.php).

Proposer une modification docs/search.md

Templates and look

The public pages extend the host's layout1.html.twig and fill its title, description, stylesheets and content blocks, as the other omnibase bundles do.

Template
@Wikidoc/client/index.html.twig every manual
@Wikidoc/client/page.html.twig a page
@Wikidoc/client/search.html.twig the results
@Wikidoc/client/_tree.html.twig the manual's contents
@Wikidoc/client/_search.html.twig the search field

A site overrides one by placing a file of the same name under templates/bundles/WikidocBundle/client/ - typically index.html.twig, to list the manuals the way its own catalogue groups them.

The look is the site's

public/css/manual.css (bundles/wikidoc/css/manual.css once assets:install ran) has no colour and no typeface of its own. A site sets custom properties, in its light and its dark mode:

:root {
    --manual-ink: #101418;        /* text */
    --manual-soft: #566070;       /* secondary text */
    --manual-line: #d5dae1;       /* rules, borders */
    --manual-bg: #ffffff;         /* the page */
    --manual-surface: #f2f4f5;    /* code, hovered rows */
    --manual-accent: #1238d6;     /* links, the current page */
    --manual-font: "Recursive", sans-serif;
    --manual-font-display: "Archivo", sans-serif;
    --manual-font-mono: "Recursive", monospace;
    --manual-top: 5rem;           /* the height of a sticky header: where the side columns stick */
}

Also: --manual-on-accent, --manual-mark (the searched words), --manual-radius, --manual-code-bg, --manual-code-ink, the code's colours --manual-hl-keyword, -type, -property, -value, -number, -comment, -variable, and the callouts' --manual-note, -tip, -warning, -caution.

The script

public/js/manual.js has no dependency: the search palette, the "copy" buttons, the current heading in "On this page", the contents folded behind a button on a narrow screen. The pages load it themselves; a site that bundles its scripts may import it instead - loading it twice does nothing - and it sets itself up again after a page is swapped in by transparentjs (transparent:load).

Without the script everything is still reachable: the search field posts to the results page, the version menu is a <details>, the contents are in the page.

Texts

translations/wikidoc+intl-icu.{en,fr}.yaml: @wikidoc.manual.*, @wikidoc.search.*, @wikidoc.callout.*.

Proposer une modification docs/templates.md

Configuration reference

config/packages/wikidoc.yaml, as Base\Wikidoc\Bridge\Symfony\DependencyInjection\WikidocConfiguration declares it (bin/console config:dump-reference wikidoc in the core's harness, this checkout mounted over vendor/omnibase/docs):

wikidoc:

    # Documentation roots, lowest priority first; a later root overrides same-path pages.
    roots:

        # Prototype
        name:
            path:                 ~ # Required
            label:                null

    # The manuals: one per package, read in the docs/ folder and the README of its repository. Keyed by the package's name.
    manuals:

        # Prototype
        name:

            # The repository's checkout.
            path:                 ~ # Required
            label:                null
            description:          null

            # The Markdown folder inside the repository (default: wikidoc.docs).
            docs:                 null

            # The README inside the repository; false: none (default: wikidoc.readme).
            readme:               null

            # Its web address, for "Edit this page". Default: the checkout's origin remote.
            repository:           null
            default_version:      null

            # Versions with a checkout of their own: {"2.x": {path: ..., branch: "2.x"}}. Default: the branch checked out, and those wikidoc:sync exported.
            versions:

                # Prototype
                name:
                    path:                 ~ # Required
                    branch:               null

    # Glob patterns: every folder matched that holds a composer.json or a package.json, and a docs/ folder or a README, is a manual named after its package.
    discover:             []

    # Package names (or folders) left out of the discovery; fnmatch patterns.
    discover_exclude:     []

    # The Markdown folder inside a repository.
    docs:                 docs

    # The README inside a repository; false: none.
    readme:               README.md

    # The git remote a repository is published on.
    remote:               origin

    # The branches that are versions.
    branches:             /^(\d+\.(x|\d+)|main|master)$/

    # Where wikidoc:sync copies the branches that are not checked out.
    export_dir:           '%kernel.project_dir%/var/wikidoc'

    # "{repository}/edit/{branch}/{file}"; null: the forge's own convention.
    edit_url:             null

    # Seconds the discovered folders and the local search records are kept; 0: read again at each request.
    cache_ttl:            0
    public:

        # Registers the public controller (import @WikidocBundle/src/Site/Controller in the routes).
        enabled:              false

        # The address the manuals are published under.
        path:                 docs
    search:
        typesense:

            # Needs glitchr/typesense-bundle; without it, or with the server down, the local index answers.
            enabled:              false

            # The typesense-bundle connection; null: its default one.
            connection:           null

            # The collections are <prefix>_<manual>_<version>.
            prefix:               wikidoc

Option Type Default Effect Page
roots.<name>.path, .label string, required; string ; the name, capitalised The back office's roots, lowest priority first: a later root's page replaces an earlier one's of the same path. A root missing on disk is skipped The back office's manual
manuals.<package> map A manual by its package name: path (the checkout, required), label, description, docs, readme, repository (for "Edit this page"; default: the checkout's origin), default_version, versions ({name: {path, branch}} or {name: path}) Public manuals
discover list of globs [] Every folder matched with a composer.json or a package.json and a docs/ folder or a README is a manual named after its package Public manuals
discover_exclude list of fnmatch patterns [] Packages or folders left out of the discovery
docs string docs The Markdown folder of a repository
readme string or false README.md The README of a repository; false: none
remote string origin The git remote a repository is published on
branches regular expression /^(\d+\.(x|\d+)|main|master)$/ The branches that are versions Versions
export_dir string %kernel.project_dir%/var/wikidoc Where wikidoc:sync copies the branches that are not checked out
edit_url string null {repository}/edit/{branch}/{file}; null: the forge's own convention
cache_ttl int, 0 or more 0 Seconds the discovered folders and the local search records are kept; 0: read again at each request
public.enabled bool false Registers the public controller (import @WikidocBundle/src/Site/Controller in the routes); off, it is removed from the container Public manuals
public.path string docs The address the manuals are published under: a path without slashes around it (docs, manuals/en), else the configuration is refused
search.typesense.enabled bool false The search in Typesense; registered only when glitchr/typesense-bundle is installed too Search
search.typesense.connection string null The typesense-bundle connection; null: its default
search.typesense.prefix string wikidoc The collections are <prefix>_<manual>_<version>

The roots are given to DocumentationRegistry and the manuals' options to ManualRegistry as arguments (their paths resolved: %kernel.project_dir%, an %env()%); the other options are also parameters (wikidoc.public.path, which the public routes read, wikidoc.cache_ttl...).

Proposer une modification docs/configuration.md

Reference

Routes

Route Path Methods Controller Imported from
backoffice_manual_search /docs/_search GET Bridge\Symfony\Controller\Backend\ManualController::search (priority 10) @WikidocBundle/src/Controller
backoffice_manual /docs/{path} (path: optional, .+) any Bridge\Symfony\Controller\Backend\ManualController @WikidocBundle/src/Controller
wikidoc_index /<public.path> GET Bridge\Symfony\Site\Controller\ManualController @WikidocBundle/src/Site/Controller
wikidoc_search /<public.path>/_search GET the same (priority 10)
wikidoc_asset /<public.path>/_file/{path} GET the same (priority 10)
wikidoc_manual /<public.path>/{path} GET the same

<public.path> is wikidoc.public.path (docs). The back office's controller is registered only when omnibase/admin is installed (it renders in its layout, with its AdminContext and MenuBuilder); the public one only with wikidoc.public.enabled. Both share /docs by default: a site that publishes its manuals and has the back office's manual gives public.path another value, or imports the two under different prefixes.

Console commands

Description:
  Copies the documentation of every version branch of the manuals' repositories

Usage:
  wikidoc:sync [options] [--] [<manual>]

Arguments:
  manual                One manual, by its package name (default: all of them)

Options:
      --remote=REMOTE   The remote whose branches are read beside the local ones [default: "origin"]
Description:
  Writes the manuals' search index into Typesense

Usage:
  wikidoc:index [<manual>]

Arguments:
  manual                One manual, by its package name (default: all of them)

What each does is in Public manuals (wikidoc:sync) and Search (wikidoc:index). Run in the core's harness, where no manual is configured and Typesense is off, they answer:

$ php bin/console wikidoc:sync

 ! [NOTE] No manual is configured (wikidoc.manuals, wikidoc.discover).

$ php bin/console wikidoc:index

 ! [NOTE] Typesense is not switched on (wikidoc.search.typesense.enabled,
 !        glitchr/typesense-bundle): the search reads the local index - 0
 !        record(s) in 0 manual(s).

Services

config/services.php registers DocumentationRegistry (its roots from wikidoc.roots), MarkdownRenderer, SearchIndexBuilder, ManualRegistry, ManualMarkdownRenderer, PageRenderer (its $editPattern from wikidoc.edit_url), ManualIndex, LocalSearch, ManualSearch, BranchExporter, the two commands and the controllers. With search.typesense.enabled and glitchr/typesense-bundle installed, the extension adds Bridge\Symfony\Search\BundleTypesenseClient (on typesense_manager and the connection named), aliased as Base\Wikidoc\Search\TypesenseClientInterface, and Search\TypesenseIndex; without them ManualSearch has no engine and the local index answers.

Class What it is
Documentation\DocumentationRegistry The back office's roots and pages: getTree(), getSequence(), getDefault(), a page by its path
Documentation\DocPage A page: its path, title, order, file, the root it comes from, the page it supersedes
Documentation\MarkdownRenderer A page's HTML (league/commonmark, GFM, callouts, raw HTML escaped) and its plain text
Documentation\SearchIndexBuilder The back office's search records
Manual\ManualRegistry The public manuals: configured and discovered, their versions, their pages
Manual\Manual, ManualVersion, ManualLocation A manual (name, label, path, versions, defaultVersion, repository, urlPath()), a version (name, branch, root, live: a checkout, or an export of wikidoc:sync), a page's place in a version (manual, version, path)
Manual\PageRenderer, RenderedPage A page rendered: html, headings, titled (whether it has a title of its own), editUrl ("Edit this page"), sourceUrl, source
Manual\ManualMarkdownRenderer The manuals' Markdown, links and pictures rewritten
Manual\BranchExporter, Git wikidoc:sync's copy of a branch's docs/ and README
Search\ManualSearch, LocalSearch, ManualIndex, TypesenseIndex The search, the local index, the records, the engine
Search\SearchResult, SearchHit A result (engine, hits), a hit (path, title, section, anchor)
Search\TypesenseClientInterface What TypesenseIndex asks of a Typesense client

Proposer une modification docs/reference.md

Testing

# in omnisense/omnibase's harness (core/docker)
docker compose -f compose.yml -f compose.checkouts.yml run --rm omnibase test docs
# or from an application that has the bundle
php vendor/bin/phpunit -c vendor/omnibase/docs/phpunit.xml.dist

The suite works on fixtures: tests/Fixtures/repos/acme/{plain,script,widget}, three repositories (a README only, an npm package, a manual with guides, a picture and versions), and tests/Fixtures/export, the copies wikidoc:sync would make.

Test What it checks
tests/Documentation/DocumentationRegistryTest.php The roots, a home file outside the folder, the reading order, a page's neighbours and ancestors
tests/Manual/ManualRegistryTest.php Discovery and exclusion, a manual found by its package name however spelt, a configured manual over a discovered one, the versions (the working tree first, the exports, checkouts of their own, newest first), the README as home, a page's address
tests/Manual/GitTest.php A branch, its siblings and its origin read from the .git folder, outside a repository, the other branches copied out as versions
tests/Manual/PageRendererTest.php Relative links (to pages, folders, inside each version), "Edit this page", the headings' ids, callouts, code blocks, a path that never leaves the repository, the back office's renderer untouched
tests/Search/ManualSearchTest.php The local index (sections, every word, code), a collection per manual and version replaced at each indexing, the engine (tests/Search/FakeTypesense.php stands in for a server), the local index when the engine is down or a manual was never indexed
tests/DependencyInjection/ExtensionTest.php A site that sets nothing new gets nothing new, the routes of the imported folders, the controllers under both names, the kept names, the public manuals on demand, the defaults
tests/BareTest.php The root namespace loaded with Composer's autoloader alone (Without Symfony)

An application's own test of its manual can build DocumentationRegistry on its docs/ and assert that every page has a title and that getSequence() is in the order it wants; a search of its own goes through ManualSearch with LocalSearch, or a TypesenseClientInterface of its own in place of a server.

Proposer une modification docs/testing.md