@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "2.0.16",
3
+ "version": "2.0.17",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -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") {