Repository navigation
fix(deps): inline comark in nitro and move to nitropack 2.13.5 #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
efb48e0
fix(deps): patch nitropack so comark plugins stay external on Nuxt 4.6
larbish e0799d0
Update .github/workflows/ci.yml
larbish 600ea8c
Merge branch 'main' into fix/playground
larbish a88dace
inline comark in nitro and drop the nitropack patch
larbish 4c40867
Merge branch 'main' into fix/playground
larbish File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { readdirSync, readFileSync } from 'node:fs' | ||
| import { isBuiltin } from 'node:module' | ||
| import { join, relative } from 'node:path' | ||
| import { fileURLToPath, pathToFileURL } from 'node:url' | ||
| import { init, parse } from 'es-module-lexer' | ||
| import { resolveModuleURL } from 'exsolve' | ||
| import { expect, it } from 'vitest' | ||
|
|
||
| const serverDir = fileURLToPath(new URL('../../playground/.output/server/', import.meta.url)) | ||
|
|
||
| // Optional peers imported behind a try/catch, absent unless a site installs them. | ||
| const OPTIONAL_IMPORTS = new Set([ | ||
| // `@nuxtjs/mcp-toolkit` Code Mode. | ||
| 'secure-exec', | ||
| ]) | ||
|
|
||
| /** The bundled chunks: the traced `node_modules/` are third-party files Nitro copied as they are. */ | ||
| function bundledFiles(): string[] { | ||
| return readdirSync(serverDir, { withFileTypes: true, recursive: true }) | ||
| .filter((entry) => entry.isFile() && entry.name.endsWith('.mjs')) | ||
| .map((entry) => join(entry.parentPath, entry.name)) | ||
| .filter((file) => !relative(serverDir, file).startsWith('node_modules')) | ||
| } | ||
|
|
||
| // A lazy chunk (a component, a route) only fails once requested, so the route crawl alone can miss it. | ||
| it('every import in the server bundle resolves inside the build output', async () => { | ||
| await init() | ||
| const files = bundledFiles() | ||
| const broken: string[] = [] | ||
| let checked = 0 | ||
|
|
||
| for (const file of files) { | ||
| const [imports] = parse(readFileSync(file, 'utf8')) | ||
| for (const entry of imports) { | ||
| // `import.meta`, computed dynamic imports and template globs name no single module. | ||
| const specifier = entry.type === 'dynamic' && entry.glob ? undefined : entry.specifier | ||
| if (!specifier || isBuiltin(specifier) || OPTIONAL_IMPORTS.has(specifier)) continue | ||
| checked++ | ||
| const resolved = resolveModuleURL(specifier, { from: pathToFileURL(file), try: true }) | ||
| // Resolving into the repo's own `node_modules` works locally but not once deployed. | ||
| if (!resolved?.startsWith(pathToFileURL(serverDir).href)) { | ||
| broken.push(`${relative(serverDir, file)} imports ${specifier}`) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Guards against a vacuous pass, e.g. the lexer reporting no specifiers after an API change. | ||
| expect(checked).toBeGreaterThan(files.length) | ||
| expect(broken).toEqual([]) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { describe, expect, inject, it } from 'vitest' | ||
|
|
||
| const baseURL = inject('baseURL') | ||
|
|
||
| /** Fetches a site path, explaining a failure with the message Nitro attaches to its JSON errors. */ | ||
| async function get(path: string, headers?: HeadersInit) { | ||
| const response = await fetch(new URL(path, baseURL), { headers }) | ||
| let failure = `${path} returned ${response.status}` | ||
| if (response.status >= 400) { | ||
| const error = await fetch(new URL(path, baseURL), { headers: { accept: 'application/json' } }) | ||
| .then((res) => res.json() as Promise<{ message?: string }>) | ||
| .catch(() => undefined) | ||
| if (error?.message) failure += `: ${error.message}` | ||
| } | ||
| return { response, failure } | ||
| } | ||
|
|
||
| /** Absolute URLs point at the production site: keep the path and query only. */ | ||
| function toPath(url: string): string { | ||
| const { pathname, search } = new URL(url.replaceAll('&', '&')) | ||
| return pathname + search | ||
| } | ||
|
|
||
| // The sitemap lists every content page plus the Vue pages, so a new page is covered without touching this file. | ||
| const sitemap = await fetch(new URL('/sitemap.xml', baseURL)).then((res) => res.text()) | ||
| const pages = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => toPath(match[1]!)) | ||
|
|
||
| describe('pages', () => { | ||
| it('lists the landing page and the docs', () => { | ||
| expect(pages).toContain('/') | ||
| expect(pages.length).toBeGreaterThan(5) | ||
| }) | ||
|
|
||
| it.each(pages)('%s renders, OG image included', async (path) => { | ||
| const { response, failure } = await get(path) | ||
| expect(response.status, failure).toBe(200) | ||
| expect(response.headers.get('content-type')).toContain('text/html') | ||
|
|
||
| const ogImage = (await response.text()).match(/<meta property="og:image" content="([^"]+)"/)?.[1] | ||
| if (ogImage) { | ||
| const og = await get(toPath(ogImage)) | ||
| expect(og.response.status, og.failure).toBe(200) | ||
| expect(og.response.headers.get('content-type')).toBe('image/png') | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| describe('agent routes', () => { | ||
| it.each([ | ||
| '/raw/index.md', | ||
| '/raw/getting-started/introduction.md', | ||
| '/llms.txt', | ||
| '/llms-full.txt', | ||
| '/sitemap.md', | ||
| '/rss.xml', | ||
| '/openapi.json', | ||
| '/robots.txt', | ||
| ])('%s responds', async (path) => { | ||
| const { response, failure } = await get(path) | ||
| expect(response.status, failure).toBe(200) | ||
| }) | ||
|
|
||
| // Cached routes answer with a redirect to the `/raw` twin rather than a rewrite. | ||
| it('negotiates markdown on a page URL', async () => { | ||
| const { response, failure } = await get('/getting-started/introduction', { accept: 'text/markdown' }) | ||
| expect(response.status, failure).toBe(200) | ||
| expect(response.headers.get('content-type')).toContain('text/markdown') | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { type ChildProcess, spawn } from 'node:child_process' | ||
| import { existsSync } from 'node:fs' | ||
| import { type AddressInfo, createServer } from 'node:net' | ||
| import { fileURLToPath } from 'node:url' | ||
| import type { TestProject } from 'vitest/node' | ||
|
|
||
| declare module 'vitest' { | ||
| export interface ProvidedContext { | ||
| baseURL: string | ||
| } | ||
| } | ||
|
|
||
| const entry = fileURLToPath(new URL('../../playground/.output/server/index.mjs', import.meta.url)) | ||
|
|
||
| function freePort(): Promise<number> { | ||
| return new Promise((resolve, reject) => { | ||
| const probe = createServer() | ||
| probe.on('error', reject) | ||
| probe.listen(0, '127.0.0.1', () => { | ||
| const { port } = probe.address() as AddressInfo | ||
| probe.close(() => resolve(port)) | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| async function waitForServer(url: string, server: ChildProcess) { | ||
| const deadline = Date.now() + 30_000 | ||
| while (Date.now() < deadline) { | ||
| if (server.exitCode !== null) { | ||
| throw new Error(`The playground server exited with code ${server.exitCode}`) | ||
| } | ||
| try { | ||
| await fetch(url) | ||
| return | ||
| } catch { | ||
| await new Promise((resolve) => setTimeout(resolve, 200)) | ||
| } | ||
| } | ||
| throw new Error(`The playground server did not answer on ${url} within 30s`) | ||
| } | ||
|
|
||
| /** Boots the built playground once for the whole e2e project. */ | ||
| export default async function setup(project: TestProject) { | ||
| if (!existsSync(entry)) { | ||
| throw new Error('No playground build found: run `pnpm build` before `pnpm test:e2e`.') | ||
| } | ||
|
|
||
| const port = await freePort() | ||
| const server = spawn(process.execPath, [entry], { | ||
| env: { ...process.env, HOST: '127.0.0.1', PORT: String(port) }, | ||
| // Server logs land in the test output: the `[request error]` line is what explains a failing route. | ||
| stdio: ['ignore', 'inherit', 'inherit'], | ||
| }) | ||
|
|
||
| const baseURL = `http://127.0.0.1:${port}` | ||
| try { | ||
| await waitForServer(`${baseURL}/robots.txt`, server) | ||
| } catch (error) { | ||
| server.kill() | ||
| throw error | ||
| } | ||
| project.provide('baseURL', baseURL) | ||
|
|
||
| return () => { | ||
| server.kill() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.