@olegkuibar/plunk 0.2.0-canary.04ff96f

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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/dist/add-5ZRFUL6Z.mjs +258 -0
  4. package/dist/chokidar-XGAEDN45.mjs +1746 -0
  5. package/dist/chunk-34UXZ622.mjs +98 -0
  6. package/dist/chunk-4O2QOKVO.mjs +1958 -0
  7. package/dist/chunk-CSMZ6DZA.mjs +367 -0
  8. package/dist/chunk-CZM4TNAI.mjs +292 -0
  9. package/dist/chunk-EDUXIQ5W.mjs +1729 -0
  10. package/dist/chunk-GAAB2TLH.mjs +160 -0
  11. package/dist/chunk-HKNM3UWU.mjs +496 -0
  12. package/dist/chunk-I6SN7BBN.mjs +1131 -0
  13. package/dist/chunk-KYDBD2KQ.mjs +39 -0
  14. package/dist/chunk-LKQINKH4.mjs +130 -0
  15. package/dist/chunk-PUSXMPOF.mjs +82 -0
  16. package/dist/chunk-S4HJSJ32.mjs +69 -0
  17. package/dist/chunk-W3C72UKC.mjs +113 -0
  18. package/dist/chunk-WSECI6M7.mjs +85 -0
  19. package/dist/chunk-XMIZ7OUZ.mjs +26 -0
  20. package/dist/chunk-XZK5T4GK.mjs +23 -0
  21. package/dist/chunk-ZOYNYK5Y.mjs +23 -0
  22. package/dist/chunk-ZQCGJUBJ.mjs +92 -0
  23. package/dist/clean-LTR5MZTY.mjs +84 -0
  24. package/dist/cli.mjs +57 -0
  25. package/dist/dev-LFXQP6SA.mjs +194 -0
  26. package/dist/dist-DUFCZSIL.mjs +813 -0
  27. package/dist/doctor-R7ZVR7PY.mjs +230 -0
  28. package/dist/hash-worker.mjs +65 -0
  29. package/dist/index.d.ts +194 -0
  30. package/dist/index.mjs +9486 -0
  31. package/dist/init-SWCNRISY.mjs +310 -0
  32. package/dist/list-B77L2F34.mjs +119 -0
  33. package/dist/migrate-X5TIC5SS.mjs +124 -0
  34. package/dist/prompt-HTPH6HQ7.mjs +756 -0
  35. package/dist/publish-UXCLPNM6.mjs +63 -0
  36. package/dist/push-JI6HGCFG.mjs +197 -0
  37. package/dist/remove-DCR7KKD5.mjs +149 -0
  38. package/dist/restore-SUN3WGSW.mjs +124 -0
  39. package/dist/status-MESRBH54.mjs +103 -0
  40. package/dist/tailwind-source-JBBEIXIJ.mjs +89 -0
  41. package/dist/update-SKDSA673.mjs +100 -0
  42. package/dist/vite-config-BAK67JHB.mjs +128 -0
  43. package/dist/vite-plugin.d.ts +5 -0
  44. package/dist/vite-plugin.mjs +42 -0
  45. package/dist/workspace-76HJPAK2.mjs +97 -0
  46. package/package.json +96 -0
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"node:module";globalThis.require=__cr(import.meta.url);
3
+ import "./chunk-KYDBD2KQ.mjs";
4
+
5
+ // src/utils/tailwind-source.ts
6
+ import { readFile, readdir, writeFile } from "fs/promises";
7
+ import { join, dirname, relative } from "path";
8
+ var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".plunk", "dist", ".git"]);
9
+ async function findTailwindCss(projectRoot) {
10
+ let entries;
11
+ try {
12
+ entries = await readdir(projectRoot, { recursive: true, encoding: "utf-8" });
13
+ } catch {
14
+ return null;
15
+ }
16
+ const cssFiles = entries.filter((entry) => {
17
+ if (!entry.endsWith(".css")) return false;
18
+ const parts = entry.replace(/\\/g, "/").split("/");
19
+ return !parts.some((p) => IGNORED_DIRS.has(p));
20
+ }).map((entry) => join(projectRoot, entry));
21
+ for (const file of cssFiles) {
22
+ let content;
23
+ try {
24
+ content = await readFile(file, "utf-8");
25
+ } catch {
26
+ continue;
27
+ }
28
+ if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'")) {
29
+ return file;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ async function addTailwindSource(cssPath, packageName, projectRoot) {
35
+ let content;
36
+ try {
37
+ content = await readFile(cssPath, "utf-8");
38
+ } catch {
39
+ return { modified: false, error: "could not read CSS file" };
40
+ }
41
+ if (content.includes(`node_modules/${packageName}`)) {
42
+ return { modified: false };
43
+ }
44
+ const sourcePath = computeSourcePath(cssPath, packageName, projectRoot);
45
+ const directiveRegex = /^@(import|source|plugin|theme)\s.+$/gm;
46
+ let lastMatch = null;
47
+ let match;
48
+ while ((match = directiveRegex.exec(content)) !== null) {
49
+ lastMatch = match;
50
+ }
51
+ const sourceLine = `@source "${sourcePath}";`;
52
+ if (lastMatch) {
53
+ const insertPos = lastMatch.index + lastMatch[0].length;
54
+ content = content.slice(0, insertPos) + "\n" + sourceLine + content.slice(insertPos);
55
+ } else {
56
+ content = sourceLine + "\n" + content;
57
+ }
58
+ await writeFile(cssPath, content);
59
+ return { modified: true };
60
+ }
61
+ async function removeTailwindSource(cssPath, packageName) {
62
+ let content;
63
+ try {
64
+ content = await readFile(cssPath, "utf-8");
65
+ } catch {
66
+ return { modified: false };
67
+ }
68
+ const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69
+ const sourceRegex = new RegExp(
70
+ `^@source\\s+["'][^"']*node_modules/${escaped}["'];?\\s*\\n?`,
71
+ "m"
72
+ );
73
+ if (!sourceRegex.test(content)) {
74
+ return { modified: false };
75
+ }
76
+ content = content.replace(sourceRegex, "");
77
+ await writeFile(cssPath, content);
78
+ return { modified: true };
79
+ }
80
+ function computeSourcePath(cssPath, packageName, projectRoot) {
81
+ const cssDir = dirname(cssPath);
82
+ const targetPath = join(projectRoot, "node_modules", packageName);
83
+ return relative(cssDir, targetPath).replace(/\\/g, "/");
84
+ }
85
+ export {
86
+ addTailwindSource,
87
+ findTailwindCss,
88
+ removeTailwindSource
89
+ };
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"node:module";globalThis.require=__cr(import.meta.url);
3
+ import {
4
+ inject
5
+ } from "./chunk-CZM4TNAI.mjs";
6
+ import "./chunk-34UXZ622.mjs";
7
+ import {
8
+ addLink,
9
+ readConsumerState
10
+ } from "./chunk-GAAB2TLH.mjs";
11
+ import {
12
+ errorWithSuggestion
13
+ } from "./chunk-S4HJSJ32.mjs";
14
+ import {
15
+ findStoreEntry
16
+ } from "./chunk-W3C72UKC.mjs";
17
+ import {
18
+ Timer
19
+ } from "./chunk-XZK5T4GK.mjs";
20
+ import "./chunk-PUSXMPOF.mjs";
21
+ import "./chunk-EDUXIQ5W.mjs";
22
+ import {
23
+ output,
24
+ suppressHumanOutput
25
+ } from "./chunk-ZOYNYK5Y.mjs";
26
+ import {
27
+ defineCommand
28
+ } from "./chunk-CSMZ6DZA.mjs";
29
+ import "./chunk-HKNM3UWU.mjs";
30
+ import {
31
+ consola,
32
+ verbose
33
+ } from "./chunk-I6SN7BBN.mjs";
34
+ import "./chunk-KYDBD2KQ.mjs";
35
+
36
+ // src/commands/update.ts
37
+ import { resolve } from "path";
38
+ var update_default = defineCommand({
39
+ meta: {
40
+ name: "update",
41
+ description: "Pull latest versions from the store for linked packages"
42
+ },
43
+ args: {
44
+ package: {
45
+ type: "positional",
46
+ description: "Package name to update (default: all linked)",
47
+ required: false
48
+ }
49
+ },
50
+ async run({ args }) {
51
+ suppressHumanOutput();
52
+ const timer = new Timer();
53
+ const consumerPath = resolve(".");
54
+ const state = await readConsumerState(consumerPath);
55
+ const links = Object.entries(state.links);
56
+ if (links.length === 0) {
57
+ consola.info("No linked packages in this project");
58
+ output({ updated: 0, skipped: 0 });
59
+ return;
60
+ }
61
+ const toUpdate = args.package ? links.filter(([name]) => name === args.package) : links;
62
+ if (args.package && toUpdate.length === 0) {
63
+ errorWithSuggestion(`Package "${args.package}" is not linked in this project`);
64
+ process.exit(1);
65
+ }
66
+ let updated = 0;
67
+ let skipped = 0;
68
+ for (const [packageName, link] of toUpdate) {
69
+ const entry = await findStoreEntry(packageName);
70
+ if (!entry) {
71
+ consola.warn(`Store entry missing for ${packageName}. Re-publish it.`);
72
+ continue;
73
+ }
74
+ if (entry.meta.contentHash === link.contentHash) {
75
+ verbose(`[update] ${packageName}@${entry.version} already up to date`);
76
+ skipped++;
77
+ continue;
78
+ }
79
+ const result = await inject(entry, consumerPath, link.packageManager);
80
+ const updatedLink = {
81
+ ...link,
82
+ version: entry.version,
83
+ contentHash: entry.meta.contentHash,
84
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
85
+ };
86
+ await addLink(consumerPath, packageName, updatedLink);
87
+ consola.success(
88
+ `Updated ${packageName}@${entry.version} (${result.copied} files changed)`
89
+ );
90
+ updated++;
91
+ }
92
+ consola.info(
93
+ `Update complete: ${updated} updated, ${skipped} unchanged in ${timer.elapsed()}`
94
+ );
95
+ output({ updated, skipped, elapsed: timer.elapsedMs() });
96
+ }
97
+ });
98
+ export {
99
+ update_default as default
100
+ };
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"node:module";globalThis.require=__cr(import.meta.url);
3
+ import "./chunk-KYDBD2KQ.mjs";
4
+
5
+ // src/utils/vite-config.ts
6
+ import { readFile, writeFile } from "fs/promises";
7
+ function detectIndent(content) {
8
+ const match = content.match(/^(\s+)\S/m);
9
+ return match?.[1] || " ";
10
+ }
11
+ function hasPlunkPlugin(content) {
12
+ return content.includes("@olegkuibar/plunk/vite") || content.includes("vite-plugin-plunk");
13
+ }
14
+ function parsePluginItems(str) {
15
+ const items = [];
16
+ let current = "";
17
+ let depth = 0;
18
+ for (const ch of str) {
19
+ if (ch === "(") depth++;
20
+ if (ch === ")") depth--;
21
+ if (ch === "," && depth === 0) {
22
+ const trimmed2 = current.trim();
23
+ if (trimmed2) items.push(trimmed2);
24
+ current = "";
25
+ } else {
26
+ current += ch;
27
+ }
28
+ }
29
+ const trimmed = current.trim();
30
+ if (trimmed) items.push(trimmed);
31
+ return items;
32
+ }
33
+ function formatPlugins(items, indent) {
34
+ if (items.length === 0) return "[]";
35
+ if (items.length === 1) return `[${items[0]}]`;
36
+ const inner = items.map((i) => `${indent}${indent}${i},`).join("\n");
37
+ return `[
38
+ ${inner}
39
+ ${indent}]`;
40
+ }
41
+ async function addPlunkVitePlugin(configPath) {
42
+ let content;
43
+ try {
44
+ content = await readFile(configPath, "utf-8");
45
+ } catch {
46
+ return { modified: false, error: "could not read config file" };
47
+ }
48
+ if (hasPlunkPlugin(content)) {
49
+ return { modified: false };
50
+ }
51
+ const indent = detectIndent(content);
52
+ const pluginsRegex = /plugins\s*:\s*\[([\s\S]*?)\]/;
53
+ const pluginsMatch = pluginsRegex.exec(content);
54
+ if (pluginsMatch) {
55
+ const items = parsePluginItems(pluginsMatch[1]);
56
+ items.push("plunk()");
57
+ const newPlugins = formatPlugins(items, indent);
58
+ content = content.replace(pluginsRegex, `plugins: ${newPlugins}`);
59
+ } else {
60
+ const configObjRegex = /(?:defineConfig\s*\(\s*\{|export\s+default\s+\{)/;
61
+ const configObjMatch = configObjRegex.exec(content);
62
+ if (configObjMatch) {
63
+ const insertPos = configObjMatch.index + configObjMatch[0].length;
64
+ const newSection = `
65
+ ${indent}plugins: [plunk()],`;
66
+ content = content.slice(0, insertPos) + newSection + content.slice(insertPos);
67
+ } else {
68
+ return {
69
+ modified: false,
70
+ error: "unrecognized Vite config pattern"
71
+ };
72
+ }
73
+ }
74
+ const importLine = `import plunk from "@olegkuibar/plunk/vite";
75
+ `;
76
+ const lastImportRegex = /^import\s.+$/gm;
77
+ let lastImportEnd = 0;
78
+ let match;
79
+ while ((match = lastImportRegex.exec(content)) !== null) {
80
+ lastImportEnd = match.index + match[0].length;
81
+ }
82
+ if (lastImportEnd > 0) {
83
+ content = content.slice(0, lastImportEnd) + "\n" + importLine + content.slice(lastImportEnd + 1);
84
+ } else {
85
+ content = importLine + "\n" + content;
86
+ }
87
+ await writeFile(configPath, content);
88
+ return { modified: true };
89
+ }
90
+ async function removeFromViteConfig(configPath) {
91
+ let content;
92
+ try {
93
+ content = await readFile(configPath, "utf-8");
94
+ } catch {
95
+ return { modified: false };
96
+ }
97
+ if (!hasPlunkPlugin(content)) {
98
+ return { modified: false };
99
+ }
100
+ let modified = false;
101
+ const importRegex = /^import\s+\w+\s+from\s+["']@olegkuibar\/plunk\/vite["'];?\s*\n?/m;
102
+ if (importRegex.test(content)) {
103
+ content = content.replace(importRegex, "");
104
+ modified = true;
105
+ }
106
+ const pluginsRegex = /plugins\s*:\s*\[([\s\S]*?)\]/;
107
+ const pluginsMatch = pluginsRegex.exec(content);
108
+ if (pluginsMatch) {
109
+ const items = parsePluginItems(pluginsMatch[1]);
110
+ const filtered = items.filter(
111
+ (item) => !item.startsWith("plunk(")
112
+ );
113
+ if (filtered.length !== items.length) {
114
+ const indent = detectIndent(content);
115
+ const newPlugins = formatPlugins(filtered, indent);
116
+ content = content.replace(pluginsRegex, `plugins: ${newPlugins}`);
117
+ modified = true;
118
+ }
119
+ }
120
+ if (modified) {
121
+ await writeFile(configPath, content);
122
+ }
123
+ return { modified };
124
+ }
125
+ export {
126
+ addPlunkVitePlugin,
127
+ removeFromViteConfig
128
+ };
@@ -0,0 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ declare function plunkPlugin(): Plugin;
4
+
5
+ export { plunkPlugin as default };
@@ -0,0 +1,42 @@
1
+ // src/vite-plugin.ts
2
+ import { join, normalize } from "path";
3
+ import { rm } from "fs/promises";
4
+ function plunkPlugin() {
5
+ let plunkStateFile;
6
+ let cacheDir;
7
+ return {
8
+ name: "vite-plugin-plunk",
9
+ apply: "serve",
10
+ configResolved(config) {
11
+ plunkStateFile = normalize(join(config.root, ".plunk", "state.json"));
12
+ cacheDir = config.cacheDir;
13
+ },
14
+ configureServer(server) {
15
+ server.watcher.add(plunkStateFile);
16
+ server.watcher.on("change", async (changedPath) => {
17
+ if (normalize(changedPath) !== plunkStateFile) return;
18
+ server.config.logger.info("[plunk] Detected injection, reloading...", {
19
+ timestamp: true
20
+ });
21
+ try {
22
+ await rm(cacheDir, { recursive: true, force: true });
23
+ } catch {
24
+ server.config.logger.warn(
25
+ "[plunk] Could not clear Vite cache (locked?). Browser may load stale deps.",
26
+ { timestamp: true }
27
+ );
28
+ }
29
+ const seen = /* @__PURE__ */ new Set();
30
+ for (const mod of server.moduleGraph.idToModuleMap.values()) {
31
+ if (mod.id?.endsWith(".css") || mod.id?.includes("lang.css")) {
32
+ server.moduleGraph.invalidateModule(mod, seen);
33
+ }
34
+ }
35
+ server.ws.send({ type: "full-reload", path: "*" });
36
+ });
37
+ }
38
+ };
39
+ }
40
+ export {
41
+ plunkPlugin as default
42
+ };
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"node:module";globalThis.require=__cr(import.meta.url);
3
+ import {
4
+ exists
5
+ } from "./chunk-HKNM3UWU.mjs";
6
+ import "./chunk-I6SN7BBN.mjs";
7
+ import "./chunk-KYDBD2KQ.mjs";
8
+
9
+ // src/utils/workspace.ts
10
+ import { readFile } from "fs/promises";
11
+ import { join, dirname } from "path";
12
+ async function findWorkspaceRoot(startDir) {
13
+ let dir = startDir;
14
+ for (; ; ) {
15
+ if (await exists(join(dir, "pnpm-workspace.yaml"))) {
16
+ return dir;
17
+ }
18
+ const parent = dirname(dir);
19
+ if (parent === dir) return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ async function parseCatalogs(workspaceRoot) {
24
+ const result = { default: {}, named: {} };
25
+ const filePath = join(workspaceRoot, "pnpm-workspace.yaml");
26
+ let content;
27
+ try {
28
+ content = await readFile(filePath, "utf-8");
29
+ } catch {
30
+ return result;
31
+ }
32
+ const lines = content.split(/\r?\n/);
33
+ let state = "top";
34
+ let currentNamedCatalog = "";
35
+ for (const line of lines) {
36
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
37
+ const indent = line.length - line.trimStart().length;
38
+ if (indent === 0) {
39
+ if (line.startsWith("catalog:")) {
40
+ state = "default-catalog";
41
+ continue;
42
+ }
43
+ if (line.startsWith("catalogs:")) {
44
+ state = "named-catalogs";
45
+ continue;
46
+ }
47
+ state = "top";
48
+ continue;
49
+ }
50
+ if (state === "default-catalog" && indent >= 2) {
51
+ const kv = parseKeyValue(line);
52
+ if (kv) result.default[kv[0]] = kv[1];
53
+ continue;
54
+ }
55
+ if (state === "named-catalogs" && indent >= 2 && indent < 4) {
56
+ const trimmed = line.trim();
57
+ if (trimmed.endsWith(":")) {
58
+ currentNamedCatalog = trimmed.slice(0, -1);
59
+ result.named[currentNamedCatalog] = {};
60
+ state = "named-catalog-entries";
61
+ }
62
+ continue;
63
+ }
64
+ if (state === "named-catalog-entries" && indent >= 4) {
65
+ const kv = parseKeyValue(line);
66
+ if (kv && currentNamedCatalog) {
67
+ result.named[currentNamedCatalog][kv[0]] = kv[1];
68
+ }
69
+ continue;
70
+ }
71
+ if (state === "named-catalog-entries" && indent >= 2 && indent < 4) {
72
+ const trimmed = line.trim();
73
+ if (trimmed.endsWith(":")) {
74
+ currentNamedCatalog = trimmed.slice(0, -1);
75
+ result.named[currentNamedCatalog] = {};
76
+ } else {
77
+ state = "named-catalogs";
78
+ }
79
+ continue;
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ function parseKeyValue(line) {
85
+ const trimmed = line.trim();
86
+ const colonIdx = trimmed.indexOf(":");
87
+ if (colonIdx <= 0) return null;
88
+ const key = trimmed.slice(0, colonIdx).trim();
89
+ const value = trimmed.slice(colonIdx + 1).trim();
90
+ if (!key || !value) return null;
91
+ const unquoted = value.replace(/^["']|["']$/g, "");
92
+ return [key, unquoted];
93
+ }
94
+ export {
95
+ findWorkspaceRoot,
96
+ parseCatalogs
97
+ };
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@olegkuibar/plunk",
3
+ "version": "0.2.0-canary.04ff96f",
4
+ "description": "Modern local package development tool. Smart file copying into node_modules — no symlinks, no git contamination.",
5
+ "type": "module",
6
+ "bin": {
7
+ "plunk": "./dist/cli.mjs"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs"
13
+ },
14
+ "./vite": {
15
+ "types": "./dist/vite-plugin.d.ts",
16
+ "import": "./dist/vite-plugin.mjs"
17
+ }
18
+ },
19
+ "typesVersions": {
20
+ "*": {
21
+ "vite": [
22
+ "./dist/vite-plugin.d.ts"
23
+ ]
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "keywords": [
30
+ "npm",
31
+ "link",
32
+ "local",
33
+ "development",
34
+ "yalc",
35
+ "package",
36
+ "monorepo",
37
+ "pnpm",
38
+ "yarn",
39
+ "bun",
40
+ "symlink",
41
+ "copy",
42
+ "hmr"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org"
47
+ },
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/oleg-kuibar/plunk.git"
52
+ },
53
+ "homepage": "https://github.com/oleg-kuibar/plunk#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/oleg-kuibar/plunk/issues"
56
+ },
57
+ "author": "Oleg Kuibar (https://github.com/oleg-kuibar)",
58
+ "engines": {
59
+ "node": ">=22.12.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^25.2.3",
63
+ "@types/picomatch": "^4.0.2",
64
+ "@types/proper-lockfile": "^4.1.4",
65
+ "tsup": "^8.5.1",
66
+ "typescript": "^5.9.3",
67
+ "vite": "^7.3.1",
68
+ "vitest": "^4.0.18"
69
+ },
70
+ "peerDependencies": {
71
+ "vite": ">=5.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "vite": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "dependencies": {
79
+ "chokidar": "^5.0.0",
80
+ "citty": "^0.2.1",
81
+ "consola": "^3.4.2",
82
+ "p-limit": "^7.3.0",
83
+ "picocolors": "^1.1.1",
84
+ "picomatch": "^4.0.3",
85
+ "proper-lockfile": "^4.1.2",
86
+ "tinypool": "^2.1.0",
87
+ "xxhash-wasm": "^1.1.0"
88
+ },
89
+ "scripts": {
90
+ "build": "tsup",
91
+ "dev": "tsup --watch",
92
+ "test": "vitest run",
93
+ "test:watch": "vitest",
94
+ "lint": "tsc --noEmit"
95
+ }
96
+ }