selenium-pw-migrator 0.0.0-preview.8

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 ADDED
@@ -0,0 +1,47 @@
1
+ # selenium-pw-migrator npm wrapper
2
+
3
+ This package is a thin npm wrapper around the standalone Selenium Playwright Migrator CLI. It does **not** require the .NET SDK or .NET Runtime on the target machine.
4
+
5
+ During `npm install`, the package downloads the matching standalone release archive for the current OS/architecture and stores the native CLI under `native/<runtime>/` inside the installed package.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g selenium-pw-migrator
11
+ selenium-pw-migrator --version
12
+ ```
13
+
14
+
15
+ Install directly from a GitHub Release asset before the package is published to npm:
16
+
17
+ ```bash
18
+ npm install -g https://github.com/AlexanderLevenskikh/selenium-playwright-ast-migrator/releases/download/v0.0.0-preview.5/selenium-pw-migrator-0.0.0-preview.5.tgz
19
+ selenium-pw-migrator --version
20
+ ```
21
+
22
+ Supported runtimes:
23
+
24
+ - `win-x64`
25
+ - `linux-x64`
26
+ - `osx-x64`
27
+ - `osx-arm64`
28
+
29
+ ## Configuration
30
+
31
+ The installer supports these environment variables:
32
+
33
+ | Variable | Purpose |
34
+ |---|---|
35
+ | `SELENIUM_PW_MIGRATOR_VERSION` | Override the release version to download. Defaults to the npm package version. |
36
+ | `SELENIUM_PW_MIGRATOR_BASE_URL` | Override the GitHub/Nexus/static release asset directory. |
37
+ | `SELENIUM_PW_MIGRATOR_RUNTIME` | Override runtime detection, for example `win-x64`. |
38
+ | `SELENIUM_PW_MIGRATOR_ARCHIVE_PATH` | Use a local standalone archive instead of downloading. Useful for smoke tests. |
39
+ | `SELENIUM_PW_MIGRATOR_CHECKSUMS_PATH` | Verify a local archive using a local `checksums.sha256`. |
40
+ | `SELENIUM_PW_MIGRATOR_SKIP_DOWNLOAD` | Skip native download during install. |
41
+
42
+ Example internal mirror install:
43
+
44
+ ```bash
45
+ SELENIUM_PW_MIGRATOR_BASE_URL=https://nexus.example/repository/migrator/releases/v0.0.0-preview.5 \
46
+ npm install -g selenium-pw-migrator@0.0.0-preview.5
47
+ ```
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const childProcess = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const packageRoot = path.resolve(__dirname, '..');
9
+ const SUPPORTED_RUNTIMES = new Set(['win-x64', 'linux-x64', 'osx-x64', 'osx-arm64']);
10
+ const runtime = validateRuntime(process.env.SELENIUM_PW_MIGRATOR_RUNTIME || resolveRuntime());
11
+ const exeName = runtime.startsWith('win-') ? 'selenium-pw-migrator.exe' : 'selenium-pw-migrator';
12
+ const explicitInstallDir = process.env.SELENIUM_PW_MIGRATOR_INSTALL_DIR || '';
13
+ const binaryPath = explicitInstallDir
14
+ ? path.join(explicitInstallDir, exeName)
15
+ : path.join(packageRoot, 'native', runtime, exeName);
16
+
17
+ if (!fs.existsSync(binaryPath)) {
18
+ const installer = path.join(packageRoot, 'scripts', 'install.js');
19
+ const installResult = childProcess.spawnSync(process.execPath, [installer], { stdio: 'inherit' });
20
+ if (installResult.error) throw installResult.error;
21
+ if (installResult.status !== 0) process.exit(installResult.status || 1);
22
+ }
23
+
24
+ const result = childProcess.spawnSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
25
+ if (result.error) {
26
+ console.error(`[selenium-pw-migrator] Failed to start native CLI: ${result.error.message}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ if (typeof result.status === 'number') {
31
+ process.exit(result.status);
32
+ }
33
+
34
+ process.exit(result.signal ? 1 : 0);
35
+
36
+ function validateRuntime(value) {
37
+ if (!SUPPORTED_RUNTIMES.has(value)) {
38
+ throw new Error(`Unsupported runtime: ${value}. Supported runtimes: ${Array.from(SUPPORTED_RUNTIMES).join(', ')}`);
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function resolveRuntime() {
44
+ const platform = process.platform;
45
+ const arch = process.arch;
46
+
47
+ let osPart;
48
+ if (platform === 'win32') osPart = 'win';
49
+ else if (platform === 'linux') osPart = 'linux';
50
+ else if (platform === 'darwin') osPart = 'osx';
51
+ else throw new Error(`Unsupported platform: ${platform}`);
52
+
53
+ let archPart;
54
+ if (arch === 'x64') archPart = 'x64';
55
+ else if (arch === 'arm64') archPart = 'arm64';
56
+ else throw new Error(`Unsupported architecture: ${arch}`);
57
+
58
+ return `${osPart}-${archPart}`;
59
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "selenium-pw-migrator",
3
+ "version": "0.0.0-preview.8",
4
+ "description": "Node/npm wrapper for the Selenium Playwright Migrator standalone CLI.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/AlexanderLevenskikh/selenium-playwright-ast-migrator#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/AlexanderLevenskikh/selenium-playwright-ast-migrator.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/AlexanderLevenskikh/selenium-playwright-ast-migrator/issues"
13
+ },
14
+ "bin": {
15
+ "selenium-pw-migrator": "bin/selenium-pw-migrator.js"
16
+ },
17
+ "scripts": {
18
+ "postinstall": "node scripts/install.js",
19
+ "install-native": "node scripts/install.js",
20
+ "smoke": "node bin/selenium-pw-migrator.js --version"
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "scripts/",
25
+ "README.md",
26
+ "package.json"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ }
31
+ }
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const childProcess = require('child_process');
5
+ const crypto = require('crypto');
6
+ const fs = require('fs');
7
+ const http = require('http');
8
+ const https = require('https');
9
+ const os = require('os');
10
+ const path = require('path');
11
+
12
+ const packageRoot = path.resolve(__dirname, '..');
13
+ const packageJson = require(path.join(packageRoot, 'package.json'));
14
+ const SUPPORTED_RUNTIMES = new Set(['win-x64', 'linux-x64', 'osx-x64', 'osx-arm64']);
15
+
16
+ const env = process.env;
17
+ const skipDownload = env.SELENIUM_PW_MIGRATOR_SKIP_DOWNLOAD === '1' || env.SELENIUM_PW_MIGRATOR_SKIP_DOWNLOAD === 'true';
18
+ const version = env.SELENIUM_PW_MIGRATOR_VERSION || packageJson.version;
19
+ const runtime = validateRuntime(env.SELENIUM_PW_MIGRATOR_RUNTIME || resolveRuntime());
20
+ const baseUrl = normalizeBaseUrl(env.SELENIUM_PW_MIGRATOR_BASE_URL || `https://github.com/AlexanderLevenskikh/selenium-playwright-ast-migrator/releases/download/v${version}`);
21
+ const archivePathOverride = env.SELENIUM_PW_MIGRATOR_ARCHIVE_PATH || '';
22
+ const checksumsPathOverride = env.SELENIUM_PW_MIGRATOR_CHECKSUMS_PATH || '';
23
+ const installDir = env.SELENIUM_PW_MIGRATOR_INSTALL_DIR || path.join(packageRoot, 'native', runtime);
24
+ const archiveName = resolveArchiveName(version, runtime, archivePathOverride);
25
+
26
+ main().catch(error => {
27
+ console.error(`[selenium-pw-migrator] ${error.message}`);
28
+ process.exit(1);
29
+ });
30
+
31
+ async function main() {
32
+ if (skipDownload) {
33
+ console.log('[selenium-pw-migrator] Native download skipped because SELENIUM_PW_MIGRATOR_SKIP_DOWNLOAD is set.');
34
+ return;
35
+ }
36
+
37
+ fs.rmSync(installDir, { recursive: true, force: true });
38
+ fs.mkdirSync(installDir, { recursive: true });
39
+
40
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'selenium-pw-migrator-npm-'));
41
+ try {
42
+ const archivePath = archivePathOverride
43
+ ? path.resolve(archivePathOverride)
44
+ : path.join(tempDir, archiveName);
45
+
46
+ if (archivePathOverride) {
47
+ if (!fs.existsSync(archivePath)) {
48
+ throw new Error(`SELENIUM_PW_MIGRATOR_ARCHIVE_PATH was not found: ${archivePath}`);
49
+ }
50
+ console.log(`[selenium-pw-migrator] Using local archive: ${archivePath}`);
51
+ } else {
52
+ const archiveUrl = `${baseUrl}/${archiveName}`;
53
+ console.log(`[selenium-pw-migrator] Downloading ${archiveUrl}`);
54
+ await downloadFile(archiveUrl, archivePath);
55
+ }
56
+
57
+ const checksumsPath = checksumsPathOverride
58
+ ? path.resolve(checksumsPathOverride)
59
+ : path.join(tempDir, 'checksums.sha256');
60
+
61
+ if (checksumsPathOverride) {
62
+ verifyChecksum(archivePath, checksumsPath, path.basename(archivePath));
63
+ } else {
64
+ try {
65
+ await downloadFile(`${baseUrl}/checksums.sha256`, checksumsPath);
66
+ verifyChecksum(archivePath, checksumsPath, archiveName);
67
+ } catch (error) {
68
+ console.warn(`[selenium-pw-migrator] Checksum verification skipped: ${error.message}`);
69
+ }
70
+ }
71
+
72
+ const extractDir = path.join(tempDir, 'extract');
73
+ fs.mkdirSync(extractDir, { recursive: true });
74
+ extractArchive(archivePath, extractDir, runtime);
75
+ copyDirectoryContents(extractDir, installDir);
76
+ ensureExecutable(path.join(installDir, executableName(runtime)));
77
+
78
+ console.log(`[selenium-pw-migrator] Installed native CLI to ${installDir}`);
79
+ } finally {
80
+ fs.rmSync(tempDir, { recursive: true, force: true });
81
+ }
82
+ }
83
+
84
+ function resolveRuntime() {
85
+ const platform = process.platform;
86
+ const arch = process.arch;
87
+
88
+ let osPart;
89
+ if (platform === 'win32') osPart = 'win';
90
+ else if (platform === 'linux') osPart = 'linux';
91
+ else if (platform === 'darwin') osPart = 'osx';
92
+ else throw new Error(`Unsupported platform: ${platform}`);
93
+
94
+ let archPart;
95
+ if (arch === 'x64') archPart = 'x64';
96
+ else if (arch === 'arm64') archPart = 'arm64';
97
+ else throw new Error(`Unsupported architecture: ${arch}`);
98
+
99
+ return `${osPart}-${archPart}`;
100
+ }
101
+
102
+ function validateRuntime(value) {
103
+ if (!SUPPORTED_RUNTIMES.has(value)) {
104
+ throw new Error(`Unsupported runtime: ${value}. Supported runtimes: ${Array.from(SUPPORTED_RUNTIMES).join(', ')}`);
105
+ }
106
+ return value;
107
+ }
108
+
109
+ function resolveArchiveName(packageVersion, rid, explicitArchivePath) {
110
+ if (explicitArchivePath) return path.basename(explicitArchivePath);
111
+ const extension = rid.startsWith('win-') ? '.zip' : '.tar.gz';
112
+ return `selenium-pw-migrator-${packageVersion}-${rid}${extension}`;
113
+ }
114
+
115
+ function normalizeBaseUrl(value) {
116
+ return String(value).replace(/\/+$/, '');
117
+ }
118
+
119
+ function executableName(rid) {
120
+ return rid.startsWith('win-') ? 'selenium-pw-migrator.exe' : 'selenium-pw-migrator';
121
+ }
122
+
123
+ function downloadFile(url, destination) {
124
+ return new Promise((resolve, reject) => {
125
+ const client = url.startsWith('https:') ? https : http;
126
+ const request = client.get(url, response => {
127
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
128
+ response.resume();
129
+ downloadFile(new URL(response.headers.location, url).toString(), destination).then(resolve, reject);
130
+ return;
131
+ }
132
+
133
+ if (response.statusCode !== 200) {
134
+ response.resume();
135
+ reject(new Error(`HTTP ${response.statusCode} while downloading ${url}`));
136
+ return;
137
+ }
138
+
139
+ const file = fs.createWriteStream(destination);
140
+ response.pipe(file);
141
+ file.on('finish', () => file.close(resolve));
142
+ file.on('error', reject);
143
+ });
144
+
145
+ request.on('error', reject);
146
+ });
147
+ }
148
+
149
+ function verifyChecksum(archivePath, checksumsPath, expectedArchiveName) {
150
+ if (!fs.existsSync(checksumsPath)) {
151
+ throw new Error(`Checksums file was not found: ${checksumsPath}`);
152
+ }
153
+
154
+ const checksums = fs.readFileSync(checksumsPath, 'utf8').split(/\r?\n/);
155
+ const escaped = escapeRegExp(expectedArchiveName);
156
+ const line = checksums.find(value => new RegExp(`\\s${escaped}$`).test(value.trim()));
157
+ if (!line) {
158
+ throw new Error(`No checksum entry for ${expectedArchiveName} in ${checksumsPath}`);
159
+ }
160
+
161
+ const expected = line.trim().split(/\s+/)[0].toLowerCase();
162
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex').toLowerCase();
163
+ if (expected !== actual) {
164
+ throw new Error(`Checksum mismatch for ${expectedArchiveName}. Expected ${expected}, actual ${actual}.`);
165
+ }
166
+
167
+ console.log('[selenium-pw-migrator] Checksum verified.');
168
+ }
169
+
170
+ function extractArchive(archivePath, destination, rid) {
171
+ if (rid.startsWith('win-')) {
172
+ const command = [
173
+ '$ErrorActionPreference = "Stop";',
174
+ 'Expand-Archive',
175
+ '-LiteralPath', quotePowerShell(archivePath),
176
+ '-DestinationPath', quotePowerShell(destination),
177
+ '-Force'
178
+ ].join(' ');
179
+ run('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command]);
180
+ return;
181
+ }
182
+
183
+ run('tar', ['-xzf', archivePath, '-C', destination]);
184
+ }
185
+
186
+ function run(command, args) {
187
+ const result = childProcess.spawnSync(command, args, { stdio: 'inherit' });
188
+ if (result.error) throw result.error;
189
+ if (result.status !== 0) throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
190
+ }
191
+
192
+ function copyDirectoryContents(source, target) {
193
+ for (const entry of fs.readdirSync(source)) {
194
+ fs.cpSync(path.join(source, entry), path.join(target, entry), { recursive: true, force: true });
195
+ }
196
+ }
197
+
198
+ function ensureExecutable(filePath) {
199
+ if (fs.existsSync(filePath) && process.platform !== 'win32') {
200
+ fs.chmodSync(filePath, 0o755);
201
+ }
202
+ }
203
+
204
+ function escapeRegExp(value) {
205
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
206
+ }
207
+
208
+ function quotePowerShell(value) {
209
+ return `'${String(value).replace(/'/g, "''")}'`;
210
+ }