Skip to content

programmfabrik/fylr-build-plugin

Repository files navigation

fylr-build-plugin

The build driver for fylr plugins — the "make" that knows how a fylr plugin is put together. This README is also the reference for how a fylr plugin repository is structured and what a plugin can offer; the living example is fylr-plugin-example, which exercises every feature described here. Start a new plugin by copying its structure.

The tool is distributed as a versioned Go module, so a plugin repo carries no build machinery (no submodule, no vendored Makefile include):

go run github.com/programmfabrik/fylr-build-plugin@latest build

Plugin repos keep a thin Makefile shim for muscle memory (make build, make zip, …) that just calls the tool.

Versioning: plugins call the tool with @latest, which resolves the newest tagged release — a v1.0.1 reaches every plugin build automatically, no plugin repo is touched. main can hold unfinished work; cutting a release is pushing a vX.Y.Z tag. An incompatible change is released as a new major version, which per Go's semantic import versioning gets a new module path (github.com/programmfabrik/fylr-build-plugin/v2) — existing plugins keep resolving the v1 line untouched and migrate by changing the one go run line when they choose to.

The name follows the fylr convention: fylr-plugin-* is reserved for plugins, tooling is named fylr-<verb>-plugin — this one builds plugins. It also seals them: the former fylr-seal-plugin is merged in here, including the pluginseal package that fylr imports to open sealed plugins.

A well-structured fylr plugin

One repo per plugin, named fylr-plugin-<name> (custom data types: custom-data-type-<type>). fylr plugins are fylr-only: no easydb5 hybrid code, and easydb-library must never be used.

Start a new plugin with fylr-build-plugin init in a fresh repo directory — it scaffolds everything below (a buildable plugin with one example API extension, webfrontend and loca) and refuses to run inside an existing plugin. Existing files (a GitHub-created README.md, LICENSE, …) are kept and skipped.

fylr-plugin-example/
├── manifest.yml          the plugin: identity + every feature wiring, read by
│                         fylr — ships VERBATIM as build/<name>/manifest.yml
├── build.yml             the build: explicit delivery lists for fylr-build-plugin
├── Makefile              thin shim: make help/build/zip/seal/loca/check/clean
├── README.md             plugin docs; the zip ships a self-contained copy
├── LICENSE
├── .github/workflows/
│   └── release.yaml      release build, from templates/ (public or private)
├── .gitignore            build/, compiled JS, .DS_Store — generated files are
│                         NEVER committed, dev and CI compile from source
├── l10n/
│   └── example-loca.csv  localization, mastered in Google Sheets ("fylr
│                         localization" sheet, one tab per plugin), pulled
│                         with `make loca` — never edited by hand
├── server/               server-side programs (node / python3 / Go), wired in
│                         the manifest as extensions and callbacks
│   └── extension/hello/  a Go module: cross-compiled by the build, sources
│                         never ship
├── fas_config/           asset-server config: cookbooks + produce config
├── webfrontend/          frontend sources and assets (= base_url_prefix)
│   ├── *.coffee          compiled + concatenated into plugin.webfrontend.url
│   ├── *.scss            compiled to same-named .css
│   └── *.css/.html/img   delivered as-is (build.yml webfrontend.install)
└── build/                generated by `build`, gitignored:
    └── fylr_example/     the plugin as fylr loads it — manifest.yml,
                          build-info.json, compiled JS/CSS/binaries, copies

The folder build/<plugin.name>/ is the plugin in its installed form: fylr loads it directly from disk during development, and the release zip is exactly this tree (top-level folder = plugin.name, which fylr enforces on install — the name comes from the manifest, not the repo).

What a fylr plugin can offer

Every feature is declared in manifest.yml; the example's manifest exercises all of them. Server-side programs are wired the same way everywhere: fylr runs them via its exec services (node, python3, or exec for compiled binaries), passing the request on stdin and reading the response from stdout; %_exec.pluginDir% resolves to the installed plugin folder, %info.json% passes fylr config + call context, and %_exec.GOOS% / %_exec.GOARCH% select the right cross-compiled binary. A callback can run with an api token (plugin_user) to call back into the fylr API.

Manifest section Feature
plugin identity: name, version, displayname, plus the l10n CSV and the webfrontend entry points (url JS bundle, css, html)
base_url_prefix where the webfrontend files are served from (by convention the webfrontend/ folder)
extensions plugin API endpoints served under fylr's API, each backed by an exec program
callbacks hooks into fylr flows: db_pre_save (an ordered steps list with objecttype filters — validate or modify objects before save), transition_db_pre_save, export, export_transport
collection_upload hooks into collection uploads (api / hotfolder), with per-hook config parameters
base_config plugin settings that appear in fylr's base config UI and /api/config, typed parameters
system_rights plugin-defined system rights with typed parameters (bool, int, text, string-list*, mask-select, objecttype-select, eas-column-select, pool-select, preview-versions)
custom_types custom data types: index mapping (text, number, geo, …, incl. skipTerms), per-schema/mask config, and a batched update callback
fas_config asset production: a produce config and cookbooks merged into the fylr asset server
custom_events plugin-defined event types

Localization: the plugin.l10n CSV has a key column and one column per language (de-DE, en-US, …); extra sheet columns (fil, the R TODO checkboxes) are ignored by fylr's loader, so the Google Sheets export is committed as-is.

Next to the manifest, the build writes build-info.json (repository, rev, release, lastchanged, builddate) — fylr's plugin manager shows it, so an installed plugin is traceable to a commit and release.

Commands

Command Does
init scaffold a new plugin into the current (fresh) directory
build assemble build/<plugin.name>/ — loadable by fylr from disk
zip build + self-contained README + release zip
seal seal a plugin zip for the marketplace (public key only)
genkey generate a seal recipient keypair
info inspect a (sealed) plugin zip: name, sealed?, recipient
loca pull loca CSV(s) from their Google Sheets master
readme write a self-contained README (relative images inlined)
check validate the build tree against the manifest
clean remove the build folder

All commands run from the plugin repo root (where manifest.yml lives). External tools are checked per step with an actionable error: coffee is only required when the plugin has CoffeeScript, sass only for SCSS, go only for Go server extensions.

The tool reads no environment variables — every input is a flag. Where a release workflow has values in env (RELEASE_TAG, ZIP_NAME), the plugin's Makefile shim translates them into -release/-out flags.

What build does

The delivered files are listed explicitly in build.yml — nothing is derived from the manifest, since a derived list is easily incomplete. The safety net is check, which runs with every build and validates the assembled tree against every path the manifest references — a dangling reference is reported as a warning (it can be deliberate, e.g. the example plugin's missing-module test callbacks).

  • manifest.yml is copied verbatim to build/<name>/manifest.yml (no master/generated split; plugin.name in the manifest names the build folder and the zip's top-level dir, which fylr enforces on install).
  • server.install and webfrontend.install entries (files or dirs) are copied verbatim. .DS_Store, .gitignore, .git, node_modules never ship.
  • webfrontend.coffee1 files compile (CoffeeScript 1.x — the version the plugins are stuck on, hence the key — with -b) and concatenate — in list order — into plugin.webfrontend.url under base_url_prefix; webfrontend.scss files compile to same-named .css.
  • server.go.modules cross-compile for every configured arch (CGO off, trimmed, stripped) into their exe template — %GOOS%/%GOARCH% replaced, matching the manifest's %_exec.GOOS%/%_exec.GOARCH% references. Go sources do not ship.
  • build-info.json is written beside the manifest: repository, rev, release (from -release; the release workflow's tag reaches the tool via the Makefile), lastchanged, builddate.
  • check runs automatically: every manifest reference (%_exec.pluginDir%/... exec programs, plugin.l10n, plugin.webfrontend.*, fas_config files) should exist in the build tree; cross-compiled references are checked for every configured arch, commented-out manifest blocks do not count. Missing references are warnings, not errors.

Development loop

build produces an on-disk plugin. In the fylr server's fylr.yml:

plugin:
    paths+:
        - ../my-plugin/build

Point at build/, not the repo root — fylr walks the path for any manifest.yml, and the root manifest would collide with the built one.

build.yml

The explicit build description. Unknown keys are rejected. Every feature is exercised by fylr-plugin-example's build.yml.

server:
  install:               # server-side delivery, copied verbatim
    - server
    - fas_config
    - l10n
  go:                    # Go server extensions, cross-compiled
    archs:               # each entry sets Go's GOOS/GOARCH build env;
      - { GOOS: linux, GOARCH: amd64 }   # optional — default: every
      - { GOOS: darwin, GOARCH: arm64 }  # platform fylr runs on
    modules:
      - dir: server/extension/hello
        exe: server/extension/hello/hello-%GOOS%-%GOARCH%.exe

webfrontend:
  coffee1:               # CoffeeScript 1.x (the version plugins are stuck
    - webfrontend/MyClass.coffee        # on), compiled + concatenated in this
    - webfrontend/UsesMyClass.coffee    # order into plugin.webfrontend.url
  scss:                  # compiled to same-named .css
    - webfrontend/MyStyle.scss
  l10n_json:             # loca CSV -> per-culture JSONs the webfrontend
    - csv: l10n/my-loca.csv       # loads (easydb-library l10n2json format),
      out: l10n                   # written under base_url_prefix (default l10n)
  install:               # delivered as-is: plain css, html, images, fonts
    - webfrontend/MyPlugin.html
    - webfrontend/MyPlugin.css

loca:                    # Google Sheets masters for the loca command
  - csv: l10n/my-loca.csv
    sheet: <spreadsheet key>
    gid: "<tab gid>"

Loca

Plugin loca CSVs are mastered in Google Sheets (fylr's "fylr localization" sheet, one tab per plugin) and pulled with fylr-build-plugin loca — never edited locally. The raw export (with the fil ticket column and R TODO checkboxes) is committed as-is; fylr's CSV loader only reads the key column and xx-XX language columns.

Sealing

A sealed plugin is still a valid zip: the plugin folder with a plaintext manifest.yml, the whole plugin encrypted to fylr's public key beside it (fylr-sealed-plugin.enc, X25519 + XSalsa20-Poly1305 sealed box). Only a fylr holding the matching private key can open and run it.

fylr-build-plugin seal                     # builds the zip if needed, dev/CI key
fylr-build-plugin seal -pubkey <base64>    # production / marketplace-source key
fylr-build-plugin genkey                   # fresh keypair
fylr-build-plugin info my-plugin_sealed.zip

Sealing uses a public key only — no secrets in CI. With no -pubkey the dev/CI key is used: such a plugin opens only on a fylr built with -tags licensetest, which is what makes a public example (fylr-plugin-example-licensed) safe to publish. fylr opens sealed plugins via the pluginseal package — sealer and server share one source of truth and cannot drift:

plain, sealed, err := pluginseal.OpenPluginZip(artifact, keysThisBuildHolds...)

Release workflows

A release is cut by a manager on the GitHub release page; the workflow builds and publishes the artifact. Two templates live in templates/:

  • release-public.yaml — attach the zip to the release; fylr installs from releases/latest/download/<repo>.zip.
  • release-private.yaml — additionally publish to GitHub Pages under an unguessable per-repo UUID URL, since a private repo's release assets are not fetchable by fylr.

Either variant becomes a sealed release by switching make zip to make seal — no secrets needed.

readme

Writes a self-contained README: every relative image reference is inlined as a data: URI. This matters because most plugin repositories are private — once a README leaves the repo (into the plugin zip, then the marketplace) its relative asset paths are unreachable. zip bundles it automatically as build/<name>/README.md.

fylr-build-plugin readme [flags]
  --in               input markdown file (default "README.md")
  --out              output self-contained markdown file (default "build/README.md")
  --max-image-bytes  leave images larger than this many bytes as relative links (default 524288)

About

Build helpers for fylr plugins (go run, no submodule). readme: self-contained README with inlined images.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages