This repository owns the public, versioned boundary between Bopli and independently deployed themes.
Licensed under the MIT License.
-
@bopli/theme-sdkcontains public prop types plus injected navigation and content-query helpers. -
@bopli/theme-clivalidates, generates types, builds, packages, and serves convention-based Vue themes without access to Bopli application source. -
starter-theme/is the minimal source scaffold.
Theme repositories install the public packages:
npm install --save-dev --save-exact @bopli/theme-cli@0.12.0 @bopli/theme-sdk@0.8.0
npx bopli-theme validate .
npx bopli-theme types .
npx bopli-theme build .
npx bopli-theme package .
npx bopli-theme dev . --app ../bopli-appToolkit contributors can run the CLI directly against sibling repositories:
npm install
npm run build
npm test
npm run check
node packages/cli/bin/bopli-theme.js validate ../dev-cosmo
node packages/cli/bin/bopli-theme.js types ../dev-cosmo
node packages/cli/bin/bopli-theme.js build ../dev-cosmo
node packages/cli/bin/bopli-theme.js package ../dev-cosmo
node packages/cli/bin/bopli-theme.js dev ../dev-cosmo --app ../bopli-appThe SDK and CLI are strict TypeScript projects compiled to their package-local dist/ directories. The CLI entry point only parses and dispatches commands; theme inspection, source-boundary validation, descriptor generation, build orchestration, deterministic release packaging, the development server, and starter-recipe validation live in focused modules under packages/cli/src/. The published executable remains a minimal JavaScript shebang wrapper because Node package bins must be directly executable, and it loads the compiled TypeScript output.
Theme source is TypeScript too: normal modules use .ts, Vue components use <script setup lang="ts">, and each theme runs vue-tsc before a production build. Every template declares its typed authoring contract with one top-level define*Template() call in that setup block. bopli-theme types generates the ignored resources/js/.bopli/types.d.ts contract from package settings and template fields; build, package, and dev regenerate it automatically. starter-theme/ is the canonical checked scaffold.
Production browser builds preserve the runtime entry exports while emitting imported fonts, images, and other static assets as separate inventoried files. This keeps the render-blocking stylesheet small and lets each immutable asset use the CDN cache independently. Server builds remain self-contained for verified in-memory execution.
Theme identity, compatibility, chooser metadata, and optional presentation settings live in the normal package.json under bopli. Composer metadata is not used. The five setting types are text, boolean, select, color, and image; Bopli merges declared defaults with per-Site overrides and supplies the resulting settings prop to every template. Themes may additionally declare one typed defineFooter() contract in an ordinary Vue component, combining footer presentation settings with authored content fields and complete defaults. Bopli supplies the resolved footer.settings and footer.content data to every public template.
build writes immutable ESM, CSS, assets, and protocol-v1 theme.json to the theme's dist/ directory. It also writes the computed release hash to the ignored .bopli-release-hash file for CI upload paths. Until Bopli is explicitly declared production, the protocol, manifest schema, runtime ABI, and nested recipe versions always remain 1; package and theme-release versions use SemVer independently.
package performs the same validated production build and writes {handle}-{version}-{release-hash}.zip beside the theme source. The archive contains only the path-sorted contents of dist/ at its root and normalizes timestamps, permissions, compression, and entry ordering, so the same release produces byte-identical ZIP output. It is ready for the landlord Upload release workflow.
The reusable tag workflow validates and builds the theme, then stores the compiled dist/ contents as a GitHub Actions artifact named {handle}-{version}-{release-hash}. Downloading that artifact produces the ZIP accepted by the landlord Upload release form. The workflow does not receive object-storage credentials, publish to the theme bucket, or register a release in Bopli.
dev accepts a relative theme path, starts a CORS-restricted Vite watch server, and can register the development release through the local Bopli Docker stack. Production registration is always performed by Bopli's bopli:theme:install command against the uploaded HTTPS theme.json URL.
Themes may import relative files, vue, and @bopli/theme-sdk. The exact Shiki core, JavaScript engine, GitHub Dark theme, and language subpaths used by Dev Cosmo are also allowed; other package imports remain rejected. Application aliases, Inertia, Node built-ins, remote imports, and non-literal dynamic imports are rejected before Vite runs.
Every theme supplies .vue templates only under resources/js/templates/pages and resources/js/templates/entries, with at least one ordinary Page and one generic Entry template and exactly one default of each kind. Each template imports typed helpers from @bopli/theme-sdk/authoring; the CLI compiles its top-level declaration and field.* calls into protocol metadata and concrete generated props, then removes that authoring-only code before Vue builds the runtime. bopli-theme add page about scaffolds one complete Page template. Native Blog helpers follow the same paired-default rule. Companion .bopli.ts files, legacy blogs and posts roots, and inline <bopli> blocks are rejected. Entry projection fields cannot shadow Bopli metadata such as url, SEO, or adjacent-navigation keys. Page slot declarations are obsolete and rejected.
Themes select the dynamic collections needed by their design through the host-injected BopliContentClient or useBopliQuery(). The browser host currently provides a bounded same-origin transport; the interface and Vue server-prefetch integration allow a future SSR host to supply a server transport without rewriting theme components.
Themes may add a bounded resources/bopli/starter.json recipe. The CLI validates its Content Models, fields, routes, Entries, Pages, Blog setting, and template references before embedding it in the release descriptor. Bopli applies this recipe per Site during provisioning or an explicit Appearance import; globally installing a release never creates tenant content.