@oondemand/create-central-oon 0.5.6 → 0.6.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 (73) hide show
  1. package/README.md +41 -89
  2. package/bin/cli.js +4 -17
  3. package/docs/ADVANCED_UX_PATTERNS.md +3 -2
  4. package/docs/AGENTS.md +4 -5
  5. package/docs/AGENT_WORKFLOW.md +1 -1
  6. package/docs/BACKEND_API.md +2 -4
  7. package/docs/BACKEND_PATTERNS.md +1 -2
  8. package/docs/BACKEND_PROCESS_MANIFEST.md +6 -6
  9. package/docs/CAPABILITIES.md +2 -3
  10. package/docs/CHECKLIST_IMPLEMENTACAO.md +4 -4
  11. package/docs/CORE_UPGRADE.md +2 -2
  12. package/docs/FRONTEND_API.md +20 -23
  13. package/docs/FRONTEND_CODE_FIRST.md +35 -0
  14. package/docs/FRONTEND_PATTERNS.md +13 -15
  15. package/docs/METADATA_CRUD_UI.md +1 -1
  16. package/docs/PORTAL_COCKPIT_PATTERNS.md +10 -145
  17. package/docs/REACTIVE_DOMAIN_FORMULAS.md +2 -2
  18. package/docs/releases/0.6.0.md +23 -0
  19. package/package.json +1 -1
  20. package/schemas/central.app.schema.json +2 -16
  21. package/src/conformance.js +42 -106
  22. package/src/index.js +4 -28
  23. package/src/render.js +4 -30
  24. package/templates/_base/README.md +7 -6
  25. package/templates/_base/backend/package.json +1 -1
  26. package/templates/_base/central.app.json +4 -9
  27. package/templates/_base/frontend/package.json +1 -1
  28. package/templates/_base/frontend/src/app/app.tsx +25 -0
  29. package/templates/_base/frontend/src/app/navigation.ts +5 -0
  30. package/templates/_base/frontend/src/app/routes.tsx +6 -0
  31. package/templates/_base/frontend/src/app/theme.ts +8 -0
  32. package/templates/_base/frontend/src/components/gitkeep +1 -0
  33. package/templates/_base/frontend/src/features/gitkeep +1 -0
  34. package/templates/_base/frontend/src/layouts/gitkeep +1 -0
  35. package/templates/_base/frontend/src/main.tsx +3 -8
  36. package/templates/_base/frontend/src/pages/HomePage.tsx +25 -0
  37. package/templates/_base/frontend/tsconfig.json +1 -1
  38. package/templates/_base/package.json +1 -1
  39. package/docs/CONNECTORS_AND_INTEGRATIONS.md +0 -66
  40. package/docs/FRONTEND_MANIFEST_REFERENCE.md +0 -487
  41. package/templates/_base/backend/src/integrations/gitkeep +0 -0
  42. package/templates/_base/frontend/central.ui.json +0 -58
  43. package/templates/_base/frontend/src/collections/gitkeep +0 -0
  44. package/templates/_base/frontend/src/dashboards/gitkeep +0 -0
  45. package/templates/_base/frontend/src/documents/gitkeep +0 -0
  46. package/templates/_base/frontend/src/overrides/gitkeep +0 -0
  47. package/templates/_base/frontend/src/pipelines/gitkeep +0 -0
  48. package/templates/basic/backend/src/models/Pessoa.js +0 -21
  49. package/templates/basic/frontend/central.ui.json +0 -8
  50. package/templates/documentos-fiscais/backend/central.config.js +0 -32
  51. package/templates/documentos-fiscais/backend/src/models/DocumentoFiscal.js +0 -23
  52. package/templates/documentos-fiscais/frontend/central.ui.json +0 -16
  53. package/templates/multi-moedas/backend/central.config.js +0 -32
  54. package/templates/multi-moedas/backend/src/models/Cotacao.js +0 -23
  55. package/templates/multi-moedas/backend/src/models/Moeda.js +0 -15
  56. package/templates/multi-moedas/frontend/central.ui.json +0 -11
  57. package/templates/omie-sidecar/backend/central.config.js +0 -33
  58. package/templates/omie-sidecar/backend/src/mappings/pessoa-cliente.js +0 -20
  59. package/templates/omie-sidecar/backend/src/models/Pessoa.js +0 -24
  60. package/templates/omie-sidecar/backend/src/triggers/Pessoa.js +0 -14
  61. package/templates/omie-sidecar/frontend/central.ui.json +0 -8
  62. package/templates/pedidos-marketplace/backend/central.config.js +0 -32
  63. package/templates/pedidos-marketplace/backend/src/models/Pedido.js +0 -22
  64. package/templates/pedidos-marketplace/backend/src/models/Produto.js +0 -16
  65. package/templates/pedidos-marketplace/frontend/central.ui.json +0 -10
  66. package/templates/servicos-prestados/backend/central.config.js +0 -32
  67. package/templates/servicos-prestados/backend/src/models/Cliente.js +0 -15
  68. package/templates/servicos-prestados/backend/src/models/ServicoPrestado.js +0 -22
  69. package/templates/servicos-prestados/frontend/central.ui.json +0 -10
  70. package/templates/servicos-tomados/backend/central.config.js +0 -32
  71. package/templates/servicos-tomados/backend/src/models/Pessoa.js +0 -15
  72. package/templates/servicos-tomados/backend/src/models/Servico.js +0 -22
  73. package/templates/servicos-tomados/frontend/central.ui.json +0 -10
