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
3 changes: 3 additions & 0 deletions .github/workflows/commander-multiplatform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
- '.github/workflows/commander-multiplatform.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
macos-host:
runs-on: macos-26
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/macos-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [ main ]

permissions:
contents: read

concurrency:
group: macos-ci-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -30,7 +33,9 @@ jobs:
bun-version: "latest"

- name: Docs lint
run: node scripts/docs-lint.mjs
run: |
node scripts/docs-lint.mjs
node --test tests/docs-site-toc.test.mjs

- name: Select Xcode 26.6 (if present) or fallback to default
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- "docs/**"
- "scripts/build-docs-site.mjs"
- "scripts/docs-site-assets.mjs"
- "scripts/docs-site-toc.mjs"
- ".github/workflows/pages.yml"
workflow_dispatch:

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/update-homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
description: 'Version to update (e.g., 2.0.1)'
required: true

# Cross-repository dispatch uses only the explicit HOMEBREW_TAP_TOKEN below.
permissions: {}

jobs:
update-homebrew-formula:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
"app:restart": "./scripts/restart-peekaboo.sh",
"app:install-companion": "./scripts/restart-peekaboo.sh --deployment",
"build": "pnpm run build:cli",
"test:safe": "pnpm run test:ambient-state-policy && pnpm run test:guidance && scripts/test-source-provenance.sh && scripts/test-terminal-artifact-env.sh && scripts/test-release-version.sh && scripts/test-read-macho-info-plist.sh && scripts/test-artifact-tree-manifest.sh && scripts/test-controller-source-manifest.sh && scripts/test-atomic-rename-exclusive.sh && scripts/test-terminal-artifact-policy.sh && node scripts/test-terminal-archive-policy.mjs && node scripts/test-terminal-dmg-payload.mjs && scripts/test-terminal-manifest-portability.sh && scripts/test-build-node-runtime-macos.sh && scripts/test-build-playground-artifact.sh && scripts/test-build-terminal-dmg.sh && scripts/test-notarize-terminal-artifact.sh && scripts/test-release-signing-policy.sh && scripts/test-release-package-resolution.sh && scripts/test-release-binary-reuse.sh && scripts/test-codesign-with-retry.sh && scripts/test-sign-release-app.sh && scripts/test-swift-runtime-libraries.sh && scripts/test-create-release-dmg.sh && scripts/test-restart-peekaboo.sh && scripts/test-swiftpm-consumer.sh && node scripts/test-update-appcast-entry.mjs && node --no-warnings scripts/test-chrome-devtools-mcp-contract.mjs && pnpm run test:release-preflight && pnpm run test:background-certification && swift test --package-path Core/PeekabooFoundation && PEEKABOO_INCLUDE_AUTOMATION_TESTS=true PEEKABOO_INCLUDE_AMBIENT_STATE_TESTS=false swift test --package-path Apps/CLI -Xswiftc -DPEEKABOO_SKIP_AUTOMATION --no-parallel",
"test:safe": "pnpm run test:ambient-state-policy && pnpm run test:guidance && pnpm run test:docs-site && scripts/test-source-provenance.sh && scripts/test-terminal-artifact-env.sh && scripts/test-release-version.sh && scripts/test-read-macho-info-plist.sh && scripts/test-artifact-tree-manifest.sh && scripts/test-controller-source-manifest.sh && scripts/test-atomic-rename-exclusive.sh && scripts/test-terminal-artifact-policy.sh && node scripts/test-terminal-archive-policy.mjs && node scripts/test-terminal-dmg-payload.mjs && scripts/test-terminal-manifest-portability.sh && scripts/test-build-node-runtime-macos.sh && scripts/test-build-playground-artifact.sh && scripts/test-build-terminal-dmg.sh && scripts/test-notarize-terminal-artifact.sh && scripts/test-release-signing-policy.sh && scripts/test-release-package-resolution.sh && scripts/test-release-binary-reuse.sh && scripts/test-codesign-with-retry.sh && scripts/test-sign-release-app.sh && scripts/test-swift-runtime-libraries.sh && scripts/test-create-release-dmg.sh && scripts/test-restart-peekaboo.sh && scripts/test-swiftpm-consumer.sh && node scripts/test-update-appcast-entry.mjs && node --no-warnings scripts/test-chrome-devtools-mcp-contract.mjs && pnpm run test:release-preflight && pnpm run test:background-certification && swift test --package-path Core/PeekabooFoundation && PEEKABOO_INCLUDE_AUTOMATION_TESTS=true PEEKABOO_INCLUDE_AMBIENT_STATE_TESTS=false swift test --package-path Apps/CLI -Xswiftc -DPEEKABOO_SKIP_AUTOMATION --no-parallel",
"test:ambient-state-policy": "node --test tests/ambient-state-test-policy.test.mjs",
"test:guidance": "node --test tests/background-capability-guidance.test.mjs",
"test:docs-site": "node --test tests/docs-site-toc.test.mjs",
"test:release-preflight": "node --test tests/release-preflight-contract.test.mjs",
"test:background-certification": "scripts/test-background-certification.sh",
"test:automation": "PEEKABOO_INCLUDE_AUTOMATION_TESTS=true swift test --package-path Apps/CLI --no-parallel",
Expand Down
6 changes: 2 additions & 4 deletions scripts/build-docs-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "node:fs";
import path from "node:path";

