@lihuu/dsh-about 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/README.md +40 -0
- package/lib/client.js +68 -0
- package/lib/index.d.ts +51 -0
- package/lib/index.js +133 -0
- package/package.json +44 -0
- package/src/index.ts +119 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# dsh-about
|
|
2
|
+
|
|
3
|
+
"About" settings section for DeepSeek Harness web GUI. Registers a "关于" page in the settings panel that shows the running version and build date, read live from the host — zero main-repo changes.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Host half** (`src/index.ts`): registers the `aboutInfo` Typert Remote service. `aboutInfo/get` reads the running version from `apps/cli/package.json` and the build date from the built CLI's mtime (`apps/cli/lib/bin.js`).
|
|
8
|
+
- **Browser half** (`lib/client.js`): registers a "关于" page into the `settings.section` list slot and resolves the data through `connection.rpc.call('/api', 'aboutInfo/get')`.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
From the `dsh-plugins` repo root:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
./install.sh
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This symlinks the plugin into `$DSH_HOME/plugins/dsh-about` and adds its row to `$DSH_HOME/cordis.patch.yml`. Then restart dsh-web:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
launchctl kickstart -k gui/501/com.lihu.dsh-web
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Build
|
|
25
|
+
|
|
26
|
+
The host half is TypeScript; rebuild it after editing `src/index.ts`:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
cd plugins/dsh-about && ./node_modules/typescript/bin/tsc -p tsconfig.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The browser half (`lib/client.js`) is hand-written plain JS and needs no build.
|
|
33
|
+
|
|
34
|
+
## Notes
|
|
35
|
+
|
|
36
|
+
- The harness checkout path is read from `$DSH_REPO` (falling back to the
|
|
37
|
+
process working directory), so the plugin carries no machine-specific path.
|
|
38
|
+
Export `DSH_REPO` in your launchd env (the dsh-plugins `env.sh` does) or run
|
|
39
|
+
dsh from its checkout.
|
|
40
|
+
- Version and build date are read live on each page open, so they always reflect the currently running build.
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-about — independent "About" settings section (BROWSER half).
|
|
3
|
+
*
|
|
4
|
+
* Plain-JS client bundle served as `/plugins/@local/dsh-about/client.js`.
|
|
5
|
+
* Registers the "关于" page into the settings.section list slot and resolves
|
|
6
|
+
* the running version + build date through the host's runtime Typert Remote
|
|
7
|
+
* endpoint `aboutInfo/get` via the generic `/api` RPC channel — no generated
|
|
8
|
+
* artifacts, no main-repo changes.
|
|
9
|
+
*/
|
|
10
|
+
(function () {
|
|
11
|
+
// The registration id MUST equal the boot graph row id (loader entry name
|
|
12
|
+
// == package name). The host composes rows from the cordis patch `name`.
|
|
13
|
+
var PLUGIN_ID = '@lihuu/dsh-about'
|
|
14
|
+
|
|
15
|
+
window.__ModuleLoader__.load({
|
|
16
|
+
id: PLUGIN_ID,
|
|
17
|
+
factory: function (require) {
|
|
18
|
+
var React = require('react')
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
inject: ['slots', 'connection'],
|
|
22
|
+
apply: function (ctx) {
|
|
23
|
+
var slots = ctx.get('slots')
|
|
24
|
+
var connection = ctx.get('connection')
|
|
25
|
+
if (slots === undefined || connection === undefined) return
|
|
26
|
+
|
|
27
|
+
slots.inject('settings.section', function () {
|
|
28
|
+
return slots.register(
|
|
29
|
+
{ name: 'settings.section', id: 'about', order: 30, label: '关于' },
|
|
30
|
+
function () {
|
|
31
|
+
var state = React.useState(null)
|
|
32
|
+
var info = state[0]
|
|
33
|
+
var setInfo = state[1]
|
|
34
|
+
|
|
35
|
+
React.useEffect(function () {
|
|
36
|
+
var alive = true
|
|
37
|
+
try {
|
|
38
|
+
connection.rpc.call('/api', 'aboutInfo/get', { args: {} })
|
|
39
|
+
.then(function (result) {
|
|
40
|
+
if (alive && result && result.ok) setInfo(result.value)
|
|
41
|
+
})
|
|
42
|
+
.catch(function () {})
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// RPC unavailable — leave the loading state
|
|
45
|
+
}
|
|
46
|
+
return function () { alive = false }
|
|
47
|
+
}, [])
|
|
48
|
+
|
|
49
|
+
function row(label, value) {
|
|
50
|
+
return React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
|
|
51
|
+
React.createElement('span', { style: { color: 'var(--dsh-text-secondary, #888)', minWidth: '64px' } }, label),
|
|
52
|
+
React.createElement('span', null, value),
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return React.createElement('div', { style: { padding: '16px', display: 'flex', flexDirection: 'column', gap: '10px' } },
|
|
57
|
+
React.createElement('h2', { style: { margin: '0 0 4px' } }, '关于'),
|
|
58
|
+
row('版本', info ? info.version : '加载中…'),
|
|
59
|
+
row('构建日期', info ? info.buildDate : '加载中…'),
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
})()
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-about — independent "About" settings section for DeepSeek Harness web GUI
|
|
3
|
+
* (HOST half).
|
|
4
|
+
*
|
|
5
|
+
* Provides the `aboutInfo` Typert Remote service: the browser half's settings
|
|
6
|
+
* "关于" page resolves the running version and build date through the runtime
|
|
7
|
+
* `aboutInfo/get` endpoint. The host gateway (`dsh-api-gateway`) dispatches
|
|
8
|
+
* `/api/aboutInfo/get` to this live service via source-mode discovery
|
|
9
|
+
* (`typertRemote` binding + `Remote` markers), so NO compile-time generated
|
|
10
|
+
* artifacts and NO main-repo changes are involved: the whole feature mounts as
|
|
11
|
+
* an installable `@local` plugin.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-about
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
17
|
+
/** Stable plugin row name (used by the cordis loader entry). */
|
|
18
|
+
export declare const name = "dsh-about";
|
|
19
|
+
/** The about payload the settings page renders. */
|
|
20
|
+
export interface AboutInfo {
|
|
21
|
+
/** Running version, read from apps/cli/package.json. */
|
|
22
|
+
readonly version: string;
|
|
23
|
+
/** Build date, from the built CLI's mtime. */
|
|
24
|
+
readonly buildDate: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* About info remote service. Registered under `aboutInfo`; the `get` method is
|
|
28
|
+
* the single `@Remote` endpoint the browser half calls through
|
|
29
|
+
* `connection.rpc.call('/api', 'aboutInfo/get', { args })`.
|
|
30
|
+
*/
|
|
31
|
+
export declare class AboutInfoService extends TypertRemoteService {
|
|
32
|
+
private readonly fs;
|
|
33
|
+
private readonly shell;
|
|
34
|
+
/**
|
|
35
|
+
* @param ctx - owning host context (fs/shell read optionally).
|
|
36
|
+
*/
|
|
37
|
+
constructor(ctx: Context);
|
|
38
|
+
/**
|
|
39
|
+
* Read the running version and build date.
|
|
40
|
+
* @Remote('get') — invoked by the host gateway in source mode.
|
|
41
|
+
* @returns version + build date; unknown fields degrade to 'unknown'.
|
|
42
|
+
*/
|
|
43
|
+
get(): Promise<AboutInfo>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Plugin body: construct the `aboutInfo` service (the Service base constructor
|
|
47
|
+
* registers it under this fiber via `ctx.reflect.provide`).
|
|
48
|
+
* @param ctx - host root context.
|
|
49
|
+
*/
|
|
50
|
+
export declare const inject: string[];
|
|
51
|
+
export declare function apply(ctx: Context): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-about — independent "About" settings section for DeepSeek Harness web GUI
|
|
3
|
+
* (HOST half).
|
|
4
|
+
*
|
|
5
|
+
* Provides the `aboutInfo` Typert Remote service: the browser half's settings
|
|
6
|
+
* "关于" page resolves the running version and build date through the runtime
|
|
7
|
+
* `aboutInfo/get` endpoint. The host gateway (`dsh-api-gateway`) dispatches
|
|
8
|
+
* `/api/aboutInfo/get` to this live service via source-mode discovery
|
|
9
|
+
* (`typertRemote` binding + `Remote` markers), so NO compile-time generated
|
|
10
|
+
* artifacts and NO main-repo changes are involved: the whole feature mounts as
|
|
11
|
+
* an installable `@local` plugin.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-about
|
|
14
|
+
*/
|
|
15
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
16
|
+
var useValue = arguments.length > 2;
|
|
17
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
18
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
19
|
+
}
|
|
20
|
+
return useValue ? value : void 0;
|
|
21
|
+
};
|
|
22
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
23
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
24
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
25
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
26
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
27
|
+
var _, done = false;
|
|
28
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
29
|
+
var context = {};
|
|
30
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
31
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
32
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
33
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
34
|
+
if (kind === "accessor") {
|
|
35
|
+
if (result === void 0) continue;
|
|
36
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
37
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
38
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
39
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
40
|
+
}
|
|
41
|
+
else if (_ = accept(result)) {
|
|
42
|
+
if (kind === "field") initializers.unshift(_);
|
|
43
|
+
else descriptor[key] = _;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
47
|
+
done = true;
|
|
48
|
+
};
|
|
49
|
+
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol';
|
|
50
|
+
/** Stable plugin row name (used by the cordis loader entry). */
|
|
51
|
+
export const name = 'dsh-about';
|
|
52
|
+
/**
|
|
53
|
+
* Harness source checkout this deployment runs from. Read from `$DSH_REPO`
|
|
54
|
+
* (the launchd env.sh exports it) so the plugin carries no machine-specific
|
|
55
|
+
* path; falls back to the process working directory, which is the repo root
|
|
56
|
+
* when dsh is launched from its checkout. A wrong value degrades to 'unknown'
|
|
57
|
+
* via the try/catch below.
|
|
58
|
+
*/
|
|
59
|
+
const REPO = process.env.DSH_REPO ?? process.cwd();
|
|
60
|
+
/**
|
|
61
|
+
* About info remote service. Registered under `aboutInfo`; the `get` method is
|
|
62
|
+
* the single `@Remote` endpoint the browser half calls through
|
|
63
|
+
* `connection.rpc.call('/api', 'aboutInfo/get', { args })`.
|
|
64
|
+
*/
|
|
65
|
+
let AboutInfoService = (() => {
|
|
66
|
+
let _classSuper = TypertRemoteService;
|
|
67
|
+
let _instanceExtraInitializers = [];
|
|
68
|
+
let _get_decorators;
|
|
69
|
+
return class AboutInfoService extends _classSuper {
|
|
70
|
+
static {
|
|
71
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
72
|
+
_get_decorators = [Remote('get')];
|
|
73
|
+
__esDecorate(this, null, _get_decorators, { kind: "method", name: "get", static: false, private: false, access: { has: obj => "get" in obj, get: obj => obj.get }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
74
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
75
|
+
}
|
|
76
|
+
fs = __runInitializers(this, _instanceExtraInitializers);
|
|
77
|
+
shell;
|
|
78
|
+
/**
|
|
79
|
+
* @param ctx - owning host context (fs/shell read optionally).
|
|
80
|
+
*/
|
|
81
|
+
constructor(ctx) {
|
|
82
|
+
super(ctx, 'aboutInfo');
|
|
83
|
+
this.fs = ctx.get('fs');
|
|
84
|
+
this.shell = ctx.get('shell');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read the running version and build date.
|
|
88
|
+
* @Remote('get') — invoked by the host gateway in source mode.
|
|
89
|
+
* @returns version + build date; unknown fields degrade to 'unknown'.
|
|
90
|
+
*/
|
|
91
|
+
async get() {
|
|
92
|
+
let version = 'unknown';
|
|
93
|
+
let buildDate = 'unknown';
|
|
94
|
+
if (this.fs !== undefined) {
|
|
95
|
+
try {
|
|
96
|
+
const target = await this.fs.resolve(`${REPO}/apps/cli/package.json`);
|
|
97
|
+
const text = await this.fs.readText(target);
|
|
98
|
+
const m = text.match(/"version"\s*:\s*"([^"]+)"/);
|
|
99
|
+
if (m !== null)
|
|
100
|
+
version = m[1];
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// fs unavailable or read failed — keep 'unknown'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (this.shell !== undefined) {
|
|
107
|
+
try {
|
|
108
|
+
const spec = this.shell.resolve({
|
|
109
|
+
command: `stat -f "%Sm" -t "%Y-%m-%d %H:%M" ${REPO}/apps/cli/lib/bin.js`,
|
|
110
|
+
});
|
|
111
|
+
const result = await this.shell.run(spec);
|
|
112
|
+
const out = result.stdout.text.trim();
|
|
113
|
+
if (out !== '')
|
|
114
|
+
buildDate = out;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// shell unavailable or stat failed — keep 'unknown'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { version, buildDate };
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
})();
|
|
124
|
+
export { AboutInfoService };
|
|
125
|
+
/**
|
|
126
|
+
* Plugin body: construct the `aboutInfo` service (the Service base constructor
|
|
127
|
+
* registers it under this fiber via `ctx.reflect.provide`).
|
|
128
|
+
* @param ctx - host root context.
|
|
129
|
+
*/
|
|
130
|
+
export const inject = ['fs', 'shell'];
|
|
131
|
+
export function apply(ctx) {
|
|
132
|
+
new AboutInfoService(ctx);
|
|
133
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lihuu/dsh-about",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "About section for DeepSeek Harness web GUI settings: shows the running version and build date, read live from the host (apps/cli/package.json + built CLI mtime) — zero main-repo changes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"client": {
|
|
14
|
+
"platform": "web"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"src",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"deepseek-harness",
|
|
27
|
+
"dsh",
|
|
28
|
+
"cordis",
|
|
29
|
+
"web",
|
|
30
|
+
"about",
|
|
31
|
+
"settings",
|
|
32
|
+
"plugin"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@deepseek-ai/cordis": "*",
|
|
37
|
+
"@deepseek-ai/dsh-typert-protocol": "*"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.5.0",
|
|
42
|
+
"@types/node": "^22.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-about — independent "About" settings section for DeepSeek Harness web GUI
|
|
3
|
+
* (HOST half).
|
|
4
|
+
*
|
|
5
|
+
* Provides the `aboutInfo` Typert Remote service: the browser half's settings
|
|
6
|
+
* "关于" page resolves the running version and build date through the runtime
|
|
7
|
+
* `aboutInfo/get` endpoint. The host gateway (`dsh-api-gateway`) dispatches
|
|
8
|
+
* `/api/aboutInfo/get` to this live service via source-mode discovery
|
|
9
|
+
* (`typertRemote` binding + `Remote` markers), so NO compile-time generated
|
|
10
|
+
* artifacts and NO main-repo changes are involved: the whole feature mounts as
|
|
11
|
+
* an installable `@local` plugin.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-about
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
17
|
+
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
|
|
18
|
+
|
|
19
|
+
/** Stable plugin row name (used by the cordis loader entry). */
|
|
20
|
+
export const name = 'dsh-about'
|
|
21
|
+
|
|
22
|
+
/** The about payload the settings page renders. */
|
|
23
|
+
export interface AboutInfo {
|
|
24
|
+
/** Running version, read from apps/cli/package.json. */
|
|
25
|
+
readonly version: string
|
|
26
|
+
/** Build date, from the built CLI's mtime. */
|
|
27
|
+
readonly buildDate: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---- Minimal structural faces of the optional host services. Type-only:
|
|
31
|
+
// the plugin reads them with ctx.get() and never imports the packages. ----
|
|
32
|
+
|
|
33
|
+
/** The `ctx.fs` service surface this plugin uses (dsh-fs FileSystem). */
|
|
34
|
+
interface FileSystemLike {
|
|
35
|
+
resolve(path: string, options?: { cwd?: string }): Promise<unknown>
|
|
36
|
+
readText(target: unknown): Promise<string>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The `ctx.shell` service surface this plugin uses (dsh-shell ShellExecutor). */
|
|
40
|
+
interface ShellLike {
|
|
41
|
+
resolve(request: { command: string; workdir?: string }): unknown
|
|
42
|
+
run(spec: unknown): Promise<{ stdout: { text: string } }>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Harness source checkout this deployment runs from. Read from `$DSH_REPO`
|
|
47
|
+
* (the launchd env.sh exports it) so the plugin carries no machine-specific
|
|
48
|
+
* path; falls back to the process working directory, which is the repo root
|
|
49
|
+
* when dsh is launched from its checkout. A wrong value degrades to 'unknown'
|
|
50
|
+
* via the try/catch below.
|
|
51
|
+
*/
|
|
52
|
+
const REPO = process.env.DSH_REPO ?? process.cwd()
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* About info remote service. Registered under `aboutInfo`; the `get` method is
|
|
56
|
+
* the single `@Remote` endpoint the browser half calls through
|
|
57
|
+
* `connection.rpc.call('/api', 'aboutInfo/get', { args })`.
|
|
58
|
+
*/
|
|
59
|
+
export class AboutInfoService extends TypertRemoteService {
|
|
60
|
+
private readonly fs: FileSystemLike | undefined
|
|
61
|
+
private readonly shell: ShellLike | undefined
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param ctx - owning host context (fs/shell read optionally).
|
|
65
|
+
*/
|
|
66
|
+
constructor(ctx: Context) {
|
|
67
|
+
super(ctx, 'aboutInfo')
|
|
68
|
+
this.fs = ctx.get('fs') as FileSystemLike | undefined
|
|
69
|
+
this.shell = ctx.get('shell') as ShellLike | undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read the running version and build date.
|
|
74
|
+
* @Remote('get') — invoked by the host gateway in source mode.
|
|
75
|
+
* @returns version + build date; unknown fields degrade to 'unknown'.
|
|
76
|
+
*/
|
|
77
|
+
@Remote('get')
|
|
78
|
+
async get(): Promise<AboutInfo> {
|
|
79
|
+
let version = 'unknown'
|
|
80
|
+
let buildDate = 'unknown'
|
|
81
|
+
|
|
82
|
+
if (this.fs !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
const target = await this.fs.resolve(`${REPO}/apps/cli/package.json`)
|
|
85
|
+
const text = await this.fs.readText(target)
|
|
86
|
+
const m = text.match(/"version"\s*:\s*"([^"]+)"/)
|
|
87
|
+
if (m !== null) version = m[1]
|
|
88
|
+
} catch {
|
|
89
|
+
// fs unavailable or read failed — keep 'unknown'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.shell !== undefined) {
|
|
94
|
+
try {
|
|
95
|
+
const spec = this.shell.resolve({
|
|
96
|
+
command: `stat -f "%Sm" -t "%Y-%m-%d %H:%M" ${REPO}/apps/cli/lib/bin.js`,
|
|
97
|
+
})
|
|
98
|
+
const result = await this.shell.run(spec)
|
|
99
|
+
const out = result.stdout.text.trim()
|
|
100
|
+
if (out !== '') buildDate = out
|
|
101
|
+
} catch {
|
|
102
|
+
// shell unavailable or stat failed — keep 'unknown'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { version, buildDate }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Plugin body: construct the `aboutInfo` service (the Service base constructor
|
|
112
|
+
* registers it under this fiber via `ctx.reflect.provide`).
|
|
113
|
+
* @param ctx - host root context.
|
|
114
|
+
*/
|
|
115
|
+
export const inject = ['fs', 'shell']
|
|
116
|
+
|
|
117
|
+
export function apply(ctx: Context): void {
|
|
118
|
+
new AboutInfoService(ctx)
|
|
119
|
+
}
|