@nextclaw/app-runtime 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
- - 用户数据目录:`~/.nextclaw/apps/data/<app-id>/`
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
@@ -18,7 +18,7 @@ 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, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.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";
22
22
  import { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
23
23
  import { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion } from "./types/app-registry.types.js";
24
24
  import { AppRegistryService } from "./services/app-registry.service.js";
@@ -51,6 +51,7 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
51
51
  import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./services/wasmtime-wasi-http-component.service.js";
52
52
  import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/app-host.service.js";
53
53
  import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
54
+ import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
54
55
  import { FileLockService } from "./services/file-lock.service.js";
55
56
  import { UiServerService } from "./services/ui-server.service.js";
56
- 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, 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 };
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
@@ -48,4 +48,5 @@ import { AppPublishValidationService } from "./services/app-publish-validation.s
48
48
  import { ValidatePublishCommand } from "./controllers/validate-publish.controller.js";
49
49
  import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
50
50
  import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
51
- export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, 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 };
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
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.11.0";
2
+ var version = "0.12.1";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -45,7 +45,9 @@ declare class AppInstallationService {
45
45
  uninstall: (appId: string, purgeData: boolean) => Promise<AppUninstallResult>;
46
46
  list: () => Promise<InstalledAppListItem[]>;
47
47
  reconcileFilesystem: () => Promise<void>;
48
- info: (appId: string) => Promise<AppInfoResult>;
48
+ info: (appId: string, options?: {
49
+ measureStorageUsage?: boolean;
50
+ }) => Promise<AppInfoResult>;
49
51
  setEnabled: (appId: string, enabled: boolean) => Promise<AppActivationResult>;
50
52
  rollback: (appId: string, version: string) => Promise<AppRollbackResult>;
51
53
  assertVersionIntegrity: (appId: string, version?: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;KAsCK,iBAAA;EACH,WAAA;EACA,UAAA,GAAa,yBAAA;EACb,QAAA;EACA,gBAAA,GAAmB,YAAA;AAAA;AAAA,cAWR,sBAAA;EAAA,iBAUQ,cAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,YAAA;EAAA,iBACA,eAAA;EAAA,iBACA,qBAAA;EAAA,iBAGA,oBAAA;EAAA,iBAjBF,oBAAA;EAAA,iBACA,sBAAA;EAAA,iBACA,eAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,mBAAA;cAGE,cAAA,GAAgB,cAAA,EAChB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,kBAAA,EACjB,YAAA,GAAc,eAAA,EACd,eAAA,GAAiB,kBAAA,EACjB,qBAAA,GAAuB,wBAAA,EAGvB,oBAAA,GAAsB,8BAAA,EAGvC,sBAAA,GAAyB,yBAAA;EAyB3B,OAAA,GACE,SAAA,UACA,OAAA,GAAS,iBAAA,KACR,OAAA,CAAQ,gBAAA;EAAA,QAkCH,qBAAA;EAAA,QAUA,qBAAA;EAmIR,MAAA,GACE,KAAA,UACA,OAAA;IACE,OAAA;IACA,WAAA;IACA,UAAA,GAAa,yBAAA;IACb,QAAA;EAAA,MAED,OAAA,CAAQ,eAAA;EAOX,SAAA,GACE,KAAA,UACA,SAAA,cACC,OAAA,CAAQ,kBAAA;EAOX,IAAA,QAAiB,OAAA,CAAQ,oBAAA;EAkBzB,mBAAA,QAAgC,OAAA;EA0GhC,IAAA,GAAc,KAAA,aAAgB,OAAA,CAAQ,aAAA;EAyCtC,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,mBAAA;EAW9D,QAAA,GAAkB,KAAA,UAAe,OAAA,aAAkB,OAAA,CAAQ,iBAAA;EAoC3D,sBAAA,GAAgC,KAAA,UAAe,OAAA,cAAmB,OAAA;EAqBlE,gBAAA,MAA6B,KAAA,UAAe,SAAA,QAAiB,OAAA,CAAQ,CAAA,MAAK,OAAA,CAAQ,CAAA;EAgBlF,aAAA,GACE,YAAA,UACA,wBAAA,EAA0B,mBAAA,KACzB,OAAA,CAAQ,mBAAA;EA6BX,aAAA,GACE,KAAA,sBACA,gBAAA,EAAkB,mBAAA,KACjB,OAAA;AAAA"}
1
+ {"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;KAsCK,iBAAA;EACH,WAAA;EACA,UAAA,GAAa,yBAAA;EACb,QAAA;EACA,gBAAA,GAAmB,YAAA;AAAA;AAAA,cAWR,sBAAA;EAAA,iBAUQ,cAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,YAAA;EAAA,iBACA,eAAA;EAAA,iBACA,qBAAA;EAAA,iBAGA,oBAAA;EAAA,iBAjBF,oBAAA;EAAA,iBACA,sBAAA;EAAA,iBACA,eAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,mBAAA;cAGE,cAAA,GAAgB,cAAA,EAChB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,kBAAA,EACjB,YAAA,GAAc,eAAA,EACd,eAAA,GAAiB,kBAAA,EACjB,qBAAA,GAAuB,wBAAA,EAGvB,oBAAA,GAAsB,8BAAA,EAGvC,sBAAA,GAAyB,yBAAA;EAyB3B,OAAA,GACE,SAAA,UACA,OAAA,GAAS,iBAAA,KACR,OAAA,CAAQ,gBAAA;EAAA,QAkCH,qBAAA;EAAA,QAUA,qBAAA;EAmIR,MAAA,GACE,KAAA,UACA,OAAA;IACE,OAAA;IACA,WAAA;IACA,UAAA,GAAa,yBAAA;IACb,QAAA;EAAA,MAED,OAAA,CAAQ,eAAA;EAOX,SAAA,GACE,KAAA,UACA,SAAA,cACC,OAAA,CAAQ,kBAAA;EAOX,IAAA,QAAiB,OAAA,CAAQ,oBAAA;EAkBzB,mBAAA,QAAgC,OAAA;EA0GhC,IAAA,GACE,KAAA,UACA,OAAA;IAAW,mBAAA;EAAA,MACV,OAAA,CAAQ,aAAA;EA0CX,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,mBAAA;EAW9D,QAAA,GAAkB,KAAA,UAAe,OAAA,aAAkB,OAAA,CAAQ,iBAAA;EAoC3D,sBAAA,GAAgC,KAAA,UAAe,OAAA,cAAmB,OAAA;EAqBlE,gBAAA,MAA6B,KAAA,UAAe,SAAA,QAAiB,OAAA,CAAQ,CAAA,MAAK,OAAA,CAAQ,CAAA;EAgBlF,aAAA,GACE,YAAA,UACA,wBAAA,EAA0B,mBAAA,KACzB,OAAA,CAAQ,mBAAA;EA6BX,aAAA,GACE,KAAA,sBACA,gBAAA,EAAkB,mBAAA,KACjB,OAAA;AAAA"}
@@ -264,10 +264,11 @@ var AppInstallationService = class {
264
264
  });
265
265
  }
266
266
  };
