wxt 0.14.5 → 0.14.7

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/cli.js CHANGED
@@ -5,7 +5,7 @@ import "./chunk-73I7FAJU.js";
5
5
  import cac from "cac";
6
6
 
7
7
  // package.json
8
- var version = "0.14.5";
8
+ var version = "0.14.7";
9
9
 
10
10
  // src/core/utils/fs.ts
11
11
  import fs from "fs-extra";
@@ -45,9 +45,14 @@ async function buildEntrypoints(groups, config, spinner) {
45
45
  const steps = [];
46
46
  for (let i = 0; i < groups.length; i++) {
47
47
  const group = groups[i];
48
- const groupNames = [group].flat().map((e) => e.name).join(pc.dim(", "));
49
- spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNames}`;
50
- steps.push(await config.builder.build(group));
48
+ const groupNames = [group].flat().map((e) => e.name);
49
+ const groupNameColored = groupNames.join(pc.dim(", "));
50
+ spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
51
+ try {
52
+ steps.push(await config.builder.build(group));
53
+ } catch (err) {
54
+ throw Error(`Failed to build ${groupNames.join(", ")}`, { cause: err });
55
+ }
51
56
  }
52
57
  const publicAssets = await copyPublicDirectory(config);
53
58
  return { publicAssets, steps };
@@ -77,11 +82,27 @@ function every(array, predicate) {
77
82
  return false;
78
83
  return true;
79
84
  }
85
+ function some(array, predicate) {
86
+ for (let i = 0; i < array.length; i++)
87
+ if (predicate(array[i], i))
88
+ return true;
89
+ return false;
90
+ }
80
91
 
81
92
  // src/core/utils/building/detect-dev-changes.ts
82
- function detectDevChanges(changedFiles, currentOutput) {
83
- if (currentOutput == null)
84
- return { type: "no-change" };
93
+ function detectDevChanges(config, changedFiles, currentOutput) {
94
+ const isConfigChange = some(
95
+ changedFiles,
96
+ (file) => file === config.userConfigMetadata.configFile
97
+ );
98
+ if (isConfigChange)
99
+ return { type: "full-restart" };
100
+ const isRunnerChange = some(
101
+ changedFiles,
102
+ (file) => file === config.runnerConfig.configFile
103
+ );
104
+ if (isRunnerChange)
105
+ return { type: "browser-restart" };
85
106
  const changedSteps = new Set(
86
107
  changedFiles.flatMap(
87
108
  (changedFile) => findEffectedSteps(changedFile, currentOutput)
@@ -113,7 +134,7 @@ function detectDevChanges(changedFiles, currentOutput) {
113
134
  unchangedOutput.publicAssets.push(asset);
114
135
  }
115
136
  }
116
- const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
137
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"));
117
138
  if (isOnlyHtmlChanges) {
118
139
  return {
119
140
  type: "html-reload",
@@ -141,7 +162,7 @@ function detectDevChanges(changedFiles, currentOutput) {
141
162
  }
142
163
  function findEffectedSteps(changedFile, currentOutput) {
143
164
  const changes = [];
144
- const changedPath = normalizePath(changedFile[1]);
165
+ const changedPath = normalizePath(changedFile);
145
166
  const isChunkEffected = (chunk) => (
146
167
  // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
147
168
  // fileName is normalized, relative bundle path
@@ -1489,6 +1510,9 @@ async function createViteBuilder(inlineConfig, userConfig, wxtConfig) {
1489
1510
  async listen() {
1490
1511
  await viteServer.listen(info.port);
1491
1512
  },
1513
+ async close() {
1514
+ await viteServer.close();
1515
+ },
1492
1516
  transformHtml(...args) {
1493
1517
  return viteServer.transformIndexHtml(...args);
1494
1518
  },
@@ -1823,15 +1847,15 @@ async function importEntrypointFile(path7, config) {
1823
1847
  const res = await jiti(path7);
1824
1848
  return res.default;
1825
1849
  } catch (err) {
1850
+ const filePath = relative5(config.root, path7);
1826
1851
  if (err instanceof ReferenceError) {
1827
1852
  const variableName = err.message.replace(" is not defined", "");
1828
- const filePath = relative5(config.root, path7);
1829
1853
  throw Error(
1830
1854
  `${filePath}: Cannot use imported variable "${variableName}" outside the main function. See https://wxt.dev/guide/entrypoints.html#side-effects`,
1831
1855
  { cause: err }
1832
1856
  );
