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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ jobs:
name: dist
path: dist

- name: Upload Nimbus link data
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: nimbus
path: .nimbus
include-hidden-files: true
if-no-files-found: error

- name: Upload build log
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
Expand Down Expand Up @@ -213,6 +221,30 @@ jobs:
- name: Tests (Workers)
run: pnpm run test:postbuild

# Lint steps use `!cancelled()` so a lint failure doesn't skip the
# others. The report step is the gate: it prints the findings and fails.
- name: Download Nimbus link data
if: ${{ !cancelled() }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: nimbus
path: .nimbus

- name: Run Nimbus lint
id: nimbus-lint
if: ${{ !cancelled() }}
run: |
set +e
pnpm exec nimbus-docs lint --format json > "$RUNNER_TEMP/nimbus-lint.json"
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
exit 0

- name: Check internal links
if: ${{ !cancelled() }}
env:
LINT_EXIT_CODE: ${{ steps.nimbus-lint.outputs.exit_code }}
run: pnpm exec tsx bin/nimbus-lint-report.ts "$RUNNER_TEMP/nimbus-lint.json"

notify:
name: Notify
needs: [pre-build, post-build]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ tools/relevant_changed_files.txt
.nimbus/coordinates.json
.nimbus/lint.json
.nimbus/routes.json
.nimbus/page-collections.json
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ For full component documentation including props, examples, and mandatory usage

```bash
pnpm run check # Type-check (validates frontmatter schemas + Astro types)
pnpm run build # Full build (validates MDX parsing, image paths, internal links) — LOCAL ONLY, skip in CI
pnpm run build # Full build (validates MDX parsing, image paths) — LOCAL ONLY, skip in CI
pnpm run lint:nimbus # Nimbus lint, including internal links (needs a prior build: reads .nimbus/routes.json)
```

### Minimum validation for code changes (.ts/.tsx/.astro/.js)
Expand Down Expand Up @@ -140,7 +141,8 @@ pnpm run format:core:check # Prettier formatting check
pnpm run check # Astro + Worker type checking
pnpm run lint # ESLint
pnpm run format:core:check # Prettier formatting check
pnpm run build # Full build with link checking
pnpm run build # Full build
pnpm run lint:nimbus # Internal link + frontmatter + image-ref lint (run after build)
pnpm run test # All test suites
pnpm exec tsm bin/validate-redirects.ts # Only if public/__redirects was modified
```
Expand Down Expand Up @@ -170,6 +172,7 @@ The CI workflow (`.github/workflows/ci.yml`) runs on PRs to `production` and che
3. ESLint (reported inline on PR via reviewdog)
4. `pnpm run format:core:check` (Prettier formatting)
5. `pnpm run build` (full build + MDX parsing + image path validation)
- Post Build runs `nimbus-docs lint` against the build's `.nimbus/` data (internal links, redirects)
6. Redirect validation (`bin/validate-redirects.ts`)
7. `pnpm run test` (all Vitest suites)

Expand Down
23 changes: 22 additions & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,29 @@ const integrations = [
"error",
{ aliases: { "~/assets/": "src/assets/" } },
],
"nimbus/internal-link": "error",
// Paths below are served by the Worker or another deployment, so the
// build's route list never contains them.
"nimbus/internal-link": [
"error",
{
ignore: [
"/api/**",
"/openapi.json",
"/.well-known/**",
"**/llms-full.txt",
"**/og.png",
"**/index.md",
// Model 404s are handled directly via custom worker/index.ts logic.
"/workers-ai/models/**",
"/ai/models/**",
],
},
],
// Reuses internal-link's `ignore` list.
"nimbus/redirected-link": "error",
},
// The Worker reads redirects from this file, not from `_redirects`.
redirectsFile: "public/__redirects",
}),
];

Expand Down
108 changes: 108 additions & 0 deletions bin/nimbus-lint-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Reports `nimbus-docs lint --format json` output and gates the CI step:
* - findings in the log, grouped by rule
* - `::error` annotations (GitHub shows at most 10 per step)
* - per-rule counts in the GitHub step summary
*
* Exits 1 if lint failed or reported errors.
*
* Usage: LINT_EXIT_CODE=<code> tsx bin/nimbus-lint-report.ts <lint.json>
*/
import { appendFileSync, readFileSync } from "node:fs";

interface Diagnostic {
code: string;
severity: "error" | "warn" | "warning" | "info";
message: string;
file: string;
line: number;
column: number;
endLine?: number;
endColumn?: number;
}

interface LintOutput {
summary: { errors: number; warnings: number; total: number; files: number };
diagnostics: Diagnostic[];
}

const [inputPath] = process.argv.slice(2);
if (!inputPath) {
console.error("Usage: tsx bin/nimbus-lint-report.ts <lint.json>");
process.exit(2);
}

const lintExitCode = process.env.LINT_EXIT_CODE ?? "0";

// Workflow command escaping: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions
const escapeData = (s: string) =>
s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
const escapeProperty = (s: string) =>
escapeData(s).replace(/:/g, "%3A").replace(/,/g, "%2C");

const raw = readFileSync(inputPath, "utf-8");
let output: LintOutput;
try {
output = JSON.parse(raw);
} catch {
// Lint crashed before producing JSON, so show whatever it wrote.
console.log(raw);
console.log(
`::error::nimbus-docs lint exited with code ${lintExitCode} and did not produce valid JSON.`,
);
process.exit(1);
}

const { diagnostics, summary } = output;

const byRule = new Map<string, Diagnostic[]>();
for (const d of diagnostics) {
byRule.set(d.code, [...(byRule.get(d.code) ?? []), d]);
}

for (const [code, items] of byRule) {
console.log(`::group::${code} (${items.length})`);
for (const d of items) {
console.log(`${d.file}:${d.line}:${d.column} ${d.severity} ${d.message}`);
}
console.log("::endgroup::");
}

// GitHub shows only the first 10 annotations per step; the log has the rest.
for (const d of diagnostics) {
const level = d.severity === "error" ? "error" : "warning";
console.log(
`::${level} file=${escapeProperty(d.file)},line=${d.line},col=${d.column},title=${escapeProperty(d.code)}::${escapeData(d.message)}`,
);
}

console.log(
`\n${summary.errors} errors, ${summary.warnings} warnings in ${summary.files} files`,
);

const summaryPath = process.env.GITHUB_STEP_SUMMARY;
if (summaryPath) {
const rows = [...byRule.entries()]
.sort((a, b) => b[1].length - a[1].length)
.map(([code, items]) => `| \`${code}\` | ${items.length} |`);
appendFileSync(
summaryPath,
[
"### Nimbus lint",
"",
`${summary.errors} errors, ${summary.warnings} warnings in ${summary.files} files`,
"",
"| Rule | Findings |",
"| --- | --- |",
...(rows.length ? rows : ["| none | 0 |"]),
"",
].join("\n"),
);
}

