@nextclaw/app-runtime 0.3.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.
- package/README.md +9 -1
- package/dist/cli/app-runtime-cli.service.d.ts +1 -0
- package/dist/cli/app-runtime-cli.service.js +17 -0
- package/dist/cli/app-runtime-options.service.d.ts +8 -1
- package/dist/cli/app-runtime-options.service.js +27 -0
- package/dist/commands/publish.controller.d.ts +17 -0
- package/dist/commands/publish.controller.js +29 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/package.js +1 -1
- package/dist/publish/app-marketplace-client.service.d.ts +13 -0
- package/dist/publish/app-marketplace-client.service.js +31 -0
- package/dist/publish/app-marketplace-metadata.service.d.ts +27 -0
- package/dist/publish/app-marketplace-metadata.service.js +84 -0
- package/dist/publish/app-publish.service.d.ts +22 -0
- package/dist/publish/app-publish.service.js +74 -0
- package/dist/publish/app-publish.types.d.ts +66 -0
- package/dist/publish/app-publish.types.js +4 -0
- package/dist/registry/app-remote-registry.types.d.ts +1 -1
- package/dist/registry/app-remote-registry.types.js +1 -1
- package/dist/scaffold/app-scaffold.service.d.ts +2 -0
- package/dist/scaffold/app-scaffold.service.js +54 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- `napp run <app-dir|app-id>`:启动本地宿主,支持目录运行和已安装应用运行
|
|
10
10
|
- `napp dev <app-dir>`:当前等价于 `run`
|
|
11
11
|
- `napp pack <app-dir>`:把应用目录打成 `.napp` bundle
|
|
12
|
+
- `napp publish <app-dir>`:把应用目录发布到官方 apps registry
|
|
12
13
|
- `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
|
|
13
14
|
- `napp update <app-id>`:更新已安装应用
|
|
14
15
|
- `napp uninstall <app-id>`:卸载已安装应用
|
|
@@ -57,7 +58,7 @@ assets/
|
|
|
57
58
|
- 用户数据目录:`~/.nextclaw/apps/data/<app-id>/`
|
|
58
59
|
- 本地 registry:`~/.nextclaw/apps/registry.json`
|
|
59
60
|
- 本地 config:`~/.nextclaw/apps/config.json`
|
|
60
|
-
- 默认 registry:`https://registry.nextclaw.
|
|
61
|
+
- 默认 registry:`https://apps-registry.nextclaw.io/api/v1/apps/registry/`
|
|
61
62
|
|
|
62
63
|
## 当前 MVP 工作流
|
|
63
64
|
|
|
@@ -67,6 +68,7 @@ assets/
|
|
|
67
68
|
napp create ./my-first-napp
|
|
68
69
|
napp inspect ./my-first-napp
|
|
69
70
|
napp pack ./my-first-napp
|
|
71
|
+
napp publish ./my-first-napp
|
|
70
72
|
```
|
|
71
73
|
|
|
72
74
|
本地安装工作流:
|
|
@@ -115,6 +117,7 @@ napp run nextclaw.my-first-napp
|
|
|
115
117
|
```bash
|
|
116
118
|
napp inspect ./apps/examples/hello-notes
|
|
117
119
|
napp pack ./apps/examples/hello-notes
|
|
120
|
+
napp publish ./apps/examples/hello-notes
|
|
118
121
|
napp install ./apps/examples/hello-notes
|
|
119
122
|
napp grant nextclaw.hello-notes --document notes=/absolute/path/to/notes
|
|
120
123
|
napp run ./apps/examples/hello-notes --document notes=/absolute/path/to/notes
|
|
@@ -129,6 +132,11 @@ napp install nextclaw.hello-notes
|
|
|
129
132
|
napp update nextclaw.hello-notes
|
|
130
133
|
```
|
|
131
134
|
|
|
135
|
+
官方 apps 入口:
|
|
136
|
+
|
|
137
|
+
- Web:`https://apps.nextclaw.io`
|
|
138
|
+
- Registry/API:`https://apps-registry.nextclaw.io`
|
|
139
|
+
|
|
132
140
|
## Bundle 结构
|
|
133
141
|
|
|
134
142
|
`.napp` bundle 的最小结构如下:
|
|
@@ -9,6 +9,7 @@ import { InstallCommand } from "../commands/install.controller.js";
|
|
|
9
9
|
import { ListCommand } from "../commands/list.controller.js";
|
|
10
10
|
import { PackCommand } from "../commands/pack.controller.js";
|
|
11
11
|
import { PermissionsCommand } from "../commands/permissions.controller.js";
|
|
12
|
+
import { PublishCommand } from "../commands/publish.controller.js";
|
|
12
13
|
import { RegistryCommand } from "../commands/registry.controller.js";
|
|
13
14
|
import { RevokeCommand } from "../commands/revoke.controller.js";
|
|
14
15
|
import { UninstallCommand } from "../commands/uninstall.controller.js";
|
|
@@ -52,6 +53,9 @@ var AppRuntimeCliService = class {
|
|
|
52
53
|
case "pack":
|
|
53
54
|
await this.handlePack(restArgs);
|
|
54
55
|
return;
|
|
56
|
+
case "publish":
|
|
57
|
+
await this.handlePublish(restArgs);
|
|
58
|
+
return;
|
|
55
59
|
case "install":
|
|
56
60
|
await this.handleInstall(restArgs);
|
|
57
61
|
return;
|
|
@@ -136,6 +140,18 @@ var AppRuntimeCliService = class {
|
|
|
136
140
|
write: this.write
|
|
137
141
|
});
|
|
138
142
|
};
|
|
143
|
+
handlePublish = async (restArgs) => {
|
|
144
|
+
const { target, optionArgs } = this.optionsService.readTarget("publish", restArgs);
|
|
145
|
+
const options = this.optionsService.readPublishOptions(optionArgs);
|
|
146
|
+
await new PublishCommand().run({
|
|
147
|
+
appDirectory: target,
|
|
148
|
+
metadataPath: options.metadataPath,
|
|
149
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
150
|
+
token: options.token,
|
|
151
|
+
json: options.json,
|
|
152
|
+
write: this.write
|
|
153
|
+
});
|
|
154
|
+
};
|
|
139
155
|
handleInstall = async (restArgs) => {
|
|
140
156
|
const { target, optionArgs } = this.optionsService.readTarget("install", restArgs);
|
|
141
157
|
const options = this.optionsService.readInstallOptions(optionArgs);
|
|
@@ -259,6 +275,7 @@ var AppRuntimeCliService = class {
|
|
|
259
275
|
this.write(" napp inspect <app-dir> [--json]\n");
|
|
260
276
|
this.write(" napp <run|dev> <app-dir|app-id> [--host 127.0.0.1] [--port 3100] [--json] [--document scope=/path]\n");
|
|
261
277
|
this.write(" napp pack <app-dir> [--out bundle.napp] [--json]\n");
|
|
278
|
+
this.write(" napp publish <app-dir> [--meta marketplace.json] [--api-base <url>] [--token <token>] [--json]\n");
|
|
262
279
|
this.write(" napp install <app-dir|bundle.napp|app-id[@version]> [--registry <url>] [--json]\n");
|
|
263
280
|
this.write(" napp update <app-id> [--version <version>] [--registry <url>] [--json]\n");
|
|
264
281
|
this.write(" napp uninstall <app-id> [--purge-data] [--json]\n");
|
|
@@ -38,6 +38,12 @@ type RevokeCliOptions = {
|
|
|
38
38
|
json: boolean;
|
|
39
39
|
documentScopeIds: string[];
|
|
40
40
|
};
|
|
41
|
+
type PublishCliOptions = {
|
|
42
|
+
json: boolean;
|
|
43
|
+
metadataPath?: string;
|
|
44
|
+
apiBaseUrl?: string;
|
|
45
|
+
token?: string;
|
|
46
|
+
};
|
|
41
47
|
declare class AppRuntimeOptionsService {
|
|
42
48
|
readTarget: (command: string, rawArgs: string[]) => {
|
|
43
49
|
target: string;
|
|
@@ -52,9 +58,10 @@ declare class AppRuntimeOptionsService {
|
|
|
52
58
|
readRuntimeOptions: (rawArgs: string[]) => RuntimeCliOptions;
|
|
53
59
|
readGrantOptions: (rawArgs: string[]) => GrantCliOptions;
|
|
54
60
|
readRevokeOptions: (rawArgs: string[]) => RevokeCliOptions;
|
|
61
|
+
readPublishOptions: (rawArgs: string[]) => PublishCliOptions;
|
|
55
62
|
private assignDocumentGrant;
|
|
56
63
|
private readDocumentScopeId;
|
|
57
64
|
private requireOptionValue;
|
|
58
65
|
}
|
|
59
66
|
//#endregion
|
|
60
|
-
export { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions };
|
|
67
|
+
export { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, PublishCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions };
|
|
@@ -193,6 +193,33 @@ var AppRuntimeOptionsService = class {
|
|
|
193
193
|
if (options.documentScopeIds.length === 0) throw new Error("revoke 至少需要一个 --document scope。");
|
|
194
194
|
return options;
|
|
195
195
|
};
|
|
196
|
+
readPublishOptions = (rawArgs) => {
|
|
197
|
+
const options = { json: false };
|
|
198
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
199
|
+
const current = rawArgs[index];
|
|
200
|
+
if (!current?.startsWith("--")) throw new Error(`未知参数:${current}`);
|
|
201
|
+
const nextValue = rawArgs[index + 1];
|
|
202
|
+
switch (current) {
|
|
203
|
+
case "--json":
|
|
204
|
+
options.json = true;
|
|
205
|
+
break;
|
|
206
|
+
case "--meta":
|
|
207
|
+
options.metadataPath = this.requireOptionValue(current, nextValue);
|
|
208
|
+
index += 1;
|
|
209
|
+
break;
|
|
210
|
+
case "--api-base":
|
|
211
|
+
options.apiBaseUrl = this.requireOptionValue(current, nextValue);
|
|
212
|
+
index += 1;
|
|
213
|
+
break;
|
|
214
|
+
case "--token":
|
|
215
|
+
options.token = this.requireOptionValue(current, nextValue);
|
|
216
|
+
index += 1;
|
|
217
|
+
break;
|
|
218
|
+
default: throw new Error(`未知参数:${current}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return options;
|
|
222
|
+
};
|
|
196
223
|
assignDocumentGrant = (documentGrantMap, rawGrant) => {
|
|
197
224
|
const delimiterIndex = rawGrant.indexOf("=");
|
|
198
225
|
if (delimiterIndex < 1) throw new Error("--document 必须使用 scopeId=/absolute/or/relative/path 格式。");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AppPublishService } from "../publish/app-publish.service.js";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/publish.controller.d.ts
|
|
4
|
+
declare class PublishCommand {
|
|
5
|
+
private readonly publishService;
|
|
6
|
+
constructor(publishService?: AppPublishService);
|
|
7
|
+
run: (params: {
|
|
8
|
+
appDirectory: string;
|
|
9
|
+
metadataPath?: string;
|
|
10
|
+
apiBaseUrl?: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
json: boolean;
|
|
13
|
+
write: (text: string) => void;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { PublishCommand };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AppPublishService } from "../publish/app-publish.service.js";
|
|
2
|
+
//#region src/commands/publish.controller.ts
|
|
3
|
+
var PublishCommand = class {
|
|
4
|
+
constructor(publishService = new AppPublishService()) {
|
|
5
|
+
this.publishService = publishService;
|
|
6
|
+
}
|
|
7
|
+
run = async (params) => {
|
|
8
|
+
const { appDirectory, metadataPath, apiBaseUrl, token, json, write } = params;
|
|
9
|
+
const result = await this.publishService.publish({
|
|
10
|
+
appDirectory,
|
|
11
|
+
metadataPath,
|
|
12
|
+
apiBaseUrl,
|
|
13
|
+
token
|
|
14
|
+
});
|
|
15
|
+
if (json) {
|
|
16
|
+
write(`${JSON.stringify({
|
|
17
|
+
ok: true,
|
|
18
|
+
publish: result
|
|
19
|
+
}, null, 2)}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
write(`${result.created ? "Published" : "Updated"} ${result.item.name} (${result.item.appId}) ${result.item.latestVersion}\n`);
|
|
23
|
+
write(`Bundle: ${result.bundle.path}\n`);
|
|
24
|
+
if (result.item.webUrl) write(`Details: ${result.item.webUrl}\n`);
|
|
25
|
+
write(`Install: ${result.item.install.command}\n`);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { PublishCommand };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { WasmSidecarClientService } from "./sidecar/wasm-sidecar-client.service.
|
|
|
10
10
|
import { WasmMainRunnerService } from "./runtime/wasm-main-runner.service.js";
|
|
11
11
|
import { AppInstanceService, AppRunResult } from "./host/app-instance.service.js";
|
|
12
12
|
import { HostBridgeServer } from "./bridge/host-bridge.service.js";
|
|
13
|
-
import { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions } from "./cli/app-runtime-options.service.js";
|
|
13
|
+
import { AppRuntimeOptionsService, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, PublishCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions } from "./cli/app-runtime-options.service.js";
|
|
14
14
|
import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
|
|
15
15
|
import { CreateCommand } from "./commands/create.controller.js";
|
|
16
16
|
import { AppHomeService } from "./paths/app-home.service.js";
|
|
@@ -28,10 +28,15 @@ import { InstallCommand } from "./commands/install.controller.js";
|
|
|
28
28
|
import { ListCommand } from "./commands/list.controller.js";
|
|
29
29
|
import { PackCommand } from "./commands/pack.controller.js";
|
|
30
30
|
import { PermissionsCommand } from "./commands/permissions.controller.js";
|
|
31
|
+
import { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./publish/app-publish.types.js";
|
|
32
|
+
import { AppMarketplaceClientService } from "./publish/app-marketplace-client.service.js";
|
|
33
|
+
import { AppMarketplaceMetadataService } from "./publish/app-marketplace-metadata.service.js";
|
|
34
|
+
import { AppPublishService } from "./publish/app-publish.service.js";
|
|
35
|
+
import { PublishCommand } from "./commands/publish.controller.js";
|
|
31
36
|
import { RegistryCommand } from "./commands/registry.controller.js";
|
|
32
37
|
import { RevokeCommand } from "./commands/revoke.controller.js";
|
|
33
38
|
import { UpdateCommand } from "./commands/update.controller.js";
|
|
34
39
|
import { UninstallCommand } from "./commands/uninstall.controller.js";
|
|
35
40
|
import { AppHostHandle, AppHostService, AppHostStartOptions } from "./host/app-host.service.js";
|
|
36
41
|
import { UiServerService } from "./ui/ui-server.service.js";
|
|
37
|
-
export { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppRunResult, AppRuntimeCliService, AppRuntimeOptionsService, AppUiManifest, AppUninstallResult, AppUpdateResult, CreateCliOptions, CreateCommand, DEFAULT_APP_REGISTRY_URL, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService };
|
|
42
|
+
export { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppRunResult, AppRuntimeCliService, AppRuntimeOptionsService, AppUiManifest, AppUninstallResult, AppUpdateResult, CreateCliOptions, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService };
|
package/dist/index.js
CHANGED
|
@@ -22,10 +22,15 @@ import { InstallCommand } from "./commands/install.controller.js";
|
|
|
22
22
|
import { ListCommand } from "./commands/list.controller.js";
|
|
23
23
|
import { PackCommand } from "./commands/pack.controller.js";
|
|
24
24
|
import { PermissionsCommand } from "./commands/permissions.controller.js";
|
|
25
|
+
import { DEFAULT_APP_MARKETPLACE_API_BASE } from "./publish/app-publish.types.js";
|
|
26
|
+
import { AppMarketplaceClientService } from "./publish/app-marketplace-client.service.js";
|
|
27
|
+
import { AppMarketplaceMetadataService } from "./publish/app-marketplace-metadata.service.js";
|
|
28
|
+
import { AppPublishService } from "./publish/app-publish.service.js";
|
|
29
|
+
import { PublishCommand } from "./commands/publish.controller.js";
|
|
25
30
|
import { RegistryCommand } from "./commands/registry.controller.js";
|
|
26
31
|
import { RevokeCommand } from "./commands/revoke.controller.js";
|
|
27
32
|
import { UninstallCommand } from "./commands/uninstall.controller.js";
|
|
28
33
|
import { UpdateCommand } from "./commands/update.controller.js";
|
|
29
34
|
import { AppRuntimeOptionsService } from "./cli/app-runtime-options.service.js";
|
|
30
35
|
import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
|
|
31
|
-
export { AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppPermissionsService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, CreateCommand, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, WasmMainRunnerService, WasmSidecarClientService };
|
|
36
|
+
export { AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, WasmMainRunnerService, WasmSidecarClientService };
|
package/dist/package.js
CHANGED
|
@@ -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 };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AppManifest, AppPermissions } from "../manifest/app-manifest.types.js";
|
|
2
|
+
import { AppPublisher } from "../registry/app-remote-registry.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/publish/app-publish.types.d.ts
|
|
5
|
+
declare const DEFAULT_APP_MARKETPLACE_API_BASE = "https://apps-registry.nextclaw.io";
|
|
6
|
+
type AppMarketplaceMetadata = {
|
|
7
|
+
slug: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
summaryI18n: Record<string, string>;
|
|
10
|
+
description?: string;
|
|
11
|
+
descriptionI18n?: Record<string, string>;
|
|
12
|
+
author: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
sourceRepo?: string;
|
|
15
|
+
homepage?: string;
|
|
16
|
+
featured?: boolean;
|
|
17
|
+
publisher?: AppPublisher;
|
|
18
|
+
};
|
|
19
|
+
type AppPublishFile = {
|
|
20
|
+
path: string;
|
|
21
|
+
contentBase64: string;
|
|
22
|
+
};
|
|
23
|
+
type AppPublishPayload = {
|
|
24
|
+
slug: string;
|
|
25
|
+
appId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
version: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
summaryI18n: Record<string, string>;
|
|
30
|
+
description?: string;
|
|
31
|
+
descriptionI18n?: Record<string, string>;
|
|
32
|
+
author: string;
|
|
33
|
+
tags: string[];
|
|
34
|
+
sourceRepo?: string;
|
|
35
|
+
homepage?: string;
|
|
36
|
+
featured: boolean;
|
|
37
|
+
publisher: AppPublisher;
|
|
38
|
+
manifest: AppManifest;
|
|
39
|
+
permissions: AppPermissions;
|
|
40
|
+
bundleBase64: string;
|
|
41
|
+
bundleSha256: string;
|
|
42
|
+
files: AppPublishFile[];
|
|
43
|
+
};
|
|
44
|
+
type AppPublishResult = {
|
|
45
|
+
created: boolean;
|
|
46
|
+
item: {
|
|
47
|
+
slug: string;
|
|
48
|
+
appId: string;
|
|
49
|
+
name: string;
|
|
50
|
+
latestVersion: string;
|
|
51
|
+
webUrl?: string;
|
|
52
|
+
install: {
|
|
53
|
+
kind: "registry";
|
|
54
|
+
spec: string;
|
|
55
|
+
command: string;
|
|
56
|
+
registry: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
bundle: {
|
|
60
|
+
path: string;
|
|
61
|
+
sha256: string;
|
|
62
|
+
};
|
|
63
|
+
fileCount: number;
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AppPermissions } from "../manifest/app-manifest.types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/registry/app-remote-registry.types.d.ts
|
|
4
|
-
declare const DEFAULT_APP_REGISTRY_URL = "https://registry.nextclaw.
|
|
4
|
+
declare const DEFAULT_APP_REGISTRY_URL = "https://apps-registry.nextclaw.io/api/v1/apps/registry/";
|
|
5
5
|
type AppPublisher = {
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
@@ -13,6 +13,8 @@ var AppScaffoldService = class {
|
|
|
13
13
|
const manifestPath = path.join(appDirectory, "manifest.json");
|
|
14
14
|
await Promise.all([
|
|
15
15
|
writeFile(manifestPath, `${JSON.stringify(this.buildManifest(appId, appName), null, 2)}\n`, "utf-8"),
|
|
16
|
+
writeFile(path.join(appDirectory, "marketplace.json"), `${JSON.stringify(this.buildMarketplaceMetadata(appName), null, 2)}\n`, "utf-8"),
|
|
17
|
+
writeFile(path.join(appDirectory, "README.md"), this.buildReadme(appName, appId), "utf-8"),
|
|
16
18
|
writeFile(path.join(appDirectory, "main", "app.wasm"), Buffer.from(APP_WASM_BASE64, "base64")),
|
|
17
19
|
writeFile(path.join(appDirectory, "main", "app.wat"), this.buildWatSource(), "utf-8"),
|
|
18
20
|
writeFile(path.join(appDirectory, "ui", "index.html"), this.buildUiHtml(appName), "utf-8"),
|
|
@@ -191,6 +193,58 @@ var AppScaffoldService = class {
|
|
|
191
193
|
<script type="module" src="./app.controller.js"><\/script>
|
|
192
194
|
</body>
|
|
193
195
|
</html>
|
|
196
|
+
`;
|
|
197
|
+
};
|
|
198
|
+
buildMarketplaceMetadata = (appName) => {
|
|
199
|
+
return {
|
|
200
|
+
slug: this.normalizeSlug(appName),
|
|
201
|
+
summary: `A minimal ${appName} app scaffold created by napp.`,
|
|
202
|
+
summaryI18n: {
|
|
203
|
+
en: `A minimal ${appName} app scaffold created by napp.`,
|
|
204
|
+
zh: `一个由 napp 创建的最小 ${appName} 应用骨架。`
|
|
205
|
+
},
|
|
206
|
+
description: `A minimal NextClaw app scaffold for ${appName}, ready for local run and marketplace publish.`,
|
|
207
|
+
descriptionI18n: {
|
|
208
|
+
en: `A minimal NextClaw app scaffold for ${appName}, ready for local run and marketplace publish.`,
|
|
209
|
+
zh: `一个面向 ${appName} 的最小 NextClaw 应用骨架,可直接本地运行并发布到 marketplace。`
|
|
210
|
+
},
|
|
211
|
+
author: "NextClaw",
|
|
212
|
+
tags: [
|
|
213
|
+
"starter",
|
|
214
|
+
"official",
|
|
215
|
+
"local"
|
|
216
|
+
],
|
|
217
|
+
sourceRepo: "https://github.com/Peiiii/nextclaw",
|
|
218
|
+
homepage: "https://nextclaw.io",
|
|
219
|
+
featured: false,
|
|
220
|
+
publisher: {
|
|
221
|
+
id: "nextclaw",
|
|
222
|
+
name: "NextClaw",
|
|
223
|
+
url: "https://nextclaw.io"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
buildReadme = (appName, appId) => {
|
|
228
|
+
return `# ${appName}
|
|
229
|
+
|
|
230
|
+
This starter app was created by \`napp create\`.
|
|
231
|
+
|
|
232
|
+
## Local workflow
|
|
233
|
+
|
|
234
|
+
\`\`\`bash
|
|
235
|
+
napp inspect .
|
|
236
|
+
napp run .
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
## Publish workflow
|
|
240
|
+
|
|
241
|
+
\`\`\`bash
|
|
242
|
+
napp publish .
|
|
243
|
+
\`\`\`
|
|
244
|
+
|
|
245
|
+
## App ID
|
|
246
|
+
|
|
247
|
+
\`${appId}\`
|
|
194
248
|
`;
|
|
195
249
|
};
|
|
196
250
|
buildUiController = () => {
|