@nextclaw/app-runtime 0.2.0 → 0.4.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 (68) hide show
  1. package/README.md +125 -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 +28 -0
  6. package/dist/cli/app-runtime-cli.service.js +298 -0
  7. package/dist/cli/app-runtime-options.service.d.ts +67 -0
  8. package/dist/cli/app-runtime-options.service.js +242 -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/publish.controller.d.ts +17 -0
  23. package/dist/commands/publish.controller.js +29 -0
  24. package/dist/commands/registry.controller.d.ts +16 -0
  25. package/dist/commands/registry.controller.js +27 -0
  26. package/dist/commands/revoke.controller.d.ts +15 -0
  27. package/dist/commands/revoke.controller.js +24 -0
  28. package/dist/commands/run.controller.js +14 -6
  29. package/dist/commands/uninstall.controller.d.ts +15 -0
  30. package/dist/commands/uninstall.controller.js +23 -0
  31. package/dist/commands/update.controller.d.ts +16 -0
  32. package/dist/commands/update.controller.js +29 -0
  33. package/dist/host/app-instance.service.d.ts +3 -1
  34. package/dist/host/app-instance.service.js +2 -2
  35. package/dist/index.d.ts +31 -2
  36. package/dist/index.js +28 -2
  37. package/dist/install/app-installation.service.d.ts +37 -0
  38. package/dist/install/app-installation.service.js +255 -0
  39. package/dist/install/app-installation.types.d.ts +62 -0
  40. package/dist/main.js +5 -132
  41. package/dist/package.js +1 -1
  42. package/dist/paths/app-home.service.d.ts +16 -0
  43. package/dist/paths/app-home.service.js +42 -0
  44. package/dist/permissions/app-grant.service.d.ts +22 -0
  45. package/dist/permissions/app-grant.service.js +63 -0
  46. package/dist/permissions/app-permissions.service.d.ts +3 -1
  47. package/dist/permissions/app-permissions.service.js +12 -5
  48. package/dist/permissions/app-permissions.types.d.ts +28 -1
  49. package/dist/publish/app-marketplace-client.service.d.ts +13 -0
  50. package/dist/publish/app-marketplace-client.service.js +31 -0
  51. package/dist/publish/app-marketplace-metadata.service.d.ts +27 -0
  52. package/dist/publish/app-marketplace-metadata.service.js +84 -0
  53. package/dist/publish/app-publish.service.d.ts +22 -0
  54. package/dist/publish/app-publish.service.js +74 -0
  55. package/dist/publish/app-publish.types.d.ts +66 -0
  56. package/dist/publish/app-publish.types.js +4 -0
  57. package/dist/registry/app-registry-config.service.d.ts +16 -0
  58. package/dist/registry/app-registry-config.service.js +84 -0
  59. package/dist/registry/app-registry.service.d.ts +38 -0
  60. package/dist/registry/app-registry.service.js +115 -0
  61. package/dist/registry/app-registry.types.d.ts +33 -0
  62. package/dist/registry/app-remote-registry-client.service.d.ts +28 -0
  63. package/dist/registry/app-remote-registry-client.service.js +114 -0
  64. package/dist/registry/app-remote-registry.types.d.ts +52 -0
  65. package/dist/registry/app-remote-registry.types.js +4 -0
  66. package/dist/scaffold/app-scaffold.service.d.ts +21 -0
  67. package/dist/scaffold/app-scaffold.service.js +55 -1
  68. package/package.json +4 -2
package/dist/main.js CHANGED
@@ -1,136 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { version } from "./package.js";
3
- import { CreateCommand } from "./commands/create.controller.js";
4
- import { RunCommand } from "./commands/run.controller.js";
5
- import { DevCommand } from "./commands/dev.controller.js";
6
- import { InspectCommand } from "./commands/inspect.controller.js";
2
+ import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
7
3
  //#region src/main.ts
