@taujs/server 0.0.8 → 0.1.0
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 +1 -1
- package/dist/data.d.ts +21 -9
- package/dist/data.js +58 -21
- package/dist/index.d.ts +17 -4
- package/dist/index.js +63 -38
- package/package.json +5 -3
package/README.md
CHANGED
package/dist/data.d.ts
CHANGED
|
@@ -7,11 +7,11 @@ type SSRStore<T> = {
|
|
|
7
7
|
setData: (newData: T) => void;
|
|
8
8
|
subscribe: (callback: () => void) => () => void;
|
|
9
9
|
};
|
|
10
|
-
declare const createSSRStore: <T>(
|
|
10
|
+
declare const createSSRStore: <T>(initialDataOrPromise: T | Promise<T>) => SSRStore<T>;
|
|
11
11
|
declare const SSRStoreProvider: React.FC<React.PropsWithChildren<{
|
|
12
12
|
store: SSRStore<Record<string, unknown>>;
|
|
13
13
|
}>>;
|
|
14
|
-
declare const useSSRStore: <T>() =>
|
|
14
|
+
declare const useSSRStore: <T>() => T;
|
|
15
15
|
|
|
16
16
|
type HydrateAppOptions = {
|
|
17
17
|
appComponent: React.ReactElement;
|
|
@@ -21,17 +21,29 @@ type HydrateAppOptions = {
|
|
|
21
21
|
};
|
|
22
22
|
declare const hydrateApp: ({ appComponent, initialDataKey, rootElementId, debug }: HydrateAppOptions) => void;
|
|
23
23
|
|
|
24
|
-
type
|
|
25
|
-
appComponent:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
headContent: string;
|
|
24
|
+
type RendererOptions = {
|
|
25
|
+
appComponent: (props: {
|
|
26
|
+
location: string;
|
|
27
|
+
}) => React.ReactElement;
|
|
28
|
+
headContent: string | ((data: Record<string, unknown>) => string);
|
|
29
29
|
};
|
|
30
30
|
type RenderCallbacks = {
|
|
31
31
|
onHead: (headContent: string) => void;
|
|
32
32
|
onFinish: (initialDataResolved: unknown) => void;
|
|
33
33
|
onError: (error: unknown) => void;
|
|
34
34
|
};
|
|
35
|
-
declare const
|
|
35
|
+
declare const resolveHeadContent: (headContent: string | ((meta: Record<string, unknown>) => string), meta?: Record<string, unknown>) => string;
|
|
36
|
+
declare const createRenderer: ({ appComponent, headContent }: RendererOptions) => {
|
|
37
|
+
renderSSR: (initialDataResolved: Record<string, unknown>, location: string, meta?: Record<string, unknown>) => Promise<{
|
|
38
|
+
headContent: string;
|
|
39
|
+
appHtml: string;
|
|
40
|
+
}>;
|
|
41
|
+
renderStream: (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataResolved: Record<string, unknown>, location: string, bootstrapModules?: string, meta?: Record<string, unknown>) => void;
|
|
42
|
+
};
|
|
43
|
+
declare const createRenderStream: (serverResponse: ServerResponse, { onHead, onFinish, onError }: RenderCallbacks, { appComponent, headContent, initialDataResolved, location, bootstrapModules, }: RendererOptions & {
|
|
44
|
+
initialDataResolved: Record<string, unknown>;
|
|
45
|
+
location: string;
|
|
46
|
+
bootstrapModules?: string;
|
|
47
|
+
}) => void;
|
|
36
48
|
|
|
37
|
-
export { type SSRStore, SSRStoreProvider,
|
|
49
|
+
export { type SSRStore, SSRStoreProvider, createRenderStream, createRenderer, createSSRStore, hydrateApp, resolveHeadContent, useSSRStore };
|
package/dist/data.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
// src/SSRDataStore.tsx
|
|
2
2
|
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
var createSSRStore = (
|
|
4
|
+
var createSSRStore = (initialDataOrPromise) => {
|
|
5
5
|
let currentData;
|
|
6
|
-
let status
|
|
6
|
+
let status;
|
|
7
7
|
const subscribers = /* @__PURE__ */ new Set();
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let serverDataPromise;
|
|
9
|
+
if (initialDataOrPromise instanceof Promise) {
|
|
10
|
+
status = "pending";
|
|
11
|
+
serverDataPromise = initialDataOrPromise.then((data) => {
|
|
12
|
+
currentData = data;
|
|
13
|
+
status = "success";
|
|
14
|
+
subscribers.forEach((callback) => callback());
|
|
15
|
+
}).catch((error) => {
|
|
16
|
+
console.error("Failed to load initial data:", error);
|
|
17
|
+
status = "error";
|
|
18
|
+
}).then(() => {
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
currentData = initialDataOrPromise;
|
|
12
22
|
status = "success";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}).catch((error) => {
|
|
16
|
-
console.error("Failed to load initial data:", error);
|
|
17
|
-
status = "error";
|
|
18
|
-
});
|
|
23
|
+
serverDataPromise = Promise.resolve();
|
|
24
|
+
}
|
|
19
25
|
const setData = (newData) => {
|
|
20
26
|
currentData = newData;
|
|
21
27
|
status = "success";
|
|
22
28
|
subscribers.forEach((callback) => callback());
|
|
23
|
-
if (resolvePromise) resolvePromise();
|
|
24
29
|
};
|
|
25
30
|
const subscribe = (callback) => {
|
|
26
31
|
subscribers.add(callback);
|
|
@@ -28,7 +33,7 @@ var createSSRStore = (initialDataPromise) => {
|
|
|
28
33
|
};
|
|
29
34
|
const getSnapshot = () => {
|
|
30
35
|
if (status === "pending") {
|
|
31
|
-
throw
|
|
36
|
+
throw serverDataPromise;
|
|
32
37
|
} else if (status === "error") {
|
|
33
38
|
throw new Error("An error occurred while fetching the data.");
|
|
34
39
|
}
|
|
@@ -105,15 +110,45 @@ var hydrateApp = ({ appComponent, initialDataKey = "__INITIAL_DATA__", rootEleme
|
|
|
105
110
|
// src/SSRRender.tsx
|
|
106
111
|
import { Writable } from "node:stream";
|
|
107
112
|
import "react";
|
|
108
|
-
import { renderToPipeableStream } from "react-dom/server";
|
|
113
|
+
import { renderToPipeableStream, renderToString } from "react-dom/server";
|
|
109
114
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
110
|
-
var
|
|
111
|
-
|
|
112
|
-
const
|
|
115
|
+
var resolveHeadContent = (headContent, meta = {}) => typeof headContent === "function" ? headContent(meta) : headContent;
|
|
116
|
+
var createRenderer = ({ appComponent, headContent }) => {
|
|
117
|
+
const renderSSR = async (initialDataResolved, location, meta = {}) => {
|
|
118
|
+
const dataForHeadContent = Object.keys(initialDataResolved).length > 0 ? initialDataResolved : meta;
|
|
119
|
+
const dynamicHeadContent = resolveHeadContent(headContent, dataForHeadContent);
|
|
120
|
+
const appHtml = renderToString(/* @__PURE__ */ jsx3(SSRStoreProvider, { store: createSSRStore(initialDataResolved), children: appComponent({ location }) }));
|
|
121
|
+
return {
|
|
122
|
+
headContent: dynamicHeadContent,
|
|
123
|
+
appHtml
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const renderStream = (serverResponse, callbacks, initialDataResolved, location, bootstrapModules, meta = {}) => {
|
|
127
|
+
const dynamicHeadContent = resolveHeadContent(headContent, meta);
|
|
128
|
+
createRenderStream(serverResponse, callbacks, {
|
|
129
|
+
appComponent: (props) => appComponent({ ...props, location }),
|
|
130
|
+
headContent: dynamicHeadContent,
|
|
131
|
+
initialDataResolved,
|
|
132
|
+
location,
|
|
133
|
+
bootstrapModules
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
return { renderSSR, renderStream };
|
|
137
|
+
};
|
|
138
|
+
var createRenderStream = (serverResponse, { onHead, onFinish, onError }, {
|
|
139
|
+
appComponent,
|
|
140
|
+
headContent,
|
|
141
|
+
initialDataResolved,
|
|
142
|
+
location,
|
|
143
|
+
bootstrapModules
|
|
144
|
+
}) => {
|
|
145
|
+
const store = createSSRStore(initialDataResolved);
|
|
146
|
+
const appElement = /* @__PURE__ */ jsx3(SSRStoreProvider, { store, children: appComponent({ location }) });
|
|
113
147
|
const { pipe } = renderToPipeableStream(appElement, {
|
|
114
|
-
bootstrapModules: [bootstrapModules],
|
|
148
|
+
bootstrapModules: bootstrapModules ? [bootstrapModules] : void 0,
|
|
115
149
|
onShellReady() {
|
|
116
|
-
|
|
150
|
+
const dynamicHeadContent = resolveHeadContent(headContent, initialDataResolved);
|
|
151
|
+
onHead(dynamicHeadContent);
|
|
117
152
|
pipe(
|
|
118
153
|
new Writable({
|
|
119
154
|
write(chunk, _encoding, callback) {
|
|
@@ -135,8 +170,10 @@ var createStreamRenderer = (serverResponse, { onHead, onFinish, onError }, { app
|
|
|
135
170
|
};
|
|
136
171
|
export {
|
|
137
172
|
SSRStoreProvider,
|
|
173
|
+
createRenderStream,
|
|
174
|
+
createRenderer,
|
|
138
175
|
createSSRStore,
|
|
139
|
-
createStreamRenderer,
|
|
140
176
|
hydrateApp,
|
|
177
|
+
resolveHeadContent,
|
|
141
178
|
useSSRStore
|
|
142
179
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { FastifyPluginAsync } from 'fastify';
|
|
2
2
|
import { ServerResponse } from 'node:http';
|
|
3
3
|
|
|
4
|
+
declare const RENDERTYPE: {
|
|
5
|
+
ssr: string;
|
|
6
|
+
streaming: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
declare const SSRServer: FastifyPluginAsync<SSRServerOptions>;
|
|
5
10
|
type ServiceRegistry = {
|
|
6
11
|
[serviceName: string]: {
|
|
@@ -40,9 +45,15 @@ type Manifest = {
|
|
|
40
45
|
assets?: string[];
|
|
41
46
|
};
|
|
42
47
|
};
|
|
43
|
-
type
|
|
48
|
+
type RenderSSR = (initialDataResolved: Record<string, unknown>, location: string, meta?: Record<string, unknown>) => Promise<{
|
|
49
|
+
headContent: string;
|
|
50
|
+
appHtml: string;
|
|
51
|
+
initialDataScript: string;
|
|
52
|
+
}>;
|
|
53
|
+
type RenderStream = (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataPromise: Promise<Record<string, unknown>>, location: string, bootstrapModules?: string, meta?: Record<string, unknown>) => void;
|
|
44
54
|
type RenderModule = {
|
|
45
|
-
|
|
55
|
+
renderSSR: RenderSSR;
|
|
56
|
+
renderStream: RenderStream;
|
|
46
57
|
};
|
|
47
58
|
type RouteAttributes<Params = {}> = {
|
|
48
59
|
fetch: (params?: Params, options?: RequestInit & {
|
|
@@ -55,9 +66,11 @@ type RouteAttributes<Params = {}> = {
|
|
|
55
66
|
serviceMethod?: string;
|
|
56
67
|
url?: string;
|
|
57
68
|
}>;
|
|
69
|
+
meta?: Record<string, unknown>;
|
|
70
|
+
render?: typeof RENDERTYPE.ssr | typeof RENDERTYPE.streaming;
|
|
58
71
|
};
|
|
59
72
|
type Route<Params = {}> = {
|
|
60
|
-
|
|
73
|
+
attr?: RouteAttributes<Params>;
|
|
61
74
|
path: string;
|
|
62
75
|
};
|
|
63
76
|
interface InitialRouteParams extends Record<string, unknown> {
|
|
@@ -67,4 +80,4 @@ interface InitialRouteParams extends Record<string, unknown> {
|
|
|
67
80
|
type RouteParams = InitialRouteParams & Record<string, unknown>;
|
|
68
81
|
type RoutePathsAndAttributes<Params = {}> = Omit<Route<Params>, 'element'>;
|
|
69
82
|
|
|
70
|
-
export { type FetchConfig, type InitialRouteParams, type Manifest, type RenderCallbacks, type RenderModule, type Route, type RouteAttributes, type RouteParams, type RoutePathsAndAttributes, SSRServer, type SSRServerOptions, type ServiceRegistry
|
|
83
|
+
export { type FetchConfig, type InitialRouteParams, type Manifest, type RenderCallbacks, type RenderModule, type RenderSSR, type RenderStream, type Route, type RouteAttributes, type RouteParams, type RoutePathsAndAttributes, SSRServer, type SSRServerOptions, type ServiceRegistry };
|
package/dist/index.js
CHANGED
|
@@ -211,9 +211,9 @@ var fetchData = async ({ url, options }) => {
|
|
|
211
211
|
}
|
|
212
212
|
throw new Error("URL must be provided to fetch data");
|
|
213
213
|
};
|
|
214
|
-
var fetchInitialData = async (
|
|
215
|
-
if (
|
|
216
|
-
return
|
|
214
|
+
var fetchInitialData = async (attr, params, serviceRegistry) => {
|
|
215
|
+
if (attr && typeof attr.fetch === "function") {
|
|
216
|
+
return attr.fetch(params, {
|
|
217
217
|
headers: { "Content-Type": "application/json" },
|
|
218
218
|
params
|
|
219
219
|
}).then(async (data) => {
|
|
@@ -257,6 +257,16 @@ var overrideCSSHMRConsoleError = () => {
|
|
|
257
257
|
};
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
+
// src/constants.ts
|
|
261
|
+
var RENDERTYPE = {
|
|
262
|
+
ssr: "ssr",
|
|
263
|
+
streaming: "streaming"
|
|
264
|
+
};
|
|
265
|
+
var SSRTAG = {
|
|
266
|
+
ssrHead: "<!--ssr-head-->",
|
|
267
|
+
ssrHtml: "<!--ssr-html-->"
|
|
268
|
+
};
|
|
269
|
+
|
|
260
270
|
// src/SSRServer.ts
|
|
261
271
|
var SSRServer = (0, import_fastify_plugin.default)(
|
|
262
272
|
async (app, opts) => {
|
|
@@ -275,6 +285,12 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
275
285
|
let template = templateHtml;
|
|
276
286
|
let viteDevServer;
|
|
277
287
|
let viteRuntime;
|
|
288
|
+
void await app.register(import("@fastify/static"), {
|
|
289
|
+
index: false,
|
|
290
|
+
prefix: "/",
|
|
291
|
+
root: clientRoot,
|
|
292
|
+
wildcard: false
|
|
293
|
+
});
|
|
278
294
|
if (isDevelopment) {
|
|
279
295
|
const { createServer } = await import("vite");
|
|
280
296
|
viteDevServer = await createServer({
|
|
@@ -326,14 +342,10 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
326
342
|
});
|
|
327
343
|
} else {
|
|
328
344
|
renderModule = await import(path.join(clientRoot, `${clientEntryServer}.js`));
|
|
329
|
-
void await app.register(import("@fastify/static"), {
|
|
330
|
-
index: false,
|
|
331
|
-
root: path.resolve(clientRoot),
|
|
332
|
-
wildcard: false
|
|
333
|
-
});
|
|
334
345
|
}
|
|
335
346
|
void app.get("/*", async (req, reply) => {
|
|
336
347
|
try {
|
|
348
|
+
if (/\.\w+$/.test(req.raw.url ?? "")) return reply.callNotFound();
|
|
337
349
|
const url = req.url ? new URL(req.url, `http://${req.headers.host}`).pathname : "/";
|
|
338
350
|
const matchedRoute = matchRoute(url, routes);
|
|
339
351
|
if (!matchedRoute) {
|
|
@@ -341,41 +353,54 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
341
353
|
return;
|
|
342
354
|
}
|
|
343
355
|
if (isDevelopment) {
|
|
344
|
-
template =
|
|
356
|
+
template = template.replace(/<script type="module" src="\/@vite\/client"><\/script>/g, "");
|
|
357
|
+
template = template.replace(/<style type="text\/css">[\s\S]*?<\/style>/g, "");
|
|
345
358
|
renderModule = await viteRuntime.executeEntrypoint(path.join(clientRoot, `${clientEntryServer}.tsx`));
|
|
346
359
|
styles = await collectStyle(viteDevServer, [`${clientRoot}/${clientEntryServer}.tsx`]);
|
|
347
360
|
template = template.replace("</head>", `<style type="text/css">${styles}</style></head>`);
|
|
361
|
+
template = await viteDevServer.transformIndexHtml(url, template);
|
|
348
362
|
}
|
|
349
|
-
const { streamRender } = renderModule;
|
|
350
363
|
const { route, params } = matchedRoute;
|
|
351
|
-
const {
|
|
352
|
-
const
|
|
353
|
-
const [
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
364
|
+
const { attr } = route;
|
|
365
|
+
const renderType = attr?.render || RENDERTYPE.streaming;
|
|
366
|
+
const [beforeBody = "", afterBody] = template.split(SSRTAG.ssrHtml);
|
|
367
|
+
const [beforeHead, afterHead] = beforeBody.split(SSRTAG.ssrHead);
|
|
368
|
+
const initialDataPromise = fetchInitialData(attr, params, serviceRegistry);
|
|
369
|
+
if (renderType === RENDERTYPE.ssr) {
|
|
370
|
+
const { renderSSR } = renderModule;
|
|
371
|
+
const initialDataResolved = await initialDataPromise;
|
|
372
|
+
const initialDataScript = `<script>window.__INITIAL_DATA__ = ${JSON.stringify(initialDataResolved).replace(/</g, "\\u003c")}</script>`;
|
|
373
|
+
const { headContent, appHtml } = await renderSSR(initialDataResolved, req.url, attr?.meta);
|
|
374
|
+
const fullHtml = template.replace(SSRTAG.ssrHead, headContent).replace(SSRTAG.ssrHtml, `${appHtml}${initialDataScript}<script type="module" src="${bootstrapModules}" async=""></script>`);
|
|
375
|
+
return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
|
|
376
|
+
} else {
|
|
377
|
+
const { renderStream } = renderModule;
|
|
378
|
+
reply.raw.writeHead(200, { "Content-Type": "text/html" });
|
|
379
|
+
renderStream(
|
|
380
|
+
reply.raw,
|
|
381
|
+
{
|
|
382
|
+
onHead: (headContent) => {
|
|
383
|
+
let aggregateHeadContent = headContent;
|
|
384
|
+
if (ssrManifest) aggregateHeadContent += preloadLinks;
|
|
385
|
+
if (manifest) aggregateHeadContent += cssLinks;
|
|
386
|
+
reply.raw.write(`${beforeHead}${aggregateHeadContent}${afterHead}`);
|
|
387
|
+
},
|
|
388
|
+
onFinish: async (initialDataResolved) => {
|
|
389
|
+
reply.raw.write(`<script>window.__INITIAL_DATA__ = ${JSON.stringify(initialDataResolved).replace(/</g, "\\u003c")}</script>`);
|
|
390
|
+
reply.raw.write(afterBody);
|
|
391
|
+
reply.raw.end();
|
|
392
|
+
},
|
|
393
|
+
onError: (error) => {
|
|
394
|
+
console.error("Critical rendering onError:", error);
|
|
395
|
+
reply.raw.end("Internal Server Error");
|
|
396
|
+
}
|
|
370
397
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
bootstrapModules
|
|
378
|
-
);
|
|
398
|
+
initialDataPromise,
|
|
399
|
+
req.url,
|
|
400
|
+
bootstrapModules,
|
|
401
|
+
attr?.meta
|
|
402
|
+
);
|
|
403
|
+
}
|
|
379
404
|
} catch (error) {
|
|
380
405
|
console.error("Error setting up SSR stream:", error);
|
|
381
406
|
if (!reply.raw.headersSent) reply.raw.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -386,7 +411,7 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
386
411
|
if (/\.\w+$/.test(req.raw.url ?? "")) return reply.callNotFound();
|
|
387
412
|
try {
|
|
388
413
|
let template2 = templateHtml;
|
|
389
|
-
template2 = template2.replace(
|
|
414
|
+
template2 = template2.replace(SSRTAG.ssrHead, "").replace(SSRTAG.ssrHtml, "");
|
|
390
415
|
if (!isDevelopment) template2 = template2.replace("</head>", `${getCssLinks(manifest)}</head>`);
|
|
391
416
|
template2 = template2.replace("</body>", `<script type="module" src="${bootstrapModules}" async=""></script></body>`);
|
|
392
417
|
reply.status(200).type("text/html").send(template2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taujs/server",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "taujs | τjs",
|
|
5
5
|
"author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/react": "^18.3.3",
|
|
53
53
|
"@types/react-dom": "^18.3.0",
|
|
54
54
|
"@vitest/coverage-v8": "^2.1.0",
|
|
55
|
-
"fastify": "^4.28.
|
|
55
|
+
"fastify": "^4.28.1",
|
|
56
56
|
"jsdom": "^25.0.0",
|
|
57
57
|
"prettier": "^3.3.3",
|
|
58
58
|
"react-dom": "^18.3.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vitest": "^2.0.5"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"fastify": "^
|
|
65
|
+
"fastify": "^4.28.1",
|
|
66
66
|
"react": "^18.3.1",
|
|
67
67
|
"react-dom": "^18.3.1",
|
|
68
68
|
"typescript": "^5.5.4",
|
|
@@ -70,9 +70,11 @@
|
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsup",
|
|
73
|
+
"build-local": "tsup && ./move.sh",
|
|
73
74
|
"ci": "npm run build && npm run check-format && npm run lint",
|
|
74
75
|
"lint": "tsc",
|
|
75
76
|
"test": "vitest run",
|
|
77
|
+
"test:ui": "vitest --ui --coverage.enabled=true",
|
|
76
78
|
"coverage": "vitest run --coverage",
|
|
77
79
|
"format": "prettier --write .",
|
|
78
80
|
"check-format": "prettier --check .",
|