Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI
Comment thread
next-devin marked this conversation as resolved.

# The same sequence a clean checkout runs locally:
# npm ci -> npm run build (generates references, stats, capability map, previews,
# then next build) -> npm run check (post-build agent-surface assertions,
# frontmatter, link validation against the generated tree).
# `validate-links` alone regenerates first; here the build already did.

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
build-and-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: '22'
cache: npm

- run: npm ci

- run: npm run build

- run: npm run check
30 changes: 30 additions & 0 deletions .github/workflows/live-surfaces.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Live agent surfaces

# Checks the deployed sites, not the branch: sitemap, robots, llms.txt links,
# capability map, bundles, 404 recovery, search index budget, and the
# deterministic half of the prospect-agent smoke set. Runs on a schedule and
# by hand; a failure here means production drifted, not that a PR is wrong.

on:
schedule:
- cron: '17 3 * * 1' # Mondays 03:17 UTC
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
live:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: '22'

- run: node scripts/check-live-surfaces.mjs
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ out/
/content/docs/storefront/graphql/mutations/
/content/docs/campaigns/api/*/
/lib/generated/
# Generated capability page (regenerated by npm run generate; source is content/capabilities.yaml)
/content/docs/capabilities/index.mdx

# Misc
.DS_Store
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ The sibling site, https://docs.nextcommerce.com, is the merchant and operator do

## Where to start

- https://developers.nextcommerce.com/llms.txt is the page index for this site, with a one-line description per page and absolute URLs.
- https://developers.nextcommerce.com/llms-full.txt is the full corpus in one file. It is large (about 1.5 MB); fetch it only when you need broad coverage rather than a specific page.
- https://developers.nextcommerce.com/llms.txt is the page index for this site, with a one-line description per page and absolute URLs. It also lists the domain bundles.
- https://developers.nextcommerce.com/capabilities.json is the platform capability map: one record per capability with a stable id, the merchant and developer pages that document it, its Admin API operations, webhook events, skills, status, and the date its links were last verified. The readable form is https://developers.nextcommerce.com/docs/capabilities. Pages on both sites declare their ids in a `capability_ids` frontmatter field.
- Domain bundles at https://developers.nextcommerce.com/llms/<bundle>.txt (`platform`, `admin-api`, `payments`, `campaigns`, `storefront`, `apps-webhooks`) are plain Markdown: the capability records for one domain followed by the full text of the developer pages they cite. Fetch the bundle for your question before the full corpus.
- https://developers.nextcommerce.com/llms-full.txt is the full corpus in one file. It is large (about 1.5 MB) and includes 500+ generated reference pages; fetch it only when a bundle or a page URL is not enough.
- Raw OpenAPI specs, which are the authority for operations, parameters, and fields:
- https://developers.nextcommerce.com/api/admin/2024-04-01.yaml (stable)
- https://developers.nextcommerce.com/api/admin/unstable.yaml
Expand Down
14 changes: 14 additions & 0 deletions app/capabilities.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { capabilityMap } from '@/lib/capabilities';

export const revalidate = false;

/**
* The platform capability map, generated at build time by
* scripts/generate-capability-map.mjs from content/capabilities.yaml.
* Stable URL: https://developers.nextcommerce.com/capabilities.json
*/
export function GET() {
return new Response(JSON.stringify(capabilityMap, null, 2) + '\n', {
headers: { 'Content-Type': 'application/json; charset=utf-8' },
});
}
2 changes: 2 additions & 0 deletions app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getMDXComponents } from '@/components/mdx';
import { createRelativeLink } from 'fumadocs-ui/mdx';
import { VersionSelector } from '@/components/version-selector';
import { AutoExpandBody } from '@/components/auto-expand-body';
import { CapabilityLinks } from '@/components/capability-links';
import type { Metadata } from 'next';

export default async function Page(props: {
Expand Down Expand Up @@ -41,6 +42,7 @@ export default async function Page(props: {
a: createRelativeLink(source, page),
})}
/>
<CapabilityLinks pageUrl={page.url} declaredIds={page.data.capability_ids} />
</DocsBody>
</DocsPage>
);
Expand Down
7 changes: 6 additions & 1 deletion app/docs/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { baseOptions } from '@/lib/layout.shared';
import { source } from '@/lib/source';
import { AlgoliaDocSearch, AlgoliaDocSearchMobile } from '@/components/search';
import type { ReactNode } from 'react';
import { ChevronsLeftRightEllipsis, Megaphone, ShoppingBag, Puzzle, Webhook, Sparkles, FlaskConical } from 'lucide-react';
import { ChevronsLeftRightEllipsis, Megaphone, ShoppingBag, Puzzle, Webhook, Sparkles, FlaskConical, Map } from 'lucide-react';
import type { SidebarTab } from 'fumadocs-ui/utils/get-sidebar-tabs';

const sectionMeta: Record<string, { icon: ReactNode; description: string; color: string }> = {
Expand Down Expand Up @@ -42,6 +42,11 @@ const sectionMeta: Record<string, { icon: ReactNode; description: string; color:
description: 'Test cards & safe QA',
color: 'bg-cyan-500/15 text-cyan-500',
},
'/docs/capabilities': {
icon: <Map size={16} />,
description: 'Platform capability map',
color: 'bg-slate-500/15 text-slate-500',
},
};

