pi-x-ide 1.3.0 → 1.4.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 +16 -2
- package/README.zh.md +16 -2
- package/dist/package.json +5 -2
- package/dist/src/pi/install.js +13 -8
- package/dist/src/pi/install.js.map +1 -1
- package/dist/src/pi/zed.js +19 -12
- package/dist/src/pi/zed.js.map +1 -1
- package/dist/src/shared/config-options.d.ts +89 -0
- package/dist/src/shared/config-options.js +88 -0
- package/dist/src/shared/config-options.js.map +1 -0
- package/dist/src/shared/config.d.ts +7 -0
- package/dist/src/shared/config.js +51 -0
- package/dist/src/shared/config.js.map +1 -0
- package/dist/src/shared/paths.js +4 -3
- package/dist/src/shared/paths.js.map +1 -1
- package/dist/test/install.test.js +13 -0
- package/dist/test/install.test.js.map +1 -1
- package/dist/test/shared.test.js +27 -0
- package/dist/test/shared.test.js.map +1 -1
- package/dist/test/zed.test.js +12 -0
- package/dist/test/zed.test.js.map +1 -1
- package/package.json +5 -2
- package/schemas/config.json +120 -0
- package/src/pi/install.ts +13 -8
- package/src/pi/zed.ts +19 -12
- package/src/shared/config-options.ts +100 -0
- package/src/shared/config.ts +54 -0
- package/src/shared/paths.ts +4 -3
package/src/pi/zed.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent" with {
|
|
|
6
6
|
"resolution-mode": "import",
|
|
7
7
|
};
|
|
8
8
|
import type { EditorSelectionSnapshot, SelectionRange } from "../shared/protocol";
|
|
9
|
+
import { resolvePiConfigEnv, isProcessEnvOrPiConfigOverlay } from "../shared/config";
|
|
9
10
|
import { snapshotKey } from "../shared/format";
|
|
10
11
|
import { isPathInsideOrEqual } from "../shared/paths";
|
|
11
12
|
import { setLatestSelection, clearLatestSelection } from "./context";
|
|
@@ -18,12 +19,14 @@ export const ZED_POLL_INTERVAL_MS = 1000;
|
|
|
18
19
|
// ── Terminal detection ──────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
export function isZedTerminal(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
21
|
-
|
|
22
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
23
|
+
return configuredEnv.ZED_TERM === "true" || configuredEnv.TERM_PROGRAM?.toLowerCase() === "zed";
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function isWsl(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
28
|
+
if (configuredEnv.WSL_DISTRO_NAME || configuredEnv.WSL_INTEROP) return true;
|
|
29
|
+
if (!isProcessEnvOrPiConfigOverlay(configuredEnv)) return false;
|
|
27
30
|
try {
|
|
28
31
|
return /microsoft|wsl/i.test(readFileSync("/proc/version", "utf8"));
|
|
29
32
|
} catch {
|
|
@@ -32,7 +35,8 @@ export function isWsl(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function normalizeZedPathForHost(input: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
35
|
-
|
|
38
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
39
|
+
if (!input || !isWsl(configuredEnv)) return input;
|
|
36
40
|
|
|
37
41
|
const driveMatch = input.match(/^([a-zA-Z]):[\\/](.*)$/);
|
|
38
42
|
if (driveMatch) {
|
|
@@ -45,7 +49,7 @@ export function normalizeZedPathForHost(input: string, env: NodeJS.ProcessEnv =
|
|
|
45
49
|
if (uncMatch) {
|
|
46
50
|
const distro = uncMatch[1];
|
|
47
51
|
const rest = uncMatch[2].replaceAll("\\", "/");
|
|
48
|
-
const currentDistro =
|
|
52
|
+
const currentDistro = configuredEnv.WSL_DISTRO_NAME;
|
|
49
53
|
if (!currentDistro || distro.toLowerCase() === currentDistro.toLowerCase()) {
|
|
50
54
|
return `/${rest}`;
|
|
51
55
|
}
|
|
@@ -57,9 +61,10 @@ export function normalizeZedPathForHost(input: string, env: NodeJS.ProcessEnv =
|
|
|
57
61
|
// ── DB path resolution ─────────────────────────────────────────
|
|
58
62
|
|
|
59
63
|
export function resolveZedDbPath(env: NodeJS.ProcessEnv = process.env, home: string = homedir()): string | undefined {
|
|
60
|
-
const
|
|
64
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
65
|
+
const override = configuredEnv[PI_X_IDE_ZED_DB_ENV]?.trim();
|
|
61
66
|
if (override) {
|
|
62
|
-
const normalizedOverride = normalizeZedPathForHost(override,
|
|
67
|
+
const normalizedOverride = normalizeZedPathForHost(override, configuredEnv);
|
|
63
68
|
return isFile(normalizedOverride) ? normalizedOverride : undefined;
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -67,8 +72,8 @@ export function resolveZedDbPath(env: NodeJS.ProcessEnv = process.env, home: str
|
|
|
67
72
|
resolve(home, ".local", "share", "zed", "db", "0-stable", "db.sqlite"), // Linux
|
|
68
73
|
resolve(home, "Library", "Application Support", "Zed", "db", "0-stable", "db.sqlite"), // macOS
|
|
69
74
|
resolve(home, "AppData", "Local", "Zed", "db", "0-stable", "db.sqlite"), // Windows
|
|
70
|
-
...zedDbCandidatesFromWindowsEnv(
|
|
71
|
-
...zedDbCandidatesFromWslMount(
|
|
75
|
+
...zedDbCandidatesFromWindowsEnv(configuredEnv),
|
|
76
|
+
...zedDbCandidatesFromWslMount(configuredEnv),
|
|
72
77
|
];
|
|
73
78
|
|
|
74
79
|
return candidates.find(isFile);
|
|
@@ -114,10 +119,11 @@ interface ZedDatabaseHandle {
|
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
function openZedDatabase(dbPath: string, env: NodeJS.ProcessEnv = process.env): ZedDatabaseHandle | undefined {
|
|
122
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
117
123
|
// Direct open on live WAL-mode databases can succeed at construction time
|
|
118
124
|
// but fail on the first query on WSL/Windows mounts. Always snapshot
|
|
119
125
|
// under WSL to avoid "disk I/O error" during SQL execution.
|
|
120
|
-
if (isWsl(
|
|
126
|
+
if (isWsl(configuredEnv)) return openZedDatabaseSnapshot(dbPath);
|
|
121
127
|
|
|
122
128
|
try {
|
|
123
129
|
return { db: new DatabaseSync(dbPath, { readOnly: true }), cleanup: () => undefined };
|
|
@@ -274,7 +280,8 @@ export interface ResolveZedSelectionOptions {
|
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
export function resolveZedSelection(options: ResolveZedSelectionOptions): EditorSelectionSnapshot | undefined {
|
|
277
|
-
const { dbPath, cwd, readFile = (path) => readFileSync(path, "utf8"), env = process.env } = options;
|
|
283
|
+
const { dbPath, cwd, readFile = (path) => readFileSync(path, "utf8"), env: inputEnv = process.env } = options;
|
|
284
|
+
const env = resolvePiConfigEnv(inputEnv);
|
|
278
285
|
const dbHandle = openZedDatabase(dbPath, env);
|
|
279
286
|
if (!dbHandle) return undefined;
|
|
280
287
|
|
|
@@ -431,7 +438,7 @@ export function startZedPolling(
|
|
|
431
438
|
env?: NodeJS.ProcessEnv;
|
|
432
439
|
},
|
|
433
440
|
): boolean {
|
|
434
|
-
const env = options?.env ?? process.env;
|
|
441
|
+
const env = resolvePiConfigEnv(options?.env ?? process.env);
|
|
435
442
|
if (!isZedTerminal(env)) return false;
|
|
436
443
|
|
|
437
444
|
const dbPath = options?.dbPath ?? resolveZedDbPath(env);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export type ConfigEnvValueType = "string" | "number" | "boolean";
|
|
2
|
+
|
|
3
|
+
export interface ConfigEnvOption {
|
|
4
|
+
readonly type: readonly ConfigEnvValueType[];
|
|
5
|
+
readonly description: string;
|
|
6
|
+
readonly default?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ConfigEnvPatternOption extends ConfigEnvOption {
|
|
10
|
+
readonly pattern: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const CONFIG_ENV_VALUE_TYPES = ["string", "number", "boolean"] as const satisfies readonly ConfigEnvValueType[];
|
|
14
|
+
|
|
15
|
+
export function isConfigEnvValue(value: unknown): value is string | number | boolean {
|
|
16
|
+
return CONFIG_ENV_VALUE_TYPES.some((type) => typeof value === type);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CONFIG_ENV_OPTIONS = {
|
|
20
|
+
PI_X_IDE_LOCK_DIR: {
|
|
21
|
+
type: ["string"],
|
|
22
|
+
default: "~/.pi/pi-x-ide/lock",
|
|
23
|
+
description: "Directory containing Pi x IDE lock files. Defaults to ~/.pi/pi-x-ide/lock.",
|
|
24
|
+
},
|
|
25
|
+
PI_X_IDE_AUTO_INSTALL: {
|
|
26
|
+
type: ["string", "number", "boolean"],
|
|
27
|
+
default: "enabled",
|
|
28
|
+
description: "Controls VS Code-family extension auto-install. Values 0, false, and off disable it.",
|
|
29
|
+
},
|
|
30
|
+
PI_X_IDE_ZED_DB: {
|
|
31
|
+
type: ["string"],
|
|
32
|
+
description: "Override path to Zed's SQLite database.",
|
|
33
|
+
},
|
|
34
|
+
TERM_PROGRAM: {
|
|
35
|
+
type: ["string"],
|
|
36
|
+
description: "Terminal program marker used to detect VS Code, Cursor, Windsurf, or Zed.",
|
|
37
|
+
},
|
|
38
|
+
VSCODE_CWD: {
|
|
39
|
+
type: ["string"],
|
|
40
|
+
description: "VS Code-family cwd marker and IDE path hint.",
|
|
41
|
+
},
|
|
42
|
+
VSCODE_PID: {
|
|
43
|
+
type: ["string", "number"],
|
|
44
|
+
description: "VS Code-family process marker.",
|
|
45
|
+
},
|
|
46
|
+
VSCODE_IPC_HOOK_CLI: {
|
|
47
|
+
type: ["string"],
|
|
48
|
+
description: "VS Code-family IPC marker and IDE path hint.",
|
|
49
|
+
},
|
|
50
|
+
VSCODE_GIT_IPC_HANDLE: {
|
|
51
|
+
type: ["string"],
|
|
52
|
+
description: "VS Code-family Git IPC marker and IDE path hint.",
|
|
53
|
+
},
|
|
54
|
+
ZED_TERM: {
|
|
55
|
+
type: ["string", "boolean"],
|
|
56
|
+
description: "Zed terminal marker. Pi x IDE detects Zed when this is true.",
|
|
57
|
+
},
|
|
58
|
+
WSL_DISTRO_NAME: {
|
|
59
|
+
type: ["string"],
|
|
60
|
+
description: "WSL distribution name used for WSL path normalization and Zed database discovery.",
|
|
61
|
+
},
|
|
62
|
+
WSL_INTEROP: {
|
|
63
|
+
type: ["string"],
|
|
64
|
+
description: "WSL interop marker used for WSL path normalization and Zed database discovery.",
|
|
65
|
+
},
|
|
66
|
+
LOCALAPPDATA: {
|
|
67
|
+
type: ["string"],
|
|
68
|
+
description: "Windows local application data directory used to find Zed's database.",
|
|
69
|
+
},
|
|
70
|
+
USERPROFILE: {
|
|
71
|
+
type: ["string"],
|
|
72
|
+
description: "Windows user profile directory used to find Zed's database when LOCALAPPDATA is unavailable.",
|
|
73
|
+
},
|
|
74
|
+
PATH: {
|
|
75
|
+
type: ["string"],
|
|
76
|
+
description: "Executable search path used to find code, cursor, and windsurf CLIs.",
|
|
77
|
+
},
|
|
78
|
+
Path: {
|
|
79
|
+
type: ["string"],
|
|
80
|
+
description: "Windows-style executable search path used to find code, cursor, and windsurf CLIs.",
|
|
81
|
+
},
|
|
82
|
+
path: {
|
|
83
|
+
type: ["string"],
|
|
84
|
+
description: "Lowercase executable search path used to find code, cursor, and windsurf CLIs.",
|
|
85
|
+
},
|
|
86
|
+
PATHEXT: {
|
|
87
|
+
type: ["string"],
|
|
88
|
+
description: "Windows executable extensions used when searching for IDE CLIs.",
|
|
89
|
+
},
|
|
90
|
+
} as const satisfies Record<string, ConfigEnvOption>;
|
|
91
|
+
|
|
92
|
+
export const CONFIG_ENV_PATTERN_OPTIONS = [
|
|
93
|
+
{
|
|
94
|
+
pattern: "^(CURSOR|WINDSURF|CODEIUM).*",
|
|
95
|
+
type: ["string", "number", "boolean"],
|
|
96
|
+
description: "IDE-specific marker used to detect Cursor or Windsurf terminals.",
|
|
97
|
+
},
|
|
98
|
+
] as const satisfies readonly ConfigEnvPatternOption[];
|
|
99
|
+
|
|
100
|
+
export type ConfigEnvOptionName = keyof typeof CONFIG_ENV_OPTIONS;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { isConfigEnvValue } from "./config-options";
|
|
5
|
+
|
|
6
|
+
export const PI_CONFIG_FILE = "config.json";
|
|
7
|
+
|
|
8
|
+
const processEnvOverlays = new WeakSet<NodeJS.ProcessEnv>();
|
|
9
|
+
|
|
10
|
+
export function resolvePiConfigPath(home: string = homedir()): string {
|
|
11
|
+
return resolve(home, ".pi", PI_CONFIG_FILE);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readPiConfigEnv(configPath: string = resolvePiConfigPath()): NodeJS.ProcessEnv {
|
|
15
|
+
let parsed: unknown;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(readFileSync(configPath, "utf8")) as unknown;
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!isRecord(parsed) || !isRecord(parsed.env)) return {};
|
|
23
|
+
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
Object.entries(parsed.env)
|
|
26
|
+
.filter((entry): entry is [string, string | number | boolean] => {
|
|
27
|
+
const [key, value] = entry;
|
|
28
|
+
return key.length > 0 && isConfigEnvValue(value);
|
|
29
|
+
})
|
|
30
|
+
.map(([key, value]) => [key, String(value)]),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolvePiConfigEnv(
|
|
35
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
36
|
+
options: { configPath?: string } = {},
|
|
37
|
+
): NodeJS.ProcessEnv {
|
|
38
|
+
if (env !== process.env && !options.configPath) return env;
|
|
39
|
+
|
|
40
|
+
const configEnv = readPiConfigEnv(options.configPath);
|
|
41
|
+
if (Object.keys(configEnv).length === 0) return env;
|
|
42
|
+
|
|
43
|
+
const merged = { ...configEnv, ...env };
|
|
44
|
+
if (env === process.env) processEnvOverlays.add(merged);
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isProcessEnvOrPiConfigOverlay(env: NodeJS.ProcessEnv): boolean {
|
|
49
|
+
return env === process.env || processEnvOverlays.has(env);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
53
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
54
|
+
}
|
package/src/shared/paths.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { resolvePiConfigEnv } from "./config";
|
|
3
4
|
import { LOCK_DIR_ENV } from "./protocol";
|
|
4
5
|
|
|
5
6
|
export function resolveLockDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const configuredEnv = resolvePiConfigEnv(env);
|
|
8
|
+
const override = configuredEnv[LOCK_DIR_ENV]?.trim();
|
|
9
|
+
return override ? resolve(override) : resolve(homedir(), ".pi", "pi-x-ide", "lock");
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function normalizePath(input: string): string {
|