@@ -1,150 +1,15 @@
1
- # Portal/Cockpit com OonCore
1
+ # Portais e cockpits code-first
2
2
 
3
- Este padrão atende aplicações como **Meus Apps**, Portal do Cliente, Portal de Parceiros, Suporte, Copilotos e outros Cockpits first-party.
3
+ Portais e cockpits usam o mesmo contrato de frontend dos demais Apps. Defina o
4
+ App com `defineOonApp`, componha rotas com `defineOonRoutes` e inicialize com
5
+ `startOonApp`.
4
6
 
5
- O objetivo é evitar que um portal precise recriar Shell, Router, AuthProvider, Menu, Guards e SDK HTTP. O portal deve usar o `central.ui.json` + `startFromManifest` e registrar apenas os componentes realmente customizados.
7
+ Use páginas locais para jornadas transversais e components do Core para
8
+ autenticação, autorização, tenant, shell, estados e acesso HTTP. O `appKind` não
9
+ cria uma exceção arquitetural nem restringe customização local.
6
10
 
7
- ## Perfis arquiteturais
11
+ Para jornadas multi-tenant, passe o tenant específico da operação ao cliente
12
+ HTTP. A seleção visual nunca substitui a autorização do backend.
8
13
 
9
- O OonCore diferencia três perfis:
14
+ Consulte `FRONTEND_CODE_FIRST.md`, `FRONTEND_API.md` e `RBAC_SECURITY.md`.
10
15
 
