create-thally-docs 0.7.1 → 0.7.3
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 +7 -1
- package/dist/{chunk-YAG5BZEM.js → chunk-PGTE4X3G.js} +1 -1
- package/dist/chunk-WMVWYKAB.js +789 -0
- package/dist/index.js +46 -16
- package/dist/migrate/index.js +2 -2
- package/dist/scaffold.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6HO6ECUE.js +0 -454
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
migrateDocs,
|
|
4
4
|
parseGitHubUrl
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PGTE4X3G.js";
|
|
6
6
|
import {
|
|
7
7
|
logo,
|
|
8
8
|
readDocsJson,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
slugify,
|
|
11
11
|
success,
|
|
12
12
|
writeDocsJson
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WMVWYKAB.js";
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
@@ -20,7 +20,7 @@ import { resolve as resolve2 } from "path";
|
|
|
20
20
|
import { input, select } from "@inquirer/prompts";
|
|
21
21
|
import { basename } from "path";
|
|
22
22
|
import { resolve } from "path";
|
|
23
|
-
async function gatherAnswers(dirArg, useDefaults) {
|
|
23
|
+
async function gatherAnswers(dirArg, useDefaults, installPreference) {
|
|
24
24
|
let projectDir;
|
|
25
25
|
if (dirArg) {
|
|
26
26
|
projectDir = resolve(dirArg);
|
|
@@ -71,13 +71,13 @@ async function gatherAnswers(dirArg, useDefaults) {
|
|
|
71
71
|
else console.log(" \u26A0 No valid owner/repo entries \u2014 skipping Track (add it later with `thally track add`).");
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
let doInstall =
|
|
75
|
-
if (!useDefaults) {
|
|
74
|
+
let doInstall = installPreference ?? false;
|
|
75
|
+
if (!useDefaults && installPreference === void 0) {
|
|
76
76
|
const shouldInstall = await input({
|
|
77
|
-
message: " Install dependencies? (
|
|
78
|
-
default: "
|
|
77
|
+
message: " Install dependencies now? (y/N):",
|
|
78
|
+
default: "N"
|
|
79
79
|
});
|
|
80
|
-
doInstall = shouldInstall.toLowerCase()
|
|
80
|
+
doInstall = shouldInstall.trim().toLowerCase().startsWith("y");
|
|
81
81
|
}
|
|
82
82
|
let i18nLocales;
|
|
83
83
|
if (!useDefaults) {
|
|
@@ -240,8 +240,17 @@ function extractLinks(content) {
|
|
|
240
240
|
}
|
|
241
241
|
return links;
|
|
242
242
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
243
|
+
function localizedPage(pageId, secondaryLocales) {
|
|
244
|
+
const [first, ...rest] = pageId.split("/");
|
|
245
|
+
if (secondaryLocales.has(first) && rest.length > 0) {
|
|
246
|
+
return { navPageId: rest.join("/"), locale: first };
|
|
247
|
+
}
|
|
248
|
+
return { navPageId: pageId };
|
|
249
|
+
}
|
|
250
|
+
function pageIdToPath(pageId, secondaryLocales) {
|
|
251
|
+
const { navPageId, locale } = localizedPage(pageId, secondaryLocales);
|
|
252
|
+
const basePath = navPageId === "introduction" ? "/" : `/${navPageId}`;
|
|
253
|
+
return locale ? `/${locale}${basePath === "/" ? "" : basePath}` : basePath;
|
|
245
254
|
}
|
|
246
255
|
function validateOpenApi(projectDir, source, issues) {
|
|
247
256
|
const specPath = join(projectDir, source);
|
|
@@ -292,6 +301,13 @@ async function runCheck(projectDir, options) {
|
|
|
292
301
|
const contentDir = join(projectDir, "src", "content");
|
|
293
302
|
const issues = [];
|
|
294
303
|
const config = readDocsJson(projectDir);
|
|
304
|
+
const secondaryLocales = new Set(
|
|
305
|
+
(config.i18n?.locales ?? []).map((locale) => locale.code).filter((code) => code !== config.i18n?.defaultLocale)
|
|
306
|
+
);
|
|
307
|
+
const generatedApiPaths = /* @__PURE__ */ new Set([
|
|
308
|
+
"/api",
|
|
309
|
+
...Array.from(secondaryLocales, (locale) => `/${locale}/api`)
|
|
310
|
+
]);
|
|
295
311
|
const navPageIds = /* @__PURE__ */ new Set();
|
|
296
312
|
const duplicates = /* @__PURE__ */ new Set();
|
|
297
313
|
for (const tab of config.tabs) {
|
|
@@ -324,7 +340,8 @@ async function runCheck(projectDir, options) {
|
|
|
324
340
|
for (const filePath of allFiles) {
|
|
325
341
|
const rel = filePath.slice(contentDir.length + 1).replace(/\.mdx$/, "").replace(/\\/g, "/");
|
|
326
342
|
const pageId = rel.endsWith("/index") ? rel.slice(0, -6) : rel;
|
|
327
|
-
|
|
343
|
+
const { navPageId } = localizedPage(pageId, secondaryLocales);
|
|
344
|
+
if (!navPageIds.has(navPageId)) {
|
|
328
345
|
if (fix) {
|
|
329
346
|
addOrphanToNav(projectDir, pageId);
|
|
330
347
|
fixedOrphans.push(pageId);
|
|
@@ -350,7 +367,7 @@ async function runCheck(projectDir, options) {
|
|
|
350
367
|
if (!data.description) issues.push({ severity: "warning", message: `Missing "description" in frontmatter`, file: rel2 });
|
|
351
368
|
if (content.trim().length < 50) issues.push({ severity: "warning", message: `Very short body (${content.trim().length} chars) \u2014 page may be empty`, file: rel2 });
|
|
352
369
|
if (options.drift) checkDrift(projectDir, rel2, data, issues);
|
|
353
|
-
const path = pageIdToPath(pageId);
|
|
370
|
+
const path = pageIdToPath(pageId, secondaryLocales);
|
|
354
371
|
const anchors = extractHeadingAnchors(content);
|
|
355
372
|
validPaths.add(path);
|
|
356
373
|
anchorsByPath.set(path, anchors);
|
|
@@ -371,7 +388,10 @@ async function runCheck(projectDir, options) {
|
|
|
371
388
|
const [beforeHash, anchor] = target.split("#");
|
|
372
389
|
let path = beforeHash.split("?")[0];
|
|
373
390
|
if (path.length > 1) path = path.replace(/\/$/, "");
|
|
374
|
-
|
|
391
|
+
const isGeneratedApiPath = Array.from(generatedApiPaths).some(
|
|
392
|
+
(prefix) => path === prefix || path.startsWith(`${prefix}/`)
|
|
393
|
+
);
|
|
394
|
+
if (isGeneratedApiPath || path.startsWith("/_next") || /\.[a-z0-9]+$/i.test(path)) continue;
|
|
375
395
|
if (!validPaths.has(path)) {
|
|
376
396
|
issues.push({ severity: "error", message: `Broken link: "${target}" \u2014 no page at "${path}"`, file, line });
|
|
377
397
|
} else if (anchor && !anchorsByPath.get(path)?.has(anchor)) {
|
|
@@ -662,10 +682,19 @@ async function runTranslateCommand(locale, pages, force, apiKey, model, yes, pro
|
|
|
662
682
|
// src/index.ts
|
|
663
683
|
var args = process.argv.slice(2);
|
|
664
684
|
var flags = args.filter((a) => a.startsWith("-"));
|
|
685
|
+
var valueFlags = /* @__PURE__ */ new Set([
|
|
686
|
+
"--api-key",
|
|
687
|
+
"--branch",
|
|
688
|
+
"--docs-dir",
|
|
689
|
+
"--into",
|
|
690
|
+
"--locale",
|
|
691
|
+
"--model",
|
|
692
|
+
"--pages"
|
|
693
|
+
]);
|
|
665
694
|
var positional = [];
|
|
666
695
|
for (let i = 0; i < args.length; i++) {
|
|
667
696
|
if (args[i].startsWith("-")) {
|
|
668
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
697
|
+
if (valueFlags.has(args[i]) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
669
698
|
i++;
|
|
670
699
|
}
|
|
671
700
|
} else {
|
|
@@ -734,6 +763,7 @@ async function runMigrateCommand() {
|
|
|
734
763
|
}
|
|
735
764
|
async function runScaffoldCommand() {
|
|
736
765
|
const useDefaults = flags.includes("--yes") || flags.includes("-y");
|
|
766
|
+
const installPreference = flags.includes("--install") ? true : flags.includes("--no-install") ? false : void 0;
|
|
737
767
|
const dirArg = positional[0];
|
|
738
768
|
if (dirArg) {
|
|
739
769
|
const resolved = resolve2(dirArg);
|
|
@@ -743,7 +773,7 @@ async function runScaffoldCommand() {
|
|
|
743
773
|
process.exit(1);
|
|
744
774
|
}
|
|
745
775
|
}
|
|
746
|
-
const answers = await gatherAnswers(dirArg, useDefaults);
|
|
776
|
+
const answers = await gatherAnswers(dirArg, useDefaults, installPreference);
|
|
747
777
|
const result = await scaffold({
|
|
748
778
|
projectDir: answers.projectDir,
|
|
749
779
|
projectName: answers.projectName,
|
|
@@ -754,7 +784,7 @@ async function runScaffoldCommand() {
|
|
|
754
784
|
i18nLocales: answers.i18nLocales,
|
|
755
785
|
trackRepos: answers.trackRepos
|
|
756
786
|
});
|
|
757
|
-
success(result.projectDir, answers.projectName);
|
|
787
|
+
success(result.projectDir, answers.projectName, answers.doInstall);
|
|
758
788
|
}
|
|
759
789
|
async function runCheckCommand() {
|
|
760
790
|
const projectDir = resolve2(positional[1] ?? ".");
|
package/dist/migrate/index.js
CHANGED
package/dist/scaffold.js
CHANGED
package/package.json
CHANGED
package/dist/chunk-6HO6ECUE.js
DELETED
|
@@ -1,454 +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 TARBALL_URL = "https://codeload.github.com/thallylabs/thally/tar.gz/main";
|
|
13
|
-
var EXCLUDE_PATHS = [
|
|
14
|
-
"/cli/",
|
|
15
|
-
"/packages/",
|
|
16
|
-
"/node_modules/",
|
|
17
|
-
"/.git/",
|
|
18
|
-
"/thally-agent.yml",
|
|
19
|
-
"/thally-track.yml",
|
|
20
|
-
"/CODEOWNERS",
|
|
21
|
-
"/CLAUDE.md",
|
|
22
|
-
"/notes/"
|
|
23
|
-
];
|
|
24
|
-
function shouldInclude(path) {
|
|
25
|
-
for (const excluded of EXCLUDE_PATHS) {
|
|
26
|
-
if (path.includes(excluded)) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
async function downloadTemplate(targetDir, siteName) {
|
|
33
|
-
console.log("");
|
|
34
|
-
console.log(` \u23F3 Creating ${siteName?.trim() || "your docs site"}...`);
|
|
35
|
-
const response = await fetch(TARBALL_URL);
|
|
36
|
-
if (!response.ok) {
|
|
37
|
-
throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
|
|
38
|
-
}
|
|
39
|
-
if (!response.body) {
|
|
40
|
-
throw new Error("Response body is empty");
|
|
41
|
-
}
|
|
42
|
-
const nodeStream = Readable.fromWeb(response.body);
|
|
43
|
-
await pipelineAsync(
|
|
44
|
-
nodeStream,
|
|
45
|
-
tar.extract({ cwd: targetDir, strip: 1, filter: shouldInclude })
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/docs-json.ts
|
|
50
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
51
|
-
import { join } from "path";
|
|
52
|
-
function readDocsJson(projectDir) {
|
|
53
|
-
const docsPath = join(projectDir, "docs.json");
|
|
54
|
-
const raw = readFileSync(docsPath, "utf8");
|
|
55
|
-
return JSON.parse(raw);
|
|
56
|
-
}
|
|
57
|
-
function writeDocsJson(projectDir, config) {
|
|
58
|
-
const docsPath = join(projectDir, "docs.json");
|
|
59
|
-
writeFileSync(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
60
|
-
}
|
|
61
|
-
function resetTrackingConfig(projectDir) {
|
|
62
|
-
const config = readDocsJson(projectDir);
|
|
63
|
-
if (config.tracking) {
|
|
64
|
-
delete config.tracking;
|
|
65
|
-
writeDocsJson(projectDir, config);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function writeTrackingConfig(projectDir, repos) {
|
|
69
|
-
if (repos.length === 0) return;
|
|
70
|
-
const config = readDocsJson(projectDir);
|
|
71
|
-
config.tracking = { repos: repos.map((r) => ({ owner: r.owner, repo: r.repo, branch: "main" })) };
|
|
72
|
-
writeDocsJson(projectDir, config);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// src/customize.ts
|
|
76
|
-
import { existsSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2, readdirSync, cpSync, rmSync } from "fs";
|
|
77
|
-
import { join as join2 } from "path";
|
|
78
|
-
import { execSync } from "child_process";
|
|
79
|
-
var STARTER_PAGES = {
|
|
80
|
-
"introduction.mdx": `---
|
|
81
|
-
title: Introduction
|
|
82
|
-
description: Welcome to {NAME} \u2014 learn what it does, how the docs are organized, and where to start.
|
|
83
|
-
keywords:
|
|
84
|
-
- {NAME}
|
|
85
|
-
- documentation
|
|
86
|
-
- overview
|
|
87
|
-
- getting started
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Welcome
|
|
91
|
-
|
|
92
|
-
Welcome to the **{NAME}** documentation. This is your home base for guides, API
|
|
93
|
-
references, and everything you need to build with {NAME}. The site is powered by
|
|
94
|
-
[Thally](https://github.com/thallylabs/thally), an agent-native docs platform \u2014 every page
|
|
95
|
-
is served to humans as polished HTML and to AI agents as structured JSON, JSON-LD,
|
|
96
|
-
and Markdown from the same URL, so assistants can read your docs accurately.
|
|
97
|
-
|
|
98
|
-
## What you'll find here
|
|
99
|
-
|
|
100
|
-
- **Guides** \u2014 step-by-step walkthroughs of common tasks and workflows.
|
|
101
|
-
- **API reference** \u2014 generated from your OpenAPI spec, with a live "Try It" console.
|
|
102
|
-
- **Quickstart** \u2014 install {NAME} and make your first call in a few minutes.
|
|
103
|
-
|
|
104
|
-
## Next steps
|
|
105
|
-
|
|
106
|
-
Start with the [Quickstart](/quickstart) to get {NAME} running, then make this site
|
|
107
|
-
your own by editing \`src/content/introduction.mdx\` and updating the navigation in
|
|
108
|
-
\`docs.json\`. Every change you save is instantly reflected for both readers and agents.
|
|
109
|
-
`,
|
|
110
|
-
"quickstart.mdx": `---
|
|
111
|
-
title: Quickstart
|
|
112
|
-
description: Install {NAME}, configure your API key, and make your first call in under five minutes.
|
|
113
|
-
keywords:
|
|
114
|
-
- {NAME}
|
|
115
|
-
- quickstart
|
|
116
|
-
- installation
|
|
117
|
-
- getting started
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Installation
|
|
121
|
-
|
|
122
|
-
Install {NAME} with your package manager of choice. We recommend pinning the
|
|
123
|
-
version in your project so builds stay reproducible across machines and CI:
|
|
124
|
-
|
|
125
|
-
\`\`\`bash
|
|
126
|
-
npm install {SLUG}
|
|
127
|
-
\`\`\`
|
|
128
|
-
|
|
129
|
-
## Basic usage
|
|
130
|
-
|
|
131
|
-
Import the client and initialize it with your API key. Keep the key in an
|
|
132
|
-
environment variable rather than committing it to source control, so it never
|
|
133
|
-
leaks into your repository or build logs:
|
|
134
|
-
|
|
135
|
-
\`\`\`ts
|
|
136
|
-
import { create } from '{SLUG}'
|
|
137
|
-
|
|
138
|
-
const client = create({ apiKey: process.env.API_KEY })
|
|
139
|
-
\`\`\`
|
|
140
|
-
|
|
141
|
-
## What's next
|
|
142
|
-
|
|
143
|
-
That's the basics \u2014 you're ready to build. Explore the guides for common workflows,
|
|
144
|
-
open the API reference to try endpoints against a live "Try It" console, or edit this
|
|
145
|
-
page at \`src/content/quickstart.mdx\` to document your own onboarding flow.
|
|
146
|
-
`,
|
|
147
|
-
"changelog.mdx": `---
|
|
148
|
-
title: Changelog
|
|
149
|
-
description: Notable changes, releases, and improvements to {NAME}.
|
|
150
|
-
keywords:
|
|
151
|
-
- {NAME}
|
|
152
|
-
- changelog
|
|
153
|
-
- releases
|
|
154
|
-
- updates
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## v0.1.0
|
|
158
|
-
|
|
159
|
-
The first release of your **{NAME}** documentation.
|
|
160
|
-
|
|
161
|
-
- Initial docs site scaffolded with [Thally](https://github.com/thallylabs/thally)
|
|
162
|
-
- Agent-ready endpoints live: \`/llms.txt\`, \`/ai.txt\`, \`/api/docs-index\`, and \`/api/agent-readiness\`
|
|
163
|
-
- Starter guides in the Overview tab and an interactive API reference
|
|
164
|
-
|
|
165
|
-
Edit this page at \`src/content/changelog.mdx\` to announce your own releases as you ship.
|
|
166
|
-
`
|
|
167
|
-
};
|
|
168
|
-
function buildStarterDocsJson({
|
|
169
|
-
enableAiChat,
|
|
170
|
-
repoUrl,
|
|
171
|
-
i18nLocales
|
|
172
|
-
}) {
|
|
173
|
-
const config = {};
|
|
174
|
-
config.theme = "sharp";
|
|
175
|
-
config.fonts = {
|
|
176
|
-
body: { family: "Plus Jakarta Sans", weight: ["400", "500", "600", "700"] },
|
|
177
|
-
heading: { family: "Outfit", weight: ["600", "700"] }
|
|
178
|
-
};
|
|
179
|
-
if (enableAiChat) {
|
|
180
|
-
config.ai = { chat: true };
|
|
181
|
-
}
|
|
182
|
-
if (repoUrl) {
|
|
183
|
-
config.navbar = {
|
|
184
|
-
links: [{ label: "GitHub", href: repoUrl, type: "github" }],
|
|
185
|
-
primary: { label: "Get started", href: "/quickstart" }
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (i18nLocales && i18nLocales.length > 0) {
|
|
189
|
-
config.i18n = {
|
|
190
|
-
defaultLocale: "en",
|
|
191
|
-
locales: [{ code: "en", label: "English" }, ...i18nLocales]
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
config.tabs = [
|
|
195
|
-
{
|
|
196
|
-
tab: "Overview",
|
|
197
|
-
groups: [{ group: "Getting Started", pages: ["introduction", "quickstart"] }]
|
|
198
|
-
},
|
|
199
|
-
{ tab: "API Reference", api: { source: "openapi.yaml" } },
|
|
200
|
-
{ tab: "Changelog", href: "/changelog" }
|
|
201
|
-
];
|
|
202
|
-
return JSON.stringify(config, null, 2) + "\n";
|
|
203
|
-
}
|
|
204
|
-
function writeStarterContent(targetDir, projectName, slug, enableAiChat = true, repoUrl = "", i18nLocales) {
|
|
205
|
-
const contentDir = join2(targetDir, "src", "content");
|
|
206
|
-
if (existsSync(contentDir)) {
|
|
207
|
-
const entries = readdirSync(contentDir);
|
|
208
|
-
for (const entry of entries) {
|
|
209
|
-
const fullPath = join2(contentDir, entry);
|
|
210
|
-
execSync(`rm -rf "${fullPath}"`);
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
mkdirSync(contentDir, { recursive: true });
|
|
214
|
-
}
|
|
215
|
-
for (const [filename, template] of Object.entries(STARTER_PAGES)) {
|
|
216
|
-
const content = template.replace(/\{NAME\}/g, projectName).replace(/\{SLUG\}/g, slug);
|
|
217
|
-
writeFileSync2(join2(contentDir, filename), content, "utf8");
|
|
218
|
-
}
|
|
219
|
-
writeFileSync2(
|
|
220
|
-
join2(targetDir, "docs.json"),
|
|
221
|
-
buildStarterDocsJson({ enableAiChat, repoUrl: repoUrl || void 0, i18nLocales }),
|
|
222
|
-
"utf8"
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
|
|
226
|
-
const siteFile = join2(targetDir, "src", "data", "site.ts");
|
|
227
|
-
if (!existsSync(siteFile)) {
|
|
228
|
-
console.log(" \u26A0\uFE0F Could not find src/data/site.ts \u2014 skipping config update.");
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
let source = readFileSync2(siteFile, "utf8");
|
|
232
|
-
source = source.replace(
|
|
233
|
-
/name:\s*'[^']*'/,
|
|
234
|
-
`name: '${projectName.replace(/'/g, "\\'")}'`
|
|
235
|
-
);
|
|
236
|
-
source = source.replace(
|
|
237
|
-
/description:\s*\n\s*'[^']*'/,
|
|
238
|
-
`description:
|
|
239
|
-
'${description.replace(/'/g, "\\'")}'`
|
|
240
|
-
);
|
|
241
|
-
source = source.replace(
|
|
242
|
-
/const brandPreset:\s*BrandPresetKey\s*=\s*'[^']*'/,
|
|
243
|
-
`const brandPreset: BrandPresetKey = '${brandPreset}'`
|
|
244
|
-
);
|
|
245
|
-
source = source.replace(/repoUrl:\s*'[^']*'/, `repoUrl: '${repoUrl}'`);
|
|
246
|
-
source = source.replace(
|
|
247
|
-
/\{\s*label:\s*'GitHub',\s*href:\s*'[^']*'\s*\}/,
|
|
248
|
-
`{ label: 'GitHub', href: '${repoUrl}' }`
|
|
249
|
-
);
|
|
250
|
-
source = source.replace(
|
|
251
|
-
/\{\s*label:\s*'Support',\s*href:\s*'[^']*'\s*\}/,
|
|
252
|
-
`{ label: 'Support', href: '${repoUrl ? `${repoUrl}/issues/new` : ""}' }`
|
|
253
|
-
);
|
|
254
|
-
writeFileSync2(siteFile, source, "utf8");
|
|
255
|
-
}
|
|
256
|
-
function patchApiReferenceGuard(targetDir) {
|
|
257
|
-
const filePath = join2(targetDir, "src", "data", "api-reference.ts");
|
|
258
|
-
if (!existsSync(filePath)) return;
|
|
259
|
-
let source = readFileSync2(filePath, "utf8");
|
|
260
|
-
source = source.replace(
|
|
261
|
-
/export async function buildApiNavigation\([^)]*\)[^{]*\{\n/,
|
|
262
|
-
(match) => `${match} if (apiReferenceConfig.specs.length === 0) return []
|
|
263
|
-
`
|
|
264
|
-
);
|
|
265
|
-
writeFileSync2(filePath, source, "utf8");
|
|
266
|
-
}
|
|
267
|
-
function patchTopBarNavigation(targetDir) {
|
|
268
|
-
const filePath = join2(targetDir, "src", "components", "layout", "top-bar.tsx");
|
|
269
|
-
if (!existsSync(filePath)) return;
|
|
270
|
-
const source = readFileSync2(filePath, "utf8");
|
|
271
|
-
if (!source.includes("target={isExternal ? '_blank' : undefined}")) return;
|
|
272
|
-
const patched = source.replace(
|
|
273
|
-
/if \(collection\.href\) \{\n const isExternal[^\n]+\n return \(\n <a[\s\S]*?<\/a>\n \)\n \}/,
|
|
274
|
-
`if (collection.href) {
|
|
275
|
-
const isExternal = /^https?:\\/\\//.test(collection.href)
|
|
276
|
-
if (isExternal) {
|
|
277
|
-
return (
|
|
278
|
-
<a
|
|
279
|
-
key={collection.id}
|
|
280
|
-
href={collection.href}
|
|
281
|
-
target="_blank"
|
|
282
|
-
rel="noreferrer"
|
|
283
|
-
className={baseClasses}
|
|
284
|
-
>
|
|
285
|
-
{collection.label}
|
|
286
|
-
</a>
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
return (
|
|
290
|
-
<Link
|
|
291
|
-
key={collection.id}
|
|
292
|
-
href={collection.href}
|
|
293
|
-
className={baseClasses}
|
|
294
|
-
>
|
|
295
|
-
{collection.label}
|
|
296
|
-
</Link>
|
|
297
|
-
)
|
|
298
|
-
}`
|
|
299
|
-
);
|
|
300
|
-
writeFileSync2(filePath, patched, "utf8");
|
|
301
|
-
}
|
|
302
|
-
function patchOpenApiFetch(targetDir) {
|
|
303
|
-
const filePath = join2(targetDir, "src", "lib", "openapi", "fetch.ts");
|
|
304
|
-
if (!existsSync(filePath)) return;
|
|
305
|
-
let source = readFileSync2(filePath, "utf8");
|
|
306
|
-
source = source.replace(
|
|
307
|
-
/const absolutePath = path\.isAbsolute\(filePath\) \? filePath : path\.resolve\(process\.cwd\(\), filePath\)/,
|
|
308
|
-
`const absolutePath = filePath.startsWith('/')
|
|
309
|
-
? path.resolve(process.cwd(), 'public', filePath.slice(1))
|
|
310
|
-
: path.resolve(process.cwd(), filePath)`
|
|
311
|
-
);
|
|
312
|
-
writeFileSync2(filePath, source, "utf8");
|
|
313
|
-
}
|
|
314
|
-
function updateEnvExample(targetDir) {
|
|
315
|
-
const envFile = join2(targetDir, ".env.example");
|
|
316
|
-
if (existsSync(envFile)) {
|
|
317
|
-
const envLocal = join2(targetDir, ".env.local");
|
|
318
|
-
if (!existsSync(envLocal)) {
|
|
319
|
-
cpSync(envFile, envLocal);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
function patchPackageJson(targetDir, slug) {
|
|
324
|
-
const pkgPath = join2(targetDir, "package.json");
|
|
325
|
-
if (!existsSync(pkgPath)) return;
|
|
326
|
-
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
327
|
-
pkg.name = slug;
|
|
328
|
-
delete pkg.workspaces;
|
|
329
|
-
if (pkg.scripts) {
|
|
330
|
-
if (pkg.scripts["prebuild"]) pkg.scripts["prebuild"] = "npm run embeddings:build";
|
|
331
|
-
delete pkg.scripts["pretest"];
|
|
332
|
-
delete pkg.scripts["packages:build"];
|
|
333
|
-
}
|
|
334
|
-
writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
335
|
-
`, "utf8");
|
|
336
|
-
const lockPath = join2(targetDir, "package-lock.json");
|
|
337
|
-
if (existsSync(lockPath)) rmSync(lockPath);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// src/utils.ts
|
|
341
|
-
import { execSync as execSync2 } from "child_process";
|
|
342
|
-
import { basename } from "path";
|
|
343
|
-
function slugify(name) {
|
|
344
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
345
|
-
}
|
|
346
|
-
function run(cmd, cwd) {
|
|
347
|
-
execSync2(cmd, { cwd, stdio: "inherit" });
|
|
348
|
-
}
|
|
349
|
-
function initGit(targetDir) {
|
|
350
|
-
try {
|
|
351
|
-
run("git init", targetDir);
|
|
352
|
-
run("git add -A", targetDir);
|
|
353
|
-
run('git commit -m "Initial commit from create-thally-docs"', targetDir);
|
|
354
|
-
} catch {
|
|
355
|
-
console.log(" \u26A0\uFE0F Could not initialize git (you can do this manually).");
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
function installDeps(targetDir) {
|
|
359
|
-
console.log("");
|
|
360
|
-
console.log(" \u{1F4E6} Installing dependencies...");
|
|
361
|
-
console.log("");
|
|
362
|
-
run("npm install", targetDir);
|
|
363
|
-
}
|
|
364
|
-
function logo() {
|
|
365
|
-
console.log("");
|
|
366
|
-
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");
|
|
367
|
-
console.log(" \u2551 \u2551");
|
|
368
|
-
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");
|
|
369
|
-
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");
|
|
370
|
-
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");
|
|
371
|
-
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");
|
|
372
|
-
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");
|
|
373
|
-
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");
|
|
374
|
-
console.log(" \u2551 \u2551");
|
|
375
|
-
console.log(" \u2551 Beautiful docs, zero lock-in. \u2551");
|
|
376
|
-
console.log(" \u2551 \u2551");
|
|
377
|
-
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");
|
|
378
|
-
console.log("");
|
|
379
|
-
}
|
|
380
|
-
function success(projectDir, projectName) {
|
|
381
|
-
console.log("");
|
|
382
|
-
console.log(" \u2705 Your Thally project is ready!");
|
|
383
|
-
console.log("");
|
|
384
|
-
console.log(` \u{1F4C2} ${projectDir}`);
|
|
385
|
-
console.log("");
|
|
386
|
-
console.log(" Next steps:");
|
|
387
|
-
console.log("");
|
|
388
|
-
console.log(` cd ${basename(projectDir)}`);
|
|
389
|
-
console.log(" npm run dev");
|
|
390
|
-
console.log("");
|
|
391
|
-
console.log(` Then open http://localhost:3040 to see your ${projectName} docs.`);
|
|
392
|
-
console.log("");
|
|
393
|
-
console.log(" \u{1F4DD} Key files to edit:");
|
|
394
|
-
console.log(" \u2022 src/data/site.ts \u2014 name, links, branding");
|
|
395
|
-
console.log(" \u2022 docs.json \u2014 navigation structure");
|
|
396
|
-
console.log(" \u2022 src/content/*.mdx \u2014 your documentation");
|
|
397
|
-
console.log(" \u2022 openapi.yaml \u2014 API spec (optional)");
|
|
398
|
-
console.log("");
|
|
399
|
-
console.log(" Happy documenting! \u{1F680}");
|
|
400
|
-
console.log("");
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/scaffold.ts
|
|
404
|
-
async function scaffold(options) {
|
|
405
|
-
const {
|
|
406
|
-
projectDir,
|
|
407
|
-
projectName,
|
|
408
|
-
description,
|
|
409
|
-
brandPreset,
|
|
410
|
-
repoUrl,
|
|
411
|
-
doInstall,
|
|
412
|
-
enableAiChat = true,
|
|
413
|
-
i18nLocales,
|
|
414
|
-
trackRepos
|
|
415
|
-
} = options;
|
|
416
|
-
const targetDir = resolve(projectDir);
|
|
417
|
-
if (existsSync2(targetDir) && readdirSync2(targetDir).length > 0) {
|
|
418
|
-
throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
|
|
419
|
-
}
|
|
420
|
-
mkdirSync2(targetDir, { recursive: true });
|
|
421
|
-
const slug = slugify(projectName);
|
|
422
|
-
await downloadTemplate(targetDir, projectName);
|
|
423
|
-
resetTrackingConfig(targetDir);
|
|
424
|
-
if (trackRepos?.length) {
|
|
425
|
-
writeTrackingConfig(targetDir, trackRepos);
|
|
426
|
-
const list = trackRepos.map((r) => `${r.owner}/${r.repo}`).join(", ");
|
|
427
|
-
console.log(` \u2713 Thally Track enabled \u2014 watching ${list} (branch main, all files; refine in docs.json).`);
|
|
428
|
-
console.log(" To finish wiring it: `thally track setup` (pick a trigger) + `thally agent init`,");
|
|
429
|
-
console.log(" then add your ANTHROPIC_API_KEY. See /guides/thally-track.");
|
|
430
|
-
}
|
|
431
|
-
writeStarterContent(targetDir, projectName, slug, enableAiChat, repoUrl, i18nLocales);
|
|
432
|
-
updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl);
|
|
433
|
-
patchApiReferenceGuard(targetDir);
|
|
434
|
-
patchTopBarNavigation(targetDir);
|
|
435
|
-
patchOpenApiFetch(targetDir);
|
|
436
|
-
patchPackageJson(targetDir, slug);
|
|
437
|
-
updateEnvExample(targetDir);
|
|
438
|
-
if (doInstall) {
|
|
439
|
-
installDeps(targetDir);
|
|
440
|
-
}
|
|
441
|
-
initGit(targetDir);
|
|
442
|
-
return { projectDir: targetDir };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export {
|
|
446
|
-
slugify,
|
|
447
|
-
initGit,
|
|
448
|
-
installDeps,
|
|
449
|
-
logo,
|
|
450
|
-
success,
|
|
451
|
-
readDocsJson,
|
|
452
|
-
writeDocsJson,
|
|
453
|
-
scaffold
|
|
454
|
-
};
|