@oondemand/create-central-oon 0.3.44 → 0.3.46

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.
package/README.md CHANGED
@@ -1,133 +1,141 @@
1
1
  # @oondemand/create-central-oon
2
2
 
3
- Gerador de **Centrais Oon**. Cria, a partir do nome da Central, um `backend/` de domínio e um `frontend/` declarativo prontos para consumir o Core (`@oondemand/oon-core-back` e `@oondemand/oon-core-front`).
4
-
5
- > A documentação pública pode existir para consulta, mas a fonte de verdade para codificação assistida fica dentro deste pacote npm, versionada junto com o gerador.
6
-
7
- Toda Central gerada nasce com o padrão visual e operacional consolidado na **Central Minexco**:
8
-
9
- - identidade visual Oon/CST com Poppins, azul `#0474AF` e fundo `#F8F9FA`;
10
- - menu lateral compacto, responsivo e agrupado por seções;
11
- - datagrids densos com busca, ordenação, paginação, badges e ações;
12
- - formulários em diálogo, duas colunas e validação detalhada por campo;
13
- - campos relacionados com seleção pesquisável;
14
- - esteiras horizontais com colunas `#E8ECEF` e cartões operacionais brancos.
15
-
16
- O padrão fica no `@oondemand/oon-core-front`; os templates funcionais apenas declaram domínio, coleções, documentos e esteiras. Assim, não é necessário copiar ou manter um frontend diferente para cada Central.
3
+ Gerador de **Centrais Oon declarativas**. Cria backend e frontend prontos para consumir `@oondemand/oon-core-back` e `@oondemand/oon-core-front`, sem copiar shell, autenticação, páginas genéricas ou infraestrutura para a Central.
17
4
 
18
5
  ## Uso
19
6
 
20
7
  ```bash
21
8
  npx create-central-oon central-transtour
22
- npx create-central-oon central-transtour --template=omie-sidecar
23
- npx create-central-oon "Minha Loja" --template=multi-moedas --no-install
9
+ npx create-central-oon central-transtour --template=servicos-tomados
24
10
  npx create-central-oon --list
25
11
  ```
26
12
 
27
13
  Aliases: o binário também responde por `scaffold-central-oon`.
28
14
 
29
- ### Opções
30
-
31
- | Flag | Efeito |
32
- | ---------------- | ------------------------------------------------------------- |
33
- | `--template=<t>` | Template funcional inicial (default: `basic`). Veja `--list`. |
34
- | `--here` | Gera na pasta atual em vez de criar `<nome>/`. |
35
- | `--force` | Sobrescreve uma pasta existente não-vazia. |
36
- | `--no-install` | Não roda `npm install` nos projetos gerados. |
37
- | `--list` | Lista os templates e sai. |
38
-
39
- ## Documentação interna para Codex
40
-
41
- O pacote inclui documentação canônica em `docs/`. Ela é publicada no npm junto com o gerador.
15
+ ## Contrato `central.app.json`
16
+
17
+ Toda Central gerada possui um manifesto raiz com identidade e capabilities:
18
+
19
+ ```json
20
+ {
21
+ "$schema": "./node_modules/@oondemand/create-central-oon/schemas/central.app.schema.json",
22
+ "schemaVersion": 1,
23
+ "id": "central-exemplo",
24
+ "name": "Central Exemplo",
25
+ "slug": "central-exemplo",
26
+ "appKind": "member-central",
27
+ "modules": {
28
+ "collections": true,
29
+ "documents": false,
30
+ "pipelines": false,
31
+ "integrations": false,
32
+ "omie": false,
33
+ "assistants": false,
34
+ "currencies": false
35
+ },
36
+ "capabilities": ["core.collections"],
37
+ "compatibility": {
38
+ "core": {
39
+ "minVersion": "0.3.45",
40
+ "maxVersionExclusive": "0.4.0"
41
+ }
42
+ }
43
+ }
44
+ ```
42
45
 
43
- A referência completa das opções do manifesto front fica em:
46
+ O schema versionado é publicado no pacote em:
44
47
 
45
48
  ```txt
46
- packages/create-central-oon/docs/FRONTEND_MANIFEST_REFERENCE.md
49
+ @oondemand/create-central-oon/schemas/central.app.schema.json
47
50
  ```
48
51
 
49
- Ao criar uma Central, o CLI gera uma pasta `.ooncore/` com um cache local da documentação da versão instalada:
52
+ Responsabilidades do manifesto:
50
53
 
51
- ```txt
52
- <central>/
53
- └── .ooncore/
54
- ├── CODEX.md
55
- ├── context.generated.md
56
- ├── manifest.json
57
- └── docs/
58
- ```
54
+ - identidade estável (`id`, `name`, `slug`);
55
+ - perfil arquitetural (`appKind`);
56
+ - módulos e capabilities habilitados;
57
+ - faixa compatível do OonCore;
58
+ - metadados declarativos de ativação.
59
59
 
60
- A pasta `.ooncore/` não é a fonte de verdade. Ela é apenas o contexto local para o Codex trabalhar sem depender de site externo.
60
+ `backend/central.config.js` fica reservado a extensões excepcionais de runtime. Em Centrais `member-central` e `portal-cockpit`, identidade, módulos e autenticação não podem ser reintroduzidos nesse arquivo. O token local de desenvolvimento é validado pelo próprio OonCore por `DEV_TOKEN`.
61
61
 
