@nextclaw/app-runtime 0.12.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/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.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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"}
@@ -7,6 +7,7 @@ declare class AppInstanceInventoryService {
7
7
  private readonly fileLockService;
8
8
  constructor(storageService?: AppInstanceStorageService);
9
9
  list: (instancesRoot: string) => Promise<AppInstanceInventory>;
10
+ reconcileDeletions: (instancesRoot: string) => Promise<AppInstanceInventory["diagnostics"]>;
10
11
  purge: (params: {
11
12
  instancesRoot: string;
12
13
  appId: string;
@@ -22,6 +23,7 @@ declare class AppInstanceInventoryService {
22
23
  }) => Promise<void>;
23
24
  private listDirectories;
24
25
  private assertSafeId;
26
+ private parseDeletingInstanceId;
25
27
  private assertWithinRoot;
26
28
  private pathExists;
27
29
  private isMissingFileError;
@@ -1 +1 @@
1
- {"version":3,"file":"app-instance-inventory.service.d.ts","names":[],"sources":["../../src/services/app-instance-inventory.service.ts"],"mappings":";;;;cAYa,2BAAA;EAAA,iBAIQ,cAAA;EAAA,iBAHF,eAAA;cAGE,cAAA,GAAc,yBAAA;EAGjC,IAAA,GAAc,aAAA,aAAwB,OAAA,CAAQ,oBAAA;EAuD9C,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,gBAAA;EAAA,QAOA,UAAA;EAAA,QASA,kBAAA;AAAA"}
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"}
@@ -5,6 +5,7 @@ import { access, readdir, rename, rm } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  //#region src/services/app-instance-inventory.service.ts
7
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})$/;
8
9
  var AppInstanceInventoryService = class {
9
10
  fileLockService = new FileLockService();
10
11
  constructor(storageService = new AppInstanceStorageService()) {
@@ -25,6 +26,14 @@ var AppInstanceInventoryService = class {
25
26
  continue;
26
27
  }
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
+ }
28
37
  const instanceId = instanceDirectory.name;
29
38
  const instancePath = path.join(appPath, instanceId);
30
39
  if (!SAFE_ID_PATTERN.test(instanceId)) {
@@ -65,6 +74,37 @@ var AppInstanceInventoryService = class {
65
74
  diagnostics
66
75
  };
67
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
+ };
68
108
  purge = async (params) => {
69
109
  const { appId, assertCanPurge, instanceId, instancesRoot } = params;
70
110
  return await this.purgeNested({
@@ -116,6 +156,7 @@ var AppInstanceInventoryService = class {
116
156
  assertSafeId = (value, field) => {
117
157
  if (!SAFE_ID_PATTERN.test(value)) throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);
118
158
  };
159
+ parseDeletingInstanceId = (directoryName) => DELETING_INSTANCE_PATTERN.exec(directoryName)?.groups?.instanceId;
119
160
  assertWithinRoot = (root, target) => {
120
161
  const relative = path.relative(root, target);
121
162
  if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`App Instance 路径不在受管根目录内:${target}`);
@@ -1 +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]+)*$/;\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 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 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 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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"}
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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"}
@@ -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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.12.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",