@docubook/cli 0.2.7 → 0.2.9
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/package.json +1 -1
- package/src/cli/program.js +2 -155
- package/src/cli/updateHandler.js +332 -0
package/package.json
CHANGED
package/src/cli/program.js
CHANGED
|
@@ -1,113 +1,12 @@
|
|
|
1
|
-
/* global fetch */
|
|
2
1
|
import { program } from "commander";
|
|
3
2
|
import { collectUserInput } from "./promptHandler.js";
|
|
4
3
|
import { createProject } from "../installer/projectInstaller.js";
|
|
4
|
+
import { handleUpdate } from "./updateHandler.js";
|
|
5
5
|
import log from "../utils/logger.js";
|
|
6
6
|
import { renderWelcome, renderDone, renderError } from "../tui/renderer.js";
|
|
7
7
|
import { CLIState } from "../tui/state.js";
|
|
8
8
|
import { detectPackageManager, getPackageManagerInfo, getPackageManagerVersion } from "../utils/packageManagerDetect.js";
|
|
9
9
|
import { getAvailableTemplates, getTemplate, getDefaultTemplate } from "../utils/templateDetect.js";
|
|
10
|
-
import { execSync } from "child_process";
|
|
11
|
-
import ora from "ora";
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import os from "os";
|
|
14
|
-
import path from "path";
|
|
15
|
-
import { lt } from "semver";
|
|
16
|
-
|
|
17
|
-
// Helpers to show changelog once per installed version. Stores shown versions under
|
|
18
|
-
// $HOME/.docubook_cli_seen_changelogs.json as a map: { "@docubook/cli": ["1.2.3"] }
|
|
19
|
-
const _CHANGELOG_STORE = path.join(os.homedir(), ".docubook_cli_seen_changelogs.json");
|
|
20
|
-
function _readChangelogStore() {
|
|
21
|
-
try {
|
|
22
|
-
const raw = fs.readFileSync(_CHANGELOG_STORE, "utf8");
|
|
23
|
-
return JSON.parse(raw || "{}");
|
|
24
|
-
} catch {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function _writeChangelogStore(obj) {
|
|
29
|
-
try {
|
|
30
|
-
fs.writeFileSync(_CHANGELOG_STORE, JSON.stringify(obj, null, 2), { mode: 0o600 });
|
|
31
|
-
} catch {
|
|
32
|
-
// non-fatal
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function _fetchChangelogFromGitHub(version) {
|
|
37
|
-
// Fetch CHANGELOG.md from the CLI release tag format: cli-v0.2.5
|
|
38
|
-
const repo = "DocuBook/docubook";
|
|
39
|
-
const bare = version.replace(/^v/, "");
|
|
40
|
-
const tag = `cli-v${bare}`;
|
|
41
|
-
|
|
42
|
-
const candidates = [
|
|
43
|
-
`https://raw.githubusercontent.com/${repo}/${tag}/CHANGELOG.md`,
|
|
44
|
-
`https://raw.githubusercontent.com/${repo}/${tag}/CHANGELOG.MD`,
|
|
45
|
-
// Fallback to main branch
|
|
46
|
-
`https://raw.githubusercontent.com/${repo}/main/CHANGELOG.md`,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
for (const url of candidates) {
|
|
50
|
-
try {
|
|
51
|
-
const res = await fetch(url);
|
|
52
|
-
if (res && res.ok) return await res.text();
|
|
53
|
-
} catch {
|
|
54
|
-
// ignore and try next
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function _extractVersionSection(changelogText, version) {
|
|
61
|
-
if (!changelogText) return null;
|
|
62
|
-
const lines = changelogText.split(/\r?\n/);
|
|
63
|
-
// Look for headings that include the version (e.g. "## v1.2.3" or "## 1.2.3")
|
|
64
|
-
const headerRe = new RegExp(`^#{1,3}\\s*(?:v)?${version.replace(/\./g, "\\.")}(?:\\b|\\D)`, "i");
|
|
65
|
-
let start = -1;
|
|
66
|
-
for (let i = 0; i < lines.length; i++) {
|
|
67
|
-
if (headerRe.test(lines[i])) {
|
|
68
|
-
start = i;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (start === -1) return changelogText.slice(0, 2000); // fallback: return beginning of changelog
|
|
73
|
-
|
|
74
|
-
let end = lines.length;
|
|
75
|
-
for (let j = start + 1; j < lines.length; j++) {
|
|
76
|
-
// Match the next version heading (level 2, ## but not ###)
|
|
77
|
-
if (/^##\s*(?!#)/.test(lines[j])) {
|
|
78
|
-
end = j;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return lines.slice(start, end).join("\n");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function showChangelogOnce(pkgName, version) {
|
|
86
|
-
try {
|
|
87
|
-
const store = _readChangelogStore();
|
|
88
|
-
const seen = Array.isArray(store[pkgName]) ? store[pkgName] : [];
|
|
89
|
-
if (seen.includes(version)) return;
|
|
90
|
-
|
|
91
|
-
const changelog = await _fetchChangelogFromGitHub(version);
|
|
92
|
-
if (!changelog) return;
|
|
93
|
-
|
|
94
|
-
const section = _extractVersionSection(changelog, version);
|
|
95
|
-
if (!section) return;
|
|
96
|
-
|
|
97
|
-
// Print a concise changelog section
|
|
98
|
-
console.log("\n===========================================================\n");
|
|
99
|
-
console.log(section.trim());
|
|
100
|
-
console.log("\nFor full changelog, visit:");
|
|
101
|
-
console.log(` https://github.com/DocuBook/docubook/blob/main/CHANGELOG.md\n`);
|
|
102
|
-
|
|
103
|
-
// Mark as shown
|
|
104
|
-
store[pkgName] = Array.from(new Set([...seen, version]));
|
|
105
|
-
_writeChangelogStore(store);
|
|
106
|
-
} catch {
|
|
107
|
-
// silent on any error - changelog is a nicety
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
10
|
|
|
112
11
|
/**
|
|
113
12
|
* Initializes the CLI program
|
|
@@ -123,59 +22,7 @@ export function initializeProgram(version) {
|
|
|
123
22
|
.command("update")
|
|
124
23
|
.description("Check for updates and install the latest DocuBook CLI globally")
|
|
125
24
|
.action(async () => {
|
|
126
|
-
|
|
127
|
-
// declare spinner in outer scope so catch block can safely reference it
|
|
128
|
-
let spinner;
|
|
129
|
-
try {
|
|
130
|
-
// Fetch package metadata from npm registry
|
|
131
|
-
const encoded = encodeURIComponent(pkgName);
|
|
132
|
-
spinner = ora('Checking for updates...').start();
|
|
133
|
-
const res = await fetch(`https://registry.npmjs.org/${encoded}`);
|
|
134
|
-
if (!res.ok) {
|
|
135
|
-
spinner.fail(`Failed to fetch registry metadata (status ${res.status})`);
|
|
136
|
-
throw new Error(`Failed to fetch registry metadata (status ${res.status})`);
|
|
137
|
-
}
|
|
138
|
-
const data = await res.json();
|
|
139
|
-
const latest = data && data["dist-tags"] && data["dist-tags"].latest;
|
|
140
|
-
if (!latest) {
|
|
141
|
-
spinner.fail("Could not determine latest version from npm registry");
|
|
142
|
-
throw new Error("Could not determine latest version from npm registry");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Stop spinner and print a plain "Checking for updates..." line (no check mark)
|
|
146
|
-
if (spinner && typeof spinner.stop === 'function') spinner.stop();
|
|
147
|
-
console.log('Checking for updates...');
|
|
148
|
-
|
|
149
|
-
if (!lt(version, latest)) {
|
|
150
|
-
console.log(`No update needed, current version is ${version}, latest release is ${latest}`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
console.log(`Updating ${pkgName} from ${version} to ${latest}...`);
|
|
155
|
-
|
|
156
|
-
// Use npm to install globally. This will stream stdout/stderr to the user.
|
|
157
|
-
const cmd = `npm install -g ${pkgName}@${latest}`;
|
|
158
|
-
try {
|
|
159
|
-
execSync(cmd, { stdio: "inherit" });
|
|
160
|
-
console.log(`Successfully updated to ${latest}`);
|
|
161
|
-
// Try to show changelog for the newly installed version once
|
|
162
|
-
try {
|
|
163
|
-
await showChangelogOnce(pkgName, latest);
|
|
164
|
-
} catch {
|
|
165
|
-
// non-fatal
|
|
166
|
-
}
|
|
167
|
-
} catch (installErr) {
|
|
168
|
-
// If install fails, provide a helpful message
|
|
169
|
-
console.error(`Update failed: ${installErr.message || installErr}`);
|
|
170
|
-
console.error(`Try running the following command manually:\n ${cmd}\nIf you see permissions errors, consider running with elevated privileges or using a Node version manager.`);
|
|
171
|
-
process.exitCode = 1;
|
|
172
|
-
}
|
|
173
|
-
} catch (err) {
|
|
174
|
-
// ensure spinner is stopped on error
|
|
175
|
-
if (spinner && typeof spinner.stop === 'function') spinner.stop();
|
|
176
|
-
console.error(err.message || err);
|
|
177
|
-
process.exitCode = 1;
|
|
178
|
-
}
|
|
25
|
+
await handleUpdate(version);
|
|
179
26
|
});
|
|
180
27
|
|
|
181
28
|
// Expose a `version` subcommand: `docubook version`
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/* global fetch */
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { lt } from "semver";
|
|
8
|
+
|
|
9
|
+
// Changelog store to avoid showing same version multiple times
|
|
10
|
+
const _CHANGELOG_STORE = path.join(os.homedir(), ".docubook_cli_seen_changelogs.json");
|
|
11
|
+
|
|
12
|
+
function _readChangelogStore() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = fs.readFileSync(_CHANGELOG_STORE, "utf8");
|
|
15
|
+
return JSON.parse(raw || "{}");
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _writeChangelogStore(obj) {
|
|
22
|
+
try {
|
|
23
|
+
fs.writeFileSync(_CHANGELOG_STORE, JSON.stringify(obj, null, 2), { mode: 0o600 });
|
|
24
|
+
} catch {
|
|
25
|
+
// non-fatal
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect which package manager was used to install this CLI globally
|
|
31
|
+
* Returns: 'npm', 'bun', 'yarn', or 'pnpm'
|
|
32
|
+
*/
|
|
33
|
+
function detectInstalledPackageManager() {
|
|
34
|
+
try {
|
|
35
|
+
// Check npm_config_user_agent environment variable
|
|
36
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
37
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
38
|
+
if (userAgent.includes("bun")) return "bun";
|
|
39
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
40
|
+
if (userAgent.includes("npm")) return "npm";
|
|
41
|
+
|
|
42
|
+
// Fallback: check what's available in PATH
|
|
43
|
+
try {
|
|
44
|
+
execSync("pnpm --version", { stdio: "ignore" });
|
|
45
|
+
return "pnpm";
|
|
46
|
+
} catch {
|
|
47
|
+
// try next
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
52
|
+
return "bun";
|
|
53
|
+
} catch {
|
|
54
|
+
// try next
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
execSync("yarn --version", { stdio: "ignore" });
|
|
59
|
+
return "yarn";
|
|
60
|
+
} catch {
|
|
61
|
+
// try next
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Default to npm
|
|
65
|
+
return "npm";
|
|
66
|
+
} catch {
|
|
67
|
+
return "npm";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetch release info from GitHub API
|
|
73
|
+
* Returns: { tag_name, name, body, created_at, html_url } or null
|
|
74
|
+
*/
|
|
75
|
+
async function fetchLatestReleaseFromGitHub() {
|
|
76
|
+
try {
|
|
77
|
+
const owner = "DocuBook";
|
|
78
|
+
const repo = "docubook";
|
|
79
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
|
80
|
+
|
|
81
|
+
const res = await fetch(url, {
|
|
82
|
+
headers: { "Accept": "application/vnd.github.v3+json" }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = await res.json();
|
|
90
|
+
|
|
91
|
+
// Ensure it's a cli release
|
|
92
|
+
if (!data.tag_name || !data.tag_name.startsWith("cli-v")) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
tag_name: data.tag_name,
|
|
98
|
+
name: data.name,
|
|
99
|
+
body: data.body || "",
|
|
100
|
+
created_at: data.created_at,
|
|
101
|
+
html_url: data.html_url,
|
|
102
|
+
};
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate changelog from git commits between two versions
|
|
110
|
+
* Parses conventional commits and categorizes them
|
|
111
|
+
*/
|
|
112
|
+
async function generateChangelogFromCommits(fromTag, toTag) {
|
|
113
|
+
try {
|
|
114
|
+
// Get commits between two tags
|
|
115
|
+
const cmd = `git log ${fromTag}..${toTag} --pretty=format:"%H|%s|%b" -- packages/cli/ 2>/dev/null || echo ""`;
|
|
116
|
+
const output = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
117
|
+
|
|
118
|
+
if (!output || output.trim() === "") {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const commits = output.trim().split("\n").filter(Boolean).map((line) => {
|
|
123
|
+
const [hash, subject] = line.split("|");
|
|
124
|
+
return { hash: hash.slice(0, 7), subject };
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (commits.length === 0) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Categorize commits by conventional commit type
|
|
132
|
+
const categories = {
|
|
133
|
+
added: [],
|
|
134
|
+
fixed: [],
|
|
135
|
+
improved: [],
|
|
136
|
+
deprecated: [],
|
|
137
|
+
removed: [],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
commits.forEach(({ hash, subject }) => {
|
|
141
|
+
const repoUrl = "https://github.com/DocuBook/docubook/commit";
|
|
142
|
+
const link = `[${hash}](${repoUrl}/${hash})`;
|
|
143
|
+
|
|
144
|
+
if (subject.startsWith("feat") || subject.startsWith("feat:")) {
|
|
145
|
+
categories.added.push(`- ${subject.replace(/^feat(\(.*?\))?:\s*/, "")} ${link}`);
|
|
146
|
+
} else if (subject.startsWith("fix") || subject.startsWith("fix:")) {
|
|
147
|
+
categories.fixed.push(`- ${subject.replace(/^fix(\(.*?\))?:\s*/, "")} ${link}`);
|
|
148
|
+
} else if (subject.startsWith("perf") || subject.startsWith("refactor")) {
|
|
149
|
+
categories.improved.push(`- ${subject.replace(/^(perf|refactor)(\(.*?\))?:\s*/, "")} ${link}`);
|
|
150
|
+
} else if (subject.startsWith("deprecate")) {
|
|
151
|
+
categories.deprecated.push(`- ${subject.replace(/^deprecate(\(.*?\))?:\s*/, "")} ${link}`);
|
|
152
|
+
} else if (subject.startsWith("remove") || subject.startsWith("remove:")) {
|
|
153
|
+
categories.removed.push(`- ${subject.replace(/^remove(\(.*?\))?:\s*/, "")} ${link}`);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Build markdown
|
|
158
|
+
let markdown = "";
|
|
159
|
+
if (categories.added.length > 0) {
|
|
160
|
+
markdown += `### Added\n${categories.added.join("\n")}\n\n`;
|
|
161
|
+
}
|
|
162
|
+
if (categories.fixed.length > 0) {
|
|
163
|
+
markdown += `### Fixed\n${categories.fixed.join("\n")}\n\n`;
|
|
164
|
+
}
|
|
165
|
+
if (categories.improved.length > 0) {
|
|
166
|
+
markdown += `### Improved\n${categories.improved.join("\n")}\n\n`;
|
|
167
|
+
}
|
|
168
|
+
if (categories.deprecated.length > 0) {
|
|
169
|
+
markdown += `### Deprecated\n${categories.deprecated.join("\n")}\n\n`;
|
|
170
|
+
}
|
|
171
|
+
if (categories.removed.length > 0) {
|
|
172
|
+
markdown += `### Removed\n${categories.removed.join("\n")}\n\n`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return markdown.trim() || null;
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Fetch and display changelog from GitHub release
|
|
183
|
+
* Uses git commits for structured changelog if available
|
|
184
|
+
*/
|
|
185
|
+
async function showChangelogOnce(pkgName, version, releaseInfo) {
|
|
186
|
+
try {
|
|
187
|
+
const store = _readChangelogStore();
|
|
188
|
+
const seen = Array.isArray(store[pkgName]) ? store[pkgName] : [];
|
|
189
|
+
if (seen.includes(version)) return;
|
|
190
|
+
|
|
191
|
+
let changelog = "";
|
|
192
|
+
|
|
193
|
+
// Try to generate from git commits first
|
|
194
|
+
try {
|
|
195
|
+
// Get the previous release tag
|
|
196
|
+
const currentTag = `cli-v${version}`;
|
|
197
|
+
const prevTagCmd = `git describe --tags --abbrev=0 ${currentTag}^ 2>/dev/null || echo ""`;
|
|
198
|
+
const prevTag = execSync(prevTagCmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
199
|
+
|
|
200
|
+
if (prevTag && prevTag.startsWith("cli-v")) {
|
|
201
|
+
const generatedChangelog = await generateChangelogFromCommits(prevTag, currentTag);
|
|
202
|
+
if (generatedChangelog) {
|
|
203
|
+
changelog = generatedChangelog;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// fallback to release body
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fallback to release body if no commits found
|
|
211
|
+
if (!changelog && releaseInfo && releaseInfo.body) {
|
|
212
|
+
changelog = releaseInfo.body.slice(0, 2000);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!changelog) return;
|
|
216
|
+
|
|
217
|
+
// Print changelog
|
|
218
|
+
console.log("\n===========================================================\n");
|
|
219
|
+
console.log(`## ${releaseInfo?.tag_name || `cli-v${version}`}`);
|
|
220
|
+
console.log("");
|
|
221
|
+
console.log(changelog);
|
|
222
|
+
console.log("\nFull changelog, visit:");
|
|
223
|
+
console.log(` ${releaseInfo?.html_url || `https://github.com/DocuBook/docubook/releases/tag/cli-v${version}`}\n`);
|
|
224
|
+
|
|
225
|
+
// Mark as shown
|
|
226
|
+
store[pkgName] = Array.from(new Set([...seen, version]));
|
|
227
|
+
_writeChangelogStore(store);
|
|
228
|
+
} catch {
|
|
229
|
+
// silent on any error - changelog is a nicety
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Install package using detected package manager
|
|
235
|
+
* Supports npm, bun, yarn, and pnpm
|
|
236
|
+
*/
|
|
237
|
+
function installGlobal(packageName, version, packageManager) {
|
|
238
|
+
let cmd;
|
|
239
|
+
|
|
240
|
+
switch (packageManager) {
|
|
241
|
+
case "pnpm":
|
|
242
|
+
cmd = `pnpm add -g ${packageName}@${version}`;
|
|
243
|
+
break;
|
|
244
|
+
case "bun":
|
|
245
|
+
cmd = `bun add -g ${packageName}@${version}`;
|
|
246
|
+
break;
|
|
247
|
+
case "yarn":
|
|
248
|
+
cmd = `yarn global add ${packageName}@${version}`;
|
|
249
|
+
break;
|
|
250
|
+
case "npm":
|
|
251
|
+
default:
|
|
252
|
+
cmd = `npm install -g ${packageName}@${version}`;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
execSync(cmd, { stdio: "inherit" });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Main update handler: check for updates and install if available
|
|
261
|
+
*/
|
|
262
|
+
export async function handleUpdate(currentVersion) {
|
|
263
|
+
const pkgName = "@docubook/cli";
|
|
264
|
+
let spinner;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
// Detect package manager
|
|
268
|
+
const packageManager = detectInstalledPackageManager();
|
|
269
|
+
|
|
270
|
+
// Fetch package metadata from npm registry
|
|
271
|
+
const encoded = encodeURIComponent(pkgName);
|
|
272
|
+
spinner = ora("Checking for updates...").start();
|
|
273
|
+
|
|
274
|
+
const res = await fetch(`https://registry.npmjs.org/${encoded}`);
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
spinner.fail(`Failed to fetch registry metadata (status ${res.status})`);
|
|
277
|
+
throw new Error(`Failed to fetch registry metadata (status ${res.status})`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const data = await res.json();
|
|
281
|
+
const latest = data && data["dist-tags"] && data["dist-tags"].latest;
|
|
282
|
+
if (!latest) {
|
|
283
|
+
spinner.fail("Could not determine latest version from npm registry");
|
|
284
|
+
throw new Error("Could not determine latest version from npm registry");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Stop spinner and print a plain line
|
|
288
|
+
if (spinner && typeof spinner.stop === "function") spinner.stop();
|
|
289
|
+
console.log("Checking for updates...");
|
|
290
|
+
|
|
291
|
+
if (!lt(currentVersion, latest)) {
|
|
292
|
+
console.log(
|
|
293
|
+
`No update needed, current version is ${currentVersion}, latest release is ${latest}`
|
|
294
|
+
);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log(
|
|
299
|
+
`Updating ${pkgName} from ${currentVersion} to ${latest} using ${packageManager}...`
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// Fetch release info from GitHub
|
|
303
|
+
const releaseInfo = await fetchLatestReleaseFromGitHub(pkgName);
|
|
304
|
+
|
|
305
|
+
// Install using detected package manager
|
|
306
|
+
try {
|
|
307
|
+
installGlobal(pkgName, latest, packageManager);
|
|
308
|
+
console.log(`Successfully updated to ${latest}`);
|
|
309
|
+
|
|
310
|
+
// Try to show changelog for the newly installed version once
|
|
311
|
+
try {
|
|
312
|
+
await showChangelogOnce(pkgName, latest, releaseInfo);
|
|
313
|
+
} catch {
|
|
314
|
+
// non-fatal
|
|
315
|
+
}
|
|
316
|
+
} catch (installErr) {
|
|
317
|
+
// If install fails, provide a helpful message
|
|
318
|
+
const cmd = `${packageManager === "bun" ? "bun install -g" : packageManager === "yarn" ? "yarn global add" : "npm install -g"} ${pkgName}@${latest}`;
|
|
319
|
+
console.error(`Update failed: ${installErr.message || installErr}`);
|
|
320
|
+
console.error(
|
|
321
|
+
`Try running the following command manually:\n ${cmd}\n` +
|
|
322
|
+
`If you see permissions errors, consider running with elevated privileges or using a Node version manager.`
|
|
323
|
+
);
|
|
324
|
+
process.exitCode = 1;
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
// ensure spinner is stopped on error
|
|
328
|
+
if (spinner && typeof spinner.stop === "function") spinner.stop();
|
|
329
|
+
console.error(err.message || err);
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
}
|
|
332
|
+
}
|