Ctrl K
ComunidadGuías…

scripts/check-workspace-splat-nesting.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bun
/**
 * Lint: the workspace not-found route stays INSIDE the `/w/:slug` element.
 *
 * React Router ranks branches by specificity, not source order. A `<Route
 * path="*">` at the top level therefore outranks the `/w/:slug` branch for any
 * unmatched `/w/<slug>/…` URL — the branch is never entered, so
 * `WorkspaceRouteGuard` never mounts.
 *
 * The guard is what projects the route slug onto the ambient workspace. Skip it
 * and the shell keeps serving the previously-focused workspace's placements, so
 * an app that IS installed in the workspace the URL names gets reported as gone.
 * Reachable by pressing Back after a workspace switch, or by opening a shared
 * `/w/<other>/app/<x>` link.
 *
 * This shipped wrong once already, which is why it is a gate rather than a
 * comment. It cannot be a unit test: React Router rejects a custom component
 * that returns `<Route>` elements, so the route table can't be mounted in
 * isolation without restructuring it into a `useRoutes` config.
 *
 * What this asserts, against `web/src/App.tsx`:
 *   - the `<Route path="/w/:slug">` element has a direct `<Route path="*">` child
 *   - that child renders `WorkspaceNotFoundPage` (the slug-derived settledness
 *     wrapper — the bare `NotFoundPage` would compare against lagging ambient
 *     state and paint a false "not available" mid-switch)
 *   - a `<Route path="*">` also exists OUTSIDE the workspace element, so a path
 *     with no workspace at all (`/nonsense`) still lands somewhere
 *
 * Position is only asserted for the nested one: the top-level splat is free to
 * move within the table, but deleting it restores the white screen for every
 * non-workspace URL, so its existence is checked.
 */

import { readFileSync } from "node:fs";
import { join } from "node:path";
import * as ts from "typescript";

const ROOT = join(import.meta.dirname ?? __dirname, "..");
const TARGET = join(ROOT, "web/src/App.tsx");
const WORKSPACE_PATH = "/w/:slug";
const SPLAT = "*";
const REQUIRED_ELEMENT = "WorkspaceNotFoundPage";

/** The literal value of a JSX attribute, or undefined when it isn't a plain string. */
function stringAttr(node: ts.JsxOpeningLikeElement, name: string): string | undefined {
  for (const prop of node.attributes.properties) {
    if (!ts.isJsxAttribute(prop) || prop.name.getText() !== name) continue;
    const init = prop.initializer;
    if (init && ts.isStringLiteral(init)) return init.text;
  }
  return undefined;
}

/** Does this `<Route>`'s `element={...}` mention `WorkspaceNotFoundPage`? */
function elementMentions(node: ts.JsxOpeningLikeElement, name: string): boolean {
  for (const prop of node.attributes.properties) {
    if (!ts.isJsxAttribute(prop) || prop.name.getText() !== "element") continue;
    return prop.initializer?.getText().includes(name) ?? false;
  }
  return false;
}

function openingOf(node: ts.Node): ts.JsxOpeningLikeElement | undefined {
  if (ts.isJsxElement(node)) return node.openingElement;
  if (ts.isJsxSelfClosingElement(node)) return node;
  return undefined;
}

const source = ts.createSourceFile(
  TARGET,
  readFileSync(TARGET, "utf8"),
  ts.ScriptTarget.Latest,
  true,
  ts.ScriptKind.TSX,
);

let workspaceRoute: ts.JsxElement | undefined;
(function find(node: ts.Node): void {
  if (ts.isJsxElement(node)) {
    const opening = node.openingElement;
    if (opening.tagName.getText() === "Route" && stringAttr(opening, "path") === WORKSPACE_PATH) {
      workspaceRoute = node;
      return;
    }
  }
  ts.forEachChild(node, find);
})(source);

if (!workspaceRoute) {
  console.error(
    `✗ check:workspace-splat-nesting — no <Route path="${WORKSPACE_PATH}"> element found in web/src/App.tsx.\n` +
      "  If the workspace route was renamed, update this check with it.",
  );
  process.exit(1);
}

const nested = workspaceRoute.children
  .map(openingOf)
  .filter((o): o is ts.JsxOpeningLikeElement => !!o)
  .filter((o) => o.tagName.getText() === "Route" && stringAttr(o, "path") === SPLAT);

if (nested.length === 0) {
  console.error(
    `✗ check:workspace-splat-nesting — <Route path="${WORKSPACE_PATH}"> has no <Route path="${SPLAT}"> child.\n\n` +
      "  A top-level splat cannot serve workspace-scoped misses: React Router ranks by\n" +
      "  specificity, so it swallows /w/<slug>/… whole and WorkspaceRouteGuard never\n" +
      "  mounts. Without the guard the shell stays on the previously-focused workspace\n" +
      "  and reports an installed app as gone. Keep the nested splat.",
  );
  process.exit(1);
}

const wrong = nested.filter((o) => !elementMentions(o, REQUIRED_ELEMENT));
if (wrong.length > 0) {
  console.error(
    `✗ check:workspace-splat-nesting — the nested <Route path="${SPLAT}"> must render ${REQUIRED_ELEMENT}.\n\n` +
      "  It derives settledness from the route slug. The bare NotFoundPage takes a\n" +
      "  `settled` prop computed from ambient workspace state, which lags a switch by a\n" +
      "  render — reporting settled while both sides still name the previous workspace,\n" +
      '  and painting "not available" for an app that is installed in the one the URL names.',
  );
  process.exit(1);
}

// A splat outside the workspace element — the terminal route for everything
// else. Position is irrelevant (React Router ranks by specificity), existence is
// not: without it an unmatched non-workspace path renders `null` into the main
// area, which is the white screen this whole gate exists to keep closed.
const wsStart = workspaceRoute.getStart();
const wsEnd = workspaceRoute.getEnd();
let hasTopLevelSplat = false;
(function findSplat(node: ts.Node): void {
  if (hasTopLevelSplat) return;
  const opening = openingOf(node);
  if (
    opening &&
    opening.tagName.getText() === "Route" &&
    stringAttr(opening, "path") === SPLAT &&
    (node.getStart() < wsStart || node.getEnd() > wsEnd)
  ) {
    hasTopLevelSplat = true;
    return;
  }
  ts.forEachChild(node, findSplat);
})(source);

if (!hasTopLevelSplat) {
  console.error(
    `✗ check-workspace-splat-nesting — no <Route path="${SPLAT}"> outside <Route path="${WORKSPACE_PATH}">.\n\n` +
      "  Workspace-scoped misses are covered by the nested splat, but a path with no\n" +
      "  workspace at all then matches nothing and <Routes> renders null into the main\n" +
      "  area — a blank page that reads as a broken app. Keep a terminal splat outside\n" +
      "  the workspace element.",
  );
  process.exit(1);
}

console.log("✓ workspace not-found route is nested under /w/:slug and slug-derived");