1833
1857
  } else {
1834
- throw err;
1858
+ throw Error(`Failed to load entrypoint: ${filePath}`, { cause: err });
1835
1859
  }
1836
1860
  }
1837
1861
  }
@@ -2539,6 +2563,70 @@ async function rebuild(config, allEntrypoints, entrypointGroups, existingOutput
2539
2563
  }
2540
2564
 
2541
2565
  // src/core/utils/building/internal-build.ts
2566
+ import managePath from "manage-path";
2567
+ import { resolve as resolve13, relative as relative6 } from "node:path";
2568
+
2569
+ // src/core/utils/validation.ts
2570
+ function validateEntrypoints(entrypoints) {
2571
+ const errors = entrypoints.flatMap((entrypoint) => {
2572
+ switch (entrypoint.type) {
2573
+ case "content-script":
2574
+ return validateContentScriptEntrypoint(entrypoint);
2575
+ default:
2576
+ return validateBaseEntrypoint(entrypoint);
2577
+ }
2578
+ });
2579
+ let errorCount = 0;
2580
+ let warningCount = 0;
2581
+ for (const err of errors) {
2582
+ if (err.type === "warning")
2583
+ warningCount++;
2584
+ else
2585
+ errorCount++;
2586
+ }
2587
+ return {
2588
+ errors,
2589
+ errorCount,
2590
+ warningCount
2591
+ };
2592
+ }
2593
+ function validateContentScriptEntrypoint(definition) {
2594
+ const errors = validateBaseEntrypoint(definition);
2595
+ if (definition.options.matches == null) {
2596
+ errors.push({
2597
+ type: "error",
2598
+ message: "`matches` is required",
2599
+ value: definition.options.matches,
2600
+ entrypoint: definition
2601
+ });
2602
+ }
2603
+ return errors;
2604
+ }
2605
+ function validateBaseEntrypoint(definition) {
2606
+ const errors = [];
2607
+ if (definition.options.exclude != null && !Array.isArray(definition.options.exclude)) {
2608
+ errors.push({
2609
+ type: "error",
2610
+ message: "`exclude` must be an array of browser names",
2611
+ value: definition.options.exclude,
2612
+ entrypoint: definition
2613
+ });
2614
+ }
2615
+ if (definition.options.include != null && !Array.isArray(definition.options.include)) {
2616
+ errors.push({
2617
+ type: "error",
2618
+ message: "`include` must be an array of browser names",
2619
+ value: definition.options.include,
2620
+ entrypoint: definition
2621
+ });
2622
+ }
2623
+ return errors;
2624
+ }
2625
+ var ValidationError = class extends Error {
2626
+ };
2627
+
2628
+ // src/core/utils/building/internal-build.ts
2629
+ import consola3 from "consola";
2542
2630
  async function internalBuild(config) {
2543
2631
  const verb = config.command === "serve" ? "Pre-rendering" : "Building";
2544
2632
  const target = `${config.browser}-mv${config.manifestVersion}`;
@@ -2552,6 +2640,15 @@ async function internalBuild(config) {
2552
2640
  await fs12.ensureDir(config.outDir);
2553
2641
  const entrypoints = await findEntrypoints(config);
2554
2642
  config.logger.debug("Detected entrypoints:", entrypoints);
2643
+ const validationResults = validateEntrypoints(entrypoints);
2644
+ if (validationResults.errorCount + validationResults.warningCount > 0) {
2645
+ printValidationResults(config, validationResults);
2646
+ }
2647
+ if (validationResults.errorCount > 0) {
2648
+ throw new ValidationError(`Entrypoint validation failed`, {
2649
+ cause: validationResults
2650
+ });
2651
+ }
2555
2652
  const groups = groupEntrypoints(entrypoints);
2556
2653
  const { output, warnings } = await rebuild(
2557
2654
  config,
@@ -2584,11 +2681,35 @@ async function combineAnalysisStats(config) {
2584
2681
  absolute: true
2585
2682
  });
2586
2683
  const absolutePaths = unixFiles.map(unnormalizePath);
2684
+ const alterPath = managePath(process.env);
2685
+ alterPath.push(resolve13(config.root, "node_modules/wxt/node_modules/.bin"));
2587
2686
  await execaCommand(
2588
2687
  `rollup-plugin-visualizer ${absolutePaths.join(" ")} --template ${config.analysis.template}`,
2589
2688
  { cwd: config.root, stdio: "inherit" }
2590
2689
  );
2591
2690
  }
2691
+ function printValidationResults(config, { errorCount, errors, warningCount }) {
2692
+ (errorCount > 0 ? config.logger.error : config.logger.warn)(
2693
+ `Entrypoint validation failed: ${errorCount} error${errorCount === 1 ? "" : "s"}, ${warningCount} warning${warningCount === 1 ? "" : "s"}`
2694
+ );
2695
+ const cwd = process.cwd();
2696
+ const entrypointErrors = errors.reduce((map, error) => {
2697
+ const entryErrors = map.get(error.entrypoint) ?? [];
2698
+ entryErrors.push(error);
2699
+ map.set(error.entrypoint, entryErrors);
2700
+ return map;
2701
+ }, /* @__PURE__ */ new Map());
2702
+ Array.from(entrypointErrors.entries()).forEach(([entrypoint, errors2]) => {
2703
+ consola3.log(relative6(cwd, entrypoint.inputPath));
2704
+ console.log();
2705
+ errors2.forEach((err) => {
2706
+ const type = err.type === "error" ? pc5.red("ERROR") : pc5.yellow("WARN");
2707
+ const recieved = pc5.dim(`(recieved: ${JSON.stringify(err.value)})`);
2708
+ consola3.log(` - ${type} ${err.message} ${recieved}`);
2709
+ });
2710
+ console.log();
2711
+ });
2712
+ }
2592
2713
 
2593
2714
  // src/core/build.ts
2594
2715
  async function build(config) {
@@ -2600,17 +2721,17 @@ async function build(config) {
2600
2721
  import path5 from "node:path";
2601
2722
  import glob4 from "fast-glob";
2602
2723
  import fs13 from "fs-extra";
2603
- import { consola as consola3 } from "consola";
2724
+ import { consola as consola4 } from "consola";
2604
2725
  import pc6 from "picocolors";
2605
2726
  async function clean(root = process.cwd()) {
2606
- consola3.info("Cleaning Project");
2727
+ consola4.info("Cleaning Project");
2607
2728
  const tempDirs = [
2608
2729
  "node_modules/.vite",
2609
2730
  "node_modules/.cache",
2610
2731
  "**/.wxt",
2611
2732
  ".output/*"
2612
2733
  ];
2613
- consola3.debug("Looking for:", tempDirs.map(pc6.cyan).join(", "));
2734
+ consola4.debug("Looking for:", tempDirs.map(pc6.cyan).join(", "));
2614
2735
  const directories = await glob4(tempDirs, {
2615
2736
  cwd: path5.resolve(root),
2616
2737
  absolute: true,
@@ -2618,26 +2739,26 @@ async function clean(root = process.cwd()) {
2618
2739
  deep: 2
2619
2740
  });
2620
2741
  if (directories.length === 0) {
2621
- consola3.debug("No generated files found.");
2742
+ consola4.debug("No generated files found.");
2622
2743
  return;
2623
2744
  }
2624
- consola3.debug(
2745
+ consola4.debug(
2625
2746
  "Found:",
2626
2747
  directories.map((dir) => pc6.cyan(path5.relative(root, dir))).join(", ")
2627
2748
  );
2628
2749
  for (const directory of directories) {
2629
2750
  await fs13.rm(directory, { force: true, recursive: true });
2630
- consola3.debug("Deleted " + pc6.cyan(path5.relative(root, directory)));
2751
+ consola4.debug("Deleted " + pc6.cyan(path5.relative(root, directory)));
2631
2752
  }
2632
2753
  }
2633
2754
 
2634
2755
  // src/core/runners/wsl.ts
2635
- import { relative as relative6 } from "node:path";
2756
+ import { relative as relative7 } from "node:path";
2636
2757
  function createWslRunner() {
2637
2758
  return {
2638
2759
  async openBrowser(config) {
2639
2760
  config.logger.warn(
2640
- `Cannot open browser when using WSL. Load "${relative6(
2761
+ `Cannot open browser when using WSL. Load "${relative7(
2641
2762
  process.cwd(),
2642
2763
  config.outDir
2643
2764
  )}" as an unpacked extension manually`
@@ -2653,7 +2774,7 @@ function createWebExtRunner() {
2653
2774
  let runner;
2654
2775
  return {
2655
2776
  async openBrowser(config) {
2656
- config.logger.info("Opening browser...");
2777
+ const startTime = Date.now();
2657
2778
  if (config.browser === "firefox" && config.manifestVersion === 3) {
2658
2779
  throw Error(
2659
2780
  "Dev mode does not support Firefox MV3. For alternatives, see https://github.com/wxt-dev/wxt/issues/230#issuecomment-1806881653"
@@ -2698,7 +2819,8 @@ function createWebExtRunner() {
2698
2819
  config.logger.debug("web-ext options:", options);
2699
2820
  const webExt = await import("web-ext-run");
2700
2821
  runner = await webExt.default.cmd.run(finalConfig, options);
2701
- config.logger.success("Opened!");
2822
+ const duration = Date.now() - startTime;
2823
+ config.logger.success(`Opened browser in ${formatDuration(duration)}`);
2702
2824
  },
2703
2825
  async closeBrowser() {
2704
2826
  return await runner?.exit();
@@ -2709,12 +2831,12 @@ var WARN_LOG_LEVEL = 40;
2709
2831
  var ERROR_LOG_LEVEL = 50;
2710
2832
 
2711
2833
  // src/core/runners/safari.ts
2712
- import { relative as relative7 } from "node:path";
2834
+ import { relative as relative8 } from "node:path";
2713
2835
  function createSafariRunner() {
2714
2836
  return {
2715
2837
  async openBrowser(config) {
2716
2838
  config.logger.warn(
2717
- `Cannot Safari using web-ext. Load "${relative7(
2839
+ `Cannot Safari using web-ext. Load "${relative8(
2718
2840
  process.cwd(),
2719
2841
  config.outDir
2720
2842
  )}" as an unpacked extension manually`
@@ -2726,12 +2848,12 @@ function createSafariRunner() {
2726
2848
  }
2727
2849
 
2728
2850
  // src/core/runners/manual.ts
2729
- import { relative as relative8 } from "node:path";
2851
+ import { relative as relative9 } from "node:path";
2730
2852
  function createManualRunner() {
2731
2853
  return {
2732
2854
  async openBrowser(config) {
2733
2855
  config.logger.info(
2734
- `Load "${relative8(
2856
+ `Load "${relative9(
2735
2857
  process.cwd(),
2736
2858
  config.outDir
2737
2859
  )}" as an unpacked extension manually`
@@ -2760,10 +2882,10 @@ async function createExtensionRunner(config) {
2760
2882
  }
2761
2883
 
2762
2884
  // src/core/create-server.ts
2763
- import { consola as consola4 } from "consola";
2885
+ import { consola as consola5 } from "consola";
2764
2886
  import { Mutex } from "async-mutex";
2765
2887
  import pc7 from "picocolors";
2766
- import { relative as relative9 } from "node:path";
2888
+ import { relative as relative10 } from "node:path";
2767
2889
  async function createServer(inlineConfig) {
2768
2890
  const port = await getPort();
2769
2891
  const hostname = "localhost";
@@ -2773,19 +2895,36 @@ async function createServer(inlineConfig) {
2773
2895
  hostname,
2774
2896
  origin
2775
2897
  };
2898
+ const buildAndOpenBrowser = async () => {
2899
+ server.currentOutput = await internalBuild(config);
2900
+ await runner.openBrowser(config);
2901
+ };
2902
+ const closeAndRecreateRunner = async () => {
2903
+ await runner.closeBrowser();
2904
+ config = await getLatestConfig();
2905
+ runner = await createExtensionRunner(config);
2906
+ };
2776
2907
  const server = {
2777
2908
  ...serverInfo,
2778
- watcher: void 0,
2779
- // Filled out later down below
2780
- ws: void 0,
2781
- // Filled out later down below
2909
+ get watcher() {
2910
+ return builderServer.watcher;
2911
+ },
2912
+ get ws() {
2913
+ return builderServer.ws;
2914
+ },
2782
2915
  currentOutput: void 0,
2783
- // Filled out later down below
2784
2916
  async start() {
2785
2917
  await builderServer.listen();
2786
2918
  config.logger.success(`Started dev server @ ${serverInfo.origin}`);
2787
- server.currentOutput = await internalBuild(config);
2788
- await runner.openBrowser(config);
2919
+ await buildAndOpenBrowser();
2920
+ },
2921
+ async stop() {
2922
+ await runner.closeBrowser();
2923
+ await builderServer.close();
2924
+ },
2925
+ async restart() {
2926
+ await closeAndRecreateRunner();
2927
+ await buildAndOpenBrowser();
2789
2928
  },
2790
2929
  transformHtml(url, html, originalUrl) {
2791
2930
  return builderServer.transformHtml(url, html, originalUrl);
@@ -2798,17 +2937,21 @@ async function createServer(inlineConfig) {
2798
2937
  },
2799
2938
  reloadExtension() {
2800
2939
  server.ws.send("wxt:reload-extension");
2940
+ },
2941
+ async restartBrowser() {
2942
+ await closeAndRecreateRunner();
2943
+ await runner.openBrowser(config);
2801
2944
  }
2802
2945
  };
2803
2946
  const getLatestConfig = () => getInternalConfig(inlineConfig ?? {}, "serve", server);
2804
2947
  let config = await getLatestConfig();
2805
- const [runner, builderServer] = await Promise.all([
2948
+ let [runner, builderServer] = await Promise.all([
2806
2949
  createExtensionRunner(config),
2807
2950
  config.builder.createServer(server)
2808
2951
  ]);
2809
- server.watcher = builderServer.watcher;
2810
- server.ws = builderServer.ws;
2811
2952
  server.ws.on("wxt:background-initialized", () => {
2953
+ if (server.currentOutput == null)
2954
+ return;
2812
2955
  reloadContentScripts(server.currentOutput.steps, config, server);
2813
2956
  });
2814
2957
  const reloadOnChange = createFileReloader({
@@ -2836,18 +2979,34 @@ function createFileReloader(options) {
2836
2979
  return;
2837
2980
  changeQueue.push([event, path7]);
2838
2981
  await fileChangedMutex.runExclusive(async () => {
2839
- const fileChanges = changeQueue.splice(0, changeQueue.length);
2982
+ if (server.currentOutput == null)
2983
+ return;
2984
+ const fileChanges = changeQueue.splice(0, changeQueue.length).map(([_, file]) => file);
2840
2985
  if (fileChanges.length === 0)
2841
2986
  return;
2842
- const changes = detectDevChanges(fileChanges, server.currentOutput);
2987
+ const changes = detectDevChanges(
2988
+ config,
2989
+ fileChanges,
2990
+ server.currentOutput
2991
+ );
2843
2992
  if (changes.type === "no-change")
2844
2993
  return;
2994
+ if (changes.type === "full-restart") {
2995
+ config.logger.info("Config changed, restarting server...");
2996
+ server.restart();
2997
+ return;
2998
+ }
2999
+ if (changes.type === "browser-restart") {
3000
+ config.logger.info("Runner config changed, restarting browser...");
3001
+ server.restartBrowser();
3002
+ return;
3003
+ }
2845
3004
  config.logger.info(
2846
- `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => pc7.dim(relative9(config.root, file))).join(", ")}`
3005
+ `Changed: ${Array.from(new Set(fileChanges)).map((file) => pc7.dim(relative10(config.root, file))).join(", ")}`
2847
3006
  );
2848
3007
  const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
2849
3008
  return pc7.cyan(
2850
- relative9(config.outDir, getEntrypointOutputFile(entry, ""))
3009
+ relative10(config.outDir, getEntrypointOutputFile(entry, ""))
2851
3010
  );
2852
3011
  }).join(pc7.dim(", "));
2853
3012
  const allEntrypoints = await findEntrypoints(config);
@@ -2870,13 +3029,15 @@ function createFileReloader(options) {
2870
3029
  reloadContentScripts(changes.changedSteps, config, server);
2871
3030
  break;
2872
3031
  }
2873
- consola4.success(`Reloaded: ${rebuiltNames}`);
3032
+ consola5.success(`Reloaded: ${rebuiltNames}`);
2874
3033
  });
2875
3034
  };
2876
3035
  }
2877
3036
  function reloadContentScripts(steps, config, server) {
2878
3037
  if (config.manifestVersion === 3) {
2879
3038
  steps.forEach((step) => {
3039
+ if (server.currentOutput == null)
3040
+ return;
2880
3041
  const entry = step.entrypoints;
2881
3042
  if (Array.isArray(entry) || entry.type !== "content-script")
2882
3043
  return;
@@ -2913,13 +3074,13 @@ function reloadHtmlPages(groups, server, config) {
2913
3074
 
2914
3075
  // src/core/initialize.ts
2915
3076
  import prompts from "prompts";
2916
- import { consola as consola5 } from "consola";
3077
+ import { consola as consola6 } from "consola";
2917
3078
  import { downloadTemplate } from "giget";
2918
3079
  import fs14 from "fs-extra";
2919
3080
  import path6 from "node:path";
2920
3081
  import pc8 from "picocolors";
2921
3082
  async function initialize(options) {
2922
- consola5.info("Initalizing new project");
3083
+ consola6.info("Initalizing new project");
2923
3084
  const templates = await listTemplates();
2924
3085
  const defaultTemplate = templates.find(
2925
3086
  (template) => template.name === options.template?.toLowerCase().trim()
@@ -2966,15 +3127,15 @@ async function initialize(options) {
2966
3127
  await cloneProject(input);
2967
3128
  const cdPath = path6.relative(process.cwd(), path6.resolve(input.directory));
2968
3129
  console.log();
2969
- consola5.log(
3130
+ consola6.log(
2970
3131
  `\u2728 WXT project created with the ${TEMPLATE_COLORS[input.template.name]?.(input.template.name) ?? input.template.name} template.`
2971
3132
  );
2972
3133
  console.log();
2973
- consola5.log("Next steps:");
3134
+ consola6.log("Next steps:");
2974
3135
  let step = 0;
2975
3136
  if (cdPath !== "")
2976
- consola5.log(` ${++step}.`, pc8.cyan(`cd ${cdPath}`));
2977
- consola5.log(` ${++step}.`, pc8.cyan(`${input.packageManager} install`));
3137
+ consola6.log(` ${++step}.`, pc8.cyan(`cd ${cdPath}`));
3138
+ consola6.log(` ${++step}.`, pc8.cyan(`${input.packageManager} install`));
2978
3139
  console.log();
2979
3140
  }
2980
3141
  async function listTemplates() {
@@ -3019,7 +3180,7 @@ async function cloneProject({
3019
3180
  path6.join(directory, "_gitignore"),
3020
3181
  path6.join(directory, ".gitignore")
3021
3182
  ).catch(
3022
- (err) => consola5.warn("Failed to move _gitignore to .gitignore:", err)
3183
+ (err) => consola6.warn("Failed to move _gitignore to .gitignore:", err)
3023
3184
  );
3024
3185
  spinner.succeed();
3025
3186
  } catch (err) {
@@ -3050,7 +3211,7 @@ async function prepare(config) {
3050
3211
 
3051
3212
  // src/core/zip.ts
3052
3213
  import zipdir from "zip-dir";
3053
- import { dirname as dirname5, relative as relative10, resolve as resolve13 } from "node:path";
3214
+ import { dirname as dirname5, relative as relative11, resolve as resolve14 } from "node:path";
3054
3215
  import fs15 from "fs-extra";
3055
3216
  import { minimatch as minimatch2 } from "minimatch";
3056
3217
  async function zip(config) {
@@ -3068,7 +3229,7 @@ async function zip(config) {
3068
3229
  ).replaceAll("{{manifestVersion}}", `mv${internalConfig.manifestVersion}`);
3069
3230
  await fs15.ensureDir(internalConfig.outBaseDir);
3070
3231
  const outZipFilename = applyTemplate(internalConfig.zip.artifactTemplate);
3071
- const outZipPath = resolve13(internalConfig.outBaseDir, outZipFilename);
3232
+ const outZipPath = resolve14(internalConfig.outBaseDir, outZipFilename);
3072
3233
  await zipdir(internalConfig.outDir, {
3073
3234
  saveTo: outZipPath
3074
3235
  });
@@ -3077,14 +3238,14 @@ async function zip(config) {
3077
3238
  const sourcesZipFilename = applyTemplate(
3078
3239
  internalConfig.zip.sourcesTemplate
3079
3240
  );
3080
- const sourcesZipPath = resolve13(
3241
+ const sourcesZipPath = resolve14(
3081
3242
  internalConfig.outBaseDir,
3082
3243
  sourcesZipFilename
3083
3244
  );
3084
3245
  await zipdir(internalConfig.zip.sourcesRoot, {
3085
3246
  saveTo: sourcesZipPath,
3086
3247
  filter(path7) {
3087
- const relativePath = relative10(internalConfig.zip.sourcesRoot, path7);
3248
+ const relativePath = relative11(internalConfig.zip.sourcesRoot, path7);
3088
3249
  const matchedPattern = internalConfig.zip.ignoredSources.find(
3089
3250
  (pattern) => minimatch2(relativePath, pattern)
3090
3251
  );
@@ -3103,7 +3264,7 @@ async function zip(config) {
3103
3264
  }
3104
3265
 
3105
3266
  // src/cli.ts
3106
- import consola6, { LogLevels as LogLevels2 } from "consola";
3267
+ import consola7, { LogLevels as LogLevels2 } from "consola";
3107
3268
  process.env.VITE_CJS_IGNORE_WARNING = "true";
3108
3269
  var cli = cac("wxt");
3109
3270
  cli.help();
@@ -3193,21 +3354,24 @@ function wrapAction(cb, options) {
3193
3354
  return async (...args) => {
3194
3355
  const isDebug = !!args.find((arg) => arg?.debug);
3195
3356
  if (isDebug) {
3196
- consola6.level = LogLevels2.debug;
3357
+ consola7.level = LogLevels2.debug;
3197
3358
  }
3198
3359
  const startTime = Date.now();
3199
3360
  try {
3200
3361
  printHeader();
3201
3362
  const status = await cb(...args);
3202
3363
  if (!status?.isOngoing && !options?.disableFinishedLog)
3203
- consola6.success(
3364
+ consola7.success(
3204
3365
  `Finished in ${formatDuration(Date.now() - startTime)}`
3205
3366
  );
3206
3367
  } catch (err) {
3207
- consola6.fail(
3368
+ consola7.fail(
3208
3369
  `Command failed after ${formatDuration(Date.now() - startTime)}`
3209
3370
  );
3210
- consola6.error(err);
3371
+ if (err instanceof ValidationError) {
3372
+ } else {
3373
+ consola7.error(err);
3374
+ }
3211
3375
  process.exit(1);
3212
3376
  }
3213
3377
  };
@@ -388,15 +388,23 @@ interface BuildStepOutput {
388
388
  entrypoints: EntrypointGroup;
389
389
  chunks: OutputFile[];
390
390
  }
391
- interface WxtDevServer extends Omit<WxtBuilderServer, 'listen'>, ServerInfo {
391
+ interface WxtDevServer extends Omit<WxtBuilderServer, 'listen' | 'close'>, ServerInfo {
392
392
  /**
393
393
  * Stores the current build output of the server.
394
394
  */
395
- currentOutput: BuildOutput;
395
+ currentOutput: BuildOutput | undefined;
396
396
  /**
397
397
  * Start the server.
398
398
  */
399
399
  start(): Promise<void>;
400
+ /**
401
+ * Stop the server.
402
+ */
403
+ stop(): Promise<void>;
404
+ /**
405
+ * Close the browser, stop the server, rebuild the entire extension, and start the server again.
406
+ */
407
+ restart(): Promise<void>;
400
408
  /**
401
409
  * Transform the HTML for dev mode.
402
410
  */
@@ -423,6 +431,10 @@ interface WxtDevServer extends Omit<WxtBuilderServer, 'listen'>, ServerInfo {
423
431
  * @param contentScript The manifest definition for a content script
424
432
  */
425
433
  reloadContentScript: (contentScript: Omit<Scripting.RegisteredContentScript, 'id'>) => void;
434
+ /**
435
+ * Grab the latest runner config and restart the browser.
436
+ */
437
+ restartBrowser: () => void;
426
438
  }
427
439
  type TargetBrowser = string;
428
440
  type TargetManifestVersion = 2 | 3;
@@ -704,6 +716,10 @@ interface WxtBuilderServer {
704
716
  * Start the server.
705
717
  */
706
718
  listen(): Promise<void>;
719
+ /**
720
+ * Stop the server.
721
+ */
722
+ close(): Promise<void>;
707
723
  /**
708
724
  * Transform the HTML for dev mode.
709
725
  */
@@ -388,15 +388,23 @@ interface BuildStepOutput {
388
388
  entrypoints: EntrypointGroup;
389
389
  chunks: OutputFile[];
390
390
  }
391
- interface WxtDevServer extends Omit<WxtBuilderServer, 'listen'>, ServerInfo {
391
+ interface WxtDevServer extends Omit<WxtBuilderServer, 'listen' | 'close'>, ServerInfo {
392
392
  /**
393
393
  * Stores the current build output of the server.
394
394
  */
395
- currentOutput: BuildOutput;
395
+ currentOutput: BuildOutput | undefined;
396
396
  /**
397
397
  * Start the server.
398
398
  */
399
399
  start(): Promise<void>;
400
+ /**
401
+ * Stop the server.
402
+ */
403
+ stop(): Promise<void>;
404
+ /**
405
+ * Close the browser, stop the server, rebuild the entire extension, and start the server again.
406
+ */
407
+ restart(): Promise<void>;
400
408
  /**
401
409
  * Transform the HTML for dev mode.
402
410
  */
@@ -423,6 +431,10 @@ interface WxtDevServer extends Omit<WxtBuilderServer, 'listen'>, ServerInfo {
423
431
  * @param contentScript The manifest definition for a content script
424
432
  */
425
433
  reloadContentScript: (contentScript: Omit<Scripting.RegisteredContentScript, 'id'>) => void;
434
+ /**
435
+ * Grab the latest runner config and restart the browser.
436
+ */
437
+ restartBrowser: () => void;
426
438
  }
427
439
  type TargetBrowser = string;
428
440
  type TargetManifestVersion = 2 | 3;
@@ -704,6 +716,10 @@ interface WxtBuilderServer {
704
716
  * Start the server.
705
717
  */
706
718
  listen(): Promise<void>;
719
+ /**
720
+ * Stop the server.
721
+ */
722
+ close(): Promise<void>;
707
723
  /**
708
724
  * Transform the HTML for dev mode.
709
725
  */