@docubook/cli 0.2.3 → 0.2.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "DocuBook CLI tool that helps you initialize, update, and deploy documentation directly from your terminal.",
5
5
  "type": "module",
6
6
  "files": [
@@ -12,11 +12,6 @@
12
12
  "docubook": "src/index.js"
13
13
  },
14
14
  "main": "./src/index.js",
15
- "scripts": {
16
- "dev": "node src/index.js",
17
- "lint": "eslint src/",
18
- "lint:fix": "eslint src/ --fix"
19
- },
20
15
  "keywords": [
21
16
  "docubook",
22
17
  "documentation",
@@ -47,5 +42,10 @@
47
42
  },
48
43
  "engines": {
49
44
  "node": ">=18.0.0"
45
+ },
46
+ "scripts": {
47
+ "dev": "node src/index.js",
48
+ "lint": "eslint src/",
49
+ "lint:fix": "eslint src/ --fix"
50
50
  }
51
- }
51
+ }
@@ -1,3 +1,4 @@
1
+ /* global fetch */
1
2
  import { program } from "commander";
2
3
  import { collectUserInput } from "./promptHandler.js";
3
4
  import { createProject } from "../installer/projectInstaller.js";
@@ -6,6 +7,109 @@ import { renderWelcome, renderDone, renderError } from "../tui/renderer.js";
6
7
  import { CLIState } from "../tui/state.js";
7
8
  import { detectPackageManager, getPackageManagerInfo, getPackageManagerVersion } from "../utils/packageManagerDetect.js";