8
- var AppRuntimeCli = class {
9
- run = async () => {
10
- const [command, appDirectory, ...restArgs] = process.argv.slice(2);
11
- if (command === "--help" || command === "help") {
12
- this.writeUsage();
13
- return;
14
- }
15
- if (command === "--version" || command === "version") {
16
- this.write(`${version}\n`);
17
- return;
18
- }
19
- if (!command || !appDirectory) {
20
- this.writeUsage();
21
- process.exit(1);
22
- }
23
- if (command === "create") {
24
- const options = this.readCreateOptions(restArgs);
25
- await new CreateCommand().run({
26
- appDirectory,
27
- json: options.json,
28
- write: this.write
29
- });
30
- return;
31
- }
32
- const options = this.readRuntimeOptions(restArgs);
33
- if (command === "inspect") {
34
- await new InspectCommand().run({
35
- appDirectory,
36
- json: options.json,
37
- write: this.write
38
- });
39
- return;
40
- }
41
- if (command === "run") {
42
- await new RunCommand().run({
43
- appDirectory,
44
- host: options.host,
45
- port: options.port,
46
- json: options.json,
47
- documentGrantMap: options.documentGrantMap,
48
- write: this.write
49
- });
50
- return;
51
- }
52
- if (command === "dev") {
53
- await new DevCommand().run({
54
- appDirectory,
55
- host: options.host,
56
- port: options.port,
57
- json: options.json,
58
- documentGrantMap: options.documentGrantMap,
59
- write: this.write
60
- });
61
- return;
62
- }
63
- this.writeUsage();
64
- process.exit(1);
65
- };
66
- readCreateOptions = (rawArgs) => {
67
- const options = { json: false };
68
- for (const current of rawArgs) {
69
- if (!current?.startsWith("--")) throw new Error(`未知参数:${current}`);
70
- switch (current) {
71
- case "--json":
72
- options.json = true;
73
- break;
74
- default: throw new Error(`未知参数:${current}`);
75
- }
76
- }
77
- return options;
78
- };
79
- readRuntimeOptions = (rawArgs) => {
80
- const options = {
81
- host: "127.0.0.1",
82
- port: 3100,
83
- json: false,
84
- documentGrantMap: {}
85
- };
86
- for (let index = 0; index < rawArgs.length; index += 1) {
87
- const current = rawArgs[index];
88
- if (!current?.startsWith("--")) throw new Error(`未知参数:${current}`);
89
- const nextValue = rawArgs[index + 1];
90
- switch (current) {
91
- case "--host":
92
- options.host = this.requireOptionValue(current, nextValue);
93
- index += 1;
94
- break;
95
- case "--port":
96
- options.port = Number.parseInt(this.requireOptionValue(current, nextValue), 10);
97
- if (Number.isNaN(options.port) || options.port < 0) throw new Error("--port 必须是非负整数。");
98
- index += 1;
99
- break;
100
- case "--json":
101
- options.json = true;
102
- break;
103
- case "--document":
104
- this.assignDocumentGrant(options.documentGrantMap, this.requireOptionValue(current, nextValue));
105
- index += 1;
106
- break;
107
- default: throw new Error(`未知参数:${current}`);
108
- }
109
- }
110
- return options;
111
- };
112
- assignDocumentGrant = (documentGrantMap, rawGrant) => {
113
- const delimiterIndex = rawGrant.indexOf("=");
114
- if (delimiterIndex < 1) throw new Error("--document 必须使用 scopeId=/absolute/or/relative/path 格式。");
115
- const scopeId = rawGrant.slice(0, delimiterIndex).trim();
116
- const directoryPath = rawGrant.slice(delimiterIndex + 1).trim();
117
- if (!scopeId || !directoryPath) throw new Error("--document 缺少 scopeId 或 path。");
118
- documentGrantMap[scopeId] = directoryPath;
119
- };
120
- requireOptionValue = (flag, nextValue) => {
121
- if (!nextValue || nextValue.startsWith("--")) throw new Error(`${flag} 缺少值。`);
122
- return nextValue;
123
- };
124
- writeUsage = () => {
125
- this.write("Usage: napp create <app-dir> [--json]\n");
126
- this.write(" napp <inspect|run|dev> <app-dir> [--host 127.0.0.1] [--port 3100] [--json] [--document scope=/path]\n");
127
- this.write(" napp --help\n");
128
- this.write(" napp --version\n");
129
- };
130
- write = (text) => {
131
- process.stdout.write(text);
132
- };
133
- };
134
- new AppRuntimeCli().run();
4
+ new AppRuntimeCliService().run().catch((error) => {
5
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
6
+ process.exit(1);
7
+ });
135
8
  //#endregion
