vitest-browser-qwik 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/{pure-CGnRrG6t.js → pure-BWRD5UQA.js} +1 -1
- package/dist/{pure-CoHVQ6I9.d.ts → pure-Cwz8HPNP.d.ts} +1 -1
- package/dist/pure.d.ts +1 -1
- package/dist/pure.js +1 -1
- package/dist/ssr-plugin.d.ts +6 -0
- package/dist/ssr-plugin.js +248 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A modern testing setup demonstrating browser-based testing for Qwik components u
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npm install -D vitest-browser-qwik
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Core Features
|
|
@@ -15,6 +15,27 @@ pnpm add vitest-browser-qwik
|
|
|
15
15
|
- **`renderHook`** - Hook testing utilities (currently only CSR supported)
|
|
16
16
|
- All functions are async for predictable testing behavior
|
|
17
17
|
|
|
18
|
+
## Vitest Config Setup
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { defineConfig } from 'vitest/config'
|
|
22
|
+
import { qwikVite } from '@builder.io/qwik/optimizer'
|
|
23
|
+
|
|
24
|
+
// optional, run the tests in SSR mode
|
|
25
|
+
import { testSSR } from 'vitest-browser-qwik/ssr-plugin'
|
|
26
|
+
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
plugins: [testSSR(), qwikVite()],
|
|
29
|
+
test: {
|
|
30
|
+
browser: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
provider: 'playwright',
|
|
33
|
+
instances: [{ browser: 'chromium' }]
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
18
39
|
### Client-Side Rendering Example
|
|
19
40
|
|
|
20
41
|
```tsx
|
|
@@ -113,6 +134,10 @@ test('renders with custom container', async () => {
|
|
|
113
134
|
- **SSR Context**: `renderSSR` executes components in a Node.js context separate from your test files, providing true server-side rendering simulation
|
|
114
135
|
- **Same Interface**: Both CSR and SSR provide the same testing interface, making it easy to test both rendering modes
|
|
115
136
|
|
|
137
|
+
## Compatibility
|
|
138
|
+
In testing, we have observed render issues with Vite 5.x. We recommend using Vite 6+. Qwik 1 currently specifies Vite 5.x,
|
|
139
|
+
but Vite 6.x should work as well.
|
|
140
|
+
|
|
116
141
|
## Limitations
|
|
117
142
|
|
|
118
143
|
- For `renderSSR` you must always import the component from another file, local components are not supported. This is because this would require importing the vitest context, or moving local components into separate files dynamically, which involves a lot of unwanted complexity.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RenderResult, SSRRenderOptions, cleanup$1 as cleanup, render$1 as render, renderHook$1 as renderHook, renderServerHTML$1 as renderServerHTML } from "./pure-
|
|
1
|
+
import { RenderResult, SSRRenderOptions, cleanup$1 as cleanup, render$1 as render, renderHook$1 as renderHook, renderServerHTML$1 as renderServerHTML } from "./pure-Cwz8HPNP.js";
|
|
2
2
|
import { JSXOutput } from "@builder.io/qwik";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cleanup, render, renderHook, renderServerHTML } from "./pure-
|
|
1
|
+
import { cleanup, render, renderHook, renderServerHTML } from "./pure-BWRD5UQA.js";
|
|
2
2
|
import { page } from "@vitest/browser/context";
|
|
3
3
|
import { beforeEach } from "vitest";
|
|
4
4
|
|
|
@@ -8,8 +8,8 @@ page.extend({
|
|
|
8
8
|
renderServerHTML,
|
|
9
9
|
[Symbol.for("vitest:component-cleanup")]: cleanup
|
|
10
10
|
});
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
cleanup();
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
await cleanup();
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
//#endregion
|
|
@@ -83,7 +83,7 @@ async function renderHook(hook) {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
function cleanup() {
|
|
86
|
+
async function cleanup() {
|
|
87
87
|
mountedContainers.forEach((container) => {
|
|
88
88
|
container.innerHTML = "";
|
|
89
89
|
if (container.parentNode === document.body) document.body.removeChild(container);
|
|
@@ -31,6 +31,6 @@ interface RenderHookResult<Result> {
|
|
|
31
31
|
unmount: () => void;
|
|
32
32
|
}
|
|
33
33
|
declare function renderHook<Result>(hook: () => Result): Promise<RenderHookResult<Result>>;
|
|
34
|
-
declare function cleanup(): void
|
|
34
|
+
declare function cleanup(): Promise<void>;
|
|
35
35
|
//#endregion
|
|
36
36
|
export { RenderHookResult, RenderOptions, RenderResult, SSRRenderOptions, cleanup as cleanup$1, render$1, renderHook as renderHook$1, renderServerHTML as renderServerHTML$1 };
|
package/dist/pure.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { RenderHookResult, RenderOptions, RenderResult, SSRRenderOptions, cleanup$1 as cleanup, render$1 as render, renderHook$1 as renderHook, renderServerHTML$1 as renderServerHTML } from "./pure-
|
|
1
|
+
import { RenderHookResult, RenderOptions, RenderResult, SSRRenderOptions, cleanup$1 as cleanup, render$1 as render, renderHook$1 as renderHook, renderServerHTML$1 as renderServerHTML } from "./pure-Cwz8HPNP.js";
|
|
2
2
|
export { RenderHookResult, RenderOptions, RenderResult, SSRRenderOptions, cleanup, render, renderHook, renderServerHTML };
|
package/dist/pure.js
CHANGED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { dirname, relative, resolve } from "node:path";
|
|
2
|
+
import { symbolMapper } from "@builder.io/qwik/optimizer";
|
|
3
|
+
|
|
4
|
+
//#region src/ssr-plugin.ts
|
|
5
|
+
function traverseChildren(node, callback) {
|
|
6
|
+
for (const key in node) {
|
|
7
|
+
const child = node[key];
|
|
8
|
+
if (Array.isArray(child)) {
|
|
9
|
+
for (const item of child) if (item && typeof item === "object" && callback(item)) return true;
|
|
10
|
+
} else if (child && typeof child === "object") {
|
|
11
|
+
if (callback(child)) return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
async function hasRenderSSRCall(code, filename) {
|
|
17
|
+
try {
|
|
18
|
+
const { parseSync } = await import("oxc-parser");
|
|
19
|
+
const ast = parseSync(filename, code);
|
|
20
|
+
const renderSSRIdentifiers = new Set(["renderSSR"]);
|
|
21
|
+
let hasRenderSSRCallInCode = false;
|
|
22
|
+
function walkForDetection(node) {
|
|
23
|
+
if (!node || typeof node !== "object") return false;
|
|
24
|
+
if (node.type === "ImportDeclaration") {
|
|
25
|
+
const importDecl = node;
|
|
26
|
+
if (!importDecl.source?.value || !importDecl.specifiers) return false;
|
|
27
|
+
for (const spec of importDecl.specifiers) if (spec.type === "ImportSpecifier") {
|
|
28
|
+
const importSpec = spec;
|
|
29
|
+
if (importSpec.imported.type !== "Identifier") continue;
|
|
30
|
+
if (importSpec.imported.name === "renderSSR") renderSSRIdentifiers.add(importSpec.local.name);
|
|
31
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
32
|
+
const defaultSpec = spec;
|
|
33
|
+
if (defaultSpec.local.name.toLowerCase().includes("renderssr")) renderSSRIdentifiers.add(defaultSpec.local.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (node.type === "FunctionDeclaration") {
|
|
37
|
+
const funcDecl = node;
|
|
38
|
+
if (funcDecl.id?.name === "renderSSR") renderSSRIdentifiers.add("renderSSR");
|
|
39
|
+
}
|
|
40
|
+
if (node.type === "TSDeclareFunction") {
|
|
41
|
+
const declareFunc = node;
|
|
42
|
+
if (declareFunc.id?.name === "renderSSR") renderSSRIdentifiers.add("renderSSR");
|
|
43
|
+
}
|
|
44
|
+
if (node.type === "VariableDeclarator") {
|
|
45
|
+
const varDecl = node;
|
|
46
|
+
if (varDecl.id.type !== "Identifier") return false;
|
|
47
|
+
if (varDecl.init?.type !== "Identifier") return false;
|
|
48
|
+
if (!renderSSRIdentifiers.has(varDecl.init.name)) return false;
|
|
49
|
+
const bindingId = varDecl.id;
|
|
50
|
+
renderSSRIdentifiers.add(bindingId.name);
|
|
51
|
+
}
|
|
52
|
+
if (node.type === "CallExpression") {
|
|
53
|
+
const callExpr = node;
|
|
54
|
+
if (callExpr.callee.type === "Identifier") {
|
|
55
|
+
if (renderSSRIdentifiers.has(callExpr.callee.name)) {
|
|
56
|
+
hasRenderSSRCallInCode = true;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return traverseChildren(node, walkForDetection);
|
|
62
|
+
}
|
|
63
|
+
walkForDetection(ast);
|
|
64
|
+
const hasCallsInString = code.includes("renderSSR(");
|
|
65
|
+
const result = hasRenderSSRCallInCode || hasCallsInString;
|
|
66
|
+
return result;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn(`Failed to parse ${filename} for renderSSR detection, falling back to string check:`, error);
|
|
69
|
+
return code.includes("renderSSR");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function resolveComponentPath(importPath, testFileId) {
|
|
73
|
+
if (!importPath.startsWith(".")) return importPath.endsWith(".tsx") || importPath.endsWith(".ts") ? importPath : `${importPath}.tsx`;
|
|
74
|
+
const testFileDir = dirname(testFileId);
|
|
75
|
+
const resolvedPath = resolve(testFileDir, importPath);
|
|
76
|
+
const projectRoot = process.cwd();
|
|
77
|
+
let componentPath = `./${relative(projectRoot, resolvedPath)}`;
|
|
78
|
+
if (!componentPath.endsWith(".tsx") && !componentPath.endsWith(".ts")) componentPath += ".tsx";
|
|
79
|
+
return componentPath;
|
|
80
|
+
}
|
|
81
|
+
function extractPropsFromJSX(attributes, sourceCode) {
|
|
82
|
+
const props = {};
|
|
83
|
+
for (const attr of attributes) {
|
|
84
|
+
if (attr.type !== "JSXAttribute") continue;
|
|
85
|
+
const jsxAttr = attr;
|
|
86
|
+
if (jsxAttr.name.type !== "JSXIdentifier") continue;
|
|
87
|
+
const propName = jsxAttr.name.name;
|
|
88
|
+
if (!jsxAttr.value) continue;
|
|
89
|
+
if (jsxAttr.value.type === "JSXExpressionContainer") {
|
|
90
|
+
const container = jsxAttr.value;
|
|
91
|
+
if (container.expression.type !== "JSXEmptyExpression") {
|
|
92
|
+
const exprSpan = container.expression;
|
|
93
|
+
const expressionCode = sourceCode.slice(exprSpan.start, exprSpan.end);
|
|
94
|
+
props[propName] = expressionCode;
|
|
95
|
+
}
|
|
96
|
+
} else if (jsxAttr.value.type === "Literal") {
|
|
97
|
+
const literal = jsxAttr.value;
|
|
98
|
+
props[propName] = JSON.stringify(literal.value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return props;
|
|
102
|
+
}
|
|
103
|
+
function isTestFile(id) {
|
|
104
|
+
return id.includes(".test.") || id.includes(".spec.");
|
|
105
|
+
}
|
|
106
|
+
function hasCommandsImport(node) {
|
|
107
|
+
if (node.type !== "ImportDeclaration") return false;
|
|
108
|
+
const importDecl = node;
|
|
109
|
+
if (importDecl.source?.value !== "@vitest/browser/context") return false;
|
|
110
|
+
if (!importDecl.specifiers) return false;
|
|
111
|
+
return importDecl.specifiers.some((spec) => spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "commands");
|
|
112
|
+
}
|
|
113
|
+
const renderSSRCommand = async (ctx, componentPath, componentName, props = {}) => {
|
|
114
|
+
try {
|
|
115
|
+
const projectRoot = process.cwd();
|
|
116
|
+
const absoluteComponentPath = resolve(projectRoot, componentPath);
|
|
117
|
+
const viteServer = ctx.project.vite;
|
|
118
|
+
for (const [key, value] of Object.entries(viteServer.config.env)) viteServer.config.define[`__vite_ssr_import_meta__.env.${key}`] = JSON.stringify(value);
|
|
119
|
+
const componentModule = await viteServer.ssrLoadModule(absoluteComponentPath);
|
|
120
|
+
const Component = componentModule[componentName];
|
|
121
|
+
if (!Component) throw new Error(`Component "${componentName}" not found in ${absoluteComponentPath}`);
|
|
122
|
+
const qwikModule = await viteServer.ssrLoadModule("@builder.io/qwik");
|
|
123
|
+
const { jsx } = qwikModule;
|
|
124
|
+
const jsxElement = jsx(Component, props);
|
|
125
|
+
const serverModule = await viteServer.ssrLoadModule("@builder.io/qwik/server");
|
|
126
|
+
const { renderToString } = serverModule;
|
|
127
|
+
const result = await renderToString(jsxElement, {
|
|
128
|
+
containerTagName: "div",
|
|
129
|
+
base: "/",
|
|
130
|
+
qwikLoader: { include: "always" },
|
|
131
|
+
symbolMapper: globalThis.qwikSymbolMapper
|
|
132
|
+
});
|
|
133
|
+
return { html: result.html };
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("SSR Command Error:", error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function testSSR() {
|
|
140
|
+
return {
|
|
141
|
+
name: "vitest:ssr-transform",
|
|
142
|
+
enforce: "pre",
|
|
143
|
+
async transform(code, id) {
|
|
144
|
+
if (!isTestFile(id)) return null;
|
|
145
|
+
if (!await hasRenderSSRCall(code, id)) return null;
|
|
146
|
+
try {
|
|
147
|
+
const { parseSync } = await import("oxc-parser");
|
|
148
|
+
const MagicString = (await import("magic-string")).default;
|
|
149
|
+
const ast = parseSync(id, code);
|
|
150
|
+
const s = new MagicString(code);
|
|
151
|
+
const componentImports = new Map();
|
|
152
|
+
const renderSSRIdentifiers = new Set(["renderSSR"]);
|
|
153
|
+
let hasExistingCommandsImport = false;
|
|
154
|
+
function walkForTransformation(node) {
|
|
155
|
+
if (!node || typeof node !== "object") return;
|
|
156
|
+
if (node.type === "ImportDeclaration") {
|
|
157
|
+
const importDecl = node;
|
|
158
|
+
if (importDecl.source?.value && importDecl.specifiers) {
|
|
159
|
+
const source = importDecl.source.value;
|
|
160
|
+
for (const spec of importDecl.specifiers) if (spec.type === "ImportSpecifier") {
|
|
161
|
+
const importSpec = spec;
|
|
162
|
+
if (importSpec.imported.type === "Identifier") {
|
|
163
|
+
componentImports.set(importSpec.imported.name, source);
|
|
164
|
+
if (importSpec.imported.name === "renderSSR") renderSSRIdentifiers.add(importSpec.local.name);
|
|
165
|
+
}
|
|
166
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
167
|
+
const defaultSpec = spec;
|
|
168
|
+
if (defaultSpec.local.name.toLowerCase().includes("renderssr")) renderSSRIdentifiers.add(defaultSpec.local.name);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (node.type === "VariableDeclarator") {
|
|
173
|
+
const varDecl = node;
|
|
174
|
+
if (varDecl.id.type === "Identifier" && varDecl.init?.type === "Identifier" && renderSSRIdentifiers.has(varDecl.init.name)) {
|
|
175
|
+
const bindingId = varDecl.id;
|
|
176
|
+
renderSSRIdentifiers.add(bindingId.name);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (hasCommandsImport(node)) hasExistingCommandsImport = true;
|
|
180
|
+
if (node.type === "CallExpression") {
|
|
181
|
+
const callExpr = node;
|
|
182
|
+
if (callExpr.callee.type === "Identifier" && renderSSRIdentifiers.has(callExpr.callee.name)) {
|
|
183
|
+
const jsxArg = callExpr.arguments?.[0];
|
|
184
|
+
if (jsxArg?.type === "JSXElement") {
|
|
185
|
+
const jsxElement = jsxArg;
|
|
186
|
+
if (jsxElement.openingElement?.name?.type === "JSXIdentifier") {
|
|
187
|
+
const componentName = jsxElement.openingElement.name.name;
|
|
188
|
+
const componentImportPath = componentImports.get(componentName);
|
|
189
|
+
if (componentImportPath) {
|
|
190
|
+
const componentPath = resolveComponentPath(componentImportPath, id);
|
|
191
|
+
const props = extractPropsFromJSX(jsxElement.openingElement.attributes || [], code);
|
|
192
|
+
let propsStr = "";
|
|
193
|
+
if (Object.keys(props).length > 0) {
|
|
194
|
+
const propsEntries = Object.entries(props).map(([key, value]) => {
|
|
195
|
+
return `${JSON.stringify(key)}: ${value}`;
|
|
196
|
+
});
|
|
197
|
+
propsStr = `, { ${propsEntries.join(", ")} }`;
|
|
198
|
+
}
|
|
199
|
+
const replacement = `(async () => {
|
|
200
|
+
const { html } = await commands.renderSSR("${componentPath}", "${componentName}"${propsStr});
|
|
201
|
+
return renderServerHTML(html);
|
|
202
|
+
})()`;
|
|
203
|
+
const spanNode = node;
|
|
204
|
+
s.overwrite(spanNode.start, spanNode.end, replacement);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
traverseChildren(node, walkForTransformation);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
walkForTransformation(ast);
|
|
214
|
+
if (!hasExistingCommandsImport && s.hasChanged()) {
|
|
215
|
+
let lastImportEnd = 0;
|
|
216
|
+
function findLastImport(node) {
|
|
217
|
+
if (!node || typeof node !== "object") return;
|
|
218
|
+
if (node.type === "ImportDeclaration") {
|
|
219
|
+
const spanNode = node;
|
|
220
|
+
lastImportEnd = Math.max(lastImportEnd, spanNode.end);
|
|
221
|
+
}
|
|
222
|
+
traverseChildren(node, findLastImport);
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
findLastImport(ast);
|
|
226
|
+
if (lastImportEnd > 0) s.appendLeft(lastImportEnd, "\nimport { commands } from \"@vitest/browser/context\";\nimport { renderServerHTML } from \"vitest-browser-qwik\";");
|
|
227
|
+
}
|
|
228
|
+
if (s.hasChanged()) return {
|
|
229
|
+
code: s.toString(),
|
|
230
|
+
map: s.generateMap({ hires: true })
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(`Failed to transform ${id}:`, error);
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
},
|
|
237
|
+
configResolved(config) {
|
|
238
|
+
globalThis.qwikSymbolMapper = symbolMapper;
|
|
239
|
+
if (config.test?.browser?.enabled) config.test.browser.commands = {
|
|
240
|
+
...config.test.browser.commands,
|
|
241
|
+
renderSSR: renderSSRCommand
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
export { testSSR };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-browser-qwik",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Render Qwik components using Vitest Browser Mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,28 +29,35 @@
|
|
|
29
29
|
"./pure": {
|
|
30
30
|
"types": "./dist/pure.d.ts",
|
|
31
31
|
"default": "./dist/pure.js"
|
|
32
|
+
},
|
|
33
|
+
"./ssr-plugin": {
|
|
34
|
+
"types": "./dist/ssr-plugin.d.ts",
|
|
35
|
+
"default": "./dist/ssr-plugin.js"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"publishConfig": {
|
|
35
39
|
"access": "public"
|
|
36
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"magic-string": "^0.30.17",
|
|
43
|
+
"oxc-parser": "^0.73.2"
|
|
44
|
+
},
|
|
37
45
|
"peerDependencies": {
|
|
38
46
|
"@builder.io/qwik": "^1.14.1",
|
|
39
47
|
"@vitest/browser": "^3.2.4",
|
|
48
|
+
"vite": ">=6.3.5",
|
|
40
49
|
"vitest": "^3.1.3"
|
|
41
50
|
},
|
|
42
51
|
"devDependencies": {
|
|
43
52
|
"@biomejs/biome": "2.0.0",
|
|
44
53
|
"@builder.io/qwik": "1.14.1",
|
|
45
54
|
"@oxc-project/types": "^0.73.2",
|
|
55
|
+
"@playwright/test": "see flake.nix",
|
|
46
56
|
"@types/node": "^22.15.17",
|
|
47
57
|
"@vitest/browser": "^3.2.4",
|
|
48
58
|
"bumpp": "^10.1.0",
|
|
49
|
-
"magic-string": "^0.30.17",
|
|
50
|
-
"oxc-parser": "^0.73.2",
|
|
51
59
|
"oxc-resolver": "^11.2.0",
|
|
52
60
|
"oxc-walker": "^0.3.0",
|
|
53
|
-
"@playwright/test": "see flake.nix",
|
|
54
61
|
"tsdown": "^0.11.9",
|
|
55
62
|
"tsx": "^4.19.4",
|
|
56
63
|
"typescript": "^5.8.3",
|