@faable/faable 1.12.1 → 1.13.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/dist/commands/upgrade/index.js +37 -0
- package/dist/index.js +8 -1
- package/dist/lib/UpdateChecker.js +111 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { version } from '../../config.js';
|
|
3
|
+
import { isDevBuild, getLatestVersion, isNewerVersion, CLI_PACKAGE } from '../../lib/UpdateChecker.js';
|
|
4
|
+
import { cmd } from '../../lib/cmd.js';
|
|
5
|
+
import { log } from '../../log.js';
|
|
6
|
+
|
|
7
|
+
const upgrade = {
|
|
8
|
+
command: "upgrade",
|
|
9
|
+
describe: "Upgrade the Faable CLI to the latest version",
|
|
10
|
+
handler: async () => {
|
|
11
|
+
if (isDevBuild(version)) {
|
|
12
|
+
log.warn("Development build, skipping self-upgrade");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const latest = await getLatestVersion();
|
|
16
|
+
if (!latest) {
|
|
17
|
+
log.error("❌ Could not reach the npm registry to check for updates");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (!isNewerVersion(latest, version)) {
|
|
21
|
+
log.info(`✅ Already on the latest version (${version})`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const spinner = ora(`Upgrading ${CLI_PACKAGE} ${version} → ${latest}`).start();
|
|
25
|
+
try {
|
|
26
|
+
await cmd(`npm install -g ${CLI_PACKAGE}@latest`, { timeout: 180_000 });
|
|
27
|
+
spinner.succeed(`Upgraded to ${latest}`);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
spinner.fail("Upgrade failed");
|
|
31
|
+
log.error(`❌ Could not upgrade automatically. Try manually: npm install -g ${CLI_PACKAGE}@latest`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { upgrade };
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,21 @@ import { deploy } from './commands/deploy/index.js';
|
|
|
4
4
|
import { link_deprecated } from './commands/link/index.js';
|
|
5
5
|
import { login } from './commands/login/index.js';
|
|
6
6
|
import { logout } from './commands/logout/index.js';
|
|
7
|
+
import { upgrade } from './commands/upgrade/index.js';
|
|
7
8
|
import { whoami } from './commands/whoami/index.js';
|
|
8
9
|
import { version } from './config.js';
|
|
9
10
|
import { Configuration } from './lib/Configuration.js';
|
|
11
|
+
import { notifyIfUpdateAvailable } from './lib/UpdateChecker.js';
|
|
10
12
|
import { log } from './log.js';
|
|
11
13
|
|
|
12
14
|
const yg = yargs();
|
|
13
15
|
yg.scriptName('faable')
|
|
14
|
-
.middleware(function (
|
|
16
|
+
.middleware(async function (argv) {
|
|
15
17
|
log.info(`Faable CLI ${version}`);
|
|
18
|
+
// `upgrade` does its own (forced) check
|
|
19
|
+
if (argv._[0] !== 'upgrade') {
|
|
20
|
+
await notifyIfUpdateAvailable(version);
|
|
21
|
+
}
|
|
16
22
|
}, true)
|
|
17
23
|
.option('c', {
|
|
18
24
|
alias: 'config',
|
|
@@ -33,6 +39,7 @@ yg.scriptName('faable')
|
|
|
33
39
|
.command(login)
|
|
34
40
|
.command(logout)
|
|
35
41
|
.command(whoami)
|
|
42
|
+
.command(upgrade)
|
|
36
43
|
.command(link_deprecated)
|
|
37
44
|
.demandCommand(1)
|
|
38
45
|
.help()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path__default from 'path';
|
|
5
|
+
import { log } from '../log.js';
|
|
6
|
+
|
|
7
|
+
const CLI_PACKAGE = "@faable/faable";
|
|
8
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${CLI_PACKAGE}/latest`;
|
|
9
|
+
// Hitting the registry at most once a day keeps the background refresh rare.
|
|
10
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
const UPGRADE_FETCH_TIMEOUT_MS = 10_000;
|
|
12
|
+
const cache_path = () => path__default.join(os.homedir(), ".faable", "update-check.json");
|
|
13
|
+
// Local dev runs with the semantic-release placeholder version.
|
|
14
|
+
const isDevBuild = (version) => version.startsWith("0.0.0");
|
|
15
|
+
const isCacheStale = (cache, now) => {
|
|
16
|
+
if (!cache.last_check)
|
|
17
|
+
return true;
|
|
18
|
+
const last = new Date(cache.last_check).getTime();
|
|
19
|
+
return Number.isNaN(last) || now - last > CHECK_INTERVAL_MS;
|
|
20
|
+
};
|
|
21
|
+
const isNewerVersion = (latest, current) => {
|
|
22
|
+
const parse = (v) => {
|
|
23
|
+
const [core, prerelease] = v.replace(/^v/, "").split("-");
|
|
24
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map(Number);
|
|
25
|
+
return { major, minor, patch, prerelease };
|
|
26
|
+
};
|
|
27
|
+
const a = parse(latest);
|
|
28
|
+
const b = parse(current);
|
|
29
|
+
if (a.major !== b.major)
|
|
30
|
+
return a.major > b.major;
|
|
31
|
+
if (a.minor !== b.minor)
|
|
32
|
+
return a.minor > b.minor;
|
|
33
|
+
if (a.patch !== b.patch)
|
|
34
|
+
return a.patch > b.patch;
|
|
35
|
+
// Same core version: the release wins over its own prereleases.
|
|
36
|
+
return !a.prerelease && !!b.prerelease;
|
|
37
|
+
};
|
|
38
|
+
const readCache = async () => {
|
|
39
|
+
try {
|
|
40
|
+
return await fs.readJSON(cache_path());
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const writeCache = async (cache) => {
|
|
47
|
+
try {
|
|
48
|
+
await fs.ensureDir(path__default.dirname(cache_path()));
|
|
49
|
+
await fs.writeJSON(cache_path(), cache, { spaces: 2 });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// A read-only home dir shouldn't break the CLI.
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Refresh the cache from a detached child so the current run never waits on
|
|
56
|
+
// the network (update-notifier pattern): the notice shows on the next run.
|
|
57
|
+
const spawnBackgroundRefresh = () => {
|
|
58
|
+
const script = `fetch(${JSON.stringify(REGISTRY_URL)})
|
|
59
|
+
.then((r) => r.json())
|
|
60
|
+
.then((d) => require("fs").writeFileSync(${JSON.stringify(cache_path())},
|
|
61
|
+
JSON.stringify({ last_check: new Date().toISOString(), latest: d.version }, null, 2)))
|
|
62
|
+
.catch(() => {})`;
|
|
63
|
+
try {
|
|
64
|
+
spawn(process.execPath, ["-e", script], {
|
|
65
|
+
detached: true,
|
|
66
|
+
stdio: "ignore",
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
}).unref();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// No update check is worth failing a command over.
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Fetch the latest published version, updating the cache. Only used by
|
|
76
|
+
* `faable upgrade`, where the user explicitly asked and waiting is expected.
|
|
77
|
+
*/
|
|
78
|
+
const getLatestVersion = async () => {
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(REGISTRY_URL, {
|
|
81
|
+
signal: AbortSignal.timeout(UPGRADE_FETCH_TIMEOUT_MS),
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok)
|
|
84
|
+
return;
|
|
85
|
+
const data = (await res.json());
|
|
86
|
+
if (data.version) {
|
|
87
|
+
await writeCache({ last_check: new Date().toISOString(), latest: data.version });
|
|
88
|
+
}
|
|
89
|
+
return data.version;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const notifyIfUpdateAvailable = async (current) => {
|
|
96
|
+
if (isDevBuild(current) || process.env.CI || process.env.GITHUB_ACTIONS) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const cache = await readCache();
|
|
100
|
+
if (cache.latest && isNewerVersion(cache.latest, current)) {
|
|
101
|
+
log.warn(`⬆️ Update available: ${current} → ${cache.latest}. Run \`faable upgrade\` to get the latest version.`);
|
|
102
|
+
}
|
|
103
|
+
if (isCacheStale(cache, Date.now())) {
|
|
104
|
+
// Stamp the attempt first so a failing child can't cause a spawn storm:
|
|
105
|
+
// whatever happens, the next refresh is a day away.
|
|
106
|
+
await writeCache({ ...cache, last_check: new Date().toISOString() });
|
|
107
|
+
spawnBackgroundRefresh();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { CLI_PACKAGE, getLatestVersion, isCacheStale, isDevBuild, isNewerVersion, notifyIfUpdateAvailable };
|