diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c6b9a81..39e86c38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,9 +154,9 @@ jobs: run: npm install --no-audit --no-fund - name: Install pinned Playwright Chromium on the hosted Ubuntu runner run: npx playwright install chromium - - name: Build and package real bilingual offline documentation - run: npm run docs:build && npm run package:offline - - name: Check English/Hindi accessibility, CLI selector, offline search and mobile navigation in Chromium + - name: Build and verify Scarp-owned offline documentation archive + run: npm run docs:build && npm run package:offline && npm run test:offline + - name: Check extracted Scarp-owned offline archive in Chromium and axe run: npm run test:browser-offline - name: Test Scarp-owned static engine in Chromium independently of VitePress run: npm run scarp:test && npm run scarp:browser-test diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 081a89cf..17c1c43b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -33,7 +33,7 @@ jobs: run: npm run check:alpha6-ast && npm run test:alpha6-ast - name: Validate bilingual compiler host and capability diagnostics run: npm run check:alpha6-diagnose && npm run test:alpha6-diagnose - - name: Keep Vue/TypeScript and Dependabot compatible + - name: Retain transitional compiler/toolchain compatibility checks run: npm run check:toolchain && npm run test:toolchain - name: Regression-test complete bilingual sitemap coverage run: npm run test:sitemap @@ -54,7 +54,7 @@ jobs: retention-days: 30 - uses: actions/upload-pages-artifact@v5 with: - path: docs/.vitepress/dist + path: docs/.scarp/dist deploy: needs: build environment: diff --git a/.gitignore b/.gitignore index 916c90de..b0b1f9a5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ artifacts/ test-results/ playwright-report/ browser-a11y/ +docs/.scarp/dist/ diff --git a/README.md b/README.md index d30fba2d..8829d663 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Scarp Documentation -**Migration underway:** Scarp Docs is developing a framework-free TypeScript documentation engine. See [architecture and migration gates](SCARP-DOCS-ENGINE.md) and [Issue #70](https://github.com/scarp-lang/scarp-docs/issues/70). The independent build and browser checks are in CI; VitePress remains the current published-site generator until the migration gate passes. No stable release is implied. +**Custom documentation engine:** Scarp Docs now builds from the Scarp-owned TypeScript generator, with bilingual HTML, local search, responsive layout and offline-browser tests. See [architecture and migration gates](SCARP-DOCS-ENGINE.md) and [Issue #70](https://github.com/scarp-lang/scarp-docs/issues/70). Legacy VitePress/Vue validation code and dependencies are transitional and will be removed after the new Pages deployment is verified. No stable release is implied. -Official VitePress documentation for the experimental [Scarp Programming Language](https://github.com/scarp-lang/scarp). +Official TypeScript-generated documentation for the experimental [Scarp Programming Language](https://github.com/scarp-lang/scarp). **Website (English):** https://scarp-lang.github.io/scarp-docs/ @@ -13,7 +13,7 @@ Official VitePress documentation for the experimental [Scarp Programming Languag **Latest published compiler:** [v0.0.6-alpha.1](https://github.com/scarp-lang/scarp/releases/tag/v0.0.6-alpha.1), November 5, 2026, source commit `aa329a22cf2bf92d2c9814a26ff9c8b67a72e6e8`. Read the [English release guide](docs/releases/v0.0.6-alpha.1.md) and [हिन्दी release guide](docs/hi/releases/v0.0.6-alpha.1.md). The [symbol navigation and CLI reference](docs/tooling/cli-alpha6.md), [हिन्दी symbols guide](docs/hi/tooling/cli-alpha6.md), [RMS tutorial](docs/language/rms-alpha6.md) and [हिन्दी RMS पाठ](docs/hi/language/rms-alpha6.md) document features **actually published** in alpha.6. The docs pin tagged compiler examples/JSON fixtures, track release drift and run the tagged CLI in CI. Native `.srp` compilation and LSP integrations remain unavailable. -**Find documentation in your language:** [Search and navigation](docs/guide/search-and-navigation.md) · [हिन्दी में खोजें](docs/hi/guide/search-and-navigation.md). The built-in VitePress search now offers Hindi labels, accessible keyboard hints and a translated no-results state; English remains unchanged. Search is client-side and does not depend on an external hosted search provider. +**Find documentation in your language:** [Search and navigation](docs/guide/search-and-navigation.md) · [हिन्दी में खोजें](docs/hi/guide/search-and-navigation.md). The Scarp-owned local search offers Hindi labels, accessible keyboard hints and a translated no-results state; English remains unchanged. Search is client-side and does not depend on an external hosted search provider. **Start learning in order:** [English: seven-stage learning path](docs/guide/learning-path.md) · [हिन्दी: सात चरणों में Scarp](docs/hi/guide/learning-path.md). Historical compiler examples retain their versioned baselines; the learning path explains which release to use, from alpha.1 through the latest alpha.6, without claiming universal compatibility. diff --git a/SCARP-DOCS-ENGINE.md b/SCARP-DOCS-ENGINE.md index 61c83d5b..4d9a8e06 100644 --- a/SCARP-DOCS-ENGINE.md +++ b/SCARP-DOCS-ENGINE.md @@ -30,7 +30,7 @@ npm run scarp:browser-test The engine's test build is generated under artifacts/scarp-engine-preview/scarp-docs/. Its assets use the existing /scarp-docs/ URL prefix; the browser test starts its own safe loopback HTTP server. -**Do not confuse the shadow build with production.** The official npm run docs:build and GitHub Pages workflow still use VitePress until the final migration gate has passed. +**Production build:** npm run docs:build now generates docs/.scarp/dist/ using the Scarp-owned renderer. npm run docs:dev starts its loopback-only watched Markdown server, and npm run docs:preview serves that production output. npm run scarp:build still creates an isolated comparison preview. Official GitHub Pages and the offline archive consume only the Scarp-owned output. Do not call the migration fully complete until deployment and the remaining legacy-tooling removal gates pass. ## Acceptance gates for replacing production @@ -45,4 +45,4 @@ The engine's test build is generated under artifacts/scarp-engine-preview/scarp- The renderer escapes unreviewed raw Markdown HTML. It explicitly recognizes only the project's callouts and named widgets; future embedded content must be implemented and tested rather than bypassing HTML safety. The core language reference remains pinned to v0.0.5-alpha.1. The latest published compiler verified at migration start is v0.0.6-alpha.1, not the changing compiler main branch. -This phase provides an **independent tested engine**, not the final VitePress removal. Complete the public-site switch in a follow-up PR tracked by Issue #70 after parity is proven. +The Scarp-owned renderer is now the production-build candidate. The legacy VitePress/Vue dependencies, old theme/config and a few source-evidence checks remain temporarily for compatibility; they are no longer the website renderer or Pages build. Remove them after live Pages, offline archive, and release CI have independently confirmed the new output. Track completion in Issue #70. diff --git a/docs/guide/offline-docs.md b/docs/guide/offline-docs.md index e2395c60..fbc40052 100644 --- a/docs/guide/offline-docs.md +++ b/docs/guide/offline-docs.md @@ -54,7 +54,7 @@ Open the local addresses: - English: `http://127.0.0.1:8080/scarp-docs/` - हिन्दी: `http://127.0.0.1:8080/scarp-docs/hi/` -The included server binds only to **127.0.0.1** (this computer). Stop with **Ctrl+C**. Do not open `index.html` with a `file://` URL: the site uses absolute `/scarp-docs/` asset paths and requires HTTP serving. +The included server binds only to **127.0.0.1** (this computer). Stop with **Ctrl+C**. Do not open `index.html` with a `file://` URL: the generated site uses absolute `/scarp-docs/` asset paths and requires HTTP serving. ## What works offline? @@ -62,7 +62,7 @@ The included server binds only to **127.0.0.1** (this computer). Stop with **Ctr **Automated browser verification:** the documentation CI now starts the extracted local server in headless Chromium and checks English/Hindi Command Finder interaction, real clipboard success and denied permissions, keyboard-activated local search and its empty-results state, language switching, skip-to-content, persisted dark/light appearance, mobile navigation, reduced-motion operation and the absence of external requests. The browser runs locally against the packaged archive, not the hosted GitHub Pages site. This does not constitute a complete WCAG audit. -The snapshot includes **all generated English and Hindi pages**, their local CSS/JavaScript, and the assets used by VitePress local search and the version-aware [Command Finder](/tooling/command-finder). Regular internal navigation runs against your local server. Links to GitHub, official releases, social profiles, or other external sites **still require internet**. The browser's actual keyboard/search behavior is being tracked separately in [accessibility test issue #65](https://github.com/scarp-lang/scarp-docs/issues/65); static asset checks are not equivalent to a full browser accessibility audit. +The snapshot includes **all generated English and Hindi pages**, their local CSS/JavaScript, and the assets used by Scarp Docs local search and the version-aware [Command Finder](/tooling/command-finder). Regular internal navigation runs against your local server. Links to GitHub, official releases, social profiles, or other external sites **still require internet**. The browser's actual keyboard/search behavior is being tracked separately in [accessibility test issue #65](https://github.com/scarp-lang/scarp-docs/issues/65); static asset checks are not equivalent to a full browser accessibility audit. Read `MANIFEST.json` inside the extracted archive for the documentation version, exact docs source commit, compiler release boundary and SHA-256 digest of every packaged file. The core reference remains pinned to alpha.1; the latest separately documented published compiler is [v0.0.6-alpha.1](/releases/v0.0.6-alpha.1). An offline snapshot does **not** upgrade the interpreter. diff --git a/docs/guide/search-and-navigation.md b/docs/guide/search-and-navigation.md index b3fad5c1..39452d04 100644 --- a/docs/guide/search-and-navigation.md +++ b/docs/guide/search-and-navigation.md @@ -1,6 +1,6 @@ # Search and navigate Scarp Docs -Find an implemented feature, an exact CLI command or a release-specific example without leaving the official documentation. Scarp Docs provides **local VitePress search** for both English and Hindi pages; it does **not** send search terms to an external search provider. +Find an implemented feature, an exact CLI command or a release-specific example without leaving the official documentation. Scarp Docs provides **local Scarp Docs search** for both English and Hindi pages; it does **not** send search terms to an external search provider. ## Open search @@ -19,4 +19,4 @@ For exact commands, compare the [alpha.6 CLI](/tooling/cli-alpha6), [AST inspect ## When search shows no results -Check the spelling of Scarp keywords, use a shorter term, or switch locale using the language menu. Search is provided by VitePress's static site index and needs JavaScript enabled to display results. The offline Chromium suite also checks the no-results message and that **Esc** returns focus to the search trigger when opened from that button. A failed local index load is not evidence that a compiler feature does not exist: use the [page navigation](/guide/learning-path) or the [compiler's published release notes](/releases/v0.0.6-alpha.1) instead. +Check the spelling of Scarp keywords, use a shorter term, or switch locale using the language menu. Search is provided by Scarp Docs' generated static full-text index and needs JavaScript enabled to display results. The offline Chromium suite also checks the no-results message and that **Esc** returns focus to the search trigger when opened from that button. A failed local index load is not evidence that a compiler feature does not exist: use the [page navigation](/guide/learning-path) or the [compiler's published release notes](/releases/v0.0.6-alpha.1) instead. diff --git a/docs/hi/guide/offline-docs.md b/docs/hi/guide/offline-docs.md index a59c3710..8966c1fe 100644 --- a/docs/hi/guide/offline-docs.md +++ b/docs/hi/guide/offline-docs.md @@ -54,7 +54,7 @@ Browser में ये local पते खोलें: - English: `http://127.0.0.1:8080/scarp-docs/` - हिन्दी: `http://127.0.0.1:8080/scarp-docs/hi/` -Server केवल **127.0.0.1** अर्थात आपके अपने कंप्यूटर पर सुनता है। इसे बंद करने के लिए **Ctrl+C** दबाएँ। `file://` के माध्यम से सीधे `index.html` न खोलें; VitePress की `/scarp-docs/` paths को स्थानीय HTTP server चाहिए। +Server केवल **127.0.0.1** अर्थात आपके अपने कंप्यूटर पर सुनता है। इसे बंद करने के लिए **Ctrl+C** दबाएँ। `file://` के माध्यम से सीधे `index.html` न खोलें; Scarp Docs की `/scarp-docs/` paths को स्थानीय HTTP server चाहिए। ## Offline में क्या उपलब्ध है? @@ -62,7 +62,7 @@ Server केवल **127.0.0.1** अर्थात आपके अपने **असल browser परीक्षण:** Documentation CI extracted local server को headless Chromium में चलाकर English/Hindi Command Finder, clipboard success या permission failure, keyboard से local search तथा zero-results, भाषा बदलना, skip-to-content, dark/light mode की preference, mobile navigation, reduced-motion और बाहरी network requests न होने की पुष्टि करती है। यह जाँच packaged offline archive पर होती है, hosted GitHub Pages पर नहीं। इसे सम्पूर्ण WCAG accessibility audit **नहीं** मानें। -इस package में सभी English/Hindi HTML पृष्ठ, उनके स्थानीय CSS/JavaScript, VitePress search assets और [Command Finder](/hi/tooling/command-finder) शामिल हैं। पृष्ठों के अंदर के links स्थानीय server पर खुलते हैं। **बाहरी GitHub, social profiles तथा download links** के लिए फिर भी इंटरनेट चाहिए। असली browser में keyboard, search और accessibility परीक्षण का अलग काम [Issue #65](https://github.com/scarp-lang/scarp-docs/issues/65) पर रखा गया है; static assets की पुष्टि को पूरी browser accessibility जाँच न समझें। +इस package में सभी English/Hindi HTML पृष्ठ, उनके स्थानीय CSS/JavaScript, Scarp Docs search assets और [Command Finder](/hi/tooling/command-finder) शामिल हैं। पृष्ठों के अंदर के links स्थानीय server पर खुलते हैं। **बाहरी GitHub, social profiles तथा download links** के लिए फिर भी इंटरनेट चाहिए। असली browser में keyboard, search और accessibility परीक्षण का अलग काम [Issue #65](https://github.com/scarp-lang/scarp-docs/issues/65) पर रखा गया है; static assets की पुष्टि को पूरी browser accessibility जाँच न समझें। Archive का `MANIFEST.json` docs version, source commit, प्रकाशित compiler version तथा हर packaged file का SHA-256 बताता है। मुख्य language reference अभी alpha.1 पर है; नवीनतम अलग सत्यापित प्रकाशित compiler [v0.0.6-alpha.1](/hi/releases/v0.0.6-alpha.1) है। सिर्फ docs डाउनलोड करने से Scarp interpreter upgrade नहीं होता। diff --git a/docs/hi/guide/search-and-navigation.md b/docs/hi/guide/search-and-navigation.md index df6b4883..d2794816 100644 --- a/docs/hi/guide/search-and-navigation.md +++ b/docs/hi/guide/search-and-navigation.md @@ -1,6 +1,6 @@ # Scarp Docs में खोजें और पृष्ठ खोलें -Scarp के सही keyword, compiler command या किसी प्रकाशित release के example को आधिकारिक दस्तावेज़ में खोज सकते हैं। English और हिन्दी पृष्ठों के लिए **VitePress की local search** उपलब्ध है। Search queries किसी बाहरी search provider को नहीं भेजी जातीं। +Scarp के सही keyword, compiler command या किसी प्रकाशित release के example को आधिकारिक दस्तावेज़ में खोज सकते हैं। English और हिन्दी पृष्ठों के लिए **Scarp Docs की अपनी local search** उपलब्ध है। Search queries किसी बाहरी search provider को नहीं भेजी जातीं। ## Search कैसे खोलें? diff --git a/docs/project/maintenance.md b/docs/project/maintenance.md index 3817d1d1..00c52a01 100644 --- a/docs/project/maintenance.md +++ b/docs/project/maintenance.md @@ -14,11 +14,19 @@ Before changing the main documentation baseline, run `npm run test:alpha2-backco The [English alpha.3 release guide](/releases/v0.0.5-alpha.3) and matching Hindi page reproduce **four complete published programs and 11 upstream source/output/JSON Git blobs**. Run `npm run check:alpha3` to validate bilingual examples, or `npm run check:alpha3 -- /path/to/tagged-alpha3/examples` to compare the exact files with the published source checkout. The `alpha3-conformance` CI job builds source commit `7715c28df09c7703b39e14fddb963275b25df476` and runs `npm run test:alpha3 -- /path/to/alpha3/scarp`, checking new `exec` and static `verify` separately from the legacy commands. Alpha.1 and alpha.2 evidence stays pinned independently. +## Scarp-owned TypeScript website build + +The official website is now authored and rendered by the independent generator in the repository's scripts/scarp-site/ directory. Run npm run docs:build to generate docs/.scarp/dist/, npm run docs:dev to serve a watched local Markdown preview, and npm run docs:preview to inspect that production output. GitHub Pages uploads exactly this new static output. The browser UI is compiled directly from TypeScript and the static full-text bilingual search index is generated from source Markdown; the website does not run VitePress/Vue. + +The same generator can build an isolated comparison copy using npm run scarp:build, verified by npm run scarp:test and npm run scarp:browser-test. The offline package contains this new production output and must pass npm run package:offline, npm run test:offline and npm run test:browser-offline before release. + +VitePress/Vue checker dependencies and earlier theme/config source may remain temporarily to preserve historical compiler/docs policy tests. They are no longer used to render Pages; move pure release evidence into neutral modules and update those policy checks before uninstalling the old dependencies. See Issue #70. + ## Offline Chromium browser acceptance -The dedicated `browser-offline` Documentation CI job installs pinned Playwright/Chromium, builds the VitePress site, packages the full English/Hindi offline archive and runs `npm run test:browser-offline` against the **extracted** standalone Node server. This validates release-aware Command Finder availability, keyboard-only version selection, copy-button success and denied-permission fallback, English/Hindi local search, a mobile-width menu, keyboard shortcuts and the restriction that no external resource is requested. +The dedicated `browser-offline` Documentation CI job installs pinned Playwright/Chromium, builds the Scarp-owned TypeScript site, packages the full English/Hindi offline archive and runs `npm run test:browser-offline` against the **extracted** standalone Node server. This validates release-aware Command Finder availability, keyboard-only version selection, copy-button success and denied-permission fallback, English/Hindi local search, a mobile-width menu, keyboard shortcuts and the restriction that no external resource is requested. -For a local repeat, install dependencies with `npm install`, install Chromium with `npx playwright install chromium` (CI uses the same browser install on GitHub-hosted Ubuntu, which has its required system libraries), run `npm run docs:build && npm run package:offline`, then `npm run test:browser-offline`. Failure screenshots, when possible, are saved under ignored `artifacts/browser-failures/` and uploaded on CI failure. The expanded suite also checks a keyboard-opened empty search state and focus restoration on close (implemented by the small theme-level `local-search-focus.ts` manager for VitePress 1.6), translated-page switching, skip-to-content link and target, persisted appearance mode across reloads, and usable CLI controls under a reduced-motion viewport. These tests exercise selected interactive controls, not every screen-reader combination or a full WCAG conformance audit; track broader access testing in [Issue #65](https://github.com/scarp-lang/scarp-docs/issues/65). The offline distribution remains pinned to the tested docs commit, and the browser suite has no external-search backend. +For a local repeat, install dependencies with `npm install`, install Chromium with `npx playwright install chromium` (CI uses the same browser install on GitHub-hosted Ubuntu, which has its required system libraries), run `npm run docs:build && npm run package:offline`, then `npm run test:browser-offline`. Failure screenshots, when possible, are saved under ignored `artifacts/browser-failures/` and uploaded on CI failure. The expanded suite also checks a keyboard-opened empty search state and focus restoration on close (implemented by the small theme-level `local-search-focus.ts` manager from the legacy VitePress 1.6 theme), translated-page switching, skip-to-content link and target, persisted appearance mode across reloads, and usable CLI controls under a reduced-motion viewport. These tests exercise selected interactive controls, not every screen-reader combination or a full WCAG conformance audit; track broader access testing in [Issue #65](https://github.com/scarp-lang/scarp-docs/issues/65). The offline distribution remains pinned to the tested docs commit, and the browser suite has no external-search backend. ## Offline accessibility rule audits diff --git a/package.json b/package.json index 8aad55c7..e0a52b09 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,15 @@ "type": "module", "description": "Official documentation website for the Scarp Programming Language", "scripts": { - "docs:dev": "vitepress dev docs --host 127.0.0.1", - "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs", + "docs:dev": "tsx scripts/scarp-site/serve-dev.ts --dev", + "docs:build": "tsx scripts/scarp-site/build.ts --production", + "docs:preview": "tsx scripts/scarp-site/serve-dev.ts --preview", "scarp:build": "tsx scripts/scarp-site/build.ts", "scarp:test": "tsx scripts/scarp-site/test.ts", "scarp:browser-test": "tsx scripts/scarp-site/browser-test.ts", "package:offline": "tsx scripts/package-offline.ts", "test:offline": "tsx scripts/test-offline.ts", - "test:browser-offline": "tsx scripts/test-browser-offline.ts", + "test:browser-offline": "tsx scripts/test-scarp-offline-browser.ts", "typecheck": "vue-tsc --noEmit -p tsconfig.json", "check:tooling": "tsx scripts/check-tooling.ts", "check:toolchain": "tsx scripts/check-toolchain.ts", diff --git a/scripts/check-site-build.ts b/scripts/check-site-build.ts index dfa5ed26..c0d0406d 100644 --- a/scripts/check-site-build.ts +++ b/scripts/check-site-build.ts @@ -8,7 +8,7 @@ import { validateSitemap } from './lib/sitemap-policy.ts' const root = resolve(fileURLToPath(new URL('..', import.meta.url))) const docs = join(root, 'docs') -const dist = join(docs, '.vitepress', 'dist') +const dist = join(docs, '.scarp', 'dist') function headTags(html: string, tag: string): Array> { const head = /]*>([\s\S]*?)<\/head>/i.exec(html)?.[1] ?? '' diff --git a/scripts/package-offline.ts b/scripts/package-offline.ts index 111a5fad..3dad28bf 100644 --- a/scripts/package-offline.ts +++ b/scripts/package-offline.ts @@ -8,9 +8,9 @@ import { gzipSync } from 'node:zlib' import ts from 'typescript' import { documentationPages } from './lib/docs-pages.ts' -/** Deterministic, offline-ready tar.gz of the already-built VitePress website. */ +/** Deterministic offline archive of the independently built Scarp documentation website. */ export const root = resolve(fileURLToPath(new URL('..', import.meta.url))) -export const dist = join(root, 'docs/.vitepress/dist') +export const dist = join(root, 'docs/.scarp/dist') export const artifactDir = join(root, 'artifacts') const stage = join(artifactDir, '.offline-stage') export const archiveName = 'scarp-docs-offline.tar.gz' @@ -34,7 +34,7 @@ const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) const docSources = documentationPages(join(root, 'docs')) for (const source of docSources) { assert.ok(existsSync(join(dist, source.replace(/\.md$/,'.html'))), - 'VitePress build is incomplete: ' + source) + 'Scarp-owned build is incomplete: ' + source) } assert.ok(docSources.some(page => page.startsWith('hi/')), 'Hindi site is absent') const commit = (process.env.GITHUB_SHA ?? @@ -72,8 +72,15 @@ for (const path of walk(join(stage, 'scarp-docs'))) { localizedAvatars += count } } -assert.ok(localizedAvatars > 0, - 'Offline snapshot must replace the hosted Scarp organization avatar locally') +// New Scarp-owned output already ships a local SVG. Older VitePress builds +// needed the hosted-avatar rewrite; retain compatibility but never require it. +assert.ok(existsSync(join(stage,'scarp-docs/assets/scarp-mark.svg')), + 'Scarp-owned offline documentation must include a local branded SVG') +for (const path of walk(join(stage, 'scarp-docs'))) { + if (!/\.(?:html|js|css|json|xml)$/.test(path)) continue + assert.ok(!readFileSync(join(stage,path),'utf8').includes(hostedAvatar), + 'Hosted organization avatar escaped local offline asset checks: ' + path) +} const serverTs = readFileSync(join(root,'scripts/offline/serve.ts'),'utf8') const compiled = ts.transpileModule(serverTs, { @@ -122,9 +129,9 @@ writeFileSync(join(stage,'README-OFFLINE.txt'),instructions,'utf8') const contents = walk(stage) assert.ok(contents.some(p => p.startsWith('scarp-docs/assets/') && p.endsWith('.js')), - 'VitePress JavaScript bundle missing from offline package') + 'Scarp JavaScript bundle missing from offline package') assert.ok(contents.some(p => p.startsWith('scarp-docs/assets/') && p.endsWith('.css')), - 'VitePress stylesheet missing from offline package') + 'Scarp stylesheet missing from offline package') const manifest = { format: 'scarp-docs-offline-v1', documentationVersion: packageJson.version, diff --git a/scripts/scarp-site/browser-test.ts b/scripts/scarp-site/browser-test.ts index 1d36a1f1..1268a614 100644 --- a/scripts/scarp-site/browser-test.ts +++ b/scripts/scarp-site/browser-test.ts @@ -6,7 +6,7 @@ import { chromium, type BrowserContext, type Page } from 'playwright' import { auditOfflineAccessibility } from '../lib/offline-a11y.ts' import { previewRoot } from './build.ts' -const root=resolve(previewRoot,'scarp-docs') +const root=process.argv[2]?resolve(process.argv[2]):resolve(previewRoot,'scarp-docs') const base='/scarp-docs/' let assertions=0 const mime:Record={ diff --git a/scripts/scarp-site/build.ts b/scripts/scarp-site/build.ts index a3a1fc6f..85144f29 100644 --- a/scripts/scarp-site/build.ts +++ b/scripts/scarp-site/build.ts @@ -11,7 +11,10 @@ import { renderMarkdown, type RenderedPage, type HomeAction, type HomeFeature } const root=resolve(fileURLToPath(new URL('../..',import.meta.url))) const docsRoot=join(root,'docs') export const previewRoot=join(root,'artifacts','scarp-engine-preview') -const siteRoot=join(previewRoot,'scarp-docs') +const production=process.argv.includes('--production') +const siteRoot=production + ?join(root,'docs','.scarp','dist') + :join(previewRoot,'scarp-docs') const styles=join(root,'scripts/scarp-site/site.css') const client=join(root,'scripts/scarp-site/client.ts') const groupOrder=['guide','tutorials','language','tooling','reference','examples','releases','community','project'] @@ -154,8 +157,8 @@ function htmlDocument(view:PageView, views:PageView[]):string{ } export async function buildSite():Promise<{pages:number;assets:number}>{ - // The engine preview NEVER overwrites VitePress production output. - rmSync(previewRoot,{recursive:true,force:true}) + // Production output is an owned .scarp/dist directory, never VitePress assets. + rmSync(production?siteRoot:previewRoot,{recursive:true,force:true}) mkdirSync(join(siteRoot,'assets'),{recursive:true}) const sources=documentationPages(docsRoot) const views=sources.map(source=>{ diff --git a/scripts/scarp-site/serve-dev.ts b/scripts/scarp-site/serve-dev.ts new file mode 100644 index 00000000..64935214 --- /dev/null +++ b/scripts/scarp-site/serve-dev.ts @@ -0,0 +1,90 @@ +import { createServer } from 'node:http' +import { readFile, stat } from 'node:fs/promises' +import { existsSync, watch } from 'node:fs' +import { extname, join, resolve, sep } from 'node:path' +import { fileURLToPath } from 'node:url' +import { buildSite, previewRoot } from './build.ts' + +const root=resolve(fileURLToPath(new URL('../..',import.meta.url))) +const preview=process.argv.includes('--preview') +const site=preview?join(root,'docs/.scarp/dist'):join(previewRoot,'scarp-docs') +const base='/scarp-docs/' +const types:Record={ + '.html':'text/html; charset=utf-8', + '.js':'text/javascript; charset=utf-8', + '.css':'text/css; charset=utf-8', + '.json':'application/json; charset=utf-8', + '.svg':'image/svg+xml', + '.xml':'application/xml; charset=utf-8' +} +if (!preview) await buildSite() +if (!existsSync(join(site,'index.html'))) { + throw new Error('Scarp site not built; run npm run docs:build first') +} + +const requestedPort=Number(process.env.PORT??'5173') +if(!Number.isInteger(requestedPort)||requestedPort<0||requestedPort>65535) { + throw new TypeError('PORT must be a valid whole number') +} +const server=createServer((req,res)=>{ + if(req.method!=='GET'&&req.method!=='HEAD'){ + res.writeHead(405,{Allow:'GET, HEAD'}).end('Method not allowed') + return + } + let pathname:string + try{pathname=decodeURIComponent(new URL(req.url??'/','http://127.0.0.1').pathname)} + catch{res.writeHead(400).end('Invalid URL');return} + if(pathname==='/'){ + res.writeHead(302,{Location:base}).end() + return + } + if(!pathname.startsWith(base)||pathname.includes('\\')||pathname.includes('\0')|| + pathname.split('/').some(x=>x==='..'||x==='.')){ + res.writeHead(404).end('Not found') + return + } + const tail=pathname.slice(base.length) + const actual=resolve(site,tail+(tail.endsWith('/')||!tail?'index.html': + extname(tail)?'':'.html')) + if(actual!==site&&!actual.startsWith(site+sep)){ + res.writeHead(404).end('Not found') + return + } + void (async()=>{ + try{ + if(!(await stat(actual)).isFile())throw new Error('Not a file') + const bytes=await readFile(actual) + res.writeHead(200,{ + 'Content-Type':types[extname(actual)]??'application/octet-stream', + 'Content-Length':bytes.byteLength, + 'Cache-Control':'no-store', + 'X-Content-Type-Options':'nosniff' + }) + res.end(req.method==='HEAD'?undefined:bytes) + }catch{res.writeHead(404).end('Not found')} + })() +}) +server.listen(requestedPort,'127.0.0.1',()=>{ + const address=server.address() + const port=address&&typeof address==='object'?address.port:requestedPort + console.log('Scarp Docs ('+(preview?'production preview':'development')+ + '): http://127.0.0.1:'+port+base) + console.log('Local machine only; press Ctrl+C to stop.') +}) + +if(!preview){ + let pending:ReturnType|undefined + try{ + const listener=watch(join(root,'docs'),{recursive:true},(_,filename)=>{ + if(!filename?.endsWith('.md'))return + if(pending)clearTimeout(pending) + pending=setTimeout(()=>{ + void buildSite().then(()=>console.log('Scarp Docs rebuilt after '+filename)) + .catch(error=>console.error('Scarp build failed: '+String(error))) + },300) + }) + server.on('close',()=>listener.close()) + }catch(error){ + console.warn('Automatic file watching unavailable; restart to rebuild: '+String(error)) + } +} diff --git a/scripts/test-scarp-offline-browser.ts b/scripts/test-scarp-offline-browser.ts new file mode 100644 index 00000000..fb9a2331 --- /dev/null +++ b/scripts/test-scarp-offline-browser.ts @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { existsSync, mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +/** + * Browser test the distribution users actually download, not only a preview. + * Previous releases used a VitePress-specific browser suite. This stage uses + * Scarp's own browser suite and preserves the standalone package checks. + */ +const root=resolve(fileURLToPath(new URL('..',import.meta.url))) +const archive=join(root,'artifacts/scarp-docs-offline.tar.gz') +assert.ok(existsSync(archive),'Run docs:build and package:offline before browser tests') +const extract=mkdtempSync(join(tmpdir(),'scarp-browser-distribution-')) +try{ + execFileSync('tar',['-xzf',archive,'-C',extract],{cwd:root}) + const site=join(extract,'scarp-docs') + assert.ok(existsSync(join(site,'index.html')),'Offline archive missing English homepage') + assert.ok(existsSync(join(site,'hi/index.html')),'Offline archive missing Hindi homepage') + execFileSync(process.execPath,[ + '--import','tsx','scripts/scarp-site/browser-test.ts',site + ],{ + cwd:root,stdio:'inherit',env:{...process.env,SCARP_BROWSER_FROM_ARCHIVE:'1'}, + maxBuffer:16*1024*1024,timeout:180_000 + }) + console.log('PASS: extracted offline archive passed Scarp-owned bilingual Chromium and axe browser suite') +}finally{ + rmSync(extract,{recursive:true,force:true}) +}