create-thally-docs 0.8.1 → 0.10.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.
@@ -1,846 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/scaffold.ts
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
5
- import { resolve } from "path";
6
-
7
- // src/download.ts
8
- import { Readable, pipeline } from "stream";
9
- import { promisify } from "util";
10
- import tar from "tar";
11
- var pipelineAsync = promisify(pipeline);
12
- var TEMPLATE_REPOSITORY = "thallylabs/docs";
13
- var TARBALL_URL = `https://codeload.github.com/${TEMPLATE_REPOSITORY}/tar.gz/main`;
14
- var EXCLUDE_PATHS = [
15
- // Match both the directory entry itself and every nested file. Tar invokes
16
- // the filter for `.../node_modules` before descendants, without a trailing `/`.
17
- "/node_modules",
18
- // The canonical docs repository may temporarily retain package sources while
19
- // runtime work is being upstreamed. A scaffold consumes the published
20
- // packages declared in package.json; it must never inherit those sources.
21
- "/packages",
22
- "/.git/",
23
- "/.next/",
24
- "/.data/",
25
- "/.thally/",
26
- // The first-party docs deployment pins its own canonical origin.
27
- "/.env.production",
28
- "/thally-track.yml",
29
- "/CODEOWNERS",
30
- "/CLAUDE.md",
31
- "/notes/",
32
- "/public/images/",
33
- "/src/public/",
34
- "/snippets/",
35
- "/.github/ISSUE_TEMPLATE/",
36
- "/.github/PULL_REQUEST_TEMPLATE.md",
37
- "/README.md"
38
- ];
39
- function shouldInclude(path) {
40
- for (const excluded of EXCLUDE_PATHS) {
41
- if (path.includes(excluded)) {
42
- return false;
43
- }
44
- }
45
- return true;
46
- }
47
- async function downloadTemplate(targetDir, siteName) {
48
- console.log("");
49
- console.log(` \u23F3 Creating ${siteName?.trim() || "your docs site"}...`);
50
- const response = await fetch(TARBALL_URL);
51
- if (!response.ok) {
52
- throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
53
- }
54
- if (!response.body) {
55
- throw new Error("Response body is empty");
56
- }
57
- const nodeStream = Readable.fromWeb(response.body);
58
- await pipelineAsync(
59
- nodeStream,
60
- tar.extract({ cwd: targetDir, strip: 1, filter: shouldInclude })
61
- );
62
- }
63
-
64
- // src/docs-json.ts
65
- import { readFileSync, writeFileSync } from "fs";
66
- import { join } from "path";
67
- function readDocsJson(projectDir) {
68
- const docsPath = join(projectDir, "docs.json");
69
- const raw = readFileSync(docsPath, "utf8");
70
- return JSON.parse(raw);
71
- }
72
- function writeDocsJson(projectDir, config) {
73
- const docsPath = join(projectDir, "docs.json");
74
- writeFileSync(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
75
- }
76
- function resetTrackingConfig(projectDir) {
77
- const config = readDocsJson(projectDir);
78
- if (config.tracking) {
79
- delete config.tracking;
80
- writeDocsJson(projectDir, config);
81
- }
82
- }
83
- function writeTrackingConfig(projectDir, repos) {
84
- if (repos.length === 0) return;
85
- const config = readDocsJson(projectDir);
86
- config.tracking = { repos: repos.map((r) => ({ owner: r.owner, repo: r.repo, branch: "main" })) };
87
- writeDocsJson(projectDir, config);
88
- }
89
-
90
- // src/customize.ts
91
- import { existsSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2, readdirSync, cpSync, rmSync } from "fs";
92
- import { join as join2 } from "path";
93
- var STARTER_PAGES = {
94
- "introduction.mdx": `---
95
- title: Introduction
96
- description: Welcome to {NAME}.
97
- mode: home
98
- keywords:
99
- - {NAME}
100
- - documentation
101
- - overview
102
- - getting started
103
- ---
104
-
105
- <Hero
106
- title="Welcome to {NAME}"
107
- subtitle="Use this starter to introduce your product, guide readers to their first successful outcome, and publish a clear API reference."
108
- primaryLabel="Start the quickstart"
109
- primaryHref="/quickstart"
110
- secondaryLabel="See components"
111
- secondaryHref="/components"
112
- />
113
-
114
- <CardGroup cols={3}>
115
- <Card title="Quickstart" icon="party-horn" href="/quickstart">
116
- Show readers the fastest path to a useful first result.
117
- </Card>
118
- <Card title="Components" icon="grid-round" href="/components">
119
- Structure guides with steps, tabs, cards, callouts, accordions, and more.
120
- </Card>
121
- <Card title="API reference" icon="code-simple" href="/api">
122
- Replace \`openapi.yaml\` with your specification to publish interactive endpoints.
123
- </Card>
124
- <Card title="Customize" icon="wrench" href="/customization">
125
- Make the navigation, brand, typography, and links your own.
126
- </Card>
127
- <Card title="Multi-language" icon="message" href="/es">
128
- Switch to the included Spanish example from the language menu.
129
- </Card>
130
- <Card title="Agent-ready docs" icon="link-simple" href="/llms.txt">
131
- Give coding agents a clean, structured version of your documentation.
132
- </Card>
133
- </CardGroup>
134
-
135
- <Note type="info" title="Make it yours">
136
- Start by editing \`src/content/introduction.mdx\`. Then update \`docs.json\` to
137
- organize navigation and \`src/data/site.ts\` to set your product name and links.
138
- </Note>
139
- `,
140
- "quickstart.mdx": `---
141
- title: Quickstart
142
- description: Give readers the fastest path to a successful first result with {NAME}.
143
- keywords:
144
- - {NAME}
145
- - quickstart
146
- - installation
147
- - getting started
148
- ---
149
-
150
- Describe the prerequisites and the shortest useful workflow. A good quickstart
151
- takes someone from zero to a visible result without explaining every option.
152
-
153
- ## Prerequisites
154
-
155
- - Requirement one, such as an account, API key, or supported runtime
156
- - Requirement two, such as a compatible device, browser, or operating system
157
-
158
- <Steps>
159
- <Step title="Install">
160
- Explain how to install {NAME} or create an account.
161
-
162
- \`\`\`bash
163
- npm install your-package
164
- \`\`\`
165
- </Step>
166
- <Step title="Configure">
167
- Show only the configuration required for the first successful run.
168
-
169
- \`\`\`bash
170
- your-cli init
171
- \`\`\`
172
- </Step>
173
- <Step title="Run it">
174
- Give readers a command or action with an observable result.
175
-
176
- \`\`\`bash
177
- your-cli start
178
- \`\`\`
179
- </Step>
180
- </Steps>
181
-
182
- <Tip>
183
- Tell readers where to get help, then link to the next guide they should read.
184
- </Tip>
185
- `,
186
- "components.mdx": `---
187
- title: Components
188
- description: A compact tour of the rich MDX components available in {NAME}.
189
- keywords:
190
- - {NAME}
191
- - components
192
- - MDX
193
- ---
194
-
195
- Use components to keep complex instructions clear without turning every page
196
- into a wall of text.
197
-
198
- ## Show equivalent paths
199
-
200
- <Tabs>
201
- <Tab title="npm">
202
- \`\`\`bash
203
- npm install your-package
204
- \`\`\`
205
- </Tab>
206
- <Tab title="pnpm">
207
- \`\`\`bash
208
- pnpm add your-package
209
- \`\`\`
210
- </Tab>
211
- <Tab title="yarn">
212
- \`\`\`bash
213
- yarn add your-package
214
- \`\`\`
215
- </Tab>
216
- </Tabs>
217
-
218
- ## Reveal detail when it matters
219
-
220
- <Accordion title="Where should advanced configuration live?">
221
- Keep the default path visible and move optional detail into an accordion. This
222
- lets new readers move quickly without hiding information from experts.
223
- </Accordion>
224
-
225
- ## Communicate status
226
-
227
- <Badge variant="success">Stable</Badge>{" "}
228
- <Badge variant="warning">Beta</Badge>{" "}
229
- <Badge variant="info">New</Badge>
230
-
231
- <Tip>
232
- Browse the complete component library at [docs.thally.io](https://docs.thally.io/components/card).
233
- </Tip>
234
- `,
235
- "customization.mdx": `---
236
- title: Customization
237
- description: Make {NAME} feel unmistakably like your product.
238
- keywords:
239
- - {NAME}
240
- - branding
241
- - navigation
242
- ---
243
-
244
- Your documentation should feel like part of the product\u2014not a separate website.
245
-
246
- <CardGroup cols={2}>
247
- <Card title="Brand and theme" icon="party-horn" href="https://docs.thally.io/guides/branding-and-theming">
248
- Configure colors, logos, favicons, typography, and light or dark presentation.
249
- </Card>
250
- <Card title="Navigation" icon="book-open" href="https://docs.thally.io/guides/configuring-navigation">
251
- Organize tabs, icon-labelled groups, pages, and external destinations in \`docs.json\`.
252
- </Card>
253
- <Card title="Domains" icon="link-simple" href="https://app.thally.io">
254
- Connect a custom domain from your site settings in Thally Cloud.
255
- </Card>
256
- <Card title="Analytics and feedback" icon="message" href="https://app.thally.io">
257
- Learn what readers need and collect feedback without third-party widgets.
258
- </Card>
259
- </CardGroup>
260
-
261
- <Note type="info" title="Start with docs.json">
262
- Navigation and portable presentation settings live in \`docs.json\`. Site
263
- identity and fallback brand values live in \`src/data/site.ts\`.
264
- </Note>
265
- `,
266
- "changelog.mdx": `---
267
- title: Changelog
268
- description: Notable changes, releases, and improvements to {NAME}.
269
- keywords:
270
- - {NAME}
271
- - changelog
272
- - releases
273
- - updates
274
- ---
275
-
276
- ## v0.1.0
277
-
278
- The first release of your **{NAME}** documentation.
279
-
280
- - Initial docs site scaffolded with [Thally](https://github.com/thallylabs/thally)
281
- - Agent-ready endpoints live: \`/llms.txt\`, \`/ai.txt\`, \`/api/docs-index\`, and \`/api/agent-readiness\`
282
- - Starter guides in the Overview tab and an interactive API reference
283
-
284
- Edit this page at \`src/content/changelog.mdx\` to announce your own releases as you ship.
285
- `
286
- };
287
- var STARTER_SPANISH_PAGES = {
288
- "introduction.mdx": `---
289
- title: Introducci\xF3n
290
- description: Te damos la bienvenida a {NAME}.
291
- mode: home
292
- ---
293
-
294
- <Hero
295
- title="Te damos la bienvenida a {NAME}"
296
- subtitle="Usa este sitio inicial para presentar tu producto, guiar a tus lectores hasta su primer resultado y publicar una referencia de API clara."
297
- primaryLabel="Abrir inicio r\xE1pido"
298
- primaryHref="/es/quickstart"
299
- secondaryLabel="Ver componentes"
300
- secondaryHref="/es/components"
301
- />
302
-
303
- <CardGroup cols={3}>
304
- <Card title="Inicio r\xE1pido" icon="party-horn" href="/es/quickstart">
305
- Ayuda a tus lectores a lograr su primer resultado en minutos.
306
- </Card>
307
- <Card title="Componentes" icon="grid-round" href="/es/components">
308
- Usa pesta\xF1as, pasos, tarjetas, avisos y acordeones.
309
- </Card>
310
- <Card title="Referencia de API" icon="code-simple" href="/es/api">
311
- Convierte \`openapi.yaml\` en documentaci\xF3n interactiva.
312
- </Card>
313
- <Card title="Personalizaci\xF3n" icon="wrench" href="/es/customization">
314
- Adapta la navegaci\xF3n, marca, tipograf\xEDa y enlaces.
315
- </Card>
316
- <Card title="Varios idiomas" icon="message" href="/">
317
- Cambia entre ingl\xE9s y espa\xF1ol desde el selector de idioma.
318
- </Card>
319
- <Card title="Preparado para IA" icon="link-simple" href="/llms.txt">
320
- Publica contenido legible por agentes desde el primer d\xEDa.
321
- </Card>
322
- </CardGroup>
323
- `,
324
- "quickstart.mdx": `---
325
- title: Inicio r\xE1pido
326
- description: Gu\xEDa a tus lectores hasta su primer resultado con {NAME}.
327
- ---
328
-
329
- Un buen inicio r\xE1pido lleva al lector de cero a un resultado visible sin explicar
330
- todas las opciones.
331
-
332
- <Steps>
333
- <Step title="Instala">
334
- Explica c\xF3mo instalar {NAME} o crear una cuenta.
335
-
336
- \`\`\`bash
337
- npm install your-package
338
- \`\`\`
339
- </Step>
340
- <Step title="Configura">
341
- Muestra \xFAnicamente la configuraci\xF3n necesaria para comenzar.
342
- </Step>
343
- <Step title="Ejecuta">
344
- Termina con una acci\xF3n y un resultado que el lector pueda comprobar.
345
- </Step>
346
- </Steps>
347
-
348
- <Tip>Enlaza la siguiente gu\xEDa que deber\xEDa leer una vez completado este flujo.</Tip>
349
- `,
350
- "components.mdx": `---
351
- title: Componentes
352
- description: Una muestra de los componentes MDX disponibles en {NAME}.
353
- ---
354
-
355
- <Tabs>
356
- <Tab title="npm">\`npm install your-package\`</Tab>
357
- <Tab title="pnpm">\`pnpm add your-package\`</Tab>
358
- <Tab title="yarn">\`yarn add your-package\`</Tab>
359
- </Tabs>
360
-
361
- <Accordion title="\xBFD\xF3nde debe ir la configuraci\xF3n avanzada?">
362
- Mant\xE9n visible el camino principal y coloca los detalles opcionales aqu\xED.
363
- </Accordion>
364
-
365
- <Badge variant="success">Estable</Badge>{" "}
366
- <Badge variant="warning">Beta</Badge>{" "}
367
- <Badge variant="info">Nuevo</Badge>
368
- `,
369
- "customization.mdx": `---
370
- title: Personalizaci\xF3n
371
- description: Haz que {NAME} se sienta como una parte natural de tu producto.
372
- ---
373
-
374
- <CardGroup cols={2}>
375
- <Card title="Marca y tema" icon="party-horn">Configura colores, logotipos, tipograf\xEDa y apariencia.</Card>
376
- <Card title="Navegaci\xF3n" icon="book-open">Organiza pesta\xF1as, grupos con iconos y p\xE1ginas en \`docs.json\`.</Card>
377
- <Card title="Dominios" icon="link-simple">Conecta un dominio personalizado desde Thally Cloud.</Card>
378
- <Card title="Anal\xEDtica y feedback" icon="message">Comprende qu\xE9 necesitan tus lectores.</Card>
379
- </CardGroup>
380
- `,
381
- "changelog.mdx": `---
382
- title: Novedades
383
- description: Cambios, versiones y mejoras destacadas de {NAME}.
384
- ---
385
-
386
- ## v0.1.0
387
-
388
- La primera versi\xF3n de la documentaci\xF3n de **{NAME}**.
389
-
390
- - Sitio creado con [Thally](https://github.com/thallylabs/thally)
391
- - Referencia de API y contenido preparado para agentes
392
- - Ejemplo biling\xFCe en ingl\xE9s y espa\xF1ol
393
- `
394
- };
395
- function buildStarterDocsJson({
396
- enableAiChat,
397
- repoUrl,
398
- i18nLocales
399
- }) {
400
- const config = {};
401
- config.theme = "default";
402
- if (enableAiChat) {
403
- config.ai = { chat: true };
404
- }
405
- if (repoUrl) {
406
- config.navbar = {
407
- links: [{ label: "GitHub", href: repoUrl, type: "github" }],
408
- primary: { label: "Get started", href: "/quickstart" }
409
- };
410
- }
411
- const locales = [
412
- { code: "en", label: "English" },
413
- { code: "es", label: "Espa\xF1ol" },
414
- ...(i18nLocales ?? []).filter(({ code }) => code !== "en" && code !== "es")
415
- ];
416
- config.i18n = { defaultLocale: "en", locales };
417
- config.tabs = [
418
- {
419
- tab: "Overview",
420
- groups: [
421
- { group: "Getting Started", icon: "book-open", pages: ["introduction", "quickstart"] },
422
- { group: "Explore", icon: "grid-round", pages: ["components"] },
423
- { group: "Project", icon: "wrench", pages: ["customization"] }
424
- ]
425
- },
426
- { tab: "API Reference", api: { source: "openapi.yaml" } },
427
- { tab: "Changelog", href: "/changelog" }
428
- ];
429
- return JSON.stringify(config, null, 2) + "\n";
430
- }
431
- function writeStarterContent(targetDir, projectName, enableAiChat = true, repoUrl = "", i18nLocales) {
432
- const contentDir = join2(targetDir, "src", "content");
433
- if (existsSync(contentDir)) {
434
- const entries = readdirSync(contentDir);
435
- for (const entry of entries) {
436
- const fullPath = join2(contentDir, entry);
437
- rmSync(fullPath, { recursive: true, force: true });
438
- }
439
- } else {
440
- mkdirSync(contentDir, { recursive: true });
441
- }
442
- for (const [filename, template] of Object.entries(STARTER_PAGES)) {
443
- const content = template.replace(/\{NAME\}/g, projectName);
444
- writeFileSync2(join2(contentDir, filename), content, "utf8");
445
- }
446
- const spanishDir = join2(contentDir, "es");
447
- mkdirSync(spanishDir, { recursive: true });
448
- for (const [filename, template] of Object.entries(STARTER_SPANISH_PAGES)) {
449
- const content = template.replace(/\{NAME\}/g, projectName);
450
- writeFileSync2(join2(spanishDir, filename), content, "utf8");
451
- }
452
- writeFileSync2(
453
- join2(targetDir, "docs.json"),
454
- buildStarterDocsJson({ enableAiChat, repoUrl: repoUrl || void 0, i18nLocales }),
455
- "utf8"
456
- );
457
- }
458
- function writeStarterAgentGuide(targetDir, projectName) {
459
- const guide = `# ${projectName} documentation instructions
460
-
461
- ## About this project
462
-
463
- - This is a documentation site built with [Thally](https://github.com/thallylabs/thally).
464
- - Pages are MDX files with YAML frontmatter in \`src/content/\`.
465
- - Navigation and product features are configured in \`docs.json\`.
466
- - Site identity and fallback brand values live in \`src/data/site.ts\`.
467
- - Use \`/llms.txt\`, \`/llms-full.txt\`, and \`/skill.md\` on the deployed site for agent-readable context.
468
-
469
- ## Terminology
470
-
471
- <!-- Add product-specific terms and preferred usage. -->
472
-
473
- ## Writing style
474
-
475
- - Use active voice and address the reader as \u201Cyou.\u201D
476
- - Keep sentences concise and headings in sentence case.
477
- - Bold interface labels and format commands, files, and code with backticks.
478
- - Lead with the outcome, then explain prerequisites and steps.
479
-
480
- ## Content boundaries
481
-
482
- <!-- Define what belongs in public docs and what must remain internal. -->
483
- `;
484
- writeFileSync2(join2(targetDir, "AGENTS.md"), guide, "utf8");
485
- }
486
- function writeStarterReadme(targetDir, projectName) {
487
- const readme = `# ${projectName}
488
-
489
- Documentation powered by [Thally](https://github.com/thallylabs/thally).
490
-
491
- ## Local development
492
-
493
- \`\`\`bash
494
- npm install
495
- npm run dev
496
- \`\`\`
497
-
498
- The server starts at [http://localhost:3040](http://localhost:3040), or the next
499
- available port when 3040 is already in use.
500
-
501
- ## Write your docs
502
-
503
- - Add MDX pages in \`src/content/\`.
504
- - Organize navigation and product features in \`docs.json\`.
505
- - Update the site name, links, and brand defaults in \`src/data/site.ts\`.
506
- - Copy \`.env.example\` to \`.env.local\` for local secrets.
507
-
508
- The starter includes a home hero, icon-grouped navigation, English and Spanish
509
- examples, a guided quickstart, component showcase, changelog, OpenAPI reference,
510
- and \`AGENTS.md\` writing instructions for coding agents.
511
-
512
- ## Publishing changes
513
-
514
- Push changes to the default branch to trigger your connected deployment. If the
515
- site is not connected yet, add the repository in
516
- [Thally Cloud](https://app.thally.io) or deploy it to any Next.js host.
517
-
518
- Run \`npx create-thally-docs check --ci .\` before publishing. Deploy the site
519
- anywhere Next.js is supported, or connect the repository to
520
- [Thally Cloud](https://app.thally.io) for managed hosting and services.
521
- `;
522
- writeFileSync2(join2(targetDir, "README.md"), readme, "utf8");
523
- }
524
- function writeCloudflareRuntimeConfig(targetDir, slug) {
525
- writeFileSync2(
526
- join2(targetDir, "open-next.config.ts"),
527
- `/** OpenNext adapter configuration for the Cloudflare Workers runtime. */
528
- import { defineCloudflareConfig } from '@opennextjs/cloudflare/config'
529
-
530
- export default defineCloudflareConfig()
531
- `,
532
- "utf8"
533
- );
534
- writeFileSync2(
535
- join2(targetDir, "wrangler.jsonc"),
536
- `${JSON.stringify(
537
- {
538
- $schema: "node_modules/wrangler/config-schema.json",
539
- name: slug,
540
- main: ".open-next/worker.js",
541
- compatibility_date: "2026-07-15",
542
- compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"],
543
- observability: {
544
- enabled: true,
545
- logs: { head_sampling_rate: 1 },
546
- traces: { enabled: true, head_sampling_rate: 0.01 }
547
- },
548
- assets: {
549
- directory: ".open-next/assets",
550
- binding: "ASSETS"
551
- }
552
- },
553
- null,
554
- 2
555
- )}
556
- `,
557
- "utf8"
558
- );
559
- }
560
- function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
561
- const siteFile = join2(targetDir, "src", "data", "site.ts");
562
- if (!existsSync(siteFile)) {
563
- console.log(" \u26A0\uFE0F Could not find src/data/site.ts \u2014 skipping config update.");
564
- return;
565
- }
566
- let source = readFileSync2(siteFile, "utf8");
567
- source = source.replace(
568
- /name:\s*'[^']*'/,
569
- `name: '${projectName.replace(/'/g, "\\'")}'`
570
- );
571
- source = source.replace(
572
- /description:\s*\n\s*'[^']*'/,
573
- `description:
574
- '${description.replace(/'/g, "\\'")}'`
575
- );
576
- source = source.replace(
577
- /const brandPreset:\s*BrandPresetKey\s*=\s*'[^']*'/,
578
- `const brandPreset: BrandPresetKey = '${brandPreset}'`
579
- );
580
- source = source.replace(/repoUrl:\s*'[^']*'/, `repoUrl: '${repoUrl}'`);
581
- source = source.replace(
582
- /\{\s*label:\s*'GitHub',\s*href:\s*'[^']*'\s*\}/,
583
- `{ label: 'GitHub', href: '${repoUrl}' }`
584
- );
585
- source = source.replace(
586
- /\{\s*label:\s*'Support',\s*href:\s*'[^']*'\s*\}/,
587
- `{ label: 'Support', href: '${repoUrl ? `${repoUrl}/issues/new` : ""}' }`
588
- );
589
- if (!repoUrl) {
590
- source = source.replace(
591
- /\n\s*\{\s*label:\s*'(?:GitHub|Support)',\s*href:\s*''\s*\},?/g,
592
- ""
593
- );
594
- }
595
- writeFileSync2(siteFile, source, "utf8");
596
- }
597
- function patchApiReferenceGuard(targetDir) {
598
- const filePath = join2(targetDir, "src", "data", "api-reference.ts");
599
- if (!existsSync(filePath)) return;
600
- let source = readFileSync2(filePath, "utf8");
601
- source = source.replace(
602
- /export async function buildApiNavigation\([^)]*\)[^{]*\{\n/,
603
- (match) => `${match} if (apiReferenceConfig.specs.length === 0) return []
604
- `
605
- );
606
- writeFileSync2(filePath, source, "utf8");
607
- }
608
- function patchTopBarNavigation(targetDir) {
609
- const filePath = join2(targetDir, "src", "components", "layout", "top-bar.tsx");
610
- if (!existsSync(filePath)) return;
611
- const source = readFileSync2(filePath, "utf8");
612
- if (!source.includes("target={isExternal ? '_blank' : undefined}")) return;
613
- const patched = source.replace(
614
- /if \(collection\.href\) \{\n const isExternal[^\n]+\n return \(\n <a[\s\S]*?<\/a>\n \)\n \}/,
615
- `if (collection.href) {
616
- const isExternal = /^https?:\\/\\//.test(collection.href)
617
- if (isExternal) {
618
- return (
619
- <a
620
- key={collection.id}
621
- href={collection.href}
622
- target="_blank"
623
- rel="noreferrer"
624
- className={baseClasses}
625
- >
626
- {collection.label}
627
- </a>
628
- )
629
- }
630
- return (
631
- <Link
632
- key={collection.id}
633
- href={collection.href}
634
- className={baseClasses}
635
- >
636
- {collection.label}
637
- </Link>
638
- )
639
- }`
640
- );
641
- writeFileSync2(filePath, patched, "utf8");
642
- }
643
- function patchOpenApiFetch(targetDir) {
644
- const filePath = join2(targetDir, "src", "lib", "openapi", "fetch.ts");
645
- if (!existsSync(filePath)) return;
646
- let source = readFileSync2(filePath, "utf8");
647
- source = source.replace(
648
- /const absolutePath = path\.isAbsolute\(filePath\) \? filePath : path\.resolve\(process\.cwd\(\), filePath\)/,
649
- `const absolutePath = filePath.startsWith('/')
650
- ? path.resolve(process.cwd(), 'public', filePath.slice(1))
651
- : path.resolve(process.cwd(), filePath)`
652
- );
653
- writeFileSync2(filePath, source, "utf8");
654
- }
655
- function updateEnvExample(targetDir) {
656
- const envFile = join2(targetDir, ".env.example");
657
- if (existsSync(envFile)) {
658
- const envLocal = join2(targetDir, ".env.local");
659
- if (!existsSync(envLocal)) {
660
- cpSync(envFile, envLocal);
661
- }
662
- }
663
- }
664
- function patchPackageJson(targetDir, slug) {
665
- const pkgPath = join2(targetDir, "package.json");
666
- if (!existsSync(pkgPath)) return;
667
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
668
- const hadWorkspaces = Array.isArray(pkg.workspaces) && pkg.workspaces.length > 0;
669
- const hasRuntimeSourceCompiler = existsSync(
670
- join2(targetDir, "scripts", "build-runtime-sources.mts")
671
- );
672
- pkg.name = slug;
673
- delete pkg.workspaces;
674
- if (pkg.scripts) {
675
- if (hasRuntimeSourceCompiler) {
676
- pkg.scripts["runtime-sources:build"] ??= "tsx scripts/build-runtime-sources.mts";
677
- pkg.scripts["predev"] = "npm run runtime-sources:build";
678
- pkg.scripts["postinstall"] = "npm run runtime-sources:build";
679
- pkg.scripts["prebuild"] = "npm run runtime-sources:build && npm run embeddings:build";
680
- } else {
681
- pkg.scripts["prebuild"] = "npm run embeddings:build";
682
- }
683
- delete pkg.scripts["pretest"];
684
- delete pkg.scripts["packages:build"];
685
- pkg.scripts["build:cloudflare"] = "opennextjs-cloudflare build";
686
- pkg.scripts["preview:cloudflare"] = "npm run build:cloudflare && opennextjs-cloudflare preview";
687
- pkg.scripts["deploy:cloudflare"] = "npm run build:cloudflare && opennextjs-cloudflare deploy";
688
- pkg.scripts["upload:cloudflare"] = "npm run build:cloudflare && opennextjs-cloudflare upload";
689
- }
690
- pkg.devDependencies ??= {};
691
- pkg.devDependencies["@thallylabs/cli"] = "0.5.2";
692
- pkg.devDependencies["@opennextjs/cloudflare"] ??= "1.15.0";
693
- pkg.devDependencies.vite ??= "7.2.6";
694
- pkg.devDependencies.wrangler ??= "4.111.0";
695
- if (pkg.dependencies?.["@thallylabs/mcp"] === "*") {
696
- pkg.dependencies["@thallylabs/mcp"] = "0.7.1";
697
- }
698
- writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
699
- `, "utf8");
700
- const lockPath = join2(targetDir, "package-lock.json");
701
- if (!existsSync(lockPath)) return;
702
- const lock = JSON.parse(readFileSync2(lockPath, "utf8"));
703
- const hasWorkspaceEntries = Object.keys(lock.packages ?? {}).some(
704
- (key) => key === "packages" || key.startsWith("packages/")
705
- );
706
- if (hadWorkspaces || hasWorkspaceEntries) {
707
- rmSync(lockPath);
708
- return;
709
- }
710
- lock.name = slug;
711
- if (lock.packages?.[""]) lock.packages[""].name = slug;
712
- writeFileSync2(lockPath, `${JSON.stringify(lock, null, 2)}
713
- `, "utf8");
714
- }
715
- function patchGitignore(targetDir) {
716
- const gitignorePath = join2(targetDir, ".gitignore");
717
- const existing = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf8") : "";
718
- const lines = existing.split(/\r?\n/);
719
- if (lines.includes("node_modules/")) return;
720
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
721
- writeFileSync2(gitignorePath, `${existing}${separator}node_modules/
722
- `, "utf8");
723
- }
724
-
725
- // src/utils.ts
726
- import { execSync } from "child_process";
727
- import { basename } from "path";
728
- function slugify(name) {
729
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
730
- }
731
- function run(cmd, cwd) {
732
- execSync(cmd, { cwd, stdio: "inherit" });
733
- }
734
- function initGit(targetDir) {
735
- try {
736
- run("git init", targetDir);
737
- run("git add -A", targetDir);
738
- run('git commit -m "Initial commit from create-thally-docs"', targetDir);
739
- } catch {
740
- console.log(" \u26A0\uFE0F Could not initialize git (you can do this manually).");
741
- }
742
- }
743
- function installDeps(targetDir) {
744
- console.log("");
745
- console.log(" \u{1F4E6} Installing dependencies...");
746
- console.log("");
747
- run("npm install --prefer-offline --no-audit --no-fund --progress=false", targetDir);
748
- }
749
- function logo() {
750
- console.log("");
751
- console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
752
- console.log(" \u2551 \u2551");
753
- console.log(" \u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2551");
754
- console.log(" \u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D \u2551");
755
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2551");
756
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2551");
757
- console.log(" \u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551");
758
- console.log(" \u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2551");
759
- console.log(" \u2551 \u2551");
760
- console.log(" \u2551Give your product and docs first-class agent visibility.\u2551");
761
- console.log(" \u2551 \u2551");
762
- console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
763
- console.log("");
764
- }
765
- function success(projectDir, projectName, dependenciesInstalled) {
766
- console.log("");
767
- console.log(" \u2705 Your Thally project is ready!");
768
- console.log("");
769
- console.log(` \u{1F4C2} ${projectDir}`);
770
- console.log("");
771
- console.log(" Next steps:");
772
- console.log("");
773
- console.log(` cd ${basename(projectDir)}`);
774
- if (!dependenciesInstalled) {
775
- console.log(" npm install");
776
- }
777
- console.log(" npm run dev");
778
- console.log("");
779
- console.log(` Your terminal will print the local URL for your ${projectName} docs.`);
780
- console.log("");
781
- console.log(" \u{1F4DD} Key files to edit:");
782
- console.log(" \u2022 src/data/site.ts \u2014 name, links, branding");
783
- console.log(" \u2022 docs.json \u2014 navigation structure");
784
- console.log(" \u2022 src/content/*.mdx \u2014 your documentation");
785
- console.log(" \u2022 openapi.yaml \u2014 API spec (optional)");
786
- console.log("");
787
- console.log(" Happy documenting! \u{1F680}");
788
- console.log("");
789
- }
790
-
791
- // src/scaffold.ts
792
- async function scaffold(options) {
793
- const {
794
- projectDir,
795
- projectName,
796
- description,
797
- brandPreset,
798
- repoUrl,
799
- doInstall,
800
- enableAiChat = true,
801
- i18nLocales,
802
- trackRepos
803
- } = options;
804
- const targetDir = resolve(projectDir);
805
- if (existsSync2(targetDir) && readdirSync2(targetDir).length > 0) {
806
- throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
807
- }
808
- mkdirSync2(targetDir, { recursive: true });
809
- const slug = slugify(projectName);
810
- await downloadTemplate(targetDir, projectName);
811
- resetTrackingConfig(targetDir);
812
- if (trackRepos?.length) {
813
- writeTrackingConfig(targetDir, trackRepos);
814
- const list = trackRepos.map((r) => `${r.owner}/${r.repo}`).join(", ");
815
- console.log(` \u2713 Thally Track enabled \u2014 watching ${list} (branch main, all files; refine in docs.json).`);
816
- console.log(" To finish wiring it: `thally track setup` (pick a trigger) + `thally agent init`,");
817
- console.log(" then add your ANTHROPIC_API_KEY. See /guides/thally-track.");
818
- }
819
- writeStarterContent(targetDir, projectName, enableAiChat, repoUrl, i18nLocales);
820
- writeStarterReadme(targetDir, projectName);
821
- writeStarterAgentGuide(targetDir, projectName);
822
- updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl);
823
- patchApiReferenceGuard(targetDir);
824
- patchTopBarNavigation(targetDir);
825
- patchOpenApiFetch(targetDir);
826
- patchPackageJson(targetDir, slug);
827
- writeCloudflareRuntimeConfig(targetDir, slug);
828
- patchGitignore(targetDir);
829
- updateEnvExample(targetDir);
830
- if (doInstall) {
831
- installDeps(targetDir);
832
- }
833
- initGit(targetDir);
834
- return { projectDir: targetDir };
835
- }
836
-
837
- export {
838
- slugify,
839
- initGit,
840
- installDeps,
841
- logo,
842
- success,
843
- readDocsJson,
844
- writeDocsJson,
845
- scaffold
846
- };