Compare commits
3 Commits
v0.1.2
..
d116418e65
| Author | SHA1 | Date | |
|---|---|---|---|
| d116418e65 | |||
| 68c2de6d26 | |||
| 2d073a2413 |
@@ -58,6 +58,25 @@ profile 的 `package.json`:
|
||||
|
||||
改完执行 `pnpm install`(关闭 DSH 后执行最稳,避免 Windows 上正在使用的原生模块被占用),再启动。
|
||||
|
||||
### 方式三:DSH 桌面版(Electron)
|
||||
|
||||
桌面版也是 DSH,插件机制相同,但 profile 名是**保留的 `desktop`**,且由桌面应用自己管理。要点:
|
||||
|
||||
1. **前提版本**:桌面版需基于 DSH **≥ 0.2.1-rc.2**(本插件 `dsh.engines.dsh` 的要求;0.1.x 不支持预设声明行机制)。
|
||||
2. **安装入口**:用**桌面版自带的插件管理界面**(设置 → 插件 / Plugin Manager)安装本仓库目录作为 bundle;
|
||||
```text
|
||||
install_bundle target = <本仓库克隆目录的绝对路径>
|
||||
```
|
||||
或安装一个可解析的包 / Git 仓库地址。
|
||||
⚠️ 不要用 npm 全局安装的 `dsh` CLI 去管 `desktop` profile——CLI 会拒绝桌面版 profile 的启动与插件管理请求,必须使用桌面安装自带的命令运行时。
|
||||
3. **依赖**:`@nefevcore/abap-adt-dsh-plugin` 会随包安装;桌面版的插件操作会使用其安装的包管理器(需能访问 npm registry)。
|
||||
4. **重启桌面应用** → 新建会话选「ABAP 工作台」→ 设置 → ABAP ADT 配置目的地/模式(用法与 Web 版一致)。
|
||||
5. **已知差异/注意**:
|
||||
- 桌面版的 Web UI 同样加载本插件的 Client 半(设置页「ABAP ADT」+ 会话状态徽章),但 `dsh.client` 支持取决于桌面版是否加载第三方 client 插件;若设置页没有出现 ABAP ADT,先确认桌面版版本与插件是否真的激活。
|
||||
- 内置 mock `demo` 会在 `127.0.0.1` 随机端口监听,桌面版可能弹一次防火墙提示(仅本机回环,放行即可/拒绝也不影响真实 SAP 目的地)。
|
||||
- 工作区是普通目录,目的地/模式配置仍写在工作区 `.dsh-abap-adt/`,与 Web 版一致。
|
||||
- Host 半只用 Node 内置模块;`@nefevcore` core 通过扫描 `~/.dsh/profiles/*` 定位,桌面版 profile 在 `~/.dsh/profiles/desktop` 下同样适用。
|
||||
|
||||
### 安装后
|
||||
|
||||
1. **重启 DSH**;
|
||||
|
||||
@@ -431,22 +431,32 @@ window.__ModuleLoader__.load({
|
||||
)
|
||||
: null
|
||||
|
||||
const workspacePaths = workspaces.map((item) => item.path).filter(Boolean)
|
||||
const workspaceOptions =
|
||||
workspace && !workspacePaths.includes(workspace) ? [workspace, ...workspacePaths] : workspacePaths
|
||||
const workspaceTitles = Object.fromEntries(workspaces.map((item) => [item.path, item.title]))
|
||||
const workspaceRow = h(
|
||||
'div',
|
||||
{ style: { display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 12, marginTop: 16 } },
|
||||
h(
|
||||
Field,
|
||||
{ label: DICTS.zh.workspace, hint: DICTS.zh.workspaceHint },
|
||||
h('input', {
|
||||
style: inputStyle,
|
||||
value: workspace,
|
||||
list: 'abap-adt-workspaces',
|
||||
onChange: (event) => setWorkspace(event.target.value),
|
||||
onBlur: () => {
|
||||
if (workspace && workspace !== file?.path?.replace(/[\\/]\.dsh-abap-adt[\\/].*$/, '')) load(workspace)
|
||||
h(
|
||||
'select',
|
||||
{
|
||||
style: inputStyle,
|
||||
value: workspace,
|
||||
onChange: (event) => {
|
||||
const next = event.target.value
|
||||
setWorkspace(next)
|
||||
if (next) load(next)
|
||||
},
|
||||
},
|
||||
placeholder: 'D:\\AIagent\\SAP',
|
||||
}),
|
||||
workspaceOptions.map((path) => {
|
||||
const title = workspaceTitles[path]
|
||||
return h('option', { key: path, value: path }, title && title !== path ? title + ' — ' + path : path)
|
||||
}),
|
||||
),
|
||||
),
|
||||
h(
|
||||
Field,
|
||||
@@ -457,11 +467,6 @@ window.__ModuleLoader__.load({
|
||||
file?.path ? `${file.path}${file.exists ? '' : DICTS.zh.notExists}` : DICTS.zh.never,
|
||||
),
|
||||
),
|
||||
h(
|
||||
'datalist',
|
||||
{ id: 'abap-adt-workspaces' },
|
||||
workspaces.map((item) => h('option', { key: item.id || item.path, value: item.path }, item.title)),
|
||||
),
|
||||
)
|
||||
|
||||
const destinationCards = destinations.length
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* the DSH credential service (falling back to the process environment).
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { existsSync, readdirSync, statSync } from 'node:fs'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { request as httpRequest } from 'node:http'
|
||||
import { request as httpsRequest } from 'node:https'
|
||||
@@ -508,8 +508,52 @@ export function apply(ctx, config) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The workspace whose destinations file was written most recently. An empty
|
||||
* destinations list (a scratch config, or one where every system was removed)
|
||||
* loses to any workspace that still lists systems, so the settings page never
|
||||
* opens on an empty page while a usable config exists elsewhere.
|
||||
*/
|
||||
async function lastModifiedWorkspace() {
|
||||
let best
|
||||
let bestTime = -Infinity
|
||||
let any
|
||||
let anyTime = -Infinity
|
||||
for (const row of listWorkspaces()) {
|
||||
if (!row.path) continue
|
||||
for (const fileName of FILE_NAMES) {
|
||||
const candidate = join(row.path, CONFIG_DIR, fileName)
|
||||
let mtimeMs
|
||||
try {
|
||||
mtimeMs = statSync(candidate).mtimeMs
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
if (mtimeMs > anyTime) {
|
||||
anyTime = mtimeMs
|
||||
any = row.path
|
||||
}
|
||||
if (mtimeMs <= bestTime) continue
|
||||
try {
|
||||
const parsed = await parseDestinations(await readFile(candidate, 'utf8'))
|
||||
if (!normalizeDestinations(parsed.destinations).length) continue
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
bestTime = mtimeMs
|
||||
best = row.path
|
||||
}
|
||||
}
|
||||
return best || any
|
||||
}
|
||||
|
||||
async function stateOf({ sessionId, workspace }) {
|
||||
const cwd = workspace || resolveCwd(sessionId) || listWorkspaces()[0]?.path || process.cwd()
|
||||
const cwd =
|
||||
workspace ||
|
||||
resolveCwd(sessionId) ||
|
||||
(await lastModifiedWorkspace()) ||
|
||||
listWorkspaces()[0]?.path ||
|
||||
process.cwd()
|
||||
const file = existingWorkspaceFile(cwd)
|
||||
let raw = ''
|
||||
let exists = false
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dsh-abap-workbench",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"private": false,
|
||||
"description": "SAP ABAP 工作台:一个 DSH bundle,内含 ABAP 预设(只读/调试/更改,按工作区策略切换)、可视化配置页(目的地、连接测试、运行模式、可写白名单)和会话状态徽章。依赖 @nefevcore/abap-adt-dsh-plugin 提供 adt_* 工具。",
|
||||
"type": "module",
|
||||
|
||||
Reference in New Issue
Block a user