@mohamed1_1ibrahim/dcli 1.0.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/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # dcli — Database CLI
2
+
3
+ **dcli** (short for **D**atabase **CLI**) is a command-line tool for MongoDB data management. Designed for terminal use.
4
+
5
+ ## Features
6
+
7
+ - **export** — Export a MongoDB database to JSON file(s)
8
+ - **import** — Import a database or specific collection(s) from JSON file(s)
9
+ - **ping** — Ping databases to prevent idle/inactivity pause
10
+ - **info** — Show database collections and document counts
11
+ - **add** — Register a database URI in the auto-ping list (with optional name)
12
+ - **remove** — Remove a database by URI or friendly name
13
+ - **auto-ping** — Schedule `dcli ping` to run automatically on Windows logon
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g dcli
19
+ ```
20
+
21
+ ```bash
22
+ git clone https://github.com/Mohamed-Ibrahim183/dcli
23
+ cd dcli
24
+ npm install
25
+ npm link
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### export
31
+
32
+ Export a MongoDB database to JSON file(s). Output is prettified by default.
33
+
34
+ ```bash
35
+ dcli export "mongodb://localhost:27017/mydb"
36
+ ```
37
+
38
+ | Option | Description |
39
+ |--------|-------------|
40
+ | `-o, --output <name>` | Output name (without or with `.json` extension) |
41
+ | `--format <type>` | `file` (single JSON), `split` (per collection), `all` (both) |
42
+ | `--compact` | Minify JSON output (default is prettified) |
43
+ | `--include <collections...>` | Only export these collections |
44
+ | `--exclude <collections...>` | Skip these collections |
45
+ | `--dry-run` | Preview without writing files |
46
+
47
+ If the output file already exists, it auto-increments — `file.json` → `file (1).json` → `file (2).json`.
48
+
49
+ ```bash
50
+ dcli export "mongodb://..." -o mydata
51
+ dcli export "mongodb://..." --format split
52
+ dcli export "mongodb://..." --format all --compact
53
+ dcli export "mongodb://..." --include users posts
54
+ dcli export "mongodb://..." --exclude logs analytics
55
+ dcli export "mongodb://..." --dry-run
56
+ ```
57
+
58
+ ### import
59
+
60
+ Restore from a full file, single collection file, or directory.
61
+
62
+ | `-f` path | Behavior |
63
+ |-----------|----------|
64
+ | Full `.json` file | Restore entire DB (backup + confirmation + drop) |
65
+ | Single collection `.json` | Restore that collection only |
66
+ | Directory of `.json` files | Restore each file as a collection |
67
+
68
+ ```bash
69
+ dcli import "mongodb://localhost:27017/mydb" -f backup.json
70
+ dcli import "mongodb://..." -f ./users.json
71
+ dcli import "mongodb://..." -f ./collections/
72
+ ```
73
+
74
+ ### ping
75
+
76
+ Ping databases to prevent inactivity pause.
77
+
78
+ ```bash
79
+ dcli ping # ping all from config
80
+ dcli ping "mongodb://..." # ping a raw URI
81
+ dcli ping dbName # ping by friendly name from config
82
+ dcli ping --file my-dbs.json # ping all from a custom file
83
+ ```
84
+
85
+ ### info
86
+
87
+ Show collections and document counts.
88
+
89
+ ```bash
90
+ dcli info "mongodb://localhost:27017/mydb"
91
+ ```
92
+
93
+ ### add / remove
94
+
95
+ Manage the auto-ping list (`~/.dcli/refresh.json`).
96
+
97
+ ```bash
98
+ dcli add "mongodb://..." -n dbName # add with friendly name
99
+ dcli add "mongodb://..." -n dbName # updates name if URI already exists
100
+ dcli remove "mongodb://user:pass@..." # remove by URI
101
+ dcli remove dbName # remove by friendly name
102
+ ```
103
+
104
+ ### auto-ping
105
+
106
+ Schedule `dcli ping` to run automatically 5 minutes after Windows logon.
107
+ Requires administrator privileges to create the task.
108
+
109
+ ```bash
110
+ dcli auto-ping # create the task (run terminal as Admin)
111
+ dcli auto-ping --remove # remove the task (run terminal as Admin)
112
+ ```
113
+
114
+ ## Config File
115
+
116
+ `~/.dcli/refresh.json`:
117
+
118
+ ```json
119
+ {
120
+ "databases": [
121
+ { "uri": "mongodb://cluster0.example.mongodb.net/mydb", "name": "dbName" },
122
+ "mongodb://backup.example.mongodb.net/otherdb"
123
+ ]
124
+ }
125
+ ```
126
+
127
+ Entries can be objects with `uri` and optional `name`, or plain strings (backward compatible).
128
+
129
+ ## Export Format
130
+
131
+ Single file:
132
+
133
+ ```json
134
+ {
135
+ "database": "mydb",
136
+ "exportedAt": "2026-05-25T14:30:00.000Z",
137
+ "data": {
138
+ "users": [ { "_id": "...", "name": "Alice" } ],
139
+ "posts": [ { "_id": "...", "title": "Hello" } ]
140
+ }
141
+ }
142
+ ```
143
+
144
+ Split (per collection in a folder):
145
+
146
+ ```
147
+ folder/
148
+ ├── users.json [ { "_id": "...", ... }, ... ]
149
+ ├── posts.json [ { "_id": "...", ... }, ... ]
150
+ ```
151
+
152
+ ## Dependencies
153
+
154
+ - [commander](https://github.com/tj/commander.js) — CLI framework
155
+ - [mongodb](https://github.com/mongodb/node-mongodb-native) — MongoDB driver
package/bin/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../src/main.js';
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mohamed1_1ibrahim/dcli",
3
+ "version": "1.0.0",
4
+ "description": "Database CLI - MongoDB data management tool (export, import, ping, info)",
5
+ "main": "src/main.js",
6
+ "bin": {
7
+ "dcli": "bin/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "start": "node bin/index.js"
12
+ },
13
+ "keywords": [
14
+ "mongodb",
15
+ "cli",
16
+ "database",
17
+ "backup",
18
+ "export",
19
+ "import",
20
+ "ping"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/Mohamed-Ibrahim183/dcli.git"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "files": [
31
+ "bin",
32
+ "src",
33
+ "README.md",
34
+ "package.json"
35
+ ],
36
+ "dependencies": {
37
+ "commander": "^12.0.0",
38
+ "mongodb": "^6.8.0"
39
+ }
40
+ }
@@ -0,0 +1,20 @@
1
+ import { addDatabase } from '../utils/config.js';
2
+ import { success, warn, error } from '../utils/logger.js';
3
+
4
+ export async function addCommand(uri, options) {
5
+ try {
6
+ const name = options.name;
7
+ const result = await addDatabase(uri, name);
8
+ const label = name ? `${name} (${uri})` : uri;
9
+ if (result === 'added') {
10
+ success(`Database added to ping list: ${label}`);
11
+ } else if (result === 'updated') {
12
+ success(`Name updated to "${name}" for ${uri}`);
13
+ } else {
14
+ warn(result === 'exists' && name ? `Database already exists${name ? ' with a different name' : ''}.` : `Database is already in the ping list.`);
15
+ }
16
+ } catch (err) {
17
+ error(`Failed to add database: ${err.message}`);
18
+ process.exit(1);
19
+ }
20
+ }
@@ -0,0 +1,34 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { success, error, info } from '../utils/logger.js';
3
+
4
+ const taskName = 'DCLI-AutoPing';
5
+
6
+ export async function autoPingCommand(options) {
7
+ if (options.remove) {
8
+ try {
9
+ execSync(`schtasks /delete /tn "${taskName}" /f`, { stdio: 'pipe' });
10
+ success(`Task "${taskName}" removed.`);
11
+ } catch {
12
+ error(`Failed to remove task. It may not exist or you need administrator privileges.`);
13
+ }
14
+ return;
15
+ }
16
+
17
+ try {
18
+ const createCmd = [
19
+ `schtasks /create`,
20
+ `/tn "${taskName}"`,
21
+ `/sc ONLOGON`,
22
+ `/delay 0000:05`,
23
+ `/tr "cmd /c dcli ping"`,
24
+ `/f`,
25
+ ].join(' ');
26
+
27
+ execSync(createCmd, { stdio: 'pipe' });
28
+ success(`Task "${taskName}" created successfully.`);
29
+ info(`It will run "dcli ping" 5 minutes after you log on.`);
30
+ } catch (err) {
31
+ error(`Failed to create task: ${err.message.includes('Access is denied') ? 'Please run as Administrator (right-click terminal → Run as administrator).' : err.message}`);
32
+ process.exit(1);
33
+ }
34
+ }
@@ -0,0 +1,109 @@
1
+ import { writeFile, mkdir, access } from 'node:fs/promises';
2
+ import { join, extname, dirname, basename } from 'node:path';
3
+ import { constants } from 'node:fs';
4
+ import { connect, exportDatabase, extractDbName } from '../utils/mongodb.js';
5
+ import { success, error, info, highlight } from '../utils/logger.js';
6
+
7
+ function ensureJsonExt(name) {
8
+ return extname(name) ? name : `${name}.json`;
9
+ }
10
+
11
+ function filterCollections(data, include, exclude) {
12
+ const entries = Object.entries(data);
13
+ let filtered = entries;
14
+
15
+ if (include && include.length > 0) {
16
+ const set = new Set(include);
17
+ filtered = filtered.filter(([name]) => set.has(name));
18
+ }
19
+
20
+ if (exclude && exclude.length > 0) {
21
+ const set = new Set(exclude);
22
+ filtered = filtered.filter(([name]) => !set.has(name));
23
+ }
24
+
25
+ return Object.fromEntries(filtered);
26
+ }
27
+
28
+ async function exists(filePath) {
29
+ try {
30
+ await access(filePath, constants.F_OK);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ async function getUniquePath(filePath) {
38
+ if (!(await exists(filePath))) return filePath;
39
+
40
+ const dir = dirname(filePath);
41
+ const ext = extname(filePath);
42
+ const base = basename(filePath, ext);
43
+
44
+ let counter = 1;
45
+ while (true) {
46
+ const candidate = join(dir, `${base} (${counter})${ext}`);
47
+ if (!(await exists(candidate))) return candidate;
48
+ counter++;
49
+ }
50
+ }
51
+
52
+ export async function exportCommand(uri, options) {
53
+ const dbName = extractDbName(uri);
54
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
55
+ const outputName = options.output || `data-${timestamp}-${dbName}`;
56
+ const format = options.format || 'file';
57
+ const compact = options.compact ? 0 : 2;
58
+ const include = options.include ? (Array.isArray(options.include) ? options.include : [options.include]) : [];
59
+ const exclude = options.exclude ? (Array.isArray(options.exclude) ? options.exclude : [options.exclude]) : [];
60
+
61
+ try {
62
+ const client = await connect(uri);
63
+ const rawData = await exportDatabase(client);
64
+ await client.close();
65
+
66
+ const data = filterCollections(rawData, include, exclude);
67
+
68
+ if (Object.keys(data).length === 0) {
69
+ info('No collections matched the given filters.');
70
+ process.exit(0);
71
+ }
72
+
73
+ if (options.dryRun) {
74
+ highlight('── Dry Run ──');
75
+ info(`Database: ${dbName}`);
76
+ info(`Collections: ${Object.keys(data).length}`);
77
+ for (const [name, docs] of Object.entries(data)) {
78
+ info(` ${name}: ${docs.length} document(s)`);
79
+ }
80
+ info(`Format: ${format === 'all' ? 'file + split' : format}`);
81
+ if (outputName) info(`Output: ${outputName}`);
82
+ return;
83
+ }
84
+
85
+ if (format === 'file' || format === 'all') {
86
+ const fileName = await getUniquePath(ensureJsonExt(outputName));
87
+ const exportData = {
88
+ database: dbName,
89
+ exportedAt: new Date().toISOString(),
90
+ data,
91
+ };
92
+ await writeFile(fileName, JSON.stringify(exportData, null, compact), 'utf-8');
93
+ success(`Exported to ${fileName}`);
94
+ }
95
+
96
+ if (format === 'split' || format === 'all') {
97
+ const dirName = extname(outputName) ? outputName.replace(/\.[^.]+$/, '') : outputName;
98
+ await mkdir(dirName, { recursive: true });
99
+ for (const [name, docs] of Object.entries(data)) {
100
+ const filePath = await getUniquePath(join(dirName, `${name}.json`));
101
+ await writeFile(filePath, JSON.stringify(docs, null, compact), 'utf-8');
102
+ }
103
+ success(`Exported ${Object.keys(data).length} collection(s) to ${dirName}/`);
104
+ }
105
+ } catch (err) {
106
+ error(`Failed to export database: ${err.message}`);
107
+ process.exit(1);
108
+ }
109
+ }
@@ -0,0 +1,116 @@
1
+ import { readFile, writeFile, readdir } from 'node:fs/promises';
2
+ import { statSync } from 'node:fs';
3
+ import { join, parse } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ import { stdin as input, stdout as output } from 'node:process';
6
+ import { connect, exportDatabase, dropDatabase, importDatabase, extractDbName } from '../utils/mongodb.js';
7
+ import { info, warn, success, error } from '../utils/logger.js';
8
+
9
+ function askQuestion(query) {
10
+ const rl = createInterface({ input, output });
11
+ return new Promise((resolve) => {
12
+ rl.question(query, (answer) => {
13
+ rl.close();
14
+ resolve(answer.trim().toLowerCase());
15
+ });
16
+ });
17
+ }
18
+
19
+ function isFullFormat(data) {
20
+ return data && typeof data === 'object' && 'database' in data && 'data' in data;
21
+ }
22
+
23
+ async function loadCollectionFile(filePath) {
24
+ const content = await readFile(filePath, 'utf-8');
25
+ const data = JSON.parse(content);
26
+ const name = parse(filePath).name;
27
+ return { name, data: isFullFormat(data) ? data.data : { [name]: Array.isArray(data) ? data : [] } };
28
+ }
29
+
30
+ async function loadDirectory(dirPath) {
31
+ const entries = await readdir(dirPath);
32
+ const jsonFiles = entries.filter(e => e.endsWith('.json')).sort();
33
+ const result = {};
34
+ for (const file of jsonFiles) {
35
+ const { data } = await loadCollectionFile(join(dirPath, file));
36
+ Object.assign(result, data);
37
+ }
38
+ return result;
39
+ }
40
+
41
+ export async function importCommand(uri, options) {
42
+ const filePath = options.file;
43
+
44
+ try {
45
+ const isDir = statSync(filePath).isDirectory();
46
+ let dataToRestore;
47
+ let restoreType;
48
+
49
+ if (isDir) {
50
+ dataToRestore = await loadDirectory(filePath);
51
+ restoreType = 'directory';
52
+ } else {
53
+ const content = await readFile(filePath, 'utf-8');
54
+ const parsed = JSON.parse(content);
55
+ if (isFullFormat(parsed)) {
56
+ dataToRestore = parsed.data;
57
+ restoreType = 'full';
58
+ } else {
59
+ const name = parse(filePath).name;
60
+ dataToRestore = { [name]: Array.isArray(parsed) ? parsed : [] };
61
+ restoreType = 'collection';
62
+ }
63
+ }
64
+
65
+ const collectionCount = Object.keys(dataToRestore).length;
66
+
67
+ if (restoreType === 'full') {
68
+ const backupName = `backup-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
69
+ warn('This operation will DELETE the current database and restore from file.');
70
+ info(`Source: ${filePath}`);
71
+ info(`Backup will be saved to: ${backupName}`);
72
+ const answer = await askQuestion('Are you sure you want to proceed? (yes/no): ');
73
+
74
+ if (answer !== 'yes') {
75
+ info('Restore cancelled.');
76
+ return;
77
+ }
78
+
79
+ info('Creating backup of current database...');
80
+ const client = await connect(uri);
81
+ const currentData = await exportDatabase(client);
82
+ await writeFile(backupName, JSON.stringify({
83
+ database: extractDbName(uri),
84
+ exportedAt: new Date().toISOString(),
85
+ data: currentData,
86
+ }, null, 2), 'utf-8');
87
+ success(`Backup saved to ${backupName}`);
88
+
89
+ info('Dropping current database...');
90
+ await dropDatabase(client);
91
+ success('Database dropped.');
92
+
93
+ info('Restoring data...');
94
+ await importDatabase(client, dataToRestore);
95
+ await client.close();
96
+ success('Database restored successfully.');
97
+ } else {
98
+ info(`This will restore ${collectionCount} collection(s) from: ${filePath}`);
99
+ const answer = await askQuestion('Are you sure you want to proceed? (yes/no): ');
100
+
101
+ if (answer !== 'yes') {
102
+ info('Restore cancelled.');
103
+ return;
104
+ }
105
+
106
+ info('Restoring data...');
107
+ const client = await connect(uri);
108
+ await importDatabase(client, dataToRestore);
109
+ await client.close();
110
+ success(`${collectionCount} collection(s) restored successfully.`);
111
+ }
112
+ } catch (err) {
113
+ error(`Failed to restore data: ${err.message}`);
114
+ process.exit(1);
115
+ }
116
+ }
@@ -0,0 +1,27 @@
1
+ import { connect, extractDbName } from '../utils/mongodb.js';
2
+ import { info, error, highlight } from '../utils/logger.js';
3
+
4
+ export async function infoCommand(uri) {
5
+ try {
6
+ const client = await connect(uri);
7
+ const db = client.db();
8
+ const dbName = extractDbName(uri);
9
+ const collections = await db.listCollections().toArray();
10
+
11
+ highlight(`── ${dbName} ──`);
12
+ info(`Collections: ${collections.length}`);
13
+
14
+ let totalDocs = 0;
15
+ for (const coll of collections) {
16
+ const count = await db.collection(coll.name).countDocuments();
17
+ totalDocs += count;
18
+ info(` ${coll.name}: ${count} document(s)`);
19
+ }
20
+
21
+ info(`Total documents: ${totalDocs}`);
22
+ await client.close();
23
+ } catch (err) {
24
+ error(`Failed to get database info: ${err.message}`);
25
+ process.exit(1);
26
+ }
27
+ }
@@ -0,0 +1,58 @@
1
+ import { connect, pingDatabase, extractDbName } from '../utils/mongodb.js';
2
+ import { readConfig, normalizeEntry } from '../utils/config.js';
3
+ import { info, success, warn, error } from '../utils/logger.js';
4
+
5
+ async function pingUri(uri) {
6
+ try {
7
+ const client = await connect(uri);
8
+ await pingDatabase(client);
9
+ await client.close();
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function displayName(entry) {
17
+ return entry.name || extractDbName(entry.uri) || entry.uri;
18
+ }
19
+
20
+ export async function pingCommand(uri, options) {
21
+ let entries;
22
+
23
+ if (uri) {
24
+ const config = await readConfig();
25
+ const named = config.databases.find(e => e.name === uri);
26
+ entries = named ? [named] : [{ uri }];
27
+ } else if (options.file) {
28
+ try {
29
+ const { readFile } = await import('node:fs/promises');
30
+ const content = await readFile(options.file, 'utf-8');
31
+ const parsed = JSON.parse(content);
32
+ entries = (parsed.databases || []).map(normalizeEntry);
33
+ } catch (err) {
34
+ error(`Failed to read file ${options.file}: ${err.message}`);
35
+ process.exit(1);
36
+ }
37
+ } else {
38
+ const config = await readConfig();
39
+ entries = config.databases;
40
+ }
41
+
42
+ if (entries.length === 0) {
43
+ warn('No databases configured for ping. Use "dcli add <uri>" to add one.');
44
+ process.exit(0);
45
+ }
46
+
47
+ info(`Pinging ${entries.length} database(s)...`);
48
+
49
+ for (const entry of entries) {
50
+ const label = displayName(entry);
51
+ const ok = await pingUri(entry.uri);
52
+ if (ok) {
53
+ success(`Pinged ${label} successfully`);
54
+ } else {
55
+ error(`Failed to ping ${label}`);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ import { removeDatabase } from '../utils/config.js';
2
+ import { success, warn, error } from '../utils/logger.js';
3
+
4
+ export async function removeCommand(target) {
5
+ try {
6
+ const removed = await removeDatabase(target);
7
+ if (removed) {
8
+ success(`Database removed from ping list.`);
9
+ } else {
10
+ warn(`No database found matching "${target}" in the ping list.`);
11
+ }
12
+ } catch (err) {
13
+ error(`Failed to remove database: ${err.message}`);
14
+ process.exit(1);
15
+ }
16
+ }
package/src/main.js ADDED
@@ -0,0 +1,86 @@
1
+ import { Command } from 'commander';
2
+ import { exportCommand } from './commands/exportCmd.js';
3
+ import { importCommand } from './commands/importCmd.js';
4
+ import { pingCommand } from './commands/pingCmd.js';
5
+ import { infoCommand } from './commands/info.js';
6
+ import { addCommand } from './commands/add.js';
7
+ import { removeCommand } from './commands/remove.js';
8
+ import { autoPingCommand } from './commands/autoPing.js';
9
+ import { generateHelp, colorizeDefaultHelp } from './utils/help.js';
10
+ import { readFileSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { dirname, join } from 'node:path';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const packageJson = JSON.parse(
17
+ readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
18
+ );
19
+
20
+ const program = new Command();
21
+
22
+ program
23
+ .name('dcli')
24
+ .description('Database CLI — MongoDB data management tool')
25
+ .version(packageJson.version);
26
+
27
+ program
28
+ .command('export')
29
+ .description('Export a MongoDB database to JSON file(s)')
30
+ .argument('<uri>', 'MongoDB connection URI')
31
+ .option('-o, --output <name>', 'Output name (default: data-<timestamp>-<db>)')
32
+ .option('--format <type>', 'Output format: file, split, all (default: file)', 'file')
33
+ .option('--compact', 'Minify JSON output (default is prettified)')
34
+ .option('--include <collections...>', 'Only export these collections')
35
+ .option('--exclude <collections...>', 'Skip these collections')
36
+ .option('--dry-run', 'Preview export without writing files')
37
+ .action(exportCommand);
38
+
39
+ program
40
+ .command('import')
41
+ .description('Import a database or collection(s) from JSON file(s)')
42
+ .argument('<uri>', 'MongoDB connection URI')
43
+ .requiredOption('-f, --file <path>', 'File (.json), collection file, or directory of .json files')
44
+ .action(importCommand);
45
+
46
+ program
47
+ .command('ping')
48
+ .description('Ping databases to prevent idle/inactivity pause')
49
+ .argument('[uri]', 'URI or friendly name from config (optional, uses config if omitted)')
50
+ .option('--file <path>', 'Path to a JSON file with database URIs (default: ~/.dcli/refresh.json)')
51
+ .action(pingCommand);
52
+
53
+ program
54
+ .command('info')
55
+ .description('Show database collections and document counts')
56
+ .argument('<uri>', 'MongoDB connection URI')
57
+ .action(infoCommand);
58
+
59
+ program
60
+ .command('add')
61
+ .description('Add a database URI to the auto-ping list')
62
+ .argument('<uri>', 'MongoDB connection URI')
63
+ .option('-n, --name <name>', 'Friendly name for this database')
64
+ .action(addCommand);
65
+
66
+ program
67
+ .command('remove')
68
+ .description('Remove a database by URI or friendly name from the ping list')
69
+ .argument('<uri>', 'URI or friendly name of the database')
70
+ .action(removeCommand);
71
+
72
+ program
73
+ .command('auto-ping')
74
+ .description('Schedule "dcli ping" to run automatically on startup')
75
+ .option('--remove', 'Remove the scheduled task')
76
+ .action(autoPingCommand);
77
+
78
+ program.helpInformation = generateHelp;
79
+
80
+ const origHelpInfo = Command.prototype.helpInformation;
81
+ for (const cmd of program.commands) {
82
+ const orig = origHelpInfo.bind(cmd);
83
+ cmd.helpInformation = () => colorizeDefaultHelp(orig());
84
+ }
85
+
86
+ program.parse();
@@ -0,0 +1,60 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const CONFIG_DIR = join(homedir(), '.dcli');
6
+ const CONFIG_PATH = join(CONFIG_DIR, 'refresh.json');
7
+
8
+ const DEFAULT_CONFIG = { databases: [] };
9
+
10
+ async function ensureConfigDir() {
11
+ await mkdir(CONFIG_DIR, { recursive: true });
12
+ }
13
+
14
+ export function normalizeEntry(entry) {
15
+ if (typeof entry === 'string') return { uri: entry };
16
+ return entry;
17
+ }
18
+
19
+ export async function readConfig() {
20
+ try {
21
+ const data = await readFile(CONFIG_PATH, 'utf-8');
22
+ const config = JSON.parse(data);
23
+ config.databases = (config.databases || []).map(normalizeEntry);
24
+ return config;
25
+ } catch {
26
+ return { databases: [] };
27
+ }
28
+ }
29
+
30
+ export async function writeConfig(config) {
31
+ ensureConfigDir();
32
+ await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
33
+ }
34
+
35
+ export async function addDatabase(uri, name) {
36
+ const config = await readConfig();
37
+ const existing = config.databases.find(e => e.uri === uri);
38
+ if (existing) {
39
+ if (name && existing.name !== name) {
40
+ existing.name = name;
41
+ await writeConfig(config);
42
+ return 'updated';
43
+ }
44
+ return 'exists';
45
+ }
46
+ config.databases.push(name ? { uri, name } : { uri });
47
+ await writeConfig(config);
48
+ return 'added';
49
+ }
50
+
51
+ export async function removeDatabase(uri) {
52
+ const config = await readConfig();
53
+ const index = config.databases.findIndex(e => e.uri === uri || e.name === uri);
54
+ if (index === -1) {
55
+ return false;
56
+ }
57
+ config.databases.splice(index, 1);
58
+ await writeConfig(config);
59
+ return true;
60
+ }
@@ -0,0 +1,217 @@
1
+ const BOLD = '\x1b[1m';
2
+ const DIM = '\x1b[2m';
3
+ const RESET = '\x1b[0m';
4
+ const CYAN = '\x1b[36m';
5
+ const GREEN = '\x1b[32m';
6
+ const YELLOW = '\x1b[33m';
7
+ const BLUE = '\x1b[34m';
8
+ const GRAY = '\x1b[90m';
9
+
10
+ const COMMANDS = [
11
+ {
12
+ name: 'export',
13
+ args: '<uri>',
14
+ desc: 'Export a MongoDB database to JSON file(s)',
15
+ options: [
16
+ ['-o, --output <name>', 'Output name (default: data-<timestamp>-<db>)'],
17
+ ['--format <type>', 'Output format: file, split, all (default: file)'],
18
+ ['--compact', 'Minify JSON output (default is prettified)'],
19
+ ['--include <col...>', 'Only export these collections'],
20
+ ['--exclude <col...>', 'Skip these collections'],
21
+ ['--dry-run', 'Preview export without writing files'],
22
+ ],
23
+ examples: [
24
+ '$ dcli export "mongodb://localhost:27017/mydb"',
25
+ '$ dcli export "mongodb://localhost:27017/mydb" -o mydata',
26
+ '$ dcli export "mongodb://localhost:27017/mydb" --format split',
27
+ '$ dcli export "mongodb://localhost:27017/mydb" --format all',
28
+ '$ dcli export "mongodb://localhost:27017/mydb" --include users posts',
29
+ '$ dcli export "mongodb://localhost:27017/mydb" --exclude logs analytics',
30
+ '$ dcli export "mongodb://localhost:27017/mydb" --dry-run',
31
+ ],
32
+ },
33
+ {
34
+ name: 'import',
35
+ args: '<uri>',
36
+ desc: 'Import a database or collection(s) from JSON file(s)',
37
+ options: [
38
+ ['-f, --file <path>', 'File (.json), collection file, or directory of .json files (required)'],
39
+ ],
40
+ examples: [
41
+ '$ dcli import "mongodb://localhost:27017/mydb" -f backup.json',
42
+ '$ dcli import "mongodb://localhost:27017/mydb" -f ./users.json',
43
+ '$ dcli import "mongodb://localhost:27017/mydb" -f ./collections/',
44
+ ],
45
+ },
46
+ {
47
+ name: 'ping',
48
+ args: '[uri]',
49
+ desc: 'Ping databases to prevent idle/inactivity pause',
50
+ options: [
51
+ ['--file <path>', 'Path to a JSON file with database URIs (default: ~/.dcli/refresh.json)'],
52
+ ],
53
+ examples: [
54
+ '$ dcli ping',
55
+ '$ dcli ping "mongodb://localhost:27017/mydb"',
56
+ '$ dcli ping --file my-dbs.json',
57
+ ],
58
+ },
59
+ {
60
+ name: 'info',
61
+ args: '<uri>',
62
+ desc: 'Show database collections and document counts',
63
+ options: [],
64
+ examples: [
65
+ '$ dcli info "mongodb://localhost:27017/mydb"',
66
+ ],
67
+ },
68
+ {
69
+ name: 'add',
70
+ args: '<uri>',
71
+ desc: 'Add a database URI to the auto-ping list',
72
+ options: [
73
+ ['-n, --name <name>', 'Friendly name for this database'],
74
+ ],
75
+ examples: [
76
+ '$ dcli add "mongodb://user:pass@cluster.mongodb.net/mydb"',
77
+ '$ dcli add "mongodb://..." -n dbName',
78
+ ],
79
+ },
80
+ {
81
+ name: 'remove',
82
+ args: '<uri|name>',
83
+ desc: 'Remove a database by URI or friendly name from the ping list',
84
+ options: [],
85
+ examples: [
86
+ '$ dcli remove "mongodb://user:pass@cluster.mongodb.net/mydb"',
87
+ '$ dcli remove dbName',
88
+ ],
89
+ },
90
+ {
91
+ name: 'auto-ping',
92
+ args: '',
93
+ desc: 'Schedule "dcli ping" to run on logon (may need admin)',
94
+ options: [
95
+ ['--remove', 'Remove the scheduled task'],
96
+ ],
97
+ examples: [
98
+ '$ dcli auto-ping',
99
+ '$ dcli auto-ping --remove',
100
+ ],
101
+ },
102
+ {
103
+ name: 'help',
104
+ args: '[command]',
105
+ desc: 'Display help for a specific command',
106
+ options: [],
107
+ examples: [],
108
+ },
109
+ ];
110
+
111
+ function label(text) {
112
+ return `${BOLD}${text}${RESET}`;
113
+ }
114
+
115
+ function cmdName(text) {
116
+ return `${YELLOW}${BOLD}${text}${RESET}`;
117
+ }
118
+
119
+ function optFlag(text) {
120
+ return `${GREEN}${text}${RESET}`;
121
+ }
122
+
123
+ function exText(text) {
124
+ return `${GRAY}${text}${RESET}`;
125
+ }
126
+
127
+ function highlight(text) {
128
+ return `${CYAN}${text}${RESET}`;
129
+ }
130
+
131
+ function dimmed(text) {
132
+ return `${DIM}${BLUE}${text}${RESET}`;
133
+ }
134
+
135
+ export function colorizeDefaultHelp(text) {
136
+ const lines = text.split('\n');
137
+ const result = [];
138
+
139
+ for (const line of lines) {
140
+ const trimmed = line.trimEnd();
141
+
142
+ if (/^Usage:/.test(trimmed)) {
143
+ result.push(`${BOLD}${trimmed}${RESET}`);
144
+ } else if (/^(Arguments|Options):$/.test(trimmed)) {
145
+ result.push(`${BOLD}${trimmed}${RESET}`);
146
+ } else if (/^\s{2}-h, --help\s/.test(trimmed)) {
147
+ result.push(`${DIM}${trimmed}${RESET}`);
148
+ } else if (/^\s{2}(-\S, )?--/.test(trimmed) && !/^Usage/.test(trimmed)) {
149
+ const sep = trimmed.slice(2).search(/\s{2,}/);
150
+ if (sep > -1) {
151
+ const flag = trimmed.slice(0, sep + 2);
152
+ const desc = trimmed.slice(sep + 2);
153
+ result.push(` ${GREEN}${flag.trim()}${RESET} ${desc}`);
154
+ } else {
155
+ result.push(` ${GREEN}${trimmed.trim()}${RESET}`);
156
+ }
157
+ } else {
158
+ result.push(trimmed);
159
+ }
160
+ }
161
+
162
+ return result.join('\n');
163
+ }
164
+
165
+ export function generateHelp() {
166
+ const lines = [];
167
+ lines.push('');
168
+ lines.push(` ${highlight('dcli')} ${DIM}— Database CLI / MongoDB data management tool${RESET}`);
169
+ lines.push('');
170
+ lines.push(` ${label('Usage:')} dcli <command> [options]`);
171
+ lines.push('');
172
+ lines.push(` ${label('Commands:')}`);
173
+ lines.push('');
174
+
175
+ for (const cmd of COMMANDS) {
176
+ const hasOpts = cmd.options.length > 0;
177
+ let display;
178
+ if (hasOpts) {
179
+ const a = cmd.args ? ` ${cmd.args}` : '';
180
+ display = `${cmdName(cmd.name)}${a} [options]`;
181
+ } else if (cmd.args) {
182
+ display = `${cmdName(cmd.name)} ${cmd.args}`;
183
+ } else {
184
+ display = cmdName(cmd.name);
185
+ }
186
+
187
+ const prefix = ` ${display}`;
188
+ const strippedLen = prefix.replace(/\x1b\[[0-9;]*m/g, '').length;
189
+ const visualPad = strippedLen < 34 ? 34 - strippedLen : 2;
190
+ lines.push(`${prefix}${' '.repeat(visualPad)}${cmd.desc}`);
191
+
192
+ if (hasOpts) {
193
+ lines.push(` ${label('Options:')}`);
194
+ for (const [flag, desc] of cmd.options) {
195
+ const f = optFlag(` ${flag}`);
196
+ const fLen = ` ${flag}`.replace(/\x1b\[[0-9;]*m/g, '').length;
197
+ const pad = fLen < 32 ? 32 - fLen : 2;
198
+ lines.push(`${f}${' '.repeat(pad)}${desc}`);
199
+ }
200
+ }
201
+
202
+ if (cmd.examples.length) {
203
+ lines.push(` ${label('Examples:')}`);
204
+ for (const ex of cmd.examples) {
205
+ lines.push(` ${exText(ex)}`);
206
+ }
207
+ }
208
+
209
+ lines.push('');
210
+ }
211
+
212
+ lines.push(` ${dimmed('Config file: ~/.dcli/refresh.json')}`);
213
+ lines.push(` ${dimmed('Repository: https://github.com/Mohamed-Ibrahim183/dcli')}`);
214
+ lines.push('');
215
+
216
+ return lines.join('\n');
217
+ }
@@ -0,0 +1,26 @@
1
+ const RESET = '\x1b[0m';
2
+ const RED = '\x1b[31m';
3
+ const GREEN = '\x1b[32m';
4
+ const YELLOW = '\x1b[33m';
5
+ const BLUE = '\x1b[34m';
6
+ const CYAN = '\x1b[36m';
7
+
8
+ export function info(message) {
9
+ console.log(`${BLUE}ℹ${RESET} ${message}`);
10
+ }
11
+
12
+ export function success(message) {
13
+ console.log(`${GREEN}✔${RESET} ${message}`);
14
+ }
15
+
16
+ export function warn(message) {
17
+ console.log(`${YELLOW}⚠${RESET} ${message}`);
18
+ }
19
+
20
+ export function error(message) {
21
+ console.log(`${RED}✖${RESET} ${message}`);
22
+ }
23
+
24
+ export function highlight(message) {
25
+ console.log(`${CYAN}${message}${RESET}`);
26
+ }
@@ -0,0 +1,53 @@
1
+ import { MongoClient } from 'mongodb';
2
+
3
+ export async function connect(uri) {
4
+ const client = new MongoClient(uri);
5
+ await client.connect();
6
+ return client;
7
+ }
8
+
9
+ export function extractDbName(uri) {
10
+ try {
11
+ const url = new URL(uri);
12
+ const db = url.pathname.replace(/^\//, '');
13
+ return db || 'test';
14
+ } catch {
15
+ return 'test';
16
+ }
17
+ }
18
+
19
+ export async function exportDatabase(client) {
20
+ const db = client.db();
21
+ const collections = await db.listCollections().toArray();
22
+ const data = {};
23
+
24
+ for (const collection of collections) {
25
+ const name = collection.name;
26
+ const docs = await db.collection(name).find({}).toArray();
27
+ data[name] = docs;
28
+ }
29
+
30
+ return data;
31
+ }
32
+
33
+ export async function importDatabase(client, data) {
34
+ const db = client.db();
35
+ const collections = Object.keys(data);
36
+
37
+ for (const name of collections) {
38
+ const docs = data[name];
39
+ if (docs.length > 0) {
40
+ await db.collection(name).insertMany(docs);
41
+ }
42
+ }
43
+ }
44
+
45
+ export async function dropDatabase(client) {
46
+ const db = client.db();
47
+ await db.dropDatabase();
48
+ }
49
+
50
+ export async function pingDatabase(client) {
51
+ const db = client.db();
52
+ await db.command({ ping: 1 });
53
+ }