@donkeylabs/cli 2.0.16 → 2.0.17
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/commands/docs.ts +207 -0
- package/src/index.ts +11 -0
package/package.json
CHANGED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs Command
|
|
3
|
+
*
|
|
4
|
+
* Syncs documentation from the installed @donkeylabs/server package
|
|
5
|
+
* to the user's project. This ensures users always have access to
|
|
6
|
+
* the latest documentation for their installed version.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* donkeylabs docs # Sync all docs to ./docs/donkeylabs/
|
|
10
|
+
* donkeylabs docs --list # List available docs
|
|
11
|
+
* donkeylabs docs workflows # Sync specific doc
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync } from "fs";
|
|
15
|
+
import { join, dirname } from "path";
|
|
16
|
+
import pc from "picocolors";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_DOCS_DIR = "docs/donkeylabs";
|
|
19
|
+
|
|
20
|
+
interface DocsCommandOptions {
|
|
21
|
+
list?: boolean;
|
|
22
|
+
output?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Find the docs directory from installed @donkeylabs/server
|
|
27
|
+
*/
|
|
28
|
+
function findDocsPath(): string | null {
|
|
29
|
+
// Try common locations
|
|
30
|
+
const possiblePaths = [
|
|
31
|
+
// node_modules (standard install)
|
|
32
|
+
join(process.cwd(), "node_modules", "@donkeylabs", "server", "docs"),
|
|
33
|
+
// bun's .bun cache
|
|
34
|
+
join(process.cwd(), "node_modules", ".bun", "@donkeylabs", "server", "docs"),
|
|
35
|
+
// Workspace/monorepo
|
|
36
|
+
join(process.cwd(), "..", "server", "docs"),
|
|
37
|
+
join(process.cwd(), "..", "..", "packages", "server", "docs"),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const path of possiblePaths) {
|
|
41
|
+
if (existsSync(path) && statSync(path).isDirectory()) {
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Try to resolve from require
|
|
47
|
+
try {
|
|
48
|
+
const serverPkgPath = require.resolve("@donkeylabs/server/package.json", {
|
|
49
|
+
paths: [process.cwd()],
|
|
50
|
+
});
|
|
51
|
+
const serverDir = dirname(serverPkgPath);
|
|
52
|
+
const docsPath = join(serverDir, "docs");
|
|
53
|
+
if (existsSync(docsPath)) {
|
|
54
|
+
return docsPath;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Package not found
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get list of available doc files
|
|
65
|
+
*/
|
|
66
|
+
function getAvailableDocs(docsPath: string): string[] {
|
|
67
|
+
return readdirSync(docsPath)
|
|
68
|
+
.filter((f) => f.endsWith(".md"))
|
|
69
|
+
.map((f) => f.replace(".md", ""));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sync a single doc file
|
|
74
|
+
*/
|
|
75
|
+
function syncDoc(docsPath: string, docName: string, outputDir: string): boolean {
|
|
76
|
+
const sourcePath = join(docsPath, `${docName}.md`);
|
|
77
|
+
if (!existsSync(sourcePath)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const content = readFileSync(sourcePath, "utf-8");
|
|
82
|
+
const outputPath = join(outputDir, `${docName}.md`);
|
|
83
|
+
|
|
84
|
+
// Create output directory if needed
|
|
85
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
86
|
+
|
|
87
|
+
writeFileSync(outputPath, content);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Sync all docs
|
|
93
|
+
*/
|
|
94
|
+
function syncAllDocs(docsPath: string, outputDir: string): number {
|
|
95
|
+
const docs = getAvailableDocs(docsPath);
|
|
96
|
+
let synced = 0;
|
|
97
|
+
|
|
98
|
+
for (const doc of docs) {
|
|
99
|
+
if (syncDoc(docsPath, doc, outputDir)) {
|
|
100
|
+
synced++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return synced;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get version from installed package
|
|
109
|
+
*/
|
|
110
|
+
function getInstalledVersion(): string | null {
|
|
111
|
+
try {
|
|
112
|
+
const pkgPath = require.resolve("@donkeylabs/server/package.json", {
|
|
113
|
+
paths: [process.cwd()],
|
|
114
|
+
});
|
|
115
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
116
|
+
return pkg.version;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function docsCommand(args: string[], options: DocsCommandOptions = {}): Promise<void> {
|
|
123
|
+
const docsPath = findDocsPath();
|
|
124
|
+
|
|
125
|
+
if (!docsPath) {
|
|
126
|
+
console.error(pc.red("Error: Could not find @donkeylabs/server docs."));
|
|
127
|
+
console.log(pc.dim("Make sure @donkeylabs/server is installed in your project."));
|
|
128
|
+
console.log(pc.dim("\nRun: bun add @donkeylabs/server"));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const version = getInstalledVersion();
|
|
133
|
+
const availableDocs = getAvailableDocs(docsPath);
|
|
134
|
+
|
|
135
|
+
// List mode
|
|
136
|
+
if (options.list || args[0] === "--list" || args[0] === "-l") {
|
|
137
|
+
console.log(pc.bold("\nAvailable Documentation"));
|
|
138
|
+
console.log(pc.dim(`Version: ${version || "unknown"}\n`));
|
|
139
|
+
|
|
140
|
+
// Group docs by category
|
|
141
|
+
const categories: Record<string, string[]> = {
|
|
142
|
+
"Core Services": ["logger", "cache", "events", "cron", "jobs", "external-jobs", "processes", "workflows", "sse", "rate-limiter", "errors"],
|
|
143
|
+
"API": ["router", "handlers", "middleware"],
|
|
144
|
+
"Server": ["lifecycle-hooks", "services", "core-services"],
|
|
145
|
+
"Infrastructure": ["database", "plugins", "sveltekit-adapter", "api-client"],
|
|
146
|
+
"Testing": ["testing"],
|
|
147
|
+
"Other": [],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Categorize docs
|
|
151
|
+
const categorized = new Set<string>();
|
|
152
|
+
for (const [category, docs] of Object.entries(categories)) {
|
|
153
|
+
const matching = availableDocs.filter((d) => docs.includes(d));
|
|
154
|
+
if (matching.length > 0) {
|
|
155
|
+
console.log(pc.cyan(` ${category}:`));
|
|
156
|
+
for (const doc of matching) {
|
|
157
|
+
console.log(` ${pc.green("•")} ${doc}`);
|
|
158
|
+
categorized.add(doc);
|
|
159
|
+
}
|
|
160
|
+
console.log();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Uncategorized
|
|
165
|
+
const uncategorized = availableDocs.filter((d) => !categorized.has(d));
|
|
166
|
+
if (uncategorized.length > 0) {
|
|
167
|
+
console.log(pc.cyan(" Other:"));
|
|
168
|
+
for (const doc of uncategorized) {
|
|
169
|
+
console.log(` ${pc.green("•")} ${doc}`);
|
|
170
|
+
}
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(pc.dim(`\nUsage:`));
|
|
175
|
+
console.log(pc.dim(` donkeylabs docs # Sync all docs`));
|
|
176
|
+
console.log(pc.dim(` donkeylabs docs workflows # Sync specific doc`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const outputDir = options.output || DEFAULT_DOCS_DIR;
|
|
181
|
+
const specificDoc = args[0];
|
|
182
|
+
|
|
183
|
+
// Sync specific doc
|
|
184
|
+
if (specificDoc && specificDoc !== "--list" && specificDoc !== "-l") {
|
|
185
|
+
if (!availableDocs.includes(specificDoc)) {
|
|
186
|
+
console.error(pc.red(`Error: Doc "${specificDoc}" not found.`));
|
|
187
|
+
console.log(pc.dim(`\nAvailable docs: ${availableDocs.join(", ")}`));
|
|
188
|
+
console.log(pc.dim(`\nRun: donkeylabs docs --list`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
syncDoc(docsPath, specificDoc, outputDir);
|
|
193
|
+
console.log(pc.green(`✓ Synced ${specificDoc}.md to ${outputDir}/`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Sync all docs
|
|
198
|
+
console.log(pc.bold("\nSyncing Documentation"));
|
|
199
|
+
console.log(pc.dim(`Version: ${version || "unknown"}`));
|
|
200
|
+
console.log(pc.dim(`Source: ${docsPath}`));
|
|
201
|
+
console.log(pc.dim(`Target: ${outputDir}/\n`));
|
|
202
|
+
|
|
203
|
+
const synced = syncAllDocs(docsPath, outputDir);
|
|
204
|
+
|
|
205
|
+
console.log(pc.green(`\n✓ Synced ${synced} documentation files to ${outputDir}/`));
|
|
206
|
+
console.log(pc.dim(`\nTip: Add ${outputDir}/ to your .gitignore if you don't want to commit docs.`));
|
|
207
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ const { positionals, values } = parseArgs({
|
|
|
18
18
|
version: { type: "boolean", short: "v" },
|
|
19
19
|
type: { type: "string", short: "t" },
|
|
20
20
|
local: { type: "boolean", short: "l" },
|
|
21
|
+
list: { type: "boolean" },
|
|
22
|
+
output: { type: "string", short: "o" },
|
|
21
23
|
},
|
|
22
24
|
allowPositionals: true,
|
|
23
25
|
});
|
|
@@ -37,6 +39,7 @@ ${pc.bold("Commands:")}
|
|
|
37
39
|
${pc.cyan("add")} Add optional plugins (images, auth, etc.)
|
|
38
40
|
${pc.cyan("generate")} Generate types (registry, context, client)
|
|
39
41
|
${pc.cyan("plugin")} Plugin management
|
|
42
|
+
${pc.cyan("docs")} Sync documentation from installed package
|
|
40
43
|
${pc.cyan("deploy")} <platform> Deploy (vercel, cloudflare, aws, vps)
|
|
41
44
|
${pc.cyan("deploy history")} Show deployment history
|
|
42
45
|
${pc.cyan("deploy rollback")} Rollback to version
|
|
@@ -57,6 +60,9 @@ ${pc.bold("Options:")}
|
|
|
57
60
|
donkeylabs init --type sveltekit # SvelteKit + adapter project
|
|
58
61
|
donkeylabs generate
|
|
59
62
|
donkeylabs plugin create myPlugin
|
|
63
|
+
donkeylabs docs # Sync all docs to ./docs/donkeylabs/
|
|
64
|
+
donkeylabs docs --list # List available docs
|
|
65
|
+
donkeylabs docs workflows # Sync specific doc
|
|
60
66
|
donkeylabs deploy vercel # Deploy to Vercel
|
|
61
67
|
donkeylabs config # Interactive configuration
|
|
62
68
|
donkeylabs config set DATABASE_URL postgresql://...
|
|
@@ -113,6 +119,11 @@ async function main() {
|
|
|
113
119
|
await mcpCommand(positionals.slice(1));
|
|
114
120
|
break;
|
|
115
121
|
|
|
122
|
+
case "docs":
|
|
123
|
+
const { docsCommand } = await import("./commands/docs");
|
|
124
|
+
await docsCommand(positionals.slice(1), { list: values.list, output: values.output });
|
|
125
|
+
break;
|
|
126
|
+
|
|
116
127
|
case "deploy":
|
|
117
128
|
const subcommand = positionals[1];
|
|
118
129
|
if (subcommand === "history") {
|