vite-userscript-plugin 2.2.0 → 2.3.1
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 +2 -2
- package/dist/index.d.ts +28 -6
- package/dist/index.js +64 -33
- package/package.json +4 -7
package/README.md
CHANGED
|
@@ -133,9 +133,9 @@ See [examples/sourcemap](./examples/sourcemap).
|
|
|
133
133
|
| Option | Default | Description |
|
|
134
134
|
| --- | --- | --- |
|
|
135
135
|
| `entry` | — | Userscript entry. Required. |
|
|
136
|
-
| `header` | — | Metablock. Required: `name`, `version`, `match`. Relative `icon` / `require` / `resource` / `supportURL` / `updateURL` / `downloadURL` join `homepage` (`homepageURL` / `website` / `source`). Absolute `http(s):` URLs stay as-is. |
|
|
136
|
+
| `header` | — | Metablock. Required: `name`, `version`, `match`. Relative `icon` / `require` / `resource` / `supportURL` / `updateURL` / `downloadURL` join `homepage` (`homepageURL` / `website` / `source`). Absolute `http(s):` URLs stay as-is. `updateURL` / `downloadURL` of `none` disable updates and are not joined. |
|
|
137
137
|
| `fileName` | sanitized `header.name` | Output base name (`{fileName}.user.js`). |
|
|
138
|
-
| `server.open` | `false` | Open the install target when Vite starts. HMR: `.dev.user.js`. `file`: `.user.js` URL. |
|
|
138
|
+
| `server.open` | `false` | Open the install target when Vite starts (`true`, `'user'`, `'proxy'`). HMR: `.dev.user.js`. `file`: `.user.js` or `.proxy.user.js`. Vite opens one URL. |
|
|
139
139
|
| `server.prefix` | `'server:'` | Prefix for `@name` in serve mode. `false` disables it. |
|
|
140
140
|
| `server.file` | `false` | Watch-build `{fileName}.user.js` + `{fileName}.js` + `{fileName}.proxy.user.js`. Install `.user.js` (or the proxy if `file://` `@require` works). No HMR. |
|
|
141
141
|
| `headerAlign` | `1` | Extra spaces after the longest `@key`. `false` — one space. |
|
package/dist/index.d.ts
CHANGED
|
@@ -63,18 +63,33 @@ type HeaderConfig = {
|
|
|
63
63
|
*/
|
|
64
64
|
'author'?: string;
|
|
65
65
|
/**
|
|
66
|
+
* Script homepage. Also the base for relative `icon`, `iconURL`,
|
|
67
|
+
* `defaulticon`, `icon64`, `icon64URL`, `require`, `resource`, `supportURL`,
|
|
68
|
+
* `updateURL`, and `downloadURL`. Absolute `http(s):` URLs stay as-is.
|
|
69
|
+
* `updateURL` / `downloadURL` of `none` are not joined.
|
|
70
|
+
*
|
|
71
|
+
* Fallback order: `homepage`, `homepageURL`, `website`, `source`.
|
|
72
|
+
*
|
|
66
73
|
* @see https://www.tampermonkey.net/documentation.php#meta:homepage
|
|
67
74
|
*/
|
|
68
75
|
'homepage'?: string;
|
|
69
76
|
/**
|
|
77
|
+
* Alias of `homepage`. Used as the relative-URL base when `homepage` is empty.
|
|
78
|
+
*
|
|
70
79
|
* @see https://www.tampermonkey.net/documentation.php#meta:homepage
|
|
71
80
|
*/
|
|
72
81
|
'homepageURL'?: string;
|
|
73
82
|
/**
|
|
83
|
+
* Alias of `homepage`. Used as the relative-URL base when `homepage` and
|
|
84
|
+
* `homepageURL` are empty.
|
|
85
|
+
*
|
|
74
86
|
* @see https://www.tampermonkey.net/documentation.php#meta:homepage
|
|
75
87
|
*/
|
|
76
88
|
'website'?: string;
|
|
77
89
|
/**
|
|
90
|
+
* Alias of `homepage`. Used as the relative-URL base when `homepage`,
|
|
91
|
+
* `homepageURL`, and `website` are empty.
|
|
92
|
+
*
|
|
78
93
|
* @see https://www.tampermonkey.net/documentation.php#meta:homepage
|
|
79
94
|
*/
|
|
80
95
|
'source'?: string;
|
|
@@ -124,13 +139,17 @@ type HeaderConfig = {
|
|
|
124
139
|
*/
|
|
125
140
|
'noframes'?: boolean;
|
|
126
141
|
/**
|
|
142
|
+
* `none` disables update checks. It is not joined with homepage.
|
|
143
|
+
*
|
|
127
144
|
* @see https://www.tampermonkey.net/documentation.php#meta:updateURL
|
|
128
145
|
*/
|
|
129
|
-
'updateURL'?: string;
|
|
146
|
+
'updateURL'?: 'none' | (string & {});
|
|
130
147
|
/**
|
|
148
|
+
* `none` disables update checks (pinned / non-latest installs). It is not joined with homepage.
|
|
149
|
+
*
|
|
131
150
|
* @see https://www.tampermonkey.net/documentation.php#meta:downloadURL
|
|
132
151
|
*/
|
|
133
|
-
'downloadURL'?: string;
|
|
152
|
+
'downloadURL'?: 'none' | (string & {});
|
|
134
153
|
/**
|
|
135
154
|
* @see https://www.tampermonkey.net/documentation.php#meta:supportURL
|
|
136
155
|
*/
|
|
@@ -144,14 +163,17 @@ type HeaderConfig = {
|
|
|
144
163
|
*/
|
|
145
164
|
'unwrap'?: boolean;
|
|
146
165
|
};
|
|
166
|
+
type ServerOpen = boolean | 'user' | 'proxy';
|
|
167
|
+
type ResolvedServerOpen = false | 'dev' | 'user' | 'proxy';
|
|
147
168
|
interface ServerConfig {
|
|
148
169
|
/**
|
|
149
170
|
* Open the install target when Vite starts.
|
|
150
|
-
* HMR
|
|
171
|
+
* `true`: HMR → `.dev.user.js`; `file` → `{fileName}.user.js`.
|
|
172
|
+
* `'user'` / `'proxy'`: file-mode install URL. Vite opens one path.
|
|
151
173
|
*
|
|
152
174
|
* @default false
|
|
153
175
|
*/
|
|
154
|
-
open?:
|
|
176
|
+
open?: ServerOpen;
|
|
155
177
|
/**
|
|
156
178
|
* Prefix applied to `@name` in serve mode.
|
|
157
179
|
* Set `false` to disable.
|
|
@@ -229,7 +251,7 @@ interface ResolvedScript {
|
|
|
229
251
|
iifeName: string;
|
|
230
252
|
header: HeaderConfig;
|
|
231
253
|
server: {
|
|
232
|
-
open:
|
|
254
|
+
open: ResolvedServerOpen;
|
|
233
255
|
prefix: string | false;
|
|
234
256
|
file: boolean;
|
|
235
257
|
};
|
|
@@ -242,4 +264,4 @@ interface ResolvedScript {
|
|
|
242
264
|
//#region src/plugin.d.ts
|
|
243
265
|
declare function UserscriptPlugin(config: UserscriptPluginConfig): Plugin[];
|
|
244
266
|
//#endregion
|
|
245
|
-
export { type HeaderConfig, type HeaderGenerateContext, type ResolvedScript, type ServerConfig, type UserscriptConfig, type UserscriptPluginConfig, UserscriptPlugin as default };
|
|
267
|
+
export { type HeaderConfig, type HeaderGenerateContext, type ResolvedScript, type ResolvedServerOpen, type ServerConfig, type ServerOpen, type UserscriptConfig, type UserscriptPluginConfig, UserscriptPlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import { Buffer } from "node:buffer";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { styleText } from "node:util";
|
|
7
|
-
import openLink from "open";
|
|
8
7
|
//#region src/grants/catalog.ts
|
|
9
8
|
const GM = [
|
|
10
9
|
"setValue",
|
|
@@ -119,16 +118,17 @@ function resolveHomePage(header) {
|
|
|
119
118
|
const trimmed = homePage.trim();
|
|
120
119
|
return trimmed === "" ? void 0 : trimmed;
|
|
121
120
|
}
|
|
122
|
-
function isResolvableHeaderPath(value) {
|
|
121
|
+
function isResolvableHeaderPath(value, field) {
|
|
123
122
|
const trimmed = value.trim();
|
|
124
123
|
if (!trimmed) return false;
|
|
124
|
+
if ((field === "updateURL" || field === "downloadURL") && trimmed.toLowerCase() === "none") return false;
|
|
125
125
|
if (ABSOLUTE_URL_RE.test(trimmed) || trimmed.startsWith("//") || trimmed.startsWith("/")) return false;
|
|
126
126
|
return true;
|
|
127
127
|
}
|
|
128
|
-
function containsResolvableHeaderPath(value) {
|
|
129
|
-
if (typeof value === "string") return isResolvableHeaderPath(value);
|
|
128
|
+
function containsResolvableHeaderPath(value, field) {
|
|
129
|
+
if (typeof value === "string") return isResolvableHeaderPath(value, field);
|
|
130
130
|
if (!Array.isArray(value)) return false;
|
|
131
|
-
return value.some((item) => typeof item === "string" && isResolvableHeaderPath(item));
|
|
131
|
+
return value.some((item) => typeof item === "string" && isResolvableHeaderPath(item, field));
|
|
132
132
|
}
|
|
133
133
|
function containsResolvableResourcePath(resource) {
|
|
134
134
|
if (!Array.isArray(resource)) return false;
|
|
@@ -138,7 +138,7 @@ function containsResolvableResourcePath(resource) {
|
|
|
138
138
|
}
|
|
139
139
|
function listHomepageRelativeFields(header) {
|
|
140
140
|
const fields = [];
|
|
141
|
-
for (const key of HOMEPAGE_RELATIVE_FIELDS) if (containsResolvableHeaderPath(header[key])) fields.push(key);
|
|
141
|
+
for (const key of HOMEPAGE_RELATIVE_FIELDS) if (containsResolvableHeaderPath(header[key], key)) fields.push(key);
|
|
142
142
|
if (containsResolvableResourcePath(header.resource)) fields.push("resource");
|
|
143
143
|
return fields;
|
|
144
144
|
}
|
|
@@ -149,12 +149,12 @@ function resolvePublicFileUrl(header, fileName) {
|
|
|
149
149
|
return new URL(fileName, ensureTrailingSlash(homePage)).href;
|
|
150
150
|
} catch {}
|
|
151
151
|
}
|
|
152
|
-
function resolveHeaderUrlValue(header, value) {
|
|
152
|
+
function resolveHeaderUrlValue(header, value, field) {
|
|
153
153
|
if (typeof value === "string") {
|
|
154
|
-
if (!isResolvableHeaderPath(value)) return value;
|
|
154
|
+
if (!isResolvableHeaderPath(value, field)) return value;
|
|
155
155
|
return resolvePublicFileUrl(header, value.trim()) ?? value;
|
|
156
156
|
}
|
|
157
|
-
if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? resolveHeaderUrlValue(header, item) : item);
|
|
157
|
+
if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? resolveHeaderUrlValue(header, item, field) : item);
|
|
158
158
|
return value;
|
|
159
159
|
}
|
|
160
160
|
function resolveResourceUrls(header, resource) {
|
|
@@ -170,7 +170,7 @@ function resolveResourceUrls(header, resource) {
|
|
|
170
170
|
function applyHomepageRelativeUrls(header) {
|
|
171
171
|
if (!resolveHomePage(header)) return header;
|
|
172
172
|
const next = { ...header };
|
|
173
|
-
for (const key of HOMEPAGE_RELATIVE_FIELDS) if (next[key] != null) Object.assign(next, { [key]: resolveHeaderUrlValue(next, next[key]) });
|
|
173
|
+
for (const key of HOMEPAGE_RELATIVE_FIELDS) if (next[key] != null) Object.assign(next, { [key]: resolveHeaderUrlValue(next, next[key], key) });
|
|
174
174
|
if (next.resource != null) next.resource = resolveResourceUrls(next, next.resource);
|
|
175
175
|
return next;
|
|
176
176
|
}
|
|
@@ -639,19 +639,25 @@ function assertHeader(header, label) {
|
|
|
639
639
|
"match"
|
|
640
640
|
]) if (isEmptyHeaderField(header[field])) throw new Error(`[${PLUGIN_NAME}] ${label} is missing required header.${field}`);
|
|
641
641
|
}
|
|
642
|
+
function resolveServerOpen(open, file) {
|
|
643
|
+
if (!open) return false;
|
|
644
|
+
if (open === true) return file ? "user" : "dev";
|
|
645
|
+
return file ? open : "dev";
|
|
646
|
+
}
|
|
642
647
|
function toResolvedScript(config) {
|
|
643
648
|
if (!config.entry) throw new Error(`[${PLUGIN_NAME}] Provide an "entry" for each userscript`);
|
|
644
649
|
assertHeader(config.header ?? {}, config.entry);
|
|
645
650
|
const fileName = sanitizeFileName(config.fileName ?? config.header.name);
|
|
651
|
+
const file = config.server?.file ?? false;
|
|
646
652
|
return {
|
|
647
653
|
entry: config.entry,
|
|
648
654
|
fileName,
|
|
649
655
|
iifeName: toIdentifier(fileName),
|
|
650
656
|
header: config.header,
|
|
651
657
|
server: {
|
|
652
|
-
open: config.server?.open
|
|
658
|
+
open: resolveServerOpen(config.server?.open, file),
|
|
653
659
|
prefix: config.server?.prefix ?? "server:",
|
|
654
|
-
file
|
|
660
|
+
file
|
|
655
661
|
},
|
|
656
662
|
headerAlign: config.headerAlign ?? 1,
|
|
657
663
|
generate: config.generate,
|
|
@@ -678,8 +684,23 @@ function collectHomepageRelativeUrlWarnings(config) {
|
|
|
678
684
|
}
|
|
679
685
|
return warnings;
|
|
680
686
|
}
|
|
681
|
-
function
|
|
682
|
-
|
|
687
|
+
function collectServerOpenWarnings(config, input) {
|
|
688
|
+
const warnings = [];
|
|
689
|
+
const items = Array.isArray(input) ? input : [input];
|
|
690
|
+
for (const [index, script] of config.scripts.entries()) {
|
|
691
|
+
const open = items[index]?.server?.open;
|
|
692
|
+
if ((open === "user" || open === "proxy") && !script.server.file) warnings.push(`[${PLUGIN_NAME}] server.open: "${open}" requires server.file for "${script.fileName}" — opening .dev.user.js instead`);
|
|
693
|
+
}
|
|
694
|
+
const opened = config.scripts.filter((script) => script.server.open);
|
|
695
|
+
if (opened.length > 1) warnings.push(`[${PLUGIN_NAME}] server.open is set on multiple scripts; Vite opens only "${opened[0]?.fileName}"`);
|
|
696
|
+
return warnings;
|
|
697
|
+
}
|
|
698
|
+
function collectConfigWarnings(config, input) {
|
|
699
|
+
return [
|
|
700
|
+
...collectAutoMetaUrlsWarnings(config),
|
|
701
|
+
...collectHomepageRelativeUrlWarnings(config),
|
|
702
|
+
...collectServerOpenWarnings(config, input)
|
|
703
|
+
];
|
|
683
704
|
}
|
|
684
705
|
function resolvePluginConfig(config) {
|
|
685
706
|
const items = Array.isArray(config) ? config : [config];
|
|
@@ -717,16 +738,24 @@ function shimModule(code, id) {
|
|
|
717
738
|
}
|
|
718
739
|
//#endregion
|
|
719
740
|
//#region src/serve/logger.ts
|
|
741
|
+
const INSTALL_LABEL_WIDTH = 10;
|
|
742
|
+
function colonPadFor(label) {
|
|
743
|
+
return " ".repeat(Math.max(0, INSTALL_LABEL_WIDTH - label.length) + 1);
|
|
744
|
+
}
|
|
745
|
+
function formatLabeledLine(label, value) {
|
|
746
|
+
return ` ${styleText("green", "➜")} ${styleText("bold", label)}:${colonPadFor(label)}${value}`;
|
|
747
|
+
}
|
|
748
|
+
function alignViteUrlLine(message) {
|
|
749
|
+
return message.replace(/(Local(?:\u001B\[[0-9;]*m)*:)(\s+)/, (_match, prefix) => `${prefix}${colonPadFor("Local")}`);
|
|
750
|
+
}
|
|
720
751
|
function formatInstallLine(installUrl, label = "Userscript") {
|
|
721
|
-
|
|
722
|
-
const padded = label.padEnd(10);
|
|
723
|
-
return ` ${styleText("green", "➜")} ${styleText("bold", padded)}: ${coloredUrl}`;
|
|
752
|
+
return formatLabeledLine(label, styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`)));
|
|
724
753
|
}
|
|
725
754
|
function formatRebuildLine(elapsedMs) {
|
|
726
755
|
return `${styleText("green", "Userscript rebuilt")} ${styleText("dim", `(${elapsedMs}ms)`)}`;
|
|
727
756
|
}
|
|
728
757
|
function formatFaqHint() {
|
|
729
|
-
return `${
|
|
758
|
+
return `${formatLabeledLine("FAQ", styleText("cyan", FAQ_URL))}\n`;
|
|
730
759
|
}
|
|
731
760
|
function stripAnsi(text) {
|
|
732
761
|
return text.replace(/\u001B\[[0-9;]*m/g, "");
|
|
@@ -744,7 +773,7 @@ function createAfterLocalLogger(info, localCount, onAfterLocal) {
|
|
|
744
773
|
};
|
|
745
774
|
return {
|
|
746
775
|
info: (msg, options) => {
|
|
747
|
-
info(msg, options);
|
|
776
|
+
info(typeof msg === "string" ? alignViteUrlLine(msg) : msg, options);
|
|
748
777
|
if (remaining > 0 && isViteLocalUrlLine(String(msg))) {
|
|
749
778
|
remaining -= 1;
|
|
750
779
|
if (remaining === 0) flush();
|
|
@@ -793,13 +822,18 @@ function matchProxyUserscript(url, fileName) {
|
|
|
793
822
|
function matchFileUserscript(url, fileName) {
|
|
794
823
|
return (url.split("?")[0] ?? "") === `/${fileName}.user.js`;
|
|
795
824
|
}
|
|
796
|
-
function
|
|
797
|
-
|
|
825
|
+
function toInstallFileName(fileName, kind) {
|
|
826
|
+
return {
|
|
798
827
|
dev: `${fileName}.dev.user.js`,
|
|
799
828
|
proxy: toProxyFileName(fileName),
|
|
800
829
|
user: `${fileName}.user.js`
|
|
801
|
-
};
|
|
802
|
-
|
|
830
|
+
}[kind];
|
|
831
|
+
}
|
|
832
|
+
function toInstallPath(fileName, kind = "dev") {
|
|
833
|
+
return `/${toInstallFileName(fileName, kind)}`;
|
|
834
|
+
}
|
|
835
|
+
function toInstallUrl(origin, fileName, kind = "dev") {
|
|
836
|
+
return `${origin.replace(/\/$/, "")}${toInstallPath(fileName, kind)}`;
|
|
803
837
|
}
|
|
804
838
|
function toServeEntryPath(root, entry) {
|
|
805
839
|
const absolute = resolve(root, entry);
|
|
@@ -959,14 +993,6 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
959
993
|
logger.flush();
|
|
960
994
|
}
|
|
961
995
|
};
|
|
962
|
-
server.httpServer?.once("listening", () => {
|
|
963
|
-
const toOpen = resolved.scripts.filter((script) => script.server.open);
|
|
964
|
-
if (!toOpen.length) return;
|
|
965
|
-
queueMicrotask(() => {
|
|
966
|
-
const origin = resolveServerOrigin(server.resolvedUrls);
|
|
967
|
-
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName, script.server.file ? "user" : "dev"));
|
|
968
|
-
});
|
|
969
|
-
});
|
|
970
996
|
}
|
|
971
997
|
//#endregion
|
|
972
998
|
//#region src/plugin.ts
|
|
@@ -1053,13 +1079,18 @@ function UserscriptPlugin(config) {
|
|
|
1053
1079
|
userConfig.build.rolldownOptions.input = input;
|
|
1054
1080
|
const userOutput = userConfig.build.rolldownOptions.output;
|
|
1055
1081
|
const userEntryFileNames = userOutput && !Array.isArray(userOutput) ? userOutput.entryFileNames : void 0;
|
|
1082
|
+
const openScript = resolved.scripts.find((script) => script.server.open);
|
|
1083
|
+
const openPath = openScript?.server.open ? toInstallPath(openScript.fileName, openScript.server.open) : void 0;
|
|
1056
1084
|
return {
|
|
1057
1085
|
appType: userConfig.appType ?? (hasHtml ? "spa" : "custom"),
|
|
1058
1086
|
optimizeDeps: {
|
|
1059
1087
|
entries: Object.values(input),
|
|
1060
1088
|
exclude: [VIRTUAL_MODULE_ID]
|
|
1061
1089
|
},
|
|
1062
|
-
server: {
|
|
1090
|
+
server: {
|
|
1091
|
+
cors: userConfig.server?.cors ?? true,
|
|
1092
|
+
...openPath ? { open: openPath } : {}
|
|
1093
|
+
},
|
|
1063
1094
|
build: {
|
|
1064
1095
|
minify: userConfig.build?.minify ?? false,
|
|
1065
1096
|
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? Number.MAX_SAFE_INTEGER,
|
|
@@ -1082,7 +1113,7 @@ function UserscriptPlugin(config) {
|
|
|
1082
1113
|
command = viteConfig.command;
|
|
1083
1114
|
resolved = absolutizeEntries(resolved, viteConfig.root);
|
|
1084
1115
|
reactPreamble = hasReactRefreshPlugin(viteConfig.plugins);
|
|
1085
|
-
for (const message of collectConfigWarnings(resolved)) viteConfig.logger.warn(message);
|
|
1116
|
+
for (const message of collectConfigWarnings(resolved, config)) viteConfig.logger.warn(message);
|
|
1086
1117
|
}
|
|
1087
1118
|
},
|
|
1088
1119
|
{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-userscript-plugin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Vitalij Ryndin",
|
|
7
7
|
"url": "https://github.com/crashmax-dev"
|
|
@@ -41,9 +41,6 @@
|
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"vite": "^8.0.0"
|
|
43
43
|
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"open": "11.0.1"
|
|
46
|
-
},
|
|
47
44
|
"devDependencies": {
|
|
48
45
|
"@antfu/eslint-config": "7.2.0",
|
|
49
46
|
"@sveltejs/vite-plugin-svelte": "7.3.0",
|
|
@@ -54,13 +51,13 @@
|
|
|
54
51
|
"eslint": "9.39.2",
|
|
55
52
|
"eslint-plugin-format": "2.0.1",
|
|
56
53
|
"prettier-plugin-css-order": "2.2.0",
|
|
57
|
-
"svelte": "5.
|
|
54
|
+
"svelte": "5.57.0",
|
|
58
55
|
"tsdown": "0.22.14",
|
|
59
|
-
"turbo": "2.10.
|
|
56
|
+
"turbo": "2.10.12",
|
|
60
57
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
61
58
|
"vite": "8.2.2",
|
|
62
59
|
"vitest": "4.1.11",
|
|
63
|
-
"vue": "3.5.
|
|
60
|
+
"vue": "3.5.42"
|
|
64
61
|
},
|
|
65
62
|
"scripts": {
|
|
66
63
|
"dev": "tsdown --watch",
|