create-thally-docs 0.10.60 → 0.10.61

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.
@@ -246,7 +246,33 @@ function pageIdToPath(pageId, secondaryLocales) {
246
246
  const basePath = navPageId === "introduction" ? "/" : `/${navPageId}`;
247
247
  return locale ? `/${locale}${basePath === "/" ? "" : basePath}` : basePath;
248
248
  }
249
- function resolveLink(target, redirects) {
249
+ function compileDynamicRedirectSource(source, destination) {
250
+ if (!source.includes(":")) return null;
251
+ const paramNames = [];
252
+ let pattern = "";
253
+ source.split("/").forEach((segment, index) => {
254
+ const match = segment.match(/^:([A-Za-z_][A-Za-z0-9_]*)([*+?]?)$/);
255
+ const separator = index === 0 ? "" : "/";
256
+ if (!match) {
257
+ pattern += separator + segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
258
+ return;
259
+ }
260
+ const [, name, quantifier] = match;
261
+ paramNames.push(name);
262
+ if (quantifier === "*") pattern += `(?:${separator}(.*))?`;
263
+ else if (quantifier === "?") pattern += `(?:${separator}([^/]+))?`;
264
+ else pattern += `${separator}(${quantifier === "+" ? ".+" : "[^/]+"})`;
265
+ });
266
+ return { regex: new RegExp(`^${pattern}$`), paramNames, destination };
267
+ }
268
+ function resolveDynamicRedirectDestination(redirect, match) {
269
+ return redirect.destination.replace(/:([A-Za-z_][A-Za-z0-9_]*)[*+?]?/g, (whole, name) => {
270
+ const index = redirect.paramNames.indexOf(name);
271
+ return index === -1 ? whole : match[index + 1] ?? "";
272
+ });
273
+ }
274
+ var MAX_REDIRECT_HOPS = 20;
275
+ function resolveLink(target, redirects, dynamicRedirects = []) {
250
276
  let current = target;
251
277
  let anchor;
252
278
  const seen = /* @__PURE__ */ new Set();
@@ -256,11 +282,16 @@ function resolveLink(target, redirects) {
256
282
  if (hash >= 0) anchor = current.slice(hash + 1);
257
283
  const beforeHash = hash >= 0 ? current.slice(0, hash) : current;
258
284
  const path = beforeHash.split("?", 1)[0].replace(/\/$/, "") || "/";
259
- if (seen.has(path)) return { path, anchor, cycle: true };
285
+ if (seen.has(path) || seen.size >= MAX_REDIRECT_HOPS) return { path, anchor, cycle: true };
260
286
  seen.add(path);
261
- const redirected = redirects.get(path);
262
- if (!redirected) return { path, anchor };
263
- current = redirected;
287
+ const literalRedirect = redirects.get(path);
288
+ if (literalRedirect) {
289
+ current = literalRedirect;
290
+ continue;
291
+ }
292
+ const dynamicMatch = dynamicRedirects.map((redirect) => ({ redirect, match: path.match(redirect.regex) })).find((candidate) => candidate.match !== null);
293
+ if (!dynamicMatch) return { path, anchor };
294
+ current = resolveDynamicRedirectDestination(dynamicMatch.redirect, dynamicMatch.match);
264
295
  }
265
296
  }
266
297
  function validateOpenApi(projectDir, source, issues) {
@@ -320,11 +351,15 @@ async function runCheck(projectDir, options) {
320
351
  ...Array.from(secondaryLocales, (locale) => `/${locale}/api`)
321
352
  ]);
322
353
  const redirectDestinations = new Map(
323
- (config.redirects ?? []).map((redirect) => [
354
+ (config.redirects ?? []).filter((redirect) => !redirect.source.includes(":")).map((redirect) => [
324
355
  redirect.source.replace(/\/$/, "") || "/",
325
356
  redirect.destination
326
357
  ])
327
358
  );
359
+ const dynamicRedirects = (config.redirects ?? []).flatMap((redirect) => {
360
+ const compiled = compileDynamicRedirectSource(redirect.source, redirect.destination);
361
+ return compiled ? [compiled] : [];
362
+ });
328
363
  const navigation = projectNavigationContract(config);
329
364
  const navPageIds = new Set(navigation.authoredPageIds);
330
365
  for (const tab of navigation.emptyTabs) {
@@ -406,7 +441,7 @@ async function runCheck(projectDir, options) {
406
441
  continue;
407
442
  }
408
443
  if (!target.startsWith("/")) continue;
409
- const { path, anchor, cycle, external } = resolveLink(target, redirectDestinations);
444
+ const { path, anchor, cycle, external } = resolveLink(target, redirectDestinations, dynamicRedirects);
410
445
  if (external) continue;
411
446
  const isGeneratedApiPath = Array.from(generatedApiPaths).some(
412
447
  (prefix) => path === prefix || path.startsWith(`${prefix}/`)
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  migrateDocs,
4
4
  parseFrontmatter,
5
5
  runCheck
6
- } from "./chunk-KJLNIXGC.js";
6
+ } from "./chunk-NNT7JTC2.js";
7
7
  import {
8
8
  logo,
9
9
  scaffold,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  migrateDocs
4
- } from "../chunk-KJLNIXGC.js";
4
+ } from "../chunk-NNT7JTC2.js";
5
5
  import "../chunk-SLDENDOT.js";
6
6
  import "../chunk-XYRM4GAT.js";
7
7
  import "../chunk-UX57AKM4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-thally-docs",
3
- "version": "0.10.60",
3
+ "version": "0.10.61",
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.9",
48
- "@thallylabs/migrate": "0.2.10",
48
+ "@thallylabs/migrate": "0.2.11",
49
49
  "p-limit": "^6.1.0",
50
50
  "tar": "^7.5.22",
51
51
  "yaml": "^2.6.0"