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
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Note that you cannot use the macros (`#include_aztec_version` and `#include_code
The way docs builds work is the following:

- Netlify builds this directory (`netlify.toml`). A push to `next` that changes `docs/` publishes docs.aztec.network about 15 minutes later; a push that changes nothing under `docs/` publishes nothing. A pull request that changes `docs/` gets a deploy preview. See [Publishing](#publishing).
- Netlify hosts and serves the site: the domain, the `netlify.toml` redirects, the `subscribe` function and the markdown edge function.
- Netlify hosts and serves the site: the domain, the `netlify.toml` redirects, the `subscribe` function, the markdown edge function and the migration notes range edge function (`migration_notes.md?from=&to=`, read from the `migration_notes.json` index the build writes).
- [The main CI workflow](../.github/workflows/ci3.yml) runs the docs reference and sync checks on every pull request. The docs build and tests (`make docs docs-tests`) run in full CI, for example with the `ci-full` label.

The preprocessing macros use environment variables to determine version numbers. For nightly builds, set `NIGHTLY_TAG` (or `COMMIT_TAG` for backwards compatibility):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ These resources help you understand Aztec concepts, read docs, or provide additi
- [awesome-noir](https://github.com/noir-lang/awesome-noir) - community Noir resources
- [awesome-aztec](https://github.com/AztecProtocol/awesome-aztec) - community Aztec resources
- **Copy docs into context** - The "Copy page" button at the top of each page copies the page as markdown. Its menu also opens the page in ChatGPT or Claude so you can ask questions about it, and links to the markdown version of the page.
- **Migration notes for an upgrade** - Append `?from=[YOUR_VERSION]&to=[TARGET_VERSION]` to the migration notes markdown URL (for example `https://docs.aztec.network/developers/docs/resources/migration_notes.md?from=5.2.0&to=6.0.0`) to get only the notes an upgrade crosses: every version after `from`, up to and including `to`, oldest first. Leave out `to` to go up to the newest release these docs cover. The migration notes page has a panel that copies the same range.
- **Context7** - [Context7](https://context7.com) is a generic MCP server. The Aztec index at [context7.com/aztecprotocol/aztec-packages](https://context7.com/aztecprotocol/aztec-packages) is scoped by [`context7.json`](https://github.com/AztecProtocol/aztec-packages/blob/next/context7.json) to the developer and operator docs of the version the networks run, and excludes the repository's build and CI infrastructure. Note that it may be less current than the MCP servers above.

## Aztec and Noir tool reference
Expand Down
1 change: 1 addition & 0 deletions docs/docs-developers/ai_tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ These resources help you understand Aztec concepts, read docs, or provide additi
- [awesome-noir](https://github.com/noir-lang/awesome-noir) - community Noir resources
- [awesome-aztec](https://github.com/AztecProtocol/awesome-aztec) - community Aztec resources
- **Copy docs into context** - The "Copy page" button at the top of each page copies the page as markdown. Its menu also opens the page in ChatGPT or Claude so you can ask questions about it, and links to the markdown version of the page.
- **Migration notes for an upgrade** - Append `?from=[YOUR_VERSION]&to=[TARGET_VERSION]` to the migration notes markdown URL (for example `https://docs.aztec.network/developers/docs/resources/migration_notes.md?from=5.2.0&to=6.0.0`) to get only the notes an upgrade crosses: every version after `from`, up to and including `to`, oldest first. Leave out `to` to go up to the newest release these docs cover. The migration notes page has a panel that copies the same range.
- **Context7** - [Context7](https://context7.com) is a generic MCP server. The Aztec index at [context7.com/aztecprotocol/aztec-packages](https://context7.com/aztecprotocol/aztec-packages) is scoped by [`context7.json`](https://github.com/AztecProtocol/aztec-packages/blob/next/context7.json) to the developer and operator docs of the version the networks run, and excludes the repository's build and CI infrastructure. Note that it may be less current than the MCP servers above.

## Aztec and Noir tool reference
Expand Down
1 change: 1 addition & 0 deletions docs/lines/v6/docs-developers/ai_tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ These resources help you understand Aztec concepts, read docs, or provide additi
- [awesome-noir](https://github.com/noir-lang/awesome-noir) - community Noir resources
- [awesome-aztec](https://github.com/AztecProtocol/awesome-aztec) - community Aztec resources
- **Copy docs into context** - The "Copy page" button at the top of each page copies the page as markdown. Its menu also opens the page in ChatGPT or Claude so you can ask questions about it, and links to the markdown version of the page.
- **Migration notes for an upgrade** - Append `?from=[YOUR_VERSION]&to=[TARGET_VERSION]` to the migration notes markdown URL (for example `https://docs.aztec.network/developers/docs/resources/migration_notes.md?from=5.2.0&to=6.0.0`) to get only the notes an upgrade crosses: every version after `from`, up to and including `to`, oldest first. Leave out `to` to go up to the newest release these docs cover. The migration notes page has a panel that copies the same range.
- **Context7** - [Context7](https://context7.com) is a generic MCP server. The Aztec index at [context7.com/aztecprotocol/aztec-packages](https://context7.com/aztecprotocol/aztec-packages) is scoped by [`context7.json`](https://github.com/AztecProtocol/aztec-packages/blob/next/context7.json) to the developer and operator docs of the version the networks run, and excludes the repository's build and CI infrastructure. Note that it may be less current than the MCP servers above.

## Aztec and Noir tool reference
Expand Down
47 changes: 47 additions & 0 deletions docs/netlify/edge-functions/migration-notes-range.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Serves `…/migration_notes.md?from=X&to=Y` from the section index the build writes beside each
// twin (scripts/index_migration_notes.mjs); with neither parameter, or with no index, the static
// twin (the full notes, with no "Included versions" header) is served instead.
import { queryProblem, renderRange, selectRange } from "../../src/migration_ranges.mjs";

const USAGE =
"Use ?from=<your version>&to=<target version>, for example ?from=5.2.0&to=6.0.0. Versions are semver, " +
"with or without a leading v. to defaults to the newest release on the page; to=unreleased adds the notes not yet released.";

function reply(status, body, type) {
return new Response(body, {
status,
headers: { "content-type": `${type}; charset=utf-8`, "x-content-type-options": "nosniff" },
});
}

const badRange = (problem, sections) =>
reply(
400,
[`Bad range: ${problem}.`, USAGE, ...(sections ? [`Versions on this page, newest first: ${sections.map((s) => s.version).join(", ")}`] : [])].join("\n") + "\n",
"text/plain",
);

export default async (request) => {
const url = new URL(request.url);
const query = { from: url.searchParams.get("from"), to: url.searchParams.get("to") };
if (query.from === null && query.to === null) return;
const problem = queryProblem(query);
if (problem) return badRange(problem);

// Assigned, never resolved as a reference: a path that starts with `//` would name another host.
const indexUrl = new URL(url.origin);
indexUrl.pathname = url.pathname.replace(/\.md$/, ".json");
const index = await fetch(indexUrl);
if (!index.ok) return;
const { sections } = await index.json();
let selection;
try {
selection = selectRange(sections, query);
} catch (error) {
if (error instanceof RangeError) return badRange(error.message, sections);
throw error;
}
return reply(200, renderRange(selection, url.origin + url.pathname.replace(/\.md$/, "")), "text/markdown");
};

export const config = { path: "/*/migration_notes.md" };
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"scripts": {
"build": "node scripts/aztec_node.mts fetch && yarn clean && yarn preprocess && yarn spellcheck && yarn preprocess:move && yarn validate:redirects && yarn validate:api-ref-links && yarn docusaurus build && node scripts/aztec_nr_api_markdown.mjs build && node scripts/split_typescript_api.mjs build && node scripts/append_api_docs_to_llms.js && node scripts/inject_md_directive.js && node scripts/inject_llms_directive.js && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/check_agent_readiness.mts build && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/check_search_index.mts build",
"build": "node scripts/aztec_node.mts fetch && yarn clean && yarn preprocess && yarn spellcheck && yarn preprocess:move && yarn validate:redirects && yarn validate:api-ref-links && yarn docusaurus build && node scripts/aztec_nr_api_markdown.mjs build && node scripts/split_typescript_api.mjs build && node scripts/append_api_docs_to_llms.js && node scripts/inject_md_directive.js && node scripts/index_migration_notes.mjs && node scripts/inject_llms_directive.js && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/check_agent_readiness.mts build && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/check_search_index.mts build",
"validate:redirects": "./scripts/validate_redirect_targets.sh",
"validate:api-ref-links": "./scripts/validate_api_ref_links.sh",
"check:orphaned-urls": "./scripts/check_orphaned_urls.sh",
Expand Down
160 changes: 160 additions & 0 deletions docs/scripts/__tests__/migration_ranges.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { type TestContext, test } from "node:test";
import { compareVersions, parseVersion, renderRange, selectRange } from "../../src/migration_ranges.mjs";
import serveRange from "../../netlify/edge-functions/migration-notes-range.mjs";
import { indexMigrationNotes, splitSections } from "../index_migration_notes.mjs";

const docs = path.join(import.meta.dirname, "../..");
const page = "docs/resources/migration_notes.md";
const copies = {
main: `docs-developers/${page}`,
"v6 line": `lines/v6/docs-developers/${page}`,
"v6.0.0 snapshot": `developer_versioned_docs/version-v6.0.0/${page}`,
};
const read = (file: string) => fs.readFileSync(path.join(docs, file), "utf8");
const main = splitSections(read(copies.main));
const versions = (sections: { version: string }[]) => sections.map((s) => s.version);

test("every copy splits into strictly descending version sections that rebuild the page", () => {
for (const [name, file] of Object.entries(copies)) {
const text = read(file);
const { preamble, sections } = splitSections(text);
assert.equal(preamble + sections.map((s) => s.markdown).join(""), text, name);
assert.ok(sections.length >= 60, `${name}: ${sections.length} sections`);
assert.equal(versions(sections).at(-1), "0.17.0", name);
}
assert.equal(versions(main.sections)[0], "TBD");
assert.equal(versions(splitSections(read(copies["v6.0.0 snapshot"])).sections)[0], "6.0.0");
});

test("headings that are not versions stay inside the version above them", () => {
const holder = (heading: string) => main.sections.find((s) => s.markdown.includes(`\n${heading}\n`))?.version;
assert.equal(holder("## [aztec.js] Removal of barrel export"), "3.0.0-devnet.4");
assert.equal(holder("# [Aztec sandbox] TXE deployment changes"), "0.47.0");
assert.equal(holder("## Public execution migrated to the Aztec Virtual Machine"), "0.42.0");
const fenced = splitSections("intro\n## 2.0.0\n```md\n## 9.9.9\n```\n## \\[Aztec.nr] x\n## 1.0.0\n");
assert.deepEqual(versions(fenced.sections), ["2.0.0", "1.0.0"]);
});

test("a page out of order, or with no version heading, does not split", () => {
assert.throws(() => splitSections("## 1.0.0\n## 2.0.0\n"), /2\.0\.0 .*after 1\.0\.0/);
assert.throws(() => splitSections("## 1.0.0\n## 1.0\n"), /after/);
assert.throws(() => splitSections("# Notes\n## [Aztec.nr] x\n"), /no version heading/);
});

test("versions order by semver precedence", () => {
const lt = (a: string, b: string) => assert.ok(compareVersions(parseVersion(a)!, parseVersion(b)!) < 0, `${a} < ${b}`);
lt("4.2.0-aztecnr-rc.2", "4.2.0");
lt("3.0.0-devnet.4", "3.0.0-devnet.20251212");
lt("4.0.0-devnet.2-patch.0", "4.1.0-rc.2");
lt("6.0.0", "TBD");
lt("9007199254740992.0.0", "9007199254740993.0.0");
lt("1.0.0-9007199254740992", "1.0.0-9007199254740993");
assert.equal(compareVersions(parseVersion("0.66")!, parseVersion("v0.66.0")!), 0);
for (const bad of ["", "6", "6.0.0.0", "01.2.3", "6.0.0-01", "6.0.0+build", "x6.0.0", "6.0.0-", "6.0.0-rc..1", "6." + "1".repeat(63)]) {
assert.equal(parseVersion(bad), null, bad);
}
});

test("a range holds the versions after from up to to, oldest first", () => {
const snapshot = splitSections(read(copies["v6.0.0 snapshot"])).sections;
const cases: [typeof main.sections, Record<string, string>, string[]][] = [
[main.sections, { from: "6.0.0-rc.1", to: "6.0.0" }, ["6.0.0-rc.3", "6.0.0"]],
[main.sections, { from: "6.0.0-rc.2", to: "v6.0.0" }, ["6.0.0-rc.3", "6.0.0"]],
[main.sections, { from: "5.1.0" }, ["5.2.0", "6.0.0-rc.1", "6.0.0-rc.3", "6.0.0"]],
[main.sections, { from: "5.1.0", to: "" }, ["5.2.0", "6.0.0-rc.1", "6.0.0-rc.3", "6.0.0"]],
[main.sections, { from: "6.0.0", to: "unreleased" }, ["TBD"]],
[snapshot, { from: "6.0.0-rc.3", to: "unreleased" }, ["6.0.0"]],
[main.sections, { from: "6.0.0" }, []],
[main.sections, { from: "0.1.0", to: "0.2.0" }, []],
[main.sections, { from: "v0.65", to: "0.67.0" }, ["0.66", "0.67.0"]],
];
for (const [sections, query, expected] of cases) {
assert.deepEqual(versions(selectRange(sections, query).included), expected, JSON.stringify(query));
}
});

test("a bad range names its problem", () => {
const fails = (query: Record<string, string>, message: RegExp) =>
assert.throws(() => selectRange(main.sections, query), (e: Error) => e instanceof RangeError && message.test(e.message));
fails({ to: "6.0.0" }, /from is required/);
fails({ from: "six" }, /from is not a version/);
fails({ from: "5.0.0", to: "latest" }, /to is not a version/);
fails({ from: "7.0.0" }, /from \(7\.0\.0\) is newer than to \(6\.0\.0\)/);
});

test("the rendered range names its versions above the sections", () => {
const selection = selectRange(main.sections, { from: "6.0.0-rc.1", to: "6.0.0" });
const text = renderRange(selection, "https://docs.example/developers/docs/resources/migration_notes");
const [title, source, included] = text.split("\n");
assert.equal(title, "# Aztec migration notes: 6.0.0-rc.1 to 6.0.0");
assert.equal(source, "Source: https://docs.example/developers/docs/resources/migration_notes");
assert.equal(included, "Included versions: 6.0.0-rc.3, 6.0.0");
assert.ok(text.indexOf("\n## 6.0.0-rc.3\n") < text.indexOf("\n## 6.0.0\n"));
const none = renderRange(selectRange(main.sections, { from: "6.0.0" }), "https://x");
assert.match(none, /^Included versions: none$/m);
});

test("the index step writes a JSON index beside every migration notes twin", () => {
const build = fs.mkdtempSync(path.join(os.tmpdir(), "migration-index-"));
try {
assert.throws(() => indexMigrationNotes(build), /no migration_notes\.md/);
const dir = path.join(build, "developers/docs/resources");
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, "migration_notes.md"), "> index\n\n# Migration notes\n\n## 2.0.0\nb\n## 1.0.0\na\n");
assert.deepEqual(indexMigrationNotes(build), ["developers/docs/resources/migration_notes.json"]);
const index = JSON.parse(fs.readFileSync(path.join(dir, "migration_notes.json"), "utf8"));
assert.deepEqual(index, { sections: [{ version: "2.0.0", markdown: "## 2.0.0\nb\n" }, { version: "1.0.0", markdown: "## 1.0.0\na\n" }] });
fs.writeFileSync(path.join(dir, "migration_notes.md"), "# Migration notes\n");
assert.throws(() => indexMigrationNotes(build), /developers\/docs\/resources\/migration_notes\.md: no version heading/);
} finally {
fs.rmSync(build, { recursive: true, force: true });
}
});

test("the edge function serves a range, refuses a bad one, and otherwise steps aside", async (t: TestContext) => {
const page = "https://docs.example/developers/docs/resources/migration_notes";
const fetched: string[] = [];
let index: Response | null = null;
t.mock.method(globalThis, "fetch", async (url: URL) => {
fetched.push(String(url));
return index ?? new Response("Not Found", { status: 404 });
});
const serve = async (query: string, json: unknown = { sections: main.sections }) => {
fetched.length = 0;
index = json === null ? null : new Response(JSON.stringify(json));
return serveRange(new Request(`${page}.md${query}`));
};

assert.equal(await serve(""), undefined);
assert.deepEqual(fetched, []);

const ok = (await serve("?from=6.0.0-rc.1&to=6.0.0"))!;
assert.equal(ok.status, 200);
assert.equal(ok.headers.get("content-type"), "text/markdown; charset=utf-8");
assert.equal(ok.headers.get("x-content-type-options"), "nosniff");
assert.deepEqual(fetched, [`${page}.json`]);
assert.deepEqual((await ok.text()).split("\n").slice(0, 3), [
"# Aztec migration notes: 6.0.0-rc.1 to 6.0.0",
`Source: ${page}`,
"Included versions: 6.0.0-rc.3, 6.0.0",
]);

for (const query of ["?to=6.0.0", "?from=six", `?from=${"1".repeat(70)}`]) {
const bad = (await serve(query))!;
assert.equal(bad.status, 400, query);
assert.deepEqual(fetched, [], query);
}
const reversed = (await serve("?from=7.0.0"))!;
assert.equal(reversed.status, 400);
assert.match(await reversed.text(), /from \(7\.0\.0\) is newer than to \(6\.0\.0\)[\s\S]*TBD, 6\.0\.0, 6\.0\.0-rc\.3/);

assert.equal(await serve("?from=5.2.0", null), undefined);

fetched.length = 0;
await serveRange(new Request("https://docs.example//elsewhere.example/x/migration_notes.md?from=5.2.0"));
assert.deepEqual(fetched.map((u) => new URL(u).origin), ["https://docs.example"]);
});
Loading
Loading