@codexa/cli 9.0.32 → 9.0.34
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/db/connection.ts +166 -69
- package/db/schema.ts +2 -1
- package/package.json +1 -1
package/db/connection.ts
CHANGED
|
@@ -4,9 +4,13 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
|
|
6
6
|
let client: Client | null = null;
|
|
7
|
+
let dbProvisioned = false;
|
|
8
|
+
|
|
9
|
+
const TURSO_API = "https://api.turso.tech/v1";
|
|
10
|
+
const CONFIG_PATH = () => join(process.cwd(), ".codexa", "config.json");
|
|
7
11
|
|
|
8
12
|
// ═══════════════════════════════════════════════════════════════
|
|
9
|
-
// Resolucao de DB URL: env → config local →
|
|
13
|
+
// Resolucao de DB URL: env → config local → provisionar via API
|
|
10
14
|
// ═══════════════════════════════════════════════════════════════
|
|
11
15
|
|
|
12
16
|
function resolveDbUrl(): string {
|
|
@@ -14,7 +18,7 @@ function resolveDbUrl(): string {
|
|
|
14
18
|
if (process.env.CODEXA_DB_URL) return process.env.CODEXA_DB_URL;
|
|
15
19
|
|
|
16
20
|
// 2. Cache local (.codexa/config.json)
|
|
17
|
-
const configPath =
|
|
21
|
+
const configPath = CONFIG_PATH();
|
|
18
22
|
if (existsSync(configPath)) {
|
|
19
23
|
try {
|
|
20
24
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
@@ -22,48 +26,33 @@ function resolveDbUrl(): string {
|
|
|
22
26
|
} catch { /* config invalido, continuar */ }
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
// 3.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"Banco de dados nao configurado.\n\n" +
|
|
30
|
-
"Opcao 1 — Configurar automaticamente:\n" +
|
|
31
|
-
" export TURSO_ORG=sua-org\n" +
|
|
32
|
-
" export TURSO_AUTH_TOKEN=eyJ...\n" +
|
|
33
|
-
" codexa init\n\n" +
|
|
34
|
-
"Opcao 2 — Configurar manualmente:\n" +
|
|
35
|
-
" export CODEXA_DB_URL=libsql://seu-db.turso.io\n" +
|
|
36
|
-
" export TURSO_AUTH_TOKEN=eyJ..."
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const slug = deriveSlugFromGit();
|
|
41
|
-
const url = `libsql://codexa-${slug}-${org}.turso.io`;
|
|
42
|
-
|
|
43
|
-
// Salvar no cache para proximas execucoes
|
|
44
|
-
saveConfig(configPath, url);
|
|
29
|
+
// 3. Precisa provisionar — retorna placeholder que sera resolvido em ensureDatabase()
|
|
30
|
+
// Nao temos a URL real sem chamar a API (por causa da regiao no hostname)
|
|
31
|
+
throw new NeedsProvisioningError();
|
|
32
|
+
}
|
|
45
33
|
|
|
46
|
-
|
|
34
|
+
class NeedsProvisioningError extends Error {
|
|
35
|
+
constructor() {
|
|
36
|
+
super("DB_NEEDS_PROVISIONING");
|
|
37
|
+
}
|
|
47
38
|
}
|
|
48
39
|
|
|
49
|
-
function
|
|
40
|
+
function deriveDbNameFromGit(): string {
|
|
50
41
|
try {
|
|
51
42
|
const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
52
|
-
// github.com/leandro/meu-saas.git → leandro--meu-saas
|
|
53
|
-
// git@github.com:leandro/meu-saas.git → leandro--meu-saas
|
|
54
43
|
const match = remote.match(/[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
55
44
|
if (match) {
|
|
56
|
-
return
|
|
45
|
+
return match[2].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
57
46
|
}
|
|
58
47
|
} catch { /* git nao disponivel */ }
|
|
59
48
|
|
|
60
|
-
// Fallback: nome da pasta atual
|
|
61
49
|
const dirName = process.cwd().split(/[/\\]/).pop() || "unknown";
|
|
62
50
|
return dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
function saveConfig(
|
|
53
|
+
function saveConfig(url: string): void {
|
|
66
54
|
try {
|
|
55
|
+
const configPath = CONFIG_PATH();
|
|
67
56
|
const dir = dirname(configPath);
|
|
68
57
|
if (!existsSync(dir)) {
|
|
69
58
|
mkdirSync(dir, { recursive: true });
|
|
@@ -72,58 +61,164 @@ function saveConfig(configPath: string, url: string): void {
|
|
|
72
61
|
} catch { /* falha ao salvar cache, nao e critico */ }
|
|
73
62
|
}
|
|
74
63
|
|
|
64
|
+
function tursoHeaders(): Record<string, string> {
|
|
65
|
+
const authToken = process.env.TURSO_AUTH_TOKEN;
|
|
66
|
+
if (!authToken) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"TURSO_AUTH_TOKEN nao configurado.\n\n" +
|
|
69
|
+
" export TURSO_AUTH_TOKEN=$(turso auth token)\n" +
|
|
70
|
+
" export TURSO_ORG=sua-org"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
"Authorization": `Bearer ${authToken}`,
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
// ═══════════════════════════════════════════════════════════════
|
|
76
|
-
//
|
|
80
|
+
// Turso API: buscar hostname real do DB (inclui regiao)
|
|
81
|
+
// GET /v1/organizations/{org}/databases/{name}
|
|
82
|
+
// Retorna { database: { Hostname: "name-org.region.turso.io" } }
|
|
77
83
|
// ═══════════════════════════════════════════════════════════════
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
async function fetchDatabaseUrl(org: string, dbName: string): Promise<string | null> {
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch(
|
|
88
|
+
`${TURSO_API}/organizations/${org}/databases/${dbName}`,
|
|
89
|
+
{ headers: tursoHeaders() }
|
|
90
|
+
);
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
if (!url.startsWith("libsql://")) return;
|
|
92
|
+
if (response.status === 404) return null;
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
if (response.status === 401 || response.status === 403) {
|
|
95
|
+
const body = await response.text();
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Autenticacao Turso falhou (${response.status}).\n` +
|
|
98
|
+
` ${body}\n\n` +
|
|
99
|
+
"Verifique:\n" +
|
|
100
|
+
" 1. TURSO_AUTH_TOKEN esta correto e nao expirado\n" +
|
|
101
|
+
" 2. TURSO_ORG corresponde a sua organizacao no Turso\n\n" +
|
|
102
|
+
" Gerar novo token: turso auth token"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
const hostMatch = url.match(/^libsql:\/\/(.+)-[^-]+\.turso\.io$/);
|
|
91
|
-
if (!hostMatch) return;
|
|
92
|
-
const dbName = hostMatch[1];
|
|
106
|
+
if (!response.ok) return null;
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
const data = await response.json() as any;
|
|
109
|
+
const hostname = data.database?.Hostname;
|
|
110
|
+
if (hostname) return `libsql://${hostname}`;
|
|
111
|
+
return null;
|
|
112
|
+
} catch (e: any) {
|
|
113
|
+
if (e.message?.includes("Autenticacao Turso")) throw e;
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════
|
|
119
|
+
// Turso API: criar DB e retornar hostname real
|
|
120
|
+
// POST /v1/organizations/{org}/databases
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
async function createDatabase(org: string, dbName: string): Promise<string> {
|
|
103
124
|
const group = process.env.TURSO_GROUP || "default";
|
|
125
|
+
|
|
126
|
+
const response = await fetch(
|
|
127
|
+
`${TURSO_API}/organizations/${org}/databases`,
|
|
128
|
+
{
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: tursoHeaders(),
|
|
131
|
+
body: JSON.stringify({ name: dbName, group }),
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (response.ok) {
|
|
136
|
+
console.log(`[codexa] Banco criado no Turso: ${dbName}`);
|
|
137
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
138
|
+
|
|
139
|
+
// Buscar hostname real (a resposta do POST pode nao ter o formato completo)
|
|
140
|
+
const url = await fetchDatabaseUrl(org, dbName);
|
|
141
|
+
if (url) return url;
|
|
142
|
+
|
|
143
|
+
// Fallback: construir URL sem regiao (pode funcionar em algumas configs)
|
|
144
|
+
return `libsql://${dbName}-${org}.turso.io`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (response.status === 409) {
|
|
148
|
+
// Ja existe — buscar hostname real
|
|
149
|
+
const url = await fetchDatabaseUrl(org, dbName);
|
|
150
|
+
if (url) return url;
|
|
151
|
+
return `libsql://${dbName}-${org}.turso.io`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (response.status === 401 || response.status === 403) {
|
|
155
|
+
const body = await response.text();
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Autenticacao Turso falhou (${response.status}).\n` +
|
|
158
|
+
` ${body}\n\n` +
|
|
159
|
+
"Verifique:\n" +
|
|
160
|
+
" 1. TURSO_AUTH_TOKEN esta correto e nao expirado\n" +
|
|
161
|
+
" 2. TURSO_ORG corresponde a sua organizacao no Turso\n\n" +
|
|
162
|
+
" Gerar novo token: turso auth token"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const body = await response.text();
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Falha ao criar banco no Turso (${response.status}): ${body}\n` +
|
|
169
|
+
` DB: ${dbName} | Org: ${org} | Group: ${group}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════
|
|
174
|
+
// Provisioning: garante que o DB existe e resolve a URL real
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════
|
|
176
|
+
|
|
177
|
+
export async function ensureDatabase(): Promise<void> {
|
|
178
|
+
if (dbProvisioned) return;
|
|
179
|
+
|
|
180
|
+
// Se ja tem URL resolvida (env ou config), apenas marcar como provisionado
|
|
104
181
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
182
|
+
resolveDbUrl();
|
|
183
|
+
dbProvisioned = true;
|
|
184
|
+
return;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (!(e instanceof NeedsProvisioningError)) throw e;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Precisa provisionar via API
|
|
190
|
+
const org = process.env.TURSO_ORG;
|
|
191
|
+
if (!org) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
"Banco de dados nao configurado.\n\n" +
|
|
194
|
+
"Opcao 1 — Configurar automaticamente:\n" +
|
|
195
|
+
" export TURSO_ORG=sua-org\n" +
|
|
196
|
+
" export TURSO_AUTH_TOKEN=eyJ...\n" +
|
|
197
|
+
" codexa init\n\n" +
|
|
198
|
+
"Opcao 2 — Configurar manualmente:\n" +
|
|
199
|
+
" export CODEXA_DB_URL=libsql://seu-db.turso.io\n" +
|
|
200
|
+
" export TURSO_AUTH_TOKEN=eyJ..."
|
|
115
201
|
);
|
|
202
|
+
}
|
|
116
203
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
204
|
+
const dbName = deriveDbNameFromGit();
|
|
205
|
+
|
|
206
|
+
// 1. Verificar se o DB ja existe
|
|
207
|
+
let url = await fetchDatabaseUrl(org, dbName);
|
|
208
|
+
|
|
209
|
+
// 2. Se nao existe, criar
|
|
210
|
+
if (!url) {
|
|
211
|
+
url = await createDatabase(org, dbName);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 3. Salvar no config para proximas execucoes
|
|
215
|
+
saveConfig(url);
|
|
216
|
+
dbProvisioned = true;
|
|
217
|
+
|
|
218
|
+
// 4. Resetar client para usar a nova URL
|
|
219
|
+
if (client) {
|
|
220
|
+
client.close();
|
|
221
|
+
client = null;
|
|
127
222
|
}
|
|
128
223
|
}
|
|
129
224
|
|
|
@@ -153,10 +248,12 @@ export function closeDb(): void {
|
|
|
153
248
|
|
|
154
249
|
export function resetClient(): void {
|
|
155
250
|
client = null;
|
|
251
|
+
dbProvisioned = false;
|
|
156
252
|
}
|
|
157
253
|
|
|
158
254
|
export function setClient(c: Client): void {
|
|
159
255
|
client = c;
|
|
256
|
+
dbProvisioned = true;
|
|
160
257
|
}
|
|
161
258
|
|
|
162
259
|
export function getResolvedDbUrl(): string {
|
package/db/schema.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Client, Transaction } from "@libsql/client";
|
|
2
|
-
import { getDb, setClient, dbGet, dbAll, dbRun, dbExec } from "./connection";
|
|
2
|
+
import { getDb, setClient, dbGet, dbAll, dbRun, dbExec, ensureDatabase } from "./connection";
|
|
3
3
|
|
|
4
4
|
export async function initSchema(): Promise<void> {
|
|
5
|
+
await ensureDatabase();
|
|
5
6
|
await dbExec(`
|
|
6
7
|
CREATE TABLE IF NOT EXISTS specs (
|
|
7
8
|
id TEXT PRIMARY KEY,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexa/cli",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.34",
|
|
4
4
|
"description": "Orchestrated workflow system for Claude Code - manages feature development through parallel subagents with structured phases, gates, and quality enforcement.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|