create-thally-docs 0.10.52 → 0.10.53

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 CHANGED
@@ -53,7 +53,10 @@ scripts and CI, pass
53
53
  `--platform mintlify`, `--platform docusaurus`, or `--platform auto`; `--yes`
54
54
  keeps backward-compatible auto-detection when no platform flag is supplied.
55
55
  Explicit `--platform auto` and `--yes` runs print the live-site limitation
56
- without introducing an interactive prompt.
56
+ without introducing an interactive prompt. Running `migrate` without
57
+ `--platform` or `--yes` from a non-interactive shell (a script, a CI job, a
58
+ piped command) also skips the platform prompt — it can't be answered there —
59
+ and auto-detects instead, with a warning naming the flag that silences it.
57
60
 
58
61
  Mintlify repository migrations preserve the source information architecture
59
62
  instead of rebuilding navigation from folders: nested project roots, `$ref`
@@ -5,7 +5,7 @@ import {
5
5
  readDocsJson,
6
6
  scaffold,
7
7
  writeDocsJson
8
- } from "./chunk-S344UFCS.js";
8
+ } from "./chunk-YFOGBPFV.js";
9
9
 
10
10
  // src/migrate/index.ts
11
11
  import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
@@ -55,6 +55,7 @@ function parseFrontmatter(raw) {
55
55
  // src/check.ts
56
56
  import { parse as parseYaml2 } from "yaml";
57
57
  import { projectNavigationContract } from "@thallylabs/core/navigation";
58
+ import { slugify } from "@thallylabs/core/slugify";
58
59
  function gitLocal(projectDir, args) {
59
60
  try {
60
61
  const out = execFileSync("git", args, { cwd: projectDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -142,14 +143,16 @@ function addOrphanToNav(projectDir, pageId) {
142
143
  writeDocsJson(projectDir, config);
143
144
  }
144
145
  }
145
- function slugify(text) {
146
- return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
147
- }
146
+ var EXPLICIT_ID_ATTRIBUTE = /\bid=(?:"([^"]*)"|'([^']*)'|\{["']([^"'}]*)["']\})/g;
148
147
  function extractHeadingAnchors(content) {
149
148
  const anchors = /* @__PURE__ */ new Set();
150
149
  for (const line of content.split("\n")) {
151
- const m = /^#{1,6}\s+(.+?)\s*#*\s*$/.exec(line);
152
- if (m) anchors.add(slugify(m[1]));
150
+ const heading = /^ {0,3}#{1,6}\s+(.+?)\s*#*\s*$/.exec(line);
151
+ if (heading) anchors.add(slugify(heading[1]));
152
+ for (const idMatch of line.matchAll(EXPLICIT_ID_ATTRIBUTE)) {
153
+ const id = idMatch[1] ?? idMatch[2] ?? idMatch[3];
154
+ if (id) anchors.add(id);
155
+ }
153
156
  }
154
157
  return anchors;
155
158
  }
@@ -173,6 +176,26 @@ function extractLinks(content) {
173
176
  }
174
177
  return links;
175
178
  }
