@nextclaw/app-runtime 0.5.0 → 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.
package/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - `napp run <app-dir|app-id>`:启动本地宿主,支持目录运行和已安装应用运行
12
12
  - `napp dev <app-dir>`:当前等价于 `run`
13
13
  - `napp pack <app-dir>`:把应用目录打成 `.napp` bundle
14
+ - `napp validate-publish <app-dir>`:本地做发布前校验并输出体积/包内容 warning
14
15
  - `napp publish <app-dir>`:把应用目录发布到官方 apps registry
15
16
  - `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
16
17
  - `napp update <app-id>`:更新已安装应用
@@ -43,8 +44,8 @@ napp --version
43
44
  manifest.json
44
45
  main/
45
46
  app.wasm
46
- package.json # ts-http 模板存在
47
- src/ # ts-http 模板存在
47
+ package.json # ts-http / ts-http-lite 模板存在
48
+ src/ # ts-http / ts-http-lite 模板存在
48
49
  ui/
49
50
  index.html
50
51
  assets/
@@ -74,11 +75,22 @@ napp doctor
74
75
  napp create ./my-first-napp --template ts-http
75
76
  napp build ./my-first-napp --install
76
77
  napp inspect ./my-first-napp
78
+ napp validate-publish ./my-first-napp
77
79
  napp run ./my-first-napp --data ./my-first-napp/.napp/data
78
80
  napp pack ./my-first-napp
79
81
  napp publish ./my-first-napp
80
82
  ```
81
83
 
84
+ 体积优先工作流:
85
+
86
+ ```bash
87
+ napp doctor
88
+ napp create ./my-small-napp --template ts-http-lite
89
+ napp build ./my-small-napp --install
90
+ napp validate-publish ./my-small-napp
91
+ napp run ./my-small-napp --data ./my-small-napp/.napp/data
92
+ ```
93
+
82
94
  本地安装工作流:
83
95
 
84
96
  ```bash
@@ -120,6 +132,11 @@ napp permissions nextclaw.my-first-napp
120
132
  napp run nextclaw.my-first-napp
121
133
  ```
122
134
 
135
+ 模板选择建议:
136
+
137
+ - `ts-http`:默认推荐,开发体验优先,适合大多数普通前后端小应用
138
+ - `ts-http-lite`:体积优先,继续走官方 WASI HTTP 路线,但不使用默认 Hono 路由层
139
+
123
140
  已有示例应用:
124
141
 
