@qualflare/cli 0.1.10

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.
Files changed (3) hide show
  1. package/bin/qf.js +18 -0
  2. package/install.js +124 -0
  3. package/package.json +48 -0
package/bin/qf.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Thin launcher: exec the platform-native `qf` binary that install.js placed next to
5
+ // this file, forwarding args, stdio, and the exit code.
6
+
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+
10
+ const bin = path.join(__dirname, process.platform === 'win32' ? 'qf.exe' : 'qf');
11
+ const res = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
12
+
13
+ if (res.error) {
14
+ console.error(`[qualflare] could not run qf: ${res.error.message}`);
15
+ console.error('[qualflare] try reinstalling: npm install -g @qualflare/cli');
16
+ process.exit(1);
17
+ }
18
+ process.exit(res.status === null ? 1 : res.status);
package/install.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Postinstall: download the platform-native `qf` binary from the matching GitHub
5
+ // release, verify its sha256 against the release checksums.txt, and extract it into
6
+ // ./bin. Dependency-free (uses Node core + the system `tar`).
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+ const os = require('node:os');
11
+ const https = require('node:https');
12
+ const crypto = require('node:crypto');
13
+ const { spawnSync } = require('node:child_process');
14
+
15
+ const REPO = 'Qualflare/qualflare-cli';
16
+ const pkg = require('./package.json');
17
+ const version = pkg.version;
18
+
19
+ if (process.env.QUALFLARE_CLI_SKIP_INSTALL === '1') {
20
+ console.log('[qualflare] QUALFLARE_CLI_SKIP_INSTALL=1 — skipping binary download.');
21
+ process.exit(0);
22
+ }
23
+
24
+ const PLATFORMS = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
25
+ const ARCHES = { x64: 'amd64', arm64: 'arm64' };
26
+ const goOS = PLATFORMS[process.platform];
27
+ const goArch = ARCHES[process.arch];
28
+ if (!goOS || !goArch) {
29
+ console.error(
30
+ `[qualflare] Unsupported platform: ${process.platform}/${process.arch}. ` +
31
+ `Install manually from https://github.com/${REPO}/releases`,
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ const isWin = goOS === 'windows';
37
+ const ext = isWin ? 'zip' : 'tar.gz';
38
+ const binName = isWin ? 'qf.exe' : 'qf';
39
+ // Must match the goreleaser archives name_template: qf_<Version>_<Os>_<Arch>.
40
+ const archive = `qf_${version}_${goOS}_${goArch}.${ext}`;
41
+ const base =
42
+ process.env.QUALFLARE_CLI_BASE_URL ||
43
+ `https://github.com/${REPO}/releases/download/v${version}`;
44
+
45
+ const binDir = path.join(__dirname, 'bin');
46
+ const binPath = path.join(binDir, binName);
47
+
48
+ // Idempotent — a re-run (or a repaired install) shouldn't re-download.
49
+ if (fs.existsSync(binPath)) process.exit(0);
50
+ fs.mkdirSync(binDir, { recursive: true });
51
+
52
+ // Resolve `tar` to a fixed absolute path rather than searching $PATH (a writable
53
+ // $PATH entry could shadow the real binary). bsdtar (macOS, Windows 10+) reads both
54
+ // tar.gz and zip; GNU tar (Linux) reads tar.gz — so one `tar -xf` covers every OS.
55
+ function resolveTar() {
56
+ const candidates = isWin
57
+ ? ['C:\\Windows\\System32\\tar.exe']
58
+ : ['/usr/bin/tar', '/bin/tar'];
59
+ const found = candidates.find((p) => fs.existsSync(p));
60
+ if (!found) throw new Error(`no usable tar found (looked in: ${candidates.join(', ')})`);
61
+ return found;
62
+ }
63
+
64
+ function get(url) {
65
+ return new Promise((resolve, reject) => {
66
+ https
67
+ .get(url, { headers: { 'User-Agent': '@qualflare/cli' } }, (res) => {
68
+ // Follow the release-asset redirect to the CDN.
69
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
70
+ res.resume();
71
+ resolve(get(res.headers.location));
72
+ return;
73
+ }
74
+ if (res.statusCode !== 200) {
75
+ res.resume();
76
+ reject(new Error(`GET ${url} -> HTTP ${res.statusCode}`));
77
+ return;
78
+ }
79
+ const chunks = [];
80
+ res.on('data', (c) => chunks.push(c));
81
+ res.on('end', () => resolve(Buffer.concat(chunks)));
82
+ res.on('error', reject);
83
+ })
84
+ .on('error', reject);
85
+ });
86
+ }
87
+
88
+ (async () => {
89
+ try {
90
+ const [data, sumsBuf] = await Promise.all([
91
+ get(`${base}/${archive}`),
92
+ get(`${base}/checksums.txt`),
93
+ ]);
94
+
95
+ // checksums.txt lines look like: "<sha256> qf_<v>_<os>_<arch>.<ext>"
96
+ const line = sumsBuf
97
+ .toString('utf8')
98
+ .split('\n')
99
+ .find((l) => l.trim().endsWith(archive));
100
+ const expected = line?.trim().split(/\s+/)[0];
101
+ if (!expected) throw new Error(`no checksum entry for ${archive}`);
102
+ const actual = crypto.createHash('sha256').update(data).digest('hex');
103
+ if (expected !== actual) {
104
+ throw new Error(`checksum mismatch for ${archive} (expected ${expected}, got ${actual})`);
105
+ }
106
+
107
+ // Extract just the binary into ./bin. `tar` preserves the archived mode (0755),
108
+ // so the extracted qf is already executable — no chmod needed.
109
+ const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qualflare-cli-'));
110
+ const tmp = path.join(workDir, archive);
111
+ fs.writeFileSync(tmp, data);
112
+ const r = spawnSync(resolveTar(), ['-xf', tmp, '-C', binDir, binName], { stdio: 'inherit' });
113
+ fs.rmSync(workDir, { recursive: true, force: true });
114
+ if (r.status !== 0) throw new Error(`extract failed (tar exit ${r.status})`);
115
+
116
+ console.log(`[qualflare] installed qf v${version} (${goOS}/${goArch}).`);
117
+ } catch (err) {
118
+ console.error(`[qualflare] failed to install the qf binary: ${err.message}`);
119
+ console.error(
120
+ `[qualflare] install manually from https://github.com/${REPO}/releases/tag/v${version}`,
121
+ );
122
+ process.exit(1);
123
+ }
124
+ })();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@qualflare/cli",
3
+ "version": "0.1.10",
4
+ "description": "Qualflare CLI (qf) — upload test results to Qualflare from any CI or local machine. Installs the platform-native binary from the matching GitHub release.",
5
+ "keywords": [
6
+ "qualflare",
7
+ "test",
8
+ "testing",
9
+ "ci",
10
+ "test-results",
11
+ "flaky-tests",
12
+ "test-reporting",
13
+ "cli"
14
+ ],
15
+ "homepage": "https://qualflare.com",
16
+ "bugs": {
17
+ "url": "https://github.com/Qualflare/qualflare-cli/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/Qualflare/qualflare-cli.git",
22
+ "directory": "npm"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "author": "Qualflare",
26
+ "bin": {
27
+ "qf": "bin/qf.js"
28
+ },
29
+ "files": [
30
+ "bin/qf.js",
31
+ "install.js"
32
+ ],
33
+ "scripts": {
34
+ "postinstall": "node install.js"
35
+ },
36
+ "engines": {
37
+ "node": ">=16"
38
+ },
39
+ "os": [
40
+ "darwin",
41
+ "linux",
42
+ "win32"
43
+ ],
44
+ "cpu": [
45
+ "x64",
46
+ "arm64"
47
+ ]
48
+ }