dsh-eda-mcp 0.6.2 → 0.7.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/CHANGELOG.md +49 -1
- package/CONTRIBUTING.md +2 -1
- package/LICENSE +55 -55
- package/README.md +24 -1
- package/README.zh.md +23 -2
- package/docs/tools.md +30 -0
- package/dsh-eda-bridge-extension-0.4.0.zip +0 -0
- package/dsh-eda-bridge-extension.zip +0 -0
- package/jlc-extension/api-bridge.js +65 -11
- package/jlc-extension/extension.json +1 -1
- package/lib/bridge.mjs +678 -14
- package/lib/client.js +34 -0
- package/lib/index.js +6 -2
- package/package.json +5 -3
- package/src/bridge/eda-launch.ts +267 -0
- package/src/bridge/index.ts +579 -15
- package/src/client/EdaMcpSettingsCard.tsx +36 -0
- package/src/client/locales.ts +4 -0
- package/src/index.ts +6 -0
- package/test/eda-launch.test.ts +114 -0
- package/test/seams.test.ts +14 -0
package/lib/client.js
CHANGED
|
@@ -1097,6 +1097,22 @@ function NumberField(props) {
|
|
|
1097
1097
|
}
|
|
1098
1098
|
) });
|
|
1099
1099
|
}
|
|
1100
|
+
function TextField(props) {
|
|
1101
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: props.label, hint: props.hint, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1102
|
+
"input",
|
|
1103
|
+
{
|
|
1104
|
+
type: "text",
|
|
1105
|
+
value: props.value ?? "",
|
|
1106
|
+
placeholder: props.placeholder,
|
|
1107
|
+
disabled: props.disabled,
|
|
1108
|
+
onChange: (event) => {
|
|
1109
|
+
const text = event.target.value.trim();
|
|
1110
|
+
props.onChange(text === "" ? void 0 : text);
|
|
1111
|
+
},
|
|
1112
|
+
style: inputStyle
|
|
1113
|
+
}
|
|
1114
|
+
) });
|
|
1115
|
+
}
|
|
1100
1116
|
function BooleanField(props) {
|
|
1101
1117
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: props.label, hint: props.hint, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1102
1118
|
import_dsh_client_ui_primitives.Switch,
|
|
@@ -1235,6 +1251,20 @@ function EdaMcpSettingsCard(props) {
|
|
|
1235
1251
|
}
|
|
1236
1252
|
}
|
|
1237
1253
|
),
|
|
1254
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1255
|
+
TextField,
|
|
1256
|
+
{
|
|
1257
|
+
label: t("settings.edaExecutable"),
|
|
1258
|
+
hint: t("settings.edaExecutableHint"),
|
|
1259
|
+
value: draft.edaExecutable,
|
|
1260
|
+
placeholder: "lceda-pro.exe",
|
|
1261
|
+
disabled,
|
|
1262
|
+
onChange: (value) => {
|
|
1263
|
+
clearNotice();
|
|
1264
|
+
update("edaExecutable", value);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
),
|
|
1238
1268
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1239
1269
|
BooleanField,
|
|
1240
1270
|
{
|
|
@@ -1417,6 +1447,8 @@ var zh = {
|
|
|
1417
1447
|
"settings.toolCallTimeoutMsHint": "\u5355\u6B21 EDA API \u8C03\u7528\u8D85\u65F6\uFF0C\u9ED8\u8BA4 60000\u3002",
|
|
1418
1448
|
"settings.allowRawApi": "\u5141\u8BB8\u901A\u7528 eda_api_call",
|
|
1419
1449
|
"settings.allowRawApiHint": "\u5F00\u542F\u540E MCP \u5DE5\u5177 eda_api_call \u53EF\u8C03\u7528\u767D\u540D\u5355\u5185\u7684 JLCEDA \u6269\u5C55 API\u3002",
|
|
1450
|
+
"settings.edaExecutable": "\u5609\u7ACB\u521BEDA \u53EF\u6267\u884C\u6587\u4EF6\uFF08\u53EF\u9009\uFF09",
|
|
1451
|
+
"settings.edaExecutableHint": "\u7559\u7A7A\u5373\u81EA\u52A8\u63A2\u6D4B\uFF1A\u73AF\u5883\u53D8\u91CF \u2192 \u6CE8\u518C\u8868\u767B\u8BB0\u7684\u6587\u4EF6\u5173\u8054 \u2192 \u5E38\u89C1\u5B89\u88C5\u76EE\u5F55 \u2192 PATH\u3002\u53EA\u6709\u81EA\u52A8\u63A2\u6D4B\u5931\u8D25\u65F6\u624D\u9700\u8981\u586B\u5B8C\u6574\u8DEF\u5F84\u3002",
|
|
1420
1452
|
"settings.save": "\u4FDD\u5B58",
|
|
1421
1453
|
"settings.saving": "\u4FDD\u5B58\u4E2D\u2026",
|
|
1422
1454
|
"settings.saved": "\u5DF2\u4FDD\u5B58",
|
|
@@ -1445,6 +1477,8 @@ var en = {
|
|
|
1445
1477
|
"settings.toolCallTimeoutMsHint": "Timeout for one EDA API call; default 60000.",
|
|
1446
1478
|
"settings.allowRawApi": "Allow generic eda_api_call",
|
|
1447
1479
|
"settings.allowRawApiHint": "Expose the eda_api_call tool restricted to the allow-listed JLCEDA APIs.",
|
|
1480
|
+
"settings.edaExecutable": "JLCEDA Pro executable (optional)",
|
|
1481
|
+
"settings.edaExecutableHint": "Leave empty to auto-detect: environment variable, the file-association command in the registry, conventional install directories, then PATH. Set a full path only when auto-detection fails.",
|
|
1448
1482
|
"settings.save": "Save",
|
|
1449
1483
|
"settings.saving": "Saving\u2026",
|
|
1450
1484
|
"settings.saved": "Saved",
|
package/lib/index.js
CHANGED
|
@@ -492,7 +492,9 @@ var Config = z.object({
|
|
|
492
492
|
enabled: z.boolean().default(true).volatile(),
|
|
493
493
|
announceToAgent: z.boolean().default(true).volatile(),
|
|
494
494
|
toolCallTimeoutMs: z.number().min(1e3).max(3e5).default(6e4).volatile(),
|
|
495
|
-
allowRawApi: z.boolean().default(true).volatile()
|
|
495
|
+
allowRawApi: z.boolean().default(true).volatile(),
|
|
496
|
+
/** Optional path to lceda-pro.exe; empty means "let eda_launch discover it". */
|
|
497
|
+
edaExecutable: z.string().default("").volatile()
|
|
496
498
|
});
|
|
497
499
|
var FIBER_DISPOSED = 4;
|
|
498
500
|
var FIBER_UNLOADING = 5;
|
|
@@ -544,7 +546,9 @@ function bridgeLaunchFor(active) {
|
|
|
544
546
|
env: {
|
|
545
547
|
...runtime.extraEnv,
|
|
546
548
|
DSH_EDA_MCP_ALLOW_RAW_API: allowRawApi ? "1" : "0",
|
|
547
|
-
DSH_EDA_MCP_TOOL_TIMEOUT_MS: String(toolCallTimeoutMs)
|
|
549
|
+
DSH_EDA_MCP_TOOL_TIMEOUT_MS: String(toolCallTimeoutMs),
|
|
550
|
+
// eda_launch falls back to the registry / install dirs / PATH when empty.
|
|
551
|
+
DSH_EDA_EXECUTABLE: typeof active.edaExecutable === "string" ? active.edaExecutable : ""
|
|
548
552
|
},
|
|
549
553
|
cwd: dirname2(BRIDGE_PATH),
|
|
550
554
|
toolCallTimeoutMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eda-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "DeepSeek Harness (DSH) plugin — connects DSH to JLCEDA Pro (EasyEDA Pro) via a local MCP bridge and draws schematics through the JLCEDA extension API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"test",
|
|
50
50
|
"docs",
|
|
51
51
|
"jlc-extension",
|
|
52
|
+
"dsh-eda-bridge-extension-*.zip",
|
|
52
53
|
"dsh-eda-bridge-extension.zip",
|
|
53
54
|
"cordis.patch.yml",
|
|
54
55
|
"CHANGELOG.md",
|
|
@@ -72,9 +73,10 @@
|
|
|
72
73
|
"scripts": {
|
|
73
74
|
"build": "node build.mjs",
|
|
74
75
|
"typecheck": "tsc --noEmit",
|
|
75
|
-
"test": "node --experimental-strip-types test/node-runtime.test.ts && node --experimental-strip-types test/config-values.test.ts && node --experimental-strip-types test/design-rules.test.ts && node --experimental-strip-types test/seams.test.ts",
|
|
76
|
+
"test": "node --experimental-strip-types test/node-runtime.test.ts && node --experimental-strip-types test/config-values.test.ts && node --experimental-strip-types test/design-rules.test.ts && node --experimental-strip-types test/seams.test.ts && node --experimental-strip-types test/eda-launch.test.ts",
|
|
76
77
|
"seam-check": "node scripts/seam-check.mjs",
|
|
77
|
-
"check": "
|
|
78
|
+
"contract-check": "node scripts/contract-check.mjs",
|
|
79
|
+
"check": "pnpm typecheck && pnpm test && pnpm contract-check && pnpm build",
|
|
78
80
|
"docs": "node scripts/gen-tools-doc.mjs",
|
|
79
81
|
"prepublishOnly": "pnpm check",
|
|
80
82
|
"prepare": "pnpm build"
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launching JLCEDA Pro (EasyEDA Pro) from a tool call.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here is machine-specific: the executable is resolved from an explicit
|
|
5
|
+
* override, an environment variable, the registry entry the installer creates, a
|
|
6
|
+
* list of conventional install directories, and finally PATH. Every step is
|
|
7
|
+
* skipped when its inputs are absent.
|
|
8
|
+
*
|
|
9
|
+
* String operations only (no regex literals): the discovery rules stay readable
|
|
10
|
+
* and the resolution order is unit-tested without spawning anything.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Platform = 'win32' | 'darwin' | 'linux' | (string & {})
|
|
14
|
+
|
|
15
|
+
/** Everything the resolver needs from the outside world. */
|
|
16
|
+
export interface LaunchProbe {
|
|
17
|
+
platform: Platform
|
|
18
|
+
env: Record<string, string | undefined>
|
|
19
|
+
exists: (path: string) => boolean
|
|
20
|
+
/** Raw stdout of the registry query, or undefined when the key does not exist. */
|
|
21
|
+
registryQuery: (key: string, valueName?: string) => string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResolveOptions {
|
|
25
|
+
executable?: string
|
|
26
|
+
project?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ExecutableSource = 'config' | 'env' | 'registry' | 'install-dir' | 'path'
|
|
30
|
+
|
|
31
|
+
export interface ResolvedLaunch {
|
|
32
|
+
executable: string
|
|
33
|
+
source: ExecutableSource
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const EXECUTABLE_ENV_KEYS = ['DSH_EDA_EXECUTABLE', 'JLCEDA_EXECUTABLE'] as const
|
|
37
|
+
export const EXECUTABLE_NAMES = ['lceda-pro.exe', 'lceda-pro', 'LCEDA Pro'] as const
|
|
38
|
+
export const PROJECT_EXTENSIONS = ['.eprj2', '.eprj'] as const
|
|
39
|
+
|
|
40
|
+
const CN_PRO_EDITION = '嘉立创EDA专业版'
|
|
41
|
+
const CLASSES = 'Software\\Classes'
|
|
42
|
+
|
|
43
|
+
export const INSTALL_SUFFIXES = [
|
|
44
|
+
'lceda-pro',
|
|
45
|
+
'Program Files\\lceda-pro',
|
|
46
|
+
'Program Files (x86)\\lceda-pro',
|
|
47
|
+
'Program Files\\' + CN_PRO_EDITION,
|
|
48
|
+
'Program Files (x86)\\' + CN_PRO_EDITION,
|
|
49
|
+
] as const
|
|
50
|
+
|
|
51
|
+
/** Strip trailing path separators from a directory. */
|
|
52
|
+
function trimSeparator(value: string): string {
|
|
53
|
+
let end = value.length
|
|
54
|
+
while (end > 0) {
|
|
55
|
+
const ch = value[end - 1]
|
|
56
|
+
if (ch !== '\\' && ch !== '/') break
|
|
57
|
+
end -= 1
|
|
58
|
+
}
|
|
59
|
+
return value.slice(0, end)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Strip leading path separators. */
|
|
63
|
+
function trimLeadingSeparators(value: string): string {
|
|
64
|
+
let start = 0
|
|
65
|
+
while (start < value.length && (value[start] === '\\' || value[start] === '/')) start += 1
|
|
66
|
+
return value.slice(start)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function joinPath(platform: Platform, ...parts: string[]): string {
|
|
70
|
+
const separator = platform === 'win32' ? '\\' : '/'
|
|
71
|
+
return parts
|
|
72
|
+
.filter(part => part !== '')
|
|
73
|
+
.map((part, index) => (index === 0 ? part : trimLeadingSeparators(part)))
|
|
74
|
+
.join(separator)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Split an install suffix into path segments, on either separator. */
|
|
78
|
+
function splitSegments(value: string): string[] {
|
|
79
|
+
return value.split('\\').join('/').split('/').filter(part => part !== '')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pull the executable out of a registered open command such as
|
|
84
|
+
* `"D:\\lceda-pro\\lceda-pro.exe" "%1"` or `C:\\App\\lceda-pro.exe %1`.
|
|
85
|
+
*/
|
|
86
|
+
export function parseExecutableFromCommand(command: string | undefined): string | undefined {
|
|
87
|
+
const text = (command ?? '').trim()
|
|
88
|
+
if (text === '') return undefined
|
|
89
|
+
if (text.startsWith('"')) {
|
|
90
|
+
const end = text.indexOf('"', 1)
|
|
91
|
+
if (end === -1) return undefined
|
|
92
|
+
const quoted = text.slice(1, end).trim()
|
|
93
|
+
return quoted === '' ? undefined : quoted
|
|
94
|
+
}
|
|
95
|
+
const space = text.indexOf(' ')
|
|
96
|
+
const tab = text.indexOf(String.fromCharCode(9))
|
|
97
|
+
const stops = [space, tab].filter(index => index > 0)
|
|
98
|
+
const end = stops.length === 0 ? text.length : Math.min(...stops)
|
|
99
|
+
const first = text.slice(0, end).trim()
|
|
100
|
+
return first === '' ? undefined : first
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decode `reg` output. The tool writes in the console code page (GBK on a Chinese
|
|
105
|
+
* Windows), and progid names are non-ASCII, so a UTF-8 decode can produce
|
|
106
|
+
* replacement characters that no longer match a registry key. When that happens,
|
|
107
|
+
* decode again as GBK (both decoders ship with the Node the host provides).
|
|
108
|
+
*/
|
|
109
|
+
export function decodeRegistryOutput(raw: Uint8Array): string {
|
|
110
|
+
const utf8 = new TextDecoder('utf-8').decode(raw)
|
|
111
|
+
if (!utf8.includes(String.fromCharCode(0xFFFD))) return utf8
|
|
112
|
+
try {
|
|
113
|
+
return new TextDecoder('gbk').decode(raw)
|
|
114
|
+
} catch {
|
|
115
|
+
return utf8
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Take the string payload of a registry line (`... REG_SZ <value>`). */
|
|
120
|
+
export function parseRegStringValue(output: string | undefined): string | undefined {
|
|
121
|
+
for (const raw of (output ?? '').split(String.fromCharCode(10))) {
|
|
122
|
+
const line = raw.trim()
|
|
123
|
+
const index = line.toUpperCase().indexOf('REG_SZ')
|
|
124
|
+
if (index === -1) continue
|
|
125
|
+
const value = line.slice(index + 'REG_SZ'.length).trim()
|
|
126
|
+
// `reg query` prints "(value not set)" for an unset default; it is not a value.
|
|
127
|
+
if (value === '' || value.startsWith('(')) continue
|
|
128
|
+
return value
|
|
129
|
+
}
|
|
130
|
+
return undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Value names of a registry listing (the first column of every value line). */
|
|
134
|
+
export function parseRegValueNames(output: string | undefined): string[] {
|
|
135
|
+
const names: string[] = []
|
|
136
|
+
for (const raw of (output ?? '').split(String.fromCharCode(10))) {
|
|
137
|
+
const line = raw.trim()
|
|
138
|
+
const marker = line.indexOf('REG_')
|
|
139
|
+
if (marker <= 0) continue
|
|
140
|
+
const name = line.slice(0, marker).trim()
|
|
141
|
+
if (name !== '' && !name.startsWith('HKEY')) names.push(name)
|
|
142
|
+
}
|
|
143
|
+
return names
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function firstExisting(probe: LaunchProbe, candidates: string[]): string | undefined {
|
|
147
|
+
for (const candidate of candidates) {
|
|
148
|
+
if (candidate !== '' && probe.exists(candidate)) return candidate
|
|
149
|
+
}
|
|
150
|
+
return undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Candidate executables derived from environment variables (no registry access). */
|
|
154
|
+
export function envCandidates(probe: LaunchProbe): string[] {
|
|
155
|
+
const env = probe.env
|
|
156
|
+
const roots = [
|
|
157
|
+
env.ProgramFiles,
|
|
158
|
+
env['ProgramFiles(x86)'],
|
|
159
|
+
env.LOCALAPPDATA === undefined ? undefined : joinPath(probe.platform, env.LOCALAPPDATA, 'Programs'),
|
|
160
|
+
env.APPDATA === undefined ? undefined : joinPath(probe.platform, env.APPDATA, 'Programs'),
|
|
161
|
+
env.SystemDrive,
|
|
162
|
+
env.USERPROFILE,
|
|
163
|
+
env.HOME,
|
|
164
|
+
].filter((root): root is string => typeof root === 'string' && root !== '')
|
|
165
|
+
|
|
166
|
+
const candidates: string[] = []
|
|
167
|
+
for (const root of roots) {
|
|
168
|
+
for (const suffix of INSTALL_SUFFIXES) {
|
|
169
|
+
for (const name of EXECUTABLE_NAMES) {
|
|
170
|
+
candidates.push(joinPath(probe.platform, trimSeparator(root), ...splitSegments(suffix), name))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return candidates
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Look for the executable in the directories listed on PATH. */
|
|
178
|
+
export function pathCandidates(probe: LaunchProbe): string[] {
|
|
179
|
+
const raw = probe.env.PATH ?? probe.env.Path ?? ''
|
|
180
|
+
const separator = probe.platform === 'win32' ? ';' : ':'
|
|
181
|
+
return raw
|
|
182
|
+
.split(separator)
|
|
183
|
+
.filter(dir => dir.trim() !== '')
|
|
184
|
+
.flatMap(dir => EXECUTABLE_NAMES.map(name => joinPath(probe.platform, trimSeparator(dir), name)))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Ask the registry where the installer registered the project handler.
|
|
189
|
+
*
|
|
190
|
+
* The extension's default progid is often empty while `OpenWithProgids` lists the
|
|
191
|
+
* real one, so both are consulted. HKCU wins over HKLM; HKCR is skipped because
|
|
192
|
+
* it merges the two.
|
|
193
|
+
*/
|
|
194
|
+
export function registryCandidates(probe: LaunchProbe): string[] {
|
|
195
|
+
if (probe.platform !== 'win32') return []
|
|
196
|
+
const found: string[] = []
|
|
197
|
+
for (const hive of ['HKCU', 'HKLM']) {
|
|
198
|
+
for (const extension of PROJECT_EXTENSIONS) {
|
|
199
|
+
const base = hive + '\\' + CLASSES + '\\' + extension
|
|
200
|
+
const defaults = [parseRegStringValue(probe.registryQuery(base, '')), parseRegStringValue(probe.registryQuery(base))]
|
|
201
|
+
const progids = [
|
|
202
|
+
...defaults.filter((value): value is string => value !== undefined),
|
|
203
|
+
...parseRegValueNames(probe.registryQuery(base + '\\OpenWithProgids')),
|
|
204
|
+
]
|
|
205
|
+
for (const progid of progids) {
|
|
206
|
+
if (progid.startsWith('Applications' + '\\')) continue
|
|
207
|
+
const command = parseRegStringValue(
|
|
208
|
+
probe.registryQuery(hive + '\\' + CLASSES + '\\' + progid + '\\shell\\open\\command'),
|
|
209
|
+
)
|
|
210
|
+
const executable = parseExecutableFromCommand(command)
|
|
211
|
+
if (executable !== undefined) found.push(executable)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return found
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Resolve the executable, or undefined when nothing on this machine matches. */
|
|
219
|
+
export function resolveExecutable(probe: LaunchProbe, options: ResolveOptions = {}): ResolvedLaunch | undefined {
|
|
220
|
+
const override = options.executable?.trim()
|
|
221
|
+
if (override !== undefined && override !== '' && probe.exists(override)) {
|
|
222
|
+
return { executable: override, source: 'config' }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const key of EXECUTABLE_ENV_KEYS) {
|
|
226
|
+
const value = probe.env[key]?.trim()
|
|
227
|
+
if (value !== undefined && value !== '' && probe.exists(value)) return { executable: value, source: 'env' }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const fromRegistry = firstExisting(probe, registryCandidates(probe))
|
|
231
|
+
if (fromRegistry !== undefined) return { executable: fromRegistry, source: 'registry' }
|
|
232
|
+
|
|
233
|
+
const fromInstallDir = firstExisting(probe, envCandidates(probe))
|
|
234
|
+
if (fromInstallDir !== undefined) return { executable: fromInstallDir, source: 'install-dir' }
|
|
235
|
+
|
|
236
|
+
const fromPath = firstExisting(probe, pathCandidates(probe))
|
|
237
|
+
if (fromPath !== undefined) return { executable: fromPath, source: 'path' }
|
|
238
|
+
|
|
239
|
+
return undefined
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Arguments used to open the app (and optionally a project) on each platform. */
|
|
243
|
+
export function buildLaunchArgs(platform: Platform, executable: string, project?: string): { command: string; args: string[] } {
|
|
244
|
+
const target = project?.trim()
|
|
245
|
+
const hasProject = target !== undefined && target !== ''
|
|
246
|
+
if (platform === 'darwin') {
|
|
247
|
+
return hasProject
|
|
248
|
+
? { command: 'open', args: ['-a', executable, target as string] }
|
|
249
|
+
: { command: 'open', args: ['-a', executable] }
|
|
250
|
+
}
|
|
251
|
+
return hasProject ? { command: executable, args: [target as string] } : { command: executable, args: [] }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Windows script used to hand the launch to the running Explorer shell.
|
|
256
|
+
*
|
|
257
|
+
* Spawning `lceda-pro.exe` directly from a host process (or even through
|
|
258
|
+
* `Start-Process`) makes it start and exit immediately, while double-clicking the
|
|
259
|
+
* same executable works. Explorer runs in the interactive desktop, so executing a
|
|
260
|
+
* script through it reproduces the double-click context; the script then starts the
|
|
261
|
+
* app with the optional project argument.
|
|
262
|
+
*/
|
|
263
|
+
export function buildLaunchScript(executable: string, project?: string): string {
|
|
264
|
+
const target = project?.trim()
|
|
265
|
+
const args = target === undefined || target === '' ? '' : ' "' + target + '"'
|
|
266
|
+
return ['@echo off', 'start "" "' + executable + '"' + args, ''].join(String.fromCharCode(13, 10))
|
|
267
|
+
}
|