@pubinfo/vite 2.0.13 → 2.0.15

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/dist/index.d.ts CHANGED
@@ -20,11 +20,6 @@ interface ChromeDevtoolsOptions {
20
20
  /** DevTools 面板访问路径 */
21
21
  panelPath?: string;
22
22
  }
23
- /**
24
- * 创建 Chrome DevTools 插件
25
- * @param options 插件配置选项
26
- * @returns Vite 插件
27
- */
28
23
  //#endregion
29
24
  //#region src/plugins/built-in/inject-auto.d.ts
30
25
  interface InjectAutoOptions {
package/dist/index.js CHANGED
@@ -1,16 +1,19 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { defineConfig, loadEnv, mergeConfig, normalizePath } from "rolldown-vite";
3
- import { cwd } from "node:process";
3
+ import process, { cwd } from "node:process";
4
4
  import chalk from "chalk";
5
5
  import consola from "consola";
6
- import { dirname, join, posix, relative, resolve } from "node:path";
6
+ import path, { dirname, join, posix, relative, resolve } from "node:path";
7
7
  import vue from "@vitejs/plugin-vue";
8
8
  import vueJsx from "@vitejs/plugin-vue-jsx";
9
+ import fs from "fs-extra";
9
10
  import boxen from "boxen";
10
11
  import MagicString from "magic-string";
11
12
  import fg from "fast-glob";
12
13
  import { merge } from "lodash-es";
13
14
  import picomatch from "picomatch";
15
+ import "micromatch";
16
+ import { execSync } from "node:child_process";
14
17
  import dayjs from "dayjs";
15
18
  import { readPackageJSON } from "pkg-types";
16
19
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
@@ -40,7 +43,7 @@ export * from "vite-plugin-fake-server/client"
40
43
  //#region src/helper/alias.ts
41
44
  function alias(root) {
42
45
  const resolvePath = (name) => join(root, name);
43
- const resolveDeps = (name, path) => join(createRequire(import.meta.url).resolve(name), path);
46
+ const resolveDeps = (name, path$1) => join(createRequire(import.meta.url).resolve(name), path$1);
44
47
  return {
45
48
  "@": resolvePath("src"),
46
49
  "#": resolvePath("types"),
@@ -66,7 +69,7 @@ function getServerProxy(env, isProxy) {
66
69
  else serverProxy[pk] = {
67
70
  target: url,
68
71
  changeOrigin: true,
69
- rewrite: (path) => path.replace(pk, ""),
72
+ rewrite: (path$1) => path$1.replace(pk, ""),
70
73
  secure: false
71
74
  };
72
75
  }
@@ -75,79 +78,171 @@ function getServerProxy(env, isProxy) {
75
78
 
76
79
  //#endregion
77
80
  //#region src/plugins/built-in/chrome-devtools.ts
81
+ const GLOBAL_STATE_KEY = "__pubinfoChromeDevtoolsState__";
82
+ const DEFAULT_VITE_PORT = 5173;
83
+ const DEFAULT_HOST = "localhost";
84
+ const RANDOM_PORT_RANGE = {
85
+ min: 56e3,
86
+ max: 64e3
87
+ };
88
+ function getGlobalState() {
89
+ const globalRef = globalThis;
90
+ if (!globalRef[GLOBAL_STATE_KEY]) globalRef[GLOBAL_STATE_KEY] = {};
91
+ return globalRef[GLOBAL_STATE_KEY];
92
+ }
78
93
  /**
79
- * Chii 服务管理器
94
+ * 管理 chii (Chrome DevTools 后端) 的生命周期与日志。
80
95
  */
81
96
  var ChiiServiceManager = class {
82
97
  port;
83
98
  isStarted = false;
99
+ loggingPatched = false;
100
+ globalState;
101
+ constructor() {
102
+ this.globalState = getGlobalState();
103
+ }
84
104
  /**
85
- * 启动 Chii 服务
86
- * @param customPort 自定义端口
87
- * @param vitePort Vite 开发服务器端口
88
- * @param viteHost Vite 开发服务器主机
89
- * @param panelPath DevTools 面板路径
90
- * @returns 服务端口号
105
+ * 启动 chii 服务,自动处理端口复用、冲突检测与启动失败兜底。
91
106
  */
92
107
  async start(customPort, vitePort, viteHost, panelPath) {
93
108
  if (this.isStarted && this.port) return this.port;
109
+ const reusedPort = await this.tryReuseExistingService(customPort, vitePort);
110
+ if (reusedPort) {
111
+ this.registerPort(reusedPort);
112
+ return reusedPort;
113
+ }
94
114
  try {
95
115
  const { start } = await this.importChii();
96
- const port = customPort || this.generatePort(vitePort);
116
+ const portCandidate = customPort || this.generatePort(vitePort);
117
+ const port = await this.findAvailablePort(portCandidate, customPort === void 0);
118
+ if (port == null) {
119
+ console.warn("[pubinfo][chrome-devtools] 未找到可用的 DevTools 端口,跳过启动");
120
+ return;
121
+ }
97
122
  start({ port });
98
- this.port = port;
99
- this.isStarted = true;
123
+ this.registerPort(port);
100
124
  return port;
101
125
  } catch (error) {
102
126
  console.warn("[pubinfo][chrome-devtools] 启动 Chii 失败:", error);
103
127
  return;
104
128
  }
105
129
  }
106
- /**
107
- * 获取服务端口
108
- */
109
130
  getPort() {
110
131
  return this.port;
111
132
  }
112
- /**
113
- * 检查服务是否已启动
114
- */
115
133
  isServiceStarted() {
116
134
  return this.isStarted && !!this.port;
117
135
  }
118
- /**
119
- * 生成可用端口
120
- * @param vitePort Vite 端口
121
- * @returns 生成的端口号
122
- */
136
+ registerPort(port) {
137
+ this.port = port;
138
+ this.isStarted = true;
139
+ this.globalState.port = port;
140
+ }
141
+ async tryReuseExistingService(customPort, vitePort) {
142
+ if (this.globalState.port != null) {
143
+ if (await this.isChiiRunning(this.globalState.port)) return this.globalState.port;
144
+ this.globalState.port = void 0;
145
+ }
146
+ if (customPort != null) {
147
+ if (await this.isChiiRunning(customPort)) return customPort;
148
+ return;
149
+ }
150
+ if (vitePort) {
151
+ const generatedPort = this.generatePort(vitePort);
152
+ if (await this.isChiiRunning(generatedPort)) return generatedPort;
153
+ }
154
+ }
123
155
  generatePort(vitePort) {
124
- if (vitePort) return vitePort + 1e3;
125
- return 9229 + Math.floor(Math.random() * 100);
156
+ if (vitePort) {
157
+ const prefixedCandidate = Number(`6${vitePort}`);
158
+ if (Number.isFinite(prefixedCandidate) && prefixedCandidate <= 65535) return prefixedCandidate;
159
+ const highRangeCandidate = 6e4 + vitePort % 1e3;
160
+ if (highRangeCandidate <= 65535) return highRangeCandidate;
161
+ }
162
+ const { min, max } = RANDOM_PORT_RANGE;
163
+ return min + Math.floor(Math.random() * (max - min + 1));
126
164
  }
127
- /**
128
- * 输出启动信息
129
- * @param host 主机地址
130
- * @param vitePort Vite 端口
131
- * @param panelPath 面板路径
132
- */
133
- logStartupInfo(host, vitePort, panelPath) {
134
- if (!this.port) return;
135
- const panelUrl = `${`http://${host}:${vitePort}`}${panelPath}`;
136
- const colorUrl = (url) => chalk.cyan(url.replace(/:(\d+)\//, (_, port) => `:${chalk.bold(port)}/`));
137
- console.log(` ${chalk.green("➜")} ${chalk.bold("Chrome DevTools")}: ${chalk.green(`Open ${colorUrl(panelUrl)} as a separate window`)}`);
165
+ async findAvailablePort(startPort, allowIncrement) {
166
+ let port = startPort;
167
+ for (let attempt = 0; attempt < 20; attempt += 1) {
168
+ if (await this.isPortAvailable(port)) return port;
169
+ if (!allowIncrement) return;
170
+ port += 1;
171
+ }
172
+ }
173
+ async isPortAvailable(port) {
174
+ const net = await this.importNet();
175
+ return await new Promise((resolve$1) => {
176
+ const tester = net.createServer();
177
+ tester.once("error", () => {
178
+ try {
179
+ tester.close();
180
+ } catch {}
181
+ resolve$1(false);
182
+ });
183
+ tester.once("listening", () => {
184
+ tester.close(() => resolve$1(true));
185
+ });
186
+ tester.listen(port);
187
+ });
188
+ }
189
+ async isChiiRunning(port) {
190
+ try {
191
+ const controller = new AbortController();
192
+ const timeout = setTimeout(() => controller.abort(), 300);
193
+ try {
194
+ const response = await fetch(`http://127.0.0.1:${port}/targets`, { signal: controller.signal });
195
+ if (!response.ok) return false;
196
+ const data = await response.json();
197
+ return Array.isArray(data?.targets);
198
+ } finally {
199
+ clearTimeout(timeout);
200
+ }
201
+ } catch {
202
+ return false;
203
+ }
138
204
  }
139
- /**
140
- * 动态导入 chii
141
- */
142
205
  async importChii() {
206
+ await this.patchChiiLogger();
143
207
  return await import(
144
208
  /* @vite-ignore */
145
209
  "chii"
146
210
  );
147
211
  }
212
+ async importNet() {
213
+ return await import("node:net");
214
+ }
215
+ async patchChiiLogger() {
216
+ if (this.loggingPatched) return;
217
+ try {
218
+ const utilModule = createRequire(import.meta.url)("chii/server/lib/util.js");
219
+ const utilTarget = typeof utilModule.log === "function" ? utilModule : utilModule.default;
220
+ if (!utilTarget || typeof utilTarget.log !== "function") {
221
+ console.warn("[pubinfo][chrome-devtools] 未检测到 chii util.log,跳过日志定制");
222
+ this.loggingPatched = true;
223
+ return;
224
+ }
225
+ const descriptor = Object.getOwnPropertyDescriptor(utilTarget, "log");
226
+ if (descriptor && descriptor.writable === false) Object.defineProperty(utilTarget, "log", {
227
+ ...descriptor,
228
+ writable: true,
229
+ configurable: true
230
+ });
231
+ const patchedLog = () => {};
232
+ const targetExports = utilTarget;
233
+ const moduleExports = utilModule;
234
+ const defaultExports = utilModule.default;
235
+ targetExports.log = patchedLog;
236
+ if (moduleExports !== targetExports) moduleExports.log = patchedLog;
237
+ if (defaultExports && defaultExports !== targetExports) defaultExports.log = patchedLog;
238
+ this.loggingPatched = true;
239
+ } catch (error) {
240
+ console.warn("[pubinfo][chrome-devtools] 自定义 DevTools 日志失败:", error);
241
+ }
242
+ }
148
243
  };
149
244
  /**
150
- * DevTools 面板处理器
245
+ * 处理 /__chrome_devtools 面板访问跳转:实时查询 chii targets,生成前端面板地址。
151
246
  */
152
247
  var DevToolsPanelHandler = class {
153
248
  panelPath;
@@ -156,9 +251,6 @@ var DevToolsPanelHandler = class {
156
251
  this.panelPath = panelPath;
157
252
  this.serviceManager = serviceManager;
158
253
  }
159
- /**
160
- * 创建面板访问中间件
161
- */
162
254
  createMiddleware(server) {
163
255
  server.middlewares.use(async (req, res, next) => {
164
256
  if (!this.isTargetPath(req.url)) return next();
@@ -176,16 +268,10 @@ var DevToolsPanelHandler = class {
176
268
  }
177
269
  });
178
270
  }
179
- /**
180
- * 检查是否为目标路径
181
- */
182
271
  isTargetPath(url) {
183
272
  if (!url) return false;
184
273
  return url === this.panelPath || url === `${this.panelPath}/`;
185
274
  }
186
- /**
187
- * 获取 DevTools URL
188
- */
189
275
  async getDevToolsUrl(port) {
190
276
  const firstTarget = (await (await fetch(`http://127.0.0.1:${port}/targets`)).json())?.targets?.[0];
191
277
  if (firstTarget) {
@@ -194,16 +280,10 @@ var DevToolsPanelHandler = class {
194
280
  }
195
281
  return `http://127.0.0.1:${port}/`;
196
282
  }
197
- /**
198
- * 发送错误响应
199
- */
200
283
  sendErrorResponse(res, message) {
201
284
  res.statusCode = 503;
202
285
  res.end(message);
203
286
  }
204
- /**
205
- * 发送重定向响应
206
- */
207
287
  sendRedirectResponse(res, location) {
208
288
  res.statusCode = 302;
209
289
  res.setHeader("Location", location);
@@ -211,16 +291,13 @@ var DevToolsPanelHandler = class {
211
291
  }
212
292
  };
213
293
  /**
214
- * HTML 注入器
294
+ * 负责把 target.js 注入到 HTML 中,使页面成为可调试目标。
215
295
  */
216
296
  var HtmlInjector = class {
217
297
  serviceManager;
218
298
  constructor(serviceManager) {
219
299
  this.serviceManager = serviceManager;
220
300
  }
221
- /**
222
- * 注入调试脚本到 HTML
223
- */
224
301
  injectScript(html) {
225
302
  const port = this.serviceManager.getPort();
226
303
  if (!port) return html;
@@ -229,53 +306,142 @@ var HtmlInjector = class {
229
306
  const scriptTag = this.createScriptTag(port, injectedMarker);
230
307
  return this.insertScriptTag(html, scriptTag);
231
308
  }
232
- /**
233
- * 创建脚本标签
234
- */
235
309
  createScriptTag(port, marker) {
236
310
  return `<script>${marker}<\/script>\n<script src="http://127.0.0.1:${port}/target.js" data-pubinfo-devtools="chrome"><\/script>`;
237
311
  }
238
- /**
239
- * 插入脚本标签到 HTML
240
- */
241
312
  insertScriptTag(html, scriptTag) {
242
313
  if (/<head[\s\S]*?>/i.test(html)) return html.replace(/<head([\s\S]*?)>/i, (match) => `${match}\n${scriptTag}`);
243
314
  return html.replace(/<body(.*?)>/i, (match) => `${match}\n${scriptTag}`);
244
315
  }
245
316
  };
246
317
  /**
247
- * 创建 Chrome DevTools 插件
248
- * @param options 插件配置选项
249
- * @returns Vite 插件
318
+ * 管理 Vite Dev Server 与 chii 服务之间的协作:监听端口、控制打印、确保服务可用。
250
319
  */
320
+ var DevServerOrchestrator = class {
321
+ server;
322
+ lastResolvedServerInfo;
323
+ constructor(serviceManager, panelHandler, panelPath, customPort) {
324
+ this.serviceManager = serviceManager;
325
+ this.panelHandler = panelHandler;
326
+ this.panelPath = panelPath;
327
+ this.customPort = customPort;
328
+ }
329
+ attach(server) {
330
+ this.server = server;
331
+ this.lastResolvedServerInfo = this.resolveServerInfo();
332
+ this.panelHandler.createMiddleware(server);
333
+ this.bindStartupHooks(server);
334
+ }
335
+ async ensureReady() {
336
+ const info = this.resolveServerInfo();
337
+ await this.serviceManager.start(this.customPort, info.port, info.host, this.panelPath);
338
+ }
339
+ async ensureReadyIfNeeded() {
340
+ if (!this.serviceManager.isServiceStarted()) await this.ensureReady();
341
+ }
342
+ bindStartupHooks(server) {
343
+ const scheduleStart = () => {
344
+ (async () => {
345
+ await this.ensureReady();
346
+ })();
347
+ };
348
+ if (server.httpServer?.listening) scheduleStart();
349
+ else server.httpServer?.once("listening", () => {
350
+ this.lastResolvedServerInfo = void 0;
351
+ scheduleStart();
352
+ });
353
+ const originalPrintUrls = server.printUrls;
354
+ server.printUrls = () => {
355
+ originalPrintUrls();
356
+ (async () => {
357
+ await this.ensureReady();
358
+ })();
359
+ };
360
+ }
361
+ resolveServerInfo() {
362
+ const info = this.computeServerInfo();
363
+ this.lastResolvedServerInfo = info;
364
+ return info;
365
+ }
366
+ computeServerInfo() {
367
+ const server = this.server;
368
+ if (!server) return {
369
+ host: DEFAULT_HOST,
370
+ port: DEFAULT_VITE_PORT
371
+ };
372
+ const localUrl = server.resolvedUrls?.local?.[0];
373
+ if (localUrl) try {
374
+ const url = new URL(localUrl);
375
+ if (url.hostname && url.port) return {
376
+ host: this.normalizeHost(url.hostname),
377
+ port: Number(url.port) || DEFAULT_VITE_PORT
378
+ };
379
+ } catch {}
380
+ const configuredHost = typeof server.config.server.host === "string" ? server.config.server.host : DEFAULT_HOST;
381
+ const address = server.httpServer?.address();
382
+ if (address && typeof address === "object" && "port" in address && typeof address.port === "number") return {
383
+ host: this.normalizeHost(configuredHost),
384
+ port: address.port
385
+ };
386
+ return {
387
+ host: this.normalizeHost(configuredHost),
388
+ port: server.config.server.port || DEFAULT_VITE_PORT
389
+ };
390
+ }
391
+ normalizeHost(host) {
392
+ if (!host || host === "0.0.0.0" || host === "::") return DEFAULT_HOST;
393
+ return host;
394
+ }
395
+ };
251
396
  function createChromeDevtools(options = {}) {
252
397
  const { enabled = true, port, autoInject = true, panelPath = "/__chrome_devtools" } = options;
253
398
  if (!enabled) return null;
254
399
  const serviceManager = new ChiiServiceManager();
255
400
  const panelHandler = new DevToolsPanelHandler(panelPath, serviceManager);
256
401
  const htmlInjector = new HtmlInjector(serviceManager);
402
+ const orchestrator = new DevServerOrchestrator(serviceManager, panelHandler, panelPath, port);
257
403
  return {
258
404
  name: "pubinfo:chrome-devtools",
259
405
  apply: "serve",
260
406
  enforce: "post",
261
407
  async configureServer(server) {
262
- const vitePort = server.config.server.port || 5173;
263
- const viteHost = typeof server.config.server.host === "string" ? server.config.server.host : "localhost";
264
- await serviceManager.start(port, vitePort, viteHost, panelPath);
265
- panelHandler.createMiddleware(server);
266
- const _printUrls = server.printUrls;
267
- server.printUrls = () => {
268
- _printUrls();
269
- serviceManager.logStartupInfo(viteHost, vitePort, panelPath);
270
- };
408
+ orchestrator.attach(server);
271
409
  },
272
- transformIndexHtml(html) {
410
+ async transformIndexHtml(html) {
273
411
  if (!autoInject) return html;
412
+ await orchestrator.ensureReadyIfNeeded();
274
413
  return htmlInjector.injectScript(html);
275
414
  }
276
415
  };
277
416
  }
278
417
 
418
+ //#endregion
419
+ //#region src/plugins/built-in/clean-build.ts
420
+ function createCleanBuild() {
421
+ let config;
422
+ return {
423
+ name: "vite-plugin-clean-build",
424
+ apply: "build",
425
+ enforce: "post",
426
+ configResolved(resolvedConfig) {
427
+ config = resolvedConfig;
428
+ },
429
+ buildStart() {
430
+ const outDir = config.build.outDir;
431
+ const root = config.root;
432
+ const outputPath = path.resolve(root, outDir);
433
+ if (fs.existsSync(outputPath)) try {
434
+ fs.rmSync(outputPath, {
435
+ recursive: true,
436
+ force: true
437
+ });
438
+ } catch (error) {
439
+ consola.error(`[vite-plugin-clean-build] Failed to clean build directory: ${error}`);
440
+ }
441
+ }
442
+ };
443
+ }
444
+
279
445
  //#endregion
280
446
  //#region src/plugins/built-in/info.ts
281
447
  var Ctx$1 = class {
@@ -369,8 +535,8 @@ function getPatternBase(pattern) {
369
535
  }
370
536
  return baseParts.join("/");
371
537
  }
372
- function normalizePath$1(path) {
373
- return posix.normalize(path.replace(/\\/g, "/"));
538
+ function normalizePath$1(path$1) {
539
+ return posix.normalize(path$1.replace(/\\/g, "/"));
374
540
  }
375
541
  function libResolverPlugin(options) {
376
542
  const virtualModuleId = "virtual:pubinfo-resolver";
@@ -468,6 +634,34 @@ function createLibResolver(options) {
468
634
 
469
635
  //#endregion
470
636
  //#region src/plugins/built-in/system-info.ts
637
+ function getGitInfo() {
638
+ try {
639
+ const commit = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
640
+ const commitShort = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
641
+ let branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.GIT_BRANCH || process.env.BRANCH_NAME;
642
+ if (!branch) {
643
+ branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
644
+ if (branch === "HEAD") try {
645
+ const branches = execSync(`git show-ref --heads | grep ${commit}`, { encoding: "utf-8" }).trim();
646
+ if (branches) {
647
+ const match = branches.split("\n")[0].match(/refs\/heads\/(.+)$/);
648
+ if (match) branch = match[1];
649
+ }
650
+ } catch {}
651
+ }
652
+ return {
653
+ commit,
654
+ commitShort,
655
+ branch
656
+ };
657
+ } catch {
658
+ return {
659
+ commit: "",
660
+ commitShort: "",
661
+ branch: ""
662
+ };
663
+ }
664
+ }
471
665
  function createSystemInfo(options = {}) {
472
666
  const defineKey = options.defineKey || "__SYSTEM_INFO__";
473
667
  return {
@@ -476,7 +670,8 @@ function createSystemInfo(options = {}) {
476
670
  const pkg = await readPackageJSON();
477
671
  const systemInfo = {
478
672
  buildTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
479
- pkg
673
+ pkg,
674
+ git: getGitInfo()
480
675
  };
481
676
  return { define: { [defineKey]: JSON.stringify(systemInfo) } };
482
677
  }
@@ -749,7 +944,7 @@ function createVitePlugins(viteEnv, isBuild = false, config, type) {
749
944
  createDTS(),
750
945
  createInjectCSS(),
751
946
  createInjectAuto({ id: config.moduleId })
752
- ] : null,
947
+ ] : [createCleanBuild()],
753
948
  isBuild ? createCompression(viteEnv) : null
754
949
  ].filter(Boolean);
755
950
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pubinfo/vite",
3
3
  "type": "module",
4
- "version": "2.0.13",
4
+ "version": "2.0.15",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "vue": "^3.5.17"
22
22
  },
23
23
  "dependencies": {
24
- "@pubinfo/unplugin-openapi": "^0.9.0",
24
+ "@pubinfo/unplugin-openapi": "^0.9.1",
25
25
  "@vitejs/plugin-legacy": "^7.2.1",
26
26
  "@vitejs/plugin-vue": "^6.0.0",
27
27
  "@vitejs/plugin-vue-jsx": "^5.0.1",
@@ -36,6 +36,7 @@
36
36
  "jszip": "^3.10.1",
37
37
  "lodash-es": "^4.17.21",
38
38
  "magic-string": "^0.30.19",
39
+ "micromatch": "^4.0.8",
39
40
  "picomatch": "^4.0.3",
40
41
  "pkg-types": "^2.3.0",
41
42
  "rolldown-vite": "^7.1.2",
@@ -54,9 +55,11 @@
54
55
  "devDependencies": {
55
56
  "@types/fs-extra": "^11.0.4",
56
57
  "@types/lodash-es": "^4.17.12",
58
+ "@types/micromatch": "^4.0.9",
57
59
  "@types/node": "^24.0.10",
58
60
  "@types/picomatch": "^4.0.2",
59
- "vue": "^3.5.17"
61
+ "vue": "^3.5.17",
62
+ "@pubinfo/shared": "2.0.15"
60
63
  },
61
64
  "scripts": {
62
65
  "dev": "tsdown --watch",