@softize/opus 8.6.6
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/CHANGELOG.md +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-dicts — emite módulos TS por domínio com `t.dict({...})`
|
|
3
|
+
* reconstruído a partir do manifest.
|
|
4
|
+
*
|
|
5
|
+
* Por quê: o `manifest.json` carrega `dicts: { Name: { keys, values } }`,
|
|
6
|
+
* mas o frontend precisa do código TS pra usar `dict.has() / .metaFor() /
|
|
7
|
+
* .options()` com inferência de tipo. Sem isso o consumer mantinha cópia
|
|
8
|
+
* manual sincronizada à mão — bug-source clássico.
|
|
9
|
+
*
|
|
10
|
+
* Estrutura emitida (um arquivo por domínio raiz, achata subdomains numa
|
|
11
|
+
* mesma saída pra refletir o agrupamento dos outros emits):
|
|
12
|
+
*
|
|
13
|
+
* // AUTO-GERADO por @softize/opus gen. Não edite.
|
|
14
|
+
* // Domínio raiz: ticket
|
|
15
|
+
*
|
|
16
|
+
* import { t } from '@softize/opus/schema/zod'
|
|
17
|
+
*
|
|
18
|
+
* export const TicketStatus = t.dict({
|
|
19
|
+
* open: { label: 'Aberto', color: 'info' },
|
|
20
|
+
* ...
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* Edge cases:
|
|
24
|
+
* - Domínio sem dicts → não emite arquivo.
|
|
25
|
+
* - Dict com `unknownShape: true` (vinda do runner pra dict-like
|
|
26
|
+
* irreconhecível) → emite `t.dict({}) as never` com warning no topo.
|
|
27
|
+
* - Valores em entries serializados via JSON.stringify pra escape correto.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export function buildDictStubs(manifest) {
|
|
31
|
+
const out = []
|
|
32
|
+
for (const domain of manifest.domains) {
|
|
33
|
+
const dicts = collectDicts(domain)
|
|
34
|
+
if (dicts.length === 0) continue
|
|
35
|
+
out.push({
|
|
36
|
+
filename: `${domain.name}.ts`,
|
|
37
|
+
content: renderDomainDicts(domain.name, dicts),
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
return out
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Agrupa dicts do domínio raiz + subdomains em uma única lista.
|
|
45
|
+
* Nome do export = chave declarada no domínio (`TicketStatus`, etc).
|
|
46
|
+
* Conflitos entre subdomínios são improváveis (dicts moram em namespaces
|
|
47
|
+
* separados na config) mas se acontecerem, ganha o primeiro encontrado.
|
|
48
|
+
*/
|
|
49
|
+
function collectDicts(domain) {
|
|
50
|
+
const seen = new Set()
|
|
51
|
+
const out = []
|
|
52
|
+
walk(domain)
|
|
53
|
+
function walk(d) {
|
|
54
|
+
if (d.dicts !== null && typeof d.dicts === 'object') {
|
|
55
|
+
for (const [name, dict] of Object.entries(d.dicts)) {
|
|
56
|
+
if (seen.has(name)) continue
|
|
57
|
+
seen.add(name)
|
|
58
|
+
out.push({ name, dict })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(d.subdomains)) {
|
|
62
|
+
for (const sub of d.subdomains) walk(sub)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderDomainDicts(domainName, dicts) {
|
|
69
|
+
const lines = []
|
|
70
|
+
lines.push('// AUTO-GERADO por @softize/opus gen. Não edite.')
|
|
71
|
+
lines.push(`// Domínio raiz: ${domainName}`)
|
|
72
|
+
lines.push('')
|
|
73
|
+
lines.push("import { t } from '@softize/opus/schema/zod'")
|
|
74
|
+
lines.push('')
|
|
75
|
+
for (const { name, dict } of dicts) {
|
|
76
|
+
lines.push(...renderDict(name, dict))
|
|
77
|
+
lines.push('')
|
|
78
|
+
}
|
|
79
|
+
return lines.join('\n').trimEnd() + '\n'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderDict(name, dict) {
|
|
83
|
+
const lines = []
|
|
84
|
+
if (dict.unknownShape === true) {
|
|
85
|
+
lines.push(
|
|
86
|
+
`// AVISO: dict "${name}" tinha shape irreconhecível no runner — emit vazio.`,
|
|
87
|
+
)
|
|
88
|
+
lines.push(`export const ${name} = t.dict({} as Record<string, { label: string }>)`)
|
|
89
|
+
return lines
|
|
90
|
+
}
|
|
91
|
+
const keys = Array.isArray(dict.keys) ? dict.keys : []
|
|
92
|
+
const values = dict.values ?? {}
|
|
93
|
+
if (keys.length === 0) {
|
|
94
|
+
lines.push(
|
|
95
|
+
`// AVISO: dict "${name}" não tinha chaves no manifest — emit vazio.`,
|
|
96
|
+
)
|
|
97
|
+
lines.push(`export const ${name} = t.dict({} as Record<string, { label: string }>)`)
|
|
98
|
+
return lines
|
|
99
|
+
}
|
|
100
|
+
lines.push(`export const ${name} = t.dict({`)
|
|
101
|
+
for (const key of keys) {
|
|
102
|
+
const entry = values[key]
|
|
103
|
+
lines.push(` ${formatKey(key)}: ${renderEntry(entry)},`)
|
|
104
|
+
}
|
|
105
|
+
lines.push('})')
|
|
106
|
+
return lines
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Emite a chave como identifier nu (`open`) quando for válido em TS,
|
|
111
|
+
* senão como literal string (`'foo-bar'`).
|
|
112
|
+
*/
|
|
113
|
+
function formatKey(key) {
|
|
114
|
+
if (typeof key !== 'string') return JSON.stringify(String(key))
|
|
115
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return key
|
|
116
|
+
return JSON.stringify(key)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function renderEntry(entry) {
|
|
120
|
+
if (entry === null || entry === undefined || typeof entry !== 'object') {
|
|
121
|
+
return '{ label: "" }'
|
|
122
|
+
}
|
|
123
|
+
const parts = []
|
|
124
|
+
// `label` primeiro pra preservar leitura.
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'label')) {
|
|
126
|
+
parts.push(`label: ${JSON.stringify(entry.label ?? '')}`)
|
|
127
|
+
}
|
|
128
|
+
for (const [k, v] of Object.entries(entry)) {
|
|
129
|
+
if (k === 'label') continue
|
|
130
|
+
parts.push(`${formatKey(k)}: ${JSON.stringify(v)}`)
|
|
131
|
+
}
|
|
132
|
+
if (parts.length === 0) parts.push('label: ""')
|
|
133
|
+
return `{ ${parts.join(', ')} }`
|
|
134
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-docs — emite Markdown human-readable por domínio.
|
|
3
|
+
*
|
|
4
|
+
* Um arquivo por domínio raiz (subdomains entram embaixo do raiz pra
|
|
5
|
+
* preservar agrupamento). Estrutura:
|
|
6
|
+
*
|
|
7
|
+
* # Domínio: <name>
|
|
8
|
+
* ## Dicts
|
|
9
|
+
* ## Actions
|
|
10
|
+
* ## Reactions
|
|
11
|
+
* ## Schedules
|
|
12
|
+
* ## Subdomínios
|
|
13
|
+
* ### <subname>
|
|
14
|
+
*
|
|
15
|
+
* Foco em legibilidade — não é spec, é overview pra humano. Schema cru
|
|
16
|
+
* fica no manifest; aqui só descrição amigável.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function buildDocs(manifest) {
|
|
20
|
+
const out = []
|
|
21
|
+
for (const domain of manifest.domains) {
|
|
22
|
+
out.push({
|
|
23
|
+
filename: `${domain.name}.md`,
|
|
24
|
+
content: renderDomain(domain, 1),
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
return out
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderDomain(domain, level) {
|
|
31
|
+
const heading = '#'.repeat(level)
|
|
32
|
+
const sub = '#'.repeat(level + 1)
|
|
33
|
+
const lines = []
|
|
34
|
+
lines.push(`${heading} Domínio: ${domain.name}`)
|
|
35
|
+
lines.push('')
|
|
36
|
+
|
|
37
|
+
if (Object.keys(domain.dicts).length > 0) {
|
|
38
|
+
lines.push(`${sub} Dicts`)
|
|
39
|
+
lines.push('')
|
|
40
|
+
for (const [name, dict] of Object.entries(domain.dicts)) {
|
|
41
|
+
lines.push(...renderDict(name, dict, level + 2))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (domain.actions.length > 0) {
|
|
46
|
+
lines.push(`${sub} Actions`)
|
|
47
|
+
lines.push('')
|
|
48
|
+
for (const action of domain.actions) {
|
|
49
|
+
lines.push(...renderAction(action, level + 2))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (domain.reactions.length > 0) {
|
|
54
|
+
lines.push(`${sub} Reactions`)
|
|
55
|
+
lines.push('')
|
|
56
|
+
for (const reaction of domain.reactions) {
|
|
57
|
+
lines.push(...renderReaction(reaction, level + 2))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (domain.schedules.length > 0) {
|
|
62
|
+
lines.push(`${sub} Schedules`)
|
|
63
|
+
lines.push('')
|
|
64
|
+
for (const schedule of domain.schedules) {
|
|
65
|
+
lines.push(...renderSchedule(schedule, level + 2))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (domain.subdomains.length > 0) {
|
|
70
|
+
lines.push(`${sub} Subdomínios`)
|
|
71
|
+
lines.push('')
|
|
72
|
+
for (const subdomain of domain.subdomains) {
|
|
73
|
+
lines.push(renderDomain(subdomain, level + 2))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lines.join('\n').trimEnd() + '\n'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderDict(name, dict, level) {
|
|
81
|
+
const heading = '#'.repeat(level)
|
|
82
|
+
const lines = []
|
|
83
|
+
lines.push(`${heading} ${name}`)
|
|
84
|
+
lines.push('')
|
|
85
|
+
// Tabela: detecta colunas extras (color, icon, order, ...) dinamicamente
|
|
86
|
+
// a partir das chaves de qualquer entry.
|
|
87
|
+
const allCols = new Set(['label'])
|
|
88
|
+
for (const v of Object.values(dict.values)) {
|
|
89
|
+
if (v === null || typeof v !== 'object') continue
|
|
90
|
+
for (const k of Object.keys(v)) allCols.add(k)
|
|
91
|
+
}
|
|
92
|
+
const cols = ['value', ...allCols]
|
|
93
|
+
lines.push(`| ${cols.join(' | ')} |`)
|
|
94
|
+
lines.push(`| ${cols.map(() => '---').join(' | ')} |`)
|
|
95
|
+
for (const key of dict.keys) {
|
|
96
|
+
const entry = dict.values[key]
|
|
97
|
+
const row = cols.map((col) => {
|
|
98
|
+
if (col === 'value') return key
|
|
99
|
+
if (entry === null || typeof entry !== 'object') return ''
|
|
100
|
+
const v = entry[col]
|
|
101
|
+
if (v === undefined || v === null) return ''
|
|
102
|
+
return String(v)
|
|
103
|
+
})
|
|
104
|
+
lines.push(`| ${row.join(' | ')} |`)
|
|
105
|
+
}
|
|
106
|
+
lines.push('')
|
|
107
|
+
return lines
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function renderAction(action, level) {
|
|
111
|
+
const heading = '#'.repeat(level)
|
|
112
|
+
const lines = []
|
|
113
|
+
lines.push(`${heading} ${action.name} (${action.kind})`)
|
|
114
|
+
lines.push('')
|
|
115
|
+
if (typeof action.summary === 'string') {
|
|
116
|
+
lines.push(`> ${action.summary}`)
|
|
117
|
+
lines.push('')
|
|
118
|
+
} else if (typeof action.description === 'string') {
|
|
119
|
+
lines.push(`> ${action.description.split('\n')[0]}`)
|
|
120
|
+
lines.push('')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const bullets = []
|
|
124
|
+
if (Array.isArray(action.permission)) {
|
|
125
|
+
// requires em lista = ANY: qualquer uma das permissões alcança a action.
|
|
126
|
+
bullets.push(`**Permission:** ${action.permission.map((p) => `\`${p}\``).join(' | ')} (qualquer uma)`)
|
|
127
|
+
} else if (action.permission !== null) {
|
|
128
|
+
bullets.push(`**Permission:** \`${action.permission}\``)
|
|
129
|
+
} else if (action.public === true) {
|
|
130
|
+
bullets.push('**Permission:** público')
|
|
131
|
+
}
|
|
132
|
+
bullets.push(`**HTTP:** \`${httpFor(action)}\``)
|
|
133
|
+
if (Array.isArray(action.emits) && action.emits.length > 0) {
|
|
134
|
+
bullets.push(`**Emite:** ${action.emits.map((e) => `\`${e}\``).join(', ')}`)
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(action.invalidates) && action.invalidates.length > 0) {
|
|
137
|
+
bullets.push(
|
|
138
|
+
`**Invalida:** ${action.invalidates.map((e) => `\`${e}\``).join(', ')}`,
|
|
139
|
+
)
|
|
140
|
+
} else if (action.invalidates === '<function>') {
|
|
141
|
+
bullets.push('**Invalida:** (computado em runtime)')
|
|
142
|
+
}
|
|
143
|
+
if (action.background === true) bullets.push('**Background:** sim')
|
|
144
|
+
if (typeof action.successStatus === 'number') {
|
|
145
|
+
bullets.push(`**Status sucesso:** \`${action.successStatus}\``)
|
|
146
|
+
}
|
|
147
|
+
for (const b of bullets) lines.push(`- ${b}`)
|
|
148
|
+
if (bullets.length > 0) lines.push('')
|
|
149
|
+
|
|
150
|
+
if (action.input !== null) {
|
|
151
|
+
lines.push('**Input:**')
|
|
152
|
+
lines.push('')
|
|
153
|
+
lines.push(...renderSchemaShape(action.input))
|
|
154
|
+
lines.push('')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (action.output !== null) {
|
|
158
|
+
lines.push('**Output:**')
|
|
159
|
+
lines.push('')
|
|
160
|
+
lines.push(...renderSchemaShape(action.output))
|
|
161
|
+
lines.push('')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (action.kind === 'form' && action.fields && Object.keys(action.fields).length > 0) {
|
|
165
|
+
lines.push('**Campos:**')
|
|
166
|
+
lines.push('')
|
|
167
|
+
for (const [name, spec] of Object.entries(action.fields)) {
|
|
168
|
+
const label = typeof spec.label === 'string' ? ` — ${spec.label}` : ''
|
|
169
|
+
const widget =
|
|
170
|
+
typeof spec.widget === 'string' ? ` (widget: \`${spec.widget}\`)` : ''
|
|
171
|
+
lines.push(`- \`${name}\`${label}${widget}`)
|
|
172
|
+
}
|
|
173
|
+
lines.push('')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (action.kind === 'list' && action.filters && Object.keys(action.filters).length > 0) {
|
|
177
|
+
lines.push('**Filtros:**')
|
|
178
|
+
lines.push('')
|
|
179
|
+
for (const [name, spec] of Object.entries(action.filters)) {
|
|
180
|
+
const label = typeof spec.label === 'string' ? ` — ${spec.label}` : ''
|
|
181
|
+
lines.push(`- \`${name}\` (${spec.type ?? 'text'})${label}`)
|
|
182
|
+
}
|
|
183
|
+
lines.push('')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return lines
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function renderReaction(reaction, level) {
|
|
190
|
+
const heading = '#'.repeat(level)
|
|
191
|
+
const lines = []
|
|
192
|
+
lines.push(`${heading} ${reaction.name}`)
|
|
193
|
+
lines.push('')
|
|
194
|
+
const ons = Array.isArray(reaction.on) ? reaction.on : [reaction.on]
|
|
195
|
+
lines.push(`- **Escuta:** ${ons.map((o) => `\`${o}\``).join(', ')}`)
|
|
196
|
+
if (typeof reaction.description === 'string') {
|
|
197
|
+
lines.push(`- **Descrição:** ${reaction.description}`)
|
|
198
|
+
}
|
|
199
|
+
if (reaction.retry !== null) {
|
|
200
|
+
lines.push(`- **Retry:** ${JSON.stringify(reaction.retry)}`)
|
|
201
|
+
}
|
|
202
|
+
lines.push('')
|
|
203
|
+
return lines
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderSchedule(sched, level) {
|
|
207
|
+
const heading = '#'.repeat(level)
|
|
208
|
+
const lines = []
|
|
209
|
+
lines.push(`${heading} ${sched.name}`)
|
|
210
|
+
lines.push('')
|
|
211
|
+
lines.push(`- **Action:** \`${sched.action}\``)
|
|
212
|
+
if (typeof sched.cron === 'string') lines.push(`- **Cron:** \`${sched.cron}\``)
|
|
213
|
+
if (typeof sched.every === 'string') lines.push(`- **Every:** \`${sched.every}\``)
|
|
214
|
+
if (typeof sched.timezone === 'string') {
|
|
215
|
+
lines.push(`- **Timezone:** \`${sched.timezone}\``)
|
|
216
|
+
}
|
|
217
|
+
if (sched.enabled === false) lines.push('- **Status:** desabilitado')
|
|
218
|
+
if (typeof sched.description === 'string') {
|
|
219
|
+
lines.push(`- **Descrição:** ${sched.description}`)
|
|
220
|
+
}
|
|
221
|
+
lines.push('')
|
|
222
|
+
return lines
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Render leve do shape de um JSON Schema — só top level, não recursivo.
|
|
227
|
+
* Pra schema completo, o consumidor vai pro manifest.
|
|
228
|
+
*/
|
|
229
|
+
function renderSchemaShape(schema) {
|
|
230
|
+
if (schema === null || typeof schema !== 'object') return ['_(vazio)_']
|
|
231
|
+
if (schema.__unserializable === true) {
|
|
232
|
+
return [`_(schema não-serializável: ${schema.reason})_`]
|
|
233
|
+
}
|
|
234
|
+
if (schema.type !== 'object' || typeof schema.properties !== 'object') {
|
|
235
|
+
// Schema escalar ou union — emite raw curto.
|
|
236
|
+
const oneLine = JSON.stringify(schema)
|
|
237
|
+
if (oneLine.length <= 100) return ['```json', oneLine, '```']
|
|
238
|
+
return [
|
|
239
|
+
'```json',
|
|
240
|
+
JSON.stringify(schema, null, 2),
|
|
241
|
+
'```',
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
const required = new Set(
|
|
245
|
+
Array.isArray(schema.required) ? schema.required : [],
|
|
246
|
+
)
|
|
247
|
+
const lines = []
|
|
248
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
249
|
+
const isReq = required.has(name)
|
|
250
|
+
const type = describeProp(prop)
|
|
251
|
+
lines.push(`- \`${name}${isReq ? '' : '?'}: ${type}\``)
|
|
252
|
+
}
|
|
253
|
+
return lines
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function describeProp(prop) {
|
|
257
|
+
if (prop === null || typeof prop !== 'object') return 'unknown'
|
|
258
|
+
if (Array.isArray(prop.enum)) {
|
|
259
|
+
return `enum<${prop.enum.join(', ')}>`
|
|
260
|
+
}
|
|
261
|
+
if (Array.isArray(prop.type)) {
|
|
262
|
+
return prop.type.join('|')
|
|
263
|
+
}
|
|
264
|
+
if (typeof prop.type === 'string') {
|
|
265
|
+
if (prop.type === 'array') {
|
|
266
|
+
return `array<${describeProp(prop.items)}>`
|
|
267
|
+
}
|
|
268
|
+
if (prop.type === 'string' && typeof prop.format === 'string') {
|
|
269
|
+
return `string<${prop.format}>`
|
|
270
|
+
}
|
|
271
|
+
const constraints = []
|
|
272
|
+
if (typeof prop.minLength === 'number') constraints.push(`min=${prop.minLength}`)
|
|
273
|
+
if (typeof prop.maxLength === 'number') constraints.push(`max=${prop.maxLength}`)
|
|
274
|
+
if (typeof prop.minimum === 'number') constraints.push(`>=${prop.minimum}`)
|
|
275
|
+
if (typeof prop.maximum === 'number') constraints.push(`<=${prop.maximum}`)
|
|
276
|
+
const suffix = constraints.length > 0 ? ` (${constraints.join(', ')})` : ''
|
|
277
|
+
return `${prop.type}${suffix}`
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(prop.oneOf) || Array.isArray(prop.anyOf)) return 'union'
|
|
280
|
+
return 'object'
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function httpFor(action) {
|
|
284
|
+
const method =
|
|
285
|
+
action.kind === 'simple' || action.kind === 'form' ? 'POST' : 'GET'
|
|
286
|
+
const path = `/api/${action.name.split('.').join('/')}`
|
|
287
|
+
return `${method} ${path}`
|
|
288
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-manifest — transforma o payload do gen-runner no manifest.json
|
|
3
|
+
* público que vai ser emitido.
|
|
4
|
+
*
|
|
5
|
+
* O payload do runner já vem quase no formato certo (ver gen-runner.mjs
|
|
6
|
+
* pra spec). Aqui é só re-shape leve + adicionar headers (`version`,
|
|
7
|
+
* `generatedAt`, `opusVersion`).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// SEM `generatedAt`: o manifest é commitado (build artifact estilo lockfile) e o
|
|
11
|
+
// check de frescura compara bytes — um timestamp tornaria todo run diferente. O
|
|
12
|
+
// histórico do git é o "quando". `opusVersion` fica (muda só com upgrade da base).
|
|
13
|
+
export function buildManifest(payload) {
|
|
14
|
+
return {
|
|
15
|
+
version: '1',
|
|
16
|
+
opusVersion: payload.opusVersion,
|
|
17
|
+
domains: payload.domains.map(shapeDomain),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shapeDomain(d) {
|
|
22
|
+
return {
|
|
23
|
+
name: d.name,
|
|
24
|
+
description: d.description ?? null,
|
|
25
|
+
dicts: d.dicts,
|
|
26
|
+
repository: d.hasRepository,
|
|
27
|
+
service: d.hasService,
|
|
28
|
+
// `entities` = a fonte estrutural+negócio (campos + docs). `models` = nomes
|
|
29
|
+
// (legado, removido no PR de rename). Ambos por ora pra não quebrar a lente.
|
|
30
|
+
entities: d.entities ?? [],
|
|
31
|
+
models: d.modelNames,
|
|
32
|
+
actions: d.actions.map(shapeAction),
|
|
33
|
+
reactions: d.reactions,
|
|
34
|
+
schedules: d.schedules,
|
|
35
|
+
subdomains: d.subdomains.map(shapeDomain),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shapeAction(a) {
|
|
40
|
+
// Já vem no formato certo do runner; cópia rasa preserva ordem das chaves
|
|
41
|
+
// pra ficar consistente entre runs.
|
|
42
|
+
const out = {
|
|
43
|
+
name: a.name,
|
|
44
|
+
kind: a.kind,
|
|
45
|
+
summary: a.summary,
|
|
46
|
+
description: a.description,
|
|
47
|
+
label: a.label,
|
|
48
|
+
title: a.title,
|
|
49
|
+
icon: a.icon,
|
|
50
|
+
color: a.color,
|
|
51
|
+
tags: a.tags,
|
|
52
|
+
permission: a.permission,
|
|
53
|
+
authorize: a.authorize,
|
|
54
|
+
public: a.public,
|
|
55
|
+
internal: a.internal,
|
|
56
|
+
successStatus: a.successStatus,
|
|
57
|
+
input: a.input,
|
|
58
|
+
output: a.output,
|
|
59
|
+
invalidates: a.invalidates,
|
|
60
|
+
confirm: a.confirm,
|
|
61
|
+
rateLimit: a.rateLimit,
|
|
62
|
+
messages: a.messages,
|
|
63
|
+
examples: a.examples,
|
|
64
|
+
errors: a.errors,
|
|
65
|
+
}
|
|
66
|
+
if (a.kind === 'simple' || a.kind === 'form') {
|
|
67
|
+
out.emits = a.emits ?? []
|
|
68
|
+
out.background = a.background === true
|
|
69
|
+
}
|
|
70
|
+
if (a.kind === 'form') out.fields = a.fields ?? {}
|
|
71
|
+
if (a.kind === 'list') {
|
|
72
|
+
out.filters = a.filters ?? {}
|
|
73
|
+
out.sort = a.sort
|
|
74
|
+
out.paginate = a.paginate
|
|
75
|
+
out.text = a.text
|
|
76
|
+
out.periods = a.periods
|
|
77
|
+
}
|
|
78
|
+
if (a.kind === 'view') {
|
|
79
|
+
out.projection = a.projection ?? []
|
|
80
|
+
out.expand = a.expand
|
|
81
|
+
}
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Achata um manifest em uma lista plana de actions (com domínio raiz pra
|
|
87
|
+
* cada). Usado pra gerar OpenAPI a partir do manifest.
|
|
88
|
+
*/
|
|
89
|
+
export function flattenManifestActions(manifest) {
|
|
90
|
+
const out = []
|
|
91
|
+
for (const domain of manifest.domains) walk(domain, out)
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function walk(domain, out) {
|
|
96
|
+
for (const action of domain.actions) {
|
|
97
|
+
out.push({ ...action, domainName: domain.name })
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(domain.subdomains)) {
|
|
100
|
+
for (const sub of domain.subdomains) walk(sub, out)
|
|
101
|
+
}
|
|
102
|
+
}
|