136
9
  export {};
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.2.0";
2
+ var version = "0.4.0";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,16 @@
1
+ //#region src/paths/app-home.service.d.ts
2
+ declare class AppHomeService {
3
+ private readonly appHomeDirectory;
4
+ constructor(appHomeDirectory?: string);
5
+ getAppHomeDirectory: () => string;
6
+ getPackagesDirectory: () => string;
7
+ getDataDirectory: () => string;
8
+ getRegistryPath: () => string;
9
+ getConfigPath: () => string;
10
+ getInstallDirectory: (appId: string, version: string) => string;
11
+ getAppDataDirectory: (appId: string) => string;
12
+ ensureBaseDirectories: () => Promise<void>;
13
+ createTemporaryDirectory: (prefix: string) => Promise<string>;
14
+ }
15
+ //#endregion
16
+ export { AppHomeService };
@@ -0,0 +1,42 @@
1
+ import { mkdir, mkdtemp } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ //#region src/paths/app-home.service.ts
5
+ var AppHomeService = class {
6
+ constructor(appHomeDirectory = process.env.NEXTCLAW_APP_HOME ? path.resolve(process.env.NEXTCLAW_APP_HOME) : path.join(process.env.HOME ?? process.cwd(), ".nextclaw", "apps")) {
7
+ this.appHomeDirectory = appHomeDirectory;
8
+ }
9
+ getAppHomeDirectory = () => {
10
+ return this.appHomeDirectory;
11
+ };
12
+ getPackagesDirectory = () => {
13
+ return path.join(this.appHomeDirectory, "packages");
14
+ };
15
+ getDataDirectory = () => {
16
+ return path.join(this.appHomeDirectory, "data");
17
+ };
18
+ getRegistryPath = () => {
19
+ return path.join(this.appHomeDirectory, "registry.json");
20
+ };
21
+ getConfigPath = () => {
22
+ return path.join(this.appHomeDirectory, "config.json");
23
+ };
24
+ getInstallDirectory = (appId, version) => {
25
+ return path.join(this.getPackagesDirectory(), appId, version);
26
+ };
27
+ getAppDataDirectory = (appId) => {
28
+ return path.join(this.getDataDirectory(), appId);
29
+ };
30
+ ensureBaseDirectories = async () => {
31
+ await Promise.all([
32
+ mkdir(this.appHomeDirectory, { recursive: true }),
33
+ mkdir(this.getPackagesDirectory(), { recursive: true }),
34
+ mkdir(this.getDataDirectory(), { recursive: true })
35
+ ]);
36
+ };
37
+ createTemporaryDirectory = async (prefix) => {
38
+ return mkdtemp(path.join(tmpdir(), prefix));
39
+ };
40
+ };
41
+ //#endregion
42
+ export { AppHomeService };
@@ -0,0 +1,22 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppDocumentGrantMutationResult, AppInstalledPermissionState } from "./app-permissions.types.js";
3
+ import { AppRegistryService } from "../registry/app-registry.service.js";
4
+
5
+ //#region src/permissions/app-grant.service.d.ts
6
+ declare class AppGrantService {
7
+ private readonly registryService;
8
+ private readonly manifestService;
9
+ constructor(registryService?: AppRegistryService, manifestService?: AppManifestService);
10
+ summarize: (appId: string) => Promise<AppInstalledPermissionState>;
11
+ grantDocumentScope: (params: {
12
+ appId: string;
13
+ scopeId: string;
14
+ directoryPath: string;
15
+ }) => Promise<AppDocumentGrantMutationResult>;
16
+ revokeDocumentScope: (params: {
17
+ appId: string;
18
+ scopeId: string;
19
+ }) => Promise<AppDocumentGrantMutationResult>;
20
+ }
21
+ //#endregion
22
+ export { AppGrantService };
@@ -0,0 +1,63 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppRegistryService } from "../registry/app-registry.service.js";
3
+ import { access } from "node:fs/promises";
4
+ import path from "node:path";
5
+ //#region src/permissions/app-grant.service.ts
6
+ var AppGrantService = class {
7
+ constructor(registryService = new AppRegistryService(), manifestService = new AppManifestService()) {
8
+ this.registryService = registryService;
9
+ this.manifestService = manifestService;
10
+ }
11
+ summarize = async (appId) => {
12
+ const appRecord = await this.registryService.getApp(appId);
13
+ if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
14
+ const activeVersion = appRecord.installedVersions[appRecord.activeVersion];
15
+ if (!activeVersion) throw new Error(`已安装应用缺少激活版本:${appId}`);
16
+ const requestedPermissions = (await this.manifestService.load(activeVersion.installDirectory)).manifest.permissions ?? {};
17
+ return {
18
+ appId: appRecord.appId,
19
+ name: appRecord.name,
20
+ activeVersion: appRecord.activeVersion,
21
+ documentAccess: (requestedPermissions.documentAccess ?? []).map((scope) => ({
22
+ id: scope.id,
23
+ mode: scope.mode,
24
+ description: scope.description,
25
+ granted: Boolean(appRecord.grants[scope.id]),
26
+ grantedPath: appRecord.grants[scope.id]
27
+ })),
28
+ allowedDomains: requestedPermissions.allowedDomains ?? [],
29
+ storage: {
30
+ enabled: requestedPermissions.storage !== void 0 && requestedPermissions.storage !== false,
31
+ namespace: typeof requestedPermissions.storage === "object" ? requestedPermissions.storage.namespace : void 0
32
+ },
33
+ capabilities: { hostBridge: requestedPermissions.capabilities?.hostBridge !== false }
34
+ };
35
+ };
36
+ grantDocumentScope = async (params) => {
37
+ const { appId, scopeId, directoryPath } = params;
38
+ if (!(await this.summarize(appId)).documentAccess.find((scope) => scope.id === scopeId)) throw new Error(`应用 ${appId} 未声明 documentAccess scope:${scopeId}`);
39
+ const normalizedDirectory = path.resolve(directoryPath);
40
+ try {
41
+ await access(normalizedDirectory);
42
+ } catch {
43
+ throw new Error(`授权目录不存在:${normalizedDirectory}`);
44
+ }
45
+ await this.registryService.setDocumentGrant(appId, scopeId, normalizedDirectory);
46
+ return {
47
+ appId,
48
+ scopeId,
49
+ grantedPath: normalizedDirectory
50
+ };
51
+ };
52
+ revokeDocumentScope = async (params) => {
53
+ const { appId, scopeId } = params;
54
+ await this.summarize(appId);
55
+ return {
56
+ appId,
57
+ scopeId,
58
+ removed: await this.registryService.removeDocumentGrant(appId, scopeId)
59
+ };
60
+ };
61
+ };
62
+ //#endregion
63
+ export { AppGrantService };
@@ -3,7 +3,9 @@ import { AppDocumentGrantMap, AppPermissionSummary, ResolvedPermissions } from "
3
3
 
