@oh-my-pi/cli 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/.github/workflows/ci.yml +32 -0
- package/.github/workflows/publish.yml +42 -0
- package/CHECK.md +352 -0
- package/README.md +224 -0
- package/biome.json +29 -0
- package/bun.lock +50 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3941 -0
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/enable.d.ts +13 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/info.d.ts +9 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/install.d.ts +13 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/link.d.ts +10 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/list.d.ts +9 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/outdated.d.ts +9 -0
- package/dist/commands/outdated.d.ts.map +1 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/uninstall.d.ts +9 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/why.d.ts +9 -0
- package/dist/commands/why.d.ts.map +1 -0
- package/dist/conflicts.d.ts +21 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/manifest.d.ts +81 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/npm.d.ts +77 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/symlinks.d.ts +33 -0
- package/dist/symlinks.d.ts.map +1 -0
- package/package.json +36 -0
- package/plugins/metal-theme/README.md +13 -0
- package/plugins/metal-theme/omp.json +8 -0
- package/plugins/metal-theme/package.json +14 -0
- package/plugins/metal-theme/themes/metal.json +79 -0
- package/plugins/subagents/README.md +25 -0
- package/plugins/subagents/agents/explore.md +71 -0
- package/plugins/subagents/agents/planner.md +51 -0
- package/plugins/subagents/agents/reviewer.md +53 -0
- package/plugins/subagents/agents/task.md +46 -0
- package/plugins/subagents/commands/architect-plan.md +9 -0
- package/plugins/subagents/commands/implement-with-critic.md +10 -0
- package/plugins/subagents/commands/implement.md +10 -0
- package/plugins/subagents/omp.json +15 -0
- package/plugins/subagents/package.json +21 -0
- package/plugins/subagents/tools/task/index.ts +1019 -0
- package/scripts/bump-version.sh +52 -0
- package/scripts/publish.sh +35 -0
- package/src/cli.ts +167 -0
- package/src/commands/create.ts +153 -0
- package/src/commands/doctor.ts +217 -0
- package/src/commands/enable.ts +105 -0
- package/src/commands/info.ts +84 -0
- package/src/commands/init.ts +42 -0
- package/src/commands/install.ts +327 -0
- package/src/commands/link.ts +108 -0
- package/src/commands/list.ts +71 -0
- package/src/commands/outdated.ts +76 -0
- package/src/commands/search.ts +60 -0
- package/src/commands/uninstall.ts +73 -0
- package/src/commands/update.ts +112 -0
- package/src/commands/why.ts +105 -0
- package/src/conflicts.ts +84 -0
- package/src/index.ts +53 -0
- package/src/manifest.ts +212 -0
- package/src/migrate.ts +181 -0
- package/src/npm.ts +150 -0
- package/src/paths.ts +72 -0
- package/src/symlinks.ts +199 -0
- package/tsconfig.json +24 -0
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
GLOBAL_PACKAGE_JSON,
|
|
6
|
+
LEGACY_MANIFEST_PATH,
|
|
7
|
+
NODE_MODULES_DIR,
|
|
8
|
+
PLUGINS_DIR,
|
|
9
|
+
PROJECT_PLUGINS_JSON,
|
|
10
|
+
} from "./paths.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* OMP field in package.json - defines what files to install
|
|
14
|
+
*/
|
|
15
|
+
export interface OmpInstallEntry {
|
|
16
|
+
src: string;
|
|
17
|
+
dest: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OmpField {
|
|
21
|
+
install?: OmpInstallEntry[];
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Package.json structure for plugins
|
|
27
|
+
*/
|
|
28
|
+
export interface PluginPackageJson {
|
|
29
|
+
name: string;
|
|
30
|
+
version: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
keywords?: string[];
|
|
33
|
+
omp?: OmpField;
|
|
34
|
+
dependencies?: Record<string, string>;
|
|
35
|
+
devDependencies?: Record<string, string>;
|
|
36
|
+
files?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Global/project plugins.json structure
|
|
41
|
+
*/
|
|
42
|
+
export interface PluginsJson {
|
|
43
|
+
plugins: Record<string, string>; // name -> version specifier
|
|
44
|
+
disabled?: string[]; // disabled plugin names
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Legacy manifest structure (for migration)
|
|
49
|
+
*/
|
|
50
|
+
export interface LegacyPluginInfo {
|
|
51
|
+
type: "github" | "local" | "npm";
|
|
52
|
+
repo?: string;
|
|
53
|
+
package?: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
subdir?: string;
|
|
56
|
+
version?: string;
|
|
57
|
+
linked?: boolean;
|
|
58
|
+
installed: string[];
|
|
59
|
+
installedAt: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LegacyManifest {
|
|
63
|
+
plugins: Record<string, LegacyPluginInfo>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initialize the global plugins directory with package.json
|
|
68
|
+
*/
|
|
69
|
+
export async function initGlobalPlugins(): Promise<void> {
|
|
70
|
+
await mkdir(PLUGINS_DIR, { recursive: true });
|
|
71
|
+
|
|
72
|
+
if (!existsSync(GLOBAL_PACKAGE_JSON)) {
|
|
73
|
+
const packageJson = {
|
|
74
|
+
name: "pi-plugins",
|
|
75
|
+
version: "1.0.0",
|
|
76
|
+
private: true,
|
|
77
|
+
description: "Global pi plugins managed by omp",
|
|
78
|
+
dependencies: {},
|
|
79
|
+
};
|
|
80
|
+
await writeFile(GLOBAL_PACKAGE_JSON, JSON.stringify(packageJson, null, 2));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load plugins.json (global or project)
|
|
86
|
+
*/
|
|
87
|
+
export async function loadPluginsJson(global = true): Promise<PluginsJson> {
|
|
88
|
+
const path = global ? GLOBAL_PACKAGE_JSON : PROJECT_PLUGINS_JSON;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const data = await readFile(path, "utf-8");
|
|
92
|
+
const parsed = JSON.parse(data);
|
|
93
|
+
|
|
94
|
+
if (global) {
|
|
95
|
+
// Global uses standard package.json format
|
|
96
|
+
return {
|
|
97
|
+
plugins: parsed.dependencies || {},
|
|
98
|
+
disabled: parsed.omp?.disabled || [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Project uses plugins.json format
|
|
103
|
+
return {
|
|
104
|
+
plugins: parsed.plugins || {},
|
|
105
|
+
disabled: parsed.disabled || [],
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
109
|
+
return { plugins: {}, disabled: [] };
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save plugins.json (global or project)
|
|
117
|
+
*/
|
|
118
|
+
export async function savePluginsJson(data: PluginsJson, global = true): Promise<void> {
|
|
119
|
+
const path = global ? GLOBAL_PACKAGE_JSON : PROJECT_PLUGINS_JSON;
|
|
120
|
+
await mkdir(dirname(path), { recursive: true });
|
|
121
|
+
|
|
122
|
+
if (global) {
|
|
123
|
+
// Read existing package.json and update dependencies
|
|
124
|
+
let existing: Record<string, unknown> = {};
|
|
125
|
+
try {
|
|
126
|
+
existing = JSON.parse(await readFile(path, "utf-8"));
|
|
127
|
+
} catch {
|
|
128
|
+
existing = {
|
|
129
|
+
name: "pi-plugins",
|
|
130
|
+
version: "1.0.0",
|
|
131
|
+
private: true,
|
|
132
|
+
description: "Global pi plugins managed by omp",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
existing.dependencies = data.plugins;
|
|
137
|
+
if (data.disabled?.length) {
|
|
138
|
+
existing.omp = { ...((existing.omp as Record<string, unknown>) || {}), disabled: data.disabled };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await writeFile(path, JSON.stringify(existing, null, 2));
|
|
142
|
+
} else {
|
|
143
|
+
// Project uses simple plugins.json format
|
|
144
|
+
await writeFile(path, JSON.stringify(data, null, 2));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Read a plugin's package.json from node_modules
|
|
150
|
+
*/
|
|
151
|
+
export async function readPluginPackageJson(pluginName: string, global = true): Promise<PluginPackageJson | null> {
|
|
152
|
+
const nodeModules = global ? NODE_MODULES_DIR : ".pi/node_modules";
|
|
153
|
+
let pkgPath: string;
|
|
154
|
+
|
|
155
|
+
// Handle scoped packages
|
|
156
|
+
if (pluginName.startsWith("@")) {
|
|
157
|
+
pkgPath = join(nodeModules, pluginName, "package.json");
|
|
158
|
+
} else {
|
|
159
|
+
pkgPath = join(nodeModules, pluginName, "package.json");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const data = await readFile(pkgPath, "utf-8");
|
|
164
|
+
return JSON.parse(data) as PluginPackageJson;
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the source directory for a plugin in node_modules
|
|
172
|
+
*/
|
|
173
|
+
export function getPluginSourceDir(pluginName: string, global = true): string {
|
|
174
|
+
const nodeModules = global ? NODE_MODULES_DIR : ".pi/node_modules";
|
|
175
|
+
return join(nodeModules, pluginName);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if legacy manifest.json exists
|
|
180
|
+
*/
|
|
181
|
+
export function hasLegacyManifest(): boolean {
|
|
182
|
+
return existsSync(LEGACY_MANIFEST_PATH);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Load legacy manifest.json
|
|
187
|
+
*/
|
|
188
|
+
export async function loadLegacyManifest(): Promise<LegacyManifest> {
|
|
189
|
+
try {
|
|
190
|
+
const data = await readFile(LEGACY_MANIFEST_PATH, "utf-8");
|
|
191
|
+
return JSON.parse(data) as LegacyManifest;
|
|
192
|
+
} catch {
|
|
193
|
+
return { plugins: {} };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get all installed plugins with their info
|
|
199
|
+
*/
|
|
200
|
+
export async function getInstalledPlugins(global = true): Promise<Map<string, PluginPackageJson>> {
|
|
201
|
+
const pluginsJson = await loadPluginsJson(global);
|
|
202
|
+
const plugins = new Map<string, PluginPackageJson>();
|
|
203
|
+
|
|
204
|
+
for (const name of Object.keys(pluginsJson.plugins)) {
|
|
205
|
+
const pkgJson = await readPluginPackageJson(name, global);
|
|
206
|
+
if (pkgJson) {
|
|
207
|
+
plugins.set(name, pkgJson);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return plugins;
|
|
212
|
+
}
|
package/src/migrate.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import {
|
|
7
|
+
hasLegacyManifest,
|
|
8
|
+
type LegacyPluginInfo,
|
|
9
|
+
loadLegacyManifest,
|
|
10
|
+
type PluginPackageJson,
|
|
11
|
+
type PluginsJson,
|
|
12
|
+
savePluginsJson,
|
|
13
|
+
} from "./manifest.js";
|
|
14
|
+
import { LEGACY_MANIFEST_PATH, NODE_MODULES_DIR, PLUGINS_DIR } from "./paths.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prompt user for migration
|
|
18
|
+
*/
|
|
19
|
+
async function promptMigration(): Promise<boolean> {
|
|
20
|
+
const rl = createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(chalk.yellow("Migrate to npm-native format? [y/N] "), (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check for and prompt migration if legacy manifest exists
|
|
35
|
+
*/
|
|
36
|
+
export async function checkMigration(auto = false): Promise<boolean> {
|
|
37
|
+
if (!hasLegacyManifest()) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(chalk.yellow("\n⚠ Legacy manifest.json detected"));
|
|
42
|
+
console.log(chalk.dim(" oh-my-pi has been updated to use npm-native plugin management."));
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
if (!auto) {
|
|
46
|
+
const shouldMigrate = await promptMigration();
|
|
47
|
+
if (!shouldMigrate) {
|
|
48
|
+
console.log(chalk.dim(" Migration skipped. Use 'omp migrate' to migrate later."));
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return await migrateToNpm();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Migrate from legacy manifest.json to npm-native format
|
|
58
|
+
*/
|
|
59
|
+
export async function migrateToNpm(): Promise<boolean> {
|
|
60
|
+
console.log(chalk.blue("\nMigrating to npm-native format..."));
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const legacyManifest = await loadLegacyManifest();
|
|
64
|
+
const plugins = Object.entries(legacyManifest.plugins);
|
|
65
|
+
|
|
66
|
+
if (plugins.length === 0) {
|
|
67
|
+
console.log(chalk.dim(" No plugins to migrate"));
|
|
68
|
+
await archiveLegacyManifest();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create node_modules directory
|
|
73
|
+
await mkdir(NODE_MODULES_DIR, { recursive: true });
|
|
74
|
+
|
|
75
|
+
const newPluginsJson: PluginsJson = { plugins: {} };
|
|
76
|
+
const migrated: string[] = [];
|
|
77
|
+
const failed: string[] = [];
|
|
78
|
+
|
|
79
|
+
for (const [name, info] of plugins) {
|
|
80
|
+
try {
|
|
81
|
+
console.log(chalk.dim(` Migrating ${name}...`));
|
|
82
|
+
await migratePlugin(name, info);
|
|
83
|
+
|
|
84
|
+
// Determine version specifier for plugins.json
|
|
85
|
+
if (info.type === "npm" && info.package) {
|
|
86
|
+
newPluginsJson.plugins[info.package] = info.version ? `^${info.version}` : "latest";
|
|
87
|
+
} else if (info.type === "local" && info.path) {
|
|
88
|
+
newPluginsJson.plugins[name] = `file:${info.path}`;
|
|
89
|
+
} else if (info.type === "github" && info.repo) {
|
|
90
|
+
// GitHub plugins become local after clone
|
|
91
|
+
newPluginsJson.plugins[name] = `file:${join(NODE_MODULES_DIR, name)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
migrated.push(name);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.log(chalk.yellow(` ⚠ Failed to migrate ${name}: ${(err as Error).message}`));
|
|
97
|
+
failed.push(name);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Save new plugins.json
|
|
102
|
+
await savePluginsJson(newPluginsJson, true);
|
|
103
|
+
|
|
104
|
+
// Archive legacy manifest
|
|
105
|
+
await archiveLegacyManifest();
|
|
106
|
+
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(chalk.green(`✓ Migrated ${migrated.length} plugin(s)`));
|
|
109
|
+
if (failed.length > 0) {
|
|
110
|
+
console.log(chalk.yellow(`⚠ Failed to migrate ${failed.length} plugin(s): ${failed.join(", ")}`));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.log(chalk.red(`Error during migration: ${(err as Error).message}`));
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Migrate a single plugin to the new structure
|
|
122
|
+
*/
|
|
123
|
+
async function migratePlugin(name: string, info: LegacyPluginInfo): Promise<void> {
|
|
124
|
+
const oldPluginDir = join(PLUGINS_DIR, name);
|
|
125
|
+
const newPluginDir = join(NODE_MODULES_DIR, name);
|
|
126
|
+
|
|
127
|
+
if (!existsSync(oldPluginDir)) {
|
|
128
|
+
throw new Error(`Plugin directory not found: ${oldPluginDir}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// For linked plugins, create symlink in node_modules
|
|
132
|
+
if (info.linked && info.path) {
|
|
133
|
+
await mkdir(NODE_MODULES_DIR, { recursive: true });
|
|
134
|
+
if (existsSync(newPluginDir)) {
|
|
135
|
+
await rm(newPluginDir, { force: true, recursive: true });
|
|
136
|
+
}
|
|
137
|
+
await symlink(info.path, newPluginDir);
|
|
138
|
+
// Remove old symlink
|
|
139
|
+
await rm(oldPluginDir, { force: true });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// For regular plugins, move to node_modules
|
|
144
|
+
if (existsSync(newPluginDir)) {
|
|
145
|
+
await rm(newPluginDir, { force: true, recursive: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Rename/move the directory
|
|
149
|
+
await rename(oldPluginDir, newPluginDir);
|
|
150
|
+
|
|
151
|
+
// Create package.json if it doesn't exist (convert from omp.json)
|
|
152
|
+
const pkgJsonPath = join(newPluginDir, "package.json");
|
|
153
|
+
const ompJsonPath = join(newPluginDir, "omp.json");
|
|
154
|
+
|
|
155
|
+
if (!existsSync(pkgJsonPath) && existsSync(ompJsonPath)) {
|
|
156
|
+
const ompJson = JSON.parse(await readFile(ompJsonPath, "utf-8"));
|
|
157
|
+
const pkgJson: PluginPackageJson = {
|
|
158
|
+
name: ompJson.name || name,
|
|
159
|
+
version: ompJson.version || info.version || "0.0.0",
|
|
160
|
+
description: ompJson.description,
|
|
161
|
+
keywords: ["omp-plugin"],
|
|
162
|
+
omp: {
|
|
163
|
+
install: ompJson.install,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Archive the legacy manifest.json
|
|
172
|
+
*/
|
|
173
|
+
async function archiveLegacyManifest(): Promise<void> {
|
|
174
|
+
if (!existsSync(LEGACY_MANIFEST_PATH)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const archivePath = join(PLUGINS_DIR, `manifest.json.bak.${Date.now()}`);
|
|
179
|
+
await rename(LEGACY_MANIFEST_PATH, archivePath);
|
|
180
|
+
console.log(chalk.dim(` Archived old manifest to ${basename(archivePath)}`));
|
|
181
|
+
}
|
package/src/npm.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export interface NpmPackageInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
keywords?: string[];
|
|
8
|
+
author?: string | { name: string; email?: string };
|
|
9
|
+
homepage?: string;
|
|
10
|
+
repository?: { type: string; url: string } | string;
|
|
11
|
+
versions?: string[];
|
|
12
|
+
"dist-tags"?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NpmSearchResult {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
keywords?: string[];
|
|
20
|
+
date?: string;
|
|
21
|
+
author?: { name: string };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute npm command and return output
|
|
26
|
+
*/
|
|
27
|
+
export function npmExec(args: string[], cwd?: string): string {
|
|
28
|
+
const cmd = `npm ${args.join(" ")}`;
|
|
29
|
+
return execSync(cmd, {
|
|
30
|
+
cwd,
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute npm command with prefix (for installing to specific directory)
|
|
38
|
+
*/
|
|
39
|
+
export function npmExecWithPrefix(args: string[], prefix: string): string {
|
|
40
|
+
return npmExec(["--prefix", prefix, ...args]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Install packages using npm
|
|
45
|
+
*/
|
|
46
|
+
export async function npmInstall(
|
|
47
|
+
packages: string[],
|
|
48
|
+
prefix: string,
|
|
49
|
+
options: { save?: boolean; saveDev?: boolean } = {},
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const args = ["install"];
|
|
52
|
+
|
|
53
|
+
if (options.save) {
|
|
54
|
+
args.push("--save");
|
|
55
|
+
} else if (options.saveDev) {
|
|
56
|
+
args.push("--save-dev");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
args.push(...packages);
|
|
60
|
+
|
|
61
|
+
npmExecWithPrefix(args, prefix);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Uninstall packages using npm
|
|
66
|
+
*/
|
|
67
|
+
export async function npmUninstall(packages: string[], prefix: string): Promise<void> {
|
|
68
|
+
npmExecWithPrefix(["uninstall", ...packages], prefix);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get package info from npm registry
|
|
73
|
+
*/
|
|
74
|
+
export async function npmInfo(packageName: string): Promise<NpmPackageInfo | null> {
|
|
75
|
+
try {
|
|
76
|
+
const output = npmExec(["info", packageName, "--json"]);
|
|
77
|
+
return JSON.parse(output);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Search npm for packages with a keyword
|
|
85
|
+
*/
|
|
86
|
+
export async function npmSearch(query: string, keyword = "omp-plugin"): Promise<NpmSearchResult[]> {
|
|
87
|
+
try {
|
|
88
|
+
// Search for packages with the omp-plugin keyword
|
|
89
|
+
const searchTerm = keyword ? `keywords:${keyword} ${query}` : query;
|
|
90
|
+
const output = npmExec(["search", searchTerm, "--json"]);
|
|
91
|
+
return JSON.parse(output);
|
|
92
|
+
} catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check for outdated packages
|
|
99
|
+
*/
|
|
100
|
+
export async function npmOutdated(
|
|
101
|
+
prefix: string,
|
|
102
|
+
): Promise<Record<string, { current: string; wanted: string; latest: string }>> {
|
|
103
|
+
try {
|
|
104
|
+
const output = npmExecWithPrefix(["outdated", "--json"], prefix);
|
|
105
|
+
return JSON.parse(output);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// npm outdated exits with code 1 if there are outdated packages
|
|
108
|
+
const error = err as { stdout?: string };
|
|
109
|
+
if (error.stdout) {
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(error.stdout);
|
|
112
|
+
} catch {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update packages using npm
|
|
122
|
+
*/
|
|
123
|
+
export async function npmUpdate(packages: string[], prefix: string): Promise<void> {
|
|
124
|
+
const args = ["update"];
|
|
125
|
+
if (packages.length > 0) {
|
|
126
|
+
args.push(...packages);
|
|
127
|
+
}
|
|
128
|
+
npmExecWithPrefix(args, prefix);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get list of installed packages
|
|
133
|
+
*/
|
|
134
|
+
export async function npmList(prefix: string): Promise<Record<string, { version: string }>> {
|
|
135
|
+
try {
|
|
136
|
+
const output = npmExecWithPrefix(["list", "--json", "--depth=0"], prefix);
|
|
137
|
+
const parsed = JSON.parse(output);
|
|
138
|
+
return parsed.dependencies || {};
|
|
139
|
+
} catch {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a package version from the registry
|
|
146
|
+
*/
|
|
147
|
+
export async function resolveVersion(packageName: string, versionRange = "latest"): Promise<string | null> {
|
|
148
|
+
const info = await npmInfo(`${packageName}@${versionRange}`);
|
|
149
|
+
return info?.version || null;
|
|
150
|
+
}
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Global pi configuration directory
|
|
5
|
+
export const PI_CONFIG_DIR = join(homedir(), ".pi");
|
|
6
|
+
|
|
7
|
+
// Global plugins directory
|
|
8
|
+
export const PLUGINS_DIR = join(PI_CONFIG_DIR, "plugins");
|
|
9
|
+
|
|
10
|
+
// npm node_modules within plugins directory
|
|
11
|
+
export const NODE_MODULES_DIR = join(PLUGINS_DIR, "node_modules");
|
|
12
|
+
|
|
13
|
+
// Global package.json for plugin management
|
|
14
|
+
export const GLOBAL_PACKAGE_JSON = join(PLUGINS_DIR, "package.json");
|
|
15
|
+
|
|
16
|
+
// Global package-lock.json
|
|
17
|
+
export const GLOBAL_LOCK_FILE = join(PLUGINS_DIR, "package-lock.json");
|
|
18
|
+
|
|
19
|
+
// Legacy manifest (for migration)
|
|
20
|
+
export const LEGACY_MANIFEST_PATH = join(PLUGINS_DIR, "manifest.json");
|
|
21
|
+
|
|
22
|
+
// Project-local config directory
|
|
23
|
+
export const PROJECT_PI_DIR = ".pi";
|
|
24
|
+
|
|
25
|
+
// Project-local plugins.json
|
|
26
|
+
export const PROJECT_PLUGINS_JSON = join(PROJECT_PI_DIR, "plugins.json");
|
|
27
|
+
|
|
28
|
+
// Project-local lock file
|
|
29
|
+
export const PROJECT_PLUGINS_LOCK = join(PROJECT_PI_DIR, "plugins-lock.json");
|
|
30
|
+
|
|
31
|
+
// Project-local node_modules
|
|
32
|
+
export const PROJECT_NODE_MODULES = join(PROJECT_PI_DIR, "node_modules");
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the agent directory (where symlinks are installed)
|
|
36
|
+
*/
|
|
37
|
+
export function getAgentDir(global = true): string {
|
|
38
|
+
if (global) {
|
|
39
|
+
return join(PI_CONFIG_DIR, "agent");
|
|
40
|
+
}
|
|
41
|
+
return join(PROJECT_PI_DIR, "agent");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the plugins directory for the given scope
|
|
46
|
+
*/
|
|
47
|
+
export function getPluginsDir(global = true): string {
|
|
48
|
+
if (global) {
|
|
49
|
+
return PLUGINS_DIR;
|
|
50
|
+
}
|
|
51
|
+
return PROJECT_PI_DIR;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the node_modules directory for the given scope
|
|
56
|
+
*/
|
|
57
|
+
export function getNodeModulesDir(global = true): string {
|
|
58
|
+
if (global) {
|
|
59
|
+
return NODE_MODULES_DIR;
|
|
60
|
+
}
|
|
61
|
+
return PROJECT_NODE_MODULES;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the package.json path for the given scope
|
|
66
|
+
*/
|
|
67
|
+
export function getPackageJsonPath(global = true): string {
|
|
68
|
+
if (global) {
|
|
69
|
+
return GLOBAL_PACKAGE_JSON;
|
|
70
|
+
}
|
|
71
|
+
return PROJECT_PLUGINS_JSON;
|
|
72
|
+
}
|