codexdb-sdk 0.1.16 → 0.1.18
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/DEFAULT_DATA_DIRECTORY.md +183 -0
- package/example_app_default.js +66 -0
- package/index.js +41 -3
- package/index.test.js +339 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +141 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# CodexDB SDK - Default Data Directory
|
|
2
|
+
|
|
3
|
+
## Novo Comportamento
|
|
4
|
+
|
|
5
|
+
A partir desta versão, o SDK CodexDB Node.js possui um comportamento padrão para armazenamento de dados:
|
|
6
|
+
|
|
7
|
+
### ✨ Comportamento Padrão (Sem opção `file`)
|
|
8
|
+
|
|
9
|
+
Quando você criar um cliente sem especificar o arquivo:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const { CodexClient } = require('codexdb-sdk');
|
|
13
|
+
const client = new CodexClient();
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
O SDK automaticamente:
|
|
17
|
+
- Usa o diretório `.codex-data` na pasta corrente
|
|
18
|
+
- Usa `default.db` como nome do arquivo padrão
|
|
19
|
+
- Cria o diretório `.codex-data` se não existir
|
|
20
|
+
- Inicializa o diretório na primeira operação
|
|
21
|
+
|
|
22
|
+
**Resultado**: Arquivo em `.codex-data/default.db` no diretório corrente
|
|
23
|
+
|
|
24
|
+
### 🎯 Comportamento com Opção `file`
|
|
25
|
+
|
|
26
|
+
Você ainda pode especificar um arquivo customizado:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// Opção 1: Caminho completo
|
|
30
|
+
const client1 = new CodexClient({
|
|
31
|
+
file: '/path/to/my-database.db'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Opção 2: Caminho relativo
|
|
35
|
+
const client2 = new CodexClient({
|
|
36
|
+
file: 'my-data/custom.db'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Opção 3: Apenas o nome do arquivo
|
|
40
|
+
const client3 = new CodexClient({
|
|
41
|
+
file: 'data.db'
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Em todos os casos:
|
|
46
|
+
- O caminho é resolvido em relação ao diretório corrente (`process.cwd()`)
|
|
47
|
+
- O diretório é criado automaticamente se não existir
|
|
48
|
+
- O arquivo é criado na primeira operação
|
|
49
|
+
|
|
50
|
+
## Exemplos
|
|
51
|
+
|
|
52
|
+
### Exemplo 1: Uso Padrão
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const { CodexClient } = require('codexdb-sdk');
|
|
56
|
+
|
|
57
|
+
(async () => {
|
|
58
|
+
// Sem opções - usa .codex-data/default.db
|
|
59
|
+
const client = new CodexClient();
|
|
60
|
+
|
|
61
|
+
// O diretório é criado automaticamente na primeira operação
|
|
62
|
+
await client.set('chave', { valor: 'dados' });
|
|
63
|
+
|
|
64
|
+
const dados = await client.get('chave');
|
|
65
|
+
console.log(dados); // { valor: 'dados' }
|
|
66
|
+
})();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Exemplo 2: Uso com Diretório Customizado
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const { CodexClient } = require('codexdb-sdk');
|
|
73
|
+
|
|
74
|
+
(async () => {
|
|
75
|
+
// Com arquivo customizado
|
|
76
|
+
const client = new CodexClient({
|
|
77
|
+
file: 'dados/meu-banco.db'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await client.set('usuario', { nome: 'João' });
|
|
81
|
+
const usuario = await client.get('usuario');
|
|
82
|
+
console.log(usuario); // { nome: 'João' }
|
|
83
|
+
})();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Exemplo 3: Com Criptografia e Ledger
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const { CodexClient } = require('codexdb-sdk');
|
|
90
|
+
|
|
91
|
+
(async () => {
|
|
92
|
+
const client = new CodexClient({
|
|
93
|
+
// Se não especificar 'file', usa .codex-data/default.db
|
|
94
|
+
encryptionKey: 'minha-chave-secreta',
|
|
95
|
+
ledger: true
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await client.set('dados-sensíveis', { token: '...' });
|
|
99
|
+
})();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Método `initialize()`
|
|
103
|
+
|
|
104
|
+
Você pode chamar explicitamente `initialize()` para garantir que o diretório foi criado:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const { CodexClient } = require('codexdb-sdk');
|
|
108
|
+
|
|
109
|
+
const client = new CodexClient();
|
|
110
|
+
|
|
111
|
+
// Cria o diretório .codex-data explicitamente
|
|
112
|
+
await client.initialize();
|
|
113
|
+
|
|
114
|
+
// Agora é seguro fazer operações
|
|
115
|
+
await client.set('chave', { valor: 'dado' });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Nota**: O método `initialize()` é chamado automaticamente na primeira operação (set, get, delete, keys, has, clear), então geralmente você não precisa chamar explicitamente.
|
|
119
|
+
|
|
120
|
+
## Estrutura de Diretórios
|
|
121
|
+
|
|
122
|
+
### Padrão (sem opção `file`)
|
|
123
|
+
```
|
|
124
|
+
projeto/
|
|
125
|
+
├── .codex-data/ ← Criado automaticamente
|
|
126
|
+
│ └── default.db ← Arquivo de banco de dados
|
|
127
|
+
├── node_modules/
|
|
128
|
+
├── package.json
|
|
129
|
+
└── app.js
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Custom (com opção `file`)
|
|
133
|
+
```
|
|
134
|
+
projeto/
|
|
135
|
+
├── dados/ ← Criado automaticamente se necessário
|
|
136
|
+
│ └── meu-banco.db ← Arquivo customizado
|
|
137
|
+
├── node_modules/
|
|
138
|
+
├── package.json
|
|
139
|
+
└── app.js
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Testes Unitários
|
|
143
|
+
|
|
144
|
+
Para validar o comportamento padrão, execute:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm run test:unit
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Os testes cobrem:
|
|
151
|
+
- ✓ Uso do diretório padrão `.codex-data`
|
|
152
|
+
- ✓ Uso de arquivo customizado
|
|
153
|
+
- ✓ Criação automática de diretórios
|
|
154
|
+
- ✓ Suporte a múltiplos níveis de diretórios
|
|
155
|
+
- ✓ Propriedades do cliente (encriptação, ledger)
|
|
156
|
+
- ✓ Flag `_initialized`
|
|
157
|
+
|
|
158
|
+
## Compatibilidade
|
|
159
|
+
|
|
160
|
+
- **Versão anterior**: `options.file` era obrigatório
|
|
161
|
+
- **Versão nova**: `options.file` é opcional (usa `.codex-data/default.db` por padrão)
|
|
162
|
+
|
|
163
|
+
Se seu código já usa a opção `file`, ele continuará funcionando normalmente! ✓
|
|
164
|
+
|
|
165
|
+
## Migração
|
|
166
|
+
|
|
167
|
+
Se você tinha código assim:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// ❌ Antigo (file era obrigatório)
|
|
171
|
+
const client = new CodexClient({
|
|
172
|
+
file: './.codex-data/default.db'
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Você pode simplificar para:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// ✓ Novo (file é opcional)
|
|
180
|
+
const client = new CodexClient();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Ambas as formas funcionam, mas a segunda é mais simples! 🎉
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { CodexClient } = require('./index');
|
|
2
|
+
|
|
3
|
+
// Example 1: Using default data directory (.codex-data/default.db)
|
|
4
|
+
(async () => {
|
|
5
|
+
console.log('Example 1: Using default data directory\n');
|
|
6
|
+
|
|
7
|
+
// No file option needed - uses .codex-data/default.db automatically
|
|
8
|
+
const client = new CodexClient();
|
|
9
|
+
|
|
10
|
+
console.log('Database file:', client.file);
|
|
11
|
+
console.log('Data directory:', client.dataDir);
|
|
12
|
+
console.log('Initialized:', client._initialized);
|
|
13
|
+
|
|
14
|
+
// Initialize creates the directory if it doesn't exist
|
|
15
|
+
await client.initialize();
|
|
16
|
+
console.log('After initialize - Initialized:', client._initialized);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Set a value
|
|
20
|
+
await client.set('message', { text: 'Hello from CodexDB!' });
|
|
21
|
+
console.log('✓ Set message');
|
|
22
|
+
|
|
23
|
+
// Get the value
|
|
24
|
+
const value = await client.get('message');
|
|
25
|
+
console.log('✓ Got message:', value.text);
|
|
26
|
+
|
|
27
|
+
// List all keys
|
|
28
|
+
const keys = await client.keys();
|
|
29
|
+
console.log('✓ All keys:', keys);
|
|
30
|
+
|
|
31
|
+
// Check if key exists
|
|
32
|
+
const exists = await client.has('message');
|
|
33
|
+
console.log('✓ Key exists:', exists);
|
|
34
|
+
|
|
35
|
+
// Delete the key
|
|
36
|
+
await client.delete('message');
|
|
37
|
+
console.log('✓ Deleted message');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error:', error.message);
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
// Example 2: Using custom data directory
|
|
44
|
+
console.log('\n---\n');
|
|
45
|
+
console.log('Example 2: Using custom data directory\n');
|
|
46
|
+
|
|
47
|
+
(async () => {
|
|
48
|
+
const path = require('path');
|
|
49
|
+
|
|
50
|
+
// Custom file path - resolved relative to current working directory
|
|
51
|
+
const client = new CodexClient({ file: 'custom-data.db' });
|
|
52
|
+
|
|
53
|
+
console.log('Database file:', client.file);
|
|
54
|
+
console.log('Data directory:', client.dataDir);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Operations automatically initialize the directory
|
|
58
|
+
await client.set('config', { theme: 'dark', language: 'pt-BR' });
|
|
59
|
+
console.log('✓ Set config');
|
|
60
|
+
|
|
61
|
+
const config = await client.get('config');
|
|
62
|
+
console.log('✓ Got config:', config);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Error:', error.message);
|
|
65
|
+
}
|
|
66
|
+
})();
|
package/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { execFile } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs/promises');
|
|
2
4
|
|
|
3
5
|
function execCodexCli(cliPath, args, env = {}) {
|
|
4
6
|
return new Promise((resolve, reject) => {
|
|
@@ -12,14 +14,32 @@ function execCodexCli(cliPath, args, env = {}) {
|
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
async function ensureDataDirectory(dataPath) {
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(dataPath, { recursive: true });
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code !== 'EEXIST') {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
class CodexClient {
|
|
16
28
|
constructor(options = {}) {
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
// If no file is provided, use default: .codex-data/default.db
|
|
30
|
+
let file = options.file;
|
|
31
|
+
if (!file) {
|
|
32
|
+
const dataDir = path.resolve(process.cwd(), '.codex-data');
|
|
33
|
+
file = path.join(dataDir, 'default.db');
|
|
34
|
+
} else {
|
|
35
|
+
// If file is provided but it's just a filename (no path separators),
|
|
36
|
+
// resolve it relative to current working directory
|
|
37
|
+
file = path.resolve(process.cwd(), file);
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
this.cliPath = options.cliPath || 'codex-cli';
|
|
22
|
-
this.file =
|
|
41
|
+
this.file = file;
|
|
42
|
+
this.dataDir = path.dirname(this.file);
|
|
23
43
|
this.ledger = options.ledger ? '--ledger' : null;
|
|
24
44
|
this.env = {};
|
|
25
45
|
|
|
@@ -28,6 +48,18 @@ class CodexClient {
|
|
|
28
48
|
}
|
|
29
49
|
}
|
|
30
50
|
|
|
51
|
+
async initialize() {
|
|
52
|
+
// Create data directory if it doesn't exist
|
|
53
|
+
await ensureDataDirectory(this.dataDir);
|
|
54
|
+
this._initialized = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _ensureInitialized() {
|
|
58
|
+
if (!this._initialized) {
|
|
59
|
+
await this.initialize();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
31
63
|
_baseArgs() {
|
|
32
64
|
const args = ['--file', this.file];
|
|
33
65
|
if (this.ledger) args.push(this.ledger);
|
|
@@ -35,6 +67,7 @@ class CodexClient {
|
|
|
35
67
|
}
|
|
36
68
|
|
|
37
69
|
async set(key, value) {
|
|
70
|
+
await this._ensureInitialized();
|
|
38
71
|
const payload = JSON.stringify(value);
|
|
39
72
|
const args = [...this._baseArgs(), 'set', key, payload];
|
|
40
73
|
await execCodexCli(this.cliPath, args, this.env);
|
|
@@ -42,6 +75,7 @@ class CodexClient {
|
|
|
42
75
|
}
|
|
43
76
|
|
|
44
77
|
async get(key) {
|
|
78
|
+
await this._ensureInitialized();
|
|
45
79
|
const args = [...this._baseArgs(), 'get', key];
|
|
46
80
|
const raw = await execCodexCli(this.cliPath, args, this.env);
|
|
47
81
|
if (!raw) return null;
|
|
@@ -49,12 +83,14 @@ class CodexClient {
|
|
|
49
83
|
}
|
|
50
84
|
|
|
51
85
|
async delete(key) {
|
|
86
|
+
await this._ensureInitialized();
|
|
52
87
|
const args = [...this._baseArgs(), 'delete', key];
|
|
53
88
|
await execCodexCli(this.cliPath, args, this.env);
|
|
54
89
|
return true;
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
async keys() {
|
|
93
|
+
await this._ensureInitialized();
|
|
58
94
|
const args = [...this._baseArgs(), 'keys'];
|
|
59
95
|
const raw = await execCodexCli(this.cliPath, args, this.env);
|
|
60
96
|
if (!raw) return [];
|
|
@@ -62,12 +98,14 @@ class CodexClient {
|
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
async has(key) {
|
|
101
|
+
await this._ensureInitialized();
|
|
65
102
|
const args = [...this._baseArgs(), 'has', key];
|
|
66
103
|
const raw = await execCodexCli(this.cliPath, args, this.env);
|
|
67
104
|
return raw.trim() === 'true';
|
|
68
105
|
}
|
|
69
106
|
|
|
70
107
|
async clear() {
|
|
108
|
+
await this._ensureInitialized();
|
|
71
109
|
const args = [...this._baseArgs(), 'clear'];
|
|
72
110
|
await execCodexCli(this.cliPath, args, this.env);
|
|
73
111
|
return true;
|
package/index.test.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const fsSync = require('fs');
|
|
4
|
+
const { CodexClient } = require('./index');
|
|
5
|
+
|
|
6
|
+
// Test utilities
|
|
7
|
+
const TEST_DIR = path.join(__dirname, '.test-temp');
|
|
8
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
|
|
10
|
+
async function setupTestDir() {
|
|
11
|
+
try {
|
|
12
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
await fs.mkdir(TEST_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function cleanupTestDir() {
|
|
20
|
+
try {
|
|
21
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// ignore
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function dirExists(dirPath) {
|
|
28
|
+
try {
|
|
29
|
+
const stats = await fs.stat(dirPath);
|
|
30
|
+
return stats.isDirectory();
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Color console output
|
|
37
|
+
const colors = {
|
|
38
|
+
reset: '\x1b[0m',
|
|
39
|
+
green: '\x1b[32m',
|
|
40
|
+
red: '\x1b[31m',
|
|
41
|
+
yellow: '\x1b[33m',
|
|
42
|
+
blue: '\x1b[34m',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function log(message, color = 'reset') {
|
|
46
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function logTest(name) {
|
|
50
|
+
log(`\n ✓ ${name}`, 'blue');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function logPass(message) {
|
|
54
|
+
log(` ✓ ${message}`, 'green');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function logFail(message) {
|
|
58
|
+
log(` ✗ ${message}`, 'red');
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// TESTS
|
|
64
|
+
// ============================================
|
|
65
|
+
|
|
66
|
+
async function testDefaultDataDirectory() {
|
|
67
|
+
logTest('Default data directory behavior');
|
|
68
|
+
|
|
69
|
+
await cleanupTestDir();
|
|
70
|
+
await setupTestDir();
|
|
71
|
+
|
|
72
|
+
const originalCwd = process.cwd();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
process.chdir(TEST_DIR);
|
|
76
|
+
|
|
77
|
+
// Create client without file option
|
|
78
|
+
const client = new CodexClient();
|
|
79
|
+
|
|
80
|
+
// Check that file path includes .codex-data
|
|
81
|
+
const expectedPath = path.join(TEST_DIR, '.codex-data', 'default.db');
|
|
82
|
+
if (client.file === expectedPath) {
|
|
83
|
+
logPass(`File path defaults to ${expectedPath}`);
|
|
84
|
+
} else {
|
|
85
|
+
logFail(
|
|
86
|
+
`File path should be ${expectedPath}, got ${client.file}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check dataDir
|
|
91
|
+
const expectedDataDir = path.join(TEST_DIR, '.codex-data');
|
|
92
|
+
if (client.dataDir === expectedDataDir) {
|
|
93
|
+
logPass(`Data directory defaults to ${expectedDataDir}`);
|
|
94
|
+
} else {
|
|
95
|
+
logFail(
|
|
96
|
+
`Data directory should be ${expectedDataDir}, got ${client.dataDir}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check that directory is created on initialize
|
|
101
|
+
const existsBefore = await dirExists(expectedDataDir);
|
|
102
|
+
if (!existsBefore) {
|
|
103
|
+
logPass('Directory does not exist before initialize()');
|
|
104
|
+
} else {
|
|
105
|
+
logFail('Directory should not exist before initialize()');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await client.initialize();
|
|
109
|
+
|
|
110
|
+
const existsAfter = await dirExists(expectedDataDir);
|
|
111
|
+
if (existsAfter) {
|
|
112
|
+
logPass('Directory created after initialize()');
|
|
113
|
+
} else {
|
|
114
|
+
logFail('Directory should be created after initialize()');
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
process.chdir(originalCwd);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function testCustomFileOption() {
|
|
122
|
+
logTest('Custom file option handling');
|
|
123
|
+
|
|
124
|
+
await cleanupTestDir();
|
|
125
|
+
await setupTestDir();
|
|
126
|
+
|
|
127
|
+
const originalCwd = process.cwd();
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
process.chdir(TEST_DIR);
|
|
131
|
+
|
|
132
|
+
// Test with full path
|
|
133
|
+
const customPath = path.join(TEST_DIR, 'custom', 'my-db.db');
|
|
134
|
+
const client = new CodexClient({ file: customPath });
|
|
135
|
+
|
|
136
|
+
if (client.file === customPath) {
|
|
137
|
+
logPass(`Full path option preserved: ${customPath}`);
|
|
138
|
+
} else {
|
|
139
|
+
logFail(
|
|
140
|
+
`File path should be ${customPath}, got ${client.file}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Test with relative filename
|
|
145
|
+
const client2 = new CodexClient({ file: 'my-db.db' });
|
|
146
|
+
const expectedPath = path.resolve(process.cwd(), 'my-db.db');
|
|
147
|
+
|
|
148
|
+
if (client2.file === expectedPath) {
|
|
149
|
+
logPass(`Relative filename resolved to: ${expectedPath}`);
|
|
150
|
+
} else {
|
|
151
|
+
logFail(
|
|
152
|
+
`File path should be ${expectedPath}, got ${client2.file}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
process.chdir(originalCwd);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function testDirectoryCreationOnDemand() {
|
|
161
|
+
logTest('Directory creation on first operation');
|
|
162
|
+
|
|
163
|
+
await cleanupTestDir();
|
|
164
|
+
await setupTestDir();
|
|
165
|
+
|
|
166
|
+
const originalCwd = process.cwd();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
process.chdir(TEST_DIR);
|
|
170
|
+
|
|
171
|
+
const client = new CodexClient();
|
|
172
|
+
const dataDir = client.dataDir;
|
|
173
|
+
|
|
174
|
+
// Check directory doesn't exist yet
|
|
175
|
+
const existsBefore = await dirExists(dataDir);
|
|
176
|
+
if (!existsBefore) {
|
|
177
|
+
logPass('Directory does not exist before first operation');
|
|
178
|
+
} else {
|
|
179
|
+
logFail('Directory should not exist before first operation');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Call initialize (which would be called automatically in real operations)
|
|
183
|
+
await client.initialize();
|
|
184
|
+
|
|
185
|
+
// Check directory now exists
|
|
186
|
+
const existsAfter = await dirExists(dataDir);
|
|
187
|
+
if (existsAfter) {
|
|
188
|
+
logPass('Directory created on initialize()');
|
|
189
|
+
} else {
|
|
190
|
+
logFail('Directory should be created on initialize()');
|
|
191
|
+
}
|
|
192
|
+
} finally {
|
|
193
|
+
process.chdir(originalCwd);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function testMultipleLevelsPath() {
|
|
198
|
+
logTest('Multiple levels of directory creation');
|
|
199
|
+
|
|
200
|
+
await cleanupTestDir();
|
|
201
|
+
await setupTestDir();
|
|
202
|
+
|
|
203
|
+
const originalCwd = process.cwd();
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
process.chdir(TEST_DIR);
|
|
207
|
+
|
|
208
|
+
const customPath = path.join(TEST_DIR, 'a', 'b', 'c', 'db.db');
|
|
209
|
+
const client = new CodexClient({ file: customPath });
|
|
210
|
+
|
|
211
|
+
const dataDir = client.dataDir;
|
|
212
|
+
const existsBefore = await dirExists(dataDir);
|
|
213
|
+
|
|
214
|
+
if (!existsBefore) {
|
|
215
|
+
logPass('Nested directories do not exist before initialize()');
|
|
216
|
+
} else {
|
|
217
|
+
logFail('Nested directories should not exist before initialize()');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await client.initialize();
|
|
221
|
+
|
|
222
|
+
const existsAfter = await dirExists(dataDir);
|
|
223
|
+
if (existsAfter) {
|
|
224
|
+
logPass('Nested directories created: ' + dataDir);
|
|
225
|
+
} else {
|
|
226
|
+
logFail('Nested directories should be created');
|
|
227
|
+
}
|
|
228
|
+
} finally {
|
|
229
|
+
process.chdir(originalCwd);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function testClientProperties() {
|
|
234
|
+
logTest('Client properties and configuration');
|
|
235
|
+
|
|
236
|
+
await cleanupTestDir();
|
|
237
|
+
await setupTestDir();
|
|
238
|
+
|
|
239
|
+
const originalCwd = process.cwd();
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
process.chdir(TEST_DIR);
|
|
243
|
+
|
|
244
|
+
const client = new CodexClient({
|
|
245
|
+
encryptionKey: 'secret-key',
|
|
246
|
+
ledger: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (client.env.CODEX_KEY === 'secret-key') {
|
|
250
|
+
logPass('Encryption key stored in environment');
|
|
251
|
+
} else {
|
|
252
|
+
logFail('Encryption key not stored correctly');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (client.ledger === '--ledger') {
|
|
256
|
+
logPass('Ledger flag set correctly');
|
|
257
|
+
} else {
|
|
258
|
+
logFail('Ledger flag not set correctly');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const baseArgs = client._baseArgs();
|
|
262
|
+
if (baseArgs.includes('--file') && baseArgs.includes('--ledger')) {
|
|
263
|
+
logPass('Base args include both --file and --ledger flags');
|
|
264
|
+
} else {
|
|
265
|
+
logFail('Base args should include both flags');
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
process.chdir(originalCwd);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function testInitializedFlag() {
|
|
273
|
+
logTest('Initialized flag behavior');
|
|
274
|
+
|
|
275
|
+
const originalCwd = process.cwd();
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
process.chdir(TEST_DIR);
|
|
279
|
+
|
|
280
|
+
const client = new CodexClient();
|
|
281
|
+
|
|
282
|
+
if (!client._initialized) {
|
|
283
|
+
logPass('_initialized flag is false before initialize()');
|
|
284
|
+
} else {
|
|
285
|
+
logFail('_initialized flag should be false initially');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await client.initialize();
|
|
289
|
+
|
|
290
|
+
if (client._initialized) {
|
|
291
|
+
logPass('_initialized flag is true after initialize()');
|
|
292
|
+
} else {
|
|
293
|
+
logFail('_initialized flag should be true after initialize()');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Calling initialize again should not cause issues
|
|
297
|
+
await client.initialize();
|
|
298
|
+
logPass('Multiple initialize() calls are safe');
|
|
299
|
+
} finally {
|
|
300
|
+
process.chdir(originalCwd);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================
|
|
305
|
+
// RUN ALL TESTS
|
|
306
|
+
// ============================================
|
|
307
|
+
|
|
308
|
+
async function runAllTests() {
|
|
309
|
+
log('\n═══════════════════════════════════════════════════════', 'yellow');
|
|
310
|
+
log(' CodexDB SDK - Unit Tests', 'yellow');
|
|
311
|
+
log('═══════════════════════════════════════════════════════\n', 'yellow');
|
|
312
|
+
|
|
313
|
+
await setupTestDir();
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await testDefaultDataDirectory();
|
|
317
|
+
await testCustomFileOption();
|
|
318
|
+
await testDirectoryCreationOnDemand();
|
|
319
|
+
await testMultipleLevelsPath();
|
|
320
|
+
await testClientProperties();
|
|
321
|
+
await testInitializedFlag();
|
|
322
|
+
|
|
323
|
+
log('\n═══════════════════════════════════════════════════════', 'yellow');
|
|
324
|
+
if (process.exitCode) {
|
|
325
|
+
log(' Some tests failed!', 'red');
|
|
326
|
+
} else {
|
|
327
|
+
log(' All tests passed! ✓', 'green');
|
|
328
|
+
}
|
|
329
|
+
log('═══════════════════════════════════════════════════════\n', 'yellow');
|
|
330
|
+
} finally {
|
|
331
|
+
await cleanupTestDir();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Run tests
|
|
336
|
+
runAllTests().catch((error) => {
|
|
337
|
+
log(`Fatal error: ${error.message}`, 'red');
|
|
338
|
+
process.exit(1);
|
|
339
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexdb-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Node.js SDK for CodexDB (wrapper around codex-cli)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "node test.js",
|
|
8
|
+
"test:unit": "node index.test.js",
|
|
8
9
|
"postinstall": "node scripts/postinstall.js",
|
|
9
10
|
"publish-check": "node scripts/publish-check.js",
|
|
10
11
|
"release": "bash scripts/publish.sh"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { https } = require('follow-redirects');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// Get package.json path relative to this script
|
|
8
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
9
|
+
const packageJson = require(packageJsonPath);
|
|
10
|
+
const version = packageJson.version;
|
|
11
|
+
|
|
12
|
+
// Map platform to OS name in release
|
|
13
|
+
const OS_MAP = {
|
|
14
|
+
darwin: 'darwin',
|
|
15
|
+
linux: 'linux',
|
|
16
|
+
win32: 'windows'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Get system info
|
|
20
|
+
const platform = os.platform();
|
|
21
|
+
const osName = OS_MAP[platform];
|
|
22
|
+
const arch = os.arch() === 'arm64' ? 'arm64' : 'amd64';
|
|
23
|
+
|
|
24
|
+
if (!osName) {
|
|
25
|
+
console.log(`⚠️ No binary available for platform: ${platform}`);
|
|
26
|
+
process.exit(0); // Don't fail, just skip
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dest = path.join(__dirname, '..', 'codex-cli' + (platform === 'win32' ? '.exe' : ''));
|
|
30
|
+
const tempDir = path.join(__dirname, '..', '.temp-extract');
|
|
31
|
+
|
|
32
|
+
console.log(`Fetching release info for v${version}...`);
|
|
33
|
+
|
|
34
|
+
// Fetch release information from GitHub API
|
|
35
|
+
const apiUrl = `https://api.github.com/repos/evertonmj/codex/releases/tags/v${version}`;
|
|
36
|
+
|
|
37
|
+
https.get(apiUrl, {
|
|
38
|
+
headers: { 'User-Agent': 'codex-postinstall' }
|
|
39
|
+
}, (res) => {
|
|
40
|
+
let data = '';
|
|
41
|
+
res.on('data', chunk => { data += chunk; });
|
|
42
|
+
res.on('end', () => {
|
|
43
|
+
try {
|
|
44
|
+
const release = JSON.parse(data);
|
|
45
|
+
|
|
46
|
+
// Check if the API returned an error
|
|
47
|
+
if (release.message) {
|
|
48
|
+
console.log(`⚠️ Release v${version} not found on GitHub`);
|
|
49
|
+
console.log(` This is normal during development before publishing`);
|
|
50
|
+
console.log(` The codex-cli binary will be available after creating a GitHub release`);
|
|
51
|
+
process.exit(0); // Don't fail, just skip
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!release.assets || !Array.isArray(release.assets)) {
|
|
55
|
+
console.log(`⚠️ Invalid release data: no assets found`);
|
|
56
|
+
process.exit(0); // Don't fail, just skip
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Find the correct binary for this platform/arch
|
|
60
|
+
const pattern = `codexdb_${version}_${osName}_${arch}`;
|
|
61
|
+
const asset = release.assets.find(a => a.name.includes(pattern));
|
|
62
|
+
|
|
63
|
+
if (!asset) {
|
|
64
|
+
console.log(`⚠️ No binary found for ${osName}/${arch}`);
|
|
65
|
+
console.log(` Expected pattern: ${pattern}`);
|
|
66
|
+
console.log(` Available assets: ${release.assets.map(a => a.name).join(', ')}`);
|
|
67
|
+
process.exit(0); // Don't fail, just skip
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`Downloading ${asset.name}...`);
|
|
71
|
+
|
|
72
|
+
// Create temp directory
|
|
73
|
+
if (!fs.existsSync(tempDir)) {
|
|
74
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tempFile = path.join(tempDir, asset.name);
|
|
78
|
+
|
|
79
|
+
https.get(asset.browser_download_url, (res) => {
|
|
80
|
+
if (res.statusCode !== 200) {
|
|
81
|
+
console.log(`⚠️ Failed to download binary: ${res.statusCode}`);
|
|
82
|
+
process.exit(0); // Don't fail, just skip
|
|
83
|
+
}
|
|
84
|
+
const file = fs.createWriteStream(tempFile);
|
|
85
|
+
res.pipe(file);
|
|
86
|
+
file.on('finish', () => {
|
|
87
|
+
file.close(() => {
|
|
88
|
+
extractAndInstall(tempFile, asset.name, dest, tempDir);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}).on('error', (err) => {
|
|
92
|
+
console.log(`⚠️ Download error: ${err.message}`);
|
|
93
|
+
process.exit(0); // Don't fail, just skip
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.log(`⚠️ Failed to parse release info: ${err.message}`);
|
|
97
|
+
process.exit(0); // Don't fail, just skip
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}).on('error', (err) => {
|
|
101
|
+
console.log(`⚠️ API error: ${err.message}`);
|
|
102
|
+
console.log(` The codex-cli binary must be downloaded manually or will be installed on first use`);
|
|
103
|
+
process.exit(0); // Don't fail, just skip
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
function extractAndInstall(archivePath, archiveName, destPath, tempDir) {
|
|
107
|
+
try {
|
|
108
|
+
console.log(`Extracting ${archiveName}...`);
|
|
109
|
+
|
|
110
|
+
if (archiveName.endsWith('.tar.gz')) {
|
|
111
|
+
execSync(`tar -xzf ${archivePath} -C ${tempDir}`);
|
|
112
|
+
} else if (archiveName.endsWith('.zip')) {
|
|
113
|
+
execSync(`unzip -q ${archivePath} -d ${tempDir}`);
|
|
114
|
+
} else if (archiveName.endsWith('.deb')) {
|
|
115
|
+
// For .deb files, extract the data
|
|
116
|
+
execSync(`ar x ${archivePath} data.tar.xz -o ${tempDir}/data.tar.xz`);
|
|
117
|
+
execSync(`tar -xf ${tempDir}/data.tar.xz -C ${tempDir}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Find the binary in the extracted files
|
|
121
|
+
const files = execSync(`find ${tempDir} -type f -name 'codex-cli*' -o -name 'codexdb' 2>/dev/null`, { encoding: 'utf-8' }).trim().split('\n').filter(f => f);
|
|
122
|
+
|
|
123
|
+
if (files.length === 0) {
|
|
124
|
+
console.log('⚠️ Binary not found in archive');
|
|
125
|
+
process.exit(0); // Don't fail, just skip
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const binaryPath = files[0];
|
|
129
|
+
fs.copyFileSync(binaryPath, destPath);
|
|
130
|
+
fs.chmodSync(destPath, 0o755);
|
|
131
|
+
|
|
132
|
+
// Cleanup
|
|
133
|
+
execSync(`rm -rf ${tempDir}`);
|
|
134
|
+
|
|
135
|
+
console.log('✓ codex-cli installed to', destPath);
|
|
136
|
+
process.exit(0);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.log(`⚠️ Extraction error: ${err.message}`);
|
|
139
|
+
process.exit(0); // Don't fail, just skip
|
|
140
|
+
}
|
|
141
|
+
}
|