62
- ### Sincronizar documentação local
62
+ ## Verificação de conformidade
63
63
 
64
64
  Na raiz da Central:
65
65
 
66
66
  ```bash
67
- npm run ooncore:docs
67
+ npm run ooncore:conformance
68
68
  ```
69
69
 
70
- ou diretamente:
70
+ ou:
71
71
 
72
72
  ```bash
73
- npx create-central-oon docs --sync
73
+ npx create-central-oon conformance
74
+ npx create-central-oon conformance --json
74
75
  ```
75
76
 
76
- ### Verificar se está atualizada
77
+ O comando falha quando encontra, entre outros desvios:
77
78
 
78
- ```bash
79
- npm run ooncore:docs:check
80
- ```
79
+ - ausência ou incompatibilidade de `central.app.json`;
80
+ - transformações ou registries no bootstrap do frontend;
81
+ - páginas genéricas e extensões executáveis locais;
82
+ - identidade ou autenticação reintroduzida em `central.config.js`;
83
+ - models técnicos de integração;
84
+ - workers, runtimes, locks, clients Omie ou registries locais;
85
+ - patches de métodos do Mongoose;
86
+ - arquivos com extensões fora da allowlist.
87
+
88
+ A CI de uma Central deve executar o comando antes do build e da publicação.
89
+
90
+ ## Documentação interna para Codex
81
91
 
82
- ou diretamente:
92
+ O pacote inclui documentação canônica em `docs/`. Ao criar uma Central, o CLI gera `.ooncore/` como cache local da documentação da versão instalada.
83
93
 
84
94
  ```bash
85
- npx create-central-oon docs --check
95
+ npm run ooncore:docs
96
+ npm run ooncore:docs:check
86
97
  ```
87
98
 
88
- O check compara a versão e o hash da documentação local com a versão instalada do pacote.
99
+ A pasta `.ooncore/` não é fonte de verdade; ela pode ser regenerada a partir do pacote npm.
89
100
 
90
101
  ## Templates funcionais
91
102
 
92
- Todos utilizam o mesmo frontend padrão Minexco.
93
-
94
- | Template | O que gera |
95
- | --------------------- | ------------------------------------------------------------- |
96
- | `basic` | Uma coleção dinâmica (`Pessoa`). |
97
- | `omie-sidecar` | Coleção + integração Omie (mapping de cliente + trigger). |
98
- | `servicos-tomados` | Prestadores + esteira de serviços por `statusProcessamento`. |
99
- | `servicos-prestados` | Clientes + esteira de serviços prestados por `etapa`. |
100
- | `pedidos-marketplace` | Catálogo de produtos + esteira de pedidos por `status`. |
101
- | `documentos-fiscais` | Documento fiscal com aprovação (`CoreDocument`). |
102
- | `multi-moedas` | `Moeda` + `Cotacao` com o módulo currencies. |
103
+ | Template | O que gera |
104
+ | --- | --- |
105
+ | `basic` | Uma coleção dinâmica (`Pessoa`). |
106
+ | `omie-sidecar` | Estrutura declarativa inicial para mapping Omie. |
107
+ | `servicos-tomados` | Prestadores e esteira por status. |
108
+ | `servicos-prestados` | Clientes e esteira por etapa. |
109
+ | `pedidos-marketplace` | Catálogo e esteira de pedidos. |
110
+ | `documentos-fiscais` | Documento fiscal com aprovação. |
111
+ | `multi-moedas` | Moedas e cotações. |
103
112
 
104
113
  ## Estrutura gerada
105
114
 
106
115
  ```txt
107
116
  <central>/
108
- ├── README.md
109
- ├── package.json # scripts de sync/check da documentação OonCore
110
- ├── .ooncore/ # cache local gerado a partir do pacote instalado
111
- ├── backend/ # consome @oondemand/oon-core-back
112
- │ ├── central.config.js # identidade + módulos + paths de domínio
113
- │ ├── central.manifest.json # valores de deploy (render no Core)
114
- │ └── src/{models,validations,triggers,hooks,mappings,documents,
115
- │ pipelines,integrations,routes,controllers,services}/
116
- └── frontend/ # consome o frontend padrão Minexco do OonCore
117
- ├── central.ui.json # menu e telas (coleções/esteiras/documentos)
118
- └── src/
119
- ├── main.tsx # startFromManifest(manifest, { apiBaseUrl })
120
- └── {collections,documents,pipelines,dashboards,overrides}/
117
+ ├── central.app.json # identidade, appKind, módulos e capabilities
118
+ ├── package.json # docs, conformance e comandos locais
119
+ ├── .ooncore/ # cache regenerável da documentação
120
+ ├── backend/
121
+ │ ├── central.config.js # somente extensão excepcional de runtime
122
+ │ ├── central.domain.json # domínio declarativo
123
+ │ └── src/{validations,triggers,hooks,mappings,rules}/
124
+ └── frontend/
125
+ ├── central.ui.json # telas, formulários, grids e esteiras
126
+ └── src/main.tsx # bootstrap mínimo gerado
121
127
  ```
122
128
 
123
- ## Próximos passos após gerar
129
+ O bootstrap gerado importa `central.app.json` e `central.ui.json` e chama `startCentralFromManifest`. Não deve transformar manifestos, registrar componentes padrão nem conhecer regras de um cliente.
130
+
131
+ ## Próximos passos
124
132
 
