@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 +19 -2
- package/dist/bundle/app-bundle.service.js +14 -8
- package/dist/bundle/app-bundle.types.d.ts +2 -0
- package/dist/cli/app-runtime-cli.service.d.ts +1 -0
- package/dist/cli/app-runtime-cli.service.js +16 -1
- package/dist/cli/app-runtime-options.service.js +2 -2
- package/dist/commands/validate-publish.controller.d.ts +15 -0
- package/dist/commands/validate-publish.controller.js +31 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/package.js +1 -1
- package/dist/publish/app-publish-validation.service.d.ts +37 -0
- package/dist/publish/app-publish-validation.service.js +76 -0
- package/dist/scaffold/app-scaffold.service.d.ts +4 -2
- package/dist/scaffold/app-scaffold.service.js +14 -1
- package/dist/scaffold/app-ts-http-lite-scaffold-template.service.d.ts +18 -0
- package/dist/scaffold/app-ts-http-lite-scaffold-template.service.js +267 -0
- package/package.json +1 -1
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
|
|
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 (
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
await this.collectDirectoryPaths(
|
|
66
|
-
await this.collectDirectoryPaths(
|
|
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
|
|
73
|
+
return {
|
|
74
|
+
appFiles,
|
|
75
|
+
filePaths: sortedPaths
|
|
76
|
+
};
|
|
71
77
|
};
|
|
72
78
|
collectDirectoryPaths = async (directoryPath, appDirectory, filePaths) => {
|
|
73
79
|
try {
|
|
@@ -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
|
@@ -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
|
-
|
|
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 };
|