# @se-studio/site-check — LLM Reference

Site validation CLI, production SEO audits, and curated smoke tests for SE marketing sites.

## Subpath exports

| Import | Purpose |
|--------|---------|
| `@se-studio/site-check` | CLI: sitemap / llms.txt / markdown export |
| `@se-studio/site-check/smoke-test` | HTTP smoke + optional CMS integrity orchestration |
| `@se-studio/site-check/cms-integrity` | Article-link integrity (Node-safe; no Next barrel) |
| `@se-studio/site-check/production-audit` | Live production SEO audit |
| `@se-studio/site-check/screaming-frog` | Screaming Frog export analysis |

## Smoke test (HTTP)

Apps commit `smoke.cases.json`. Requires **^2.0.0** for static smoke; **^2.6.1** when `cmsIntegrity` is enabled; **^2.7.0** for build route policy; **^2.7.2** for `expectHtmlStatus` (404 probes); **^2.9.0** for `discovery` endpoint checks (`llms.txt`, `markdown-index.txt`, `cms.txt`, `site-info.md`).

```json
"discovery": {
  "llmsTxt": true,
  "markdownIndexTxt": true,
  "cmsTxt": true,
  "siteInfoMd": true,
  "urlOrigin": "auto",
  "canonicalBaseUrl": "https://www.example.com"
}
```

Set any endpoint flag to `false` to skip it (e.g. PointMe before CloudFront). `urlOrigin` defaults to `auto` — **content** may list a single consistent canonical HTTPS origin in `llms.txt` / `markdown-index.txt` when live smoke hits a `*.vercel.app` deployment URL (production pre-alias deploys). Spot-checks of Key pages `.md` links are rewritten onto the smoke deployment host so Deployment Checks validate the build under test, not still-live CDN/www. Use `deployment` for strict origin matching in the body. `canonicalBaseUrl` is optional; when set, skips inference. CLI: `--strict-discovery` requires all four on `site-check <url>`.

```ts
import {
  runStaticSmokeTest,
  runStaticSmokeTestWithIntegrity,
  formatSmokeTestReport,
  formatCombinedSmokeReport,
  getSmokeTestExitCode,
  getCombinedSmokeExitCode,
  validateBuildRoutesFromBuildOutput,
  formatBuildRoutePolicyReport,
} from '@se-studio/site-check/smoke-test';
```

**SSG guardrails:** commit `route-build-policy.json`; run `validate-build-routes` or `pnpm validate:routes`. Templates in package `templates/`.

**Per-app scripts:**

```json
"smoke-test": "smoke-test-one 3012",
"smoke-test:run": "tsx scripts/smoke-test-run.ts",
"smoke-test:live": "smoke-test-live"
```

**`smoke-test-run.ts` with optional integrity:**

```ts
const skipIntegrity = (process.env.SMOKE_TEST_SKIP_INTEGRITY ?? '').toLowerCase() === 'true';
const { integrity, http } = await runStaticSmokeTestWithIntegrity('smoke.cases.json', {
  skipIntegrity,
});
```

## CMS article-link integrity (local only)

Opt-in via `cmsIntegrity` in `smoke.cases.json`. Fetches all article links from Contentful once; validates `href` for entries with body content. **Not** used on deployment / live smoke.

```json
{
  "cmsIntegrity": {
    "enabled": true,
    "routing": {
      "articleTypesBasePath": "/resources",
      "tagsBasePath": "/topics",
      "peopleBasePath": "/team",
      "defaultTopic": "other",
      "enablePrimaryTagPartOfSlug": true,
      "topiclessArticleTypeSlugs": ["ebooks"]
    }
  }
}
```

```ts
import { runArticleLinkIntegrityFromSmokeConfig } from '@se-studio/site-check/cms-integrity';
// or from smoke-test barrel: runArticleLinkIntegrityFromSmokeConfig
```

**Deps when enabled:** `@se-studio/contentful-rest-api@^1.10.0`, `@se-studio/core-data-types@^1.5.1`, Contentful env in `.env.local`.

**`pnpm smoke-test`:** `smoke-test-one` runs integrity in parallel with dev server boot; sets `SMOKE_TEST_SKIP_INTEGRITY=true` on the HTTP child run.

## Smoke env vars

| Variable | Purpose |
|----------|---------|
| `SMOKE_TEST_IGNORE=true` | Skip smoke entirely |
| `SMOKE_TEST_SERVER_SCRIPT` | `dev:dev` (default) or `start:dev` |
| `SMOKE_TEST_SKIP_INTEGRITY` | HTTP-only (set by smoke-test-one when integrity parallel) |
| `SMOKE_TEST_VALIDATE_ANALYTICS_CONTEXT=false` | Skip dual AnalyticsProvider chunk scan (default on when `.next/static/chunks` exists) |
| `PREVIEW_SITE_URL` | Preview smoke base URL |
| `DEPLOYMENT_URL` | Live deployment smoke URL |
| `VERCEL_PROTECTION_BYPASS_TOKEN` | Deployment Protection bypass |

### Analytics context bundle check

After a production build (or when `.next` already exists for `smoke-test:deploy-check`), `smoke-test-one` scans `.next/static/chunks` for multiple webpack modules that define `AnalyticsProvider` + React context. **More than one module fails the smoke run** — usually duplicate `@se-studio/core-ui` from pnpm peer isolation in monorepos (provider in a workspace package, consumers in the app).

**Proper fix (allowed):** shared package `peerDependencies` for `@se-studio/core-ui` + app `dependenciesMeta.<shared>.injected: true`.  
**Banned on develop/main:** webpack `resolve.alias` for `@se-studio/core-ui`, pnpm `overrides` for it, `public-hoist-pattern` hacks — run `check-no-context-workarounds`.

### check-no-context-workarounds

```bash
# repo root — wire into root "validate" / CI on develop
pnpm exec check-no-context-workarounds
```

| Env | Purpose |
|-----|---------|
| `ALLOW_CONTEXT_WORKAROUNDS=1` | Feature-branch only escape hatch — **never** set on develop/main |

Fails on: Next webpack aliases for `@se-studio/core-ui` / `@se-studio/ab-testing`, pnpm overrides for those packages, `.npmrc` public-hoist-pattern workarounds.

## Vercel build cache (not smoke)

Set on **Vercel project** env (not app `.env.local`, not `vercel.json`):

| Variable | Value | Purpose |
|----------|-------|---------|
| `VERCEL_FORCE_NO_BUILD_CACHE` | `1` | Skip remote build cache when webpack `WasmHash` fails on Vercel |

Mitigates intermittent `WasmHash._updateWithBuffer` / `Cannot read properties of undefined (reading 'length')` during `next build`. Local builds may still pass.

## Skill

Setup and regeneration: `se-marketing-sites-smoke-test-setup` (`@se-studio/skills`). Example app: `apps/example-empty`.