8
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
+
16
+ // Helpers to show changelog once per installed version. Stores shown versions under
17
+ // $HOME/.docubook_cli_seen_changelogs.json as a map: { "@docubook/cli": ["1.2.3"] }
18
+ const _CHANGELOG_STORE = path.join(os.homedir(), ".docubook_cli_seen_changelogs.json");
19
+ function _readChangelogStore() {
20
+ try {
21
+ const raw = fs.readFileSync(_CHANGELOG_STORE, "utf8");
22
+ return JSON.parse(raw || "{}");
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+ function _writeChangelogStore(obj) {
28
+ try {
29
+ fs.writeFileSync(_CHANGELOG_STORE, JSON.stringify(obj, null, 2), { mode: 0o600 });
30
+ } catch {
31
+ // non-fatal
32
+ }
33
+ }
34
+
35
+ async function _fetchChangelogFromGitHub(tag) {
36
+ // Try to fetch CHANGELOG.md from the repo tag. Support several tag-name variants
37
+ // (e.g. v1.2.3, 1.2.3, cli-v1.2.3, cli-1.2.3) and common filename variants.
38
+ const repo = "DocuBook/docubook";
39
+
40
+ const bare = tag.replace(/^v/, "");
41
+ const variants = [tag, bare, `cli-${tag}`, `cli-${bare}`].filter(Boolean);
42
+
43
+ const candidates = [];
44
+ for (const v of variants) {
45
+ candidates.push(`https://raw.githubusercontent.com/${repo}/${v}/CHANGELOG.md`);
46
+ candidates.push(`https://raw.githubusercontent.com/${repo}/${v}/CHANGELOG.MD`);
47
+ }
48
+ // final fallback to main branch
49
+ candidates.push(`https://raw.githubusercontent.com/${repo}/main/CHANGELOG.md`);
50
+
51
+ for (const url of candidates) {
52
+ try {
53
+ const res = await fetch(url);
54
+ if (res && res.ok) return await res.text();
55
+ } catch {
56
+ // ignore and try next
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function _extractVersionSection(changelogText, version) {
63
+ if (!changelogText) return null;
64
+ const lines = changelogText.split(/\r?\n/);
65
+ // Look for headings that include the version (e.g. "## v1.2.3" or "## 1.2.3")
66
+ const headerRe = new RegExp(`^#{1,3}\\s*(?:v)?${version.replace(/\./g, "\\.")}(?:\\b|\\D)`, "i");
67
+ let start = -1;
68
+ for (let i = 0; i < lines.length; i++) {
69
+ if (headerRe.test(lines[i])) {
70
+ start = i;
71
+ break;
72
+ }
73
+ }
74
+ if (start === -1) return changelogText.slice(0, 2000); // fallback: return beginning of changelog
75
+
76
+ let end = lines.length;
77
+ for (let j = start + 1; j < lines.length; j++) {
78
+ if (/^#{1,3}\s*/.test(lines[j])) {
79
+ end = j;
80
+ break;
81
+ }
82
+ }
83
+ return lines.slice(start, end).join("\n");
84
+ }
85
+
86
+ async function showChangelogOnce(pkgName, version) {
87
+ try {
88
+ const store = _readChangelogStore();
89
+ const seen = Array.isArray(store[pkgName]) ? store[pkgName] : [];
90
+ if (seen.includes(version)) return;
91
+
92
+ const tag = version.startsWith("v") ? version : `v${version}`;
93
+ const changelog = await _fetchChangelogFromGitHub(tag);
94
+ if (!changelog) return;
95
+
96
+ const section = _extractVersionSection(changelog, version);
97
+ if (!section) return;
98
+
99
+ // Print a concise changelog section
100
+ console.log("\n=== DocuBook CLI changelog (new) ===\n");
101
+ console.log(section.trim());
102
+ console.log("\nFor full changelog, visit:");
103
+ console.log(` https://github.com/DocuBook/docubook/blob/main/CHANGELOG.md\n`);
104
+
105
+ // Mark as shown
106
+ store[pkgName] = Array.from(new Set([...seen, version]));
107
+ _writeChangelogStore(store);
108
+ } catch {
109
+ // silent on any error - changelog is a nicety
110
+ }
111
+ }
112
+
9
113
 
10
114
  /**
11
115
  * Initializes the CLI program
@@ -14,7 +118,80 @@ import { getAvailableTemplates, getTemplate, getDefaultTemplate } from "../utils
14
118
  export function initializeProgram(version) {
15
119
  program
16
120
  .version(version)
17
- .description("CLI to create a new DocuBook project")
121
+ .description("CLI to create a new DocuBook project");
122
+
123
+ // Add `update` command: check npm registry and install latest globally if needed
124
+ program
125
+ .command("update")
126
+ .description("Check for updates and install the latest DocuBook CLI globally")
127
+ .action(async () => {
128
+ const pkgName = "@docubook/cli";
129
+ // declare spinner in outer scope so catch block can safely reference it
130
+ let spinner;
131
+ try {
132
+ // Fetch package metadata from npm registry
133
+ const encoded = encodeURIComponent(pkgName);
134
+ spinner = ora('Checking for updates...').start();
135
+ const res = await fetch(`https://registry.npmjs.org/${encoded}`);
136
+ if (!res.ok) {
137
+ spinner.fail(`Failed to fetch registry metadata (status ${res.status})`);
138
+ throw new Error(`Failed to fetch registry metadata (status ${res.status})`);
139
+ }
140
+ const data = await res.json();
141
+ const latest = data && data["dist-tags"] && data["dist-tags"].latest;
142
+ if (!latest) {
143
+ spinner.fail("Could not determine latest version from npm registry");
144
+ throw new Error("Could not determine latest version from npm registry");
145
+ }
146
+
147
+ // Stop spinner and print a plain "Checking for updates..." line (no check mark)
148
+ if (spinner && typeof spinner.stop === 'function') spinner.stop();
149
+ console.log('Checking for updates...');
150
+
151
+ if (latest === version) {
152
+ console.log(`No update needed, current version is ${version}, fetched latest release is ${latest}`);
153
+ return;
154
+ }
155
+
156
+ console.log(`Updating ${pkgName} from ${version} to ${latest}...`);
157
+
158
+ // Use npm to install globally. This will stream stdout/stderr to the user.
159
+ const cmd = `npm install -g ${pkgName}@${latest}`;
160
+ try {
161
+ execSync(cmd, { stdio: "inherit" });
162
+ console.log(`Successfully updated to ${latest}`);
163
+ // Try to show changelog for the newly installed version once
164
+ try {
165
+ await showChangelogOnce(pkgName, latest);
166
+ } catch {
167
+ // non-fatal
168
+ }
169
+ } catch (installErr) {
170
+ // If install fails, provide a helpful message
171
+ console.error(`Update failed: ${installErr.message || installErr}`);
172
+ 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.`);
173
+ process.exitCode = 1;
174
+ }
175
+ } catch (err) {
176
+ // ensure spinner is stopped on error
177
+ if (spinner && typeof spinner.stop === 'function') spinner.stop();
178
+ console.error(err.message || err);
179
+ process.exitCode = 1;
180
+ }
181
+ });
182
+
183
+ // Expose a `version` subcommand: `docubook version`
184
+ program
185
+ .command('version')
186
+ .description('Print the DocuBook CLI version')
187
+ .action(() => {
188
+ console.log(`DocuBook CLI ${version}`);
189
+ console.log("Run 'docubook update' to check for updates.");
190
+ process.exit(0);
191
+ });
192
+
193
+ // Default behavior (create project)
194
+ program
18
195
  .argument("[directory]", "The name of the project directory")
19
196
  .action(async (directory) => {
20
197
  const state = new CLIState();
package/src/index.js CHANGED
@@ -14,6 +14,14 @@ const packageJsonPath = path.join(__dirname, '..', 'package.json');
14
14
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
15
15
  const VERSION = packageJson.version;
16
16
 
17
+ // Handle --version / -v early to print custom output
18
+ const args = process.argv.slice(2);
19
+ if (args.includes('--version') || args.includes('-v')) {
20
+ console.log(`DocuBook CLI ${VERSION}`);
21
+ console.log("Run 'docubook update' to check for updates.");
22
+ process.exit(0);
23
+ }
24
+
17
25
  // Initialize and parse CLI arguments
18
26
  const program = initializeProgram(VERSION);
19
27
  program.parse(process.argv);
package/src/tui/ascii.js CHANGED
@@ -62,7 +62,7 @@ export function createWelcomeBanner(version) {
62
62
  const logoMid = `${colors.cyan}${colors.bright}▌>_▐${colors.reset}`
63
63
  const logoBot = `${colors.cyan}▙▄▄▟${colors.reset}`
64
64
 
65
- const title = `${colors.cyan}${colors.bright}DocuBook${colors.reset} v${version}`
65
+ const title = `${colors.cyan}${colors.bright}DocuBook CLI${colors.reset} v${version}`
66
66
  const subtitle = `${colors.gray}Initialize, build, and deploy docs from terminal.${colors.reset}`
67
67
 
68
68
  const tip = `${colors.gray}Visit our documentation.${colors.reset}`
@@ -103,16 +103,8 @@ export function createBoxedMessage(title, content, color = colors.green) {
103
103
  const reset = "\x1b[0m";
104
104
  const termWidth = process.stdout.columns || 80;
105
105
 
106
- // 1. Tentukan total lebar box (termasuk border)
107
106
  const boxWidth = Math.min(80, termWidth - 4);
108
-
109
- // 2. width adalah panjang garis horizontal (─) di atas dan bawah
110
- // Total lebar box adalah width + 2 (untuk karakter pojok ┌ dan ┐)
111
107
  const width = boxWidth - 2;
112
-
113
- // 3. inner adalah ruang bersih di dalam box untuk teks (tanpa padding spasi)
114
- // Kita beri padding 2 spasi di kiri dan 2 spasi di kanan (total 4)
115
- // Jadi: 1(│) + 2(spasi) + inner + 2(spasi) + 1(│) = width + 2
116
108
  const inner = (width + 2) - 6;
117
109
 
118
110
  const centerTitle = () => {
@@ -126,16 +118,11 @@ export function createBoxedMessage(title, content, color = colors.green) {
126
118
 
127
119
  const pad = (text = "") => {
128
120
  const len = stringWidth(text);
129
- // Tambahkan spasi hingga tepat mengisi 'inner'
130
121
  return text + " ".repeat(Math.max(0, inner - len));
131
122
  };
132
123
 
133
124
  const lines = [];
134
-
135
- // Header
136
125
  lines.push(`${color}┌${centerTitle()}┐${reset}`);
137
-
138
- // Padding atas (opsional)
139
126
  lines.push(`${color}│${reset} ${pad("")} ${color}│${reset}`);
140
127
 
141
128
  const items = typeof content === "string"
@@ -145,15 +132,10 @@ export function createBoxedMessage(title, content, color = colors.green) {
145
132
  for (const line of items) {
146
133
  const wrapped = wrapText(line, inner);
147
134
  wrapped.forEach((w) => {
148
- // Pastikan struktur: │ + spasi(2) + konten + spasi(2) + │
149
135
  lines.push(`${color}│${reset} ${pad(w)} ${color}│${reset}`);
150
136
  });
151
137
  }
152
-
153
- // Padding bawah (opsional)
154
138
  lines.push(`${color}│${reset} ${pad("")} ${color}│${reset}`);
155
-
156
- // Footer
157
139
  lines.push(`${color}└${"─".repeat(width)}┘${reset}`);
158
140
 
159
141
  return "\n" + lines.join("\n") + "\n";
@@ -1,32 +1,6 @@
1
- import { execSync } from "child_process";
2
1
  import fs from "fs";
3
2
  import path from "path";
4
3
 
5
- /**
6
- * Gets the version of the specified package manager
7
- * @param {string} pm - Package manager name
8
- * @returns {string|null} Version string or null if not installed
9
- */
10
- export function getPackageManagerVersion(pm) {
11
- try {
12
- return execSync(`${pm} --version`).toString().trim();
13
- } catch {
14
- return null;
15
- }
16
- }
17
-
18
- /**
19
- * Detects the default package manager from user environment
20
- * @returns {string} Default package manager name
21
- */
22
- export function detectDefaultPackageManager() {
23
- const userAgent = process.env.npm_config_user_agent || "";
24
- if (userAgent.includes("pnpm")) return "pnpm";
25
- if (userAgent.includes("yarn")) return "yarn";
26
- if (userAgent.includes("bun")) return "bun";
27
- return "npm";
28
- }
29
-
30
4
  /**
31
5
  * Updates postcss config file extension for Bun compatibility
32
6
  * @param {string} projectPath - Path to the project directory
package/templates.json CHANGED
@@ -16,6 +16,23 @@
16
16
  ],
17
17
  "url": "https://github.com/DocuBook/docubook/tree/main/packages/template/nextjs-vercel"
18
18
  },
19
+ {
20
+ "id": "nextjs-docker",
21
+ "name": "nextjs-docker",
22
+ "description": "Modern documentation with Next.js standalone and Docker deployment",
23
+ "features": [
24
+ "Next.js 16",
25
+ "React 19",
26
+ "TypeScript",
27
+ "Tailwind CSS",
28
+ "MDX Support",
29
+ "Dark Mode",
30
+ "Search (Algolia)",
31
+ "Responsive Design",
32
+ "Docker Deployment"
33
+ ],
34
+ "url": "https://github.com/DocuBook/docubook/tree/main/packages/template/nextjs-docker"
35
+ },
19
36
  {
20
37
  "id": "react-router",
21
38
  "name": "react-router",