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.
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readStarterReleaseManifest
4
+ } from "./chunk-KXJT2MG5.js";
5
+ import {
6
+ STABLE_SCAFFOLD_RELEASE
7
+ } from "./chunk-WMHBVXWW.js";
8
+
9
+ // src/download.ts
10
+ import { Readable, Transform, pipeline } from "stream";
11
+ import { promisify } from "util";
12
+ import {
13
+ cpSync,
14
+ lstatSync,
15
+ mkdtempSync,
16
+ readdirSync,
17
+ renameSync,
18
+ rmdirSync,
19
+ rmSync
20
+ } from "fs";
21
+ import { tmpdir } from "os";
22
+ import { dirname, join } from "path";
23
+ import { extract } from "tar";
24
+ var pipelineAsync = promisify(pipeline);
25
+ var STARTER_FETCH_TIMEOUT_MS = 15e3;
26
+ var MAX_COMPRESSED_ARCHIVE_BYTES = 64 * 1024 * 1024;
27
+ var MAX_EXTRACTED_ARCHIVE_BYTES = 256 * 1024 * 1024;
28
+ var MAX_ARCHIVE_FILE_BYTES = 32 * 1024 * 1024;
29
+ var MAX_ARCHIVE_ENTRIES = 1e4;
30
+ var STARTER_REPOSITORY = STABLE_SCAFFOLD_RELEASE.source.repository;
31
+ var STARTER_COMMIT_SHA = STABLE_SCAFFOLD_RELEASE.source.commitSha;
32
+ var STARTER_ARCHIVE_ROOT = `${STARTER_REPOSITORY.split("/").at(-1)}-${STARTER_COMMIT_SHA}`;
33
+ function starterArchiveRoot(release) {
34
+ return `${release.source.repository.split("/").at(-1)}-${release.source.commitSha}`;
35
+ }
36
+ function validateStarterArchiveEntry(archivePath, entry, archiveRoot = STARTER_ARCHIVE_ROOT) {
37
+ if (!archivePath || archivePath.includes("\0") || archivePath.includes("\\") || archivePath.startsWith("/")) {
38
+ throw new Error("The stable Thally starter archive contains an unsafe path.");
39
+ }
40
+ const pathWithoutTrailingSlash = archivePath.endsWith("/") ? archivePath.slice(0, -1) : archivePath;
41
+ const parts = pathWithoutTrailingSlash.split("/");
42
+ const [root, ...relativeParts] = parts;
43
+ if (root !== archiveRoot || relativeParts.some((part) => !part || part === "." || part === "..")) {
44
+ throw new Error("The stable Thally starter archive contains an unsafe path.");
45
+ }
46
+ if (relativeParts.length === 0 && entry.type !== "Directory") {
47
+ throw new Error("The stable Thally starter archive has an invalid root entry.");
48
+ }
49
+ if (!["File", "OldFile", "Directory"].includes(entry.type)) {
50
+ throw new Error(
51
+ `The stable Thally starter archive contains unsupported entry type ${entry.type}.`
52
+ );
53
+ }
54
+ if (!Number.isSafeInteger(entry.size) || entry.size < 0 || entry.size > MAX_ARCHIVE_FILE_BYTES) {
55
+ throw new Error("The stable Thally starter archive contains an oversized file.");
56
+ }
57
+ }
58
+ function starterArchiveFilter(archiveRoot) {
59
+ let entryCount = 0;
60
+ let extractedBytes = 0;
61
+ return (path, entry) => {
62
+ if (!("type" in entry)) {
63
+ throw new Error("The starter archive filter requires extraction metadata.");
64
+ }
65
+ validateStarterArchiveEntry(path, entry, archiveRoot);
66
+ entryCount += 1;
67
+ extractedBytes += entry.size;
68
+ if (entryCount > MAX_ARCHIVE_ENTRIES || extractedBytes > MAX_EXTRACTED_ARCHIVE_BYTES) {
69
+ throw new Error("The stable Thally starter archive exceeds extraction limits.");
70
+ }
71
+ return true;
72
+ };
73
+ }
74
+ function compressedArchiveLimit() {
75
+ let compressedBytes = 0;
76
+ return new Transform({
77
+ transform(chunk, _encoding, callback) {
78
+ compressedBytes += chunk.length;
79
+ if (compressedBytes > MAX_COMPRESSED_ARCHIVE_BYTES) {
80
+ callback(new Error("The stable Thally starter archive is too large."));
81
+ return;
82
+ }
83
+ callback(null, chunk);
84
+ }
85
+ });
86
+ }
87
+ async function downloadStarter(targetDir, siteName, release = STABLE_SCAFFOLD_RELEASE, options = {}) {
88
+ if (options.announce !== false) {
89
+ console.log("");
90
+ console.log(` \u23F3 Creating ${siteName?.trim() || "your docs site"}...`);
91
+ }
92
+ const targetEntry = lstatSync(targetDir);
93
+ if (!targetEntry.isDirectory() || targetEntry.isSymbolicLink()) {
94
+ throw new Error("The Thally starter target must be a regular directory.");
95
+ }
96
+ if (readdirSync(targetDir).length > 0) {
97
+ throw new Error("The Thally starter target directory must be empty.");
98
+ }
99
+ const response = await fetch(release.source.archiveUrl, {
100
+ headers: { accept: "application/gzip, application/octet-stream;q=0.9" },
101
+ cache: "no-store",
102
+ signal: AbortSignal.timeout(STARTER_FETCH_TIMEOUT_MS)
103
+ });
104
+ if (!response.ok) {
105
+ throw new Error(
106
+ `Failed to download the stable Thally starter: ${response.status} ${response.statusText}`
107
+ );
108
+ }
109
+ if (!response.body) {
110
+ throw new Error("The stable Thally starter response body is empty.");
111
+ }
112
+ const contentLength = Number(response.headers.get("content-length"));
113
+ if (Number.isFinite(contentLength) && contentLength > MAX_COMPRESSED_ARCHIVE_BYTES) {
114
+ throw new Error("The stable Thally starter archive is too large.");
115
+ }
116
+ const nodeStream = Readable.fromWeb(
117
+ response.body
118
+ );
119
+ const stagingDir = mkdtempSync(join(tmpdir(), "thally-starter-download-"));
120
+ try {
121
+ await pipelineAsync(
122
+ nodeStream,
123
+ compressedArchiveLimit(),
124
+ extract({
125
+ cwd: stagingDir,
126
+ strip: 1,
127
+ filter: starterArchiveFilter(starterArchiveRoot(release)),
128
+ preservePaths: false,
129
+ strict: true,
130
+ unlink: true,
131
+ maxDecompressionRatio: 100
132
+ })
133
+ );
134
+ readStarterReleaseManifest(stagingDir, release);
135
+ const deliveryDir = mkdtempSync(
136
+ join(dirname(targetDir), ".thally-starter-delivery-")
137
+ );
138
+ try {
139
+ for (const entry of readdirSync(stagingDir)) {
140
+ cpSync(join(stagingDir, entry), join(deliveryDir, entry), {
141
+ recursive: true,
142
+ errorOnExist: true
143
+ });
144
+ }
145
+ if (readdirSync(targetDir).length > 0) {
146
+ throw new Error("The Thally starter target changed during download.");
147
+ }
148
+ rmdirSync(targetDir);
149
+ renameSync(deliveryDir, targetDir);
150
+ } finally {
151
+ rmSync(deliveryDir, { recursive: true, force: true });
152
+ }
153
+ } finally {
154
+ rmSync(stagingDir, { recursive: true, force: true });
155
+ }
156
+ }
157
+
158
+ export {
159
+ STARTER_REPOSITORY,
160
+ STARTER_COMMIT_SHA,
161
+ STARTER_ARCHIVE_ROOT,
162
+ validateStarterArchiveEntry,
163
+ downloadStarter
164
+ };
@@ -3,7 +3,7 @@ import {
3
3
  initGit,
4
4
  installDeps,
5
5
  scaffold
6
- } from "./chunk-RS6U2GSX.js";
6
+ } from "./chunk-GTGHYJXS.js";
7
7
 
