wanas-zcrm-extractor 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,140 @@
1
+ # Zoho CRM V8 Metadata Extractor
2
+
3
+ A robust, production-quality local web application built using **Node.js** and **Express** that authenticates with Zoho CRM using OAuth2 (V8 APIs) and extracts all CRM metadata into structured JSON schemas stored locally.
4
+
5
+ This tool is designed with a clean, modular architecture, enabling future extensions for Zoho Creator and Zoho Projects metadata extraction.
6
+
7
+ ---
8
+
9
+ ## 🌟 Key Features
10
+
11
+ 1. **OAuth2 Connection Dashboard:** A premium, glassmorphic dark-theme user interface with a real-time system status indicator.
12
+ 2. **Server-Sent Events (SSE) Progress Terminal:** Displays live step-by-step extraction logs from the backend directly in a console window.
13
+ 3. **Consolidated Data Analysis:** Generates:
14
+ - Module summaries detailing field types, required fields, picklists, formulas, lookup associations, and layout counts.
15
+ - A unified `field_map.json` linking all module fields to their data types.
16
+ - A consolidated `complete_metadata.json` merging all API responses into a single large object.
17
+ 4. **Interactive Module Explorer:** Browse, search, filter, and inspect extracted modules, fields, related lists, and raw summaries through an interactive modal viewer.
18
+ 5. **Consolidated ZIP Downloads:** Automatically packages all exported data in a single zip archive using native compression.
19
+ 6. **Robust Error Handling:**
20
+ - Automatic token refreshes on expiry.
21
+ - Retry mechanism for API rate limits (HTTP 429) using exponential backoff.
22
+ - Detailed failure logging in `storage/logs/errors.log`.
23
+
24
+ ---
25
+
26
+ ## 📂 Project Structure
27
+
28
+ ```text
29
+ project/
30
+ ├── public/ # Glassmorphic Frontend UI Assets
31
+ │ ├── index.html # Dashboard HTML
32
+ │ ├── style.css # Premium Dark-Theme Styles
33
+ │ └── app.js # SSE Event Listeners & Interactive Explorer logic
34
+
35
+ ├── src/
36
+ │ ├── controllers/ # Handles Route Requests (OAuth, Extraction, Downloads)
37
+ │ │ ├── authController.js
38
+ │ │ └── metadataController.js
39
+ │ │
40
+ │ ├── middleware/ # Security & Validation Middlewares
41
+ │ │ └── authMiddleware.js
42
+ │ │
43
+ │ ├── routes/ # API Endpoints Routing
44
+ │ │ ├── authRoutes.js
45
+ │ │ └── metadataRoutes.js
46
+ │ │
47
+ │ ├── services/ # Business Logic Layer
48
+ │ │ ├── authService.js # Token Storage, Validation, and Refresh Flow
49
+ │ │ └── metadata/ # Platform-Specific Metadata Extractors
50
+ │ │ ├── baseMetadataService.js # Abstract Base Extractor
51
+ │ │ ├── crmMetadataService.js # CRM V8 Extraction Implementation
52
+ │ │ ├── creatorMetadataService.js # Creator Extractor Stub (Future use)
53
+ │ │ └── projectsMetadataService.js # Projects Extractor Stub (Future use)
54
+ │ │
55
+ │ └── utils/ # Common Utilities
56
+ │ ├── apiClient.js # Axios wrapper with retry, rate-limiting & refresh
57
+ │ └── logger.js # Writes errors/logs to storage/logs/errors.log
58
+
59
+ ├── storage/ # Local Filesystem Output
60
+ │ ├── auth/
61
+ │ │ └── tokens.json # Saved OAuth2 Access and Refresh Tokens
62
+ │ ├── logs/
63
+ │ │ └── errors.log # API and Process Error Logs
64
+ │ └── metadata/ # Extracted JSON Metadata Files
65
+ │ ├── index.json # Master Index file
66
+ │ ├── modules.json # List of Modules
67
+ │ ├── export/ # Export Files (field_map, complete_metadata, modules)
68
+ │ └── modules/ # Module details, layouts, custom_views, related_lists
69
+
70
+ ├── .env # Local Environment Configuration variables
71
+ ├── package.json # Node.js dependencies
72
+ └── server.js # Application Express Server Entrypoint
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 🛠️ Setup & Installation
78
+
79
+ ### 1. Prerequisite
80
+ Ensure you have [Node.js](https://nodejs.org/) (v18+ LTS recommended) installed.
81
+
82
+ ### 2. Install Dependencies
83
+ Run the following command inside the root folder:
84
+ ```bash
85
+ npm install
86
+ ```
87
+
88
+ ### 3. Environment Configuration
89
+ Create a `.env` file in the root directory and configure your Zoho client credentials:
90
+ ```env
91
+ PORT=3000
92
+ ZOHO_CLIENT_ID=your_client_id_here
93
+ ZOHO_CLIENT_SECRET=your_client_secret_here
94
+ ZOHO_REDIRECT_URI=http://localhost:3000/zoho/callback
95
+ ZOHO_DC=com
96
+ ```
97
+ *Note: Make sure your Client ID and Client Secret match the data center domain configured in `ZOHO_DC` (e.g. `com`, `eu`, `in`, `com.au`).*
98
+
99
+ ---
100
+
101
+ ## 🚀 Running the Application
102
+
103
+ ### Development Mode
104
+ Runs the server using `nodemon` to automatically restart on code changes:
105
+ ```bash
106
+ npm run dev
107
+ ```
108
+
109
+ ### Production Mode
110
+ Runs the server normally:
111
+ ```bash
112
+ npm start
113
+ ```
114
+
115
+ Once running, navigate to the local dashboard in your browser:
116
+ **[http://localhost:3000](http://localhost:3000)**
117
+
118
+ ---
119
+
120
+ ## 🔄 How the Authentication & Extraction Works
121
+
122
+ 1. **Authentication:**
123
+ - Click **Login To Zoho** on the dashboard. You will be redirected to Zoho's secure OAuth2 consent screen.
124
+ - Upon granting permissions, Zoho redirects back to `http://localhost:3000/zoho/callback`.
125
+ - The server exchanges the authorization code for `access_token` and `refresh_token`, storing them securely in `storage/auth/tokens.json`.
126
+
127
+ 2. **Metadata Extraction:**
128
+ - Click **Extract Metadata** to fire the extraction task.
129
+ - The UI connects to `/extract?stream=true` using standard Server-Sent Events, opening a live logging terminal showing the crawler step-by-step.
130
+ - The service walks through all modules, queries fields, layouts, related lists, custom views, webhooks, and custom functions.
131
+ - Summary files are written sequentially on the disk.
132
+ - The master index and consolidated export files are compiled once the extraction completes.
133
+
134
+ ---
135
+
136
+ ## 🔧 Extensibility (Zoho Creator & Zoho Projects)
137
+
138
+ To extend the extractor to Zoho Creator or Zoho Projects, simply:
139
+ 1. Implement the extraction pipeline inside `src/services/metadata/creatorMetadataService.js` or `projectsMetadataService.js` which already inherit from the abstract `BaseMetadataService` template.
140
+ 2. Update the target selector inside `public/index.html` to enable the platform option.
package/banner.txt ADDED
@@ -0,0 +1,7 @@
1
+ ___
2
+ \ \ __ __ _
3
+ ___ \ \ \ \ / /_ _ _ __ __ _ ___ / \ _ __ _ __ ___
4
+ / / / / \ \ /\ / / _` | '_ \ / _` / __| / _ \ | '_ \| '_ \/ __|
5
+ / / /__/ \ V V / (_| | | | | (_| \__ \ / ___ \| |_) | |_) \__ \
6
+ \ \ \_/\_/ \__,_|_| |_|\__,_|___/ /_/ \_\ .__/| .__/|___/
7
+ \__\ |_| |_|
package/bin/cli.js ADDED
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Enable CLI Mode configuration before loading services
4
+ process.env.ZCRM_CLI_MODE = 'true';
5
+
6
+ const { program } = require('commander');
7
+ const inquirer = require('inquirer').default || require('inquirer');
8
+ const http = require('http');
9
+ const url = require('url');
10
+ const path = require('path');
11
+ const { exec } = require('child_process');
12
+ const configStore = require('../src/utils/configStore');
13
+ const authService = require('../src/services/authService');
14
+ const crmMetadataService = require('../src/services/metadata/crmMetadataService');
15
+
16
+ // Default client credentials for generating Zoho login links
17
+ const DEFAULT_CLIENT_ID = '1000.M1XE1M7CA4VFDDIRANB8E5AKIBQ72U';
18
+ const DEFAULT_CLIENT_SECRET = 'baae847487d4d1fc762214bad5aeb272dd02f6f991';
19
+
20
+ // Load and color the ASCII banner
21
+ const getBanner = () => {
22
+ try {
23
+ const fs = require('fs');
24
+ const bannerPath = path.join(__dirname, '../banner.txt');
25
+ if (fs.existsSync(bannerPath)) {
26
+ const banner = fs.readFileSync(bannerPath, 'utf8');
27
+ return `\x1b[34m${banner}\x1b[0m\n`;
28
+ }
29
+ } catch (err) {
30
+ // Fail silently
31
+ }
32
+ return '';
33
+ };
34
+
35
+ /**
36
+ * Draws a beautiful text progress bar in the console.
37
+ * @param {number} current - Current completed index
38
+ * @param {number} total - Total items to complete
39
+ * @param {string} label - The phase label (e.g., 'Modules', 'Functions')
40
+ * @param {string} action - The currently executing specific item
41
+ */
42
+ function drawProgress(current, total, label, action) {
43
+ const readline = require('readline');
44
+ const width = 30;
45
+ const percent = total > 0 ? Math.min(100, Math.max(0, Math.round((current / total) * 100))) : 0;
46
+ const filled = Math.round((percent / 100) * width);
47
+ const empty = width - filled;
48
+
49
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
50
+ const truncatedAction = action.length > 40 ? action.substring(0, 37) + '...' : action;
51
+
52
+ // Clear the current line and move cursor to start
53
+ readline.clearLine(process.stdout, 0);
54
+ readline.cursorTo(process.stdout, 0);
55
+
56
+ // Print progress bar, percentage, and label in beautiful colors
57
+ process.stdout.write(`\x1b[34m[${bar}]\x1b[0m \x1b[1;32m${percent}%\x1b[0m | \x1b[1m${label}\x1b[0m: ${truncatedAction}`);
58
+ }
59
+
60
+ // Version and metadata configuration
61
+ program
62
+ .name('zcrm')
63
+ .description('Zoho CRM V8 Metadata Extractor CLI Utility')
64
+ .version('1.0.0')
65
+ .addHelpText('before', getBanner());
66
+
67
+ /**
68
+ * CLI Callback Server validation & token retrieval flow.
69
+ * Spins up a temporary server to process Zoho redirect callback.
70
+ *
71
+ * @param {string} clientId - Zoho Client ID.
72
+ * @param {string} clientSecret - Zoho Client Secret.
73
+ * @param {string} redirectUri - Zoho Redirect URI (configured in developer console).
74
+ * @param {string} dc - Zoho DC domain.
75
+ * @returns {Promise<void>}
76
+ */
77
+ function runOAuthServer(clientId, clientSecret, redirectUri, dc) {
78
+ return new Promise((resolve, reject) => {
79
+ let parsedUrl;
80
+ try {
81
+ parsedUrl = new URL(redirectUri);
82
+ } catch (e) {
83
+ return reject(new Error(`Invalid redirect URI structure: ${redirectUri}`));
84
+ }
85
+
86
+ const port = parsedUrl.port || 80;
87
+
88
+ const server = http.createServer(async (req, res) => {
89
+ const parsedReqUrl = url.parse(req.url, true);
90
+
91
+ // Handle the redirect callback path
92
+ if (parsedReqUrl.pathname === parsedUrl.pathname) {
93
+ const code = parsedReqUrl.query.code;
94
+ const error = parsedReqUrl.query.error;
95
+
96
+ if (error) {
97
+ res.writeHead(400, { 'Content-Type': 'text/html' });
98
+ res.end(`
99
+ <html>
100
+ <body style="font-family: Arial, sans-serif; text-align: center; padding-top: 50px; background-color: #0b0f19; color: #f3f4f6;">
101
+ <h1 style="color: #ef4444;">Authorization Failed</h1>
102
+ <p>${error}</p>
103
+ </body>
104
+ </html>
105
+ `);
106
+ server.close();
107
+ reject(new Error(`Authorization failed: ${error}`));
108
+ return;
109
+ }
110
+
111
+ if (code) {
112
+ res.writeHead(200, { 'Content-Type': 'text/html' });
113
+ res.end(`
114
+ <html>
115
+ <body style="font-family: Arial, sans-serif; text-align: center; padding-top: 50px; background-color: #0b0f19; color: #f3f4f6;">
116
+ <h1 style="color: #10b981;">Authentication Successful!</h1>
117
+ <p>You may now close this browser window and return to your terminal.</p>
118
+ </body>
119
+ </html>
120
+ `);
121
+
122
+ try {
123
+ console.log('\nCode received. Fetching tokens from Zoho accounts server...');
124
+
125
+ // Save client credentials to configStore so handleCallback has them
126
+ configStore.save({
127
+ client_id: clientId,
128
+ client_secret: clientSecret,
129
+ redirect_uri: redirectUri,
130
+ dc: dc
131
+ });
132
+
133
+ await authService.handleCallback(code);
134
+ server.close();
135
+ resolve();
136
+ } catch (exchangeErr) {
137
+ server.close();
138
+ reject(exchangeErr);
139
+ }
140
+ } else {
141
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
142
+ res.end('Missing code parameter.');
143
+ }
144
+ } else {
145
+ res.writeHead(404);
146
+ res.end();
147
+ }
148
+ });
149
+
150
+ server.on('error', (err) => {
151
+ if (err.code === 'EADDRINUSE') {
152
+ reject(new Error(`Port ${port} is currently in use. Please stop other servers or choose another port.`));
153
+ } else {
154
+ reject(err);
155
+ }
156
+ });
157
+
158
+ server.listen(port, () => {
159
+ console.log(`Temporary listener started on port ${port}...`);
160
+
161
+ // Initialize config details for authorizer URL generation
162
+ configStore.save({
163
+ client_id: clientId,
164
+ client_secret: clientSecret,
165
+ redirect_uri: redirectUri,
166
+ dc: dc
167
+ });
168
+
169
+ const authUrl = authService.getAuthorizationUrl();
170
+
171
+ console.log('\n========================================================================');
172
+ console.log('Please complete the authentication process in your browser.');
173
+ console.log('If the browser does not open automatically, visit this URL:');
174
+ console.log(authUrl);
175
+ console.log('========================================================================\n');
176
+
177
+ // Attempt to auto-open in default browser
178
+ const launch = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
179
+ exec(`${launch} "${authUrl.replace(/"/g, '\\"')}"`, (execErr) => {
180
+ // Suppress errors (user can manually click printed link if this fails)
181
+ });
182
+ });
183
+ });
184
+ }
185
+
186
+ // REGISTER COMMAND: login
187
+ program
188
+ .command('login')
189
+ .description('Login to Zoho CRM using OAuth2 credentials')
190
+ .option('--client-id <id>', 'Zoho Client ID')
191
+ .option('--client-secret <secret>', 'Zoho Client Secret')
192
+ .option('--dc <dc>', 'Zoho Data Center (com, eu, in, jp, com.au, com.cn)')
193
+ .option('--redirect-uri <uri>', 'OAuth Redirect Callback URI')
194
+ .option('--refresh-token <token>', 'Headless Refresh Token bypass')
195
+ .action(async (options) => {
196
+ try {
197
+ let clientId = options.clientId || DEFAULT_CLIENT_ID;
198
+ let clientSecret = options.clientSecret || DEFAULT_CLIENT_SECRET;
199
+ let dc = options.dc;
200
+ let redirectUri = options.redirectUri;
201
+ const refreshToken = options.refreshToken;
202
+
203
+ // Run interactive prompts if parameters are missing
204
+ const prompts = [];
205
+ if (!dc) {
206
+ prompts.push({
207
+ type: 'select',
208
+ name: 'dc',
209
+ message: 'Select Zoho Data Center (DC):',
210
+ choices: ['com', 'eu', 'in', 'jp', 'com.au', 'com.cn'],
211
+ default: 'com'
212
+ });
213
+ }
214
+ if (!redirectUri && !refreshToken) {
215
+ prompts.push({
216
+ type: 'input',
217
+ name: 'redirectUri',
218
+ message: 'Enter your Redirect URI:',
219
+ default: 'http://localhost:3000/zoho/callback'
220
+ });
221
+ }
222
+
223
+ if (prompts.length > 0) {
224
+ const answers = await inquirer.prompt(prompts);
225
+ dc = dc || answers.dc;
226
+ redirectUri = redirectUri || answers.redirectUri;
227
+ }
228
+
229
+ // 1. Headless flow using direct refresh token
230
+ if (refreshToken) {
231
+ console.log('Bypassing browser callback. Verifying refresh token credentials...');
232
+
233
+ configStore.save({
234
+ client_id: clientId,
235
+ client_secret: clientSecret,
236
+ dc: dc || 'com',
237
+ redirect_uri: redirectUri || 'http://localhost:3000/zoho/callback',
238
+ tokens: {
239
+ refresh_token: refreshToken
240
+ }
241
+ });
242
+
243
+ // Initialize and refresh to verify key validity
244
+ authService.loadTokens();
245
+ await authService.refreshAccessToken();
246
+
247
+ console.log('🎉 Credentials and session authenticated successfully via Refresh Token!');
248
+ process.exit(0);
249
+ }
250
+
251
+ // 2. Interactive Browser OAuth2 flow
252
+ await runOAuthServer(clientId, clientSecret, redirectUri, dc);
253
+ console.log('🎉 Successfully logged in! Access configuration saved.');
254
+ process.exit(0);
255
+ } catch (err) {
256
+ console.error('\n❌ Login error:', err.message);
257
+ process.exit(1);
258
+ }
259
+ });
260
+
261
+ // REGISTER COMMAND: logout
262
+ program
263
+ .command('logout')
264
+ .description('Logout of Zoho CRM and clear stored configurations')
265
+ .action(() => {
266
+ configStore.clear();
267
+ console.log('🎉 Stored credentials cleared successfully.');
268
+ process.exit(0);
269
+ });
270
+
271
+ // REGISTER COMMAND: pull
272
+ program
273
+ .command('pull')
274
+ .description('Pull Zoho CRM metadata and save as JSON file directory structure')
275
+ .option('-o, --output <dir>', 'Output folder path', './metadata')
276
+ .action(async (options) => {
277
+ console.log(getBanner());
278
+ // Reload tokens
279
+ authService.loadTokens();
280
+
281
+ if (!authService.isAuthenticated()) {
282
+ console.error('\n❌ Error: Not authenticated. Please login first using:\n zcrm login');
283
+ process.exit(1);
284
+ }
285
+
286
+ const outputDir = path.resolve(options.output);
287
+ console.log(`\x1b[36mInitializing CRM Metadata Pulling...\x1b[0m`);
288
+ console.log(`Destination Folder: ${outputDir}\n`);
289
+
290
+ let currentModule = 0;
291
+ let totalModules = 0;
292
+ let currentFunction = 0;
293
+ let totalFunctions = 0;
294
+ let inModulesPhase = false;
295
+ let inFunctionsPhase = false;
296
+
297
+ try {
298
+ await crmMetadataService.extract((stage, message, details = {}) => {
299
+ switch (stage) {
300
+ case 'FETCH_ORG':
301
+ console.log(`\x1b[34mℹ\x1b[0m ${message}`);
302
+ break;
303
+ case 'FETCH_ORG_SUCCESS':
304
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
305
+ break;
306
+ case 'FETCH_MODULES':
307
+ console.log(`\x1b[34mℹ\x1b[0m ${message}`);
308
+ break;
309
+ case 'FETCH_MODULES_SUCCESS':
310
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
311
+ break;
312
+ case 'MODULE_START':
313
+ if (!inModulesPhase) {
314
+ console.log(`\n\x1b[1mExtracting Modules Metadata:\x1b[0m`);
315
+ inModulesPhase = true;
316
+ }
317
+ currentModule = details.index;
318
+ totalModules = details.total;
319
+ drawProgress(currentModule - 1, totalModules, 'Modules', `Extracting ${details.module}...`);
320
+ break;
321
+ case 'MODULE_SUCCESS':
322
+ if (currentModule === totalModules) {
323
+ const readline = require('readline');
324
+ readline.clearLine(process.stdout, 0);
325
+ readline.cursorTo(process.stdout, 0);
326
+ console.log(`\x1b[32m✔\x1b[0m All modules metadata extracted successfully.`);
327
+ inModulesPhase = false;
328
+ } else {
329
+ drawProgress(currentModule, totalModules, 'Modules', `Completed ${details.module || ''}`);
330
+ }
331
+ break;
332
+ case 'FETCH_WEBHOOKS':
333
+ console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
334
+ break;
335
+ case 'FETCH_WEBHOOKS_SUCCESS':
336
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
337
+ break;
338
+ case 'FETCH_PROFILES':
339
+ console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
340
+ break;
341
+ case 'FETCH_PROFILES_SUCCESS':
342
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
343
+ break;
344
+ case 'FETCH_ROLES':
345
+ console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
346
+ break;
347
+ case 'FETCH_ROLES_SUCCESS':
348
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
349
+ break;
350
+ case 'FETCH_FUNCTIONS':
351
+ console.log(`\n\x1b[34mℹ\x1b[0m ${message}`);
352
+ break;
353
+ case 'FETCH_FUNCTIONS_SUCCESS':
354
+ console.log(`\x1b[32m✔\x1b[0m ${message}`);
355
+ break;
356
+ case 'FUNCTION_START':
357
+ if (!inFunctionsPhase) {
358
+ console.log(`\n\x1b[1mExtracting Custom Functions Deluge Scripts:\x1b[0m`);
359
+ inFunctionsPhase = true;
360
+ }
361
+ currentFunction = details.index;
362
+ totalFunctions = details.total;
363
+ drawProgress(currentFunction - 1, totalFunctions, 'Functions', `Downloading ${details.name}...`);
364
+ break;
365
+ case 'FUNCTION_SUCCESS':
366
+ case 'FUNCTION_WARNING':
367
+ if (currentFunction === totalFunctions) {
368
+ const readline = require('readline');
369
+ readline.clearLine(process.stdout, 0);
370
+ readline.cursorTo(process.stdout, 0);
371
+ console.log(`\x1b[32m✔\x1b[0m All custom functions deluge scripts extracted.`);
372
+ inFunctionsPhase = false;
373
+ } else {
374
+ drawProgress(currentFunction, totalFunctions, 'Functions', `Completed ${details.name || ''}`);
375
+ }
376
+ break;
377
+ case 'FAILURE':
378
+ if (inModulesPhase || inFunctionsPhase) {
379
+ process.stdout.write('\n');
380
+ }
381
+ console.error(`\n❌ Error: ${message}`);
382
+ break;
383
+ case 'COMPLETE':
384
+ console.log('\n\x1b[1;32m====================================================================\x1b[0m');
385
+ console.log('🎉 \x1b[1;32mZoho CRM Metadata Pull Completed Successfully!\x1b[0m');
386
+ console.log('\x1b[1;32m====================================================================\x1b[0m');
387
+ console.log(` \x1b[1mOrganization:\x1b[0m \x1b[36m${details.crm_org || 'Unknown Org'}\x1b[0m`);
388
+ console.log(` \x1b[1mModules Processed:\x1b[0m \x1b[32m${details.modulesCount || 0}\x1b[0m`);
389
+ console.log(` \x1b[1mTotal Fields:\x1b[0m \x1b[32m${details.totalFields || 0}\x1b[0m`);
390
+ console.log(` \x1b[1mTotal Layouts:\x1b[0m \x1b[32m${details.totalLayouts || 0}\x1b[0m`);
391
+ console.log(` \x1b[1mRelated Lists:\x1b[0m \x1b[35m${details.totalRelatedLists || 0}\x1b[0m (saved to extra/)`);
392
+ console.log(` \x1b[1mCustom Views:\x1b[0m \x1b[35m${details.totalCustomViews || 0}\x1b[0m (saved to extra/)`);
393
+ console.log(` \x1b[1mOutput Directory:\x1b[0m \x1b[34m${outputDir}\x1b[0m`);
394
+
395
+ if (details.errors && details.errors.length > 0) {
396
+ console.log(`\n \x1b[1;33m⚠️ Warnings:\x1b[0m \x1b[33m${details.errors.length} API request(s) failed or were skipped due to permissions.\x1b[0m`);
397
+ }
398
+ console.log('\x1b[1;32m====================================================================\x1b[0m\n');
399
+ break;
400
+ default:
401
+ if (message && !message.startsWith(' ->')) {
402
+ console.log(`\x1b[34mℹ\x1b[0m ${message}`);
403
+ }
404
+ break;
405
+ }
406
+ }, outputDir);
407
+
408
+ process.exit(0);
409
+ } catch (error) {
410
+ console.error(`\n❌ Metadata pull failed: ${error.message}`);
411
+ process.exit(1);
412
+ }
413
+ });
414
+
415
+ // REGISTER COMMAND: dashboard
416
+ program
417
+ .command('dashboard')
418
+ .description('Start the local Zoho CRM Extractor web dashboard')
419
+ .option('-p, --port <port>', 'Port to run the dashboard on', '3000')
420
+ .action((options) => {
421
+ process.env.PORT = options.port;
422
+ process.env.ZCRM_CLI_MODE = 'false';
423
+ console.log(`\x1b[36mLaunching Extractor Dashboard...\x1b[0m`);
424
+ require('../server.js');
425
+ });
426
+
427
+ // Run parser
428
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "wanas-zcrm-extractor",
3
+ "version": "1.0.0",
4
+ "description": "Local Zoho CRM V8 Metadata Extractor CLI and Web Dashboard Utility",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "zcrm": "bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node server.js",
11
+ "dev": "nodemon server.js"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "src",
16
+ "public",
17
+ "server.js",
18
+ "banner.txt",
19
+ "package.json",
20
+ "README.md"
21
+ ],
22
+ "keywords": [
23
+ "zoho",
24
+ "crm",
25
+ "metadata",
26
+ "extractor",
27
+ "oauth2",
28
+ "cli",
29
+ "deluge",
30
+ "zdk"
31
+ ],
32
+ "author": "Wanas Apps",
33
+ "license": "ISC",
34
+ "dependencies": {
35
+ "axios": "^1.7.2",
36
+ "commander": "^15.0.0",
37
+ "dotenv": "^16.4.5",
38
+ "express": "^4.19.2",
39
+ "fs-extra": "^11.2.0",
40
+ "inquirer": "^14.0.2"
41
+ },
42
+ "devDependencies": {
43
+ "nodemon": "^3.1.3"
44
+ }
45
+ }