@nozomiishii/pm 0.2.3 → 0.2.4
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/dist/cli.mjs +229 -0
- package/package.json +48 -27
- package/dist/cli.js +0 -290
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
//#region package.json
|
|
7
|
+
var version = "0.2.4";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/expand-home.ts
|
|
10
|
+
function expandHome(p) {
|
|
11
|
+
if (p.startsWith("~/")) return path.join(process.env.HOME ?? "", p.slice(2));
|
|
12
|
+
if (p === "~") return process.env.HOME ?? "";
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/filter-projects.ts
|
|
17
|
+
function filterProjects(projects, tags) {
|
|
18
|
+
return projects.filter((p) => {
|
|
19
|
+
if (p.enabled === false) return false;
|
|
20
|
+
if (tags.length > 0) return tags.every((t) => p.tags?.includes(t));
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/strip-emoji-label.ts
|
|
26
|
+
/** Remove emoji (incl. modifiers, ZWJ) and normalize whitespace. */
|
|
27
|
+
function stripEmojiLabel(s) {
|
|
28
|
+
let t = s.normalize("NFC");
|
|
29
|
+
for (let i = 0; i < 4; i++) t = t.replaceAll(/[\p{Extended_Pictographic}\p{Emoji_Modifier}\p{Variation_Selector}\u{200D}]/gv, "");
|
|
30
|
+
return t.replaceAll(/\s+/g, " ").trim();
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/find-project.ts
|
|
34
|
+
/**
|
|
35
|
+
* 引数の名前から一致するプロジェクトを探す。
|
|
36
|
+
* 生の name とプレーンラベルの両方にマッチする。
|
|
37
|
+
* 重複した場合はエラーを投げる。
|
|
38
|
+
*/
|
|
39
|
+
function findProject(projects, query) {
|
|
40
|
+
const matches = projects.filter((p) => {
|
|
41
|
+
if (p.name === query) return true;
|
|
42
|
+
return stripEmojiLabel(p.name) === query;
|
|
43
|
+
});
|
|
44
|
+
if (matches.length > 1) throw new Error(`Ambiguous name "${query}" matches: ${matches.map((p) => p.name).join(", ")}`);
|
|
45
|
+
return matches[0];
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/logo/logo-color.ascii
|
|
49
|
+
var logo_color_default = "\x1B[38;2;120;180;255m zzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m\n\x1B[38;2;100;160;245m zzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m\n\x1B[38;2;80;140;235mzzzzz.----.______.zzzzzzzzz\x1B[0m\n\x1B[38;2;60;120;225mzzzzz | __________zzzzzz\x1B[0m\n\x1B[38;2;45;100;215mzzzzz | / /zzzz\x1B[0m\n\x1B[38;2;30;80;205mzzzzz | / /zzzzz\x1B[0m\n\x1B[38;2;20;65;195mzzzzz | / pm /zzzzzz\x1B[0m\n\x1B[38;2;10;50;185mzzzzz |/__________ /zzzzzzz\x1B[0m\n\x1B[38;2;10;50;185mnozozzzzzzzzzzzzzzzzzzzzzz\x1B[0m\n\x1B[38;2;10;50;185mzzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m\n";
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/cli.ts
|
|
52
|
+
var ExitError = class extends Error {
|
|
53
|
+
code;
|
|
54
|
+
constructor(message, code) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.code = code;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function defaultConfigPath() {
|
|
60
|
+
const home = process.env.HOME ?? "";
|
|
61
|
+
switch (process.platform) {
|
|
62
|
+
case "darwin": return path.join(home, "Library/Application Support/Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
63
|
+
case "win32": return path.join(process.env.APPDATA ?? "", "Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
64
|
+
default: return path.join(home, ".config/Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function printLogo() {
|
|
68
|
+
process.stdout.write(`${logo_color_default}\n`);
|
|
69
|
+
}
|
|
70
|
+
function usage() {
|
|
71
|
+
process.stdout.write(`Usage: pm [options] [command]
|
|
72
|
+
|
|
73
|
+
Commands:
|
|
74
|
+
cd [name] Jump to a project (fzf if no name given)
|
|
75
|
+
ls List project names
|
|
76
|
+
logo Display the pm logo
|
|
77
|
+
uninstall Uninstall pm from your system
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--config <path> Path to projects.json (or PM_CONFIG)
|
|
81
|
+
--help Show this help
|
|
82
|
+
--version Show version
|
|
83
|
+
|
|
84
|
+
Running \`pm\` without a command opens the fzf picker.\n`);
|
|
85
|
+
}
|
|
86
|
+
const SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
87
|
+
"cd",
|
|
88
|
+
"logo",
|
|
89
|
+
"ls",
|
|
90
|
+
"uninstall"
|
|
91
|
+
]);
|
|
92
|
+
function fzfSelect(projects) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const lines = projects.map((p) => `${plainLabel(p.name)}\t${expandHome(p.rootPath)}`);
|
|
95
|
+
const proc = spawn("fzf", [
|
|
96
|
+
"--delimiter= ",
|
|
97
|
+
"--with-nth=1",
|
|
98
|
+
"--preview",
|
|
99
|
+
"bat --color=always --style=header,grid --line-range :80 {2}/README.* 2>/dev/null || echo 'No README found'"
|
|
100
|
+
], { stdio: [
|
|
101
|
+
"pipe",
|
|
102
|
+
"pipe",
|
|
103
|
+
"inherit"
|
|
104
|
+
] });
|
|
105
|
+
let stdout = "";
|
|
106
|
+
proc.stdout.on("data", (d) => {
|
|
107
|
+
stdout += d.toString();
|
|
108
|
+
});
|
|
109
|
+
proc.stdin.write(lines.join("\n") + "\n");
|
|
110
|
+
proc.stdin.end();
|
|
111
|
+
proc.on("close", (code) => {
|
|
112
|
+
if (code !== 0) {
|
|
113
|
+
resolve(void 0);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const selected = stdout.trim();
|
|
117
|
+
const idx = lines.indexOf(selected);
|
|
118
|
+
resolve(idx === -1 ? void 0 : projects[idx]);
|
|
119
|
+
});
|
|
120
|
+
proc.on("error", (err) => {
|
|
121
|
+
if (err.code === "ENOENT") {
|
|
122
|
+
process.stderr.write("fzf is not installed. Install it or pass a project name directly.\n");
|
|
123
|
+
resolve(void 0);
|
|
124
|
+
} else reject(err);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function handleArg(state, arg, nextArg) {
|
|
129
|
+
switch (arg) {
|
|
130
|
+
case "--config":
|
|
131
|
+
state.config = nextArg();
|
|
132
|
+
break;
|
|
133
|
+
case "--help":
|
|
134
|
+
state.help = true;
|
|
135
|
+
break;
|
|
136
|
+
case "--version":
|
|
137
|
+
state.version = true;
|
|
138
|
+
break;
|
|
139
|
+
default: if (!state.subcommand && SUBCOMMANDS.has(arg)) state.subcommand = arg;
|
|
140
|
+
else state.rest.push(arg);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function jumpToProject(projects, name) {
|
|
144
|
+
let target;
|
|
145
|
+
if (name) {
|
|
146
|
+
target = findProject(projects, name);
|
|
147
|
+
if (!target) throw new ExitError(`Project not found: ${name}`, 1);
|
|
148
|
+
} else {
|
|
149
|
+
target = await fzfSelect(projects);
|
|
150
|
+
if (!target) throw new ExitError("", 1);
|
|
151
|
+
}
|
|
152
|
+
const dir = expandHome(target.rootPath);
|
|
153
|
+
if (!existsSync(dir)) throw new ExitError(`Directory not found: ${dir}`, 1);
|
|
154
|
+
process.stdout.write(`${dir}\n`);
|
|
155
|
+
}
|
|
156
|
+
async function main() {
|
|
157
|
+
const args = parseArgs(process.argv.slice(2));
|
|
158
|
+
if (args.version) {
|
|
159
|
+
process.stdout.write(`${version}\n`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (args.help) {
|
|
163
|
+
usage();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (args.subcommand === "logo") {
|
|
167
|
+
printLogo();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (args.subcommand === "uninstall") {
|
|
171
|
+
await runUninstall();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const filePath = expandHome(args.config);
|
|
175
|
+
if (!existsSync(filePath)) throw new ExitError(`File not found: ${filePath}`, 1);
|
|
176
|
+
const raw = await readFile(filePath, "utf8");
|
|
177
|
+
const allProjects = JSON.parse(raw);
|
|
178
|
+
switch (args.subcommand) {
|
|
179
|
+
case "cd":
|
|
180
|
+
await jumpToProject(filterProjects(allProjects, []), args.rest[0]);
|
|
181
|
+
break;
|
|
182
|
+
case "ls": {
|
|
183
|
+
const projects = filterProjects(allProjects, []);
|
|
184
|
+
for (const p of projects) process.stdout.write(`${plainLabel(p.name)}\n`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
await jumpToProject(filterProjects(allProjects, []));
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function parseArgs(argv) {
|
|
193
|
+
const state = {
|
|
194
|
+
config: process.env.PM_CONFIG ?? defaultConfigPath(),
|
|
195
|
+
help: false,
|
|
196
|
+
rest: [],
|
|
197
|
+
subcommand: void 0,
|
|
198
|
+
version: false
|
|
199
|
+
};
|
|
200
|
+
for (let i = 0; i < argv.length; i++) {
|
|
201
|
+
const arg = argv[i];
|
|
202
|
+
if (arg !== void 0) handleArg(state, arg, () => argv[++i] ?? "");
|
|
203
|
+
}
|
|
204
|
+
return state;
|
|
205
|
+
}
|
|
206
|
+
function plainLabel(name) {
|
|
207
|
+
return stripEmojiLabel(name) || name;
|
|
208
|
+
}
|
|
209
|
+
function runUninstall() {
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
spawn("bash", ["-c", `curl -fsSL "https://raw.githubusercontent.com/nozomiishii/pm/main/uninstall.sh" | bash`], { stdio: "inherit" }).on("close", (code) => {
|
|
212
|
+
process.exitCode = code ?? 0;
|
|
213
|
+
resolve();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
await main();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof ExitError) {
|
|
221
|
+
if (error.message) process.stderr.write(`${error.message}\n`);
|
|
222
|
+
process.exitCode = error.code;
|
|
223
|
+
} else {
|
|
224
|
+
process.stderr.write(`${String(error)}\n`);
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nozomiishii/pm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Project manager CLI — jump to projects via fzf",
|
|
5
|
-
"type": "module",
|
|
6
5
|
"homepage": "https://github.com/nozomiishii/pm#readme",
|
|
7
6
|
"bugs": {
|
|
8
7
|
"url": "https://github.com/nozomiishii/pm/issues"
|
|
@@ -13,40 +12,62 @@
|
|
|
13
12
|
},
|
|
14
13
|
"license": "MIT",
|
|
15
14
|
"author": "Nozomi Ishii",
|
|
15
|
+
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
|
-
"pm": "./dist/cli.
|
|
17
|
+
"pm": "./dist/cli.mjs"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist/cli.
|
|
20
|
+
"dist/cli.mjs",
|
|
21
21
|
"src/pm.zsh"
|
|
22
22
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "bun build --compile src/cli.ts --outfile dist/pm",
|
|
25
|
-
"prepublishOnly": "bun build src/cli.ts --outfile dist/cli.js --target=node --banner '#!/usr/bin/env node'",
|
|
26
|
-
"prepare": "lefthook install --force",
|
|
27
|
-
"typecheck": "tsc --noEmit",
|
|
28
|
-
"dev": "bun run src/cli.ts",
|
|
29
|
-
"demo": "docker run --rm -v \"$(pwd)\":/vhs pm-vhs",
|
|
30
|
-
"demo:all": "bash demo/create.sh",
|
|
31
|
-
"demo:logo": "docker run --rm -v \"$(pwd)\":/vhs pm-vhs demo/logo.tape",
|
|
32
|
-
"logo:colorize": "bun run src/logo/create-logo-color.ts",
|
|
33
|
-
"install:local": "bash install.sh",
|
|
34
|
-
"uninstall:local": "bash uninstall.sh",
|
|
35
|
-
"test": "vitest run"
|
|
36
|
-
},
|
|
37
23
|
"devDependencies": {
|
|
38
|
-
"@nozomiishii/commitlint-config": "1.
|
|
39
|
-
"@nozomiishii/
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
24
|
+
"@nozomiishii/commitlint-config": "1.11.0",
|
|
25
|
+
"@nozomiishii/eslint-config": "1.11.0",
|
|
26
|
+
"@nozomiishii/lefthook-config": "1.11.0",
|
|
27
|
+
"@nozomiishii/prettier-config": "1.11.0",
|
|
28
|
+
"@nozomiishii/tsconfig": "1.11.0",
|
|
29
|
+
"@tsdown/exe": "0.22.11",
|
|
30
|
+
"@types/node": "24.13.3",
|
|
31
|
+
"eslint": "10.7.0",
|
|
32
|
+
"lefthook": "2.1.10",
|
|
33
|
+
"prettier": "3.9.5",
|
|
34
|
+
"tsdown": "0.22.11",
|
|
35
|
+
"tsx": "4.23.1",
|
|
36
|
+
"typescript": "6.0.3",
|
|
37
|
+
"vitest": "4.1.10",
|
|
38
|
+
"node": "runtime:24.18.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=24"
|
|
42
|
+
},
|
|
43
|
+
"devEngines": {
|
|
44
|
+
"runtime": {
|
|
45
|
+
"name": "node",
|
|
46
|
+
"version": "24.18.0",
|
|
47
|
+
"onFail": "download"
|
|
48
|
+
}
|
|
43
49
|
},
|
|
44
50
|
"publishConfig": {
|
|
45
51
|
"access": "public",
|
|
46
52
|
"provenance": true
|
|
47
53
|
},
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
"
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsdown -F lib",
|
|
56
|
+
"build:exe": "tsdown -F exe",
|
|
57
|
+
"demo": "docker run --rm -v \"$(pwd)\":/vhs pm-vhs",
|
|
58
|
+
"demo:all": "bash demo/create.sh",
|
|
59
|
+
"demo:logo": "docker run --rm -v \"$(pwd)\":/vhs pm-vhs demo/logo.tape",
|
|
60
|
+
"dev": "tsx src/cli.ts",
|
|
61
|
+
"eslint": "eslint --max-warnings=0 --cache",
|
|
62
|
+
"format": "pnpm prettier . --check",
|
|
63
|
+
"format:fix": "pnpm prettier . --write",
|
|
64
|
+
"install:local": "bash install.sh",
|
|
65
|
+
"lint": "pnpm eslint",
|
|
66
|
+
"lint:fix": "pnpm eslint --fix",
|
|
67
|
+
"logo:colorize": "tsx src/logo/create-logo-color.ts",
|
|
68
|
+
"prettier": "prettier --ignore-unknown --cache",
|
|
69
|
+
"test": "vitest run",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"uninstall:local": "bash uninstall.sh"
|
|
51
72
|
}
|
|
52
|
-
}
|
|
73
|
+
}
|
package/dist/cli.js
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
import path2 from "node:path";
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
|
-
|
|
9
|
-
// src/filter-projects.ts
|
|
10
|
-
function filterProjects(projects, tags) {
|
|
11
|
-
return projects.filter((p) => {
|
|
12
|
-
if (p.enabled === false)
|
|
13
|
-
return false;
|
|
14
|
-
if (tags.length > 0) {
|
|
15
|
-
return tags.every((t) => p.tags?.includes(t));
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// src/expand-home.ts
|
|
22
|
-
import path from "node:path";
|
|
23
|
-
function expandHome(p) {
|
|
24
|
-
if (p.startsWith("~/")) {
|
|
25
|
-
return path.join(process.env.HOME ?? "", p.slice(2));
|
|
26
|
-
}
|
|
27
|
-
if (p === "~") {
|
|
28
|
-
return process.env.HOME ?? "";
|
|
29
|
-
}
|
|
30
|
-
return p;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// src/strip-emoji-label.ts
|
|
34
|
-
function stripEmojiLabel(s) {
|
|
35
|
-
let t = s.normalize("NFC");
|
|
36
|
-
for (let i = 0;i < 4; i++) {
|
|
37
|
-
t = t.replace(/\p{Extended_Pictographic}/gu, "").replace(/\p{Emoji_Modifier}/gu, "").replace(/\u200d/g, "").replace(/\ufe0f/g, "").replace(/\ufe0e/g, "");
|
|
38
|
-
}
|
|
39
|
-
return t.replace(/\s+/g, " ").trim();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/find-project.ts
|
|
43
|
-
function findProject(projects, query) {
|
|
44
|
-
const matches = projects.filter((p) => {
|
|
45
|
-
if (p.name === query)
|
|
46
|
-
return true;
|
|
47
|
-
const plain = stripEmojiLabel(p.name);
|
|
48
|
-
return plain === query;
|
|
49
|
-
});
|
|
50
|
-
if (matches.length > 1) {
|
|
51
|
-
throw new Error(`Ambiguous name "${query}" matches: ${matches.map((p) => p.name).join(", ")}`);
|
|
52
|
-
}
|
|
53
|
-
return matches[0];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// src/logo/logo-color.ascii
|
|
57
|
-
var logo_color_default = `\x1B[38;2;120;180;255m zzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m
|
|
58
|
-
\x1B[38;2;100;160;245m zzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m
|
|
59
|
-
\x1B[38;2;80;140;235mzzzzz.----.______.zzzzzzzzz\x1B[0m
|
|
60
|
-
\x1B[38;2;60;120;225mzzzzz | __________zzzzzz\x1B[0m
|
|
61
|
-
\x1B[38;2;45;100;215mzzzzz | / /zzzz\x1B[0m
|
|
62
|
-
\x1B[38;2;30;80;205mzzzzz | / /zzzzz\x1B[0m
|
|
63
|
-
\x1B[38;2;20;65;195mzzzzz | / pm /zzzzzz\x1B[0m
|
|
64
|
-
\x1B[38;2;10;50;185mzzzzz |/__________ /zzzzzzz\x1B[0m
|
|
65
|
-
\x1B[38;2;10;50;185mnozozzzzzzzzzzzzzzzzzzzzzz\x1B[0m
|
|
66
|
-
\x1B[38;2;10;50;185mzzzzzzzzzzzzzzzzzzzzzzzzzz\x1B[0m
|
|
67
|
-
`;
|
|
68
|
-
// package.json
|
|
69
|
-
var package_default = {
|
|
70
|
-
name: "@nozomiishii/pm",
|
|
71
|
-
version: "0.2.3",
|
|
72
|
-
description: "Project manager CLI — jump to projects via fzf",
|
|
73
|
-
type: "module",
|
|
74
|
-
homepage: "https://github.com/nozomiishii/pm#readme",
|
|
75
|
-
bugs: {
|
|
76
|
-
url: "https://github.com/nozomiishii/pm/issues"
|
|
77
|
-
},
|
|
78
|
-
repository: {
|
|
79
|
-
type: "git",
|
|
80
|
-
url: "git+https://github.com/nozomiishii/pm.git"
|
|
81
|
-
},
|
|
82
|
-
license: "MIT",
|
|
83
|
-
author: "Nozomi Ishii",
|
|
84
|
-
bin: {
|
|
85
|
-
pm: "./dist/cli.js"
|
|
86
|
-
},
|
|
87
|
-
files: [
|
|
88
|
-
"dist/cli.js",
|
|
89
|
-
"src/pm.zsh"
|
|
90
|
-
],
|
|
91
|
-
scripts: {
|
|
92
|
-
build: "bun build --compile src/cli.ts --outfile dist/pm",
|
|
93
|
-
prepublishOnly: "bun build src/cli.ts --outfile dist/cli.js --target=node --banner '#!/usr/bin/env node'",
|
|
94
|
-
prepare: "lefthook install --force",
|
|
95
|
-
typecheck: "tsc --noEmit",
|
|
96
|
-
dev: "bun run src/cli.ts",
|
|
97
|
-
demo: 'docker run --rm -v "$(pwd)":/vhs pm-vhs',
|
|
98
|
-
"demo:all": "bash demo/create.sh",
|
|
99
|
-
"demo:logo": 'docker run --rm -v "$(pwd)":/vhs pm-vhs demo/logo.tape',
|
|
100
|
-
"logo:colorize": "bun run src/logo/create-logo-color.ts",
|
|
101
|
-
"install:local": "bash install.sh",
|
|
102
|
-
"uninstall:local": "bash uninstall.sh",
|
|
103
|
-
test: "vitest run"
|
|
104
|
-
},
|
|
105
|
-
devDependencies: {
|
|
106
|
-
"@nozomiishii/commitlint-config": "1.1.1",
|
|
107
|
-
"@nozomiishii/lefthook-config": "1.1.1",
|
|
108
|
-
"@types/node": "25.8.0",
|
|
109
|
-
lefthook: "2.1.6",
|
|
110
|
-
vitest: "4.1.6"
|
|
111
|
-
},
|
|
112
|
-
publishConfig: {
|
|
113
|
-
access: "public",
|
|
114
|
-
provenance: true
|
|
115
|
-
},
|
|
116
|
-
packageManager: "bun@1.3.11",
|
|
117
|
-
engines: {
|
|
118
|
-
node: "24.15.0"
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// src/cli.ts
|
|
123
|
-
function defaultConfigPath() {
|
|
124
|
-
const home = process.env.HOME ?? "";
|
|
125
|
-
switch (process.platform) {
|
|
126
|
-
case "darwin":
|
|
127
|
-
return path2.join(home, "Library/Application Support/Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
128
|
-
case "win32":
|
|
129
|
-
return path2.join(process.env.APPDATA ?? "", "Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
130
|
-
default:
|
|
131
|
-
return path2.join(home, ".config/Code/User/globalStorage/alefragnani.project-manager/projects.json");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function usage() {
|
|
135
|
-
console.log(`Usage: pm [options] [command]
|
|
136
|
-
|
|
137
|
-
Commands:
|
|
138
|
-
cd [name] Jump to a project (fzf if no name given)
|
|
139
|
-
ls List project names
|
|
140
|
-
logo Display the pm logo
|
|
141
|
-
uninstall Uninstall pm from your system
|
|
142
|
-
|
|
143
|
-
Options:
|
|
144
|
-
--config <path> Path to projects.json (or PM_CONFIG)
|
|
145
|
-
--help Show this help
|
|
146
|
-
--version Show version
|
|
147
|
-
|
|
148
|
-
Running \`pm\` without a command opens the fzf picker.`);
|
|
149
|
-
}
|
|
150
|
-
function printLogo() {
|
|
151
|
-
console.log(logo_color_default);
|
|
152
|
-
}
|
|
153
|
-
var SUBCOMMANDS = new Set(["cd", "ls", "logo", "uninstall"]);
|
|
154
|
-
function parseArgs(argv) {
|
|
155
|
-
let config = process.env.PM_CONFIG ?? defaultConfigPath();
|
|
156
|
-
let help = false;
|
|
157
|
-
let version = false;
|
|
158
|
-
let subcommand;
|
|
159
|
-
const rest = [];
|
|
160
|
-
for (let i = 0;i < argv.length; i++) {
|
|
161
|
-
const arg = argv[i];
|
|
162
|
-
if (arg === "--config") {
|
|
163
|
-
config = argv[++i] ?? "";
|
|
164
|
-
} else if (arg === "--help") {
|
|
165
|
-
help = true;
|
|
166
|
-
} else if (arg === "--version") {
|
|
167
|
-
version = true;
|
|
168
|
-
} else if (!subcommand && SUBCOMMANDS.has(arg)) {
|
|
169
|
-
subcommand = arg;
|
|
170
|
-
} else {
|
|
171
|
-
rest.push(arg);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return { config, help, version, subcommand, rest };
|
|
175
|
-
}
|
|
176
|
-
function plainLabel(name) {
|
|
177
|
-
return stripEmojiLabel(name) || name;
|
|
178
|
-
}
|
|
179
|
-
function fzfSelect(projects) {
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
const lines = projects.map((p) => `${plainLabel(p.name)} ${expandHome(p.rootPath)}`);
|
|
182
|
-
const proc = spawn("fzf", [
|
|
183
|
-
"--delimiter=\t",
|
|
184
|
-
"--with-nth=1",
|
|
185
|
-
"--preview",
|
|
186
|
-
"bat --color=always --style=header,grid --line-range :80 {2}/README.* 2>/dev/null || echo 'No README found'"
|
|
187
|
-
], {
|
|
188
|
-
stdio: ["pipe", "pipe", "inherit"]
|
|
189
|
-
});
|
|
190
|
-
let stdout = "";
|
|
191
|
-
proc.stdout.on("data", (d) => {
|
|
192
|
-
stdout += d.toString();
|
|
193
|
-
});
|
|
194
|
-
proc.stdin.write(lines.join(`
|
|
195
|
-
`) + `
|
|
196
|
-
`);
|
|
197
|
-
proc.stdin.end();
|
|
198
|
-
proc.on("close", (code) => {
|
|
199
|
-
if (code !== 0) {
|
|
200
|
-
resolve(undefined);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const selected = stdout.trim();
|
|
204
|
-
const idx = lines.indexOf(selected);
|
|
205
|
-
resolve(idx >= 0 ? projects[idx] : undefined);
|
|
206
|
-
});
|
|
207
|
-
proc.on("error", (err) => {
|
|
208
|
-
if (err.code === "ENOENT") {
|
|
209
|
-
console.error("fzf is not installed. Install it or pass a project name directly.");
|
|
210
|
-
resolve(undefined);
|
|
211
|
-
} else {
|
|
212
|
-
reject(err);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
async function jumpToProject(projects, name) {
|
|
218
|
-
let target;
|
|
219
|
-
if (name) {
|
|
220
|
-
target = findProject(projects, name);
|
|
221
|
-
if (!target) {
|
|
222
|
-
console.error(`Project not found: ${name}`);
|
|
223
|
-
process.exit(1);
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
target = await fzfSelect(projects);
|
|
227
|
-
if (!target) {
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
const dir = expandHome(target.rootPath);
|
|
232
|
-
if (!existsSync(dir)) {
|
|
233
|
-
console.error(`Directory not found: ${dir}`);
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
console.log(dir);
|
|
237
|
-
}
|
|
238
|
-
async function main() {
|
|
239
|
-
const args = parseArgs(process.argv.slice(2));
|
|
240
|
-
if (args.version) {
|
|
241
|
-
console.log(package_default.version);
|
|
242
|
-
process.exit(0);
|
|
243
|
-
}
|
|
244
|
-
if (args.help) {
|
|
245
|
-
usage();
|
|
246
|
-
process.exit(0);
|
|
247
|
-
}
|
|
248
|
-
if (args.subcommand === "logo") {
|
|
249
|
-
printLogo();
|
|
250
|
-
process.exit(0);
|
|
251
|
-
}
|
|
252
|
-
if (args.subcommand === "uninstall") {
|
|
253
|
-
const url = "https://raw.githubusercontent.com/nozomiishii/pm/main/uninstall.sh";
|
|
254
|
-
const proc = spawn("bash", ["-c", `curl -fsSL "${url}" | bash`], {
|
|
255
|
-
stdio: "inherit"
|
|
256
|
-
});
|
|
257
|
-
proc.on("close", (code) => process.exit(code ?? 0));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const filePath = expandHome(args.config);
|
|
261
|
-
if (!existsSync(filePath)) {
|
|
262
|
-
console.error(`File not found: ${filePath}`);
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
const raw = await readFile(filePath, "utf-8");
|
|
266
|
-
const allProjects = JSON.parse(raw);
|
|
267
|
-
switch (args.subcommand) {
|
|
268
|
-
case "ls": {
|
|
269
|
-
const projects = filterProjects(allProjects, []);
|
|
270
|
-
for (const p of projects) {
|
|
271
|
-
console.log(plainLabel(p.name));
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
case "cd": {
|
|
276
|
-
const projects = filterProjects(allProjects, []);
|
|
277
|
-
await jumpToProject(projects, args.rest[0]);
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
default: {
|
|
281
|
-
const projects = filterProjects(allProjects, []);
|
|
282
|
-
await jumpToProject(projects);
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
main().catch((err) => {
|
|
288
|
-
console.error(err);
|
|
289
|
-
process.exit(1);
|
|
290
|
-
});
|