125
133
  ```bash
126
134
  cd <central>
127
- npm run ooncore:docs:check
135
+ npm run check
128
136
 
129
- cd backend && cp .env.example .env && npm run dev
137
+ cd backend && cp .env.example .env && npm run dev
130
138
  cd ../frontend && cp .env.example .env && npm run dev
131
139
  ```
132
140
 
133
- Evoluir a Central significa criar models em `backend/src/models` e declarar as telas em `frontend/central.ui.json`. Menu, rotas, datagrids, formulários e esteiras são montados pelo Core mantendo o padrão Minexco.
141
+ Evoluir a Central significa declarar domínio, UI, mappings e regras específicas. O OonCore implementa como a aplicação funciona.
package/bin/cli.js CHANGED
@@ -1,26 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- /**
5
- * CLI do create-central-oon.
6
- *
7
- * npx create-central-oon <nome-da-central> [opções]
8
- * npx create-central-oon docs --sync
9
- * npx create-central-oon docs --check
10
- *
11
- * Opções:
12
- * --template=<t> Template inicial (default: basic). Veja --list.
13
- * --here Gera na pasta atual em vez de criar <nome>/.
14
- * --force Sobrescreve pasta existente.
15
- * --no-install Não roda npm install nos projetos gerados.
16
- * --list Lista os templates disponíveis e sai.
17
- *
18
- * Documentação:
19
- * docs --sync Gera/atualiza .ooncore/ com a documentação da versão instalada.
20
- * docs --check Valida se .ooncore/ está sincronizado com a versão instalada.
21
- */
22
-
23
4
  const { run, listTemplates, syncDocs, checkDocs } = require("../src/index");
5
+ const { checkConformance } = require("../src/conformance");
24
6
 
