@nextclaw/app-runtime 0.4.1 → 0.6.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 (39) hide show
  1. package/README.md +29 -4
  2. package/dist/bundle/app-bundle.service.js +14 -8
  3. package/dist/bundle/app-bundle.types.d.ts +2 -0
  4. package/dist/cli/app-runtime-cli.service.d.ts +3 -0
  5. package/dist/cli/app-runtime-cli.service.js +70 -2
  6. package/dist/cli/app-runtime-options.service.d.ts +10 -1
  7. package/dist/cli/app-runtime-options.service.js +39 -2
  8. package/dist/commands/create.controller.d.ts +2 -1
  9. package/dist/commands/create.controller.js +3 -2
  10. package/dist/commands/inspect.controller.js +2 -1
  11. package/dist/commands/run.controller.js +25 -7
  12. package/dist/commands/validate-publish.controller.d.ts +15 -0
  13. package/dist/commands/validate-publish.controller.js +31 -0
  14. package/dist/host/app-host.service.d.ts +3 -1
  15. package/dist/host/app-host.service.js +5 -1
  16. package/dist/host/app-instance.service.js +1 -0
  17. package/dist/index.d.ts +11 -3
  18. package/dist/index.js +9 -1
  19. package/dist/install/app-installation.service.js +1 -0
  20. package/dist/install/app-installation.types.d.ts +1 -0
  21. package/dist/manifest/app-manifest.service.js +9 -3
  22. package/dist/manifest/app-manifest.types.d.ts +9 -3
  23. package/dist/package.js +1 -1
  24. package/dist/publish/app-publish-validation.service.d.ts +37 -0
  25. package/dist/publish/app-publish-validation.service.js +76 -0
  26. package/dist/runtime/app-build.service.d.ts +24 -0
  27. package/dist/runtime/app-build.service.js +55 -0
  28. package/dist/runtime/app-runtime-toolchain.service.d.ts +30 -0
  29. package/dist/runtime/app-runtime-toolchain.service.js +96 -0
  30. package/dist/runtime/wasm-main-runner.service.js +1 -0
  31. package/dist/runtime/wasmtime-wasi-http-component.service.d.ts +24 -0
  32. package/dist/runtime/wasmtime-wasi-http-component.service.js +129 -0
  33. package/dist/scaffold/app-scaffold.service.d.ts +13 -2
  34. package/dist/scaffold/app-scaffold.service.js +39 -2
  35. package/dist/scaffold/app-ts-http-lite-scaffold-template.service.d.ts +18 -0
  36. package/dist/scaffold/app-ts-http-lite-scaffold-template.service.js +267 -0
  37. package/dist/scaffold/app-ts-http-scaffold-template.service.d.ts +25 -0
  38. package/dist/scaffold/app-ts-http-scaffold-template.service.js +496 -0
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { AppManifestService } from "./manifest/app-manifest.service.js";
2
2
  import { AppBundleService } from "./bundle/app-bundle.service.js";
3
3
  import { HostBridgeServer } from "./bridge/host-bridge.service.js";
4
+ import { AppTsHttpScaffoldTemplateService } from "./scaffold/app-ts-http-scaffold-template.service.js";
5
+ import { AppTsHttpLiteScaffoldTemplateService } from "./scaffold/app-ts-http-lite-scaffold-template.service.js";
6
+ import { AppScaffoldService } from "./scaffold/app-scaffold.service.js";
4
7
  import { CreateCommand } from "./commands/create.controller.js";
5
8
  import { UiServerService } from "./ui/ui-server.service.js";
6
9
  import { AppHostService } from "./host/app-host.service.js";
@@ -15,6 +18,7 @@ import { AppRegistryConfigService } from "./registry/app-registry-config.service
15
18
  import { AppRemoteRegistryClientService } from "./registry/app-remote-registry-client.service.js";
16
19
  import { AppRegistryService } from "./registry/app-registry.service.js";
17
20
  import { AppInstallationService } from "./install/app-installation.service.js";
21
+ import { WasmtimeWasiHttpComponentService } from "./runtime/wasmtime-wasi-http-component.service.js";
18
22
  import { AppGrantService } from "./permissions/app-grant.service.js";
19
23
  import { GrantCommand } from "./commands/grant.controller.js";
20
24
  import { InfoCommand } from "./commands/info.controller.js";
