@oh-my-pi/cli 0.3.0 → 0.5.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 +79 -84
- package/dist/cli.js +5025 -1016
- package/dist/commands/config.d.ts +27 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/features.d.ts.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install.d.ts +6 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/outdated.d.ts.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/uninstall.d.ts +3 -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.map +1 -1
- package/dist/conflicts.d.ts +7 -2
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lock.d.ts.map +1 -1
- package/dist/lockfile.d.ts +24 -3
- package/dist/lockfile.d.ts.map +1 -1
- package/dist/manifest.d.ts +12 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/npm.d.ts +11 -0
- package/dist/npm.d.ts.map +1 -1
- package/dist/output.d.ts +51 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/progress.d.ts +78 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/symlinks.d.ts +1 -0
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +24 -10
- package/.github/icon.png +0 -0
- package/.github/logo.png +0 -0
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/publish.yml +0 -42
- package/biome.json +0 -29
- package/bun.lock +0 -109
- package/plugins/exa/README.md +0 -153
- package/plugins/exa/package.json +0 -56
- package/plugins/exa/tools/exa/company.ts +0 -35
- package/plugins/exa/tools/exa/index.ts +0 -66
- package/plugins/exa/tools/exa/linkedin.ts +0 -35
- package/plugins/exa/tools/exa/researcher.ts +0 -40
- package/plugins/exa/tools/exa/runtime.json +0 -4
- package/plugins/exa/tools/exa/search.ts +0 -46
- package/plugins/exa/tools/exa/shared.ts +0 -230
- package/plugins/exa/tools/exa/websets.ts +0 -62
- package/plugins/metal-theme/README.md +0 -13
- package/plugins/metal-theme/omp.json +0 -8
- package/plugins/metal-theme/package.json +0 -19
- package/plugins/metal-theme/themes/metal.json +0 -79
- package/plugins/subagents/README.md +0 -25
- package/plugins/subagents/agents/explore.md +0 -71
- package/plugins/subagents/agents/planner.md +0 -51
- package/plugins/subagents/agents/reviewer.md +0 -53
- package/plugins/subagents/agents/task.md +0 -46
- package/plugins/subagents/commands/architect-plan.md +0 -9
- package/plugins/subagents/commands/implement-with-critic.md +0 -10
- package/plugins/subagents/commands/implement.md +0 -10
- package/plugins/subagents/omp.json +0 -15
- package/plugins/subagents/package.json +0 -26
- package/plugins/subagents/tools/task/index.ts +0 -1019
- package/plugins/user-prompt/README.md +0 -130
- package/plugins/user-prompt/package.json +0 -19
- package/plugins/user-prompt/tools/user-prompt/index.ts +0 -235
- package/scripts/bump-version.sh +0 -52
- package/scripts/publish.sh +0 -35
- package/src/cli.ts +0 -242
- package/src/commands/config.ts +0 -384
- package/src/commands/create.ts +0 -203
- package/src/commands/doctor.ts +0 -305
- package/src/commands/enable.ts +0 -122
- package/src/commands/env.ts +0 -38
- package/src/commands/features.ts +0 -295
- package/src/commands/info.ts +0 -120
- package/src/commands/init.ts +0 -60
- package/src/commands/install.ts +0 -700
- package/src/commands/link.ts +0 -159
- package/src/commands/list.ts +0 -186
- package/src/commands/outdated.ts +0 -87
- package/src/commands/search.ts +0 -77
- package/src/commands/uninstall.ts +0 -124
- package/src/commands/update.ts +0 -170
- package/src/commands/why.ts +0 -136
- package/src/conflicts.ts +0 -116
- package/src/errors.ts +0 -22
- package/src/index.ts +0 -46
- package/src/lock.ts +0 -46
- package/src/lockfile.ts +0 -132
- package/src/manifest.ts +0 -360
- package/src/npm.ts +0 -206
- package/src/paths.ts +0 -137
- package/src/runtime.ts +0 -116
- package/src/symlinks.ts +0 -455
- package/tsconfig.json +0 -28
package/src/commands/update.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { rm } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { loadPluginsJson, type OmpInstallEntry, type PluginPackageJson, readPluginPackageJson } from "@omp/manifest";
|
|
4
|
-
import { npmUpdate } from "@omp/npm";
|
|
5
|
-
import {
|
|
6
|
-
NODE_MODULES_DIR,
|
|
7
|
-
PI_CONFIG_DIR,
|
|
8
|
-
PLUGINS_DIR,
|
|
9
|
-
PROJECT_NODE_MODULES,
|
|
10
|
-
PROJECT_PI_DIR,
|
|
11
|
-
resolveScope,
|
|
12
|
-
} from "@omp/paths";
|
|
13
|
-
import { createPluginSymlinks, removePluginSymlinks } from "@omp/symlinks";
|
|
14
|
-
import chalk from "chalk";
|
|
15
|
-
|
|
16
|
-
export interface UpdateOptions {
|
|
17
|
-
global?: boolean;
|
|
18
|
-
local?: boolean;
|
|
19
|
-
json?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Update plugin(s) to latest within semver range
|
|
24
|
-
*/
|
|
25
|
-
export async function updatePlugin(name?: string, options: UpdateOptions = {}): Promise<void> {
|
|
26
|
-
const isGlobal = resolveScope(options);
|
|
27
|
-
const prefix = isGlobal ? PLUGINS_DIR : ".pi";
|
|
28
|
-
const _nodeModules = isGlobal ? NODE_MODULES_DIR : PROJECT_NODE_MODULES;
|
|
29
|
-
|
|
30
|
-
const pluginsJson = await loadPluginsJson(isGlobal);
|
|
31
|
-
const pluginNames = Object.keys(pluginsJson.plugins);
|
|
32
|
-
|
|
33
|
-
if (pluginNames.length === 0) {
|
|
34
|
-
console.log(chalk.yellow("No plugins installed."));
|
|
35
|
-
process.exitCode = 1;
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// If specific plugin name provided, verify it's installed
|
|
40
|
-
if (name && !pluginsJson.plugins[name]) {
|
|
41
|
-
console.log(chalk.yellow(`Plugin "${name}" is not installed.`));
|
|
42
|
-
process.exitCode = 1;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const toUpdate = name ? [name] : pluginNames;
|
|
47
|
-
|
|
48
|
-
// Filter out file: dependencies (local plugins)
|
|
49
|
-
const npmPlugins = toUpdate.filter((n) => {
|
|
50
|
-
const version = pluginsJson.plugins[n];
|
|
51
|
-
return !version.startsWith("file:");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const localPlugins = toUpdate.filter((n) => {
|
|
55
|
-
const version = pluginsJson.plugins[n];
|
|
56
|
-
return version.startsWith("file:");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (localPlugins.length > 0) {
|
|
60
|
-
console.log(chalk.dim(`Skipping ${localPlugins.length} local plugin(s): ${localPlugins.join(", ")}`));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (npmPlugins.length === 0) {
|
|
64
|
-
console.log(chalk.yellow("No npm plugins to update."));
|
|
65
|
-
process.exitCode = 1;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log(chalk.blue(`Updating ${npmPlugins.length} plugin(s)...`));
|
|
70
|
-
|
|
71
|
-
const results: Array<{ name: string; from: string; to: string; success: boolean }> = [];
|
|
72
|
-
|
|
73
|
-
// Save old package info before removing symlinks (for recovery on failure)
|
|
74
|
-
const oldPkgJsons = new Map<string, PluginPackageJson>();
|
|
75
|
-
const beforeVersions: Record<string, string> = {};
|
|
76
|
-
const oldInstallEntries = new Map<string, OmpInstallEntry[]>();
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
// Get current versions and install entries before update
|
|
80
|
-
for (const pluginName of npmPlugins) {
|
|
81
|
-
const pkgJson = await readPluginPackageJson(pluginName, isGlobal);
|
|
82
|
-
if (pkgJson) {
|
|
83
|
-
oldPkgJsons.set(pluginName, pkgJson);
|
|
84
|
-
beforeVersions[pluginName] = pkgJson.version;
|
|
85
|
-
|
|
86
|
-
// Save old install entries for later comparison
|
|
87
|
-
if (pkgJson.omp?.install) {
|
|
88
|
-
oldInstallEntries.set(pluginName, [...pkgJson.omp.install]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Remove old symlinks before update
|
|
92
|
-
await removePluginSymlinks(pluginName, pkgJson, isGlobal);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// npm update
|
|
97
|
-
await npmUpdate(npmPlugins, prefix);
|
|
98
|
-
|
|
99
|
-
// Base directory for symlink destinations
|
|
100
|
-
const baseDir = isGlobal ? PI_CONFIG_DIR : PROJECT_PI_DIR;
|
|
101
|
-
|
|
102
|
-
// Re-process symlinks for each updated plugin
|
|
103
|
-
for (const pluginName of npmPlugins) {
|
|
104
|
-
const pkgJson = await readPluginPackageJson(pluginName, isGlobal);
|
|
105
|
-
if (pkgJson) {
|
|
106
|
-
const beforeVersion = beforeVersions[pluginName] || "unknown";
|
|
107
|
-
const afterVersion = pkgJson.version;
|
|
108
|
-
|
|
109
|
-
// Handle changed omp.install entries: remove orphaned symlinks
|
|
110
|
-
const oldEntries = oldInstallEntries.get(pluginName) || [];
|
|
111
|
-
const newEntries = pkgJson.omp?.install || [];
|
|
112
|
-
const newDests = new Set(newEntries.map((e) => e.dest));
|
|
113
|
-
|
|
114
|
-
for (const oldEntry of oldEntries) {
|
|
115
|
-
if (!newDests.has(oldEntry.dest)) {
|
|
116
|
-
// This destination was in the old version but not in the new one
|
|
117
|
-
const dest = join(baseDir, oldEntry.dest);
|
|
118
|
-
try {
|
|
119
|
-
await rm(dest, { force: true });
|
|
120
|
-
console.log(chalk.dim(` Removed orphaned: ${oldEntry.dest}`));
|
|
121
|
-
} catch {
|
|
122
|
-
// Ignore removal errors for orphaned symlinks
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Create new symlinks (handles overwrites for existing destinations)
|
|
128
|
-
await createPluginSymlinks(pluginName, pkgJson, isGlobal);
|
|
129
|
-
|
|
130
|
-
const changed = beforeVersion !== afterVersion;
|
|
131
|
-
if (changed) {
|
|
132
|
-
console.log(chalk.green(` ✓ ${pluginName}: ${beforeVersion} → ${afterVersion}`));
|
|
133
|
-
} else {
|
|
134
|
-
console.log(chalk.dim(` · ${pluginName}: ${afterVersion} (already latest)`));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
results.push({
|
|
138
|
-
name: pluginName,
|
|
139
|
-
from: beforeVersion,
|
|
140
|
-
to: afterVersion,
|
|
141
|
-
success: true,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const updated = results.filter((r) => r.from !== r.to);
|
|
147
|
-
console.log();
|
|
148
|
-
console.log(chalk.dim(`Updated: ${updated.length}, Already latest: ${results.length - updated.length}`));
|
|
149
|
-
|
|
150
|
-
if (options.json) {
|
|
151
|
-
console.log(JSON.stringify({ results }, null, 2));
|
|
152
|
-
}
|
|
153
|
-
} catch (err) {
|
|
154
|
-
// Restore old symlinks on failure
|
|
155
|
-
if (oldPkgJsons.size > 0) {
|
|
156
|
-
console.log(chalk.yellow(" Update failed, restoring symlinks..."));
|
|
157
|
-
for (const [pluginName, pkgJson] of oldPkgJsons) {
|
|
158
|
-
try {
|
|
159
|
-
await createPluginSymlinks(pluginName, pkgJson, isGlobal);
|
|
160
|
-
} catch (restoreErr) {
|
|
161
|
-
console.log(
|
|
162
|
-
chalk.red(` Failed to restore symlinks for ${pluginName}: ${(restoreErr as Error).message}`),
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
console.log(chalk.red(`Error updating plugins: ${(err as Error).message}`));
|
|
168
|
-
process.exitCode = 1;
|
|
169
|
-
}
|
|
170
|
-
}
|
package/src/commands/why.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { existsSync, lstatSync } from "node:fs";
|
|
2
|
-
import { readlink } from "node:fs/promises";
|
|
3
|
-
import { join, relative, resolve } from "node:path";
|
|
4
|
-
import { getInstalledPlugins, getPluginSourceDir, readPluginPackageJson } from "@omp/manifest";
|
|
5
|
-
import { PI_CONFIG_DIR, PROJECT_PI_DIR, resolveScope } from "@omp/paths";
|
|
6
|
-
import { traceInstalledFile } from "@omp/symlinks";
|
|
7
|
-
import chalk from "chalk";
|
|
8
|
-
|
|
9
|
-
export interface WhyOptions {
|
|
10
|
-
global?: boolean;
|
|
11
|
-
local?: boolean;
|
|
12
|
-
json?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Show which plugin installed a file
|
|
17
|
-
*/
|
|
18
|
-
export async function whyFile(filePath: string, options: WhyOptions = {}): Promise<void> {
|
|
19
|
-
const isGlobal = resolveScope(options);
|
|
20
|
-
|
|
21
|
-
// Determine the base directory based on scope
|
|
22
|
-
const baseDir = isGlobal ? PI_CONFIG_DIR : resolve(PROJECT_PI_DIR);
|
|
23
|
-
|
|
24
|
-
// Normalize path - make it relative to the appropriate base directory
|
|
25
|
-
let relativePath = filePath;
|
|
26
|
-
if (isGlobal) {
|
|
27
|
-
if (filePath.startsWith(PI_CONFIG_DIR)) {
|
|
28
|
-
relativePath = relative(PI_CONFIG_DIR, filePath);
|
|
29
|
-
} else if (filePath.startsWith("~/.pi/")) {
|
|
30
|
-
relativePath = filePath.slice(6); // Remove ~/.pi/
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
// Project-local mode
|
|
34
|
-
const resolvedProjectDir = resolve(PROJECT_PI_DIR);
|
|
35
|
-
if (filePath.startsWith(resolvedProjectDir)) {
|
|
36
|
-
relativePath = relative(resolvedProjectDir, filePath);
|
|
37
|
-
} else if (filePath.startsWith(".pi/")) {
|
|
38
|
-
relativePath = filePath.slice(4); // Remove .pi/
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check if it's a path in agent/ directory
|
|
43
|
-
if (!relativePath.startsWith("agent/")) {
|
|
44
|
-
// Try prepending agent/
|
|
45
|
-
const withAgent = `agent/${relativePath}`;
|
|
46
|
-
const fullWithAgent = join(baseDir, withAgent);
|
|
47
|
-
if (existsSync(fullWithAgent)) {
|
|
48
|
-
relativePath = withAgent;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const fullPath = join(baseDir, relativePath);
|
|
53
|
-
|
|
54
|
-
// Check if file exists
|
|
55
|
-
if (!existsSync(fullPath)) {
|
|
56
|
-
console.log(chalk.yellow(`File not found: ${fullPath}`));
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check if it's a symlink
|
|
62
|
-
const stats = lstatSync(fullPath);
|
|
63
|
-
const isSymlink = stats.isSymbolicLink();
|
|
64
|
-
|
|
65
|
-
let target: string | null = null;
|
|
66
|
-
if (isSymlink) {
|
|
67
|
-
target = await readlink(fullPath);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Search through installed plugins
|
|
71
|
-
const installedPlugins = await getInstalledPlugins(isGlobal);
|
|
72
|
-
const result = await traceInstalledFile(relativePath, installedPlugins, isGlobal);
|
|
73
|
-
|
|
74
|
-
if (options.json) {
|
|
75
|
-
console.log(
|
|
76
|
-
JSON.stringify(
|
|
77
|
-
{
|
|
78
|
-
path: relativePath,
|
|
79
|
-
fullPath,
|
|
80
|
-
isSymlink,
|
|
81
|
-
target,
|
|
82
|
-
plugin: result?.plugin || null,
|
|
83
|
-
source: result?.entry.src || null,
|
|
84
|
-
},
|
|
85
|
-
null,
|
|
86
|
-
2,
|
|
87
|
-
),
|
|
88
|
-
);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(chalk.bold(`File: ${relativePath}`));
|
|
93
|
-
console.log(chalk.dim(`Full path: ${fullPath}`));
|
|
94
|
-
console.log();
|
|
95
|
-
|
|
96
|
-
if (isSymlink && target) {
|
|
97
|
-
console.log(`${chalk.dim("Type: ")}symlink`);
|
|
98
|
-
console.log(chalk.dim("Target: ") + target);
|
|
99
|
-
console.log();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (result) {
|
|
103
|
-
// Verify it's actually a symlink pointing to the right place
|
|
104
|
-
if (!isSymlink) {
|
|
105
|
-
console.log(chalk.yellow("⚠ This file exists but is not a symlink"));
|
|
106
|
-
console.log(chalk.dim(" It may have been manually created or the symlink was replaced."));
|
|
107
|
-
console.log(chalk.dim(` Expected to be installed by: ${result.plugin}`));
|
|
108
|
-
} else {
|
|
109
|
-
// Verify symlink points to correct source
|
|
110
|
-
const expectedSrc = join(getPluginSourceDir(result.plugin, isGlobal), result.entry.src);
|
|
111
|
-
if (target !== expectedSrc) {
|
|
112
|
-
console.log(chalk.yellow("⚠ Symlink target does not match expected source"));
|
|
113
|
-
console.log(chalk.dim(` Expected: ${expectedSrc}`));
|
|
114
|
-
console.log(chalk.dim(` Actual: ${target}`));
|
|
115
|
-
console.log(chalk.dim(` Expected to be installed by: ${result.plugin}`));
|
|
116
|
-
} else {
|
|
117
|
-
console.log(chalk.green(`✓ Installed by: ${result.plugin}`));
|
|
118
|
-
console.log(chalk.dim(` Source: ${result.entry.src}`));
|
|
119
|
-
console.log(chalk.dim(` Dest: ${result.entry.dest}`));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Get plugin info
|
|
124
|
-
const pkgJson = await readPluginPackageJson(result.plugin, isGlobal);
|
|
125
|
-
if (pkgJson) {
|
|
126
|
-
console.log();
|
|
127
|
-
console.log(chalk.dim(`Plugin version: ${pkgJson.version}`));
|
|
128
|
-
if (pkgJson.description) {
|
|
129
|
-
console.log(chalk.dim(`Description: ${pkgJson.description}`));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
console.log(chalk.yellow("⚠ Not installed by any tracked plugin"));
|
|
134
|
-
console.log(chalk.dim(" This file may have been created manually or by a plugin that was uninstalled."));
|
|
135
|
-
}
|
|
136
|
-
}
|
package/src/conflicts.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { PluginPackageJson } from "@omp/manifest";
|
|
2
|
-
|
|
3
|
-
export interface Conflict {
|
|
4
|
-
dest: string;
|
|
5
|
-
plugins: Array<{ name: string; src: string }>;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface IntraPluginDuplicate {
|
|
9
|
-
dest: string;
|
|
10
|
-
sources: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Detect duplicate destinations within a single plugin's omp.install array
|
|
15
|
-
*/
|
|
16
|
-
export function detectIntraPluginDuplicates(pkgJson: PluginPackageJson): IntraPluginDuplicate[] {
|
|
17
|
-
const duplicates: IntraPluginDuplicate[] = [];
|
|
18
|
-
|
|
19
|
-
if (!pkgJson.omp?.install?.length) {
|
|
20
|
-
return duplicates;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const destMap = new Map<string, string[]>();
|
|
24
|
-
|
|
25
|
-
for (const entry of pkgJson.omp.install) {
|
|
26
|
-
const sources = destMap.get(entry.dest) || [];
|
|
27
|
-
sources.push(entry.src);
|
|
28
|
-
destMap.set(entry.dest, sources);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
for (const [dest, sources] of destMap) {
|
|
32
|
-
if (sources.length > 1) {
|
|
33
|
-
duplicates.push({ dest, sources });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return duplicates;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Detect conflicts between a new plugin and existing plugins
|
|
42
|
-
*/
|
|
43
|
-
export function detectConflicts(
|
|
44
|
-
newPluginName: string,
|
|
45
|
-
newPkgJson: PluginPackageJson,
|
|
46
|
-
existingPlugins: Map<string, PluginPackageJson>,
|
|
47
|
-
): Conflict[] {
|
|
48
|
-
const conflicts: Conflict[] = [];
|
|
49
|
-
|
|
50
|
-
if (!newPkgJson.omp?.install?.length) {
|
|
51
|
-
return conflicts;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Build a map of existing destinations
|
|
55
|
-
const destMap = new Map<string, Array<{ name: string; src: string }>>();
|
|
56
|
-
|
|
57
|
-
for (const [name, pkgJson] of existingPlugins) {
|
|
58
|
-
if (pkgJson.omp?.install) {
|
|
59
|
-
for (const entry of pkgJson.omp.install) {
|
|
60
|
-
const existing = destMap.get(entry.dest) || [];
|
|
61
|
-
existing.push({ name, src: entry.src });
|
|
62
|
-
destMap.set(entry.dest, existing);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check new plugin's destinations
|
|
68
|
-
for (const entry of newPkgJson.omp.install) {
|
|
69
|
-
const existing = destMap.get(entry.dest);
|
|
70
|
-
if (existing && existing.length > 0) {
|
|
71
|
-
conflicts.push({
|
|
72
|
-
dest: entry.dest,
|
|
73
|
-
plugins: [...existing, { name: newPluginName, src: entry.src }],
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return conflicts;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Detect all conflicts among a set of plugins
|
|
83
|
-
*/
|
|
84
|
-
export function detectAllConflicts(plugins: Map<string, PluginPackageJson>): Conflict[] {
|
|
85
|
-
const conflicts: Conflict[] = [];
|
|
86
|
-
const destMap = new Map<string, Array<{ name: string; src: string }>>();
|
|
87
|
-
|
|
88
|
-
for (const [name, pkgJson] of plugins) {
|
|
89
|
-
if (pkgJson.omp?.install) {
|
|
90
|
-
for (const entry of pkgJson.omp.install) {
|
|
91
|
-
const existing = destMap.get(entry.dest) || [];
|
|
92
|
-
existing.push({ name, src: entry.src });
|
|
93
|
-
destMap.set(entry.dest, existing);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Find destinations with multiple sources
|
|
99
|
-
for (const [dest, sources] of destMap) {
|
|
100
|
-
if (sources.length > 1) {
|
|
101
|
-
conflicts.push({ dest, plugins: sources });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return conflicts;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Format conflicts for display
|
|
110
|
-
*/
|
|
111
|
-
export function formatConflicts(conflicts: Conflict[]): string[] {
|
|
112
|
-
return conflicts.map((conflict) => {
|
|
113
|
-
const plugins = conflict.plugins.map((p) => p.name).join(" and ");
|
|
114
|
-
return `${plugins} both install ${conflict.dest}`;
|
|
115
|
-
});
|
|
116
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Wraps a command function with consistent error handling.
|
|
5
|
-
* - Catches errors and logs user-friendly messages
|
|
6
|
-
* - Shows stack trace only when DEBUG env var is set
|
|
7
|
-
* - Sets non-zero exit code on error
|
|
8
|
-
*/
|
|
9
|
-
export function withErrorHandling<T extends (...args: any[]) => Promise<void>>(fn: T): T {
|
|
10
|
-
return (async (...args: any[]) => {
|
|
11
|
-
try {
|
|
12
|
-
await fn(...args);
|
|
13
|
-
} catch (err) {
|
|
14
|
-
const error = err as Error;
|
|
15
|
-
console.log(chalk.red(`Error: ${error.message}`));
|
|
16
|
-
if (process.env.DEBUG) {
|
|
17
|
-
console.log(chalk.dim(error.stack));
|
|
18
|
-
}
|
|
19
|
-
process.exitCode = 1;
|
|
20
|
-
}
|
|
21
|
-
}) as T;
|
|
22
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export { createPlugin } from "@omp/commands/create";
|
|
2
|
-
export { runDoctor } from "@omp/commands/doctor";
|
|
3
|
-
export { disablePlugin, enablePlugin } from "@omp/commands/enable";
|
|
4
|
-
export { showInfo } from "@omp/commands/info";
|
|
5
|
-
export { initProject } from "@omp/commands/init";
|
|
6
|
-
export { installPlugin } from "@omp/commands/install";
|
|
7
|
-
export { linkPlugin } from "@omp/commands/link";
|
|
8
|
-
export { listPlugins } from "@omp/commands/list";
|
|
9
|
-
export { showOutdated } from "@omp/commands/outdated";
|
|
10
|
-
export { searchPlugins } from "@omp/commands/search";
|
|
11
|
-
export { uninstallPlugin } from "@omp/commands/uninstall";
|
|
12
|
-
export { updatePlugin } from "@omp/commands/update";
|
|
13
|
-
export { whyFile } from "@omp/commands/why";
|
|
14
|
-
export {
|
|
15
|
-
detectAllConflicts,
|
|
16
|
-
detectConflicts,
|
|
17
|
-
formatConflicts,
|
|
18
|
-
} from "@omp/conflicts";
|
|
19
|
-
|
|
20
|
-
export type {
|
|
21
|
-
OmpField,
|
|
22
|
-
OmpInstallEntry,
|
|
23
|
-
PluginPackageJson,
|
|
24
|
-
PluginsJson,
|
|
25
|
-
} from "@omp/manifest";
|
|
26
|
-
|
|
27
|
-
export {
|
|
28
|
-
getInstalledPlugins,
|
|
29
|
-
initGlobalPlugins,
|
|
30
|
-
loadPluginsJson,
|
|
31
|
-
readPluginPackageJson,
|
|
32
|
-
savePluginsJson,
|
|
33
|
-
} from "@omp/manifest";
|
|
34
|
-
export {
|
|
35
|
-
npmInfo,
|
|
36
|
-
npmInstall,
|
|
37
|
-
npmOutdated,
|
|
38
|
-
npmSearch,
|
|
39
|
-
npmUninstall,
|
|
40
|
-
npmUpdate,
|
|
41
|
-
} from "@omp/npm";
|
|
42
|
-
export {
|
|
43
|
-
checkPluginSymlinks,
|
|
44
|
-
createPluginSymlinks,
|
|
45
|
-
removePluginSymlinks,
|
|
46
|
-
} from "@omp/symlinks";
|
package/src/lock.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { PI_CONFIG_DIR, PROJECT_PI_DIR } from "@omp/paths";
|
|
5
|
-
|
|
6
|
-
const LOCK_TIMEOUT_MS = 60000; // 1 minute
|
|
7
|
-
|
|
8
|
-
export async function acquireLock(global = true): Promise<boolean> {
|
|
9
|
-
const lockPath = global ? join(PI_CONFIG_DIR, ".lock") : join(PROJECT_PI_DIR, ".lock");
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
await mkdir(dirname(lockPath), { recursive: true });
|
|
13
|
-
|
|
14
|
-
// Check for existing lock
|
|
15
|
-
if (existsSync(lockPath)) {
|
|
16
|
-
const content = await readFile(lockPath, "utf-8");
|
|
17
|
-
const { pid, timestamp } = JSON.parse(content);
|
|
18
|
-
|
|
19
|
-
// Check if stale (older than timeout)
|
|
20
|
-
if (Date.now() - timestamp > LOCK_TIMEOUT_MS) {
|
|
21
|
-
// Stale lock, remove it
|
|
22
|
-
await rm(lockPath, { force: true });
|
|
23
|
-
} else {
|
|
24
|
-
// Check if process is still alive
|
|
25
|
-
try {
|
|
26
|
-
process.kill(pid, 0); // Signal 0 = check existence
|
|
27
|
-
return false; // Process alive, can't acquire
|
|
28
|
-
} catch {
|
|
29
|
-
// Process dead, remove stale lock
|
|
30
|
-
await rm(lockPath, { force: true });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Create lock
|
|
36
|
-
await writeFile(lockPath, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
37
|
-
return true;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function releaseLock(global = true): Promise<void> {
|
|
44
|
-
const lockPath = global ? join(PI_CONFIG_DIR, ".lock") : join(PROJECT_PI_DIR, ".lock");
|
|
45
|
-
await rm(lockPath, { force: true });
|
|
46
|
-
}
|
package/src/lockfile.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { GLOBAL_LOCK_FILE, PROJECT_PLUGINS_LOCK } from "@omp/paths";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Lock file schema version
|
|
8
|
-
*/
|
|
9
|
-
export const LOCKFILE_VERSION = 1;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Package entry in the lock file
|
|
13
|
-
*/
|
|
14
|
-
export interface LockFilePackage {
|
|
15
|
-
version: string;
|
|
16
|
-
resolved?: string;
|
|
17
|
-
integrity?: string;
|
|
18
|
-
dependencies?: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Lock file structure
|
|
23
|
-
*/
|
|
24
|
-
export interface LockFile {
|
|
25
|
-
lockfileVersion: number;
|
|
26
|
-
packages: Record<string, LockFilePackage>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Load and validate a lock file.
|
|
31
|
-
*
|
|
32
|
-
* Returns null if:
|
|
33
|
-
* - File doesn't exist
|
|
34
|
-
* - File contains invalid JSON (corrupted)
|
|
35
|
-
* - File has invalid/incompatible schema
|
|
36
|
-
*/
|
|
37
|
-
export async function loadLockFile(global = true): Promise<LockFile | null> {
|
|
38
|
-
const path = global ? GLOBAL_LOCK_FILE : PROJECT_PLUGINS_LOCK;
|
|
39
|
-
try {
|
|
40
|
-
if (!existsSync(path)) return null;
|
|
41
|
-
const data = await readFile(path, "utf-8");
|
|
42
|
-
const parsed = JSON.parse(data);
|
|
43
|
-
|
|
44
|
-
// Validate schema
|
|
45
|
-
if (typeof parsed.lockfileVersion !== "number" || typeof parsed.packages !== "object") {
|
|
46
|
-
console.log(chalk.yellow(`Warning: ${path} has invalid schema, ignoring`));
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check for incompatible version
|
|
51
|
-
if (parsed.lockfileVersion > LOCKFILE_VERSION) {
|
|
52
|
-
console.log(
|
|
53
|
-
chalk.yellow(
|
|
54
|
-
`Warning: ${path} was created by a newer version of omp (lockfile v${parsed.lockfileVersion}), ignoring`,
|
|
55
|
-
),
|
|
56
|
-
);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return parsed as LockFile;
|
|
61
|
-
} catch (err) {
|
|
62
|
-
if ((err as Error).name === "SyntaxError") {
|
|
63
|
-
console.log(chalk.yellow(`Warning: ${path} is corrupted (invalid JSON), ignoring`));
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Save lock file
|
|
71
|
-
*/
|
|
72
|
-
export async function saveLockFile(lockFile: LockFile, global = true): Promise<void> {
|
|
73
|
-
const path = global ? GLOBAL_LOCK_FILE : PROJECT_PLUGINS_LOCK;
|
|
74
|
-
await writeFile(path, JSON.stringify(lockFile, null, 2));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Create a new empty lock file
|
|
79
|
-
*/
|
|
80
|
-
export function createLockFile(): LockFile {
|
|
81
|
-
return {
|
|
82
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
83
|
-
packages: {},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Validate and optionally regenerate a corrupted lock file.
|
|
89
|
-
*
|
|
90
|
-
* @returns The loaded lock file, a new empty lock file if corrupted/missing, or null if validation fails
|
|
91
|
-
*/
|
|
92
|
-
export async function validateOrRegenerateLockFile(global = true): Promise<LockFile> {
|
|
93
|
-
const existing = await loadLockFile(global);
|
|
94
|
-
if (existing) {
|
|
95
|
-
return existing;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Lock file is missing or corrupted - create a fresh one
|
|
99
|
-
const path = global ? GLOBAL_LOCK_FILE : PROJECT_PLUGINS_LOCK;
|
|
100
|
-
if (existsSync(path)) {
|
|
101
|
-
console.log(chalk.yellow(`Regenerating corrupted lock file: ${path}`));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return createLockFile();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get the locked version for a package, if it exists in the lock file.
|
|
109
|
-
*/
|
|
110
|
-
export async function getLockedVersion(packageName: string, global = true): Promise<string | null> {
|
|
111
|
-
const lockFile = await loadLockFile(global);
|
|
112
|
-
if (!lockFile) return null;
|
|
113
|
-
|
|
114
|
-
const entry = lockFile.packages[packageName];
|
|
115
|
-
return entry?.version ?? null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Update the lock file with a package's exact version.
|
|
120
|
-
*/
|
|
121
|
-
export async function updateLockFile(packageName: string, version: string, global = true): Promise<void> {
|
|
122
|
-
let lockFile = await loadLockFile(global);
|
|
123
|
-
if (!lockFile) {
|
|
124
|
-
lockFile = createLockFile();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
lockFile.packages[packageName] = {
|
|
128
|
-
version,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
await saveLockFile(lockFile, global);
|
|
132
|
-
}
|