11
- | Perfil | Uso |
12
- | --- | --- |
13
- | `root-central` | Central de Ativações, raiz de confiança. |
14
- | `member-central` | Central cliente/licenciada, ativada por instância. |
15
- | `portal-cockpit` | Portal/Cockpit first-party, autenticado por AppClient/BFF. |
16
-
17
- ## Auth modes
18
-
19
- | `auth.mode` | Uso |
20
- | --- | --- |
21
- | `bearer` | Token local simples, compatível com Centrais existentes. |
22
- | `cookie` | Sessão futura via cookie HTTP-only. |
23
- | `external-sso` | Redireciona para login externo. |
24
- | `central-instance` | Central membro usando instância ativada. |
25
- | `central-client` | Portal/Cockpit usando AppClient/BFF. |
26
-
27
- ## Capabilities no manifesto
28
-
29
- Além de `permissions`, o manifesto pode declarar `capabilities`. Elas são permissões dinâmicas vindas da Central de Ativações e evitam criar campos fixos para cada produto.
30
-
31
- Exemplos:
32
-
33
- ```txt
34
- apps:read
35
- users:manage
36
- tickets:read
37
- tickets:assign
38
- copilots:read
39
- copilots:test
40
- billing:read
41
- ```
42
-
43
- O Core trata `permissions` e `capabilities` como requisitos de UI. A segurança real continua no backend.
44
-
45
- ## Manifesto recomendado
46
-
47
- ```json
48
- {
49
- "schemaVersion": 2,
50
- "name": "Portal Cliente",
51
- "slug": "portal-cliente",
52
- "appKind": "portal-cockpit",
53
- "auth": {
54
- "mode": "central-client",
55
- "tokenParam": "code"
56
- },
57
- "layout": {
58
- "shell": "portal",
59
- "sidebar": "core",
60
- "topbar": "none",
61
- "header": "none",
62
- "footer": "core"
63
- },
64
- "navigation": {
65
- "mode": "manual",
66
- "items": [
67
- { "label": "Meus Apps", "href": "/apps", "capabilities": ["apps:read"], "order": 10 },
68
- { "label": "Suporte", "href": "/suporte", "capabilities": ["tickets:read"], "order": 20 },
69
- { "label": "Copilotos", "href": "/copilotos", "capabilities": ["copilots:read"], "order": 30 },
70
- { "label": "Usuários", "href": "/usuarios", "capabilities": ["users:manage"], "order": 40 }
71
- ]
72
- },
73
- "pages": [
74
- { "path": "/apps", "label": "Meus Apps", "component": "AppsPortalPage", "capabilities": ["apps:read"] },
75
- { "path": "/suporte", "label": "Suporte", "component": "SupportPage", "capabilities": ["tickets:read"] },
76
- { "path": "/copilotos", "label": "Copilotos", "component": "CopilotsPage", "capabilities": ["copilots:read"] },
77
- { "path": "/usuarios", "label": "Usuários", "component": "UsersPermissionsPage", "capabilities": ["users:manage"] }
78
- ],
79
- "collections": [],
80
- "pipelines": [],
81
- "documents": []
82
- }
83
- ```
84
-
85
- ## Bootstrap recomendado
86
-
87
- ```ts
88
- import { startFromManifest } from "@oondemand/oon-core-front";
89
- import manifest from "../central.ui.json";
90
- import { AppsPortalPage } from "./custom/AppsPortalPage";
91
- import { SupportPage } from "./custom/SupportPage";
92
- import { CopilotsPage } from "./custom/CopilotsPage";
93
- import { UsersPermissionsPage } from "./custom/UsersPermissionsPage";
94
-
95
- startFromManifest(manifest, {
96
- apiBaseUrl: import.meta.env.VITE_API_URL,
97
- appKind: "portal-cockpit",
98
- auth: {
99
- mode: "central-client"
100
- },
101
- customComponents: {
102
- AppsPortalPage,
103
- SupportPage,
104
- CopilotsPage,
105
- UsersPermissionsPage
106
- }
107
- });
108
- ```
109
-
110
- ## Contrato esperado do BFF
111
-
112
- O frontend conversa apenas com o BFF do portal. O BFF fala com a Central de Ativações usando AppClient.
113
-
114
- Rotas genéricas esperadas no BFF podem espelhar a Central de Ativações:
115
-
116
- ```http
117
- GET /api/portal/contexto
118
- GET /api/portal/apps
119
- GET /api/portal/apps/:appCode
120
- GET /api/portal/apps/:appCode/capabilities
121
- POST /api/portal/apps/:appCode/authorize
122
- ```
123
-
124
- O BFF também pode expor rotas de domínio próprias, como:
125
-
126
- ```http
127
- GET /api/suporte/tickets
128
- POST /api/suporte/tickets
129
- GET /api/copilotos/assistentes
130
- POST /api/copilotos/assistentes/:id/testar
131
- ```
132
-
133
- Essas rotas de domínio validam capability na Central de Ativações antes de executar a ação.
134
-
135
- ## Regra de segurança
136
-
137
- O frontend nunca deve receber `clientSecret`, `x-oon-instance-token`, hash de credencial ou segredo completo. Portais devem falar com um BFF próprio, e o BFF fala com a Central de Ativações usando AppClient.
138
-
139
- ## Quando usar página custom
140
-
141
- Use página custom apenas quando a tela não for CRUD/esteira/documento declarativo, por exemplo:
142
-
143
- - cards de apps licenciados;
144
- - matriz de permissões;
145
- - cockpit de status;
146
- - tickets de suporte;
147
- - gestão de copilotos;
148
- - onboarding orientado por negócio.
149
-
150
- Mesmo nesses casos, o Shell, Router, Auth, Menu, Guards e SDK HTTP devem continuar no Core.
@@ -2,7 +2,7 @@
2
2
 
3
3
  A partir do contrato declarativo de domínio, campos com `computed` são recalculados imediatamente nos formulários padrão do `@oondemand/oon-core-front`.
4
4
 
5
- A Central não precisa repetir a fórmula em `central.ui.json` nem criar componentes React específicos. A declaração continua existindo uma única vez no `central.domain.json` do backend.
5
+ O App não precisa repetir a fórmula no frontend nem criar componentes React específicos. A declaração continua existindo uma única vez no `central.domain.json` do backend.
6
6
 
7
7
  ## Fluxo
8
8
 
@@ -21,7 +21,7 @@ A Central não precisa repetir a fórmula em `central.ui.json` nem criar compone
21
21
  - formulário principal em modal com abas (`CoreTabbedDetail`);
22
22
  - criação e edição;
23
23
  - formulários derivados integralmente da metadata;
24
- - formulários com overrides de apresentação no `central.ui.json`, desde que a model continue sendo carregada pela metadata do Core.
24
+ - formulários com apresentação local, desde que a model continue sendo carregada pela metadata do Core.
25
25
 
26
26
  ## Exemplo
27
27
 