import { css, faviconSvg, js } from "./docs-site-assets.mjs";
import { renderedHeadingText } from "./docs-site-toc.mjs";

const root = process.cwd();
const docsDir = path.join(root, "docs");
Expand Down Expand Up @@ -482,10 +483,7 @@ function tocFromHtml(html) {
const re = /<h([23]) id="([^"]+)">([\s\S]*?)<\/h[23]>/g;
let m;
while ((m = re.exec(html))) {
const text = m[3]
.replace(/<a class="anchor"[^>]*>.*?<\/a>/, "")
.replace(/<[^>]+>/g, "")
.trim();
const text = renderedHeadingText(m[3]);
items.push({ level: Number(m[1]), id: m[2], text });
}
if (items.length < 2) return "";
Expand Down
42 changes: 42 additions & 0 deletions scripts/docs-site-toc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export function renderedHeadingText(html) {
let content = html;
if (content.startsWith('<a class="anchor" ')) {
const anchorEnd = content.indexOf("</a>");
if (anchorEnd >= 0) content = content.slice(anchorEnd + "</a>".length);
}

// This is renderer-owned HTML. Walk complete tags so removing one cannot expose another tag-like sequence.
let text = "";
for (let index = 0; index < content.length; ) {
if (content[index] !== "<") {
text += content[index];
index += 1;
continue;
}

const tagEnd = renderedTagEnd(content, index + 1);
if (tagEnd < 0) {
text += content.slice(index);
break;
}
index = tagEnd + 1;
}
return text.trim();
}

function renderedTagEnd(html, start) {
let quote = null;
for (let index = start; index < html.length; index += 1) {
const char = html[index];
if (quote) {
if (char === quote) quote = null;
continue;
}
if (char === '"' || char === "'") {
quote = char;
continue;
}
if (char === ">") return index;
}
return -1;
}
42 changes: 42 additions & 0 deletions tests/docs-site-toc.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

import { renderedHeadingText } from '../scripts/docs-site-toc.mjs';

const projectRoot = fileURLToPath(new URL('..', import.meta.url));

test('heading text ignores quoted tag delimiters', () => {
const heading = '<a class="anchor" href="#x">#</a><a href="https://example.test/?q=>">visible</a>';
assert.equal(renderedHeadingText(heading), 'visible');
});

test('docs TOC structurally extracts renderer-owned heading text', (t) => {
const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), 'peekaboo-docs-toc-'));
t.after(() => rmSync(fixtureRoot, { recursive: true, force: true }));
mkdirSync(path.join(fixtureRoot, 'docs'), { recursive: true });
writeFileSync(path.join(fixtureRoot, 'docs', 'index.md'), [
'# Fixture',
'',
'## **Bold** _emphasis_ [link](https://example.com) `code`',
'',
'### Literal <tag> & text',
''
].join('\n'));

const result = spawnSync(process.execPath, [path.join(projectRoot, 'scripts', 'build-docs-site.mjs')], {
cwd: fixtureRoot,
encoding: 'utf8'
});
assert.equal(result.status, 0, result.stderr || result.stdout);

const html = readFileSync(path.join(fixtureRoot, '_site', 'index.html'), 'utf8');
assert.match(html, /<nav class="toc"/);
assert.match(html, /<a class="toc-l2"[^>]*>Bold emphasis link code<\/a>/);
assert.match(html, /<a class="toc-l3"[^>]*>Literal &amp;lt;tag&amp;gt; &amp;amp; text<\/a>/);
assert.doesNotMatch(html, /<a class="toc-l[23]"[^>]*>#/);
});
Loading