diff --git a/.changeset/lazy-pandas-repeat.md b/.changeset/lazy-pandas-repeat.md new file mode 100644 index 00000000000..bc623b022a7 --- /dev/null +++ b/.changeset/lazy-pandas-repeat.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/router': patch +--- + +chore: trim dependencies by inlining small single-use helpers diff --git a/.changeset/warm-crabs-smile.md b/.changeset/warm-crabs-smile.md new file mode 100644 index 00000000000..01a58f6fc1b --- /dev/null +++ b/.changeset/warm-crabs-smile.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +chore: update magic-string to v1 diff --git a/packages/docs/package.json b/packages/docs/package.json index f4356604c91..fe4e52754a8 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -40,7 +40,7 @@ "fflate": "0.8.2", "gray-matter": "4.0.3", "leaflet": "1.9.4", - "magic-string": "0.30.21", + "magic-string": "1.2.3", "pagefind": "1.4.0", "playwright": "1.61.1", "prettier": "3.7.4", diff --git a/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/api.json b/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/api.json index 9d86e4d4a4b..1c3964ad7fa 100644 --- a/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/api.json +++ b/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/api.json @@ -2,6 +2,48 @@ "id": "qwik-router-middleware-azure-swa", "package": "@qwik.dev/qwik-router/middleware/azure-swa", "members": [ + { + "name": "AzureContext", + "id": "azurecontext", + "hierarchy": [ + { + "name": "AzureContext", + "id": "azurecontext" + } + ], + "kind": "Interface", + "content": "```typescript\nexport interface AzureContext \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nbindingData\n\n\n\n\n\n\n\n{ \\[key: string\\]: any; }\n\n\n\n\n\n
\n\nbindingDefinitions\n\n\n\n\n\n\n\n{ name: string; type: string; direction: 'in' \\| 'out' \\| 'inout'; }\\[\\]\n\n\n\n\n\n
\n\nbindings\n\n\n\n\n\n\n\n{ \\[key: string\\]: any; }\n\n\n\n\n\n
\n\nexecutionContext\n\n\n\n\n\n\n\n{ invocationId: string; functionName: string; functionDirectory: string; }\n\n\n\n\n\n
\n\ninvocationId\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\nlog\n\n\n\n\n\n\n\n((...args: any\\[\\]) => void) & { error(...args: any\\[\\]): void; warn(...args: any\\[\\]): void; info(...args: any\\[\\]): void; verbose(...args: any\\[\\]): void; }\n\n\n\n\n\n
\n\nreq?\n\n\n\n\n\n\n\n[AzureHttpRequest](#azurehttprequest)\n\n\n\n\n_(Optional)_\n\n\n
\n\nres?\n\n\n\n\n\n\n\n{ \\[key: string\\]: any; }\n\n\n\n\n_(Optional)_\n\n\n
\n\ntraceContext\n\n\n\n\n\n\n\n{ traceparent?: string \\| null; tracestate?: string \\| null; attributes?: { \\[key: string\\]: string; } \\| null; }\n\n\n\n\n\n
\n\n\n\n\n
\n\nMethod\n\n\n\n\nDescription\n\n\n
\n\n[done(err, result)](#azurecontext-done)\n\n\n\n\n\n
", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts", + "mdFile": "router.azurecontext.md" + }, + { + "name": "AzureFunction", + "id": "azurefunction", + "hierarchy": [ + { + "name": "AzureFunction", + "id": "azurefunction" + } + ], + "kind": "TypeAlias", + "content": "```typescript\nexport type AzureFunction = (context: AzureContext, ...args: any[]) => Promise | void;\n```\n**References:** [AzureContext](#azurecontext)", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts", + "mdFile": "router.azurefunction.md" + }, + { + "name": "AzureHttpRequest", + "id": "azurehttprequest", + "hierarchy": [ + { + "name": "AzureHttpRequest", + "id": "azurehttprequest" + } + ], + "kind": "Interface", + "content": "```typescript\nexport interface AzureHttpRequest \n```\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nbody?\n\n\n\n\n\n\n\nany\n\n\n\n\n_(Optional)_\n\n\n
\n\nbufferBody?\n\n\n\n\n\n\n\nUint8Array\n\n\n\n\n_(Optional)_\n\n\n
\n\nheaders\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n\n
\n\nmethod\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
\n\nparams\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n\n
\n\nquery\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n\n
\n\nrawBody?\n\n\n\n\n\n\n\nany\n\n\n\n\n_(Optional)_\n\n\n
\n\nurl\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts", + "mdFile": "router.azurehttprequest.md" + }, { "name": "createQwikCity", "id": "createqwikcity", @@ -26,10 +68,27 @@ } ], "kind": "Function", - "content": "```typescript\nexport declare function createQwikRouter(opts: QwikRouterAzureOptions): AzureFunction;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n[QwikRouterAzureOptions](#qwikrouterazureoptions)\n\n\n\n\n\n
\n\n**Returns:**\n\nAzureFunction", + "content": "```typescript\nexport declare function createQwikRouter(opts: QwikRouterAzureOptions): AzureFunction;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n[QwikRouterAzureOptions](#qwikrouterazureoptions)\n\n\n\n\n\n
\n\n**Returns:**\n\n[AzureFunction](#azurefunction)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts", "mdFile": "router.createqwikrouter.md" }, + { + "name": "done", + "id": "azurecontext-done", + "hierarchy": [ + { + "name": "AzureContext", + "id": "azurecontext-done" + }, + { + "name": "done", + "id": "azurecontext-done" + } + ], + "kind": "MethodSignature", + "content": "```typescript\ndone(err?: Error | string | null, result?: any): void;\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nerr\n\n\n\n\nError \\| string \\| null\n\n\n\n\n_(Optional)_\n\n\n
\n\nresult\n\n\n\n\nany\n\n\n\n\n_(Optional)_\n\n\n
\n\n**Returns:**\n\nvoid", + "mdFile": "router.azurecontext.done.md" + }, { "name": "PlatformAzure", "id": "platformazure", @@ -40,7 +99,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface PlatformAzure extends Partial \n```\n**Extends:** Partial<Context>", + "content": "```typescript\nexport interface PlatformAzure extends Partial \n```\n**Extends:** Partial<[AzureContext](#azurecontext)>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts", "mdFile": "router.platformazure.md" }, diff --git a/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/index.mdx b/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/index.mdx index 7c9c43112d7..51705437760 100644 --- a/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/index.mdx +++ b/packages/docs/src/routes/api/qwik-router-middleware-azure-swa/index.mdx @@ -4,6 +4,322 @@ title: \@qwik.dev/qwik-router/middleware/azure-swa API Reference # [API](/api) › @qwik.dev/qwik-router/middleware/azure-swa +

AzureContext

+ +```typescript +export interface AzureContext +``` + + + + + + + + + + + +
+ +Property + + + +Modifiers + + + +Type + + + +Description + +
+ +bindingData + + + + + +\{ [key: string]: any; } + + + +
+ +bindingDefinitions + + + + + +\{ name: string; type: string; direction: 'in' \| 'out' \| 'inout'; }[] + + + +
+ +bindings + + + + + +\{ [key: string]: any; } + + + +
+ +executionContext + + + + + +\{ invocationId: string; functionName: string; functionDirectory: string; } + + + +
+ +invocationId + + + + + +string + + + +
+ +log + + + + + +((...args: any[]) => void) & \{ error(...args: any[]): void; warn(...args: any[]): void; info(...args: any[]): void; verbose(...args: any[]): void; } + + + +
+ +req? + + + + + +[AzureHttpRequest](#azurehttprequest) + + + +_(Optional)_ + +
+ +res? + + + + + +\{ [key: string]: any; } + + + +_(Optional)_ + +
+ +traceContext + + + + + +\{ traceparent?: string \| null; tracestate?: string \| null; attributes?: \{ [key: string]: string; } \| null; } + + + +
+ + + +
+ +Method + + + +Description + +
+ +[done(err, result)](#azurecontext-done) + + + +
+ +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts) + +

AzureFunction

+ +```typescript +export type AzureFunction = ( + context: AzureContext, + ...args: any[] +) => Promise | void; +``` + +**References:** [AzureContext](#azurecontext) + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts) + +

AzureHttpRequest

+ +```typescript +export interface AzureHttpRequest +``` + + + + + + + + + + +
+ +Property + + + +Modifiers + + + +Type + + + +Description + +
+ +body? + + + + + +any + + + +_(Optional)_ + +
+ +bufferBody? + + + + + +Uint8Array + + + +_(Optional)_ + +
+ +headers + + + + + +\{ [name: string]: string; } + + + +
+ +method + + + + + +string \| null + + + +
+ +params + + + + + +\{ [name: string]: string; } + + + +
+ +query + + + + + +\{ [name: string]: string; } + + + +
+ +rawBody? + + + + + +any + + + +_(Optional)_ + +
+ +url + + + + + +string + + + +
+ +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts) +

createQwikCity

> Warning: This API is now obsolete. @@ -52,17 +368,68 @@ opts **Returns:** -AzureFunction +[AzureFunction](#azurefunction) [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts) +

done

+ +```typescript +done(err?: Error | string | null, result?: any): void; +``` + + + + +
+ +Parameter + + + +Type + + + +Description + +
+ +err + + + +Error \| string \| null + + + +_(Optional)_ + +
+ +result + + + +any + + + +_(Optional)_ + +
+ +**Returns:** + +void +

PlatformAzure

```typescript -export interface PlatformAzure extends Partial +export interface PlatformAzure extends Partial ``` -**Extends:** Partial<Context> +**Extends:** Partial<[AzureContext](#azurecontext)> [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/azure-swa/index.ts) diff --git a/packages/qwik-router/package.json b/packages/qwik-router/package.json index fe3313bb9ef..e3d00707760 100644 --- a/packages/qwik-router/package.json +++ b/packages/qwik-router/package.json @@ -4,23 +4,14 @@ "version": "2.0.0-beta.42", "bugs": "https://github.com/QwikDev/qwik/issues", "dependencies": { - "@azure/functions": "3.5.1", "@mdx-js/mdx": "^3.1.1", "@netlify/edge-functions": "^3.0.6", - "@types/mdx": "^2.0.13", - "estree-util-value-to-estree": "^3.5.0", - "github-slugger": "^2.0.0", - "hast-util-heading-rank": "^2.1.1", - "hast-util-to-string": "^2.0.0", - "kleur": "^4.1.5", - "marked": "^12.0.2", - "mdast-util-mdx": "^3.0.0", "mlly": "^1.8.2", "refractor": "^4.8.1", "rehype-autolink-headings": "^7.1.0", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", - "set-cookie-parser": "^2.7.2", + "remark-parse": "^11.0.0", "source-map": "^0.7.6", "svgo": "^3.3.4", "unified": "^11.0.5", @@ -34,13 +25,11 @@ "devDependencies": { "@microsoft/api-extractor": "7.57.6", "@qwik.dev/core": "workspace:*", + "@types/estree": "1.0.8", "@types/mdast": "4.0.4", "@types/node": "24.10.0", - "@types/refractor": "4.0.0", - "@types/set-cookie-parser": "2.4.10", - "tsm": "2.3.0", + "mdast-util-mdx": "3.0.0", "typescript": "5.9.3", - "uvu": "0.5.6", "vite": "8.2.1" }, "engines": { diff --git a/packages/qwik-router/src/buildtime/build-endpoints.unit.ts b/packages/qwik-router/src/buildtime/build-endpoints.unit.ts index 1128b4ee038..613922cabc8 100644 --- a/packages/qwik-router/src/buildtime/build-endpoints.unit.ts +++ b/packages/qwik-router/src/buildtime/build-endpoints.unit.ts @@ -1,20 +1,20 @@ -import { equal } from 'uvu/assert'; +import { assert } from 'vitest'; import { testAppSuite } from '../utils/test-suite'; const test = testAppSuite('Build Endpoints'); test('endpoint', ({ assertRoute }) => { const r = assertRoute('/api/data.json'); - equal(r.id, 'CommonApiDataRoute'); - equal(r.pattern, /^\/api\/data\.json\/?$/); - equal(r.paramNames.length, 0); + assert.deepEqual(r.id, 'CommonApiDataRoute'); + assert.deepEqual(r.pattern, /^\/api\/data\.json\/?$/); + assert.deepEqual(r.paramNames.length, 0); }); test('endpoint w/ params', ({ assertRoute }) => { const r = assertRoute('/api/[org]/[user].json'); - equal(r.id, 'CommonApiOrgUserRoute'); - equal(r.pattern, /^\/api\/([^/]+?)\/([^/]+?)\.json\/?$/); - equal(r.paramNames.length, 2); - equal(r.paramNames[0], 'org'); - equal(r.paramNames[1], 'user'); + assert.deepEqual(r.id, 'CommonApiOrgUserRoute'); + assert.deepEqual(r.pattern, /^\/api\/([^/]+?)\/([^/]+?)\.json\/?$/); + assert.deepEqual(r.paramNames.length, 2); + assert.deepEqual(r.paramNames[0], 'org'); + assert.deepEqual(r.paramNames[1], 'user'); }); diff --git a/packages/qwik-router/src/buildtime/build-menus.unit.ts b/packages/qwik-router/src/buildtime/build-menus.unit.ts index 5b27470dc85..baf4064a834 100644 --- a/packages/qwik-router/src/buildtime/build-menus.unit.ts +++ b/packages/qwik-router/src/buildtime/build-menus.unit.ts @@ -1,14 +1,14 @@ -import { equal, ok } from 'uvu/assert'; +import { assert } from 'vitest'; import { testAppSuite } from '../utils/test-suite'; const test = testAppSuite('Build Menu'); test('menus found', ({ ctx: { menus } }) => { - equal(menus.length, 1); + assert.deepEqual(menus.length, 1); }); test('docs menu', ({ ctx: { menus } }) => { const docsMenu = menus.find((r) => r.pathname === '/docs/')!; - ok(docsMenu, 'found docs menu'); - equal(docsMenu.pathname, '/docs/'); + assert.ok(docsMenu, 'found docs menu'); + assert.deepEqual(docsMenu.pathname, '/docs/'); }); diff --git a/packages/qwik-router/src/buildtime/build-pages-rewrited.unit.ts b/packages/qwik-router/src/buildtime/build-pages-rewrited.unit.ts index 3af18235189..e6e535125c1 100644 --- a/packages/qwik-router/src/buildtime/build-pages-rewrited.unit.ts +++ b/packages/qwik-router/src/buildtime/build-pages-rewrited.unit.ts @@ -1,4 +1,4 @@ -import * as assert from 'uvu/assert'; +import { assert } from 'vitest'; import { testAppSuite } from '../utils/test-suite'; const test = testAppSuite('Build Pages Rewrited', { @@ -25,13 +25,13 @@ const test = testAppSuite('Build Pages Rewrited', { test('translated pathname / with prefix', ({ assertRoute }) => { const r = assertRoute('/it/'); - assert.equal(r.id, 'CommonRouteIT'); - assert.equal(r.pathname, '/it/'); - assert.equal(r.routeName, 'it/'); - assert.equal(r.pattern, /^\/it\/$/); - assert.equal(r.paramNames.length, 0); - assert.equal(r.segments[0][0].content, 'it'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'CommonRouteIT'); + assert.deepEqual(r.pathname, '/it/'); + assert.deepEqual(r.routeName, 'it/'); + assert.deepEqual(r.pattern, /^\/it\/$/); + assert.deepEqual(r.paramNames.length, 0); + assert.deepEqual(r.segments[0][0].content, 'it'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -45,15 +45,15 @@ test('translated pathname / with prefix', ({ assertRoute }) => { test('translated pathname /docs/getting-started with prefix', ({ assertRoute }) => { const r = assertRoute('/it/documentazione/per-iniziare/'); - assert.equal(r.id, 'DocsGettingstartedRouteIT'); - assert.equal(r.pathname, '/it/documentazione/per-iniziare/'); - assert.equal(r.routeName, 'it/documentazione/per-iniziare/'); - assert.equal(r.pattern, /^\/it\/documentazione\/per-iniziare\/?/); - assert.equal(r.paramNames.length, 0); - assert.equal(r.segments[0][0].content, 'it'); - assert.equal(r.segments[1][0].content, 'documentazione'); - assert.equal(r.segments[2][0].content, 'per-iniziare'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'DocsGettingstartedRouteIT'); + assert.deepEqual(r.pathname, '/it/documentazione/per-iniziare/'); + assert.deepEqual(r.routeName, 'it/documentazione/per-iniziare/'); + assert.deepEqual(r.pattern, /^\/it\/documentazione\/per-iniziare\/?/); + assert.deepEqual(r.paramNames.length, 0); + assert.deepEqual(r.segments[0][0].content, 'it'); + assert.deepEqual(r.segments[1][0].content, 'documentazione'); + assert.deepEqual(r.segments[2][0].content, 'per-iniziare'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -69,17 +69,17 @@ test('translated pathname /docs/getting-started with prefix', ({ assertRoute }) test('translated pathname /docs/[category]/[id] with prefix', ({ assertRoute }) => { const r = assertRoute('/it/documentazione/[category]/[id]/'); - assert.equal(r.id, 'DocsCategoryIdRouteIT'); - assert.equal(r.pathname, '/it/documentazione/[category]/[id]/'); - assert.equal(r.routeName, 'it/documentazione/[category]/[id]/'); - assert.equal(r.pattern, /^\/it\/documentazione\/([^/]+?)\/([^/]+?)\/?/); - assert.equal(r.paramNames[0], 'category'); - assert.equal(r.paramNames[1], 'id'); - assert.equal(r.segments[0][0].content, 'it'); - assert.equal(r.segments[1][0].content, 'documentazione'); - assert.equal(r.segments[2][0].content, 'category'); - assert.equal(r.segments[3][0].content, 'id'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'DocsCategoryIdRouteIT'); + assert.deepEqual(r.pathname, '/it/documentazione/[category]/[id]/'); + assert.deepEqual(r.routeName, 'it/documentazione/[category]/[id]/'); + assert.deepEqual(r.pattern, /^\/it\/documentazione\/([^/]+?)\/([^/]+?)\/?/); + assert.deepEqual(r.paramNames[0], 'category'); + assert.deepEqual(r.paramNames[1], 'id'); + assert.deepEqual(r.segments[0][0].content, 'it'); + assert.deepEqual(r.segments[1][0].content, 'documentazione'); + assert.deepEqual(r.segments[2][0].content, 'category'); + assert.deepEqual(r.segments[3][0].content, 'id'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -95,14 +95,14 @@ test('translated pathname /docs/[category]/[id] with prefix', ({ assertRoute }) test('translated pathname /about-us with prefix', ({ assertRoute }) => { const r = assertRoute('/it/informazioni/'); - assert.equal(r.id, 'CommonAboutusRouteIT'); - assert.equal(r.pathname, '/it/informazioni/'); - assert.equal(r.routeName, 'it/informazioni/'); - assert.equal(r.pattern, /^\/it\/informazioni\/?/); - assert.equal(r.paramNames.length, 0); - assert.equal(r.segments[0][0].content, 'it'); - assert.equal(r.segments[1][0].content, 'informazioni'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'CommonAboutusRouteIT'); + assert.deepEqual(r.pathname, '/it/informazioni/'); + assert.deepEqual(r.routeName, 'it/informazioni/'); + assert.deepEqual(r.pattern, /^\/it\/informazioni\/?/); + assert.deepEqual(r.paramNames.length, 0); + assert.deepEqual(r.segments[0][0].content, 'it'); + assert.deepEqual(r.segments[1][0].content, 'informazioni'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -118,15 +118,15 @@ test('translated pathname /about-us with prefix', ({ assertRoute }) => { test('translated pathname /products/[id] with prefix', ({ assertRoute }) => { const r = assertRoute('/it/prodotti/[id]/'); - assert.equal(r.id, 'CommonProductsIdRouteIT'); - assert.equal(r.pathname, '/it/prodotti/[id]/'); - assert.equal(r.routeName, 'it/prodotti/[id]/'); - assert.equal(r.pattern, /^\/it\/prodotti\/([^/]+?)\/?/); - assert.equal(r.paramNames[0], 'id'); - assert.equal(r.segments[0][0].content, 'it'); - assert.equal(r.segments[1][0].content, 'prodotti'); - assert.equal(r.segments[2][0].content, 'id'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'CommonProductsIdRouteIT'); + assert.deepEqual(r.pathname, '/it/prodotti/[id]/'); + assert.deepEqual(r.routeName, 'it/prodotti/[id]/'); + assert.deepEqual(r.pattern, /^\/it\/prodotti\/([^/]+?)\/?/); + assert.deepEqual(r.paramNames[0], 'id'); + assert.deepEqual(r.segments[0][0].content, 'it'); + assert.deepEqual(r.segments[1][0].content, 'prodotti'); + assert.deepEqual(r.segments[2][0].content, 'id'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -144,14 +144,14 @@ test('translated pathname /products/[id] with prefix', ({ assertRoute }) => { test('translated pathname /docs/getting-started', ({ assertRoute }) => { const r = assertRoute('/documentazione/per-iniziare/'); - assert.equal(r.id, 'DocsGettingstartedRoute0'); - assert.equal(r.pathname, '/documentazione/per-iniziare/'); - assert.equal(r.routeName, 'documentazione/per-iniziare/'); - assert.equal(r.pattern, /^\/documentazione\/per-iniziare\/?/); - assert.equal(r.paramNames.length, 0); - assert.equal(r.segments[0][0].content, 'documentazione'); - assert.equal(r.segments[1][0].content, 'per-iniziare'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'DocsGettingstartedRoute0'); + assert.deepEqual(r.pathname, '/documentazione/per-iniziare/'); + assert.deepEqual(r.routeName, 'documentazione/per-iniziare/'); + assert.deepEqual(r.pattern, /^\/documentazione\/per-iniziare\/?/); + assert.deepEqual(r.paramNames.length, 0); + assert.deepEqual(r.segments[0][0].content, 'documentazione'); + assert.deepEqual(r.segments[1][0].content, 'per-iniziare'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -167,16 +167,16 @@ test('translated pathname /docs/getting-started', ({ assertRoute }) => { test('translated pathname /docs/[category]/[id]', ({ assertRoute }) => { const r = assertRoute('/documentazione/[category]/[id]/'); - assert.equal(r.id, 'DocsCategoryIdRoute0'); - assert.equal(r.pathname, '/documentazione/[category]/[id]/'); - assert.equal(r.routeName, 'documentazione/[category]/[id]/'); - assert.equal(r.pattern, /^\/documentazione\/([^/]+?)\/([^/]+?)\/?/); - assert.equal(r.paramNames[0], 'category'); - assert.equal(r.paramNames[1], 'id'); - assert.equal(r.segments[0][0].content, 'documentazione'); - assert.equal(r.segments[1][0].content, 'category'); - assert.equal(r.segments[2][0].content, 'id'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'DocsCategoryIdRoute0'); + assert.deepEqual(r.pathname, '/documentazione/[category]/[id]/'); + assert.deepEqual(r.routeName, 'documentazione/[category]/[id]/'); + assert.deepEqual(r.pattern, /^\/documentazione\/([^/]+?)\/([^/]+?)\/?/); + assert.deepEqual(r.paramNames[0], 'category'); + assert.deepEqual(r.paramNames[1], 'id'); + assert.deepEqual(r.segments[0][0].content, 'documentazione'); + assert.deepEqual(r.segments[1][0].content, 'category'); + assert.deepEqual(r.segments[2][0].content, 'id'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -192,13 +192,13 @@ test('translated pathname /docs/[category]/[id]', ({ assertRoute }) => { test('translated pathname /about-us', ({ assertRoute }) => { const r = assertRoute('/informazioni/'); - assert.equal(r.id, 'CommonAboutusRoute0'); - assert.equal(r.pathname, '/informazioni/'); - assert.equal(r.routeName, 'informazioni/'); - assert.equal(r.pattern, /^\/informazioni\/?/); - assert.equal(r.paramNames.length, 0); - assert.equal(r.segments[0][0].content, 'informazioni'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'CommonAboutusRoute0'); + assert.deepEqual(r.pathname, '/informazioni/'); + assert.deepEqual(r.routeName, 'informazioni/'); + assert.deepEqual(r.pattern, /^\/informazioni\/?/); + assert.deepEqual(r.paramNames.length, 0); + assert.deepEqual(r.segments[0][0].content, 'informazioni'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -214,14 +214,14 @@ test('translated pathname /about-us', ({ assertRoute }) => { test('translated pathname /products/[id]', ({ assertRoute }) => { const r = assertRoute('/prodotti/[id]/'); - assert.equal(r.id, 'CommonProductsIdRoute0'); - assert.equal(r.pathname, '/prodotti/[id]/'); - assert.equal(r.routeName, 'prodotti/[id]/'); - assert.equal(r.pattern, /^\/prodotti\/([^/]+?)\/?/); - assert.equal(r.paramNames[0], 'id'); - assert.equal(r.segments[0][0].content, 'prodotti'); - assert.equal(r.segments[1][0].content, 'id'); - assert.equal(r.layouts.length, 2); + assert.deepEqual(r.id, 'CommonProductsIdRoute0'); + assert.deepEqual(r.pathname, '/prodotti/[id]/'); + assert.deepEqual(r.routeName, 'prodotti/[id]/'); + assert.deepEqual(r.pattern, /^\/prodotti\/([^/]+?)\/?/); + assert.deepEqual(r.paramNames[0], 'id'); + assert.deepEqual(r.segments[0][0].content, 'prodotti'); + assert.deepEqual(r.segments[1][0].content, 'id'); + assert.deepEqual(r.layouts.length, 2); assert.ok( r.layouts[0].filePath.endsWith('e2e/qwik-e2e/apps/qwikrouter-test/src/routes/layout.tsx') ); @@ -245,32 +245,32 @@ test('trie has _G nodes for translated routes', ({ ctx }) => { assert.ok(documentazione, 'trie should have "documentazione" child'); const perIniziare = documentazione!.children.get('per-iniziare'); assert.ok(perIniziare, 'trie should have "per-iniziare" under "documentazione"'); - assert.equal(perIniziare!._G, 'docs/getting-started'); + assert.deepEqual(perIniziare!._G, 'docs/getting-started'); // Without prefix: 'informazioni' should point to about-us const informazioni = trie.children.get('informazioni'); assert.ok(informazioni, 'trie should have "informazioni" child'); - assert.equal(informazioni!._G, 'about-us'); + assert.deepEqual(informazioni!._G, 'about-us'); // Without prefix: 'prodotti' should have _W child with _G pointing to products/_W const prodotti = trie.children.get('prodotti'); assert.ok(prodotti, 'trie should have "prodotti" child'); const prodottiW = prodotti!.children.get('_W'); assert.ok(prodottiW, 'trie should have "_W" under "prodotti"'); - assert.equal(prodottiW!._G, 'products/_W'); - assert.equal(prodottiW!._P, 'id'); + assert.deepEqual(prodottiW!._G, 'products/_W'); + assert.deepEqual(prodottiW!._P, 'id'); // With prefix: 'it' should contain translated children const it = trie.children.get('it'); assert.ok(it, 'trie should have "it" child'); // 'it' root index rewrite - assert.equal(it!._G, ''); + assert.deepEqual(it!._G, ''); // 'it/documentazione/per-iniziare' const itDoc = it!.children.get('documentazione'); assert.ok(itDoc, 'trie should have "documentazione" under "it"'); const itDocGs = itDoc!.children.get('per-iniziare'); assert.ok(itDocGs, 'trie should have "per-iniziare" under "it/documentazione"'); - assert.equal(itDocGs!._G, 'docs/getting-started'); + assert.deepEqual(itDocGs!._G, 'docs/getting-started'); }); const testWithDuplicatedRoutes = testAppSuite('Duplicated segments with multiple prefixes', { @@ -301,7 +301,7 @@ testWithDuplicatedRoutes( ({ assertRoute }) => { const r = assertRoute('/produkt/'); - assert.equal(r.pathname, '/produkt/'); + assert.deepEqual(r.pathname, '/produkt/'); } ); @@ -330,7 +330,7 @@ testSameRoutes( 'Issue #6799: Bug while using rewrite routes pointing to the same file', ({ assertRoute }) => { const r = assertRoute('/produkt/'); - assert.equal(r.pathname, '/produkt/'); + assert.deepEqual(r.pathname, '/produkt/'); } ); @@ -342,5 +342,9 @@ testExclude('exclude skips localized mirrors for matched routes', ({ ctx }) => { const it = ctx.routeTrie.children.get('it'); assert.ok(it, 'prefix mirror "it" should exist for non-excluded routes'); assert.ok(it!.children.get('about-us'), 'non-excluded route is mirrored under "it"'); - assert.is(it!.children.get('docs'), undefined, '"/docs/*" is excluded from the "it" mirror'); + assert.strictEqual( + it!.children.get('docs'), + undefined, + '"/docs/*" is excluded from the "it" mirror' + ); }); diff --git a/packages/qwik-router/src/buildtime/markdown/hast-utils.ts b/packages/qwik-router/src/buildtime/markdown/hast-utils.ts new file mode 100644 index 00000000000..9ff27139d3e --- /dev/null +++ b/packages/qwik-router/src/buildtime/markdown/hast-utils.ts @@ -0,0 +1,23 @@ +// Minimal inlined replacement for `hast-util-to-string` and `hast-util-heading-rank`. + +/** Concatenated text content of a hast node's text descendants. */ +export function hastToString(node: any): string { + if (node && Array.isArray(node.children)) { + return node.children.map(textContent).join(''); + } + return typeof node?.value === 'string' ? node.value : ''; +} + +function textContent(node: any): string { + if (node.type === 'text') { + return node.value || ''; + } + return Array.isArray(node.children) ? node.children.map(textContent).join('') : ''; +} + +/** Heading level (1-6) of a hast element, or undefined for non-headings. */ +export function headingRank(node: any): number | undefined { + const tagName = node?.type === 'element' && typeof node.tagName === 'string' ? node.tagName : ''; + const rank = tagName.length === 2 && tagName[0] === 'h' ? Number(tagName[1]) : NaN; + return rank >= 1 && rank <= 6 ? rank : undefined; +} diff --git a/packages/qwik-router/src/buildtime/markdown/menu.ts b/packages/qwik-router/src/buildtime/markdown/menu.ts index 0fb6760444b..0e5ce313af9 100644 --- a/packages/qwik-router/src/buildtime/markdown/menu.ts +++ b/packages/qwik-router/src/buildtime/markdown/menu.ts @@ -1,5 +1,7 @@ import type { NormalizedPluginOptions, BuiltMenu, ParsedMenuItem, RouteSourceFile } from '../types'; -import { marked } from 'marked'; +import type { List, PhrasingContent, RootContent } from 'mdast'; +import remarkParse from 'remark-parse'; +import { unified } from 'unified'; import { createFileId, getMenuPathname } from '../../utils/fs'; import { getMarkdownRelativeUrl } from './markdown-url'; @@ -32,12 +34,12 @@ export function parseMenu( content: string, checkFileExists = true ) { - const tokens = marked.lexer(content, {}); + const tree = unified().use(remarkParse).parse(content); let currentDepth = 0; const stack: ParsedMenuItem[] = []; - for (const t of tokens) { - if (t.type === 'heading') { - const diff = currentDepth - t.depth; + for (const node of tree.children) { + if (node.type === 'heading') { + const diff = currentDepth - node.depth; if (diff >= 0) { stack.length -= diff + 1; } @@ -45,23 +47,26 @@ export function parseMenu( throw new Error( `Menu hierarchy skipped a level, went from to , in menu: ${filePath}` + )}> to , in menu: ${filePath}` ); } - currentDepth = t.depth; + currentDepth = node.depth; const parentNode = stack[stack.length - 1]; - for (const h2Token of t.tokens || []) { + for (const inline of node.children) { const lastNode: ParsedMenuItem = { text: '', }; - if (h2Token.type === 'text') { - lastNode.text = h2Token.text; - } else if (h2Token.type === 'link') { - lastNode.text = h2Token.text; - lastNode.href = getMarkdownRelativeUrl(opts, filePath, h2Token.href, checkFileExists); + if (inline.type === 'text') { + lastNode.text = inline.value; + } else if (inline.type === 'link') { + lastNode.text = inlineText(inline.children); + lastNode.href = getMarkdownRelativeUrl(opts, filePath, inline.url, checkFileExists); } else { throw new Error( - `Headings can only be a text or link. Received "${h2Token.type}", value "${h2Token.raw}", in menu: ${filePath}` + `Headings can only be a text or link. Received "${inline.type}", value "${rawSource( + content, + inline + )}", in menu: ${filePath}` ); } if (parentNode) { @@ -70,50 +75,19 @@ export function parseMenu( } stack.push(lastNode); } - } else if (t.type === 'list') { + } else if (node.type === 'list') { const parentNode = stack[stack.length - 1]; - - parentNode.items = parentNode.items || []; - for (const li of t.items) { - if (li.type === 'list_item') { - for (const liToken of li.tokens) { - if (liToken.type === 'text') { - for (const liItem of (liToken as any).tokens) { - if (liItem.type === 'text') { - parentNode.items.push({ text: liItem.text }); - } else if (liItem.type === 'link') { - parentNode.items.push({ - text: liItem.text, - href: getMarkdownRelativeUrl(opts, filePath, liItem.href, checkFileExists), - }); - } else { - throw new Error( - `List items can only be a text or link. Received "${liItem.type}", value "${liItem.raw}", in menu: ${filePath}` - ); - } - } - } else if (liToken.type === 'link') { - parentNode.items.push({ - text: liToken.text, - href: getMarkdownRelativeUrl(opts, filePath, liToken.href, checkFileExists), - }); - } else { - throw new Error( - `List items can only be a text or link. Received "${liToken.type}", value "${liToken.raw}", in menu: ${filePath}` - ); - } - } - } else { - throw new Error( - `Only list items can be used in lists. Received "${li.type}", value "${li.raw}", in menu: ${filePath}` - ); - } + if (!parentNode) { + throw new Error(`Menu must start with an h1 in the index: ${filePath}`); } - } else if (t.type === 'space') { - continue; + parentNode.items = parentNode.items || []; + parseListItems(opts, filePath, content, node, parentNode, checkFileExists); } else { throw new Error( - `Menu has a "${t.type}" with the value "${t.raw}". However, only headings and lists can be used in the menu: ${filePath}` + `Menu has a "${node.type}" with the value "${rawSource( + content, + node + )}". However, only headings and lists can be used in the menu: ${filePath}` ); } } @@ -123,3 +97,52 @@ export function parseMenu( } return stack[0]; } + +function parseListItems( + opts: NormalizedPluginOptions, + filePath: string, + content: string, + list: List, + parentNode: ParsedMenuItem, + checkFileExists: boolean +) { + for (const listItem of list.children) { + for (const block of listItem.children) { + if (block.type !== 'paragraph') { + throw new Error( + `List items can only be a text or link. Received "${block.type}", value "${rawSource( + content, + block + )}", in menu: ${filePath}` + ); + } + for (const inline of block.children) { + if (inline.type === 'text') { + parentNode.items!.push({ text: inline.value }); + } else if (inline.type === 'link') { + parentNode.items!.push({ + text: inlineText(inline.children), + href: getMarkdownRelativeUrl(opts, filePath, inline.url, checkFileExists), + }); + } else { + throw new Error( + `List items can only be a text or link. Received "${inline.type}", value "${rawSource( + content, + inline + )}", in menu: ${filePath}` + ); + } + } + } + } +} + +function inlineText(children: PhrasingContent[]): string { + return children.map((child) => ('value' in child ? child.value : '')).join(''); +} + +function rawSource(content: string, node: RootContent | PhrasingContent) { + const start = node.position?.start.offset; + const end = node.position?.end.offset; + return start != null && end != null ? content.slice(start, end) : node.type; +} diff --git a/packages/qwik-router/src/buildtime/markdown/rehype.ts b/packages/qwik-router/src/buildtime/markdown/rehype.ts index a7577e50dd1..4707340dd3d 100644 --- a/packages/qwik-router/src/buildtime/markdown/rehype.ts +++ b/packages/qwik-router/src/buildtime/markdown/rehype.ts @@ -1,10 +1,9 @@ import type { Transformer } from 'unified'; -import Slugger from 'github-slugger'; import type { Root } from 'mdast'; import type { MdxjsEsm } from 'mdast-util-mdx'; -import { valueToEstree } from 'estree-util-value-to-estree'; -import { headingRank } from 'hast-util-heading-rank'; -import { toString } from 'hast-util-to-string'; +import { valueToEstree } from './value-to-estree'; +import { hastToString, headingRank } from './hast-utils'; +import { Slugger } from './slugger'; import { visit } from 'unist-util-visit'; import type { ContentHeading } from '../../runtime/src'; import type { RoutingContext, NormalizedPluginOptions } from '../types'; @@ -21,7 +20,7 @@ export function rehypeSlug(): Transformer { visit(mdast, 'element', (node: any) => { const level = headingRank(node); if (level && node.properties) { - const text = toString(node); + const text = hastToString(node); if (!hasProperty(node, 'id')) { node.properties.id = slugs.slug(text); @@ -116,7 +115,7 @@ function exportContentHeadings(mdast: Root) { const level = headingRank(node); if (level && node.properties) { if (hasProperty(node, 'id')) { - const text = toString(node); + const text = hastToString(node); headings.push({ text, id: node.properties.id, diff --git a/packages/qwik-router/src/buildtime/markdown/slugger.ts b/packages/qwik-router/src/buildtime/markdown/slugger.ts new file mode 100644 index 00000000000..3a2e7b42a37 --- /dev/null +++ b/packages/qwik-router/src/buildtime/markdown/slugger.ts @@ -0,0 +1,21 @@ +// Minimal inlined replacement for `github-slugger`. +// Characters GitHub strips from heading anchors: everything except letters, +// marks, numbers, connector punctuation, emoji (incl. ZWJ), spaces, and hyphens. +const INVALID_SLUG_CHARS = /[^\p{L}\p{M}\p{N}\p{Pc}\p{Extended_Pictographic}‍\- ]/gu; + +/** GitHub-style heading slugger with per-instance duplicate tracking. */ +export class Slugger { + private occurrences = new Map(); + + slug(value: string): string { + const base = value.trim().toLowerCase().replace(INVALID_SLUG_CHARS, '').replace(/ /g, '-'); + let result = base; + while (this.occurrences.has(result)) { + const count = this.occurrences.get(base)! + 1; + this.occurrences.set(base, count); + result = `${base}-${count}`; + } + this.occurrences.set(result, 0); + return result; + } +} diff --git a/packages/qwik-router/src/buildtime/markdown/slugger.unit.ts b/packages/qwik-router/src/buildtime/markdown/slugger.unit.ts new file mode 100644 index 00000000000..3d438b0cb2a --- /dev/null +++ b/packages/qwik-router/src/buildtime/markdown/slugger.unit.ts @@ -0,0 +1,48 @@ +import { assert, test } from 'vitest'; +import { Slugger } from './slugger'; + +test('basic text is lowercased and spaces become dashes', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug('Getting Started'), 'getting-started'); +}); + +test('punctuation is stripped', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug(`What's new? (v2.0)`), 'whats-new-v20'); +}); + +test('underscores and hyphens are kept', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug('use_task - api'), 'use_task---api'); +}); + +test('leading and trailing whitespace is trimmed', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug(' Hello World '), 'hello-world'); +}); + +test('unicode letters are preserved', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug('Héllo Wörld'), 'héllo-wörld'); +}); + +test('duplicate slugs get numeric suffixes', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug('Intro'), 'intro'); + assert.equal(slugger.slug('Intro'), 'intro-1'); + assert.equal(slugger.slug('Intro'), 'intro-2'); +}); + +test('suffixed duplicates do not collide with explicit headings', () => { + const slugger = new Slugger(); + assert.equal(slugger.slug('foo'), 'foo'); + assert.equal(slugger.slug('foo 1'), 'foo-1'); + assert.equal(slugger.slug('foo'), 'foo-2'); +}); + +test('each instance tracks its own occurrences', () => { + const a = new Slugger(); + const b = new Slugger(); + assert.equal(a.slug('same'), 'same'); + assert.equal(b.slug('same'), 'same'); +}); diff --git a/packages/qwik-router/src/buildtime/markdown/syntax-highlight.ts b/packages/qwik-router/src/buildtime/markdown/syntax-highlight.ts index ac9b65331e7..51380cc3152 100644 --- a/packages/qwik-router/src/buildtime/markdown/syntax-highlight.ts +++ b/packages/qwik-router/src/buildtime/markdown/syntax-highlight.ts @@ -1,5 +1,5 @@ import type { Transformer } from 'unified'; -import { toString } from 'hast-util-to-string'; +import { hastToString } from './hast-utils'; import { visit } from 'unist-util-visit'; import { refractor } from 'refractor'; import tsxLang from 'refractor/lang/tsx.js'; @@ -32,7 +32,7 @@ export function rehypeSyntaxHighlight(): Transformer { } function syntaxHighlight(node: any, lang: string) { - const code = toString(node); + const code = hastToString(node); const result = refractor.highlight(code, lang); if (result && Array.isArray(node.children)) { node.children = result.children; diff --git a/packages/qwik-router/src/buildtime/markdown/value-to-estree.ts b/packages/qwik-router/src/buildtime/markdown/value-to-estree.ts new file mode 100644 index 00000000000..07e3030db7a --- /dev/null +++ b/packages/qwik-router/src/buildtime/markdown/value-to-estree.ts @@ -0,0 +1,55 @@ +// Minimal inlined replacement for `estree-util-value-to-estree` (JSON values plus Date). + +import type { Expression } from 'estree'; + +/** Convert a JSON-serializable value (plus Date) to an ESTree expression. */ +export function valueToEstree(value: unknown): Expression { + if (value === undefined) { + return { type: 'Identifier', name: 'undefined' }; + } + if (value === null || typeof value === 'string' || typeof value === 'boolean') { + return { type: 'Literal', value }; + } + if (typeof value === 'number') { + if (Number.isNaN(value)) { + return { type: 'Identifier', name: 'NaN' }; + } + if (value < 0 || Object.is(value, -0)) { + return { + type: 'UnaryExpression', + operator: '-', + prefix: true, + argument: valueToEstree(-value), + }; + } + if (!Number.isFinite(value)) { + return { type: 'Identifier', name: 'Infinity' }; + } + return { type: 'Literal', value }; + } + if (Array.isArray(value)) { + return { type: 'ArrayExpression', elements: value.map(valueToEstree) }; + } + if (value instanceof Date) { + return { + type: 'NewExpression', + callee: { type: 'Identifier', name: 'Date' }, + arguments: [{ type: 'Literal', value: value.getTime() }], + }; + } + if (typeof value === 'object') { + return { + type: 'ObjectExpression', + properties: Object.entries(value).map(([key, propValue]) => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + kind: 'init', + key: { type: 'Literal', value: key }, + value: valueToEstree(propValue), + })), + }; + } + throw new TypeError(`Unsupported value in markdown export: ${typeof value}`); +} diff --git a/packages/qwik-router/src/buildtime/markdown/value-to-estree.unit.ts b/packages/qwik-router/src/buildtime/markdown/value-to-estree.unit.ts new file mode 100644 index 00000000000..be30addc3f9 --- /dev/null +++ b/packages/qwik-router/src/buildtime/markdown/value-to-estree.unit.ts @@ -0,0 +1,53 @@ +import { assert, test } from 'vitest'; +import { valueToEstree } from './value-to-estree'; + +test('primitives', () => { + assert.deepEqual(valueToEstree(null), { type: 'Literal', value: null }); + assert.deepEqual(valueToEstree(true), { type: 'Literal', value: true }); + assert.deepEqual(valueToEstree('hi'), { type: 'Literal', value: 'hi' }); + assert.deepEqual(valueToEstree(42), { type: 'Literal', value: 42 }); +}); + +test('undefined becomes an identifier', () => { + assert.deepEqual(valueToEstree(undefined), { type: 'Identifier', name: 'undefined' }); +}); + +test('negative and non-finite numbers', () => { + assert.deepEqual(valueToEstree(-3), { + type: 'UnaryExpression', + operator: '-', + prefix: true, + argument: { type: 'Literal', value: 3 }, + }); + assert.deepEqual(valueToEstree(Infinity), { type: 'Identifier', name: 'Infinity' }); + assert.deepEqual(valueToEstree(NaN), { type: 'Identifier', name: 'NaN' }); +}); + +test('arrays and nested objects', () => { + assert.deepEqual(valueToEstree([1, 'a']), { + type: 'ArrayExpression', + elements: [ + { type: 'Literal', value: 1 }, + { type: 'Literal', value: 'a' }, + ], + }); + const estree = valueToEstree({ title: 'Docs', meta: { level: 2 } }) as any; + assert.equal(estree.type, 'ObjectExpression'); + assert.equal(estree.properties[0].key.value, 'title'); + assert.equal(estree.properties[0].value.value, 'Docs'); + assert.equal(estree.properties[1].value.properties[0].value.value, 2); +}); + +test('dates become new Date() expressions', () => { + const date = new Date('2026-01-01T00:00:00Z'); + assert.deepEqual(valueToEstree(date), { + type: 'NewExpression', + callee: { type: 'Identifier', name: 'Date' }, + arguments: [{ type: 'Literal', value: date.getTime() }], + }); +}); + +test('unsupported values throw', () => { + assert.throws(() => valueToEstree(() => 0)); + assert.throws(() => valueToEstree(Symbol('nope'))); +}); diff --git a/packages/qwik-router/src/middleware/azure-swa/index.ts b/packages/qwik-router/src/middleware/azure-swa/index.ts index d1a92a19325..53d27587e88 100644 --- a/packages/qwik-router/src/middleware/azure-swa/index.ts +++ b/packages/qwik-router/src/middleware/azure-swa/index.ts @@ -1,4 +1,3 @@ -import type { AzureFunction, Context, HttpRequest } from '@azure/functions'; import { setServerPlatform } from '@qwik.dev/core/server'; import type { ServerRenderOptions, @@ -9,10 +8,55 @@ import { isStaticPath, requestHandler, } from '@qwik.dev/router/middleware/request-handler'; -import { parseString } from 'set-cookie-parser'; +import { parseSetCookieString } from './parse-set-cookie'; // @qwik.dev/router/middleware/azure-swa +// Minimal structural types for the Azure Functions v3 programming model, +// matching what `@azure/functions@3` declares for the fields this middleware touches. + +/** @public */ +export interface AzureHttpRequest { + method: string | null; + url: string; + headers: { [name: string]: string }; + query: { [name: string]: string }; + params: { [name: string]: string }; + body?: any; + rawBody?: any; + bufferBody?: Uint8Array; +} + +/** @public */ +export interface AzureContext { + invocationId: string; + executionContext: { + invocationId: string; + functionName: string; + functionDirectory: string; + }; + bindings: { [key: string]: any }; + bindingData: { [key: string]: any }; + bindingDefinitions: { name: string; type: string; direction: 'in' | 'out' | 'inout' }[]; + traceContext: { + traceparent?: string | null; + tracestate?: string | null; + attributes?: { [key: string]: string } | null; + }; + log: ((...args: any[]) => void) & { + error(...args: any[]): void; + warn(...args: any[]): void; + info(...args: any[]): void; + verbose(...args: any[]): void; + }; + done(err?: Error | string | null, result?: any): void; + req?: AzureHttpRequest; + res?: { [key: string]: any }; +} + +/** @public */ +export type AzureFunction = (context: AzureContext, ...args: any[]) => Promise | void; + interface AzureResponse { status: number; headers: { [key: string]: any }; @@ -53,7 +97,10 @@ export function createQwikRouter(opts: QwikRouterAzureOptions): AzureFunction { if (opts.manifest) { setServerPlatform(opts.manifest); } - async function onAzureSwaRequest(context: Context, req: HttpRequest): Promise { + async function onAzureSwaRequest( + context: AzureContext, + req: AzureHttpRequest + ): Promise { try { const url = new URL(req.headers['x-ms-original-url']!); const options: RequestInit = { @@ -79,7 +126,7 @@ export function createQwikRouter(opts: QwikRouterAzureOptions): AzureFunction { const response: AzureResponse = { status, headers: {}, - cookies: cookies.headers().map((header) => parseString(header)), + cookies: cookies.headers().map((header) => parseSetCookieString(header)), }; headers.forEach((value, key) => (response.headers[key] = value)); return new WritableStream({ @@ -160,4 +207,4 @@ export interface QwikRouterAzureOptions extends ServerRenderOptions {} export type QwikCityAzureOptions = QwikRouterAzureOptions; /** @public */ -export interface PlatformAzure extends Partial {} +export interface PlatformAzure extends Partial {} diff --git a/packages/qwik-router/src/middleware/azure-swa/middleware.azure-swa.api.md b/packages/qwik-router/src/middleware/azure-swa/middleware.azure-swa.api.md index cf1728f8a3b..7cecb70e33e 100644 --- a/packages/qwik-router/src/middleware/azure-swa/middleware.azure-swa.api.md +++ b/packages/qwik-router/src/middleware/azure-swa/middleware.azure-swa.api.md @@ -4,10 +4,86 @@ ```ts -import type { AzureFunction } from '@azure/functions'; -import type { Context } from '@azure/functions'; import type { ServerRenderOptions } from '@qwik.dev/router/middleware/request-handler'; +// @public (undocumented) +export interface AzureContext { + // (undocumented) + bindingData: { + [key: string]: any; + }; + // (undocumented) + bindingDefinitions: { + name: string; + type: string; + direction: 'in' | 'out' | 'inout'; + }[]; + // (undocumented) + bindings: { + [key: string]: any; + }; + // (undocumented) + done(err?: Error | string | null, result?: any): void; + // (undocumented) + executionContext: { + invocationId: string; + functionName: string; + functionDirectory: string; + }; + // (undocumented) + invocationId: string; + // (undocumented) + log: ((...args: any[]) => void) & { + error(...args: any[]): void; + warn(...args: any[]): void; + info(...args: any[]): void; + verbose(...args: any[]): void; + }; + // (undocumented) + req?: AzureHttpRequest; + // (undocumented) + res?: { + [key: string]: any; + }; + // (undocumented) + traceContext: { + traceparent?: string | null; + tracestate?: string | null; + attributes?: { + [key: string]: string; + } | null; + }; +} + +// @public (undocumented) +export type AzureFunction = (context: AzureContext, ...args: any[]) => Promise | void; + +// @public (undocumented) +export interface AzureHttpRequest { + // (undocumented) + body?: any; + // (undocumented) + bufferBody?: Uint8Array; + // (undocumented) + headers: { + [name: string]: string; + }; + // (undocumented) + method: string | null; + // (undocumented) + params: { + [name: string]: string; + }; + // (undocumented) + query: { + [name: string]: string; + }; + // (undocumented) + rawBody?: any; + // (undocumented) + url: string; +} + // @public @deprecated (undocumented) export const createQwikCity: typeof createQwikRouter; @@ -15,7 +91,7 @@ export const createQwikCity: typeof createQwikRouter; export function createQwikRouter(opts: QwikRouterAzureOptions): AzureFunction; // @public (undocumented) -export interface PlatformAzure extends Partial { +export interface PlatformAzure extends Partial { } // @public @deprecated (undocumented) diff --git a/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.ts b/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.ts new file mode 100644 index 00000000000..b7a5d57c34a --- /dev/null +++ b/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.ts @@ -0,0 +1,54 @@ +// Minimal inlined replacement for `set-cookie-parser`'s parseString. + +export interface ParsedSetCookie { + name: string; + value: string; + domain?: string; + path?: string; + expires?: Date; + maxAge?: number; + secure?: boolean; + httpOnly?: boolean; + sameSite?: string; +} + +/** Parse a single Set-Cookie header string into the Azure cookie shape. */ +export function parseSetCookieString(setCookieHeader: string): ParsedSetCookie { + const [nameValuePair, ...attributeParts] = setCookieHeader.split(';'); + const equalsIndex = nameValuePair.indexOf('='); + const cookie: ParsedSetCookie = { + name: nameValuePair.slice(0, equalsIndex).trim(), + value: tryDecode(nameValuePair.slice(equalsIndex + 1).trim()), + }; + for (const part of attributeParts) { + const attrEqualsIndex = part.indexOf('='); + const key = (attrEqualsIndex === -1 ? part : part.slice(0, attrEqualsIndex)) + .trim() + .toLowerCase(); + const value = attrEqualsIndex === -1 ? '' : part.slice(attrEqualsIndex + 1).trim(); + if (key === 'domain') { + cookie.domain = value; + } else if (key === 'path') { + cookie.path = value; + } else if (key === 'expires') { + cookie.expires = new Date(value); + } else if (key === 'max-age') { + cookie.maxAge = parseInt(value, 10); + } else if (key === 'secure') { + cookie.secure = true; + } else if (key === 'httponly') { + cookie.httpOnly = true; + } else if (key === 'samesite') { + cookie.sameSite = value; + } + } + return cookie; +} + +function tryDecode(value: string): string { + try { + return decodeURIComponent(value); + } catch { + return value; + } +} diff --git a/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.unit.ts b/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.unit.ts new file mode 100644 index 00000000000..4620547fe45 --- /dev/null +++ b/packages/qwik-router/src/middleware/azure-swa/parse-set-cookie.unit.ts @@ -0,0 +1,45 @@ +import { assert, test } from 'vitest'; +import { parseSetCookieString } from './parse-set-cookie'; + +test('parses name and value', () => { + assert.deepEqual(parseSetCookieString('foo=bar'), { name: 'foo', value: 'bar' }); +}); + +test('decodes percent-encoded values', () => { + assert.deepEqual(parseSetCookieString('foo=hello%20world'), { + name: 'foo', + value: 'hello world', + }); +}); + +test('keeps values that are not valid percent-encoding', () => { + assert.deepEqual(parseSetCookieString('foo=100%'), { name: 'foo', value: '100%' }); +}); + +test('keeps = inside the value', () => { + assert.deepEqual(parseSetCookieString('foo=a=b'), { name: 'foo', value: 'a=b' }); +}); + +test('parses all supported attributes', () => { + const cookie = parseSetCookieString( + 'session=abc123; Domain=example.com; Path=/app; Expires=Wed, 21 Oct 2026 07:28:00 GMT; Max-Age=3600; Secure; HttpOnly; SameSite=Lax' + ); + assert.deepEqual(cookie, { + name: 'session', + value: 'abc123', + domain: 'example.com', + path: '/app', + expires: new Date('Wed, 21 Oct 2026 07:28:00 GMT'), + maxAge: 3600, + secure: true, + httpOnly: true, + sameSite: 'Lax', + }); +}); + +test('ignores unknown attributes', () => { + assert.deepEqual(parseSetCookieString('foo=bar; Partitioned; Priority=High'), { + name: 'foo', + value: 'bar', + }); +}); diff --git a/packages/qwik-router/src/ssg/ansi-colors.ts b/packages/qwik-router/src/ssg/ansi-colors.ts new file mode 100644 index 00000000000..add393a31b1 --- /dev/null +++ b/packages/qwik-router/src/ssg/ansi-colors.ts @@ -0,0 +1,24 @@ +// Minimal inlined replacement for `kleur/colors` — same color-support detection. +const isColorEnabled = () => { + const env = typeof process !== 'undefined' ? process.env : undefined; + if (!env || env.NODE_DISABLE_COLORS != null || env.NO_COLOR != null || env.TERM === 'dumb') { + return false; + } + if (env.FORCE_COLOR != null) { + return env.FORCE_COLOR !== '0'; + } + return !!process.stdout?.isTTY; +}; + +const enabled = /*#__PURE__*/ isColorEnabled(); + +const style = + (open: number, close: number) => + (text: string | number): string => + enabled ? `\x1b[${open}m${text}\x1b[${close}m` : String(text); + +export const bold = /*#__PURE__*/ style(1, 22); +export const dim = /*#__PURE__*/ style(2, 22); +export const red = /*#__PURE__*/ style(31, 39); +export const green = /*#__PURE__*/ style(32, 39); +export const magenta = /*#__PURE__*/ style(35, 39); diff --git a/packages/qwik-router/src/ssg/orchestrator.ts b/packages/qwik-router/src/ssg/orchestrator.ts index 439a984ad71..941992d808d 100644 --- a/packages/qwik-router/src/ssg/orchestrator.ts +++ b/packages/qwik-router/src/ssg/orchestrator.ts @@ -1,5 +1,5 @@ import type { PageModule, PathParams, RouteData } from '@qwik.dev/router'; -import { bold, dim, green, magenta, red } from 'kleur/colors'; +import { bold, dim, green, magenta, red } from './ansi-colors'; import { relative } from 'node:path'; import { msToString } from '../utils/format'; import { ensureSlash, getPathnameForDynamicRoute } from '../utils/pathname'; diff --git a/packages/qwik-vite/package.json b/packages/qwik-vite/package.json index 8b3bc454b62..cb47ad3044e 100644 --- a/packages/qwik-vite/package.json +++ b/packages/qwik-vite/package.json @@ -2,7 +2,7 @@ "name": "@qwik.dev/qwik-vite", "devDependencies": { "image-size": "2.0.2", - "magic-string": "0.30.21", + "magic-string": "1.2.3", "rolldown": "1.2.4", "vite": "8.2.1" }, diff --git a/packages/qwik/package.json b/packages/qwik/package.json index b3b15cd678f..fc39a8182d6 100644 --- a/packages/qwik/package.json +++ b/packages/qwik/package.json @@ -11,7 +11,7 @@ "@qwik.dev/optimizer": "workspace:*", "csstype": "^3.2.3", "launch-editor": "^2.14.1", - "magic-string": "0.30.21" + "magic-string": "1.2.3" }, "devDependencies": { "@croct/json5-parser": "0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40720840248..52c25d560fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -595,8 +595,8 @@ importers: specifier: 1.9.4 version: 1.9.4 magic-string: - specifier: 0.30.21 - version: 0.30.21 + specifier: 1.2.3 + version: 1.2.3 pagefind: specifier: 1.4.0 version: 1.4.0 @@ -791,8 +791,8 @@ importers: specifier: ^2.14.1 version: 2.14.1 magic-string: - specifier: 0.30.21 - version: 0.30.21 + specifier: 1.2.3 + version: 1.2.3 devDependencies: '@croct/json5-parser': specifier: 0.2.2 @@ -853,39 +853,12 @@ importers: packages/qwik-router: dependencies: - '@azure/functions': - specifier: 3.5.1 - version: 3.5.1 '@mdx-js/mdx': specifier: ^3.1.1 version: 3.1.1(supports-color@10.2.2) '@netlify/edge-functions': specifier: ^3.0.6 version: 3.0.6 - '@types/mdx': - specifier: ^2.0.13 - version: 2.0.13 - estree-util-value-to-estree: - specifier: ^3.5.0 - version: 3.5.0 - github-slugger: - specifier: ^2.0.0 - version: 2.0.0 - hast-util-heading-rank: - specifier: ^2.1.1 - version: 2.1.1 - hast-util-to-string: - specifier: ^2.0.0 - version: 2.0.0 - kleur: - specifier: ^4.1.5 - version: 4.1.5 - marked: - specifier: ^12.0.2 - version: 12.0.2 - mdast-util-mdx: - specifier: ^3.0.0 - version: 3.0.0(supports-color@10.2.2) mlly: specifier: ^1.8.2 version: 1.8.2 @@ -901,9 +874,9 @@ importers: remark-gfm: specifier: ^4.0.1 version: 4.0.1(supports-color@10.2.2) - set-cookie-parser: - specifier: ^2.7.2 - version: 2.7.2 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0(supports-color@10.2.2) source-map: specifier: ^0.7.6 version: 0.7.6 @@ -938,27 +911,21 @@ importers: '@qwik.dev/core': specifier: workspace:* version: link:../qwik + '@types/estree': + specifier: 1.0.8 + version: 1.0.8 '@types/mdast': specifier: 4.0.4 version: 4.0.4 '@types/node': specifier: 24.10.0 version: 24.10.0 - '@types/refractor': - specifier: 4.0.0 - version: 4.0.0 - '@types/set-cookie-parser': - specifier: 2.4.10 - version: 2.4.10 - tsm: - specifier: 2.3.0 - version: 2.3.0 + mdast-util-mdx: + specifier: 3.0.0 + version: 3.0.0(supports-color@10.2.2) typescript: specifier: 5.9.3 version: 5.9.3 - uvu: - specifier: 0.5.6 - version: 0.5.6 vite: specifier: 8.2.1 version: 8.2.1(@types/node@24.10.0)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) @@ -981,8 +948,8 @@ importers: specifier: 2.0.2 version: 2.0.2(patch_hash=528a7f0bab7d6d539a7b50aba6b45cb5e94dd506574151b0a5adc3a84721800c) magic-string: - specifier: 0.30.21 - version: 0.30.21 + specifier: 1.2.3 + version: 1.2.3 rolldown: specifier: 1.2.4 version: 1.2.4 @@ -1087,9 +1054,6 @@ packages: resolution: {integrity: sha512-DBQXuds7Inna78djvURx6oCfBZ11hyYM56sEfU03Z4iKpYa4QPSN4UGajGGP1oiYOeJXvnRypKnwzRUqDa2uyQ==} engines: {node: '>=18.17'} - '@azure/functions@3.5.1': - resolution: {integrity: sha512-6UltvJiuVpvHSwLcK/Zc6NfUwlkDLOFFx97BHCJzlWNsfiWwzwmTsxJXg4kE/LemKTHxPpfoPE+kOJ8hAdiKFQ==} - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -4434,10 +4398,6 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/refractor@4.0.0': - resolution: {integrity: sha512-qba+Qpzch8TqKwHPw3TIR8VaCK3o7spwnlQd51cbYgrZyBOc9K9kFSdH3zxlAmcD0RN6vWBwFZBe6FwJSfM/LA==} - deprecated: This is a stub types definition. refractor provides its own type definitions, so you do not need this installed. - '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -4450,9 +4410,6 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/set-cookie-parser@2.4.10': - resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} - '@types/tmp@0.2.6': resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} @@ -5945,10 +5902,6 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - diff@5.2.2: - resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} - engines: {node: '>=0.3.1'} - diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -6401,9 +6354,6 @@ packages: estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} - estree-util-value-to-estree@3.5.0: - resolution: {integrity: sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==} - estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} @@ -6844,9 +6794,6 @@ packages: gitconfiglocal@2.1.0: resolution: {integrity: sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==} - github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - github-url-from-git@1.5.0: resolution: {integrity: sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==} @@ -6980,9 +6927,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-heading-rank@2.1.1: - resolution: {integrity: sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==} - hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} @@ -7001,9 +6945,6 @@ packages: hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - hast-util-to-string@2.0.0: - resolution: {integrity: sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==} - hast-util-to-string@3.0.1: resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} @@ -7103,10 +7044,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -8111,9 +8048,6 @@ packages: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -8150,6 +8084,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.2.3: + resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==} + magicast@0.5.2: resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} @@ -8175,11 +8112,6 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - marked@12.0.2: - resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==} - engines: {node: '>= 18'} - hasBin: true - marked@14.0.0: resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} engines: {node: '>= 18'} @@ -9755,10 +9687,6 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -10870,11 +10798,6 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - uvu@0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -11438,12 +11361,6 @@ snapshots: - '@simplewebauthn/server' - nodemailer - '@azure/functions@3.5.1': - dependencies: - iconv-lite: 0.6.3 - long: 4.0.0 - uuid: 11.1.1 - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -14663,10 +14580,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/refractor@4.0.0': - dependencies: - refractor: 4.9.0 - '@types/retry@0.12.2': {} '@types/semver@7.7.1': {} @@ -14680,10 +14593,6 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 24.10.0 - '@types/set-cookie-parser@2.4.10': - dependencies: - '@types/node': 24.10.0 - '@types/tmp@0.2.6': {} '@types/triple-beam@1.3.5': {} @@ -16342,8 +16251,6 @@ snapshots: diff@4.0.4: {} - diff@5.2.2: {} - diff@8.0.4: {} dir-glob@3.0.1: @@ -16854,10 +16761,6 @@ snapshots: astring: 1.9.0 source-map: 0.7.6 - estree-util-value-to-estree@3.5.0: - dependencies: - '@types/estree': 1.0.8 - estree-util-visit@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -17395,8 +17298,6 @@ snapshots: dependencies: ini: 1.3.8 - github-slugger@2.0.0: {} - github-url-from-git@1.5.0: {} glob-parent@5.1.2: @@ -17538,10 +17439,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-heading-rank@2.1.1: - dependencies: - '@types/hast': 2.3.10 - hast-util-heading-rank@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -17609,10 +17506,6 @@ snapshots: transitivePeerDependencies: - supports-color - hast-util-to-string@2.0.0: - dependencies: - '@types/hast': 2.3.10 - hast-util-to-string@3.0.1: dependencies: '@types/hast': 3.0.4 @@ -17729,10 +17622,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -18725,8 +18614,6 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 - long@4.0.0: {} - longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -18763,6 +18650,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@1.2.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.2: dependencies: '@babel/parser': 7.29.2 @@ -18783,8 +18674,6 @@ snapshots: markdown-table@3.0.4: {} - marked@12.0.2: {} - marked@14.0.0: {} marky@1.3.0: {} @@ -20996,10 +20885,6 @@ snapshots: dependencies: tslib: 2.8.1 - sade@1.8.1: - dependencies: - mri: 1.2.0 - safe-array-concat@1.1.3: dependencies: call-bind: 1.0.9 @@ -22127,13 +22012,6 @@ snapshots: uuid@11.1.1: {} - uvu@0.5.6: - dependencies: - dequal: 2.0.3 - diff: 5.2.2 - kleur: 4.1.5 - sade: 1.8.1 - v8-compile-cache-lib@3.0.1: {} valibot@1.4.2(typescript@5.9.3):