@oh-my-pi/cli 0.1.0 → 0.2.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/.github/icon.png +0 -0
- package/.github/logo.png +0 -0
- package/.github/workflows/publish.yml +1 -1
- package/LICENSE +21 -0
- package/README.md +131 -145
- package/biome.json +1 -1
- package/dist/cli.js +2032 -1136
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/enable.d.ts +1 -0
- package/dist/commands/enable.d.ts.map +1 -1
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/outdated.d.ts +1 -0
- package/dist/commands/outdated.d.ts.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/why.d.ts +1 -0
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/conflicts.d.ts +9 -1
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/lock.d.ts +3 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lockfile.d.ts +52 -0
- package/dist/lockfile.d.ts.map +1 -0
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/migrate.d.ts.map +1 -1
- package/dist/npm.d.ts +14 -2
- package/dist/npm.d.ts.map +1 -1
- package/dist/paths.d.ts +34 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/symlinks.d.ts +10 -4
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +7 -2
- package/plugins/metal-theme/package.json +6 -1
- package/plugins/subagents/package.json +6 -1
- package/src/cli.ts +69 -43
- package/src/commands/create.ts +51 -1
- package/src/commands/doctor.ts +95 -7
- package/src/commands/enable.ts +25 -8
- package/src/commands/info.ts +41 -5
- package/src/commands/init.ts +20 -2
- package/src/commands/install.ts +266 -52
- package/src/commands/link.ts +60 -9
- package/src/commands/list.ts +10 -5
- package/src/commands/outdated.ts +17 -6
- package/src/commands/search.ts +20 -3
- package/src/commands/uninstall.ts +57 -6
- package/src/commands/update.ts +67 -9
- package/src/commands/why.ts +47 -16
- package/src/conflicts.ts +33 -1
- package/src/errors.ts +22 -0
- package/src/index.ts +19 -25
- package/src/lock.ts +46 -0
- package/src/lockfile.ts +132 -0
- package/src/manifest.ts +143 -35
- package/src/migrate.ts +14 -3
- package/src/npm.ts +74 -18
- package/src/paths.ts +77 -9
- package/src/symlinks.ts +134 -17
- package/tsconfig.json +7 -3
- package/CHECK.md +0 -352
package/src/cli.ts
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { createPlugin } from "@omp/commands/create";
|
|
4
|
+
import { runDoctor } from "@omp/commands/doctor";
|
|
5
|
+
import { disablePlugin, enablePlugin } from "@omp/commands/enable";
|
|
6
|
+
import { showInfo } from "@omp/commands/info";
|
|
7
|
+
import { initProject } from "@omp/commands/init";
|
|
8
|
+
import { installPlugin } from "@omp/commands/install";
|
|
9
|
+
import { linkPlugin } from "@omp/commands/link";
|
|
10
|
+
import { listPlugins } from "@omp/commands/list";
|
|
11
|
+
import { showOutdated } from "@omp/commands/outdated";
|
|
12
|
+
import { searchPlugins } from "@omp/commands/search";
|
|
13
|
+
import { uninstallPlugin } from "@omp/commands/uninstall";
|
|
14
|
+
import { updatePlugin } from "@omp/commands/update";
|
|
15
|
+
import { whyFile } from "@omp/commands/why";
|
|
16
|
+
import { withErrorHandling } from "@omp/errors";
|
|
17
|
+
import { checkMigration, migrateToNpm } from "@omp/migrate";
|
|
18
|
+
import { checkNpmAvailable } from "@omp/npm";
|
|
19
|
+
import chalk from "chalk";
|
|
3
20
|
import { program } from "commander";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { searchPlugins } from "./commands/search.js";
|
|
14
|
-
import { uninstallPlugin } from "./commands/uninstall.js";
|
|
15
|
-
import { updatePlugin } from "./commands/update.js";
|
|
16
|
-
import { whyFile } from "./commands/why.js";
|
|
17
|
-
import { checkMigration, migrateToNpm } from "./migrate.js";
|
|
18
|
-
|
|
19
|
-
program.name("omp").description("Oh My Pi - Plugin manager for pi configuration").version("0.1.0");
|
|
21
|
+
|
|
22
|
+
// Check npm availability at startup
|
|
23
|
+
const npmCheck = checkNpmAvailable();
|
|
24
|
+
if (!npmCheck.available) {
|
|
25
|
+
console.log(chalk.red(npmCheck.error));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
program.name("omp").description("Oh My Pi - Plugin manager for pi configuration").version("0.2.0");
|
|
20
30
|
|
|
21
31
|
// Check for migration on startup (only for commands that need it)
|
|
22
32
|
program.hook("preAction", async (thisCommand) => {
|
|
@@ -45,36 +55,40 @@ Examples:
|
|
|
45
55
|
$ omp install # Install all from plugins.json
|
|
46
56
|
`,
|
|
47
57
|
)
|
|
48
|
-
.option("-g, --global", "Install globally to ~/.pi
|
|
58
|
+
.option("-g, --global", "Install globally to ~/.pi")
|
|
59
|
+
.option("-l, --local", "Install to project-local .pi/")
|
|
49
60
|
.option("-S, --save", "Add to plugins.json")
|
|
50
61
|
.option("-D, --save-dev", "Add as dev dependency")
|
|
51
62
|
.option("--force", "Overwrite conflicts without prompting")
|
|
52
63
|
.option("--json", "Output as JSON")
|
|
53
|
-
.action(installPlugin);
|
|
64
|
+
.action(withErrorHandling(installPlugin));
|
|
54
65
|
|
|
55
66
|
program
|
|
56
67
|
.command("uninstall <name>")
|
|
57
68
|
.alias("rm")
|
|
58
69
|
.description("Remove plugin and its symlinks")
|
|
59
|
-
.option("-g, --global", "Uninstall from ~/.pi
|
|
70
|
+
.option("-g, --global", "Uninstall from ~/.pi")
|
|
71
|
+
.option("-l, --local", "Uninstall from project-local .pi/")
|
|
60
72
|
.option("--json", "Output as JSON")
|
|
61
|
-
.action(uninstallPlugin);
|
|
73
|
+
.action(withErrorHandling(uninstallPlugin));
|
|
62
74
|
|
|
63
75
|
program
|
|
64
76
|
.command("update [name]")
|
|
65
77
|
.alias("up")
|
|
66
78
|
.description("Update to latest within semver range")
|
|
67
|
-
.option("-g, --global", "Update global plugins
|
|
79
|
+
.option("-g, --global", "Update global plugins")
|
|
80
|
+
.option("-l, --local", "Update project-local plugins")
|
|
68
81
|
.option("--json", "Output as JSON")
|
|
69
|
-
.action(updatePlugin);
|
|
82
|
+
.action(withErrorHandling(updatePlugin));
|
|
70
83
|
|
|
71
84
|
program
|
|
72
85
|
.command("list")
|
|
73
86
|
.alias("ls")
|
|
74
87
|
.description("Show installed plugins")
|
|
75
|
-
.option("-g, --global", "List global plugins
|
|
88
|
+
.option("-g, --global", "List global plugins")
|
|
89
|
+
.option("-l, --local", "List project-local plugins")
|
|
76
90
|
.option("--json", "Output as JSON")
|
|
77
|
-
.action(listPlugins);
|
|
91
|
+
.action(withErrorHandling(listPlugins));
|
|
78
92
|
|
|
79
93
|
program
|
|
80
94
|
.command("link <path>")
|
|
@@ -87,8 +101,10 @@ so changes are reflected immediately without reinstalling.
|
|
|
87
101
|
`,
|
|
88
102
|
)
|
|
89
103
|
.option("-n, --name <name>", "Custom name for the plugin")
|
|
90
|
-
.option("-g, --global", "Link globally
|
|
91
|
-
.
|
|
104
|
+
.option("-g, --global", "Link globally")
|
|
105
|
+
.option("-l, --local", "Link to project-local .pi/")
|
|
106
|
+
.option("--force", "Overwrite existing npm-installed plugin")
|
|
107
|
+
.action(withErrorHandling(linkPlugin));
|
|
92
108
|
|
|
93
109
|
// ============================================================================
|
|
94
110
|
// New Commands
|
|
@@ -98,70 +114,80 @@ program
|
|
|
98
114
|
.command("init")
|
|
99
115
|
.description("Create .pi/plugins.json in current project")
|
|
100
116
|
.option("--force", "Overwrite existing plugins.json")
|
|
101
|
-
.action(initProject);
|
|
117
|
+
.action(withErrorHandling(initProject));
|
|
102
118
|
|
|
103
119
|
program
|
|
104
120
|
.command("search <query>")
|
|
105
121
|
.description("Search npm for omp-plugin keyword")
|
|
106
122
|
.option("--json", "Output as JSON")
|
|
107
123
|
.option("--limit <n>", "Maximum results to show", "20")
|
|
108
|
-
.action(
|
|
124
|
+
.action(
|
|
125
|
+
withErrorHandling((query, options) => searchPlugins(query, { ...options, limit: parseInt(options.limit, 10) })),
|
|
126
|
+
);
|
|
109
127
|
|
|
110
128
|
program
|
|
111
129
|
.command("info <package>")
|
|
112
130
|
.description("Show plugin details before install")
|
|
113
131
|
.option("--json", "Output as JSON")
|
|
114
132
|
.option("--versions", "Show available versions")
|
|
115
|
-
.
|
|
133
|
+
.option("--all-versions", "Show all published versions")
|
|
134
|
+
.action(withErrorHandling(showInfo));
|
|
116
135
|
|
|
117
136
|
program
|
|
118
137
|
.command("outdated")
|
|
119
138
|
.description("List plugins with newer versions")
|
|
120
|
-
.option("-g, --global", "Check global plugins
|
|
139
|
+
.option("-g, --global", "Check global plugins")
|
|
140
|
+
.option("-l, --local", "Check project-local plugins")
|
|
121
141
|
.option("--json", "Output as JSON")
|
|
122
|
-
.action(showOutdated);
|
|
142
|
+
.action(withErrorHandling(showOutdated));
|
|
123
143
|
|
|
124
144
|
program
|
|
125
145
|
.command("doctor")
|
|
126
146
|
.description("Check for broken symlinks, conflicts")
|
|
127
|
-
.option("-g, --global", "Check global plugins
|
|
147
|
+
.option("-g, --global", "Check global plugins")
|
|
148
|
+
.option("-l, --local", "Check project-local plugins")
|
|
128
149
|
.option("--fix", "Attempt to fix issues")
|
|
129
150
|
.option("--json", "Output as JSON")
|
|
130
|
-
.action(runDoctor);
|
|
151
|
+
.action(withErrorHandling(runDoctor));
|
|
131
152
|
|
|
132
153
|
program
|
|
133
154
|
.command("create <name>")
|
|
134
155
|
.description("Scaffold new plugin from template")
|
|
135
156
|
.option("-d, --description <desc>", "Plugin description")
|
|
136
157
|
.option("-a, --author <author>", "Plugin author")
|
|
137
|
-
.action(createPlugin);
|
|
158
|
+
.action(withErrorHandling(createPlugin));
|
|
138
159
|
|
|
139
160
|
program
|
|
140
161
|
.command("why <file>")
|
|
141
162
|
.description("Show which plugin installed a file")
|
|
142
|
-
.option("-g, --global", "Check global plugins
|
|
163
|
+
.option("-g, --global", "Check global plugins")
|
|
164
|
+
.option("-l, --local", "Check project-local plugins")
|
|
143
165
|
.option("--json", "Output as JSON")
|
|
144
|
-
.action(whyFile);
|
|
166
|
+
.action(withErrorHandling(whyFile));
|
|
145
167
|
|
|
146
168
|
program
|
|
147
169
|
.command("enable <name>")
|
|
148
170
|
.description("Enable a disabled plugin")
|
|
149
|
-
.option("-g, --global", "Target global plugins
|
|
171
|
+
.option("-g, --global", "Target global plugins")
|
|
172
|
+
.option("-l, --local", "Target project-local plugins")
|
|
150
173
|
.option("--json", "Output as JSON")
|
|
151
|
-
.action(enablePlugin);
|
|
174
|
+
.action(withErrorHandling(enablePlugin));
|
|
152
175
|
|
|
153
176
|
program
|
|
154
177
|
.command("disable <name>")
|
|
155
178
|
.description("Disable plugin without uninstalling")
|
|
156
|
-
.option("-g, --global", "Target global plugins
|
|
179
|
+
.option("-g, --global", "Target global plugins")
|
|
180
|
+
.option("-l, --local", "Target project-local plugins")
|
|
157
181
|
.option("--json", "Output as JSON")
|
|
158
|
-
.action(disablePlugin);
|
|
182
|
+
.action(withErrorHandling(disablePlugin));
|
|
159
183
|
|
|
160
184
|
program
|
|
161
185
|
.command("migrate")
|
|
162
186
|
.description("Migrate from legacy manifest.json to npm-native format")
|
|
163
|
-
.action(
|
|
164
|
-
|
|
165
|
-
|
|
187
|
+
.action(
|
|
188
|
+
withErrorHandling(async () => {
|
|
189
|
+
await migrateToNpm();
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
166
192
|
|
|
167
193
|
program.parse();
|
package/src/commands/create.ts
CHANGED
|
@@ -8,16 +8,65 @@ export interface CreateOptions {
|
|
|
8
8
|
author?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const VALID_NPM_CHARS = new Set("abcdefghijklmnopqrstuvwxyz0123456789-_.");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a name conforms to npm naming rules
|
|
15
|
+
*/
|
|
16
|
+
function isValidNpmName(name: string): boolean {
|
|
17
|
+
if (!name || name.length === 0) return false;
|
|
18
|
+
if (name.startsWith(".") || name.startsWith("_")) return false;
|
|
19
|
+
if (name.includes(" ")) return false;
|
|
20
|
+
for (const char of name) {
|
|
21
|
+
if (!VALID_NPM_CHARS.has(char)) return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a string to be a valid npm package name
|
|
28
|
+
*/
|
|
29
|
+
function normalizePluginName(name: string): string {
|
|
30
|
+
let normalized = name.toLowerCase().split(" ").join("-");
|
|
31
|
+
|
|
32
|
+
// Remove invalid characters (keep alphanumeric, -, _, .)
|
|
33
|
+
normalized = Array.from(normalized)
|
|
34
|
+
.filter((char) => VALID_NPM_CHARS.has(char))
|
|
35
|
+
.join("");
|
|
36
|
+
|
|
37
|
+
// Can't start with . or _ or -
|
|
38
|
+
while (normalized.startsWith(".") || normalized.startsWith("_") || normalized.startsWith("-")) {
|
|
39
|
+
normalized = normalized.slice(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
/**
|
|
12
46
|
* Scaffold a new plugin from template
|
|
13
47
|
*/
|
|
14
48
|
export async function createPlugin(name: string, options: CreateOptions = {}): Promise<void> {
|
|
15
49
|
// Ensure name follows conventions
|
|
16
|
-
|
|
50
|
+
let pluginName = name.startsWith("omp-") ? name : `omp-${name}`;
|
|
51
|
+
|
|
52
|
+
// Validate and normalize the plugin name
|
|
53
|
+
if (!isValidNpmName(pluginName)) {
|
|
54
|
+
const normalized = normalizePluginName(pluginName);
|
|
55
|
+
if (!normalized || normalized === "omp-" || normalized === "omp") {
|
|
56
|
+
console.log(chalk.red(`Error: Invalid plugin name "${name}" cannot be normalized to a valid npm name`));
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Ensure omp- prefix after normalization
|
|
61
|
+
const finalName = normalized.startsWith("omp-") ? normalized : `omp-${normalized}`;
|
|
62
|
+
console.log(chalk.yellow(`Invalid plugin name. Normalized to: ${finalName}`));
|
|
63
|
+
pluginName = finalName;
|
|
64
|
+
}
|
|
17
65
|
const pluginDir = pluginName;
|
|
18
66
|
|
|
19
67
|
if (existsSync(pluginDir)) {
|
|
20
68
|
console.log(chalk.red(`Error: Directory ${pluginDir} already exists`));
|
|
69
|
+
process.exitCode = 1;
|
|
21
70
|
return;
|
|
22
71
|
}
|
|
23
72
|
|
|
@@ -149,5 +198,6 @@ Provide instructions for the agent here.
|
|
|
149
198
|
console.log(chalk.dim(" 5. Publish: npm publish"));
|
|
150
199
|
} catch (err) {
|
|
151
200
|
console.log(chalk.red(`Error creating plugin: ${(err as Error).message}`));
|
|
201
|
+
process.exitCode = 1;
|
|
152
202
|
}
|
|
153
203
|
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { detectAllConflicts, formatConflicts } from "@omp/conflicts";
|
|
3
|
+
import { getInstalledPlugins, loadPluginsJson, readPluginPackageJson, savePluginsJson } from "@omp/manifest";
|
|
4
|
+
import {
|
|
5
|
+
GLOBAL_PACKAGE_JSON,
|
|
6
|
+
NODE_MODULES_DIR,
|
|
7
|
+
PLUGINS_DIR,
|
|
8
|
+
PROJECT_NODE_MODULES,
|
|
9
|
+
PROJECT_PLUGINS_JSON,
|
|
10
|
+
resolveScope,
|
|
11
|
+
} from "@omp/paths";
|
|
12
|
+
import { checkPluginSymlinks, createPluginSymlinks } from "@omp/symlinks";
|
|
2
13
|
import chalk from "chalk";
|
|
3
|
-
import { detectAllConflicts, formatConflicts } from "../conflicts.js";
|
|
4
|
-
import { getInstalledPlugins, loadPluginsJson, readPluginPackageJson } from "../manifest.js";
|
|
5
|
-
import { GLOBAL_PACKAGE_JSON, NODE_MODULES_DIR, PLUGINS_DIR } from "../paths.js";
|
|
6
|
-
import { checkPluginSymlinks } from "../symlinks.js";
|
|
7
14
|
|
|
8
15
|
export interface DoctorOptions {
|
|
9
16
|
global?: boolean;
|
|
17
|
+
local?: boolean;
|
|
10
18
|
fix?: boolean;
|
|
11
19
|
json?: boolean;
|
|
12
20
|
}
|
|
@@ -22,7 +30,7 @@ interface DiagnosticResult {
|
|
|
22
30
|
* Run health checks on the plugin system
|
|
23
31
|
*/
|
|
24
32
|
export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
25
|
-
const isGlobal = options
|
|
33
|
+
const isGlobal = resolveScope(options);
|
|
26
34
|
const results: DiagnosticResult[] = [];
|
|
27
35
|
|
|
28
36
|
console.log(chalk.blue("Running health checks...\n"));
|
|
@@ -45,7 +53,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
// 2. Check package.json exists
|
|
48
|
-
const packageJsonPath = isGlobal ? GLOBAL_PACKAGE_JSON :
|
|
56
|
+
const packageJsonPath = isGlobal ? GLOBAL_PACKAGE_JSON : PROJECT_PLUGINS_JSON;
|
|
49
57
|
if (!existsSync(packageJsonPath)) {
|
|
50
58
|
results.push({
|
|
51
59
|
check: "Package manifest",
|
|
@@ -62,7 +70,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
// 3. Check node_modules exists
|
|
65
|
-
const nodeModules = isGlobal ? NODE_MODULES_DIR :
|
|
73
|
+
const nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
|
|
66
74
|
if (!existsSync(nodeModules)) {
|
|
67
75
|
results.push({
|
|
68
76
|
check: "Node modules",
|
|
@@ -152,6 +160,38 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
|
152
160
|
});
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
// 7. Check for missing omp dependencies
|
|
164
|
+
const missingDeps: string[] = [];
|
|
165
|
+
for (const [name, pkgJson] of installedPlugins) {
|
|
166
|
+
if (pkgJson.dependencies) {
|
|
167
|
+
for (const depName of Object.keys(pkgJson.dependencies)) {
|
|
168
|
+
const depPkgJson = await readPluginPackageJson(depName, isGlobal);
|
|
169
|
+
if (!depPkgJson) {
|
|
170
|
+
// Dependency not found in node_modules
|
|
171
|
+
// Check if it's supposed to be an omp plugin by looking in the plugins manifest
|
|
172
|
+
if (pluginsJson.plugins[depName]) {
|
|
173
|
+
missingDeps.push(`${name} requires ${depName} (not in node_modules)`);
|
|
174
|
+
}
|
|
175
|
+
} else if (depPkgJson.omp?.install && depPkgJson.omp.install.length > 0) {
|
|
176
|
+
// Dependency is an omp plugin (has install entries) and is present - that's fine
|
|
177
|
+
// But check if it's registered in the plugins manifest
|
|
178
|
+
if (!pluginsJson.plugins[depName]) {
|
|
179
|
+
missingDeps.push(`${name} requires omp plugin ${depName} (installed but not in manifest)`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (missingDeps.length > 0) {
|
|
187
|
+
results.push({
|
|
188
|
+
check: "Missing omp dependencies",
|
|
189
|
+
status: "warning",
|
|
190
|
+
message: missingDeps.join("; "),
|
|
191
|
+
fix: isGlobal ? "Run: npm install in ~/.pi/plugins" : "Run: npm install in .pi",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
155
195
|
// Output results
|
|
156
196
|
if (options.json) {
|
|
157
197
|
console.log(JSON.stringify({ results }, null, 2));
|
|
@@ -194,6 +234,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
|
194
234
|
} else {
|
|
195
235
|
if (errors.length > 0) {
|
|
196
236
|
console.log(chalk.red(`${errors.length} error(s) found`));
|
|
237
|
+
process.exitCode = 1;
|
|
197
238
|
}
|
|
198
239
|
if (warnings.length > 0) {
|
|
199
240
|
console.log(chalk.yellow(`${warnings.length} warning(s) found`));
|
|
@@ -214,4 +255,51 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<void> {
|
|
|
214
255
|
console.log(chalk.dim(` - ${s}`));
|
|
215
256
|
}
|
|
216
257
|
}
|
|
258
|
+
|
|
259
|
+
// Apply fixes if --fix flag was passed
|
|
260
|
+
if (options.fix) {
|
|
261
|
+
let fixedAnything = false;
|
|
262
|
+
|
|
263
|
+
// Fix broken/missing symlinks by re-creating them
|
|
264
|
+
if (brokenSymlinks.length > 0 || missingSymlinks.length > 0) {
|
|
265
|
+
console.log(chalk.blue("\nAttempting to fix broken/missing symlinks..."));
|
|
266
|
+
for (const [name, pkgJson] of installedPlugins) {
|
|
267
|
+
const symlinkResult = await createPluginSymlinks(name, pkgJson, isGlobal, false);
|
|
268
|
+
if (symlinkResult.created.length > 0) {
|
|
269
|
+
fixedAnything = true;
|
|
270
|
+
console.log(chalk.green(` ✓ Re-created symlinks for ${name}`));
|
|
271
|
+
}
|
|
272
|
+
if (symlinkResult.errors.length > 0) {
|
|
273
|
+
for (const err of symlinkResult.errors) {
|
|
274
|
+
console.log(chalk.red(` ✗ ${name}: ${err}`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Remove orphaned manifest entries
|
|
281
|
+
if (orphaned.length > 0) {
|
|
282
|
+
console.log(chalk.blue("\nRemoving orphaned entries from manifest..."));
|
|
283
|
+
for (const name of orphaned) {
|
|
284
|
+
delete pluginsJson.plugins[name];
|
|
285
|
+
console.log(chalk.green(` ✓ Removed ${name}`));
|
|
286
|
+
}
|
|
287
|
+
await savePluginsJson(pluginsJson, isGlobal);
|
|
288
|
+
fixedAnything = true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Conflicts cannot be auto-fixed
|
|
292
|
+
if (conflicts.length > 0) {
|
|
293
|
+
console.log(chalk.yellow("\nConflicts cannot be auto-fixed. Please resolve manually:"));
|
|
294
|
+
for (const conflict of formatConflicts(conflicts)) {
|
|
295
|
+
console.log(chalk.dim(` - ${conflict}`));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (fixedAnything) {
|
|
300
|
+
console.log(chalk.green("\n✓ Fixes applied. Run 'omp doctor' again to verify."));
|
|
301
|
+
} else if (conflicts.length === 0) {
|
|
302
|
+
console.log(chalk.dim("\nNo fixable issues found."));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
217
305
|
}
|
package/src/commands/enable.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "@omp/manifest";
|
|
2
|
+
import { resolveScope } from "@omp/paths";
|
|
3
|
+
import { checkPluginSymlinks, createPluginSymlinks, removePluginSymlinks } from "@omp/symlinks";
|
|
1
4
|
import chalk from "chalk";
|
|
2
|
-
import { loadPluginsJson, readPluginPackageJson, savePluginsJson } from "../manifest.js";
|
|
3
|
-
import { createPluginSymlinks, removePluginSymlinks } from "../symlinks.js";
|
|
4
5
|
|
|
5
6
|
export interface EnableDisableOptions {
|
|
6
7
|
global?: boolean;
|
|
8
|
+
local?: boolean;
|
|
7
9
|
json?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -11,19 +13,21 @@ export interface EnableDisableOptions {
|
|
|
11
13
|
* Enable a disabled plugin (re-create symlinks)
|
|
12
14
|
*/
|
|
13
15
|
export async function enablePlugin(name: string, options: EnableDisableOptions = {}): Promise<void> {
|
|
14
|
-
const isGlobal = options
|
|
16
|
+
const isGlobal = resolveScope(options);
|
|
15
17
|
|
|
16
18
|
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
17
19
|
|
|
18
20
|
// Check if plugin exists
|
|
19
21
|
if (!pluginsJson.plugins[name]) {
|
|
20
22
|
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
23
|
+
process.exitCode = 1;
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
// Check if already enabled
|
|
25
28
|
if (!pluginsJson.disabled?.includes(name)) {
|
|
26
29
|
console.log(chalk.yellow(`Plugin "${name}" is already enabled.`));
|
|
30
|
+
process.exitCode = 1;
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -32,12 +36,20 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
32
36
|
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
33
37
|
if (!pkgJson) {
|
|
34
38
|
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
39
|
+
process.exitCode = 1;
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
// Check if symlinks are already in place
|
|
44
|
+
const symlinkStatus = await checkPluginSymlinks(name, pkgJson, isGlobal);
|
|
45
|
+
|
|
46
|
+
if (symlinkStatus.valid.length > 0 && symlinkStatus.broken.length === 0 && symlinkStatus.missing.length === 0) {
|
|
47
|
+
console.log(chalk.yellow(`Plugin "${name}" symlinks are already in place.`));
|
|
48
|
+
} else {
|
|
49
|
+
// Re-create symlinks
|
|
50
|
+
console.log(chalk.blue(`Enabling ${name}...`));
|
|
51
|
+
await createPluginSymlinks(name, pkgJson, isGlobal);
|
|
52
|
+
}
|
|
41
53
|
|
|
42
54
|
// Remove from disabled list
|
|
43
55
|
pluginsJson.disabled = pluginsJson.disabled.filter((n) => n !== name);
|
|
@@ -50,6 +62,7 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
50
62
|
}
|
|
51
63
|
} catch (err) {
|
|
52
64
|
console.log(chalk.red(`Error enabling plugin: ${(err as Error).message}`));
|
|
65
|
+
process.exitCode = 1;
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
68
|
|
|
@@ -57,19 +70,21 @@ export async function enablePlugin(name: string, options: EnableDisableOptions =
|
|
|
57
70
|
* Disable a plugin (remove symlinks but keep installed)
|
|
58
71
|
*/
|
|
59
72
|
export async function disablePlugin(name: string, options: EnableDisableOptions = {}): Promise<void> {
|
|
60
|
-
const isGlobal = options
|
|
73
|
+
const isGlobal = resolveScope(options);
|
|
61
74
|
|
|
62
75
|
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
63
76
|
|
|
64
77
|
// Check if plugin exists
|
|
65
78
|
if (!pluginsJson.plugins[name]) {
|
|
66
79
|
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
80
|
+
process.exitCode = 1;
|
|
67
81
|
return;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// Check if already disabled
|
|
71
85
|
if (pluginsJson.disabled?.includes(name)) {
|
|
72
86
|
console.log(chalk.yellow(`Plugin "${name}" is already disabled.`));
|
|
87
|
+
process.exitCode = 1;
|
|
73
88
|
return;
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -78,12 +93,13 @@ export async function disablePlugin(name: string, options: EnableDisableOptions
|
|
|
78
93
|
const pkgJson = await readPluginPackageJson(name, isGlobal);
|
|
79
94
|
if (!pkgJson) {
|
|
80
95
|
console.log(chalk.red(`Could not read package.json for ${name}`));
|
|
96
|
+
process.exitCode = 1;
|
|
81
97
|
return;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
// Remove symlinks
|
|
85
101
|
console.log(chalk.blue(`Disabling ${name}...`));
|
|
86
|
-
await removePluginSymlinks(name, pkgJson);
|
|
102
|
+
await removePluginSymlinks(name, pkgJson, isGlobal);
|
|
87
103
|
|
|
88
104
|
// Add to disabled list
|
|
89
105
|
if (!pluginsJson.disabled) {
|
|
@@ -101,5 +117,6 @@ export async function disablePlugin(name: string, options: EnableDisableOptions
|
|
|
101
117
|
}
|
|
102
118
|
} catch (err) {
|
|
103
119
|
console.log(chalk.red(`Error disabling plugin: ${(err as Error).message}`));
|
|
120
|
+
process.exitCode = 1;
|
|
104
121
|
}
|
|
105
122
|
}
|
package/src/commands/info.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { npmInfo } from "@omp/npm";
|
|
1
2
|
import chalk from "chalk";
|
|
2
|
-
import { npmInfo } from "../npm.js";
|
|
3
3
|
|
|
4
4
|
export interface InfoOptions {
|
|
5
5
|
json?: boolean;
|
|
6
6
|
versions?: boolean;
|
|
7
|
+
allVersions?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -17,6 +18,7 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
17
18
|
|
|
18
19
|
if (!info) {
|
|
19
20
|
console.log(chalk.red(`Package not found: ${packageName}`));
|
|
21
|
+
process.exitCode = 1;
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -59,6 +61,14 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
59
61
|
console.log(chalk.dim("keywords: ") + info.keywords.join(", "));
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// Dependencies
|
|
65
|
+
if (info.dependencies && Object.keys(info.dependencies).length > 0) {
|
|
66
|
+
console.log(chalk.dim("\ndependencies:"));
|
|
67
|
+
for (const [depName, depVersion] of Object.entries(info.dependencies)) {
|
|
68
|
+
console.log(chalk.dim(` ${depName}: ${depVersion}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
// Is it an omp plugin?
|
|
63
73
|
const isOmpPlugin = info.keywords?.includes("omp-plugin");
|
|
64
74
|
if (isOmpPlugin) {
|
|
@@ -68,11 +78,36 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
68
78
|
console.log(chalk.dim(" It may work, but might not have omp.install configuration"));
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
// Show what files will be installed
|
|
82
|
+
if (info.omp?.install?.length) {
|
|
83
|
+
console.log(chalk.dim("\nFiles to install:"));
|
|
84
|
+
for (const entry of info.omp.install) {
|
|
85
|
+
console.log(chalk.dim(` ${entry.src} → ${entry.dest}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
// Versions
|
|
72
|
-
if (options.versions
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
if (options.versions || options.allVersions) {
|
|
91
|
+
if (info["dist-tags"]) {
|
|
92
|
+
console.log(chalk.dim("\ndist-tags:"));
|
|
93
|
+
for (const [tag, version] of Object.entries(info["dist-tags"])) {
|
|
94
|
+
console.log(chalk.dim(` ${tag}: `) + version);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (info.versions?.length) {
|
|
99
|
+
console.log(chalk.dim("\nall versions:"));
|
|
100
|
+
if (options.allVersions) {
|
|
101
|
+
// Show all versions
|
|
102
|
+
console.log(chalk.dim(` ${info.versions.join(", ")}`));
|
|
103
|
+
} else {
|
|
104
|
+
// Show last 10
|
|
105
|
+
const versionsToShow = info.versions.slice(-10);
|
|
106
|
+
console.log(chalk.dim(` ${versionsToShow.join(", ")}`));
|
|
107
|
+
if (info.versions.length > 10) {
|
|
108
|
+
console.log(chalk.dim(` ... and ${info.versions.length - 10} more (use --all-versions to see all)`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
76
111
|
}
|
|
77
112
|
}
|
|
78
113
|
|
|
@@ -80,5 +115,6 @@ export async function showInfo(packageName: string, options: InfoOptions = {}):
|
|
|
80
115
|
console.log(chalk.dim(`Install with: omp install ${packageName}`));
|
|
81
116
|
} catch (err) {
|
|
82
117
|
console.log(chalk.red(`Error fetching info: ${(err as Error).message}`));
|
|
118
|
+
process.exitCode = 1;
|
|
83
119
|
}
|
|
84
120
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { PROJECT_PI_DIR, PROJECT_PLUGINS_JSON } from "@omp/paths";
|
|
3
4
|
import chalk from "chalk";
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format permission-related errors with actionable guidance
|
|
8
|
+
*/
|
|
9
|
+
function formatPermissionError(err: NodeJS.ErrnoException, path: string): string {
|
|
10
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
11
|
+
return `Permission denied: Cannot write to ${path}. Check directory permissions or run with appropriate privileges.`;
|
|
12
|
+
}
|
|
13
|
+
return err.message;
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
export interface InitOptions {
|
|
7
17
|
force?: boolean;
|
|
@@ -15,6 +25,7 @@ export async function initProject(options: InitOptions = {}): Promise<void> {
|
|
|
15
25
|
if (existsSync(PROJECT_PLUGINS_JSON) && !options.force) {
|
|
16
26
|
console.log(chalk.yellow(`${PROJECT_PLUGINS_JSON} already exists.`));
|
|
17
27
|
console.log(chalk.dim("Use --force to overwrite"));
|
|
28
|
+
process.exitCode = 1;
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
20
31
|
|
|
@@ -37,6 +48,13 @@ export async function initProject(options: InitOptions = {}): Promise<void> {
|
|
|
37
48
|
console.log(chalk.dim(" 2. Or edit plugins.json directly"));
|
|
38
49
|
console.log(chalk.dim(" 3. Run: omp install (to install all)"));
|
|
39
50
|
} catch (err) {
|
|
40
|
-
|
|
51
|
+
const error = err as NodeJS.ErrnoException;
|
|
52
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
53
|
+
console.log(chalk.red(formatPermissionError(error, PROJECT_PI_DIR)));
|
|
54
|
+
console.log(chalk.dim(" Check directory permissions or run with appropriate privileges."));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.red(`Error initializing project: ${error.message}`));
|
|
57
|
+
}
|
|
58
|
+
process.exitCode = 1;
|
|
41
59
|
}
|
|
42
60
|
}
|