create-mk-cms
v0.1.0
Published
Scaffold a production-ready React CMS for a specific data provider (REST, GraphQL or Supabase).
Maintainers
Readme
create-mk-cms: the project generator
Generates a complete CMS for one data provider from the template/ folder next to this one. The result is not a copy of the template with switches: it contains only the selected provider's code, dependencies, scripts, env, docs, CI and tests, and passes every quality gate on its own.
pnpm create mk-cms my-cms --provider graphql
# or: pnpm dlx create-mk-cms my-cms --provider supabaseUsage
mk-cms <directory> [options]
-p, --provider <name> rest | graphql | supabase
--name <text> display name shown in the UI (default: derived from the directory)
--auth <bearer|cookie> auth strategy (rest and graphql only; default bearer)
--api-url <url|path> REST base URL (rest only, default /api)
--graphql-url <url|path> GraphQL endpoint (graphql only, default /graphql)
--supabase-url <url> Supabase project URL (supabase only)
--supabase-anon-key <k> Supabase anon key (supabase only; public by design)
--install / --no-install install dependencies with pnpm (default: install)
--git / --no-git run `git init` (default: init)
-y, --yes never prompt; everything must come from flags
--dry-run list the files that would be created, write nothingIn a terminal, anything you omit is prompted for (directory, provider, display name, auth strategy, endpoints, install, git). Without a TTY, or with --yes, <directory> and --provider are required.
Safety: it refuses to write into a non-empty directory (a directory containing only .git is fine), validates every value that ends up in generated files (no quotes, $, backticks or angle brackets in the display name; https for Supabase; http(s) or absolute paths for API URLs), rejects flags that do not apply to the chosen provider, and removes a directory it created if generation fails. Requires Node 24+.
What each provider gets
| | rest | graphql | supabase |
| --------------------------------------- | ---------------- | -------------------- | ----------------------------- |
| Provider code | providers/rest | providers/graphql | providers/supabase |
| Shared HTTP transport + session manager | yes | yes | no (the SDK owns the session) |
| Bearer / cookie auth config | yes | yes | no |
| GraphQL schema, documents, codegen | no | yes (pnpm codegen) | no |
| Reference SQL migration + RLS | no | no | yes (supabase/) |
| Verified by the repo's E2E suite | yes | yes | no (needs a live project) |
| Docker/CSP | API origin | API origin | adds *.supabase.co |
Every project keeps the full feature-first architecture: features with their own models, ports, routes and translations, shared lib, i18n (en/ne/ar with RTL), theme, RBAC, error handling, test utilities, Oxlint + Prettier, Docker, CI, and the architecture test that forbids UI from importing a provider. Adding another provider later is documented in adding-a-provider.md.
pnpm-lock.yaml is intentionally not shipped (a lockfile for all providers would not match a pruned package.json). The installer generates one; commit it. With --no-install run pnpm install yourself.
How it works
template/ is the single source of truth. scripts/snapshot-template.ts copies it into cli/bundled-template/ (what gets published; npm-hostile dotfiles are stored as _gitignore/_npmrc). At run time the CLI applies, per file:
- Removal of provider-owned paths (
src/manifest.ts), e.g. other providers' folders andsrc/data/httpfor SDK-managed providers. - Markers in source files (below).
- JSON edits for
package.json,tsconfig*.json,.oxlintrc.json(JSON has no comments): drops other providers' dependencies, scripts and include/ignore entries. - Substitutions: package name, display name (title, i18n
appName, env, Docker), auth strategy and endpoints. - Prettier on every edited file, so
pnpm format:checkpasses. - A generated
README.md.
Marker grammar
Markers are comments, so the template stays valid, lintable, testable code.
// #region provider:rest,graphql keep the block only for the listed providers
// #endregion (`!name` = all except name, `*` = all)
// #region template-only block that never reaches a generated project
foo(); // @provider:supabase keep this single line only for the listed providers
mode = "rest"; // @provider-value replace the provider name on the line with the target
[rest, graphql] // @provider-list replace the [..] list on the line with [target]
// @provider-value-next same as @provider-value, for the next lineComment styles //, # and <!-- --> are supported. In Markdown, fenced code blocks are not processed, and conditional table rows are not possible (use lists). Where two providers need different code, write keyed strategies (see e2e/fixtures/backend-protocol.ts for the same idea) so the file is valid both here and after stripping.
Keeping the template and the generator honest
You never edit a copy of the template; you edit the template, and the generator follows. Three layers stop drift:
src/validate.tsscans each generated tree: no leftover markers, no mention of other providers (or of a mock backend / Playwright, which never ship in a project), all Markdown links and backtickedsrc/…paths resolve, no foreign lockfiles.test/*.test.ts(pnpm test): the marker engine, generation for all three providers, file/dependency/script/tsconfig/env assertions, and the CLI's exit codes and safety checks.pnpm verify(CI runs it per provider): really generates each project, installs it, and runstypecheck,lint,format:check,test,build, GraphQL codegen stability, and, for REST and GraphQL, the repository's Playwright suite from../e2epointed at the generated project (E2E_APP_DIR).
pnpm test # fast (generation only, no install)
pnpm verify # all providers, full gates
pnpm verify --provider rest # one provider (add --skip-e2e, --keep)
pnpm build # snapshot + compile to dist
node src/index.ts ../tmp-app --provider rest --yes # run from source (Node 24)If a template edit breaks a provider, pnpm test (or pnpm verify) names the file. From the repository root the same commands are pnpm cli:test, pnpm cli:verify and pnpm cli:build.
Publishing
pnpm build # snapshots ../template into bundled-template and compiles dist
pnpm pack --pack-destination /tmp # inspect the tarball (dist + bundled-template)Publish this folder as create-mk-cms (it exposes the mk-cms executable). Users then run pnpm create mk-cms. Rebuild before every release so the snapshot matches the template.
Adding a provider to the generator
- Implement the provider in
template/(adding-a-provider.md) with// #region provider:<name>around its registration points (config/data-providers.ts,env.ts,create-repositories.ts, env files, Dockerfile, CI, docs). src/providers.ts: add the name and itsProviderInfo.src/manifest.ts: owned paths, dependencies, scripts.src/readme.ts: quick start for the new provider.- Run
pnpm testandpnpm verify --provider <name>.
Limitations
- Locales are not pruned per project (all three ship; enable/disable with
VITE_SUPPORTED_LOCALES). - Generated projects ship no mock backend and no browser tests: those live in
../e2e. Supabase has no E2E at all (it needs a seeded live project); unit and component tests run against in-memory repositories. - The generated project does not track future template changes; regenerate and diff to adopt them.
