@nextclaw/app-runtime 0.10.0 → 0.12.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/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/package.js +1 -1
- package/dist/services/app-home.service.d.ts +4 -0
- package/dist/services/app-home.service.d.ts.map +1 -1
- package/dist/services/app-home.service.js +15 -1
- package/dist/services/app-home.service.js.map +1 -1
- package/dist/services/app-install-source.service.js +14 -1
- package/dist/services/app-install-source.service.js.map +1 -1
- package/dist/services/app-installation-filesystem.service.js +68 -0
- package/dist/services/app-installation-filesystem.service.js.map +1 -0
- package/dist/services/app-installation-integrity.service.d.ts +20 -0
- package/dist/services/app-installation-integrity.service.d.ts.map +1 -0
- package/dist/services/app-installation-integrity.service.js +84 -0
- package/dist/services/app-installation-integrity.service.js.map +1 -0
- package/dist/services/app-installation-lifecycle.service.js +110 -0
- package/dist/services/app-installation-lifecycle.service.js.map +1 -0
- package/dist/services/app-installation.service.d.ts +21 -11
- package/dist/services/app-installation.service.d.ts.map +1 -1
- package/dist/services/app-installation.service.js +262 -280
- package/dist/services/app-installation.service.js.map +1 -1
- package/dist/services/app-instance-inventory.service.d.ts +31 -0
- package/dist/services/app-instance-inventory.service.d.ts.map +1 -0
- package/dist/services/app-instance-inventory.service.js +136 -0
- package/dist/services/app-instance-inventory.service.js.map +1 -0
- package/dist/services/app-instance-storage.service.d.ts +55 -0
- package/dist/services/app-instance-storage.service.d.ts.map +1 -0
- package/dist/services/app-instance-storage.service.js +269 -0
- package/dist/services/app-instance-storage.service.js.map +1 -0
- package/dist/services/app-manifest.service.d.ts +5 -1
- package/dist/services/app-manifest.service.d.ts.map +1 -1
- package/dist/services/app-manifest.service.js +59 -3
- package/dist/services/app-manifest.service.js.map +1 -1
- package/dist/services/app-publish.service.js +1 -1
- package/dist/services/app-publish.service.js.map +1 -1
- package/dist/services/app-registry.service.d.ts +13 -3
- package/dist/services/app-registry.service.d.ts.map +1 -1
- package/dist/services/app-registry.service.js +143 -13
- package/dist/services/app-registry.service.js.map +1 -1
- package/dist/services/file-lock.service.d.ts +20 -0
- package/dist/services/file-lock.service.d.ts.map +1 -0
- package/dist/services/file-lock.service.js +156 -0
- package/dist/services/file-lock.service.js.map +1 -0
- package/dist/types/app-installation.types.d.ts +7 -0
- package/dist/types/app-installation.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.d.ts +22 -1
- package/dist/types/app-manifest.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.js.map +1 -1
- package/dist/types/app-registry.types.d.ts +7 -1
- package/dist/types/app-registry.types.d.ts.map +1 -1
- package/dist/types/app-storage.types.d.ts +64 -0
- package/dist/types/app-storage.types.d.ts.map +1 -0
- package/dist/types/app-storage.types.js +7 -0
- package/dist/types/app-storage.types.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- `napp publish <app-dir>`:把应用目录发布到官方 apps registry;独立应用默认使用 `source`,组合包默认使用 `bundle`
|
|
16
16
|
- `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
|
|
17
17
|
- `napp update <app-id>`:更新已安装应用
|
|
18
|
-
- `napp uninstall <app-id
|
|
18
|
+
- `napp uninstall <app-id>`:卸载已安装应用,默认保留受管实例数据;增加 `--purge-data` 才会同时删除
|
|
19
19
|
- `napp list`:列出已安装应用
|
|
20
20
|
- `napp info <app-id>`:查看已安装应用详情
|
|
21
21
|
- `napp registry [get|set|reset]`:查看或切换 registry
|
|
@@ -61,7 +61,9 @@ assets/
|
|
|
61
61
|
- 当前 Wasm 执行底座:Node 原生 `WebAssembly` 与 Wasmtime `serve`
|
|
62
62
|
- 分发包形态:`.napp`(底层为 zip,支持 `source` / `bundle` 双模式)
|
|
63
63
|
- 安装目录:`~/.nextclaw/apps/packages/<app-id>/<version>/`
|
|
64
|
-
-
|
|
64
|
+
- 受管实例目录:`~/.nextclaw/apps/instances/<app-id>/default/`
|
|
65
|
+
- 受管实例分类:`data/`、`config/`、`state/`、`cache/`、`tmp/`、`logs/`,并由 `metadata.json` 记录稳定身份
|
|
66
|
+
- 旧用户数据目录:`~/.nextclaw/apps/data/<app-id>/`(首次使用时原子迁移到受管实例)
|
|
65
67
|
- 本地 registry:`~/.nextclaw/apps/registry.json`
|
|
66
68
|
- 本地 config:`~/.nextclaw/apps/config.json`
|
|
67
69
|
- 默认 registry:`https://apps-registry.nextclaw.io/api/v1/apps/registry/`
|
|
@@ -119,6 +121,21 @@ napp uninstall nextclaw.my-first-napp
|
|
|
119
121
|
napp uninstall nextclaw.my-first-napp --purge-data
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
不带 `--purge-data` 时只卸载应用代码和注册记录,受管实例会保留,以便重装后继续使用。带上该选项后,卸载事务才会同时移除该 App 的默认实例。宿主只管理上述实例目录;通过文档授权访问的外部文件或目录不属于 App 实例,卸载和清理数据都不会删除它们。
|
|
125
|
+
|
|
126
|
+
运行中的 NextClaw 还提供统一的数据清单和残留数据清理入口:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
nextclaw app data list --json
|
|
130
|
+
nextclaw app data delete <data-id> --confirm <app-id> --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`data-id` 必须取自最新的 `list` 结果;删除命令只接受已经卸载或已经从 workspace 移除的 `retained` 实例,并要求 `--confirm` 与目标 App id 完全一致。开发 Service App 时,可以显式重置隔离的开发实例:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
nextclaw app dev <service-app-dir> --reset-data --confirm <app-id> --json
|
|
137
|
+
```
|
|
138
|
+
|
|
122
139
|
## 示例
|
|
123
140
|
|
|
124
141
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppDistributionMode } from "./types/app-bundle.types.js";
|
|
2
2
|
import { AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService } from "./services/app-artifact-validation.service.js";
|
|
3
|
-
import { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppResolvedComponent, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
|
|
3
|
+
import { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent, AppRuntimeDeclaration, AppRuntimeIsolation, AppRuntimeProfile, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageDeclaration, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
|
|
4
4
|
import { AppManifestService } from "./services/app-manifest.service.js";
|
|
5
5
|
import { AppBundleService } from "./services/app-bundle.service.js";
|
|
6
6
|
import { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions } from "./types/app-permissions.types.js";
|
|
@@ -18,11 +18,13 @@ import { AppRuntimeOptionsService, BuildCliOptions, CreateCliOptions, GrantCliOp
|
|
|
18
18
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
19
19
|
import { CreateCommand } from "./controllers/create.controller.js";
|
|
20
20
|
import { AppHomeService } from "./services/app-home.service.js";
|
|
21
|
+
import { APP_STORAGE_LAYOUT_VERSION, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceMetadata, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
21
22
|
import { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
|
|
22
23
|
import { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion } from "./types/app-registry.types.js";
|
|
23
24
|
import { AppRegistryService } from "./services/app-registry.service.js";
|
|
24
25
|
import { AppGrantService } from "./services/app-grant.service.js";
|
|
25
26
|
import { GrantCommand } from "./controllers/grant.controller.js";
|
|
27
|
+
import { AppInstanceStorageService } from "./services/app-instance-storage.service.js";
|
|
26
28
|
import { AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeToolStatus, AppRuntimeToolchainService } from "./services/app-runtime-toolchain.service.js";
|
|
27
29
|
import { AppBuildResult, AppBuildService } from "./services/app-build.service.js";
|
|
28
30
|
import { AppRegistryConfigService } from "./services/app-registry-config.service.js";
|
|
@@ -48,5 +50,8 @@ import { AppPublishValidationResult, AppPublishValidationService, AppPublishVali
|
|
|
48
50
|
import { ValidatePublishCommand } from "./controllers/validate-publish.controller.js";
|
|
49
51
|
import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./services/wasmtime-wasi-http-component.service.js";
|
|
50
52
|
import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/app-host.service.js";
|
|
53
|
+
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
54
|
+
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
55
|
+
import { FileLockService } from "./services/file-lock.service.js";
|
|
51
56
|
import { UiServerService } from "./services/ui-server.service.js";
|
|
52
|
-
export { AppActivationResult, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeOptionsService, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, 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, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
57
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, 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, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,10 @@ import { WasmSidecarClientService } from "./services/wasm-sidecar-client.service
|
|
|
15
15
|
import { WasmMainRunnerService } from "./services/wasm-main-runner.service.js";
|
|
16
16
|
import { AppInstanceService } from "./services/app-instance.service.js";
|
|
17
17
|
import { AppHomeService } from "./services/app-home.service.js";
|
|
18
|
+
import { FileLockService } from "./services/file-lock.service.js";
|
|
19
|
+
import { APP_STORAGE_LAYOUT_VERSION, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
20
|
+
import { AppInstanceStorageService } from "./services/app-instance-storage.service.js";
|
|
21
|
+
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
18
22
|
import { AppRuntimeToolchainService } from "./services/app-runtime-toolchain.service.js";
|
|
19
23
|
import { AppBuildService } from "./services/app-build.service.js";
|
|
20
24
|
import { DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
|
|
@@ -44,4 +48,5 @@ import { AppPublishValidationService } from "./services/app-publish-validation.s
|
|
|
44
48
|
import { ValidatePublishCommand } from "./controllers/validate-publish.controller.js";
|
|
45
49
|
import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
|
|
46
50
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
47
|
-
|
|
51
|
+
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
52
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/package.js
CHANGED
|
@@ -5,11 +5,15 @@ declare class AppHomeService {
|
|
|
5
5
|
getAppHomeDirectory: () => string;
|
|
6
6
|
getPackagesDirectory: () => string;
|
|
7
7
|
getDataDirectory: () => string;
|
|
8
|
+
getInstancesDirectory: () => string;
|
|
9
|
+
getLocksDirectory: () => string;
|
|
8
10
|
getRegistryPath: () => string;
|
|
9
11
|
getConfigPath: () => string;
|
|
10
12
|
getOperationsPath: () => string;
|
|
11
13
|
getInstallDirectory: (appId: string, version: string) => string;
|
|
12
14
|
getAppDataDirectory: (appId: string) => string;
|
|
15
|
+
getAppInstanceDirectory: (appId: string, instanceId: string) => string;
|
|
16
|
+
getAppOperationLockPath: (appId: string) => string;
|
|
13
17
|
ensureBaseDirectories: () => Promise<void>;
|
|
14
18
|
createTemporaryDirectory: (prefix: string) => Promise<string>;
|
|
15
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-home.service.d.ts","names":[],"sources":["../../src/services/app-home.service.ts"],"mappings":";cAIa,cAAA;EAAA,iBAEQ,gBAAA;cAAA,gBAAA;EAKnB,mBAAA;EAIA,oBAAA;EAIA,gBAAA;EAIA,eAAA;EAIA,aAAA;EAIA,iBAAA;EAIA,mBAAA,GAAuB,KAAA,UAAe,OAAA;EAItC,mBAAA,GAAuB,KAAA;EAIvB,qBAAA,QAAkC,OAAA;
|
|
1
|
+
{"version":3,"file":"app-home.service.d.ts","names":[],"sources":["../../src/services/app-home.service.ts"],"mappings":";cAIa,cAAA;EAAA,iBAEQ,gBAAA;cAAA,gBAAA;EAKnB,mBAAA;EAIA,oBAAA;EAIA,gBAAA;EAIA,qBAAA;EAIA,iBAAA;EAIA,eAAA;EAIA,aAAA;EAIA,iBAAA;EAIA,mBAAA,GAAuB,KAAA,UAAe,OAAA;EAItC,mBAAA,GAAuB,KAAA;EAIvB,uBAAA,GAA2B,KAAA,UAAe,UAAA;EAI1C,uBAAA,GAA2B,KAAA;EAI3B,qBAAA,QAAkC,OAAA;EAUlC,wBAAA,GAAkC,MAAA,aAAiB,OAAA;AAAA"}
|
|
@@ -15,6 +15,12 @@ var AppHomeService = class {
|
|
|
15
15
|
getDataDirectory = () => {
|
|
16
16
|
return path.join(this.appHomeDirectory, "data");
|
|
17
17
|
};
|
|
18
|
+
getInstancesDirectory = () => {
|
|
19
|
+
return path.join(this.appHomeDirectory, "instances");
|
|
20
|
+
};
|
|
21
|
+
getLocksDirectory = () => {
|
|
22
|
+
return path.join(this.appHomeDirectory, "locks");
|
|
23
|
+
};
|
|
18
24
|
getRegistryPath = () => {
|
|
19
25
|
return path.join(this.appHomeDirectory, "registry.json");
|
|
20
26
|
};
|
|
@@ -30,11 +36,19 @@ var AppHomeService = class {
|
|
|
30
36
|
getAppDataDirectory = (appId) => {
|
|
31
37
|
return path.join(this.getDataDirectory(), appId);
|
|
32
38
|
};
|
|
39
|
+
getAppInstanceDirectory = (appId, instanceId) => {
|
|
40
|
+
return path.join(this.getInstancesDirectory(), appId, instanceId);
|
|
41
|
+
};
|
|
42
|
+
getAppOperationLockPath = (appId) => {
|
|
43
|
+
return path.join(this.getLocksDirectory(), "apps", `${appId}.lock`);
|
|
44
|
+
};
|
|
33
45
|
ensureBaseDirectories = async () => {
|
|
34
46
|
await Promise.all([
|
|
35
47
|
mkdir(this.appHomeDirectory, { recursive: true }),
|
|
36
48
|
mkdir(this.getPackagesDirectory(), { recursive: true }),
|
|
37
|
-
mkdir(this.getDataDirectory(), { recursive: true })
|
|
49
|
+
mkdir(this.getDataDirectory(), { recursive: true }),
|
|
50
|
+
mkdir(this.getInstancesDirectory(), { recursive: true }),
|
|
51
|
+
mkdir(this.getLocksDirectory(), { recursive: true })
|
|
38
52
|
]);
|
|
39
53
|
};
|
|
40
54
|
createTemporaryDirectory = async (prefix) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-home.service.js","names":[],"sources":["../../src/services/app-home.service.ts"],"sourcesContent":["import { mkdir, mkdtemp } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nexport class AppHomeService {\n constructor(\n private readonly appHomeDirectory: string = process.env.NEXTCLAW_APP_HOME\n ? path.resolve(process.env.NEXTCLAW_APP_HOME)\n : path.join(process.env.HOME ?? process.cwd(), \".nextclaw\", \"apps\"),\n ) {}\n\n getAppHomeDirectory = (): string => {\n return this.appHomeDirectory;\n };\n\n getPackagesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"packages\");\n };\n\n getDataDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"data\");\n };\n\n getRegistryPath = (): string => {\n return path.join(this.appHomeDirectory, \"registry.json\");\n };\n\n getConfigPath = (): string => {\n return path.join(this.appHomeDirectory, \"config.json\");\n };\n\n getOperationsPath = (): string => {\n return path.join(this.appHomeDirectory, \"operations.json\");\n };\n\n getInstallDirectory = (appId: string, version: string): string => {\n return path.join(this.getPackagesDirectory(), appId, version);\n };\n\n getAppDataDirectory = (appId: string): string => {\n return path.join(this.getDataDirectory(), appId);\n };\n\n ensureBaseDirectories = async (): Promise<void> => {\n await Promise.all([\n mkdir(this.appHomeDirectory, { recursive: true }),\n mkdir(this.getPackagesDirectory(), { recursive: true }),\n mkdir(this.getDataDirectory(), { recursive: true }),\n ]);\n };\n\n createTemporaryDirectory = async (prefix: string): Promise<string> => {\n return mkdtemp(path.join(tmpdir(), prefix));\n };\n}\n"],"mappings":";;;;AAIA,IAAa,iBAAb,MAA4B;CAC1B,YACE,mBAA4C,QAAQ,IAAI,oBACpD,KAAK,QAAQ,QAAQ,IAAI,kBAAkB,GAC3C,KAAK,KAAK,QAAQ,IAAI,QAAQ,QAAQ,KAAK,EAAE,aAAa,OAAO,EACrE;AAHiB,OAAA,mBAAA;;CAKnB,4BAAoC;AAClC,SAAO,KAAK;;CAGd,6BAAqC;AACnC,SAAO,KAAK,KAAK,KAAK,kBAAkB,WAAW;;CAGrD,yBAAiC;AAC/B,SAAO,KAAK,KAAK,KAAK,kBAAkB,OAAO;;CAGjD,wBAAgC;AAC9B,SAAO,KAAK,KAAK,KAAK,kBAAkB,gBAAgB;;CAG1D,sBAA8B;AAC5B,SAAO,KAAK,KAAK,KAAK,kBAAkB,cAAc;;CAGxD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,kBAAkB;;CAG5D,uBAAuB,OAAe,YAA4B;AAChE,SAAO,KAAK,KAAK,KAAK,sBAAsB,EAAE,OAAO,QAAQ;;CAG/D,uBAAuB,UAA0B;AAC/C,SAAO,KAAK,KAAK,KAAK,kBAAkB,EAAE,MAAM;;CAGlD,wBAAwB,YAA2B;AACjD,QAAM,QAAQ,IAAI;GAChB,MAAM,KAAK,kBAAkB,EAAE,WAAW,MAAM,CAAC;GACjD,MAAM,KAAK,sBAAsB,EAAE,EAAE,WAAW,MAAM,CAAC;GACvD,MAAM,KAAK,kBAAkB,EAAE,EAAE,WAAW,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"app-home.service.js","names":[],"sources":["../../src/services/app-home.service.ts"],"sourcesContent":["import { mkdir, mkdtemp } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nexport class AppHomeService {\n constructor(\n private readonly appHomeDirectory: string = process.env.NEXTCLAW_APP_HOME\n ? path.resolve(process.env.NEXTCLAW_APP_HOME)\n : path.join(process.env.HOME ?? process.cwd(), \".nextclaw\", \"apps\"),\n ) {}\n\n getAppHomeDirectory = (): string => {\n return this.appHomeDirectory;\n };\n\n getPackagesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"packages\");\n };\n\n getDataDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"data\");\n };\n\n getInstancesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"instances\");\n };\n\n getLocksDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"locks\");\n };\n\n getRegistryPath = (): string => {\n return path.join(this.appHomeDirectory, \"registry.json\");\n };\n\n getConfigPath = (): string => {\n return path.join(this.appHomeDirectory, \"config.json\");\n };\n\n getOperationsPath = (): string => {\n return path.join(this.appHomeDirectory, \"operations.json\");\n };\n\n getInstallDirectory = (appId: string, version: string): string => {\n return path.join(this.getPackagesDirectory(), appId, version);\n };\n\n getAppDataDirectory = (appId: string): string => {\n return path.join(this.getDataDirectory(), appId);\n };\n\n getAppInstanceDirectory = (appId: string, instanceId: string): string => {\n return path.join(this.getInstancesDirectory(), appId, instanceId);\n };\n\n getAppOperationLockPath = (appId: string): string => {\n return path.join(this.getLocksDirectory(), \"apps\", `${appId}.lock`);\n };\n\n ensureBaseDirectories = async (): Promise<void> => {\n await Promise.all([\n mkdir(this.appHomeDirectory, { recursive: true }),\n mkdir(this.getPackagesDirectory(), { recursive: true }),\n mkdir(this.getDataDirectory(), { recursive: true }),\n mkdir(this.getInstancesDirectory(), { recursive: true }),\n mkdir(this.getLocksDirectory(), { recursive: true }),\n ]);\n };\n\n createTemporaryDirectory = async (prefix: string): Promise<string> => {\n return mkdtemp(path.join(tmpdir(), prefix));\n };\n}\n"],"mappings":";;;;AAIA,IAAa,iBAAb,MAA4B;CAC1B,YACE,mBAA4C,QAAQ,IAAI,oBACpD,KAAK,QAAQ,QAAQ,IAAI,kBAAkB,GAC3C,KAAK,KAAK,QAAQ,IAAI,QAAQ,QAAQ,KAAK,EAAE,aAAa,OAAO,EACrE;AAHiB,OAAA,mBAAA;;CAKnB,4BAAoC;AAClC,SAAO,KAAK;;CAGd,6BAAqC;AACnC,SAAO,KAAK,KAAK,KAAK,kBAAkB,WAAW;;CAGrD,yBAAiC;AAC/B,SAAO,KAAK,KAAK,KAAK,kBAAkB,OAAO;;CAGjD,8BAAsC;AACpC,SAAO,KAAK,KAAK,KAAK,kBAAkB,YAAY;;CAGtD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,QAAQ;;CAGlD,wBAAgC;AAC9B,SAAO,KAAK,KAAK,KAAK,kBAAkB,gBAAgB;;CAG1D,sBAA8B;AAC5B,SAAO,KAAK,KAAK,KAAK,kBAAkB,cAAc;;CAGxD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,kBAAkB;;CAG5D,uBAAuB,OAAe,YAA4B;AAChE,SAAO,KAAK,KAAK,KAAK,sBAAsB,EAAE,OAAO,QAAQ;;CAG/D,uBAAuB,UAA0B;AAC/C,SAAO,KAAK,KAAK,KAAK,kBAAkB,EAAE,MAAM;;CAGlD,2BAA2B,OAAe,eAA+B;AACvE,SAAO,KAAK,KAAK,KAAK,uBAAuB,EAAE,OAAO,WAAW;;CAGnE,2BAA2B,UAA0B;AACnD,SAAO,KAAK,KAAK,KAAK,mBAAmB,EAAE,QAAQ,GAAG,MAAM,OAAO;;CAGrE,wBAAwB,YAA2B;AACjD,QAAM,QAAQ,IAAI;GAChB,MAAM,KAAK,kBAAkB,EAAE,WAAW,MAAM,CAAC;GACjD,MAAM,KAAK,sBAAsB,EAAE,EAAE,WAAW,MAAM,CAAC;GACvD,MAAM,KAAK,kBAAkB,EAAE,EAAE,WAAW,MAAM,CAAC;GACnD,MAAM,KAAK,uBAAuB,EAAE,EAAE,WAAW,MAAM,CAAC;GACxD,MAAM,KAAK,mBAAmB,EAAE,EAAE,WAAW,MAAM,CAAC;GACrD,CAAC;;CAGJ,2BAA2B,OAAO,WAAoC;AACpE,SAAO,QAAQ,KAAK,KAAK,QAAQ,EAAE,OAAO,CAAC"}
|
|
@@ -2,10 +2,23 @@ import { access } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
//#region src/services/app-install-source.service.ts
|
|
4
4
|
var AppInstallSourceService = class {
|
|
5
|
-
constructor(manifestService, remoteRegistryClient) {
|
|
5
|
+
constructor(manifestService, remoteRegistryClient, bundleService) {
|
|
6
6
|
this.manifestService = manifestService;
|
|
7
7
|
this.remoteRegistryClient = remoteRegistryClient;
|
|
8
|
+
this.bundleService = bundleService;
|
|
8
9
|
}
|
|
10
|
+
materializeBundle = async (source, tempDirectory, onProgress) => {
|
|
11
|
+
if (source.kind === "directory") return (await this.bundleService.packAppDirectory({
|
|
12
|
+
appDirectory: source.appDirectory,
|
|
13
|
+
outputPath: path.join(tempDirectory, "app.napp")
|
|
14
|
+
})).bundlePath;
|
|
15
|
+
if (source.kind === "bundle") return source.bundlePath;
|
|
16
|
+
await onProgress?.("downloading");
|
|
17
|
+
return (await this.remoteRegistryClient.downloadBundle({
|
|
18
|
+
resolution: source.registryResolution,
|
|
19
|
+
targetDirectory: tempDirectory
|
|
20
|
+
})).bundlePath;
|
|
21
|
+
};
|
|
9
22
|
resolve = async (appSource, registryUrl) => {
|
|
10
23
|
const localSource = await this.detectLocal(appSource);
|
|
11
24
|
if (localSource.kind === "directory") return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-install-source.service.js","names":[],"sources":["../../src/services/app-install-source.service.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\n\ntype LocalAppSource =\n | { kind: \"directory\"; appDirectory: string }\n | { kind: \"bundle\"; bundlePath: string }\n | { kind: \"missing\" };\n\nexport type ResolvedAppInstallSource =\n | {\n kind: \"directory\";\n appDirectory: string;\n sourceRef: string;\n }\n | {\n kind: \"bundle\";\n bundlePath: string;\n sourceRef: string;\n }\n | {\n kind: \"registry\";\n sourceRef: string;\n registryResolution: Awaited<ReturnType<AppRemoteRegistryClientService[\"resolve\"]>>;\n };\n\nexport class AppInstallSourceService {\n constructor(\n private readonly manifestService: AppManifestService,\n private readonly remoteRegistryClient: AppRemoteRegistryClientService,\n ) {}\n\n resolve = async (\n appSource: string,\n registryUrl?: string,\n ): Promise<ResolvedAppInstallSource> => {\n const localSource = await this.detectLocal(appSource);\n if (localSource.kind === \"directory\") {\n return {\n kind: \"directory\",\n appDirectory: localSource.appDirectory,\n sourceRef: localSource.appDirectory,\n };\n }\n if (localSource.kind === \"bundle\") {\n return {\n kind: \"bundle\",\n bundlePath: localSource.bundlePath,\n sourceRef: localSource.bundlePath,\n };\n }\n const registrySpec = this.parseRegistrySpec(appSource);\n if (!registrySpec) {\n if (this.looksLikePath(appSource)) {\n throw new Error(`本地安装源不存在:${appSource}`);\n }\n throw new Error(`无法识别安装源:${appSource}`);\n }\n const registryResolution = await this.remoteRegistryClient.resolve({\n appId: registrySpec.appId,\n version: registrySpec.version,\n registryUrl,\n });\n return {\n kind: \"registry\",\n sourceRef: `${registryResolution.appId}@${registryResolution.version}`,\n registryResolution,\n };\n };\n\n detectLocal = async (\n sourcePath: string,\n allowBundle: boolean = true,\n ): Promise<LocalAppSource> => {\n const normalizedSource = path.resolve(sourcePath);\n try {\n await access(normalizedSource);\n const manifestBundle = await this.manifestService.load(normalizedSource);\n if (manifestBundle.appDirectory) {\n return {\n kind: \"directory\",\n appDirectory: normalizedSource,\n };\n }\n } catch {\n if (allowBundle && normalizedSource.endsWith(\".napp\")) {\n try {\n await access(normalizedSource);\n return {\n kind: \"bundle\",\n bundlePath: normalizedSource,\n };\n } catch {\n return { kind: \"missing\" };\n }\n }\n return { kind: \"missing\" };\n }\n return { kind: \"missing\" };\n };\n\n private parseRegistrySpec = (\n appSource: string,\n ): { appId: string; version?: string } | undefined => {\n const match = /^(?<appId>[a-z0-9][a-z0-9._-]*)(?:@(?<version>[A-Za-z0-9._+-]+))?$/i.exec(\n appSource.trim(),\n );\n if (!match?.groups?.appId) {\n return undefined;\n }\n return {\n appId: match.groups.appId,\n version: match.groups.version,\n };\n };\n\n private looksLikePath = (appSource: string): boolean =>\n appSource.startsWith(\".\") ||\n appSource.startsWith(\"/\") ||\n appSource.includes(path.sep);\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"app-install-source.service.js","names":[],"sources":["../../src/services/app-install-source.service.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport type { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport type { AppInstallProgressHandler } from \"#app-runtime/types/app-installation.types.js\";\n\ntype LocalAppSource =\n | { kind: \"directory\"; appDirectory: string }\n | { kind: \"bundle\"; bundlePath: string }\n | { kind: \"missing\" };\n\nexport type ResolvedAppInstallSource =\n | {\n kind: \"directory\";\n appDirectory: string;\n sourceRef: string;\n }\n | {\n kind: \"bundle\";\n bundlePath: string;\n sourceRef: string;\n }\n | {\n kind: \"registry\";\n sourceRef: string;\n registryResolution: Awaited<ReturnType<AppRemoteRegistryClientService[\"resolve\"]>>;\n };\n\nexport class AppInstallSourceService {\n constructor(\n private readonly manifestService: AppManifestService,\n private readonly remoteRegistryClient: AppRemoteRegistryClientService,\n private readonly bundleService: AppBundleService,\n ) {}\n\n materializeBundle = async (\n source: ResolvedAppInstallSource,\n tempDirectory: string,\n onProgress?: AppInstallProgressHandler,\n ): Promise<string> => {\n if (source.kind === \"directory\") {\n return (await this.bundleService.packAppDirectory({\n appDirectory: source.appDirectory,\n outputPath: path.join(tempDirectory, \"app.napp\"),\n })).bundlePath;\n }\n if (source.kind === \"bundle\") {\n return source.bundlePath;\n }\n await onProgress?.(\"downloading\");\n return (await this.remoteRegistryClient.downloadBundle({\n resolution: source.registryResolution,\n targetDirectory: tempDirectory,\n })).bundlePath;\n };\n\n resolve = async (\n appSource: string,\n registryUrl?: string,\n ): Promise<ResolvedAppInstallSource> => {\n const localSource = await this.detectLocal(appSource);\n if (localSource.kind === \"directory\") {\n return {\n kind: \"directory\",\n appDirectory: localSource.appDirectory,\n sourceRef: localSource.appDirectory,\n };\n }\n if (localSource.kind === \"bundle\") {\n return {\n kind: \"bundle\",\n bundlePath: localSource.bundlePath,\n sourceRef: localSource.bundlePath,\n };\n }\n const registrySpec = this.parseRegistrySpec(appSource);\n if (!registrySpec) {\n if (this.looksLikePath(appSource)) {\n throw new Error(`本地安装源不存在:${appSource}`);\n }\n throw new Error(`无法识别安装源:${appSource}`);\n }\n const registryResolution = await this.remoteRegistryClient.resolve({\n appId: registrySpec.appId,\n version: registrySpec.version,\n registryUrl,\n });\n return {\n kind: \"registry\",\n sourceRef: `${registryResolution.appId}@${registryResolution.version}`,\n registryResolution,\n };\n };\n\n detectLocal = async (\n sourcePath: string,\n allowBundle: boolean = true,\n ): Promise<LocalAppSource> => {\n const normalizedSource = path.resolve(sourcePath);\n try {\n await access(normalizedSource);\n const manifestBundle = await this.manifestService.load(normalizedSource);\n if (manifestBundle.appDirectory) {\n return {\n kind: \"directory\",\n appDirectory: normalizedSource,\n };\n }\n } catch {\n if (allowBundle && normalizedSource.endsWith(\".napp\")) {\n try {\n await access(normalizedSource);\n return {\n kind: \"bundle\",\n bundlePath: normalizedSource,\n };\n } catch {\n return { kind: \"missing\" };\n }\n }\n return { kind: \"missing\" };\n }\n return { kind: \"missing\" };\n };\n\n private parseRegistrySpec = (\n appSource: string,\n ): { appId: string; version?: string } | undefined => {\n const match = /^(?<appId>[a-z0-9][a-z0-9._-]*)(?:@(?<version>[A-Za-z0-9._+-]+))?$/i.exec(\n appSource.trim(),\n );\n if (!match?.groups?.appId) {\n return undefined;\n }\n return {\n appId: match.groups.appId,\n version: match.groups.version,\n };\n };\n\n private looksLikePath = (appSource: string): boolean =>\n appSource.startsWith(\".\") ||\n appSource.startsWith(\"/\") ||\n appSource.includes(path.sep);\n}\n"],"mappings":";;;AA6BA,IAAa,0BAAb,MAAqC;CACnC,YACE,iBACA,sBACA,eACA;AAHiB,OAAA,kBAAA;AACA,OAAA,uBAAA;AACA,OAAA,gBAAA;;CAGnB,oBAAoB,OAClB,QACA,eACA,eACoB;AACpB,MAAI,OAAO,SAAS,YAClB,SAAQ,MAAM,KAAK,cAAc,iBAAiB;GAChD,cAAc,OAAO;GACrB,YAAY,KAAK,KAAK,eAAe,WAAW;GACjD,CAAC,EAAE;AAEN,MAAI,OAAO,SAAS,SAClB,QAAO,OAAO;AAEhB,QAAM,aAAa,cAAc;AACjC,UAAQ,MAAM,KAAK,qBAAqB,eAAe;GACrD,YAAY,OAAO;GACnB,iBAAiB;GAClB,CAAC,EAAE;;CAGN,UAAU,OACR,WACA,gBACsC;EACtC,MAAM,cAAc,MAAM,KAAK,YAAY,UAAU;AACrD,MAAI,YAAY,SAAS,YACvB,QAAO;GACL,MAAM;GACN,cAAc,YAAY;GAC1B,WAAW,YAAY;GACxB;AAEH,MAAI,YAAY,SAAS,SACvB,QAAO;GACL,MAAM;GACN,YAAY,YAAY;GACxB,WAAW,YAAY;GACxB;EAEH,MAAM,eAAe,KAAK,kBAAkB,UAAU;AACtD,MAAI,CAAC,cAAc;AACjB,OAAI,KAAK,cAAc,UAAU,CAC/B,OAAM,IAAI,MAAM,YAAY,YAAY;AAE1C,SAAM,IAAI,MAAM,WAAW,YAAY;;EAEzC,MAAM,qBAAqB,MAAM,KAAK,qBAAqB,QAAQ;GACjE,OAAO,aAAa;GACpB,SAAS,aAAa;GACtB;GACD,CAAC;AACF,SAAO;GACL,MAAM;GACN,WAAW,GAAG,mBAAmB,MAAM,GAAG,mBAAmB;GAC7D;GACD;;CAGH,cAAc,OACZ,YACA,cAAuB,SACK;EAC5B,MAAM,mBAAmB,KAAK,QAAQ,WAAW;AACjD,MAAI;AACF,SAAM,OAAO,iBAAiB;AAE9B,QADuB,MAAM,KAAK,gBAAgB,KAAK,iBAAiB,EACrD,aACjB,QAAO;IACL,MAAM;IACN,cAAc;IACf;UAEG;AACN,OAAI,eAAe,iBAAiB,SAAS,QAAQ,CACnD,KAAI;AACF,UAAM,OAAO,iBAAiB;AAC9B,WAAO;KACL,MAAM;KACN,YAAY;KACb;WACK;AACN,WAAO,EAAE,MAAM,WAAW;;AAG9B,UAAO,EAAE,MAAM,WAAW;;AAE5B,SAAO,EAAE,MAAM,WAAW;;CAG5B,qBACE,cACoD;EACpD,MAAM,QAAQ,sEAAsE,KAClF,UAAU,MAAM,CACjB;AACD,MAAI,CAAC,OAAO,QAAQ,MAClB;AAEF,SAAO;GACL,OAAO,MAAM,OAAO;GACpB,SAAS,MAAM,OAAO;GACvB;;CAGH,iBAAyB,cACvB,UAAU,WAAW,IAAI,IACzB,UAAU,WAAW,IAAI,IACzB,UAAU,SAAS,KAAK,IAAI"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { cp, mkdir, readdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/services/app-installation-filesystem.service.ts
|
|
6
|
+
var AppInstallationFilesystemService = class {
|
|
7
|
+
constructor(params) {
|
|
8
|
+
this.params = params;
|
|
9
|
+
}
|
|
10
|
+
copyToImmutableInstallDirectory = async (params) => {
|
|
11
|
+
const { appId, appVersion, extractedDirectory, installDirectory } = params;
|
|
12
|
+
if (await this.params.integrityService.pathExists(installDirectory)) throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);
|
|
13
|
+
await mkdir(path.dirname(installDirectory), { recursive: true });
|
|
14
|
+
const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;
|
|
15
|
+
try {
|
|
16
|
+
await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });
|
|
17
|
+
const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);
|
|
18
|
+
if (stagedManifest.manifest.id !== appId || stagedManifest.manifest.version !== appVersion) throw new Error("staging manifest 与已验证 bundle 身份不一致。");
|
|
19
|
+
const contentSha256 = await this.params.integrityService.calculateDigest(stagedInstallDirectory);
|
|
20
|
+
await rename(stagedInstallDirectory, installDirectory);
|
|
21
|
+
await this.params.integrityService.protectDirectory(installDirectory);
|
|
22
|
+
return contentSha256;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
await rm(stagedInstallDirectory, {
|
|
25
|
+
recursive: true,
|
|
26
|
+
force: true
|
|
27
|
+
});
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
materializeDistribution = async (params) => {
|
|
32
|
+
if (params.distributionMode !== "source") return;
|
|
33
|
+
const manifestBundle = await this.params.manifestService.load(params.appDirectory);
|
|
34
|
+
if (!isAppStandaloneManifestBundle(manifestBundle)) throw new Error("schema v2 组合包不允许运行安装期 build。");
|
|
35
|
+
if (manifestBundle.manifest.main.kind !== "wasi-http-component") return;
|
|
36
|
+
await this.params.buildService.build({
|
|
37
|
+
appDirectory: params.appDirectory,
|
|
38
|
+
install: true
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
reconcileGeneratedSiblings = async (parentDirectory, referencedPaths) => {
|
|
42
|
+
for (const entry of await this.readDirectories(parentDirectory)) {
|
|
43
|
+
const entryName = path.basename(entry);
|
|
44
|
+
if (entryName.includes(".staging-")) {
|
|
45
|
+
await this.params.integrityService.removeDirectory(entry);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const markerIndex = entryName.indexOf(".uninstalling-");
|
|
49
|
+
if (markerIndex < 0) continue;
|
|
50
|
+
const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));
|
|
51
|
+
if (referencedPaths.has(path.resolve(originalPath)) && !await this.params.integrityService.pathExists(originalPath)) await rename(entry, originalPath);
|
|
52
|
+
else await this.params.integrityService.removeDirectory(entry);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
readDirectories = async (directory) => {
|
|
56
|
+
try {
|
|
57
|
+
return (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path.join(directory, entry.name));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (this.isMissingFileError(error)) return [];
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { AppInstallationFilesystemService };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=app-installation-filesystem.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-filesystem.service.js","names":[],"sources":["../../src/services/app-installation-filesystem.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { cp, mkdir, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { isAppStandaloneManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\n\nexport class AppInstallationFilesystemService {\n constructor(private readonly params: {\n buildService: AppBuildService;\n integrityService: AppInstallationIntegrityService;\n manifestService: AppManifestService;\n }) {}\n\n copyToImmutableInstallDirectory = async (params: {\n appId: string;\n appVersion: string;\n extractedDirectory: string;\n installDirectory: string;\n }): Promise<string> => {\n const { appId, appVersion, extractedDirectory, installDirectory } = params;\n if (await this.params.integrityService.pathExists(installDirectory)) {\n throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);\n }\n await mkdir(path.dirname(installDirectory), { recursive: true });\n const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;\n try {\n await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });\n const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);\n if (\n stagedManifest.manifest.id !== appId ||\n stagedManifest.manifest.version !== appVersion\n ) {\n throw new Error(\"staging manifest 与已验证 bundle 身份不一致。\");\n }\n const contentSha256 = await this.params.integrityService.calculateDigest(\n stagedInstallDirectory,\n );\n await rename(stagedInstallDirectory, installDirectory);\n await this.params.integrityService.protectDirectory(installDirectory);\n return contentSha256;\n } catch (error) {\n await rm(stagedInstallDirectory, { recursive: true, force: true });\n throw error;\n }\n };\n\n materializeDistribution = async (params: {\n appDirectory: string;\n distributionMode: AppDistributionMode;\n }): Promise<void> => {\n if (params.distributionMode !== \"source\") {\n return;\n }\n const manifestBundle = await this.params.manifestService.load(params.appDirectory);\n if (!isAppStandaloneManifestBundle(manifestBundle)) {\n throw new Error(\"schema v2 组合包不允许运行安装期 build。\");\n }\n if (manifestBundle.manifest.main.kind !== \"wasi-http-component\") {\n return;\n }\n await this.params.buildService.build({\n appDirectory: params.appDirectory,\n install: true,\n });\n };\n\n reconcileGeneratedSiblings = async (\n parentDirectory: string,\n referencedPaths: Set<string>,\n ): Promise<void> => {\n for (const entry of await this.readDirectories(parentDirectory)) {\n const entryName = path.basename(entry);\n if (entryName.includes(\".staging-\")) {\n await this.params.integrityService.removeDirectory(entry);\n continue;\n }\n const markerIndex = entryName.indexOf(\".uninstalling-\");\n if (markerIndex < 0) {\n continue;\n }\n const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));\n if (\n referencedPaths.has(path.resolve(originalPath)) &&\n !await this.params.integrityService.pathExists(originalPath)\n ) {\n await rename(entry, originalPath);\n } else {\n await this.params.integrityService.removeDirectory(entry);\n }\n }\n };\n\n readDirectories = async (directory: string): Promise<string[]> => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(directory, entry.name));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":";;;;;AASA,IAAa,mCAAb,MAA8C;CAC5C,YAAY,QAIT;AAJ0B,OAAA,SAAA;;CAM7B,kCAAkC,OAAO,WAKlB;EACrB,MAAM,EAAE,OAAO,YAAY,oBAAoB,qBAAqB;AACpE,MAAI,MAAM,KAAK,OAAO,iBAAiB,WAAW,iBAAiB,CACjE,OAAM,IAAI,MAAM,uBAAuB,MAAM,GAAG,aAAa;AAE/D,QAAM,MAAM,KAAK,QAAQ,iBAAiB,EAAE,EAAE,WAAW,MAAM,CAAC;EAChE,MAAM,yBAAyB,GAAG,iBAAiB,WAAW,YAAY;AAC1E,MAAI;AACF,SAAM,GAAG,oBAAoB,wBAAwB,EAAE,WAAW,MAAM,CAAC;GACzE,MAAM,iBAAiB,MAAM,KAAK,OAAO,gBAAgB,KAAK,uBAAuB;AACrF,OACE,eAAe,SAAS,OAAO,SAC/B,eAAe,SAAS,YAAY,WAEpC,OAAM,IAAI,MAAM,sCAAsC;GAExD,MAAM,gBAAgB,MAAM,KAAK,OAAO,iBAAiB,gBACvD,uBACD;AACD,SAAM,OAAO,wBAAwB,iBAAiB;AACtD,SAAM,KAAK,OAAO,iBAAiB,iBAAiB,iBAAiB;AACrE,UAAO;WACA,OAAO;AACd,SAAM,GAAG,wBAAwB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAClE,SAAM;;;CAIV,0BAA0B,OAAO,WAGZ;AACnB,MAAI,OAAO,qBAAqB,SAC9B;EAEF,MAAM,iBAAiB,MAAM,KAAK,OAAO,gBAAgB,KAAK,OAAO,aAAa;AAClF,MAAI,CAAC,8BAA8B,eAAe,CAChD,OAAM,IAAI,MAAM,+BAA+B;AAEjD,MAAI,eAAe,SAAS,KAAK,SAAS,sBACxC;AAEF,QAAM,KAAK,OAAO,aAAa,MAAM;GACnC,cAAc,OAAO;GACrB,SAAS;GACV,CAAC;;CAGJ,6BAA6B,OAC3B,iBACA,oBACkB;AAClB,OAAK,MAAM,SAAS,MAAM,KAAK,gBAAgB,gBAAgB,EAAE;GAC/D,MAAM,YAAY,KAAK,SAAS,MAAM;AACtC,OAAI,UAAU,SAAS,YAAY,EAAE;AACnC,UAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM;AACzD;;GAEF,MAAM,cAAc,UAAU,QAAQ,iBAAiB;AACvD,OAAI,cAAc,EAChB;GAEF,MAAM,eAAe,KAAK,KAAK,iBAAiB,UAAU,MAAM,GAAG,YAAY,CAAC;AAChF,OACE,gBAAgB,IAAI,KAAK,QAAQ,aAAa,CAAC,IAC/C,CAAC,MAAM,KAAK,OAAO,iBAAiB,WAAW,aAAa,CAE5D,OAAM,OAAO,OAAO,aAAa;OAEjC,OAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM;;;CAK/D,kBAAkB,OAAO,cAAyC;AAChE,MAAI;AACF,WAAQ,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC,EACtD,QAAQ,UAAU,MAAM,aAAa,CAAC,CACtC,KAAK,UAAU,KAAK,KAAK,WAAW,MAAM,KAAK,CAAC;WAC5C,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO,EAAE;AAEX,SAAM;;;CAIV,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AppManifestService } from "./app-manifest.service.js";
|
|
2
|
+
import { AppRegistryInstalledVersion } from "../types/app-registry.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/app-installation-integrity.service.d.ts
|
|
5
|
+
declare class AppInstallationIntegrityService {
|
|
6
|
+
private readonly manifestService;
|
|
7
|
+
constructor(manifestService?: AppManifestService);
|
|
8
|
+
calculateDigest: (installDirectory: string) => Promise<string>;
|
|
9
|
+
assertVersion: (params: {
|
|
10
|
+
appId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
versionRecord: AppRegistryInstalledVersion;
|
|
13
|
+
}) => Promise<string>;
|
|
14
|
+
protectDirectory: (installDirectory: string) => Promise<void>;
|
|
15
|
+
removeDirectory: (directory: string) => Promise<void>;
|
|
16
|
+
pathExists: (targetPath: string) => Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppInstallationIntegrityService };
|
|
20
|
+
//# sourceMappingURL=app-installation-integrity.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-integrity.service.d.ts","names":[],"sources":["../../src/services/app-installation-integrity.service.ts"],"mappings":";;;;cAMa,+BAAA;EAAA,iBAEQ,eAAA;cAAA,eAAA,GAAiB,kBAAA;EAGpC,eAAA,GAAyB,gBAAA,aAA2B,OAAA;EAgCpD,aAAA,GAAuB,MAAA;IACrB,KAAA;IACA,OAAA;IACA,aAAA,EAAe,2BAAA;EAAA,MACb,OAAA;EAkBJ,gBAAA,GAA0B,gBAAA,aAA2B,OAAA;EAiBrD,eAAA,GAAyB,SAAA,aAAoB,OAAA;EAoB7C,UAAA,GAAoB,UAAA,aAAqB,OAAA;AAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AppManifestService } from "./app-manifest.service.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { access, chmod, lstat, readFile, readdir, readlink, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/services/app-installation-integrity.service.ts
|
|
6
|
+
var AppInstallationIntegrityService = class {
|
|
7
|
+
constructor(manifestService = new AppManifestService()) {
|
|
8
|
+
this.manifestService = manifestService;
|
|
9
|
+
}
|
|
10
|
+
calculateDigest = async (installDirectory) => {
|
|
11
|
+
const digest = createHash("sha256");
|
|
12
|
+
const visit = async (directory) => {
|
|
13
|
+
const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const entryPath = path.join(directory, entry.name);
|
|
16
|
+
const relativePath = path.relative(installDirectory, entryPath).replace(/\\/g, "/");
|
|
17
|
+
digest.update(relativePath);
|
|
18
|
+
digest.update("\0");
|
|
19
|
+
if (entry.isSymbolicLink()) {
|
|
20
|
+
digest.update(`link:${await readlink(entryPath)}`);
|
|
21
|
+
digest.update("\0");
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
digest.update("directory\0");
|
|
26
|
+
await visit(entryPath);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!entry.isFile()) throw new Error(`应用版本目录只允许普通文件、目录或符号链接:${relativePath}`);
|
|
30
|
+
digest.update("file\0");
|
|
31
|
+
digest.update(await readFile(entryPath));
|
|
32
|
+
digest.update("\0");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
await visit(path.resolve(installDirectory));
|
|
36
|
+
return digest.digest("hex");
|
|
37
|
+
};
|
|
38
|
+
assertVersion = async (params) => {
|
|
39
|
+
const { appId, version, versionRecord } = params;
|
|
40
|
+
const targetManifest = await this.manifestService.load(versionRecord.installDirectory);
|
|
41
|
+
if (targetManifest.manifest.id !== appId || targetManifest.manifest.version !== version) throw new Error(`应用 ${appId}@${version} manifest 身份校验失败。`);
|
|
42
|
+
const contentSha256 = await this.calculateDigest(versionRecord.installDirectory);
|
|
43
|
+
if (versionRecord.contentSha256 && contentSha256 !== versionRecord.contentSha256) throw new Error(`应用 ${appId}@${version} 代码完整性校验失败,已阻止激活。请重新安装该版本。`);
|
|
44
|
+
return contentSha256;
|
|
45
|
+
};
|
|
46
|
+
protectDirectory = async (installDirectory) => {
|
|
47
|
+
const protect = async (targetPath) => {
|
|
48
|
+
const stats = await lstat(targetPath);
|
|
49
|
+
if (stats.isSymbolicLink()) return;
|
|
50
|
+
if (stats.isDirectory()) {
|
|
51
|
+
for (const entry of await readdir(targetPath)) await protect(path.join(targetPath, entry));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await chmod(targetPath, stats.mode & -147);
|
|
55
|
+
};
|
|
56
|
+
await protect(path.resolve(installDirectory));
|
|
57
|
+
};
|
|
58
|
+
removeDirectory = async (directory) => {
|
|
59
|
+
if (!await this.pathExists(directory)) return;
|
|
60
|
+
const makeWritable = async (targetPath) => {
|
|
61
|
+
const stats = await lstat(targetPath);
|
|
62
|
+
if (stats.isSymbolicLink()) return;
|
|
63
|
+
await chmod(targetPath, stats.mode | 128 | (stats.isDirectory() ? 448 : 0));
|
|
64
|
+
if (stats.isDirectory()) for (const entry of await readdir(targetPath)) await makeWritable(path.join(targetPath, entry));
|
|
65
|
+
};
|
|
66
|
+
await makeWritable(directory);
|
|
67
|
+
await rm(directory, {
|
|
68
|
+
recursive: true,
|
|
69
|
+
force: true
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
pathExists = async (targetPath) => {
|
|
73
|
+
try {
|
|
74
|
+
await access(targetPath);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { AppInstallationIntegrityService };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=app-installation-integrity.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-integrity.service.js","names":[],"sources":["../../src/services/app-installation-integrity.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { access, chmod, lstat, readFile, readdir, readlink, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRegistryInstalledVersion } from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppInstallationIntegrityService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n ) {}\n\n calculateDigest = async (installDirectory: string): Promise<string> => {\n const digest = createHash(\"sha256\");\n const visit = async (directory: string): Promise<void> => {\n const entries = (await readdir(directory, { withFileTypes: true }))\n .sort((left, right) => left.name.localeCompare(right.name));\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n const relativePath = path.relative(installDirectory, entryPath).replace(/\\\\/g, \"/\");\n digest.update(relativePath);\n digest.update(\"\\0\");\n if (entry.isSymbolicLink()) {\n digest.update(`link:${await readlink(entryPath)}`);\n digest.update(\"\\0\");\n continue;\n }\n if (entry.isDirectory()) {\n digest.update(\"directory\\0\");\n await visit(entryPath);\n continue;\n }\n if (!entry.isFile()) {\n throw new Error(`应用版本目录只允许普通文件、目录或符号链接:${relativePath}`);\n }\n digest.update(\"file\\0\");\n digest.update(await readFile(entryPath));\n digest.update(\"\\0\");\n }\n };\n await visit(path.resolve(installDirectory));\n return digest.digest(\"hex\");\n };\n\n assertVersion = async (params: {\n appId: string;\n version: string;\n versionRecord: AppRegistryInstalledVersion;\n }): Promise<string> => {\n const { appId, version, versionRecord } = params;\n const targetManifest = await this.manifestService.load(versionRecord.installDirectory);\n if (\n targetManifest.manifest.id !== appId ||\n targetManifest.manifest.version !== version\n ) {\n throw new Error(`应用 ${appId}@${version} manifest 身份校验失败。`);\n }\n const contentSha256 = await this.calculateDigest(versionRecord.installDirectory);\n if (versionRecord.contentSha256 && contentSha256 !== versionRecord.contentSha256) {\n throw new Error(\n `应用 ${appId}@${version} 代码完整性校验失败,已阻止激活。请重新安装该版本。`,\n );\n }\n return contentSha256;\n };\n\n protectDirectory = async (installDirectory: string): Promise<void> => {\n const protect = async (targetPath: string): Promise<void> => {\n const stats = await lstat(targetPath);\n if (stats.isSymbolicLink()) {\n return;\n }\n if (stats.isDirectory()) {\n for (const entry of await readdir(targetPath)) {\n await protect(path.join(targetPath, entry));\n }\n return;\n }\n await chmod(targetPath, stats.mode & ~0o222);\n };\n await protect(path.resolve(installDirectory));\n };\n\n removeDirectory = async (directory: string): Promise<void> => {\n if (!await this.pathExists(directory)) {\n return;\n }\n const makeWritable = async (targetPath: string): Promise<void> => {\n const stats = await lstat(targetPath);\n if (stats.isSymbolicLink()) {\n return;\n }\n await chmod(targetPath, stats.mode | 0o200 | (stats.isDirectory() ? 0o700 : 0));\n if (stats.isDirectory()) {\n for (const entry of await readdir(targetPath)) {\n await makeWritable(path.join(targetPath, entry));\n }\n }\n };\n await makeWritable(directory);\n await rm(directory, { recursive: true, force: true });\n };\n\n pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n}\n"],"mappings":";;;;;AAMA,IAAa,kCAAb,MAA6C;CAC3C,YACE,kBAAuD,IAAI,oBAAoB,EAC/E;AADiB,OAAA,kBAAA;;CAGnB,kBAAkB,OAAO,qBAA8C;EACrE,MAAM,SAAS,WAAW,SAAS;EACnC,MAAM,QAAQ,OAAO,cAAqC;GACxD,MAAM,WAAW,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC,EAC/D,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,KAAK,CAAC;AAC7D,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,KAAK,KAAK,WAAW,MAAM,KAAK;IAClD,MAAM,eAAe,KAAK,SAAS,kBAAkB,UAAU,CAAC,QAAQ,OAAO,IAAI;AACnF,WAAO,OAAO,aAAa;AAC3B,WAAO,OAAO,KAAK;AACnB,QAAI,MAAM,gBAAgB,EAAE;AAC1B,YAAO,OAAO,QAAQ,MAAM,SAAS,UAAU,GAAG;AAClD,YAAO,OAAO,KAAK;AACnB;;AAEF,QAAI,MAAM,aAAa,EAAE;AACvB,YAAO,OAAO,cAAc;AAC5B,WAAM,MAAM,UAAU;AACtB;;AAEF,QAAI,CAAC,MAAM,QAAQ,CACjB,OAAM,IAAI,MAAM,yBAAyB,eAAe;AAE1D,WAAO,OAAO,SAAS;AACvB,WAAO,OAAO,MAAM,SAAS,UAAU,CAAC;AACxC,WAAO,OAAO,KAAK;;;AAGvB,QAAM,MAAM,KAAK,QAAQ,iBAAiB,CAAC;AAC3C,SAAO,OAAO,OAAO,MAAM;;CAG7B,gBAAgB,OAAO,WAIA;EACrB,MAAM,EAAE,OAAO,SAAS,kBAAkB;EAC1C,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,KAAK,cAAc,iBAAiB;AACtF,MACE,eAAe,SAAS,OAAO,SAC/B,eAAe,SAAS,YAAY,QAEpC,OAAM,IAAI,MAAM,MAAM,MAAM,GAAG,QAAQ,mBAAmB;EAE5D,MAAM,gBAAgB,MAAM,KAAK,gBAAgB,cAAc,iBAAiB;AAChF,MAAI,cAAc,iBAAiB,kBAAkB,cAAc,cACjE,OAAM,IAAI,MACR,MAAM,MAAM,GAAG,QAAQ,4BACxB;AAEH,SAAO;;CAGT,mBAAmB,OAAO,qBAA4C;EACpE,MAAM,UAAU,OAAO,eAAsC;GAC3D,MAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,OAAI,MAAM,gBAAgB,CACxB;AAEF,OAAI,MAAM,aAAa,EAAE;AACvB,SAAK,MAAM,SAAS,MAAM,QAAQ,WAAW,CAC3C,OAAM,QAAQ,KAAK,KAAK,YAAY,MAAM,CAAC;AAE7C;;AAEF,SAAM,MAAM,YAAY,MAAM,OAAO,KAAO;;AAE9C,QAAM,QAAQ,KAAK,QAAQ,iBAAiB,CAAC;;CAG/C,kBAAkB,OAAO,cAAqC;AAC5D,MAAI,CAAC,MAAM,KAAK,WAAW,UAAU,CACnC;EAEF,MAAM,eAAe,OAAO,eAAsC;GAChE,MAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,OAAI,MAAM,gBAAgB,CACxB;AAEF,SAAM,MAAM,YAAY,MAAM,OAAO,OAAS,MAAM,aAAa,GAAG,MAAQ,GAAG;AAC/E,OAAI,MAAM,aAAa,CACrB,MAAK,MAAM,SAAS,MAAM,QAAQ,WAAW,CAC3C,OAAM,aAAa,KAAK,KAAK,YAAY,MAAM,CAAC;;AAItD,QAAM,aAAa,UAAU;AAC7B,QAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;CAGvD,aAAa,OAAO,eAAyC;AAC3D,MAAI;AACF,SAAM,OAAO,WAAW;AACxB,UAAO;UACD;AACN,UAAO"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rename } from "node:fs/promises";
|
|
3
|
+
//#region src/services/app-installation-lifecycle.service.ts
|
|
4
|
+
var AppInstallationLifecycleService = class {
|
|
5
|
+
constructor(params) {
|
|
6
|
+
this.params = params;
|
|
7
|
+
}
|
|
8
|
+
update = async (appId, options = {}) => {
|
|
9
|
+
const { onProgress, registryUrl: requestedRegistryUrl, version } = options;
|
|
10
|
+
await onProgress?.("resolving");
|
|
11
|
+
const appRecord = await this.params.registryService.getApp(appId);
|
|
12
|
+
if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
|
|
13
|
+
const activeVersionRecord = appRecord.installedVersions[appRecord.activeVersion];
|
|
14
|
+
const registryUrl = requestedRegistryUrl ?? activeVersionRecord?.registryUrl ?? (await this.params.registryConfigService.getSnapshot()).currentUrl;
|
|
15
|
+
const resolution = await this.params.remoteRegistryClient.resolve({
|
|
16
|
+
appId,
|
|
17
|
+
version,
|
|
18
|
+
registryUrl
|
|
19
|
+
});
|
|
20
|
+
if (resolution.version === appRecord.activeVersion) {
|
|
21
|
+
if (!activeVersionRecord) throw new Error(`已安装应用缺少激活版本:${appId}`);
|
|
22
|
+
return this.toUpdateResult(appRecord, activeVersionRecord, appRecord.activeVersion, false);
|
|
23
|
+
}
|
|
24
|
+
const installedTarget = appRecord.installedVersions[resolution.version];
|
|
25
|
+
if (installedTarget) {
|
|
26
|
+
await onProgress?.("verifying");
|
|
27
|
+
await onProgress?.("installing");
|
|
28
|
+
if (options.activate !== false) await this.params.rollback(appId, resolution.version);
|
|
29
|
+
await onProgress?.("finalizing");
|
|
30
|
+
return this.toUpdateResult(appRecord, installedTarget, resolution.version, true);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...await this.params.install(`${appId}@${resolution.version}`, {
|
|
34
|
+
registryUrl,
|
|
35
|
+
onProgress,
|
|
36
|
+
activate: options.activate
|
|
37
|
+
}),
|
|
38
|
+
previousVersion: appRecord.activeVersion,
|
|
39
|
+
updated: true
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
uninstall = async (appId, purgeData) => {
|
|
43
|
+
const appRecord = await this.params.registryService.getApp(appId);
|
|
44
|
+
if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
|
|
45
|
+
if (!purgeData) await this.params.instanceStorageService.materializeDefaultInstance({
|
|
46
|
+
appId,
|
|
47
|
+
publisherId: appRecord.publisher?.id,
|
|
48
|
+
legacyDataDirectory: appRecord.defaultInstance.storage.layout === "legacy" ? appRecord.dataDirectory : void 0,
|
|
49
|
+
dataSchemaVersion: appRecord.defaultInstance.dataSchemaVersion
|
|
50
|
+
});
|
|
51
|
+
const removedVersions = Object.keys(appRecord.installedVersions).sort((left, right) => left.localeCompare(right));
|
|
52
|
+
const stagedPaths = [];
|
|
53
|
+
try {
|
|
54
|
+
for (const versionRecord of Object.values(appRecord.installedVersions)) await this.stagePath(versionRecord.installDirectory, stagedPaths);
|
|
55
|
+
if (purgeData) await this.stagePath(appRecord.defaultInstance.storage.layout === "instance-v1" ? appRecord.defaultInstance.storage.instanceDirectory : appRecord.dataDirectory, stagedPaths);
|
|
56
|
+
if (!await this.params.registryService.removeApp(appId)) throw new Error(`卸载过程中应用记录已发生变化:${appId}`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
await this.restoreOrThrow(appId, stagedPaths, error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
await Promise.all(stagedPaths.map(async (entry) => await this.params.integrityService.removeDirectory(entry.stagedPath)));
|
|
62
|
+
return {
|
|
63
|
+
appId,
|
|
64
|
+
removedVersions,
|
|
65
|
+
dataRemoved: purgeData
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
toUpdateResult = (appRecord, versionRecord, version, updated) => ({
|
|
69
|
+
appId: appRecord.appId,
|
|
70
|
+
name: appRecord.name,
|
|
71
|
+
version,
|
|
72
|
+
previousVersion: appRecord.activeVersion,
|
|
73
|
+
installDirectory: versionRecord.installDirectory,
|
|
74
|
+
dataDirectory: appRecord.dataDirectory,
|
|
75
|
+
instance: appRecord.defaultInstance,
|
|
76
|
+
sourceKind: versionRecord.sourceKind,
|
|
77
|
+
distributionMode: versionRecord.distributionMode,
|
|
78
|
+
sourceRef: versionRecord.sourceRef,
|
|
79
|
+
permissions: versionRecord.permissions,
|
|
80
|
+
registryUrl: versionRecord.registryUrl,
|
|
81
|
+
bundleUrl: versionRecord.bundleUrl,
|
|
82
|
+
sha256: versionRecord.sha256,
|
|
83
|
+
publisher: versionRecord.publisher,
|
|
84
|
+
enabled: appRecord.enabled,
|
|
85
|
+
manifestSchemaVersion: versionRecord.manifestSchemaVersion,
|
|
86
|
+
components: versionRecord.components,
|
|
87
|
+
primaryPanelId: versionRecord.primaryPanelId,
|
|
88
|
+
updated
|
|
89
|
+
});
|
|
90
|
+
stagePath = async (originalPath, stagedPaths) => {
|
|
91
|
+
if (!await this.params.integrityService.pathExists(originalPath)) return;
|
|
92
|
+
const stagedPath = `${originalPath}.uninstalling-${randomUUID()}`;
|
|
93
|
+
await rename(originalPath, stagedPath);
|
|
94
|
+
stagedPaths.push({
|
|
95
|
+
originalPath,
|
|
96
|
+
stagedPath
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
restoreOrThrow = async (appId, stagedPaths, operationError) => {
|
|
100
|
+
try {
|
|
101
|
+
for (const entry of [...stagedPaths].reverse()) if (await this.params.integrityService.pathExists(entry.stagedPath)) await rename(entry.stagedPath, entry.originalPath);
|
|
102
|
+
} catch (restoreError) {
|
|
103
|
+
throw new AggregateError([operationError, restoreError], `卸载 ${appId} 失败,且无法完全恢复已暂存文件。`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { AppInstallationLifecycleService };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=app-installation-lifecycle.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-lifecycle.service.js","names":[],"sources":["../../src/services/app-installation-lifecycle.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { rename } from \"node:fs/promises\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport type { AppRegistryConfigService } from \"#app-runtime/services/app-registry-config.service.js\";\nimport type { AppRegistryService } from \"#app-runtime/services/app-registry.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport type {\n AppInstallProgressHandler,\n AppInstallResult,\n AppRollbackResult,\n AppUninstallResult,\n AppUpdateResult,\n} from \"#app-runtime/types/app-installation.types.js\";\n\ntype AppUpdateOptions = {\n version?: string;\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n};\n\nexport class AppInstallationLifecycleService {\n constructor(private readonly params: {\n registryService: AppRegistryService;\n registryConfigService: AppRegistryConfigService;\n remoteRegistryClient: AppRemoteRegistryClientService;\n instanceStorageService: AppInstanceStorageService;\n integrityService: AppInstallationIntegrityService;\n install: (source: string, options: AppUpdateOptions) => Promise<AppInstallResult>;\n rollback: (appId: string, version: string) => Promise<AppRollbackResult>;\n }) {}\n\n update = async (appId: string, options: AppUpdateOptions = {}): Promise<AppUpdateResult> => {\n const { onProgress, registryUrl: requestedRegistryUrl, version } = options;\n await onProgress?.(\"resolving\");\n const appRecord = await this.params.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const activeVersionRecord = appRecord.installedVersions[appRecord.activeVersion];\n const registryUrl = requestedRegistryUrl ??\n activeVersionRecord?.registryUrl ??\n (await this.params.registryConfigService.getSnapshot()).currentUrl;\n const resolution = await this.params.remoteRegistryClient.resolve({\n appId,\n version,\n registryUrl,\n });\n if (resolution.version === appRecord.activeVersion) {\n if (!activeVersionRecord) {\n throw new Error(`已安装应用缺少激活版本:${appId}`);\n }\n return this.toUpdateResult(appRecord, activeVersionRecord, appRecord.activeVersion, false);\n }\n const installedTarget = appRecord.installedVersions[resolution.version];\n if (installedTarget) {\n await onProgress?.(\"verifying\");\n await onProgress?.(\"installing\");\n if (options.activate !== false) {\n await this.params.rollback(appId, resolution.version);\n }\n await onProgress?.(\"finalizing\");\n return this.toUpdateResult(appRecord, installedTarget, resolution.version, true);\n }\n const installResult = await this.params.install(`${appId}@${resolution.version}`, {\n registryUrl,\n onProgress,\n activate: options.activate,\n });\n return {\n ...installResult,\n previousVersion: appRecord.activeVersion,\n updated: true,\n };\n };\n\n uninstall = async (appId: string, purgeData: boolean): Promise<AppUninstallResult> => {\n const appRecord = await this.params.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n if (!purgeData) {\n await this.params.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: appRecord.publisher?.id,\n legacyDataDirectory: appRecord.defaultInstance.storage.layout === \"legacy\"\n ? appRecord.dataDirectory\n : undefined,\n dataSchemaVersion: appRecord.defaultInstance.dataSchemaVersion,\n });\n }\n const removedVersions = Object.keys(appRecord.installedVersions).sort((left, right) =>\n left.localeCompare(right));\n const stagedPaths: Array<{ originalPath: string; stagedPath: string }> = [];\n try {\n for (const versionRecord of Object.values(appRecord.installedVersions)) {\n await this.stagePath(versionRecord.installDirectory, stagedPaths);\n }\n if (purgeData) {\n await this.stagePath(\n appRecord.defaultInstance.storage.layout === \"instance-v1\"\n ? appRecord.defaultInstance.storage.instanceDirectory\n : appRecord.dataDirectory,\n stagedPaths,\n );\n }\n if (!await this.params.registryService.removeApp(appId)) {\n throw new Error(`卸载过程中应用记录已发生变化:${appId}`);\n }\n } catch (error) {\n await this.restoreOrThrow(appId, stagedPaths, error);\n throw error;\n }\n await Promise.all(stagedPaths.map(async (entry) =>\n await this.params.integrityService.removeDirectory(entry.stagedPath)));\n return { appId, removedVersions, dataRemoved: purgeData };\n };\n\n private toUpdateResult = (\n appRecord: NonNullable<Awaited<ReturnType<AppRegistryService[\"getApp\"]>>>,\n versionRecord: NonNullable<Awaited<ReturnType<AppRegistryService[\"getApp\"]>>>[\"installedVersions\"][string],\n version: string,\n updated: boolean,\n ): AppUpdateResult => ({\n appId: appRecord.appId,\n name: appRecord.name,\n version,\n previousVersion: appRecord.activeVersion,\n installDirectory: versionRecord.installDirectory,\n dataDirectory: appRecord.dataDirectory,\n instance: appRecord.defaultInstance,\n sourceKind: versionRecord.sourceKind,\n distributionMode: versionRecord.distributionMode,\n sourceRef: versionRecord.sourceRef,\n permissions: versionRecord.permissions,\n registryUrl: versionRecord.registryUrl,\n bundleUrl: versionRecord.bundleUrl,\n sha256: versionRecord.sha256,\n publisher: versionRecord.publisher,\n enabled: appRecord.enabled,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion,\n components: versionRecord.components,\n primaryPanelId: versionRecord.primaryPanelId,\n updated,\n });\n\n private stagePath = async (\n originalPath: string,\n stagedPaths: Array<{ originalPath: string; stagedPath: string }>,\n ): Promise<void> => {\n if (!await this.params.integrityService.pathExists(originalPath)) {\n return;\n }\n const stagedPath = `${originalPath}.uninstalling-${randomUUID()}`;\n await rename(originalPath, stagedPath);\n stagedPaths.push({ originalPath, stagedPath });\n };\n\n private restoreOrThrow = async (\n appId: string,\n stagedPaths: Array<{ originalPath: string; stagedPath: string }>,\n operationError: unknown,\n ): Promise<void> => {\n try {\n for (const entry of [...stagedPaths].reverse()) {\n if (await this.params.integrityService.pathExists(entry.stagedPath)) {\n await rename(entry.stagedPath, entry.originalPath);\n }\n }\n } catch (restoreError) {\n throw new AggregateError(\n [operationError, restoreError],\n `卸载 ${appId} 失败,且无法完全恢复已暂存文件。`,\n );\n }\n };\n}\n"],"mappings":";;;AAsBA,IAAa,kCAAb,MAA6C;CAC3C,YAAY,QAQT;AAR0B,OAAA,SAAA;;CAU7B,SAAS,OAAO,OAAe,UAA4B,EAAE,KAA+B;EAC1F,MAAM,EAAE,YAAY,aAAa,sBAAsB,YAAY;AACnE,QAAM,aAAa,YAAY;EAC/B,MAAM,YAAY,MAAM,KAAK,OAAO,gBAAgB,OAAO,MAAM;AACjE,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;EAEtC,MAAM,sBAAsB,UAAU,kBAAkB,UAAU;EAClE,MAAM,cAAc,wBAClB,qBAAqB,gBACpB,MAAM,KAAK,OAAO,sBAAsB,aAAa,EAAE;EAC1D,MAAM,aAAa,MAAM,KAAK,OAAO,qBAAqB,QAAQ;GAChE;GACA;GACA;GACD,CAAC;AACF,MAAI,WAAW,YAAY,UAAU,eAAe;AAClD,OAAI,CAAC,oBACH,OAAM,IAAI,MAAM,eAAe,QAAQ;AAEzC,UAAO,KAAK,eAAe,WAAW,qBAAqB,UAAU,eAAe,MAAM;;EAE5F,MAAM,kBAAkB,UAAU,kBAAkB,WAAW;AAC/D,MAAI,iBAAiB;AACnB,SAAM,aAAa,YAAY;AAC/B,SAAM,aAAa,aAAa;AAChC,OAAI,QAAQ,aAAa,MACvB,OAAM,KAAK,OAAO,SAAS,OAAO,WAAW,QAAQ;AAEvD,SAAM,aAAa,aAAa;AAChC,UAAO,KAAK,eAAe,WAAW,iBAAiB,WAAW,SAAS,KAAK;;AAOlF,SAAO;GACL,GANoB,MAAM,KAAK,OAAO,QAAQ,GAAG,MAAM,GAAG,WAAW,WAAW;IAChF;IACA;IACA,UAAU,QAAQ;IACnB,CAAC;GAGA,iBAAiB,UAAU;GAC3B,SAAS;GACV;;CAGH,YAAY,OAAO,OAAe,cAAoD;EACpF,MAAM,YAAY,MAAM,KAAK,OAAO,gBAAgB,OAAO,MAAM;AACjE,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;AAEtC,MAAI,CAAC,UACH,OAAM,KAAK,OAAO,uBAAuB,2BAA2B;GAClE;GACA,aAAa,UAAU,WAAW;GAClC,qBAAqB,UAAU,gBAAgB,QAAQ,WAAW,WAC9D,UAAU,gBACV,KAAA;GACJ,mBAAmB,UAAU,gBAAgB;GAC9C,CAAC;EAEJ,MAAM,kBAAkB,OAAO,KAAK,UAAU,kBAAkB,CAAC,MAAM,MAAM,UAC3E,KAAK,cAAc,MAAM,CAAC;EAC5B,MAAM,cAAmE,EAAE;AAC3E,MAAI;AACF,QAAK,MAAM,iBAAiB,OAAO,OAAO,UAAU,kBAAkB,CACpE,OAAM,KAAK,UAAU,cAAc,kBAAkB,YAAY;AAEnE,OAAI,UACF,OAAM,KAAK,UACT,UAAU,gBAAgB,QAAQ,WAAW,gBACzC,UAAU,gBAAgB,QAAQ,oBAClC,UAAU,eACd,YACD;AAEH,OAAI,CAAC,MAAM,KAAK,OAAO,gBAAgB,UAAU,MAAM,CACrD,OAAM,IAAI,MAAM,kBAAkB,QAAQ;WAErC,OAAO;AACd,SAAM,KAAK,eAAe,OAAO,aAAa,MAAM;AACpD,SAAM;;AAER,QAAM,QAAQ,IAAI,YAAY,IAAI,OAAO,UACvC,MAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM,WAAW,CAAC,CAAC;AACxE,SAAO;GAAE;GAAO;GAAiB,aAAa;GAAW;;CAG3D,kBACE,WACA,eACA,SACA,aACqB;EACrB,OAAO,UAAU;EACjB,MAAM,UAAU;EAChB;EACA,iBAAiB,UAAU;EAC3B,kBAAkB,cAAc;EAChC,eAAe,UAAU;EACzB,UAAU,UAAU;EACpB,YAAY,cAAc;EAC1B,kBAAkB,cAAc;EAChC,WAAW,cAAc;EACzB,aAAa,cAAc;EAC3B,aAAa,cAAc;EAC3B,WAAW,cAAc;EACzB,QAAQ,cAAc;EACtB,WAAW,cAAc;EACzB,SAAS,UAAU;EACnB,uBAAuB,cAAc;EACrC,YAAY,cAAc;EAC1B,gBAAgB,cAAc;EAC9B;EACD;CAED,YAAoB,OAClB,cACA,gBACkB;AAClB,MAAI,CAAC,MAAM,KAAK,OAAO,iBAAiB,WAAW,aAAa,CAC9D;EAEF,MAAM,aAAa,GAAG,aAAa,gBAAgB,YAAY;AAC/D,QAAM,OAAO,cAAc,WAAW;AACtC,cAAY,KAAK;GAAE;GAAc;GAAY,CAAC;;CAGhD,iBAAyB,OACvB,OACA,aACA,mBACkB;AAClB,MAAI;AACF,QAAK,MAAM,SAAS,CAAC,GAAG,YAAY,CAAC,SAAS,CAC5C,KAAI,MAAM,KAAK,OAAO,iBAAiB,WAAW,MAAM,WAAW,CACjE,OAAM,OAAO,MAAM,YAAY,MAAM,aAAa;WAG/C,cAAc;AACrB,SAAM,IAAI,eACR,CAAC,gBAAgB,aAAa,EAC9B,MAAM,MAAM,mBACb"}
|