4
4
  //#region src/permissions/app-permissions.service.d.ts
5
5
  declare class AppPermissionsService {
6
- resolve: (bundle: AppManifestBundle, documentGrantMap: AppDocumentGrantMap) => Promise<ResolvedPermissions>;
6
+ resolve: (bundle: AppManifestBundle, documentGrantMap: AppDocumentGrantMap, context?: {
7
+ appId?: string;
8
+ }) => Promise<ResolvedPermissions>;
7
9
  summarize: (bundle: AppManifestBundle, permissions: ResolvedPermissions) => AppPermissionSummary;
8
10
  private resolveDocumentGrant;
9
11
  }
@@ -2,10 +2,10 @@ import { access } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/permissions/app-permissions.service.ts
4
4
  var AppPermissionsService = class {
5
- resolve = async (bundle, documentGrantMap) => {
5
+ resolve = async (bundle, documentGrantMap, context) => {
6
6
  const requestedPermissions = bundle.manifest.permissions ?? {};
7
7
  return {
8
- documentAccess: requestedPermissions.documentAccess === void 0 ? [] : await Promise.all(requestedPermissions.documentAccess.map((scope) => this.resolveDocumentGrant(scope, documentGrantMap))),
8
+ documentAccess: requestedPermissions.documentAccess === void 0 ? [] : await Promise.all(requestedPermissions.documentAccess.map((scope) => this.resolveDocumentGrant(scope, documentGrantMap, context?.appId))),
9
9
  allowedDomains: requestedPermissions.allowedDomains ?? [],
10
10
  storage: {
11
11
  enabled: requestedPermissions.storage !== void 0 && requestedPermissions.storage !== false,
@@ -23,11 +23,18 @@ var AppPermissionsService = class {
23
23
  capabilities: permissions.capabilities
24
24
  };
25
25
  };
26
- resolveDocumentGrant = async (scope, documentGrantMap) => {
26
+ resolveDocumentGrant = async (scope, documentGrantMap, appId) => {
27
27
  const grantedPath = documentGrantMap[scope.id];
28
- if (!grantedPath) throw new Error(`缺少 documentAccess 授权:${scope.id}`);
28
+ if (!grantedPath) {
29
+ if (appId) throw new Error(`缺少 documentAccess 授权:${scope.id}。请先执行 napp grant ${appId} --document ${scope.id}=/absolute/path`);
30
+ throw new Error(`缺少 documentAccess 授权:${scope.id}。请通过 --document ${scope.id}=/absolute/path 提供授权。`);
31
+ }
29
32
  const normalizedPath = path.resolve(grantedPath);
30
- await access(normalizedPath);
33
+ try {
34
+ await access(normalizedPath);
35
+ } catch {
36
+ throw new Error(`documentAccess 授权路径不存在:${scope.id} -> ${normalizedPath}`);
37
+ }
31
38
  return {
32
39
  id: scope.id,
33
40
  mode: scope.mode,
@@ -22,5 +22,32 @@ type ResolvedPermissions = {
22
22
  type AppPermissionSummary = Pick<ResolvedPermissions, "documentAccess" | "allowedDomains" | "storage" | "capabilities"> & {
23
23
  requested: AppPermissions;
24
24
  };
25
+ type AppDocumentGrantState = {
26
+ id: string;
27
+ mode: "read" | "read-write";
28
+ description?: string;
29
+ granted: boolean;
30
+ grantedPath?: string;
31
+ };
32
+ type AppInstalledPermissionState = {
33
+ appId: string;
34
+ name: string;
35
+ activeVersion: string;
36
+ documentAccess: AppDocumentGrantState[];
37
+ allowedDomains: string[];
38
+ storage: {
39
+ enabled: boolean;
40
+ namespace?: string;
41
+ };
42
+ capabilities: {
43
+ hostBridge: boolean;
44
+ };
45
+ };
46
+ type AppDocumentGrantMutationResult = {
47
+ appId: string;
48
+ scopeId: string;
49
+ grantedPath?: string;
50
+ removed?: boolean;
51
+ };
25
52
  //#endregion
26
- export { AppDocumentGrantMap, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions };
53
+ export { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions };
@@ -0,0 +1,13 @@
1
+ import { AppPublishPayload, AppPublishResult } from "./app-publish.types.js";
2
+
3
+ //#region src/publish/app-marketplace-client.service.d.ts
4
+ declare class AppMarketplaceClientService {
5
+ publish: (params: {
6
+ payload: AppPublishPayload;
7
+ apiBaseUrl?: string;
8
+ token?: string;
9
+ }) => Promise<AppPublishResult>;
10
+ private normalizeApiBase;
11
+ }
12
+ //#endregion
13
+ export { AppMarketplaceClientService };
@@ -0,0 +1,31 @@
1
+ import "./app-publish.types.js";
2
+ //#region src/publish/app-marketplace-client.service.ts
3
+ var AppMarketplaceClientService = class {
4
+ publish = async (params) => {
5
+ const apiBaseUrl = this.normalizeApiBase(params.apiBaseUrl ?? "https://apps-registry.nextclaw.io");
6
+ const token = params.token?.trim() || process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();
7
+ if (!token) throw new Error("缺少 marketplace admin token,请传 --token 或设置 NEXTCLAW_MARKETPLACE_ADMIN_TOKEN。");
8
+ const response = await fetch(`${apiBaseUrl}/api/v1/apps/publish`, {
9
+ method: "POST",
10
+ headers: {
11
+ "content-type": "application/json",
12
+ authorization: `Bearer ${token}`,
13
+ accept: "application/json"
14
+ },
15
+ body: JSON.stringify(params.payload)
16
+ });
17
+ const payload = await response.json().catch(() => null);
18
+ if (!response.ok) {
19
+ const message = typeof payload === "object" && payload && "error" in payload && typeof payload.error?.message === "string" ? payload.error.message : `${response.status} ${response.statusText}`;
20
+ throw new Error(`发布 app 失败:${message}`);
21
+ }
22
+ const data = typeof payload === "object" && payload && "data" in payload && typeof payload.data === "object" ? payload.data : null;
23
+ if (!data) throw new Error("marketplace publish 返回格式无效。");
24
+ return data;
25
+ };
26
+ normalizeApiBase = (apiBaseUrl) => {
27
+ return new URL(apiBaseUrl).toString().replace(/\/+$/, "");
28
+ };
29
+ };
30
+ //#endregion
31
+ export { AppMarketplaceClientService };
@@ -0,0 +1,27 @@
1
+ import { AppManifest } from "../manifest/app-manifest.types.js";
2
+ import { AppMarketplaceMetadata } from "./app-publish.types.js";
3
+
4
+ //#region src/publish/app-marketplace-metadata.service.d.ts
5
+ declare class AppMarketplaceMetadataService {
6
+ load: (params: {
7
+ appDirectory: string;
8
+ manifest: AppManifest;
9
+ metadataPath?: string;
10
+ }) => Promise<AppMarketplaceMetadata>;
11
+ collectPublishFiles: (params: {
12
+ appDirectory: string;
13
+ metadataPath?: string;
14
+ }) => Promise<Array<{
15
+ path: string;
16
+ bytes: Buffer;
17
+ }>>;
18
+ private parseMetadata;
19
+ private parsePublisher;
20
+ private readRequiredString;
21
+ private readOptionalString;
22
+ private readStringArray;
23
+ private readOptionalBoolean;
24
+ private readLocalizedTextMap;
25
+ }
26
+ //#endregion
27
+ export { AppMarketplaceMetadataService };
@@ -0,0 +1,84 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ //#region src/publish/app-marketplace-metadata.service.ts
5
+ var AppMarketplaceMetadataService = class {
6
+ load = async (params) => {
7
+ const { appDirectory, manifest, metadataPath: customMetadataPath } = params;
8
+ const metadataPath = customMetadataPath ? path.resolve(customMetadataPath) : path.join(path.resolve(appDirectory), "marketplace.json");
9
+ const raw = JSON.parse(await readFile(metadataPath, "utf-8"));
10
+ return this.parseMetadata(raw, manifest);
11
+ };
12
+ collectPublishFiles = async (params) => {
13
+ const appDirectory = path.resolve(params.appDirectory);
14
+ const publishFiles = [];
15
+ const metadataPath = params.metadataPath ? path.resolve(params.metadataPath) : path.join(appDirectory, "marketplace.json");
16
+ publishFiles.push({
17
+ path: "marketplace.json",
18
+ bytes: Buffer.from(await readFile(metadataPath))
19
+ });
20
+ const readmePath = path.join(appDirectory, "README.md");
21
+ if (existsSync(readmePath)) publishFiles.push({
22
+ path: "README.md",
23
+ bytes: Buffer.from(await readFile(readmePath))
24
+ });
25
+ return publishFiles;
26
+ };
27
+ parseMetadata = (rawMetadata, manifest) => {
28
+ if (!rawMetadata || typeof rawMetadata !== "object" || Array.isArray(rawMetadata)) throw new Error("marketplace.json 必须是对象。");
29
+ const candidate = rawMetadata;
30
+ const slug = this.readRequiredString(candidate.slug, "slug");
31
+ const summary = this.readRequiredString(candidate.summary, "summary");
32
+ const description = this.readOptionalString(candidate.description, "description");
33
+ const author = this.readOptionalString(candidate.author, "author") ?? manifest.name;
34
+ const publisher = this.parsePublisher(candidate.publisher);
35
+ return {
36
+ slug,
37
+ summary,
38
+ summaryI18n: this.readLocalizedTextMap(candidate.summaryI18n, "summaryI18n", summary),
39
+ description,
40
+ descriptionI18n: description ? this.readLocalizedTextMap(candidate.descriptionI18n, "descriptionI18n", description) : void 0,
41
+ author,
42
+ tags: this.readStringArray(candidate.tags, "tags"),
43
+ sourceRepo: this.readOptionalString(candidate.sourceRepo, "sourceRepo"),
44
+ homepage: this.readOptionalString(candidate.homepage, "homepage"),
45
+ featured: this.readOptionalBoolean(candidate.featured, "featured") ?? false,
46
+ publisher
47
+ };
48
+ };
49
+ parsePublisher = (rawPublisher) => {
50
+ if (rawPublisher === void 0) return;
51
+ if (!rawPublisher || typeof rawPublisher !== "object" || Array.isArray(rawPublisher)) throw new Error("publisher 必须是对象。");
52
+ const candidate = rawPublisher;
53
+ return {
54
+ id: this.readRequiredString(candidate.id, "publisher.id"),
55
+ name: this.readRequiredString(candidate.name, "publisher.name"),
56
+ url: this.readOptionalString(candidate.url, "publisher.url")
57
+ };
58
+ };
59
+ readRequiredString = (value, fieldName) => {
60
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${fieldName} 必须是非空字符串。`);
61
+ return value.trim();
62
+ };
63
+ readOptionalString = (value, fieldName) => {
64
+ if (value === void 0) return;
65
+ return this.readRequiredString(value, fieldName);
66
+ };
67
+ readStringArray = (value, fieldName) => {
68
+ if (!Array.isArray(value) || value.length === 0) throw new Error(`${fieldName} 必须是非空字符串数组。`);
69
+ return value.map((item, index) => this.readRequiredString(item, `${fieldName}[${index}]`));
70
+ };
71
+ readOptionalBoolean = (value, fieldName) => {
72
+ if (value === void 0) return;
73
+ if (typeof value !== "boolean") throw new Error(`${fieldName} 必须是布尔值。`);
74
+ return value;
75
+ };
76
+ readLocalizedTextMap = (value, fieldName, fallbackEn) => {
77
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${fieldName} 必须是对象。`);
78
+ const normalized = Object.fromEntries(Object.entries(value).map(([locale, localeValue]) => [locale, this.readRequiredString(localeValue, `${fieldName}.${locale}`)]));
79
+ if (!normalized.en) normalized.en = fallbackEn;
80
+ return normalized;
81
+ };
82
+ };
83
+ //#endregion
84
+ export { AppMarketplaceMetadataService };
@@ -0,0 +1,22 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppPublishResult } from "./app-publish.types.js";
4
+ import { AppMarketplaceClientService } from "./app-marketplace-client.service.js";
5
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
6
+
7
+ //#region src/publish/app-publish.service.d.ts
8
+ declare class AppPublishService {
9
+ private readonly manifestService;
10
+ private readonly bundleService;
11
+ private readonly metadataService;
12
+ private readonly marketplaceClient;
13
+ constructor(manifestService?: AppManifestService, bundleService?: AppBundleService, metadataService?: AppMarketplaceMetadataService, marketplaceClient?: AppMarketplaceClientService);
14
+ publish: (params: {
15
+ appDirectory: string;
16
+ metadataPath?: string;
17
+ apiBaseUrl?: string;
18
+ token?: string;
19
+ }) => Promise<AppPublishResult>;
20
+ }
21
+ //#endregion
22
+ export { AppPublishService };
@@ -0,0 +1,74 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppMarketplaceClientService } from "./app-marketplace-client.service.js";
4
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
5
+ import { readFile } from "node:fs/promises";
6
+ import { createHash } from "node:crypto";
7
+ import path from "node:path";
8
+ //#region src/publish/app-publish.service.ts
9
+ var AppPublishService = class {
10
+ constructor(manifestService = new AppManifestService(), bundleService = new AppBundleService(), metadataService = new AppMarketplaceMetadataService(), marketplaceClient = new AppMarketplaceClientService()) {
11
+ this.manifestService = manifestService;
12
+ this.bundleService = bundleService;
13
+ this.metadataService = metadataService;
14
+ this.marketplaceClient = marketplaceClient;
15
+ }
16
+ publish = async (params) => {
17
+ const { appDirectory: inputAppDirectory, metadataPath, apiBaseUrl, token } = params;
18
+ const appDirectory = path.resolve(inputAppDirectory);
19
+ const manifestBundle = await this.manifestService.load(appDirectory);
20
+ const metadata = await this.metadataService.load({
21
+ appDirectory,
22
+ manifest: manifestBundle.manifest,
23
+ metadataPath
24
+ });
25
+ const bundle = await this.bundleService.packAppDirectory({ appDirectory });
26
+ const bundleBytes = Buffer.from(await readFile(bundle.bundlePath));
27
+ const bundleSha256 = createHash("sha256").update(bundleBytes).digest("hex");
28
+ const publishFiles = await this.metadataService.collectPublishFiles({
29
+ appDirectory,
30
+ metadataPath
31
+ });
32
+ const payload = {
33
+ slug: metadata.slug,
34
+ appId: manifestBundle.manifest.id,
35
+ name: manifestBundle.manifest.name,
36
+ version: manifestBundle.manifest.version,
37
+ summary: metadata.summary,
38
+ summaryI18n: metadata.summaryI18n,
39
+ description: metadata.description ?? manifestBundle.manifest.description,
40
+ descriptionI18n: metadata.descriptionI18n,
41
+ author: metadata.author,
42
+ tags: metadata.tags,
43
+ sourceRepo: metadata.sourceRepo,
44
+ homepage: metadata.homepage,
45
+ featured: metadata.featured ?? false,
46
+ publisher: metadata.publisher ?? {
47
+ id: "nextclaw",
48
+ name: "NextClaw",
49
+ url: "https://nextclaw.io"
50
+ },
51
+ manifest: manifestBundle.manifest,
52
+ permissions: manifestBundle.manifest.permissions ?? {},
53
+ bundleBase64: bundleBytes.toString("base64"),
54
+ bundleSha256,
55
+ files: publishFiles.map((file) => ({
56
+ path: file.path,
57
+ contentBase64: file.bytes.toString("base64")
58
+ }))
59
+ };
60
+ return {
61
+ ...await this.marketplaceClient.publish({
62
+ payload,
63
+ apiBaseUrl,
64
+ token
65
+ }),
66
+ bundle: {
67
+ path: bundle.bundlePath,
68
+ sha256: bundleSha256
69
+ }
70
+ };
71
+ };
72
+ };
73
+ //#endregion
74
+ export { AppPublishService };