@nextclaw/app-runtime 0.2.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 +117 -4
- 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/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 -132
- 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 +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppRemoteRegistryResolution } from "./app-remote-registry.types.js";
|
|
2
|
+
import { AppRegistryConfigService } from "./app-registry-config.service.js";
|
|
3
|
+
|
|
4
|
+
//#region src/registry/app-remote-registry-client.service.d.ts
|
|
5
|
+
declare class AppRemoteRegistryClientService {
|
|
6
|
+
private readonly configService;
|
|
7
|
+
constructor(configService?: AppRegistryConfigService);
|
|
8
|
+
resolve: (params: {
|
|
9
|
+
appId: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
registryUrl?: string;
|
|
12
|
+
}) => Promise<AppRemoteRegistryResolution>;
|
|
13
|
+
downloadBundle: (params: {
|
|
14
|
+
resolution: AppRemoteRegistryResolution;
|
|
15
|
+
targetDirectory: string;
|
|
16
|
+
}) => Promise<{
|
|
17
|
+
bundlePath: string;
|
|
18
|
+
}>;
|
|
19
|
+
private parseDocument;
|
|
20
|
+
private parseVersion;
|
|
21
|
+
private parsePublisher;
|
|
22
|
+
private readRequiredString;
|
|
23
|
+
private readOptionalString;
|
|
24
|
+
private normalizeBundleFileName;
|
|
25
|
+
private normalizeRegistryUrl;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { AppRemoteRegistryClientService };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AppRegistryConfigService } from "./app-registry-config.service.js";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/registry/app-remote-registry-client.service.ts
|
|
6
|
+
var AppRemoteRegistryClientService = class {
|
|
7
|
+
constructor(configService = new AppRegistryConfigService()) {
|
|
8
|
+
this.configService = configService;
|
|
9
|
+
}
|
|
10
|
+
resolve = async (params) => {
|
|
11
|
+
const { appId, version: requestedVersion, registryUrl: overrideRegistryUrl } = params;
|
|
12
|
+
const registryUrl = overrideRegistryUrl ? this.normalizeRegistryUrl(overrideRegistryUrl) : (await this.configService.getSnapshot()).currentUrl;
|
|
13
|
+
const metadataUrl = new URL(encodeURIComponent(appId), registryUrl).toString();
|
|
14
|
+
const response = await fetch(metadataUrl);
|
|
15
|
+
if (!response.ok) throw new Error(`无法从 registry 拉取 ${appId} metadata:${response.status} ${response.statusText}`);
|
|
16
|
+
const document = this.parseDocument(await response.json(), appId);
|
|
17
|
+
const version = requestedVersion ?? document["dist-tags"].latest;
|
|
18
|
+
const versionRecord = document.versions[version];
|
|
19
|
+
if (!versionRecord) throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);
|
|
20
|
+
return {
|
|
21
|
+
registryUrl,
|
|
22
|
+
metadataUrl,
|
|
23
|
+
appId,
|
|
24
|
+
version,
|
|
25
|
+
description: versionRecord.description ?? document.description,
|
|
26
|
+
publisher: versionRecord.publisher,
|
|
27
|
+
permissions: versionRecord.permissions,
|
|
28
|
+
bundleUrl: new URL(versionRecord.dist.bundle, metadataUrl).toString(),
|
|
29
|
+
sha256: versionRecord.dist.sha256
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
downloadBundle = async (params) => {
|
|
33
|
+
const { resolution, targetDirectory } = params;
|
|
34
|
+
const response = await fetch(resolution.bundleUrl);
|
|
35
|
+
if (!response.ok) throw new Error(`无法下载 bundle:${resolution.bundleUrl} (${response.status} ${response.statusText})`);
|
|
36
|
+
const bundleBytes = Buffer.from(await response.arrayBuffer());
|
|
37
|
+
const actualSha256 = createHash("sha256").update(bundleBytes).digest("hex");
|
|
38
|
+
if (actualSha256 !== resolution.sha256) throw new Error(`bundle checksum 校验失败:期望 ${resolution.sha256},实际 ${actualSha256}`);
|
|
39
|
+
const bundlePath = path.join(targetDirectory, `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}.napp`);
|
|
40
|
+
await writeFile(bundlePath, bundleBytes);
|
|
41
|
+
return { bundlePath };
|
|
42
|
+
};
|
|
43
|
+
parseDocument = (rawDocument, appId) => {
|
|
44
|
+
if (!rawDocument || typeof rawDocument !== "object" || Array.isArray(rawDocument)) throw new Error(`registry ${appId} metadata 必须是对象。`);
|
|
45
|
+
const candidate = rawDocument;
|
|
46
|
+
const name = this.readRequiredString(candidate.name, "name");
|
|
47
|
+
if (name !== appId) throw new Error(`registry 返回的包名与请求不一致:请求 ${appId},收到 ${name}`);
|
|
48
|
+
const distTags = candidate["dist-tags"];
|
|
49
|
+
if (!distTags || typeof distTags !== "object" || Array.isArray(distTags)) throw new Error("registry metadata 缺少 dist-tags。");
|
|
50
|
+
const versions = candidate.versions;
|
|
51
|
+
if (!versions || typeof versions !== "object" || Array.isArray(versions)) throw new Error("registry metadata 缺少 versions。");
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
description: this.readOptionalString(candidate.description, "description"),
|
|
55
|
+
"dist-tags": { latest: this.readRequiredString(distTags.latest, "dist-tags.latest") },
|
|
56
|
+
versions: Object.fromEntries(Object.entries(versions).map(([version, rawVersion]) => [version, this.parseVersion(rawVersion, name, version)]))
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
parseVersion = (rawVersion, appId, version) => {
|
|
60
|
+
if (!rawVersion || typeof rawVersion !== "object" || Array.isArray(rawVersion)) throw new Error(`registry metadata 的 versions.${version} 必须是对象。`);
|
|
61
|
+
const candidate = rawVersion;
|
|
62
|
+
const name = this.readRequiredString(candidate.name, `versions.${version}.name`);
|
|
63
|
+
const parsedVersion = this.readRequiredString(candidate.version, `versions.${version}.version`);
|
|
64
|
+
if (name !== appId || parsedVersion !== version) throw new Error(`registry metadata 的 versions.${version} 与 app id/version 不一致。`);
|
|
65
|
+
const dist = candidate.dist;
|
|
66
|
+
if (!dist || typeof dist !== "object" || Array.isArray(dist)) throw new Error(`registry metadata 的 versions.${version}.dist 必须是对象。`);
|
|
67
|
+
const distCandidate = dist;
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
version: parsedVersion,
|
|
71
|
+
description: this.readOptionalString(candidate.description, `versions.${version}.description`),
|
|
72
|
+
publisher: this.parsePublisher(candidate.publisher, version),
|
|
73
|
+
permissions: candidate.permissions,
|
|
74
|
+
dist: {
|
|
75
|
+
bundle: this.readRequiredString(distCandidate.bundle, `versions.${version}.dist.bundle`),
|
|
76
|
+
sha256: this.readRequiredString(distCandidate.sha256, `versions.${version}.dist.sha256`)
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
parsePublisher = (rawPublisher, version) => {
|
|
81
|
+
if (rawPublisher === void 0) return;
|
|
82
|
+
if (!rawPublisher || typeof rawPublisher !== "object" || Array.isArray(rawPublisher)) throw new Error(`versions.${version}.publisher 必须是对象。`);
|
|
83
|
+
const candidate = rawPublisher;
|
|
84
|
+
return {
|
|
85
|
+
id: this.readRequiredString(candidate.id, `versions.${version}.publisher.id`),
|
|
86
|
+
name: this.readRequiredString(candidate.name, `versions.${version}.publisher.name`),
|
|
87
|
+
url: this.readOptionalString(candidate.url, `versions.${version}.publisher.url`)
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
readRequiredString = (value, fieldName) => {
|
|
91
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${fieldName} 必须是非空字符串。`);
|
|
92
|
+
return value.trim();
|
|
93
|
+
};
|
|
94
|
+
readOptionalString = (value, fieldName) => {
|
|
95
|
+
if (value === void 0) return;
|
|
96
|
+
return this.readRequiredString(value, fieldName);
|
|
97
|
+
};
|
|
98
|
+
normalizeBundleFileName = (appId) => {
|
|
99
|
+
return appId.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
100
|
+
};
|
|
101
|
+
normalizeRegistryUrl = (registryUrl) => {
|
|
102
|
+
let normalized;
|
|
103
|
+
try {
|
|
104
|
+
normalized = new URL(registryUrl);
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`非法 registry URL:${registryUrl}`);
|
|
107
|
+
}
|
|
108
|
+
if (normalized.protocol !== "http:" && normalized.protocol !== "https:") throw new Error(`registry URL 只支持 http/https:${registryUrl}`);
|
|
109
|
+
const text = normalized.toString();
|
|
110
|
+
return text.endsWith("/") ? text : `${text}/`;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
export { AppRemoteRegistryClientService };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AppPermissions } from "../manifest/app-manifest.types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/registry/app-remote-registry.types.d.ts
|
|
4
|
+
declare const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.com/";
|
|
5
|
+
type AppPublisher = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
};
|
|
10
|
+
type AppRegistryConfig = {
|
|
11
|
+
schemaVersion: 1;
|
|
12
|
+
registry: {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type AppRegistryConfigSnapshot = {
|
|
17
|
+
defaultUrl: string;
|
|
18
|
+
currentUrl: string;
|
|
19
|
+
source: "default" | "config" | "env";
|
|
20
|
+
};
|
|
21
|
+
type AppRemoteRegistryVersion = {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
publisher?: AppPublisher;
|
|
26
|
+
permissions?: AppPermissions;
|
|
27
|
+
dist: {
|
|
28
|
+
bundle: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
type AppRemoteRegistryDocument = {
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
"dist-tags": {
|
|
36
|
+
latest: string;
|
|
37
|
+
};
|
|
38
|
+
versions: Record<string, AppRemoteRegistryVersion>;
|
|
39
|
+
};
|
|
40
|
+
type AppRemoteRegistryResolution = {
|
|
41
|
+
registryUrl: string;
|
|
42
|
+
metadataUrl: string;
|
|
43
|
+
appId: string;
|
|
44
|
+
version: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
publisher?: AppPublisher;
|
|
47
|
+
permissions?: AppPermissions;
|
|
48
|
+
bundleUrl: string;
|
|
49
|
+
sha256: string;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/scaffold/app-scaffold.service.d.ts
|
|
2
|
+
type AppScaffoldResult = {
|
|
3
|
+
appDirectory: string;
|
|
4
|
+
manifestPath: string;
|
|
5
|
+
};
|
|
6
|
+
declare class AppScaffoldService {
|
|
7
|
+
scaffold: (targetDirectory: string) => Promise<AppScaffoldResult>;
|
|
8
|
+
private assertTargetDoesNotExist;
|
|
9
|
+
private buildAppId;
|
|
10
|
+
private buildAppName;
|
|
11
|
+
private normalizeSlug;
|
|
12
|
+
private buildManifest;
|
|
13
|
+
private buildWatSource;
|
|
14
|
+
private buildUiHtml;
|
|
15
|
+
private buildUiController;
|
|
16
|
+
private buildIconSvg;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppScaffoldService };
|
|
@@ -255,6 +255,6 @@ await loadManifest();
|
|
|
255
255
|
`;
|
|
256
256
|
};
|
|
257
257
|
};
|
|
258
|
-
const APP_WASM_BASE64 = "AGFzbQEAAAABBwFgAn9/
|
|
258
|
+
const APP_WASM_BASE64 = "AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL";
|
|
259
259
|
//#endregion
|
|
260
260
|
export { AppScaffoldService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/app-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Standalone micro app runtime and CLI for NextClaw apps.",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"dist",
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
|
-
"dependencies": {
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"fflate": "^0.8.2"
|
|
43
|
+
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^20.17.6",
|
|
44
46
|
"prettier": "^3.3.3",
|