@docubook/cli 0.2.1 → 0.2.4
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 +7 -7
- package/src/cli/program.js +62 -2
- package/src/index.js +8 -0
- package/src/installer/projectInstaller.js +47 -39
- package/src/utils/display.js +0 -57
- package/src/utils/templateDetect.js +10 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docubook/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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
|
+
}
|
package/src/cli/program.js
CHANGED
|
@@ -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,8 @@ 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";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Initializes the CLI program
|
|
@@ -14,7 +17,64 @@ import { getAvailableTemplates, getTemplate, getDefaultTemplate } from "../utils
|
|
|
14
17
|
export function initializeProgram(version) {
|
|
15
18
|
program
|
|
16
19
|
.version(version)
|
|
17
|
-
.description("CLI to create a new DocuBook project")
|
|
20
|
+
.description("CLI to create a new DocuBook project");
|
|
21
|
+
|
|
22
|
+
// Add `update` command: check npm registry and install latest globally if needed
|
|
23
|
+
program
|
|
24
|
+
.command("update")
|
|
25
|
+
.description("Check for updates and install the latest DocuBook CLI globally")
|
|
26
|
+
.action(async () => {
|
|
27
|
+
const pkgName = "@docubook/cli";
|
|
28
|
+
// declare spinner in outer scope so catch block can safely reference it
|
|
29
|
+
let spinner;
|
|
30
|
+
try {
|
|
31
|
+
// Fetch package metadata from npm registry
|
|
32
|
+
const encoded = encodeURIComponent(pkgName);
|
|
33
|
+
spinner = ora('Checking for updates...').start();
|
|
34
|
+
const res = await fetch(`https://registry.npmjs.org/${encoded}`);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
spinner.fail(`Failed to fetch registry metadata (status ${res.status})`);
|
|
37
|
+
throw new Error(`Failed to fetch registry metadata (status ${res.status})`);
|
|
38
|
+
}
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
const latest = data && data["dist-tags"] && data["dist-tags"].latest;
|
|
41
|
+
if (!latest) {
|
|
42
|
+
spinner.fail("Could not determine latest version from npm registry");
|
|
43
|
+
throw new Error("Could not determine latest version from npm registry");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Stop spinner and print a plain "Checking for updates..." line (no check mark)
|
|
47
|
+
if (spinner && typeof spinner.stop === 'function') spinner.stop();
|
|
48
|
+
console.log('Checking for updates...');
|
|
49
|
+
|
|
50
|
+
if (latest === version) {
|
|
51
|
+
console.log(`No update needed, current version is ${version}, fetched latest release is ${latest}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Updating ${pkgName} from ${version} to ${latest}...`);
|
|
56
|
+
|
|
57
|
+
// Use npm to install globally. This will stream stdout/stderr to the user.
|
|
58
|
+
const cmd = `npm install -g ${pkgName}@${latest}`;
|
|
59
|
+
try {
|
|
60
|
+
execSync(cmd, { stdio: "inherit" });
|
|
61
|
+
console.log(`Successfully updated to ${latest}`);
|
|
62
|
+
} catch (installErr) {
|
|
63
|
+
// If install fails, provide a helpful message
|
|
64
|
+
console.error(`Update failed: ${installErr.message || installErr}`);
|
|
65
|
+
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.`);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
// ensure spinner is stopped on error
|
|
70
|
+
if (spinner && typeof spinner.stop === 'function') spinner.stop();
|
|
71
|
+
console.error(err.message || err);
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Default behavior (create project)
|
|
77
|
+
program
|
|
18
78
|
.argument("[directory]", "The name of the project directory")
|
|
19
79
|
.action(async (directory) => {
|
|
20
80
|
const state = new CLIState();
|
|
@@ -63,7 +123,7 @@ export function initializeProgram(version) {
|
|
|
63
123
|
|
|
64
124
|
// Show success message
|
|
65
125
|
const pmInfo = getPackageManagerInfo(detectedPM);
|
|
66
|
-
renderDone(userInput.directoryName, detectedPM, pmInfo.devCmd);
|
|
126
|
+
renderDone(userInput.directoryName, detectedPM, pmInfo.devCmd, userInput.autoInstall !== false);
|
|
67
127
|
} catch (err) {
|
|
68
128
|
renderError(err.message || "An unexpected error occurred.");
|
|
69
129
|
log.error(err.message || "An unexpected error occurred.");
|
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);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import { URL } from "url";
|
|
3
4
|
import ora from "ora";
|
|
4
5
|
import chalk from "chalk";
|
|
5
6
|
import { execSync } from "child_process";
|
|
@@ -36,9 +37,9 @@ export async function createProject(options) {
|
|
|
36
37
|
// 1. Create project directory and get/download template
|
|
37
38
|
state?.setCurrentStep("Creating directories...");
|
|
38
39
|
renderScaffolding(state || {});
|
|
39
|
-
|
|
40
|
+
|
|
40
41
|
const templatePath = await getOrDownloadTemplate(template, state);
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
if (!templatePath || !fs.existsSync(templatePath)) {
|
|
43
44
|
throw new Error(`Template "${template}" could not be found or downloaded.`);
|
|
44
45
|
}
|
|
@@ -93,46 +94,31 @@ async function getOrDownloadTemplate(templateId, state) {
|
|
|
93
94
|
// Download from GitHub
|
|
94
95
|
state?.setCurrentStep("Downloading template...");
|
|
95
96
|
renderScaffolding(state || {});
|
|
96
|
-
|
|
97
|
+
|
|
97
98
|
const templateInfo = getTemplate(templateId);
|
|
98
|
-
if (!templateInfo
|
|
99
|
-
throw new Error(`Template "${templateId}"
|
|
99
|
+
if (!templateInfo) {
|
|
100
|
+
throw new Error(`Template "${templateId}" not found.`);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
return await downloadTemplateFromGitHub(templateId, templateInfo.url);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
|
-
* Gets local template path if it exists
|
|
107
|
+
* Gets local template path if it exists (for development)
|
|
107
108
|
* @param {string} templateId - Template ID
|
|
108
109
|
* @returns {string|null} Path to local template or null
|
|
109
110
|
*/
|
|
110
111
|
function getLocalTemplatePath(templateId) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"..",
|
|
116
|
-
"..",
|
|
117
|
-
"dist",
|
|
118
|
-
templateId
|
|
119
|
-
);
|
|
120
|
-
|
|
112
|
+
const currentDir = new URL(".", import.meta.url).pathname;
|
|
113
|
+
|
|
114
|
+
// Check dist/ folder first (if built)
|
|
115
|
+
const distPath = path.join(currentDir, "..", "..", "dist", templateId);
|
|
121
116
|
if (fs.existsSync(distPath)) {
|
|
122
117
|
return distPath;
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"..",
|
|
128
|
-
"..",
|
|
129
|
-
"..",
|
|
130
|
-
"..",
|
|
131
|
-
"packages",
|
|
132
|
-
"template",
|
|
133
|
-
templateId
|
|
134
|
-
);
|
|
135
|
-
|
|
120
|
+
// Check packages/template/ folder (dev environment)
|
|
121
|
+
const devPath = path.join(currentDir, "..", "..", "..", "..", "packages", "template", templateId);
|
|
136
122
|
if (fs.existsSync(devPath)) {
|
|
137
123
|
return devPath;
|
|
138
124
|
}
|
|
@@ -143,28 +129,50 @@ function getLocalTemplatePath(templateId) {
|
|
|
143
129
|
/**
|
|
144
130
|
* Downloads template from GitHub repository
|
|
145
131
|
* @param {string} templateId - Template ID
|
|
146
|
-
* @param {string}
|
|
132
|
+
* @param {string} templateUrl - Template URL from templates.json
|
|
147
133
|
* @returns {Promise<string>} Path to downloaded template
|
|
148
134
|
*/
|
|
149
|
-
async function downloadTemplateFromGitHub(templateId,
|
|
135
|
+
async function downloadTemplateFromGitHub(templateId, templateUrl) {
|
|
150
136
|
const tempDir = fs.mkdtempSync(path.join("/tmp", "docubook-"));
|
|
151
|
-
|
|
137
|
+
|
|
152
138
|
try {
|
|
153
|
-
//
|
|
139
|
+
// Build archive URL from template URL
|
|
154
140
|
// https://github.com/DocuBook/docubook/tree/main/packages/template/nextjs-vercel
|
|
155
141
|
// -> https://github.com/DocuBook/docubook/archive/refs/heads/main.tar.gz
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
const repoMatch = templateUrl.match(/https:\/\/github\.com\/([^/]+\/[^/]+)\//);
|
|
143
|
+
if (!repoMatch) {
|
|
144
|
+
throw new Error(`Invalid template URL: ${templateUrl}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const archiveUrl = `https://github.com/${repoMatch[1]}/archive/refs/heads/main.tar.gz`;
|
|
159
148
|
const archivePath = path.join(tempDir, "repo.tar.gz");
|
|
160
|
-
execSync(`curl -L -o "${archivePath}" "${archiveUrl}"`, { stdio: "pipe" });
|
|
161
149
|
|
|
162
|
-
//
|
|
163
|
-
|
|
150
|
+
// Show progress for download
|
|
151
|
+
const downloadSpinner = ora(`Downloading template...`).start();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
execSync(`curl -L -o "${archivePath}" "${archiveUrl}"`, { stdio: "pipe" });
|
|
155
|
+
downloadSpinner.succeed("Template downloaded");
|
|
156
|
+
} catch (err) {
|
|
157
|
+
downloadSpinner.fail("Failed to download template");
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Show progress for extraction
|
|
162
|
+
const extractSpinner = ora(`Extracting template...`).start();
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
execSync(`tar -xzf "${archivePath}" -C "${tempDir}"`, { stdio: "pipe" });
|
|
166
|
+
extractSpinner.succeed("Template extracted");
|
|
167
|
+
} catch (err) {
|
|
168
|
+
extractSpinner.fail("Failed to extract template");
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
164
171
|
|
|
165
172
|
// Find template in extracted repo
|
|
166
|
-
const
|
|
167
|
-
|
|
173
|
+
const repoName = repoMatch[1].split('/')[1];
|
|
174
|
+
const extractedDir = path.join(tempDir, `${repoName}-main`, "packages", "template", templateId);
|
|
175
|
+
|
|
168
176
|
if (!fs.existsSync(extractedDir)) {
|
|
169
177
|
throw new Error(`Template "${templateId}" not found in repository.`);
|
|
170
178
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -1,37 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import boxen from "boxen";
|
|
3
|
-
import cliProgress from "cli-progress";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Displays an introduction message for the CLI.
|
|
7
|
-
*/
|
|
8
|
-
export function displayIntro() {
|
|
9
|
-
console.log(`\n${chalk.bold.green("🚀 DocuBook Installer")}\n`);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Displays a progress bar to simulate final setup.
|
|
14
|
-
* @returns {Promise<void>} Promise that resolves when simulation completes.
|
|
15
|
-
*/
|
|
16
|
-
export async function simulateInstallation() {
|
|
17
|
-
const bar = new cliProgress.SingleBar(
|
|
18
|
-
{
|
|
19
|
-
format: `Finishing setup... ${chalk.greenBright("{bar}")} | {percentage}%`,
|
|
20
|
-
barCompleteChar: "\u2588",
|
|
21
|
-
barIncompleteChar: "\u2591",
|
|
22
|
-
hideCursor: true,
|
|
23
|
-
},
|
|
24
|
-
cliProgress.Presets.shades_classic
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
bar.start(100, 0);
|
|
28
|
-
for (let i = 0; i <= 100; i++) {
|
|
29
|
-
await new Promise((r) => setTimeout(r, 20)); // Faster simulation
|
|
30
|
-
bar.update(i);
|
|
31
|
-
}
|
|
32
|
-
bar.stop();
|
|
33
|
-
console.log("\n");
|
|
34
|
-
}
|
|
35
3
|
|
|
36
4
|
/**
|
|
37
5
|
* Displays manual installation steps if automatic installation fails.
|
|
@@ -56,29 +24,4 @@ export function displayManualSteps(projectDirectory, packageManager) {
|
|
|
56
24
|
titleAlignment: "center",
|
|
57
25
|
})
|
|
58
26
|
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Displays next steps after successful installation.
|
|
63
|
-
* @param {string} directoryName - Project directory name.
|
|
64
|
-
* @param {string} packageManager - Package manager being used.
|
|
65
|
-
*/
|
|
66
|
-
export function displayNextSteps(directoryName, packageManager) {
|
|
67
|
-
const steps = `
|
|
68
|
-
${chalk.bold("Next steps:")}
|
|
69
|
-
|
|
70
|
-
1. ${chalk.blueBright(`cd ${directoryName}`)}
|
|
71
|
-
2. ${chalk.blueBright(`${packageManager} run dev`)}
|
|
72
|
-
`;
|
|
73
|
-
|
|
74
|
-
console.log(
|
|
75
|
-
boxen(steps, {
|
|
76
|
-
padding: 1,
|
|
77
|
-
margin: 1,
|
|
78
|
-
borderStyle: "round",
|
|
79
|
-
borderColor: "green",
|
|
80
|
-
title: "Success!",
|
|
81
|
-
titleAlignment: "center",
|
|
82
|
-
})
|
|
83
|
-
);
|
|
84
27
|
}
|
|
@@ -15,30 +15,30 @@ export function getAvailableTemplates() {
|
|
|
15
15
|
const data = JSON.parse(content);
|
|
16
16
|
return data.templates || [];
|
|
17
17
|
}
|
|
18
|
-
} catch
|
|
18
|
+
} catch {
|
|
19
19
|
// Fallback to directory scanning
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Fallback: scan template directories
|
|
23
23
|
const distPath = path.join(__dirname, '..', '..', 'dist');
|
|
24
|
-
const templateDir = fs.existsSync(distPath)
|
|
25
|
-
? distPath
|
|
24
|
+
const templateDir = fs.existsSync(distPath)
|
|
25
|
+
? distPath
|
|
26
26
|
: path.join(__dirname, '..', '..', '..', '..', 'packages', 'template');
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
if (!fs.existsSync(templateDir)) {
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
const entries = fs.readdirSync(templateDir, { withFileTypes: true });
|
|
33
33
|
const templates = [];
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
for (const entry of entries) {
|
|
36
36
|
if (!entry.isDirectory()) continue;
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
const configPath = path.join(templateDir, entry.name, 'template.config.json');
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
if (!fs.existsSync(configPath)) continue;
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
try {
|
|
43
43
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
44
44
|
templates.push({
|
|
@@ -51,7 +51,7 @@ export function getAvailableTemplates() {
|
|
|
51
51
|
console.error(`Failed to read template config for ${entry.name}`);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
return templates;
|
|
56
56
|
}
|
|
57
57
|
|