pwvm 0.1.1 → 1.0.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
@@ -1,5 +1,7 @@
1
1
  # pwvm
2
2
 
3
+ ![CI](https://github.com/eaccmk/pwvm/actions/workflows/ci.yml/badge.svg)
4
+
3
5
  ### **Playwright Version Manager**
4
6
 
5
7
  A simple *Playwright Version Manager* that solves a common pain point:
@@ -8,6 +10,8 @@ A simple *Playwright Version Manager* that solves a common pain point:
8
10
 
9
11
  `pwvm` lets you install, manage, and switch Playwright versions reliably — **one command, predictable behavior**, just like `nvm` for Node.js.
10
12
 
13
+ ![pwvm icon ](./wiki/pwvm_icon_white.png)
14
+
11
15
  ---
12
16
 
13
17
  ## Why pwvm
@@ -49,12 +53,23 @@ playwright --version
49
53
  playwright test
50
54
  ```
51
55
 
56
+ ![pwvm commmands demo](./wiki/switching_playwright_vesions.gif)
57
+
52
58
  Pin versions per project with `.pwvmrc`:
53
59
 
54
60
  ```text
55
61
  1.57.0
56
62
  ```
57
63
 
64
+ ### Deterministic guarantees pwvm already provides
65
+ • Playwright installed by exact version (`playwright@x.y.z`)
66
+ • Versions stored in isolated directories (`~/.pwvm/versions/<version>`)
67
+ • Browser binaries scoped per Playwright version
68
+ • `.pwvmrc` pins version per repo
69
+ • No implicit upgrades (no latest unless explicitly requested)
70
+
71
+ > [!NOTE] pwvm performs no background network activity and only installs software when explicitly requested.
72
+
58
73
  ---
59
74
 
60
75
  ### CI-friendly
@@ -0,0 +1,25 @@
1
+ import { uninstallPlaywrightVersion, } from "../core/uninstall.js";
2
+ import { createLogger, formatError } from "../utils/logger.js";
3
+ export const runUninstallCommand = async (version, deps = {}) => {
4
+ const logger = deps.logger ?? createLogger();
5
+ const uninstall = deps.uninstall ?? uninstallPlaywrightVersion;
6
+ try {
7
+ await uninstall(version, deps.uninstallOptions);
8
+ logger.info(`Uninstalled Playwright ${version}`);
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ let hint;
13
+ if (message.includes("is currently active")) {
14
+ hint = "Run `pwvm use <version>` to switch first.";
15
+ }
16
+ else if (message.includes("is not installed")) {
17
+ hint = "Run `pwvm list` to see installed versions.";
18
+ }
19
+ else if (message.includes("Invalid Playwright version")) {
20
+ hint = "Use a valid semver like 1.2.3.";
21
+ }
22
+ logger.error(formatError(message, hint));
23
+ process.exitCode = 1;
24
+ }
25
+ };
@@ -0,0 +1,27 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import semver from "semver";
4
+ import { getVersionsDir } from "./paths.js";
5
+ import { getGlobalActiveVersion } from "./versions.js";
6
+ export const uninstallPlaywrightVersion = async (version, options = {}) => {
7
+ if (!semver.valid(version)) {
8
+ throw new Error(`Invalid Playwright version "${version}".`);
9
+ }
10
+ const fsImpl = options.fs ?? fs;
11
+ const activeVersion = await getGlobalActiveVersion({
12
+ homeDir: options.homeDir,
13
+ activeVersionFile: options.activeVersionFile,
14
+ fs: fsImpl,
15
+ });
16
+ if (activeVersion === version) {
17
+ throw new Error(`Playwright version "${version}" is currently active.`);
18
+ }
19
+ const resolveVersionsDir = options.getVersionsDir ?? getVersionsDir;
20
+ const versionsDir = options.versionsDir ?? resolveVersionsDir(options.homeDir);
21
+ const targetDir = path.join(versionsDir, version);
22
+ const exists = await fsImpl.pathExists(targetDir);
23
+ if (!exists) {
24
+ throw new Error(`Playwright version "${version}" is not installed.`);
25
+ }
26
+ await fsImpl.remove(targetDir);
27
+ };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import fs from "fs-extra";
4
+ import { createRequire } from "node:module";
4
5
  import path from "node:path";
5
6
  import { runCurrentCommand } from "./commands/current.js";
6
7
  import { runDoctorCommand } from "./commands/doctor.js";
@@ -9,10 +10,13 @@ import { runListCommand } from "./commands/list.js";
9
10
  import { runListRemoteCommand } from "./commands/listRemote.js";
10
11
  import { runPruneCommand } from "./commands/prune.js";
11
12
  import { runSetupCommand } from "./commands/setup.js";
13
+ import { runUninstallCommand } from "./commands/uninstall.js";
12
14
  import { runUseCommand } from "./commands/use.js";
13
15
  import { getPwvmDir, getShimsDir } from "./core/paths.js";
14
16
  import { runPlaywrightShim } from "./core/shim.js";
15
17
  import { createLogger } from "./utils/logger.js";
18
+ const require = createRequire(import.meta.url);
19
+ const { version: pwvmVersion } = require("../package.json");
16
20
  const program = new Command();
17
21
  program.enablePositionalOptions();
18
22
  const logger = createLogger();
@@ -34,8 +38,78 @@ const getShimArgs = () => {
34
38
  };
35
39
  program
36
40
  .name("pwvm")
37
- .description("Playwright Version Manager")
38
- .version("0.0.1");
41
+ .description(`Playwright Version Manager (v${pwvmVersion})`)
42
+ .version(pwvmVersion, "-v, --version");
43
+ program.configureOutput({
44
+ outputError: () => { },
45
+ });
46
+ program.exitOverride((err) => {
47
+ // Commander control-flow exits (help / version)
48
+ if (err.code === "commander.helpDisplayed" ||
49
+ err.code === "commander.versionDisplayed" ||
50
+ err.code === "commander.help" ||
51
+ err.exitCode === 0) {
52
+ process.exitCode = err.exitCode || 0;
53
+ return;
54
+ }
55
+ let message = err.message;
56
+ if (message.startsWith("error: ")) {
57
+ message = message.slice("error: ".length);
58
+ }
59
+ message = message.replace(/'([^']+)'/g, "\"$1\"");
60
+ if (message.length > 0) {
61
+ message = `${message[0].toUpperCase()}${message.slice(1)}`;
62
+ }
63
+ if (!message.endsWith(".")) {
64
+ message = `${message}.`;
65
+ }
66
+ logger.error(message);
67
+ process.exitCode = err.exitCode || 1;
68
+ });
69
+ program.configureHelp({
70
+ formatHelp: (cmd, helper) => {
71
+ const help = Object.getPrototypeOf(helper).formatHelp.call(helper, cmd, helper);
72
+ if (cmd !== program) {
73
+ return help;
74
+ }
75
+ const sections = help.split("\n\n");
76
+ if (sections.length < 2 || !sections[0].startsWith("Usage:")) {
77
+ return help;
78
+ }
79
+ let reordered = [sections[1], sections[0], ...sections.slice(2)].join("\n\n");
80
+ const installLine = " install Install a Playwright version";
81
+ const uninstallLine = " uninstall <version> Uninstall a specific Playwright version";
82
+ if (!reordered.includes(uninstallLine) && reordered.includes(installLine)) {
83
+ reordered = reordered.replace(`${installLine}\n`, `${installLine}\n${uninstallLine}\n`);
84
+ }
85
+ const lines = reordered.split("\n");
86
+ let inCommands = false;
87
+ for (let i = 0; i < lines.length; i += 1) {
88
+ const line = lines[i];
89
+ if (line === "Commands:") {
90
+ inCommands = true;
91
+ continue;
92
+ }
93
+ if (inCommands) {
94
+ if (line.trim().length === 0) {
95
+ inCommands = false;
96
+ continue;
97
+ }
98
+ const match = /^ (.+?)( {2,})(\S.*)$/.exec(line);
99
+ if (match) {
100
+ const prefix = "pwvm ";
101
+ const term = match[1];
102
+ const spacing = match[2];
103
+ const desc = match[3];
104
+ const adjusted = Math.max(2, spacing.length - prefix.length);
105
+ lines[i] = ` ${prefix}${term}${" ".repeat(adjusted)}${desc}`;
106
+ }
107
+ }
108
+ }
109
+ reordered = lines.join("\n");
110
+ return `\n${reordered}`;
111
+ },
112
+ });
39
113
  const shouldShowSetupNotice = async () => {
40
114
  if (setupNoticeShown) {
41
115
  return false;
@@ -99,6 +173,11 @@ program
99
173
  .option("--with-browsers", "Install Playwright browsers (default)")
100
174
  .option("--no-browsers", "Skip Playwright browser install")
101
175
  .action((version, options) => invoke(runInstallCommand, version, options));
176
+ program
177
+ .command("uninstall")
178
+ .description("Uninstall a specific Playwright version")
179
+ .argument("<version>")
180
+ .action((version) => invoke(runUninstallCommand, version));
102
181
  program
103
182
  .command("list")
104
183
  .description("List installed Playwright versions")
@@ -143,7 +222,9 @@ program
143
222
  process.exitCode = 1;
144
223
  }
145
224
  });
146
- void program.parseAsync();
225
+ program.parseAsync().catch(() => {
226
+ // All commander control-flow exits are handled by exitOverride.
227
+ });
147
228
  function getShimErrorDetails(message) {
148
229
  const guidanceMarker = ". Run ";
149
230
  const guidanceIndex = message.indexOf(guidanceMarker);
@@ -1,5 +1,7 @@
1
1
  import https from "node:https";
2
2
  const REGISTRY_URL = "https://registry.npmjs.org";
3
+ // NOTE: This function MUST only be called from user-invoked commands
4
+ // such as `list-remote` or `install latest`. No background usage allowed.
3
5
  export const fetchPackageMetadata = async (packageName) => new Promise((resolve, reject) => {
4
6
  const url = `${REGISTRY_URL}/${encodeURIComponent(packageName)}`;
5
7
  const request = https.get(url, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "pwvm",
4
- "version": "0.1.1",
4
+ "version": "1.0.0",
5
5
  "description": "Playwright Version Manager",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "dist",
14
14
  "README.md",
15
+ "wiki",
15
16
  "LICENSE"
16
17
  ],
17
18
  "scripts": {
@@ -25,8 +26,11 @@
25
26
  "playwright-version-manager",
26
27
  "playwright-cli",
27
28
  "version-manager",
29
+ "version",
30
+ "manager",
28
31
  "testing",
29
- "e2e"
32
+ "ci",
33
+ "cli"
30
34
  ],
31
35
  "engines": {
32
36
  "node": ">=18"
Binary file
Binary file