if (lintExitCode !== "0" || summary.errors > 0) {
console.log(
`::error::nimbus-docs lint found ${summary.errors} errors (exit code ${lintExitCode}). See the grouped log and job summary.`,
);
process.exit(1);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"flue:replay:spam": "tsx .flue/bin/replay-spam.ts",
"flue:replay:comment-spam": "tsx .flue/bin/replay-comment-spam.ts",
"lint": "eslint",
"lint:nimbus": "nimbus-docs lint",
"generate:config-reference": "node bin/generate-cf-config-reference.mjs",
"prepare": "husky",
"prebuild:incremental": "pnpm run fetch:assets && pnpm run generate:config-reference",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { DirectoryListing } from "~/components";

Standard traffic policies match on network-layer attributes like IP addresses and port ranges. Application-aware policies go further — they identify traffic by the application generating it, so you can make routing and security decisions based on what the traffic is, not just where it is going.

Cloudflare One Appliance (formerly Magic WAN Connector) classifies traffic using the same application categories used across Cloudflare's [Secure Web Gateway](/cloudflare-one/traffic-policies/). This means routing decisions on the Appliance and security policies in Gateway use the same application definitions.
Cloudflare One Appliance (formerly Magic WAN Connector) classifies traffic using the same application categories used across Cloudflare's [Secure Web Gateway](/cloudflare-one/traffic-controls/). This means routing decisions on the Appliance and security policies in Gateway use the same application definitions.

For the full list of recognized applications and categories, refer to [Applications and app types](/cloudflare-one/traffic-policies/application-app-types/).
For the full list of recognized applications and categories, refer to [Applications and app types](/cloudflare-one/traffic-controls/application-app-types/).

With application-aware policies, you can:

Expand Down
Loading