vite-userscript-plugin 2.2.0 → 2.3.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 CHANGED
@@ -135,7 +135,7 @@ See [examples/sourcemap](./examples/sourcemap).
135
135
  | `entry` | — | Userscript entry. Required. |
136
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. |
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
@@ -144,14 +144,17 @@ type HeaderConfig = {
144
144
  */
145
145
  'unwrap'?: boolean;
146
146
  };
147
+ type ServerOpen = boolean | 'user' | 'proxy';
148
+ type ResolvedServerOpen = false | 'dev' | 'user' | 'proxy';
147
149
  interface ServerConfig {
148
150
  /**
149
151
  * Open the install target when Vite starts.
150
- * HMR: `.dev.user.js` URL. `file`: `{fileName}.user.js` URL.
152
+ * `true`: HMR `.dev.user.js`; `file` `{fileName}.user.js`.
153
+ * `'user'` / `'proxy'`: file-mode install URL. Vite opens one path.
151
154
  *
152
155
  * @default false
153
156
  */
154
- open?: boolean;
157
+ open?: ServerOpen;
155
158
  /**
156
159
  * Prefix applied to `@name` in serve mode.
157
160
  * Set `false` to disable.
@@ -229,7 +232,7 @@ interface ResolvedScript {
229
232
  iifeName: string;
230
233
  header: HeaderConfig;
231
234
  server: {
232
- open: boolean;
235
+ open: ResolvedServerOpen;
233
236
  prefix: string | false;
234
237
  file: boolean;
235
238
  };
@@ -242,4 +245,4 @@ interface ResolvedScript {
242
245
  //#region src/plugin.d.ts
243
246
  declare function UserscriptPlugin(config: UserscriptPluginConfig): Plugin[];
244
247
  //#endregion
245
- export { type HeaderConfig, type HeaderGenerateContext, type ResolvedScript, type ServerConfig, type UserscriptConfig, type UserscriptPluginConfig, UserscriptPlugin as default };
248
+ 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",
@@ -639,19 +638,25 @@ function assertHeader(header, label) {
639
638
  "match"
640
639
  ]) if (isEmptyHeaderField(header[field])) throw new Error(`[${PLUGIN_NAME}] ${label} is missing required header.${field}`);
641
640
  }
641
+ function resolveServerOpen(open, file) {
642
+ if (!open) return false;
643
+ if (open === true) return file ? "user" : "dev";
644
+ return file ? open : "dev";
645
+ }
642
646
  function toResolvedScript(config) {
643
647
  if (!config.entry) throw new Error(`[${PLUGIN_NAME}] Provide an "entry" for each userscript`);
644
648
  assertHeader(config.header ?? {}, config.entry);
645
649
  const fileName = sanitizeFileName(config.fileName ?? config.header.name);
650
+ const file = config.server?.file ?? false;
646
651
  return {
647
652
  entry: config.entry,
648
653
  fileName,
649
654
  iifeName: toIdentifier(fileName),
650
655
  header: config.header,
651
656
  server: {
652
- open: config.server?.open ?? false,
657
+ open: resolveServerOpen(config.server?.open, file),
653
658
  prefix: config.server?.prefix ?? "server:",
654
- file: config.server?.file ?? false
659
+ file
655
660
  },
656
661
  headerAlign: config.headerAlign ?? 1,
657
662
  generate: config.generate,
@@ -678,8 +683,23 @@ function collectHomepageRelativeUrlWarnings(config) {
678
683
  }
679
684
  return warnings;
680
685
  }
681
- function collectConfigWarnings(config) {
682
- return [...collectAutoMetaUrlsWarnings(config), ...collectHomepageRelativeUrlWarnings(config)];
686
+ function collectServerOpenWarnings(config, input) {
687
+ const warnings = [];
688
+ const items = Array.isArray(input) ? input : [input];
689
+ for (const [index, script] of config.scripts.entries()) {
690
+ const open = items[index]?.server?.open;
691
+ 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`);
692
+ }
693
+ const opened = config.scripts.filter((script) => script.server.open);
694
+ if (opened.length > 1) warnings.push(`[${PLUGIN_NAME}] server.open is set on multiple scripts; Vite opens only "${opened[0]?.fileName}"`);
695
+ return warnings;
696
+ }
697
+ function collectConfigWarnings(config, input) {
698
+ return [
699
+ ...collectAutoMetaUrlsWarnings(config),
700
+ ...collectHomepageRelativeUrlWarnings(config),
701
+ ...collectServerOpenWarnings(config, input)
702
+ ];
683
703
  }
684
704
  function resolvePluginConfig(config) {
685
705
  const items = Array.isArray(config) ? config : [config];
@@ -717,16 +737,24 @@ function shimModule(code, id) {
717
737
  }
718
738
  //#endregion
719
739
  //#region src/serve/logger.ts
740
+ const INSTALL_LABEL_WIDTH = 10;
741
+ function colonPadFor(label) {
742
+ return " ".repeat(Math.max(0, INSTALL_LABEL_WIDTH - label.length) + 1);
743
+ }
744
+ function formatLabeledLine(label, value) {
745
+ return ` ${styleText("green", "➜")} ${styleText("bold", label)}:${colonPadFor(label)}${value}`;
746
+ }
747
+ function alignViteUrlLine(message) {
748
+ return message.replace(/(Local(?:\u001B\[[0-9;]*m)*:)(\s+)/, (_match, prefix) => `${prefix}${colonPadFor("Local")}`);
749
+ }
720
750
  function formatInstallLine(installUrl, label = "Userscript") {
721
- const coloredUrl = styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`));
722
- const padded = label.padEnd(10);
723
- return ` ${styleText("green", "➜")} ${styleText("bold", padded)}: ${coloredUrl}`;
751
+ return formatLabeledLine(label, styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`)));
724
752
  }
725
753
  function formatRebuildLine(elapsedMs) {
726
754
  return `${styleText("green", "Userscript rebuilt")} ${styleText("dim", `(${elapsedMs}ms)`)}`;
727
755
  }
728
756
  function formatFaqHint() {
729
- return `${` ${styleText("green", "➜")} ${styleText("bold", "FAQ")}: `}${styleText("cyan", FAQ_URL)}\n`;
757
+ return `${formatLabeledLine("FAQ", styleText("cyan", FAQ_URL))}\n`;
730
758
  }
731
759
  function stripAnsi(text) {
732
760
  return text.replace(/\u001B\[[0-9;]*m/g, "");
@@ -744,7 +772,7 @@ function createAfterLocalLogger(info, localCount, onAfterLocal) {
744
772
  };
745
773
  return {
746
774
  info: (msg, options) => {
747
- info(msg, options);
775
+ info(typeof msg === "string" ? alignViteUrlLine(msg) : msg, options);
748
776
  if (remaining > 0 && isViteLocalUrlLine(String(msg))) {
749
777
  remaining -= 1;
750
778
  if (remaining === 0) flush();
@@ -793,13 +821,18 @@ function matchProxyUserscript(url, fileName) {
793
821
  function matchFileUserscript(url, fileName) {
794
822
  return (url.split("?")[0] ?? "") === `/${fileName}.user.js`;
795
823
  }
796
- function toInstallUrl(origin, fileName, kind = "dev") {
797
- const names = {
824
+ function toInstallFileName(fileName, kind) {
825
+ return {
798
826
  dev: `${fileName}.dev.user.js`,
799
827
  proxy: toProxyFileName(fileName),
800
828
  user: `${fileName}.user.js`
801
- };
802
- return `${origin.replace(/\/$/, "")}/${names[kind]}`;
829
+ }[kind];
830
+ }
831
+ function toInstallPath(fileName, kind = "dev") {
832
+ return `/${toInstallFileName(fileName, kind)}`;
833
+ }
834
+ function toInstallUrl(origin, fileName, kind = "dev") {
835
+ return `${origin.replace(/\/$/, "")}${toInstallPath(fileName, kind)}`;
803
836
  }
804
837
  function toServeEntryPath(root, entry) {
805
838
  const absolute = resolve(root, entry);
@@ -959,14 +992,6 @@ function configureDevServer(server, resolved, reactPreamble) {
959
992
  logger.flush();
960
993
  }
961
994
  };
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
995
  }
971
996
  //#endregion
972
997
  //#region src/plugin.ts
@@ -1053,13 +1078,18 @@ function UserscriptPlugin(config) {
1053
1078
  userConfig.build.rolldownOptions.input = input;
1054
1079
  const userOutput = userConfig.build.rolldownOptions.output;
1055
1080
  const userEntryFileNames = userOutput && !Array.isArray(userOutput) ? userOutput.entryFileNames : void 0;
1081
+ const openScript = resolved.scripts.find((script) => script.server.open);
1082
+ const openPath = openScript?.server.open ? toInstallPath(openScript.fileName, openScript.server.open) : void 0;
1056
1083
  return {
1057
1084
  appType: userConfig.appType ?? (hasHtml ? "spa" : "custom"),
1058
1085
  optimizeDeps: {
1059
1086
  entries: Object.values(input),
1060
1087
  exclude: [VIRTUAL_MODULE_ID]
1061
1088
  },
1062
- server: { cors: userConfig.server?.cors ?? true },
1089
+ server: {
1090
+ cors: userConfig.server?.cors ?? true,
1091
+ ...openPath ? { open: openPath } : {}
1092
+ },
1063
1093
  build: {
1064
1094
  minify: userConfig.build?.minify ?? false,
1065
1095
  assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? Number.MAX_SAFE_INTEGER,
@@ -1082,7 +1112,7 @@ function UserscriptPlugin(config) {
1082
1112
  command = viteConfig.command;
1083
1113
  resolved = absolutizeEntries(resolved, viteConfig.root);
1084
1114
  reactPreamble = hasReactRefreshPlugin(viteConfig.plugins);
1085
- for (const message of collectConfigWarnings(resolved)) viteConfig.logger.warn(message);
1115
+ for (const message of collectConfigWarnings(resolved, config)) viteConfig.logger.warn(message);
1086
1116
  }
1087
1117
  },
1088
1118
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-userscript-plugin",
3
3
  "type": "module",
4
- "version": "2.2.0",
4
+ "version": "2.3.0",
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.56.10",
54
+ "svelte": "5.57.0",
58
55
  "tsdown": "0.22.14",
59
- "turbo": "2.10.11",
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.41"
60
+ "vue": "3.5.42"
64
61
  },
65
62
  "scripts": {
66
63
  "dev": "tsdown --watch",