@package-pal/cli 0.0.3 → 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/bin/ppal ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ throw new Error("Native binary not linked yet. Re-run `npm install` or check postinstall script.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@package-pal/cli",
3
- "version": "0.0.3",
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,17 +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/link-binary.js"
39
+ "src/lib/install",
40
+ "bin"
42
41
  ],
43
42
  "scripts": {
44
- "postinstall": "node ./src/link-binary.js"
43
+ "postinstall": "node ./src/lib/install/install-binary.js"
45
44
  }
46
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
+ }
@@ -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
- }