vite-plugin-enter-dev 0.0.10 → 0.0.12
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 +27 -0
- package/dist/configure.d.ts +4 -0
- package/dist/configure.js +123 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +84 -30
- package/package.json +24 -14
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# vite-plugin-enter-dev
|
|
2
|
+
|
|
3
|
+
Version 0.0.12 preserves the host React compiler and its Babel options. Use
|
|
4
|
+
`enterDevPlugin({ react: false })` alongside `@vitejs/plugin-react`; Enter adds
|
|
5
|
+
source locations through `api.reactBabel`. Templates without a host compiler
|
|
6
|
+
can keep `enterDevPlugin()`. Duplicate React compilers fail at configuration
|
|
7
|
+
resolution with an actionable error instead of serving broken entry modules.
|
|
8
|
+
|
|
9
|
+
`vite-plugin-enter-dev/configure` exports `configureVite(source)` and
|
|
10
|
+
`configureViteFile(path, check?)`. The Gateway uses these to repair existing
|
|
11
|
+
and imported Vite configurations. Migration preserves host options, handles
|
|
12
|
+
static configurations and aliased imports, and is idempotent. Unsupported
|
|
13
|
+
dynamic configurations fail without changing the file; check mode never writes.
|
|
14
|
+
File migration requires the caller to exclude concurrent file writers for the
|
|
15
|
+
whole operation; the Gateway holds and drains its workspace lock before calling.
|
|
16
|
+
|
|
17
|
+
Version 0.0.11's reload-generation recovery remains enabled: served documents
|
|
18
|
+
compare the server instance and reload revision on initial HMR connection and
|
|
19
|
+
reconnection, reloading once when stale.
|
|
20
|
+
|
|
21
|
+
Run `pnpm test`, `pnpm lint`, and `pnpm typecheck` in this package. Tests compile
|
|
22
|
+
a real TSX entry through Vite and verify source annotations, migration, and
|
|
23
|
+
host Babel options.
|
|
24
|
+
|
|
25
|
+
Publish 0.0.12 before deploying a Gateway requiring it. Existing sandboxes must
|
|
26
|
+
upgrade the package, migrate the configuration, and restart Vite; the Gateway
|
|
27
|
+
checks this even when the old server's readiness endpoint still responds.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { parseSync, types, traverse, transformFromAstSync } from '@babel/core';
|
|
2
|
+
import { readFileSync, writeFileSync, statSync, renameSync, rmSync } from 'fs';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
|
|
5
|
+
// src/configure.ts
|
|
6
|
+
function configureVite(source) {
|
|
7
|
+
const ast = parseSync(source, { babelrc: false, configFile: false, parserOpts: { plugins: ["typescript"] } });
|
|
8
|
+
if (!ast) throw new Error("Cannot parse Vite config");
|
|
9
|
+
const imports = ast.program.body.filter((node) => types.isImportDeclaration(node));
|
|
10
|
+
if (imports.some((node) => node.source.value === "@vitejs/plugin-react" && node.specifiers.some((item) => !types.isImportDefaultSpecifier(item)))) {
|
|
11
|
+
throw new Error("Unsupported React import: expected a default import");
|
|
12
|
+
}
|
|
13
|
+
const defineNames = imports.filter((node) => node.source.value === "vite").flatMap((node) => node.specifiers.filter((item) => types.isImportSpecifier(item) && types.isIdentifier(item.imported, { name: "defineConfig" })).map((item) => item.local.name));
|
|
14
|
+
const reactNames = imports.filter((node) => node.source.value === "@vitejs/plugin-react").flatMap((node) => node.specifiers.filter((item) => types.isImportDefaultSpecifier(item)).map((item) => item.local.name));
|
|
15
|
+
const entry = ast.program.body.find((node) => types.isExportDefaultDeclaration(node));
|
|
16
|
+
let config = entry?.declaration;
|
|
17
|
+
if (types.isIdentifier(config)) {
|
|
18
|
+
const name = config.name;
|
|
19
|
+
traverse(ast, {
|
|
20
|
+
Program(path) {
|
|
21
|
+
const binding2 = path.scope.getBinding(name);
|
|
22
|
+
if (!binding2?.constant || binding2.referencePaths.length !== 1 || !binding2.referencePaths[0]?.parentPath?.isExportDefaultDeclaration()) {
|
|
23
|
+
throw new Error("Unsupported Vite config: exported binding has other references");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const binding = ast.program.body.filter((node) => types.isVariableDeclaration(node, { kind: "const" })).flatMap((node) => node.declarations).find((node) => types.isIdentifier(node.id, { name }));
|
|
28
|
+
config = binding?.init ?? void 0;
|
|
29
|
+
}
|
|
30
|
+
if (types.isCallExpression(config)) {
|
|
31
|
+
if (!types.isIdentifier(config.callee) || !defineNames.includes(config.callee.name) || config.arguments.length !== 1) throw new Error("Unsupported Vite config wrapper");
|
|
32
|
+
config = config.arguments[0];
|
|
33
|
+
}
|
|
34
|
+
if (types.isArrowFunctionExpression(config) || types.isFunctionExpression(config)) {
|
|
35
|
+
if (types.isBlockStatement(config.body)) {
|
|
36
|
+
if (config.body.body.length !== 1 || !types.isReturnStatement(config.body.body[0])) {
|
|
37
|
+
throw new Error("Unsupported Vite config: expected a single unconditional return");
|
|
38
|
+
}
|
|
39
|
+
config = config.body.body[0].argument ?? void 0;
|
|
40
|
+
} else {
|
|
41
|
+
config = config.body;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!types.isObjectExpression(config)) throw new Error("Unsupported Vite config: expected a static exported configuration object");
|
|
45
|
+
if (config.properties.some((node) => types.isSpreadElement(node) || node.computed || types.isObjectMethod(node) && (types.isIdentifier(node.key, { name: "plugins" }) || types.isStringLiteral(node.key, { value: "plugins" })))) {
|
|
46
|
+
throw new Error("Unsupported Vite config: dynamic configuration may override plugins");
|
|
47
|
+
}
|
|
48
|
+
const pluginProperties = config.properties.filter((node) => types.isObjectProperty(node) && !node.computed && (types.isIdentifier(node.key, { name: "plugins" }) || types.isStringLiteral(node.key, { value: "plugins" })));
|
|
49
|
+
if (pluginProperties.length > 1) throw new Error("Unsupported Vite config: duplicate plugins properties");
|
|
50
|
+
let changed = false;
|
|
51
|
+
let plugins = pluginProperties[0];
|
|
52
|
+
if (!plugins) {
|
|
53
|
+
plugins = types.objectProperty(types.identifier("plugins"), types.arrayExpression([]));
|
|
54
|
+
config.properties.push(plugins);
|
|
55
|
+
changed = true;
|
|
56
|
+
}
|
|
57
|
+
if (!types.isObjectProperty(plugins) || !types.isArrayExpression(plugins.value)) {
|
|
58
|
+
throw new Error("Unsupported Vite config: expected a literal plugins array");
|
|
59
|
+
}
|
|
60
|
+
const calls = plugins.value.elements.map((node) => types.isSpreadElement(node) ? node.argument : node);
|
|
61
|
+
if (calls.some((node) => !types.isCallExpression(node) && node !== null && !types.isBooleanLiteral(node))) {
|
|
62
|
+
throw new Error("Unsupported Vite config: dynamic plugin list");
|
|
63
|
+
}
|
|
64
|
+
const hostReact = calls.some((node) => types.isCallExpression(node) && types.isIdentifier(node.callee) && reactNames.includes(node.callee.name));
|
|
65
|
+
for (const name of ["enterProdPlugin", "enterDevPlugin"]) {
|
|
66
|
+
const specifiers = imports.filter((node) => node.source.value === "vite-plugin-enter-dev").flatMap((node) => node.specifiers).filter((node) => types.isImportSpecifier(node) && types.isIdentifier(node.imported, { name }));
|
|
67
|
+
const aliases = specifiers.map((node) => node.local.name);
|
|
68
|
+
let local = aliases[0];
|
|
69
|
+
if (!local) {
|
|
70
|
+
local = name;
|
|
71
|
+
while (new RegExp(`\\b${local}\\b`).test(source)) local = `_${local}`;
|
|
72
|
+
ast.program.body.unshift(types.importDeclaration([types.importSpecifier(types.identifier(local), types.identifier(name))], types.stringLiteral("vite-plugin-enter-dev")));
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
const matching = calls.filter((node) => types.isCallExpression(node) && types.isIdentifier(node.callee) && (aliases.includes(node.callee.name) || node.callee.name === local));
|
|
76
|
+
if (matching.length > 1) throw new Error(`Duplicate ${name} calls in Vite config`);
|
|
77
|
+
let call = matching[0];
|
|
78
|
+
if (!call) {
|
|
79
|
+
call = types.callExpression(types.identifier(local), []);
|
|
80
|
+
plugins.value.elements.push(types.spreadElement(call));
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
if (name !== "enterDevPlugin" || !hostReact || !types.isCallExpression(call)) continue;
|
|
84
|
+
if (call.arguments.length === 0) call.arguments.push(types.objectExpression([]));
|
|
85
|
+
const options = call.arguments[0];
|
|
86
|
+
if (!types.isObjectExpression(options) || options.properties.some((node) => !types.isObjectProperty(node) || node.computed)) {
|
|
87
|
+
throw new Error("Unsupported enterDevPlugin options: expected a static object");
|
|
88
|
+
}
|
|
89
|
+
const matchingOptions = options.properties.filter((node) => types.isObjectProperty(node) && !node.computed && (types.isIdentifier(node.key, { name: "react" }) || types.isStringLiteral(node.key, { value: "react" })));
|
|
90
|
+
if (matchingOptions.length > 1) throw new Error("Unsupported duplicate react options");
|
|
91
|
+
const option = matchingOptions[0];
|
|
92
|
+
if (types.isObjectProperty(option)) {
|
|
93
|
+
if (!types.isBooleanLiteral(option.value)) throw new Error("Unsupported dynamic enterDevPlugin react option");
|
|
94
|
+
if (!option.value.value) continue;
|
|
95
|
+
option.value = types.booleanLiteral(false);
|
|
96
|
+
} else {
|
|
97
|
+
options.properties.push(types.objectProperty(types.identifier("react"), types.booleanLiteral(false)));
|
|
98
|
+
}
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
if (!changed) return source;
|
|
102
|
+
const result = transformFromAstSync(ast, source, { babelrc: false, configFile: false });
|
|
103
|
+
if (!result?.code) throw new Error("Cannot generate Vite config");
|
|
104
|
+
return `${result.code}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
function configureViteFile(path, check = false) {
|
|
108
|
+
const source = readFileSync(path, "utf8");
|
|
109
|
+
const output = configureVite(source);
|
|
110
|
+
if (source === output) return false;
|
|
111
|
+
if (check) return true;
|
|
112
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
113
|
+
try {
|
|
114
|
+
writeFileSync(temporary, output, { flag: "wx", mode: statSync(path).mode });
|
|
115
|
+
if (readFileSync(path, "utf8") !== source) throw new Error("Vite config changed during migration");
|
|
116
|
+
renameSync(temporary, path);
|
|
117
|
+
} finally {
|
|
118
|
+
rmSync(temporary, { force: true });
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { configureVite, configureViteFile };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,9 +11,8 @@ interface DevOptions {
|
|
|
11
11
|
* 宿主框架自己已经注册了 React 转换时必须传 false:vinext 会在
|
|
12
12
|
* configResolved 里检测重复注册并直接抛错
|
|
13
13
|
* ("[vinext] Duplicate @vitejs/plugin-react detected"),
|
|
14
|
-
* 整个 dev server 和 build
|
|
15
|
-
*
|
|
16
|
-
* 需要的源码位置注入。
|
|
14
|
+
* 整个 dev server 和 build 都起不来。源码位置通过宿主 React
|
|
15
|
+
* 插件的 Babel 扩展接口注入,无需重复注册编译器。
|
|
17
16
|
*/
|
|
18
17
|
react?: boolean;
|
|
19
18
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import react from '@vitejs/plugin-react';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
2
3
|
|
|
3
4
|
// src/index.ts
|
|
4
5
|
|
|
@@ -164,7 +165,7 @@ function injectSourcePlugin({ types: t }) {
|
|
|
164
165
|
name: "inject-source-location-enhanced",
|
|
165
166
|
visitor: {
|
|
166
167
|
Program: {
|
|
167
|
-
enter(
|
|
168
|
+
enter(_path, state) {
|
|
168
169
|
state.threeImportedElements = /* @__PURE__ */ new Set();
|
|
169
170
|
state.threeNamespaces = /* @__PURE__ */ new Set();
|
|
170
171
|
}
|
|
@@ -229,7 +230,7 @@ function injectSourcePlugin({ types: t }) {
|
|
|
229
230
|
);
|
|
230
231
|
const sourceStackAttr = t.jsxAttribute(
|
|
231
232
|
t.jsxIdentifier("data-source-stack"),
|
|
232
|
-
t.stringLiteral(JSON.stringify(elementInfo))
|
|
233
|
+
t.jsxExpressionContainer(t.stringLiteral(JSON.stringify(elementInfo)))
|
|
233
234
|
);
|
|
234
235
|
const elementTypeAttr = t.jsxAttribute(
|
|
235
236
|
t.jsxIdentifier("data-element-type"),
|
|
@@ -273,7 +274,7 @@ function getElementType(nameNode) {
|
|
|
273
274
|
if (nameNode.type === "JSXIdentifier") {
|
|
274
275
|
return nameNode.name;
|
|
275
276
|
} else if (nameNode.type === "JSXMemberExpression") {
|
|
276
|
-
return `${nameNode.object
|
|
277
|
+
return `${getElementType(nameNode.object)}.${nameNode.property.name}`;
|
|
277
278
|
}
|
|
278
279
|
return "unknown";
|
|
279
280
|
}
|
|
@@ -283,35 +284,80 @@ function getParentElementType(parentNode) {
|
|
|
283
284
|
}
|
|
284
285
|
return null;
|
|
285
286
|
}
|
|
287
|
+
function reloadGenerationPlugin() {
|
|
288
|
+
const instance = randomUUID();
|
|
289
|
+
let revision = 0;
|
|
290
|
+
let base = "/";
|
|
291
|
+
const generation = () => `${instance}:${revision}`;
|
|
292
|
+
return {
|
|
293
|
+
name: "enter-dev-reload-generation",
|
|
294
|
+
apply: "serve",
|
|
295
|
+
configResolved(config) {
|
|
296
|
+
base = config.base;
|
|
297
|
+
},
|
|
298
|
+
configureServer(server) {
|
|
299
|
+
const originalSend = server.ws.send.bind(server.ws);
|
|
300
|
+
const send = (payload, data) => {
|
|
301
|
+
if (typeof payload === "string") {
|
|
302
|
+
originalSend(payload, data);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (payload.type === "full-reload") revision += 1;
|
|
306
|
+
originalSend(payload);
|
|
307
|
+
};
|
|
308
|
+
server.ws.send = send;
|
|
309
|
+
server.ws.on("enter:check-generation", (data, client) => {
|
|
310
|
+
if (data?.generation !== generation()) {
|
|
311
|
+
client.send("enter:reload-required", { generation: generation() });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
server.httpServer?.once("close", () => {
|
|
315
|
+
if (server.ws.send === send) server.ws.send = originalSend;
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
transformIndexHtml() {
|
|
319
|
+
return [
|
|
320
|
+
{
|
|
321
|
+
tag: "script",
|
|
322
|
+
attrs: { type: "module" },
|
|
323
|
+
injectTo: "head-prepend",
|
|
324
|
+
children: `import { createHotContext } from ${JSON.stringify(`${base}@vite/client`)};
|
|
325
|
+
const generation = ${JSON.stringify(generation())};
|
|
326
|
+
const hot = createHotContext('/@enter/reload-generation');
|
|
327
|
+
let reloading = false;
|
|
328
|
+
hot.on('enter:reload-required', () => {
|
|
329
|
+
if (reloading) return;
|
|
330
|
+
reloading = true;
|
|
331
|
+
window.location.reload();
|
|
332
|
+
});
|
|
333
|
+
const check = () => hot.send('enter:check-generation', { generation });
|
|
334
|
+
hot.on('vite:ws:connect', check);
|
|
335
|
+
check();`
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
286
341
|
|
|
287
342
|
// src/index.ts
|
|
288
343
|
function enterDevPlugin(options = {}) {
|
|
289
|
-
const {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"@babel/plugin-transform-react-jsx",
|
|
305
|
-
{
|
|
306
|
-
runtime: "automatic",
|
|
307
|
-
importSource: "react",
|
|
308
|
-
development: true
|
|
309
|
-
// 保持开发模式以获得更好的调试体验
|
|
310
|
-
}
|
|
311
|
-
]
|
|
312
|
-
]
|
|
344
|
+
const { injectMessageListener = true, react: enableReact = true } = options;
|
|
345
|
+
const reactPlugin = enableReact ? react() : [];
|
|
346
|
+
const sourcePlugin = {
|
|
347
|
+
name: "enter-dev-source-location",
|
|
348
|
+
api: {
|
|
349
|
+
reactBabel(config) {
|
|
350
|
+
if (!config.plugins.some((plugin) => (Array.isArray(plugin) ? plugin[0] : plugin) === injectSourcePlugin)) {
|
|
351
|
+
config.plugins.push(injectSourcePlugin);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
configResolved(config) {
|
|
356
|
+
if (config.plugins.filter((plugin) => plugin.name === "vite:react-babel").length > 1) {
|
|
357
|
+
throw new Error("Enter preview: duplicate React plugins. Use enterDevPlugin({ react: false }) when the project provides React.");
|
|
358
|
+
}
|
|
313
359
|
}
|
|
314
|
-
}
|
|
360
|
+
};
|
|
315
361
|
const htmlInjectPlugin = {
|
|
316
362
|
name: "enter-dev-message-listener",
|
|
317
363
|
enforce: "pre",
|
|
@@ -330,7 +376,7 @@ function enterDevPlugin(options = {}) {
|
|
|
330
376
|
if (target && (target.tagName === 'SCRIPT' || target.tagName === 'LINK')) {
|
|
331
377
|
const isStylesheet =
|
|
332
378
|
target.tagName === 'LINK' &&
|
|
333
|
-
String(target.rel || '').toLowerCase().split(
|
|
379
|
+
String(target.rel || '').toLowerCase().split(/\\s+/).includes('stylesheet');
|
|
334
380
|
if (target.tagName === 'SCRIPT' || isStylesheet) {
|
|
335
381
|
window.__earlyErrors__.push({
|
|
336
382
|
type: 'resource',
|
|
@@ -502,7 +548,15 @@ function enterDevPlugin(options = {}) {
|
|
|
502
548
|
}
|
|
503
549
|
}
|
|
504
550
|
};
|
|
505
|
-
return [
|
|
551
|
+
return [
|
|
552
|
+
...reactPlugin,
|
|
553
|
+
sourcePlugin,
|
|
554
|
+
htmlInjectPlugin,
|
|
555
|
+
sandboxServerPlugin,
|
|
556
|
+
routerBasePlugin,
|
|
557
|
+
publicAssetReloadPlugin,
|
|
558
|
+
reloadGenerationPlugin()
|
|
559
|
+
];
|
|
506
560
|
}
|
|
507
561
|
function enterProdPlugin(options = {}) {
|
|
508
562
|
const {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-enter-dev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "A Vite plugin for development enhancements",
|
|
@@ -11,17 +11,15 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./configure": {
|
|
16
|
+
"types": "./dist/configure.d.ts",
|
|
17
|
+
"import": "./dist/configure.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
21
|
"dist"
|
|
18
22
|
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsup",
|
|
21
|
-
"dev": "tsup --watch",
|
|
22
|
-
"lint": "eslint . --max-warnings 0",
|
|
23
|
-
"typecheck": "tsc --noEmit"
|
|
24
|
-
},
|
|
25
23
|
"publishConfig": {
|
|
26
24
|
"registry": "https://registry.npmjs.org/"
|
|
27
25
|
},
|
|
@@ -34,19 +32,31 @@
|
|
|
34
32
|
],
|
|
35
33
|
"author": "",
|
|
36
34
|
"license": "MIT",
|
|
37
|
-
"packageManager": "pnpm@10.15.0",
|
|
38
35
|
"devDependencies": {
|
|
39
36
|
"@types/node": "^20.19.9",
|
|
40
|
-
"@workspace/eslint-config": "workspace:*",
|
|
41
|
-
"@workspace/typescript-config": "workspace:*",
|
|
42
37
|
"tsup": "^8.5.1",
|
|
43
|
-
"typescript": "
|
|
44
|
-
"
|
|
38
|
+
"@typescript/native": "npm:typescript@7.0.2",
|
|
39
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
40
|
+
"vite": "^6.0.0",
|
|
41
|
+
"react": "^19.2.3",
|
|
42
|
+
"react-dom": "^19.2.3",
|
|
43
|
+
"@types/babel__core": "^7.20.5",
|
|
44
|
+
"eslint": "^9.32.0",
|
|
45
|
+
"@workspace/eslint-config": "0.0.0",
|
|
46
|
+
"@workspace/typescript-config": "0.0.0"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
|
-
"@vitejs/plugin-react": "^5.1.1"
|
|
52
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
53
|
+
"@babel/core": "^7.28.3"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"lint": "eslint . --max-warnings 0",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"test": "pnpm build && node --test --test-timeout=15000 *.test.mjs"
|
|
51
61
|
}
|
|
52
|
-
}
|
|
62
|
+
}
|