@nextclaw/app-runtime 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/index.d.ts +6 -2
  2. package/dist/index.js +5 -1
  3. package/dist/package.js +1 -1
  4. package/dist/services/app-home.service.d.ts +4 -0
  5. package/dist/services/app-home.service.d.ts.map +1 -1
  6. package/dist/services/app-home.service.js +15 -1
  7. package/dist/services/app-home.service.js.map +1 -1
  8. package/dist/services/app-install-source.service.js +14 -1
  9. package/dist/services/app-install-source.service.js.map +1 -1
  10. package/dist/services/app-installation-filesystem.service.js +68 -0
  11. package/dist/services/app-installation-filesystem.service.js.map +1 -0
  12. package/dist/services/app-installation-integrity.service.d.ts +20 -0
  13. package/dist/services/app-installation-integrity.service.d.ts.map +1 -0
  14. package/dist/services/app-installation-integrity.service.js +84 -0
  15. package/dist/services/app-installation-integrity.service.js.map +1 -0
  16. package/dist/services/app-installation-lifecycle.service.js +110 -0
  17. package/dist/services/app-installation-lifecycle.service.js.map +1 -0
  18. package/dist/services/app-installation.service.d.ts +21 -11
  19. package/dist/services/app-installation.service.d.ts.map +1 -1
  20. package/dist/services/app-installation.service.js +262 -280
  21. package/dist/services/app-installation.service.js.map +1 -1
  22. package/dist/services/app-instance-storage.service.d.ts +49 -0
  23. package/dist/services/app-instance-storage.service.d.ts.map +1 -0
  24. package/dist/services/app-instance-storage.service.js +256 -0
  25. package/dist/services/app-instance-storage.service.js.map +1 -0
  26. package/dist/services/app-manifest.service.d.ts +5 -1
  27. package/dist/services/app-manifest.service.d.ts.map +1 -1
  28. package/dist/services/app-manifest.service.js +59 -3
  29. package/dist/services/app-manifest.service.js.map +1 -1
  30. package/dist/services/app-publish.service.js +1 -1
  31. package/dist/services/app-publish.service.js.map +1 -1
  32. package/dist/services/app-registry.service.d.ts +13 -3
  33. package/dist/services/app-registry.service.d.ts.map +1 -1
  34. package/dist/services/app-registry.service.js +143 -13
  35. package/dist/services/app-registry.service.js.map +1 -1
  36. package/dist/services/file-lock.service.d.ts +20 -0
  37. package/dist/services/file-lock.service.d.ts.map +1 -0
  38. package/dist/services/file-lock.service.js +156 -0
  39. package/dist/services/file-lock.service.js.map +1 -0
  40. package/dist/types/app-installation.types.d.ts +7 -0
  41. package/dist/types/app-installation.types.d.ts.map +1 -1
  42. package/dist/types/app-manifest.types.d.ts +22 -1
  43. package/dist/types/app-manifest.types.d.ts.map +1 -1
  44. package/dist/types/app-manifest.types.js.map +1 -1
  45. package/dist/types/app-registry.types.d.ts +7 -1
  46. package/dist/types/app-registry.types.d.ts.map +1 -1
  47. package/dist/types/app-storage.types.d.ts +37 -0
  48. package/dist/types/app-storage.types.d.ts.map +1 -0
  49. package/dist/types/app-storage.types.js +7 -0
  50. package/dist/types/app-storage.types.js.map +1 -0
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.service.js","names":[],"sources":["../../src/services/app-installation.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, cp, mkdir, readdir, rename, 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 {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.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 { AppInstallSourceService } 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\nexport class AppInstallationService {\n private readonly installSourceService: AppInstallSourceService;\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 ) {\n this.installSourceService = new AppInstallSourceService(\n this.manifestService,\n this.remoteRegistryClient,\n );\n }\n\n install = async (\n appSource: string,\n options?: {\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n },\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 if (source.kind === \"registry\") {\n await onProgress?.(\"downloading\");\n }\n const bundlePath =\n source.kind === \"directory\"\n ? (await this.bundleService.packAppDirectory({\n appDirectory: source.appDirectory,\n outputPath: path.join(tempDirectory, \"app.napp\"),\n })).bundlePath\n : source.kind === \"bundle\"\n ? source.bundlePath\n : (\n await this.remoteRegistryClient.downloadBundle({\n resolution: source.registryResolution,\n targetDirectory: tempDirectory,\n })\n ).bundlePath;\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.materializeDistribution({\n appDirectory: extractedDirectory,\n distributionMode: extractedMetadata.metadata.distributionMode,\n });\n const manifestBundle = await this.manifestService.load(extractedDirectory);\n const installDirectory = this.appHomeService.getInstallDirectory(\n manifestBundle.manifest.id,\n manifestBundle.manifest.version,\n );\n if (\n source.kind === \"registry\" &&\n manifestBundle.manifest.id !== source.registryResolution.appId\n ) {\n throw new Error(\n `bundle manifest.appId 与 registry 请求不一致:期望 ${source.registryResolution.appId},实际 ${manifestBundle.manifest.id}`,\n );\n }\n await onProgress?.(\"installing\");\n const dataDirectory = this.appHomeService.getAppDataDirectory(manifestBundle.manifest.id);\n await this.copyToImmutableInstallDirectory({\n appId: manifestBundle.manifest.id,\n appVersion: manifestBundle.manifest.version,\n extractedDirectory,\n installDirectory,\n });\n await mkdir(dataDirectory, { recursive: true });\n await onProgress?.(\"finalizing\");\n let registryRecord;\n try {\n registryRecord = await this.registryService.upsertInstallation({\n appId: manifestBundle.manifest.id,\n name: manifestBundle.manifest.name,\n description: manifestBundle.manifest.description,\n version: manifestBundle.manifest.version,\n installDirectory,\n dataDirectory,\n sourceKind: source.kind,\n distributionMode:\n 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 : {},\n registryUrl:\n source.kind === \"registry\" ? source.registryResolution.registryUrl : undefined,\n bundleUrl:\n source.kind === \"registry\" ? source.registryResolution.bundleUrl : undefined,\n sha256: source.kind === \"registry\" ? source.registryResolution.sha256 : undefined,\n publisher:\n source.kind === \"registry\" ? source.registryResolution.publisher : undefined,\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 });\n } catch (error) {\n await rm(installDirectory, { recursive: true, force: true });\n throw error;\n }\n const activeVersionRecord = registryRecord.installedVersions[registryRecord.activeVersion];\n return {\n appId: registryRecord.appId,\n name: registryRecord.name,\n version: registryRecord.activeVersion,\n installDirectory,\n dataDirectory,\n sourceKind: source.kind,\n sourceRef: source.sourceRef,\n distributionMode:\n registryRecord.installedVersions[registryRecord.activeVersion]?.distributionMode,\n permissions:\n registryRecord.installedVersions[registryRecord.activeVersion]?.permissions ?? {},\n registryUrl:\n registryRecord.installedVersions[registryRecord.activeVersion]?.registryUrl,\n bundleUrl:\n registryRecord.installedVersions[registryRecord.activeVersion]?.bundleUrl,\n sha256: registryRecord.installedVersions[registryRecord.activeVersion]?.sha256,\n publisher:\n registryRecord.installedVersions[registryRecord.activeVersion]?.publisher,\n enabled: registryRecord.enabled,\n manifestSchemaVersion: activeVersionRecord?.manifestSchemaVersion ?? 1,\n components: activeVersionRecord?.components,\n primaryPanelId: activeVersionRecord?.primaryPanelId,\n };\n } finally {\n await rm(tempDirectory, { recursive: true, force: true });\n }\n };\n\n update = async (\n appId: string,\n options?: {\n version?: string;\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n },\n ): Promise<AppUpdateResult> => {\n const { onProgress, registryUrl: requestedRegistryUrl, version } = options ?? {};\n await onProgress?.(\"resolving\");\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const activeVersionRecord = appRecord.installedVersions[appRecord.activeVersion];\n const registryUrl =\n requestedRegistryUrl ??\n activeVersionRecord?.registryUrl ??\n (await this.registryConfigService.getSnapshot()).currentUrl;\n const resolution = await this.remoteRegistryClient.resolve({\n appId,\n version,\n registryUrl,\n });\n if (resolution.version === appRecord.activeVersion) {\n if (!activeVersionRecord) {\n throw new Error(`已安装应用缺少激活版本:${appId}`);\n }\n return {\n appId: appRecord.appId,\n name: appRecord.name,\n version: appRecord.activeVersion,\n previousVersion: appRecord.activeVersion,\n installDirectory: activeVersionRecord.installDirectory,\n dataDirectory: appRecord.dataDirectory,\n sourceKind: activeVersionRecord.sourceKind,\n sourceRef: activeVersionRecord.sourceRef,\n permissions: activeVersionRecord.permissions,\n registryUrl: activeVersionRecord.registryUrl,\n bundleUrl: activeVersionRecord.bundleUrl,\n sha256: activeVersionRecord.sha256,\n publisher: activeVersionRecord.publisher,\n enabled: appRecord.enabled,\n manifestSchemaVersion: activeVersionRecord.manifestSchemaVersion,\n components: activeVersionRecord.components,\n primaryPanelId: activeVersionRecord.primaryPanelId,\n updated: false,\n };\n }\n const installedTarget = appRecord.installedVersions[resolution.version];\n if (installedTarget) {\n await onProgress?.(\"verifying\");\n await onProgress?.(\"installing\");\n await this.rollback(appId, resolution.version);\n await onProgress?.(\"finalizing\");\n return {\n appId: appRecord.appId,\n name: appRecord.name,\n version: resolution.version,\n previousVersion: appRecord.activeVersion,\n installDirectory: installedTarget.installDirectory,\n dataDirectory: appRecord.dataDirectory,\n sourceKind: installedTarget.sourceKind,\n distributionMode: installedTarget.distributionMode,\n sourceRef: installedTarget.sourceRef,\n permissions: installedTarget.permissions,\n registryUrl: installedTarget.registryUrl,\n bundleUrl: installedTarget.bundleUrl,\n sha256: installedTarget.sha256,\n publisher: installedTarget.publisher,\n enabled: appRecord.enabled,\n manifestSchemaVersion: installedTarget.manifestSchemaVersion,\n components: installedTarget.components,\n primaryPanelId: installedTarget.primaryPanelId,\n updated: true,\n };\n }\n const installResult = await this.install(`${appId}@${resolution.version}`, {\n registryUrl,\n onProgress,\n });\n return {\n ...installResult,\n previousVersion: appRecord.activeVersion,\n updated: true,\n };\n };\n\n uninstall = async (\n appId: string,\n purgeData: boolean,\n ): Promise<AppUninstallResult> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const removedVersions = Object.keys(appRecord.installedVersions).sort((left, right) =>\n left.localeCompare(right),\n );\n const stagedPaths: Array<{ originalPath: string; stagedPath: string }> = [];\n const stagePath = async (originalPath: string): Promise<void> => {\n if (!await this.pathExists(originalPath)) {\n return;\n }\n const stagedPath = `${originalPath}.uninstalling-${randomUUID()}`;\n await rename(originalPath, stagedPath);\n stagedPaths.push({ originalPath, stagedPath });\n };\n const restoreStagedPaths = async (): Promise<void> => {\n for (const entry of [...stagedPaths].reverse()) {\n if (await this.pathExists(entry.stagedPath)) {\n await rename(entry.stagedPath, entry.originalPath);\n }\n }\n };\n try {\n for (const versionRecord of Object.values(appRecord.installedVersions)) {\n await stagePath(versionRecord.installDirectory);\n }\n if (purgeData) {\n await stagePath(appRecord.dataDirectory);\n }\n const removedRecord = await this.registryService.removeApp(appId);\n if (!removedRecord) {\n throw new Error(`卸载过程中应用记录已发生变化:${appId}`);\n }\n } catch (error) {\n try {\n await restoreStagedPaths();\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `卸载 ${appId} 失败,且无法完全恢复已暂存文件。`,\n );\n }\n throw error;\n }\n await Promise.all(stagedPaths.map((entry) =>\n rm(entry.stagedPath, { recursive: true, force: true }),\n ));\n return {\n appId,\n removedVersions,\n dataRemoved: 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 const 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(record.dataDirectory)));\n\n const packageDirectories = await this.readDirectories(this.appHomeService.getPackagesDirectory());\n for (const appDirectory of packageDirectories) {\n await this.reconcileGeneratedSiblings(appDirectory, referencedInstallPaths);\n }\n await this.reconcileGeneratedSiblings(\n this.appHomeService.getDataDirectory(),\n referencedDataPaths,\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 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 })),\n grants: appRecord.grants,\n };\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppActivationResult> => {\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 rollback = async (appId: string, version: string): Promise<AppRollbackResult> => {\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 const targetManifest = await this.manifestService.load(targetVersion.installDirectory);\n if (targetManifest.manifest.id !== appId || targetManifest.manifest.version !== version) {\n throw new Error(`回滚目标 ${appId}@${version} 校验失败。`);\n }\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 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 return {\n appDirectory: activeVersion.installDirectory,\n appId: appRecord.appId,\n dataDirectory: appRecord.dataDirectory,\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 private copyToImmutableInstallDirectory = async (params: {\n appId: string;\n appVersion: string;\n extractedDirectory: string;\n installDirectory: string;\n }): Promise<void> => {\n const { appId, appVersion, extractedDirectory, installDirectory } = params;\n if (await this.pathExists(installDirectory)) {\n throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);\n }\n await mkdir(path.dirname(installDirectory), { recursive: true });\n const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;\n try {\n await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });\n const stagedManifest = await this.manifestService.load(stagedInstallDirectory);\n if (\n stagedManifest.manifest.id !== appId ||\n stagedManifest.manifest.version !== appVersion\n ) {\n throw new Error(\"staging manifest 与已验证 bundle 身份不一致。\");\n }\n await rename(stagedInstallDirectory, installDirectory);\n } catch (error) {\n await rm(stagedInstallDirectory, { recursive: true, force: true });\n throw error;\n }\n };\n\n private materializeDistribution = async (params: {\n appDirectory: string;\n distributionMode: AppDistributionMode;\n }): Promise<void> => {\n if (params.distributionMode !== \"source\") {\n return;\n }\n const manifestBundle = await this.manifestService.load(params.appDirectory);\n if (!isAppStandaloneManifestBundle(manifestBundle)) {\n throw new Error(\"schema v2 组合包不允许运行安装期 build。\");\n }\n if (manifestBundle.manifest.main.kind !== \"wasi-http-component\") {\n return;\n }\n await this.buildService.build({\n appDirectory: params.appDirectory,\n install: true,\n });\n };\n\n private reconcileGeneratedSiblings = async (\n parentDirectory: string,\n referencedPaths: Set<string>,\n ): Promise<void> => {\n for (const entry of await this.readDirectories(parentDirectory)) {\n const entryName = path.basename(entry);\n const stagingMarker = \".staging-\";\n const uninstallingMarker = \".uninstalling-\";\n if (entryName.includes(stagingMarker)) {\n await rm(entry, { recursive: true, force: true });\n continue;\n }\n const markerIndex = entryName.indexOf(uninstallingMarker);\n if (markerIndex < 0) {\n continue;\n }\n const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));\n if (referencedPaths.has(path.resolve(originalPath)) && !await this.pathExists(originalPath)) {\n await rename(entry, originalPath);\n } else {\n await rm(entry, { recursive: true, force: true });\n }\n }\n };\n\n private readDirectories = async (directory: string): Promise<string[]> => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(directory, entry.name));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private 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-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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"}
@@ -0,0 +1,49 @@
1
+ import { AppHomeService } from "./app-home.service.js";
2
+ import { AppInstanceRecord, AppStorageContext, AppStorageUsage } from "../types/app-storage.types.js";
3
+
4
+ //#region src/services/app-instance-storage.service.d.ts
5
+ declare class AppInstanceStorageService {
6
+ private readonly appHomeService;
7
+ private readonly fileLockService;
8
+ constructor(appHomeService?: AppHomeService);
9
+ materializeDefaultInstance: (params: {
10
+ appId: string;
11
+ publisherId?: string;
12
+ legacyDataDirectory?: string;
13
+ dataSchemaVersion?: number;
14
+ }) => Promise<AppInstanceRecord>;
15
+ materialize: (params: {
16
+ appId: string;
17
+ instanceId: string;
18
+ instanceDirectory: string;
19
+ publisherId?: string;
20
+ legacyDataDirectory?: string;
21
+ dataSchemaVersion?: number;
22
+ }) => Promise<AppInstanceRecord>;
23
+ private materializeUnlocked;
24
+ private materializeNewInstance;
25
+ buildLegacyDefaultInstance: (params: {
26
+ appId: string;
27
+ dataDirectory: string;
28
+ createdAt: string;
29
+ }) => AppInstanceRecord;
30
+ measureUsage: (storage: AppStorageContext) => Promise<AppStorageUsage>;
31
+ rollbackNewInstance: (params: {
32
+ instance: AppInstanceRecord;
33
+ legacyDataDirectory?: string;
34
+ }) => Promise<void>;
35
+ private resolveExistingInstance;
36
+ private buildContext;
37
+ private ensureStorageDirectories;
38
+ private readMetadata;
39
+ private toInstanceRecord;
40
+ private assertMetadataIdentity;
41
+ private bindPublisher;
42
+ private assertSafeId;
43
+ private measureDirectory;
44
+ private pathExists;
45
+ private isMissingFileError;
46
+ }
47
+ //#endregion
48
+ export { AppInstanceStorageService };
49
+ //# sourceMappingURL=app-instance-storage.service.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,256 @@
1
+ import { AppHomeService } from "./app-home.service.js";
2
+ import { FileLockService } from "./file-lock.service.js";
3
+ import { DEFAULT_APP_INSTANCE_ID } from "../types/app-storage.types.js";
4
+ import { randomUUID } from "node:crypto";
5
+ import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ //#region src/services/app-instance-storage.service.ts
8
+ const STORAGE_METADATA_FILE = "metadata.json";
9
+ const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
10
+ var AppInstanceStorageService = class {
11
+ fileLockService = new FileLockService();
12
+ constructor(appHomeService = new AppHomeService()) {
13
+ this.appHomeService = appHomeService;
14
+ }
15
+ materializeDefaultInstance = async (params) => {
16
+ return await this.materialize({
17
+ ...params,
18
+ instanceId: DEFAULT_APP_INSTANCE_ID,
19
+ instanceDirectory: this.appHomeService.getAppInstanceDirectory(params.appId, DEFAULT_APP_INSTANCE_ID)
20
+ });
21
+ };
22
+ materialize = async (params) => {
23
+ this.assertSafeId(params.appId, "appId");
24
+ this.assertSafeId(params.instanceId, "instanceId");
25
+ const instanceDirectory = path.resolve(params.instanceDirectory);
26
+ return await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => await this.materializeUnlocked({
27
+ ...params,
28
+ instanceDirectory
29
+ }));
30
+ };
31
+ materializeUnlocked = async (params) => {
32
+ const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;
33
+ const existingInstance = await this.resolveExistingInstance({
34
+ appId,
35
+ dataSchemaVersion,
36
+ instanceDirectory,
37
+ instanceId,
38
+ publisherId
39
+ });
40
+ if (existingInstance) return existingInstance;
41
+ return await this.materializeNewInstance(params);
42
+ };
43
+ materializeNewInstance = async (params) => {
44
+ const { appId, dataSchemaVersion, instanceDirectory, instanceId, legacyDataDirectory: legacyDataPath, publisherId } = params;
45
+ await mkdir(path.dirname(instanceDirectory), { recursive: true });
46
+ const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;
47
+ const legacyDataDirectory = legacyDataPath ? path.resolve(legacyDataPath) : void 0;
48
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
49
+ let movedLegacyData = false;
50
+ try {
51
+ const stagingStorage = this.buildContext(stagingDirectory, instanceId);
52
+ await mkdir(stagingDirectory, { recursive: false });
53
+ if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {
54
+ await rename(legacyDataDirectory, stagingStorage.dataDirectory);
55
+ movedLegacyData = true;
56
+ }
57
+ await this.ensureStorageDirectories(stagingStorage);
58
+ const metadata = {
59
+ schemaVersion: 1,
60
+ appId,
61
+ instanceId,
62
+ publisherId,
63
+ layoutVersion: 1,
64
+ createdAt,
65
+ ...movedLegacyData ? {
66
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
67
+ legacyDataDirectory
68
+ } : {}
69
+ };
70
+ await writeFile(path.join(stagingDirectory, STORAGE_METADATA_FILE), `${JSON.stringify(metadata, null, 2)}\n`, {
71
+ encoding: "utf8",
72
+ mode: 384,
73
+ flag: "wx"
74
+ });
75
+ await rename(stagingDirectory, instanceDirectory);
76
+ return this.toInstanceRecord(metadata, this.buildContext(instanceDirectory, instanceId), dataSchemaVersion);
77
+ } catch (error) {
78
+ if (movedLegacyData && legacyDataDirectory && !await this.pathExists(legacyDataDirectory)) {
79
+ const stagedDataDirectory = path.join(stagingDirectory, "data");
80
+ if (await this.pathExists(stagedDataDirectory)) await rename(stagedDataDirectory, legacyDataDirectory);
81
+ }
82
+ await rm(stagingDirectory, {
83
+ recursive: true,
84
+ force: true
85
+ });
86
+ if (await this.pathExists(instanceDirectory)) {
87
+ const racedMetadata = await this.readMetadata(instanceDirectory);
88
+ if (racedMetadata) {
89
+ this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);
90
+ const storage = this.buildContext(instanceDirectory, instanceId);
91
+ await this.ensureStorageDirectories(storage);
92
+ return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);
93
+ }
94
+ }
95
+ throw error;
96
+ }
97
+ };
98
+ buildLegacyDefaultInstance = (params) => {
99
+ const { appId, createdAt, dataDirectory } = params;
100
+ const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, DEFAULT_APP_INSTANCE_ID);
101
+ return {
102
+ id: DEFAULT_APP_INSTANCE_ID,
103
+ publisherId: void 0,
104
+ storage: {
105
+ ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),
106
+ layout: "legacy",
107
+ dataDirectory: path.resolve(dataDirectory)
108
+ },
109
+ dataSchemaVersion: 1,
110
+ createdAt,
111
+ legacyDataDirectory: path.resolve(dataDirectory)
112
+ };
113
+ };
114
+ measureUsage = async (storage) => {
115
+ const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] = await Promise.all([
116
+ this.measureDirectory(storage.dataDirectory),
117
+ this.measureDirectory(storage.configDirectory),
118
+ this.measureDirectory(storage.stateDirectory),
119
+ this.measureDirectory(storage.cacheDirectory),
120
+ this.measureDirectory(storage.temporaryDirectory),
121
+ this.measureDirectory(storage.logsDirectory)
122
+ ]);
123
+ return {
124
+ dataBytes,
125
+ configBytes,
126
+ stateBytes,
127
+ cacheBytes,
128
+ temporaryBytes,
129
+ logsBytes,
130
+ totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes
131
+ };
132
+ };
133
+ rollbackNewInstance = async (params) => {
134
+ const { instance, legacyDataDirectory: legacyDataPath } = params;
135
+ const instanceDirectory = instance.storage.instanceDirectory;
136
+ if (!await this.readMetadata(instanceDirectory)) return;
137
+ const legacyDataDirectory = legacyDataPath ? path.resolve(legacyDataPath) : void 0;
138
+ if (legacyDataDirectory && !await this.pathExists(legacyDataDirectory) && await this.pathExists(instance.storage.dataDirectory)) {
139
+ await mkdir(path.dirname(legacyDataDirectory), { recursive: true });
140
+ await rename(instance.storage.dataDirectory, legacyDataDirectory);
141
+ }
142
+ await rm(instanceDirectory, {
143
+ recursive: true,
144
+ force: true
145
+ });
146
+ };
147
+ resolveExistingInstance = async (params) => {
148
+ const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;
149
+ let metadata = await this.readMetadata(instanceDirectory);
150
+ if (!metadata) return;
151
+ this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);
152
+ if (publisherId && !metadata.publisherId) metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);
153
+ const storage = this.buildContext(instanceDirectory, instanceId);
154
+ await this.ensureStorageDirectories(storage);
155
+ return this.toInstanceRecord(metadata, storage, dataSchemaVersion);
156
+ };
157
+ buildContext = (instanceDirectory, instanceId) => ({
158
+ layout: "instance-v1",
159
+ layoutVersion: 1,
160
+ instanceId,
161
+ instanceDirectory,
162
+ dataDirectory: path.join(instanceDirectory, "data"),
163
+ configDirectory: path.join(instanceDirectory, "config"),
164
+ stateDirectory: path.join(instanceDirectory, "state"),
165
+ cacheDirectory: path.join(instanceDirectory, "cache"),
166
+ temporaryDirectory: path.join(instanceDirectory, "tmp"),
167
+ logsDirectory: path.join(instanceDirectory, "logs")
168
+ });
169
+ ensureStorageDirectories = async (storage) => {
170
+ await Promise.all([
171
+ mkdir(storage.dataDirectory, { recursive: true }),
172
+ mkdir(storage.configDirectory, { recursive: true }),
173
+ mkdir(storage.stateDirectory, { recursive: true }),
174
+ mkdir(storage.cacheDirectory, { recursive: true }),
175
+ mkdir(storage.temporaryDirectory, { recursive: true }),
176
+ mkdir(storage.logsDirectory, { recursive: true })
177
+ ]);
178
+ };
179
+ readMetadata = async (instanceDirectory) => {
180
+ try {
181
+ const raw = JSON.parse(await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), "utf8"));
182
+ if (raw.schemaVersion !== 1 || raw.layoutVersion !== 1 || typeof raw.appId !== "string" || typeof raw.instanceId !== "string" || raw.publisherId !== void 0 && typeof raw.publisherId !== "string" || typeof raw.createdAt !== "string") throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);
183
+ return raw;
184
+ } catch (error) {
185
+ if (this.isMissingFileError(error)) return;
186
+ throw error;
187
+ }
188
+ };
189
+ toInstanceRecord = (metadata, storage, dataSchemaVersion = 1) => ({
190
+ id: metadata.instanceId,
191
+ publisherId: metadata.publisherId,
192
+ storage,
193
+ dataSchemaVersion,
194
+ createdAt: metadata.createdAt,
195
+ migratedAt: metadata.migratedAt,
196
+ legacyDataDirectory: metadata.legacyDataDirectory
197
+ });
198
+ 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}`);
200
+ if (metadata.publisherId && metadata.publisherId !== publisherId) throw new Error(`App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? "未验证本地来源"} 接管。`);
201
+ };
202
+ bindPublisher = async (instanceDirectory, metadata, publisherId) => {
203
+ const nextMetadata = {
204
+ ...metadata,
205
+ publisherId
206
+ };
207
+ const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);
208
+ const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;
209
+ try {
210
+ await writeFile(temporaryPath, `${JSON.stringify(nextMetadata, null, 2)}\n`, {
211
+ encoding: "utf8",
212
+ mode: 384,
213
+ flag: "wx"
214
+ });
215
+ await rename(temporaryPath, metadataPath);
216
+ return nextMetadata;
217
+ } finally {
218
+ await rm(temporaryPath, { force: true });
219
+ }
220
+ };
221
+ assertSafeId = (value, field) => {
222
+ if (!SAFE_ID_PATTERN.test(value)) throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);
223
+ };
224
+ measureDirectory = async (directory) => {
225
+ try {
226
+ const entries = await readdir(directory, { withFileTypes: true });
227
+ let bytes = 0;
228
+ for (const entry of entries) {
229
+ const entryPath = path.join(directory, entry.name);
230
+ if (entry.isSymbolicLink()) continue;
231
+ if (entry.isDirectory()) {
232
+ bytes += await this.measureDirectory(entryPath);
233
+ continue;
234
+ }
235
+ if (entry.isFile()) bytes += (await stat(entryPath)).size;
236
+ }
237
+ return bytes;
238
+ } catch (error) {
239
+ if (this.isMissingFileError(error)) return 0;
240
+ throw error;
241
+ }
242
+ };
243
+ pathExists = async (targetPath) => {
244
+ try {
245
+ await access(targetPath);
246
+ return true;
247
+ } catch {
248
+ return false;
249
+ }
250
+ };
251
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
252
+ };
253
+ //#endregion
254
+ export { AppInstanceStorageService };
255
+
256
+ //# sourceMappingURL=app-instance-storage.service.js.map
@@ -0,0 +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,4 +1,4 @@
1
- import { AppManifestBundle, AppManifestSummary } from "../types/app-manifest.types.js";
1
+ import { AppComponentManifest, AppManifestBundle, AppManifestSummary, AppPlatformSecuritySummary } from "../types/app-manifest.types.js";
2
2
 
3
3
  //#region src/services/app-manifest.service.d.ts
4
4
  declare class AppManifestService {
@@ -7,12 +7,16 @@ declare class AppManifestService {
7
7
  private loadStandaloneBundle;
8
8
  private loadComponentBundle;
9
9
  private parseManifest;
10
+ resolvePlatformSecurity: (manifest: AppComponentManifest) => AppPlatformSecuritySummary;
10
11
  private parseCommonManifest;
11
12
  private parseMain;
12
13
  private parseUi;
13
14
  private parseComponents;
14
15
  private parseEngines;
15
16
  private parsePresentation;
17
+ private parseRuntime;
18
+ private parseAppStorage;
19
+ private assertRuntimeMatchesComponents;
16
20
  private readComponentId;
17
21
  private parsePermissions;
18
22
  private parseDocumentAccess;
@@ -1 +1 @@
1
- {"version":3,"file":"app-manifest.service.d.ts","names":[],"sources":["../../src/services/app-manifest.service.ts"],"mappings":";;;cAmBa,kBAAA;EACX,IAAA,GAAc,YAAA,aAAuB,OAAA,CAAQ,iBAAA;EAU7C,SAAA,GAAa,MAAA,EAAQ,iBAAA,KAAoB,kBAAA;EAAA,QAoCjC,oBAAA;EAAA,QA8BA,mBAAA;EAAA,QA8DA,aAAA;EAAA,QAyBA,mBAAA;EAAA,QAcA,SAAA;EAAA,QAoBA,OAAA;EAAA,QAKA,eAAA;EAAA,QA+BA,YAAA;EAAA,QAUA,iBAAA;EAAA,QAeA,eAAA;EAAA,QA0BA,gBAAA;EAAA,QAaA,mBAAA;EAAA,QAwBA,gBAAA;EAAA,QAUA,YAAA;EAAA,QAQA,iBAAA;EAAA,QAYA,kBAAA;EAAA,QAaA,uBAAA;EAAA,QAcA,aAAA;EAAA,QAUA,qBAAA;EAAA,QAWA,YAAA;EAAA,QAOA,kBAAA;EAAA,QAOA,kBAAA;EAAA,QAIA,UAAA;EAAA,QAOA,WAAA;AAAA"}
1
+ {"version":3,"file":"app-manifest.service.d.ts","names":[],"sources":["../../src/services/app-manifest.service.ts"],"mappings":";;;cAoBa,kBAAA;EACX,IAAA,GAAc,YAAA,aAAuB,OAAA,CAAQ,iBAAA;EAU7C,SAAA,GAAa,MAAA,EAAQ,iBAAA,KAAoB,kBAAA;EAAA,QAqCjC,oBAAA;EAAA,QA8BA,mBAAA;EAAA,QA8DA,aAAA;EA+BR,uBAAA,GACE,QAAA,EAAU,oBAAA,KACT,0BAAA;EAAA,QAgCK,mBAAA;EAAA,QAcA,SAAA;EAAA,QAoBA,OAAA;EAAA,QAKA,eAAA;EAAA,QA+BA,YAAA;EAAA,QAUA,iBAAA;EAAA,QAeA,YAAA;EAAA,QAcA,eAAA;EAAA,QAkBA,8BAAA;EAAA,QAgBA,eAAA;EAAA,QA0BA,gBAAA;EAAA,QAaA,mBAAA;EAAA,QAwBA,gBAAA;EAAA,QAUA,YAAA;EAAA,QAQA,iBAAA;EAAA,QAeA,kBAAA;EAAA,QAaA,uBAAA;EAAA,QAcA,aAAA;EAAA,QAUA,qBAAA;EAAA,QAWA,YAAA;EAAA,QAOA,kBAAA;EAAA,QAOA,kBAAA;EAAA,QAIA,UAAA;EAAA,QAOA,WAAA;AAAA"}
@@ -23,7 +23,8 @@ var AppManifestService = class {
23
23
  manifestPath: componentBundle.manifestPath,
24
24
  iconPath: componentBundle.iconPath,
25
25
  primaryPanelId: componentBundle.primaryPanelId,
26
- components: componentBundle.components
26
+ components: componentBundle.components,
27
+ security: this.resolvePlatformSecurity(componentBundle.manifest)
27
28
  };
28
29
  }
29
30
  const standaloneBundle = bundle;
@@ -102,12 +103,39 @@ var AppManifestService = class {
102
103
  ui: this.parseUi(candidate.ui),
103
104
  permissions: this.parsePermissions(candidate.permissions)
104
105
  };
106
+ const components = this.parseComponents(candidate.components);
107
+ const runtime = this.parseRuntime(candidate.runtime);
108
+ this.assertRuntimeMatchesComponents(runtime, components);
105
109
  return {
106
110
  schemaVersion: 2,
107
111
  ...common,
108
112
  engines: this.parseEngines(candidate.engines),
109
113
  presentation: this.parsePresentation(candidate.presentation),
110
- components: this.parseComponents(candidate.components)
114
+ runtime,
115
+ storage: this.parseAppStorage(candidate.storage),
116
+ permissions: this.parsePermissions(candidate.permissions),
117
+ components
118
+ };
119
+ };
120
+ resolvePlatformSecurity = (manifest) => {
121
+ const hasServiceComponents = manifest.components.some((component) => component.kind === "service");
122
+ const runtimeProfile = manifest.runtime?.profile ?? (hasServiceComponents ? "native-process" : "panel-only");
123
+ const isolation = runtimeProfile === "panel-only" ? "sandboxed" : runtimeProfile === "wasi" ? "host-mediated" : "full-user";
124
+ const declaredPermissions = manifest.permissions ?? {};
125
+ const permissions = runtimeProfile === "native-process" ? {
126
+ ...declaredPermissions,
127
+ storage: declaredPermissions.storage ?? true,
128
+ capabilities: {
129
+ ...declaredPermissions.capabilities,
130
+ nativeProcess: true
131
+ }
132
+ } : declaredPermissions;
133
+ return {
134
+ runtimeProfile,
135
+ isolation,
136
+ hasServiceComponents,
137
+ inferred: manifest.runtime === void 0,
138
+ permissions
111
139
  };
112
140
  };
113
141
  parseCommonManifest = (candidate) => {
@@ -168,6 +196,31 @@ var AppManifestService = class {
168
196
  const candidate = this.assertObject(rawPresentation, "presentation");
169
197
  return { primaryPanel: this.readOptionalString(candidate.primaryPanel, "presentation.primaryPanel") };
170
198
  };
199
+ parseRuntime = (rawRuntime) => {
200
+ if (rawRuntime === void 0) return;
201
+ const candidate = this.assertObject(rawRuntime, "runtime");
202
+ const profile = this.readRequiredString(candidate.profile, "runtime.profile");
203
+ if (profile !== "panel-only" && profile !== "wasi" && profile !== "native-process") throw new Error("runtime.profile 只支持 panel-only、wasi 或 native-process。");
204
+ return { profile };
205
+ };
206
+ parseAppStorage = (rawStorage) => {
207
+ if (rawStorage === void 0) return;
208
+ const candidate = this.assertObject(rawStorage, "storage");
209
+ const scope = this.readRequiredString(candidate.scope, "storage.scope");
210
+ if (scope !== "global") throw new Error("当前 storage.scope 只支持 global。");
211
+ const schemaVersion = this.readNumber(candidate.schemaVersion, "storage.schemaVersion");
212
+ if (!Number.isSafeInteger(schemaVersion) || schemaVersion < 1) throw new Error("storage.schemaVersion 必须是正整数。");
213
+ return {
214
+ scope,
215
+ schemaVersion
216
+ };
217
+ };
218
+ assertRuntimeMatchesComponents = (runtime, components) => {
219
+ if (!runtime) return;
220
+ const hasService = components.some((component) => component.kind === "service");
221
+ if (runtime.profile === "panel-only" && hasService) throw new Error("runtime.profile=panel-only 不能包含 Service component。");
222
+ if (runtime.profile !== "panel-only" && !hasService) throw new Error(`${runtime.profile} runtime 必须包含 Service component。`);
223
+ };
171
224
  readComponentId = async (component, componentDirectory) => {
172
225
  const manifestFile = component.kind === "panel" ? "panel-app.json" : "service-app.json";
173
226
  const raw = JSON.parse(await readFile(path.join(componentDirectory, manifestFile), "utf-8"));
@@ -217,7 +270,10 @@ var AppManifestService = class {
217
270
  parseCapabilities = (rawCapabilities) => {
218
271
  if (rawCapabilities === void 0) return;
219
272
  const candidate = this.assertObject(rawCapabilities, "permissions.capabilities");
220
- return { hostBridge: candidate.hostBridge === void 0 ? void 0 : this.readBoolean(candidate.hostBridge, "permissions.capabilities.hostBridge") };
273
+ return {
274
+ hostBridge: candidate.hostBridge === void 0 ? void 0 : this.readBoolean(candidate.hostBridge, "permissions.capabilities.hostBridge"),
275
+ nativeProcess: candidate.nativeProcess === void 0 ? void 0 : this.readBoolean(candidate.nativeProcess, "permissions.capabilities.nativeProcess")
276
+ };
221
277
  };
222
278
  assertResolvedFile = async (appDirectory, relativePath, fieldName) => {
223
279
  const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);