docsui-cli 0.0.59
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/README.md +279 -0
- package/dist/commands/add.d.ts +5 -0
- package/dist/commands/add.js +254 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +250 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +548 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.js +84 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +1562 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.js +113 -0
- package/dist/commands/remove.d.ts +5 -0
- package/dist/commands/remove.js +134 -0
- package/dist/commands/save.d.ts +5 -0
- package/dist/commands/save.js +60 -0
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.js +247 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +88 -0
- package/dist/latex-to-primitives.d.ts +59 -0
- package/dist/latex-to-primitives.js +1019 -0
- package/dist/lib/component-registry.d.ts +33 -0
- package/dist/lib/component-registry.js +843 -0
- package/dist/lib/css-tokens.d.ts +8 -0
- package/dist/lib/css-tokens.js +294 -0
- package/dist/metadata.d.ts +1 -0
- package/dist/metadata.js +4 -0
- package/dist/symbol-map.d.ts +30 -0
- package/dist/symbol-map.js +1607 -0
- package/dist/utils/detect-structure.d.ts +16 -0
- package/dist/utils/detect-structure.js +58 -0
- package/dist/utils/fetch-component.d.ts +13 -0
- package/dist/utils/fetch-component.js +81 -0
- package/dist/utils/get-config.d.ts +14 -0
- package/dist/utils/get-config.js +19 -0
- package/dist/utils/install-deps.d.ts +3 -0
- package/dist/utils/install-deps.js +23 -0
- package/dist/utils/save-mdx-page.d.ts +35 -0
- package/dist/utils/save-mdx-page.js +44 -0
- package/dist/utils/scan-mdx.d.ts +20 -0
- package/dist/utils/scan-mdx.js +106 -0
- package/dist/utils/telemetry.d.ts +3 -0
- package/dist/utils/telemetry.js +42 -0
- package/dist/utils/write-component.d.ts +7 -0
- package/dist/utils/write-component.js +25 -0
- package/dist/webview-bundle.js +3478 -0
- package/package.json +94 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
function slugify(title) {
|
|
7
|
+
return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8
|
+
}
|
|
9
|
+
function todayIso() {
|
|
10
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
11
|
+
}
|
|
12
|
+
function buildTemplate(title, description) {
|
|
13
|
+
return `---
|
|
14
|
+
title: "${title}"
|
|
15
|
+
description: "${description}"
|
|
16
|
+
date: ${todayIso()}
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# ${title}
|
|
20
|
+
|
|
21
|
+
<Callout>
|
|
22
|
+
Write your introduction here.
|
|
23
|
+
</Callout>
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
Your content goes here.
|
|
28
|
+
|
|
29
|
+
## Key Concepts
|
|
30
|
+
|
|
31
|
+
<Definition term="Term">
|
|
32
|
+
Define your key terms here using the Definition component.
|
|
33
|
+
</Definition>
|
|
34
|
+
|
|
35
|
+
## Summary
|
|
36
|
+
|
|
37
|
+
<Callout variant="success">
|
|
38
|
+
Summarize what the reader has learned.
|
|
39
|
+
</Callout>
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
const newCommand = new Command().name("new").description("Scaffold a new blank .mdx page with starter components").argument("[title]", "page title (will be slugified for the filename)").option("-o, --out <dir>", "output directory").option(
|
|
43
|
+
"-p, --path <segments...>",
|
|
44
|
+
"optional sub-path segments placed between --out and the filename"
|
|
45
|
+
).action(
|
|
46
|
+
async (titleArg, opts) => {
|
|
47
|
+
console.log();
|
|
48
|
+
let title = titleArg;
|
|
49
|
+
if (!title) {
|
|
50
|
+
const res = await prompts({
|
|
51
|
+
type: "text",
|
|
52
|
+
name: "title",
|
|
53
|
+
message: "Page title",
|
|
54
|
+
validate: (v) => v.trim() ? true : "Title cannot be empty"
|
|
55
|
+
});
|
|
56
|
+
if (!res.title) process.exit(0);
|
|
57
|
+
title = res.title;
|
|
58
|
+
}
|
|
59
|
+
let outDir = opts.out;
|
|
60
|
+
if (!outDir) {
|
|
61
|
+
const res = await prompts({
|
|
62
|
+
type: "text",
|
|
63
|
+
name: "out",
|
|
64
|
+
message: "Output directory",
|
|
65
|
+
initial: "content"
|
|
66
|
+
});
|
|
67
|
+
if (res.out === void 0) process.exit(0);
|
|
68
|
+
outDir = res.out || "content";
|
|
69
|
+
}
|
|
70
|
+
const { description } = await prompts({
|
|
71
|
+
type: "text",
|
|
72
|
+
name: "description",
|
|
73
|
+
message: "Short description (optional)",
|
|
74
|
+
initial: ""
|
|
75
|
+
});
|
|
76
|
+
const slug = slugify(title);
|
|
77
|
+
const fileName = `${slug}.mdx`;
|
|
78
|
+
const segments = opts.path ?? [];
|
|
79
|
+
const cwd = process.cwd();
|
|
80
|
+
const absoluteOut = path.isAbsolute(outDir) ? outDir : path.join(cwd, outDir);
|
|
81
|
+
const targetDir = path.join(absoluteOut, ...segments);
|
|
82
|
+
const filePath = path.join(targetDir, fileName);
|
|
83
|
+
const relativePath = path.relative(cwd, filePath);
|
|
84
|
+
if (await fs.pathExists(filePath)) {
|
|
85
|
+
const { overwrite } = await prompts({
|
|
86
|
+
type: "confirm",
|
|
87
|
+
name: "overwrite",
|
|
88
|
+
message: `${relativePath} already exists. Overwrite?`,
|
|
89
|
+
initial: false
|
|
90
|
+
});
|
|
91
|
+
if (!overwrite) {
|
|
92
|
+
console.log(chalk.yellow("Cancelled."));
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await fs.ensureDir(targetDir);
|
|
97
|
+
await fs.writeFile(
|
|
98
|
+
filePath,
|
|
99
|
+
buildTemplate(title, description || title),
|
|
100
|
+
"utf-8"
|
|
101
|
+
);
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(` ${chalk.green("\u2713")} ${chalk.bold(relativePath)}`);
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.dim(" Open the file and replace the placeholder content.")
|
|
107
|
+
);
|
|
108
|
+
console.log();
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
export {
|
|
112
|
+
newCommand
|
|
113
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { getConfig } from "../utils/get-config.js";
|
|
7
|
+
import {
|
|
8
|
+
COMPONENT_MDX_MAP,
|
|
9
|
+
COMPONENT_FILES
|
|
10
|
+
} from "../lib/component-registry.js";
|
|
11
|
+
async function unpatchMdxComponents(componentName, componentsDir, cwd) {
|
|
12
|
+
const mapping = COMPONENT_MDX_MAP[componentName];
|
|
13
|
+
if (!mapping) return;
|
|
14
|
+
const mdxPath = path.join(cwd, componentsDir, "mdx-components.tsx");
|
|
15
|
+
if (!await fs.pathExists(mdxPath)) return;
|
|
16
|
+
let content = await fs.readFile(mdxPath, "utf-8");
|
|
17
|
+
const lines = content.split("\n");
|
|
18
|
+
content = lines.filter((line) => !line.includes(`from "${mapping.importFile}"`)).join("\n");
|
|
19
|
+
for (const [element, component] of Object.entries(mapping.elementMappings)) {
|
|
20
|
+
content = content.replace(
|
|
21
|
+
new RegExp(`
|
|
22
|
+
[ ]+${element}:\\s*${component},`, "g"),
|
|
23
|
+
""
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
for (const exportName of mapping.imports) {
|
|
27
|
+
const alreadyMapped = Object.values(mapping.elementMappings).includes(
|
|
28
|
+
exportName
|
|
29
|
+
);
|
|
30
|
+
if (!alreadyMapped) {
|
|
31
|
+
content = content.replace(new RegExp(`
|
|
32
|
+
[ ]+${exportName},`, "g"), "");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
await fs.writeFile(mdxPath, content, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
async function discoverInstalled(componentsDir, cwd) {
|
|
38
|
+
const dir = path.join(cwd, componentsDir);
|
|
39
|
+
if (!await fs.pathExists(dir)) return [];
|
|
40
|
+
const files = await fs.readdir(dir);
|
|
41
|
+
const installed = [];
|
|
42
|
+
for (const [name, componentFiles] of Object.entries(COMPONENT_FILES)) {
|
|
43
|
+
const ownFiles = componentFiles.filter((f) => !f.startsWith("lib/"));
|
|
44
|
+
if (ownFiles.length > 0 && ownFiles.every((f) => files.includes(f)))
|
|
45
|
+
installed.push(name);
|
|
46
|
+
}
|
|
47
|
+
if (files.includes("mdx-components.tsx")) installed.push("mdx-components");
|
|
48
|
+
const libDir = componentsDir.startsWith("src/") ? "src/lib" : "lib";
|
|
49
|
+
if (await fs.pathExists(path.join(cwd, libDir, "utils.ts")))
|
|
50
|
+
installed.push("utils");
|
|
51
|
+
return [...new Set(installed)];
|
|
52
|
+
}
|
|
53
|
+
const remove = new Command().name("remove").description("Remove installed components from your project").argument("[components...]", "components to remove").action(async (components) => {
|
|
54
|
+
console.log();
|
|
55
|
+
const config = await getConfig();
|
|
56
|
+
if (!config) {
|
|
57
|
+
console.log(chalk.red("\u2717 No docsui.json found."));
|
|
58
|
+
console.log(chalk.yellow(" Run: npx docsui-cli@latest init\n"));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
if (components.length === 0) {
|
|
63
|
+
const installed = await discoverInstalled(config.componentsDir, cwd);
|
|
64
|
+
if (installed.length === 0) {
|
|
65
|
+
console.log(chalk.yellow("No installed components found.\n"));
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
const { selected } = await prompts({
|
|
69
|
+
type: "multiselect",
|
|
70
|
+
name: "selected",
|
|
71
|
+
message: "Which components would you like to remove?",
|
|
72
|
+
choices: installed.map((name) => ({ title: name, value: name }))
|
|
73
|
+
});
|
|
74
|
+
if (!selected || selected.length === 0) {
|
|
75
|
+
console.log(chalk.yellow("Nothing selected.\n"));
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
components = selected;
|
|
79
|
+
}
|
|
80
|
+
const { confirmed } = await prompts({
|
|
81
|
+
type: "confirm",
|
|
82
|
+
name: "confirmed",
|
|
83
|
+
message: `Remove ${components.length} component${components.length !== 1 ? "s" : ""}? (${components.join(", ")})`,
|
|
84
|
+
initial: false
|
|
85
|
+
});
|
|
86
|
+
if (!confirmed) {
|
|
87
|
+
console.log(chalk.yellow("\nCancelled.\n"));
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
console.log();
|
|
91
|
+
const removed = [];
|
|
92
|
+
const notFound = [];
|
|
93
|
+
for (const name of components) {
|
|
94
|
+
const files = name === "mdx-components" ? ["mdx-components.tsx"] : COMPONENT_FILES[name];
|
|
95
|
+
if (!files) {
|
|
96
|
+
notFound.push(name);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let deleted = false;
|
|
100
|
+
const libRoot = config.componentsDir.startsWith("src/") ? path.join(cwd, "src") : cwd;
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
const filePath = file.startsWith("lib/") ? path.join(libRoot, file) : path.join(cwd, config.componentsDir, file);
|
|
103
|
+
if (await fs.pathExists(filePath)) {
|
|
104
|
+
await fs.remove(filePath);
|
|
105
|
+
deleted = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await unpatchMdxComponents(name, config.componentsDir, cwd);
|
|
109
|
+
if (deleted) {
|
|
110
|
+
removed.push(name);
|
|
111
|
+
console.log(chalk.green(` \u2713 removed ${name}`));
|
|
112
|
+
} else {
|
|
113
|
+
notFound.push(name);
|
|
114
|
+
console.log(chalk.dim(` \u2013 ${name} (not installed)`));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log();
|
|
118
|
+
if (removed.length > 0) {
|
|
119
|
+
console.log(
|
|
120
|
+
chalk.bold(
|
|
121
|
+
`Removed ${removed.length} component${removed.length !== 1 ? "s" : ""}.`
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (notFound.length > 0) {
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.yellow(`${notFound.length} not found: ${notFound.join(", ")}`)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
console.log();
|
|
131
|
+
});
|
|
132
|
+
export {
|
|
133
|
+
remove
|
|
134
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import { saveMdxPage } from "../utils/save-mdx-page.js";
|
|
6
|
+
const save = new Command().name("save").description("Save an MDX string to a structured file path").requiredOption("-o, --out <dir>", "root output folder (e.g. content, docs)").requiredOption(
|
|
7
|
+
"-p, --path <segments...>",
|
|
8
|
+
"ordered path segments \u2014 all but the last become folders, last becomes filename"
|
|
9
|
+
).option("-f, --file <file>", "read MDX content from a file instead of stdin").action(async (opts) => {
|
|
10
|
+
console.log();
|
|
11
|
+
let content;
|
|
12
|
+
if (opts.file) {
|
|
13
|
+
if (!await fs.pathExists(opts.file)) {
|
|
14
|
+
console.log(chalk.red(`\u2717 File not found: ${opts.file}`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
content = await fs.readFile(opts.file, "utf-8");
|
|
18
|
+
} else if (!process.stdin.isTTY) {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
21
|
+
content = Buffer.concat(chunks).toString("utf-8");
|
|
22
|
+
} else {
|
|
23
|
+
console.log(
|
|
24
|
+
chalk.red("\u2717 Provide MDX via --file or pipe it through stdin")
|
|
25
|
+
);
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(chalk.dim("Examples:"));
|
|
28
|
+
console.log(
|
|
29
|
+
chalk.dim(
|
|
30
|
+
" docsui save --out content --path 2025 ds bst intro --file page.mdx"
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
console.log(
|
|
34
|
+
chalk.dim(
|
|
35
|
+
" echo '<Callout>Hello</Callout>' | docsui save --out content --path 2025 ds bst intro"
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const spinner = ora("Writing MDX file\u2026").start();
|
|
41
|
+
try {
|
|
42
|
+
const result = await saveMdxPage({
|
|
43
|
+
content,
|
|
44
|
+
outDir: opts.out,
|
|
45
|
+
path: opts.path
|
|
46
|
+
});
|
|
47
|
+
spinner.succeed("MDX file saved");
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(` ${chalk.green("\u2713")} ${chalk.bold(result.relativePath)}`);
|
|
50
|
+
console.log();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
spinner.fail("Failed to save MDX file");
|
|
53
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
54
|
+
console.error(chalk.red(msg));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
export {
|
|
59
|
+
save
|
|
60
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { getConfig } from "../utils/get-config.js";
|
|
8
|
+
import {
|
|
9
|
+
fetchComponent
|
|
10
|
+
} from "../utils/fetch-component.js";
|
|
11
|
+
import { installDependencies } from "../utils/install-deps.js";
|
|
12
|
+
import { writeComponent } from "../utils/write-component.js";
|
|
13
|
+
import {
|
|
14
|
+
FILE_TO_COMPONENT,
|
|
15
|
+
COMPONENT_MDX_MAP
|
|
16
|
+
} from "../lib/component-registry.js";
|
|
17
|
+
async function patchMdxComponents(componentName, componentsDir, cwd) {
|
|
18
|
+
const mapping = COMPONENT_MDX_MAP[componentName];
|
|
19
|
+
if (!mapping) return;
|
|
20
|
+
if (mapping.imports.length === 0 && Object.keys(mapping.elementMappings).length === 0)
|
|
21
|
+
return;
|
|
22
|
+
let mdxPath = path.join(cwd, componentsDir, "mdx-components.tsx");
|
|
23
|
+
if (!await fs.pathExists(mdxPath)) {
|
|
24
|
+
const jsxPath = path.join(cwd, componentsDir, "mdx-components.jsx");
|
|
25
|
+
if (await fs.pathExists(jsxPath)) mdxPath = jsxPath;
|
|
26
|
+
else return;
|
|
27
|
+
}
|
|
28
|
+
let content = await fs.readFile(mdxPath, "utf-8");
|
|
29
|
+
if (!content.includes(mapping.importFile)) {
|
|
30
|
+
const importLine = `import { ${mapping.imports.join(", ")} } from "${mapping.importFile}"
|
|
31
|
+
`;
|
|
32
|
+
const exportIdx = content.indexOf("export const mdxComponents");
|
|
33
|
+
content = exportIdx !== -1 ? content.slice(0, exportIdx) + importLine + content.slice(exportIdx) : importLine + content;
|
|
34
|
+
}
|
|
35
|
+
const ANCHOR = "export const mdxComponents";
|
|
36
|
+
const anchorIdx = content.indexOf(ANCHOR);
|
|
37
|
+
const openBrace = anchorIdx !== -1 ? content.indexOf("{", anchorIdx) : -1;
|
|
38
|
+
let closeIdx = -1;
|
|
39
|
+
if (openBrace !== -1) {
|
|
40
|
+
let depth = 0;
|
|
41
|
+
for (let i = openBrace; i < content.length; i++) {
|
|
42
|
+
if (content[i] === "{") depth++;
|
|
43
|
+
else if (content[i] === "}") {
|
|
44
|
+
depth--;
|
|
45
|
+
if (depth === 0) {
|
|
46
|
+
closeIdx = i;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const objectBody = openBrace !== -1 && closeIdx !== -1 ? content.slice(openBrace + 1, closeIdx) : "";
|
|
53
|
+
const additions = [];
|
|
54
|
+
for (const [element, component] of Object.entries(mapping.elementMappings)) {
|
|
55
|
+
if (!objectBody.includes(`${element}:`) && !objectBody.includes(`${element} :`))
|
|
56
|
+
additions.push(` ${element}: ${component},`);
|
|
57
|
+
}
|
|
58
|
+
for (const exportName of mapping.imports) {
|
|
59
|
+
const alreadyMapped = Object.values(mapping.elementMappings).includes(
|
|
60
|
+
exportName
|
|
61
|
+
);
|
|
62
|
+
if (!alreadyMapped && !objectBody.includes(`${exportName},`) && !objectBody.includes(`${exportName}:`))
|
|
63
|
+
additions.push(` ${exportName},`);
|
|
64
|
+
}
|
|
65
|
+
if (additions.length > 0 && closeIdx !== -1) {
|
|
66
|
+
content = content.slice(0, closeIdx) + additions.join("\n") + "\n" + content.slice(closeIdx);
|
|
67
|
+
}
|
|
68
|
+
await fs.writeFile(mdxPath, content);
|
|
69
|
+
}
|
|
70
|
+
async function diffComponent(data, config, cwd) {
|
|
71
|
+
const changedFiles = [];
|
|
72
|
+
const framework = config.framework ?? "unknown";
|
|
73
|
+
for (const file of data.files) {
|
|
74
|
+
const libRoot = config.componentsDir.startsWith("src/") ? path.join(cwd, "src") : cwd;
|
|
75
|
+
const filePath = file.path.startsWith("lib/") ? path.join(libRoot, file.path) : path.join(cwd, config.componentsDir, file.path);
|
|
76
|
+
if (!await fs.pathExists(filePath)) {
|
|
77
|
+
changedFiles.push(file.path);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const existing = await fs.readFile(filePath, "utf-8");
|
|
81
|
+
let incoming = file.content;
|
|
82
|
+
if (framework === "react") {
|
|
83
|
+
incoming = incoming.replace(/^["']use client["']\n\n?/m, "");
|
|
84
|
+
}
|
|
85
|
+
if (existing.trim() !== incoming.trim()) {
|
|
86
|
+
changedFiles.push(file.path);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
name: data.name,
|
|
91
|
+
data,
|
|
92
|
+
changedFiles,
|
|
93
|
+
hasChanges: changedFiles.length > 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function discoverInstalled(componentsDir, cwd) {
|
|
97
|
+
const dir = path.join(cwd, componentsDir);
|
|
98
|
+
if (!await fs.pathExists(dir)) return [];
|
|
99
|
+
const files = await fs.readdir(dir);
|
|
100
|
+
const found = files.map((f) => FILE_TO_COMPONENT[f]).filter((n) => !!n);
|
|
101
|
+
const libDir = componentsDir.startsWith("src/") ? "src/lib" : "lib";
|
|
102
|
+
if (await fs.pathExists(path.join(cwd, libDir, "utils.ts"))) {
|
|
103
|
+
found.unshift("utils");
|
|
104
|
+
}
|
|
105
|
+
return [...new Set(found)];
|
|
106
|
+
}
|
|
107
|
+
const update = new Command().name("update").description("Update installed components to their latest versions").argument(
|
|
108
|
+
"[components...]",
|
|
109
|
+
"components to update (omit to update all installed)"
|
|
110
|
+
).action(async (targets) => {
|
|
111
|
+
console.log();
|
|
112
|
+
const config = await getConfig();
|
|
113
|
+
if (!config) {
|
|
114
|
+
console.log(chalk.red("\u2717 No docsui.json found."));
|
|
115
|
+
console.log(chalk.yellow(" Run: npx docsui-cli@latest init\n"));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const cwd = process.cwd();
|
|
119
|
+
let toUpdate;
|
|
120
|
+
if (targets.length > 0) {
|
|
121
|
+
toUpdate = targets;
|
|
122
|
+
} else {
|
|
123
|
+
toUpdate = await discoverInstalled(config.componentsDir, cwd);
|
|
124
|
+
if (toUpdate.length === 0) {
|
|
125
|
+
console.log(chalk.yellow("No installed components found."));
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.dim(" Run: npx docsui-cli@latest add <component>\n")
|
|
128
|
+
);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const spinner = ora("Checking for updates...").start();
|
|
133
|
+
try {
|
|
134
|
+
const componentsData = [];
|
|
135
|
+
const processed = /* @__PURE__ */ new Set();
|
|
136
|
+
async function fetchRecursive(name) {
|
|
137
|
+
if (processed.has(name)) return;
|
|
138
|
+
processed.add(name);
|
|
139
|
+
const data = await fetchComponent(name);
|
|
140
|
+
if (data.registryDependencies?.length) {
|
|
141
|
+
for (const dep of data.registryDependencies) {
|
|
142
|
+
await fetchRecursive(dep);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
componentsData.push(data);
|
|
146
|
+
}
|
|
147
|
+
for (const name of toUpdate) {
|
|
148
|
+
await fetchRecursive(name);
|
|
149
|
+
}
|
|
150
|
+
const diffs = await Promise.all(
|
|
151
|
+
componentsData.map((data) => diffComponent(data, config, cwd))
|
|
152
|
+
);
|
|
153
|
+
spinner.stop();
|
|
154
|
+
console.log();
|
|
155
|
+
const maxLen = Math.max(...diffs.map((d) => d.name.length));
|
|
156
|
+
for (const diff of diffs) {
|
|
157
|
+
const name = diff.name.padEnd(maxLen);
|
|
158
|
+
if (diff.hasChanges) {
|
|
159
|
+
console.log(
|
|
160
|
+
` ${chalk.yellow("~")} ${name} ${chalk.dim(diff.changedFiles.join(", "))}`
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(
|
|
164
|
+
` ${chalk.green("\u2713")} ${name} ${chalk.dim("up to date")}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
const changed = diffs.filter((d) => d.hasChanges);
|
|
170
|
+
if (changed.length === 0) {
|
|
171
|
+
console.log(chalk.green("All components are up to date."));
|
|
172
|
+
console.log();
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
console.log(
|
|
176
|
+
chalk.bold(
|
|
177
|
+
`${changed.length} component${changed.length !== 1 ? "s have" : " has"} updates.`
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
console.log();
|
|
181
|
+
const { strategy } = await prompts({
|
|
182
|
+
type: "select",
|
|
183
|
+
name: "strategy",
|
|
184
|
+
message: "How would you like to proceed?",
|
|
185
|
+
choices: [
|
|
186
|
+
{
|
|
187
|
+
title: `Overwrite all ${chalk.dim(`(${changed.length} component${changed.length !== 1 ? "s" : ""})`)}`,
|
|
188
|
+
value: "all"
|
|
189
|
+
},
|
|
190
|
+
{ title: "Skip all", value: "skip" },
|
|
191
|
+
{ title: "Choose which to update", value: "pick" }
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
if (!strategy || strategy === "skip") {
|
|
195
|
+
console.log(chalk.yellow("\nSkipped. No files written.\n"));
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
let toWrite = [];
|
|
199
|
+
if (strategy === "all") {
|
|
200
|
+
toWrite = changed;
|
|
201
|
+
} else {
|
|
202
|
+
const { selected } = await prompts({
|
|
203
|
+
type: "multiselect",
|
|
204
|
+
name: "selected",
|
|
205
|
+
message: "Select components to update",
|
|
206
|
+
choices: changed.map((d) => ({
|
|
207
|
+
title: d.name,
|
|
208
|
+
value: d.name,
|
|
209
|
+
description: d.changedFiles.join(", "),
|
|
210
|
+
selected: true
|
|
211
|
+
}))
|
|
212
|
+
});
|
|
213
|
+
if (!selected || selected.length === 0) {
|
|
214
|
+
console.log(chalk.yellow("\nNothing selected. No files written.\n"));
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
toWrite = changed.filter((d) => selected.includes(d.name));
|
|
218
|
+
}
|
|
219
|
+
console.log();
|
|
220
|
+
const writeSpinner = ora("Installing dependencies...").start();
|
|
221
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
222
|
+
for (const diff of toWrite) {
|
|
223
|
+
diff.data.dependencies?.forEach((d) => allDeps.add(d));
|
|
224
|
+
}
|
|
225
|
+
if (allDeps.size > 0) await installDependencies(Array.from(allDeps));
|
|
226
|
+
writeSpinner.text = "Writing updated components...";
|
|
227
|
+
for (const diff of toWrite) {
|
|
228
|
+
await writeComponent(diff.data, config);
|
|
229
|
+
await patchMdxComponents(diff.name, config.componentsDir, cwd);
|
|
230
|
+
}
|
|
231
|
+
writeSpinner.succeed("Done!");
|
|
232
|
+
console.log();
|
|
233
|
+
for (const diff of toWrite) {
|
|
234
|
+
console.log(chalk.green(` \u2713 ${diff.name}`));
|
|
235
|
+
}
|
|
236
|
+
console.log();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
spinner.fail("Update failed");
|
|
239
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
240
|
+
console.error(chalk.red(` ${msg}
|
|
241
|
+
`));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
export {
|
|
246
|
+
update
|
|
247
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
import { add } from "./commands/add.js";
|
|
8
|
+
import { init } from "./commands/init.js";
|
|
9
|
+
import { list } from "./commands/list.js";
|
|
10
|
+
import { update } from "./commands/update.js";
|
|
11
|
+
import { remove } from "./commands/remove.js";
|
|
12
|
+
import { doctor } from "./commands/doctor.js";
|
|
13
|
+
import { save } from "./commands/save.js";
|
|
14
|
+
import { newCommand } from "./commands/new.js";
|
|
15
|
+
import { startMcpServer } from "./commands/mcp.js";
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const packageJson = JSON.parse(
|
|
19
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf-8")
|
|
20
|
+
);
|
|
21
|
+
process.on("SIGINT", () => process.exit(0));
|
|
22
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
23
|
+
async function main() {
|
|
24
|
+
if (process.argv[2] === "mcp") {
|
|
25
|
+
await startMcpServer();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const program = new Command().name("docsui").description("Add beautiful MDX components to your project").version(
|
|
29
|
+
packageJson.version || "0.0.1",
|
|
30
|
+
"-v, --version",
|
|
31
|
+
"display the version number"
|
|
32
|
+
);
|
|
33
|
+
program.addCommand(init).addCommand(add).addCommand(list).addCommand(update).addCommand(remove).addCommand(doctor).addCommand(save).addCommand(newCommand);
|
|
34
|
+
if (process.argv.length <= 2) {
|
|
35
|
+
console.log();
|
|
36
|
+
const { action } = await prompts({
|
|
37
|
+
type: "select",
|
|
38
|
+
name: "action",
|
|
39
|
+
message: "What would you like to do?",
|
|
40
|
+
choices: [
|
|
41
|
+
{
|
|
42
|
+
title: "Add components",
|
|
43
|
+
value: "add",
|
|
44
|
+
description: "Add MDX components to your project"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: "Update components",
|
|
48
|
+
value: "update",
|
|
49
|
+
description: "Update installed components to latest"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Initialize project",
|
|
53
|
+
value: "init",
|
|
54
|
+
description: "Set up docsui in your project"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: "Remove components",
|
|
58
|
+
value: "remove",
|
|
59
|
+
description: "Remove installed components from your project"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: "List components",
|
|
63
|
+
value: "list",
|
|
64
|
+
description: "Show all available components"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: "Doctor",
|
|
68
|
+
value: "doctor",
|
|
69
|
+
description: "Check project health \u2014 missing deps, broken imports"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: "Save MDX",
|
|
73
|
+
value: "save",
|
|
74
|
+
description: "Save an MDX string to a structured .ai.mdx file"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: "New page",
|
|
78
|
+
value: "new",
|
|
79
|
+
description: "Scaffold a new blank .mdx page with starter components"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
if (!action) process.exit(0);
|
|
84
|
+
process.argv.push(action);
|
|
85
|
+
}
|
|
86
|
+
program.parse();
|
|
87
|
+
}
|
|
88
|
+
main();
|