diff --git a/.github/actions/website-cache/action.yml b/.github/actions/website-cache/action.yml
new file mode 100644
index 000000000000..040d1d460f45
--- /dev/null
+++ b/.github/actions/website-cache/action.yml
@@ -0,0 +1,25 @@
+name: Restore website build work
+description: Reuse compiled examples and Webpack work without caching generated pages
+
+outputs:
+ key:
+ description: Primary key to use when checkpointing the completed build
+ value: ${{ steps.restore.outputs.cache-primary-key }}
+
+runs:
+ using: composite
+ steps:
+ - name: Get Node version
+ id: node
+ shell: bash
+ run: echo "version=$(node --version)" >> "$GITHUB_OUTPUT"
+
+ - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ id: restore
+ with:
+ path: |
+ node_modules/.cache/twoslash
+ packages/typescriptlang-org/.cache/webpack
+ key: website-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.node.outputs.version }}-${{ github.run_id }}-${{ github.run_attempt }}
+ restore-keys: |
+ website-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.node.outputs.version }}-
diff --git a/.github/actions/website-cache/prune-twoslash-cache.js b/.github/actions/website-cache/prune-twoslash-cache.js
new file mode 100644
index 000000000000..0daa6ed972b1
--- /dev/null
+++ b/.github/actions/website-cache/prune-twoslash-cache.js
@@ -0,0 +1,51 @@
+const fs = require("fs")
+const path = require("path")
+
+const MAX_BYTES = 64 * 1024 * 1024
+
+function pruneTwoslashCache(directory) {
+ let entries
+ try {
+ if (!fs.lstatSync(directory).isDirectory()) {
+ throw new Error(`Expected a Twoslash cache directory: ${directory}`)
+ }
+ entries = fs.readdirSync(directory, { withFileTypes: true })
+ } catch (error) {
+ if (error.code !== "ENOENT") throw error
+ return { bytes: 0, removed: 0, removedBytes: 0 }
+ }
+
+ const files = entries.map(entry => {
+ if (!entry.isFile() || !/^[a-f0-9]{40}\.json$/.test(entry.name)) {
+ throw new Error(`Unexpected Twoslash cache entry: ${entry.name}`)
+ }
+ const filename = path.join(directory, entry.name)
+ const { size, mtimeMs } = fs.statSync(filename)
+ return { filename, size, mtimeMs }
+ })
+ files.sort((a, b) => a.mtimeMs - b.mtimeMs || a.filename.localeCompare(b.filename))
+
+ let bytes = files.reduce((total, file) => total + file.size, 0)
+ let removed = 0
+ let removedBytes = 0
+ for (const file of files) {
+ if (bytes <= MAX_BYTES) break
+ fs.unlinkSync(file.filename)
+ bytes -= file.size
+ removedBytes += file.size
+ removed++
+ }
+ return { bytes, removed, removedBytes }
+}
+
+module.exports = { MAX_BYTES, pruneTwoslashCache }
+
+if (require.main === module) {
+ const directory = path.resolve(__dirname, "../../../node_modules/.cache/twoslash")
+ const { bytes, removed, removedBytes } = pruneTwoslashCache(directory)
+ console.log(
+ `Twoslash cache: ${(bytes / 1024 / 1024).toFixed(2)} MiB retained; ` +
+ `removed ${removed} entries (${(removedBytes / 1024 / 1024).toFixed(2)} MiB). ` +
+ `Limit: ${MAX_BYTES / 1024 / 1024} MiB.`
+ )
+}
diff --git a/.github/actions/website-cache/save/action.yml b/.github/actions/website-cache/save/action.yml
new file mode 100644
index 000000000000..436959eef2e7
--- /dev/null
+++ b/.github/actions/website-cache/save/action.yml
@@ -0,0 +1,28 @@
+name: Save website build work
+description: Prune least-recently-used compiled examples and checkpoint the successful build
+
+inputs:
+ key:
+ description: Primary key returned by the website cache restore action
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: Prune Twoslash cache to 64 MiB
+ shell: bash
+ env:
+ CACHE_KEY: ${{ inputs.key }}
+ run: |
+ if [[ -z "$CACHE_KEY" ]]; then
+ echo "::error::Missing website cache restore key"
+ exit 1
+ fi
+ node "$GITHUB_ACTION_PATH/../prune-twoslash-cache.js"
+
+ - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: |
+ node_modules/.cache/twoslash
+ packages/typescriptlang-org/.cache/webpack
+ key: ${{ inputs.key }}
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index bff849e3510a..81ab2f87ba44 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -11,7 +11,7 @@ defaults:
shell: bash
jobs:
- tests:
+ site:
strategy:
fail-fast: false
matrix:
@@ -24,22 +24,31 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20.x"
- - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
+ cache: pnpm
- - run: pnpm install
+ - run: pnpm install --frozen-lockfile
# Grab localizations
- run: pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1
# Build the packages
- run: pnpm bootstrap
- - run: pnpm build
+
+ - name: Restore website build caches
+ id: website-cache
+ uses: ./.github/actions/website-cache
# Verify it compiles
- run: pnpm build-site
+
+ - name: Save website build caches
+ uses: ./.github/actions/website-cache/save
+ with:
+ key: ${{ steps.website-cache.outputs.key }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
@@ -47,8 +56,57 @@ jobs:
name: site
path: packages/typescriptlang-org/public
- # Run all the package's tests
- - run: pnpm test
+ # Validate documentation and types generated by Gatsby
+ - run: pnpm test:ci:site
+
+ - name: Save website validation caches
+ uses: ./.github/actions/website-cache/save
+ with:
+ key: ${{ steps.website-cache.outputs.key }}-tests
+
+ - run: |
+ git add .
+ if ! git diff --staged --exit-code --quiet; then
+ echo "This PR is missing some generated changes. Please update locally or merge the patch artifact."
+ echo ""
+ git diff --staged
+ git diff --staged > missing.patch
+ exit 1
+ fi
+ name: Check for uncommitted changes
+ id: check-diff
+ if: github.event_name == 'pull_request'
+
+ - name: Upload diff artifact
+ if: ${{ failure() && steps.check-diff.conclusion == 'failure' }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: missing.patch-site-${{ matrix.os }}
+ path: missing.patch
+
+ tests:
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - ubuntu-latest
+ - windows-latest
+ - macos-latest
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: "20.x"
+ cache: pnpm
+
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1
+ - run: pnpm bootstrap
+ - run: pnpm test:ci:packages
- run: |
git add .
@@ -67,7 +125,7 @@ jobs:
if: ${{ failure() && steps.check-diff.conclusion == 'failure' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
- name: missing.patch
+ name: missing.patch-packages-${{ matrix.os }}
path: missing.patch
changesets:
@@ -103,6 +161,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ always() }}
needs:
+ - site
- tests
- changesets
diff --git a/.github/workflows/deploy-prod-static.yml b/.github/workflows/deploy-prod-static.yml
index f70ec81bb839..b962fc44c5a8 100644
--- a/.github/workflows/deploy-prod-static.yml
+++ b/.github/workflows/deploy-prod-static.yml
@@ -31,25 +31,36 @@ jobs:
with:
# Fetch the full history, to build attribution.json
fetch-depth: 0
+ - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20.x"
+ cache: pnpm
- - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Pull localizations
+ run: pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1
- # Builds the modules, and boostraps the other modules
- - name: Build website
- run: |
- pnpm install
- pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1
- pnpm bootstrap
- pnpm run --filter=typescriptlang-org setup-playground-cache-bust
- pnpm build
+ - name: Build packages
+ run: pnpm bootstrap
- - name: Makes the site
- run: |
- pnpm build-site
- cp -r packages/typescriptlang-org/public site
+ - name: Restore website build caches
+ id: website-cache
+ uses: ./.github/actions/website-cache
+
+ - name: Set up playground cache busting
+ run: pnpm run --filter=typescriptlang-org setup-playground-cache-bust
+
+ - name: Build static site
+ run: pnpm build-site
+
+ - name: Save website build caches
+ uses: ./.github/actions/website-cache/save
+ with:
+ key: ${{ steps.website-cache.outputs.key }}
- name: Setup Pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
@@ -57,7 +68,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v3
with:
- path: './site'
+ path: packages/typescriptlang-org/public
deploy:
environment:
diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml
index 63632d66c56f..d58cf049e48c 100644
--- a/.github/workflows/publish-packages.yml
+++ b/.github/workflows/publish-packages.yml
@@ -35,7 +35,6 @@ jobs:
run: |
pnpm install
pnpm bootstrap
- pnpm build
- uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
with:
diff --git a/package.json b/package.json
index 200cf3cd8336..e3cc29de9812 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,8 @@
"webpack": "^5.105.3"
},
"patchedDependencies": {
- "gatsby-remark-shiki-twoslash@3.0.38": "patches/gatsby-remark-shiki-twoslash@3.0.38.patch"
+ "gatsby-remark-shiki-twoslash@3.0.38": "patches/gatsby-remark-shiki-twoslash@3.0.38.patch",
+ "remark-shiki-twoslash@3.1.3": "patches/remark-shiki-twoslash@3.1.3.patch"
},
"ignoredBuiltDependencies": [
"@parcel/watcher",
@@ -67,7 +68,7 @@
},
"scripts": {
"ci:publish": "pnpm changeset git-tag && pnpm publish -r",
- "bootstrap": "pnpm -r run bootstrap && BOOTSTRAPPING=true pnpm -r run build",
+ "bootstrap": "pnpm -r --filter=!@typescript/ata --filter=!@typescript/vfs --filter=!@typescript/twoslash --filter=!playground-examples --filter=!tsconfig-reference run bootstrap && pnpm build",
"start": "concurrently -p \"[{name}]\" -n \"BUILD,SITE\" -c \"bgBlue.bold,bgMagenta.bold\" \"node watcher.js\" \"pnpm run --filter=typescriptlang-org start\"",
"build": "pnpm run --filter=!typescriptlang-org build",
"build-site": "pnpm run --filter=typescriptlang-org build",
@@ -76,6 +77,9 @@
"clean": "pnpm run --filter=typescriptlang-org gatsby clean",
"clean-twoslash": "rm -rf packages/.cache/twoslash",
"test": "CI=true pnpm -r run test",
+ "test:ci": "pnpm test:ci:site && pnpm test:ci:packages",
+ "test:ci:site": "CI=true pnpm -r --filter=glossary --filter=tsconfig-reference run lint && CI=true pnpm --filter=typescriptlang-org test",
+ "test:ci:packages": "CI=true pnpm -r --filter=!root --filter=!typescriptlang-org --filter=!glossary --filter=!tsconfig-reference --no-sort run test",
"update-test-snapshots": "CI=true pnpm run --filter=@typescript/twoslash --filter=@typescript/vfs test -u"
},
"repository": "microsoft/TypeScript-Website",
diff --git a/packages/tsconfig-reference/package.json b/packages/tsconfig-reference/package.json
index cc54552f67a8..7447712e62ee 100644
--- a/packages/tsconfig-reference/package.json
+++ b/packages/tsconfig-reference/package.json
@@ -12,7 +12,7 @@
"generate:md:tsconfig": "tsc --build && node scripts/tsconfig/generateMarkdown",
"generate:md:cli": "tsc --build && node scripts/cli/generateMarkdown",
"generate:md:msbuild": "tsc --build && node scripts/msbuild/generateMarkdown",
- "generate:md": "pnpm generate:md:tsconfig && pnpm generate:md:cli && pnpm generate:md:tsconfig",
+ "generate:md": "pnpm generate:md:tsconfig && pnpm generate:md:cli",
"download": "node scripts/schema/downloadSchemaBase.js",
"bootstrap": "pnpm generate:json",
"build": "pnpm generate:json && pnpm generate:md",
diff --git a/packages/typescriptlang-org/lib/bootup/shiki.test.js b/packages/typescriptlang-org/lib/bootup/shiki.test.js
new file mode 100644
index 000000000000..b90024399937
--- /dev/null
+++ b/packages/typescriptlang-org/lib/bootup/shiki.test.js
@@ -0,0 +1,465 @@
+jest.mock("remark-shiki-twoslash", () => ({
+ setupForFile: jest.fn(),
+ remarkVisitor: jest.fn(),
+}))
+
+const { setupForFile, remarkVisitor } = require("remark-shiki-twoslash")
+const plugin = require("gatsby-remark-shiki-twoslash")
+
+const ast = (value = "const value = 1", lang = "ts", meta = undefined) => ({
+ type: "root",
+ children: [{ type: "code", value, lang, meta }],
+})
+
+let highlighter
+
+beforeEach(() => {
+ jest.clearAllMocks()
+ highlighter = {
+ codeToThemedTokens: jest.fn(() => [[{ content: "token", color: "#000" }]]),
+ }
+ setupForFile.mockImplementation(async settings => ({
+ settings,
+ highlighters: [highlighter],
+ }))
+ remarkVisitor.mockImplementation(highlighters => node => {
+ const tokens = highlighters[0].codeToThemedTokens(node.value, node.lang)
+ node.type = "html"
+ node.value = `
${tokens[0][0].content}
`
+ tokens[0][0].content = "mutated"
+ node.children = []
+ })
+})
+
+it("reuses highlighting without sharing mutable tokens or losing focusability", async () => {
+ const first = ast()
+ const second = ast()
+ await plugin({ markdownAST: first }, {})
+ await plugin({ markdownAST: second }, {})
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledTimes(1)
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledWith(
+ "const value = 1",
+ "ts",
+ undefined,
+ { includeExplanation: false }
+ )
+ expect(second).toEqual(first)
+ expect(second.children[0].value).toContain("tabindex='0'>token")
+ expect(setupForFile).toHaveBeenCalledTimes(2)
+ expect(remarkVisitor).toHaveBeenCalledTimes(2)
+})
+
+it("keeps scopes for TSConfig property hovers", async () => {
+ await plugin({ markdownAST: ast('{"strict": true}', "json", "tsconfig") }, {})
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledWith(
+ '{"strict": true}',
+ "json",
+ undefined,
+ { includeExplanation: true }
+ )
+})
+
+it("distinguishes languages and code", async () => {
+ await plugin({ markdownAST: ast() }, {})
+ await plugin({ markdownAST: ast("const value = 2") }, {})
+ await plugin({ markdownAST: ast("const value = 1", "js") }, {})
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledTimes(3)
+})
+
+it("does not cache highlighting failures", async () => {
+ highlighter.codeToThemedTokens.mockImplementationOnce(() => {
+ throw new Error("highlighting failed")
+ })
+ await expect(plugin({ markdownAST: ast() }, {})).rejects.toThrow(
+ "highlighting failed"
+ )
+ await plugin({ markdownAST: ast() }, {})
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledTimes(2)
+})
+
+it("isolates highlighter instances", async () => {
+ const firstHighlighter = highlighter
+ await plugin({ markdownAST: ast() }, {})
+ highlighter = {
+ codeToThemedTokens: jest.fn(() => [[{ content: "different theme" }]]),
+ }
+ const second = ast()
+ await plugin({ markdownAST: second }, {})
+ expect(firstHighlighter.codeToThemedTokens).toHaveBeenCalledTimes(1)
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledTimes(1)
+ expect(second.children[0].value).toContain("different theme")
+})
+
+it("bounds the highlighting cache", async () => {
+ for (let i = 0; i < 257; i++) {
+ await plugin({ markdownAST: ast(`const value = ${i}`) }, {})
+ }
+ await plugin({ markdownAST: ast("const value = 0") }, {})
+ expect(highlighter.codeToThemedTokens).toHaveBeenCalledTimes(258)
+})
+
+describe("persistent Twoslash cache", () => {
+ const fs = require("fs")
+ const shikiTwoslash = require("shiki-twoslash")
+ const { runTwoSlashOnNode } = jest.requireActual("remark-shiki-twoslash")
+ const fence = lang => ({ lang, meta: { twoslash: true } })
+ let runTwoSlash
+
+ beforeEach(() => {
+ const files = new Map()
+ const existsSync = fs.existsSync
+ const readFileSync = fs.readFileSync
+ const writeFileSync = fs.writeFileSync
+ const utimesSync = fs.utimesSync
+ const isCache = path =>
+ typeof path === "string" &&
+ (path.includes(".cache/twoslash") || path.includes(".cache\\twoslash"))
+ jest
+ .spyOn(fs, "existsSync")
+ .mockImplementation(path =>
+ isCache(path) ? files.has(path) : existsSync(path)
+ )
+ const mkdirSync = fs.mkdirSync
+ jest
+ .spyOn(fs, "mkdirSync")
+ .mockImplementation((path, ...args) =>
+ isCache(path) ? undefined : mkdirSync(path, ...args)
+ )
+ jest
+ .spyOn(fs, "readFileSync")
+ .mockImplementation((path, ...args) =>
+ isCache(path) ? files.get(path) : readFileSync(path, ...args)
+ )
+ jest
+ .spyOn(fs, "writeFileSync")
+ .mockImplementation((path, data, ...args) => {
+ if (isCache(path)) files.set(path, data)
+ else writeFileSync(path, data, ...args)
+ })
+ jest.spyOn(fs, "utimesSync").mockImplementation((path, ...args) => {
+ if (!isCache(path)) return utimesSync(path, ...args)
+ })
+ runTwoSlash = jest
+ .spyOn(shikiTwoslash, "runTwoSlash")
+ .mockImplementation((code, lang, { tsModule, ...settings }) => ({
+ code, lang, settings,
+ }))
+ })
+
+ afterEach(() => jest.restoreAllMocks())
+
+ it("reuses identical semantic inputs", () => {
+ const first = runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ const second = runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ expect(second).toEqual(first)
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ expect(fs.utimesSync).toHaveBeenCalledTimes(1)
+ expect(fs.utimesSync).toHaveBeenCalledWith(
+ expect.any(String),
+ expect.any(Date),
+ expect.any(Date)
+ )
+ })
+
+ it("distinguishes TypeScript from JavaScript", () => {
+ runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ runTwoSlashOnNode("const value = 1", fence("js"), {})
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("invalidates changed examples without discarding other examples", () => {
+ runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ runTwoSlashOnNode("const other = 1", fence("ts"), {})
+ runTwoSlashOnNode("const value = 2", fence("ts"), {})
+ runTwoSlashOnNode("const other = 1", fence("ts"), {})
+ expect(runTwoSlash).toHaveBeenCalledTimes(3)
+ })
+
+ it("invalidates results when compiler or Twoslash options change", () => {
+ runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ runTwoSlashOnNode("const value = 1", fence("ts"), {
+ defaultCompilerOptions: { strict: true },
+ })
+ runTwoSlashOnNode("const value = 1", fence("ts"), {
+ defaultOptions: { noErrors: true },
+ })
+ expect(runTwoSlash).toHaveBeenCalledTimes(3)
+ })
+
+ it("does not save failed compilations", () => {
+ runTwoSlash.mockImplementationOnce(() => {
+ throw new Error("compilation failed")
+ })
+ expect(() => runTwoSlashOnNode("bad code", fence("ts"), {})).toThrow(
+ "compilation failed"
+ )
+ runTwoSlashOnNode("bad code", fence("ts"), {})
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("does not hide errors recording cache usage", () => {
+ runTwoSlashOnNode("const value = 1", fence("ts"), {})
+ fs.utimesSync.mockImplementationOnce(() => {
+ throw new Error("access time update failed")
+ })
+ expect(() => runTwoSlashOnNode("const value = 1", fence("ts"), {})).toThrow(
+ "access time update failed"
+ )
+ })
+
+ it.each(["runtime", "manifest"])("invalidates changed compiler %s without a version bump", kind => {
+ const freshCache = () => {
+ let cache
+ jest.isolateModules(() => {
+ cache = jest.requireActual("remark-shiki-twoslash/dist/twoslash-cache.js")
+ })
+ return cache
+ }
+ freshCache()("const value = 1", "ts", {}, runTwoSlash)
+ freshCache()("const value = 1", "ts", {}, runTwoSlash)
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ const readFileSync = fs.readFileSync.getMockImplementation()
+ fs.readFileSync.mockImplementation((filename, ...args) => {
+ const contents = readFileSync(filename, ...args)
+ if (typeof filename === "string" && filename.replace(/\\/g, "/").includes("ts-twoslasher/")) {
+ if (kind === "runtime" && filename.endsWith(".js")) {
+ return Buffer.concat([Buffer.from(contents), Buffer.from("\n// compiler rebuild\n")])
+ }
+ if (kind === "manifest" && filename.endsWith("package.json")) {
+ return Buffer.from(JSON.stringify({ ...JSON.parse(contents), main: "./dist/changed-compiler.js" }))
+ }
+ }
+ return contents
+ })
+ freshCache()("const value = 1", "ts", {}, runTwoSlash)
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it.each([
+ { tsModule: { version: "custom compiler" } },
+ { fsMap: new Map() },
+ { customTransformers: { before: [] } },
+ { lzstringModule: { compressToEncodedURIComponent: () => "custom" } },
+ ])("does not reuse non-serializable compiler inputs: %p", settings => {
+ runTwoSlashOnNode("const value = 1", fence("ts"), settings)
+ runTwoSlashOnNode("const value = 1", fence("ts"), settings)
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ expect(fs.writeFileSync).not.toHaveBeenCalled()
+ })
+
+ describe("filesystem inputs", () => {
+ const os = require("os")
+ const path = require("path")
+ let directory
+
+ beforeEach(() => {
+ directory = fs.mkdtempSync(path.join(os.tmpdir(), "website-twoslash-input-"))
+ })
+
+ afterEach(() => fs.rmSync(directory, { recursive: true, force: true }))
+
+ it("invalidates changed declarations", () => {
+ const filename = path.join(directory, "dependency.d.ts")
+ fs.writeFileSync(filename, "export const value: number")
+ runTwoSlash.mockImplementation((code, lang, settings) => ({
+ declaration: settings.tsModule.sys.readFile(filename),
+ }))
+ const first = runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {})).toEqual(first)
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ fs.writeFileSync(filename, "export const value: string")
+ const second = runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ expect(second.declaration).toBe("export const value: string")
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {})).toEqual(second)
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("recompiles real imports when their declarations change", () => {
+ runTwoSlash.mockRestore()
+ runTwoSlash = jest.spyOn(shikiTwoslash, "runTwoSlash")
+ const filename = path.join(directory, "dependency.d.ts")
+ const code = 'import { value } from "./dependency"\nconst answer = value\n// ^?'
+ const settings = { vfsRoot: directory, defaultCompilerOptions: { types: [] } }
+ fs.writeFileSync(filename, "export const value: number")
+ const first = runTwoSlashOnNode(code, fence("ts"), settings)
+ expect(first.queries[0].text).toBe("const answer: number")
+ expect(runTwoSlashOnNode(code, fence("ts"), settings)).toEqual(first)
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ fs.writeFileSync(filename, "export const value: string")
+ const second = runTwoSlashOnNode(code, fence("ts"), settings)
+ expect(second.queries[0].text).toBe("const answer: string")
+ expect(runTwoSlashOnNode(code, fence("ts"), settings)).toEqual(second)
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("invalidates missing imports when a file becomes available", () => {
+ const filename = path.join(directory, "dependency.d.ts")
+ runTwoSlash.mockImplementation((code, lang, settings) => ({
+ exists: settings.tsModule.sys.fileExists(filename),
+ }))
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {}).exists).toBe(false)
+ runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ fs.writeFileSync(filename, "export const value: number")
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {}).exists).toBe(true)
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("invalidates removed declarations", () => {
+ const filename = path.join(directory, "dependency.d.ts")
+ fs.writeFileSync(filename, "export const value: number")
+ runTwoSlash.mockImplementation((code, lang, settings) => ({
+ declaration: settings.tsModule.sys.readFile(filename),
+ }))
+ runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ fs.unlinkSync(filename)
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {}).declaration).toBeUndefined()
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("validates directory listings and preserves omitted arguments", () => {
+ runTwoSlash.mockImplementation((code, lang, settings) => ({
+ files: settings.tsModule.sys.readDirectory(directory, undefined, undefined, ["**/*"])
+ .map(filename => path.resolve(filename)),
+ }))
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {}).files).toEqual([])
+ runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ expect(runTwoSlash).toHaveBeenCalledTimes(1)
+ const filename = path.join(directory, "dependency.d.ts")
+ fs.writeFileSync(filename, "export const value: number")
+ expect(runTwoSlashOnNode("import 'dependency'", fence("ts"), {}).files).toEqual([filename])
+ expect(runTwoSlash).toHaveBeenCalledTimes(2)
+ })
+
+ it("surfaces errors validating dependencies", () => {
+ const filename = path.join(directory, "dependency.d.ts")
+ fs.writeFileSync(filename, "export const value: number")
+ runTwoSlash.mockImplementation((code, lang, settings) => ({
+ declaration: settings.tsModule.sys.readFile(filename),
+ }))
+ runTwoSlashOnNode("import 'dependency'", fence("ts"), {})
+ jest.spyOn(fs, "statSync").mockImplementation(() => {
+ throw new Error("dependency access failed")
+ })
+ expect(() => runTwoSlashOnNode("import 'dependency'", fence("ts"), {})).toThrow(
+ "dependency access failed"
+ )
+ })
+ })
+})
+
+describe("Twoslash LRU pruning", () => {
+ const fs = require("fs")
+ const os = require("os")
+ const path = require("path")
+ const {
+ MAX_BYTES,
+ pruneTwoslashCache,
+ } = require("../../../../.github/actions/website-cache/prune-twoslash-cache")
+ let directory
+
+ beforeEach(() => {
+ directory = fs.mkdtempSync(
+ path.join(os.tmpdir(), "website-twoslash-cache-")
+ )
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ fs.rmSync(directory, { recursive: true, force: true })
+ })
+
+ const entry = (id, bytes, accessed) => {
+ const filename = path.join(
+ directory,
+ id.toString(16).padStart(40, "0") + ".json"
+ )
+ fs.writeFileSync(filename, "")
+ fs.truncateSync(filename, bytes)
+ fs.utimesSync(filename, accessed, accessed)
+ return filename
+ }
+
+ it("keeps an empty or absent cache", () => {
+ const empty = { bytes: 0, removed: 0, removedBytes: 0 }
+ expect(pruneTwoslashCache(directory)).toEqual(empty)
+ expect(pruneTwoslashCache(path.join(directory, "missing"))).toEqual(empty)
+ })
+
+ it("retains exactly 64 MiB without pruning", () => {
+ const file = entry(1, MAX_BYTES, 1)
+ expect(pruneTwoslashCache(directory)).toEqual({
+ bytes: MAX_BYTES,
+ removed: 0,
+ removedBytes: 0,
+ })
+ expect(fs.existsSync(file)).toBe(true)
+ })
+
+ it("evicts least-recently-used entries rather than oldest creations", () => {
+ const oldest = entry(1, MAX_BYTES / 2, 1)
+ const middle = entry(2, MAX_BYTES / 2, 2)
+ const newest = entry(3, MAX_BYTES / 2, 3)
+ fs.utimesSync(oldest, 4, 4)
+ expect(pruneTwoslashCache(directory)).toEqual({
+ bytes: MAX_BYTES,
+ removed: 1,
+ removedBytes: MAX_BYTES / 2,
+ })
+ expect(fs.existsSync(oldest)).toBe(true)
+ expect(fs.existsSync(middle)).toBe(false)
+ expect(fs.existsSync(newest)).toBe(true)
+ })
+
+ it("evicts as many entries as needed and drops oversized entries", () => {
+ const oldest = entry(1, MAX_BYTES, 1)
+ const middle = entry(2, MAX_BYTES, 2)
+ const newest = entry(3, MAX_BYTES + 1, 3)
+ expect(pruneTwoslashCache(directory)).toEqual({
+ bytes: 0,
+ removed: 3,
+ removedBytes: 3 * MAX_BYTES + 1,
+ })
+ expect([oldest, middle, newest].some(file => fs.existsSync(file))).toBe(
+ false
+ )
+ })
+
+ it("breaks timestamp ties deterministically", () => {
+ const first = entry(1, MAX_BYTES / 2, 1)
+ const second = entry(2, MAX_BYTES / 2, 1)
+ const third = entry(3, MAX_BYTES / 2, 1)
+ pruneTwoslashCache(directory)
+ expect(fs.existsSync(first)).toBe(false)
+ expect(fs.existsSync(second)).toBe(true)
+ expect(fs.existsSync(third)).toBe(true)
+ })
+
+ it("preserves usage timestamps across repeated pruning", () => {
+ const first = entry(1, MAX_BYTES / 2, 1)
+ entry(2, MAX_BYTES / 2, 2)
+ pruneTwoslashCache(directory)
+ expect(fs.statSync(first).mtimeMs).toBe(1000)
+ entry(3, MAX_BYTES / 2, 3)
+ pruneTwoslashCache(directory)
+ expect(fs.existsSync(first)).toBe(false)
+ })
+
+ it("rejects unexpected entries instead of deleting unrelated files", () => {
+ const filename = path.join(directory, "unexpected.txt")
+ fs.writeFileSync(filename, "preserve")
+ expect(() => pruneTwoslashCache(directory)).toThrow(
+ "Unexpected Twoslash cache entry"
+ )
+ expect(fs.readFileSync(filename, "utf8")).toBe("preserve")
+ })
+
+ it("surfaces filesystem failures", () => {
+ entry(1, MAX_BYTES + 1, 1)
+ jest.spyOn(fs, "unlinkSync").mockImplementationOnce(() => {
+ throw new Error("cannot prune cache")
+ })
+ expect(() => pruneTwoslashCache(directory)).toThrow("cannot prune cache")
+ })
+})
diff --git a/patches/gatsby-remark-shiki-twoslash@3.0.38.patch b/patches/gatsby-remark-shiki-twoslash@3.0.38.patch
index b398a78298ed..844d0f4ec4e8 100644
--- a/patches/gatsby-remark-shiki-twoslash@3.0.38.patch
+++ b/patches/gatsby-remark-shiki-twoslash@3.0.38.patch
@@ -2,15 +2,48 @@ diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 6036d987fc2889ec9bc230f7774013ae4f736dd9..0000000000000000000000000000000000000000
diff --git a/src/index.js b/src/index.js
-index e16676ef50b660d520a0efc0b73b68411db349af..bb21bc500a34ac31063774ba4ba133daed2c4f14 100755
+index e16676ef50b660d520a0efc0b73b68411db349af..5c40ade16dce3f83390819e053ee12e53e770ef4 100755
--- a/src/index.js
+++ b/src/index.js
-@@ -4,7 +4,15 @@ const { setupForFile, remarkVisitor } = require("remark-shiki-twoslash")
+@@ -2,9 +2,47 @@
+ const visit = require("unist-util-visit")
+ const { setupForFile, remarkVisitor } = require("remark-shiki-twoslash")
++const optimizedHighlighters = new WeakMap()
++
++const optimizeHighlighter = highlighter => {
++ if (optimizedHighlighters.has(highlighter)) {
++ return optimizedHighlighters.get(highlighter)
++ }
++
++ const tokensByCode = new Map()
++ const optimized = {
++ ...highlighter,
++ codeToThemedTokens(code, lang, theme) {
++ const key = JSON.stringify([code, lang, theme])
++ let tokens = tokensByCode.get(key)
++ if (tokens === undefined) {
++ // Only TSConfig's JSON renderer inspects TextMate scopes.
++ tokens = highlighter.codeToThemedTokens(code, lang, theme, {
++ includeExplanation: !!lang && lang.startsWith("json"),
++ })
++ if (tokensByCode.size === 256) {
++ tokensByCode.delete(tokensByCode.keys().next().value)
++ }
++ tokensByCode.set(key, tokens)
++ }
++ // Twoslash's renderer splits and annotates tokens in place.
++ return structuredClone(tokens)
++ },
++ }
++ optimizedHighlighters.set(highlighter, optimized)
++ return optimized
++}
++
const remarkShiki = async function ({ markdownAST }, userConfig) {
const {settings, highlighters} = await setupForFile(userConfig)
- visit(markdownAST, "code", remarkVisitor(highlighters, settings))
-+ const orig = remarkVisitor(highlighters, settings)
++ const orig = remarkVisitor(highlighters.map(optimizeHighlighter), settings)
+ /** @type {typeof orig} */
+ const visitor = (node) => {
+ orig(node)
@@ -21,4 +54,3 @@ index e16676ef50b660d520a0efc0b73b68411db349af..bb21bc500a34ac31063774ba4ba133da
}
module.exports = remarkShiki
-+
diff --git a/patches/remark-shiki-twoslash@3.1.3.patch b/patches/remark-shiki-twoslash@3.1.3.patch
new file mode 100644
index 000000000000..2b3d5adcc5e6
--- /dev/null
+++ b/patches/remark-shiki-twoslash@3.1.3.patch
@@ -0,0 +1,290 @@
+diff --git a/dist/remark-shiki-twoslash.cjs.development.js b/dist/remark-shiki-twoslash.cjs.development.js
+index efaed7711553c44c31abcaf53abab08b8780b248..782e3362bfd5be74d087c01571ce7e858697dec7 100644
+--- a/dist/remark-shiki-twoslash.cjs.development.js
++++ b/dist/remark-shiki-twoslash.cjs.development.js
+@@ -808,57 +808,7 @@ var cachedTwoslashCall = function cachedTwoslashCall(code, lang, settings) {
+ return shikiTwoslash.runTwoSlash(code, lang, settings);
+ }
+
+- var _require = require("crypto"),
+- createHash = _require.createHash;
+-
+- var _require2 = require("fs"),
+- readFileSync = _require2.readFileSync,
+- existsSync = _require2.existsSync,
+- mkdirSync = _require2.mkdirSync,
+- writeFileSync = _require2.writeFileSync;
+-
+- var _require3 = require("path"),
+- join = _require3.join;
+-
+- var shikiVersion = require('@typescript/twoslash/package.json').version;
+-
+- var tsVersion = require('typescript/package.json').version;
+-
+- var shasum = createHash("sha1");
+- var codeSha = shasum.update(code + "-" + shikiVersion + "-" + tsVersion).digest("hex");
+-
+- var getNmCache = function getNmCache() {
+- if (__dirname.includes("node_modules")) {
+- return join(__dirname.split("node_modules")[0], "node_modules", ".cache", "twoslash");
+- } else {
+- return join(__dirname, "..", "..", ".cache", "twoslash");
+- }
+- };
+-
+- var getPnpCache = function getPnpCache() {
+- try {
+- var pnp = require("pnpapi");
+-
+- return join(pnp.getPackageInformation(pnp.topLevel).packageLocation, "node_modules", ".cache", "twoslash");
+- } catch (error) {
+- return getNmCache();
+- }
+- };
+-
+- var cacheRoot = process.versions.pnp ? getPnpCache() : getNmCache();
+- var cachePath = join(cacheRoot, codeSha + ".json");
+-
+- if (existsSync(cachePath)) {
+- if (process.env.debug) console.log("Using cached twoslash results from " + cachePath);
+- return JSON.parse(readFileSync(cachePath, "utf8"));
+- } else {
+- var results = shikiTwoslash.runTwoSlash(code, lang, settings);
+- if (!existsSync(cacheRoot)) mkdirSync(cacheRoot, {
+- recursive: true
+- });
+- writeFileSync(cachePath, JSON.stringify(results), "utf8");
+- return results;
+- }
++ return require("./twoslash-cache.js")(code, lang, settings, shikiTwoslash.runTwoSlash);
+ };
+
+ var addIncludes = function addIncludes(map, name, code) {
+diff --git a/dist/remark-shiki-twoslash.cjs.production.min.js b/dist/remark-shiki-twoslash.cjs.production.min.js
+index 9ee26741d08d555a0ef3c0e0917ef0d6342020a5..2df325d745cc341393c3c38c72ad29342f2631b7 100644
+--- a/dist/remark-shiki-twoslash.cjs.production.min.js
++++ b/dist/remark-shiki-twoslash.cjs.production.min.js
+@@ -1,2 +1,3 @@
+-"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("@typescript/twoslash"),r=(e=require("unist-util-visit"))&&"object"==typeof e&&"default"in e?e.default:e,n=require("fenceparser"),o=require("shiki"),i=require("shiki-twoslash");function a(e,t,r,n,o,i,a){try{var s=e[i](a),c=s.value}catch(e){return void r(e)}s.done?t(c):Promise.resolve(c).then(n,o)}function s(e){return function(){var t=this,r=arguments;return new Promise((function(n,o){var i=e.apply(t,r);function s(e){a(i,n,o,s,c,"next",e)}function c(e){a(i,n,o,s,c,"throw",e)}s(void 0)}))}}function c(){return(c=Object.assign||function(e){for(var t=1;t=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=r.call(i,"catchLoc"),c=r.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),k(r),l}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var o=n.arg;k(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,r){return this.delegate={iterator:L(e),resultName:t,nextLoc:r},"next"===this.method&&(this.arg=void 0),l}},e}(e.exports);try{regeneratorRuntime=t}catch(e){Function("r","regeneratorRuntime = r")(t)}}));function f(e){return e.replace(//g,">")}var p=function(e,r,n){var o="undefined"!=typeof self&&void 0!==self.WorkerGlobalScope||"undefined"!=typeof window&&void 0!==window.document&&"undefined"!=typeof fetch,i="undefined"!=typeof jest,a=o||i?function(e){}:console.error,s=""+n+"
";"object"!=typeof n?(s=String(n),a("### Unexpected error:"),a(n)):n instanceof t.TwoslashError?(s=function(e){return"\n"+f(e.title)+"
\n"+f(e.description).replace(/(?:\r\n|\r|\n)/g,"
")+"
\n"+f(e.recommendation).replace(/(?:\r\n|\r|\n)/g,"
")+"\n"}(n),a("### Twoslash error: "+n.title),a(n.description),a(n.recommendation),a("\n### Code Sample"),a(e)):n instanceof Error&&(s=function(e){return""+e.message.split("## Code")[0]+"
"}(n),a("### Unexpected error:"),a(n));var c="\n Twoslash failure
\n "+s+"
Raising Code:
"+f(e)+"
";r.type="html",r.value=''+c+"
",r.children=[]},h=new Map;function d(e,t,r,n,o){var a,s={json5:"json"};if(s[t.lang]&&(t.lang=s[t.lang]),"twoslash"===t.lang){if(!t.meta.include||"string"!=typeof t.meta.include)throw new Error("A twoslash code block needs a pragma like 'twoslash include [name]'");!function(e,t,r){var n=[];r.split("\n").forEach((function(r,o){var i=r.trim();if(i.startsWith("// - ")){var a=i.split("// - ")[1].split(" ")[0];e.set(t+"-"+a,n.join("\n"))}else n.push(r)})),e.set(t,n.join("\n"))}(h,t.meta.include,e),a=o.wrapFragments?'':""}else a=r.map((function(r){var a=r.customName.split("/").pop().replace(".json","");return i.renderCodeToHTML(e,t.lang,t.meta,c({themeName:a},o),r,n)})).join("\n"),r.length>1&&o.wrapFragments&&(a=''+a+"
");return a}var v=function(e,t,r){var n=t.lang;if(void 0===r&&(r={}),("undefined"==typeof process||!process.env||!process.env.TWOSLASH_DISABLE)&&t.meta.twoslash)return function(e,t,r){if("undefined"!=typeof self&&void 0!==self.WorkerGlobalScope||"undefined"!=typeof window&&void 0!==window.document&&"undefined"!=typeof fetch)return i.runTwoSlash(e,t,r);var n=require("crypto").createHash,o=require("fs"),a=o.readFileSync,s=o.existsSync,c=o.mkdirSync,u=o.writeFileSync,l=require("path").join,f=require("@typescript/twoslash/package.json").version,p=require("typescript/package.json").version,h=n("sha1").update(e+"-"+f+"-"+p).digest("hex"),d=function(){return __dirname.includes("node_modules")?l(__dirname.split("node_modules")[0],"node_modules",".cache","twoslash"):l(__dirname,"..","..",".cache","twoslash")},v=process.versions.pnp?function(){try{var e=require("pnpapi");return l(e.getPackageInformation(e.topLevel).packageLocation,"node_modules",".cache","twoslash")}catch(e){return d()}}():d(),m=l(v,h+".json");if(s(m))return process.env.debug&&console.log("Using cached twoslash results from "+m),JSON.parse(a(m,"utf8"));var g=i.runTwoSlash(e,t,r);return s(v)||c(v,{recursive:!0}),u(m,JSON.stringify(g),"utf8"),g}(function(e,t){for(var r,n=/\/\/ @include: (.*)$/gm,o=[];null!==(r=n.exec(t));){r.index===n.lastIndex&&n.lastIndex++;var i=r[1],a=e.get(i);if(!a){var s="Could not find an include with the key: '"+i+"'.\nThere is: "+Array.from(e.keys())+".";throw new Error(s)}o.push([r.index,r[0].length,a])}var c=t.toString();return o.reverse().forEach((function(e){c=c.substring(0,e[0])+e[2]+c.substring(e[0]+e[1])})),c}(h,e),n,r)},m=new Map,g=function(e){return Promise.all((e.themes||(e.theme?[e.theme]:["light-plus"])).map(function(){var t=s(l.mark((function t(r){var n,i;return l.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=r.name||r,t.next=3,o.getHighlighter(c({},e,{theme:r,themes:void 0}));case 3:return(i=t.sent).customName=n,t.abrupt("return",i);case 6:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()))},y=function(){return h.clear()},w=function(e){var t,r=n.lex(e),o=r[0],i=r.slice(1);if("twoslash"===o){var a=i.indexOf("include");-1!==a&&i.splice(a+1,0,"=")}var s=null!=(t=n.parse(i))?t:{};return{lang:(o||"").toString(),meta:s}},x=function(e,r){return void 0===r&&(r={}),function(n){var o=n.value,i=void 0;try{i=w([n.lang,n.meta].filter(Boolean).join(" "))}catch(e){var a=new t.TwoslashError("Codefence error","Could not parse the codefence for this code sample","It's usually an unclosed string",o);return p(o,n,a)}if(!(Object.keys(i.meta).filter((function(e){return(r.ignoreCodeblocksWithCodefenceMeta||[]).includes(e)})).length>0)){var s;try{s=n.twoslash||v(o,i,r)}catch(e){var c=process&&process.env&&!!process.env.CI,u="undefined"==typeof jest;if(c&&u||r.alwayRaiseForTwoslashExceptions)throw e;return p(o,n,e)}s&&(n.value=s.code,n.lang=s.extension,n.twoslash=s);var l=d(n.value,i,e,s,r);n.type="html",n.value=l,n.children=[]}}},b=function(){var e=s(l.mark((function e(t){return l.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return void 0===t&&(t={}),y(),m.has(t)||m.set(t,g(t)),e.next=5,m.get(t);case 5:return e.abrupt("return",{settings:t,highlighters:e.sent});case 7:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}();exports.default=function(e){return void 0===e&&(e={}),m.has(e)||m.set(e,g(e)),function(){var t=s(l.mark((function t(n){var o;return l.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,m.get(e);case 2:o=t.sent,y(),r(n,"code",x(o,e));case 5:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()},exports.highlightersFromSettings=g,exports.remarkVisitor=x,exports.runTwoSlashOnNode=v,exports.setupForFile=b,exports.transformAttributesToHTML=function(e,t,r,n){var o=w(t),i=v(e,o,n);return d(i&&i.code||e,o,r,i,n)};
++"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("@typescript/twoslash"),r=(e=require("unist-util-visit"))&&"object"==typeof e&&"default"in e?e.default:e,n=require("fenceparser"),o=require("shiki"),i=require("shiki-twoslash");function a(e,t,r,n,o,i,a){try{var s=e[i](a),c=s.value}catch(e){return void r(e)}s.done?t(c):Promise.resolve(c).then(n,o)}function s(e){return function(){var t=this,r=arguments;return new Promise((function(n,o){var i=e.apply(t,r);function s(e){a(i,n,o,s,c,"next",e)}function c(e){a(i,n,o,s,c,"throw",e)}s(void 0)}))}}function c(){return(c=Object.assign||function(e){for(var t=1;t=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=r.call(i,"catchLoc"),c=r.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),k(r),l}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var o=n.arg;k(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,r){return this.delegate={iterator:L(e),resultName:t,nextLoc:r},"next"===this.method&&(this.arg=void 0),l}},e}(e.exports);try{regeneratorRuntime=t}catch(e){Function("r","regeneratorRuntime = r")(t)}}));function f(e){return e.replace(//g,">")}var p=function(e,r,n){var o="undefined"!=typeof self&&void 0!==self.WorkerGlobalScope||"undefined"!=typeof window&&void 0!==window.document&&"undefined"!=typeof fetch,i="undefined"!=typeof jest,a=o||i?function(e){}:console.error,s=""+n+"
";"object"!=typeof n?(s=String(n),a("### Unexpected error:"),a(n)):n instanceof t.TwoslashError?(s=function(e){return"\n"+f(e.title)+"
\n"+f(e.description).replace(/(?:\r\n|\r|\n)/g,"
")+"
\n"+f(e.recommendation).replace(/(?:\r\n|\r|\n)/g,"
")+"\n"}(n),a("### Twoslash error: "+n.title),a(n.description),a(n.recommendation),a("\n### Code Sample"),a(e)):n instanceof Error&&(s=function(e){return""+e.message.split("## Code")[0]+"
"}(n),a("### Unexpected error:"),a(n));var c="\n Twoslash failure
\n "+s+"
Raising Code:
"+f(e)+"
";r.type="html",r.value=''+c+"
",r.children=[]},h=new Map;function d(e,t,r,n,o){var a,s={json5:"json"};if(s[t.lang]&&(t.lang=s[t.lang]),"twoslash"===t.lang){if(!t.meta.include||"string"!=typeof t.meta.include)throw new Error("A twoslash code block needs a pragma like 'twoslash include [name]'");!function(e,t,r){var n=[];r.split("\n").forEach((function(r,o){var i=r.trim();if(i.startsWith("// - ")){var a=i.split("// - ")[1].split(" ")[0];e.set(t+"-"+a,n.join("\n"))}else n.push(r)})),e.set(t,n.join("\n"))}(h,t.meta.include,e),a=o.wrapFragments?'':""}else a=r.map((function(r){var a=r.customName.split("/").pop().replace(".json","");return i.renderCodeToHTML(e,t.lang,t.meta,c({themeName:a},o),r,n)})).join("\n"),r.length>1&&o.wrapFragments&&(a=''+a+"
");return a}var v=function(e,t,r){var n=t.lang;if(void 0===r&&(r={}),("undefined"==typeof process||!process.env||!process.env.TWOSLASH_DISABLE)&&t.meta.twoslash)return function(e,t,r){if("undefined"!=typeof self&&void 0!==self.WorkerGlobalScope||"undefined"!=typeof window&&void 0!==window.document&&"undefined"!=typeof fetch)return i.runTwoSlash(e,t,r);return require("./twoslash-cache.js")(e, t, r, i.runTwoSlash);
++}(function(e,t){for(var r,n=/\/\/ @include: (.*)$/gm,o=[];null!==(r=n.exec(t));){r.index===n.lastIndex&&n.lastIndex++;var i=r[1],a=e.get(i);if(!a){var s="Could not find an include with the key: '"+i+"'.\nThere is: "+Array.from(e.keys())+".";throw new Error(s)}o.push([r.index,r[0].length,a])}var c=t.toString();return o.reverse().forEach((function(e){c=c.substring(0,e[0])+e[2]+c.substring(e[0]+e[1])})),c}(h,e),n,r)},m=new Map,g=function(e){return Promise.all((e.themes||(e.theme?[e.theme]:["light-plus"])).map(function(){var t=s(l.mark((function t(r){var n,i;return l.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=r.name||r,t.next=3,o.getHighlighter(c({},e,{theme:r,themes:void 0}));case 3:return(i=t.sent).customName=n,t.abrupt("return",i);case 6:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()))},y=function(){return h.clear()},w=function(e){var t,r=n.lex(e),o=r[0],i=r.slice(1);if("twoslash"===o){var a=i.indexOf("include");-1!==a&&i.splice(a+1,0,"=")}var s=null!=(t=n.parse(i))?t:{};return{lang:(o||"").toString(),meta:s}},x=function(e,r){return void 0===r&&(r={}),function(n){var o=n.value,i=void 0;try{i=w([n.lang,n.meta].filter(Boolean).join(" "))}catch(e){var a=new t.TwoslashError("Codefence error","Could not parse the codefence for this code sample","It's usually an unclosed string",o);return p(o,n,a)}if(!(Object.keys(i.meta).filter((function(e){return(r.ignoreCodeblocksWithCodefenceMeta||[]).includes(e)})).length>0)){var s;try{s=n.twoslash||v(o,i,r)}catch(e){var c=process&&process.env&&!!process.env.CI,u="undefined"==typeof jest;if(c&&u||r.alwayRaiseForTwoslashExceptions)throw e;return p(o,n,e)}s&&(n.value=s.code,n.lang=s.extension,n.twoslash=s);var l=d(n.value,i,e,s,r);n.type="html",n.value=l,n.children=[]}}},b=function(){var e=s(l.mark((function e(t){return l.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return void 0===t&&(t={}),y(),m.has(t)||m.set(t,g(t)),e.next=5,m.get(t);case 5:return e.abrupt("return",{settings:t,highlighters:e.sent});case 7:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}();exports.default=function(e){return void 0===e&&(e={}),m.has(e)||m.set(e,g(e)),function(){var t=s(l.mark((function t(n){var o;return l.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,m.get(e);case 2:o=t.sent,y(),r(n,"code",x(o,e));case 5:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}()},exports.highlightersFromSettings=g,exports.remarkVisitor=x,exports.runTwoSlashOnNode=v,exports.setupForFile=b,exports.transformAttributesToHTML=function(e,t,r,n){var o=w(t),i=v(e,o,n);return d(i&&i.code||e,o,r,i,n)};
+ //# sourceMappingURL=remark-shiki-twoslash.cjs.production.min.js.map
+diff --git a/dist/remark-shiki-twoslash.esm.js b/dist/remark-shiki-twoslash.esm.js
+index 7d005c0ae14fff368e1808ffae3ecef5dab756b9..cabf08a27259ac62316f3459c95ecd5bf9ae0082 100644
+--- a/dist/remark-shiki-twoslash.esm.js
++++ b/dist/remark-shiki-twoslash.esm.js
+@@ -802,57 +802,7 @@ var cachedTwoslashCall = function cachedTwoslashCall(code, lang, settings) {
+ return runTwoSlash(code, lang, settings);
+ }
+
+- var _require = require("crypto"),
+- createHash = _require.createHash;
+-
+- var _require2 = require("fs"),
+- readFileSync = _require2.readFileSync,
+- existsSync = _require2.existsSync,
+- mkdirSync = _require2.mkdirSync,
+- writeFileSync = _require2.writeFileSync;
+-
+- var _require3 = require("path"),
+- join = _require3.join;
+-
+- var shikiVersion = require('@typescript/twoslash/package.json').version;
+-
+- var tsVersion = require('typescript/package.json').version;
+-
+- var shasum = createHash("sha1");
+- var codeSha = shasum.update(code + "-" + shikiVersion + "-" + tsVersion).digest("hex");
+-
+- var getNmCache = function getNmCache() {
+- if (__dirname.includes("node_modules")) {
+- return join(__dirname.split("node_modules")[0], "node_modules", ".cache", "twoslash");
+- } else {
+- return join(__dirname, "..", "..", ".cache", "twoslash");
+- }
+- };
+-
+- var getPnpCache = function getPnpCache() {
+- try {
+- var pnp = require("pnpapi");
+-
+- return join(pnp.getPackageInformation(pnp.topLevel).packageLocation, "node_modules", ".cache", "twoslash");
+- } catch (error) {
+- return getNmCache();
+- }
+- };
+-
+- var cacheRoot = process.versions.pnp ? getPnpCache() : getNmCache();
+- var cachePath = join(cacheRoot, codeSha + ".json");
+-
+- if (existsSync(cachePath)) {
+- if (process.env.debug) console.log("Using cached twoslash results from " + cachePath);
+- return JSON.parse(readFileSync(cachePath, "utf8"));
+- } else {
+- var results = runTwoSlash(code, lang, settings);
+- if (!existsSync(cacheRoot)) mkdirSync(cacheRoot, {
+- recursive: true
+- });
+- writeFileSync(cachePath, JSON.stringify(results), "utf8");
+- return results;
+- }
++ return require("./twoslash-cache.js")(code, lang, settings, runTwoSlash);
+ };
+
+ var addIncludes = function addIncludes(map, name, code) {
+diff --git a/dist/twoslash-cache.js b/dist/twoslash-cache.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..7ae5d5ad41e6284ba9eefcfbfb86235800c94a9d
+--- /dev/null
++++ b/dist/twoslash-cache.js
+@@ -0,0 +1,149 @@
++const fs = require("fs")
++const path = require("path")
++const { createHash } = require("crypto")
++const { createRequire } = require("module")
++
++const shikiRequire = createRequire(require.resolve("shiki-twoslash"))
++const twoslashRequire = createRequire(shikiRequire.resolve("@typescript/twoslash"))
++const ts = twoslashRequire("typescript")
++const readTokens = new Map()
++let implementation
++
++const digest = value => createHash("sha1").update(value).digest("hex")
++const portable = filename =>
++ path.isAbsolute(filename) ? path.relative(process.cwd(), filename) : filename
++
++function compilerImplementation() {
++ if (implementation) return implementation
++ const hash = createHash("sha1")
++ hash.update(fs.readFileSync(__filename))
++ for (const [name, resolver] of [
++ ["shiki-twoslash", require],
++ ["@typescript/twoslash", shikiRequire],
++ ["@typescript/vfs", twoslashRequire],
++ ]) {
++ hash.update(name).update(fs.readFileSync(resolver.resolve(name + "/package.json")))
++ const directory = path.join(
++ path.dirname(resolver.resolve(name + "/package.json")),
++ "dist"
++ )
++ for (const filename of fs.readdirSync(directory).filter(f => f.endsWith(".js")).sort()) {
++ hash.update(filename).update(fs.readFileSync(path.join(directory, filename)))
++ }
++ }
++ for (const name of ["typescript", "lz-string"]) {
++ hash.update(name).update(fs.readFileSync(twoslashRequire.resolve(name)))
++ }
++ implementation = hash.digest("hex")
++ return implementation
++}
++
++function rememberRead(filename, contents, stat) {
++ filename = path.resolve(filename)
++ const token = contents === undefined ? null : digest(contents)
++ if (stat) {
++ const signature = [stat.size, stat.mtimeMs, stat.ctimeMs, stat.ino].join(":")
++ readTokens.delete(filename)
++ readTokens.set(filename, { signature, token })
++ if (readTokens.size > 256) readTokens.delete(readTokens.keys().next().value)
++ }
++ return token
++}
++
++function readToken(filename) {
++ filename = path.resolve(filename)
++ const stat = fs.statSync(filename, { throwIfNoEntry: false })
++ if (!stat) return null
++ const signature = [stat.size, stat.mtimeMs, stat.ctimeMs, stat.ino].join(":")
++ const cached = readTokens.get(filename)
++ if (cached && cached.signature === signature) {
++ readTokens.delete(filename)
++ readTokens.set(filename, cached)
++ return cached.token
++ }
++ return rememberRead(filename, ts.sys.readFile(filename), stat)
++}
++
++function inputToken(value) {
++ if (Array.isArray(value)) return JSON.stringify(value.map(portable))
++ if (typeof value === "string") return portable(value)
++ return value
++}
++
++function inputsMatch(inputs) {
++ return inputs.every(input => {
++ const args = input.args.map((arg, i) =>
++ input.undefinedArgs.includes(i) ? undefined : arg
++ )
++ args[0] = path.resolve(args[0])
++ const token = input.method === "readFile"
++ ? readToken(args[0])
++ : inputToken(ts.sys[input.method](...args))
++ return token === input.token
++ })
++}
++
++function compile(code, lang, settings, runTwoSlash) {
++ const inputs = new Map()
++ const sys = { ...ts.sys }
++ // Misses and directory probes matter when new typings become available.
++ for (const method of [
++ "readFile", "fileExists", "directoryExists", "getDirectories",
++ "readDirectory", "realpath", "resolvePath",
++ ]) {
++ if (!ts.sys[method]) continue
++ sys[method] = (...args) => {
++ const value = ts.sys[method](...args)
++ const input = {
++ method,
++ args: args.map((arg, i) => i === 0 ? portable(path.resolve(arg)) : arg),
++ undefinedArgs: args.flatMap((arg, i) => arg === undefined ? [i] : []),
++ }
++ const key = JSON.stringify(input)
++ input.token = method === "readFile"
++ ? rememberRead(args[0], value, fs.statSync(args[0], { throwIfNoEntry: false }))
++ : inputToken(value)
++ inputs.set(key, input)
++ return value
++ }
++ }
++ const options = { ...settings, tsModule: { ...ts, sys } }
++ const results = runTwoSlash(code, lang, options)
++ return { results, inputs: [...inputs.values()] }
++}
++
++function cacheDirectory() {
++ if (process.versions.pnp) {
++ const pnp = require("pnpapi")
++ return path.join(
++ pnp.getPackageInformation(pnp.topLevel).packageLocation,
++ "node_modules", ".cache", "twoslash"
++ )
++ }
++ return __dirname.includes("node_modules")
++ ? path.join(__dirname.split("node_modules")[0], "node_modules", ".cache", "twoslash")
++ : path.join(__dirname, "..", "..", ".cache", "twoslash")
++}
++
++module.exports = function cachedTwoslash(code, lang, settings, runTwoSlash) {
++ if (settings.tsModule || settings.fsMap || settings.customTransformers || settings.lzstringModule) {
++ if (process.env.debug) console.log("Skipping Twoslash cache for custom compiler inputs")
++ return runTwoSlash(code, lang, settings)
++ }
++ const key = digest(JSON.stringify([code, lang, settings, compilerImplementation()]))
++ const directory = cacheDirectory()
++ const filename = path.join(directory, key + ".json")
++ if (fs.existsSync(filename)) {
++ const cached = JSON.parse(fs.readFileSync(filename, "utf8"))
++ if (inputsMatch(cached.inputs)) {
++ if (process.env.debug) console.log("Using cached twoslash results from " + filename)
++ const now = new Date()
++ fs.utimesSync(filename, now, now)
++ return cached.results
++ }
++ }
++ const cached = compile(code, lang, settings, runTwoSlash)
++ fs.mkdirSync(directory, { recursive: true })
++ fs.writeFileSync(filename, JSON.stringify(cached), "utf8")
++ return cached.results
++}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0c8438854935..fa0560b58e97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,8 +21,11 @@ overrides:
patchedDependencies:
gatsby-remark-shiki-twoslash@3.0.38:
- hash: 5584d3c994349e1a6796ab8e22811fc912fad257e9ad818930836c458ab23640
+ hash: 3aa0dc2d9c0fd3921986acbadfef7a10df948fb30d02590f86cecab9b91800bf
path: patches/gatsby-remark-shiki-twoslash@3.0.38.patch
+ remark-shiki-twoslash@3.1.3:
+ hash: 5bd6570cdb97b323924ced9d2e68aae4b5f7bea40b13ee9a3ca712e774883c30
+ path: patches/remark-shiki-twoslash@3.1.3.patch
importers:
@@ -81,7 +84,7 @@ importers:
version: 13.0.2
remark-shiki-twoslash:
specifier: ^3.1.3
- version: 3.1.3(typescript@6.0.2)
+ version: 3.1.3(patch_hash=5bd6570cdb97b323924ced9d2e68aae4b5f7bea40b13ee9a3ca712e774883c30)(typescript@6.0.2)
packages/ata:
dependencies:
@@ -395,7 +398,7 @@ importers:
version: 6.16.0(gatsby@5.16.1(babel-eslint@10.1.0(eslint@8.57.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.41.0)(typescript@6.0.2))
gatsby-remark-shiki-twoslash:
specifier: ^3.0.38
- version: 3.0.38(patch_hash=5584d3c994349e1a6796ab8e22811fc912fad257e9ad818930836c458ab23640)
+ version: 3.0.38(patch_hash=3aa0dc2d9c0fd3921986acbadfef7a10df948fb30d02590f86cecab9b91800bf)
gatsby-remark-smartypants:
specifier: ^6.16.0
version: 6.16.0(gatsby@5.16.1(babel-eslint@10.1.0(eslint@8.57.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.41.0)(typescript@6.0.2))
@@ -16338,9 +16341,9 @@ snapshots:
lodash: 4.17.23
unist-util-visit: 2.0.3
- gatsby-remark-shiki-twoslash@3.0.38(patch_hash=5584d3c994349e1a6796ab8e22811fc912fad257e9ad818930836c458ab23640):
+ gatsby-remark-shiki-twoslash@3.0.38(patch_hash=3aa0dc2d9c0fd3921986acbadfef7a10df948fb30d02590f86cecab9b91800bf):
dependencies:
- remark-shiki-twoslash: 3.1.3(typescript@6.0.2)
+ remark-shiki-twoslash: 3.1.3(patch_hash=5bd6570cdb97b323924ced9d2e68aae4b5f7bea40b13ee9a3ca712e774883c30)(typescript@6.0.2)
typescript: 6.0.2
unist-util-visit: 2.0.3
@@ -20181,7 +20184,7 @@ snapshots:
dependencies:
mdast-util-to-nlcst: 4.0.1
- remark-shiki-twoslash@3.1.3(typescript@6.0.2):
+ remark-shiki-twoslash@3.1.3(patch_hash=5bd6570cdb97b323924ced9d2e68aae4b5f7bea40b13ee9a3ca712e774883c30)(typescript@6.0.2):
dependencies:
'@types/unist': 2.0.11
'@typescript/twoslash': link:packages/ts-twoslasher
@@ -21433,7 +21436,7 @@ snapshots:
hast-util-to-html: 7.1.3
mdast-util-to-hast: 10.2.0
remark: 13.0.0
- remark-shiki-twoslash: 3.1.3(typescript@6.0.2)
+ remark-shiki-twoslash: 3.1.3(patch_hash=5bd6570cdb97b323924ced9d2e68aae4b5f7bea40b13ee9a3ca712e774883c30)(typescript@6.0.2)
unist-util-visit: 3.1.0
transitivePeerDependencies:
- supports-color