@opendocsdev/cli 0.2.0

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.
Files changed (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +300 -0
  3. package/dist/bin/opendocs.js +712 -0
  4. package/dist/bin/opendocs.js.map +1 -0
  5. package/dist/templates/api-reference.mdx +308 -0
  6. package/dist/templates/components.mdx +286 -0
  7. package/dist/templates/configuration.mdx +190 -0
  8. package/dist/templates/docs.json +27 -0
  9. package/dist/templates/introduction.mdx +25 -0
  10. package/dist/templates/logo.svg +4 -0
  11. package/dist/templates/quickstart.mdx +59 -0
  12. package/dist/templates/writing-content.mdx +236 -0
  13. package/package.json +92 -0
  14. package/src/engine/astro.config.ts +75 -0
  15. package/src/engine/src/components/Analytics.astro +57 -0
  16. package/src/engine/src/components/ApiPlayground.astro +24 -0
  17. package/src/engine/src/components/Callout.astro +66 -0
  18. package/src/engine/src/components/Card.astro +75 -0
  19. package/src/engine/src/components/CardGroup.astro +29 -0
  20. package/src/engine/src/components/CodeGroup.astro +231 -0
  21. package/src/engine/src/components/CopyButton.astro +179 -0
  22. package/src/engine/src/components/Steps.astro +27 -0
  23. package/src/engine/src/components/Tab.astro +21 -0
  24. package/src/engine/src/components/TableOfContents.astro +119 -0
  25. package/src/engine/src/components/Tabs.astro +135 -0
  26. package/src/engine/src/components/index.ts +107 -0
  27. package/src/engine/src/components/react/ApiPlayground/AuthSection.tsx +91 -0
  28. package/src/engine/src/components/react/ApiPlayground/CodeBlock.tsx +66 -0
  29. package/src/engine/src/components/react/ApiPlayground/CodeSnippets.tsx +66 -0
  30. package/src/engine/src/components/react/ApiPlayground/CollapsibleSection.tsx +26 -0
  31. package/src/engine/src/components/react/ApiPlayground/KeyValueEditor.tsx +58 -0
  32. package/src/engine/src/components/react/ApiPlayground/ResponseDisplay.tsx +109 -0
  33. package/src/engine/src/components/react/ApiPlayground/Spinner.tsx +6 -0
  34. package/src/engine/src/components/react/ApiPlayground/constants.ts +16 -0
  35. package/src/engine/src/components/react/ApiPlayground/generators.test.ts +130 -0
  36. package/src/engine/src/components/react/ApiPlayground/generators.ts +75 -0
  37. package/src/engine/src/components/react/ApiPlayground/index.tsx +490 -0
  38. package/src/engine/src/components/react/ApiPlayground/types.ts +35 -0
  39. package/src/engine/src/components/react/Callout.tsx +54 -0
  40. package/src/engine/src/components/react/Card.tsx +48 -0
  41. package/src/engine/src/components/react/CardGroup.tsx +24 -0
  42. package/src/engine/src/components/react/FeedbackWidget.tsx +166 -0
  43. package/src/engine/src/components/react/GitHubLink.tsx +28 -0
  44. package/src/engine/src/components/react/NavigationCard.tsx +53 -0
  45. package/src/engine/src/components/react/PageActions.tsx +124 -0
  46. package/src/engine/src/components/react/PageFooter.tsx +91 -0
  47. package/src/engine/src/components/react/SearchModal.tsx +358 -0
  48. package/src/engine/src/components/react/SearchProvider.tsx +37 -0
  49. package/src/engine/src/components/react/Sidebar.tsx +369 -0
  50. package/src/engine/src/components/react/SidebarSearchTrigger.tsx +57 -0
  51. package/src/engine/src/components/react/Steps.tsx +25 -0
  52. package/src/engine/src/components/react/ThemeToggle.tsx +72 -0
  53. package/src/engine/src/components/react/index.ts +14 -0
  54. package/src/engine/src/env.d.ts +10 -0
  55. package/src/engine/src/layouts/DocsLayout.astro +357 -0
  56. package/src/engine/src/lib/__tests__/markdown.test.ts +124 -0
  57. package/src/engine/src/lib/__tests__/mdx-loader.test.ts +205 -0
  58. package/src/engine/src/lib/config.ts +79 -0
  59. package/src/engine/src/lib/markdown.ts +54 -0
  60. package/src/engine/src/lib/mdx-loader.ts +143 -0
  61. package/src/engine/src/lib/mdx-utils.ts +72 -0
  62. package/src/engine/src/lib/remark-opendocs.ts +195 -0
  63. package/src/engine/src/lib/utils.ts +221 -0
  64. package/src/engine/src/pages/[...slug].astro +115 -0
  65. package/src/engine/src/pages/index.astro +71 -0
  66. package/src/engine/src/scripts/mobile-sidebar.ts +56 -0
  67. package/src/engine/src/scripts/theme-init.ts +25 -0
  68. package/src/engine/src/styles/global.css +703 -0
  69. package/src/engine/tailwind.config.mjs +60 -0
  70. package/src/engine/tsconfig.json +15 -0
  71. package/src/templates/api-reference.mdx +308 -0
  72. package/src/templates/components.mdx +286 -0
  73. package/src/templates/configuration.mdx +190 -0
  74. package/src/templates/docs.json +27 -0
  75. package/src/templates/introduction.mdx +25 -0
  76. package/src/templates/logo.svg +4 -0
  77. package/src/templates/quickstart.mdx +59 -0
  78. package/src/templates/writing-content.mdx +236 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env node
2
+
3
+ // bin/opendocs.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import fs from "fs-extra";
8
+ import path from "path";
9
+ import chalk2 from "chalk";
10
+ import ora from "ora";
11
+ import { fileURLToPath } from "url";
12
+
13
+ // src/utils/logger.ts
14
+ import chalk from "chalk";
15
+ var LOG_LEVEL_ORDER = {
16
+ debug: 0,
17
+ info: 1,
18
+ warn: 2,
19
+ error: 3
20
+ };
21
+ var currentLevel = "info";
22
+ function shouldLog(level) {
23
+ return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[currentLevel];
24
+ }
25
+ var logger = {
26
+ debug(message, ...args) {
27
+ if (shouldLog("debug")) {
28
+ console.log(chalk.gray(`[debug] ${message}`), ...args);
29
+ }
30
+ },
31
+ info(message, ...args) {
32
+ if (shouldLog("info")) {
33
+ console.log(message, ...args);
34
+ }
35
+ },
36
+ warn(message, ...args) {
37
+ if (shouldLog("warn")) {
38
+ console.warn(chalk.yellow(message), ...args);
39
+ }
40
+ },
41
+ error(message, ...args) {
42
+ if (shouldLog("error")) {
43
+ console.error(chalk.red(message), ...args);
44
+ }
45
+ },
46
+ success(message, ...args) {
47
+ if (shouldLog("info")) {
48
+ console.log(chalk.green(message), ...args);
49
+ }
50
+ },
51
+ plain(message, ...args) {
52
+ if (shouldLog("info")) {
53
+ console.log(message, ...args);
54
+ }
55
+ }
56
+ };
57
+
58
+ // src/utils/errors.ts
59
+ function getErrorMessage(error) {
60
+ if (error instanceof Error) {
61
+ return error.message;
62
+ }
63
+ return String(error);
64
+ }
65
+
66
+ // src/constants.ts
67
+ var CLI_NAME = "opendocs";
68
+ var CLI_VERSION = "0.1.0";
69
+ var DOCS_CONFIG_FILE = "docs.json";
70
+ var DEFAULT_SNIPPETS = ["snippets"];
71
+ var ENGINE_RELATIVE_PATH = "../../src/engine";
72
+ var DEFAULT_API_URL = "https://app.opendocs.dev";
73
+ var DIST_DIR = "dist";
74
+
75
+ // src/commands/init.ts
76
+ var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
77
+ function getTemplatesDir() {
78
+ const devPath = path.join(__dirname2, "..", "templates");
79
+ const prodPath = path.join(__dirname2, "..", "..", "templates");
80
+ if (fs.existsSync(devPath)) {
81
+ return devPath;
82
+ }
83
+ return prodPath;
84
+ }
85
+ async function readTemplate(filename, replacements) {
86
+ const templatesDir = getTemplatesDir();
87
+ const filePath = path.join(templatesDir, filename);
88
+ let content = await fs.readFile(filePath, "utf-8");
89
+ if (replacements) {
90
+ for (const [key, value] of Object.entries(replacements)) {
91
+ content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
92
+ }
93
+ }
94
+ return content;
95
+ }
96
+ var TEMPLATE_FILES = [
97
+ { template: "introduction.mdx", output: "introduction.mdx" },
98
+ { template: "quickstart.mdx", output: "quickstart.mdx" },
99
+ { template: "components.mdx", output: "components.mdx" },
100
+ { template: "configuration.mdx", output: "configuration.mdx" },
101
+ { template: "writing-content.mdx", output: "writing-content.mdx" },
102
+ { template: "api-reference.mdx", output: "api-reference.mdx" },
103
+ { template: "logo.svg", output: "public/logo.svg" }
104
+ ];
105
+ async function init(name = "my-docs") {
106
+ const projectDir = path.resolve(process.cwd(), name);
107
+ if (await fs.pathExists(projectDir)) {
108
+ logger.error(`Error: Directory "${name}" already exists.`);
109
+ logger.info(chalk2.gray("Please choose a different name or remove the existing directory."));
110
+ process.exit(1);
111
+ }
112
+ const spinner = ora(`Creating new opendocs project: ${name}`).start();
113
+ try {
114
+ await fs.mkdir(projectDir, { recursive: true });
115
+ spinner.text = `Creating ${DOCS_CONFIG_FILE} configuration...`;
116
+ const docsConfig = await readTemplate(DOCS_CONFIG_FILE, { PROJECT_NAME: name });
117
+ await fs.writeFile(path.join(projectDir, DOCS_CONFIG_FILE), docsConfig);
118
+ for (const { template, output } of TEMPLATE_FILES) {
119
+ spinner.text = `Creating ${output}...`;
120
+ const content = await readTemplate(template);
121
+ const outputPath = path.join(projectDir, output);
122
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
123
+ await fs.writeFile(outputPath, content);
124
+ }
125
+ spinner.succeed(chalk2.green(`Successfully created "${name}"!`));
126
+ logger.plain("");
127
+ logger.info(chalk2.bold("Next steps:"));
128
+ logger.plain("");
129
+ logger.info(chalk2.gray(" 1.") + ` cd ${name}`);
130
+ logger.info(chalk2.gray(" 2.") + " opendocs dev");
131
+ logger.plain("");
132
+ logger.info(chalk2.gray("Then open") + " " + chalk2.cyan("http://localhost:4321") + " " + chalk2.gray("in your browser."));
133
+ logger.plain("");
134
+ logger.info(chalk2.gray("Edit") + " " + chalk2.cyan(DOCS_CONFIG_FILE) + " " + chalk2.gray("to customize your documentation."));
135
+ } catch (error) {
136
+ spinner.fail(chalk2.red("Failed to create project"));
137
+ logger.error(getErrorMessage(error));
138
+ process.exit(1);
139
+ }
140
+ }
141
+
142
+ // src/commands/dev.ts
143
+ import { spawn } from "child_process";
144
+ import path3 from "path";
145
+ import { fileURLToPath as fileURLToPath2 } from "url";
146
+ import chalk4 from "chalk";
147
+
148
+ // src/utils/docs-config.ts
149
+ import path2 from "path";
150
+ import fs2 from "fs-extra";
151
+ import chalk3 from "chalk";
152
+
153
+ // src/config/schema.ts
154
+ import { z } from "zod";
155
+ var logoSchema = z.union([
156
+ z.string(),
157
+ z.object({
158
+ light: z.string(),
159
+ dark: z.string()
160
+ })
161
+ ]);
162
+ var pageItemSchema = z.lazy(
163
+ () => z.union([
164
+ z.string(),
165
+ z.object({
166
+ page: z.string(),
167
+ children: z.array(z.string())
168
+ }),
169
+ z.object({
170
+ group: z.string(),
171
+ pages: z.array(pageItemSchema)
172
+ })
173
+ ])
174
+ );
175
+ var navigationItemSchema = z.object({
176
+ group: z.string(),
177
+ pages: z.array(pageItemSchema)
178
+ });
179
+ var socialLinksSchema = z.object({
180
+ github: z.string().url().optional(),
181
+ twitter: z.string().url().optional(),
182
+ discord: z.string().url().optional()
183
+ }).optional();
184
+ var themeSchema = z.object({
185
+ primaryColor: z.string().optional(),
186
+ accentColor: z.string().optional(),
187
+ darkMode: z.boolean().optional()
188
+ }).optional();
189
+ var featuresSchema = z.object({
190
+ search: z.boolean().optional(),
191
+ feedback: z.boolean().optional(),
192
+ analytics: z.boolean().optional()
193
+ }).optional();
194
+ var footerSchema = z.object({
195
+ copyright: z.string().optional(),
196
+ links: z.array(
197
+ z.object({
198
+ title: z.string(),
199
+ url: z.string()
200
+ })
201
+ ).optional()
202
+ }).optional();
203
+ var metadataSchema = z.object({
204
+ url: z.string().url().optional(),
205
+ ogImage: z.string().optional()
206
+ }).optional();
207
+ var docsConfigSchema = z.object({
208
+ // Required fields
209
+ name: z.string(),
210
+ // Optional content fields
211
+ logo: logoSchema.optional(),
212
+ favicon: z.string().optional(),
213
+ // Navigation structure
214
+ navigation: z.array(navigationItemSchema),
215
+ // Styling
216
+ theme: themeSchema,
217
+ // Backend integration (optional - for analytics/feedback/deploy)
218
+ backend: z.object({
219
+ siteId: z.string(),
220
+ apiUrl: z.string().url().optional()
221
+ }).optional(),
222
+ // Feature flags
223
+ features: featuresSchema,
224
+ // Footer content
225
+ footer: footerSchema,
226
+ // Social links (GitHub, Twitter, etc.)
227
+ socialLinks: socialLinksSchema,
228
+ // SEO metadata
229
+ metadata: metadataSchema,
230
+ // Snippet folders configuration
231
+ snippets: z.array(z.string()).optional().default(["snippets"])
232
+ });
233
+
234
+ // src/utils/docs-config.ts
235
+ async function requireDocsJson(cwd) {
236
+ const docsJsonPath = path2.join(cwd, DOCS_CONFIG_FILE);
237
+ if (!await fs2.pathExists(docsJsonPath)) {
238
+ logger.error(`Error: ${DOCS_CONFIG_FILE} not found in current directory.`);
239
+ logger.info(chalk3.gray("Make sure you're in an opendocs project directory or run:"));
240
+ logger.info(chalk3.cyan(" opendocs init my-docs"));
241
+ process.exit(1);
242
+ }
243
+ return docsJsonPath;
244
+ }
245
+ async function loadDocsConfig(docsJsonPath) {
246
+ const raw = await fs2.readJson(docsJsonPath);
247
+ const result = docsConfigSchema.safeParse(raw);
248
+ if (!result.success) {
249
+ logger.warn(
250
+ "docs.json has validation issues: " + result.error.issues.map((i) => i.message).join(", ")
251
+ );
252
+ return raw;
253
+ }
254
+ return result.data;
255
+ }
256
+ function getSnippetsDirs(config) {
257
+ return Array.isArray(config.snippets) ? config.snippets : DEFAULT_SNIPPETS;
258
+ }
259
+ function buildEngineEnv(cwd, snippets) {
260
+ return {
261
+ ...process.env,
262
+ OPENDOCS_PROJECT_DIR: cwd,
263
+ OPENDOCS_SNIPPETS: JSON.stringify(snippets)
264
+ };
265
+ }
266
+ function resolveEngineDir(dirname) {
267
+ return path2.resolve(dirname, ENGINE_RELATIVE_PATH);
268
+ }
269
+
270
+ // src/utils/process.ts
271
+ function attachProcessHandlers(child, label) {
272
+ child.on("error", (error) => {
273
+ logger.error(`Failed to start ${label}: ${error.message}`);
274
+ process.exit(1);
275
+ });
276
+ child.on("close", (code) => {
277
+ if (code !== 0 && code !== null) {
278
+ process.exit(code);
279
+ }
280
+ });
281
+ const cleanup = () => {
282
+ child.kill("SIGTERM");
283
+ };
284
+ process.on("SIGINT", cleanup);
285
+ process.on("SIGTERM", cleanup);
286
+ }
287
+
288
+ // src/commands/dev.ts
289
+ var __filename2 = fileURLToPath2(import.meta.url);
290
+ var __dirname3 = path3.dirname(__filename2);
291
+ async function dev(options) {
292
+ const cwd = process.cwd();
293
+ const docsJsonPath = await requireDocsJson(cwd);
294
+ const docsConfig = await loadDocsConfig(docsJsonPath);
295
+ const snippets = getSnippetsDirs(docsConfig);
296
+ const engineDir = resolveEngineDir(__dirname3);
297
+ const env = buildEngineEnv(cwd, snippets);
298
+ logger.info(chalk4.cyan("Starting development server..."));
299
+ logger.info(chalk4.gray(`Project directory: ${cwd}`));
300
+ logger.info(chalk4.gray(`Port: ${options.port}`));
301
+ logger.plain("");
302
+ const child = spawn(
303
+ "npx",
304
+ ["astro", "dev", "--port", options.port, "--root", engineDir],
305
+ {
306
+ cwd: engineDir,
307
+ env,
308
+ stdio: "inherit",
309
+ shell: true
310
+ }
311
+ );
312
+ attachProcessHandlers(child, "development server");
313
+ }
314
+
315
+ // src/commands/build.ts
316
+ import { spawn as spawn2 } from "child_process";
317
+ import path5 from "path";
318
+ import { fileURLToPath as fileURLToPath3 } from "url";
319
+ import fs4 from "fs-extra";
320
+ import chalk5 from "chalk";
321
+ import ora2 from "ora";
322
+
323
+ // src/utils/sitemap.ts
324
+ import fs3 from "fs-extra";
325
+ import path4 from "path";
326
+ import fg from "fast-glob";
327
+ async function generateSitemap(distDir, baseUrl = "") {
328
+ const htmlFiles = await fg("**/*.html", {
329
+ cwd: distDir,
330
+ absolute: true
331
+ });
332
+ const entries = await Promise.all(
333
+ htmlFiles.map(async (filePath) => {
334
+ const relativePath = path4.relative(distDir, filePath);
335
+ let urlPath = "/" + relativePath.replace(/\\/g, "/");
336
+ if (urlPath.endsWith("/index.html")) {
337
+ urlPath = urlPath.slice(0, -"index.html".length);
338
+ } else if (urlPath.endsWith(".html")) {
339
+ urlPath = urlPath.slice(0, -".html".length);
340
+ }
341
+ if (!urlPath.endsWith("/") && !urlPath.includes(".")) {
342
+ urlPath = urlPath + "/";
343
+ }
344
+ const stats = await fs3.stat(filePath);
345
+ const lastmod = stats.mtime.toISOString().split("T")[0];
346
+ return {
347
+ loc: `${baseUrl}${urlPath}`,
348
+ lastmod
349
+ };
350
+ })
351
+ );
352
+ entries.sort((a, b) => a.loc.localeCompare(b.loc));
353
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
354
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
355
+ ${entries.map((entry) => ` <url>
356
+ <loc>${escapeXml(entry.loc)}</loc>
357
+ <lastmod>${entry.lastmod}</lastmod>
358
+ </url>`).join("\n")}
359
+ </urlset>
360
+ `;
361
+ return xml;
362
+ }
363
+ function escapeXml(str) {
364
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
365
+ }
366
+ async function writeSitemap(distDir, baseUrl = "") {
367
+ const sitemapContent = await generateSitemap(distDir, baseUrl);
368
+ const sitemapPath = path4.join(distDir, "sitemap.xml");
369
+ await fs3.writeFile(sitemapPath, sitemapContent, "utf-8");
370
+ }
371
+
372
+ // src/commands/build.ts
373
+ var __filename3 = fileURLToPath3(import.meta.url);
374
+ var __dirname4 = path5.dirname(__filename3);
375
+ function runCommand(command, args, options) {
376
+ return new Promise((resolve, reject) => {
377
+ const child = spawn2(command, args, {
378
+ cwd: options.cwd,
379
+ env: options.env || process.env,
380
+ stdio: "inherit",
381
+ shell: true
382
+ });
383
+ child.on("error", (error) => {
384
+ reject(error);
385
+ });
386
+ child.on("close", (code) => {
387
+ if (code === 0) {
388
+ resolve();
389
+ } else {
390
+ reject(new Error(`Command exited with code ${code}`));
391
+ }
392
+ });
393
+ });
394
+ }
395
+ async function build() {
396
+ const cwd = process.cwd();
397
+ const docsJsonPath = await requireDocsJson(cwd);
398
+ const docsConfig = await loadDocsConfig(docsJsonPath);
399
+ const snippets = getSnippetsDirs(docsConfig);
400
+ const engineDir = resolveEngineDir(__dirname4);
401
+ const projectDistDir = path5.join(cwd, DIST_DIR);
402
+ const env = buildEngineEnv(cwd, snippets);
403
+ logger.info(chalk5.cyan("\n\u{1F4E6} Building documentation site...\n"));
404
+ const cleanSpinner = ora2("Cleaning existing dist folder...").start();
405
+ try {
406
+ if (await fs4.pathExists(projectDistDir)) {
407
+ await fs4.remove(projectDistDir);
408
+ }
409
+ cleanSpinner.succeed("Cleaned dist folder");
410
+ } catch (error) {
411
+ cleanSpinner.fail("Failed to clean dist folder");
412
+ logger.error(getErrorMessage(error));
413
+ process.exit(1);
414
+ }
415
+ const buildSpinner = ora2("Running Astro build...").start();
416
+ try {
417
+ buildSpinner.text = "Running Astro build (this may take a moment)...";
418
+ await runCommand("npx", ["astro", "build", "--root", engineDir], {
419
+ cwd: engineDir,
420
+ env
421
+ });
422
+ buildSpinner.succeed("Astro build completed");
423
+ } catch (error) {
424
+ buildSpinner.fail("Astro build failed");
425
+ logger.error(getErrorMessage(error));
426
+ process.exit(1);
427
+ }
428
+ if (!await fs4.pathExists(projectDistDir)) {
429
+ logger.error("Build output not found at: " + projectDistDir);
430
+ process.exit(1);
431
+ }
432
+ const pagefindSpinner = ora2("Generating search index with Pagefind...").start();
433
+ try {
434
+ await runCommand("npx", ["pagefind", "--site", projectDistDir], {
435
+ cwd
436
+ });
437
+ pagefindSpinner.succeed("Search index generated");
438
+ } catch (error) {
439
+ pagefindSpinner.fail("Pagefind indexing failed");
440
+ logger.error(getErrorMessage(error));
441
+ process.exit(1);
442
+ }
443
+ const sitemapSpinner = ora2("Generating sitemap.xml...").start();
444
+ try {
445
+ const baseUrl = docsConfig.metadata?.url || docsConfig.baseUrl || "";
446
+ await writeSitemap(projectDistDir, baseUrl);
447
+ sitemapSpinner.succeed("Sitemap generated");
448
+ } catch (error) {
449
+ sitemapSpinner.fail("Sitemap generation failed");
450
+ logger.error(getErrorMessage(error));
451
+ process.exit(1);
452
+ }
453
+ const robotsSpinner = ora2("Generating robots.txt...").start();
454
+ try {
455
+ const siteUrl = docsConfig.metadata?.url || "";
456
+ const sitemapLine = siteUrl ? `
457
+ Sitemap: ${siteUrl}/sitemap.xml` : "";
458
+ const robotsTxt = `User-agent: *
459
+ Allow: /${sitemapLine}
460
+ `;
461
+ await fs4.writeFile(path5.join(projectDistDir, "robots.txt"), robotsTxt, "utf-8");
462
+ robotsSpinner.succeed("robots.txt generated");
463
+ } catch (error) {
464
+ robotsSpinner.fail("robots.txt generation failed");
465
+ logger.error(getErrorMessage(error));
466
+ process.exit(1);
467
+ }
468
+ logger.plain("");
469
+ logger.success("\u2713 Build completed successfully!");
470
+ logger.plain("");
471
+ logger.info(chalk5.gray("Output directory:") + " " + chalk5.cyan(projectDistDir));
472
+ logger.plain("");
473
+ logger.info(chalk5.gray("To preview the build locally, run:"));
474
+ logger.info(chalk5.cyan(" opendocs preview"));
475
+ logger.plain("");
476
+ }
477
+
478
+ // src/commands/preview.ts
479
+ import { spawn as spawn3 } from "child_process";
480
+ import path6 from "path";
481
+ import fs5 from "fs-extra";
482
+ import chalk6 from "chalk";
483
+ async function preview(options) {
484
+ const cwd = process.cwd();
485
+ const distDir = path6.join(cwd, DIST_DIR);
486
+ if (!await fs5.pathExists(distDir)) {
487
+ logger.error("Error: dist folder not found.");
488
+ logger.info(chalk6.gray("The production build has not been created yet."));
489
+ logger.info(chalk6.gray("Run the build command first:"));
490
+ logger.info(chalk6.cyan(" opendocs build"));
491
+ process.exit(1);
492
+ }
493
+ logger.info(chalk6.cyan("Starting preview server..."));
494
+ logger.info(chalk6.gray(`Serving: ${distDir}`));
495
+ logger.info(chalk6.gray(`Port: ${options.port}`));
496
+ logger.plain("");
497
+ logger.success(` Local: http://localhost:${options.port}`);
498
+ logger.plain("");
499
+ const child = spawn3(
500
+ "npx",
501
+ ["serve", distDir, "-l", options.port],
502
+ {
503
+ cwd,
504
+ stdio: "inherit",
505
+ shell: true
506
+ }
507
+ );
508
+ attachProcessHandlers(child, "preview server");
509
+ }
510
+
511
+ // src/commands/deploy.ts
512
+ import path7 from "path";
513
+ import fs6 from "fs-extra";
514
+ import chalk7 from "chalk";
515
+ import ora3 from "ora";
516
+ import * as tar from "tar-stream";
517
+ import { createGzip } from "zlib";
518
+ import { password } from "@inquirer/prompts";
519
+
520
+ // src/utils/format.ts
521
+ function formatBytes(bytes) {
522
+ if (bytes === 0) return "0 B";
523
+ const k = 1024;
524
+ const sizes = ["B", "KB", "MB", "GB"];
525
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
526
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
527
+ }
528
+
529
+ // src/commands/deploy.ts
530
+ async function deploy(options = {}) {
531
+ const cwd = process.cwd();
532
+ const distDir = path7.join(cwd, DIST_DIR);
533
+ const docsJsonPath = await requireDocsJson(cwd);
534
+ const docsConfig = await loadDocsConfig(docsJsonPath);
535
+ const siteId = docsConfig.backend?.siteId;
536
+ if (!siteId) {
537
+ logger.error("Error: No siteId configured in docs.json");
538
+ logger.info(chalk7.gray("Add the following to your docs.json:"));
539
+ logger.info(
540
+ chalk7.cyan(` "backend": {
541
+ "siteId": "your-site-id"
542
+ }`)
543
+ );
544
+ process.exit(1);
545
+ }
546
+ logger.info(chalk7.cyan("\u{1F511} Authentication required\n"));
547
+ let apiKey;
548
+ try {
549
+ apiKey = await password({
550
+ message: "Deploy key:",
551
+ mask: "*"
552
+ });
553
+ } catch (err) {
554
+ logger.error("\nPrompt error: " + getErrorMessage(err));
555
+ logger.info(chalk7.yellow("Deployment cancelled."));
556
+ process.exit(0);
557
+ }
558
+ if (!apiKey || !apiKey.trim()) {
559
+ logger.error("\nError: Deploy key is required.");
560
+ process.exit(1);
561
+ }
562
+ logger.plain("");
563
+ if (!options.skipBuild) {
564
+ await build();
565
+ logger.plain("");
566
+ }
567
+ if (!await fs6.pathExists(distDir)) {
568
+ logger.error("Error: dist folder not found.");
569
+ logger.info(
570
+ chalk7.gray("Run 'opendocs build' first or remove --skip-build.")
571
+ );
572
+ process.exit(1);
573
+ }
574
+ logger.info(chalk7.cyan("\u{1F680} Deploying documentation...\n"));
575
+ const tarballSpinner = ora3("Creating deployment package...").start();
576
+ let tarballBuffer;
577
+ try {
578
+ tarballBuffer = await createTarball(distDir);
579
+ tarballSpinner.succeed(
580
+ `Created deployment package (${formatBytes(tarballBuffer.length)})`
581
+ );
582
+ } catch (error) {
583
+ tarballSpinner.fail("Failed to create deployment package");
584
+ logger.error(getErrorMessage(error));
585
+ process.exit(1);
586
+ }
587
+ const uploadSpinner = ora3("Uploading to server...").start();
588
+ try {
589
+ const apiUrl = docsConfig.backend?.apiUrl || DEFAULT_API_URL;
590
+ const deployUrl = `${apiUrl}/api/sites/${siteId}/deploy`;
591
+ const response = await fetch(deployUrl, {
592
+ method: "POST",
593
+ headers: {
594
+ Authorization: `Bearer ${apiKey}`,
595
+ "Content-Type": "application/gzip"
596
+ },
597
+ body: new Uint8Array(tarballBuffer)
598
+ });
599
+ if (!response.ok) {
600
+ const errorBody = await response.text();
601
+ let errorMessage = `Upload failed with status ${response.status}`;
602
+ try {
603
+ const errorJson = JSON.parse(errorBody);
604
+ if (errorJson.error) {
605
+ errorMessage = errorJson.error;
606
+ }
607
+ } catch {
608
+ logger.error("Error: " + errorBody);
609
+ }
610
+ throw new Error(errorMessage);
611
+ }
612
+ const result = await response.json();
613
+ uploadSpinner.succeed("Upload complete");
614
+ logger.plain("");
615
+ logger.success("\u2713 Deployment successful!");
616
+ logger.plain("");
617
+ logger.info(
618
+ chalk7.gray("Version:") + " " + chalk7.cyan(`v${result.version}`)
619
+ );
620
+ logger.info(
621
+ chalk7.gray("Files:") + " " + chalk7.cyan(result.fileCount.toString())
622
+ );
623
+ logger.info(
624
+ chalk7.gray("Size:") + " " + chalk7.cyan(formatBytes(result.sizeBytes))
625
+ );
626
+ if (result.url) {
627
+ logger.plain("");
628
+ logger.info(chalk7.gray("Your docs are live at:"));
629
+ logger.info(chalk7.cyan.bold(` ${result.url}`));
630
+ } else {
631
+ logger.plain("");
632
+ logger.info(
633
+ chalk7.yellow(
634
+ "Note: Configure a subdomain to access your docs publicly."
635
+ )
636
+ );
637
+ }
638
+ logger.plain("");
639
+ } catch (error) {
640
+ uploadSpinner.fail("Deployment failed");
641
+ logger.error(getErrorMessage(error));
642
+ process.exit(1);
643
+ }
644
+ }
645
+ async function createTarball(distDir) {
646
+ const pack2 = tar.pack();
647
+ const gzip = createGzip();
648
+ const chunks = [];
649
+ const bufferPromise = new Promise((resolve, reject) => {
650
+ gzip.on("data", (chunk) => {
651
+ chunks.push(chunk);
652
+ });
653
+ gzip.on("end", () => {
654
+ resolve(Buffer.concat(chunks));
655
+ });
656
+ gzip.on("error", reject);
657
+ pack2.on("error", reject);
658
+ });
659
+ pack2.pipe(gzip);
660
+ await addFilesToPack(pack2, distDir, "");
661
+ pack2.finalize();
662
+ return bufferPromise;
663
+ }
664
+ async function addFilesToPack(pack2, baseDir, relativePath) {
665
+ const fullPath = path7.join(baseDir, relativePath);
666
+ const entries = await fs6.readdir(fullPath, { withFileTypes: true });
667
+ for (const entry of entries) {
668
+ const entryRelativePath = path7.join(relativePath, entry.name);
669
+ if (entry.isDirectory()) {
670
+ await addFilesToPack(pack2, baseDir, entryRelativePath);
671
+ } else if (entry.isFile()) {
672
+ const filePath = path7.join(baseDir, entryRelativePath);
673
+ const content = await fs6.readFile(filePath);
674
+ const stats = await fs6.stat(filePath);
675
+ await new Promise((resolve, reject) => {
676
+ const entry2 = pack2.entry(
677
+ {
678
+ name: entryRelativePath,
679
+ size: content.length,
680
+ mtime: stats.mtime
681
+ },
682
+ (err) => {
683
+ if (err) reject(err);
684
+ else resolve();
685
+ }
686
+ );
687
+ entry2.end(content);
688
+ });
689
+ }
690
+ }
691
+ }
692
+
693
+ // bin/opendocs.ts
694
+ var program = new Command();
695
+ program.name(CLI_NAME).description("Beautiful docs with zero config. One codebase, deploy anywhere.").version(CLI_VERSION);
696
+ program.command("init [name]").description("Initialize a new opendocs project").action(async (name) => {
697
+ await init(name);
698
+ });
699
+ program.command("dev").description("Start the development server with hot reload").option("-p, --port <port>", "Port to run the dev server on", "4321").action(async (options) => {
700
+ await dev(options);
701
+ });
702
+ program.command("build").description("Build the documentation site for production").action(async () => {
703
+ await build();
704
+ });
705
+ program.command("preview").description("Preview the production build locally").option("-p, --port <port>", "Port to run the preview server on", "4321").action(async (options) => {
706
+ await preview(options);
707
+ });
708
+ program.command("deploy").description("Deploy documentation to the hosted platform").option("--skip-build", "Skip the build step (use existing dist folder)").action(async (options) => {
709
+ await deploy(options);
710
+ });
711
+ program.parse(process.argv);
712
+ //# sourceMappingURL=opendocs.js.map