taskmonkey-cli 0.5.1 → 0.6.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/bin/tm.js CHANGED
@@ -41,6 +41,7 @@ program
41
41
  program
42
42
  .command('sync')
43
43
  .description('Upload local config files to server')
44
+ .option('-d, --delete', 'Delete remote files that no longer exist locally')
44
45
  .action(sync);
45
46
 
46
47
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskmonkey-cli",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "TaskMonkey CLI — Remote dev tools for tenant config editing and tool testing",
5
5
  "bin": {
6
6
  "tm": "./bin/tm.js",
@@ -338,6 +338,32 @@ Jeder Handler bekommt einen Kontext mit:
338
338
  \\GuzzleHttp\\Client // Alternative HTTP-Library
339
339
  \`\`\`
340
340
 
341
+ ## Database Gateway (Remote-SQL)
342
+
343
+ Für Zugriff auf externe Datenbanken (Contao, WordPress, etc.) gibt es das Database Gateway System.
344
+ Der Server hat SSH-Zugang zu Remote-Servern und deployt ein temporäres PHP-Gateway.
345
+
346
+ **Workflow in einem Tool/Prompt:**
347
+ 1. \`deployGateway\` mit \`project\` und optional \`allow_write: true\`
348
+ 2. \`executeQuery\` / \`listTables\` / \`describeTable\`
349
+ 3. \`removeGateway\` am Ende
350
+
351
+ **Projekte konfigurieren:** \`database_connections.php\`
352
+ \`\`\`php
353
+ 'database_connections' => [
354
+ 'meine_website' => [
355
+ 'host' => 'server.example.com',
356
+ 'user' => 'ssh-user',
357
+ 'port' => 22,
358
+ 'key' => '~/.ssh/id_rsa', // Auf dem Server, nicht lokal!
359
+ 'web_root' => '/var/www/html',
360
+ 'cms' => 'contao',
361
+ ],
362
+ ],
363
+ \`\`\`
364
+
365
+ Siehe \`docs/DatabaseGateway.md\` für die vollständige Dokumentation inkl. SQL-Beispiele und CMS-Tabellen.
366
+
341
367
  ## Conversation Tests
342
368
 
343
369
  Definiere Tests um Prompt-Änderungen automatisch zu validieren:
@@ -1,4 +1,4 @@
1
- import { readdirSync, readFileSync, statSync } from 'fs';
1
+ import { readdirSync, readFileSync } from 'fs';
2
2
  import { join, relative } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
@@ -22,7 +22,7 @@ function collectFiles(dir, base = dir) {
22
22
  return files;
23
23
  }
24
24
 
25
- export async function sync() {
25
+ export async function sync(options = {}) {
26
26
  const config = loadConfig();
27
27
  if (!config) {
28
28
  console.error(chalk.red('Not logged in. Run `tm login` first.'));
@@ -30,22 +30,46 @@ export async function sync() {
30
30
  }
31
31
 
32
32
  const tenantDir = join(config._configDir, config.tenant_path || '.');
33
+ const deleteRemote = options.delete || false;
33
34
 
34
35
  const spinner = ora('Collecting files...').start();
35
36
 
36
- const files = collectFiles(tenantDir);
37
- const fileCount = Object.keys(files).length;
37
+ const localFiles = collectFiles(tenantDir);
38
+ const localPaths = Object.keys(localFiles);
38
39
 
39
- if (fileCount === 0) {
40
+ if (localPaths.length === 0) {
40
41
  spinner.warn('No .php files found');
41
42
  return;
42
43
  }
43
44
 
44
- spinner.text = `Syncing ${fileCount} files...`;
45
+ const client = createClient();
46
+
47
+ // Delete remote files that don't exist locally
48
+ if (deleteRemote) {
49
+ spinner.text = 'Comparing with server...';
50
+ try {
51
+ const remoteList = await client.get('/api/tenant/files');
52
+ const remotePaths = (remoteList.files || []).map(f => f.path);
53
+ const toDelete = remotePaths.filter(p => !localPaths.includes(p));
54
+
55
+ if (toDelete.length > 0) {
56
+ const result = await client.post('/api/tenant/delete-files', { files: toDelete });
57
+ spinner.stop();
58
+ console.log(chalk.red(`✗ ${result.deleted || 0} files deleted on server`));
59
+ for (const file of result.files || []) {
60
+ console.log(chalk.red(` - ${file}`));
61
+ }
62
+ }
63
+ } catch (err) {
64
+ spinner.stop();
65
+ console.error(chalk.yellow(` Delete check failed: ${err.message}`));
66
+ }
67
+ }
68
+
69
+ spinner.text = `Syncing ${localPaths.length} files...`;
45
70
 
46
71
  try {
47
- const client = createClient();
48
- const result = await client.post('/api/tenant/sync', { files });
72
+ const result = await client.post('/api/tenant/sync', { files: localFiles });
49
73
 
50
74
  spinner.stop();
51
75
 
@@ -1,8 +1,9 @@
1
1
  import chokidar from 'chokidar';
2
2
  import chalk from 'chalk';
3
3
  import { loadConfig } from '../config.js';
4
+ import { createClient } from '../lib/api.js';
4
5
  import { sync } from './sync.js';
5
- import { join } from 'path';
6
+ import { join, relative } from 'path';
6
7
 
7
8
  export async function watch() {
8
9
  const config = loadConfig();
@@ -16,28 +17,57 @@ export async function watch() {
16
17
  console.log(chalk.cyan('👀 Watching'), tenantDir);
17
18
  console.log(chalk.gray(' Ctrl+C to stop\n'));
18
19
 
19
- let debounceTimer = null;
20
+ let syncTimer = null;
21
+ const pendingDeletes = [];
20
22
 
21
23
  const watcher = chokidar.watch(join(tenantDir, '**/*.php'), {
22
24
  ignoreInitial: true,
23
25
  awaitWriteFinish: { stabilityThreshold: 300 },
24
26
  });
25
27
 
26
- watcher.on('all', (event, path) => {
27
- console.log(chalk.gray(` ${event}: ${path.replace(tenantDir + '/', '')}`));
28
+ watcher.on('add', (path) => {
29
+ console.log(chalk.green(` + ${relative(tenantDir, path)}`));
30
+ debouncedSync();
31
+ });
32
+
33
+ watcher.on('change', (path) => {
34
+ console.log(chalk.yellow(` ~ ${relative(tenantDir, path)}`));
35
+ debouncedSync();
36
+ });
28
37
 
29
- // Debounce: wait 500ms after last change
30
- if (debounceTimer) clearTimeout(debounceTimer);
31
- debounceTimer = setTimeout(async () => {
38
+ watcher.on('unlink', (path) => {
39
+ const relPath = relative(tenantDir, path);
40
+ console.log(chalk.red(` - ${relPath}`));
41
+ pendingDeletes.push(relPath);
42
+ debouncedSync();
43
+ });
44
+
45
+ function debouncedSync() {
46
+ if (syncTimer) clearTimeout(syncTimer);
47
+ syncTimer = setTimeout(async () => {
32
48
  try {
49
+ // Delete files first
50
+ if (pendingDeletes.length > 0) {
51
+ const toDelete = [...pendingDeletes];
52
+ pendingDeletes.length = 0;
53
+ try {
54
+ const client = createClient();
55
+ const result = await client.post('/api/tenant/delete-files', { files: toDelete });
56
+ if (result.deleted > 0) {
57
+ console.log(chalk.red(` ✓ ${result.deleted} deleted on server`));
58
+ }
59
+ } catch (err) {
60
+ console.error(chalk.red(` Delete error: ${err.message}`));
61
+ }
62
+ }
63
+ // Then sync remaining files
33
64
  await sync();
34
65
  } catch (err) {
35
66
  console.error(chalk.red(` Sync error: ${err.message}`));
36
67
  }
37
68
  }, 500);
38
- });
69
+ }
39
70
 
40
- // Keep process alive
41
71
  process.on('SIGINT', () => {
42
72
  watcher.close();
43
73
  console.log(chalk.gray('\nStopped.'));