maskedemail-cli 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/index.js +61 -0
- package/package.json +18 -0
- package/postinstall.js +224 -0
- package/update_check.js +165 -0
package/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawn } = require("child_process");
|
|
5
|
+
|
|
6
|
+
const { ensureInstalled } = require("./postinstall");
|
|
7
|
+
const { startUpdateCheck } = require("./update_check");
|
|
8
|
+
|
|
9
|
+
const exe = process.platform === "win32" ? "maskedemail-cli.exe" : "maskedemail-cli";
|
|
10
|
+
const binPath = path.join(__dirname, exe);
|
|
11
|
+
|
|
12
|
+
const PKG = (() => {
|
|
13
|
+
try {
|
|
14
|
+
return require("./package.json");
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
(async function main() {
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(binPath)) {
|
|
23
|
+
console.error(`Binary not found: ${binPath}`);
|
|
24
|
+
console.error("Attempting to download it now...");
|
|
25
|
+
await ensureInstalled();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(binPath)) {
|
|
29
|
+
console.error(`Binary not found: ${binPath}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (process.platform !== "win32") {
|
|
34
|
+
try {
|
|
35
|
+
fs.chmodSync(binPath, 0o755);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const child = spawn(binPath, process.argv.slice(2), {
|
|
40
|
+
stdio: "inherit",
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const updateAbort = new AbortController();
|
|
45
|
+
startUpdateCheck({ installedVersion: PKG && PKG.version, signal: updateAbort.signal });
|
|
46
|
+
|
|
47
|
+
child.on("exit", (code) => {
|
|
48
|
+
try {
|
|
49
|
+
updateAbort.abort();
|
|
50
|
+
} catch {}
|
|
51
|
+
process.exitCode = code == null ? 1 : code;
|
|
52
|
+
});
|
|
53
|
+
child.on("error", (err) => {
|
|
54
|
+
console.error(err.message);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(err.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maskedemail-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Release package for maskedemail CLI",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"postinstall": "node postinstall.js"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"maskedemail": "index.js",
|
|
13
|
+
"maskedemail-cli": "index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"semver": "^7.7.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const { spawnSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const OWNER = "dvcrn";
|
|
10
|
+
const REPO = "maskedemail-cli";
|
|
11
|
+
const BIN = "maskedemail-cli";
|
|
12
|
+
const VERSION_ENV = "MASKEDEMAIL_CLI_VERSION";
|
|
13
|
+
const BASE_URL_ENV = "MASKEDEMAIL_CLI_BASE_URL";
|
|
14
|
+
const ARCH_ENV = "MASKEDEMAIL_CLI_ARCH";
|
|
15
|
+
const PLATFORM_ENV = "MASKEDEMAIL_CLI_PLATFORM";
|
|
16
|
+
const SKIP_POSTINSTALL_ENV = "MASKEDEMAIL_CLI_SKIP_POSTINSTALL";
|
|
17
|
+
const HTTP_TIMEOUT_MS = 15000;
|
|
18
|
+
const MAX_REDIRECTS = 10;
|
|
19
|
+
|
|
20
|
+
function isTruthyEnv(name) {
|
|
21
|
+
const value = process.env[name];
|
|
22
|
+
return value === "1" || value === "true";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function httpGet(url, { headers, redirectsRemaining = MAX_REDIRECTS } = {}) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const req = https.get(url, { headers }, (res) => {
|
|
28
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
29
|
+
if (redirectsRemaining <= 0) {
|
|
30
|
+
res.resume();
|
|
31
|
+
reject(new Error(`too many redirects for ${url}`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const redirectedUrl = new URL(res.headers.location, url).toString();
|
|
36
|
+
res.resume();
|
|
37
|
+
resolve(httpGet(redirectedUrl, { headers, redirectsRemaining: redirectsRemaining - 1 }));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (res.statusCode !== 200) {
|
|
41
|
+
res.resume();
|
|
42
|
+
reject(new Error(`GET ${url} -> ${res.statusCode}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const chunks = [];
|
|
46
|
+
res.on("data", (c) => chunks.push(c));
|
|
47
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
48
|
+
res.on("error", reject);
|
|
49
|
+
});
|
|
50
|
+
req.on("error", reject);
|
|
51
|
+
req.setTimeout(HTTP_TIMEOUT_MS, () => req.destroy(new Error(`timeout after ${HTTP_TIMEOUT_MS}ms`)));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sha256(buf) {
|
|
56
|
+
const h = crypto.createHash("sha256");
|
|
57
|
+
h.update(buf);
|
|
58
|
+
return h.digest("hex");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseChecksums(text) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
const lines = text
|
|
64
|
+
.split(/\r?\n/)
|
|
65
|
+
.map((l) => l.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
let m = line.match(/^([a-f0-9]{64})\s+(.+)$/i);
|
|
69
|
+
if (m) {
|
|
70
|
+
map.set(m[2], m[1]);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
m = line.match(/^sha256:([a-f0-9]{64})\s+(.+)$/i);
|
|
74
|
+
if (m) {
|
|
75
|
+
map.set(m[2], m[1]);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
m = line.match(/^SHA256\s+\((.+)\)\s+=\s+([a-f0-9]{64})$/i);
|
|
79
|
+
if (m) {
|
|
80
|
+
map.set(m[1], m[2]);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return map;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getTargetInfo() {
|
|
88
|
+
const platformRaw = process.env[PLATFORM_ENV] || process.platform;
|
|
89
|
+
const platform = platformRaw === "win32" ? "windows" : platformRaw;
|
|
90
|
+
if (!["darwin", "linux", "windows"].includes(platform)) {
|
|
91
|
+
throw new Error("maskedemail-cli: npm install supports macOS (darwin), Linux, and Windows only");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"));
|
|
95
|
+
const version = process.env[VERSION_ENV] || pkg.version || "";
|
|
96
|
+
if (!version) {
|
|
97
|
+
throw new Error("postinstall: could not determine version");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const detectedArch =
|
|
101
|
+
process.arch === "x64"
|
|
102
|
+
? "amd64"
|
|
103
|
+
: process.arch === "arm64"
|
|
104
|
+
? "arm64"
|
|
105
|
+
: process.arch === "arm"
|
|
106
|
+
? "armv7"
|
|
107
|
+
: process.arch;
|
|
108
|
+
const arch = process.env[ARCH_ENV] || detectedArch;
|
|
109
|
+
if (!["amd64", "arm64", "armv7"].includes(arch)) {
|
|
110
|
+
throw new Error(`Unsupported arch: ${arch}`);
|
|
111
|
+
}
|
|
112
|
+
if (platform === "windows" && arch === "armv7") {
|
|
113
|
+
throw new Error(`Unsupported Windows arch: ${arch}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const exe = platform === "windows" ? `${BIN}.exe` : BIN;
|
|
117
|
+
const outDir = __dirname;
|
|
118
|
+
const binPath = path.join(outDir, exe);
|
|
119
|
+
const assetName = `${BIN}_${version}_${platform}_${arch}.tar.gz`;
|
|
120
|
+
const baseOverride = process.env[BASE_URL_ENV];
|
|
121
|
+
const bases = baseOverride
|
|
122
|
+
? [baseOverride]
|
|
123
|
+
: [
|
|
124
|
+
`https://github.com/${OWNER}/${REPO}/releases/download/${version}`,
|
|
125
|
+
`https://github.com/${OWNER}/${REPO}/releases/download/v${version}`,
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
version,
|
|
130
|
+
platform,
|
|
131
|
+
arch,
|
|
132
|
+
exe,
|
|
133
|
+
outDir,
|
|
134
|
+
binPath,
|
|
135
|
+
assetName,
|
|
136
|
+
bases,
|
|
137
|
+
headers: { "User-Agent": `${REPO}-postinstall` },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function ensureExecutable(binPath) {
|
|
142
|
+
if (process.platform !== "win32") {
|
|
143
|
+
try {
|
|
144
|
+
fs.chmodSync(binPath, 0o755);
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function ensureInstalled({ log = console.log } = {}) {
|
|
150
|
+
if (isTruthyEnv(SKIP_POSTINSTALL_ENV)) {
|
|
151
|
+
log(`postinstall: skipping binary download because ${SKIP_POSTINSTALL_ENV} is set`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const target = getTargetInfo();
|
|
156
|
+
|
|
157
|
+
if (fs.existsSync(target.binPath)) {
|
|
158
|
+
ensureExecutable(target.binPath);
|
|
159
|
+
return target.binPath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let tarGz = null;
|
|
163
|
+
let baseUsed = "";
|
|
164
|
+
let lastErr = null;
|
|
165
|
+
for (const base of target.bases) {
|
|
166
|
+
const url = `${base}/${target.assetName}`;
|
|
167
|
+
log(`postinstall: downloading ${target.assetName} from ${url}`);
|
|
168
|
+
try {
|
|
169
|
+
tarGz = await httpGet(url, { headers: target.headers });
|
|
170
|
+
baseUsed = base;
|
|
171
|
+
break;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
lastErr = e;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!tarGz) throw lastErr || new Error("failed to download binary");
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const checksumsUrl = `${baseUsed}/checksums.txt`;
|
|
180
|
+
const checksumsBuf = await httpGet(checksumsUrl, { headers: target.headers });
|
|
181
|
+
const checksums = parseChecksums(checksumsBuf.toString("utf8"));
|
|
182
|
+
const sumExpected = checksums.get(target.assetName);
|
|
183
|
+
if (!sumExpected) throw new Error("asset not in checksums.txt");
|
|
184
|
+
const sumActual = sha256(tarGz);
|
|
185
|
+
if (sumActual.toLowerCase() !== sumExpected.toLowerCase()) throw new Error("checksum mismatch");
|
|
186
|
+
log("postinstall: checksum OK");
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.warn(`postinstall: checksum skipped/failed: ${e.message}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const tmpFile = path.join(os.tmpdir(), `${REPO}-${Date.now()}.tar.gz`);
|
|
192
|
+
try {
|
|
193
|
+
fs.writeFileSync(tmpFile, tarGz);
|
|
194
|
+
const tarRes = spawnSync("tar", ["-xzf", tmpFile, "-C", target.outDir, target.exe], { stdio: "inherit" });
|
|
195
|
+
if (tarRes.status !== 0) {
|
|
196
|
+
throw new Error("postinstall: failed to extract binary");
|
|
197
|
+
}
|
|
198
|
+
ensureExecutable(target.binPath);
|
|
199
|
+
log(`postinstall: installed ${target.exe} to ${target.outDir}`);
|
|
200
|
+
return target.binPath;
|
|
201
|
+
} finally {
|
|
202
|
+
try {
|
|
203
|
+
fs.unlinkSync(tmpFile);
|
|
204
|
+
} catch {}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function main() {
|
|
209
|
+
try {
|
|
210
|
+
await ensureInstalled();
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error(`postinstall error: ${err.message}`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (require.main === module) {
|
|
218
|
+
main();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
ensureInstalled,
|
|
223
|
+
getTargetInfo,
|
|
224
|
+
};
|
package/update_check.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const semver = require("semver");
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PACKAGE_NAME = "maskedemail-cli";
|
|
8
|
+
const DEFAULT_UPDATE_COMMAND = "npm install -g maskedemail-cli";
|
|
9
|
+
const CACHE_DIR_NAME = "maskedemail-cli";
|
|
10
|
+
|
|
11
|
+
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
const UPDATE_CHECK_TIMEOUT_MS = 1200;
|
|
13
|
+
|
|
14
|
+
function isDisabled() {
|
|
15
|
+
return (
|
|
16
|
+
process.env.MASKEDEMAIL_CLI_NO_UPDATE_NOTICE === "1" ||
|
|
17
|
+
process.env.MASKEDEMAIL_CLI_NO_UPDATE_NOTICE === "true" ||
|
|
18
|
+
process.env.NO_UPDATE_NOTIFIER === "1" ||
|
|
19
|
+
process.env.NO_UPDATE_NOTIFIER === "true" ||
|
|
20
|
+
process.env.CI === "1" ||
|
|
21
|
+
process.env.CI === "true" ||
|
|
22
|
+
process.env.CI === "yes"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getCacheFile() {
|
|
27
|
+
const home = os.homedir();
|
|
28
|
+
if (!home) return null;
|
|
29
|
+
|
|
30
|
+
if (process.platform === "win32") {
|
|
31
|
+
const base = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
32
|
+
if (!base) return path.join(home, "AppData", "Local", CACHE_DIR_NAME, "update.json");
|
|
33
|
+
return path.join(base, CACHE_DIR_NAME, "update.json");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
return path.join(home, "Library", "Caches", CACHE_DIR_NAME, "update.json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
|
|
41
|
+
return path.join(base, CACHE_DIR_NAME, "update.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readCache() {
|
|
45
|
+
const file = getCacheFile();
|
|
46
|
+
if (!file) return null;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function writeCache(data) {
|
|
55
|
+
const file = getCacheFile();
|
|
56
|
+
if (!file) return;
|
|
57
|
+
try {
|
|
58
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
+
fs.writeFileSync(file, JSON.stringify(data), "utf8");
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fetchLatestVersion(packageName, timeoutMs, signal) {
|
|
64
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
65
|
+
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const req = https.get(url, { headers: { "User-Agent": `${DEFAULT_PACKAGE_NAME}-update-check` } }, (res) => {
|
|
68
|
+
if (res.statusCode !== 200) {
|
|
69
|
+
reject(new Error(`GET ${url} -> ${res.statusCode}`));
|
|
70
|
+
res.resume();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const chunks = [];
|
|
75
|
+
res.on("data", (c) => chunks.push(c));
|
|
76
|
+
res.on("end", () => {
|
|
77
|
+
try {
|
|
78
|
+
const json = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
79
|
+
resolve(typeof json.version === "string" ? json.version : "");
|
|
80
|
+
} catch (e) {
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
req.on("error", reject);
|
|
87
|
+
req.setTimeout(timeoutMs, () => req.destroy(new Error("timeout")));
|
|
88
|
+
|
|
89
|
+
if (signal) {
|
|
90
|
+
if (signal.aborted) {
|
|
91
|
+
req.destroy(new Error("aborted"));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
signal.addEventListener("abort", () => req.destroy(new Error("aborted")), { once: true });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeVersion(version) {
|
|
100
|
+
if (!version) return "";
|
|
101
|
+
const cleaned = semver.clean(version);
|
|
102
|
+
if (cleaned) return cleaned;
|
|
103
|
+
const coerced = semver.coerce(version);
|
|
104
|
+
return coerced ? semver.valid(coerced) || "" : "";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function printNotice({ packageName, installed, latest, updateCommand }) {
|
|
108
|
+
const prefix = `${packageName}:`;
|
|
109
|
+
if (installed && latest) {
|
|
110
|
+
console.error(`${prefix} an update is available (installed ${installed}, latest ${latest}). Update with: ${updateCommand}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.error(`${prefix} an update is available. Update with: ${updateCommand}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function runUpdateCheck({ packageName, installedVersion, updateCommand, signal }) {
|
|
117
|
+
if (isDisabled()) return;
|
|
118
|
+
if (!process.stderr.isTTY) return;
|
|
119
|
+
|
|
120
|
+
const installed = normalizeVersion(installedVersion);
|
|
121
|
+
if (!installed) return;
|
|
122
|
+
|
|
123
|
+
const cache = readCache() || {};
|
|
124
|
+
const lastChecked = Number.isFinite(cache.lastChecked) ? cache.lastChecked : 0;
|
|
125
|
+
const lastNotified = Number.isFinite(cache.lastNotified) ? cache.lastNotified : 0;
|
|
126
|
+
const cachedLatest = typeof cache.latest === "string" ? cache.latest : "";
|
|
127
|
+
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
|
|
130
|
+
if (lastChecked && now - lastChecked < UPDATE_CHECK_INTERVAL_MS) {
|
|
131
|
+
const latestCached = normalizeVersion(cachedLatest);
|
|
132
|
+
if (latestCached && semver.gt(latestCached, installed)) {
|
|
133
|
+
if (!lastNotified || now - lastNotified >= UPDATE_CHECK_INTERVAL_MS) {
|
|
134
|
+
printNotice({ packageName, installed, latest: latestCached, updateCommand });
|
|
135
|
+
writeCache({ ...cache, lastNotified: now });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const latestRaw = await fetchLatestVersion(packageName, UPDATE_CHECK_TIMEOUT_MS, signal);
|
|
142
|
+
const latest = normalizeVersion(latestRaw);
|
|
143
|
+
|
|
144
|
+
writeCache({ lastChecked: now, lastNotified, latest: latest || latestRaw });
|
|
145
|
+
|
|
146
|
+
if (latest && semver.gt(latest, installed)) {
|
|
147
|
+
if (!lastNotified || now - lastNotified >= UPDATE_CHECK_INTERVAL_MS) {
|
|
148
|
+
printNotice({ packageName, installed, latest, updateCommand });
|
|
149
|
+
writeCache({ lastChecked: now, lastNotified: now, latest });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function startUpdateCheck({
|
|
155
|
+
packageName = DEFAULT_PACKAGE_NAME,
|
|
156
|
+
installedVersion,
|
|
157
|
+
updateCommand = DEFAULT_UPDATE_COMMAND,
|
|
158
|
+
signal,
|
|
159
|
+
} = {}) {
|
|
160
|
+
runUpdateCheck({ packageName, installedVersion, updateCommand, signal }).catch(() => {});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
startUpdateCheck,
|
|
165
|
+
};
|