@package-pal/cli 0.0.4 → 0.0.5
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/package.json +7 -9
- package/src/lib/install/functions/clear-placeholder-binary.js +14 -0
- package/src/lib/install/functions/get-path-info.js +32 -0
- package/src/lib/install/functions/get-platform-info.js +50 -0
- package/src/lib/install/functions/link-existing-binary.js +30 -0
- package/src/lib/install/functions/load-missing-binary.js +79 -0
- package/src/lib/install/functions/prepare-binary.js +34 -0
- package/src/lib/install/install-binary.js +18 -0
- package/src/link-binary.js +0 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@package-pal/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "CLI tool exposing core PackagePal functionality.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"package",
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"typescript": "^5.8.3",
|
|
17
|
-
"@types/bun": "^1.2.19",
|
|
18
16
|
"@clack/prompts": "^0.11.0",
|
|
19
17
|
"@package-pal/core": "^0.0.2",
|
|
20
18
|
"@package-pal/util": "^0.0.4",
|
|
21
19
|
"@stricli/core": "^1.2.0",
|
|
20
|
+
"@types/bun": "^1.2.19",
|
|
21
|
+
"tar": "^7.4.3",
|
|
22
|
+
"typescript": "^5.8.3",
|
|
22
23
|
"yoctocolors": "^2.1.1"
|
|
23
24
|
},
|
|
24
25
|
"optionalDependencies": {
|
|
@@ -30,18 +31,15 @@
|
|
|
30
31
|
"@package-pal/cli-darwin-x64": "0.0.1",
|
|
31
32
|
"@package-pal/cli-windows-x64": "0.0.1"
|
|
32
33
|
},
|
|
33
|
-
"engines": {
|
|
34
|
-
"bun": ">=1.2.0"
|
|
35
|
-
},
|
|
36
34
|
"bin": {
|
|
37
35
|
"ppal": "bin/ppal",
|
|
38
36
|
"dppal": "./src/index.ts"
|
|
39
37
|
},
|
|
40
38
|
"files": [
|
|
41
|
-
"src/
|
|
42
|
-
"bin
|
|
39
|
+
"src/lib/install",
|
|
40
|
+
"bin"
|
|
43
41
|
],
|
|
44
42
|
"scripts": {
|
|
45
|
-
"postinstall": "node ./src/
|
|
43
|
+
"postinstall": "node ./src/lib/install/install-binary.js"
|
|
46
44
|
}
|
|
47
45
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { rmSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Bun.Platform} platform
|
|
5
|
+
* @param {string} outputBinTargetBasePath
|
|
6
|
+
*/
|
|
7
|
+
export const clearPlaceholderBinary = (platform, outputBinTargetBasePath) => {
|
|
8
|
+
rmSync(outputBinTargetBasePath, { force: true });
|
|
9
|
+
|
|
10
|
+
if (platform === 'win32') {
|
|
11
|
+
rmSync(`${outputBinTargetBasePath}.exe`, { force: true });
|
|
12
|
+
rmSync(`${outputBinTargetBasePath}.cmd`, { force: true });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dirname, join, resolve,
|
|
3
|
+
} from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import packageJson from '../../../../package.json';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Bun.Platform} platform
|
|
9
|
+
* @param {string} targetPackage
|
|
10
|
+
*/
|
|
11
|
+
export const getPathInfo = (platform, targetPackage) => {
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const binName = Object.keys(packageJson.bin)[0];
|
|
14
|
+
if (!binName) {
|
|
15
|
+
throw new Error('Expected bin name.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const outputBinDir = resolve(
|
|
19
|
+
__dirname, '..', 'bin',
|
|
20
|
+
);
|
|
21
|
+
const outputBinTargetBasePath = join(outputBinDir, binName);
|
|
22
|
+
const targetBinPath = resolve(
|
|
23
|
+
__dirname, '..', '..', targetPackage, platform === 'win32' ? `${binName}.exe` : binName,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
outputBinDir,
|
|
28
|
+
binName,
|
|
29
|
+
outputBinTargetBasePath,
|
|
30
|
+
targetBinPath,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arch, platform,
|
|
3
|
+
} from 'os';
|
|
4
|
+
|
|
5
|
+
export const getPlatformInfo = () => {
|
|
6
|
+
const usePlatform = platform();
|
|
7
|
+
const useArch = arch();
|
|
8
|
+
let targetPackage = '';
|
|
9
|
+
|
|
10
|
+
switch (usePlatform) {
|
|
11
|
+
case 'darwin':
|
|
12
|
+
targetPackage = useArch === 'arm64' ? 'cli-darwin-arm64' : 'cli-darwin-x64';
|
|
13
|
+
break;
|
|
14
|
+
|
|
15
|
+
case 'win32':
|
|
16
|
+
targetPackage = 'cli-windows-x64';
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case 'linux':
|
|
20
|
+
let isMusl = false;
|
|
21
|
+
try {
|
|
22
|
+
// Determine if the OS is using musl libc.
|
|
23
|
+
// The report will not have a glibcVersionRuntime property if musl is being used.
|
|
24
|
+
// @ts-expect-error unknown type
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
26
|
+
isMusl = !process.report.getReport().header?.glibcVersionRuntime;
|
|
27
|
+
} catch {
|
|
28
|
+
isMusl = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
targetPackage
|
|
32
|
+
= useArch === 'arm64'
|
|
33
|
+
? isMusl
|
|
34
|
+
? 'cli-linux-arm64-musl'
|
|
35
|
+
: 'cli-linux-arm64'
|
|
36
|
+
: isMusl
|
|
37
|
+
? 'cli-linux-x64-musl'
|
|
38
|
+
: 'cli-linux-x64';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!targetPackage) {
|
|
42
|
+
throw new Error(`Unsupported platform: ${usePlatform} ${useArch}.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
platform: usePlatform,
|
|
47
|
+
arch: useArch,
|
|
48
|
+
targetPackage,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync, linkSync, symlinkSync, writeFileSync,
|
|
3
|
+
} from 'fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {Bun.Platform} platform
|
|
7
|
+
* @param {string} targetBinPath
|
|
8
|
+
* @param {string} binName
|
|
9
|
+
* @param {string} outputBinTargetBasePath
|
|
10
|
+
*/
|
|
11
|
+
export const linkExistingBinary = (
|
|
12
|
+
platform, binName, targetBinPath, outputBinTargetBasePath,
|
|
13
|
+
) => {
|
|
14
|
+
if (platform !== 'win32') {
|
|
15
|
+
symlinkSync(targetBinPath, outputBinTargetBasePath);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
linkSync(targetBinPath, outputBinTargetBasePath);
|
|
21
|
+
} catch {
|
|
22
|
+
try {
|
|
23
|
+
copyFileSync(targetBinPath, outputBinTargetBasePath);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw new Error(`Failed to link to or copy target binary '${targetBinPath}'.`, { cause: e });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
writeFileSync(`${outputBinTargetBasePath}.cmd`, `@echo off\r\n"%~dp0\\${binName}.exe" %*\r\n`);
|
|
30
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { get } from 'https';
|
|
2
|
+
import { pipeline } from 'stream/promises';
|
|
3
|
+
import { x } from 'tar';
|
|
4
|
+
import packageJson from '../../../../package.json' with { type: 'json' };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} tarballUrl
|
|
8
|
+
* @param {string} binName
|
|
9
|
+
* @param {string} outputBinDir
|
|
10
|
+
*/
|
|
11
|
+
const downloadAndExtract = async (
|
|
12
|
+
tarballUrl, binName, outputBinDir,
|
|
13
|
+
) => {
|
|
14
|
+
for (let i = 0; i < 3; i++) {
|
|
15
|
+
try {
|
|
16
|
+
await new Promise((resolve, reject) => {
|
|
17
|
+
get(tarballUrl, (res) => {
|
|
18
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
19
|
+
if (!res.headers.location) {
|
|
20
|
+
reject(new Error(`Failed to download binary: Redirect location missing.`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
downloadAndExtract(
|
|
25
|
+
res.headers.location, binName, outputBinDir,
|
|
26
|
+
).then(resolve, reject);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (res.statusCode !== 200) {
|
|
31
|
+
reject(new Error(`Failed to download binary: ${res.statusCode?.toString() ?? 'Unknown'}.`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const extractStream = x({
|
|
36
|
+
cwd: outputBinDir,
|
|
37
|
+
strip: 1,
|
|
38
|
+
filter: path => path === `package/bin/${binName}`,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
pipeline(res, extractStream).then(resolve)
|
|
42
|
+
.catch(reject);
|
|
43
|
+
}).on('error', reject);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (i < 2) {
|
|
49
|
+
console.warn(`Download failed, retrying...`);
|
|
50
|
+
} else {
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} binName
|
|
59
|
+
* @param {string} targetPackage
|
|
60
|
+
* @param {string} outputBinDir
|
|
61
|
+
*/
|
|
62
|
+
export const loadMissingBinary = async (
|
|
63
|
+
binName, targetPackage, outputBinDir,
|
|
64
|
+
) => {
|
|
65
|
+
const fullTargetPackageName = `@package-pal/${targetPackage}`;
|
|
66
|
+
// @ts-expect-error unknown key
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
68
|
+
const targetPackageVersion = packageJson.optionalDependencies[fullTargetPackageName];
|
|
69
|
+
if (!targetPackageVersion) {
|
|
70
|
+
throw new Error(`No version found for target package '${fullTargetPackageName}'.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tarballUrl = `https://registry.npmjs.org/${fullTargetPackageName}/-/${fullTargetPackageName.replace('@', '').replace('/', '-')}-${String(targetPackageVersion)}.tgz`;
|
|
74
|
+
console.log(`Downloading '${fullTargetPackageName}' from ${tarballUrl}...`);
|
|
75
|
+
|
|
76
|
+
await downloadAndExtract(
|
|
77
|
+
tarballUrl, binName, outputBinDir,
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync, mkdirSync,
|
|
3
|
+
} from 'fs';
|
|
4
|
+
import { clearPlaceholderBinary } from './clear-placeholder-binary.js';
|
|
5
|
+
import { linkExistingBinary } from './link-existing-binary.js';
|
|
6
|
+
import { loadMissingBinary } from './load-missing-binary.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Bun.Platform} platform
|
|
10
|
+
* @param {string} binName
|
|
11
|
+
* @param {string} targetPackage
|
|
12
|
+
* @param {string} targetBinPath
|
|
13
|
+
* @param {string} outputBinDir
|
|
14
|
+
* @param {string} outputBinTargetBasePath
|
|
15
|
+
*/
|
|
16
|
+
export const prepareBinary = (
|
|
17
|
+
platform, binName, targetPackage, targetBinPath, outputBinDir, outputBinTargetBasePath,
|
|
18
|
+
) => {
|
|
19
|
+
clearPlaceholderBinary(platform, outputBinTargetBasePath);
|
|
20
|
+
mkdirSync(outputBinDir, { recursive: true });
|
|
21
|
+
|
|
22
|
+
if (existsSync(targetBinPath)) {
|
|
23
|
+
console.info(`Expected CLI binary package is available in '${targetBinPath}'.`);
|
|
24
|
+
linkExistingBinary(
|
|
25
|
+
platform, binName, targetBinPath, outputBinTargetBasePath,
|
|
26
|
+
);
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.warn(`Expected CLI binary was not found in '${targetBinPath}'.`);
|
|
31
|
+
return loadMissingBinary(
|
|
32
|
+
binName, targetPackage, outputBinDir,
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getPathInfo } from './functions/get-path-info.js';
|
|
2
|
+
import { getPlatformInfo } from './functions/get-platform-info.js';
|
|
3
|
+
import { prepareBinary } from './functions/prepare-binary.js';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const {
|
|
7
|
+
platform, targetPackage,
|
|
8
|
+
} = getPlatformInfo();
|
|
9
|
+
const {
|
|
10
|
+
outputBinDir, binName, outputBinTargetBasePath, targetBinPath,
|
|
11
|
+
} = getPathInfo(platform, targetPackage);
|
|
12
|
+
|
|
13
|
+
await prepareBinary(
|
|
14
|
+
platform, binName, targetPackage, targetBinPath, outputBinDir, outputBinTargetBasePath,
|
|
15
|
+
);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw new Error('Postinstall failed to install CLI binary.', { cause: e });
|
|
18
|
+
}
|
package/src/link-binary.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copyFileSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync,
|
|
3
|
-
} from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
arch, platform,
|
|
6
|
-
} from 'os';
|
|
7
|
-
import {
|
|
8
|
-
resolve, dirname,
|
|
9
|
-
} from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import packageJson from '../package.json' with { type: 'json' };
|
|
12
|
-
|
|
13
|
-
const usePlatform = platform();
|
|
14
|
-
const useArch = arch();
|
|
15
|
-
|
|
16
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
const binName = Object.keys(packageJson.bin)[0];
|
|
18
|
-
if (!binName) {
|
|
19
|
-
throw new Error('Expected bin name.');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// const binName = usePlatform === 'win32' ? `${rawBinName}.exe` : rawBinName;
|
|
23
|
-
const binDir = resolve(
|
|
24
|
-
__dirname, '..', 'bin',
|
|
25
|
-
);
|
|
26
|
-
const binTarget = resolve(binDir, binName);
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
rmSync(binTarget, { force: true });
|
|
30
|
-
rmSync(`${binTarget}.exe`, { force: true });
|
|
31
|
-
rmSync(`${binTarget}.cmd`, { force: true });
|
|
32
|
-
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
33
|
-
} catch (_) { /* empty */ }
|
|
34
|
-
|
|
35
|
-
let platformPackage = '';
|
|
36
|
-
switch (usePlatform) {
|
|
37
|
-
case 'darwin':
|
|
38
|
-
platformPackage = useArch === 'arm64' ? 'cli-darwin-arm64' : 'cli-darwin-x64';
|
|
39
|
-
break;
|
|
40
|
-
|
|
41
|
-
case 'win32':
|
|
42
|
-
platformPackage = 'cli-windows-x64';
|
|
43
|
-
break;
|
|
44
|
-
|
|
45
|
-
case 'linux':
|
|
46
|
-
let isMusl = false;
|
|
47
|
-
try {
|
|
48
|
-
// @ts-expect-error unknown type
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
50
|
-
isMusl = !process.report.getReport().header?.glibcVersionRuntime;
|
|
51
|
-
} catch {
|
|
52
|
-
isMusl = true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
platformPackage
|
|
56
|
-
= useArch === 'arm64'
|
|
57
|
-
? isMusl
|
|
58
|
-
? 'cli-linux-arm64-musl'
|
|
59
|
-
: 'cli-linux-arm64'
|
|
60
|
-
: isMusl
|
|
61
|
-
? 'cli-linux-x64-musl'
|
|
62
|
-
: 'cli-linux-x64';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!platformPackage) {
|
|
66
|
-
throw new Error(`Unsupported platform: ${usePlatform} ${useArch}.`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const binPath = resolve(
|
|
70
|
-
__dirname, '..', '..', platformPackage, usePlatform === 'win32' ? `${binName}.exe` : binName,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (!existsSync(binPath)) {
|
|
74
|
-
throw new Error(`Expected binary not found: ${binPath}.`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
mkdirSync(binDir, { recursive: true });
|
|
78
|
-
|
|
79
|
-
if (usePlatform === 'win32') {
|
|
80
|
-
copyFileSync(binPath, `${binTarget}.exe`);
|
|
81
|
-
writeFileSync(`${binTarget}.cmd`, `@echo off\r\n"%~dp0\\${binName}.exe" %*\r\n`);
|
|
82
|
-
} else {
|
|
83
|
-
symlinkSync(binPath, binTarget);
|
|
84
|
-
}
|