25
7
  function parseArgs(argv) {
26
8
  const opts = {
@@ -32,33 +14,29 @@ function parseArgs(argv) {
32
14
  list: false,
33
15
  docsSync: false,
34
16
  docsCheck: false,
17
+ json: false,
18
+ root: undefined,
35
19
  };
36
20
  const positionals = [];
37
21
 
38
22
  for (let i = 0; i < argv.length; i += 1) {
39
23
  const arg = argv[i];
40
-
41
24
  if (arg === "--here") opts.here = true;
42
25
  else if (arg === "--force") opts.force = true;
43
26
  else if (arg === "--no-install") opts.install = false;
44
27
  else if (arg === "--list") opts.list = true;
45
28
  else if (arg === "--sync") opts.docsSync = true;
46
29
  else if (arg === "--check") opts.docsCheck = true;
30
+ else if (arg === "--json") opts.json = true;
31
+ else if (arg.startsWith("--root=")) opts.root = arg.slice("--root=".length);
32
+ else if (arg === "--root") { opts.root = argv[i + 1]; i += 1; }
47
33
  else if (arg.startsWith("--template=")) opts.template = arg.slice("--template=".length);
48
- else if (arg === "--template") {
49
- opts.template = argv[i + 1];
50
- i += 1;
51
- } else if (!arg.startsWith("-")) {
52
- positionals.push(arg);
53
- }
54
- }
55
-
56
- if (positionals[0] === "docs") {
57
- opts.command = "docs";
58
- } else {
59
- opts.name = positionals[0];
34
+ else if (arg === "--template") { opts.template = argv[i + 1]; i += 1; }
35
+ else if (!arg.startsWith("-")) positionals.push(arg);
60
36
  }
61
37
 
38
+ if (["docs", "conformance"].includes(positionals[0])) opts.command = positionals[0];
39
+ else opts.name = positionals[0];
62
40
  return opts;
63
41
  }
64
42
 
@@ -66,34 +44,42 @@ function printUsage() {
66
44
  console.error("Uso:");
67
45
  console.error(" npx create-central-oon <nome-da-central> [--template=basic]");
68
46
  console.error(" npx create-central-oon --list");
69
- console.error(" npx create-central-oon docs --sync");
70
- console.error(" npx create-central-oon docs --check");
47
+ console.error(" npx create-central-oon docs --sync|--check");
48
+ console.error(" npx create-central-oon conformance [--root .] [--json]");
71
49
  }
72
50
 
73
51
  async function main() {
74
52
  const opts = parseArgs(process.argv.slice(2));
75
-
76
53
  if (opts.list) {
77
54
  console.log("Templates disponíveis:\n");
78
- for (const t of listTemplates()) console.log(` ${t.id.padEnd(20)} ${t.description}`);
79
- process.exit(0);
55
+ for (const template of listTemplates()) {
56
+ console.log(` ${template.id.padEnd(20)} ${template.description}`);
57
+ }
58
+ return;
80
59
  }
81
60
 
82
61
  try {
83
62
  if (opts.command === "docs") {
84
- if (opts.docsCheck) {
85
- checkDocs();
86
- } else {
87
- syncDocs();
88
- }
63
+ if (opts.docsCheck) checkDocs();
64
+ else syncDocs();
89
65
  return;
90
66
  }
91
-
67
+ if (opts.command === "conformance") {
68
+ const result = checkConformance({ cwd: opts.root || process.cwd() });
69
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
70
+ else if (result.ok) console.log(`✔ Central conforme (${result.filesChecked} arquivos verificados).`);
71
+ else {
72
+ console.error("✖ Conformidade da Central reprovada:");
73
+ for (const issue of result.issues) {
74
+ console.error(` - [${issue.code}] ${issue.path}: ${issue.message}`);
75
+ }
76
+ }
77
+ process.exit(result.ok ? 0 : 1);
78
+ }
92
79
  if (!opts.name) {
93
80
  printUsage();
94
81
  process.exit(1);
95
82
  }
96
-
97
83
  await run(opts);
98
84
  } catch (err) {
99
85
  console.error("\n✖", err.message);
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@oondemand/create-central-oon",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
7
7
  "directory": "packages/create-central-oon"
8
8
  },
9
- "description": "Gerador de Centrais Oon com backend declarativo, frontend padrão e documentação interna OonCore para Codex.",
9
+ "description": "Gerador de Centrais Oon com contratos declarativos, guardrails de conformidade e documentação interna para Codex.",
10
10
  "main": "src/index.js",
11
11
  "exports": {
12
- ".": "./src/index.js"
12
+ ".": "./src/index.js",
13
+ "./conformance": "./src/conformance.js",
14
+ "./schemas/central.app.schema.json": "./schemas/central.app.schema.json"
13
15
  },
14
16
  "bin": {
15
17
  "create-central-oon": "bin/cli.js",
@@ -19,9 +21,13 @@
19
21
  "bin",
20
22
  "src",
21
23
  "templates",
24
+ "schemas",
22
25
  "docs",
23
26
  "README.md"
24
27
  ],
28
+ "scripts": {
29
+ "test": "node --test"
30
+ },
25
31
  "engines": {
26
32
  "node": ">=18"
27
33
  },
@@ -33,7 +39,8 @@
33
39
  "create",
34
40
  "generator",
35
41
  "codex",
36
- "docs"
42
+ "docs",
43
+ "conformance"
37
44
  ],
38
45
  "license": "SEE LICENSE IN LICENSE",
39
46
  "publishConfig": {
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.oondemand.com.br/central.app.v1.schema.json",
4
+ "title": "Oon Central App Manifest",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schemaVersion",
9
+ "id",
10
+ "name",
11
+ "slug",
12
+ "appKind",
13
+ "modules",
14
+ "capabilities",
15
+ "compatibility"
16
+ ],
17
+ "properties": {
18
+ "$schema": { "type": "string" },
19
+ "schemaVersion": { "const": 1 },
20
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
21
+ "name": { "type": "string", "minLength": 1 },
22
+ "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
23
+ "appKind": {
24
+ "enum": ["root-central", "member-central", "portal-cockpit"]
25
+ },
26
+ "modules": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "properties": {
30
+ "collections": { "type": "boolean" },
31
+ "documents": { "type": "boolean" },
32
+ "pipelines": { "type": "boolean" },
33
+ "integrations": { "type": "boolean" },
34
+ "omie": { "type": "boolean" },
35
+ "assistants": { "type": "boolean" },
36
+ "currencies": { "type": "boolean" }
37
+ }
38
+ },
39
+ "capabilities": {
40
+ "type": "array",
41
+ "uniqueItems": true,
42
+ "items": {
43
+ "type": "string",
44
+ "pattern": "^[a-z][a-z0-9]*(?:[._:-][a-z0-9][a-z0-9-]*)+$"
45
+ }
46
+ },
47
+ "compatibility": {
48
+ "type": "object",
49
+ "additionalProperties": false,
50
+ "required": ["core"],
51
+ "properties": {
52
+ "core": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "required": ["minVersion"],
56
+ "properties": {
57
+ "minVersion": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$" },
58
+ "maxVersionExclusive": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$" }
59
+ }
60
+ }
61
+ }
62
+ },
63
+ "activation": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "properties": {
67
+ "enabled": { "type": "boolean" },
68
+ "configurationVersion": { "type": "integer", "minimum": 1 },
69
+ "fields": { "type": "array" }
70
+ }
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const IGNORED_DIRS = new Set([".git", ".ooncore", "node_modules", "dist", "build", "coverage"]);
7
+ const EXECUTABLE_EXTENSIONS = new Set([".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx"]);
8
+ const TECHNICAL_MODEL_NAMES = new Set([
9
+ "IntegrationExecution",
10
+ "IntegrationOutbox",
11
+ "IntegrationInbox",
12
+ "WebhookInbox",
13
+ "ExecutionHistory",
14
+ "ProviderRegistry",
15
+ "DistributedLock",
16
+ "OmieConfiguracao",
17
+ "OmieCategoria",
18
+ "OmieContaCorrente",
19
+ "OmieBaixaPagamento",
20
+ ]);
21
+
22
+ function normalize(relativePath) {
23
+ return relativePath.split(path.sep).join("/");
24
+ }
25
+
26
+ function listFiles(root, current = root) {
27
+ const files = [];
28
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
29
+ if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
30
+ const absolute = path.join(current, entry.name);
31
+ if (entry.isDirectory()) files.push(...listFiles(root, absolute));
32
+ else if (entry.isFile()) files.push(normalize(path.relative(root, absolute)));
33
+ }
34
+ return files.sort();
35
+ }
36
+
37
+ function readText(root, relativePath) {
38
+ return fs.readFileSync(path.join(root, relativePath), "utf8");
39
+ }
40
+
41
+ function addIssue(issues, code, file, message) {
42
+ issues.push({ code, path: file, message });
43
+ }
44
+
45
+ function fallbackValidateAppManifest(manifest) {
46
+ const required = ["schemaVersion", "id", "name", "slug", "appKind", "modules", "capabilities", "compatibility"];
47
+ const missing = required.filter((key) => manifest?.[key] === undefined);
48
+ if (missing.length) throw new Error(`campos obrigatórios ausentes: ${missing.join(", ")}.`);
49
+ if (manifest.schemaVersion !== 1) throw new Error("schemaVersion deve ser 1.");
50
+ if (!Array.isArray(manifest.capabilities)) throw new Error("capabilities deve ser uma lista.");
51
+ }
52
+
53
+ function validateAppManifest(root, manifest) {
54
+ try {
55
+ const entry = require.resolve("@oondemand/oon-core-back", {
56
+ paths: [path.join(root, "backend"), root, __dirname],
57
+ });
58
+ const core = require(entry);
59
+ if (typeof core.validateAppManifest === "function") {
60
+ core.validateAppManifest(manifest);
61
+ return;
62
+ }
63
+ } catch (error) {
64
+ if (error?.code !== "MODULE_NOT_FOUND") throw error;
65
+ }
66
+ fallbackValidateAppManifest(manifest);
67
+ }
68
+
69
+ function checkAppManifest(root, issues) {
70
+ const file = "central.app.json";
71
+ const absolute = path.join(root, file);
72
+ if (!fs.existsSync(absolute)) {
73
+ addIssue(issues, "APP_MANIFEST_REQUIRED", file, "central.app.json é obrigatório.");
74
+ return;
75
+ }
76
+ try {
77
+ validateAppManifest(root, JSON.parse(readText(root, file)));
78
+ } catch (error) {
79
+ const details = error?.issues?.length
80
+ ? error.issues.map((item) => `${item.path}: ${item.message}`).join("; ")
81
+ : error.message;
82
+ addIssue(issues, "APP_MANIFEST_INVALID", file, details);
83
+ }
84
+ }
85
+
86
+ function checkBootstrap(root, files, issues) {
87
+ const file = files.find((candidate) => /^frontend\/src\/main\.(?:t|j)sx?$/.test(candidate));
88
+ if (!file) {
89
+ addIssue(issues, "FRONTEND_BOOTSTRAP_REQUIRED", "frontend/src/main.tsx", "bootstrap gerado não encontrado.");
90
+ return;
91
+ }
92
+ const source = readText(root, file);
93
+ if (!/\bstart(?:Central)?FromManifest\b/.test(source)) {
94
+ addIssue(
95
+ issues,
96
+ "FRONTEND_BOOTSTRAP_INVALID",
97
+ file,
98
+ "deve iniciar o Core com startCentralFromManifest.",
99
+ );
100
+ }
101
+ const forbidden = [
102
+ /structuredClone\s*\(/,
103
+ /\b(?:configure|prepare|transform)[A-Z_a-z0-9]*\s*\(/,
104
+ /\b(?:registry|pageComponents|cellRenderers|fieldRenderers|cardRenderers|layoutSlots)\b/,
105
+ /\.find\s*\(/,
106
+ /\.push\s*\(/,
107
+ /Object\.assign\s*\(/,
108
+ ];
109
+ for (const pattern of forbidden) {
110
+ if (pattern.test(source)) {
111
+ addIssue(issues, "FRONTEND_BOOTSTRAP_TRANSFORM", file, `transform local detectado: ${pattern}.`);
112
+ }
113
+ }
114
+ const localImports = [...source.matchAll(/from\s+["'](\.\.?\/[^"']+)["']/g)]
115
+ .map((match) => match[1])
116
+ .filter((value) => !value.endsWith(".json"));
117
+ if (localImports.length) {
118
+ addIssue(
119
+ issues,
120
+ "FRONTEND_BOOTSTRAP_LOCAL_CODE",
121
+ file,
122
+ `imports locais executáveis não permitidos: ${localImports.join(", ")}.`,
123
+ );
124
+ }
125
+
126
+ const extraFrontendSources = files.filter(
127
+ (candidate) => candidate.startsWith("frontend/src/")
128
+ && candidate !== file
129
+ && candidate !== "frontend/src/vite-env.d.ts",
130
+ );
131
+ for (const extra of extraFrontendSources) {
132
+ addIssue(issues, "FRONTEND_LOCAL_EXTENSION", extra, "frontend declarativo deve manter somente o bootstrap gerado.");
133
+ }
134
+ }
135
+
136
+ function checkCentralConfig(root, files, issues) {
137
+ const file = files.find((candidate) => candidate === "backend/central.config.js");
138
+ if (!file) return;
139
+ const source = readText(root, file);
140
+ const forbiddenKeys = ["name", "slug", "appKind", "appCode", "appCodigo", "ecosystem", "activation", "modules", "capabilities", "auth"];
141
+ for (const key of forbiddenKeys) {
142
+ if (new RegExp(`\\b${key}\\s*:`).test(source)) {
143
+ addIssue(
144
+ issues,
145
+ "CENTRAL_CONFIG_BOUNDARY",
146
+ file,
147
+ `${key} deve ser declarado em central.app.json ou fornecido pelo Core.`,
148
+ );
149
+ }
150
+ }
151
+ }
152
+
153
+ function checkDomainManifest(root, issues) {
154
+ const file = "backend/central.domain.json";
155
+ if (!fs.existsSync(path.join(root, file))) return;
156
+ try {
157
+ const manifest = JSON.parse(readText(root, file));
158
+ for (const model of manifest.models || []) {
159
+ if (TECHNICAL_MODEL_NAMES.has(model.name) || /(?:Outbox|Inbox|Execution|Provider|Lock|Webhook|Omie)/i.test(model.name)) {
160
+ addIssue(issues, "TECHNICAL_MODEL", file, `model técnica não permitida na Central: ${model.name}.`);
161
+ }
162
+ }
163
+ } catch (error) {
164
+ addIssue(issues, "DOMAIN_MANIFEST_INVALID", file, error.message);
165
+ }
166
+ }
167
+
168
+ function checkExecutableFiles(root, files, issues) {
169
+ const suspiciousPath = /(?:^|\/)(?:workers?|runtime|registr(?:y|ies)|clients?|patches?|infrastructure|infra)(?:\/|\.)/i;
170
+ const omieClient = /(?:^|\/)(?:omie[-_.]?(?:client|http|service)|clients?\/omie)(?:\.|\/)/i;
171
+ const genericPage = /^frontend\/src\/(?:pages|components)\/(?:Core|Integration|Integracao|Settings|Configurac)/i;
172
+ const mongoosePatch = /mongoose\.(?:Model|Query|Document)\.prototype|\.prototype\.(?:save|updateOne|findOneAndUpdate)\s*=|patchMongoose/i;
173
+ const registryCode = /\b(?:pageComponents|cellRenderers|fieldRenderers|cardRenderers|actionRenderers|layoutSlots)\b/;
174
+
175
+ for (const file of files) {
176
+ if (!EXECUTABLE_EXTENSIONS.has(path.extname(file))) continue;
177
+ if (suspiciousPath.test(file)) addIssue(issues, "GENERIC_RUNTIME", file, "runtime/worker/registry local não permitido.");
178
+ if (omieClient.test(file)) addIssue(issues, "OMIE_CLIENT", file, "cliente Omie deve pertencer ao adaptador nativo do Core.");
179
+ if (genericPage.test(file)) addIssue(issues, "GENERIC_PAGE", file, "página genérica deve pertencer ao OonCore.");
180
+
181
+ const source = readText(root, file);
182
+ if (mongoosePatch.test(source)) addIssue(issues, "MONGOOSE_PATCH", file, "patch de Mongoose não permitido na Central.");
183
+ if (file !== "frontend/src/main.tsx" && file.startsWith("frontend/") && registryCode.test(source)) {
184
+ addIssue(issues, "LOCAL_REGISTRY", file, "registry de componentes padrão não permitido na Central.");
185
+ }
186
+ }
187
+ }
188
+
189
+ function checkAllowedExtensions(files, issues) {
190
+ const allowed = new Set([
191
+ "", ".json", ".md", ".txt", ".yml", ".yaml", ".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx",
192
+ ".html", ".css", ".svg", ".png", ".jpg", ".jpeg", ".webp", ".ico", ".env", ".example", ".sh",
193
+ ]);
194
+ for (const file of files) {
195
+ const base = path.basename(file);
196
+ if (base.startsWith(".env")) continue;
197
+ const extension = path.extname(file).toLowerCase();
198
+ if (!allowed.has(extension)) addIssue(issues, "FILE_EXTENSION", file, `extensão não permitida: ${extension || "(sem extensão)"}.`);
199
+ }
200
+ }
201
+
202
+ function checkConformance({ cwd = process.cwd() } = {}) {
203
+ const root = path.resolve(cwd);
204
+ const files = listFiles(root);
205
+ const issues = [];
206
+ checkAppManifest(root, issues);
207
+ checkBootstrap(root, files, issues);
208
+ checkCentralConfig(root, files, issues);
209
+ checkDomainManifest(root, issues);
210
+ checkExecutableFiles(root, files, issues);
211
+ checkAllowedExtensions(files, issues);
212
+ return { ok: issues.length === 0, root, filesChecked: files.length, issues };
213
+ }
214
+
215
+ function assertConformance(options = {}) {
216
+ const result = checkConformance(options);
217
+ if (!result.ok) {
218
+ const error = new Error(
219
+ `Conformidade da Central reprovada:\n${result.issues.map((issue) => `- [${issue.code}] ${issue.path}: ${issue.message}`).join("\n")}`,
220
+ );
221
+ error.code = "OON_CONFORMANCE_FAILED";
222
+ error.issues = result.issues;
223
+ throw error;
224
+ }
225
+ return result;
226
+ }
227
+
228
+ module.exports = { checkConformance, assertConformance, listFiles };
@@ -1,44 +1,45 @@
1
1
  # __NAME__
2
2
 
3
- Central Oon gerada com `create-central-oon`. É composta por dois projetos mínimos que consomem o Core:
3
+ Central Oon declarativa gerada com `create-central-oon`.
4
4
 
5
- - **backend/** — só domínio (`src/models`, `validations`, `triggers`, …).
6
- Sobe com `oonCore-back dev`. Toda a infra (boot, db, auth, RBAC, metadata,
7
- CRUD, deploy) vem do `@oondemand/oon-core-back`.
8
- - **frontend/** — só declaração (`central.ui.json`). Sobe com `oonCore-front
9
- dev`. Shell, providers, roteamento, auth e telas vêm do
10
- `@oondemand/oon-core-front`, renderizados a partir do `/core/metadata` do back.
5
+ ## Fronteira arquitetural
11
6
 
12
- ## Documentação local do OonCore
7
+ - `central.app.json` declara identidade, `appKind`, módulos, capabilities e compatibilidade com o OonCore;
8
+ - `backend/central.domain.json` declara models e regras do domínio;
9
+ - `frontend/central.ui.json` declara telas, formulários, grids e esteiras;
10
+ - `backend/central.config.js` fica reservado a extensões excepcionais de runtime;
11
+ - `frontend/src/main.tsx` é um bootstrap gerado e não deve transformar manifestos.
13
12
 
14
- A pasta `.ooncore/` é gerada automaticamente a partir da versão instalada do pacote `@oondemand/create-central-oon`.
13
+ Autenticação, shell, roteamento, RBAC, metadata, CRUD, páginas genéricas e infraestrutura pertencem ao OonCore.
15
14
 
16
- Ela serve como contexto local para o Codex codificar sem depender de site externo.
15
+ ## Documentação e conformidade
17
16
 
18
17
  ```bash
19
18
  npm run ooncore:docs # sincroniza .ooncore/
20
- npm run ooncore:docs:check # valida versão/hash da documentação local
19
+ npm run ooncore:docs:check # valida versão/hash do cache local
20
+ npm run ooncore:conformance # valida a fronteira da Central
21
+ npm run check # executa os gates locais
21
22
  ```
22
23
 
23
- Não edite `.ooncore/context.generated.md` manualmente. Atualize o pacote e rode o sync.
24
+ A pasta `.ooncore/` é um cache regenerável da documentação publicada no pacote. Não edite `context.generated.md` manualmente.
24
25
 
25
26
  ## Rodando
26
27
 
27
28
  ```bash
28
- # raiz da Central
29
29
  npm install
30
- npm run ooncore:docs:check
30
+ npm run check
31
31
 
32
- # backend
33
32
  cd backend && cp .env.example .env && npm install && npm run dev
34
-
35
- # frontend (noutro terminal)
36
- cd frontend && cp .env.example .env && npm install && npm run dev
33
+ cd ../frontend && cp .env.example .env && npm install && npm run dev
37
34
  ```
38
35
 
36
+ Em desenvolvimento, configure o mesmo valor em `DEV_TOKEN` no backend e `VITE_DEV_TOKEN` no frontend. A validação do token local é fornecida pelo Core; não implemente `auth.verifyToken` na Central.
37
+
39
38
  ## Evoluindo a Central
40
39
 
41
- 1. Crie models em `backend/src/models` (só schema + CRUD).
42
- 2. Declare as telas em `frontend/central.ui.json` (coleções/esteiras/documentos).
43
- 3. Use validations, triggers, hooks, mappings e integrations para regras e processos.
44
- 4. O resto grid, form, rotas, menu é montado pelo Core automaticamente.
40
+ 1. Declare domínio e fórmulas em `backend/central.domain.json`.
41
+ 2. Declare telas em `frontend/central.ui.json`.
42
+ 3. Use validações, triggers, hooks, mappings e regras somente para comportamento específico do negócio.
43
+ 4. Execute `npm run check` antes de abrir o PR.
44
+
45
+ O OonCore implementa como a aplicação funciona; a Central declara o que pertence ao negócio.
@@ -1,54 +1,6 @@
1
1
  /**
2
- * Configuração da Central __NAME__ (Seção 8). Apenas identidade, módulos
3
- * ativos e os paths de domínio. Sem db/auth/deploy — isso vive no Core e nas
4
- * variáveis de ambiente.
2
+ * Extensões exclusivamente de runtime da Central __NAME__.
3
+ * Identidade, appKind, módulos, capabilities e ativação vivem em
4
+ * ../central.app.json. Autenticação local é fornecida pelo OonCore.
5
5
  */
6
-
7
- // Em desenvolvimento, aceita o DEV_TOKEN sem chamar o Meus Apps.
8
- // Para usar: acesse http://localhost:5173/?code=<DEV_TOKEN>
9
- const devAuth =
10
- process.env.NODE_ENV === "development" && process.env.DEV_TOKEN
11
- ? {
12
- verifyToken: async (token) => {
13
- if (token !== process.env.DEV_TOKEN) {
14
- const err = new Error("Token inválido.");
15
- err.statusCode = 401;
16
- throw err;
17
- }
18
- return { tipo: "admin", nome: "Dev Local", email: "dev@local" };
19
- },
20
- }
21
- : undefined;
22
-
23
- module.exports = {
24
- ecosystem: { role: "member" },
25
- activation: { enabled: true, configurationVersion: 1, fields: [] },
26
- name: "__NAME__",
27
- slug: "__SLUG__",
28
-
29
- auth: devAuth,
30
-
31
- // Módulos opinativos do Core ligados nesta Central.
32
- modules: {
33
- collections: true,
34
- documents: false,
35
- pipelines: false,
36
- integrations: false,
37
- omie: false,
38
- assistants: false,
39
- currencies: false,
40
- },
41
-
42
- // Paths de domínio (carregados por convenção pelo Core no boot).
43
- domain: {
44
- models: "src/models",
45
- validations: "src/validations",
46
- triggers: "src/triggers",
47
- hooks: "src/hooks",
48
- mappings: "src/mappings",
49
- documents: "src/documents",
50
- pipelines: "src/pipelines",
51
- integrations: "src/integrations",
52
- routes: "src/routes",
53
- },
54
- };
6
+ module.exports = {};
@@ -12,6 +12,6 @@
12
12
  "deploy": "oonCore-back deploy"
13
13
  },
14
14
  "dependencies": {
15
- "@oondemand/oon-core-back": "^0.3.44"
15
+ "@oondemand/oon-core-back": "^0.3.46"
16
16
  }
17
17
  }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "./node_modules/@oondemand/create-central-oon/schemas/central.app.schema.json",
3
+ "schemaVersion": 1,
4
+ "id": "__SLUG__",
5
+ "name": "__NAME__",
6
+ "slug": "__SLUG__",
7
+ "appKind": "member-central",
8
+ "modules": {
9
+ "collections": true,
10
+ "documents": false,
11
+ "pipelines": false,
12
+ "integrations": false,
13
+ "omie": false,
14
+ "assistants": false,
15
+ "currencies": false
16
+ },
17
+ "capabilities": [
18
+ "core.collections"
19
+ ],
20
+ "compatibility": {
21
+ "core": {
22
+ "minVersion": "0.3.45",
23
+ "maxVersionExclusive": "0.4.0"
24
+ }
25
+ },
26
+ "activation": {
27
+ "enabled": true,
28
+ "configurationVersion": 1,
29
+ "fields": []
30
+ }
31
+ }
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [],
@@ -10,7 +10,7 @@
10
10
  "sync:metadata": "oonCore-front sync:metadata"
11
11
  },
12
12
  "dependencies": {
13
- "@oondemand/oon-core-front": "^0.3.44",
13
+ "@oondemand/oon-core-front": "^0.3.46",
14
14
  "@chakra-ui/react": "^3.13.0",
15
15
  "@emotion/react": "^11.14.0",
16
16
  "@tanstack/react-query": "^5.65.0",
@@ -1,15 +1,9 @@
1
- import { startFromManifest } from "@oondemand/oon-core-front";
2
- import manifest from "../central.ui.json";
1
+ import { startCentralFromManifest } from "@oondemand/oon-core-front";
2
+ import app from "../../central.app.json";
3
+ import ui from "../central.ui.json";
3
4
 
4
- /**
5
- * Toda a Central __NAME__ inicia por aqui. Sem providers, router, auth, layout
6
- * ou páginas — só o manifesto declarativo `central.ui.json`. O Core resolve as
7
- * telas a partir do `/core/metadata` do backend.
8
- */
9
- startFromManifest(manifest, {
5
+ startCentralFromManifest({ app, ui }, {
10
6
  apiBaseUrl: import.meta.env.VITE_API_URL ?? "http://localhost:4000",
11
7
  meusAppsUrl: import.meta.env.VITE_MEUS_APPS_URL,
12
- // O valor só entra no bundle servido pelo Vite em modo de desenvolvimento.
13
- // O backend continua validando se ele coincide com DEV_TOKEN.
14
8
  devToken: import.meta.env.DEV ? (import.meta.env.VITE_DEV_TOKEN ?? "dev-local") : undefined,
15
9
  });
@@ -3,5 +3,8 @@ import react from "@vitejs/plugin-react";
3
3
 
4
4
  export default defineConfig({
5
5
  plugins: [react()],
6
- server: { port: 5173 },
6
+ server: {
7
+ port: 5173,
8
+ fs: { allow: [".."] },
9
+ },
7
10
  });
@@ -6,10 +6,12 @@
6
6
  "scripts": {
7
7
  "ooncore:docs": "create-central-oon docs --sync",
8
8
  "ooncore:docs:check": "create-central-oon docs --check",
9
+ "ooncore:conformance": "create-central-oon conformance",
10
+ "check": "npm run ooncore:docs:check && npm run ooncore:conformance",
9
11
  "dev:backend": "npm run dev --prefix backend",
10
12
  "dev:frontend": "npm run dev --prefix frontend"
11
13
  },
12
14
  "devDependencies": {
13
- "@oondemand/create-central-oon": "^0.3.8"
15
+ "@oondemand/create-central-oon": "0.3.45"
14
16
  }
15
17
  }
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [{ "model": "Pessoa", "mode": "dynamic" }],
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [],
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [{ "model": "Pessoa", "mode": "dynamic", "section": "Cadastros" }],
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [{ "model": "Produto", "mode": "dynamic", "section": "Catálogo" }],
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [{ "model": "Cliente", "mode": "dynamic", "section": "Cadastros" }],
@@ -1,6 +1,5 @@
1
1
  {
2
- "name": "__NAME__",
3
- "slug": "__SLUG__",
2
+ "schemaVersion": 2,
4
3
  "backend": { "metadataUrl": "/core/metadata" },
5
4
  "navigation": { "mode": "auto" },
6
5
  "collections": [{ "model": "Pessoa", "label": "Prestadores", "mode": "dynamic", "section": "Cadastros" }],