8
8
  // src/migrate/index.ts
9
9
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ personalizeStarter,
4
+ updateEnvExample
5
+ } from "./chunk-ORUAMPNF.js";
6
+ import {
7
+ downloadStarter
8
+ } from "./chunk-ENQIF6MA.js";
9
+
10
+ // src/scaffold.ts
11
+ import { existsSync, mkdirSync, readdirSync } from "fs";
12
+ import { resolve } from "path";
13
+
14
+ // src/docs-json.ts
15
+ import { readFileSync, writeFileSync } from "fs";
16
+ import { join } from "path";
17
+ function readDocsJson(projectDir) {
18
+ const docsPath = join(projectDir, "docs.json");
19
+ const raw = readFileSync(docsPath, "utf8");
20
+ return JSON.parse(raw);
21
+ }
22
+ function writeDocsJson(projectDir, config) {
23
+ const docsPath = join(projectDir, "docs.json");
24
+ writeFileSync(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
25
+ }
26
+ function resetTrackingConfig(projectDir) {
27
+ const config = readDocsJson(projectDir);
28
+ if (config.tracking) {
29
+ delete config.tracking;
30
+ writeDocsJson(projectDir, config);
31
+ }
32
+ }
33
+ function writeTrackingConfig(projectDir, repos) {
34
+ if (repos.length === 0) return;
35
+ const config = readDocsJson(projectDir);
36
+ config.tracking = { repos: repos.map((r) => ({ owner: r.owner, repo: r.repo, branch: "main" })) };
37
+ writeDocsJson(projectDir, config);
38
+ }
39
+
40
+ // src/utils.ts
41
+ import { execSync } from "child_process";
42
+ import { basename } from "path";
43
+ function slugify(name) {
44
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
45
+ }
46
+ function run(cmd, cwd) {
47
+ execSync(cmd, { cwd, stdio: "inherit" });
48
+ }
49
+ function initGit(targetDir) {
50
+ try {
51
+ run("git init", targetDir);
52
+ run("git add -A", targetDir);
53
+ run('git commit -m "Initial commit from create-thally-docs"', targetDir);
54
+ } catch {
55
+ console.log(" \u26A0\uFE0F Could not initialize git (you can do this manually).");
56
+ }
57
+ }
58
+ function installDeps(targetDir) {
59
+ console.log("");
60
+ console.log(" \u{1F4E6} Installing dependencies...");
61
+ console.log("");
62
+ run("npm install --prefer-offline --no-audit --no-fund --progress=false", targetDir);
63
+ }
64
+ function logo() {
65
+ console.log("");
66
+ 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");
67
+ console.log(" \u2551 \u2551");
68
+ 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");
69
+ 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");
70
+ 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");
71
+ 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");
72
+ 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");
73
+ 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");
74
+ console.log(" \u2551 \u2551");
75
+ console.log(" \u2551Give your product and docs first-class agent visibility.\u2551");
76
+ console.log(" \u2551 \u2551");
77
+ 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");
78
+ console.log("");
79
+ }
80
+ function success(projectDir, projectName, dependenciesInstalled) {
81
+ console.log("");
82
+ console.log(" \u2705 Your Thally project is ready!");
83
+ console.log("");
84
+ console.log(` \u{1F4C2} ${projectDir}`);
85
+ console.log("");
86
+ console.log(" Next steps:");
87
+ console.log("");
88
+ console.log(` cd ${basename(projectDir)}`);
89
+ if (!dependenciesInstalled) {
90
+ console.log(" npm install");
91
+ }
92
+ console.log(" npm run dev");
93
+ console.log("");
94
+ console.log(` Your terminal will print the local URL for your ${projectName} docs.`);
95
+ console.log("");
96
+ console.log(" \u{1F4DD} Key files to edit:");
97
+ console.log(" \u2022 src/data/site.ts \u2014 name, links, branding");
98
+ console.log(" \u2022 docs.json \u2014 navigation structure");
99
+ console.log(" \u2022 src/content/*.mdx \u2014 your documentation");
100
+ console.log(" \u2022 openapi.yaml \u2014 API spec (optional)");
101
+ console.log("");
102
+ console.log(" Happy documenting! \u{1F680}");
103
+ console.log("");
104
+ }
105
+
106
+ // src/scaffold.ts
107
+ async function scaffold(options) {
108
+ const {
109
+ projectDir,
110
+ projectName,
111
+ description,
112
+ brandPreset,
113
+ repoUrl,
114
+ doInstall,
115
+ enableAiChat = true,
116
+ i18nLocales,
117
+ trackRepos
118
+ } = options;
119
+ const targetDir = resolve(projectDir);
120
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
121
+ throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
122
+ }
123
+ mkdirSync(targetDir, { recursive: true });
124
+ const slug = slugify(projectName);
125
+ await downloadStarter(targetDir, projectName);
126
+ personalizeStarter(targetDir, {
127
+ projectName,
128
+ packageName: slug,
129
+ description,
130
+ brandPreset,
131
+ repoUrl,
132
+ enableAiChat,
133
+ i18nLocales
134
+ });
135
+ resetTrackingConfig(targetDir);
136
+ if (trackRepos?.length) {
137
+ writeTrackingConfig(targetDir, trackRepos);
138
+ const list = trackRepos.map((r) => `${r.owner}/${r.repo}`).join(", ");
139
+ console.log(` \u2713 Thally Track enabled \u2014 watching ${list} (branch main, all files; refine in docs.json).`);
140
+ console.log(" To finish wiring it: `thally track setup` (pick a trigger) + `thally agent init`,");
141
+ console.log(" then add your ANTHROPIC_API_KEY. See /guides/thally-track.");
142
+ }
143
+ updateEnvExample(targetDir);
144
+ if (doInstall) {
145
+ installDeps(targetDir);
146
+ }
147
+ initGit(targetDir);
148
+ return { projectDir: targetDir };
149
+ }
150
+
151
+ export {
152
+ slugify,
153
+ initGit,
154
+ installDeps,
155
+ logo,
156
+ success,
157
+ readDocsJson,
158
+ writeDocsJson,
159
+ scaffold
160
+ };