@softize/opus 12.11.0 → 13.1.0
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 +76 -0
- package/PROMOTED.md +46 -0
- package/README.md +28 -19
- package/bin/cli.mjs +87 -216
- package/bin/lib/check.mjs +2 -7
- package/bin/lib/cli-shared.mjs +131 -0
- package/bin/lib/copy.mjs +1 -5
- package/bin/lib/db.mjs +16 -74
- package/bin/lib/gen-openapi.mjs +3 -3
- package/bin/lib/gen-runner.mjs +1 -1
- package/bin/lib/gen.mjs +14 -69
- package/bin/lib/mcp.mjs +3 -1
- package/bin/lib/seed.mjs +5 -62
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
- package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
- package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
- package/docs/ownership-vs-shadcn-lock.md +2 -3
- package/docs/protocol.md +7 -7
- package/docs/radius-scale.md +1 -1
- package/docs/releasing.md +8 -2
- package/package.json +7 -3
- package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
- package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
- package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
- package/registry/templates/app/package.json +1 -1
- package/registry/templates/app/src/main.tsx +4 -4
- package/src/audit/drivers/console.ts +1 -0
- package/src/auth/drivers/better-auth.ts +1 -0
- package/src/auth/drivers/jwt.ts +1 -0
- package/src/cache/drivers/memory.ts +1 -0
- package/src/client/drivers/fetch.ts +2 -1
- package/src/core/actions.ts +6 -1
- package/src/core/audit.ts +9 -3
- package/src/core/contracts.ts +7 -0
- package/src/core/domain.ts +1 -1
- package/src/core/errors.ts +18 -15
- package/src/core/index.ts +4 -2
- package/src/core/package-version.ts +26 -0
- package/src/core/reactions.ts +1 -1
- package/src/core/runtime.ts +33 -23
- package/src/core/schedules.ts +1 -1
- package/src/core/types.ts +2 -2
- package/src/dsl/eval.ts +2 -2
- package/src/dsl/kysely.ts +2 -2
- package/src/dsl/loads.ts +1 -1
- package/src/dsl/parser.ts +5 -5
- package/src/events/drivers/mitt.ts +1 -0
- package/src/mcp/index.ts +2 -1
- package/src/observability/drivers/opentelemetry.ts +1 -0
- package/src/queue/drivers/bullmq.ts +3 -3
- package/src/scheduler/drivers/node-cron.ts +3 -2
- package/src/scheduler/every.ts +7 -7
- package/src/schema/openapi.ts +3 -3
- package/src/seed/index.ts +29 -0
- package/src/server/drivers/fastify.ts +5 -2
- package/src/server/drivers/node.ts +9 -6
- package/src/server/index.ts +3 -1
- package/src/storage/drivers/fs.ts +1 -0
- package/src/testing/index.ts +3 -3
- package/src/ui/components/patterns/confirm.tsx +142 -42
- package/src/ui/components/patterns/content-header.tsx +7 -1
- package/src/ui/components/patterns/data-state.tsx +1 -1
- package/src/ui/components/patterns/dock.tsx +20 -3
- package/src/ui/components/patterns/form.tsx +12 -8
- package/src/ui/components/patterns/list.tsx +36 -41
- package/src/ui/components/patterns/page-state.tsx +2 -2
- package/src/ui/components/patterns/page.tsx +19 -1
- package/src/ui/components/patterns/shell-nav.tsx +10 -3
- package/src/ui/components/patterns/sidebar.tsx +43 -32
- package/src/ui/components/patterns/trigger.tsx +39 -38
- package/src/ui/components/patterns/view.tsx +26 -17
- package/src/ui/components/primitives/alert.tsx +14 -8
- package/src/ui/components/primitives/ask.tsx +3 -3
- package/src/ui/components/primitives/badge.tsx +11 -6
- package/src/ui/components/primitives/breadcrumb.tsx +2 -2
- package/src/ui/components/primitives/button.tsx +16 -3
- package/src/ui/components/primitives/calendar.tsx +28 -2
- package/src/ui/components/primitives/carousel.tsx +3 -3
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +2 -2
- package/src/ui/components/primitives/control.ts +12 -0
- package/src/ui/components/primitives/copyable.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +202 -40
- package/src/ui/components/primitives/dot.tsx +5 -0
- package/src/ui/components/primitives/drawer.tsx +18 -8
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/field.tsx +3 -3
- package/src/ui/components/primitives/icon-picker.tsx +3 -1
- package/src/ui/components/primitives/input-group.tsx +1 -1
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +3 -3
- package/src/ui/components/primitives/progress.tsx +32 -3
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/resizable.tsx +3 -1
- package/src/ui/components/primitives/select.tsx +5 -5
- package/src/ui/components/primitives/slider.tsx +5 -1
- package/src/ui/components/primitives/sonner.tsx +190 -8
- package/src/ui/components/primitives/switch.tsx +1 -0
- package/src/ui/components/primitives/tabs.tsx +1 -0
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/components/primitives/tooltip.tsx +1 -0
- package/src/ui/docs/DocBrowser.tsx +102 -23
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/accordion.md +22 -16
- package/src/ui/docs/content/action-form-card.md +8 -8
- package/src/ui/docs/content/action-form-dialog.md +9 -9
- package/src/ui/docs/content/action-form.md +37 -36
- package/src/ui/docs/content/action-list-dialog.md +11 -6
- package/src/ui/docs/content/action-list.md +73 -39
- package/src/ui/docs/content/action-trigger.md +29 -15
- package/src/ui/docs/content/action-view.md +17 -10
- package/src/ui/docs/content/actions.md +9 -9
- package/src/ui/docs/content/ai.md +3 -3
- package/src/ui/docs/content/alert.md +14 -12
- package/src/ui/docs/content/ask.md +11 -0
- package/src/ui/docs/content/aspect-ratio.md +4 -4
- package/src/ui/docs/content/audit.md +2 -2
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/avatar.md +34 -14
- package/src/ui/docs/content/badge.md +3 -3
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +81 -6
- package/src/ui/docs/content/calendar.md +18 -5
- package/src/ui/docs/content/card.md +27 -1
- package/src/ui/docs/content/carousel.md +16 -11
- package/src/ui/docs/content/chat.md +23 -3
- package/src/ui/docs/content/checkbox.md +7 -7
- package/src/ui/docs/content/cli.md +74 -22
- package/src/ui/docs/content/collapsible.md +8 -8
- package/src/ui/docs/content/command.md +16 -8
- package/src/ui/docs/content/composer.md +17 -2
- package/src/ui/docs/content/content.md +17 -2
- package/src/ui/docs/content/copyable.md +12 -3
- package/src/ui/docs/content/customization.md +5 -5
- package/src/ui/docs/content/cycle.md +3 -3
- package/src/ui/docs/content/data-state.md +11 -12
- package/src/ui/docs/content/data.md +26 -33
- package/src/ui/docs/content/detail.md +22 -4
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +17 -10
- package/src/ui/docs/content/dock.md +11 -3
- package/src/ui/docs/content/dot.md +8 -0
- package/src/ui/docs/content/drawer.md +27 -14
- package/src/ui/docs/content/empty-value.md +2 -2
- package/src/ui/docs/content/empty.md +19 -12
- package/src/ui/docs/content/events.md +4 -4
- package/src/ui/docs/content/field.md +34 -12
- package/src/ui/docs/content/getting-started.md +3 -3
- package/src/ui/docs/content/icon-picker.md +19 -4
- package/src/ui/docs/content/input-otp.md +20 -12
- package/src/ui/docs/content/input.md +121 -9
- package/src/ui/docs/content/item.md +27 -13
- package/src/ui/docs/content/kbd.md +19 -11
- package/src/ui/docs/content/label.md +12 -3
- package/src/ui/docs/content/log.md +4 -4
- package/src/ui/docs/content/markdown.md +7 -6
- package/src/ui/docs/content/mcp.md +13 -15
- package/src/ui/docs/content/menu.md +40 -16
- package/src/ui/docs/content/metric-card.md +13 -0
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +59 -6
- package/src/ui/docs/content/pagination.md +22 -17
- package/src/ui/docs/content/popover.md +22 -8
- package/src/ui/docs/content/progress.md +15 -16
- package/src/ui/docs/content/queue.md +5 -5
- package/src/ui/docs/content/radio-group.md +20 -12
- package/src/ui/docs/content/router.md +11 -6
- package/src/ui/docs/content/scheduler.md +4 -5
- package/src/ui/docs/content/scroll-area.md +12 -7
- package/src/ui/docs/content/select.md +47 -34
- package/src/ui/docs/content/semantic-context.md +2 -2
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +329 -54
- package/src/ui/docs/content/skeleton.md +9 -2
- package/src/ui/docs/content/slider.md +8 -7
- package/src/ui/docs/content/spinner.md +8 -8
- package/src/ui/docs/content/split.md +29 -5
- package/src/ui/docs/content/storage.md +6 -8
- package/src/ui/docs/content/switch.md +8 -7
- package/src/ui/docs/content/table.md +13 -3
- package/src/ui/docs/content/tabs.md +28 -14
- package/src/ui/docs/content/testing.md +9 -11
- package/src/ui/docs/content/textarea.md +12 -4
- package/src/ui/docs/content/toast.md +47 -13
- package/src/ui/docs/content/toggle.md +75 -7
- package/src/ui/docs/content/tokens.md +7 -7
- package/src/ui/docs/content/tooltip.md +19 -11
- package/src/ui/docs/content/truncate.md +15 -8
- package/src/ui/docs/content/ui.md +24 -9
- package/src/ui/docs/content/upgrading.md +7 -8
- package/src/ui/docs/doc-client.tsx +5 -5
- package/src/ui/docs/doc.tsx +26 -14
- package/src/ui/docs/registry.tsx +25 -42
- package/src/ui/docs/standalone.tsx +2 -2
- package/src/ui/drivers/react.tsx +17 -12
- package/src/ui/lib/action-errors.ts +45 -0
- package/src/ui/lib/zod-pt-br.ts +31 -4
- package/src/ui/meta.ts +65 -95
- package/src/ui/react.tsx +17 -16
- package/src/ui/theme.css +60 -8
- package/src/vite/design.ts +6 -18
- package/src/ui/components/primitives/alert-dialog.tsx +0 -192
- package/src/ui/docs/content/alert-dialog.md +0 -73
- package/src/ui/docs/content/button-group.md +0 -71
- package/src/ui/docs/content/confirm.md +0 -120
- package/src/ui/docs/content/input-group.md +0 -79
- package/src/ui/docs/content/page-state.md +0 -45
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilitários compartilhados pelos comandos do CLI `opus`.
|
|
3
|
+
*
|
|
4
|
+
* Concentra o que mais de um comando repetia: log colorido no terminal, teste de
|
|
5
|
+
* existência de arquivo, resolução do `opus.config.ts` do consumer e o spawn do `tsx`
|
|
6
|
+
* que carrega esse config TypeScript num runner isolado (`gen`, `db` e `seed` usam a
|
|
7
|
+
* mesma infra: o runner emite uma linha JSON na última linha de stdout).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFile } from 'node:child_process'
|
|
11
|
+
import { promises as fs } from 'node:fs'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
import { promisify } from 'node:util'
|
|
15
|
+
|
|
16
|
+
import { canonicalProjectDirectory, safeProjectPath } from '@softize/base/project-path'
|
|
17
|
+
|
|
18
|
+
const execFileAsync = promisify(execFile)
|
|
19
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
20
|
+
|
|
21
|
+
export const CONFIG_FILE = 'opus.config.ts'
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Log
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
const COLORS = {
|
|
28
|
+
info: '\x1b[36m',
|
|
29
|
+
success: '\x1b[32m',
|
|
30
|
+
error: '\x1b[31m',
|
|
31
|
+
warn: '\x1b[33m',
|
|
32
|
+
dim: '\x1b[2m',
|
|
33
|
+
}
|
|
34
|
+
const RESET = '\x1b[0m'
|
|
35
|
+
|
|
36
|
+
/** Escreve uma linha colorida em stdout. Nível desconhecido sai sem cor. */
|
|
37
|
+
export function log(level, message) {
|
|
38
|
+
console.log(`${COLORS[level] ?? ''}${message}${RESET}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// FS
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
export async function fileExists(file) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(file)
|
|
48
|
+
return true
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Config do consumer
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve o `opus.config.ts` a partir de `flags.config` (default: `./opus.config.ts`),
|
|
60
|
+
* contido no projeto. Lança `Error` com uma mensagem única quando o arquivo não existe;
|
|
61
|
+
* cada comando decide o canal (texto ou JSON) e o código de saída.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveConfig(flags, cwd = process.cwd()) {
|
|
64
|
+
const root = canonicalProjectDirectory(cwd)
|
|
65
|
+
const config = safeProjectPath(root, flags.config ?? CONFIG_FILE)
|
|
66
|
+
if (!config.exists) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`${CONFIG_FILE} não encontrado em ${config.path}. ` +
|
|
69
|
+
'Passe o caminho com --config <path> ou crie o arquivo na raiz do projeto.',
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
return { cwd: root, configPath: config.path }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// Runner via tsx
|
|
77
|
+
// =============================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Binário do tsx: o empacotado em `<package>/node_modules/.bin/tsx` quando existe
|
|
81
|
+
* (independe do PATH do consumer); senão confia no `tsx` do PATH.
|
|
82
|
+
*/
|
|
83
|
+
export async function resolveTsxBin() {
|
|
84
|
+
const local = path.join(PACKAGE_ROOT, 'node_modules', '.bin', 'tsx')
|
|
85
|
+
return (await fileExists(local)) ? local : 'tsx'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Executa `tsx <runnerPath> ...args` capturando stdout/stderr em utf8 e herdando o
|
|
90
|
+
* ambiente. Rejeita como o `execFile` (o erro traz `stdout` e `stderr`) quando o
|
|
91
|
+
* processo falha.
|
|
92
|
+
*/
|
|
93
|
+
export async function spawnTsx(runnerPath, args, { maxBuffer = 16 * 1024 * 1024 } = {}) {
|
|
94
|
+
const command = await resolveTsxBin()
|
|
95
|
+
return execFileAsync(command, [runnerPath, ...args], {
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
maxBuffer,
|
|
98
|
+
env: { ...process.env },
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Última linha de stdout que parece um objeto JSON. Logs do consumer durante o
|
|
104
|
+
* import do config vão para o início; o runner emite o payload por último.
|
|
105
|
+
*/
|
|
106
|
+
export function lastJsonLine(stdout) {
|
|
107
|
+
const lines = stdout.split('\n').filter((line) => line.trim().length > 0)
|
|
108
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
109
|
+
const line = lines[index].trim()
|
|
110
|
+
if (line.startsWith('{') && line.endsWith('}')) return line
|
|
111
|
+
}
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Roda um runner que responde `{ ok, ... }` em JSON. Nunca rejeita: falha de spawn ou
|
|
117
|
+
* ausência de payload viram `{ ok: false, error }` para o comando reportar.
|
|
118
|
+
*/
|
|
119
|
+
export async function runJsonRunner(runnerPath, args, options) {
|
|
120
|
+
try {
|
|
121
|
+
const { stdout } = await spawnTsx(runnerPath, args, options)
|
|
122
|
+
const line = lastJsonLine(stdout)
|
|
123
|
+
return line === null ? { ok: false, error: 'runner não emitiu JSON' } : JSON.parse(line)
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const detail =
|
|
126
|
+
typeof error.stderr === 'string' && error.stderr.trim().length > 0
|
|
127
|
+
? error.stderr.trim()
|
|
128
|
+
: error.message
|
|
129
|
+
return { ok: false, error: detail }
|
|
130
|
+
}
|
|
131
|
+
}
|
package/bin/lib/copy.mjs
CHANGED
|
@@ -71,10 +71,6 @@ const JSX_CHILD_ROLES = new Map([
|
|
|
71
71
|
['ActionFormCard', 'message'],
|
|
72
72
|
['ActionFormDialog', 'message'],
|
|
73
73
|
['AlertDescription', 'message'],
|
|
74
|
-
['AlertDialogAction', 'button'],
|
|
75
|
-
['AlertDialogCancel', 'button'],
|
|
76
|
-
['AlertDialogDescription', 'dialog-body'],
|
|
77
|
-
['AlertDialogTitle', 'title'],
|
|
78
74
|
['AlertTitle', 'title'],
|
|
79
75
|
['Badge', 'badge'],
|
|
80
76
|
['BreadcrumbLink', 'breadcrumb'],
|
|
@@ -215,7 +211,7 @@ const JSX_PROP_STYLE = new Map([
|
|
|
215
211
|
])
|
|
216
212
|
|
|
217
213
|
const JSX_PORTAL_BOUNDARIES = new Set([
|
|
218
|
-
'
|
|
214
|
+
'DialogContent', 'DrawerContent', 'MenuContent', 'MenuSubContent',
|
|
219
215
|
'PopoverContent', 'TooltipContent',
|
|
220
216
|
])
|
|
221
217
|
|
package/bin/lib/db.mjs
CHANGED
|
@@ -10,31 +10,12 @@
|
|
|
10
10
|
* Ver `docs/data-layer.md`.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { promises as fs } from 'node:fs'
|
|
14
13
|
import path from 'node:path'
|
|
15
14
|
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import { execFile } from 'node:child_process'
|
|
17
|
-
import { promisify } from 'node:util'
|
|
18
|
-
import { canonicalProjectDirectory, safeProjectPath } from '@softize/base/project-path'
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
import { log, resolveConfig, runJsonRunner } from './cli-shared.mjs'
|
|
21
17
|
|
|
22
|
-
const
|
|
23
|
-
const __dirname = path.dirname(__filename)
|
|
24
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
25
|
-
|
|
26
|
-
const COLORS = {
|
|
27
|
-
info: '\x1b[36m',
|
|
28
|
-
success: '\x1b[32m',
|
|
29
|
-
error: '\x1b[31m',
|
|
30
|
-
warn: '\x1b[33m',
|
|
31
|
-
dim: '\x1b[2m',
|
|
32
|
-
}
|
|
33
|
-
const RESET = '\x1b[0m'
|
|
34
|
-
|
|
35
|
-
function log(level, msg) {
|
|
36
|
-
console.log(`${COLORS[level] ?? ''}${msg}${RESET}`)
|
|
37
|
-
}
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
38
19
|
|
|
39
20
|
// =============================================================================
|
|
40
21
|
// Dispatch
|
|
@@ -67,17 +48,13 @@ export async function cmdDb(rest, flags) {
|
|
|
67
48
|
// Config
|
|
68
49
|
// =============================================================================
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!config.exists) {
|
|
76
|
-
log('error', `opus.config.ts não encontrado em ${configPath}`)
|
|
77
|
-
log('dim', ' Passa o path via --config <path> ou cria um na raiz do projeto.')
|
|
51
|
+
function resolveConfigOrExit(flags) {
|
|
52
|
+
try {
|
|
53
|
+
return resolveConfig(flags)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
log('error', error instanceof Error ? error.message : String(error))
|
|
78
56
|
process.exit(1)
|
|
79
57
|
}
|
|
80
|
-
return { cwd, configPath }
|
|
81
58
|
}
|
|
82
59
|
|
|
83
60
|
// =============================================================================
|
|
@@ -85,7 +62,7 @@ async function resolveConfig(flags) {
|
|
|
85
62
|
// =============================================================================
|
|
86
63
|
|
|
87
64
|
async function cmdDbCheck(flags) {
|
|
88
|
-
const { cwd, configPath } =
|
|
65
|
+
const { cwd, configPath } = resolveConfigOrExit(flags)
|
|
89
66
|
log('info', `→ drift-check via ${path.relative(cwd, configPath)}...`)
|
|
90
67
|
|
|
91
68
|
const result = await runRunner('db-check-runner.mjs', configPath)
|
|
@@ -128,7 +105,7 @@ async function cmdDbMigrate(sub2, flags) {
|
|
|
128
105
|
log('dim', ' Rollback = editar o schema e re-rodar `opus db migrate`; catástrofe = snapshot pré-deploy.')
|
|
129
106
|
process.exit(1)
|
|
130
107
|
}
|
|
131
|
-
const { cwd, configPath } =
|
|
108
|
+
const { cwd, configPath } = resolveConfigOrExit(flags)
|
|
132
109
|
log('info', `→ db migrate via ${path.relative(cwd, configPath)}...`)
|
|
133
110
|
|
|
134
111
|
const result = await runRunner('db-migrate-runner.mjs', configPath)
|
|
@@ -163,7 +140,7 @@ async function cmdDbMigrate(sub2, flags) {
|
|
|
163
140
|
// =============================================================================
|
|
164
141
|
|
|
165
142
|
async function cmdDbScaffold(flags) {
|
|
166
|
-
const { cwd, configPath } =
|
|
143
|
+
const { cwd, configPath } = resolveConfigOrExit(flags)
|
|
167
144
|
log('info', `→ db scaffold via ${path.relative(cwd, configPath)}...`)
|
|
168
145
|
|
|
169
146
|
const result = await runRunner('db-scaffold-runner.mjs', configPath)
|
|
@@ -188,46 +165,8 @@ async function cmdDbScaffold(flags) {
|
|
|
188
165
|
// Runner spawn (via tsx)
|
|
189
166
|
// =============================================================================
|
|
190
167
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const hasLocalTsx = await fileExists(tsxBin)
|
|
194
|
-
const runnerPath = path.join(__dirname, runnerFile)
|
|
195
|
-
const cmd = hasLocalTsx ? tsxBin : 'tsx'
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
const { stdout } = await execFileAsync(cmd, [runnerPath, configPath, ...extraArgs], {
|
|
199
|
-
encoding: 'utf8',
|
|
200
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
201
|
-
env: { ...process.env },
|
|
202
|
-
})
|
|
203
|
-
const line = lastJsonLine(stdout)
|
|
204
|
-
if (line === null) return { ok: false, error: 'runner não emitiu JSON' }
|
|
205
|
-
return JSON.parse(line)
|
|
206
|
-
} catch (err) {
|
|
207
|
-
const detail =
|
|
208
|
-
typeof err.stderr === 'string' && err.stderr.trim().length > 0
|
|
209
|
-
? err.stderr.trim()
|
|
210
|
-
: err.message
|
|
211
|
-
return { ok: false, error: detail }
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function lastJsonLine(stdout) {
|
|
216
|
-
const lines = stdout.split('\n').filter((l) => l.trim().length > 0)
|
|
217
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
218
|
-
const trimmed = lines[i].trim()
|
|
219
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed
|
|
220
|
-
}
|
|
221
|
-
return null
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function fileExists(p) {
|
|
225
|
-
try {
|
|
226
|
-
await fs.access(p)
|
|
227
|
-
return true
|
|
228
|
-
} catch {
|
|
229
|
-
return false
|
|
230
|
-
}
|
|
168
|
+
function runRunner(runnerFile, configPath, extraArgs = []) {
|
|
169
|
+
return runJsonRunner(path.join(__dirname, runnerFile), [configPath, ...extraArgs])
|
|
231
170
|
}
|
|
232
171
|
|
|
233
172
|
// =============================================================================
|
|
@@ -245,9 +184,12 @@ export function helpDb() {
|
|
|
245
184
|
re-rodável) e roda o drift-check na sequência. Exit ≠ 0 se divergir.
|
|
246
185
|
db scaffold Gera um rascunho kysely a partir do diff — REFERÊNCIA pra escrever o
|
|
247
186
|
SQL no schema (a verdade é o script; revise à mão).
|
|
187
|
+
db migrate down Aposentado: encerra com erro. O schema é um script idempotente sem
|
|
188
|
+
histórico a reverter; rollback = editar o script e rodar db migrate.
|
|
248
189
|
|
|
249
190
|
Flags:
|
|
250
191
|
--config <path> Caminho interno ao projeto para opus.config.ts. Default: ./opus.config.ts
|
|
192
|
+
--help, -h Mostra esta mensagem
|
|
251
193
|
|
|
252
194
|
O opus.config.ts precisa expor, pros comandos db:
|
|
253
195
|
database: () => Kysely factory LAZY do banco (gen não a chama)
|
|
@@ -258,6 +200,6 @@ O opus.config.ts precisa expor, pros comandos db:
|
|
|
258
200
|
Exemplos:
|
|
259
201
|
npx @softize/opus db check
|
|
260
202
|
npx @softize/opus db migrate
|
|
261
|
-
npx @softize/opus db
|
|
203
|
+
npx @softize/opus db scaffold --config ./apps/api/opus.config.ts
|
|
262
204
|
`)
|
|
263
205
|
}
|
package/bin/lib/gen-openapi.mjs
CHANGED
|
@@ -82,15 +82,15 @@ function buildResponses(action) {
|
|
|
82
82
|
const successStatus = String(action.successStatus ?? 200)
|
|
83
83
|
return {
|
|
84
84
|
[successStatus]: {
|
|
85
|
-
description: '
|
|
85
|
+
description: 'Sucesso',
|
|
86
86
|
content: {
|
|
87
87
|
'application/json': {
|
|
88
88
|
schema: wrapEnvelope(action.output, action.kind),
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
},
|
|
92
|
-
'4XX': errorResponse('
|
|
93
|
-
'5XX': errorResponse('
|
|
92
|
+
'4XX': errorResponse('Erro do cliente', action.errors),
|
|
93
|
+
'5XX': errorResponse('Erro do servidor'),
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -75,7 +75,7 @@ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
|
75
75
|
async function main() {
|
|
76
76
|
const configPath = process.argv[2]
|
|
77
77
|
if (typeof configPath !== 'string' || configPath.length === 0) {
|
|
78
|
-
return emitError('
|
|
78
|
+
return emitError('Caminho do config ausente (argv[2])')
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const configAbs = path.resolve(configPath)
|
package/bin/lib/gen.mjs
CHANGED
|
@@ -16,17 +16,13 @@
|
|
|
16
16
|
* - config: ./opus.config.ts (no cwd)
|
|
17
17
|
* - output: o que o config disser, ou ./.gen
|
|
18
18
|
*
|
|
19
|
-
* Flag `--force`:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* flag pra paridade com `add`).
|
|
19
|
+
* Flag `--force`: aceita por compatibilidade, sem efeito — o gen sempre
|
|
20
|
+
* sobrescreve (é idempotente e o output mora num dir dedicado, então não
|
|
21
|
+
* há valor real em bloquear a escrita).
|
|
23
22
|
*/
|
|
24
23
|
|
|
25
|
-
import { promises as fs } from 'node:fs'
|
|
26
24
|
import path from 'node:path'
|
|
27
25
|
import { fileURLToPath } from 'node:url'
|
|
28
|
-
import { execFile } from 'node:child_process'
|
|
29
|
-
import { promisify } from 'node:util'
|
|
30
26
|
import {
|
|
31
27
|
canonicalProjectDirectory,
|
|
32
28
|
ensureProjectDirectory,
|
|
@@ -42,46 +38,25 @@ import { buildOpenAPI } from './gen-openapi.mjs'
|
|
|
42
38
|
import { buildDocs } from './gen-docs.mjs'
|
|
43
39
|
import { buildClientStubs } from './gen-stubs.mjs'
|
|
44
40
|
import { buildDictStubs } from './gen-dicts.mjs'
|
|
45
|
-
|
|
46
|
-
const execFileAsync = promisify(execFile)
|
|
41
|
+
import { lastJsonLine, log, resolveConfig, spawnTsx } from './cli-shared.mjs'
|
|
47
42
|
|
|
48
43
|
const __filename = fileURLToPath(import.meta.url)
|
|
49
44
|
const __dirname = path.dirname(__filename)
|
|
50
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
51
45
|
const RUNNER_PATH = path.join(__dirname, 'gen-runner.mjs')
|
|
52
46
|
|
|
53
|
-
const COLORS = {
|
|
54
|
-
info: '\x1b[36m',
|
|
55
|
-
success: '\x1b[32m',
|
|
56
|
-
error: '\x1b[31m',
|
|
57
|
-
warn: '\x1b[33m',
|
|
58
|
-
dim: '\x1b[2m',
|
|
59
|
-
}
|
|
60
|
-
const RESET = '\x1b[0m'
|
|
61
|
-
|
|
62
|
-
function log(level, msg) {
|
|
63
|
-
console.log(`${COLORS[level] ?? ''}${msg}${RESET}`)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
47
|
// =============================================================================
|
|
67
48
|
// Comando
|
|
68
49
|
// =============================================================================
|
|
69
50
|
|
|
70
51
|
export async function cmdGen(flags) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!config.exists) {
|
|
77
|
-
log('error', `opus.config.ts não encontrado em ${configPath}`)
|
|
78
|
-
log(
|
|
79
|
-
'dim',
|
|
80
|
-
' Cria um arquivo na raiz do projeto exportando `{ domains: [...] }` como default,',
|
|
81
|
-
)
|
|
82
|
-
log('dim', ' ou passa o path via --config <path>.')
|
|
52
|
+
let resolved
|
|
53
|
+
try {
|
|
54
|
+
resolved = resolveConfig(flags)
|
|
55
|
+
} catch (error) {
|
|
56
|
+
log('error', error instanceof Error ? error.message : String(error))
|
|
83
57
|
process.exit(1)
|
|
84
58
|
}
|
|
59
|
+
const { cwd, configPath } = resolved
|
|
85
60
|
|
|
86
61
|
log('info', `→ Carregando ${path.relative(cwd, configPath)}...`)
|
|
87
62
|
|
|
@@ -190,22 +165,12 @@ function auditDocs(manifest, cwd) {
|
|
|
190
165
|
// =============================================================================
|
|
191
166
|
|
|
192
167
|
async function loadConfig(configPath) {
|
|
193
|
-
// tsx
|
|
194
|
-
//
|
|
195
|
-
const tsxBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', 'tsx')
|
|
196
|
-
const hasLocalTsx = await fileExists(tsxBin)
|
|
197
|
-
|
|
198
|
-
// Args do runner: <runner.mjs> <configPath>. tsx aceita .mjs também
|
|
199
|
-
// (passthrough) e nos permite resolver TS na cadeia de imports.
|
|
200
|
-
const args = [RUNNER_PATH, configPath]
|
|
201
|
-
const cmd = hasLocalTsx ? tsxBin : 'tsx'
|
|
202
|
-
|
|
168
|
+
// tsx aceita .mjs também (passthrough) e nos permite resolver TS na cadeia
|
|
169
|
+
// de imports do config.
|
|
203
170
|
try {
|
|
204
171
|
// Buffer grande pra acomodar manifests gordos.
|
|
205
|
-
const { stdout, stderr } = await
|
|
206
|
-
encoding: 'utf8',
|
|
172
|
+
const { stdout, stderr } = await spawnTsx(RUNNER_PATH, [configPath], {
|
|
207
173
|
maxBuffer: 64 * 1024 * 1024,
|
|
208
|
-
env: { ...process.env },
|
|
209
174
|
})
|
|
210
175
|
|
|
211
176
|
// Runner emite a linha JSON na ÚLTIMA linha de stdout. Qualquer log do
|
|
@@ -236,30 +201,10 @@ async function loadConfig(configPath) {
|
|
|
236
201
|
}
|
|
237
202
|
}
|
|
238
203
|
|
|
239
|
-
function lastJsonLine(stdout) {
|
|
240
|
-
const lines = stdout.split('\n').filter((l) => l.trim().length > 0)
|
|
241
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
242
|
-
const trimmed = lines[i].trim()
|
|
243
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
244
|
-
return trimmed
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return null
|
|
248
|
-
}
|
|
249
|
-
|
|
250
204
|
// =============================================================================
|
|
251
205
|
// FS helpers
|
|
252
206
|
// =============================================================================
|
|
253
207
|
|
|
254
|
-
async function fileExists(p) {
|
|
255
|
-
try {
|
|
256
|
-
await fs.access(p)
|
|
257
|
-
return true
|
|
258
|
-
} catch {
|
|
259
|
-
return false
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
208
|
function ensureProjectTree(root, requested) {
|
|
264
209
|
let cursor = '.'
|
|
265
210
|
for (const segment of requested.split(path.sep).filter((item) => item !== '' && item !== '.')) {
|
|
@@ -332,7 +277,7 @@ Lê opus.config.ts do consumer e gera:
|
|
|
332
277
|
Flags:
|
|
333
278
|
--config <path> Caminho interno ao projeto para opus.config.ts. Default: ./opus.config.ts
|
|
334
279
|
--output <path> Pasta interna ao projeto. Default: do config (ou ./.gen)
|
|
335
|
-
--force, -f
|
|
280
|
+
--force, -f Aceita por compatibilidade; sem efeito, o gen sempre sobrescreve a saída
|
|
336
281
|
|
|
337
282
|
Exemplos:
|
|
338
283
|
npx @softize/opus gen
|
package/bin/lib/mcp.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* opus_get_component — detalhe de um componente pelo nome
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { createRequire } from 'node:module'
|
|
14
15
|
import path from 'node:path'
|
|
15
16
|
import { z } from 'zod'
|
|
16
17
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
@@ -23,10 +24,11 @@ const asText = (data) => ({
|
|
|
23
24
|
content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }],
|
|
24
25
|
})
|
|
25
26
|
const dirOf = (d) => path.resolve(process.cwd(), d ?? '.')
|
|
27
|
+
const { version: OPUS_VERSION } = createRequire(import.meta.url)('../../package.json')
|
|
26
28
|
|
|
27
29
|
/** Monta o McpServer com as tools da base. Exportado pra testar (transport in-memory). */
|
|
28
30
|
export function buildServer() {
|
|
29
|
-
const server = new McpServer({ name: 'opus', version:
|
|
31
|
+
const server = new McpServer({ name: 'opus', version: OPUS_VERSION })
|
|
30
32
|
|
|
31
33
|
server.registerTool(
|
|
32
34
|
'opus_introspect',
|
package/bin/lib/seed.mjs
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
/** Grupo `opus seed`: descoberta, gate e execução segura dos seeds do projeto. */
|
|
2
2
|
|
|
3
|
-
import { execFile } from 'node:child_process'
|
|
4
|
-
import { promises as fs } from 'node:fs'
|
|
5
3
|
import path from 'node:path'
|
|
6
4
|
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import { promisify } from 'node:util'
|
|
8
5
|
|
|
9
|
-
import {
|
|
6
|
+
import { readProjectFile } from '@softize/base/project-path'
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
-
const __dirname = path.dirname(__filename)
|
|
14
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
8
|
+
import { log, resolveConfig, runJsonRunner } from './cli-shared.mjs'
|
|
15
9
|
|
|
16
|
-
const
|
|
17
|
-
info: '\x1b[36m',
|
|
18
|
-
success: '\x1b[32m',
|
|
19
|
-
error: '\x1b[31m',
|
|
20
|
-
warn: '\x1b[33m',
|
|
21
|
-
dim: '\x1b[2m',
|
|
22
|
-
}
|
|
23
|
-
const RESET = '\x1b[0m'
|
|
24
|
-
|
|
25
|
-
function log(level, message) {
|
|
26
|
-
console.log(`${COLORS[level] ?? ''}${message}${RESET}`)
|
|
27
|
-
}
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
28
11
|
|
|
29
12
|
export function findAdHocSeedScripts(scripts = {}) {
|
|
30
13
|
return Object.entries(scripts).flatMap(([name, command]) => {
|
|
@@ -116,14 +99,6 @@ function fail(flags, message) {
|
|
|
116
99
|
process.exitCode = 1
|
|
117
100
|
}
|
|
118
101
|
|
|
119
|
-
function resolveConfig(flags) {
|
|
120
|
-
const cwd = canonicalProjectDirectory(process.cwd())
|
|
121
|
-
const configRel = flags.config ?? 'opus.config.ts'
|
|
122
|
-
const config = safeProjectPath(cwd, configRel)
|
|
123
|
-
if (!config.exists) throw new Error(`opus.config.ts não encontrado em ${config.path}`)
|
|
124
|
-
return { cwd, configPath: config.path }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
102
|
function readSeedScriptFindings(cwd) {
|
|
128
103
|
const packageFile = readProjectFile(cwd, 'package.json', { allowMissing: true })
|
|
129
104
|
if (!packageFile.exists) return []
|
|
@@ -140,22 +115,8 @@ function readSeedScriptFindings(cwd) {
|
|
|
140
115
|
}))
|
|
141
116
|
}
|
|
142
117
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const command = (await fileExists(tsxBin)) ? tsxBin : 'tsx'
|
|
146
|
-
const runnerPath = path.join(__dirname, 'seed-runner.mjs')
|
|
147
|
-
try {
|
|
148
|
-
const { stdout } = await execFileAsync(command, [runnerPath, configPath, ...extraArgs], {
|
|
149
|
-
encoding: 'utf8',
|
|
150
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
151
|
-
env: { ...process.env },
|
|
152
|
-
})
|
|
153
|
-
const line = lastJsonLine(stdout)
|
|
154
|
-
return line === null ? { ok: false, error: 'runner não emitiu JSON' } : JSON.parse(line)
|
|
155
|
-
} catch (error) {
|
|
156
|
-
const detail = typeof error.stderr === 'string' && error.stderr.trim() ? error.stderr.trim() : error.message
|
|
157
|
-
return { ok: false, error: detail }
|
|
158
|
-
}
|
|
118
|
+
function runRunner(configPath, extraArgs) {
|
|
119
|
+
return runJsonRunner(path.join(__dirname, 'seed-runner.mjs'), [configPath, ...extraArgs])
|
|
159
120
|
}
|
|
160
121
|
|
|
161
122
|
function printList(seeds) {
|
|
@@ -188,24 +149,6 @@ function printDiagnostics(diagnostics) {
|
|
|
188
149
|
for (const diagnostic of diagnostics) log('warn', `[${diagnostic.code}] ${diagnostic.message}`)
|
|
189
150
|
}
|
|
190
151
|
|
|
191
|
-
function lastJsonLine(stdout) {
|
|
192
|
-
const lines = stdout.split('\n').filter((line) => line.trim())
|
|
193
|
-
for (let index = lines.length - 1; index >= 0; index--) {
|
|
194
|
-
const line = lines[index].trim()
|
|
195
|
-
if (line.startsWith('{') && line.endsWith('}')) return line
|
|
196
|
-
}
|
|
197
|
-
return null
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async function fileExists(file) {
|
|
201
|
-
try {
|
|
202
|
-
await fs.access(file)
|
|
203
|
-
return true
|
|
204
|
-
} catch {
|
|
205
|
-
return false
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
152
|
export function helpSeed() {
|
|
210
153
|
console.log(`
|
|
211
154
|
@softize/opus seed — datasets estruturados do projeto
|