179
+ function extractImageRefs(content) {
180
+ const refs = [];
181
+ const lines = content.split("\n");
182
+ let inFence = false;
183
+ for (let i = 0; i < lines.length; i++) {
184
+ if (/^\s*(```|~~~)/.test(lines[i])) {
185
+ inFence = !inFence;
186
+ continue;
187
+ }
188
+ if (inFence) continue;
189
+ const line = lines[i].replace(/`[^`]*`/g, "");
190
+ for (const m of line.matchAll(/!\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
191
+ refs.push({ target: m[1], line: i + 1 });
192
+ }
193
+ for (const m of line.matchAll(/\bsrc=["']([^"']+)["']/g)) {
194
+ refs.push({ target: m[1], line: i + 1 });
195
+ }
196
+ }
197
+ return refs;
198
+ }
176
199
  function localizedPage(pageId, secondaryLocales) {
177
200
  const [first, ...rest] = pageId.split("/");
178
201
  if (secondaryLocales.has(first) && rest.length > 0) {
@@ -322,7 +345,7 @@ async function runCheck(projectDir, options) {
322
345
  validPaths.add(`/${pageId}`);
323
346
  anchorsByPath.set(path, anchors);
324
347
  anchorsByPath.set(`/${pageId}`, anchors);
325
- linksByFile.push({ file: rel2, path, anchors, links: extractLinks(content), offset: lineOffset });
348
+ linksByFile.push({ file: rel2, path, anchors, links: extractLinks(content), images: extractImageRefs(content), offset: lineOffset });
326
349
  }
327
350
  for (const [path, anchors] of [...anchorsByPath]) {
328
351
  if (secondaryLocales.has(path.split("/")[1])) continue;
@@ -358,6 +381,24 @@ async function runCheck(projectDir, options) {
358
381
  }
359
382
  }
360
383
  }
384
+ const publicDir = join(projectDir, "public");
385
+ for (const { file, images, offset } of linksByFile) {
386
+ for (const { target, line: contentLine } of images) {
387
+ const line = contentLine + offset;
388
+ if (/^(https?:|data:|\{)/i.test(target)) continue;
389
+ if (!target.startsWith("/")) continue;
390
+ const pathOnly = target.split(/[?#]/, 1)[0];
391
+ let decoded;
392
+ try {
393
+ decoded = decodeURIComponent(pathOnly);
394
+ } catch {
395
+ continue;
396
+ }
397
+ if (!existsSync(join(publicDir, decoded))) {
398
+ issues.push({ severity: "warning", message: `Image not found: "${target}" has no file under public/`, file, line });
399
+ }
400
+ }
401
+ }
361
402
  for (const tab of config.tabs) {
362
403
  if (tab.api?.source) validateOpenApi(projectDir, tab.api.source, issues);
363
404
  }
@@ -490,13 +531,15 @@ async function discoverMigration(options) {
490
531
  const cloneDir = join3(temporaryRoot, "repository");
491
532
  console.log(` \u{1F4E6} Cloning ${source.owner}/${source.repo}...`);
492
533
  try {
493
- await cloneGitHubRepository(source, cloneDir);
494
- return migrateRepository({
534
+ const cloneWarnings = [];
535
+ await cloneGitHubRepository(source, cloneDir, cloneWarnings);
536
+ const bundle = migrateRepository({
495
537
  repositoryDir: cloneDir,
496
538
  sourceUrl: options.sourceUrl,
497
539
  docsDir: options.docsDir ?? (source.docsDir || void 0),
498
540
  platform: options.platform
499
541
  });
542
+ return cloneWarnings.length > 0 ? { ...bundle, warnings: [...cloneWarnings, ...bundle.warnings] } : bundle;
500
543
  } finally {
501
544
  rmSync(temporaryRoot, { recursive: true, force: true });
502
545
  }
@@ -515,7 +558,8 @@ async function migrateDocs(options) {
515
558
  // Source provenance is not the destination repository. Migrated pages
516
559
  // live at new paths, so source URLs cannot power edit/issue actions.
517
560
  repoUrl: "",
518
- doInstall: false
561
+ doInstall: false,
562
+ colors: bundle.site?.colors
519
563
  });
520
564
  resetFreshMigrationContent(projectDir);
521
565
  } else if (!existsSync3(projectDir)) {
@@ -66,7 +66,39 @@ function replaceRequired(source, pattern, replacement, field) {
66
66
  }
67
67
  return source.replace(pattern, replacement);
68
68
  }
69
- function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
69
+ var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
70
+ function contrastForeground(hex) {
71
+ const digits = hex.slice(1);
72
+ const value = digits.length <= 4 ? digits.slice(0, 3).split("").map((c) => c + c).join("") : digits.slice(0, 6);
73
+ const channel = (index) => {
74
+ const c = Number.parseInt(value.slice(index, index + 2), 16) / 255;
75
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
76
+ };
77
+ const luminance = 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
78
+ const contrastWithWhite = 1.05 / (luminance + 0.05);
79
+ const contrastWithBlack = (luminance + 0.05) / 0.05;
80
+ return contrastWithWhite >= contrastWithBlack ? "#ffffff" : "#000000";
81
+ }
82
+ function replaceHexKey(block, key, hex) {
83
+ const pattern = new RegExp(`(${key}:[ \\t]*)'#[0-9a-fA-F]{3,8}'`);
84
+ return pattern.test(block) ? block.replace(pattern, `$1'${hex}'`) : block;
85
+ }
86
+ function replacePresetAccent(source, preset, mode, hex) {
87
+ const presetPattern = new RegExp(` ${preset}: \\{[\\s\\S]*?\\n \\},`);
88
+ const presetBlock = source.match(presetPattern)?.[0];
89
+ if (!presetBlock) return source;
90
+ const modePattern = new RegExp(` ${mode}: \\{[\\s\\S]*?\\n \\},`);
91
+ const modeBlock = presetBlock.match(modePattern)?.[0];
92
+ if (!modeBlock) return source;
93
+ let updatedModeBlock = modeBlock;
94
+ updatedModeBlock = replaceHexKey(updatedModeBlock, "accent", hex);
95
+ updatedModeBlock = replaceHexKey(updatedModeBlock, "ring", hex);
96
+ updatedModeBlock = replaceHexKey(updatedModeBlock, "sidebarActiveText", hex);
97
+ updatedModeBlock = replaceHexKey(updatedModeBlock, "accentForeground", contrastForeground(hex));
98
+ if (updatedModeBlock === modeBlock) return source;
99
+ return source.replace(presetBlock, presetBlock.replace(modeBlock, updatedModeBlock));
100
+ }
101
+ function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl, colors) {
70
102
  if (!projectName.trim() || /[\0\r\n]/.test(projectName)) {
71
103
  throw new Error("The documentation project name is invalid.");
72
104
  }
@@ -120,6 +152,14 @@ function updateSiteConfig(targetDir, projectName, description, brandPreset, repo
120
152
  ""
121
153
  );
122
154
  }
155
+ const lightAccent = colors?.dark ?? colors?.primary;
156
+ const darkAccent = colors?.light ?? colors?.primary;
157
+ if (lightAccent && HEX_COLOR_PATTERN.test(lightAccent)) {
158
+ source = replacePresetAccent(source, brandPreset, "light", lightAccent);
159
+ }
160
+ if (darkAccent && HEX_COLOR_PATTERN.test(darkAccent)) {
161
+ source = replacePresetAccent(source, brandPreset, "dark", darkAccent);
162
+ }
123
163
  writeFileSync(siteFile, source, "utf8");
124
164
  }
125
165
  function updatePackageIdentity(targetDir, packageName) {
@@ -171,7 +211,8 @@ function personalizeStarter(targetDir, options) {
171
211
  options.projectName,
172
212
  options.description,
173
213
  options.brandPreset,
174
- options.repoUrl
214
+ options.repoUrl,
215
+ options.colors
175
216
  );
176
217
  updatePackageIdentity(targetDir, options.packageName);
177
218
  updateCloudflareRuntimeName(targetDir, options.packageName);
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  personalizeStarter,
4
4
  updateEnvExample
5
- } from "./chunk-ORUAMPNF.js";
5
+ } from "./chunk-XYRM4GAT.js";
6
6
  import {
7
7
  downloadStarter
8
8
  } from "./chunk-5TY2RFVK.js";
@@ -114,7 +114,8 @@ async function scaffold(options) {
114
114
  doInstall,
115
115
  enableAiChat = true,
116
116
  i18nLocales,
117
- trackRepos
117
+ trackRepos,
118
+ colors
118
119
  } = options;
119
120
  const targetDir = resolve(projectDir);
120
121
  if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
@@ -130,7 +131,8 @@ async function scaffold(options) {
130
131
  brandPreset,
131
132
  repoUrl,
132
133
  enableAiChat,
133
- i18nLocales
134
+ i18nLocales,
135
+ colors
134
136
  });
135
137
  resetTrackingConfig(targetDir);
136
138
  if (trackRepos?.length) {
@@ -17,6 +17,12 @@ interface StarterPersonalizationOptions {
17
17
  repoUrl: string;
18
18
  enableAiChat: boolean;
19
19
  i18nLocales?: Array<StarterLocale>;
20
+ /** Source theme accent color(s) to apply over the chosen brand preset. */
21
+ colors?: {
22
+ primary?: string;
23
+ light?: string;
24
+ dark?: string;
25
+ };
20
26
  }
21
27
  /**
22
28
  * Personalize only the owner-controlled settings in `docs.json`.
@@ -26,7 +32,11 @@ interface StarterPersonalizationOptions {
26
32
  */
27
33
  declare function updateStarterDocsConfig(targetDir: string, enableAiChat: boolean, repoUrl: string, i18nLocales?: Array<StarterLocale>): void;
28
34
  /** Personalize the documented identity and repository fields in `site.ts`. */
29
- declare function updateSiteConfig(targetDir: string, projectName: string, description: string, brandPreset: string, repoUrl: string): void;
35
+ declare function updateSiteConfig(targetDir: string, projectName: string, description: string, brandPreset: string, repoUrl: string, colors?: {
36
+ primary?: string;
37
+ light?: string;
38
+ dark?: string;
39
+ }): void;
30
40
  /** Rename only the root package identity; dependency pins belong to starter. */
31
41
  declare function updatePackageIdentity(targetDir: string, packageName: string): void;
32
42
  /** Update the provider-portable Worker name without recreating its config. */
package/dist/customize.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  updatePackageIdentity,
7
7
  updateSiteConfig,
8
8
  updateStarterDocsConfig
9
- } from "./chunk-ORUAMPNF.js";
9
+ } from "./chunk-XYRM4GAT.js";
10
10
  export {
11
11
  personalizeStarter,
12
12
  updateCloudflareRuntimeName,
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  migrateDocs,
4
4
  parseFrontmatter,
5
5
  runCheck
6
- } from "./chunk-5GIP6NA7.js";
6
+ } from "./chunk-5DNAAGM6.js";
7
7
  import {
8
8
  logo,
9
9
  scaffold,
10
10
  slugify,
11
11
  success
12
- } from "./chunk-S344UFCS.js";
13
- import "./chunk-ORUAMPNF.js";
12
+ } from "./chunk-YFOGBPFV.js";
13
+ import "./chunk-XYRM4GAT.js";
14
14
  import "./chunk-5TY2RFVK.js";
15
15
  import "./chunk-GI5TSGSK.js";
16
16
  import "./chunk-EHH4OP7W.js";
@@ -28,17 +28,27 @@ import {
28
28
  } from "@thallylabs/migrate";
29
29
  function parseMigrationPlatform(value) {
30
30
  if (!value || value === "auto") return void 0;
31
- if (value === "mintlify" || value === "docusaurus") return value;
32
- throw new Error("--platform must be mintlify, docusaurus, or auto.");
31
+ if (value === "mintlify" || value === "docusaurus" || value === "fern") return value;
32
+ throw new Error("--platform must be mintlify, docusaurus, fern, or auto.");
33
+ }
34
+ function isInteractiveStdin() {
35
+ return Boolean(process.stdin.isTTY);
33
36
  }
34
37
  async function gatherMigrationPlatform(value, useDefaults) {
35
38
  const configured = parseMigrationPlatform(value);
36
39
  if (configured || value === "auto" || useDefaults) return configured;
40
+ if (!isInteractiveStdin()) {
41
+ console.warn(
42
+ "\n \u26A0 No platform was specified and this shell is not interactive; auto-detecting the source platform instead. Pass --platform mintlify|docusaurus|fern (or -y) to skip detection and avoid this warning."
43
+ );
44
+ return void 0;
45
+ }
37
46
  return select({
38
47
  message: " Which platform currently hosts these docs?",
39
48
  choices: [
40
49
  { name: "Mintlify", value: "mintlify" },
41
50
  { name: "Docusaurus", value: "docusaurus" },
51
+ { name: "Fern", value: "fern" },
42
52
  { name: "Other / detect automatically", value: void 0 }
43
53
  ],
44
54
  default: "mintlify"
@@ -498,7 +508,7 @@ Options:
498
508
  --branch <name> Override the detected Git branch
499
509
  --docs-dir <path> Override the detected documentation directory
500
510
  --max-pages <count> Limit a public URL crawl to 1-1000 pages
501
- --platform <name> Use mintlify, docusaurus, or auto
511
+ --platform <name> Use mintlify, docusaurus, fern, or auto
502
512
  --skip-validation Import only; explicitly skip content and build verification
503
513
  --api-key <key> Anthropic API key for non-Markdown conversion
504
514
  -y, --yes Skip interactive prompts
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  migrateDocs
4
- } from "../chunk-5GIP6NA7.js";
5
- import "../chunk-S344UFCS.js";
6
- import "../chunk-ORUAMPNF.js";
4
+ } from "../chunk-5DNAAGM6.js";
5
+ import "../chunk-YFOGBPFV.js";
6
+ import "../chunk-XYRM4GAT.js";
7
7
  import "../chunk-5TY2RFVK.js";
8
8
  import "../chunk-GI5TSGSK.js";
9
9
  import "../chunk-EHH4OP7W.js";
@@ -44,6 +44,12 @@ interface ScaffoldOptions {
44
44
  owner: string;
45
45
  repo: string;
46
46
  }>;
47
+ /** Source theme accent color(s) to apply over the chosen brand preset. */
48
+ colors?: {
49
+ primary?: string;
50
+ light?: string;
51
+ dark?: string;
52
+ };
47
53
  }
48
54
  interface ScaffoldResult {
49
55
  projectDir: string;
package/dist/scaffold.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scaffold
4
- } from "./chunk-S344UFCS.js";
5
- import "./chunk-ORUAMPNF.js";
4
+ } from "./chunk-YFOGBPFV.js";
5
+ import "./chunk-XYRM4GAT.js";
6
6
  import {
7
7
  STARTER_ARCHIVE_ROOT,
8
8
  STARTER_COMMIT_SHA,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-thally-docs",
3
- "version": "0.10.52",
3
+ "version": "0.10.53",
4
4
  "description": "Scaffold an open-source Thally docs site for people, search engines, and AI tools.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,7 +45,7 @@
45
45
  "@anthropic-ai/sdk": "^0.36.0",
46
46
  "@inquirer/prompts": "^7.0.0",
47
47
  "@thallylabs/core": "0.2.8",
48
- "@thallylabs/migrate": "0.2.6",
48
+ "@thallylabs/migrate": "0.2.7",
49
49
  "p-limit": "^6.1.0",
50
50
  "tar": "^7.5.22",
51
51
  "yaml": "^2.6.0"