@oh-my-pi/cli 0.1.0 → 0.3.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 +243 -138
- package/biome.json +1 -1
- package/bun.lock +59 -0
- package/dist/cli.js +6311 -2900
- package/dist/commands/config.d.ts +32 -0
- package/dist/commands/config.d.ts.map +1 -0
- 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/env.d.ts +14 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/features.d.ts +25 -0
- package/dist/commands/features.d.ts.map +1 -0
- 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 +37 -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 +18 -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 +60 -25
- package/dist/manifest.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 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/runtime.d.ts +14 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/symlinks.d.ts +43 -7
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +11 -5
- package/plugins/exa/README.md +153 -0
- package/plugins/exa/package.json +56 -0
- package/plugins/exa/tools/exa/company.ts +35 -0
- package/plugins/exa/tools/exa/index.ts +66 -0
- package/plugins/exa/tools/exa/linkedin.ts +35 -0
- package/plugins/exa/tools/exa/researcher.ts +40 -0
- package/plugins/exa/tools/exa/runtime.json +4 -0
- package/plugins/exa/tools/exa/search.ts +46 -0
- package/plugins/exa/tools/exa/shared.ts +230 -0
- package/plugins/exa/tools/exa/websets.ts +62 -0
- package/plugins/metal-theme/package.json +7 -2
- package/plugins/subagents/package.json +7 -2
- package/plugins/user-prompt/README.md +130 -0
- package/plugins/user-prompt/package.json +19 -0
- package/plugins/user-prompt/tools/user-prompt/index.ts +235 -0
- package/src/cli.ts +133 -58
- package/src/commands/config.ts +384 -0
- package/src/commands/create.ts +51 -1
- package/src/commands/doctor.ts +95 -7
- package/src/commands/enable.ts +25 -8
- package/src/commands/env.ts +38 -0
- package/src/commands/features.ts +295 -0
- package/src/commands/info.ts +41 -5
- package/src/commands/init.ts +20 -2
- package/src/commands/install.ts +453 -80
- package/src/commands/link.ts +60 -9
- package/src/commands/list.ts +122 -7
- 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 +18 -25
- package/src/lock.ts +46 -0
- package/src/lockfile.ts +132 -0
- package/src/manifest.ts +219 -71
- package/src/npm.ts +74 -18
- package/src/paths.ts +77 -12
- package/src/runtime.ts +116 -0
- package/src/symlinks.ts +291 -35
- package/tsconfig.json +7 -3
- package/CHECK.md +0 -352
- package/dist/migrate.d.ts +0 -9
- package/dist/migrate.d.ts.map +0 -1
- package/src/migrate.ts +0 -181
package/src/commands/why.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, lstatSync } from "node:fs";
|
|
2
2
|
import { readlink } from "node:fs/promises";
|
|
3
|
-
import { join, relative } from "node:path";
|
|
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";
|
|
4
7
|
import chalk from "chalk";
|
|
5
|
-
import { getInstalledPlugins, readPluginPackageJson } from "../manifest.js";
|
|
6
|
-
import { PI_CONFIG_DIR } from "../paths.js";
|
|
7
|
-
import { traceInstalledFile } from "../symlinks.js";
|
|
8
8
|
|
|
9
9
|
export interface WhyOptions {
|
|
10
10
|
global?: boolean;
|
|
11
|
+
local?: boolean;
|
|
11
12
|
json?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -15,31 +16,45 @@ export interface WhyOptions {
|
|
|
15
16
|
* Show which plugin installed a file
|
|
16
17
|
*/
|
|
17
18
|
export async function whyFile(filePath: string, options: WhyOptions = {}): Promise<void> {
|
|
18
|
-
const isGlobal = options
|
|
19
|
+
const isGlobal = resolveScope(options);
|
|
19
20
|
|
|
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
|
|
21
25
|
let relativePath = filePath;
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
// Check if it's a path in agent/ directory
|
|
29
43
|
if (!relativePath.startsWith("agent/")) {
|
|
30
44
|
// Try prepending agent/
|
|
31
45
|
const withAgent = `agent/${relativePath}`;
|
|
32
|
-
const fullWithAgent = join(
|
|
46
|
+
const fullWithAgent = join(baseDir, withAgent);
|
|
33
47
|
if (existsSync(fullWithAgent)) {
|
|
34
48
|
relativePath = withAgent;
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
const fullPath = join(
|
|
52
|
+
const fullPath = join(baseDir, relativePath);
|
|
39
53
|
|
|
40
54
|
// Check if file exists
|
|
41
55
|
if (!existsSync(fullPath)) {
|
|
42
56
|
console.log(chalk.yellow(`File not found: ${fullPath}`));
|
|
57
|
+
process.exitCode = 1;
|
|
43
58
|
return;
|
|
44
59
|
}
|
|
45
60
|
|
|
@@ -54,7 +69,7 @@ export async function whyFile(filePath: string, options: WhyOptions = {}): Promi
|
|
|
54
69
|
|
|
55
70
|
// Search through installed plugins
|
|
56
71
|
const installedPlugins = await getInstalledPlugins(isGlobal);
|
|
57
|
-
const result = await traceInstalledFile(relativePath, installedPlugins);
|
|
72
|
+
const result = await traceInstalledFile(relativePath, installedPlugins, isGlobal);
|
|
58
73
|
|
|
59
74
|
if (options.json) {
|
|
60
75
|
console.log(
|
|
@@ -85,9 +100,25 @@ export async function whyFile(filePath: string, options: WhyOptions = {}): Promi
|
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
if (result) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
}
|
|
91
122
|
|
|
92
123
|
// Get plugin info
|
|
93
124
|
const pkgJson = await readPluginPackageJson(result.plugin, isGlobal);
|
package/src/conflicts.ts
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
|
-
import type { PluginPackageJson } from "
|
|
1
|
+
import type { PluginPackageJson } from "@omp/manifest";
|
|
2
2
|
|
|
3
3
|
export interface Conflict {
|
|
4
4
|
dest: string;
|
|
5
5
|
plugins: Array<{ name: string; src: string }>;
|
|
6
6
|
}
|
|
7
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
|
+
|
|
8
40
|
/**
|
|
9
41
|
* Detect conflicts between a new plugin and existing plugins
|
|
10
42
|
*/
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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
CHANGED
|
@@ -1,43 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export { uninstallPlugin } from "./commands/uninstall.js";
|
|
15
|
-
export { updatePlugin } from "./commands/update.js";
|
|
16
|
-
export { whyFile } from "./commands/why.js";
|
|
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";
|
|
17
14
|
export {
|
|
18
15
|
detectAllConflicts,
|
|
19
16
|
detectConflicts,
|
|
20
17
|
formatConflicts,
|
|
21
|
-
} from "
|
|
18
|
+
} from "@omp/conflicts";
|
|
22
19
|
|
|
23
|
-
// Types
|
|
24
20
|
export type {
|
|
25
21
|
OmpField,
|
|
26
22
|
OmpInstallEntry,
|
|
27
23
|
PluginPackageJson,
|
|
28
24
|
PluginsJson,
|
|
29
|
-
} from "
|
|
25
|
+
} from "@omp/manifest";
|
|
30
26
|
|
|
31
|
-
// Utilities
|
|
32
27
|
export {
|
|
33
28
|
getInstalledPlugins,
|
|
34
29
|
initGlobalPlugins,
|
|
35
30
|
loadPluginsJson,
|
|
36
31
|
readPluginPackageJson,
|
|
37
32
|
savePluginsJson,
|
|
38
|
-
} from "
|
|
39
|
-
// Migration
|
|
40
|
-
export { checkMigration, migrateToNpm } from "./migrate.js";
|
|
33
|
+
} from "@omp/manifest";
|
|
41
34
|
export {
|
|
42
35
|
npmInfo,
|
|
43
36
|
npmInstall,
|
|
@@ -45,9 +38,9 @@ export {
|
|
|
45
38
|
npmSearch,
|
|
46
39
|
npmUninstall,
|
|
47
40
|
npmUpdate,
|
|
48
|
-
} from "
|
|
41
|
+
} from "@omp/npm";
|
|
49
42
|
export {
|
|
50
43
|
checkPluginSymlinks,
|
|
51
44
|
createPluginSymlinks,
|
|
52
45
|
removePluginSymlinks,
|
|
53
|
-
} from "
|
|
46
|
+
} from "@omp/symlinks";
|
package/src/lock.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
}
|