wanas-zcrm-extractor 1.6.2 → 1.7.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 +2 -0
- package/bin/cli.js +8 -0
- package/package.json +1 -1
- package/src/utils/updateCheckWorker.js +43 -0
- package/src/utils/updateNotifier.js +112 -0
package/README.md
CHANGED
|
@@ -424,6 +424,8 @@ Errors are reported in a single, consistent format: **expected** problems (e.g.
|
|
|
424
424
|
|
|
425
425
|
Every command also writes a timestamped **debug log** to `~/.zcrm/logs/` (e.g. `pull-<timestamp>.log`, `audit-<timestamp>.log`) capturing all activity for that run — including the quiet "skipped" lines and the **full API error bodies** that aren't printed to the console. When a command fails, its log path is shown in the output. `zcrm pull` additionally writes a copy into its output folder at `<output>/.zcrm/.logs/`.
|
|
426
426
|
|
|
427
|
+
The CLI also checks npm for a newer version **once a day in the background** (never blocking your command) and shows a one-line update notice when one is available. To disable it, set `NO_UPDATE_NOTIFIER=1` (or `ZCRM_NO_UPDATE_CHECK=1`); it is skipped automatically in CI.
|
|
428
|
+
|
|
427
429
|
---
|
|
428
430
|
|
|
429
431
|
## ✉️ Support & Feedback
|
package/bin/cli.js
CHANGED
|
@@ -40,6 +40,12 @@ let debugLogPath = null;
|
|
|
40
40
|
}
|
|
41
41
|
})();
|
|
42
42
|
|
|
43
|
+
// Non-blocking check for a newer published version (reads a cached result and
|
|
44
|
+
// refreshes it in a detached background process). The notice is shown on exit.
|
|
45
|
+
const { checkForUpdates, renderUpdateNotice } = require('../src/utils/updateNotifier');
|
|
46
|
+
let updateInfo = null;
|
|
47
|
+
try { updateInfo = checkForUpdates(require('../package.json')); } catch (e) { updateInfo = null; }
|
|
48
|
+
|
|
43
49
|
const { CliError, reportError } = require('../src/utils/errorReporter');
|
|
44
50
|
|
|
45
51
|
let errorReported = false;
|
|
@@ -69,6 +75,8 @@ process.on('exit', (code) => {
|
|
|
69
75
|
if (code && !errorReported && debugLogPath) {
|
|
70
76
|
try { process.stderr.write(`\x1b[90mℹ Debug log: ${debugLogPath}\x1b[0m\n`); } catch (e) { /* ignore */ }
|
|
71
77
|
}
|
|
78
|
+
// Show the "update available" notice last, after the command's own output.
|
|
79
|
+
try { renderUpdateNotice(updateInfo); } catch (e) { /* ignore */ }
|
|
72
80
|
});
|
|
73
81
|
|
|
74
82
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached background worker: fetches the latest published version of the
|
|
3
|
+
* package from the npm registry and writes it to ~/.zcrm/update-check.json.
|
|
4
|
+
* Spawned by updateNotifier so the check never blocks (or slows) a CLI command.
|
|
5
|
+
* It is silent and never throws — any failure just leaves the cache unchanged.
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
|
|
12
|
+
const pkgName = process.argv[2];
|
|
13
|
+
if (!pkgName) process.exit(0);
|
|
14
|
+
|
|
15
|
+
const CACHE_FILE = path.join(os.homedir(), '.zcrm', 'update-check.json');
|
|
16
|
+
|
|
17
|
+
function done() { try { process.exit(0); } catch (e) { /* ignore */ } }
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const req = https.get(
|
|
21
|
+
`https://registry.npmjs.org/${encodeURIComponent(pkgName)}/latest`,
|
|
22
|
+
{ timeout: 8000, headers: { Accept: 'application/json' } },
|
|
23
|
+
(res) => {
|
|
24
|
+
if (res.statusCode !== 200) { res.resume(); return done(); }
|
|
25
|
+
let body = '';
|
|
26
|
+
res.on('data', (c) => { body += c; if (body.length > 1e6) req.destroy(); });
|
|
27
|
+
res.on('end', () => {
|
|
28
|
+
try {
|
|
29
|
+
const latest = JSON.parse(body).version;
|
|
30
|
+
if (latest) {
|
|
31
|
+
fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
|
|
32
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
33
|
+
}
|
|
34
|
+
} catch (e) { /* ignore parse/write errors */ }
|
|
35
|
+
done();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
req.on('error', done);
|
|
40
|
+
req.on('timeout', () => { try { req.destroy(); } catch (e) {} done(); });
|
|
41
|
+
} catch (e) {
|
|
42
|
+
done();
|
|
43
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight, non-blocking "a new version is available" notifier.
|
|
3
|
+
*
|
|
4
|
+
* On each run it reads a small cache (~/.zcrm/update-check.json). If the cache
|
|
5
|
+
* already knows of a newer published version, `checkForUpdates` returns the info
|
|
6
|
+
* so the CLI can show a notice. When the cache is stale (older than a day) it
|
|
7
|
+
* spawns a detached background worker to refresh it for next time — so the
|
|
8
|
+
* registry lookup never blocks or slows down the command.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
|
|
15
|
+
const CACHE_FILE = path.join(os.homedir(), '.zcrm', 'update-check.json');
|
|
16
|
+
const WORKER = path.join(__dirname, 'updateCheckWorker.js');
|
|
17
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // at most once per day
|
|
18
|
+
|
|
19
|
+
function readCache() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')) || {};
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compares two `X.Y.Z` version strings. Returns true if `a` is newer than `b`.
|
|
29
|
+
* Pre-release/build suffixes are ignored (we only notify on stable bumps).
|
|
30
|
+
*/
|
|
31
|
+
function isNewer(a, b) {
|
|
32
|
+
const parse = (v) => String(v).split('-')[0].split('.').map((n) => parseInt(n, 10) || 0);
|
|
33
|
+
const pa = parse(a);
|
|
34
|
+
const pb = parse(b);
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return true;
|
|
37
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return false;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function scheduleBackgroundCheck(pkgName) {
|
|
43
|
+
try {
|
|
44
|
+
const child = spawn(process.execPath, [WORKER, pkgName], {
|
|
45
|
+
detached: true,
|
|
46
|
+
stdio: 'ignore',
|
|
47
|
+
windowsHide: true
|
|
48
|
+
});
|
|
49
|
+
child.unref();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// never let the update check break the CLI
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {{name:string, version:string}} pkg
|
|
57
|
+
* @returns {{name:string, current:string, latest:string}|null} update info, or null.
|
|
58
|
+
*/
|
|
59
|
+
function checkForUpdates(pkg) {
|
|
60
|
+
// Respect common opt-outs and skip in CI / non-interactive shells.
|
|
61
|
+
if (
|
|
62
|
+
process.env.NO_UPDATE_NOTIFIER ||
|
|
63
|
+
process.env.ZCRM_NO_UPDATE_CHECK ||
|
|
64
|
+
process.env.CI
|
|
65
|
+
) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let info = null;
|
|
70
|
+
try {
|
|
71
|
+
const cache = readCache();
|
|
72
|
+
const fresh = cache.checkedAt && (Date.now() - cache.checkedAt) < CHECK_INTERVAL_MS;
|
|
73
|
+
if (!fresh) {
|
|
74
|
+
scheduleBackgroundCheck(pkg.name);
|
|
75
|
+
}
|
|
76
|
+
if (cache.latest && isNewer(cache.latest, pkg.version)) {
|
|
77
|
+
info = { name: pkg.name, current: pkg.version, latest: cache.latest };
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return info;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Renders the update notice to stderr. Only a left border is drawn so the
|
|
87
|
+
* variable-width content (with ANSI colors) never needs right-edge alignment.
|
|
88
|
+
* @param {{name:string, current:string, latest:string}|null} info
|
|
89
|
+
*/
|
|
90
|
+
function renderUpdateNotice(info) {
|
|
91
|
+
if (!info) return;
|
|
92
|
+
const y = '\x1b[33m';
|
|
93
|
+
const dim = '\x1b[2m';
|
|
94
|
+
const green = '\x1b[1;32m';
|
|
95
|
+
const cyan = '\x1b[36m';
|
|
96
|
+
const reset = '\x1b[0m';
|
|
97
|
+
const lines = [
|
|
98
|
+
'',
|
|
99
|
+
`${y}╭──────────────────────────────────────────────╮${reset}`,
|
|
100
|
+
`${y}│${reset} 📦 Update available ${dim}${info.current}${reset} → ${green}${info.latest}${reset}`,
|
|
101
|
+
`${y}│${reset} Run ${cyan}npm i -g ${info.name}${reset} to update.`,
|
|
102
|
+
`${y}╰──────────────────────────────────────────────╯${reset}`,
|
|
103
|
+
''
|
|
104
|
+
];
|
|
105
|
+
try {
|
|
106
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { checkForUpdates, renderUpdateNotice, isNewer };
|