@nextclaw/app-runtime 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/README.md +125 -3
- package/dist/bundle/app-bundle.service.d.ts +27 -0
- package/dist/bundle/app-bundle.service.js +133 -0
- package/dist/bundle/app-bundle.types.d.ts +24 -0
- package/dist/cli/app-runtime-cli.service.d.ts +27 -0
- package/dist/cli/app-runtime-cli.service.js +281 -0
- package/dist/cli/app-runtime-options.service.d.ts +60 -0
- package/dist/cli/app-runtime-options.service.js +215 -0
- package/dist/commands/create.controller.d.ts +14 -0
- package/dist/commands/create.controller.js +23 -0
- package/dist/commands/grant.controller.d.ts +16 -0
- package/dist/commands/grant.controller.js +25 -0
- package/dist/commands/info.controller.d.ts +14 -0
- package/dist/commands/info.controller.js +27 -0
- package/dist/commands/install.controller.d.ts +15 -0
- package/dist/commands/install.controller.js +24 -0
- package/dist/commands/list.controller.d.ts +13 -0
- package/dist/commands/list.controller.js +25 -0
- package/dist/commands/pack.controller.d.ts +15 -0
- package/dist/commands/pack.controller.js +25 -0
- package/dist/commands/permissions.controller.d.ts +14 -0
- package/dist/commands/permissions.controller.js +26 -0
- package/dist/commands/registry.controller.d.ts +16 -0
- package/dist/commands/registry.controller.js +27 -0
- package/dist/commands/revoke.controller.d.ts +15 -0
- package/dist/commands/revoke.controller.js +24 -0
- package/dist/commands/run.controller.js +14 -6
- package/dist/commands/uninstall.controller.d.ts +15 -0
- package/dist/commands/uninstall.controller.js +23 -0
- package/dist/commands/update.controller.d.ts +16 -0
- package/dist/commands/update.controller.js +29 -0
- package/dist/host/app-instance.service.d.ts +3 -1
- package/dist/host/app-instance.service.js +2 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +23 -2
- package/dist/install/app-installation.service.d.ts +37 -0
- package/dist/install/app-installation.service.js +255 -0
- package/dist/install/app-installation.types.d.ts +62 -0
- package/dist/main.js +5 -108
- package/dist/package.js +1 -1
- package/dist/paths/app-home.service.d.ts +16 -0
- package/dist/paths/app-home.service.js +42 -0
- package/dist/permissions/app-grant.service.d.ts +22 -0
- package/dist/permissions/app-grant.service.js +63 -0
- package/dist/permissions/app-permissions.service.d.ts +3 -1
- package/dist/permissions/app-permissions.service.js +12 -5
- package/dist/permissions/app-permissions.types.d.ts +28 -1
- package/dist/registry/app-registry-config.service.d.ts +16 -0
- package/dist/registry/app-registry-config.service.js +84 -0
- package/dist/registry/app-registry.service.d.ts +38 -0
- package/dist/registry/app-registry.service.js +115 -0
- package/dist/registry/app-registry.types.d.ts +33 -0
- package/dist/registry/app-remote-registry-client.service.d.ts +28 -0
- package/dist/registry/app-remote-registry-client.service.js +114 -0
- package/dist/registry/app-remote-registry.types.d.ts +52 -0
- package/dist/registry/app-remote-registry.types.js +4 -0
- package/dist/scaffold/app-scaffold.service.d.ts +19 -0
- package/dist/scaffold/app-scaffold.service.js +260 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
`@nextclaw/app-runtime` 是一个独立的微应用 runtime/CLI 包。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
当前相对完善 MVP 提供下面这些能力:
|
|
6
6
|
|
|
7
|
+
- `napp create <app-dir>`:生成一个最小可跑的微应用骨架
|
|
7
8
|
- `napp inspect <app-dir>`:校验应用目录与 manifest
|
|
8
|
-
- `napp run <app-dir
|
|
9
|
+
- `napp run <app-dir|app-id>`:启动本地宿主,支持目录运行和已安装应用运行
|
|
9
10
|
- `napp dev <app-dir>`:当前等价于 `run`
|
|
11
|
+
- `napp pack <app-dir>`:把应用目录打成 `.napp` bundle
|
|
12
|
+
- `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
|
|
13
|
+
- `napp update <app-id>`:更新已安装应用
|
|
14
|
+
- `napp uninstall <app-id>`:卸载已安装应用
|
|
15
|
+
- `napp list`:列出已安装应用
|
|
16
|
+
- `napp info <app-id>`:查看已安装应用详情
|
|
17
|
+
- `napp registry [get|set|reset]`:查看或切换 registry
|
|
18
|
+
- `napp permissions <app-id>`:查看应用权限状态
|
|
19
|
+
- `napp grant <app-id> --document scope=/path`:写入目录授权
|
|
20
|
+
- `napp revoke <app-id> --document scope`:撤销目录授权
|
|
10
21
|
|
|
11
22
|
## 安装
|
|
12
23
|
|
|
@@ -21,7 +32,7 @@ napp --help
|
|
|
21
32
|
napp --version
|
|
22
33
|
```
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
第一版先聚焦“独立可运行的微应用宿主 + 可分发、可安装、可更新、可授权的 CLI/runtime 闭环”,不接入现有 NextClaw 主产品路由、服务或 GUI app store。
|
|
25
36
|
|
|
26
37
|
## 应用目录
|
|
27
38
|
|
|
@@ -41,10 +52,121 @@ assets/
|
|
|
41
52
|
- 宿主桥接:`/__napp/*`
|
|
42
53
|
- 权限词汇:`documentAccess`、`allowedDomains`、`storage`、`capabilities`
|
|
43
54
|
- 当前 Wasm 执行底座:Node 原生 `WebAssembly`
|
|
55
|
+
- 分发包形态:`.napp`(底层为 zip)
|
|
56
|
+
- 安装目录:`~/.nextclaw/apps/packages/<app-id>/<version>/`
|
|
57
|
+
- 用户数据目录:`~/.nextclaw/apps/data/<app-id>/`
|
|
58
|
+
- 本地 registry:`~/.nextclaw/apps/registry.json`
|
|
59
|
+
- 本地 config:`~/.nextclaw/apps/config.json`
|
|
60
|
+
- 默认 registry:`https://registry.nextclaw.com/`
|
|
61
|
+
|
|
62
|
+
## 当前 MVP 工作流
|
|
63
|
+
|
|
64
|
+
开发者工作流:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
napp create ./my-first-napp
|
|
68
|
+
napp inspect ./my-first-napp
|
|
69
|
+
napp pack ./my-first-napp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
本地安装工作流:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
napp install ./my-first-napp
|
|
76
|
+
napp list
|
|
77
|
+
napp info nextclaw.my-first-napp
|
|
78
|
+
napp run nextclaw.my-first-napp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
registry 安装与更新工作流:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
napp registry set https://registry.example.com/
|
|
85
|
+
napp install nextclaw.hello-notes
|
|
86
|
+
napp permissions nextclaw.hello-notes
|
|
87
|
+
napp grant nextclaw.hello-notes --document notes=/absolute/path/to/notes
|
|
88
|
+
napp run nextclaw.hello-notes
|
|
89
|
+
napp update nextclaw.hello-notes
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
卸载与权限回收:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
napp revoke nextclaw.my-first-napp --document notes
|
|
96
|
+
napp uninstall nextclaw.my-first-napp
|
|
97
|
+
napp uninstall nextclaw.my-first-napp --purge-data
|
|
98
|
+
```
|
|
44
99
|
|
|
45
100
|
## 示例
|
|
46
101
|
|
|
102
|
+
```bash
|
|
103
|
+
napp create ./my-first-napp
|
|
104
|
+
napp inspect ./my-first-napp
|
|
105
|
+
napp pack ./my-first-napp
|
|
106
|
+
napp install ./my-first-napp
|
|
107
|
+
napp list
|
|
108
|
+
napp info nextclaw.my-first-napp
|
|
109
|
+
napp permissions nextclaw.my-first-napp
|
|
110
|
+
napp run nextclaw.my-first-napp
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
已有示例应用:
|
|
114
|
+
|
|
47
115
|
```bash
|
|
48
116
|
napp inspect ./apps/examples/hello-notes
|
|
117
|
+
napp pack ./apps/examples/hello-notes
|
|
118
|
+
napp install ./apps/examples/hello-notes
|
|
119
|
+
napp grant nextclaw.hello-notes --document notes=/absolute/path/to/notes
|
|
49
120
|
napp run ./apps/examples/hello-notes --document notes=/absolute/path/to/notes
|
|
50
121
|
```
|
|
122
|
+
|
|
123
|
+
registry 示例:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
napp registry get
|
|
127
|
+
napp registry set https://registry.example.com/
|
|
128
|
+
napp install nextclaw.hello-notes
|
|
129
|
+
napp update nextclaw.hello-notes
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Bundle 结构
|
|
133
|
+
|
|
134
|
+
`.napp` bundle 的最小结构如下:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
manifest.json
|
|
138
|
+
main/
|
|
139
|
+
ui/
|
|
140
|
+
assets/
|
|
141
|
+
.napp/
|
|
142
|
+
bundle.json
|
|
143
|
+
checksums.json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Registry Metadata 结构
|
|
147
|
+
|
|
148
|
+
registry 按 npm 风格使用一个 base URL,再按 app id 拉取 metadata 文档。metadata 的最小结构如下:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"name": "nextclaw.hello-notes",
|
|
153
|
+
"description": "Registry-hosted Hello Notes",
|
|
154
|
+
"dist-tags": {
|
|
155
|
+
"latest": "0.2.0"
|
|
156
|
+
},
|
|
157
|
+
"versions": {
|
|
158
|
+
"0.2.0": {
|
|
159
|
+
"name": "nextclaw.hello-notes",
|
|
160
|
+
"version": "0.2.0",
|
|
161
|
+
"publisher": {
|
|
162
|
+
"id": "nextclaw",
|
|
163
|
+
"name": "NextClaw Official"
|
|
164
|
+
},
|
|
165
|
+
"dist": {
|
|
166
|
+
"bundle": "./-/hello-notes-0.2.0.napp",
|
|
167
|
+
"sha256": "<sha256>"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AppManifestService } from "../manifest/app-manifest.service.js";
|
|
2
|
+
import { AppBundleExtractResult, AppBundlePackResult } from "./app-bundle.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/bundle/app-bundle.service.d.ts
|
|
5
|
+
declare class AppBundleService {
|
|
6
|
+
private readonly manifestService;
|
|
7
|
+
constructor(manifestService?: AppManifestService);
|
|
8
|
+
packAppDirectory: (params: {
|
|
9
|
+
appDirectory: string;
|
|
10
|
+
outputPath?: string;
|
|
11
|
+
}) => Promise<AppBundlePackResult>;
|
|
12
|
+
extractBundle: (params: {
|
|
13
|
+
bundlePath: string;
|
|
14
|
+
targetDirectory: string;
|
|
15
|
+
}) => Promise<AppBundleExtractResult>;
|
|
16
|
+
private collectAppFiles;
|
|
17
|
+
private collectDirectoryPaths;
|
|
18
|
+
private buildMetadata;
|
|
19
|
+
private buildChecksums;
|
|
20
|
+
private computeSha256;
|
|
21
|
+
private normalizeBundleFileName;
|
|
22
|
+
private normalizeArchiveEntry;
|
|
23
|
+
private readJsonFile;
|
|
24
|
+
private verifyChecksums;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { AppBundleService };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AppManifestService } from "../manifest/app-manifest.service.js";
|
|
2
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { strToU8, unzipSync, zipSync } from "fflate";
|
|
6
|
+
//#region src/bundle/app-bundle.service.ts
|
|
7
|
+
var AppBundleService = class {
|
|
8
|
+
constructor(manifestService = new AppManifestService()) {
|
|
9
|
+
this.manifestService = manifestService;
|
|
10
|
+
}
|
|
11
|
+
packAppDirectory = async (params) => {
|
|
12
|
+
const bundle = await this.manifestService.load(params.appDirectory);
|
|
13
|
+
const appFiles = await this.collectAppFiles(bundle.appDirectory);
|
|
14
|
+
const metadata = this.buildMetadata(bundle.manifest.id, bundle.manifest.name, bundle.manifest.version);
|
|
15
|
+
const bundleJsonPath = ".napp/bundle.json";
|
|
16
|
+
const checksumsJsonPath = ".napp/checksums.json";
|
|
17
|
+
const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\n`);
|
|
18
|
+
const checksums = this.buildChecksums({
|
|
19
|
+
...appFiles,
|
|
20
|
+
[bundleJsonPath]: bundleJsonBytes
|
|
21
|
+
});
|
|
22
|
+
const checksumsJsonBytes = strToU8(`${JSON.stringify(checksums, null, 2)}\n`);
|
|
23
|
+
const archiveBytes = zipSync({
|
|
24
|
+
...appFiles,
|
|
25
|
+
[bundleJsonPath]: bundleJsonBytes,
|
|
26
|
+
[checksumsJsonPath]: checksumsJsonBytes
|
|
27
|
+
}, { level: 9 });
|
|
28
|
+
const outputPath = params.outputPath ? path.resolve(params.outputPath) : path.join(path.dirname(bundle.appDirectory), `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}.napp`);
|
|
29
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
30
|
+
await writeFile(outputPath, Buffer.from(archiveBytes));
|
|
31
|
+
return {
|
|
32
|
+
bundlePath: outputPath,
|
|
33
|
+
metadata
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
extractBundle = async (params) => {
|
|
37
|
+
const bundlePath = path.resolve(params.bundlePath);
|
|
38
|
+
const targetDirectory = path.resolve(params.targetDirectory);
|
|
39
|
+
await rm(targetDirectory, {
|
|
40
|
+
recursive: true,
|
|
41
|
+
force: true
|
|
42
|
+
});
|
|
43
|
+
await mkdir(targetDirectory, { recursive: true });
|
|
44
|
+
const archive = unzipSync(new Uint8Array(await readFile(bundlePath)));
|
|
45
|
+
const entryNames = Object.keys(archive).sort((left, right) => left.localeCompare(right));
|
|
46
|
+
for (const entryName of entryNames) {
|
|
47
|
+
const normalizedEntry = this.normalizeArchiveEntry(entryName);
|
|
48
|
+
const targetPath = path.join(targetDirectory, normalizedEntry);
|
|
49
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
50
|
+
await writeFile(targetPath, Buffer.from(archive[entryName]));
|
|
51
|
+
}
|
|
52
|
+
const metadata = await this.readJsonFile(path.join(targetDirectory, ".napp", "bundle.json"), "bundle metadata");
|
|
53
|
+
const checksums = await this.readJsonFile(path.join(targetDirectory, ".napp", "checksums.json"), "bundle checksums");
|
|
54
|
+
await this.verifyChecksums(targetDirectory, checksums);
|
|
55
|
+
await this.manifestService.load(targetDirectory);
|
|
56
|
+
return {
|
|
57
|
+
appDirectory: targetDirectory,
|
|
58
|
+
metadata,
|
|
59
|
+
checksums
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
collectAppFiles = async (appDirectory) => {
|
|
63
|
+
const filePaths = new Set(["manifest.json"]);
|
|
64
|
+
await this.collectDirectoryPaths(path.join(appDirectory, "main"), appDirectory, filePaths);
|
|
65
|
+
await this.collectDirectoryPaths(path.join(appDirectory, "ui"), appDirectory, filePaths);
|
|
66
|
+
await this.collectDirectoryPaths(path.join(appDirectory, "assets"), appDirectory, filePaths);
|
|
67
|
+
const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));
|
|
68
|
+
const appFiles = {};
|
|
69
|
+
for (const relativePath of sortedPaths) appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));
|
|
70
|
+
return appFiles;
|
|
71
|
+
};
|
|
72
|
+
collectDirectoryPaths = async (directoryPath, appDirectory, filePaths) => {
|
|
73
|
+
try {
|
|
74
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const entryPath = path.join(directoryPath, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
await this.collectDirectoryPaths(entryPath, appDirectory, filePaths);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!entry.isFile()) continue;
|
|
82
|
+
filePaths.add(path.relative(appDirectory, entryPath));
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
buildMetadata = (appId, name, version) => {
|
|
89
|
+
return {
|
|
90
|
+
bundleFormatVersion: 1,
|
|
91
|
+
appId,
|
|
92
|
+
name,
|
|
93
|
+
version,
|
|
94
|
+
entryManifest: "manifest.json",
|
|
95
|
+
checksumsFile: ".napp/checksums.json"
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
buildChecksums = (files) => {
|
|
99
|
+
const entries = Object.entries(files).sort(([left], [right]) => left.localeCompare(right));
|
|
100
|
+
return {
|
|
101
|
+
algorithm: "sha256",
|
|
102
|
+
files: Object.fromEntries(entries.map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]))
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
computeSha256 = (fileBytes) => {
|
|
106
|
+
return createHash("sha256").update(Buffer.from(fileBytes)).digest("hex");
|
|
107
|
+
};
|
|
108
|
+
normalizeBundleFileName = (appId) => {
|
|
109
|
+
return appId.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
110
|
+
};
|
|
111
|
+
normalizeArchiveEntry = (entryName) => {
|
|
112
|
+
const normalized = entryName.replace(/\\/g, "/");
|
|
113
|
+
if (!normalized || normalized.startsWith("/") || normalized.includes("../")) throw new Error(`bundle 内包含非法路径:${entryName}`);
|
|
114
|
+
return normalized;
|
|
115
|
+
};
|
|
116
|
+
readJsonFile = async (filePath, label) => {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(await readFile(filePath, "utf-8"));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw new Error(`无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
verifyChecksums = async (appDirectory, checksums) => {
|
|
124
|
+
if (checksums.algorithm !== "sha256") throw new Error("当前只支持 sha256 checksums。");
|
|
125
|
+
const entries = Object.entries(checksums.files).sort(([left], [right]) => left.localeCompare(right));
|
|
126
|
+
for (const [relativePath, expectedHash] of entries) {
|
|
127
|
+
const fileBytes = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));
|
|
128
|
+
if (this.computeSha256(fileBytes) !== expectedHash) throw new Error(`bundle checksum 校验失败:${relativePath}`);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { AppBundleService };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/bundle/app-bundle.types.d.ts
|
|
2
|
+
type AppBundleMetadata = {
|
|
3
|
+
bundleFormatVersion: 1;
|
|
4
|
+
appId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
entryManifest: "manifest.json";
|
|
8
|
+
checksumsFile: ".napp/checksums.json";
|
|
9
|
+
};
|
|
10
|
+
type AppBundleChecksums = {
|
|
11
|
+
algorithm: "sha256";
|
|
12
|
+
files: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
type AppBundlePackResult = {
|
|
15
|
+
bundlePath: string;
|
|
16
|
+
metadata: AppBundleMetadata;
|
|
17
|
+
};
|
|
18
|
+
type AppBundleExtractResult = {
|
|
19
|
+
appDirectory: string;
|
|
20
|
+
metadata: AppBundleMetadata;
|
|
21
|
+
checksums: AppBundleChecksums;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AppRuntimeOptionsService } from "./app-runtime-options.service.js";
|
|
2
|
+
|
|
3
|
+
//#region src/cli/app-runtime-cli.service.d.ts
|
|
4
|
+
declare class AppRuntimeCliService {
|
|
5
|
+
private readonly optionsService;
|
|
6
|
+
constructor(optionsService?: AppRuntimeOptionsService);
|
|
7
|
+
run: () => Promise<void>;
|
|
8
|
+
private dispatch;
|
|
9
|
+
private handleCreate;
|
|
10
|
+
private handleInspect;
|
|
11
|
+
private handleRun;
|
|
12
|
+
private handleDev;
|
|
13
|
+
private handlePack;
|
|
14
|
+
private handleInstall;
|
|
15
|
+
private handleUpdate;
|
|
16
|
+
private handleUninstall;
|
|
17
|
+
private handleList;
|
|
18
|
+
private handleInfo;
|
|
19
|
+
private handleRegistry;
|
|
20
|
+
private handlePermissions;
|
|
21
|
+
private handleGrant;
|
|
22
|
+
private handleRevoke;
|
|
23
|
+
private writeUsage;
|
|
24
|
+
private write;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { AppRuntimeCliService };
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { version } from "../package.js";
|
|
2
|
+
import { CreateCommand } from "../commands/create.controller.js";
|
|
3
|
+
import { RunCommand } from "../commands/run.controller.js";
|
|
4
|
+
import { DevCommand } from "../commands/dev.controller.js";
|
|
5
|
+
import { GrantCommand } from "../commands/grant.controller.js";
|
|
6
|
+
import { InfoCommand } from "../commands/info.controller.js";
|
|
7
|
+
import { InspectCommand } from "../commands/inspect.controller.js";
|
|
8
|
+
import { InstallCommand } from "../commands/install.controller.js";
|
|
9
|
+
import { ListCommand } from "../commands/list.controller.js";
|
|
10
|
+
import { PackCommand } from "../commands/pack.controller.js";
|
|
11
|
+
import { PermissionsCommand } from "../commands/permissions.controller.js";
|
|
12
|
+
import { RegistryCommand } from "../commands/registry.controller.js";
|
|
13
|
+
import { RevokeCommand } from "../commands/revoke.controller.js";
|
|
14
|
+
import { UninstallCommand } from "../commands/uninstall.controller.js";
|
|
15
|
+
import { UpdateCommand } from "../commands/update.controller.js";
|
|
16
|
+
import { AppRuntimeOptionsService } from "./app-runtime-options.service.js";
|
|
17
|
+
//#region src/cli/app-runtime-cli.service.ts
|
|
18
|
+
var AppRuntimeCliService = class {
|
|
19
|
+
constructor(optionsService = new AppRuntimeOptionsService()) {
|
|
20
|
+
this.optionsService = optionsService;
|
|
21
|
+
}
|
|
22
|
+
run = async () => {
|
|
23
|
+
const [command, ...restArgs] = process.argv.slice(2);
|
|
24
|
+
if (command === "--help" || command === "help") {
|
|
25
|
+
this.writeUsage();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (command === "--version" || command === "version") {
|
|
29
|
+
this.write(`${version}\n`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!command) {
|
|
33
|
+
this.writeUsage();
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
await this.dispatch(command, restArgs);
|
|
37
|
+
};
|
|
38
|
+
dispatch = async (command, restArgs) => {
|
|
39
|
+
switch (command) {
|
|
40
|
+
case "create":
|
|
41
|
+
await this.handleCreate(restArgs);
|
|
42
|
+
return;
|
|
43
|
+
case "inspect":
|
|
44
|
+
await this.handleInspect(restArgs);
|
|
45
|
+
return;
|
|
46
|
+
case "run":
|
|
47
|
+
await this.handleRun(restArgs);
|
|
48
|
+
return;
|
|
49
|
+
case "dev":
|
|
50
|
+
await this.handleDev(restArgs);
|
|
51
|
+
return;
|
|
52
|
+
case "pack":
|
|
53
|
+
await this.handlePack(restArgs);
|
|
54
|
+
return;
|
|
55
|
+
case "install":
|
|
56
|
+
await this.handleInstall(restArgs);
|
|
57
|
+
return;
|
|
58
|
+
case "update":
|
|
59
|
+
await this.handleUpdate(restArgs);
|
|
60
|
+
return;
|
|
61
|
+
case "uninstall":
|
|
62
|
+
await this.handleUninstall(restArgs);
|
|
63
|
+
return;
|
|
64
|
+
case "list":
|
|
65
|
+
await this.handleList(restArgs);
|
|
66
|
+
return;
|
|
67
|
+
case "info":
|
|
68
|
+
await this.handleInfo(restArgs);
|
|
69
|
+
return;
|
|
70
|
+
case "registry":
|
|
71
|
+
await this.handleRegistry(restArgs);
|
|
72
|
+
return;
|
|
73
|
+
case "permissions":
|
|
74
|
+
await this.handlePermissions(restArgs);
|
|
75
|
+
return;
|
|
76
|
+
case "grant":
|
|
77
|
+
await this.handleGrant(restArgs);
|
|
78
|
+
return;
|
|
79
|
+
case "revoke":
|
|
80
|
+
await this.handleRevoke(restArgs);
|
|
81
|
+
return;
|
|
82
|
+
default:
|
|
83
|
+
this.writeUsage();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
handleCreate = async (restArgs) => {
|
|
88
|
+
const { target, optionArgs } = this.optionsService.readTarget("create", restArgs);
|
|
89
|
+
const options = this.optionsService.readCreateOptions(optionArgs);
|
|
90
|
+
await new CreateCommand().run({
|
|
91
|
+
appDirectory: target,
|
|
92
|
+
json: options.json,
|
|
93
|
+
write: this.write
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
handleInspect = async (restArgs) => {
|
|
97
|
+
const { target, optionArgs } = this.optionsService.readTarget("inspect", restArgs);
|
|
98
|
+
const options = this.optionsService.readJsonOnlyOptions(optionArgs);
|
|
99
|
+
await new InspectCommand().run({
|
|
100
|
+
appDirectory: target,
|
|
101
|
+
json: options.json,
|
|
102
|
+
write: this.write
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
handleRun = async (restArgs) => {
|
|
106
|
+
const { target, optionArgs } = this.optionsService.readTarget("run", restArgs);
|
|
107
|
+
const options = this.optionsService.readRuntimeOptions(optionArgs);
|
|
108
|
+
await new RunCommand().run({
|
|
109
|
+
appReference: target,
|
|
110
|
+
host: options.host,
|
|
111
|
+
port: options.port,
|
|
112
|
+
json: options.json,
|
|
113
|
+
documentGrantMap: options.documentGrantMap,
|
|
114
|
+
write: this.write
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
handleDev = async (restArgs) => {
|
|
118
|
+
const { target, optionArgs } = this.optionsService.readTarget("dev", restArgs);
|
|
119
|
+
const options = this.optionsService.readRuntimeOptions(optionArgs);
|
|
120
|
+
await new DevCommand().run({
|
|
121
|
+
appReference: target,
|
|
122
|
+
host: options.host,
|
|
123
|
+
port: options.port,
|
|
124
|
+
json: options.json,
|
|
125
|
+
documentGrantMap: options.documentGrantMap,
|
|
126
|
+
write: this.write
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
handlePack = async (restArgs) => {
|
|
130
|
+
const { target, optionArgs } = this.optionsService.readTarget("pack", restArgs);
|
|
131
|
+
const options = this.optionsService.readPackOptions(optionArgs);
|
|
132
|
+
await new PackCommand().run({
|
|
133
|
+
appDirectory: target,
|
|
134
|
+
outputPath: options.outputPath,
|
|
135
|
+
json: options.json,
|
|
136
|
+
write: this.write
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
handleInstall = async (restArgs) => {
|
|
140
|
+
const { target, optionArgs } = this.optionsService.readTarget("install", restArgs);
|
|
141
|
+
const options = this.optionsService.readInstallOptions(optionArgs);
|
|
142
|
+
await new InstallCommand().run({
|
|
143
|
+
appSource: target,
|
|
144
|
+
registryUrl: options.registryUrl,
|
|
145
|
+
json: options.json,
|
|
146
|
+
write: this.write
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
handleUpdate = async (restArgs) => {
|
|
150
|
+
const { target, optionArgs } = this.optionsService.readTarget("update", restArgs);
|
|
151
|
+
const options = this.optionsService.readUpdateOptions(optionArgs);
|
|
152
|
+
await new UpdateCommand().run({
|
|
153
|
+
appId: target,
|
|
154
|
+
version: options.version,
|
|
155
|
+
registryUrl: options.registryUrl,
|
|
156
|
+
json: options.json,
|
|
157
|
+
write: this.write
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
handleUninstall = async (restArgs) => {
|
|
161
|
+
const { target, optionArgs } = this.optionsService.readTarget("uninstall", restArgs);
|
|
162
|
+
const options = this.optionsService.readUninstallOptions(optionArgs);
|
|
163
|
+
await new UninstallCommand().run({
|
|
164
|
+
appId: target,
|
|
165
|
+
purgeData: options.purgeData,
|
|
166
|
+
json: options.json,
|
|
167
|
+
write: this.write
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
handleList = async (restArgs) => {
|
|
171
|
+
const options = this.optionsService.readJsonOnlyOptions(restArgs);
|
|
172
|
+
await new ListCommand().run({
|
|
173
|
+
json: options.json,
|
|
174
|
+
write: this.write
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
handleInfo = async (restArgs) => {
|
|
178
|
+
const { target, optionArgs } = this.optionsService.readTarget("info", restArgs);
|
|
179
|
+
const options = this.optionsService.readJsonOnlyOptions(optionArgs);
|
|
180
|
+
await new InfoCommand().run({
|
|
181
|
+
appId: target,
|
|
182
|
+
json: options.json,
|
|
183
|
+
write: this.write
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
handleRegistry = async (restArgs) => {
|
|
187
|
+
const [actionOrUrl, ...optionArgs] = restArgs;
|
|
188
|
+
if (!actionOrUrl || actionOrUrl === "get") {
|
|
189
|
+
const options = this.optionsService.readJsonOnlyOptions(optionArgs);
|
|
190
|
+
await new RegistryCommand().run({
|
|
191
|
+
action: "get",
|
|
192
|
+
json: options.json,
|
|
193
|
+
write: this.write
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (actionOrUrl === "--json") {
|
|
198
|
+
const options = this.optionsService.readJsonOnlyOptions([actionOrUrl, ...optionArgs]);
|
|
199
|
+
await new RegistryCommand().run({
|
|
200
|
+
action: "get",
|
|
201
|
+
json: options.json,
|
|
202
|
+
write: this.write
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (actionOrUrl === "set") {
|
|
207
|
+
const { target, optionArgs: actionOptionArgs } = this.optionsService.readTarget("registry set", optionArgs);
|
|
208
|
+
const options = this.optionsService.readJsonOnlyOptions(actionOptionArgs);
|
|
209
|
+
await new RegistryCommand().run({
|
|
210
|
+
action: "set",
|
|
211
|
+
registryUrl: target,
|
|
212
|
+
json: options.json,
|
|
213
|
+
write: this.write
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (actionOrUrl === "reset") {
|
|
218
|
+
const options = this.optionsService.readJsonOnlyOptions(optionArgs);
|
|
219
|
+
await new RegistryCommand().run({
|
|
220
|
+
action: "reset",
|
|
221
|
+
json: options.json,
|
|
222
|
+
write: this.write
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`未知 registry 子命令:${actionOrUrl}`);
|
|
227
|
+
};
|
|
228
|
+
handlePermissions = async (restArgs) => {
|
|
229
|
+
const { target, optionArgs } = this.optionsService.readTarget("permissions", restArgs);
|
|
230
|
+
const options = this.optionsService.readJsonOnlyOptions(optionArgs);
|
|
231
|
+
await new PermissionsCommand().run({
|
|
232
|
+
appId: target,
|
|
233
|
+
json: options.json,
|
|
234
|
+
write: this.write
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
handleGrant = async (restArgs) => {
|
|
238
|
+
const { target, optionArgs } = this.optionsService.readTarget("grant", restArgs);
|
|
239
|
+
const options = this.optionsService.readGrantOptions(optionArgs);
|
|
240
|
+
await new GrantCommand().run({
|
|
241
|
+
appId: target,
|
|
242
|
+
documentGrantMap: options.documentGrantMap,
|
|
243
|
+
json: options.json,
|
|
244
|
+
write: this.write
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
handleRevoke = async (restArgs) => {
|
|
248
|
+
const { target, optionArgs } = this.optionsService.readTarget("revoke", restArgs);
|
|
249
|
+
const options = this.optionsService.readRevokeOptions(optionArgs);
|
|
250
|
+
await new RevokeCommand().run({
|
|
251
|
+
appId: target,
|
|
252
|
+
documentScopeIds: options.documentScopeIds,
|
|
253
|
+
json: options.json,
|
|
254
|
+
write: this.write
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
writeUsage = () => {
|
|
258
|
+
this.write("Usage: napp create <app-dir> [--json]\n");
|
|
259
|
+
this.write(" napp inspect <app-dir> [--json]\n");
|
|
260
|
+
this.write(" napp <run|dev> <app-dir|app-id> [--host 127.0.0.1] [--port 3100] [--json] [--document scope=/path]\n");
|
|
261
|
+
this.write(" napp pack <app-dir> [--out bundle.napp] [--json]\n");
|
|
262
|
+
this.write(" napp install <app-dir|bundle.napp|app-id[@version]> [--registry <url>] [--json]\n");
|
|
263
|
+
this.write(" napp update <app-id> [--version <version>] [--registry <url>] [--json]\n");
|
|
264
|
+
this.write(" napp uninstall <app-id> [--purge-data] [--json]\n");
|
|
265
|
+
this.write(" napp list [--json]\n");
|
|
266
|
+
this.write(" napp info <app-id> [--json]\n");
|
|
267
|
+
this.write(" napp registry [get] [--json]\n");
|
|
268
|
+
this.write(" napp registry set <url> [--json]\n");
|
|
269
|
+
this.write(" napp registry reset [--json]\n");
|
|
270
|
+
this.write(" napp permissions <app-id> [--json]\n");
|
|
271
|
+
this.write(" napp grant <app-id> --document scope=/path [--json]\n");
|
|
272
|
+
this.write(" napp revoke <app-id> --document scope [--json]\n");
|
|
273
|
+
this.write(" napp --help\n");
|
|
274
|
+
this.write(" napp --version\n");
|
|
275
|
+
};
|
|
276
|
+
write = (text) => {
|
|
277
|
+
process.stdout.write(text);
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
//#endregion
|
|
281
|
+
export { AppRuntimeCliService };
|