taskmonkey-cli 0.5.2 → 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.2",
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",
@@ -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.'));