@neetru/cli 2.7.5 → 2.9.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.
- package/CHANGELOG.md +316 -220
- package/README.md +137 -137
- package/dist/cli-kit/format.d.ts +49 -0
- package/dist/cli-kit/format.js +88 -0
- package/dist/cli-kit/format.js.map +1 -0
- package/dist/cli-kit/glyphs.d.ts +22 -0
- package/dist/cli-kit/glyphs.js +22 -0
- package/dist/cli-kit/glyphs.js.map +1 -0
- package/dist/cli-kit/index.d.ts +13 -0
- package/dist/cli-kit/index.js +12 -0
- package/dist/cli-kit/index.js.map +1 -0
- package/dist/cli-kit/palette.d.ts +10 -0
- package/dist/cli-kit/palette.js +36 -0
- package/dist/cli-kit/palette.js.map +1 -0
- package/dist/commands/ai.js +8 -8
- package/dist/commands/autocomplete.js +34 -34
- package/dist/commands/bug.d.ts +87 -0
- package/dist/commands/bug.js +419 -0
- package/dist/commands/bug.js.map +1 -0
- package/dist/commands/customers.d.ts +17 -0
- package/dist/commands/customers.js +160 -0
- package/dist/commands/customers.js.map +1 -0
- package/dist/commands/db.d.ts +91 -7
- package/dist/commands/db.js +898 -123
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.js +68 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +68 -0
- package/dist/commands/dev.js +345 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.js +99 -7
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +121 -121
- package/dist/commands/marketplace.d.ts +36 -0
- package/dist/commands/marketplace.js +584 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/commands/new.d.ts +6 -0
- package/dist/commands/new.js +220 -40
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/open.d.ts +8 -0
- package/dist/commands/open.js +61 -13
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/products-db.d.ts +1 -1
- package/dist/commands/products-db.js +17 -4
- package/dist/commands/products-db.js.map +1 -1
- package/dist/commands/products.d.ts +23 -0
- package/dist/commands/products.js +39 -1
- package/dist/commands/products.js.map +1 -1
- package/dist/commands/tenants.js +15 -0
- package/dist/commands/tenants.js.map +1 -1
- package/dist/commands/ui.d.ts +1 -1
- package/dist/commands/ui.js +172 -2
- package/dist/commands/ui.js.map +1 -1
- package/dist/commands/workspaces.d.ts +10 -1
- package/dist/commands/workspaces.js +136 -22
- package/dist/commands/workspaces.js.map +1 -1
- package/dist/index.js +532 -44
- package/dist/index.js.map +1 -1
- package/dist/lib/ai/context.js +90 -90
- package/dist/lib/config-schema.d.ts +8 -8
- package/dist/lib/db-local/db-json.d.ts +63 -0
- package/dist/lib/db-local/db-json.js +189 -0
- package/dist/lib/db-local/db-json.js.map +1 -0
- package/dist/lib/db-local/env.d.ts +26 -0
- package/dist/lib/db-local/env.js +64 -0
- package/dist/lib/db-local/env.js.map +1 -0
- package/dist/lib/db-local/fingerprint.d.ts +8 -0
- package/dist/lib/db-local/fingerprint.js +28 -0
- package/dist/lib/db-local/fingerprint.js.map +1 -0
- package/dist/lib/db-local/index.d.ts +15 -0
- package/dist/lib/db-local/index.js +14 -0
- package/dist/lib/db-local/index.js.map +1 -0
- package/dist/lib/db-pipeline/build-deps.d.ts +14 -0
- package/dist/lib/db-pipeline/build-deps.js +158 -0
- package/dist/lib/db-pipeline/build-deps.js.map +1 -0
- package/dist/lib/db-pipeline/errors.d.ts +29 -0
- package/dist/lib/db-pipeline/errors.js +29 -0
- package/dist/lib/db-pipeline/errors.js.map +1 -0
- package/dist/lib/db-pipeline/index.d.ts +26 -0
- package/dist/lib/db-pipeline/index.js +25 -0
- package/dist/lib/db-pipeline/index.js.map +1 -0
- package/dist/lib/db-pipeline/pipeline.d.ts +13 -0
- package/dist/lib/db-pipeline/pipeline.js +119 -0
- package/dist/lib/db-pipeline/pipeline.js.map +1 -0
- package/dist/lib/db-pipeline/rehearse.d.ts +99 -0
- package/dist/lib/db-pipeline/rehearse.js +219 -0
- package/dist/lib/db-pipeline/rehearse.js.map +1 -0
- package/dist/lib/db-pipeline/types.d.ts +112 -0
- package/dist/lib/db-pipeline/types.js +20 -0
- package/dist/lib/db-pipeline/types.js.map +1 -0
- package/dist/lib/pickers.d.ts +12 -0
- package/dist/lib/pickers.js +34 -0
- package/dist/lib/pickers.js.map +1 -1
- package/package.json +66 -62
- package/templates/auth/callback.ts +22 -22
- package/templates/auth/sign-in.tsx +41 -41
- package/templates/billing/checkout.ts +22 -22
- package/templates/billing/page.tsx +43 -43
- package/templates/support/ticket-form.tsx +68 -68
- package/templates/usage/track.ts +30 -30
- package/templates/users/profile.tsx +43 -43
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — contrato do registry local `.neetru/db.json`.
|
|
3
|
+
*
|
|
4
|
+
* O `.neetru/db.json` é o registry per-repo que mapeia bancos lógicos do
|
|
5
|
+
* produto (`name`) → IDs reais de banco por ambiente. Este módulo é a fonte
|
|
6
|
+
* única de verdade do formato: tipos, migração, leitura/escrita atômica e
|
|
7
|
+
* resolução de entrada.
|
|
8
|
+
*
|
|
9
|
+
* Sem dependências externas — só builtins do Node.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { randomBytes } from 'node:crypto';
|
|
14
|
+
/** Registry v2 vazio — usado quando o arquivo ainda não existe. */
|
|
15
|
+
function emptyDbJson() {
|
|
16
|
+
return { version: 2, databases: [] };
|
|
17
|
+
}
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// migrateDbJson — qualquer entrada → v2 canônico
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const VALID_ENV_KEYS = ['dev-local', 'staging', 'production'];
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
/** Coerção defensiva: extrai os 3 IDs canônicos de um `ids` arbitrário. */
|
|
26
|
+
function coerceIds(raw) {
|
|
27
|
+
const ids = {};
|
|
28
|
+
if (!isPlainObject(raw))
|
|
29
|
+
return ids;
|
|
30
|
+
for (const key of VALID_ENV_KEYS) {
|
|
31
|
+
const value = raw[key];
|
|
32
|
+
if (typeof value === 'string')
|
|
33
|
+
ids[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return ids;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Coerce uma entrada de banco arbitrária para `DbJsonDatabase`.
|
|
39
|
+
* Retorna `null` quando a entrada não tem `name` string não-vazio (descartável).
|
|
40
|
+
*/
|
|
41
|
+
function coerceDatabase(raw) {
|
|
42
|
+
if (!isPlainObject(raw))
|
|
43
|
+
return null;
|
|
44
|
+
const name = raw.name;
|
|
45
|
+
if (typeof name !== 'string' || name.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
const entry = {
|
|
48
|
+
name,
|
|
49
|
+
productId: typeof raw.productId === 'string' ? raw.productId : '',
|
|
50
|
+
engine: typeof raw.engine === 'string' ? raw.engine : '',
|
|
51
|
+
ids: coerceIds(raw.ids),
|
|
52
|
+
};
|
|
53
|
+
// Preserva lastAppliedFingerprint quando presente e válido (64 chars hex SHA-256).
|
|
54
|
+
if (typeof raw.lastAppliedFingerprint === 'string') {
|
|
55
|
+
entry.lastAppliedFingerprint = raw.lastAppliedFingerprint;
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
/** Reduz um array arbitrário a entradas de banco válidas. */
|
|
60
|
+
function coerceDatabaseArray(raw) {
|
|
61
|
+
if (!Array.isArray(raw))
|
|
62
|
+
return [];
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const entry of raw) {
|
|
65
|
+
const db = coerceDatabase(entry);
|
|
66
|
+
if (db)
|
|
67
|
+
out.push(db);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Migra qualquer entrada para um `DbJson` v2 estruturalmente válido.
|
|
73
|
+
*
|
|
74
|
+
* - v2 válido → normalizado (entradas malformadas reparadas ou descartadas).
|
|
75
|
+
* - v1 ou objeto sem `version` → upgrade best-effort.
|
|
76
|
+
* - `null` / não-objeto / array / garbage → lança.
|
|
77
|
+
*
|
|
78
|
+
* NUNCA retorna algo que não seja um `DbJson` v2 válido.
|
|
79
|
+
*/
|
|
80
|
+
export function migrateDbJson(raw) {
|
|
81
|
+
if (!isPlainObject(raw)) {
|
|
82
|
+
throw new Error(`migrateDbJson: conteúdo inválido — esperava um objeto, recebeu ` +
|
|
83
|
+
`${raw === null ? 'null' : Array.isArray(raw) ? 'array' : typeof raw}.`);
|
|
84
|
+
}
|
|
85
|
+
const version = raw.version;
|
|
86
|
+
// v2 explícito → normaliza as entradas.
|
|
87
|
+
if (version === 2) {
|
|
88
|
+
return { version: 2, databases: coerceDatabaseArray(raw.databases) };
|
|
89
|
+
}
|
|
90
|
+
// v1 ou objeto sem version → upgrade best-effort.
|
|
91
|
+
if (version === 1 || version === undefined) {
|
|
92
|
+
return { version: 2, databases: coerceDatabaseArray(raw.databases) };
|
|
93
|
+
}
|
|
94
|
+
// version presente mas desconhecida (ex.: 3, 'x') → não sabemos migrar.
|
|
95
|
+
throw new Error(`migrateDbJson: versão de registry não suportada (${JSON.stringify(version)}). ` +
|
|
96
|
+
`Versões aceitas: 1, 2 ou ausente.`);
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// readDbJson / writeDbJson
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Lê + parseia + migra um `.neetru/db.json`.
|
|
103
|
+
*
|
|
104
|
+
* Arquivo ausente → registry v2 vazio (não é erro — é um registry novo).
|
|
105
|
+
* JSON inválido → lança um `Error` nomeando o path.
|
|
106
|
+
*/
|
|
107
|
+
export function readDbJson(filePath) {
|
|
108
|
+
let raw;
|
|
109
|
+
try {
|
|
110
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err.code === 'ENOENT') {
|
|
114
|
+
return emptyDbJson();
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(raw);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
throw new Error(`readDbJson: JSON inválido em ${filePath} — ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
return migrateDbJson(parsed);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Escreve um `DbJson` de forma ATÔMICA.
|
|
129
|
+
*
|
|
130
|
+
* Estratégia: escreve num arquivo temporário no MESMO diretório do destino
|
|
131
|
+
* (rename só é atômico dentro do mesmo filesystem) e faz `renameSync` —
|
|
132
|
+
* que sobrescreve o arquivo existente tanto no Windows quanto no POSIX.
|
|
133
|
+
*
|
|
134
|
+
* Cria o diretório pai se ausente. Se algo falhar depois do temp ser criado,
|
|
135
|
+
* tenta remover o temp (best-effort) pra não deixar lixo `.tmp-*`.
|
|
136
|
+
*/
|
|
137
|
+
export function writeDbJson(filePath, data) {
|
|
138
|
+
const dir = path.dirname(filePath);
|
|
139
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
140
|
+
const tmpPath = path.join(dir, `${path.basename(filePath)}.tmp-${process.pid}-${randomBytes(6).toString('hex')}`);
|
|
141
|
+
const payload = JSON.stringify(data, null, 2) + '\n';
|
|
142
|
+
try {
|
|
143
|
+
fs.writeFileSync(tmpPath, payload, 'utf8');
|
|
144
|
+
fs.renameSync(tmpPath, filePath);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
// Best-effort: remove o temp pra não deixar lixo se algo falhou.
|
|
148
|
+
try {
|
|
149
|
+
fs.rmSync(tmpPath, { force: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignora — limpeza best-effort */
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// resolveDbEntry
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Resolve uma entrada de banco do registry.
|
|
162
|
+
*
|
|
163
|
+
* - Com `name` → busca por nome exato. Não encontrado → lança listando os
|
|
164
|
+
* nomes disponíveis.
|
|
165
|
+
* - Sem `name` → retorna a única entrada se houver exatamente 1; caso
|
|
166
|
+
* contrário lança (0 ou >1 é ambíguo — o caller precisa passar um nome).
|
|
167
|
+
*/
|
|
168
|
+
export function resolveDbEntry(dbJson, name) {
|
|
169
|
+
const databases = dbJson.databases;
|
|
170
|
+
const available = databases.map((d) => d.name);
|
|
171
|
+
if (name !== undefined) {
|
|
172
|
+
const found = databases.find((d) => d.name === name);
|
|
173
|
+
if (!found) {
|
|
174
|
+
throw new Error(`resolveDbEntry: banco "${name}" não encontrado no .neetru/db.json. ` +
|
|
175
|
+
`Bancos disponíveis: ${available.length ? available.join(', ') : '(nenhum)'}.`);
|
|
176
|
+
}
|
|
177
|
+
return found;
|
|
178
|
+
}
|
|
179
|
+
if (databases.length === 1) {
|
|
180
|
+
return databases[0];
|
|
181
|
+
}
|
|
182
|
+
if (databases.length === 0) {
|
|
183
|
+
throw new Error(`resolveDbEntry: nenhum banco registrado no .neetru/db.json. ` +
|
|
184
|
+
`Registre um banco antes de continuar.`);
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`resolveDbEntry: múltiplos bancos no .neetru/db.json — informe qual usar. ` +
|
|
187
|
+
`Bancos disponíveis: ${available.join(', ')}.`);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=db-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-json.js","sourceRoot":"","sources":["../../../src/lib/db-local/db-json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — normalização de ambiente.
|
|
3
|
+
*
|
|
4
|
+
* O `.neetru/db.json` persiste IDs de banco por 3 ambientes canônicos:
|
|
5
|
+
* `dev-local`, `staging`, `production`. A CLI aceita atalhos de digitação
|
|
6
|
+
* (`dev`, `prod`, `stg`, …) — esta função colapsa tudo num dos 3 valores.
|
|
7
|
+
*
|
|
8
|
+
* REGRA DURA (D-4): `dev-local` é o valor CANÔNICO e VISÍVEL. `dev` é um
|
|
9
|
+
* alias EXPLÍCITO e DOCUMENTADO — esta função imprime aviso no stderr quando
|
|
10
|
+
* um alias não-canônico é usado, para que o usuário veja o mapeamento e
|
|
11
|
+
* atualize seus scripts/config. NUNCA um rewrite silencioso.
|
|
12
|
+
*
|
|
13
|
+
* Aliases que emitem aviso: dev, local (→ dev-local), stg (→ staging), prod (→ production).
|
|
14
|
+
*/
|
|
15
|
+
/** Os 3 ambientes canônicos persistidos no `.neetru/db.json`. */
|
|
16
|
+
export type NeetruEnv = 'dev-local' | 'staging' | 'production';
|
|
17
|
+
/**
|
|
18
|
+
* Normaliza um nome de ambiente para um dos 3 valores canônicos.
|
|
19
|
+
*
|
|
20
|
+
* D-4: se o input é um alias não-canônico (ex: "dev"), imprime aviso explícito
|
|
21
|
+
* no stderr listando o valor canônico. Nunca normaliza silenciosamente.
|
|
22
|
+
*
|
|
23
|
+
* Faz lowercase + trim antes do lookup. Entrada não-string, vazia ou
|
|
24
|
+
* desconhecida lança um `Error` que lista os valores aceitos.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeEnv(input: string): NeetruEnv;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — normalização de ambiente.
|
|
3
|
+
*
|
|
4
|
+
* O `.neetru/db.json` persiste IDs de banco por 3 ambientes canônicos:
|
|
5
|
+
* `dev-local`, `staging`, `production`. A CLI aceita atalhos de digitação
|
|
6
|
+
* (`dev`, `prod`, `stg`, …) — esta função colapsa tudo num dos 3 valores.
|
|
7
|
+
*
|
|
8
|
+
* REGRA DURA (D-4): `dev-local` é o valor CANÔNICO e VISÍVEL. `dev` é um
|
|
9
|
+
* alias EXPLÍCITO e DOCUMENTADO — esta função imprime aviso no stderr quando
|
|
10
|
+
* um alias não-canônico é usado, para que o usuário veja o mapeamento e
|
|
11
|
+
* atualize seus scripts/config. NUNCA um rewrite silencioso.
|
|
12
|
+
*
|
|
13
|
+
* Aliases que emitem aviso: dev, local (→ dev-local), stg (→ staging), prod (→ production).
|
|
14
|
+
*/
|
|
15
|
+
/** Mapa de aliases aceitos → valor canônico. */
|
|
16
|
+
const ENV_ALIASES = {
|
|
17
|
+
dev: 'dev-local',
|
|
18
|
+
'dev-local': 'dev-local',
|
|
19
|
+
local: 'dev-local',
|
|
20
|
+
staging: 'staging',
|
|
21
|
+
stg: 'staging',
|
|
22
|
+
prod: 'production',
|
|
23
|
+
production: 'production',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Aliases que devem emitir aviso visível (D-4: sem rewrite silencioso).
|
|
27
|
+
* O alias canônico de cada grupo é o ÚNICO que não emite aviso.
|
|
28
|
+
*/
|
|
29
|
+
const ALIAS_NOTICE = {
|
|
30
|
+
dev: 'dev-local',
|
|
31
|
+
local: 'dev-local',
|
|
32
|
+
stg: 'staging',
|
|
33
|
+
prod: 'production',
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Normaliza um nome de ambiente para um dos 3 valores canônicos.
|
|
37
|
+
*
|
|
38
|
+
* D-4: se o input é um alias não-canônico (ex: "dev"), imprime aviso explícito
|
|
39
|
+
* no stderr listando o valor canônico. Nunca normaliza silenciosamente.
|
|
40
|
+
*
|
|
41
|
+
* Faz lowercase + trim antes do lookup. Entrada não-string, vazia ou
|
|
42
|
+
* desconhecida lança um `Error` que lista os valores aceitos.
|
|
43
|
+
*/
|
|
44
|
+
export function normalizeEnv(input) {
|
|
45
|
+
if (typeof input !== 'string') {
|
|
46
|
+
throw new Error(`normalizeEnv: ambiente inválido (esperava string, recebeu ${typeof input}). ` +
|
|
47
|
+
`Valores aceitos: dev-local, staging, production.`);
|
|
48
|
+
}
|
|
49
|
+
const key = input.trim().toLowerCase();
|
|
50
|
+
const canonical = ENV_ALIASES[key];
|
|
51
|
+
if (!canonical) {
|
|
52
|
+
throw new Error(`normalizeEnv: ambiente "${input}" não reconhecido. ` +
|
|
53
|
+
`Valores aceitos: dev-local, staging, production. ` +
|
|
54
|
+
`Aliases aceitos: dev/local → dev-local, stg → staging, prod → production.`);
|
|
55
|
+
}
|
|
56
|
+
// D-4: aviso explícito quando alias não-canônico é usado
|
|
57
|
+
const notice = ALIAS_NOTICE[key];
|
|
58
|
+
if (notice) {
|
|
59
|
+
process.stderr.write(`Aviso: ambiente "${input}" é um alias — o nome canônico é "${notice}". ` +
|
|
60
|
+
`Atualize seus scripts e config para usar "${notice}" diretamente.\n`);
|
|
61
|
+
}
|
|
62
|
+
return canonical;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/lib/db-local/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,gDAAgD;AAChD,MAAM,WAAW,GAA8B;IAC7C,GAAG,EAAE,WAAW;IAChB,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,WAAW;IAClB,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,YAAY;IAClB,UAAU,EAAE,YAAY;CACzB,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,WAAW;IAChB,KAAK,EAAE,WAAW;IAClB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,YAAY;CACnB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,6DAA6D,OAAO,KAAK,KAAK;YAC5E,kDAAkD,CACrD,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEnC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,2BAA2B,KAAK,qBAAqB;YACnD,mDAAmD;YACnD,2EAA2E,CAC9E,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oBAAoB,KAAK,qCAAqC,MAAM,KAAK;YACvE,6CAA6C,MAAM,kBAAkB,CACxE,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calcula o SHA-256 hex (64 chars) do conteúdo de um schema.
|
|
3
|
+
*
|
|
4
|
+
* Normaliza `\r\n` e `\r` isolado para `\n` ANTES de hashear — assim Windows
|
|
5
|
+
* e Linux produzem hashes idênticos pro mesmo schema. String vazia é válida
|
|
6
|
+
* (hasheia a string vazia). Entrada não-string lança.
|
|
7
|
+
*/
|
|
8
|
+
export declare function schemaFingerprint(content: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — fingerprint determinístico de schema.
|
|
3
|
+
*
|
|
4
|
+
* `schemaFingerprint` produz um SHA-256 estável do conteúdo de um schema,
|
|
5
|
+
* usado pra detectar drift entre o schema local e o que foi aplicado num
|
|
6
|
+
* banco provisionado.
|
|
7
|
+
*
|
|
8
|
+
* ARMADILHA REAL — line endings: sem normalizar CRLF/CR, o MESMO schema
|
|
9
|
+
* gera hashes diferentes no Windows (CRLF) e no Linux (LF). A normalização
|
|
10
|
+
* abaixo é OBRIGATÓRIA pra garantir determinismo cross-OS.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Calcula o SHA-256 hex (64 chars) do conteúdo de um schema.
|
|
15
|
+
*
|
|
16
|
+
* Normaliza `\r\n` e `\r` isolado para `\n` ANTES de hashear — assim Windows
|
|
17
|
+
* e Linux produzem hashes idênticos pro mesmo schema. String vazia é válida
|
|
18
|
+
* (hasheia a string vazia). Entrada não-string lança.
|
|
19
|
+
*/
|
|
20
|
+
export function schemaFingerprint(content) {
|
|
21
|
+
if (typeof content !== 'string') {
|
|
22
|
+
throw new Error(`schemaFingerprint: conteúdo inválido (esperava string, recebeu ${typeof content}).`);
|
|
23
|
+
}
|
|
24
|
+
// Normalização cross-OS: CRLF → LF, depois CR isolado → LF.
|
|
25
|
+
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
26
|
+
return createHash('sha256').update(normalized, 'utf8').digest('hex');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../../src/lib/db-local/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,kEAAkE,OAAO,OAAO,IAAI,CACrF,CAAC;IACJ,CAAC;IAED,4DAA4D;IAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEvE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — contrato do registry local `.neetru/db.json`.
|
|
3
|
+
*
|
|
4
|
+
* Helper do `@neetru/cli` que possui:
|
|
5
|
+
* - o formato do `.neetru/db.json` (tipos + migração + I/O atômico);
|
|
6
|
+
* - o `schemaFingerprint` determinístico cross-OS;
|
|
7
|
+
* - o `normalizeEnv` (aliases de ambiente → 3 valores canônicos).
|
|
8
|
+
*
|
|
9
|
+
* Zero dependências externas — só builtins do Node.
|
|
10
|
+
*/
|
|
11
|
+
export { normalizeEnv } from './env.js';
|
|
12
|
+
export type { NeetruEnv } from './env.js';
|
|
13
|
+
export { schemaFingerprint } from './fingerprint.js';
|
|
14
|
+
export { migrateDbJson, readDbJson, writeDbJson, resolveDbEntry, } from './db-json.js';
|
|
15
|
+
export type { DbJson, DbJsonDatabase, DbJsonIds } from './db-json.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-local` — contrato do registry local `.neetru/db.json`.
|
|
3
|
+
*
|
|
4
|
+
* Helper do `@neetru/cli` que possui:
|
|
5
|
+
* - o formato do `.neetru/db.json` (tipos + migração + I/O atômico);
|
|
6
|
+
* - o `schemaFingerprint` determinístico cross-OS;
|
|
7
|
+
* - o `normalizeEnv` (aliases de ambiente → 3 valores canônicos).
|
|
8
|
+
*
|
|
9
|
+
* Zero dependências externas — só builtins do Node.
|
|
10
|
+
*/
|
|
11
|
+
export { normalizeEnv } from './env.js';
|
|
12
|
+
export { schemaFingerprint } from './fingerprint.js';
|
|
13
|
+
export { migrateDbJson, readDbJson, writeDbJson, resolveDbEntry, } from './db-json.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/db-local/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,GACf,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PipelineDeps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Constrói o conjunto de `PipelineDeps` de PRODUÇÃO.
|
|
4
|
+
*
|
|
5
|
+
* `runApplyPipeline(input, buildPipelineDeps())` roda o pipeline real completo:
|
|
6
|
+
* - `rehearseInContainer` usa Docker real (`buildRehearseInContainer(buildDockerExecFn())`).
|
|
7
|
+
* - Docker ausente → lança `RehearseDockerUnavailableError` (fail-closed).
|
|
8
|
+
*
|
|
9
|
+
* Para testes: injete deps mockadas em vez de chamar isto.
|
|
10
|
+
*
|
|
11
|
+
* `overrides` permite sobrescrever deps individuais (ex.: `pushToCore` em
|
|
12
|
+
* `--dry-run`, ou `onPhase` para ligar num spinner do `ora`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildPipelineDeps(overrides?: Partial<PipelineDeps>): PipelineDeps;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-pipeline` — fiação de produção das `PipelineDeps`.
|
|
3
|
+
*
|
|
4
|
+
* `runApplyPipeline` (em `./pipeline.js`) é deps-injetado: ele não sabe nada
|
|
5
|
+
* de drizzle-kit, Docker ou HTTP. `buildPipelineDeps()` é onde os efeitos
|
|
6
|
+
* colaterais REAIS são fiados:
|
|
7
|
+
*
|
|
8
|
+
* - readSchemaFile → `fs.readFileSync` (builtin).
|
|
9
|
+
* - generateSql → `drizzle-kit generate` via `child_process` (best-effort).
|
|
10
|
+
* - rehearseInContainer → Postgres efêmero via Docker real.
|
|
11
|
+
* `buildRehearseInContainer(buildDockerExecFn())`.
|
|
12
|
+
* Lança `RehearseDockerUnavailableError` quando Docker
|
|
13
|
+
* ausente — fail-closed, NUNCA retorna `{ ok: true }`
|
|
14
|
+
* sem fazer o trabalho.
|
|
15
|
+
* - classify → `@neetru/db-classifier` (`classifyMigration`).
|
|
16
|
+
* - pushToCore → `POST /api/cli/v1/db/apply` via `api-client`.
|
|
17
|
+
*
|
|
18
|
+
* `buildPipelineDeps` aceita `overrides: Partial<PipelineDeps>` — o caller
|
|
19
|
+
* pode substituir qualquer dep (ex.: `pushToCore` em `--dry-run`, ou
|
|
20
|
+
* `rehearseInContainer` nos testes).
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from 'node:fs';
|
|
23
|
+
import { spawn } from 'node:child_process';
|
|
24
|
+
import { classifyMigration } from '@neetru/db-classifier';
|
|
25
|
+
import { apiRequest } from '../api-client.js';
|
|
26
|
+
import { buildRehearseInContainer, buildDockerExecFn, } from './rehearse.js';
|
|
27
|
+
/**
|
|
28
|
+
* Prova em COMPILE-TIME que `classifyMigration` do `@neetru/db-classifier` é
|
|
29
|
+
* estruturalmente assinável ao tipo da dep `classify` que o pipeline espera
|
|
30
|
+
* (`PipelineDeps['classify']`).
|
|
31
|
+
*
|
|
32
|
+
* Se a API do `@neetru/db-classifier` algum dia divergir do que o pipeline
|
|
33
|
+
* assume (ex.: `ClassificationReport` perde `requiresConfirmation`, renomeia
|
|
34
|
+
* `overallSeverity`, ou muda a assinatura de `classifyMigration`), este
|
|
35
|
+
* `satisfies` faz o `tsc` FALHAR aqui — em vez de só explodir em runtime.
|
|
36
|
+
*
|
|
37
|
+
* O `classify` exportado abaixo é um adaptador fino que extrai exatamente os 3
|
|
38
|
+
* campos do contrato local (`overallSeverity` / `requiresConfirmation` /
|
|
39
|
+
* `statements`); este guard cobre o classifier upstream na raiz.
|
|
40
|
+
*/
|
|
41
|
+
const _classifierApiContract = classifyMigration;
|
|
42
|
+
void _classifierApiContract;
|
|
43
|
+
/**
|
|
44
|
+
* Lê um arquivo de schema do disco. Erros (arquivo ausente, permissão)
|
|
45
|
+
* propagam — o orquestrador os encadeia num `PipelineError` da fase
|
|
46
|
+
* `fingerprint`.
|
|
47
|
+
*/
|
|
48
|
+
function readSchemaFile(path) {
|
|
49
|
+
return fs.readFileSync(path, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Roda `drizzle-kit generate` e captura o `.sql` gerado.
|
|
53
|
+
*
|
|
54
|
+
* Best-effort: invoca `npx drizzle-kit generate` no diretório do projeto. O
|
|
55
|
+
* drizzle-kit escreve o `.sql` no diretório de migrations configurado; aqui
|
|
56
|
+
* apenas capturamos stdout pra detectar falhas de processo. Quando o
|
|
57
|
+
* `drizzle-kit` não está instalado, o `spawn` falha e o erro propaga (o
|
|
58
|
+
* orquestrador o transforma num `PipelineError` da fase `generate`).
|
|
59
|
+
*
|
|
60
|
+
* NOTA: a leitura do `.sql` final do diretório de migrations depende da
|
|
61
|
+
* config `drizzle.config.ts` do produto — essa resolução de path fica como
|
|
62
|
+
* follow-up. Por ora capturamos o stdout do comando.
|
|
63
|
+
*/
|
|
64
|
+
function generateSql(schemaPath) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const child = spawn('npx', ['drizzle-kit', 'generate'], {
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
env: { ...process.env, NEETRU_DB_SCHEMA: schemaPath },
|
|
69
|
+
shell: process.platform === 'win32',
|
|
70
|
+
});
|
|
71
|
+
let stdout = '';
|
|
72
|
+
let stderr = '';
|
|
73
|
+
child.stdout?.on('data', (chunk) => {
|
|
74
|
+
stdout += String(chunk);
|
|
75
|
+
});
|
|
76
|
+
child.stderr?.on('data', (chunk) => {
|
|
77
|
+
stderr += String(chunk);
|
|
78
|
+
});
|
|
79
|
+
child.on('error', (err) => {
|
|
80
|
+
reject(new Error(`não foi possível executar drizzle-kit generate: ${err.message}`));
|
|
81
|
+
});
|
|
82
|
+
child.on('exit', (code) => {
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
resolve(stdout);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
reject(new Error(`drizzle-kit generate falhou (exit ${code ?? '?'})` +
|
|
88
|
+
`${stderr.trim() ? `: ${stderr.trim()}` : '.'}`));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Ensaia o `.sql` num Postgres efêmero via Docker CLI real.
|
|
95
|
+
*
|
|
96
|
+
* Construída com `buildRehearseInContainer(buildDockerExecFn())`:
|
|
97
|
+
* - Verifica disponibilidade do Docker (`docker version`).
|
|
98
|
+
* - Sobe `postgres:16-alpine` efêmero, aguarda `pg_isready`.
|
|
99
|
+
* - Aplica o `.sql` via `docker exec psql --single-transaction`.
|
|
100
|
+
* - Derruba o container no `finally`.
|
|
101
|
+
*
|
|
102
|
+
* Lança `RehearseDockerUnavailableError` quando Docker indisponível — nunca
|
|
103
|
+
* retorna `{ ok: true }` sem fazer o trabalho.
|
|
104
|
+
*/
|
|
105
|
+
const rehearseInContainer = buildRehearseInContainer(buildDockerExecFn());
|
|
106
|
+
/**
|
|
107
|
+
* Classifica o `.sql` em aditiva/destrutiva via `@neetru/db-classifier`.
|
|
108
|
+
* Função pura — sem I/O. O shape do `ClassificationReport` do classifier é
|
|
109
|
+
* estruturalmente compatível com `ClassificationResult`.
|
|
110
|
+
*/
|
|
111
|
+
function classify(sql) {
|
|
112
|
+
const report = classifyMigration(sql);
|
|
113
|
+
return {
|
|
114
|
+
overallSeverity: report.overallSeverity,
|
|
115
|
+
requiresConfirmation: report.requiresConfirmation,
|
|
116
|
+
statements: report.statements,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Adquire o lock e empurra o artefato validado pro Core via
|
|
121
|
+
* `POST /api/cli/v1/db/apply`. O Core registra a migração, aplica o lock e
|
|
122
|
+
* devolve `{ migrationId, status }`. Erros de API/rede propagam — o
|
|
123
|
+
* orquestrador os encadeia num `PipelineError` da fase `push`.
|
|
124
|
+
*
|
|
125
|
+
* GAP-M1-1 fix: path era `/cli/v1/db/apply` (sem o prefixo `/api`) — corrigido
|
|
126
|
+
* para `/api/cli/v1/db/apply`, alinhado com o prefixo canônico de todas as
|
|
127
|
+
* rotas do control plane (vide `src/app/api/cli/v1/`).
|
|
128
|
+
*/
|
|
129
|
+
async function pushToCore(artifact) {
|
|
130
|
+
// `artifact.dbId` is required by the Core endpoint (BL-5 fix).
|
|
131
|
+
return apiRequest('/api/cli/v1/db/apply', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
body: artifact,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Constrói o conjunto de `PipelineDeps` de PRODUÇÃO.
|
|
138
|
+
*
|
|
139
|
+
* `runApplyPipeline(input, buildPipelineDeps())` roda o pipeline real completo:
|
|
140
|
+
* - `rehearseInContainer` usa Docker real (`buildRehearseInContainer(buildDockerExecFn())`).
|
|
141
|
+
* - Docker ausente → lança `RehearseDockerUnavailableError` (fail-closed).
|
|
142
|
+
*
|
|
143
|
+
* Para testes: injete deps mockadas em vez de chamar isto.
|
|
144
|
+
*
|
|
145
|
+
* `overrides` permite sobrescrever deps individuais (ex.: `pushToCore` em
|
|
146
|
+
* `--dry-run`, ou `onPhase` para ligar num spinner do `ora`).
|
|
147
|
+
*/
|
|
148
|
+
export function buildPipelineDeps(overrides = {}) {
|
|
149
|
+
return {
|
|
150
|
+
readSchemaFile,
|
|
151
|
+
generateSql,
|
|
152
|
+
rehearseInContainer,
|
|
153
|
+
classify,
|
|
154
|
+
pushToCore,
|
|
155
|
+
...overrides,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=build-deps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-deps.js","sourceRoot":"","sources":["../../../src/lib/db-pipeline/build-deps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-pipeline` — erro tipado do pipeline.
|
|
3
|
+
*
|
|
4
|
+
* `PipelineError` é lançado quando uma fase do `neetru db apply` falha de
|
|
5
|
+
* forma que NÃO pode prosseguir: o `.sql` não gera, ou a ensaiada num
|
|
6
|
+
* Postgres efêmero falha. O pipeline é fail-closed — nunca empurra pro Core
|
|
7
|
+
* um `.sql` que não passou na própria ensaiada.
|
|
8
|
+
*
|
|
9
|
+
* Carrega a fase em que falhou (`phase`) pra que o caller possa dar uma
|
|
10
|
+
* mensagem precisa ao dev.
|
|
11
|
+
*/
|
|
12
|
+
import type { PipelinePhase } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Erro de uma fase do pipeline `neetru db apply`.
|
|
15
|
+
*
|
|
16
|
+
* `phase` identifica onde quebrou. `cause` (opcional) preserva o erro
|
|
17
|
+
* original — o pipeline NUNCA engole um erro, sempre o encadeia.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PipelineError extends Error {
|
|
20
|
+
/** Marcador de tipo — sobrevive a `instanceof` cross-realm / minificação. */
|
|
21
|
+
readonly isPipelineError: true;
|
|
22
|
+
/** A fase do pipeline em que a falha ocorreu. */
|
|
23
|
+
readonly phase: PipelinePhase;
|
|
24
|
+
constructor(phase: PipelinePhase, message: string, options?: {
|
|
25
|
+
cause?: unknown;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Type guard — `true` sse `value` é um `PipelineError`. */
|
|
29
|
+
export declare function isPipelineError(value: unknown): value is PipelineError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erro de uma fase do pipeline `neetru db apply`.
|
|
3
|
+
*
|
|
4
|
+
* `phase` identifica onde quebrou. `cause` (opcional) preserva o erro
|
|
5
|
+
* original — o pipeline NUNCA engole um erro, sempre o encadeia.
|
|
6
|
+
*/
|
|
7
|
+
export class PipelineError extends Error {
|
|
8
|
+
/** Marcador de tipo — sobrevive a `instanceof` cross-realm / minificação. */
|
|
9
|
+
isPipelineError = true;
|
|
10
|
+
/** A fase do pipeline em que a falha ocorreu. */
|
|
11
|
+
phase;
|
|
12
|
+
constructor(phase, message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = 'PipelineError';
|
|
15
|
+
this.phase = phase;
|
|
16
|
+
// Mantém a stack apontando pro call site real (V8).
|
|
17
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
18
|
+
Error.captureStackTrace(this, PipelineError);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Type guard — `true` sse `value` é um `PipelineError`. */
|
|
23
|
+
export function isPipelineError(value) {
|
|
24
|
+
return (value instanceof PipelineError ||
|
|
25
|
+
(typeof value === 'object' &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
value.isPipelineError === true));
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/db-pipeline/errors.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,6EAA6E;IACpE,eAAe,GAAG,IAAa,CAAC;IAEzC,iDAAiD;IACxC,KAAK,CAAgB;IAE9B,YAAY,KAAoB,EAAE,OAAe,EAAE,OAA6B;QAC9E,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,oDAAoD;QACpD,IAAI,OAAO,KAAK,CAAC,iBAAiB,KAAK,UAAU,EAAE,CAAC;YAClD,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;CACF;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,CACL,KAAK,YAAY,aAAa;QAC9B,CAAC,OAAO,KAAK,KAAK,QAAQ;YACxB,KAAK,KAAK,IAAI;YACb,KAAuC,CAAC,eAAe,KAAK,IAAI,CAAC,CACrE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neetru/db-pipeline` — o motor client-side do `neetru db apply`.
|
|
3
|
+
*
|
|
4
|
+
* O `neetru db apply` publica uma mudança de schema de um produto. Este
|
|
5
|
+
* pacote roda, CLIENT-SIDE, as fases 1-5:
|
|
6
|
+
* 1. fingerprint — hash determinístico do schema.
|
|
7
|
+
* 2. generate — `drizzle-kit generate` produz o `.sql`.
|
|
8
|
+
* 3. rehearse — ensaia o `.sql` num Postgres efêmero real.
|
|
9
|
+
* 4. classify — `@neetru/db-classifier` marca aditiva/destrutiva.
|
|
10
|
+
* 5. push — adquire um lock + envia o artefato validado pro Core.
|
|
11
|
+
*
|
|
12
|
+
* Ele NÃO decide política — produz um `.sql` validado e classificado e o
|
|
13
|
+
* envia. A decisão de aplicar é do Core.
|
|
14
|
+
*
|
|
15
|
+
* Superfície pública:
|
|
16
|
+
* - `runApplyPipeline` — o orquestrador deps-injetado (unit-testável).
|
|
17
|
+
* - `buildPipelineDeps` — a fiação de produção (drizzle-kit / classifier / HTTP).
|
|
18
|
+
* - `PipelineError` / `isPipelineError` — o erro tipado do pipeline.
|
|
19
|
+
* - os tipos do contrato.
|
|
20
|
+
*/
|
|
21
|
+
export { runApplyPipeline } from './pipeline.js';
|
|
22
|
+
export { buildPipelineDeps } from './build-deps.js';
|
|
23
|
+
export { PipelineError, isPipelineError } from './errors.js';
|
|
24
|
+
export { buildRehearseInContainer, buildDockerExecFn, RehearseDockerUnavailableError, isRehearseDockerUnavailableError, } from './rehearse.js';
|
|
25
|
+
export type { PipelinePhase, MigrationSeverity, ApplyEnv, ClassificationResult, RehearsalResult, ApplyArtifact, PushResult, PipelineDeps, PipelineInput, PipelineResult, } from './types.js';
|
|
26
|
+
export type { DockerExecFn, DockerExecResult, RehearseOptions } from './rehearse.js';
|