@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.
Files changed (58) hide show
  1. package/README.md +117 -4
  2. package/dist/bundle/app-bundle.service.d.ts +27 -0
  3. package/dist/bundle/app-bundle.service.js +133 -0
  4. package/dist/bundle/app-bundle.types.d.ts +24 -0
  5. package/dist/cli/app-runtime-cli.service.d.ts +27 -0
  6. package/dist/cli/app-runtime-cli.service.js +281 -0
  7. package/dist/cli/app-runtime-options.service.d.ts +60 -0
  8. package/dist/cli/app-runtime-options.service.js +215 -0
  9. package/dist/commands/create.controller.d.ts +14 -0
  10. package/dist/commands/grant.controller.d.ts +16 -0
  11. package/dist/commands/grant.controller.js +25 -0
  12. package/dist/commands/info.controller.d.ts +14 -0
  13. package/dist/commands/info.controller.js +27 -0
  14. package/dist/commands/install.controller.d.ts +15 -0
  15. package/dist/commands/install.controller.js +24 -0
  16. package/dist/commands/list.controller.d.ts +13 -0
  17. package/dist/commands/list.controller.js +25 -0
  18. package/dist/commands/pack.controller.d.ts +15 -0
  19. package/dist/commands/pack.controller.js +25 -0
  20. package/dist/commands/permissions.controller.d.ts +14 -0
  21. package/dist/commands/permissions.controller.js +26 -0
  22. package/dist/commands/registry.controller.d.ts +16 -0
  23. package/dist/commands/registry.controller.js +27 -0
  24. package/dist/commands/revoke.controller.d.ts +15 -0
  25. package/dist/commands/revoke.controller.js +24 -0
  26. package/dist/commands/run.controller.js +14 -6
  27. package/dist/commands/uninstall.controller.d.ts +15 -0
  28. package/dist/commands/uninstall.controller.js +23 -0
  29. package/dist/commands/update.controller.d.ts +16 -0
  30. package/dist/commands/update.controller.js +29 -0
  31. package/dist/host/app-instance.service.d.ts +3 -1
  32. package/dist/host/app-instance.service.js +2 -2
  33. package/dist/index.d.ts +26 -2
  34. package/dist/index.js +23 -2
  35. package/dist/install/app-installation.service.d.ts +37 -0
  36. package/dist/install/app-installation.service.js +255 -0
  37. package/dist/install/app-installation.types.d.ts +62 -0
  38. package/dist/main.js +5 -132
  39. package/dist/package.js +1 -1
  40. package/dist/paths/app-home.service.d.ts +16 -0
  41. package/dist/paths/app-home.service.js +42 -0
  42. package/dist/permissions/app-grant.service.d.ts +22 -0
  43. package/dist/permissions/app-grant.service.js +63 -0
  44. package/dist/permissions/app-permissions.service.d.ts +3 -1
  45. package/dist/permissions/app-permissions.service.js +12 -5
  46. package/dist/permissions/app-permissions.types.d.ts +28 -1
  47. package/dist/registry/app-registry-config.service.d.ts +16 -0
  48. package/dist/registry/app-registry-config.service.js +84 -0
  49. package/dist/registry/app-registry.service.d.ts +38 -0
  50. package/dist/registry/app-registry.service.js +115 -0
  51. package/dist/registry/app-registry.types.d.ts +33 -0
  52. package/dist/registry/app-remote-registry-client.service.d.ts +28 -0
  53. package/dist/registry/app-remote-registry-client.service.js +114 -0
  54. package/dist/registry/app-remote-registry.types.d.ts +52 -0
  55. package/dist/registry/app-remote-registry.types.js +4 -0
  56. package/dist/scaffold/app-scaffold.service.d.ts +19 -0
  57. package/dist/scaffold/app-scaffold.service.js +1 -1
  58. 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,4 @@
1
+ //#region src/registry/app-remote-registry.types.ts
2
+ const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.com/";
3
+ //#endregion
4
+ export { 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/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABagsQQQQK";
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.2.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",