@@ -0,0 +1,23 @@
1
+ # OonCore 0.6.0
2
+
3
+ A linha 0.6 substitui o bootstrap orientado a manifesto por uma API pública code-first, preservando a governança da Plataforma Oon.
4
+
5
+ ## Destaques
6
+
7
+ - bootstrap com `defineOonApp` e `startOonApp`;
8
+ - rotas, menus, páginas, componentes e temas tipados;
9
+ - scaffold único e neutro em `_base`;
10
+ - `central.app.json` schema v2;
11
+ - remoção integral dos templates selecionáveis, Omie e provider genérico de integrações;
12
+ - resolução de capabilities técnicas pela Plataforma Oon, sem expor detalhes de implementação aos Apps;
13
+ - documentação distribuída atualizada para desenvolvimento assistido por Agents.
14
+
15
+ ## Upgrade
16
+
17
+ - alinhe `@oondemand/oon-core-back`, `@oondemand/oon-core-front` e `@oondemand/create-central-oon` na linha `0.6.x`;
18
+ - declare compatibilidade `>=0.6.0 <0.7.0`;
19
+ - migre o frontend para `defineOonApp`/`startOonApp`;
20
+ - remova flags e módulos legados;
21
+ - execute docs check, conformance, typecheck, testes e smoke offline antes da publicação.
22
+
23
+ A versão 0.6 é incompatível com os contratos removidos da linha 0.5.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/create-central-oon",
3
- "version": "0.5.6",
3
+ "version": "0.6.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://schemas.oondemand.com.br/central.app.v1.schema.json",
3
+ "$id": "https://schemas.oondemand.com.br/central.app.v2.schema.json",
4
4
  "title": "Oon Central App Manifest",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
@@ -19,7 +19,7 @@
19
19
  "type": "string"
20
20
  },
21
21
  "schemaVersion": {
22
- "const": 1
22
+ "const": 2
23
23
  },
24
24
  "id": {
25
25
  "type": "string",
@@ -67,14 +67,6 @@
67
67
  "rbac"
68
68
  ]
69
69
  },
70
- "accessPolicy": {
71
- "enum": [
72
- "tenant_members",
73
- "explicit_users",
74
- "rbac"
75
- ],
76
- "description": "Compatibilidade legada. Novos manifestos devem usar accessMode e authorizationMode."
77
- },
78
70
  "deploymentMode": {
79
71
  "enum": [
80
72
  "singleton",
@@ -105,12 +97,6 @@
105
97
  "pipelines": {
106
98
  "type": "boolean"
107
99
  },
108
- "integrations": {
109
- "type": "boolean"
110
- },
111
- "omie": {
112
- "type": "boolean"
113
- },
114
100
  "assistants": {
115
101
  "type": "boolean"
116
102
  },
@@ -5,19 +5,7 @@ const path = require("node:path");
5
5
 
6
6
  const IGNORED_DIRS = new Set([".git", ".ooncore", "node_modules", "dist", "build", "coverage"]);
7
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
- ]);
8
+ const PUBLIC_FRONT_SUBPATHS = new Set(["ui", "routing", "theme", "hooks", "testing"]);
21
9
 
