bin-home 0.1.0
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 +105 -0
- package/bin/cli.js +82 -0
- package/lib/commandValidator.js +16 -0
- package/lib/index.js +6 -0
- package/lib/packageFinder.js +236 -0
- package/lib/packageInfoFormatter.js +35 -0
- package/lib/repositoryParser.js +56 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# bin-home
|
|
2
|
+
|
|
3
|
+
bin-home is a CLI tool that finds which global npm package provides a given
|
|
4
|
+
command. It helps when you know the command name (for example, `codex`) but not
|
|
5
|
+
the package you need to install or manage (for example, `@openai/codex`).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g bin-home
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bin-home <command> [--open]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Examples
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bin-home codex
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Expected output:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
npm: @openai/codex
|
|
29
|
+
npm url: https://www.npmjs.com/package/@openai/codex
|
|
30
|
+
github: https://github.com/openai/codex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Open npm package page automatically:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bin-home codex --open
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Options
|
|
40
|
+
|
|
41
|
+
- `--help`, `-h`: Show help
|
|
42
|
+
- `--version`, `-v`: Show version
|
|
43
|
+
- `--open`, `-o`: Open npm package page in browser
|
|
44
|
+
|
|
45
|
+
## Publishing
|
|
46
|
+
|
|
47
|
+
To publish to npm and GitHub:
|
|
48
|
+
|
|
49
|
+
1. Update `package.json` metadata (name, version, repository, author).
|
|
50
|
+
2. Create a GitHub repository and push the code.
|
|
51
|
+
3. Run `npm publish`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# bin-home(中文)
|
|
56
|
+
|
|
57
|
+
bin-home 是一个 CLI 工具,用于查找某个命令来自哪个全局 npm 包。它解决了
|
|
58
|
+
“知道命令名但不知道包名”的痛点,比如命令是 `codex`,但包名是
|
|
59
|
+
`@openai/codex`。
|
|
60
|
+
|
|
61
|
+
## 安装
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g bin-home
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 使用方法
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bin-home <命令名> [--open]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 示例
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bin-home codex
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
预期输出:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
npm: @openai/codex
|
|
83
|
+
npm url: https://www.npmjs.com/package/@openai/codex
|
|
84
|
+
github: https://github.com/openai/codex
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
自动打开 npm 包页面:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bin-home codex --open
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 选项
|
|
94
|
+
|
|
95
|
+
- `--help`, `-h`: 显示帮助
|
|
96
|
+
- `--version`, `-v`: 显示版本号
|
|
97
|
+
- `--open`, `-o`: 打开 npm 包页面
|
|
98
|
+
|
|
99
|
+
## 发布说明
|
|
100
|
+
|
|
101
|
+
发布到 npm 和 GitHub 的步骤:
|
|
102
|
+
|
|
103
|
+
1. 更新 `package.json` 元数据(name、version、repository、author)。
|
|
104
|
+
2. 创建 GitHub 仓库并推送代码。
|
|
105
|
+
3. 运行 `npm publish`。
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const validateCommand = require("../lib/commandValidator");
|
|
4
|
+
const findPackageForCommand = require("../lib/packageFinder");
|
|
5
|
+
const displayPackageInfo = require("../lib/packageInfoFormatter");
|
|
6
|
+
|
|
7
|
+
const COMMAND_NOT_FOUND_EXIT_CODE = 1;
|
|
8
|
+
const PACKAGE_NOT_FOUND_EXIT_CODE = 2;
|
|
9
|
+
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log("Usage: bin-home <command> [--open]");
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log("Options:");
|
|
14
|
+
console.log(" --help, -h Show help");
|
|
15
|
+
console.log(" --version, -v Show version");
|
|
16
|
+
console.log(" --open, -o Open npm package page in browser");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function printVersion() {
|
|
20
|
+
const packageJson = require(path.join(__dirname, "..", "package.json"));
|
|
21
|
+
console.log(packageJson.version);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const flags = new Set();
|
|
26
|
+
let commandName = null;
|
|
27
|
+
|
|
28
|
+
for (const arg of argv) {
|
|
29
|
+
if (arg.startsWith("-")) {
|
|
30
|
+
flags.add(arg);
|
|
31
|
+
} else if (!commandName) {
|
|
32
|
+
commandName = arg;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
commandName,
|
|
38
|
+
help: flags.has("--help") || flags.has("-h"),
|
|
39
|
+
version: flags.has("--version") || flags.has("-v"),
|
|
40
|
+
open: flags.has("--open") || flags.has("-o")
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function run() {
|
|
45
|
+
const { commandName, help, version, open } = parseArgs(process.argv.slice(2));
|
|
46
|
+
|
|
47
|
+
if (version) {
|
|
48
|
+
printVersion();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (help || !commandName) {
|
|
53
|
+
printHelp();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const commandExists = await validateCommand(commandName);
|
|
58
|
+
if (!commandExists) {
|
|
59
|
+
console.error(`Command '${commandName}' not found in system PATH`);
|
|
60
|
+
process.exit(COMMAND_NOT_FOUND_EXIT_CODE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const packageInfo = await findPackageForCommand(commandName);
|
|
64
|
+
if (!packageInfo) {
|
|
65
|
+
console.error(
|
|
66
|
+
`No npm package found for command '${commandName}'. It may not be installed via npm.`
|
|
67
|
+
);
|
|
68
|
+
process.exit(PACKAGE_NOT_FOUND_EXIT_CODE);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await displayPackageInfo(commandName, packageInfo, open);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
run().catch((error) => {
|
|
75
|
+
if (typeof error.exitCode === "number") {
|
|
76
|
+
console.error(error.message);
|
|
77
|
+
process.exit(error.exitCode);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.error(`Unexpected error: ${error.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const which = require("which");
|
|
2
|
+
|
|
3
|
+
async function validateCommand(commandName) {
|
|
4
|
+
if (!commandName || typeof commandName !== "string") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
await which(commandName);
|
|
10
|
+
return true;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = validateCommand;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const { exec } = require("child_process");
|
|
2
|
+
const which = require("which");
|
|
3
|
+
const fs = require("fs/promises");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const NPM_LIST_EXIT_CODE = 3;
|
|
7
|
+
const PACKAGE_JSON_EXIT_CODE = 4;
|
|
8
|
+
|
|
9
|
+
function execNpmList() {
|
|
10
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
11
|
+
const command = `${npmCommand} ls -g --depth=0 --json`;
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
exec(
|
|
14
|
+
command,
|
|
15
|
+
{ maxBuffer: 10 * 1024 * 1024, windowsHide: true },
|
|
16
|
+
(error, stdout) => {
|
|
17
|
+
if (error) {
|
|
18
|
+
const err = new Error(
|
|
19
|
+
`Failed to list global npm packages: ${error.message}`
|
|
20
|
+
);
|
|
21
|
+
err.exitCode = NPM_LIST_EXIT_CODE;
|
|
22
|
+
return reject(err);
|
|
23
|
+
}
|
|
24
|
+
resolve(stdout);
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function execNpmRoot() {
|
|
31
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
32
|
+
const command = `${npmCommand} root -g`;
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
exec(command, { windowsHide: true }, (error, stdout) => {
|
|
35
|
+
if (error) {
|
|
36
|
+
const err = new Error(
|
|
37
|
+
`Failed to list global npm packages: ${error.message}`
|
|
38
|
+
);
|
|
39
|
+
err.exitCode = NPM_LIST_EXIT_CODE;
|
|
40
|
+
return reject(err);
|
|
41
|
+
}
|
|
42
|
+
resolve(stdout.trim());
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getCommandNamesForStringBin(packageName) {
|
|
48
|
+
const names = new Set();
|
|
49
|
+
if (packageName) {
|
|
50
|
+
names.add(packageName);
|
|
51
|
+
if (packageName.startsWith("@")) {
|
|
52
|
+
const parts = packageName.split("/");
|
|
53
|
+
if (parts[1]) {
|
|
54
|
+
names.add(parts[1]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return names;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function readPackageJson(packagePath, packageName) {
|
|
62
|
+
const packageJsonPath = path.join(packagePath, "package.json");
|
|
63
|
+
try {
|
|
64
|
+
const contents = await fs.readFile(packageJsonPath, "utf8");
|
|
65
|
+
return JSON.parse(contents);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const err = new Error(
|
|
68
|
+
`Failed to read package.json for ${packageName}: ${error.message}`
|
|
69
|
+
);
|
|
70
|
+
err.exitCode = PACKAGE_JSON_EXIT_CODE;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function listGlobalPackages(nodeModulesRoot) {
|
|
76
|
+
try {
|
|
77
|
+
const entries = await fs.readdir(nodeModulesRoot, { withFileTypes: true });
|
|
78
|
+
const packages = [];
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (!entry.isDirectory()) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (entry.name.startsWith("@")) {
|
|
86
|
+
const scopePath = path.join(nodeModulesRoot, entry.name);
|
|
87
|
+
const scopedEntries = await fs.readdir(scopePath, {
|
|
88
|
+
withFileTypes: true
|
|
89
|
+
});
|
|
90
|
+
for (const scopedEntry of scopedEntries) {
|
|
91
|
+
if (!scopedEntry.isDirectory()) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
packages.push({
|
|
95
|
+
name: `${entry.name}/${scopedEntry.name}`,
|
|
96
|
+
path: path.join(scopePath, scopedEntry.name)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
packages.push({
|
|
101
|
+
name: entry.name,
|
|
102
|
+
path: path.join(nodeModulesRoot, entry.name)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return packages;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const err = new Error(
|
|
110
|
+
`Failed to read package.json for global packages: ${error.message}`
|
|
111
|
+
);
|
|
112
|
+
err.exitCode = PACKAGE_JSON_EXIT_CODE;
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function resolveVoltaPackage(commandName) {
|
|
118
|
+
try {
|
|
119
|
+
await which("volta");
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const command = `volta which ${commandName}`;
|
|
125
|
+
const voltaPath = await new Promise((resolve, reject) => {
|
|
126
|
+
exec(command, { windowsHide: true }, (error, stdout) => {
|
|
127
|
+
if (error) {
|
|
128
|
+
return reject(error);
|
|
129
|
+
}
|
|
130
|
+
resolve(stdout.trim());
|
|
131
|
+
});
|
|
132
|
+
}).catch(() => null);
|
|
133
|
+
|
|
134
|
+
if (!voltaPath) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const parts = path.normalize(voltaPath).split(path.sep);
|
|
139
|
+
const packagesIndex = parts.lastIndexOf("packages");
|
|
140
|
+
if (packagesIndex === -1 || !parts[packagesIndex + 1]) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const packageName = parts[packagesIndex + 1];
|
|
145
|
+
const packagePath = path.join(
|
|
146
|
+
...parts.slice(0, packagesIndex + 2),
|
|
147
|
+
"node_modules",
|
|
148
|
+
packageName
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const packageJson = await readPackageJson(packagePath, packageName);
|
|
152
|
+
return { packageName, packagePath, packageJson };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function findPackageForCommand(commandName) {
|
|
156
|
+
const [output, npmRoot] = await Promise.all([
|
|
157
|
+
execNpmList(),
|
|
158
|
+
execNpmRoot()
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
let parsed;
|
|
162
|
+
try {
|
|
163
|
+
parsed = JSON.parse(output);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const err = new Error(
|
|
166
|
+
`Failed to list global npm packages: ${error.message}`
|
|
167
|
+
);
|
|
168
|
+
err.exitCode = NPM_LIST_EXIT_CODE;
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const dependencies = parsed.dependencies || {};
|
|
173
|
+
const nodeModulesRoot = npmRoot || "";
|
|
174
|
+
const checked = new Set();
|
|
175
|
+
|
|
176
|
+
for (const packageName of Object.keys(dependencies)) {
|
|
177
|
+
const packagePath = path.join(nodeModulesRoot, packageName);
|
|
178
|
+
checked.add(packageName);
|
|
179
|
+
const packageJson = await readPackageJson(packagePath, packageName);
|
|
180
|
+
const bin = packageJson.bin;
|
|
181
|
+
|
|
182
|
+
if (typeof bin === "string") {
|
|
183
|
+
const candidateNames = getCommandNamesForStringBin(
|
|
184
|
+
packageJson.name || packageName
|
|
185
|
+
);
|
|
186
|
+
if (candidateNames.has(commandName)) {
|
|
187
|
+
return { packageName, packagePath, packageJson };
|
|
188
|
+
}
|
|
189
|
+
} else if (bin && typeof bin === "object") {
|
|
190
|
+
if (Object.prototype.hasOwnProperty.call(bin, commandName)) {
|
|
191
|
+
return { packageName, packagePath, packageJson };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (nodeModulesRoot) {
|
|
197
|
+
const packages = await listGlobalPackages(nodeModulesRoot);
|
|
198
|
+
for (const pkg of packages) {
|
|
199
|
+
if (checked.has(pkg.name)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const packageJson = await readPackageJson(pkg.path, pkg.name);
|
|
203
|
+
const bin = packageJson.bin;
|
|
204
|
+
|
|
205
|
+
if (typeof bin === "string") {
|
|
206
|
+
const candidateNames = getCommandNamesForStringBin(
|
|
207
|
+
packageJson.name || pkg.name
|
|
208
|
+
);
|
|
209
|
+
if (candidateNames.has(commandName)) {
|
|
210
|
+
return {
|
|
211
|
+
packageName: pkg.name,
|
|
212
|
+
packagePath: pkg.path,
|
|
213
|
+
packageJson
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
} else if (bin && typeof bin === "object") {
|
|
217
|
+
if (Object.prototype.hasOwnProperty.call(bin, commandName)) {
|
|
218
|
+
return {
|
|
219
|
+
packageName: pkg.name,
|
|
220
|
+
packagePath: pkg.path,
|
|
221
|
+
packageJson
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const voltaPackage = await resolveVoltaPackage(commandName);
|
|
229
|
+
if (voltaPackage) {
|
|
230
|
+
return voltaPackage;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = findPackageForCommand;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const parseRepository = require("./repositoryParser");
|
|
2
|
+
|
|
3
|
+
async function resolveOpen() {
|
|
4
|
+
const mod = await import("open");
|
|
5
|
+
return mod.default || mod;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function displayPackageInfo(
|
|
9
|
+
commandName,
|
|
10
|
+
packageInfo,
|
|
11
|
+
shouldOpen = false,
|
|
12
|
+
openImpl
|
|
13
|
+
) {
|
|
14
|
+
const packageName = packageInfo.packageName;
|
|
15
|
+
const npmUrl = `https://www.npmjs.com/package/${packageName}`;
|
|
16
|
+
const repoUrl = parseRepository(packageInfo.packageJson.repository);
|
|
17
|
+
|
|
18
|
+
console.log(`npm: ${packageName}`);
|
|
19
|
+
console.log(`npm url: ${npmUrl}`);
|
|
20
|
+
console.log(`github: ${repoUrl || "unavailable"}`);
|
|
21
|
+
|
|
22
|
+
if (shouldOpen) {
|
|
23
|
+
try {
|
|
24
|
+
const openFn = openImpl || (await resolveOpen());
|
|
25
|
+
if (typeof openFn !== "function") {
|
|
26
|
+
throw new Error("open is not a function");
|
|
27
|
+
}
|
|
28
|
+
await openFn(npmUrl);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Failed to open URL in browser: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = displayPackageInfo;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function normalizeGitHubUrl(repo) {
|
|
2
|
+
if (!repo || typeof repo !== "string") {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const trimmed = repo.trim();
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let candidate = trimmed;
|
|
12
|
+
|
|
13
|
+
if (candidate.startsWith("git+")) {
|
|
14
|
+
candidate = candidate.slice(4);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (candidate.startsWith("github:")) {
|
|
18
|
+
candidate = candidate.slice("github:".length);
|
|
19
|
+
} else if (
|
|
20
|
+
!candidate.startsWith("http://") &&
|
|
21
|
+
!candidate.startsWith("https://") &&
|
|
22
|
+
/^[a-z]+:/i.test(candidate)
|
|
23
|
+
) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const githubMatch =
|
|
28
|
+
candidate.match(/github\.com[:/]+([^/]+)\/([^/]+?)(?:\.git)?$/i) ||
|
|
29
|
+
candidate.match(/^([^/]+)\/([^/]+)$/);
|
|
30
|
+
|
|
31
|
+
if (!githubMatch) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const owner = githubMatch[1];
|
|
36
|
+
const repoName = githubMatch[2].replace(/\.git$/, "");
|
|
37
|
+
return `https://github.com/${owner}/${repoName}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseRepository(repository) {
|
|
41
|
+
if (!repository) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof repository === "string") {
|
|
46
|
+
return normalizeGitHubUrl(repository);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof repository === "object" && typeof repository.url === "string") {
|
|
50
|
+
return normalizeGitHubUrl(repository.url);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = parseRepository;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bin-home",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Find which global npm package provides a CLI command",
|
|
5
|
+
"author": "bin-home",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"bin-home": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/yourname/bin-home.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"npm",
|
|
21
|
+
"bin",
|
|
22
|
+
"package",
|
|
23
|
+
"finder"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=14.0.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"lib",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"open": "^9.1.0",
|
|
35
|
+
"which": "^4.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|