@mulham28/jirac 0.27.1
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 +19 -0
- package/bin/jirac.js +19 -0
- package/lib/install.js +128 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @mulham28/jirac npm package
|
|
2
|
+
|
|
3
|
+
Install `jirac` from GitHub Releases using npm.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @mulham28/jirac
|
|
7
|
+
jirac --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
This package downloads the matching prebuilt `jirac` binary for your platform during `postinstall`.
|
|
11
|
+
|
|
12
|
+
Supported targets:
|
|
13
|
+
- macOS arm64
|
|
14
|
+
- macOS x64
|
|
15
|
+
- Linux x64
|
|
16
|
+
- Linux arm64
|
|
17
|
+
- Windows x64
|
|
18
|
+
|
|
19
|
+
Source repo: <https://github.com/mulhamna/jira-commands>
|
package/bin/jirac.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require('node:child_process');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const binName = process.platform === 'win32' ? 'jirac.exe' : 'jirac';
|
|
7
|
+
const installedBin = path.join(__dirname, '..', 'lib', 'bin', binName);
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(installedBin)) {
|
|
10
|
+
console.error('jirac binary not installed yet. Re-run: npm rebuild -g jira-commands');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const result = spawnSync(installedBin, process.argv.slice(2), { stdio: 'inherit' });
|
|
15
|
+
if (result.error) {
|
|
16
|
+
console.error(result.error.message);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
process.exit(result.status ?? 0);
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const { pipeline } = require('node:stream/promises');
|
|
7
|
+
const { Readable } = require('node:stream');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
const REPO = 'mulhamna/jira-commands';
|
|
11
|
+
const VERSION = require('../package.json').version;
|
|
12
|
+
const TAG = `v${VERSION}`;
|
|
13
|
+
const BASE_URL = `https://github.com/${REPO}/releases/download/${TAG}`;
|
|
14
|
+
const BIN_DIR = path.join(__dirname, 'bin');
|
|
15
|
+
const TMP_DIR = path.join(os.tmpdir(), `jirac-npm-${process.pid}-${Date.now()}`);
|
|
16
|
+
|
|
17
|
+
function getPlatformAsset() {
|
|
18
|
+
const platform = process.platform;
|
|
19
|
+
const arch = process.arch;
|
|
20
|
+
|
|
21
|
+
if (platform === 'darwin' && arch === 'arm64') return { archive: 'jirac-macos-aarch64.tar.gz', binary: 'jirac' };
|
|
22
|
+
if (platform === 'darwin' && arch === 'x64') return { archive: 'jirac-macos-x86_64.tar.gz', binary: 'jirac' };
|
|
23
|
+
if (platform === 'linux' && arch === 'x64') return { archive: 'jirac-linux-x86_64.tar.gz', binary: 'jirac' };
|
|
24
|
+
if (platform === 'linux' && arch === 'arm64') return { archive: 'jirac-linux-aarch64.tar.gz', binary: 'jirac' };
|
|
25
|
+
if (platform === 'win32' && arch === 'x64') return { archive: 'jirac-windows-x86_64.zip', binary: 'jirac.exe' };
|
|
26
|
+
|
|
27
|
+
throw new Error(`Unsupported platform for jira-commands npm install: ${platform}/${arch}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function fetchToFile(url, outPath) {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
headers: {
|
|
33
|
+
'user-agent': 'jira-commands-npm-installer'
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!response.ok || !response.body) {
|
|
38
|
+
throw new Error(`Download failed: ${url} (${response.status})`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await fs.promises.mkdir(path.dirname(outPath), { recursive: true });
|
|
42
|
+
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(outPath));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function sha256File(filePath) {
|
|
46
|
+
const hash = crypto.createHash('sha256');
|
|
47
|
+
const stream = fs.createReadStream(filePath);
|
|
48
|
+
for await (const chunk of stream) hash.update(chunk);
|
|
49
|
+
return hash.digest('hex');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function extractArchive(archivePath, destDir) {
|
|
53
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
if (archivePath.endsWith('.tar.gz')) {
|
|
56
|
+
const result = spawnSync('tar', ['-xzf', archivePath, '-C', destDir], { stdio: 'inherit' });
|
|
57
|
+
if (result.status !== 0) throw new Error('Failed to extract tar.gz archive with tar');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (archivePath.endsWith('.zip')) {
|
|
62
|
+
if (process.platform === 'win32') {
|
|
63
|
+
const command = `Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`;
|
|
64
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], { stdio: 'inherit' });
|
|
65
|
+
if (result.status !== 0) throw new Error('Failed to extract zip archive with PowerShell');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = spawnSync('unzip', ['-o', archivePath, '-d', destDir], { stdio: 'inherit' });
|
|
70
|
+
if (result.status !== 0) throw new Error('Failed to extract zip archive with unzip');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new Error(`Unsupported archive format: ${archivePath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
const { archive, binary } = getPlatformAsset();
|
|
79
|
+
const archiveUrl = `${BASE_URL}/${archive}`;
|
|
80
|
+
const checksumsUrl = `${BASE_URL}/checksums.txt`;
|
|
81
|
+
const archivePath = path.join(TMP_DIR, archive);
|
|
82
|
+
const checksumsPath = path.join(TMP_DIR, 'checksums.txt');
|
|
83
|
+
const extractDir = path.join(TMP_DIR, 'extract');
|
|
84
|
+
const finalBinPath = path.join(BIN_DIR, binary);
|
|
85
|
+
|
|
86
|
+
console.log(`jira-commands npm installer: ${TAG} -> ${archive}`);
|
|
87
|
+
|
|
88
|
+
await fetchToFile(checksumsUrl, checksumsPath);
|
|
89
|
+
await fetchToFile(archiveUrl, archivePath);
|
|
90
|
+
|
|
91
|
+
const checksums = await fs.promises.readFile(checksumsPath, 'utf8');
|
|
92
|
+
const expectedLine = checksums.split(/\r?\n/).find((line) => line.includes(archive));
|
|
93
|
+
if (!expectedLine) throw new Error(`Missing checksum entry for ${archive}`);
|
|
94
|
+
const expectedSha = expectedLine.trim().split(/\s+/)[0];
|
|
95
|
+
const actualSha = await sha256File(archivePath);
|
|
96
|
+
if (expectedSha !== actualSha) {
|
|
97
|
+
throw new Error(`Checksum mismatch for ${archive}. Expected ${expectedSha}, got ${actualSha}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await extractArchive(archivePath, extractDir);
|
|
101
|
+
|
|
102
|
+
const candidates = [
|
|
103
|
+
path.join(extractDir, binary),
|
|
104
|
+
path.join(extractDir, archive.replace(/\.tar\.gz$/, '').replace(/\.zip$/, ''))
|
|
105
|
+
];
|
|
106
|
+
const extractedBinary = candidates.find((candidate) => fs.existsSync(candidate));
|
|
107
|
+
if (!extractedBinary) {
|
|
108
|
+
throw new Error(`Extracted binary not found in archive: ${binary}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await fs.promises.mkdir(BIN_DIR, { recursive: true });
|
|
112
|
+
await fs.promises.copyFile(extractedBinary, finalBinPath);
|
|
113
|
+
|
|
114
|
+
if (process.platform !== 'win32') {
|
|
115
|
+
await fs.promises.chmod(finalBinPath, 0o755);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await fs.promises.rm(TMP_DIR, { recursive: true, force: true });
|
|
119
|
+
console.log(`Installed ${finalBinPath}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch(async (error) => {
|
|
123
|
+
console.error(error.message || error);
|
|
124
|
+
try {
|
|
125
|
+
await fs.promises.rm(TMP_DIR, { recursive: true, force: true });
|
|
126
|
+
} catch {}
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mulham28/jirac",
|
|
3
|
+
"version": "0.27.1",
|
|
4
|
+
"description": "Install the jirac Jira CLI from GitHub Releases",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mulhamna/jira-commands.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/mulhamna/jira-commands",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/mulhamna/jira-commands/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"jira",
|
|
16
|
+
"atlassian",
|
|
17
|
+
"cli",
|
|
18
|
+
"tui"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"jirac": "bin/jirac.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin",
|
|
25
|
+
"lib",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"postinstall": "node ./lib/install.js",
|
|
30
|
+
"release:check": "node ../../scripts/sync-npm-version.mjs && git diff --exit-code -- packaging/npm/package.json"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
}
|
|
35
|
+
}
|