22
10
  function normalize(relativePath) {
23
11
  return relativePath.split(path.sep).join("/");
@@ -49,15 +37,15 @@ function fallbackValidateAppManifest(manifest) {
49
37
  ];
50
38
  const missing = required.filter((key) => manifest?.[key] === undefined);
51
39
  if (missing.length) throw new Error(`campos obrigatórios ausentes: ${missing.join(", ")}.`);
52
- if (manifest.schemaVersion !== 1) throw new Error("schemaVersion deve ser 1.");
40
+ if (manifest.schemaVersion !== 2) throw new Error("schemaVersion deve ser 2.");
53
41
  if (!Array.isArray(manifest.capabilities)) throw new Error("capabilities deve ser uma lista.");
54
- const hasCanonicalAccess = manifest.accessMode !== undefined && manifest.authorizationMode !== undefined;
55
- const hasLegacyAccess = manifest.accessPolicy !== undefined;
56
- if (!hasCanonicalAccess && !hasLegacyAccess) {
57
- throw new Error("informe accessMode e authorizationMode (ou accessPolicy em manifesto legado).");
42
+ if (manifest.accessMode === undefined || manifest.authorizationMode === undefined) {
43
+ throw new Error("informe accessMode e authorizationMode.");
58
44
  }
59
- if (hasCanonicalAccess && hasLegacyAccess) {
60
- throw new Error("accessPolicy não pode ser combinado com accessMode/authorizationMode.");
45
+ const supportedModules = new Set(["collections", "documents", "pipelines", "assistants", "currencies"]);
46
+ for (const [moduleName, enabled] of Object.entries(manifest.modules || {})) {
47
+ if (!supportedModules.has(moduleName)) throw new Error(`módulo não suportado: ${moduleName}.`);
48
+ if (typeof enabled !== "boolean") throw new Error(`modules.${moduleName} deve ser booleano.`);
61
49
  }
62
50
 
63
51
  const optionalDimensions = {
@@ -78,16 +66,11 @@ function fallbackValidateAppManifest(manifest) {
78
66
  "member-central": { tenancyModel: "single_tenant", availability: "licensed", accessMode: "explicit_users", authorizationMode: "rbac", deploymentMode: "dedicated" },
79
67
  "portal-cockpit": { tenancyModel: "multi_tenant", availability: "common", accessMode: "tenant_members", authorizationMode: "none", deploymentMode: "shared" },
80
68
  }[manifest.appKind] || {};
81
- const legacy = {
82
- tenant_members: { accessMode: "tenant_members", authorizationMode: "none" },
83
- explicit_users: { accessMode: "explicit_users", authorizationMode: "none" },
84
- rbac: { accessMode: "explicit_users", authorizationMode: "rbac" },
85
- }[manifest.accessPolicy] || {};
86
69
  const dimensions = {
87
70
  tenancyModel: manifest.tenancyModel || defaults.tenancyModel,
88
71
  availability: manifest.availability || defaults.availability,
89
- accessMode: manifest.accessMode || legacy.accessMode || defaults.accessMode,
90
- authorizationMode: manifest.authorizationMode || legacy.authorizationMode || defaults.authorizationMode,
72
+ accessMode: manifest.accessMode || defaults.accessMode,
73
+ authorizationMode: manifest.authorizationMode || defaults.authorizationMode,
91
74
  deploymentMode: manifest.deploymentMode || defaults.deploymentMode,
92
75
  };
93
76
  if (dimensions.tenancyModel === "none" && dimensions.accessMode === "tenant_members") {
@@ -137,72 +120,21 @@ function checkAppManifest(root, issues) {
137
120
  }
138
121
  }
139
122
 
140
- function appKind(root) {
141
- try {
142
- return JSON.parse(readText(root, "central.app.json")).appKind;
143
- } catch {
144
- return null;
145
- }
146
- }
147
-
148
- function isPortalExtension(candidate) {
149
- return candidate.startsWith("frontend/src/portal/");
150
- }
151
-
152
123
  function checkBootstrap(root, files, issues) {
153
- const portalCockpit = appKind(root) === "portal-cockpit";
154
124
  const file = files.find((candidate) => /^frontend\/src\/main\.(?:t|j)sx?$/.test(candidate));
155
125
  if (!file) {
156
126
  addIssue(issues, "FRONTEND_BOOTSTRAP_REQUIRED", "frontend/src/main.tsx", "bootstrap gerado não encontrado.");
157
127
  return;
158
128
  }
159
129
  const source = readText(root, file);
160
- if (!/\bstart(?:Central)?FromManifest\b/.test(source)) {
130
+ if (!/\bstartOonApp\b/.test(source)) {
161
131
  addIssue(
162
132
  issues,
163
133
  "FRONTEND_BOOTSTRAP_INVALID",
164
134
  file,
165
- "deve iniciar o Core com startCentralFromManifest.",
135
+ "deve iniciar o Core com startOonApp.",
166
136
  );
167
137
  }
168
- const forbidden = [
169
- /structuredClone\s*\(/,
170
- /\b(?:configure|prepare|transform)[A-Z_a-z0-9]*\s*\(/,
171
- /\b(?:registry|pageComponents|cellRenderers|fieldRenderers|cardRenderers|layoutSlots)\b/,
172
- /\.find\s*\(/,
173
- /\.push\s*\(/,
174
- /Object\.assign\s*\(/,
175
- ];
176
- for (const pattern of forbidden) {
177
- if (pattern.test(source)) {
178
- addIssue(issues, "FRONTEND_BOOTSTRAP_TRANSFORM", file, `transform local detectado: ${pattern}.`);
179
- }
180
- }
181
- const localImports = [...source.matchAll(/from\s+["'](\.\.?\/[^"']+)["']/g)]
182
- .map((match) => match[1])
183
- .filter((value) => !value.endsWith(".json"));
184
- const forbiddenLocalImports = portalCockpit
185
- ? localImports.filter((value) => !value.startsWith("./portal/"))
186
- : localImports;
187
- if (forbiddenLocalImports.length) {
188
- addIssue(
189
- issues,
190
- "FRONTEND_BOOTSTRAP_LOCAL_CODE",
191
- file,
192
- `imports locais executáveis não permitidos: ${forbiddenLocalImports.join(", ")}.`,
193
- );
194
- }
195
-
196
- const extraFrontendSources = files.filter(
197
- (candidate) => candidate.startsWith("frontend/src/")
198
- && candidate !== file
199
- && candidate !== "frontend/src/vite-env.d.ts"
200
- && path.basename(candidate) !== ".gitkeep"
201
- && !(portalCockpit && isPortalExtension(candidate)),
202
- );
203
- for (const extra of extraFrontendSources) {
204
- addIssue(issues, "FRONTEND_LOCAL_EXTENSION", extra, "frontend declarativo deve manter somente o bootstrap gerado.");
205
- }
206
138
  }
207
139
 
208
140
  function checkCentralConfig(root, files, issues) {
@@ -222,42 +154,46 @@ function checkCentralConfig(root, files, issues) {
222
154
  }
223
155
  }
224
156
 
225
- function checkDomainManifest(root, issues) {
226
- const file = "backend/central.domain.json";
227
- if (!fs.existsSync(path.join(root, file))) return;
228
- try {
229
- const manifest = JSON.parse(readText(root, file));
230
- for (const model of manifest.models || []) {
231
- if (TECHNICAL_MODEL_NAMES.has(model.name) || /(?:Outbox|Inbox|Execution|Provider|Lock|Webhook|Omie)/i.test(model.name)) {
232
- addIssue(issues, "TECHNICAL_MODEL", file, `model técnica não permitida na Central: ${model.name}.`);
233
- }
234
- }
235
- } catch (error) {
236
- addIssue(issues, "DOMAIN_MANIFEST_INVALID", file, error.message);
237
- }
238
- }
239
-
240
157
  function checkExecutableFiles(root, files, issues) {
241
- const suspiciousPath = /(?:^|\/)(?:workers?|runtime|registr(?:y|ies)|clients?|patches?|infrastructure|infra)(?:\/|\.)/i;
242
- const omieClient = /(?:^|\/)(?:omie[-_.]?(?:client|http|service)|clients?\/omie)(?:\.|\/)/i;
243
- const genericPage = /^frontend\/src\/(?:pages|components)\/(?:Core|Integration|Integracao|Settings|Configurac)/i;
244
158
  const mongoosePatch = /mongoose\.(?:Model|Query|Document)\.prototype|\.prototype\.(?:save|updateOne|findOneAndUpdate)\s*=|patchMongoose/i;
245
- const registryCode = /\b(?:pageComponents|cellRenderers|fieldRenderers|cardRenderers|actionRenderers|layoutSlots)\b/;
159
+ const remoteCode = /(?:import\s*\(\s*|from\s+|src\s*=\s*)["']https?:\/\//i;
160
+ const dynamicCode = /\b(?:eval|Function)\s*\(/;
161
+ const secretInBundle = /(?:import\.meta\.env|process\.env)\.[A-Z0-9_]*(?:SECRET|PASSWORD|TOKEN|PRIVATE_KEY|API_KEY)/;
162
+ const disabledGuard = /enable(?:RouteGuard|PermissionGate)\s*:\s*false/;
246
163
 
247
164
  for (const file of files) {
248
165
  if (!EXECUTABLE_EXTENSIONS.has(path.extname(file))) continue;
249
- if (suspiciousPath.test(file)) addIssue(issues, "GENERIC_RUNTIME", file, "runtime/worker/registry local não permitido.");
250
- if (omieClient.test(file)) addIssue(issues, "OMIE_CLIENT", file, "cliente Omie deve pertencer ao adaptador nativo do Core.");
251
- if (genericPage.test(file)) addIssue(issues, "GENERIC_PAGE", file, "página genérica deve pertencer ao OonCore.");
252
-
253
166
  const source = readText(root, file);
254
167
  if (mongoosePatch.test(source)) addIssue(issues, "MONGOOSE_PATCH", file, "patch de Mongoose não permitido na Central.");
255
- if (file !== "frontend/src/main.tsx" && file.startsWith("frontend/") && registryCode.test(source)) {
256
- addIssue(issues, "LOCAL_REGISTRY", file, "registry de componentes padrão não permitido na Central.");
168
+ if (!file.startsWith("frontend/")) continue;
169
+ if (remoteCode.test(source) || dynamicCode.test(source)) addIssue(issues, "FRONTEND_REMOTE_CODE", file, "código remoto ou avaliação dinâmica não é permitido no bundle.");
170
+ if (secretInBundle.test(source)) addIssue(issues, "FRONTEND_SECRET", file, "secret, token, senha ou chave privada não pode entrar no bundle frontend.");
171
+ if (disabledGuard.test(source)) addIssue(issues, "FRONTEND_SECURITY_GUARD", file, "guards obrigatórios do OonCore não podem ser desabilitados.");
172
+ for (const match of source.matchAll(/@oondemand\/oon-core-front\/([^"'\s)]+)/g)) {
173
+ const subpath = match[1].split("/")[0];
174
+ if (!PUBLIC_FRONT_SUBPATHS.has(subpath)) addIssue(issues, "FRONTEND_INTERNAL_IMPORT", file, `subpath interno do OonCore não permitido: ${match[0]}.`);
257
175
  }
258
176
  }
259
177
  }
260
178
 
179
+ function checkFrontendDependencies(root, issues) {
180
+ const file = "frontend/package.json";
181
+ if (!fs.existsSync(path.join(root, file))) return;
182
+ try {
183
+ const manifest = JSON.parse(readText(root, file));
184
+ const dependencies = { ...manifest.dependencies, ...manifest.devDependencies, ...manifest.optionalDependencies };
185
+ for (const [name, spec] of Object.entries(dependencies)) {
186
+ if (typeof spec !== "string" || !spec.startsWith("file:")) continue;
187
+ const target = path.resolve(root, "frontend", spec.slice(5));
188
+ const relative = path.relative(path.join(root, "frontend"), target);
189
+ if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) continue;
190
+ addIssue(issues, "FRONTEND_FILE_DEPENDENCY", file, `dependência ${name} aponta para fora de frontend/: ${spec}.`);
191
+ }
192
+ } catch (error) {
193
+ addIssue(issues, "FRONTEND_PACKAGE_INVALID", file, error.message);
194
+ }
195
+ }
196
+
261
197
  function checkAllowedExtensions(files, issues) {
262
198
  const allowed = new Set([
263
199
  "", ".json", ".md", ".txt", ".yml", ".yaml", ".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx",
@@ -295,8 +231,8 @@ function checkConformance({ cwd = process.cwd() } = {}) {
295
231
  checkAppManifest(root, issues);
296
232
  checkBootstrap(root, files, issues);
297
233
  checkCentralConfig(root, files, issues);
298
- checkDomainManifest(root, issues);
299
234
  checkExecutableFiles(root, files, issues);
235
+ checkFrontendDependencies(root, issues);
300
236
  checkAllowedExtensions(files, issues);
301
237
  checkLocalRuntimeSafety(root, files, issues);
302
238
  return { ok: issues.length === 0, root, filesChecked: files.length, issues };
package/src/index.js CHANGED
@@ -11,20 +11,6 @@ const PACKAGE_ROOT = path.join(__dirname, "..");
11
11
  const TEMPLATES_DIR = path.join(PACKAGE_ROOT, "templates");
12
12
  const DOCS_DIR = path.join(PACKAGE_ROOT, "docs");
13
13
 
14
- const TEMPLATES = [
15
- { id: "basic", description: "Central mínima com uma coleção dinâmica (Pessoa)." },
16
- { id: "omie-sidecar", description: "Coleção + integração Omie (mapping de cliente)." },
17
- { id: "servicos-tomados", description: "Serviços tomados com esteira por status." },
18
- { id: "servicos-prestados", description: "Serviços prestados com esteira por etapa." },
19
- { id: "pedidos-marketplace", description: "Pedidos de marketplace com esteira." },
20
- { id: "documentos-fiscais", description: "Documentos fiscais com aprovação." },
21
- { id: "multi-moedas", description: "Moedas + cotações (módulo currencies)." },
22
- ];
23
-
24
- function listTemplates() {
25
- return TEMPLATES;
26
- }
27
-
28
14
  function toSlug(value) {
29
15
  return String(value)
30
16
  .normalize("NFD")
@@ -307,13 +293,6 @@ function checkDocs(opts = {}) {
307
293
  }
308
294
 
309
295
  async function run(opts) {
310
- const template = TEMPLATES.find((t) => t.id === opts.template);
311
- if (!template) {
312
- throw new Error(
313
- `Template "${opts.template}" não existe. Use --list para ver os disponíveis.`
314
- );
315
- }
316
-
317
296
  const slug = toSlug(opts.name);
318
297
  if (!slug) throw new Error("Nome de Central inválido.");
319
298
 
@@ -328,14 +307,12 @@ async function run(opts) {
328
307
  }
329
308
  }
330
309
 
331
- console.log(`\n⚙ Criando Central "${tokens.name}" (template: ${template.id})`);
310
+ console.log(`\n⚙ Criando oon-app "${tokens.name}"`);
332
311
 
333
- // 1. Estrutura comum (backend + frontend mínimos, só domínio/declaração).
312
+ // Fundação canônica única. Não existem overlays ou templates funcionais.
334
313
  copyTemplate(path.join(TEMPLATES_DIR, "_base"), targetDir, tokens);
335
- // 2. Overlay do template escolhido (vence sobre o _base).
336
- copyTemplate(path.join(TEMPLATES_DIR, template.id), targetDir, tokens, { mergeCentralUi: true });
337
314
 
338
- // 3. Contexto local neutro para Agents derivado da versão instalada.
315
+ // Contexto local neutro para Agents derivado da versão instalada.
339
316
  syncDocs({ cwd: targetDir });
340
317
 
341
318
  console.log("✔ Arquivos gerados.");
@@ -371,13 +348,12 @@ ${cd} # Sincronizar/validar documentação local do Core para Agents
371
348
  npm run dev
372
349
 
373
350
  O backend expõe /core/metadata; o frontend renderiza as telas a partir dele.
374
- Edite backend/src/models e frontend/central.ui.json para evoluir a Central.
351
+ Crie páginas, componentes e features em frontend/src e modele o domínio no backend.
375
352
  `);
376
353
  }
377
354
 
378
355
  module.exports = {
379
356
  run,
380
- listTemplates,
381
357
  toSlug,
382
358
  toPascal,
383
359
  toTitle,
package/src/render.js CHANGED
@@ -42,32 +42,12 @@ function applyTokens(content, tokens) {
42
42
  .replace(/__PASCAL__/g, tokens.pascal);
43
43
  }
44
44
 
45
- function mergePages(basePages = [], overlayPages = []) {
46
- const merged = [...basePages];
47
- for (const page of overlayPages) {
48
- const index = page?.path
49
- ? merged.findIndex((candidate) => candidate?.path === page.path)
50
- : -1;
51
- if (index >= 0) merged[index] = page;
52
- else merged.push(page);
53
- }
54
- return merged;
55
- }
56
-
57
- function mergeCentralUiManifest(base, overlay) {
58
- return {
59
- ...base,
60
- ...overlay,
61
- pages: mergePages(base.pages, overlay.pages),
62
- };
63
- }
64
-
65
45
  /**
66
46
  * Copia recursivamente `srcDir` para `destDir`, substituindo tokens em
67
47
  * arquivos de texto. Arquivos já existentes no destino são sobrescritos
68
48
  * (overlay): o template escolhido vence sobre o _base.
69
49
  */
70
- function copyTemplate(srcDir, destDir, tokens, options = {}) {
50
+ function copyTemplate(srcDir, destDir, tokens) {
71
51
  if (!fs.existsSync(srcDir)) return;
72
52
  fs.mkdirSync(destDir, { recursive: true });
73
53
 
@@ -79,18 +59,12 @@ function copyTemplate(srcDir, destDir, tokens, options = {}) {
79
59
  const to = path.join(destDir, destName);
80
60
 
81
61
  if (entry.isDirectory()) {
82
- copyTemplate(from, to, tokens, options);
62
+ copyTemplate(from, to, tokens);
83
63
  } else {
84
64
  const raw = fs.readFileSync(from);
85
65
  if (isText(from)) {
86
66
  const rendered = applyTokens(raw.toString("utf8"), tokens);
87
- if (options.mergeCentralUi === true && entry.name === "central.ui.json" && fs.existsSync(to)) {
88
- const base = JSON.parse(fs.readFileSync(to, "utf8"));
89
- const overlay = JSON.parse(rendered);
90
- fs.writeFileSync(to, JSON.stringify(mergeCentralUiManifest(base, overlay), null, 2) + "\n");
91
- } else {
92
- fs.writeFileSync(to, rendered);
93
- }
67
+ fs.writeFileSync(to, rendered);
94
68
  } else {
95
69
  fs.writeFileSync(to, raw);
96
70
  }
@@ -98,4 +72,4 @@ function copyTemplate(srcDir, destDir, tokens, options = {}) {
98
72
  }
99
73
  }
100
74
 
101
- module.exports = { copyTemplate, applyTokens, isText, mergeCentralUiManifest };
75
+ module.exports = { copyTemplate, applyTokens, isText };
@@ -1,16 +1,17 @@
1
1
  # __NAME__
2
2
 
3
- Central Oon declarativa gerada com `create-central-oon`.
3
+ App Oon code-first gerado com `create-central-oon`.
4
4
 
5
5
  ## Fronteira arquitetural
6
6
 
7
7
  - `central.app.json` declara identidade, `appKind`, módulos, capabilities e compatibilidade com o OonCore;
8
8
  - `backend/central.domain.json` declara models e regras do domínio;
9
- - `frontend/central.ui.json` declara telas, formulários, grids e esteiras;
9
+ - `frontend/src/app` compõe rotas, navegação, layouts e temas;
10
+ - `frontend/src/{pages,components,features}` contém a experiência local;
10
11
  - `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.
12
+ - `frontend/src/main.tsx` chama o bootstrap code-first `startOonApp`.
12
13
 
13
- Autenticação, shell, roteamento, RBAC, metadata, CRUD, páginas genéricas e infraestrutura pertencem ao OonCore.
14
+ Autenticação, guards, shell, RBAC, metadata, CRUD e infraestrutura pertencem ao OonCore.
14
15
 
15
16
  ## Documentação e conformidade
16
17
 
@@ -39,8 +40,8 @@ O comando inicia backend e frontend exclusivamente em loopback, abre uma sessão
39
40
  ## Evoluindo a Central
40
41
 
41
42
  1. Declare domínio e fórmulas em `backend/central.domain.json`.
42
- 2. Declare telas em `frontend/central.ui.json`.
43
+ 2. Crie rotas, páginas, componentes, layouts e temas no frontend.
43
44
  3. Use validações, triggers, hooks, mappings e regras somente para comportamento específico do negócio.
44
45
  4. Execute `npm run check` antes de abrir o PR.
45
46
 
46
- O OonCore implementa como a aplicação funciona; a Central declara o que pertence ao negócio.
47
+ O OonCore fornece a fundação; o App implementa sua experiência e seu domínio.
@@ -12,6 +12,6 @@
12
12
  "deploy": "oonCore-back deploy"
13
13
  },
14
14
  "dependencies": {
15
- "@oondemand/oon-core-back": "^0.5.6"
15
+ "@oondemand/oon-core-back": "^0.6.1"
16
16
  }
17
17
  }