@@ -32,6 +36,10 @@ import { RegistryCommand } from "./commands/registry.controller.js";
32
36
  import { RevokeCommand } from "./commands/revoke.controller.js";
33
37
  import { UninstallCommand } from "./commands/uninstall.controller.js";
34
38
  import { UpdateCommand } from "./commands/update.controller.js";
39
+ import { AppPublishValidationService } from "./publish/app-publish-validation.service.js";
40
+ import { ValidatePublishCommand } from "./commands/validate-publish.controller.js";
41
+ import { AppRuntimeToolchainService } from "./runtime/app-runtime-toolchain.service.js";
42
+ import { AppBuildService } from "./runtime/app-build.service.js";
35
43
  import { AppRuntimeOptionsService } from "./cli/app-runtime-options.service.js";
36
44
  import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
37
- 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, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, WasmMainRunnerService, WasmSidecarClientService };
45
+ export { AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService };
@@ -179,6 +179,7 @@ var AppInstallationService = class {
179
179
  return {
180
180
  appDirectory: activeVersion.installDirectory,
181
181
  appId: appRecord.appId,
182
+ dataDirectory: appRecord.dataDirectory,
182
183
  documentGrantMap: {
183
184
  ...appRecord.grants,
184
185
  ...explicitDocumentGrantMap
@@ -52,6 +52,7 @@ type AppUninstallResult = {
52
52
  type AppLaunchResolution = {
53
53
  appDirectory: string;
54
54
  appId?: string;
55
+ dataDirectory?: string;
55
56
  documentGrantMap: AppDocumentGrantMap;
56
57
  };
57
58
  type AppUpdateResult = AppInstallResult & {
@@ -27,7 +27,8 @@ var AppManifestService = class {
27
27
  name: bundle.manifest.name,
28
28
  version: bundle.manifest.version,
29
29
  description: bundle.manifest.description,
30
- action: bundle.manifest.main.action,
30
+ mainKind: bundle.manifest.main.kind,
31
+ action: bundle.manifest.main.kind === "wasm" ? bundle.manifest.main.action : void 0,
31
32
  manifestPath: bundle.manifestPath,
32
33
  mainEntryPath: bundle.mainEntryPath,
33
34
  uiEntryPath: bundle.uiEntryPath,
@@ -54,13 +55,18 @@ var AppManifestService = class {
54
55
  };
55
56
  parseMain = (rawMain) => {
56
57
  const candidate = this.assertObject(rawMain, "main");
57
- if (this.readRequiredString(candidate.kind, "main.kind") !== "wasm") throw new Error("当前 main.kind 只支持 wasm。");
58
- return {
58
+ const kind = this.readRequiredString(candidate.kind, "main.kind");
59
+ if (kind === "wasm") return {
59
60
  kind: "wasm",
60
61
  entry: this.readRequiredString(candidate.entry, "main.entry"),
61
62
  export: this.readRequiredString(candidate.export, "main.export"),
62
63
  action: this.readRequiredString(candidate.action, "main.action")
63
64
  };
65
+ if (kind === "wasi-http-component") return {
66
+ kind: "wasi-http-component",
67
+ entry: this.readRequiredString(candidate.entry, "main.entry")
68
+ };
69
+ throw new Error("当前 main.kind 只支持 wasm 或 wasi-http-component。");
64
70
  };
65
71
  parseUi = (rawUi) => {
66
72
  const candidate = this.assertObject(rawUi, "ui");
@@ -15,12 +15,17 @@ type AppPermissions = {
15
15
  hostBridge?: boolean;
16
16
  };
17
17
  };
18
- type AppMainManifest = {
18
+ type AppCoreWasmMainManifest = {
19
19
  kind: "wasm";
20
20
  entry: string;
21
21
  export: string;
22
22
  action: string;
23
23
  };
24
+ type AppWasiHttpComponentMainManifest = {
25
+ kind: "wasi-http-component";
26
+ entry: string;
27
+ };
28
+ type AppMainManifest = AppCoreWasmMainManifest | AppWasiHttpComponentMainManifest;
24
29
  type AppUiManifest = {
25
30
  entry: string;
26
31
  };
@@ -50,7 +55,8 @@ type AppManifestSummary = {
50
55
  name: string;
51
56
  version: string;
52
57
  description?: string;
53
- action: string;
58
+ mainKind: AppMainManifest["kind"];
59
+ action?: string;
54
60
  manifestPath: string;
55
61
  mainEntryPath: string;
56
62
  uiEntryPath: string;
@@ -58,4 +64,4 @@ type AppManifestSummary = {
58
64
  permissions: AppPermissions;
59
65
  };
60
66
  //#endregion
61
- export { AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppUiManifest };
67
+ export { AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppUiManifest, AppWasiHttpComponentMainManifest };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.4.1";
2
+ var version = "0.6.0";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,37 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
4
+
5
+ //#region src/publish/app-publish-validation.service.d.ts
6
+ type AppPublishValidationWarningCode = "main-entry-large" | "bundle-large";
7
+ type AppPublishValidationWarning = {
8
+ code: AppPublishValidationWarningCode;
9
+ message: string;
10
+ };
11
+ type AppPublishValidationResult = {
12
+ ok: boolean;
13
+ appDirectory: string;
14
+ metadataPath: string;
15
+ appId: string;
16
+ version: string;
17
+ mainKind: string;
18
+ mainEntryPath: string;
19
+ mainEntrySizeBytes: number;
20
+ bundleSizeBytes: number;
21
+ bundleFilePaths: string[];
22
+ warnings: AppPublishValidationWarning[];
23
+ };
24
+ declare class AppPublishValidationService {
25
+ private readonly manifestService;
26
+ private readonly metadataService;
27
+ private readonly bundleService;
28
+ constructor(manifestService?: AppManifestService, metadataService?: AppMarketplaceMetadataService, bundleService?: AppBundleService);
29
+ validate: (params: {
30
+ appDirectory: string;
31
+ metadataPath?: string;
32
+ }) => Promise<AppPublishValidationResult>;
33
+ private buildWarnings;
34
+ private formatBytes;
35
+ }
36
+ //#endregion
37
+ export { AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode };
@@ -0,0 +1,76 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppBundleService } from "../bundle/app-bundle.service.js";
3
+ import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
4
+ import { mkdtemp, rm, stat } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ //#region src/publish/app-publish-validation.service.ts
8
+ const MAIN_ENTRY_WARN_BYTES = 10 * 1024 * 1024;
9
+ const BUNDLE_WARN_BYTES = 5 * 1024 * 1024;
10
+ var AppPublishValidationService = class {
11
+ constructor(manifestService = new AppManifestService(), metadataService = new AppMarketplaceMetadataService(), bundleService = new AppBundleService()) {
12
+ this.manifestService = manifestService;
13
+ this.metadataService = metadataService;
14
+ this.bundleService = bundleService;
15
+ }
16
+ validate = async (params) => {
17
+ const appDirectory = path.resolve(params.appDirectory);
18
+ const bundle = await this.manifestService.load(appDirectory);
19
+ const metadataPath = params.metadataPath ? path.resolve(params.metadataPath) : path.join(appDirectory, "marketplace.json");
20
+ await this.metadataService.load({
21
+ appDirectory,
22
+ manifest: bundle.manifest,
23
+ metadataPath
24
+ });
25
+ const tempDirectory = await mkdtemp(path.join(tmpdir(), "napp-validate-publish-"));
26
+ try {
27
+ const packResult = await this.bundleService.packAppDirectory({
28
+ appDirectory,
29
+ outputPath: path.join(tempDirectory, `${bundle.manifest.id}-${bundle.manifest.version}.napp`)
30
+ });
31
+ const mainEntryStats = await stat(bundle.mainEntryPath);
32
+ const warnings = this.buildWarnings({
33
+ mainEntrySizeBytes: mainEntryStats.size,
34
+ bundleSizeBytes: packResult.sizeBytes
35
+ });
36
+ return {
37
+ ok: true,
38
+ appDirectory,
39
+ metadataPath,
40
+ appId: bundle.manifest.id,
41
+ version: bundle.manifest.version,
42
+ mainKind: bundle.manifest.main.kind,
43
+ mainEntryPath: bundle.mainEntryPath,
44
+ mainEntrySizeBytes: mainEntryStats.size,
45
+ bundleSizeBytes: packResult.sizeBytes,
46
+ bundleFilePaths: packResult.filePaths,
47
+ warnings
48
+ };
49
+ } finally {
50
+ await rm(tempDirectory, {
51
+ recursive: true,
52
+ force: true
53
+ });
54
+ }
55
+ };
56
+ buildWarnings = (params) => {
57
+ const { bundleSizeBytes, mainEntrySizeBytes } = params;
58
+ const warnings = [];
59
+ if (mainEntrySizeBytes > MAIN_ENTRY_WARN_BYTES) warnings.push({
60
+ code: "main-entry-large",
61
+ message: `main entry is ${this.formatBytes(mainEntrySizeBytes)}, which is larger than the ${this.formatBytes(MAIN_ENTRY_WARN_BYTES)} warning threshold.`
62
+ });
63
+ if (bundleSizeBytes > BUNDLE_WARN_BYTES) warnings.push({
64
+ code: "bundle-large",
65
+ message: `packed .napp is ${this.formatBytes(bundleSizeBytes)}, which is larger than the ${this.formatBytes(BUNDLE_WARN_BYTES)} warning threshold.`
66
+ });
67
+ return warnings;
68
+ };
69
+ formatBytes = (value) => {
70
+ if (value < 1024) return `${value} B`;
71
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
72
+ return `${(value / (1024 * 1024)).toFixed(1)} MB`;
73
+ };
74
+ };
75
+ //#endregion
76
+ export { AppPublishValidationService };
@@ -0,0 +1,24 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppRuntimeToolchainService } from "./app-runtime-toolchain.service.js";
3
+
4
+ //#region src/runtime/app-build.service.d.ts
5
+ type AppBuildResult = {
6
+ appDirectory: string;
7
+ mainKind: string;
8
+ mainEntryPath: string;
9
+ installedDependencies: boolean;
10
+ built: boolean;
11
+ skippedReason?: string;
12
+ };
13
+ declare class AppBuildService {
14
+ private readonly manifestService;
15
+ private readonly toolchainService;
16
+ constructor(manifestService?: AppManifestService, toolchainService?: AppRuntimeToolchainService);
17
+ build: (params: {
18
+ appDirectory: string;
19
+ install: boolean;
20
+ }) => Promise<AppBuildResult>;
21
+ private pathExists;
22
+ }
23
+ //#endregion
24
+ export { AppBuildResult, AppBuildService };
@@ -0,0 +1,55 @@
1
+ import { AppManifestService } from "../manifest/app-manifest.service.js";
2
+ import { AppRuntimeToolchainService } from "./app-runtime-toolchain.service.js";
3
+ import { access } from "node:fs/promises";
4
+ import path from "node:path";
5
+ //#region src/runtime/app-build.service.ts
6
+ var AppBuildService = class {
7
+ constructor(manifestService = new AppManifestService(), toolchainService = new AppRuntimeToolchainService()) {
8
+ this.manifestService = manifestService;
9
+ this.toolchainService = toolchainService;
10
+ }
11
+ build = async (params) => {
12
+ const appDirectory = path.resolve(params.appDirectory);
13
+ const bundle = await this.manifestService.load(appDirectory);
14
+ if (bundle.manifest.main.kind !== "wasi-http-component") return {
15
+ appDirectory,
16
+ mainKind: bundle.manifest.main.kind,
17
+ mainEntryPath: bundle.mainEntryPath,
18
+ installedDependencies: false,
19
+ built: false,
20
+ skippedReason: "main.kind=wasm 应用不需要 TS/WASI HTTP 构建。"
21
+ };
22
+ await this.toolchainService.assertReadyForWasiHttpBuild();
23
+ const mainDirectory = path.join(appDirectory, "main");
24
+ await access(path.join(mainDirectory, "package.json"));
25
+ const shouldInstall = params.install || !await this.pathExists(path.join(mainDirectory, "node_modules"));
26
+ if (shouldInstall) await this.toolchainService.runCommand({
27
+ command: "npm",
28
+ args: ["install"],
29
+ cwd: mainDirectory
30
+ });
31
+ await this.toolchainService.runCommand({
32
+ command: "npm",
33
+ args: ["run", "build"],
34
+ cwd: mainDirectory
35
+ });
36
+ await access(bundle.mainEntryPath);
37
+ return {
38
+ appDirectory,
39
+ mainKind: bundle.manifest.main.kind,
40
+ mainEntryPath: bundle.mainEntryPath,
41
+ installedDependencies: shouldInstall,
42
+ built: true
43
+ };
44
+ };
45
+ pathExists = async (targetPath) => {
46
+ try {
47
+ await access(targetPath);
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ };
53
+ };
54
+ //#endregion
55
+ export { AppBuildService };
@@ -0,0 +1,30 @@
1
+ //#region src/runtime/app-runtime-toolchain.service.d.ts
2
+ type AppRuntimeToolStatus = {
3
+ name: string;
4
+ command: string;
5
+ ok: boolean;
6
+ version?: string;
7
+ installHint: string;
8
+ error?: string;
9
+ };
10
+ type AppRuntimeDoctorResult = {
11
+ ok: boolean;
12
+ tools: AppRuntimeToolStatus[];
13
+ };
14
+ type AppRuntimeCommandResult = {
15
+ stdout: string;
16
+ stderr: string;
17
+ };
18
+ declare class AppRuntimeToolchainService {
19
+ doctor: () => Promise<AppRuntimeDoctorResult>;
20
+ assertReadyForWasiHttpBuild: () => Promise<void>;
21
+ runCommand: (params: {
22
+ command: string;
23
+ args: string[];
24
+ cwd: string;
25
+ }) => Promise<AppRuntimeCommandResult>;
26
+ private checkTool;
27
+ private collectProcess;
28
+ }
29
+ //#endregion
30
+ export { AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeToolStatus, AppRuntimeToolchainService };
@@ -0,0 +1,96 @@
1
+ import { spawn } from "node:child_process";
2
+ //#region src/runtime/app-runtime-toolchain.service.ts
3
+ const REQUIRED_TOOLS = [
4
+ {
5
+ name: "npm",
6
+ command: "npm",
7
+ args: ["--version"],
8
+ installHint: "请安装 Node.js,或使用 NextClaw 桌面版内置运行环境。"
9
+ },
10
+ {
11
+ name: "wasmtime",
12
+ command: "wasmtime",
13
+ args: ["--version"],
14
+ installHint: "请安装 Wasmtime,或使用后续内置 Wasmtime 的 NextClaw 发行包。"
15
+ },
16
+ {
17
+ name: "wkg",
18
+ command: "wkg",
19
+ args: ["--version"],
20
+ installHint: "请安装 Bytecode Alliance wkg;有 Rust/Cargo 时可执行:cargo install wkg --locked。"
21
+ }
22
+ ];
23
+ var AppRuntimeToolchainService = class {
24
+ doctor = async () => {
25
+ const tools = await Promise.all(REQUIRED_TOOLS.map((tool) => this.checkTool(tool)));
26
+ return {
27
+ ok: tools.every((tool) => tool.ok),
28
+ tools
29
+ };
30
+ };
31
+ assertReadyForWasiHttpBuild = async () => {
32
+ const missing = (await this.doctor()).tools.filter((tool) => !tool.ok);
33
+ if (missing.length === 0) return;
34
+ throw new Error(["NApp WASI HTTP 开发环境尚未就绪。", ...missing.map((tool) => `- 缺少 ${tool.name}: ${tool.installHint}`)].join("\n"));
35
+ };
36
+ runCommand = async (params) => {
37
+ const { command, args, cwd } = params;
38
+ const child = spawn(command, args, {
39
+ cwd,
40
+ stdio: [
41
+ "ignore",
42
+ "pipe",
43
+ "pipe"
44
+ ]
45
+ });
46
+ return await this.collectProcess(child, command);
47
+ };
48
+ checkTool = async (tool) => {
49
+ try {
50
+ const result = await this.runCommand({
51
+ command: tool.command,
52
+ args: tool.args,
53
+ cwd: process.cwd()
54
+ });
55
+ const version = (result.stdout || result.stderr).trim().split(/\r?\n/)[0]?.trim();
56
+ return {
57
+ name: tool.name,
58
+ command: tool.command,
59
+ ok: true,
60
+ version,
61
+ installHint: tool.installHint
62
+ };
63
+ } catch (error) {
64
+ return {
65
+ name: tool.name,
66
+ command: tool.command,
67
+ ok: false,
68
+ installHint: tool.installHint,
69
+ error: error instanceof Error ? error.message : String(error)
70
+ };
71
+ }
72
+ };
73
+ collectProcess = async (child, command) => {
74
+ const stdoutChunks = [];
75
+ const stderrChunks = [];
76
+ child.stdout.on("data", (chunk) => {
77
+ stdoutChunks.push(chunk);
78
+ });
79
+ child.stderr.on("data", (chunk) => {
80
+ stderrChunks.push(chunk);
81
+ });
82
+ const exitCode = await new Promise((resolve, reject) => {
83
+ child.once("error", reject);
84
+ child.once("exit", resolve);
85
+ });
86
+ const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
87
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8");
88
+ if (exitCode !== 0) throw new Error(`${command} exited with ${exitCode ?? "unknown"}\n${stderr || stdout}`);
89
+ return {
90
+ stdout,
91
+ stderr
92
+ };
93
+ };
94
+ };
95
+ //#endregion
96
+ export { AppRuntimeToolchainService };
@@ -7,6 +7,7 @@ var WasmMainRunnerService = class extends MainRunnerService {
7
7
  this.sidecarClient = sidecarClient;
8
8
  }
9
9
  runDocumentSummary = async (request) => {
10
+ if (request.bundle.manifest.main.kind !== "wasm") throw new Error("runDocumentSummary 只支持 main.kind=wasm。");
10
11
  const output = await this.sidecarClient.runExport({
11
12
  wasmPath: request.bundle.mainEntryPath,
12
13
  exportName: request.bundle.manifest.main.export,
@@ -0,0 +1,24 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+
3
+ //#region src/runtime/wasmtime-wasi-http-component.service.d.ts
4
+ type WasmtimeWasiHttpComponentStartOptions = {
5
+ wasmPath: string;
6
+ dataDirectory: string;
7
+ };
8
+ type WasmtimeWasiHttpComponentHandle = {
9
+ url: string;
10
+ port: number;
11
+ };
12
+ declare class WasmtimeWasiHttpComponentService {
13
+ private process?;
14
+ private handle?;
15
+ start: (options: WasmtimeWasiHttpComponentStartOptions) => Promise<WasmtimeWasiHttpComponentHandle>;
16
+ stop: () => Promise<void>;
17
+ handleRequest: (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
18
+ private readBody;
19
+ private methodSupportsBody;
20
+ private findAvailablePort;
21
+ private waitUntilReady;
22
+ }
23
+ //#endregion
24
+ export { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions };
@@ -0,0 +1,129 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createServer } from "node:http";
4
+ import { spawn } from "node:child_process";
5
+ //#region src/runtime/wasmtime-wasi-http-component.service.ts
6
+ var WasmtimeWasiHttpComponentService = class {
7
+ process;
8
+ handle;
9
+ start = async (options) => {
10
+ if (this.process) throw new Error("WASI HTTP component 已经启动。");
11
+ const dataDirectory = path.resolve(options.dataDirectory);
12
+ await mkdir(dataDirectory, { recursive: true });
13
+ const port = await this.findAvailablePort();
14
+ const child = spawn("wasmtime", [
15
+ "serve",
16
+ "-S",
17
+ "cli=y",
18
+ "-S",
19
+ "http=y",
20
+ "-S",
21
+ "inherit-network=y",
22
+ "--addr",
23
+ `127.0.0.1:${port}`,
24
+ "--dir",
25
+ `${dataDirectory}::/data`,
26
+ options.wasmPath
27
+ ], { stdio: [
28
+ "ignore",
29
+ "pipe",
30
+ "pipe"
31
+ ] });
32
+ this.process = child;
33
+ const handle = {
34
+ url: `http://127.0.0.1:${port}`,
35
+ port
36
+ };
37
+ this.handle = handle;
38
+ await this.waitUntilReady(handle.url, child);
39
+ return handle;
40
+ };
41
+ stop = async () => {
42
+ const child = this.process;
43
+ if (!child) return;
44
+ await new Promise((resolve) => {
45
+ child.once("exit", () => {
46
+ resolve();
47
+ });
48
+ child.kill("SIGTERM");
49
+ setTimeout(() => {
50
+ if (!child.killed) child.kill("SIGKILL");
51
+ resolve();
52
+ }, 1e3).unref();
53
+ });
54
+ this.process = void 0;
55
+ this.handle = void 0;
56
+ };
57
+ handleRequest = async (request, response) => {
58
+ const handle = this.handle;
59
+ if (!handle) return false;
60
+ const requestUrl = new URL(request.url ?? "/", handle.url);
61
+ if (!requestUrl.pathname.startsWith("/api/")) return false;
62
+ const headers = new Headers();
63
+ for (const [key, value] of Object.entries(request.headers)) {
64
+ if (value === void 0 || key.toLowerCase() === "host") continue;
65
+ if (Array.isArray(value)) {
66
+ for (const item of value) headers.append(key, item);
67
+ continue;
68
+ }
69
+ headers.set(key, value);
70
+ }
71
+ const body = await this.readBody(request);
72
+ const upstreamResponse = await fetch(requestUrl, {
73
+ method: request.method,
74
+ headers,
75
+ body: this.methodSupportsBody(request.method) ? body : void 0
76
+ });
77
+ response.writeHead(upstreamResponse.status, Object.fromEntries(upstreamResponse.headers));
78
+ response.end(Buffer.from(await upstreamResponse.arrayBuffer()));
79
+ return true;
80
+ };
81
+ readBody = async (request) => {
82
+ const chunks = [];
83
+ for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
84
+ return Buffer.concat(chunks);
85
+ };
86
+ methodSupportsBody = (method) => {
87
+ return method !== void 0 && method !== "GET" && method !== "HEAD";
88
+ };
89
+ findAvailablePort = async () => {
90
+ const server = createServer();
91
+ await new Promise((resolve, reject) => {
92
+ server.once("error", reject);
93
+ server.listen(0, "127.0.0.1", () => {
94
+ server.off("error", reject);
95
+ resolve();
96
+ });
97
+ });
98
+ const address = server.address();
99
+ await new Promise((resolve, reject) => {
100
+ server.close((error) => {
101
+ if (error) {
102
+ reject(error);
103
+ return;
104
+ }
105
+ resolve();
106
+ });
107
+ });
108
+ if (!address || typeof address === "string") throw new Error("无法分配 WASI HTTP component 端口。");
109
+ return address.port;
110
+ };
111
+ waitUntilReady = async (url, child) => {
112
+ const errors = [];
113
+ child.stderr.on("data", (chunk) => {
114
+ errors.push(chunk.toString("utf-8"));
115
+ });
116
+ for (let attempt = 0; attempt < 50; attempt += 1) {
117
+ if (child.exitCode !== null) throw new Error(`WASI HTTP component 启动失败:${errors.join("").trim() || `exit ${child.exitCode}`}`);
118
+ try {
119
+ await fetch(url);
120
+ return;
121
+ } catch {
122
+ await new Promise((resolve) => setTimeout(resolve, 100));
123
+ }
124
+ }
125
+ throw new Error(`WASI HTTP component 启动超时:${errors.join("").trim()}`);
126
+ };
127
+ };
128
+ //#endregion
129
+ export { WasmtimeWasiHttpComponentService };
@@ -1,10 +1,21 @@
1
+ import { AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+ import { AppTsHttpLiteScaffoldTemplateService } from "./app-ts-http-lite-scaffold-template.service.js";
3
+
1
4
  //#region src/scaffold/app-scaffold.service.d.ts
5
+ type AppScaffoldTemplate = "starter" | "ts-http" | "ts-http-lite";
2
6
  type AppScaffoldResult = {
3
7
  appDirectory: string;
4
8
  manifestPath: string;
9
+ template: AppScaffoldTemplate;
5
10
  };
6
11
  declare class AppScaffoldService {
7
- scaffold: (targetDirectory: string) => Promise<AppScaffoldResult>;
12
+ private readonly tsHttpTemplateService;
13
+ private readonly tsHttpLiteTemplateService;
14
+ constructor(tsHttpTemplateService?: AppTsHttpScaffoldTemplateService, tsHttpLiteTemplateService?: AppTsHttpLiteScaffoldTemplateService);
15
+ scaffold: (targetDirectory: string, options?: {
16
+ template?: AppScaffoldTemplate;
17
+ }) => Promise<AppScaffoldResult>;
18
+ private writeTemplateFiles;
8
19
  private assertTargetDoesNotExist;
9
20
  private buildAppId;
10
21
  private buildAppName;
@@ -18,4 +29,4 @@ declare class AppScaffoldService {
18
29
  private buildIconSvg;
19
30
  }
20
31
  //#endregion
21
- export { AppScaffoldService };
32
+ export { AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate };