@oktis-works/cms 0.1.14 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/assets.d.ts +94 -0
  2. package/dist/assets.d.ts.map +1 -0
  3. package/dist/assets.js +671 -0
  4. package/dist/assets.js.map +1 -0
  5. package/dist/bluegreen.d.ts +121 -0
  6. package/dist/bluegreen.d.ts.map +1 -0
  7. package/dist/bluegreen.js +393 -0
  8. package/dist/bluegreen.js.map +1 -0
  9. package/dist/commands.d.ts +6 -0
  10. package/dist/commands.d.ts.map +1 -1
  11. package/dist/commands.js +158 -61
  12. package/dist/commands.js.map +1 -1
  13. package/dist/config-wizard.d.ts +39 -0
  14. package/dist/config-wizard.d.ts.map +1 -0
  15. package/dist/config-wizard.js +308 -0
  16. package/dist/config-wizard.js.map +1 -0
  17. package/dist/deploy.d.ts +20 -0
  18. package/dist/deploy.d.ts.map +1 -0
  19. package/dist/deploy.js +304 -0
  20. package/dist/deploy.js.map +1 -0
  21. package/dist/docker.d.ts +148 -0
  22. package/dist/docker.d.ts.map +1 -0
  23. package/dist/docker.js +324 -0
  24. package/dist/docker.js.map +1 -0
  25. package/dist/docs-updater.d.ts +10 -0
  26. package/dist/docs-updater.d.ts.map +1 -0
  27. package/dist/docs-updater.js +45 -0
  28. package/dist/docs-updater.js.map +1 -0
  29. package/dist/doctor.d.ts +2 -0
  30. package/dist/doctor.d.ts.map +1 -1
  31. package/dist/doctor.js +51 -0
  32. package/dist/doctor.js.map +1 -1
  33. package/dist/env-file.d.ts +45 -0
  34. package/dist/env-file.d.ts.map +1 -0
  35. package/dist/env-file.js +200 -0
  36. package/dist/env-file.js.map +1 -0
  37. package/dist/env-schema.d.ts +28 -0
  38. package/dist/env-schema.d.ts.map +1 -0
  39. package/dist/env-schema.js +497 -0
  40. package/dist/env-schema.js.map +1 -0
  41. package/dist/guards.d.ts +40 -0
  42. package/dist/guards.d.ts.map +1 -0
  43. package/dist/guards.js +159 -0
  44. package/dist/guards.js.map +1 -0
  45. package/dist/history.d.ts +48 -0
  46. package/dist/history.d.ts.map +1 -0
  47. package/dist/history.js +104 -0
  48. package/dist/history.js.map +1 -0
  49. package/dist/parser.d.ts.map +1 -1
  50. package/dist/parser.js +12 -3
  51. package/dist/parser.js.map +1 -1
  52. package/dist/project-config.js +2 -2
  53. package/dist/prompt.d.ts +81 -0
  54. package/dist/prompt.d.ts.map +1 -0
  55. package/dist/prompt.js +379 -0
  56. package/dist/prompt.js.map +1 -0
  57. package/dist/redeploy.d.ts +109 -0
  58. package/dist/redeploy.d.ts.map +1 -0
  59. package/dist/redeploy.js +363 -0
  60. package/dist/redeploy.js.map +1 -0
  61. package/dist/rollback.d.ts +15 -0
  62. package/dist/rollback.d.ts.map +1 -0
  63. package/dist/rollback.js +170 -0
  64. package/dist/rollback.js.map +1 -0
  65. package/dist/scaffold-docs.d.ts +30 -6
  66. package/dist/scaffold-docs.d.ts.map +1 -1
  67. package/dist/scaffold-docs.js +728 -33
  68. package/dist/scaffold-docs.js.map +1 -1
  69. package/dist/scaffold.d.ts +17 -1
  70. package/dist/scaffold.d.ts.map +1 -1
  71. package/dist/scaffold.js +77 -14
  72. package/dist/scaffold.js.map +1 -1
  73. package/dist/theme-build.d.ts +35 -0
  74. package/dist/theme-build.d.ts.map +1 -0
  75. package/dist/theme-build.js +86 -0
  76. package/dist/theme-build.js.map +1 -0
  77. package/dist/update.d.ts +52 -0
  78. package/dist/update.d.ts.map +1 -0
  79. package/dist/update.js +273 -0
  80. package/dist/update.js.map +1 -0
  81. package/package.json +7 -7
@@ -1,8 +1,64 @@
1
1
  // @oktis-works/cms - Documentação gerada no scaffold do projeto
2
2
  // README.md (uso local/Docker + visão do sistema), PLUGIN.md e THEME.md
3
3
  // (guias de desenvolvimento de extensões). Só escreve se o arquivo não existir.