export default function Layout({ children }: { children: ReactNode }) {
Expand Down
11 changes: 10 additions & 1 deletion app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { source } from '@/lib/source';
import { capabilityMap } from '@/lib/capabilities';

export const revalidate = false;

Expand Down Expand Up @@ -65,7 +66,8 @@ function header(): string {
`- [Merchant docs](${MERCHANT_SITE}): guides for store operators, on the sibling site`,
`- [Merchant docs index](${MERCHANT_SITE}/llms.txt)`,
`- [Platform and API changelog](${MERCHANT_SITE}/changelog): the developer portal has no changelog of its own`,
`- [Full corpus](${SITE}/llms-full.txt): every page in one file (large, about 1.5 MB)`,
`- [Capability map (JSON)](${SITE}/capabilities.json): one record per platform capability linking merchant guides, developer guides, Admin API operations, webhook events, and skills under a stable id`,
`- [Capability map (readable)](${SITE}/docs/capabilities)`,
`- [Admin API spec, 2024-04-01](${SITE}/api/admin/2024-04-01.yaml): stable, raw OpenAPI`,
`- [Admin API spec, unstable](${SITE}/api/admin/unstable.yaml): raw OpenAPI`,
`- [Admin API spec, 2023-02-10](${SITE}/api/admin/2023-02-10.yaml): deprecated, raw OpenAPI`,
Expand All @@ -75,6 +77,13 @@ function header(): string {
`- [Testing](${SITE}/docs/testing)`,
`- [Agent guide (AGENTS.md)](https://github.com/NextCommerceCo/developer-docs/blob/main/AGENTS.md): navigation and evidence rules for agents reading this site`,
'',
'## Domain bundles',
'',
'Fetch the bundle for your question before the full corpus. Each is plain Markdown: the capability records for one domain followed by the full text of the developer pages they cite.',
'',
...capabilityMap.bundles.map((b) => `- [${linkText(b.title)}](${b.url}): ${oneLine(b.intro)}`),
`- [Full corpus](${SITE}/llms-full.txt): every page in one file, including 500+ generated reference pages (large, about 1.5 MB); use a bundle or a page URL instead unless you need everything`,
'',
'## Legacy identifiers',
'',
'Next Commerce was formerly 29 Next. Hostnames like `{store}.29next.store`, `accounts.29next.com`, and headers like `X-29next-API-Version` and `X-29Next-Signature` are current, valid technical identifiers and must be used exactly as written.',
Expand Down
5 changes: 5 additions & 0 deletions app/llms/admin-api.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('admin-api');
5 changes: 5 additions & 0 deletions app/llms/apps-webhooks.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('apps-webhooks');
5 changes: 5 additions & 0 deletions app/llms/campaigns.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('campaigns');
5 changes: 5 additions & 0 deletions app/llms/payments.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('payments');
5 changes: 5 additions & 0 deletions app/llms/platform.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('platform');
5 changes: 5 additions & 0 deletions app/llms/storefront.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bundleResponse } from '@/lib/bundles';

export const revalidate = false;

export const GET = bundleResponse('storefront');
1 change: 1 addition & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function NotFound() {
<li><Link href="/docs">Browse all docs</Link></li>
<li><Link href="/docs/admin-api">Admin API</Link></li>
<li><Link href="/docs/webhooks">Webhooks</Link></li>
<li><Link href="/docs/capabilities">Capability map</Link></li>
<li><a href="https://docs.nextcommerce.com">Merchant docs</a></li>
<li><a href="https://docs.nextcommerce.com/changelog">Changelog</a></li>
<li><Link href="/llms.txt">Agent index (llms.txt)</Link></li>
Expand Down
55 changes: 55 additions & 0 deletions components/capability-links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { capabilitiesForPage, getCapability, MERCHANT_SITE, type Capability } from '@/lib/capabilities';

/**
* Reciprocal links from a developer page back to the merchant guides for the
* same capability. Driven entirely by the capability map: a page is linked when
* the map's developer_docs cite it or its frontmatter declares capability_ids.
*/
export function CapabilityLinks({ pageUrl, declaredIds }: { pageUrl: string; declaredIds?: string[] }) {
const byId = new Map<string, Capability>();
for (const c of capabilitiesForPage(pageUrl)) byId.set(c.id, c);
for (const id of declaredIds ?? []) {
const c = getCapability(id);
if (c) byId.set(c.id, c);
}
const capabilities = [...byId.values()];
if (capabilities.length === 0) return null;

// Several capabilities on one page can cite the same merchant guide; list it once.
const merchantLinks = [...new Set(capabilities.flatMap((c) => c.operator_docs))].map((url) => ({ url }));

return (
<aside
aria-label="Related merchant guides"
className="mt-10 rounded-lg border bg-fd-card p-4 text-sm text-fd-card-foreground"
>
<p className="font-medium">
Capabilit{capabilities.length === 1 ? 'y' : 'ies'}:{' '}
{capabilities.map((c, i) => (
<span key={c.id}>
{i > 0 && ', '}
<a href={`/docs/capabilities#${c.id}`} className="underline underline-offset-4">
{c.title}
</a>
</span>
))}
</p>
{merchantLinks.length > 0 && (
<>
<p className="mt-2 text-fd-muted-foreground">
Merchant and operator guides for the same capability on docs.nextcommerce.com:
</p>
<ul className="mt-1 list-disc pl-5">
{merchantLinks.map(({ url }) => (
<li key={url}>
<a href={url} className="underline underline-offset-4">
{url.replace(MERCHANT_SITE, '')}
</a>
</li>
))}
</ul>
</>
)}
</aside>
);
}
Loading