125
142
  ```bash
@@ -10,7 +10,7 @@ var AppBundleService = class {
10
10
  }
11
11
  packAppDirectory = async (params) => {
12
12
  const bundle = await this.manifestService.load(params.appDirectory);
13
- const appFiles = await this.collectAppFiles(bundle.appDirectory);
13
+ const { appFiles, filePaths } = await this.collectAppFiles(bundle);
14
14
  const metadata = this.buildMetadata(bundle.manifest.id, bundle.manifest.name, bundle.manifest.version);
15
15
  const bundleJsonPath = ".napp/bundle.json";
16
16
  const checksumsJsonPath = ".napp/checksums.json";
@@ -30,7 +30,9 @@ var AppBundleService = class {
30
30
  await writeFile(outputPath, Buffer.from(archiveBytes));
31
31
  return {
32
32
  bundlePath: outputPath,
33
- metadata
33
+ metadata,
34
+ sizeBytes: archiveBytes.byteLength,
35
+ filePaths
34
36
  };
35
37
  };
36
38
  extractBundle = async (params) => {
@@ -59,15 +61,19 @@ var AppBundleService = class {
59
61
  checksums
60
62
  };
61
63
  };
62
- collectAppFiles = async (appDirectory) => {
63
- const filePaths = new Set(["manifest.json"]);
64
- await this.collectDirectoryPaths(path.join(appDirectory, "main"), appDirectory, filePaths);
65
- await this.collectDirectoryPaths(path.join(appDirectory, "ui"), appDirectory, filePaths);
66
- await this.collectDirectoryPaths(path.join(appDirectory, "assets"), appDirectory, filePaths);
64
+ collectAppFiles = async (bundle) => {
65
+ const appDirectory = bundle.appDirectory;
66
+ const filePaths = new Set([path.relative(appDirectory, bundle.manifestPath), path.relative(appDirectory, bundle.mainEntryPath)]);
67
+ await this.collectDirectoryPaths(bundle.uiDirectoryPath, appDirectory, filePaths);
68
+ await this.collectDirectoryPaths(bundle.assetsDirectoryPath, appDirectory, filePaths);
69
+ if (bundle.iconPath) filePaths.add(path.relative(appDirectory, bundle.iconPath));
67
70
  const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));
68
71
  const appFiles = {};
69
72
  for (const relativePath of sortedPaths) appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));
70
- return appFiles;
73
+ return {
74
+ appFiles,
75
+ filePaths: sortedPaths
76
+ };
71
77
  };
72
78
  collectDirectoryPaths = async (directoryPath, appDirectory, filePaths) => {
73
79
  try {
@@ -14,6 +14,8 @@ type AppBundleChecksums = {
14
14
  type AppBundlePackResult = {
15
15
  bundlePath: string;
16
16
  metadata: AppBundleMetadata;
17
+ sizeBytes: number;
18
+ filePaths: string[];
17
19
  };
18
20
  type AppBundleExtractResult = {
19
21
  appDirectory: string;
@@ -14,6 +14,7 @@ declare class AppRuntimeCliService {
14
14
  private handleDev;
15
15
  private handlePack;
16
16
  private handlePublish;
17
+ private handleValidatePublish;
17
18
  private handleInstall;
18
19
  private handleUpdate;
19
20
  private handleUninstall;
@@ -14,6 +14,7 @@ import { RegistryCommand } from "../commands/registry.controller.js";
14
14
  import { RevokeCommand } from "../commands/revoke.controller.js";
15
15
  import { UninstallCommand } from "../commands/uninstall.controller.js";
16
16
  import { UpdateCommand } from "../commands/update.controller.js";
17
+ import { ValidatePublishCommand } from "../commands/validate-publish.controller.js";
17
18
  import { AppRuntimeToolchainService } from "../runtime/app-runtime-toolchain.service.js";
18
19
  import { AppBuildService } from "../runtime/app-build.service.js";
19
20
  import { AppRuntimeOptionsService } from "./app-runtime-options.service.js";
@@ -64,6 +65,9 @@ var AppRuntimeCliService = class {
64
65
  case "publish":
65
66
  await this.handlePublish(restArgs);
66
67
  return;
68
+ case "validate-publish":
69
+ await this.handleValidatePublish(restArgs);
70
+ return;
67
71
  case "install":
68
72
  await this.handleInstall(restArgs);
69
73
  return;
@@ -203,6 +207,16 @@ var AppRuntimeCliService = class {
203
207
  write: this.write
204
208
  });
205
209
  };
210
+ handleValidatePublish = async (restArgs) => {
211
+ const { target, optionArgs } = this.optionsService.readTarget("validate-publish", restArgs);
212
+ const options = this.optionsService.readPublishOptions(optionArgs);
213
+ await new ValidatePublishCommand().run({
214
+ appDirectory: target,
215
+ metadataPath: options.metadataPath,
216
+ json: options.json,
217
+ write: this.write
218
+ });
219
+ };
206
220
  handleInstall = async (restArgs) => {
207
221
  const { target, optionArgs } = this.optionsService.readTarget("install", restArgs);
208
222
  const options = this.optionsService.readInstallOptions(optionArgs);
@@ -322,13 +336,14 @@ var AppRuntimeCliService = class {
322
336
  });
323
337
  };
324
338
  writeUsage = () => {
325
- this.write("Usage: napp create <app-dir> [--template starter|ts-http] [--json]\n");
339
+ this.write("Usage: napp create <app-dir> [--template starter|ts-http|ts-http-lite] [--json]\n");
326
340
  this.write(" napp inspect <app-dir> [--json]\n");
327
341
  this.write(" napp build <app-dir> [--install] [--json]\n");
328
342
  this.write(" napp doctor [--json]\n");
329
343
  this.write(" napp <run|dev> <app-dir|app-id> [--host 127.0.0.1] [--port 3100] [--data /path] [--json] [--document scope=/path]\n");
330
344
  this.write(" napp pack <app-dir> [--out bundle.napp] [--json]\n");
331
345
  this.write(" napp publish <app-dir> [--meta marketplace.json] [--api-base <url>] [--token <token>] [--json]\n");
346
+ this.write(" napp validate-publish <app-dir> [--meta marketplace.json] [--json]\n");
332
347
  this.write(" napp install <app-dir|bundle.napp|app-id[@version]> [--registry <url>] [--json]\n");
333
348
  this.write(" napp update <app-id> [--version <version>] [--registry <url>] [--json]\n");
334
349
  this.write(" napp uninstall <app-id> [--purge-data] [--json]\n");
@@ -31,8 +31,8 @@ var AppRuntimeOptionsService = class {
31
31
  return options;
32
32
  };
33
33
  readCreateTemplate = (rawTemplate) => {
34
- if (rawTemplate === "starter" || rawTemplate === "ts-http") return rawTemplate;
35
- throw new Error("--template 只支持 starter 或 ts-http。");
34
+ if (rawTemplate === "starter" || rawTemplate === "ts-http" || rawTemplate === "ts-http-lite") return rawTemplate;
35
+ throw new Error("--template 只支持 starter、ts-http 或 ts-http-lite。");
36
36
  };
37
37
  readPackOptions = (rawArgs) => {
38
38
  const options = { json: false };
@@ -0,0 +1,15 @@
1
+ import { AppPublishValidationService } from "../publish/app-publish-validation.service.js";
2
+
3
+ //#region src/commands/validate-publish.controller.d.ts
4
+ declare class ValidatePublishCommand {
5
+ private readonly validationService;
6
+ constructor(validationService?: AppPublishValidationService);
7
+ run: (params: {
8
+ appDirectory: string;
9
+ metadataPath?: string;
10
+ json: boolean;
11
+ write: (text: string) => void;
12
+ }) => Promise<void>;
13
+ }
14
+ //#endregion
15
+ export { ValidatePublishCommand };
@@ -0,0 +1,31 @@
1
+ import { AppPublishValidationService } from "../publish/app-publish-validation.service.js";
2
+ //#region src/commands/validate-publish.controller.ts
3
+ var ValidatePublishCommand = class {
4
+ constructor(validationService = new AppPublishValidationService()) {
5
+ this.validationService = validationService;
6
+ }
7
+ run = async (params) => {
8
+ const { appDirectory, metadataPath, json, write } = params;
9
+ const result = await this.validationService.validate({
10
+ appDirectory,
11
+ metadataPath
12
+ });
13
+ if (json) {
14
+ write(`${JSON.stringify({
15
+ ok: true,
16
+ validation: result
17
+ }, null, 2)}\n`);
18
+ return;
19
+ }
20
+ write(`Publish validation ok for ${result.appId}@${result.version}\n`);
21
+ write(`Main kind: ${result.mainKind}\n`);
22
+ write(`Main: ${result.mainEntryPath}\n`);
23
+ write(`Metadata: ${result.metadataPath}\n`);
24
+ write(`Bundle size: ${result.bundleSizeBytes} bytes\n`);
25
+ write(`Main entry size: ${result.mainEntrySizeBytes} bytes\n`);
26
+ write(`Bundle files (${result.bundleFilePaths.length}): ${result.bundleFilePaths.join(", ")}\n`);
27
+ if (result.warnings.length > 0) for (const warning of result.warnings) write(`Warning [${warning.code}]: ${warning.message}\n`);
28
+ };
29
+ };
30
+ //#endregion
31
+ export { ValidatePublishCommand };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ 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
13
  import { AppScaffoldFile, AppTsHttpScaffoldTemplateService } from "./scaffold/app-ts-http-scaffold-template.service.js";
14
+ import { AppTsHttpLiteScaffoldTemplateService } from "./scaffold/app-ts-http-lite-scaffold-template.service.js";
14
15
  import { AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate } from "./scaffold/app-scaffold.service.js";
15
16
  import { AppRuntimeOptionsService, BuildCliOptions, CreateCliOptions, GrantCliOptions, InstallCliOptions, JsonOnlyCliOptions, PackCliOptions, PublishCliOptions, RevokeCliOptions, RuntimeCliOptions, UninstallCliOptions, UpdateCliOptions } from "./cli/app-runtime-options.service.js";
16
17
  import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
@@ -40,9 +41,11 @@ import { RegistryCommand } from "./commands/registry.controller.js";
40
41
  import { RevokeCommand } from "./commands/revoke.controller.js";
41
42
  import { UpdateCommand } from "./commands/update.controller.js";
42
43
  import { UninstallCommand } from "./commands/uninstall.controller.js";
44
+ import { AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode } from "./publish/app-publish-validation.service.js";
45
+ import { ValidatePublishCommand } from "./commands/validate-publish.controller.js";
43
46
  import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./runtime/wasmtime-wasi-http-component.service.js";
44
47
  import { AppHostHandle, AppHostService, AppHostStartOptions } from "./host/app-host.service.js";
45
48
  import { AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeToolStatus, AppRuntimeToolchainService } from "./runtime/app-runtime-toolchain.service.js";
46
49
  import { AppBuildResult, AppBuildService } from "./runtime/app-build.service.js";
47
50
  import { UiServerService } from "./ui/ui-server.service.js";
48
- export { AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppCoreWasmMainManifest, 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, AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeOptionsService, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, 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, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions };
51
+ export { AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppCoreWasmMainManifest, 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, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeOptionsService, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, 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, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ 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
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";
5
6
  import { AppScaffoldService } from "./scaffold/app-scaffold.service.js";
6
7
  import { CreateCommand } from "./commands/create.controller.js";
7
8
  import { UiServerService } from "./ui/ui-server.service.js";
@@ -35,8 +36,10 @@ import { RegistryCommand } from "./commands/registry.controller.js";
35
36
  import { RevokeCommand } from "./commands/revoke.controller.js";
36
37
  import { UninstallCommand } from "./commands/uninstall.controller.js";
37
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";
38
41
  import { AppRuntimeToolchainService } from "./runtime/app-runtime-toolchain.service.js";
39
42
  import { AppBuildService } from "./runtime/app-build.service.js";
40
43
  import { AppRuntimeOptionsService } from "./cli/app-runtime-options.service.js";
41
44
  import { AppRuntimeCliService } from "./cli/app-runtime-cli.service.js";
42
- export { AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, 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, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService };
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 };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.5.0";
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 };
@@ -1,7 +1,8 @@
1
1
  import { AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+ import { AppTsHttpLiteScaffoldTemplateService } from "./app-ts-http-lite-scaffold-template.service.js";
2
3
 
3
4
  //#region src/scaffold/app-scaffold.service.d.ts
4
- type AppScaffoldTemplate = "starter" | "ts-http";
5
+ type AppScaffoldTemplate = "starter" | "ts-http" | "ts-http-lite";
5
6
  type AppScaffoldResult = {
6
7
  appDirectory: string;
7
8
  manifestPath: string;
@@ -9,7 +10,8 @@ type AppScaffoldResult = {
9
10
  };
10
11
  declare class AppScaffoldService {
11
12
  private readonly tsHttpTemplateService;
12
- constructor(tsHttpTemplateService?: AppTsHttpScaffoldTemplateService);
13
+ private readonly tsHttpLiteTemplateService;
14
+ constructor(tsHttpTemplateService?: AppTsHttpScaffoldTemplateService, tsHttpLiteTemplateService?: AppTsHttpLiteScaffoldTemplateService);
13
15
  scaffold: (targetDirectory: string, options?: {
14
16
  template?: AppScaffoldTemplate;
15
17
  }) => Promise<AppScaffoldResult>;
@@ -1,10 +1,12 @@
1
1
  import { AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+ import { AppTsHttpLiteScaffoldTemplateService } from "./app-ts-http-lite-scaffold-template.service.js";
2
3
  import { access, mkdir, writeFile } from "node:fs/promises";
3
4
  import path from "node:path";
4
5
  //#region src/scaffold/app-scaffold.service.ts
5
6
  var AppScaffoldService = class {
6
- constructor(tsHttpTemplateService = new AppTsHttpScaffoldTemplateService()) {
7
+ constructor(tsHttpTemplateService = new AppTsHttpScaffoldTemplateService(), tsHttpLiteTemplateService = new AppTsHttpLiteScaffoldTemplateService()) {
7
8
  this.tsHttpTemplateService = tsHttpTemplateService;
9
+ this.tsHttpLiteTemplateService = tsHttpLiteTemplateService;
8
10
  }
9
11
  scaffold = async (targetDirectory, options) => {
10
12
  const appDirectory = path.resolve(targetDirectory);
@@ -27,6 +29,17 @@ var AppScaffoldService = class {
27
29
  template
28
30
  };
29
31
  }
32
+ if (template === "ts-http-lite") {
33
+ await this.writeTemplateFiles(appDirectory, this.tsHttpLiteTemplateService.buildFiles({
34
+ appId,
35
+ appName
36
+ }));
37
+ return {
38
+ appDirectory,
39
+ manifestPath,
40
+ template
41
+ };
42
+ }
30
43
  await Promise.all([
31
44
  writeFile(manifestPath, `${JSON.stringify(this.buildManifest(appId, appName), null, 2)}\n`, "utf-8"),
32
45
  writeFile(path.join(appDirectory, "marketplace.json"), `${JSON.stringify(this.buildMarketplaceMetadata(appName), null, 2)}\n`, "utf-8"),
@@ -0,0 +1,18 @@
1
+ import { AppScaffoldFile, AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+
3
+ //#region src/scaffold/app-ts-http-lite-scaffold-template.service.d.ts
4
+ declare class AppTsHttpLiteScaffoldTemplateService {
5
+ private readonly standardTemplateService;
6
+ constructor(standardTemplateService?: AppTsHttpScaffoldTemplateService);
7
+ buildFiles: (params: {
8
+ appId: string;
9
+ appName: string;
10
+ }) => AppScaffoldFile[];
11
+ private buildMainPackageJson;
12
+ private buildMarketplaceMetadata;
13
+ private buildReadme;
14
+ private buildComponentSource;
15
+ private normalizeSlug;
16
+ }
17
+ //#endregion
18
+ export { AppTsHttpLiteScaffoldTemplateService };
@@ -0,0 +1,267 @@
1
+ import { AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+ //#region src/scaffold/app-ts-http-lite-scaffold-template.service.ts
3
+ var AppTsHttpLiteScaffoldTemplateService = class {
4
+ constructor(standardTemplateService = new AppTsHttpScaffoldTemplateService()) {
5
+ this.standardTemplateService = standardTemplateService;
6
+ }
7
+ buildFiles = (params) => {
8
+ return this.standardTemplateService.buildFiles(params).map((file) => {
9
+ switch (file.relativePath) {
10
+ case "marketplace.json": return {
11
+ relativePath: file.relativePath,
12
+ content: `${JSON.stringify(this.buildMarketplaceMetadata(params.appName), null, 2)}\n`
13
+ };
14
+ case "README.md": return {
15
+ relativePath: file.relativePath,
16
+ content: this.buildReadme(params.appName, params.appId)
17
+ };
18
+ case "main/package.json": return {
19
+ relativePath: file.relativePath,
20
+ content: `${JSON.stringify(this.buildMainPackageJson(params.appId), null, 2)}\n`
21
+ };
22
+ case "main/src/component.ts": return {
23
+ relativePath: file.relativePath,
24
+ content: this.buildComponentSource()
25
+ };
26
+ default: return file;
27
+ }
28
+ });
29
+ };
30
+ buildMainPackageJson = (appId) => {
31
+ return {
32
+ name: `${appId.replace(/\./g, "-")}-main`,
33
+ version: "0.1.0",
34
+ private: true,
35
+ type: "module",
36
+ scripts: {
37
+ build: "npm run prepare-wit && npm run guest-types && npm run typecheck && npm run bundle && npm run componentize",
38
+ "prepare-wit": "wkg wit fetch",
39
+ "guest-types": "jco guest-types wit --world-name napp-http --out-dir generated/types",
40
+ typecheck: "tsc --noEmit",
41
+ bundle: "rolldown -c",
42
+ componentize: "jco componentize -w wit -n napp-http -o app.wasm dist/component.js"
43
+ },
44
+ dependencies: { "@bytecodealliance/jco-std": "^0.1.3" },
45
+ devDependencies: {
46
+ "@bytecodealliance/componentize-js": "^0.20.0",
47
+ "@bytecodealliance/jco": "^1.19.0",
48
+ rolldown: "^1.0.0-rc.17",
49
+ typescript: "^5.6.3"
50
+ }
51
+ };
52
+ };
53
+ buildMarketplaceMetadata = (appName) => {
54
+ return {
55
+ slug: this.normalizeSlug(appName),
56
+ summary: `A lightweight TypeScript WASI HTTP ${appName} scaffold created by napp.`,
57
+ summaryI18n: {
58
+ en: `A lightweight TypeScript WASI HTTP ${appName} scaffold created by napp.`,
59
+ zh: `一个由 napp 创建的轻量 TypeScript WASI HTTP ${appName} 应用骨架。`
60
+ },
61
+ description: `A lightweight TypeScript NApp scaffold for ${appName}, designed to keep the WASI HTTP backend smaller than the default Hono-based template.`,
62
+ descriptionI18n: {
63
+ en: `A lightweight TypeScript NApp scaffold for ${appName}, designed to keep the WASI HTTP backend smaller than the default Hono-based template.`,
64
+ zh: `一个面向 ${appName} 的轻量 TypeScript NApp 应用骨架,用更薄的 WASI HTTP handler 替代默认的 Hono 模板,优先追求更小包体。`
65
+ },
66
+ author: "NextClaw",
67
+ tags: [
68
+ "starter",
69
+ "typescript",
70
+ "wasi-http",
71
+ "official",
72
+ "lite"
73
+ ],
74
+ sourceRepo: "https://github.com/Peiiii/nextclaw",
75
+ homepage: "https://nextclaw.io",
76
+ featured: false
77
+ };
78
+ };
79
+ buildReadme = (appName, appId) => {
80
+ return `# ${appName}
81
+
82
+ This app was created by \`napp create --template ts-http-lite\`.
83
+
84
+ It keeps the existing NApp directory contract, while making \`main/app.wasm\` a lighter WASI HTTP component without the default Hono routing layer.
85
+
86
+ ## App ID
87
+
88
+ \`${appId}\`
89
+
90
+ ## Build the TypeScript backend
91
+
92
+ Recommended:
93
+
94
+ \`\`\`bash
95
+ napp build . --install
96
+ \`\`\`
97
+
98
+ Manual equivalent:
99
+
100
+ \`\`\`bash
101
+ cd main
102
+ npm install
103
+ npm run build
104
+ \`\`\`
105
+
106
+ The build writes the backend component to:
107
+
108
+ \`\`\`text
109
+ main/app.wasm
110
+ \`\`\`
111
+
112
+ ## Local workflow
113
+
114
+ \`\`\`bash
115
+ napp inspect .
116
+ napp validate-publish .
117
+ napp run . --data ./data
118
+ \`\`\`
119
+
120
+ The runtime mounts the host data directory to guest \`/data\`. This example stores todos in:
121
+
122
+ \`\`\`text
123
+ ./data/todos.json
124
+ \`\`\`
125
+
126
+ The frontend still uses ordinary same-origin HTTP:
127
+
128
+ \`\`\`js
129
+ await fetch("/api/todos");
130
+ \`\`\`
131
+ `;
132
+ };
133
+ buildComponentSource = () => {
134
+ return `import { fire, incomingHandler } from "@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server";
135
+ import { getDirectories } from "wasi:filesystem/preopens@0.2.2";
136
+
137
+ type Todo = {
138
+ id: string;
139
+ title: string;
140
+ completed: boolean;
141
+ };
142
+
143
+ type TodoInput = {
144
+ title?: string;
145
+ };
146
+
147
+ const TODOS_FILE = "todos.json";
148
+ const encoder = new TextEncoder();
149
+ const decoder = new TextDecoder();
150
+ const app = {
151
+ fetch: (request: Request): Promise<Response> => routeRequest(request),
152
+ };
153
+
154
+ fire(app as never);
155
+
156
+ export { incomingHandler };
157
+
158
+ async function routeRequest(request: Request): Promise<Response> {
159
+ const url = new URL(request.url);
160
+ const pathname = url.pathname;
161
+
162
+ if (request.method === "GET" && pathname === "/api/todos") {
163
+ return json(loadTodos());
164
+ }
165
+
166
+ if (request.method === "POST" && pathname === "/api/todos") {
167
+ const input = await request.json() as TodoInput;
168
+ if (!input.title?.trim()) {
169
+ return json({ error: "title is required" }, 400);
170
+ }
171
+ const todos = loadTodos();
172
+ todos.push({
173
+ id: crypto.randomUUID(),
174
+ title: input.title.trim(),
175
+ completed: false,
176
+ });
177
+ saveTodos(todos);
178
+ return json(todos);
179
+ }
180
+
181
+ if (request.method === "PATCH" && pathname.startsWith("/api/todos/")) {
182
+ const todoId = pathname.slice("/api/todos/".length);
183
+ const input = await request.json() as Partial<Todo>;
184
+ const todos = loadTodos();
185
+ const todo = todos.find((entry) => entry.id === todoId);
186
+ if (!todo) {
187
+ return json({ error: "todo not found" }, 404);
188
+ }
189
+ if (typeof input.title === "string") {
190
+ todo.title = input.title;
191
+ }
192
+ if (typeof input.completed === "boolean") {
193
+ todo.completed = input.completed;
194
+ }
195
+ saveTodos(todos);
196
+ return json(todos);
197
+ }
198
+
199
+ if (request.method === "DELETE" && pathname.startsWith("/api/todos/")) {
200
+ const todoId = pathname.slice("/api/todos/".length);
201
+ const nextTodos = loadTodos().filter((entry) => entry.id !== todoId);
202
+ saveTodos(nextTodos);
203
+ return json(nextTodos);
204
+ }
205
+
206
+ return text("Not found", 404);
207
+ }
208
+
209
+ function json(value: unknown, status = 200): Response {
210
+ return new Response(JSON.stringify(value), {
211
+ status,
212
+ headers: {
213
+ "content-type": "application/json; charset=utf-8",
214
+ },
215
+ });
216
+ }
217
+
218
+ function text(value: string, status = 200): Response {
219
+ return new Response(value, { status });
220
+ }
221
+
222
+ function loadTodos(): Todo[] {
223
+ const file = openTodosFile({ read: true });
224
+ if (!file) {
225
+ return [];
226
+ }
227
+ const [bytes] = file.read(BigInt(1024 * 1024), BigInt(0));
228
+ if (bytes.length === 0) {
229
+ return [];
230
+ }
231
+ return JSON.parse(decoder.decode(bytes)) as Todo[];
232
+ }
233
+
234
+ function saveTodos(todos: Todo[]): void {
235
+ const file = getDataDirectory().openAt(
236
+ { symlinkFollow: true },
237
+ TODOS_FILE,
238
+ { create: true, truncate: true },
239
+ { write: true },
240
+ );
241
+ file.write(encoder.encode(JSON.stringify(todos, null, 2)), BigInt(0));
242
+ }
243
+
244
+ function openTodosFile(flags: { read?: boolean; write?: boolean }) {
245
+ try {
246
+ return getDataDirectory().openAt({ symlinkFollow: true }, TODOS_FILE, {}, flags);
247
+ } catch {
248
+ return undefined;
249
+ }
250
+ }
251
+
252
+ function getDataDirectory() {
253
+ for (const [descriptor, guestPath] of getDirectories()) {
254
+ if (guestPath === "/data") {
255
+ return descriptor;
256
+ }
257
+ }
258
+ throw new Error("Missing /data preopen. NApp runtime must mount the app data directory at /data.");
259
+ }
260
+ `;
261
+ };
262
+ normalizeSlug = (value) => {
263
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
264
+ };
265
+ };
266
+ //#endregion
267
+ export { AppTsHttpLiteScaffoldTemplateService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",