4
- /** README.md do projeto scaffoldado. */
5
- export function projectReadme(projectName) {
4
+ //
5
+ // O exemplo de manifest usa o CMS_VERSION real: é o MESMO valor que o
6
+ // `okcms plugin:create`/`theme:create` grava em `compatibility.okcms`, então
7
+ // o doc nunca fica desatualizado em relação ao scaffold.
8
+ //
9
+ // Cada documento existe em duas línguas (en/pt), mas o ARQUIVO gerado tem
10
+ // sempre o mesmo nome — a língua é escolhida no `okcms init` (prompt com TTY,
11
+ // `--lang en|pt` sem TTY) e não muda o path. No repositório, os guias derivados
12
+ // (docs/plugin-development*.md) existem nos dois nomes, com banner de idioma.
13
+ import { CMS_VERSION } from '@oktis-works/validation';
14
+ import { existsSync, readFileSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ /** Línguas suportadas (ordem = ordem do menu do init). */
17
+ export const DOCS_LANGS = ['en', 'pt'];
18
+ /**
19
+ * Default: inglês — é o que o Enter do prompt escolhe e o único valor em
20
+ * modo não-interativo (CI/scripts), onde o prompt nunca aparece.
21
+ */
22
+ export const DEFAULT_DOCS_LANG = 'en';
23
+ /**
24
+ * Normaliza o valor de `--lang`. Devolve `null` para valor desconhecido (ou
25
+ * vazio): o chamador decide entre erro (flag explícita) e default (flag ausente).
26
+ */
27
+ export function parseDocsLang(value) {
28
+ if (value === undefined || value === null)
29
+ return null;
30
+ const normalized = value.trim().toLowerCase();
31
+ if (normalized === '')
32
+ return null;
33
+ if (['en', 'en-us', 'en-gb', 'english', 'ingles', 'inglês'].includes(normalized)) {
34
+ return 'en';
35
+ }
36
+ if (['pt', 'pt-br', 'ptb', 'br', 'portuguese', 'portugues', 'português'].includes(normalized)) {
37
+ return 'pt';
38
+ }
39
+ return null;
40
+ }
41
+ /** Rótulo legível da língua (mensagens da CLI). */
42
+ export function docsLangLabel(lang) {
43
+ return lang === 'pt' ? 'português' : 'inglês';
44
+ }
45
+ /**
46
+ * Detecta a língua do projeto lendo o README.md existente.
47
+ * Usado pelo `okcms update`/`deploy` para atualizar docs na língua certa.
48
+ */
49
+ export function detectProjectDocsLang(cwd) {
50
+ const readmePath = join(cwd, 'README.md');
51
+ if (!existsSync(readmePath))
52
+ return DEFAULT_DOCS_LANG;
53
+ const content = readFileSync(readmePath, 'utf-8');
54
+ if (content.includes('O que é o OkCMS') || content.includes('## Produção'))
55
+ return 'pt';
56
+ if (content.includes('What is OkCMS') || content.includes('## Production'))
57
+ return 'en';
58
+ return DEFAULT_DOCS_LANG;
59
+ }
60
+ /** README.md do projeto scaffoldado — versão em português. */
61
+ function projectReadmePt(projectName) {
6
62
  return `# ${projectName}
7
63
 
8
64
  Projeto OkCMS criado com \`okcms init\`. Este guia explica o sistema e como
@@ -34,6 +90,11 @@ CMS API-first, multi-tenant e extensível. Quatro apps + uma CLI:
34
90
  | \`plugins/\`, \`themes/\` | Extensões do projeto |
35
91
  | \`migrations/\` | SQL de migrations (\`okcms db:migrate\`) |
36
92
  | \`docker-compose.yml\` | Infra local: PostgreSQL + Redis (com healthcheck e volume) |
93
+ | \`docker/Dockerfile\`, \`.dockerignore\` | Imagem única \`okcms/app\` — 4 entrypoints: api, admin, web, worker |
94
+ | \`docker-compose.infra.yml\` | Projeto \`okcms\`: rede, postgres (só 127.0.0.1), redis (só interna) e proxy |
95
+ | \`docker-compose.deploy.yml\` | As 8 services blue/green: api, web, admin, worker × blue, green |
96
+ | \`deploy/nginx/\` | Template do proxy + \`conf.d/00-upstreams.conf\` (reescrito no swap) |
97
+ | \`.deploy/\` | Estado do deploy: lane ativa, lane anterior e histórico (não versionado) |
37
98
 
38
99
  ## Começando (desenvolvimento local)
39
100
 
@@ -71,8 +132,8 @@ npx okcms start
71
132
  npx okcms doctor
72
133
  \`\`\`
73
134
 
74
- Ports padrão (ajustáveis no \`.env\`): **API 3000** (\`PORT\`) · **Admin 3001**
75
- (\`ADMIN_PORT\`) · **Web 3002** (\`WEB_PORT\`).
135
+ Ports padrão (ajustáveis no \`.env\`): **API 3000** (\`PORT\`) · **Web 3001**
136
+ (\`WEB_PORT\`) · **Admin 3011** (\`ADMIN_PORT\`).
76
137
 
77
138
  ## Começando (Docker)
78
139
 
@@ -86,6 +147,16 @@ O \`.env\` gerado já aponta para \`localhost\`, que é onde o compose expõe as
86
147
  portas do Postgres (5432) e do Redis (6379). Os apps em si rodam via
87
148
  \`okcms start\` — o compose é só a infraestrutura.
88
149
 
150
+ Para subir a aplicação **inteira em Docker** (api, admin, web e worker em
151
+ containers, atrás de um nginx), use o deploy blue/green:
152
+
153
+ \`\`\`bash
154
+ npx okcms update --mode deploy
155
+ \`\`\`
156
+
157
+ Esse é o caminho de produção — ver o guia completo em
158
+ [docs/docker-deploy.md](https://github.com/oktis-works/cms/blob/main/docs/docker-deploy.md).
159
+
89
160
  ## Produção
90
161
 
91
162
  Em produção **não use \`okcms start\` como supervisor** — ele é conveniência
@@ -104,8 +175,8 @@ startup\` na inicialização do servidor e \`pm2 reload\` individual
104
175
  module.exports = {
105
176
  apps: [
106
177
  { name: 'okcms-api', script: 'bunx', args: '@oktis-works/api', env: { NODE_ENV: 'production', PORT: 3000 } },
107
- { name: 'okcms-admin', script: 'bunx', args: '@oktis-works/admin', env: { NODE_ENV: 'production', PORT: 3001 } },
108
- { name: 'okcms-web', script: 'bunx', args: '@oktis-works/web', env: { NODE_ENV: 'production', PORT: 3002 } },
178
+ { name: 'okcms-admin', script: 'bunx', args: '@oktis-works/admin', env: { NODE_ENV: 'production', PORT: 3011 } },
179
+ { name: 'okcms-web', script: 'bunx', args: '@oktis-works/web', env: { NODE_ENV: 'production', WEB_PORT: 3001 } },
109
180
  { name: 'okcms-worker', script: 'bunx', args: '@oktis-works/worker', env: { NODE_ENV: 'production', WORKER_MODE: 'pm2' } },
110
181
  ],
111
182
  };
@@ -124,26 +195,41 @@ pm2 unmonitor okcms-api && pm2 delete okcms-api # remover
124
195
  - Como cada app é um executável independente, o \`okcms start\` não é
125
196
  necessário em produção — o pm2 supervisoria cada um diretamente.
126
197
 
127
- ### Caminho 2: Docker imutável
198
+ ### Caminho 2: Docker blue/green (recomendado)
128
199
 
129
- Deploy por **imagem**: builda → sobe → troca; nunca se atualiza pacote dentro
130
- de um container rodando. O template de imagem da API é multi-stage (bun,
131
- \`NODE_ENV=production\`, expõe 3000) e fica em \`apps/api/Dockerfile\` no
132
- repositório do OkCMS — o build precisa do contexto do monorepo:
200
+ O projeto já nasce com tudo que o deploy precisa (o \`okcms init\` escreve
201
+ \`docker/\`, os dois composes e o \`deploy/\` do proxy). Um deploy é um único
202
+ comando a partir do host:
133
203
 
134
204
  \`\`\`bash
135
- git clone https://github.com/oktis-works/cms.git
136
- cd cms
137
- docker build -f apps/api/Dockerfile -t okcms-api:v1 .
138
- docker run -d --env-file /caminho/para/.env -p 3000:3000 okcms-api:v1
205
+ npx okcms update --mode deploy # sem TTY; com TTY abre o wizard
139
206
  \`\`\`
140
207
 
141
- - **Atualização** = build da imagem nova + subir + desligar a antiga;
142
- **rollback** = voltar à imagem anterior (tague cada build).
208
+ A aplicação roda em **duas lanes** (\`okcms-blue\` e \`okcms-green\`) atrás de
209
+ um nginx público. Cada deploy:
210
+
211
+ 1. builda a imagem nova numa lane que **não recebe tráfego**;
212
+ 2. roda \`okcms db:migrate\` **no host** (falhou = nada mudou);
213
+ 3. sobe o edge da lane nova e espera o healthcheck de cada contêiner;
214
+ 4. troca o proxy reescrevendo \`00-upstreams.conf\` + \`nginx -s reload\`
215
+ (conexões existentes vivas);
216
+ 5. **drena** o worker antigo (SIGTERM + \`stop_grace_period\`), sobe o novo;
217
+ 6. derruba a lane antiga — **sem \`-v\`**: o volume de mídia é compartilhado.
218
+
219
+ **Rollback** = rodar o mesmo comando de novo, ou apontar o upstream de volta
220
+ para a outra lane. Detalhes, portas e segurança em
221
+ [docs/docker-deploy.md](https://github.com/oktis-works/cms/blob/main/docs/docker-deploy.md).
222
+
223
+ Alternativa manual (sem a CLI): \`docker build -f docker/Dockerfile\` com o
224
+ contexto do monorepo e \`docker run --env-file .env\` — funcional, mas entrega
225
+ os 6 passos acima na mão. Prefira o comando da CLI.
226
+
143
227
  - Postgres/Redis de produção: serviços gerenciados ou containers próprios com
144
- volume e backup — não use o \`docker-compose.yml\` de dev.
145
- - **Migração como passo de deploy**: \`okcms db:migrate\` antes de subir a
146
- versão nova da aplicação.
228
+ volume e backup — não use o \`docker-compose.yml\` de dev. O
229
+ \`docker-compose.infra.yml\` do projeto é o mínimo aceitável (postgres só
230
+ em \`127.0.0.1\`, redis sem porta nenhuma).
231
+ - **Migração como passo de deploy**: já é feito pelo \`--mode deploy\`, entre
232
+ o build e o primeiro tráfego.
147
233
 
148
234
  ### Checklist (ambos os caminhos)
149
235
 
@@ -165,19 +251,29 @@ docker run -d --env-file /caminho/para/.env -p 3000:3000 okcms-api:v1
165
251
 
166
252
  | Comando | O que faz |
167
253
  |---|---|
168
- | \`okcms init <dir>\` | Cria o projeto (arquivos + docs) e instala as dependências |
169
- | \`okcms start [--api\|--admin\|--web\|--worker\|--all]\` | Sobe os apps (default: todos) |
254
+ | \`okcms init <dir>\` | Cria o projeto (arquivos + docs em \`--lang en\\|pt\`) e instala as dependências |
255
+ | \`okcms start [--api\\|--admin\\|--web\\|--worker\\|--all]\` | Sobe os apps (default: todos) |
170
256
  | \`okcms stop\` / \`okcms status\` | Para / mostra os processos (pidfiles) |
171
- | \`okcms doctor\` | Diagnóstico: node, \`.env\`, database (\`DB_*\` ou \`DATABASE_URL\`), config |
257
+ | \`okcms doctor\` | Diagnóstico: node, \`.env\`, database (\`DB_*\` ou \`DATABASE_URL\`), config, docker, compose, lane e proxy |
258
+ | \`okcms config\` | Wizard de configuração do \`.env\` (Enter mantém, Ctrl+C descarta) · \`--list\` · \`--set K=V\` · \`--section <s>\` |
172
259
  | \`okcms db:migrate\` · \`db:rollback\` · \`db:status\` | Migrations da pasta \`migrations/\` (\`--tenant\` opcional) |
173
260
  | \`okcms db:backup\` · \`db:restore -f <arquivo>\` | Backup/restore do Postgres |
174
261
  | \`okcms plugin:create -n <nome>\` | Scaffold de plugin — guia em [PLUGIN.md](./PLUGIN.md) |
175
262
  | \`okcms plugin:install -n <caminho>\` | Instala plugin de um caminho local + registra |
176
263
  | \`okcms plugin:list\` · \`plugin:search\` · \`plugin:manage\` | Lista, busca (npm) e habilita/desabilita plugins |
177
- | \`okcms theme:create -n <nome> --style css\|scss\|tailwind\` | Scaffold de tema — guia em [THEME.md](./THEME.md) |
264
+ | \`okcms theme:create -n <nome> --style css\\|scss\\|tailwind\` | Scaffold de tema — guia em [THEME.md](./THEME.md) |
178
265
  | \`okcms theme:install\` · \`theme:list\` · \`theme:search\` · \`theme:manage\` | Gestão de temas (\`--set-active\` define o ativo) |
179
266
  | \`okcms theme:build -n <nome>\` | Compila SCSS/Tailwind → \`dist/theme.css\` isolado |
180
- | \`okcms update [-i]\` | Lista/atualiza os pacotes \`@oktis-works/*\` do projeto |
267
+ | \`okcms update\` | Wizard: **só baixar pacotes** ou **deploy Docker blue/green** (\`--mode download\\|deploy\`) |
268
+ | \`okcms update -i\` | Só baixa/aplica os pacotes \`@oktis-works/*\` (comportamento clássico, não-TTY) |
269
+ | \`okcms update --mode deploy\` | Deploy blue/green completo: build → migrations → healthcheck → swap → worker |
270
+ | \`okcms redeploy\` | Depois de instalar plugin/tema: SQL do plugin → \`migrations/\`, \`dist/theme.css\` do tema e deploy blue/green (\`--dry-run\` só mostra o plano) |
271
+ | \`okcms seed\` | Semeia dados iniciais (roles + settings) — idempotente |
272
+ | \`okcms user:create\` | Cria usuário e atribui um papel dentro do tenant |
273
+ | \`okcms system:status\` | Status do sistema (ambiente + saúde do banco) |
274
+ | \`okcms media:migrate\` | Migra arquivos de mídia entre drivers (\`local\` ↔ \`s3\`/\`r2\`/\`minio\`) |
275
+ | \`okcms prerender\` | Gera HTML estático das páginas publicadas (best-effort) |
276
+ | \`okcms build\` | Build dos apps em monorepo (\`--apps api,admin,web\`) |
181
277
 
182
278
  ## Banco de dados
183
279
 
@@ -191,8 +287,37 @@ docker run -d --env-file /caminho/para/.env -p 3000:3000 okcms-api:v1
191
287
  ## Atualizando
192
288
 
193
289
  \`\`\`bash
194
- npx okcms update # lista pacotes @oktis-works/* com versão nova
195
- npx okcms update -i # aplica a atualização
290
+ npx okcms update # com TTY: escolhe entre baixar pacotes ou deploy
291
+ npx okcms update -i # só baixa/aplica os pacotes (igual sempre foi)
292
+ npx okcms update --mode deploy # deploy blue/green completo
293
+ npx okcms update --mode deploy --yes # sem prompts (CI/CD)
294
+ \`\`\`
295
+
296
+ Em **não-TTY** (script/CI) o default continua sendo \`download\` — um \`-i\`
297
+ em cron nunca passa a fazer deploy por acidente. Só \`--mode deploy\` aciona
298
+ o Docker.
299
+
300
+ Flags de deploy: \`--no-cache\` (rebuild limpo) · \`--remove-orphans\`
301
+ (default) · \`--keep-orphans\` · \`--yes\` (sem prompts).
302
+
303
+ ### Aplicando um plugin ou tema novo
304
+
305
+ Instalar extensão não muda o que está no ar: \`plugins/\` e \`themes/\` entram
306
+ na imagem no build, e \`themes/<n>/dist/theme.css\` é compilado **no host**.
307
+ Depois de \`plugin:install\` / \`theme:install\`, rode:
308
+
309
+ \`\`\`bash
310
+ npx okcms redeploy
311
+ \`\`\`
312
+
313
+ Ele copia \`plugins/<n>/migrations/*.sql\` para \`migrations/\` (idempotente),
314
+ compila o estilo de cada tema com entrada e refaz o deploy blue/green — sem
315
+ \`bun add\`. Use \`--dry-run\` para ver o plano, \`--plugin <nome>\` /
316
+ \`--theme <nome>\` para restringir o preparo, \`--skip-migrations\` e
317
+ \`--skip-theme-build\` para pular etapas.
318
+
319
+ \`\`\`bash
320
+ npx okcms doctor # confere docker, compose v2, lane e proxy antes/depois
196
321
  \`\`\`
197
322
 
198
323
  ## Próximos passos
@@ -201,8 +326,277 @@ npx okcms update -i # aplica a atualização
201
326
  - Criar/ativar um tema → [THEME.md](./THEME.md)
202
327
  `;
203
328
  }
204
- /** PLUGIN.md — guia de desenvolvimento de plugins. */
205
- export const PLUGIN_DOC = `# Desenvolvendo um Plugin
329
+ /** README.md do projeto scaffoldado — versão em inglês. */
330
+ function projectReadmeEn(projectName) {
331
+ return `# ${projectName}
332
+
333
+ OkCMS project created with \`okcms init\`. This guide explains the system and
334
+ how to run it in local development and with Docker.
335
+
336
+ ## What is OkCMS
337
+
338
+ An API-first, multi-tenant, extensible CMS. Four apps + one CLI:
339
+
340
+ | App | Package | Role |
341
+ |---|---|---|
342
+ | **API** | \`@oktis-works/api\` | REST server (Hono) at \`/api/v1\`: content, auth, media, plugins |
343
+ | **Admin** | \`@oktis-works/admin\` | Admin panel (Astro + SolidJS) |
344
+ | **Web** | \`@oktis-works/web\` | Public site (Astro) |
345
+ | **Worker** | \`@oktis-works/worker\` | Background queues/jobs (bullmq): publishing, media, webhooks, cache |
346
+ | **CLI** | \`@oktis-works/cms\` | \`okcms\` — init, start, database, extensions, update |
347
+
348
+ - **Database:** PostgreSQL (Row-Level Security, multi-tenant)
349
+ - **Cache and queues:** Redis
350
+ - **Extensions:** [plugins](./PLUGIN.md) (hooks/filters at runtime) and [themes](./THEME.md) (templates + isolated styles)
351
+
352
+ ## Project structure
353
+
354
+ | Path | What it is |
355
+ |---|---|
356
+ | \`okcms.config.json\` | Structure: name, ports, storage, dirs, active theme. **No database credentials.** |
357
+ | \`.env\` | **Single source of connection**: database (\`DB_*\` or \`DATABASE_URL\`), Redis, JWT, ports |
358
+ | \`package.json\` | CMS apps (\`api\`, \`admin\`, \`web\`, \`worker\`) + CLI in devDependencies + shortcut scripts (start, migrate, doctor…) |
359
+ | \`plugins/\`, \`themes/\` | Project extensions |
360
+ | \`migrations/\` | SQL migrations (\`okcms db:migrate\`) |
361
+ | \`docker-compose.yml\` | Local infrastructure: PostgreSQL + Redis (healthcheck + volume) |
362
+ | \`docker/Dockerfile\`, \`.dockerignore\` | Single \`okcms/app\` image — 4 entrypoints: api, admin, web, worker |
363
+ | \`docker-compose.infra.yml\` | \`okcms\` project: network, postgres (127.0.0.1 only), redis (internal only) and proxy |
364
+ | \`docker-compose.deploy.yml\` | The 8 blue/green services: api, web, admin, worker × blue, green |
365
+ | \`deploy/nginx/\` | Proxy template + \`conf.d/00-upstreams.conf\` (rewritten on swap) |
366
+ | \`.deploy/\` | Deploy state: active lane, previous lane and history (not versioned) |
367
+
368
+ ## Getting started (local development)
369
+
370
+ Prerequisites: **Node 20+** or **Bun 1.3+** · **PostgreSQL 16+** (required) ·
371
+ **Redis** (optional — only for the worker queues).
372
+
373
+ > **Project CLI:** the commands below run with \`npx okcms\` (with Bun,
374
+ > \`bunx okcms\`) — the CLI is installed in devDependencies, **not** on PATH.
375
+ > Shortcuts already in \`package.json\`: \`bun run start\` · \`bun run migrate\`
376
+ > · \`bun run doctor\`. To use bare \`okcms\` without a prefix anywhere,
377
+ > install the CLI globally: \`bun add -g @oktis-works/cms\`.
378
+
379
+ \`\`\`bash
380
+ # 1. Database in .env — pick ONE format:
381
+ # (a) separate variables (default)
382
+ DB_HOST=localhost
383
+ DB_PORT=5432
384
+ DB_NAME=okcms
385
+ DB_USER=postgres
386
+ # (b) single URL — takes precedence over the DB_* above
387
+ # DATABASE_URL=postgresql://postgres:password@localhost:5432/okcms
388
+
389
+ # 2. Start the infrastructure (Postgres + Redis) — with Docker:
390
+ docker compose up -d
391
+ # Without Docker: point DB_* to your own Postgres. Without Redis, use:
392
+ # REDIS_HOST=disabled (in-memory cache; the worker queues require Redis)
393
+
394
+ # 3. Migrations
395
+ npx okcms db:migrate
396
+
397
+ # 4. Start everything: api + admin + web + worker
398
+ npx okcms start
399
+
400
+ # 5. If something fails
401
+ npx okcms doctor
402
+ \`\`\`
403
+
404
+ Default ports (adjustable in \`.env\`): **API 3000** (\`PORT\`) · **Web 3001**
405
+ (\`WEB_PORT\`) · **Admin 3011** (\`ADMIN_PORT\`).
406
+
407
+ ## Getting started (Docker)
408
+
409
+ \`\`\`bash
410
+ docker compose up -d # infrastructure only: postgres:16 + redis:7 (healthcheck + volumes)
411
+ npx okcms db:migrate
412
+ npx okcms start # apps run as normal processes, outside the compose
413
+ \`\`\`
414
+
415
+ The generated \`.env\` already points to \`localhost\`, where the compose publishes
416
+ the Postgres (5432) and Redis (6379) ports. The apps themselves run via
417
+ \`okcms start\` — the compose is only the infrastructure.
418
+
419
+ To run the **whole application in Docker** (api, admin, web and worker in
420
+ containers behind an nginx), use the blue/green deploy:
421
+
422
+ \`\`\`bash
423
+ npx okcms update --mode deploy
424
+ \`\`\`
425
+
426
+ That is the production path — see the complete guide at
427
+ [docs/docker-deploy.md](https://github.com/oktis-works/cms/blob/main/docs/docker-deploy.md).
428
+
429
+ ## Production
430
+
431
+ In production **do not use \`okcms start\` as a supervisor** — it is a dev
432
+ convenience: it does not restart children that die and mixes every app's logs
433
+ into a single stream. Pick one of the paths below.
434
+
435
+ ### Path 1: pm2 (host processes)
436
+
437
+ One pm2 **process per app** — restart on crash, log per process, \`pm2
438
+ startup\` on server boot and individual \`pm2 reload\`
439
+ (zero-downtime). Requires **Bun installed on the server** (apps start via
440
+ \`bunx\`).
441
+
442
+ \`\`\`js
443
+ // ecosystem.config.js
444
+ module.exports = {
445
+ apps: [
446
+ { name: 'okcms-api', script: 'bunx', args: '@oktis-works/api', env: { NODE_ENV: 'production', PORT: 3000 } },
447
+ { name: 'okcms-admin', script: 'bunx', args: '@oktis-works/admin', env: { NODE_ENV: 'production', PORT: 3011 } },
448
+ { name: 'okcms-web', script: 'bunx', args: '@oktis-works/web', env: { NODE_ENV: 'production', WEB_PORT: 3001 } },
449
+ { name: 'okcms-worker', script: 'bunx', args: '@oktis-works/worker', env: { NODE_ENV: 'production', WORKER_MODE: 'pm2' } },
450
+ ],
451
+ };
452
+ \`\`\`
453
+
454
+ \`\`\`bash
455
+ pm2 start ecosystem.config.js
456
+ pm2 save && pm2 startup # start on boot
457
+ pm2 reload okcms-api # update with no downtime (per app)
458
+ pm2 logs okcms-api # log per process
459
+ pm2 unmonitor okcms-api && pm2 delete okcms-api # remove
460
+ \`\`\`
461
+
462
+ - The worker has a **native pm2 mode**: \`WORKER_MODE=pm2\` (cluster) combined with
463
+ \`WORKER_COUNT\` and \`WORKER_CONCURRENCY\`.
464
+ - Since each app is an independent executable, \`okcms start\` is not
465
+ needed in production — pm2 supervises each one directly.
466
+
467
+ ### Path 2: Docker blue/green (recommended)
468
+
469
+ The project ships with everything a deploy needs ( \`okcms init\` writes
470
+ \`docker/\`, both compose files and the proxy's \`deploy/\`). A deploy is a single
471
+ command from the host:
472
+
473
+ \`\`\`bash
474
+ npx okcms update --mode deploy # no TTY; with a TTY it opens the wizard
475
+ \`\`\`
476
+
477
+ The application runs in **two lanes** (\`okcms-blue\` and \`okcms-green\`) behind a
478
+ public nginx. Each deploy:
479
+
480
+ 1. builds the new image in a lane that **receives no traffic**;
481
+ 2. runs \`okcms db:migrate\` **on the host** (if it fails, nothing changed);
482
+ 3. starts the new lane's edge and waits for every container's healthcheck;
483
+ 4. swaps the proxy by rewriting \`00-upstreams.conf\` + \`nginx -s reload\`
484
+ (existing connections stay alive);
485
+ 5. **drains** the old worker (SIGTERM + \`stop_grace_period\`) and starts the new one;
486
+ 6. tears down the old lane — **without \`-v\`**: the media volume is shared.
487
+
488
+ **Rollback** = run the same command again, or point the upstream back to the
489
+ other lane. Details, ports and security in
490
+ [docs/docker-deploy.md](https://github.com/oktis-works/cms/blob/main/docs/docker-deploy.md).
491
+
492
+ Manual alternative (without the CLI): \`docker build -f docker/Dockerfile\` with the
493
+ monorepo context and \`docker run --env-file .env\` — functional, but it hands you
494
+ the 6 steps above to do by hand. Prefer the CLI command.
495
+
496
+ - Production Postgres/Redis: managed services or your own containers with a
497
+ volume and backups — do not use the dev \`docker-compose.yml\`. The project's
498
+ \`docker-compose.infra.yml\` is the acceptable minimum (postgres only on
499
+ \`127.0.0.1\`, redis with no published port).
500
+ - **Migration as a deploy step**: already handled by \`--mode deploy\`, between
501
+ the build and the first traffic.
502
+
503
+ ### Checklist (both paths)
504
+
505
+ - [ ] \`NODE_ENV=production\` and a strong, unique \`JWT_SECRET\`
506
+ - [ ] \`.env\` out of version control; credentials never in \`okcms.config.json\`
507
+ - [ ] TLS reverse proxy (nginx/caddy) in front of api/admin/web
508
+ - [ ] Pinned versions in the project \`package.json\` (no \`@latest\` in a deploy)
509
+ - [ ] \`okcms db:migrate\` in the deploy pipeline and scheduled \`okcms db:backup\`
510
+ - [ ] Real Redis in production (the worker queues do not work without Redis)
511
+ - [ ] \`okcms doctor\` green on the target environment
512
+ - [ ] Logs with a destination (pm2 logrotate, or container stdout collected)
513
+
514
+ ## CLI commands
515
+
516
+ > The names below are the command itself: in the project, prefix them with
517
+ > \`npx\` (e.g. \`npx okcms doctor\`) or use a \`package.json\` script (\`bun run
518
+ > doctor\`); without a prefix, \`okcms\` only works with the CLI installed
519
+ > globally (\`bun add -g @oktis-works/cms\`).
520
+
521
+ | Command | What it does |
522
+ |---|---|
523
+ | \`okcms init <dir>\` | Creates the project (files + docs in \`--lang en\\|pt\`) and installs dependencies |
524
+ | \`okcms start [--api\\|--admin\\|--web\\|--worker\\|--all]\` | Starts the apps (default: all) |
525
+ | \`okcms stop\` / \`okcms status\` | Stops / shows the processes (pidfiles) |
526
+ | \`okcms doctor\` | Diagnostics: node, \`.env\`, database (\`DB_*\` or \`DATABASE_URL\`), config, docker, compose, lane and proxy |
527
+ | \`okcms config\` | \`.env\` configuration wizard (Enter keeps, Ctrl+C discards) · \`--list\` · \`--set K=V\` · \`--section <s>\` |
528
+ | \`okcms db:migrate\` · \`db:rollback\` · \`db:status\` | Migrations from the \`migrations/\` folder (\`--tenant\` optional) |
529
+ | \`okcms db:backup\` · \`db:restore -f <file>\` | Postgres backup/restore |
530
+ | \`okcms plugin:create -n <name>\` | Plugin scaffold — guide in [PLUGIN.md](./PLUGIN.md) |
531
+ | \`okcms plugin:install -n <path>\` | Installs a plugin from a local path + registers it |
532
+ | \`okcms plugin:list\` · \`plugin:search\` · \`plugin:manage\` | Lists, searches (npm) and enables/disables plugins |
533
+ | \`okcms theme:create -n <name> --style css\\|scss\\|tailwind\` | Theme scaffold — guide in [THEME.md](./THEME.md) |
534
+ | \`okcms theme:install\` · \`theme:list\` · \`theme:search\` · \`theme:manage\` | Theme management (\`--set-active\` sets the active one) |
535
+ | \`okcms theme:build -n <name>\` | Compiles SCSS/Tailwind → isolated \`dist/theme.css\` |
536
+ | \`okcms update\` | Wizard: **download packages only** or **Docker blue/green deploy** (\`--mode download\\|deploy\`) |
537
+ | \`okcms update -i\` | Only downloads/applies the \`@oktis-works/*\` packages (classic non-TTY behaviour) |
538
+ | \`okcms update --mode deploy\` | Full blue/green deploy: build → migrations → healthcheck → swap → worker |
539
+ | \`okcms redeploy\` | After installing a plugin/theme: plugin SQL → \`migrations/\`, theme \`dist/theme.css\` and blue/green deploy (\`--dry-run\` only shows the plan) |
540
+ | \`okcms seed\` | Seeds initial data (roles + settings) — idempotent |
541
+ | \`okcms user:create\` | Creates a user and assigns a role inside the tenant |
542
+ | \`okcms system:status\` | System status (environment + database health) |
543
+ | \`okcms media:migrate\` | Migrates media files between drivers (\`local\` ↔ \`s3\`/\`r2\`/\`minio\`) |
544
+ | \`okcms prerender\` | Generates static HTML for published pages (best-effort) |
545
+ | \`okcms build\` | Builds the monorepo apps (\`--apps api,admin,web\`) |
546
+
547
+ ## Database
548
+
549
+ - **Connection** (in \`.env\`, pick one format): \`DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD\`
550
+ **or** \`DATABASE_URL\` (takes precedence). \`okcms doctor\` validates both.
551
+ - **Migrations**: SQL files in \`migrations/\`; \`okcms db:migrate\` applies,
552
+ \`db:rollback\` reverts, \`db:status\` shows the state. \`--tenant\` is selective
553
+ (default \`default\`) — the database is multi-tenant via Row-Level Security.
554
+ - **Backup**: \`okcms db:backup\` writes a timestamped dump; \`db:restore -f <file>\` restores it.
555
+
556
+ ## Updating
557
+
558
+ \`\`\`bash
559
+ npx okcms update # with TTY: choose between downloading packages or deploying
560
+ npx okcms update -i # only downloads/applies the packages (as always)
561
+ npx okcms update --mode deploy # full blue/green deploy
562
+ npx okcms update --mode deploy --yes # no prompts (CI/CD)
563
+ \`\`\`
564
+
565
+ In **non-TTY** (script/CI) the default is still \`download\` — an \`-i\` in a
566
+ cron job never starts deploying by accident. Only \`--mode deploy\` engages
567
+ the Docker.
568
+
569
+ Deploy flags: \`--no-cache\` (clean rebuild) · \`--remove-orphans\`
570
+ (default) · \`--keep-orphans\` · \`--yes\` (no prompts).
571
+
572
+ ### Applying a new plugin or theme
573
+
574
+ Installing an extension does not change what is running: \`plugins/\` and \`themes/\`
575
+ go into the image at build time, and \`themes/<n>/dist/theme.css\` is compiled
576
+ **on the host**. After \`plugin:install\` / \`theme:install\`, run:
577
+
578
+ \`\`\`bash
579
+ npx okcms redeploy
580
+ \`\`\`
581
+
582
+ It copies \`plugins/<n>/migrations/*.sql\` into \`migrations/\` (idempotent),
583
+ compiles the style of every theme with an entry and redoes the blue/green
584
+ deploy — without \`bun add\`. Use \`--dry-run\` to see the plan,
585
+ \`--plugin <name>\` / \`--theme <name>\` to narrow the preparation, and
586
+ \`--skip-migrations\` / \`--skip-theme-build\` to skip steps.
587
+
588
+ \`\`\`bash
589
+ npx okcms doctor # checks docker, compose v2, lane and proxy before/after
590
+ \`\`\`
591
+
592
+ ## Next steps
593
+
594
+ - Extend the CMS with plugins → [PLUGIN.md](./PLUGIN.md)
595
+ - Create/activate a theme → [THEME.md](./THEME.md)
596
+ `;
597
+ }
598
+ /** PLUGIN.md — guia de desenvolvimento de plugins (português). */
599
+ const PLUGIN_DOC_PT = `# Desenvolvendo um Plugin
206
600
 
207
601
  Plugin é a extensão que se registra no runtime do CMS e participa do sistema
208
602
  de **hooks e filters** (transformar dados, reagir a eventos de conteúdo etc.).
@@ -237,7 +631,7 @@ plugins/meu-plugin/
237
631
  "main": "index.js",
238
632
  "scope": "tenant",
239
633
  "permissions": [],
240
- "compatibility": { "okcms": "^0.1.x" }
634
+ "compatibility": { "okcms": "^${CMS_VERSION}" }
241
635
  }
242
636
  \`\`\`
243
637
 
@@ -263,6 +657,36 @@ O \`register(registry)\` é chamado quando o plugin é carregado. Use o registro
263
657
  para expor filters (transformações) — veja o que existe em
264
658
  \`GET /api/v1/hooks/catalog\` para os nomes e contratos disponíveis.
265
659
 
660
+ ## Migrations do banco
661
+
662
+ Se o plugin cria ou altera tabelas, o SQL mora **dentro do plugin**:
663
+
664
+ \`\`\`bash
665
+ plugins/meu-plugin/
666
+ └── migrations/
667
+ ├── V001__meu_plugin__tabelas.sql
668
+ └── V002__meu_plugin__colunas.sql
669
+ \`\`\`
670
+
671
+ O nome segue o padrão do runner — \`V<numero>__<owner>__<nome>.sql\` — e o
672
+ \`owner\` (aqui \`meu_plugin\`) é o que marca a migration como do plugin: o
673
+ rastreamento é por \`owner:version\`, então cada plugin tem a sua própria
674
+ linha de versão.
675
+
676
+ O SQL **não** é aplicado direto do diretório do plugin. O \`okcms redeploy\`
677
+ copia \`plugins/<n>/migrations/*.sql\` para \`migrations/\` (o único diretório
678
+ que o runner lê) e o \`db:migrate\` do deploy aplica — assim o schema do plugin
679
+ fica versionado e revisável no repositório do projeto.
680
+
681
+ Duas regras que o redeploy impõe:
682
+
683
+ - arquivo fora do padrão \`V###__owner__nome.sql\` **nunca** é copiado (e um
684
+ já existente em \`migrations/\` quebraria todo \`db:migrate\` — o comando
685
+ falha antes de tocar no Docker e diz qual arquivo renomear);
686
+ - uma migration já aplicada é imutável: se o plugin novo trouxer outro
687
+ conteúdo com o mesmo nome, o redeploy **não** sobrescreve o arquivo que já
688
+ está em \`migrations/\`, só avisa. Publique uma \`V00n+1\`.
689
+
266
690
  ## Fluxo de desenvolvimento
267
691
 
268
692
  \`\`\`bash
@@ -283,6 +707,10 @@ npx okcms plugin:manage -n meu-plugin --uninstall # remove arquivos + registr
283
707
 
284
708
  # 5. Busca no npm (pacotes com a keyword okcms-plugin)
285
709
  npx okcms plugin:search -q galeria
710
+
711
+ # 6. Publicou/atualizou? Stage do SQL + build dos temas + deploy blue/green
712
+ npx okcms redeploy --dry-run # só mostra o plano
713
+ npx okcms redeploy
286
714
  \`\`\`
287
715
 
288
716
  ## Publicando
@@ -291,8 +719,8 @@ Empacote o diretório do plugin como pacote npm com a keyword
291
719
  \`okcms-plugin\` (é o que o \`okcms plugin:search\` consulta) e mantenha
292
720
  \`compatibility.okcms\` atualizado para a linha do CMS que você suporta.
293
721
  `;
294
- /** THEME.md — guia de desenvolvimento de temas. */
295
- export const THEME_DOC = `# Desenvolvendo um Tema
722
+ /** THEME.md — guia de desenvolvimento de temas (português). */
723
+ const THEME_DOC_PT = `# Desenvolvendo um Tema
296
724
 
297
725
  Tema é o visual do site público: **templates** (hierarquia de páginas) +
298
726
  **estilos**, com isolamento por \`[data-theme]\` para que um tema nunca vaze
@@ -326,7 +754,7 @@ themes/meu-tema/
326
754
  "version": "0.1.0",
327
755
  "description": "Tema meu-tema para OkCMS",
328
756
  "type": "theme",
329
- "compatibility": { "okcms": "^0.1.x" }
757
+ "compatibility": { "okcms": "^${CMS_VERSION}" }
330
758
  }
331
759
  \`\`\`
332
760
 
@@ -365,6 +793,11 @@ O build compila para \`dist/theme.css\` **com isolamento
365
793
  \`[data-theme="meu-tema"]\`** (sem vazar estilos entre temas; o Tailwind já
366
794
  sai com \`preflight: false\`).
367
795
 
796
+ > \`dist/theme.css\` é lido **pronto** pelo container — nada compila SCSS ou
797
+ > Tailwind dentro da imagem. O arquivo existe no host e só chega em
798
+ > produção quando a imagem é reconstruída: \`okcms theme:build\` seguido de
799
+ > \`okcms redeploy\`.
800
+
368
801
  ## Fluxo de desenvolvimento
369
802
 
370
803
  \`\`\`bash
@@ -389,6 +822,10 @@ npx okcms theme:search -q blog # busca no npm (keyword okcms
389
822
 
390
823
  # Se criou FORA do projeto:
391
824
  npx okcms theme:install --name ../meu-tema-fonte
825
+
826
+ # 6. Depois de mudar templates/estilos: recompila e refaz o deploy
827
+ npx okcms theme:build --name meu-tema # (o redeploy já compila sozinho)
828
+ npx okcms redeploy
392
829
  \`\`\`
393
830
 
394
831
  ## Publicando
@@ -398,4 +835,262 @@ Publique como pacote npm com a keyword \`okcms-theme\` (alvo do
398
835
  do CMS. O \`theme:build\` deve ser executado no seu processo de release para
399
836
  que \`dist/theme.css\` vá junto no pacote.
400
837
  `;
838
+ /** PLUGIN.md — plugin development guide (English). */
839
+ const PLUGIN_DOC_EN = `# Developing a Plugin
840
+
841
+ A plugin is the extension that registers with the CMS runtime and takes part
842
+ in the **hooks and filters** system (transform data, react to content events,
843
+ etc.). The catalogue of available hooks is served by the API at
844
+ \`GET /api/v1/hooks/catalog\`.
845
+
846
+ > The commands use \`npx\` with the project's local CLI (no global
847
+ > install). With the global CLI (\`bun add -g @oktis-works/cms\`), run
848
+ > \`okcms ...\` directly.
849
+
850
+ ## Generated structure
851
+
852
+ \`\`\`bash
853
+ npx okcms plugin:create --name my-plugin
854
+ \`\`\`
855
+
856
+ \`\`\`
857
+ plugins/my-plugin/
858
+ ├── manifest.json # identity + permissions + compatibility
859
+ ├── index.js # entrypoint: module.exports.register(registry)
860
+ └── README.md
861
+ \`\`\`
862
+
863
+ ### manifest.json
864
+
865
+ \`\`\`json
866
+ {
867
+ "name": "my-plugin",
868
+ "version": "0.1.0",
869
+ "description": "my-plugin plugin for OkCMS",
870
+ "type": "plugin",
871
+ "main": "index.js",
872
+ "scope": "tenant",
873
+ "permissions": [],
874
+ "compatibility": { "okcms": "^${CMS_VERSION}" }
875
+ }
876
+ \`\`\`
877
+
878
+ | Field | Meaning |
879
+ |---|---|
880
+ | \`type\` | \`"plugin"\` (fixed — themes use \`"theme"\`) |
881
+ | \`main\` | Entry file with the registration |
882
+ | \`scope\` | Scope of action in the multi-tenant setup (e.g. \`tenant\`) |
883
+ | \`permissions\` | Permissions the plugin requests (empty = none) |
884
+ | \`compatibility.okcms\` | Required semver range of the CMS. **The scaffold fills it in with the current version automatically** and the value is validated at scaffold time and on every install — an incompatible manifest is rejected. |
885
+
886
+ ### index.js
887
+
888
+ \`\`\`js
889
+ 'use strict';
890
+
891
+ module.exports.register = function register(registry) {
892
+ registry.addFilter('theme:data:posts', (value) => value);
893
+ };
894
+ \`\`\`
895
+
896
+ \`register(registry)\` is called when the plugin is loaded. Use the registry
897
+ to expose filters (transformations) — see what exists at
898
+ \`GET /api/v1/hooks/catalog\` for the available names and contracts.
899
+
900
+ ## Database migrations
901
+
902
+ If the plugin creates or changes tables, the SQL lives **inside the plugin**:
903
+
904
+ \`\`\`bash
905
+ plugins/my-plugin/
906
+ └── migrations/
907
+ ├── V001__my_plugin__tables.sql
908
+ └── V002__my_plugin__columns.sql
909
+ \`\`\`
910
+
911
+ The name follows the runner's pattern — \`V<number>__<owner>__<name>.sql\` — and
912
+ the \`owner\` (here \`my_plugin\`) is what marks the migration as the plugin's:
913
+ tracking is per \`owner:version\`, so each plugin has its own
914
+ version row.
915
+
916
+ The SQL is **not** applied straight from the plugin directory. \`okcms redeploy\`
917
+ copies \`plugins/<n>/migrations/*.sql\` into \`migrations/\` (the only directory
918
+ the runner reads) and the deploy's \`db:migrate\` applies it — so the plugin's
919
+ schema stays versioned and reviewable in the project repository.
920
+
921
+ Two rules the redeploy enforces:
922
+
923
+ - a file outside the \`V###__owner__name.sql\` pattern is **never** copied (and one
924
+ already in \`migrations/\` would break every \`db:migrate\` — the command
925
+ fails before touching Docker and tells you which file to rename);
926
+ - an applied migration is immutable: if a new plugin ships different content
927
+ under the same name, the redeploy does **not** overwrite the file already
928
+ in \`migrations/\`, it only warns. Ship a \`V00n+1\`.
929
+
930
+ ## Development workflow
931
+
932
+ \`\`\`bash
933
+ # 1. Create it (in the project's ./plugins, or an external workspace with --dir)
934
+ npx okcms plugin:create --name my-plugin
935
+
936
+ # 2. Implement plugins/my-plugin/index.js
937
+
938
+ # 3. If you created it OUTSIDE the project, install it (validates compatibility and copies into plugins/)
939
+ npx okcms plugin:install --name ../my-plugin-source
940
+
941
+ # 4. Management
942
+ npx okcms plugin:list # installed + status
943
+ npx okcms plugin:manage -n my-plugin --info # manifest information
944
+ npx okcms plugin:manage -n my-plugin --disable # disables (keeps the files)
945
+ npx okcms plugin:manage -n my-plugin --enable # enables it again
946
+ npx okcms plugin:manage -n my-plugin --uninstall # removes files + registration
947
+
948
+ # 5. Search npm (packages with the okcms-plugin keyword)
949
+ npx okcms plugin:search -q gallery
950
+
951
+ # 6. Published/updated? Stage the SQL + build the themes + blue/green deploy
952
+ npx okcms redeploy --dry-run # only shows the plan
953
+ npx okcms redeploy
954
+ \`\`\`
955
+
956
+ ## Publishing
957
+
958
+ Package the plugin directory as an npm package with the keyword
959
+ \`okcms-plugin\` (that is what \`okcms plugin:search\` queries) and keep
960
+ \`compatibility.okcms\` up to date for the CMS line you support.
961
+ `;
962
+ /** THEME.md — theme development guide (English). */
963
+ const THEME_DOC_EN = `# Developing a Theme
964
+
965
+ A theme is the look of the public site: **templates** (a page hierarchy) +
966
+ **styles**, isolated by \`[data-theme]\` so a theme never leaks CSS into
967
+ another one.
968
+
969
+ > The commands use \`npx\` with the project's local CLI (no global
970
+ > install). With the global CLI (\`bun add -g @oktis-works/cms\`), run
971
+ > \`okcms ...\` directly.
972
+
973
+ ## Generated structure
974
+
975
+ \`\`\`bash
976
+ npx okcms theme:create --name my-theme --style css # css | scss | tailwind
977
+ \`\`\`
978
+
979
+ \`\`\`
980
+ themes/my-theme/
981
+ ├── theme.json # manifest (type: theme + compatibility)
982
+ ├── templates/
983
+ │ ├── index.html # listing (home)
984
+ │ └── single.html # single page (post/content)
985
+ ├── style.css # css engine — or styles/main.scss — or src/input.css
986
+ └── README.md
987
+ \`\`\`
988
+
989
+ ### theme.json
990
+
991
+ \`\`\`json
992
+ {
993
+ "name": "my-theme",
994
+ "version": "0.1.0",
995
+ "description": "my-theme theme for OkCMS",
996
+ "type": "theme",
997
+ "compatibility": { "okcms": "^${CMS_VERSION}" }
998
+ }
999
+ \`\`\`
1000
+
1001
+ With \`--style scss\` or \`--style tailwind\`, the manifest also gets
1002
+ \`stylesConfig\` (\`engine\`, \`entry\`, \`output\`, \`isolation\`) pointing at the
1003
+ style entry. \`compatibility.okcms\` is filled in and validated
1004
+ automatically, just like with plugins.
1005
+
1006
+ ### Templates
1007
+
1008
+ Template syntax with variables and loops:
1009
+
1010
+ \`\`\`html
1011
+ <main class="site">
1012
+ {{#each posts}}
1013
+ <article>
1014
+ <h2>{{this.title}}</h2>
1015
+ <p>{{this.excerpt}}</p>
1016
+ </article>
1017
+ {{/each}}
1018
+ </main>
1019
+ \`\`\`
1020
+
1021
+ - \`templates/index.html\` — listing (e.g. \`{{#each posts}}\`)
1022
+ - \`templates/single.html\` — single item (e.g. \`{{title}}\`, \`{{content}}\`)
1023
+
1024
+ ## Styles per engine
1025
+
1026
+ | \`--style\` | Generated file | Build |
1027
+ |---|---|---|
1028
+ | \`css\` (default) | \`style.css\` | none — served as is |
1029
+ | \`scss\` | \`styles/main.scss\` (+ \`components.scss\`) | \`okcms theme:build -n my-theme\` |
1030
+ | \`tailwind\` | \`src/input.css\` + \`tailwind.config.js\` | \`okcms theme:build -n my-theme\` |
1031
+
1032
+ The build compiles into \`dist/theme.css\` **with \`[data-theme="my-theme"]\`
1033
+ isolation** (styles never leak between themes; Tailwind already ships with
1034
+ \`preflight: false\`).
1035
+
1036
+ > \`dist/theme.css\` is read **ready-made** by the container — nothing compiles
1037
+ > SCSS or Tailwind inside the image. The file exists on the host and only
1038
+ > reaches production when the image is rebuilt: \`okcms theme:build\` followed by
1039
+ > \`okcms redeploy\`.
1040
+
1041
+ ## Development workflow
1042
+
1043
+ \`\`\`bash
1044
+ # 1. Create it
1045
+ npx okcms theme:create --name my-theme --style scss
1046
+
1047
+ # 2. Edit templates/ and styles
1048
+
1049
+ # 3. Compile the styles (scss/tailwind)
1050
+ npx okcms theme:build --name my-theme
1051
+
1052
+ # 4. Activate it (writes activeTheme into okcms.config.json)
1053
+ npx okcms theme:manage --name my-theme --set-active
1054
+ # (equivalent: ACTIVE_THEME=my-theme in .env)
1055
+
1056
+ # 5. Management
1057
+ npx okcms theme:list # installed + status
1058
+ npx okcms theme:manage -n my-theme --info # theme.json information
1059
+ npx okcms theme:manage -n my-theme --disable|enable
1060
+ npx okcms theme:manage -n my-theme --uninstall
1061
+ npx okcms theme:search -q blog # npm search (keyword okcms-theme)
1062
+
1063
+ # If you created it OUTSIDE the project:
1064
+ npx okcms theme:install --name ../my-theme-source
1065
+
1066
+ # 6. After changing templates/styles: recompile and redo the deploy
1067
+ npx okcms theme:build --name my-theme # (the redeploy compiles it anyway)
1068
+ npx okcms redeploy
1069
+ \`\`\`
1070
+
1071
+ ## Publishing
1072
+
1073
+ Publish as an npm package with the keyword \`okcms-theme\` (the target of
1074
+ \`okcms theme:search\`) and keep \`compatibility.okcms\` on the supported CMS
1075
+ line. Run \`theme:build\` in your release process so \`dist/theme.css\` ships
1076
+ inside the package.
1077
+ `;
1078
+ // ---------------------------------------------------------------------------
1079
+ // Superfície pública — uma entrada por documento, com a língua pedida
1080
+ // ---------------------------------------------------------------------------
1081
+ /**
1082
+ * README.md do projeto scaffoldado, na língua escolhida no \`okcms init\`.
1083
+ * Default \`en\` (Enter do prompt e único valor em modo não-interativo).
1084
+ */
1085
+ export function projectReadme(projectName, lang = DEFAULT_DOCS_LANG) {
1086
+ return lang === 'pt' ? projectReadmePt(projectName) : projectReadmeEn(projectName);
1087
+ }
1088
+ /** PLUGIN.md na língua pedida. */
1089
+ export function pluginDoc(lang = DEFAULT_DOCS_LANG) {
1090
+ return lang === 'pt' ? PLUGIN_DOC_PT : PLUGIN_DOC_EN;
1091
+ }
1092
+ /** THEME.md na língua pedida. */
1093
+ export function themeDoc(lang = DEFAULT_DOCS_LANG) {
1094
+ return lang === 'pt' ? THEME_DOC_PT : THEME_DOC_EN;
1095
+ }
401
1096
  //# sourceMappingURL=scaffold-docs.js.map