267
- info = async (appId) => {
267
+ info = async (appId, options = {}) => {
268
268
  const appRecord = await this.registryService.getApp(appId);
269
269
  if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
270
270
  const installedVersions = Object.values(appRecord.installedVersions).sort((left, right) => left.version.localeCompare(right.version));
271
+ const storageUsage = options.measureStorageUsage === false ? void 0 : await this.instanceStorageService.measureUsage(appRecord.defaultInstance.storage);
271
272
  return {
272
273
  appId: appRecord.appId,
273
274
  name: appRecord.name,
@@ -277,7 +278,7 @@ var AppInstallationService = class {
277
278
  dataDirectory: appRecord.dataDirectory,
278
279
  instance: appRecord.defaultInstance,
279
280
  storage: appRecord.defaultInstance.storage,
280
- storageUsage: await this.instanceStorageService.measureUsage(appRecord.defaultInstance.storage),
281
+ storageUsage,
281
282
  installedVersions: installedVersions.map((versionRecord) => ({
282
283
  version: versionRecord.version,
283
284
  installDirectory: versionRecord.installDirectory,
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.service.js","names":[],"sources":["../../src/services/app-installation.service.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { isAppComponentManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport { AppInstallationFilesystemService } from \"#app-runtime/services/app-installation-filesystem.service.js\";\nimport { AppInstallationLifecycleService } from \"#app-runtime/services/app-installation-lifecycle.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppBundleExtractResult } from \"#app-runtime/types/app-bundle.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\nimport { AppRegistryConfigService } from \"#app-runtime/services/app-registry-config.service.js\";\nimport { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport { AppRegistryService } from \"#app-runtime/services/app-registry.service.js\";\nimport {\n AppInstallSourceService,\n type ResolvedAppInstallSource,\n} from \"#app-runtime/services/app-install-source.service.js\";\nimport type {\n AppInfoResult,\n AppInstallProgressHandler,\n AppActivationResult,\n AppInstallResult,\n AppLaunchResolution,\n AppUpdateResult,\n AppRollbackResult,\n AppUninstallResult,\n InstalledAppListItem,\n} from \"#app-runtime/types/app-installation.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\ntype AppInstallOptions = {\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n trustedPublisher?: AppPublisher;\n};\n\ntype PreparedAppInstall = {\n source: ResolvedAppInstallSource;\n extractedDirectory: string;\n extractedMetadata: AppBundleExtractResult;\n manifestBundle: AppManifestBundle;\n options: AppInstallOptions;\n};\n\nexport class AppInstallationService {\n private readonly installSourceService: AppInstallSourceService;\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n private readonly integrityService: AppInstallationIntegrityService;\n private readonly filesystemService: AppInstallationFilesystemService;\n private readonly lifecycleService: AppInstallationLifecycleService;\n private readonly appOperationContext = new AsyncLocalStorage<ReadonlySet<string>>();\n\n constructor(\n private readonly appHomeService: AppHomeService = new AppHomeService(),\n private readonly bundleService: AppBundleService = new AppBundleService(),\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly buildService: AppBuildService = new AppBuildService(),\n private readonly registryService: AppRegistryService = new AppRegistryService(appHomeService),\n private readonly registryConfigService: AppRegistryConfigService = new AppRegistryConfigService(\n appHomeService,\n ),\n private readonly remoteRegistryClient: AppRemoteRegistryClientService = new AppRemoteRegistryClientService(\n new AppRegistryConfigService(appHomeService),\n ),\n instanceStorageService?: AppInstanceStorageService,\n ) {\n this.installSourceService = new AppInstallSourceService(\n this.manifestService,\n this.remoteRegistryClient,\n this.bundleService,\n );\n this.instanceStorageService = instanceStorageService ?? new AppInstanceStorageService(appHomeService);\n this.integrityService = new AppInstallationIntegrityService(manifestService);\n this.filesystemService = new AppInstallationFilesystemService({\n buildService,\n integrityService: this.integrityService,\n manifestService,\n });\n this.lifecycleService = new AppInstallationLifecycleService({\n registryService,\n registryConfigService,\n remoteRegistryClient,\n instanceStorageService: this.instanceStorageService,\n integrityService: this.integrityService,\n install: this.install,\n rollback: this.rollback,\n });\n }\n\n install = async (\n appSource: string,\n options: AppInstallOptions = {},\n ): Promise<AppInstallResult> => {\n const { onProgress, registryUrl } = options;\n await onProgress?.(\"resolving\");\n const source = await this.installSourceService.resolve(appSource, registryUrl);\n const tempDirectory = await this.appHomeService.createTemporaryDirectory(\"napp-install-\");\n try {\n const bundlePath = await this.installSourceService.materializeBundle(\n source,\n tempDirectory,\n onProgress,\n );\n await onProgress?.(\"verifying\");\n const extractedDirectory = path.join(tempDirectory, \"bundle\");\n const extractedMetadata = await this.bundleService.extractBundle({\n bundlePath,\n targetDirectory: extractedDirectory,\n });\n await this.filesystemService.materializeDistribution({\n appDirectory: extractedDirectory,\n distributionMode: extractedMetadata.metadata.distributionMode,\n });\n const manifestBundle = await this.manifestService.load(extractedDirectory);\n return await this.installPreparedBundle({\n source,\n extractedDirectory,\n extractedMetadata,\n manifestBundle,\n options,\n });\n } finally {\n await rm(tempDirectory, { recursive: true, force: true });\n }\n };\n\n private installPreparedBundle = async (\n prepared: PreparedAppInstall,\n ): Promise<AppInstallResult> => {\n const appId = prepared.manifestBundle.manifest.id;\n return await this.withAppOperation(\n appId,\n async () => await this.commitPreparedInstall(prepared),\n );\n };\n\n private commitPreparedInstall = async (\n prepared: PreparedAppInstall,\n ): Promise<AppInstallResult> => {\n const { extractedDirectory, extractedMetadata, manifestBundle, options, source } = prepared;\n const { onProgress } = options;\n const { id: appId, version } = manifestBundle.manifest;\n const installDirectory = this.appHomeService.getInstallDirectory(appId, version);\n if (source.kind === \"registry\" && appId !== source.registryResolution.appId) {\n throw new Error(\n `bundle manifest.appId 与 registry 请求不一致:期望 ${source.registryResolution.appId},实际 ${appId}`,\n );\n }\n await onProgress?.(\"installing\");\n const existingRecord = await this.registryService.getApp(appId);\n const publisher = source.kind === \"registry\"\n ? source.registryResolution.publisher\n : options.trustedPublisher;\n const currentPublisher = existingRecord?.publisher ??\n existingRecord?.installedVersions[existingRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== publisher?.id) {\n throw new Error(\n `应用 ${appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const dataSchemaVersion = manifestBundle.manifest.schemaVersion === 2\n ? manifestBundle.manifest.storage?.schemaVersion ?? 1\n : 1;\n if (existingRecord?.defaultInstance.dataSchemaVersion !== undefined &&\n existingRecord.defaultInstance.dataSchemaVersion !== dataSchemaVersion) {\n throw new Error(\n `应用 ${appId} 数据 schema 从 ${existingRecord.defaultInstance.dataSchemaVersion} 升级到 ${dataSchemaVersion},但包内没有受支持的迁移合同。更新已停止,现有数据和版本保持不变。`,\n );\n }\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, \"default\");\n const instanceExisted = await this.integrityService.pathExists(instanceDirectory);\n const contentSha256 = await this.filesystemService.copyToImmutableInstallDirectory({\n appId,\n appVersion: version,\n extractedDirectory,\n installDirectory,\n });\n const defaultInstance = await this.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: (currentPublisher ?? publisher)?.id,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n dataSchemaVersion,\n });\n await onProgress?.(\"finalizing\");\n let registryRecord;\n try {\n registryRecord = await this.registryService.upsertInstallation({\n appId,\n name: manifestBundle.manifest.name,\n description: manifestBundle.manifest.description,\n version,\n installDirectory,\n defaultInstance,\n sourceKind: source.kind,\n distributionMode: source.kind === \"registry\"\n ? source.registryResolution.distributionMode\n : extractedMetadata.metadata.distributionMode,\n sourceRef: source.sourceRef,\n installedAt: new Date().toISOString(),\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n registryUrl: source.kind === \"registry\"\n ? source.registryResolution.registryUrl\n : undefined,\n bundleUrl: source.kind === \"registry\" ? source.registryResolution.bundleUrl : undefined,\n sha256: source.kind === \"registry\" ? source.registryResolution.sha256 : undefined,\n publisher,\n manifestSchemaVersion: manifestBundle.manifest.schemaVersion,\n components: isAppComponentManifestBundle(manifestBundle)\n ? manifestBundle.components.map((component) => ({\n ...component,\n componentDirectory: path.join(installDirectory, component.path),\n manifestPath: path.join(\n installDirectory,\n component.path,\n component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n ),\n }))\n : undefined,\n primaryPanelId: isAppComponentManifestBundle(manifestBundle)\n ? manifestBundle.primaryPanelId\n : undefined,\n security: isAppComponentManifestBundle(manifestBundle)\n ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest)\n : undefined,\n dataSchemaVersion,\n contentSha256,\n activate: options.activate,\n });\n } catch (error) {\n await this.integrityService.removeDirectory(installDirectory);\n if (!instanceExisted) {\n await this.instanceStorageService.rollbackNewInstance({\n instance: defaultInstance,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n });\n }\n throw error;\n }\n const installedVersion = registryRecord.installedVersions[version];\n return {\n appId: registryRecord.appId,\n name: registryRecord.name,\n version,\n installDirectory,\n dataDirectory: defaultInstance.storage.dataDirectory,\n instance: registryRecord.defaultInstance,\n sourceKind: source.kind,\n sourceRef: source.sourceRef,\n distributionMode: installedVersion?.distributionMode,\n permissions: installedVersion?.permissions ?? {},\n registryUrl: installedVersion?.registryUrl,\n bundleUrl: installedVersion?.bundleUrl,\n sha256: installedVersion?.sha256,\n publisher: installedVersion?.publisher,\n enabled: registryRecord.enabled,\n manifestSchemaVersion: installedVersion?.manifestSchemaVersion ?? 1,\n components: installedVersion?.components,\n primaryPanelId: installedVersion?.primaryPanelId,\n };\n };\n\n update = async (\n appId: string,\n options?: {\n version?: string;\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n },\n ): Promise<AppUpdateResult> => {\n return await this.withAppOperation(\n appId,\n async () => await this.lifecycleService.update(appId, options),\n );\n };\n\n uninstall = async (\n appId: string,\n purgeData: boolean,\n ): Promise<AppUninstallResult> => {\n return await this.withAppOperation(\n appId,\n async () => await this.lifecycleService.uninstall(appId, purgeData),\n );\n };\n\n list = async (): Promise<InstalledAppListItem[]> => {\n const appRecords = await this.registryService.listApps();\n return appRecords.map((appRecord) => ({\n appId: appRecord.appId,\n name: appRecord.name,\n activeVersion: appRecord.activeVersion,\n sourceKind:\n appRecord.installedVersions[appRecord.activeVersion]?.sourceKind ?? \"directory\",\n distributionMode:\n appRecord.installedVersions[appRecord.activeVersion]?.distributionMode,\n enabled: appRecord.enabled,\n manifestSchemaVersion:\n appRecord.installedVersions[appRecord.activeVersion]?.manifestSchemaVersion ?? 1,\n primaryPanelId:\n appRecord.installedVersions[appRecord.activeVersion]?.primaryPanelId,\n }));\n };\n\n reconcileFilesystem = async (): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n let appRecords = await this.registryService.listApps();\n for (const appRecord of appRecords) {\n await this.withAppOperation(appRecord.appId, async () => {\n for (const versionRecord of Object.values(appRecord.installedVersions)) {\n if (!await this.integrityService.pathExists(versionRecord.installDirectory)) {\n continue;\n }\n if (!versionRecord.contentSha256) {\n const contentSha256 = await this.integrityService.calculateDigest(\n versionRecord.installDirectory,\n );\n await this.registryService.setVersionContentDigest(\n appRecord.appId,\n versionRecord.version,\n contentSha256,\n );\n }\n await this.integrityService.protectDirectory(versionRecord.installDirectory);\n }\n });\n }\n appRecords = await this.registryService.listApps();\n const referencedInstallPaths = new Set(appRecords.flatMap((record) =>\n Object.values(record.installedVersions).map((version) => path.resolve(version.installDirectory))));\n const referencedDataPaths = new Set(appRecords.map((record) => path.resolve(\n record.defaultInstance.storage.layout === \"instance-v1\"\n ? record.defaultInstance.storage.instanceDirectory\n : record.dataDirectory,\n )));\n\n const packageDirectories = await this.filesystemService.readDirectories(\n this.appHomeService.getPackagesDirectory(),\n );\n for (const appDirectory of packageDirectories) {\n await this.filesystemService.reconcileGeneratedSiblings(appDirectory, referencedInstallPaths);\n }\n await this.filesystemService.reconcileGeneratedSiblings(\n this.appHomeService.getDataDirectory(),\n referencedDataPaths,\n );\n for (const appRecord of appRecords) {\n if (appRecord.defaultInstance.storage.layout !== \"instance-v1\") {\n continue;\n }\n await this.filesystemService.reconcileGeneratedSiblings(\n appRecord.defaultInstance.storage.instanceDirectory,\n new Set([path.resolve(appRecord.defaultInstance.storage.dataDirectory)]),\n );\n }\n for (const appRecord of appRecords) {\n if (appRecord.defaultInstance.storage.layout !== \"legacy\") {\n continue;\n }\n await this.withAppOperation(appRecord.appId, async () => {\n const currentRecord = await this.registryService.getApp(appRecord.appId);\n if (!currentRecord || currentRecord.defaultInstance.storage.layout !== \"legacy\") {\n return;\n }\n const activeVersion = currentRecord.installedVersions[currentRecord.activeVersion];\n if (!activeVersion) {\n throw new Error(`已安装应用缺少激活版本:${currentRecord.appId}`);\n }\n const instance = await this.instanceStorageService.materializeDefaultInstance({\n appId: currentRecord.appId,\n publisherId: currentRecord.publisher?.id,\n legacyDataDirectory: currentRecord.dataDirectory,\n dataSchemaVersion: currentRecord.defaultInstance.dataSchemaVersion,\n });\n try {\n await this.registryService.upsertInstallation({\n appId: currentRecord.appId,\n name: currentRecord.name,\n description: currentRecord.description,\n version: currentRecord.activeVersion,\n installDirectory: activeVersion.installDirectory,\n defaultInstance: instance,\n sourceKind: activeVersion.sourceKind,\n sourceRef: activeVersion.sourceRef,\n installedAt: activeVersion.installedAt,\n distributionMode: activeVersion.distributionMode,\n permissions: activeVersion.permissions,\n registryUrl: activeVersion.registryUrl,\n bundleUrl: activeVersion.bundleUrl,\n sha256: activeVersion.sha256,\n publisher: currentRecord.publisher ?? activeVersion.publisher,\n manifestSchemaVersion: activeVersion.manifestSchemaVersion,\n components: activeVersion.components,\n primaryPanelId: activeVersion.primaryPanelId,\n security: activeVersion.security,\n dataSchemaVersion: activeVersion.dataSchemaVersion,\n contentSha256: activeVersion.contentSha256,\n enabled: currentRecord.enabled,\n });\n } catch (error) {\n await this.instanceStorageService.rollbackNewInstance({\n instance,\n legacyDataDirectory: currentRecord.dataDirectory,\n });\n throw error;\n }\n });\n }\n };\n\n info = async (appId: string): Promise<AppInfoResult> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const installedVersions = Object.values(appRecord.installedVersions).sort((left, right) =>\n left.version.localeCompare(right.version),\n );\n return {\n appId: appRecord.appId,\n name: appRecord.name,\n description: appRecord.description,\n activeVersion: appRecord.activeVersion,\n enabled: appRecord.enabled,\n dataDirectory: appRecord.dataDirectory,\n instance: appRecord.defaultInstance,\n storage: appRecord.defaultInstance.storage,\n storageUsage: await this.instanceStorageService.measureUsage(\n appRecord.defaultInstance.storage,\n ),\n installedVersions: installedVersions.map((versionRecord) => ({\n version: versionRecord.version,\n installDirectory: versionRecord.installDirectory,\n sourceKind: versionRecord.sourceKind,\n distributionMode: versionRecord.distributionMode,\n sourceRef: versionRecord.sourceRef,\n installedAt: versionRecord.installedAt,\n permissions: versionRecord.permissions,\n registryUrl: versionRecord.registryUrl,\n bundleUrl: versionRecord.bundleUrl,\n sha256: versionRecord.sha256,\n publisher: versionRecord.publisher,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion,\n components: versionRecord.components,\n primaryPanelId: versionRecord.primaryPanelId,\n contentSha256: versionRecord.contentSha256,\n })),\n grants: appRecord.grants,\n };\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppActivationResult> => {\n return await this.withAppOperation(appId, async () => {\n const appRecord = await this.registryService.setEnabled(appId, enabled);\n return {\n appId: appRecord.appId,\n activeVersion: appRecord.activeVersion,\n enabled: appRecord.enabled,\n };\n });\n };\n\n rollback = async (appId: string, version: string): Promise<AppRollbackResult> => {\n return await this.withAppOperation(appId, async () => {\n const currentRecord = await this.registryService.getApp(appId);\n if (!currentRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n if (currentRecord.activeVersion === version) {\n return {\n appId,\n activeVersion: version,\n previousVersion: version,\n enabled: currentRecord.enabled,\n rolledBack: false,\n };\n }\n const targetVersion = currentRecord.installedVersions[version];\n if (!targetVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (targetVersion.dataSchemaVersion !== currentRecord.defaultInstance.dataSchemaVersion) {\n throw new Error(\n `应用 ${appId}@${version} 需要数据 schema ${targetVersion.dataSchemaVersion},当前实例为 ${currentRecord.defaultInstance.dataSchemaVersion}。没有可用 checkpoint,已阻止回滚。`,\n );\n }\n await this.assertVersionIntegrity(appId, version);\n const appRecord = await this.registryService.activateVersion(appId, version);\n return {\n appId,\n activeVersion: appRecord.activeVersion,\n previousVersion: currentRecord.activeVersion,\n enabled: appRecord.enabled,\n rolledBack: true,\n };\n });\n };\n\n assertVersionIntegrity = async (appId: string, version?: string): Promise<void> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const targetVersion = version ?? appRecord.activeVersion;\n const versionRecord = appRecord.installedVersions[targetVersion];\n if (!versionRecord) {\n throw new Error(`应用 ${appId} 未安装版本 ${targetVersion}。`);\n }\n const contentSha256 = await this.integrityService.assertVersion({\n appId,\n version: targetVersion,\n versionRecord,\n });\n if (!versionRecord.contentSha256) {\n await this.registryService.setVersionContentDigest(appId, targetVersion, contentSha256);\n await this.integrityService.protectDirectory(versionRecord.installDirectory);\n }\n };\n\n withAppOperation = async <T>(appId: string, operation: () => Promise<T>): Promise<T> => {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`appId 不是安全的 App 操作标识:${appId}`);\n }\n const lockPath = path.resolve(this.appHomeService.getAppOperationLockPath(appId));\n const activeLocks = this.appOperationContext.getStore();\n if (activeLocks?.has(lockPath)) {\n return await operation();\n }\n return await this.fileLockService.withLock(lockPath, async () => {\n const nextLocks = new Set(activeLocks ?? []);\n nextLocks.add(lockPath);\n return await this.appOperationContext.run(nextLocks, operation);\n });\n };\n\n resolveLaunch = async (\n appReference: string,\n explicitDocumentGrantMap: AppDocumentGrantMap,\n ): Promise<AppLaunchResolution> => {\n const sourceType = await this.installSourceService.detectLocal(appReference, false);\n if (sourceType.kind === \"directory\") {\n return {\n appDirectory: sourceType.appDirectory,\n documentGrantMap: explicitDocumentGrantMap,\n };\n }\n const appRecord = await this.registryService.getApp(appReference);\n if (!appRecord) {\n throw new Error(`未找到应用目录,也未找到已安装应用:${appReference}`);\n }\n const activeVersion = appRecord.installedVersions[appRecord.activeVersion];\n if (!activeVersion) {\n throw new Error(`已安装应用缺少激活版本:${appReference}`);\n }\n await this.assertVersionIntegrity(appRecord.appId, appRecord.activeVersion);\n return {\n appDirectory: activeVersion.installDirectory,\n appId: appRecord.appId,\n dataDirectory: appRecord.dataDirectory,\n storage: appRecord.defaultInstance.storage,\n documentGrantMap: {\n ...appRecord.grants,\n ...explicitDocumentGrantMap,\n },\n };\n };\n\n persistGrants = async (\n appId: string | undefined,\n documentGrantMap: AppDocumentGrantMap,\n ): Promise<void> => {\n if (!appId || Object.keys(documentGrantMap).length === 0) {\n return;\n }\n await this.registryService.updateGrants(appId, documentGrantMap);\n };\n\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-installation.service.js","names":[],"sources":["../../src/services/app-installation.service.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { isAppComponentManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport { AppInstallationFilesystemService } from \"#app-runtime/services/app-installation-filesystem.service.js\";\nimport { AppInstallationLifecycleService } from \"#app-runtime/services/app-installation-lifecycle.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppBundleExtractResult } from \"#app-runtime/types/app-bundle.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\nimport { AppRegistryConfigService } from \"#app-runtime/services/app-registry-config.service.js\";\nimport { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport { AppRegistryService } from \"#app-runtime/services/app-registry.service.js\";\nimport {\n AppInstallSourceService,\n type ResolvedAppInstallSource,\n} from \"#app-runtime/services/app-install-source.service.js\";\nimport type {\n AppInfoResult,\n AppInstallProgressHandler,\n AppActivationResult,\n AppInstallResult,\n AppLaunchResolution,\n AppUpdateResult,\n AppRollbackResult,\n AppUninstallResult,\n InstalledAppListItem,\n} from \"#app-runtime/types/app-installation.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\ntype AppInstallOptions = {\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n trustedPublisher?: AppPublisher;\n};\n\ntype PreparedAppInstall = {\n source: ResolvedAppInstallSource;\n extractedDirectory: string;\n extractedMetadata: AppBundleExtractResult;\n manifestBundle: AppManifestBundle;\n options: AppInstallOptions;\n};\n\nexport class AppInstallationService {\n private readonly installSourceService: AppInstallSourceService;\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n private readonly integrityService: AppInstallationIntegrityService;\n private readonly filesystemService: AppInstallationFilesystemService;\n private readonly lifecycleService: AppInstallationLifecycleService;\n private readonly appOperationContext = new AsyncLocalStorage<ReadonlySet<string>>();\n\n constructor(\n private readonly appHomeService: AppHomeService = new AppHomeService(),\n private readonly bundleService: AppBundleService = new AppBundleService(),\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly buildService: AppBuildService = new AppBuildService(),\n private readonly registryService: AppRegistryService = new AppRegistryService(appHomeService),\n private readonly registryConfigService: AppRegistryConfigService = new AppRegistryConfigService(\n appHomeService,\n ),\n private readonly remoteRegistryClient: AppRemoteRegistryClientService = new AppRemoteRegistryClientService(\n new AppRegistryConfigService(appHomeService),\n ),\n instanceStorageService?: AppInstanceStorageService,\n ) {\n this.installSourceService = new AppInstallSourceService(\n this.manifestService,\n this.remoteRegistryClient,\n this.bundleService,\n );\n this.instanceStorageService = instanceStorageService ?? new AppInstanceStorageService(appHomeService);\n this.integrityService = new AppInstallationIntegrityService(manifestService);\n this.filesystemService = new AppInstallationFilesystemService({\n buildService,\n integrityService: this.integrityService,\n manifestService,\n });\n this.lifecycleService = new AppInstallationLifecycleService({\n registryService,\n registryConfigService,\n remoteRegistryClient,\n instanceStorageService: this.instanceStorageService,\n integrityService: this.integrityService,\n install: this.install,\n rollback: this.rollback,\n });\n }\n\n install = async (\n appSource: string,\n options: AppInstallOptions = {},\n ): Promise<AppInstallResult> => {\n const { onProgress, registryUrl } = options;\n await onProgress?.(\"resolving\");\n const source = await this.installSourceService.resolve(appSource, registryUrl);\n const tempDirectory = await this.appHomeService.createTemporaryDirectory(\"napp-install-\");\n try {\n const bundlePath = await this.installSourceService.materializeBundle(\n source,\n tempDirectory,\n onProgress,\n );\n await onProgress?.(\"verifying\");\n const extractedDirectory = path.join(tempDirectory, \"bundle\");\n const extractedMetadata = await this.bundleService.extractBundle({\n bundlePath,\n targetDirectory: extractedDirectory,\n });\n await this.filesystemService.materializeDistribution({\n appDirectory: extractedDirectory,\n distributionMode: extractedMetadata.metadata.distributionMode,\n });\n const manifestBundle = await this.manifestService.load(extractedDirectory);\n return await this.installPreparedBundle({\n source,\n extractedDirectory,\n extractedMetadata,\n manifestBundle,\n options,\n });\n } finally {\n await rm(tempDirectory, { recursive: true, force: true });\n }\n };\n\n private installPreparedBundle = async (\n prepared: PreparedAppInstall,\n ): Promise<AppInstallResult> => {\n const appId = prepared.manifestBundle.manifest.id;\n return await this.withAppOperation(\n appId,\n async () => await this.commitPreparedInstall(prepared),\n );\n };\n\n private commitPreparedInstall = async (\n prepared: PreparedAppInstall,\n ): Promise<AppInstallResult> => {\n const { extractedDirectory, extractedMetadata, manifestBundle, options, source } = prepared;\n const { onProgress } = options;\n const { id: appId, version } = manifestBundle.manifest;\n const installDirectory = this.appHomeService.getInstallDirectory(appId, version);\n if (source.kind === \"registry\" && appId !== source.registryResolution.appId) {\n throw new Error(\n `bundle manifest.appId 与 registry 请求不一致:期望 ${source.registryResolution.appId},实际 ${appId}`,\n );\n }\n await onProgress?.(\"installing\");\n const existingRecord = await this.registryService.getApp(appId);\n const publisher = source.kind === \"registry\"\n ? source.registryResolution.publisher\n : options.trustedPublisher;\n const currentPublisher = existingRecord?.publisher ??\n existingRecord?.installedVersions[existingRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== publisher?.id) {\n throw new Error(\n `应用 ${appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const dataSchemaVersion = manifestBundle.manifest.schemaVersion === 2\n ? manifestBundle.manifest.storage?.schemaVersion ?? 1\n : 1;\n if (existingRecord?.defaultInstance.dataSchemaVersion !== undefined &&\n existingRecord.defaultInstance.dataSchemaVersion !== dataSchemaVersion) {\n throw new Error(\n `应用 ${appId} 数据 schema 从 ${existingRecord.defaultInstance.dataSchemaVersion} 升级到 ${dataSchemaVersion},但包内没有受支持的迁移合同。更新已停止,现有数据和版本保持不变。`,\n );\n }\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, \"default\");\n const instanceExisted = await this.integrityService.pathExists(instanceDirectory);\n const contentSha256 = await this.filesystemService.copyToImmutableInstallDirectory({\n appId,\n appVersion: version,\n extractedDirectory,\n installDirectory,\n });\n const defaultInstance = await this.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: (currentPublisher ?? publisher)?.id,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n dataSchemaVersion,\n });\n await onProgress?.(\"finalizing\");\n let registryRecord;\n try {\n registryRecord = await this.registryService.upsertInstallation({\n appId,\n name: manifestBundle.manifest.name,\n description: manifestBundle.manifest.description,\n version,\n installDirectory,\n defaultInstance,\n sourceKind: source.kind,\n distributionMode: source.kind === \"registry\"\n ? source.registryResolution.distributionMode\n : extractedMetadata.metadata.distributionMode,\n sourceRef: source.sourceRef,\n installedAt: new Date().toISOString(),\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n registryUrl: source.kind === \"registry\"\n ? source.registryResolution.registryUrl\n : undefined,\n bundleUrl: source.kind === \"registry\" ? source.registryResolution.bundleUrl : undefined,\n sha256: source.kind === \"registry\" ? source.registryResolution.sha256 : undefined,\n publisher,\n manifestSchemaVersion: manifestBundle.manifest.schemaVersion,\n components: isAppComponentManifestBundle(manifestBundle)\n ? manifestBundle.components.map((component) => ({\n ...component,\n componentDirectory: path.join(installDirectory, component.path),\n manifestPath: path.join(\n installDirectory,\n component.path,\n component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n ),\n }))\n : undefined,\n primaryPanelId: isAppComponentManifestBundle(manifestBundle)\n ? manifestBundle.primaryPanelId\n : undefined,\n security: isAppComponentManifestBundle(manifestBundle)\n ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest)\n : undefined,\n dataSchemaVersion,\n contentSha256,\n activate: options.activate,\n });\n } catch (error) {\n await this.integrityService.removeDirectory(installDirectory);\n if (!instanceExisted) {\n await this.instanceStorageService.rollbackNewInstance({\n instance: defaultInstance,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n });\n }\n throw error;\n }\n const installedVersion = registryRecord.installedVersions[version];\n return {\n appId: registryRecord.appId,\n name: registryRecord.name,\n version,\n installDirectory,\n dataDirectory: defaultInstance.storage.dataDirectory,\n instance: registryRecord.defaultInstance,\n sourceKind: source.kind,\n sourceRef: source.sourceRef,\n distributionMode: installedVersion?.distributionMode,\n permissions: installedVersion?.permissions ?? {},\n registryUrl: installedVersion?.registryUrl,\n bundleUrl: installedVersion?.bundleUrl,\n sha256: installedVersion?.sha256,\n publisher: installedVersion?.publisher,\n enabled: registryRecord.enabled,\n manifestSchemaVersion: installedVersion?.manifestSchemaVersion ?? 1,\n components: installedVersion?.components,\n primaryPanelId: installedVersion?.primaryPanelId,\n };\n };\n\n update = async (\n appId: string,\n options?: {\n version?: string;\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n },\n ): Promise<AppUpdateResult> => {\n return await this.withAppOperation(\n appId,\n async () => await this.lifecycleService.update(appId, options),\n );\n };\n\n uninstall = async (\n appId: string,\n purgeData: boolean,\n ): Promise<AppUninstallResult> => {\n return await this.withAppOperation(\n appId,\n async () => await this.lifecycleService.uninstall(appId, purgeData),\n );\n };\n\n list = async (): Promise<InstalledAppListItem[]> => {\n const appRecords = await this.registryService.listApps();\n return appRecords.map((appRecord) => ({\n appId: appRecord.appId,\n name: appRecord.name,\n activeVersion: appRecord.activeVersion,\n sourceKind:\n appRecord.installedVersions[appRecord.activeVersion]?.sourceKind ?? \"directory\",\n distributionMode:\n appRecord.installedVersions[appRecord.activeVersion]?.distributionMode,\n enabled: appRecord.enabled,\n manifestSchemaVersion:\n appRecord.installedVersions[appRecord.activeVersion]?.manifestSchemaVersion ?? 1,\n primaryPanelId:\n appRecord.installedVersions[appRecord.activeVersion]?.primaryPanelId,\n }));\n };\n\n reconcileFilesystem = async (): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n let appRecords = await this.registryService.listApps();\n for (const appRecord of appRecords) {\n await this.withAppOperation(appRecord.appId, async () => {\n for (const versionRecord of Object.values(appRecord.installedVersions)) {\n if (!await this.integrityService.pathExists(versionRecord.installDirectory)) {\n continue;\n }\n if (!versionRecord.contentSha256) {\n const contentSha256 = await this.integrityService.calculateDigest(\n versionRecord.installDirectory,\n );\n await this.registryService.setVersionContentDigest(\n appRecord.appId,\n versionRecord.version,\n contentSha256,\n );\n }\n await this.integrityService.protectDirectory(versionRecord.installDirectory);\n }\n });\n }\n appRecords = await this.registryService.listApps();\n const referencedInstallPaths = new Set(appRecords.flatMap((record) =>\n Object.values(record.installedVersions).map((version) => path.resolve(version.installDirectory))));\n const referencedDataPaths = new Set(appRecords.map((record) => path.resolve(\n record.defaultInstance.storage.layout === \"instance-v1\"\n ? record.defaultInstance.storage.instanceDirectory\n : record.dataDirectory,\n )));\n\n const packageDirectories = await this.filesystemService.readDirectories(\n this.appHomeService.getPackagesDirectory(),\n );\n for (const appDirectory of packageDirectories) {\n await this.filesystemService.reconcileGeneratedSiblings(appDirectory, referencedInstallPaths);\n }\n await this.filesystemService.reconcileGeneratedSiblings(\n this.appHomeService.getDataDirectory(),\n referencedDataPaths,\n );\n for (const appRecord of appRecords) {\n if (appRecord.defaultInstance.storage.layout !== \"instance-v1\") {\n continue;\n }\n await this.filesystemService.reconcileGeneratedSiblings(\n appRecord.defaultInstance.storage.instanceDirectory,\n new Set([path.resolve(appRecord.defaultInstance.storage.dataDirectory)]),\n );\n }\n for (const appRecord of appRecords) {\n if (appRecord.defaultInstance.storage.layout !== \"legacy\") {\n continue;\n }\n await this.withAppOperation(appRecord.appId, async () => {\n const currentRecord = await this.registryService.getApp(appRecord.appId);\n if (!currentRecord || currentRecord.defaultInstance.storage.layout !== \"legacy\") {\n return;\n }\n const activeVersion = currentRecord.installedVersions[currentRecord.activeVersion];\n if (!activeVersion) {\n throw new Error(`已安装应用缺少激活版本:${currentRecord.appId}`);\n }\n const instance = await this.instanceStorageService.materializeDefaultInstance({\n appId: currentRecord.appId,\n publisherId: currentRecord.publisher?.id,\n legacyDataDirectory: currentRecord.dataDirectory,\n dataSchemaVersion: currentRecord.defaultInstance.dataSchemaVersion,\n });\n try {\n await this.registryService.upsertInstallation({\n appId: currentRecord.appId,\n name: currentRecord.name,\n description: currentRecord.description,\n version: currentRecord.activeVersion,\n installDirectory: activeVersion.installDirectory,\n defaultInstance: instance,\n sourceKind: activeVersion.sourceKind,\n sourceRef: activeVersion.sourceRef,\n installedAt: activeVersion.installedAt,\n distributionMode: activeVersion.distributionMode,\n permissions: activeVersion.permissions,\n registryUrl: activeVersion.registryUrl,\n bundleUrl: activeVersion.bundleUrl,\n sha256: activeVersion.sha256,\n publisher: currentRecord.publisher ?? activeVersion.publisher,\n manifestSchemaVersion: activeVersion.manifestSchemaVersion,\n components: activeVersion.components,\n primaryPanelId: activeVersion.primaryPanelId,\n security: activeVersion.security,\n dataSchemaVersion: activeVersion.dataSchemaVersion,\n contentSha256: activeVersion.contentSha256,\n enabled: currentRecord.enabled,\n });\n } catch (error) {\n await this.instanceStorageService.rollbackNewInstance({\n instance,\n legacyDataDirectory: currentRecord.dataDirectory,\n });\n throw error;\n }\n });\n }\n };\n\n info = async (\n appId: string,\n options: { measureStorageUsage?: boolean } = {},\n ): Promise<AppInfoResult> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const installedVersions = Object.values(appRecord.installedVersions).sort((left, right) =>\n left.version.localeCompare(right.version),\n );\n const storageUsage = options.measureStorageUsage === false\n ? undefined\n : await this.instanceStorageService.measureUsage(appRecord.defaultInstance.storage);\n return {\n appId: appRecord.appId,\n name: appRecord.name,\n description: appRecord.description,\n activeVersion: appRecord.activeVersion,\n enabled: appRecord.enabled,\n dataDirectory: appRecord.dataDirectory,\n instance: appRecord.defaultInstance,\n storage: appRecord.defaultInstance.storage,\n storageUsage,\n installedVersions: installedVersions.map((versionRecord) => ({\n version: versionRecord.version,\n installDirectory: versionRecord.installDirectory,\n sourceKind: versionRecord.sourceKind,\n distributionMode: versionRecord.distributionMode,\n sourceRef: versionRecord.sourceRef,\n installedAt: versionRecord.installedAt,\n permissions: versionRecord.permissions,\n registryUrl: versionRecord.registryUrl,\n bundleUrl: versionRecord.bundleUrl,\n sha256: versionRecord.sha256,\n publisher: versionRecord.publisher,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion,\n components: versionRecord.components,\n primaryPanelId: versionRecord.primaryPanelId,\n contentSha256: versionRecord.contentSha256,\n })),\n grants: appRecord.grants,\n };\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppActivationResult> => {\n return await this.withAppOperation(appId, async () => {\n const appRecord = await this.registryService.setEnabled(appId, enabled);\n return {\n appId: appRecord.appId,\n activeVersion: appRecord.activeVersion,\n enabled: appRecord.enabled,\n };\n });\n };\n\n rollback = async (appId: string, version: string): Promise<AppRollbackResult> => {\n return await this.withAppOperation(appId, async () => {\n const currentRecord = await this.registryService.getApp(appId);\n if (!currentRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n if (currentRecord.activeVersion === version) {\n return {\n appId,\n activeVersion: version,\n previousVersion: version,\n enabled: currentRecord.enabled,\n rolledBack: false,\n };\n }\n const targetVersion = currentRecord.installedVersions[version];\n if (!targetVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (targetVersion.dataSchemaVersion !== currentRecord.defaultInstance.dataSchemaVersion) {\n throw new Error(\n `应用 ${appId}@${version} 需要数据 schema ${targetVersion.dataSchemaVersion},当前实例为 ${currentRecord.defaultInstance.dataSchemaVersion}。没有可用 checkpoint,已阻止回滚。`,\n );\n }\n await this.assertVersionIntegrity(appId, version);\n const appRecord = await this.registryService.activateVersion(appId, version);\n return {\n appId,\n activeVersion: appRecord.activeVersion,\n previousVersion: currentRecord.activeVersion,\n enabled: appRecord.enabled,\n rolledBack: true,\n };\n });\n };\n\n assertVersionIntegrity = async (appId: string, version?: string): Promise<void> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const targetVersion = version ?? appRecord.activeVersion;\n const versionRecord = appRecord.installedVersions[targetVersion];\n if (!versionRecord) {\n throw new Error(`应用 ${appId} 未安装版本 ${targetVersion}。`);\n }\n const contentSha256 = await this.integrityService.assertVersion({\n appId,\n version: targetVersion,\n versionRecord,\n });\n if (!versionRecord.contentSha256) {\n await this.registryService.setVersionContentDigest(appId, targetVersion, contentSha256);\n await this.integrityService.protectDirectory(versionRecord.installDirectory);\n }\n };\n\n withAppOperation = async <T>(appId: string, operation: () => Promise<T>): Promise<T> => {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`appId 不是安全的 App 操作标识:${appId}`);\n }\n const lockPath = path.resolve(this.appHomeService.getAppOperationLockPath(appId));\n const activeLocks = this.appOperationContext.getStore();\n if (activeLocks?.has(lockPath)) {\n return await operation();\n }\n return await this.fileLockService.withLock(lockPath, async () => {\n const nextLocks = new Set(activeLocks ?? []);\n nextLocks.add(lockPath);\n return await this.appOperationContext.run(nextLocks, operation);\n });\n };\n\n resolveLaunch = async (\n appReference: string,\n explicitDocumentGrantMap: AppDocumentGrantMap,\n ): Promise<AppLaunchResolution> => {\n const sourceType = await this.installSourceService.detectLocal(appReference, false);\n if (sourceType.kind === \"directory\") {\n return {\n appDirectory: sourceType.appDirectory,\n documentGrantMap: explicitDocumentGrantMap,\n };\n }\n const appRecord = await this.registryService.getApp(appReference);\n if (!appRecord) {\n throw new Error(`未找到应用目录,也未找到已安装应用:${appReference}`);\n }\n const activeVersion = appRecord.installedVersions[appRecord.activeVersion];\n if (!activeVersion) {\n throw new Error(`已安装应用缺少激活版本:${appReference}`);\n }\n await this.assertVersionIntegrity(appRecord.appId, appRecord.activeVersion);\n return {\n appDirectory: activeVersion.installDirectory,\n appId: appRecord.appId,\n dataDirectory: appRecord.dataDirectory,\n storage: appRecord.defaultInstance.storage,\n documentGrantMap: {\n ...appRecord.grants,\n ...explicitDocumentGrantMap,\n },\n };\n };\n\n persistGrants = async (\n appId: string | undefined,\n documentGrantMap: AppDocumentGrantMap,\n ): Promise<void> => {\n if (!appId || Object.keys(documentGrantMap).length === 0) {\n return;\n }\n await this.registryService.updateGrants(appId, documentGrantMap);\n };\n\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { AppInstanceInventory } from "../types/app-storage.types.js";
2
+ import { AppInstanceStorageService } from "./app-instance-storage.service.js";
3
+
4
+ //#region src/services/app-instance-inventory.service.d.ts
5
+ declare class AppInstanceInventoryService {
6
+ private readonly storageService;
7
+ private readonly fileLockService;
8
+ constructor(storageService?: AppInstanceStorageService);
9
+ list: (instancesRoot: string) => Promise<AppInstanceInventory>;
10
+ reconcileDeletions: (instancesRoot: string) => Promise<AppInstanceInventory["diagnostics"]>;
11
+ purge: (params: {
12
+ instancesRoot: string;
13
+ appId: string;
14
+ instanceId: string;
15
+ assertCanPurge?: () => Promise<void>;
16
+ }) => Promise<void>;
17
+ purgeNested: (params: {
18
+ instancesRoot: string;
19
+ pathSegments: string[];
20
+ appId: string;
21
+ instanceId: string;
22
+ assertCanPurge?: () => Promise<void>;
23
+ }) => Promise<void>;
24
+ private listDirectories;
25
+ private assertSafeId;
26
+ private parseDeletingInstanceId;
27
+ private assertWithinRoot;
28
+ private pathExists;
29
+ private isMissingFileError;
30
+ }
31
+ //#endregion
32
+ export { AppInstanceInventoryService };
33
+ //# sourceMappingURL=app-instance-inventory.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-instance-inventory.service.d.ts","names":[],"sources":["../../src/services/app-instance-inventory.service.ts"],"mappings":";;;;cAaa,2BAAA;EAAA,iBAIQ,cAAA;EAAA,iBAHF,eAAA;cAGE,cAAA,GAAc,yBAAA;EAGjC,IAAA,GAAc,aAAA,aAAwB,OAAA,CAAQ,oBAAA;EA+D9C,kBAAA,GACE,aAAA,aACC,OAAA,CAAQ,oBAAA;EAqCX,KAAA,GAAe,MAAA;IACb,aAAA;IACA,KAAA;IACA,UAAA;IACA,cAAA,SAAuB,OAAA;EAAA,MACrB,OAAA;EAWJ,WAAA,GAAqB,MAAA;IACnB,aAAA;IACA,YAAA;IACA,KAAA;IACA,UAAA;IACA,cAAA,SAAuB,OAAA;EAAA,MACrB,OAAA;EAAA,QAuCI,eAAA;EAAA,QAYA,YAAA;EAAA,QAMA,uBAAA;EAAA,QAGA,gBAAA;EAAA,QAOA,UAAA;EAAA,QASA,kBAAA;AAAA"}
@@ -0,0 +1,177 @@
1
+ import { FileLockService } from "./file-lock.service.js";
2
+ import { AppInstanceStorageService } from "./app-instance-storage.service.js";
3
+ import { randomUUID } from "node:crypto";
4
+ import { access, readdir, rename, rm } from "node:fs/promises";
5
+ import path from "node:path";
6
+ //#region src/services/app-instance-inventory.service.ts
7
+ const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
8
+ const DELETING_INSTANCE_PATTERN = /^(?<instanceId>[a-z0-9]+(?:[.-][a-z0-9]+)*)\.deleting-(?<transactionId>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/;
9
+ var AppInstanceInventoryService = class {
10
+ fileLockService = new FileLockService();
11
+ constructor(storageService = new AppInstanceStorageService()) {
12
+ this.storageService = storageService;
13
+ }
14
+ list = async (instancesRoot) => {
15
+ const root = path.resolve(instancesRoot);
16
+ const entries = [];
17
+ const diagnostics = [];
18
+ for (const appDirectory of await this.listDirectories(root)) {
19
+ const appId = appDirectory.name;
20
+ const appPath = path.join(root, appId);
21
+ if (!SAFE_ID_PATTERN.test(appId)) {
22
+ diagnostics.push({
23
+ instanceDirectory: appPath,
24
+ message: `不安全的 App Instance appId:${appId}`
25
+ });
26
+ continue;
27
+ }
28
+ for (const instanceDirectory of await this.listDirectories(appPath)) {
29
+ const deletingInstanceId = this.parseDeletingInstanceId(instanceDirectory.name);
30
+ if (deletingInstanceId) {
31
+ diagnostics.push({
32
+ instanceDirectory: path.join(appPath, instanceDirectory.name),
33
+ message: `App Instance 删除清理等待启动恢复:${appId}/${deletingInstanceId}`
34
+ });
35
+ continue;
36
+ }
37
+ const instanceId = instanceDirectory.name;
38
+ const instancePath = path.join(appPath, instanceId);
39
+ if (!SAFE_ID_PATTERN.test(instanceId)) {
40
+ diagnostics.push({
41
+ instanceDirectory: instancePath,
42
+ message: `不安全的 App Instance instanceId:${instanceId}`
43
+ });
44
+ continue;
45
+ }
46
+ try {
47
+ const instance = await this.storageService.inspect({
48
+ appId,
49
+ instanceId,
50
+ instanceDirectory: instancePath
51
+ });
52
+ const usage = await this.storageService.measureUsage(instance.storage);
53
+ entries.push({
54
+ appId,
55
+ instanceId,
56
+ publisherId: instance.publisherId,
57
+ storage: instance.storage,
58
+ usage,
59
+ createdAt: instance.createdAt,
60
+ migratedAt: instance.migratedAt
61
+ });
62
+ } catch (error) {
63
+ diagnostics.push({
64
+ instanceDirectory: instancePath,
65
+ message: error instanceof Error ? error.message : String(error)
66
+ });
67
+ }
68
+ }
69
+ }
70
+ entries.sort((left, right) => left.appId.localeCompare(right.appId) || left.instanceId.localeCompare(right.instanceId));
71
+ diagnostics.sort((left, right) => left.instanceDirectory.localeCompare(right.instanceDirectory));
72
+ return {
73
+ entries,
74
+ diagnostics
75
+ };
76
+ };
77
+ reconcileDeletions = async (instancesRoot) => {
78
+ const root = path.resolve(instancesRoot);
79
+ const diagnostics = [];
80
+ for (const appDirectory of await this.listDirectories(root)) {
81
+ const appId = appDirectory.name;
82
+ if (!SAFE_ID_PATTERN.test(appId)) continue;
83
+ const appPath = path.join(root, appId);
84
+ for (const instanceDirectory of await this.listDirectories(appPath)) {
85
+ const instanceId = this.parseDeletingInstanceId(instanceDirectory.name);
86
+ if (!instanceId) continue;
87
+ const stagedPath = path.join(appPath, instanceDirectory.name);
88
+ const canonicalPath = path.join(appPath, instanceId);
89
+ try {
90
+ await this.fileLockService.withLock(`${canonicalPath}.lock`, async () => {
91
+ await this.storageService.inspect({
92
+ appId,
93
+ instanceId,
94
+ instanceDirectory: stagedPath
95
+ });
96
+ await rm(stagedPath, { recursive: true });
97
+ });
98
+ } catch (error) {
99
+ diagnostics.push({
100
+ instanceDirectory: stagedPath,
101
+ message: `App Instance 删除清理恢复失败:${error instanceof Error ? error.message : String(error)}`
102
+ });
103
+ }
104
+ }
105
+ }
106
+ return diagnostics.sort((left, right) => left.instanceDirectory.localeCompare(right.instanceDirectory));
107
+ };
108
+ purge = async (params) => {
109
+ const { appId, assertCanPurge, instanceId, instancesRoot } = params;
110
+ return await this.purgeNested({
111
+ instancesRoot,
112
+ pathSegments: [appId, instanceId],
113
+ appId,
114
+ instanceId,
115
+ assertCanPurge
116
+ });
117
+ };
118
+ purgeNested = async (params) => {
119
+ const { appId, assertCanPurge, instanceId, instancesRoot, pathSegments } = params;
120
+ this.assertSafeId(appId, "appId");
121
+ this.assertSafeId(instanceId, "instanceId");
122
+ if (pathSegments.length === 0) throw new Error("App Instance 相对路径不能为空。");
123
+ pathSegments.forEach((segment, index) => this.assertSafeId(segment, `pathSegments[${index}]`));
124
+ const root = path.resolve(instancesRoot);
125
+ const instanceDirectory = path.resolve(root, ...pathSegments);
126
+ this.assertWithinRoot(root, instanceDirectory);
127
+ await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => {
128
+ await assertCanPurge?.();
129
+ await this.storageService.inspect({
130
+ appId,
131
+ instanceId,
132
+ instanceDirectory
133
+ });
134
+ const stagingDirectory = `${instanceDirectory}.deleting-${randomUUID()}`;
135
+ await rename(instanceDirectory, stagingDirectory);
136
+ try {
137
+ await rm(stagingDirectory, { recursive: true });
138
+ } catch (error) {
139
+ try {
140
+ if (!await this.pathExists(instanceDirectory)) await rename(stagingDirectory, instanceDirectory);
141
+ } catch (restoreError) {
142
+ throw new AggregateError([error, restoreError], `App Instance 删除失败且恢复失败:${appId}/${instanceId}`);
143
+ }
144
+ throw error;
145
+ }
146
+ });
147
+ };
148
+ listDirectories = async (directory) => {
149
+ try {
150
+ return (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
151
+ } catch (error) {
152
+ if (this.isMissingFileError(error)) return [];
153
+ throw error;
154
+ }
155
+ };
156
+ assertSafeId = (value, field) => {
157
+ if (!SAFE_ID_PATTERN.test(value)) throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);
158
+ };
159
+ parseDeletingInstanceId = (directoryName) => DELETING_INSTANCE_PATTERN.exec(directoryName)?.groups?.instanceId;
160
+ assertWithinRoot = (root, target) => {
161
+ const relative = path.relative(root, target);
162
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`App Instance 路径不在受管根目录内:${target}`);
163
+ };
164
+ pathExists = async (targetPath) => {
165
+ try {
166
+ await access(targetPath);
167
+ return true;
168
+ } catch {
169
+ return false;
170
+ }
171
+ };
172
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
173
+ };
174
+ //#endregion
175
+ export { AppInstanceInventoryService };
176
+
177
+ //# sourceMappingURL=app-instance-inventory.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-instance-inventory.service.js","names":[],"sources":["../../src/services/app-instance-inventory.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type {\n AppInstanceInventory,\n AppInstanceInventoryEntry,\n} from \"#app-runtime/types/app-storage.types.js\";\n\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst DELETING_INSTANCE_PATTERN = /^(?<instanceId>[a-z0-9]+(?:[.-][a-z0-9]+)*)\\.deleting-(?<transactionId>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/;\n\nexport class AppInstanceInventoryService {\n private readonly fileLockService = new FileLockService();\n\n constructor(\n private readonly storageService = new AppInstanceStorageService(),\n ) {}\n\n list = async (instancesRoot: string): Promise<AppInstanceInventory> => {\n const root = path.resolve(instancesRoot);\n const entries: AppInstanceInventoryEntry[] = [];\n const diagnostics: AppInstanceInventory[\"diagnostics\"] = [];\n for (const appDirectory of await this.listDirectories(root)) {\n const appId = appDirectory.name;\n const appPath = path.join(root, appId);\n if (!SAFE_ID_PATTERN.test(appId)) {\n diagnostics.push({\n instanceDirectory: appPath,\n message: `不安全的 App Instance appId:${appId}`,\n });\n continue;\n }\n for (const instanceDirectory of await this.listDirectories(appPath)) {\n const deletingInstanceId = this.parseDeletingInstanceId(instanceDirectory.name);\n if (deletingInstanceId) {\n diagnostics.push({\n instanceDirectory: path.join(appPath, instanceDirectory.name),\n message: `App Instance 删除清理等待启动恢复:${appId}/${deletingInstanceId}`,\n });\n continue;\n }\n const instanceId = instanceDirectory.name;\n const instancePath = path.join(appPath, instanceId);\n if (!SAFE_ID_PATTERN.test(instanceId)) {\n diagnostics.push({\n instanceDirectory: instancePath,\n message: `不安全的 App Instance instanceId:${instanceId}`,\n });\n continue;\n }\n try {\n const instance = await this.storageService.inspect({\n appId,\n instanceId,\n instanceDirectory: instancePath,\n });\n const usage = await this.storageService.measureUsage(instance.storage);\n entries.push({\n appId,\n instanceId,\n publisherId: instance.publisherId,\n storage: instance.storage,\n usage,\n createdAt: instance.createdAt,\n migratedAt: instance.migratedAt,\n });\n } catch (error) {\n diagnostics.push({\n instanceDirectory: instancePath,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n entries.sort((left, right) =>\n left.appId.localeCompare(right.appId) || left.instanceId.localeCompare(right.instanceId));\n diagnostics.sort((left, right) =>\n left.instanceDirectory.localeCompare(right.instanceDirectory));\n return { entries, diagnostics };\n };\n\n reconcileDeletions = async (\n instancesRoot: string,\n ): Promise<AppInstanceInventory[\"diagnostics\"]> => {\n const root = path.resolve(instancesRoot);\n const diagnostics: AppInstanceInventory[\"diagnostics\"] = [];\n for (const appDirectory of await this.listDirectories(root)) {\n const appId = appDirectory.name;\n if (!SAFE_ID_PATTERN.test(appId)) {\n continue;\n }\n const appPath = path.join(root, appId);\n for (const instanceDirectory of await this.listDirectories(appPath)) {\n const instanceId = this.parseDeletingInstanceId(instanceDirectory.name);\n if (!instanceId) {\n continue;\n }\n const stagedPath = path.join(appPath, instanceDirectory.name);\n const canonicalPath = path.join(appPath, instanceId);\n try {\n await this.fileLockService.withLock(`${canonicalPath}.lock`, async () => {\n await this.storageService.inspect({\n appId,\n instanceId,\n instanceDirectory: stagedPath,\n });\n await rm(stagedPath, { recursive: true });\n });\n } catch (error) {\n diagnostics.push({\n instanceDirectory: stagedPath,\n message: `App Instance 删除清理恢复失败:${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n }\n return diagnostics.sort((left, right) =>\n left.instanceDirectory.localeCompare(right.instanceDirectory));\n };\n\n purge = async (params: {\n instancesRoot: string;\n appId: string;\n instanceId: string;\n assertCanPurge?: () => Promise<void>;\n }): Promise<void> => {\n const { appId, assertCanPurge, instanceId, instancesRoot } = params;\n return await this.purgeNested({\n instancesRoot,\n pathSegments: [appId, instanceId],\n appId,\n instanceId,\n assertCanPurge,\n });\n };\n\n purgeNested = async (params: {\n instancesRoot: string;\n pathSegments: string[];\n appId: string;\n instanceId: string;\n assertCanPurge?: () => Promise<void>;\n }): Promise<void> => {\n const { appId, assertCanPurge, instanceId, instancesRoot, pathSegments } = params;\n this.assertSafeId(appId, \"appId\");\n this.assertSafeId(instanceId, \"instanceId\");\n if (pathSegments.length === 0) {\n throw new Error(\"App Instance 相对路径不能为空。\");\n }\n pathSegments.forEach((segment, index) =>\n this.assertSafeId(segment, `pathSegments[${index}]`));\n const root = path.resolve(instancesRoot);\n const instanceDirectory = path.resolve(root, ...pathSegments);\n this.assertWithinRoot(root, instanceDirectory);\n await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => {\n await assertCanPurge?.();\n await this.storageService.inspect({\n appId,\n instanceId,\n instanceDirectory,\n });\n const stagingDirectory = `${instanceDirectory}.deleting-${randomUUID()}`;\n await rename(instanceDirectory, stagingDirectory);\n try {\n await rm(stagingDirectory, { recursive: true });\n } catch (error) {\n try {\n if (!await this.pathExists(instanceDirectory)) {\n await rename(stagingDirectory, instanceDirectory);\n }\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `App Instance 删除失败且恢复失败:${appId}/${instanceId}`,\n );\n }\n throw error;\n }\n });\n };\n\n private listDirectories = async (directory: string) => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private parseDeletingInstanceId = (directoryName: string): string | undefined =>\n DELETING_INSTANCE_PATTERN.exec(directoryName)?.groups?.instanceId;\n\n private assertWithinRoot = (root: string, target: string): void => {\n const relative = path.relative(root, target);\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`App Instance 路径不在受管根目录内:${target}`);\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\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":"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"}
@@ -28,6 +28,11 @@ declare class AppInstanceStorageService {
28
28
  createdAt: string;
29
29
  }) => AppInstanceRecord;
30
30
  measureUsage: (storage: AppStorageContext) => Promise<AppStorageUsage>;
31
+ inspect: (params: {
32
+ appId: string;
33
+ instanceId: string;
34
+ instanceDirectory: string;
35
+ }) => Promise<AppInstanceRecord>;
31
36
  rollbackNewInstance: (params: {
32
37
  instance: AppInstanceRecord;
33
38
  legacyDataDirectory?: string;
@@ -38,6 +43,7 @@ declare class AppInstanceStorageService {
38
43
  private readMetadata;
39
44
  private toInstanceRecord;
40
45
  private assertMetadataIdentity;
46
+ private assertMetadataCoordinates;
41
47
  private bindPublisher;
42
48
  private assertSafeId;
43
49
  private measureDirectory;
@@ -1 +1 @@
1
- {"version":3,"file":"app-instance-storage.service.d.ts","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"mappings":";;;;cA2Ba,yBAAA;EAAA,iBAGkB,cAAA;EAAA,iBAFZ,eAAA;cAEY,cAAA,GAAgB,cAAA;EAE7C,0BAAA,GAAoC,MAAA;IAClC,KAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAWZ,WAAA,GAAqB,MAAA;IACnB,KAAA;IACA,UAAA;IACA,iBAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAAA,QAUJ,mBAAA;EAAA,QA6BA,sBAAA;EA+ER,0BAAA,GAA8B,MAAA;IAC5B,KAAA;IACA,aAAA;IACA,SAAA;EAAA,MACE,iBAAA;EAoBJ,YAAA,GAAsB,OAAA,EAAS,iBAAA,KAAoB,OAAA,CAAQ,eAAA;EAqB3D,mBAAA,GAA6B,MAAA;IAC3B,QAAA,EAAU,iBAAA;IACV,mBAAA;EAAA,MACE,OAAA;EAAA,QAqBI,uBAAA;EAAA,QAqBA,YAAA;EAAA,QAgBA,wBAAA;EAAA,QAWA,YAAA;EAAA,QAwBA,gBAAA;EAAA,QAcA,sBAAA;EAAA,QAkBA,aAAA;EAAA,QAqBA,YAAA;EAAA,QAMA,gBAAA;EAAA,QA0BA,UAAA;EAAA,QASA,kBAAA;AAAA"}
1
+ {"version":3,"file":"app-instance-storage.service.d.ts","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"mappings":";;;;cAiBa,yBAAA;EAAA,iBAGkB,cAAA;EAAA,iBAFZ,eAAA;cAEY,cAAA,GAAgB,cAAA;EAE7C,0BAAA,GAAoC,MAAA;IAClC,KAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAWZ,WAAA,GAAqB,MAAA;IACnB,KAAA;IACA,UAAA;IACA,iBAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAAA,QAUJ,mBAAA;EAAA,QA6BA,sBAAA;EA+ER,0BAAA,GAA8B,MAAA;IAC5B,KAAA;IACA,aAAA;IACA,SAAA;EAAA,MACE,iBAAA;EAoBJ,YAAA,GAAsB,OAAA,EAAS,iBAAA,KAAoB,OAAA,CAAQ,eAAA;EAqB3D,OAAA,GAAiB,MAAA;IACf,KAAA;IACA,UAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAgBZ,mBAAA,GAA6B,MAAA;IAC3B,QAAA,EAAU,iBAAA;IACV,mBAAA;EAAA,MACE,OAAA;EAAA,QAqBI,uBAAA;EAAA,QAqBA,YAAA;EAAA,QAgBA,wBAAA;EAAA,QAWA,YAAA;EAAA,QA0BA,gBAAA;EAAA,QAcA,sBAAA;EAAA,QAcA,yBAAA;EAAA,QAYA,aAAA;EAAA,QAqBA,YAAA;EAAA,QAMA,gBAAA;EAAA,QA0BA,UAAA;EAAA,QASA,kBAAA;AAAA"}
@@ -130,6 +130,16 @@ var AppInstanceStorageService = class {
130
130
  totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes
131
131
  };
132
132
  };
133
+ inspect = async (params) => {
134
+ const { appId, instanceId, instanceDirectory: instancePath } = params;
135
+ this.assertSafeId(appId, "appId");
136
+ this.assertSafeId(instanceId, "instanceId");
137
+ const instanceDirectory = path.resolve(instancePath);
138
+ const metadata = await this.readMetadata(instanceDirectory);
139
+ if (!metadata) throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);
140
+ this.assertMetadataCoordinates(metadata, appId, instanceId);
141
+ return this.toInstanceRecord(metadata, this.buildContext(instanceDirectory, instanceId));
142
+ };
133
143
  rollbackNewInstance = async (params) => {
134
144
  const { instance, legacyDataDirectory: legacyDataPath } = params;
135
145
  const instanceDirectory = instance.storage.instanceDirectory;
@@ -196,9 +206,12 @@ var AppInstanceStorageService = class {
196
206
  legacyDataDirectory: metadata.legacyDataDirectory
197
207
  });
198
208
  assertMetadataIdentity = (metadata, appId, instanceId, publisherId) => {
199
- if (metadata.appId !== appId || metadata.instanceId !== instanceId) throw new Error(`App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`);
209
+ this.assertMetadataCoordinates(metadata, appId, instanceId);
200
210
  if (metadata.publisherId && metadata.publisherId !== publisherId) throw new Error(`App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? "未验证本地来源"} 接管。`);
201
211
  };
212
+ assertMetadataCoordinates = (metadata, appId, instanceId) => {
213
+ if (metadata.appId !== appId || metadata.instanceId !== instanceId) throw new Error(`App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`);
214
+ };
202
215
  bindPublisher = async (instanceDirectory, metadata, publisherId) => {
203
216
  const nextMetadata = {
204
217
  ...metadata,
@@ -1 +1 @@
1
- {"version":3,"file":"app-instance-storage.service.js","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, readdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport {\n APP_STORAGE_LAYOUT_VERSION,\n DEFAULT_APP_INSTANCE_ID,\n type AppInstanceRecord,\n type AppStorageContext,\n type AppStorageUsage,\n} from \"#app-runtime/types/app-storage.types.js\";\n\ntype StorageMetadata = {\n schemaVersion: 1;\n appId: string;\n instanceId: string;\n publisherId?: string;\n layoutVersion: 1;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nconst STORAGE_METADATA_FILE = \"metadata.json\";\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceStorageService {\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n materializeDefaultInstance = async (params: {\n appId: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n return await this.materialize({\n ...params,\n instanceId: DEFAULT_APP_INSTANCE_ID,\n instanceDirectory: this.appHomeService.getAppInstanceDirectory(\n params.appId,\n DEFAULT_APP_INSTANCE_ID,\n ),\n });\n };\n\n materialize = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n this.assertSafeId(params.appId, \"appId\");\n this.assertSafeId(params.instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(params.instanceDirectory);\n return await this.fileLockService.withLock(\n `${instanceDirectory}.lock`,\n async () => await this.materializeUnlocked({ ...params, instanceDirectory }),\n );\n };\n\n private materializeUnlocked = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n } = params;\n const existingInstance = await this.resolveExistingInstance({\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n });\n if (existingInstance) {\n return existingInstance;\n }\n\n return await this.materializeNewInstance(params);\n };\n\n private materializeNewInstance = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n legacyDataDirectory: legacyDataPath,\n publisherId,\n } = params;\n\n await mkdir(path.dirname(instanceDirectory), { recursive: true });\n const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n const createdAt = new Date().toISOString();\n let movedLegacyData = false;\n try {\n const stagingStorage = this.buildContext(stagingDirectory, instanceId);\n await mkdir(stagingDirectory, { recursive: false });\n if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {\n await rename(legacyDataDirectory, stagingStorage.dataDirectory);\n movedLegacyData = true;\n }\n await this.ensureStorageDirectories(stagingStorage);\n const metadata: StorageMetadata = {\n schemaVersion: 1,\n appId,\n instanceId,\n publisherId,\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n createdAt,\n ...(movedLegacyData\n ? { migratedAt: new Date().toISOString(), legacyDataDirectory }\n : {}),\n };\n await writeFile(\n path.join(stagingDirectory, STORAGE_METADATA_FILE),\n `${JSON.stringify(metadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(stagingDirectory, instanceDirectory);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n dataSchemaVersion,\n );\n } catch (error) {\n if (\n movedLegacyData &&\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory)\n ) {\n const stagedDataDirectory = path.join(stagingDirectory, \"data\");\n if (await this.pathExists(stagedDataDirectory)) {\n await rename(stagedDataDirectory, legacyDataDirectory);\n }\n }\n await rm(stagingDirectory, { recursive: true, force: true });\n if (await this.pathExists(instanceDirectory)) {\n const racedMetadata = await this.readMetadata(instanceDirectory);\n if (racedMetadata) {\n this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);\n }\n }\n throw error;\n }\n };\n\n buildLegacyDefaultInstance = (params: {\n appId: string;\n dataDirectory: string;\n createdAt: string;\n }): AppInstanceRecord => {\n const { appId, createdAt, dataDirectory } = params;\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(\n appId,\n DEFAULT_APP_INSTANCE_ID,\n );\n return {\n id: DEFAULT_APP_INSTANCE_ID,\n publisherId: undefined,\n storage: {\n ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),\n layout: \"legacy\",\n dataDirectory: path.resolve(dataDirectory),\n },\n dataSchemaVersion: 1,\n createdAt,\n legacyDataDirectory: path.resolve(dataDirectory),\n };\n };\n\n measureUsage = async (storage: AppStorageContext): Promise<AppStorageUsage> => {\n const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] =\n await Promise.all([\n this.measureDirectory(storage.dataDirectory),\n this.measureDirectory(storage.configDirectory),\n this.measureDirectory(storage.stateDirectory),\n this.measureDirectory(storage.cacheDirectory),\n this.measureDirectory(storage.temporaryDirectory),\n this.measureDirectory(storage.logsDirectory),\n ]);\n return {\n dataBytes,\n configBytes,\n stateBytes,\n cacheBytes,\n temporaryBytes,\n logsBytes,\n totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes,\n };\n };\n\n rollbackNewInstance = async (params: {\n instance: AppInstanceRecord;\n legacyDataDirectory?: string;\n }): Promise<void> => {\n const { instance, legacyDataDirectory: legacyDataPath } = params;\n const instanceDirectory = instance.storage.instanceDirectory;\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return;\n }\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n if (\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory) &&\n await this.pathExists(instance.storage.dataDirectory)\n ) {\n await mkdir(path.dirname(legacyDataDirectory), { recursive: true });\n await rename(instance.storage.dataDirectory, legacyDataDirectory);\n }\n await rm(instanceDirectory, { recursive: true, force: true });\n };\n\n private resolveExistingInstance = async (params: {\n appId: string;\n dataSchemaVersion?: number;\n instanceDirectory: string;\n instanceId: string;\n publisherId?: string;\n }): Promise<AppInstanceRecord | undefined> => {\n const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;\n let metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return undefined;\n }\n this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);\n if (publisherId && !metadata.publisherId) {\n metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);\n }\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(metadata, storage, dataSchemaVersion);\n };\n\n private buildContext = (\n instanceDirectory: string,\n instanceId: string,\n ): AppStorageContext => ({\n layout: \"instance-v1\",\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n instanceId,\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n });\n\n private ensureStorageDirectories = async (storage: AppStorageContext): Promise<void> => {\n await Promise.all([\n mkdir(storage.dataDirectory, { recursive: true }),\n mkdir(storage.configDirectory, { recursive: true }),\n mkdir(storage.stateDirectory, { recursive: true }),\n mkdir(storage.cacheDirectory, { recursive: true }),\n mkdir(storage.temporaryDirectory, { recursive: true }),\n mkdir(storage.logsDirectory, { recursive: true }),\n ]);\n };\n\n private readMetadata = async (instanceDirectory: string): Promise<StorageMetadata | undefined> => {\n try {\n const raw = JSON.parse(\n await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), \"utf8\"),\n ) as Partial<StorageMetadata>;\n if (\n raw.schemaVersion !== 1 ||\n raw.layoutVersion !== APP_STORAGE_LAYOUT_VERSION ||\n typeof raw.appId !== \"string\" ||\n typeof raw.instanceId !== \"string\" ||\n (raw.publisherId !== undefined && typeof raw.publisherId !== \"string\") ||\n typeof raw.createdAt !== \"string\"\n ) {\n throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);\n }\n return raw as StorageMetadata;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return undefined;\n }\n throw error;\n }\n };\n\n private toInstanceRecord = (\n metadata: StorageMetadata,\n storage: AppStorageContext,\n dataSchemaVersion = 1,\n ): AppInstanceRecord => ({\n id: metadata.instanceId,\n publisherId: metadata.publisherId,\n storage,\n dataSchemaVersion,\n createdAt: metadata.createdAt,\n migratedAt: metadata.migratedAt,\n legacyDataDirectory: metadata.legacyDataDirectory,\n });\n\n private assertMetadataIdentity = (\n metadata: StorageMetadata,\n appId: string,\n instanceId: string,\n publisherId?: string,\n ): void => {\n if (metadata.appId !== appId || metadata.instanceId !== instanceId) {\n throw new Error(\n `App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`,\n );\n }\n if (metadata.publisherId && metadata.publisherId !== publisherId) {\n throw new Error(\n `App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? \"未验证本地来源\"} 接管。`,\n );\n }\n };\n\n private bindPublisher = async (\n instanceDirectory: string,\n metadata: StorageMetadata,\n publisherId: string,\n ): Promise<StorageMetadata> => {\n const nextMetadata = { ...metadata, publisherId };\n const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);\n const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;\n try {\n await writeFile(\n temporaryPath,\n `${JSON.stringify(nextMetadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(temporaryPath, metadataPath);\n return nextMetadata;\n } finally {\n await rm(temporaryPath, { force: true });\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private measureDirectory = async (directory: string): Promise<number> => {\n try {\n const entries = await readdir(directory, { withFileTypes: true });\n let bytes = 0;\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n continue;\n }\n if (entry.isDirectory()) {\n bytes += await this.measureDirectory(entryPath);\n continue;\n }\n if (entry.isFile()) {\n bytes += (await stat(entryPath)).size;\n }\n }\n return bytes;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return 0;\n }\n throw error;\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\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":"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"}
1
+ {"version":3,"file":"app-instance-storage.service.js","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, readdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport {\n APP_STORAGE_LAYOUT_VERSION,\n DEFAULT_APP_INSTANCE_ID,\n type AppInstanceMetadata,\n type AppInstanceRecord,\n type AppStorageContext,\n type AppStorageUsage,\n} from \"#app-runtime/types/app-storage.types.js\";\n\nconst STORAGE_METADATA_FILE = \"metadata.json\";\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceStorageService {\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n materializeDefaultInstance = async (params: {\n appId: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n return await this.materialize({\n ...params,\n instanceId: DEFAULT_APP_INSTANCE_ID,\n instanceDirectory: this.appHomeService.getAppInstanceDirectory(\n params.appId,\n DEFAULT_APP_INSTANCE_ID,\n ),\n });\n };\n\n materialize = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n this.assertSafeId(params.appId, \"appId\");\n this.assertSafeId(params.instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(params.instanceDirectory);\n return await this.fileLockService.withLock(\n `${instanceDirectory}.lock`,\n async () => await this.materializeUnlocked({ ...params, instanceDirectory }),\n );\n };\n\n private materializeUnlocked = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n } = params;\n const existingInstance = await this.resolveExistingInstance({\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n });\n if (existingInstance) {\n return existingInstance;\n }\n\n return await this.materializeNewInstance(params);\n };\n\n private materializeNewInstance = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n legacyDataDirectory: legacyDataPath,\n publisherId,\n } = params;\n\n await mkdir(path.dirname(instanceDirectory), { recursive: true });\n const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n const createdAt = new Date().toISOString();\n let movedLegacyData = false;\n try {\n const stagingStorage = this.buildContext(stagingDirectory, instanceId);\n await mkdir(stagingDirectory, { recursive: false });\n if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {\n await rename(legacyDataDirectory, stagingStorage.dataDirectory);\n movedLegacyData = true;\n }\n await this.ensureStorageDirectories(stagingStorage);\n const metadata: AppInstanceMetadata = {\n schemaVersion: 1,\n appId,\n instanceId,\n publisherId,\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n createdAt,\n ...(movedLegacyData\n ? { migratedAt: new Date().toISOString(), legacyDataDirectory }\n : {}),\n };\n await writeFile(\n path.join(stagingDirectory, STORAGE_METADATA_FILE),\n `${JSON.stringify(metadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(stagingDirectory, instanceDirectory);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n dataSchemaVersion,\n );\n } catch (error) {\n if (\n movedLegacyData &&\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory)\n ) {\n const stagedDataDirectory = path.join(stagingDirectory, \"data\");\n if (await this.pathExists(stagedDataDirectory)) {\n await rename(stagedDataDirectory, legacyDataDirectory);\n }\n }\n await rm(stagingDirectory, { recursive: true, force: true });\n if (await this.pathExists(instanceDirectory)) {\n const racedMetadata = await this.readMetadata(instanceDirectory);\n if (racedMetadata) {\n this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);\n }\n }\n throw error;\n }\n };\n\n buildLegacyDefaultInstance = (params: {\n appId: string;\n dataDirectory: string;\n createdAt: string;\n }): AppInstanceRecord => {\n const { appId, createdAt, dataDirectory } = params;\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(\n appId,\n DEFAULT_APP_INSTANCE_ID,\n );\n return {\n id: DEFAULT_APP_INSTANCE_ID,\n publisherId: undefined,\n storage: {\n ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),\n layout: \"legacy\",\n dataDirectory: path.resolve(dataDirectory),\n },\n dataSchemaVersion: 1,\n createdAt,\n legacyDataDirectory: path.resolve(dataDirectory),\n };\n };\n\n measureUsage = async (storage: AppStorageContext): Promise<AppStorageUsage> => {\n const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] =\n await Promise.all([\n this.measureDirectory(storage.dataDirectory),\n this.measureDirectory(storage.configDirectory),\n this.measureDirectory(storage.stateDirectory),\n this.measureDirectory(storage.cacheDirectory),\n this.measureDirectory(storage.temporaryDirectory),\n this.measureDirectory(storage.logsDirectory),\n ]);\n return {\n dataBytes,\n configBytes,\n stateBytes,\n cacheBytes,\n temporaryBytes,\n logsBytes,\n totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes,\n };\n };\n\n inspect = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n }): Promise<AppInstanceRecord> => {\n const { appId, instanceId, instanceDirectory: instancePath } = params;\n this.assertSafeId(appId, \"appId\");\n this.assertSafeId(instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(instancePath);\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);\n }\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n );\n };\n\n rollbackNewInstance = async (params: {\n instance: AppInstanceRecord;\n legacyDataDirectory?: string;\n }): Promise<void> => {\n const { instance, legacyDataDirectory: legacyDataPath } = params;\n const instanceDirectory = instance.storage.instanceDirectory;\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return;\n }\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n if (\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory) &&\n await this.pathExists(instance.storage.dataDirectory)\n ) {\n await mkdir(path.dirname(legacyDataDirectory), { recursive: true });\n await rename(instance.storage.dataDirectory, legacyDataDirectory);\n }\n await rm(instanceDirectory, { recursive: true, force: true });\n };\n\n private resolveExistingInstance = async (params: {\n appId: string;\n dataSchemaVersion?: number;\n instanceDirectory: string;\n instanceId: string;\n publisherId?: string;\n }): Promise<AppInstanceRecord | undefined> => {\n const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;\n let metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return undefined;\n }\n this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);\n if (publisherId && !metadata.publisherId) {\n metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);\n }\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(metadata, storage, dataSchemaVersion);\n };\n\n private buildContext = (\n instanceDirectory: string,\n instanceId: string,\n ): AppStorageContext => ({\n layout: \"instance-v1\",\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n instanceId,\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n });\n\n private ensureStorageDirectories = async (storage: AppStorageContext): Promise<void> => {\n await Promise.all([\n mkdir(storage.dataDirectory, { recursive: true }),\n mkdir(storage.configDirectory, { recursive: true }),\n mkdir(storage.stateDirectory, { recursive: true }),\n mkdir(storage.cacheDirectory, { recursive: true }),\n mkdir(storage.temporaryDirectory, { recursive: true }),\n mkdir(storage.logsDirectory, { recursive: true }),\n ]);\n };\n\n private readMetadata = async (\n instanceDirectory: string,\n ): Promise<AppInstanceMetadata | undefined> => {\n try {\n const raw = JSON.parse(\n await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), \"utf8\"),\n ) as Partial<AppInstanceMetadata>;\n if (\n raw.schemaVersion !== 1 ||\n raw.layoutVersion !== APP_STORAGE_LAYOUT_VERSION ||\n typeof raw.appId !== \"string\" ||\n typeof raw.instanceId !== \"string\" ||\n (raw.publisherId !== undefined && typeof raw.publisherId !== \"string\") ||\n typeof raw.createdAt !== \"string\"\n ) {\n throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);\n }\n return raw as AppInstanceMetadata;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return undefined;\n }\n throw error;\n }\n };\n\n private toInstanceRecord = (\n metadata: AppInstanceMetadata,\n storage: AppStorageContext,\n dataSchemaVersion = 1,\n ): AppInstanceRecord => ({\n id: metadata.instanceId,\n publisherId: metadata.publisherId,\n storage,\n dataSchemaVersion,\n createdAt: metadata.createdAt,\n migratedAt: metadata.migratedAt,\n legacyDataDirectory: metadata.legacyDataDirectory,\n });\n\n private assertMetadataIdentity = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n publisherId?: string,\n ): void => {\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n if (metadata.publisherId && metadata.publisherId !== publisherId) {\n throw new Error(\n `App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? \"未验证本地来源\"} 接管。`,\n );\n }\n };\n\n private assertMetadataCoordinates = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n ): void => {\n if (metadata.appId !== appId || metadata.instanceId !== instanceId) {\n throw new Error(\n `App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`,\n );\n }\n };\n\n private bindPublisher = async (\n instanceDirectory: string,\n metadata: AppInstanceMetadata,\n publisherId: string,\n ): Promise<AppInstanceMetadata> => {\n const nextMetadata = { ...metadata, publisherId };\n const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);\n const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;\n try {\n await writeFile(\n temporaryPath,\n `${JSON.stringify(nextMetadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(temporaryPath, metadataPath);\n return nextMetadata;\n } finally {\n await rm(temporaryPath, { force: true });\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private measureDirectory = async (directory: string): Promise<number> => {\n try {\n const entries = await readdir(directory, { withFileTypes: true });\n let bytes = 0;\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n continue;\n }\n if (entry.isDirectory()) {\n bytes += await this.measureDirectory(entryPath);\n continue;\n }\n if (entry.isFile()) {\n bytes += (await stat(entryPath)).size;\n }\n }\n return bytes;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return 0;\n }\n throw error;\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;cAuBa,kBAAA;EAAA,iBAIkB,cAAA;EAAA,iBAHZ,sBAAA;EAAA,iBACA,eAAA;cAEY,cAAA,GAAgB,cAAA;EAI7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAazB,IAAA,GAAc,QAAA,EAAU,WAAA,KAAc,OAAA;EAAA,QAI9B,YAAA;EAsBR,QAAA,QAAqB,OAAA,CAAQ,oBAAA;EAK7B,MAAA,GAAgB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAKxC,gBAAA,GAA0B,KAAA,aAAgB,OAAA,CAAQ,2BAAA;EAKlD,kBAAA,GAA4B,MAAA;IAC1B,KAAA;IACA,IAAA;IACA,WAAA;IACA,OAAA;IACA,gBAAA;IACA,eAAA,EAAiB,iBAAA;IACjB,UAAA,EAAY,oBAAA;IACZ,SAAA;IACA,WAAA;IACA,gBAAA,GAAmB,2BAAA;IACnB,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,2BAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,QAAA,GAAW,0BAAA;IACX,iBAAA;IACA,aAAA;IACA,OAAA;IACA,QAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAoDZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,uBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAmBX,YAAA,GACE,KAAA,UACA,MAAA,EAAQ,mBAAA,KACP,OAAA,CAAQ,oBAAA;EAOX,gBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAIX,mBAAA,GAA6B,KAAA,UAAe,OAAA,aAAkB,OAAA;EAc9D,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAa3C,mBAAA,GAA6B,KAAA,aAAgB,OAAA;EAK7C,oBAAA,GAA8B,KAAA,UAAe,UAAA,cAAsB,OAAA;EAAA,QAY3D,SAAA;EAAA,QAiBA,YAAA;EAAA,QAKA,aAAA;EAAA,QA+FA,oBAAA;EAAA,QAyGA,eAAA;EAAA,QAMA,eAAA;EAAA,QA6DA,YAAA;EAAA,QAOA,aAAA;EAAA,QAOA,kBAAA;AAAA"}
1
+ {"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;cAuBa,kBAAA;EAAA,iBAIkB,cAAA;EAAA,iBAHZ,sBAAA;EAAA,iBACA,eAAA;cAEY,cAAA,GAAgB,cAAA;EAI7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAYzB,IAAA,GAAc,QAAA,EAAU,WAAA,KAAc,OAAA;EAAA,QAI9B,YAAA;EAsBR,QAAA,QAAqB,OAAA,CAAQ,oBAAA;EAK7B,MAAA,GAAgB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAKxC,gBAAA,GAA0B,KAAA,aAAgB,OAAA,CAAQ,2BAAA;EAKlD,kBAAA,GAA4B,MAAA;IAC1B,KAAA;IACA,IAAA;IACA,WAAA;IACA,OAAA;IACA,gBAAA;IACA,eAAA,EAAiB,iBAAA;IACjB,UAAA,EAAY,oBAAA;IACZ,SAAA;IACA,WAAA;IACA,gBAAA,GAAmB,2BAAA;IACnB,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,2BAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,QAAA,GAAW,0BAAA;IACX,iBAAA;IACA,aAAA;IACA,OAAA;IACA,QAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAoDZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,uBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAmBX,YAAA,GACE,KAAA,UACA,MAAA,EAAQ,mBAAA,KACP,OAAA,CAAQ,oBAAA;EAOX,gBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAIX,mBAAA,GAA6B,KAAA,UAAe,OAAA,aAAkB,OAAA;EAc9D,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAa3C,mBAAA,GAA6B,KAAA,aAAgB,OAAA;EAK7C,oBAAA,GAA8B,KAAA,UAAe,UAAA,cAAsB,OAAA;EAAA,QAY3D,SAAA;EAAA,QAiBA,YAAA;EAAA,QAKA,aAAA;EAAA,QA+FA,oBAAA;EAAA,QAyGA,eAAA;EAAA,QAMA,eAAA;EAAA,QA6DA,YAAA;EAAA,QAOA,aAAA;EAAA,QAOA,kBAAA;AAAA"}
@@ -15,7 +15,6 @@ var AppRegistryService = class {
15
15
  this.instanceStorageService = new AppInstanceStorageService(appHomeService);
16
16
  }
17
17
  load = async () => {
18
- await this.appHomeService.ensureBaseDirectories();
19
18
  try {
20
19
  const raw = await readFile(this.appHomeService.getRegistryPath(), "utf-8");
21
20
  return this.parseRegistry(JSON.parse(raw));
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst SAFE_VERSION_PATTERN = /^[0-9A-Za-z]+(?:[._+-][0-9A-Za-z]+)*$/;\nconst SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppRegistryService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n await this.appHomeService.ensureBaseDirectories();\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n defaultInstance: AppInstanceRecord;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n security?: AppPlatformSecuritySummary;\n dataSchemaVersion: number;\n contentSha256?: string;\n enabled?: boolean;\n activate?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const currentPublisher = currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== params.publisher?.id) {\n throw new Error(\n `应用 ${params.appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${params.publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion: params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.defaultInstance.storage.dataDirectory,\n defaultInstance: params.defaultInstance,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n security: params.security,\n dataSchemaVersion: params.dataSchemaVersion,\n contentSha256: params.contentSha256,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n setVersionContentDigest = async (\n appId: string,\n version: string,\n contentSha256: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const installedVersion = record.installedVersions[version];\n if (!installedVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (installedVersion.contentSha256 && installedVersion.contentSha256 !== contentSha256) {\n throw new Error(`应用 ${appId}@${version} 已存在不同的代码完整性摘要。`);\n }\n return {\n ...record,\n installedVersions: {\n ...record.installedVersions,\n [version]: { ...installedVersion, contentSha256 },\n },\n };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n return await this.fileLockService.withLock(`${registryPath}.lock`, operation);\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n }\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) {\n throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n }\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.components`,\n ),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt = Object.values(installedVersions)\n .map((version) => version.installedAt)\n .filter((value): value is string => typeof value === \"string\")\n .sort()[0] ?? new Date(0).toISOString();\n const defaultInstance = this.parseDefaultInstance(\n app.defaultInstance,\n appId,\n dataDirectory,\n firstInstalledAt,\n );\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n publisher: app.publisher ?? installedVersions[activeVersion]?.publisher,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n dataDirectory: defaultInstance.storage.dataDirectory,\n defaultInstance,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private parseDefaultInstance = (\n rawInstance: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!rawInstance || typeof rawInstance !== \"object\" || Array.isArray(rawInstance)) {\n this.assertExactPath(\n dataDirectory,\n this.appHomeService.getAppDataDirectory(appId),\n `registry.apps.${appId}.dataDirectory`,\n );\n return this.instanceStorageService.buildLegacyDefaultInstance({\n appId,\n dataDirectory,\n createdAt,\n });\n }\n const instance = rawInstance as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (instance.publisherId !== undefined && typeof instance.publisherId !== \"string\") {\n throw new Error(`registry.apps.${appId}.defaultInstance.publisherId 无效。`);\n }\n const dataSchemaVersion = typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\"\n ? instance.createdAt\n : createdAt;\n const instanceDirectory = path.resolve(\n this.appHomeService.getAppInstanceDirectory(appId, \"default\"),\n );\n const expectedStorage = {\n layout: \"instance-v1\" as const,\n layoutVersion: 1 as const,\n instanceId: \"default\",\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n };\n if (instance.storage.layout === \"legacy\") {\n const expectedLegacyDataDirectory = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n expectedLegacyDataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: {\n ...expectedStorage,\n layout: \"legacy\",\n dataDirectory: path.resolve(expectedLegacyDataDirectory),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(expectedLegacyDataDirectory),\n };\n }\n for (const [field, expectedPath] of Object.entries({\n instanceDirectory: expectedStorage.instanceDirectory,\n dataDirectory: expectedStorage.dataDirectory,\n configDirectory: expectedStorage.configDirectory,\n stateDirectory: expectedStorage.stateDirectory,\n cacheDirectory: expectedStorage.cacheDirectory,\n temporaryDirectory: expectedStorage.temporaryDirectory,\n logsDirectory: expectedStorage.logsDirectory,\n })) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage] as string,\n expectedPath,\n `registry.apps.${appId}.defaultInstance.storage.${field}`,\n );\n }\n this.assertExactPath(\n dataDirectory,\n expectedStorage.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: expectedStorage,\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: instance.legacyDataDirectory,\n };\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) {\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n }\n };\n\n private parseComponents = (\n rawComponents: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (rawComponents === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawComponents)) {\n throw new Error(`${field} 必须是数组。`);\n }\n return rawComponents.map((rawComponent, index) => {\n const component = this.assertRecord(rawComponent, `${field}[${index}]`);\n const kind = component.kind;\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`${field}[${index}].kind 无效。`);\n }\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`${field}[${index}].id 不安全。`);\n }\n const relativeComponentPath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const expectedComponentDirectory = path.resolve(installDirectory, relativeComponentPath);\n const relativeToInstall = path.relative(\n path.resolve(installDirectory),\n expectedComponentDirectory,\n );\n if (\n !relativeToInstall ||\n relativeToInstall.startsWith(\"..\") ||\n path.isAbsolute(relativeToInstall)\n ) {\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n }\n const expectedManifestPath = path.join(\n expectedComponentDirectory,\n kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n expectedComponentDirectory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n expectedManifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind,\n id,\n path: relativeComponentPath,\n componentDirectory: expectedComponentDirectory,\n manifestPath: expectedManifestPath,\n };\n });\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\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":"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"}
1
+ {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst SAFE_VERSION_PATTERN = /^[0-9A-Za-z]+(?:[._+-][0-9A-Za-z]+)*$/;\nconst SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppRegistryService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n defaultInstance: AppInstanceRecord;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n security?: AppPlatformSecuritySummary;\n dataSchemaVersion: number;\n contentSha256?: string;\n enabled?: boolean;\n activate?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const currentPublisher = currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== params.publisher?.id) {\n throw new Error(\n `应用 ${params.appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${params.publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion: params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.defaultInstance.storage.dataDirectory,\n defaultInstance: params.defaultInstance,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n security: params.security,\n dataSchemaVersion: params.dataSchemaVersion,\n contentSha256: params.contentSha256,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n setVersionContentDigest = async (\n appId: string,\n version: string,\n contentSha256: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const installedVersion = record.installedVersions[version];\n if (!installedVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (installedVersion.contentSha256 && installedVersion.contentSha256 !== contentSha256) {\n throw new Error(`应用 ${appId}@${version} 已存在不同的代码完整性摘要。`);\n }\n return {\n ...record,\n installedVersions: {\n ...record.installedVersions,\n [version]: { ...installedVersion, contentSha256 },\n },\n };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n return await this.fileLockService.withLock(`${registryPath}.lock`, operation);\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n }\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) {\n throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n }\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.components`,\n ),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt = Object.values(installedVersions)\n .map((version) => version.installedAt)\n .filter((value): value is string => typeof value === \"string\")\n .sort()[0] ?? new Date(0).toISOString();\n const defaultInstance = this.parseDefaultInstance(\n app.defaultInstance,\n appId,\n dataDirectory,\n firstInstalledAt,\n );\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n publisher: app.publisher ?? installedVersions[activeVersion]?.publisher,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n dataDirectory: defaultInstance.storage.dataDirectory,\n defaultInstance,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private parseDefaultInstance = (\n rawInstance: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!rawInstance || typeof rawInstance !== \"object\" || Array.isArray(rawInstance)) {\n this.assertExactPath(\n dataDirectory,\n this.appHomeService.getAppDataDirectory(appId),\n `registry.apps.${appId}.dataDirectory`,\n );\n return this.instanceStorageService.buildLegacyDefaultInstance({\n appId,\n dataDirectory,\n createdAt,\n });\n }\n const instance = rawInstance as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (instance.publisherId !== undefined && typeof instance.publisherId !== \"string\") {\n throw new Error(`registry.apps.${appId}.defaultInstance.publisherId 无效。`);\n }\n const dataSchemaVersion = typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\"\n ? instance.createdAt\n : createdAt;\n const instanceDirectory = path.resolve(\n this.appHomeService.getAppInstanceDirectory(appId, \"default\"),\n );\n const expectedStorage = {\n layout: \"instance-v1\" as const,\n layoutVersion: 1 as const,\n instanceId: \"default\",\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n };\n if (instance.storage.layout === \"legacy\") {\n const expectedLegacyDataDirectory = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n expectedLegacyDataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: {\n ...expectedStorage,\n layout: \"legacy\",\n dataDirectory: path.resolve(expectedLegacyDataDirectory),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(expectedLegacyDataDirectory),\n };\n }\n for (const [field, expectedPath] of Object.entries({\n instanceDirectory: expectedStorage.instanceDirectory,\n dataDirectory: expectedStorage.dataDirectory,\n configDirectory: expectedStorage.configDirectory,\n stateDirectory: expectedStorage.stateDirectory,\n cacheDirectory: expectedStorage.cacheDirectory,\n temporaryDirectory: expectedStorage.temporaryDirectory,\n logsDirectory: expectedStorage.logsDirectory,\n })) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage] as string,\n expectedPath,\n `registry.apps.${appId}.defaultInstance.storage.${field}`,\n );\n }\n this.assertExactPath(\n dataDirectory,\n expectedStorage.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: expectedStorage,\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: instance.legacyDataDirectory,\n };\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) {\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n }\n };\n\n private parseComponents = (\n rawComponents: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (rawComponents === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawComponents)) {\n throw new Error(`${field} 必须是数组。`);\n }\n return rawComponents.map((rawComponent, index) => {\n const component = this.assertRecord(rawComponent, `${field}[${index}]`);\n const kind = component.kind;\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`${field}[${index}].kind 无效。`);\n }\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`${field}[${index}].id 不安全。`);\n }\n const relativeComponentPath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const expectedComponentDirectory = path.resolve(installDirectory, relativeComponentPath);\n const relativeToInstall = path.relative(\n path.resolve(installDirectory),\n expectedComponentDirectory,\n );\n if (\n !relativeToInstall ||\n relativeToInstall.startsWith(\"..\") ||\n path.isAbsolute(relativeToInstall)\n ) {\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n }\n const expectedManifestPath = path.join(\n expectedComponentDirectory,\n kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n expectedComponentDirectory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n expectedManifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind,\n id,\n path: relativeComponentPath,\n componentDirectory: expectedComponentDirectory,\n manifestPath: expectedManifestPath,\n };\n });\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\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":"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"}
@@ -37,7 +37,7 @@ type AppInfoResult = {
37
37
  dataDirectory: string;
38
38
  instance: AppInstanceRecord;
39
39
  storage: AppStorageContext;
40
- storageUsage: AppStorageUsage;
40
+ storageUsage?: AppStorageUsage;
41
41
  installedVersions: Array<{
42
42
  version: string;
43
43
  installDirectory: string;
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;;KAWY,uBAAA;AAAA,KAOA,yBAAA,IACV,KAAA,EAAO,uBAAA,YACG,OAAA;AAAA,KAEA,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,OAAA,EAAS,iBAAA;EACT,YAAA,EAAc,eAAA;EACd,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,aAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,OAAA,GAAU,iBAAA;EACV,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
1
+ {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;;KAWY,uBAAA;AAAA,KAOA,yBAAA,IACV,KAAA,EAAO,uBAAA,YACG,OAAA;AAAA,KAEA,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,OAAA,EAAS,iBAAA;EACT,YAAA,GAAe,eAAA;EACf,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,aAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,OAAA,GAAU,iBAAA;EACV,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
@@ -32,6 +32,33 @@ type AppInstanceRecord = {
32
32
  migratedAt?: string;
33
33
  legacyDataDirectory?: string;
34
34
  };
35
+ type AppInstanceMetadata = {
36
+ schemaVersion: 1;
37
+ appId: string;
38
+ instanceId: string;
39
+ publisherId?: string;
40
+ layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;
41
+ createdAt: string;
42
+ migratedAt?: string;
43
+ legacyDataDirectory?: string;
44
+ };
45
+ type AppInstanceInventoryEntry = {
46
+ appId: string;
47
+ instanceId: string;
48
+ publisherId?: string;
49
+ storage: AppStorageContext;
50
+ usage: AppStorageUsage;
51
+ createdAt: string;
52
+ migratedAt?: string;
53
+ };
54
+ type AppInstanceInventoryDiagnostic = {
55
+ instanceDirectory: string;
56
+ message: string;
57
+ };
58
+ type AppInstanceInventory = {
59
+ entries: AppInstanceInventoryEntry[];
60
+ diagnostics: AppInstanceInventoryDiagnostic[];
61
+ };
35
62
  //#endregion
36
- export { APP_STORAGE_LAYOUT_VERSION, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID };
63
+ export { APP_STORAGE_LAYOUT_VERSION, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceMetadata, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID };
37
64
  //# sourceMappingURL=app-storage.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-storage.types.d.ts","names":[],"sources":["../../src/types/app-storage.types.ts"],"mappings":";cAAa,uBAAA;AAAA,cACA,0BAAA;AAAA,KAED,gBAAA;AAAA,KAEA,iBAAA;EACV,MAAA,EAAQ,gBAAA;EACR,aAAA,SAAsB,0BAAA;EACtB,UAAA;EACA,iBAAA;EACA,aAAA;EACA,eAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,aAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,UAAA;EACA,cAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,iBAAA;EACV,EAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,iBAAA;EACA,SAAA;EACA,UAAA;EACA,mBAAA;AAAA"}
1
+ {"version":3,"file":"app-storage.types.d.ts","names":[],"sources":["../../src/types/app-storage.types.ts"],"mappings":";cAAa,uBAAA;AAAA,cACA,0BAAA;AAAA,KAED,gBAAA;AAAA,KAEA,iBAAA;EACV,MAAA,EAAQ,gBAAA;EACR,aAAA,SAAsB,0BAAA;EACtB,UAAA;EACA,iBAAA;EACA,aAAA;EACA,eAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,aAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,UAAA;EACA,cAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,iBAAA;EACV,EAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,iBAAA;EACA,SAAA;EACA,UAAA;EACA,mBAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,KAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA,SAAsB,0BAAA;EACtB,SAAA;EACA,UAAA;EACA,mBAAA;AAAA;AAAA,KAGU,yBAAA;EACV,KAAA;EACA,UAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,KAAA,EAAO,eAAA;EACP,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,8BAAA;EACV,iBAAA;EACA,OAAA;AAAA;AAAA,KAGU,oBAAA;EACV,OAAA,EAAS,yBAAA;EACT,WAAA,EAAa,8BAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-storage.types.js","names":[],"sources":["../../src/types/app-storage.types.ts"],"sourcesContent":["export const DEFAULT_APP_INSTANCE_ID = \"default\";\nexport const APP_STORAGE_LAYOUT_VERSION = 1;\n\nexport type AppStorageLayout = \"legacy\" | \"instance-v1\";\n\nexport type AppStorageContext = {\n layout: AppStorageLayout;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n instanceId: string;\n instanceDirectory: string;\n dataDirectory: string;\n configDirectory: string;\n stateDirectory: string;\n cacheDirectory: string;\n temporaryDirectory: string;\n logsDirectory: string;\n};\n\nexport type AppStorageUsage = {\n dataBytes: number;\n configBytes: number;\n stateBytes: number;\n cacheBytes: number;\n temporaryBytes: number;\n logsBytes: number;\n totalBytes: number;\n};\n\nexport type AppInstanceRecord = {\n id: string;\n publisherId?: string;\n storage: AppStorageContext;\n dataSchemaVersion: number;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n"],"mappings":";AAAA,MAAa,0BAA0B;AACvC,MAAa,6BAA6B"}
1
+ {"version":3,"file":"app-storage.types.js","names":[],"sources":["../../src/types/app-storage.types.ts"],"sourcesContent":["export const DEFAULT_APP_INSTANCE_ID = \"default\";\nexport const APP_STORAGE_LAYOUT_VERSION = 1;\n\nexport type AppStorageLayout = \"legacy\" | \"instance-v1\";\n\nexport type AppStorageContext = {\n layout: AppStorageLayout;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n instanceId: string;\n instanceDirectory: string;\n dataDirectory: string;\n configDirectory: string;\n stateDirectory: string;\n cacheDirectory: string;\n temporaryDirectory: string;\n logsDirectory: string;\n};\n\nexport type AppStorageUsage = {\n dataBytes: number;\n configBytes: number;\n stateBytes: number;\n cacheBytes: number;\n temporaryBytes: number;\n logsBytes: number;\n totalBytes: number;\n};\n\nexport type AppInstanceRecord = {\n id: string;\n publisherId?: string;\n storage: AppStorageContext;\n dataSchemaVersion: number;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nexport type AppInstanceMetadata = {\n schemaVersion: 1;\n appId: string;\n instanceId: string;\n publisherId?: string;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nexport type AppInstanceInventoryEntry = {\n appId: string;\n instanceId: string;\n publisherId?: string;\n storage: AppStorageContext;\n usage: AppStorageUsage;\n createdAt: string;\n migratedAt?: string;\n};\n\nexport type AppInstanceInventoryDiagnostic = {\n instanceDirectory: string;\n message: string;\n};\n\nexport type AppInstanceInventory = {\n entries: AppInstanceInventoryEntry[];\n diagnostics: AppInstanceInventoryDiagnostic[];\n};\n"],"mappings":";AAAA,MAAa,0BAA0B;AACvC,MAAa,6BAA6B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",