@nextclaw/app-runtime 0.16.0 → 0.16.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/index.d.ts +2 -2
- package/dist/package.js +1 -1
- package/dist/services/app-grant.service.d.ts +2 -0
- package/dist/services/app-grant.service.d.ts.map +1 -1
- package/dist/services/app-grant.service.js +39 -12
- package/dist/services/app-grant.service.js.map +1 -1
- package/dist/services/app-installation.service.d.ts.map +1 -1
- package/dist/services/app-installation.service.js +1 -1
- package/dist/services/app-installation.service.js.map +1 -1
- package/dist/services/app-registry-parser.service.js +29 -1
- package/dist/services/app-registry-parser.service.js.map +1 -1
- package/dist/services/app-registry.service.d.ts +3 -2
- package/dist/services/app-registry.service.d.ts.map +1 -1
- package/dist/services/app-registry.service.js +52 -10
- package/dist/services/app-registry.service.js.map +1 -1
- package/dist/types/app-installation.types.d.ts +2 -2
- package/dist/types/app-installation.types.d.ts.map +1 -1
- package/dist/types/app-permissions.types.d.ts +12 -1
- package/dist/types/app-permissions.types.d.ts.map +1 -1
- package/dist/types/app-registry.types.d.ts +2 -2
- package/dist/types/app-registry.types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtif
|
|
|
4
4
|
import { AppPlatformTargetService } from "./services/app-platform-target.service.js";
|
|
5
5
|
import { AppManifestService } from "./services/app-manifest.service.js";
|
|
6
6
|
import { AppBundleService } from "./services/app-bundle.service.js";
|
|
7
|
-
import { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions } from "./types/app-permissions.types.js";
|
|
7
|
+
import { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, AppStoredDocumentGrant, AppStoredDocumentGrantMap, ResolvedDocumentGrant, ResolvedPermissions } from "./types/app-permissions.types.js";
|
|
8
8
|
import { AppPermissionsService } from "./services/app-permissions.service.js";
|
|
9
9
|
import { MainRunRequest, MainRunResult, WasmDocumentSummaryInput } from "./types/main-runner.types.js";
|
|
10
10
|
import { MainRunnerService } from "./services/main-runner.service.js";
|
|
@@ -58,4 +58,4 @@ import { AppServiceLaunch, AppServiceLaunchService } from "./services/app-servic
|
|
|
58
58
|
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
59
59
|
import { FileLockBusyError, FileLockLease, FileLockService } from "./services/file-lock.service.js";
|
|
60
60
|
import { UiServerService } from "./services/ui-server.service.js";
|
|
61
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactArchitecture, AppArtifactTarget, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDarwinArtifactTarget, AppDistributionDeclaration, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppLinuxArtifactTarget, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppNativeArtifactTarget, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPlatformTargetService, AppPreparedPublishArtifact, AppPublishArtifact, AppPublishArtifactService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryArtifact, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainProfile, AppRuntimeToolchainService, AppRustWasiScaffoldTemplateService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppSecretBinding, AppSecretBindingMap, AppSecretSlot, AppServiceLaunch, AppServiceLaunchService, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUniversalArtifactTarget, AppUpdateResult, AppWasiHttpComponentMainManifest, AppWindowsArtifactTarget, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockBusyError, FileLockLease, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
61
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactArchitecture, AppArtifactTarget, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDarwinArtifactTarget, AppDistributionDeclaration, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppLinuxArtifactTarget, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppNativeArtifactTarget, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPlatformTargetService, AppPreparedPublishArtifact, AppPublishArtifact, AppPublishArtifactService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryArtifact, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainProfile, AppRuntimeToolchainService, AppRustWasiScaffoldTemplateService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppSecretBinding, AppSecretBindingMap, AppSecretSlot, AppServiceLaunch, AppServiceLaunchService, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppStoredDocumentGrant, AppStoredDocumentGrantMap, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUniversalArtifactTarget, AppUpdateResult, AppWasiHttpComponentMainManifest, AppWindowsArtifactTarget, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockBusyError, FileLockLease, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/package.js
CHANGED
|
@@ -12,11 +12,13 @@ declare class AppGrantService {
|
|
|
12
12
|
appId: string;
|
|
13
13
|
scopeId: string;
|
|
14
14
|
directoryPath: string;
|
|
15
|
+
mode?: "read" | "read-write";
|
|
15
16
|
}) => Promise<AppDocumentGrantMutationResult>;
|
|
16
17
|
revokeDocumentScope: (params: {
|
|
17
18
|
appId: string;
|
|
18
19
|
scopeId: string;
|
|
19
20
|
}) => Promise<AppDocumentGrantMutationResult>;
|
|
21
|
+
private isAvailableDirectory;
|
|
20
22
|
}
|
|
21
23
|
//#endregion
|
|
22
24
|
export { AppGrantService };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-grant.service.d.ts","names":[],"sources":["../../src/services/app-grant.service.ts"],"mappings":";;;;;cASa,eAAA;EAAA,iBAEQ,eAAA;EAAA,iBACA,eAAA;cADA,eAAA,GAAiB,kBAAA,EACjB,eAAA,GAAiB,kBAAA;EAGpC,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,2BAAA;
|
|
1
|
+
{"version":3,"file":"app-grant.service.d.ts","names":[],"sources":["../../src/services/app-grant.service.ts"],"mappings":";;;;;cASa,eAAA;EAAA,iBAEQ,eAAA;EAAA,iBACA,eAAA;cADA,eAAA,GAAiB,kBAAA,EACjB,eAAA,GAAiB,kBAAA;EAGpC,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,2BAAA;EAoE3C,kBAAA,GAA4B,MAAA;IAC1B,KAAA;IACA,OAAA;IACA,aAAA;IACA,IAAA;EAAA,MACE,OAAA,CAAQ,8BAAA;EA0CZ,mBAAA,GAA6B,MAAA;IAC3B,KAAA;IACA,OAAA;EAAA,MACE,OAAA,CAAQ,8BAAA;EAAA,QAcJ,oBAAA;AAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppManifestService } from "./app-manifest.service.js";
|
|
2
2
|
import { AppRegistryService } from "./app-registry.service.js";
|
|
3
|
-
import {
|
|
3
|
+
import { realpath, stat } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
//#region src/services/app-grant.service.ts
|
|
6
6
|
var AppGrantService = class {
|
|
@@ -18,12 +18,26 @@ var AppGrantService = class {
|
|
|
18
18
|
appId: appRecord.appId,
|
|
19
19
|
name: appRecord.name,
|
|
20
20
|
activeVersion: appRecord.activeVersion,
|
|
21
|
-
documentAccess: (requestedPermissions.documentAccess ?? []).map((scope) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
documentAccess: await Promise.all((requestedPermissions.documentAccess ?? []).map(async (scope) => {
|
|
22
|
+
const grant = appRecord.grants[scope.id];
|
|
23
|
+
const available = grant ? await this.isAvailableDirectory(grant.path) : false;
|
|
24
|
+
const status = !grant ? "ungranted" : !available ? "unavailable" : "granted";
|
|
25
|
+
return {
|
|
26
|
+
id: scope.id,
|
|
27
|
+
mode: scope.mode,
|
|
28
|
+
description: scope.description,
|
|
29
|
+
granted: Boolean(grant),
|
|
30
|
+
grantedPath: grant?.path,
|
|
31
|
+
effectiveMode: grant?.mode,
|
|
32
|
+
grantedAt: grant?.grantedAt,
|
|
33
|
+
status,
|
|
34
|
+
availableActions: !grant ? ["grant"] : [
|
|
35
|
+
"replace",
|
|
36
|
+
...grant.mode === "read" && scope.mode === "read-write" ? ["upgrade"] : [],
|
|
37
|
+
...grant.mode === "read-write" ? ["downgrade"] : [],
|
|
38
|
+
"revoke"
|
|
39
|
+
]
|
|
40
|
+
};
|
|
27
41
|
})),
|
|
28
42
|
allowedDomains: requestedPermissions.allowedDomains ?? [],
|
|
29
43
|
storage: {
|
|
@@ -35,18 +49,24 @@ var AppGrantService = class {
|
|
|
35
49
|
};
|
|
36
50
|
grantDocumentScope = async (params) => {
|
|
37
51
|
const { appId, scopeId, directoryPath } = params;
|
|
38
|
-
|
|
52
|
+
const requestedScope = (await this.summarize(appId)).documentAccess.find((scope) => scope.id === scopeId);
|
|
53
|
+
if (!requestedScope) throw new Error(`应用 ${appId} 未声明 documentAccess scope:${scopeId}`);
|
|
54
|
+
const effectiveMode = params.mode ?? requestedScope.mode;
|
|
55
|
+
if (effectiveMode === "read-write" && requestedScope.mode !== "read-write") throw new Error(`应用 ${appId} 的 documentAccess scope ${scopeId} 只声明了 read。`);
|
|
39
56
|
const normalizedDirectory = path.resolve(directoryPath);
|
|
57
|
+
let canonicalDirectory;
|
|
40
58
|
try {
|
|
41
|
-
await
|
|
59
|
+
canonicalDirectory = await realpath(normalizedDirectory);
|
|
60
|
+
if (!(await stat(canonicalDirectory)).isDirectory()) throw new Error("not-directory");
|
|
42
61
|
} catch {
|
|
43
|
-
throw new Error(
|
|
62
|
+
throw new Error(`授权资源不是可用目录:${normalizedDirectory}`);
|
|
44
63
|
}
|
|
45
|
-
await this.registryService.setDocumentGrant(appId, scopeId,
|
|
64
|
+
await this.registryService.setDocumentGrant(appId, scopeId, canonicalDirectory, effectiveMode);
|
|
46
65
|
return {
|
|
47
66
|
appId,
|
|
48
67
|
scopeId,
|
|
49
|
-
grantedPath:
|
|
68
|
+
grantedPath: canonicalDirectory,
|
|
69
|
+
effectiveMode
|
|
50
70
|
};
|
|
51
71
|
};
|
|
52
72
|
revokeDocumentScope = async (params) => {
|
|
@@ -58,6 +78,13 @@ var AppGrantService = class {
|
|
|
58
78
|
removed: await this.registryService.removeDocumentGrant(appId, scopeId)
|
|
59
79
|
};
|
|
60
80
|
};
|
|
81
|
+
isAvailableDirectory = async (directoryPath) => {
|
|
82
|
+
try {
|
|
83
|
+
return (await stat(await realpath(directoryPath))).isDirectory();
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
61
88
|
};
|
|
62
89
|
//#endregion
|
|
63
90
|
export { AppGrantService };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-grant.service.js","names":[],"sources":["../../src/services/app-grant.service.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"app-grant.service.js","names":[],"sources":["../../src/services/app-grant.service.ts"],"sourcesContent":["import { realpath, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { AppRegistryService } from \"#app-runtime/services/app-registry.service.js\";\nimport type {\n AppDocumentGrantMutationResult,\n AppInstalledPermissionState,\n} from \"#app-runtime/types/app-permissions.types.js\";\n\nexport class AppGrantService {\n constructor(\n private readonly registryService: AppRegistryService = new AppRegistryService(),\n private readonly manifestService: AppManifestService = new AppManifestService(),\n ) {}\n\n summarize = async (appId: string): Promise<AppInstalledPermissionState> => {\n const appRecord = await this.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const activeVersion = appRecord.installedVersions[appRecord.activeVersion];\n if (!activeVersion) {\n throw new Error(`已安装应用缺少激活版本:${appId}`);\n }\n const bundle = await this.manifestService.load(\n activeVersion.installDirectory,\n );\n const requestedPermissions = bundle.manifest.permissions ?? {};\n return {\n appId: appRecord.appId,\n name: appRecord.name,\n activeVersion: appRecord.activeVersion,\n documentAccess: await Promise.all(\n (requestedPermissions.documentAccess ?? []).map(async (scope) => {\n const grant = appRecord.grants[scope.id];\n const available = grant\n ? await this.isAvailableDirectory(grant.path)\n : false;\n const status = !grant\n ? (\"ungranted\" as const)\n : !available\n ? (\"unavailable\" as const)\n : (\"granted\" as const);\n return {\n id: scope.id,\n mode: scope.mode,\n description: scope.description,\n granted: Boolean(grant),\n grantedPath: grant?.path,\n effectiveMode: grant?.mode,\n grantedAt: grant?.grantedAt,\n status,\n availableActions: !grant\n ? [\"grant\" as const]\n : [\n \"replace\" as const,\n ...(grant.mode === \"read\" && scope.mode === \"read-write\"\n ? [\"upgrade\" as const]\n : []),\n ...(grant.mode === \"read-write\"\n ? [\"downgrade\" as const]\n : []),\n \"revoke\" as const,\n ],\n };\n }),\n ),\n allowedDomains: requestedPermissions.allowedDomains ?? [],\n storage: {\n enabled:\n requestedPermissions.storage !== undefined &&\n requestedPermissions.storage !== false,\n namespace:\n typeof requestedPermissions.storage === \"object\"\n ? requestedPermissions.storage.namespace\n : undefined,\n },\n capabilities: {\n hostBridge: requestedPermissions.capabilities?.hostBridge !== false,\n },\n };\n };\n\n grantDocumentScope = async (params: {\n appId: string;\n scopeId: string;\n directoryPath: string;\n mode?: \"read\" | \"read-write\";\n }): Promise<AppDocumentGrantMutationResult> => {\n const { appId, scopeId, directoryPath } = params;\n const permissionState = await this.summarize(appId);\n const requestedScope = permissionState.documentAccess.find(\n (scope) => scope.id === scopeId,\n );\n if (!requestedScope) {\n throw new Error(`应用 ${appId} 未声明 documentAccess scope:${scopeId}`);\n }\n const effectiveMode = params.mode ?? requestedScope.mode;\n if (\n effectiveMode === \"read-write\" &&\n requestedScope.mode !== \"read-write\"\n ) {\n throw new Error(\n `应用 ${appId} 的 documentAccess scope ${scopeId} 只声明了 read。`,\n );\n }\n const normalizedDirectory = path.resolve(directoryPath);\n let canonicalDirectory: string;\n try {\n canonicalDirectory = await realpath(normalizedDirectory);\n if (!(await stat(canonicalDirectory)).isDirectory()) {\n throw new Error(\"not-directory\");\n }\n } catch {\n throw new Error(`授权资源不是可用目录:${normalizedDirectory}`);\n }\n await this.registryService.setDocumentGrant(\n appId,\n scopeId,\n canonicalDirectory,\n effectiveMode,\n );\n return {\n appId,\n scopeId,\n grantedPath: canonicalDirectory,\n effectiveMode,\n };\n };\n\n revokeDocumentScope = async (params: {\n appId: string;\n scopeId: string;\n }): Promise<AppDocumentGrantMutationResult> => {\n const { appId, scopeId } = params;\n await this.summarize(appId);\n const removed = await this.registryService.removeDocumentGrant(\n appId,\n scopeId,\n );\n return {\n appId,\n scopeId,\n removed,\n };\n };\n\n private isAvailableDirectory = async (\n directoryPath: string,\n ): Promise<boolean> => {\n try {\n return (await stat(await realpath(directoryPath))).isDirectory();\n } catch {\n return false;\n }\n };\n}\n"],"mappings":";;;;;AASA,IAAa,kBAAb,MAA6B;CAC3B,YACE,kBAAuD,IAAI,oBAAoB,EAC/E,kBAAuD,IAAI,oBAAoB,EAC/E;AAFiB,OAAA,kBAAA;AACA,OAAA,kBAAA;;CAGnB,YAAY,OAAO,UAAwD;EACzE,MAAM,YAAY,MAAM,KAAK,gBAAgB,OAAO,MAAM;AAC1D,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;EAEtC,MAAM,gBAAgB,UAAU,kBAAkB,UAAU;AAC5D,MAAI,CAAC,cACH,OAAM,IAAI,MAAM,eAAe,QAAQ;EAKzC,MAAM,wBAHS,MAAM,KAAK,gBAAgB,KACxC,cAAc,iBACf,EACmC,SAAS,eAAe,EAAE;AAC9D,SAAO;GACL,OAAO,UAAU;GACjB,MAAM,UAAU;GAChB,eAAe,UAAU;GACzB,gBAAgB,MAAM,QAAQ,KAC3B,qBAAqB,kBAAkB,EAAE,EAAE,IAAI,OAAO,UAAU;IAC/D,MAAM,QAAQ,UAAU,OAAO,MAAM;IACrC,MAAM,YAAY,QACd,MAAM,KAAK,qBAAqB,MAAM,KAAK,GAC3C;IACJ,MAAM,SAAS,CAAC,QACX,cACD,CAAC,YACE,gBACA;AACP,WAAO;KACL,IAAI,MAAM;KACV,MAAM,MAAM;KACZ,aAAa,MAAM;KACnB,SAAS,QAAQ,MAAM;KACvB,aAAa,OAAO;KACpB,eAAe,OAAO;KACtB,WAAW,OAAO;KAClB;KACA,kBAAkB,CAAC,QACf,CAAC,QAAiB,GAClB;MACE;MACA,GAAI,MAAM,SAAS,UAAU,MAAM,SAAS,eACxC,CAAC,UAAmB,GACpB,EAAE;MACN,GAAI,MAAM,SAAS,eACf,CAAC,YAAqB,GACtB,EAAE;MACN;MACD;KACN;KACD,CACH;GACD,gBAAgB,qBAAqB,kBAAkB,EAAE;GACzD,SAAS;IACP,SACE,qBAAqB,YAAY,KAAA,KACjC,qBAAqB,YAAY;IACnC,WACE,OAAO,qBAAqB,YAAY,WACpC,qBAAqB,QAAQ,YAC7B,KAAA;IACP;GACD,cAAc,EACZ,YAAY,qBAAqB,cAAc,eAAe,OAC/D;GACF;;CAGH,qBAAqB,OAAO,WAKmB;EAC7C,MAAM,EAAE,OAAO,SAAS,kBAAkB;EAE1C,MAAM,kBADkB,MAAM,KAAK,UAAU,MAAM,EACZ,eAAe,MACnD,UAAU,MAAM,OAAO,QACzB;AACD,MAAI,CAAC,eACH,OAAM,IAAI,MAAM,MAAM,MAAM,4BAA4B,UAAU;EAEpE,MAAM,gBAAgB,OAAO,QAAQ,eAAe;AACpD,MACE,kBAAkB,gBAClB,eAAe,SAAS,aAExB,OAAM,IAAI,MACR,MAAM,MAAM,0BAA0B,QAAQ,aAC/C;EAEH,MAAM,sBAAsB,KAAK,QAAQ,cAAc;EACvD,IAAI;AACJ,MAAI;AACF,wBAAqB,MAAM,SAAS,oBAAoB;AACxD,OAAI,EAAE,MAAM,KAAK,mBAAmB,EAAE,aAAa,CACjD,OAAM,IAAI,MAAM,gBAAgB;UAE5B;AACN,SAAM,IAAI,MAAM,cAAc,sBAAsB;;AAEtD,QAAM,KAAK,gBAAgB,iBACzB,OACA,SACA,oBACA,cACD;AACD,SAAO;GACL;GACA;GACA,aAAa;GACb;GACD;;CAGH,sBAAsB,OAAO,WAGkB;EAC7C,MAAM,EAAE,OAAO,YAAY;AAC3B,QAAM,KAAK,UAAU,MAAM;AAK3B,SAAO;GACL;GACA;GACA,SAPc,MAAM,KAAK,gBAAgB,oBACzC,OACA,QACD;GAKA;;CAGH,uBAA+B,OAC7B,kBACqB;AACrB,MAAI;AACF,WAAQ,MAAM,KAAK,MAAM,SAAS,cAAc,CAAC,EAAE,aAAa;UAC1D;AACN,UAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;KAqCK,iBAAA;EACH,WAAA;EACA,UAAA,GAAa,yBAAA;EACb,QAAA;EACA,gBAAA,GAAmB,YAAA;AAAA;AAAA,cAaR,sBAAA;EAAA,iBAUQ,cAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,YAAA;EAAA,iBACA,eAAA;EAAA,iBACA,qBAAA;EAAA,iBACA,oBAAA;EAAA,iBAfF,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,EACvB,oBAAA,GAAsB,8BAAA,EACvC,sBAAA,GAAyB,yBAAA;EAqB3B,OAAA,GAAiB,SAAA,UAAmB,OAAA,GAAS,iBAAA,KAAyB,OAAA,CAAQ,gBAAA;EAAA,QA8BtE,qBAAA;EAAA,QAKA,qBAAA;EAkGR,MAAA,GACE,KAAA,UACA,OAAA;IACE,OAAA;IACA,WAAA;IACA,UAAA,GAAa,yBAAA;IACb,QAAA;EAAA,MAED,OAAA,CAAQ,eAAA;EAIX,SAAA,GAAmB,KAAA,UAAe,SAAA,cAAqB,OAAA,CAAQ,kBAAA;EAI/D,IAAA,QAAiB,OAAA,CAAQ,oBAAA;EAczB,mBAAA,QAAgC,OAAA;EAqFhC,IAAA,GAAc,KAAA,UAAe,OAAA;IAAW,mBAAA;EAAA,MAAuC,OAAA,CAAQ,aAAA;EAoCvF,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,mBAAA;EAW9D,QAAA,GAAkB,KAAA,UAAe,OAAA,aAAkB,OAAA,CAAQ,iBAAA;EAkC3D,sBAAA,GAAgC,KAAA,UAAe,OAAA,cAAmB,OAAA;EAqBlE,gBAAA,MAA6B,KAAA,UAAe,SAAA,QAAiB,OAAA,CAAQ,CAAA,MAAK,OAAA,CAAQ,CAAA;EAgBlF,aAAA,GAAuB,YAAA,UAAsB,wBAAA,EAA0B,mBAAA,KAAsB,OAAA,CAAQ,mBAAA;EAAA,QA6B7F,sBAAA;EAOR,aAAA,GAAuB,KAAA,sBAA2B,gBAAA,EAAkB,mBAAA,KAAsB,OAAA;AAAA"}
|
|
@@ -376,7 +376,7 @@ var AppInstallationService = class {
|
|
|
376
376
|
dataDirectory: appRecord.dataDirectory,
|
|
377
377
|
storage: appRecord.defaultInstance.storage,
|
|
378
378
|
documentGrantMap: {
|
|
379
|
-
...appRecord.grants,
|
|
379
|
+
...Object.fromEntries(Object.entries(appRecord.grants).map(([scopeId, grant]) => [scopeId, grant.path])),
|
|
380
380
|
...explicitDocumentGrantMap
|
|
381
381
|
}
|
|
382
382
|
};
|
|
@@ -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 type { AppRegistryInstalledVersion } from \"#app-runtime/types/app-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 { resolveAppInstallationContract } from \"#app-runtime/utils/app-installation-contract.utils.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\ntype RemoteSourceFields = Pick<AppRegistryInstalledVersion, \"registryUrl\" | \"bundleUrl\" | \"sha256\" | \"target\">;\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 { currentPublisher, dataSchemaVersion, publisher } =\n resolveAppInstallationContract({\n appId, existingRecord, manifestBundle, source,\n trustedPublisher: options.trustedPublisher,\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, appVersion: version, extractedDirectory, installDirectory,\n target: extractedMetadata.metadata.target?.kind === \"native\"\n ? extractedMetadata.metadata.target\n : undefined,\n });\n let defaultInstance: Awaited<ReturnType<\n AppInstanceStorageService[\"materializeDefaultInstance\"]\n >> | undefined;\n let registryRecord;\n try {\n 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 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 ...this.readRemoteSourceFields(source.kind === \"registry\" ? source.registryResolution : 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 && defaultInstance) {\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 if (!defaultInstance) {\n throw new Error(`应用 ${appId} 安装完成但缺少默认实例。`);\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 ...this.readRemoteSourceFields(installedVersion),\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 ...this.readRemoteSourceFields(activeVersion),\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 ...this.readRemoteSourceFields(versionRecord),\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 private readRemoteSourceFields = (source?: RemoteSourceFields): RemoteSourceFields => ({\n registryUrl: source?.registryUrl,\n bundleUrl: source?.bundleUrl,\n sha256: source?.sha256,\n target: source?.target,\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 type { AppRegistryInstalledVersion } from \"#app-runtime/types/app-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 { AppInstallSourceService, type ResolvedAppInstallSource } from \"#app-runtime/services/app-install-source.service.js\";\nimport { resolveAppInstallationContract } from \"#app-runtime/utils/app-installation-contract.utils.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\ntype RemoteSourceFields = Pick<AppRegistryInstalledVersion, \"registryUrl\" | \"bundleUrl\" | \"sha256\" | \"target\">;\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(appHomeService),\n private readonly remoteRegistryClient: AppRemoteRegistryClientService = new AppRemoteRegistryClientService(new AppRegistryConfigService(appHomeService)),\n instanceStorageService?: AppInstanceStorageService,\n ) {\n this.installSourceService = new AppInstallSourceService(this.manifestService, this.remoteRegistryClient, this.bundleService);\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 (appSource: string, options: AppInstallOptions = {}): 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(source, tempDirectory, onProgress);\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 (prepared: PreparedAppInstall): Promise<AppInstallResult> => {\n const appId = prepared.manifestBundle.manifest.id;\n return await this.withAppOperation(appId, async () => await this.commitPreparedInstall(prepared));\n };\n\n private commitPreparedInstall = async (prepared: PreparedAppInstall): 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(`bundle manifest.appId 与 registry 请求不一致:期望 ${source.registryResolution.appId},实际 ${appId}`);\n }\n await onProgress?.(\"installing\");\n const existingRecord = await this.registryService.getApp(appId);\n const { currentPublisher, dataSchemaVersion, publisher } = resolveAppInstallationContract({\n appId,\n existingRecord,\n manifestBundle,\n source,\n trustedPublisher: options.trustedPublisher,\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 target: extractedMetadata.metadata.target?.kind === \"native\" ? extractedMetadata.metadata.target : undefined,\n });\n let defaultInstance: Awaited<ReturnType<AppInstanceStorageService[\"materializeDefaultInstance\"]>> | undefined;\n let registryRecord;\n try {\n defaultInstance = await this.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: (currentPublisher ?? publisher)?.id,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\" ? existingRecord.dataDirectory : undefined,\n dataSchemaVersion,\n });\n await onProgress?.(\"finalizing\");\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\" ? source.registryResolution.distributionMode : extractedMetadata.metadata.distributionMode,\n sourceRef: source.sourceRef,\n installedAt: new Date().toISOString(),\n permissions: manifestBundle.manifest.schemaVersion === 1 ? (manifestBundle.manifest.permissions ?? {}) : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n ...this.readRemoteSourceFields(source.kind === \"registry\" ? source.registryResolution : 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(installDirectory, component.path, component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\"),\n }))\n : undefined,\n primaryPanelId: isAppComponentManifestBundle(manifestBundle) ? manifestBundle.primaryPanelId : undefined,\n security: isAppComponentManifestBundle(manifestBundle) ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest) : undefined,\n dataSchemaVersion,\n contentSha256,\n activate: options.activate,\n });\n } catch (error) {\n await this.integrityService.removeDirectory(installDirectory);\n if (!instanceExisted && defaultInstance) {\n await this.instanceStorageService.rollbackNewInstance({\n instance: defaultInstance,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\" ? existingRecord.dataDirectory : undefined,\n });\n }\n throw error;\n }\n if (!defaultInstance) {\n throw new Error(`应用 ${appId} 安装完成但缺少默认实例。`);\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 ...this.readRemoteSourceFields(installedVersion),\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(appId, async () => await this.lifecycleService.update(appId, options));\n };\n\n uninstall = async (appId: string, purgeData: boolean): Promise<AppUninstallResult> => {\n return await this.withAppOperation(appId, async () => await this.lifecycleService.uninstall(appId, purgeData));\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: appRecord.installedVersions[appRecord.activeVersion]?.sourceKind ?? \"directory\",\n distributionMode: appRecord.installedVersions[appRecord.activeVersion]?.distributionMode,\n enabled: appRecord.enabled,\n manifestSchemaVersion: appRecord.installedVersions[appRecord.activeVersion]?.manifestSchemaVersion ?? 1,\n primaryPanelId: 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(versionRecord.installDirectory);\n await this.registryService.setVersionContentDigest(appRecord.appId, versionRecord.version, contentSha256);\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) => Object.values(record.installedVersions).map((version) => path.resolve(version.installDirectory))));\n const referencedDataPaths = new Set(appRecords.map((record) => path.resolve(record.defaultInstance.storage.layout === \"instance-v1\" ? record.defaultInstance.storage.instanceDirectory : record.dataDirectory)));\n\n const packageDirectories = await this.filesystemService.readDirectories(this.appHomeService.getPackagesDirectory());\n for (const appDirectory of packageDirectories) {\n await this.filesystemService.reconcileGeneratedSiblings(appDirectory, referencedInstallPaths);\n }\n await this.filesystemService.reconcileGeneratedSiblings(this.appHomeService.getDataDirectory(), referencedDataPaths);\n for (const appRecord of appRecords) {\n if (appRecord.defaultInstance.storage.layout !== \"instance-v1\") {\n continue;\n }\n await this.filesystemService.reconcileGeneratedSiblings(appRecord.defaultInstance.storage.instanceDirectory, new Set([path.resolve(appRecord.defaultInstance.storage.dataDirectory)]));\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 ...this.readRemoteSourceFields(activeVersion),\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, options: { measureStorageUsage?: boolean } = {}): 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) => left.version.localeCompare(right.version));\n const storageUsage = options.measureStorageUsage === false ? undefined : 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 ...this.readRemoteSourceFields(versionRecord),\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(`应用 ${appId}@${version} 需要数据 schema ${targetVersion.dataSchemaVersion},当前实例为 ${currentRecord.defaultInstance.dataSchemaVersion}。没有可用 checkpoint,已阻止回滚。`);\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 (appReference: string, explicitDocumentGrantMap: AppDocumentGrantMap): 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 ...Object.fromEntries(Object.entries(appRecord.grants).map(([scopeId, grant]) => [scopeId, grant.path])),\n ...explicitDocumentGrantMap,\n },\n };\n };\n\n private readRemoteSourceFields = (source?: RemoteSourceFields): RemoteSourceFields => ({\n registryUrl: source?.registryUrl,\n bundleUrl: source?.bundleUrl,\n sha256: source?.sha256,\n target: source?.target,\n });\n\n persistGrants = async (appId: string | undefined, documentGrantMap: AppDocumentGrantMap): Promise<void> => {\n if (!appId || Object.keys(documentGrantMap).length === 0) {\n return;\n }\n await this.registryService.updateGrants(appId, documentGrantMap);\n };\n}\n"],"mappings":"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"}
|
|
@@ -36,7 +36,7 @@ var AppRegistryParserService = class {
|
|
|
36
36
|
dataDirectory: defaultInstance.storage.dataDirectory,
|
|
37
37
|
defaultInstance,
|
|
38
38
|
installedVersions,
|
|
39
|
-
grants:
|
|
39
|
+
grants: this.parseDocumentGrants(app.grants, installedVersions[activeVersion]?.permissions ?? {}, `registry.apps.${appId}.grants`),
|
|
40
40
|
secretBindings: this.parseSecretBindings(app.secretBindings, `registry.apps.${appId}.secretBindings`)
|
|
41
41
|
};
|
|
42
42
|
}
|
|
@@ -74,6 +74,34 @@ var AppRegistryParserService = class {
|
|
|
74
74
|
const declared = new Set((permissions.secrets ?? []).map((slot) => slot.id));
|
|
75
75
|
return Object.fromEntries(Object.entries(bindings).filter(([slotId]) => declared.has(slotId)));
|
|
76
76
|
};
|
|
77
|
+
retainCompatibleDocumentGrants = (grants, permissions) => {
|
|
78
|
+
const declared = new Map((permissions.documentAccess ?? []).map((scope) => [scope.id, scope]));
|
|
79
|
+
return Object.fromEntries(Object.entries(grants).filter(([scopeId, grant]) => {
|
|
80
|
+
const scope = declared.get(scopeId);
|
|
81
|
+
return scope && (scope.mode === "read-write" || grant.mode === "read");
|
|
82
|
+
}));
|
|
83
|
+
};
|
|
84
|
+
parseDocumentGrants = (raw, permissions, field) => {
|
|
85
|
+
if (raw === void 0) return {};
|
|
86
|
+
const declared = new Map((permissions.documentAccess ?? []).map((scope) => [scope.id, scope]));
|
|
87
|
+
return Object.fromEntries(Object.entries(this.assertRecord(raw, field)).flatMap(([scopeId, value]) => {
|
|
88
|
+
const scope = declared.get(scopeId);
|
|
89
|
+
if (!scope) return [];
|
|
90
|
+
if (typeof value === "string") return [[scopeId, {
|
|
91
|
+
path: value,
|
|
92
|
+
mode: scope.mode,
|
|
93
|
+
grantedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
94
|
+
}]];
|
|
95
|
+
const grant = this.assertRecord(value, `${field}.${scopeId}`);
|
|
96
|
+
const mode = grant.mode === "read-write" ? "read-write" : grant.mode === "read" ? "read" : void 0;
|
|
97
|
+
if (!mode || mode === "read-write" && scope.mode !== "read-write") return [];
|
|
98
|
+
return [[scopeId, {
|
|
99
|
+
path: this.requireString(grant.path, `${field}.${scopeId}.path`),
|
|
100
|
+
mode,
|
|
101
|
+
grantedAt: typeof grant.grantedAt === "string" ? grant.grantedAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
102
|
+
}]];
|
|
103
|
+
}));
|
|
104
|
+
};
|
|
77
105
|
parseInstalledVersions = (appId, raw) => {
|
|
78
106
|
const installedVersions = {};
|
|
79
107
|
const rawVersions = this.assertRecord(raw, `registry.apps.${appId}.installedVersions`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-registry-parser.service.js","names":[],"sources":["../../src/services/app-registry-parser.service.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { AppPermissions, AppResolvedComponent } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { AppPlatformTargetService } from \"#app-runtime/services/app-platform-target.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n AppSecretBinding,\n AppSecretBindingMap,\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]+)*$/;\nconst SAFE_SECRET_SLOT_ID_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;\n\nexport class AppRegistryParserService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly platformTargetService = new AppPlatformTargetService();\n\n constructor(private readonly appHomeService: AppHomeService) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n parse = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) throw new Error(\"当前只支持 registry schemaVersion = 1。\");\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)) throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions = this.parseInstalledVersions(appId, app.installedVersions);\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n const dataDirectory = this.requireString(app.dataDirectory, `registry.apps.${appId}.dataDirectory`);\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(app.defaultInstance, appId, dataDirectory, firstInstalledAt);\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 secretBindings: this.parseSecretBindings(app.secretBindings, `registry.apps.${appId}.secretBindings`),\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" && !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return [];\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 parseSecretSlotId = (slotId: string, field: string): string => {\n if (!SAFE_SECRET_SLOT_ID_PATTERN.test(slotId)) throw new Error(`${field} 必须是稳定的 lowercase slot id。`);\n return slotId;\n };\n\n parseSecretBinding = (rawBinding: unknown, field: string): AppSecretBinding => {\n const binding = this.assertRecord(rawBinding, field);\n if (Object.prototype.hasOwnProperty.call(binding, \"value\")) throw new Error(`${field} 不能保存 Secret 明文。`);\n const unsupportedField = Object.keys(binding).find((key) => ![\"source\", \"provider\", \"id\"].includes(key));\n if (unsupportedField) throw new Error(`${field} 只允许保存 source、provider 和 id。`);\n const source = this.requireString(binding.source, `${field}.source`);\n if (source !== \"env\" && source !== \"file\" && source !== \"exec\") {\n throw new Error(`${field}.source 只支持 env、file 或 exec。`);\n }\n return {\n source,\n provider: binding.provider === undefined ? undefined : this.requireString(binding.provider, `${field}.provider`),\n id: this.requireString(binding.id, `${field}.id`),\n };\n };\n\n retainDeclaredSecretBindings = (bindings: AppSecretBindingMap, permissions: AppPermissions): AppSecretBindingMap => {\n const declared = new Set((permissions.secrets ?? []).map((slot) => slot.id));\n return Object.fromEntries(Object.entries(bindings).filter(([slotId]) => declared.has(slotId)));\n };\n\n private parseInstalledVersions = (appId: string, raw: unknown): Record<string, AppRegistryInstalledVersion> => {\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(raw, `registry.apps.${appId}.installedVersions`);\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n const field = `registry.apps.${appId}.installedVersions.${version}`;\n const versionRecord = this.assertRecord(rawVersion, field) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(versionRecord.installDirectory, `${field}.installDirectory`);\n this.assertExactPath(installDirectory, this.appHomeService.getInstallDirectory(appId, version), `${field}.installDirectory`);\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n target: versionRecord.target === undefined ? undefined : this.platformTargetService.parseArtifactTarget(versionRecord.target, `${field}.target`),\n components: this.parseComponents(versionRecord.components, installDirectory, `${field}.components`),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) && versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion : 1,\n };\n }\n return installedVersions;\n };\n\n private parseDefaultInstance = (raw: unknown, appId: string, dataDirectory: string, createdAt: string): AppInstanceRecord => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n this.assertExactPath(dataDirectory, this.appHomeService.getAppDataDirectory(appId), `registry.apps.${appId}.dataDirectory`);\n return this.instanceStorageService.buildLegacyDefaultInstance({ appId, dataDirectory, createdAt });\n }\n const instance = raw as Partial<AppInstanceRecord>;\n if (instance.id !== \"default\" || !instance.storage || typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")) {\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\" && Number.isSafeInteger(instance.dataSchemaVersion) && instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\" ? instance.createdAt : createdAt;\n const instanceDirectory = path.resolve(this.appHomeService.getAppInstanceDirectory(appId, \"default\"));\n const expectedStorage = {\n layout: \"instance-v1\" as const, layoutVersion: 1 as const, instanceId: \"default\", instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"), configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"), cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"), logsDirectory: path.join(instanceDirectory, \"logs\"),\n };\n if (instance.storage.layout === \"legacy\") {\n const legacy = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(dataDirectory, legacy, `registry.apps.${appId}.dataDirectory`);\n return { id: \"default\", publisherId: instance.publisherId, storage: { ...expectedStorage, layout: \"legacy\", dataDirectory: path.resolve(legacy) }, dataSchemaVersion, createdAt: instanceCreatedAt, migratedAt: instance.migratedAt, legacyDataDirectory: path.resolve(legacy) };\n }\n for (const [field, expectedPath] of Object.entries(expectedStorage).filter(([key]) => key.endsWith(\"Directory\"))) {\n this.assertExactPath(instance.storage[field as keyof typeof instance.storage], expectedPath as string, `registry.apps.${appId}.defaultInstance.storage.${field}`);\n }\n this.assertExactPath(dataDirectory, expectedStorage.dataDirectory, `registry.apps.${appId}.dataDirectory`);\n return { id: \"default\", publisherId: instance.publisherId, storage: expectedStorage, dataSchemaVersion, createdAt: instanceCreatedAt, migratedAt: instance.migratedAt, legacyDataDirectory: instance.legacyDataDirectory };\n };\n\n private parseSecretBindings = (raw: unknown, field: string): AppSecretBindingMap => {\n if (raw === undefined) return {};\n return Object.fromEntries(Object.entries(this.assertRecord(raw, field)).map(([slotId, binding]) => {\n const normalized = this.parseSecretSlotId(slotId, `${field} slot id`);\n return [normalized, this.parseSecretBinding(binding, `${field}.${normalized}`)];\n }));\n };\n\n private parseComponents = (raw: unknown, installDirectory: string, field: string): AppResolvedComponent[] | undefined => {\n if (raw === undefined) return undefined;\n if (!Array.isArray(raw)) throw new Error(`${field} 必须是数组。`);\n return raw.map((item, index) => {\n const component = this.assertRecord(item, `${field}[${index}]`);\n if (component.kind !== \"panel\" && component.kind !== \"service\") throw new Error(`${field}[${index}].kind 无效。`);\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) throw new Error(`${field}[${index}].id 不安全。`);\n const relativePath = this.requireString(component.path, `${field}[${index}].path`);\n const directory = path.resolve(installDirectory, relativePath);\n const relative = path.relative(path.resolve(installDirectory), directory);\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative)) throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n const manifestPath = path.join(directory, component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\");\n this.assertExactPath(component.componentDirectory, directory, `${field}[${index}].componentDirectory`);\n this.assertExactPath(component.manifestPath, manifestPath, `${field}[${index}].manifestPath`);\n return { kind: component.kind, id, path: relativePath, componentDirectory: directory, manifestPath };\n });\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`${field} 必须是对象。`);\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) throw new Error(`${field} 必须是非空字符串。`);\n return value;\n };\n}\n"],"mappings":";;;;AAcA,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,4BAA4B;AAClC,MAAM,8BAA8B;AAEpC,IAAa,2BAAb,MAAsC;CACpC;CACA,wBAAyC,IAAI,0BAA0B;CAEvE,YAAY,gBAAiD;AAAhC,OAAA,iBAAA;AAC3B,OAAK,yBAAyB,IAAI,0BAA0B,eAAe;;CAG7E,SAAS,gBAAsC;EAC7C,MAAM,YAAY,KAAK,aAAa,aAAa,gBAAgB;AACjE,MAAI,UAAU,kBAAkB,EAAG,OAAM,IAAI,MAAM,oCAAoC;EACvF,MAAM,UAAU,KAAK,aAAa,UAAU,MAAM,gBAAgB;EAClE,MAAM,OAA6C,EAAE;AACrD,OAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,QAAQ,EAAE;AACrD,OAAI,CAAC,oBAAoB,KAAK,MAAM,CAAE,OAAM,IAAI,MAAM,8BAA8B,QAAQ;GAC5F,MAAM,MAAM,KAAK,aAAa,QAAQ,iBAAiB,QAAQ;GAC/D,MAAM,oBAAoB,KAAK,uBAAuB,OAAO,IAAI,kBAAkB;GACnF,MAAM,gBAAgB,KAAK,cAAc,IAAI,eAAe,iBAAiB,MAAM,gBAAgB;AACnG,OAAI,CAAC,kBAAkB,eAAgB,OAAM,IAAI,MAAM,iBAAiB,MAAM,oBAAoB,cAAc,GAAG;GACnH,MAAM,gBAAgB,KAAK,cAAc,IAAI,eAAe,iBAAiB,MAAM,gBAAgB;GACnG,MAAM,mBAAmB,OAAO,OAAO,kBAAkB,CACtD,KAAK,YAAY,QAAQ,YAAY,CACrC,QAAQ,UAA2B,OAAO,UAAU,SAAS,CAC7D,MAAM,CAAC,uBAAM,IAAI,KAAK,EAAE,EAAC,aAAa;GACzC,MAAM,kBAAkB,KAAK,qBAAqB,IAAI,iBAAiB,OAAO,eAAe,iBAAiB;AAC9G,QAAK,SAAS;IACZ,GAAI;IACJ;IACA,WAAW,IAAI,aAAa,kBAAkB,gBAAgB;IAC9D,SAAS,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU;IAC1D;IACA,eAAe,gBAAgB,QAAQ;IACvC;IACA;IACA,QAAQ,IAAI,UAAU,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,EAAE;IACtE,gBAAgB,KAAK,oBAAoB,IAAI,gBAAgB,iBAAiB,MAAM,iBAAiB;IACtG;;AAUH,SAAO;GAAE,eAAe;GAAG;GAAM,oBARN,UAAU,sBACnC,OAAO,UAAU,uBAAuB,YAAY,CAAC,MAAM,QAAQ,UAAU,mBAAmB,GAC9F,OAAO,YAAY,OAAO,QAAQ,UAAU,mBAAmB,CAAC,SAAS,CAAC,OAAO,SAAS;AACxF,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAAE,QAAO,EAAE;IACpE,MAAM,eAAgB,IAAmC;AACzD,WAAO,OAAO,iBAAiB,WAAW,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE;KAC1E,CAAC,GACH,EAAE;GAC+C;;CAGvD,qBAAqB,QAAgB,UAA0B;AAC7D,MAAI,CAAC,4BAA4B,KAAK,OAAO,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,4BAA4B;AACpG,SAAO;;CAGT,sBAAsB,YAAqB,UAAoC;EAC7E,MAAM,UAAU,KAAK,aAAa,YAAY,MAAM;AACpD,MAAI,OAAO,UAAU,eAAe,KAAK,SAAS,QAAQ,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB;AAEvG,MADyB,OAAO,KAAK,QAAQ,CAAC,MAAM,QAAQ,CAAC;GAAC;GAAU;GAAY;GAAK,CAAC,SAAS,IAAI,CAAC,CAClF,OAAM,IAAI,MAAM,GAAG,MAAM,8BAA8B;EAC7E,MAAM,SAAS,KAAK,cAAc,QAAQ,QAAQ,GAAG,MAAM,SAAS;AACpE,MAAI,WAAW,SAAS,WAAW,UAAU,WAAW,OACtD,OAAM,IAAI,MAAM,GAAG,MAAM,8BAA8B;AAEzD,SAAO;GACL;GACA,UAAU,QAAQ,aAAa,KAAA,IAAY,KAAA,IAAY,KAAK,cAAc,QAAQ,UAAU,GAAG,MAAM,WAAW;GAChH,IAAI,KAAK,cAAc,QAAQ,IAAI,GAAG,MAAM,KAAK;GAClD;;CAGH,gCAAgC,UAA+B,gBAAqD;EAClH,MAAM,WAAW,IAAI,KAAK,YAAY,WAAW,EAAE,EAAE,KAAK,SAAS,KAAK,GAAG,CAAC;AAC5E,SAAO,OAAO,YAAY,OAAO,QAAQ,SAAS,CAAC,QAAQ,CAAC,YAAY,SAAS,IAAI,OAAO,CAAC,CAAC;;CAGhG,0BAAkC,OAAe,QAA8D;EAC7G,MAAM,oBAAiE,EAAE;EACzE,MAAM,cAAc,KAAK,aAAa,KAAK,iBAAiB,MAAM,oBAAoB;AACtF,OAAK,MAAM,CAAC,SAAS,eAAe,OAAO,QAAQ,YAAY,EAAE;AAC/D,OAAI,CAAC,qBAAqB,KAAK,QAAQ,CAAE,OAAM,IAAI,MAAM,iBAAiB,MAAM,YAAY,UAAU;GACtG,MAAM,QAAQ,iBAAiB,MAAM,qBAAqB;GAC1D,MAAM,gBAAgB,KAAK,aAAa,YAAY,MAAM;GAC1D,MAAM,mBAAmB,KAAK,cAAc,cAAc,kBAAkB,GAAG,MAAM,mBAAmB;AACxG,QAAK,gBAAgB,kBAAkB,KAAK,eAAe,oBAAoB,OAAO,QAAQ,EAAE,GAAG,MAAM,mBAAmB;AAC5H,qBAAkB,WAAW;IAC3B,GAAI;IACJ;IACA,kBAAkB,KAAK,QAAQ,iBAAiB;IAChD,uBAAuB,cAAc,0BAA0B,IAAI,IAAI;IACvE,QAAQ,cAAc,WAAW,KAAA,IAAY,KAAA,IAAY,KAAK,sBAAsB,oBAAoB,cAAc,QAAQ,GAAG,MAAM,SAAS;IAChJ,YAAY,KAAK,gBAAgB,cAAc,YAAY,kBAAkB,GAAG,MAAM,aAAa;IACnG,mBAAmB,OAAO,cAAc,sBAAsB,YAC5D,OAAO,cAAc,cAAc,kBAAkB,IAAI,cAAc,oBAAoB,IACzF,cAAc,oBAAoB;IACvC;;AAEH,SAAO;;CAGT,wBAAgC,KAAc,OAAe,eAAuB,cAAyC;AAC3H,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,EAAE;AACzD,QAAK,gBAAgB,eAAe,KAAK,eAAe,oBAAoB,MAAM,EAAE,iBAAiB,MAAM,gBAAgB;AAC3H,UAAO,KAAK,uBAAuB,2BAA2B;IAAE;IAAO;IAAe;IAAW,CAAC;;EAEpG,MAAM,WAAW;AACjB,MAAI,SAAS,OAAO,aAAa,CAAC,SAAS,WAAW,OAAO,SAAS,YAAY,YAC/E,SAAS,QAAQ,WAAW,YAAY,SAAS,QAAQ,WAAW,cACrE,OAAM,IAAI,MAAM,iBAAiB,MAAM,sBAAsB;AAE/D,MAAI,SAAS,gBAAgB,KAAA,KAAa,OAAO,SAAS,gBAAgB,SACxE,OAAM,IAAI,MAAM,iBAAiB,MAAM,kCAAkC;EAE3E,MAAM,oBAAoB,OAAO,SAAS,sBAAsB,YAAY,OAAO,cAAc,SAAS,kBAAkB,IAAI,SAAS,oBAAoB,IACzJ,SAAS,oBAAoB;EACjC,MAAM,oBAAoB,OAAO,SAAS,cAAc,WAAW,SAAS,YAAY;EACxF,MAAM,oBAAoB,KAAK,QAAQ,KAAK,eAAe,wBAAwB,OAAO,UAAU,CAAC;EACrG,MAAM,kBAAkB;GACtB,QAAQ;GAAwB,eAAe;GAAY,YAAY;GAAW;GAClF,eAAe,KAAK,KAAK,mBAAmB,OAAO;GAAE,iBAAiB,KAAK,KAAK,mBAAmB,SAAS;GAC5G,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;GAAE,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;GAC5G,oBAAoB,KAAK,KAAK,mBAAmB,MAAM;GAAE,eAAe,KAAK,KAAK,mBAAmB,OAAO;GAC7G;AACD,MAAI,SAAS,QAAQ,WAAW,UAAU;GACxC,MAAM,SAAS,KAAK,eAAe,oBAAoB,MAAM;AAC7D,QAAK,gBAAgB,eAAe,QAAQ,iBAAiB,MAAM,gBAAgB;AACnF,UAAO;IAAE,IAAI;IAAW,aAAa,SAAS;IAAa,SAAS;KAAE,GAAG;KAAiB,QAAQ;KAAU,eAAe,KAAK,QAAQ,OAAO;KAAE;IAAE;IAAmB,WAAW;IAAmB,YAAY,SAAS;IAAY,qBAAqB,KAAK,QAAQ,OAAO;IAAE;;AAElR,OAAK,MAAM,CAAC,OAAO,iBAAiB,OAAO,QAAQ,gBAAgB,CAAC,QAAQ,CAAC,SAAS,IAAI,SAAS,YAAY,CAAC,CAC9G,MAAK,gBAAgB,SAAS,QAAQ,QAAyC,cAAwB,iBAAiB,MAAM,2BAA2B,QAAQ;AAEnK,OAAK,gBAAgB,eAAe,gBAAgB,eAAe,iBAAiB,MAAM,gBAAgB;AAC1G,SAAO;GAAE,IAAI;GAAW,aAAa,SAAS;GAAa,SAAS;GAAiB;GAAmB,WAAW;GAAmB,YAAY,SAAS;GAAY,qBAAqB,SAAS;GAAqB;;CAG5N,uBAA+B,KAAc,UAAuC;AAClF,MAAI,QAAQ,KAAA,EAAW,QAAO,EAAE;AAChC,SAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,aAAa,KAAK,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,aAAa;GACjG,MAAM,aAAa,KAAK,kBAAkB,QAAQ,GAAG,MAAM,UAAU;AACrE,UAAO,CAAC,YAAY,KAAK,mBAAmB,SAAS,GAAG,MAAM,GAAG,aAAa,CAAC;IAC/E,CAAC;;CAGL,mBAA2B,KAAc,kBAA0B,UAAsD;AACvH,MAAI,QAAQ,KAAA,EAAW,QAAO,KAAA;AAC9B,MAAI,CAAC,MAAM,QAAQ,IAAI,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,SAAS;AAC3D,SAAO,IAAI,KAAK,MAAM,UAAU;GAC9B,MAAM,YAAY,KAAK,aAAa,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG;AAC/D,OAAI,UAAU,SAAS,WAAW,UAAU,SAAS,UAAW,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,YAAY;GAC9G,MAAM,KAAK,KAAK,cAAc,UAAU,IAAI,GAAG,MAAM,GAAG,MAAM,MAAM;AACpE,OAAI,CAAC,0BAA0B,KAAK,GAAG,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,WAAW;GACtF,MAAM,eAAe,KAAK,cAAc,UAAU,MAAM,GAAG,MAAM,GAAG,MAAM,QAAQ;GAClF,MAAM,YAAY,KAAK,QAAQ,kBAAkB,aAAa;GAC9D,MAAM,WAAW,KAAK,SAAS,KAAK,QAAQ,iBAAiB,EAAE,UAAU;AACzE,OAAI,CAAC,YAAY,SAAS,WAAW,KAAK,IAAI,KAAK,WAAW,SAAS,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,mBAAmB;GAC9H,MAAM,eAAe,KAAK,KAAK,WAAW,UAAU,SAAS,UAAU,mBAAmB,mBAAmB;AAC7G,QAAK,gBAAgB,UAAU,oBAAoB,WAAW,GAAG,MAAM,GAAG,MAAM,sBAAsB;AACtG,QAAK,gBAAgB,UAAU,cAAc,cAAc,GAAG,MAAM,GAAG,MAAM,gBAAgB;AAC7F,UAAO;IAAE,MAAM,UAAU;IAAM;IAAI,MAAM;IAAc,oBAAoB;IAAW;IAAc;IACpG;;CAGJ,mBAA2B,QAAiB,UAAkB,UAAwB;AACpF,MAAI,OAAO,WAAW,YAAY,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,SAAS,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,YAAY,KAAK,QAAQ,SAAS,CAAC,GAAG;;CAGpJ,gBAAwB,OAAgB,UAA2C;AACjF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,SAAS;AACnG,SAAO;;CAGT,iBAAyB,OAAgB,UAA0B;AACjE,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,CAAE,OAAM,IAAI,MAAM,GAAG,MAAM,YAAY;AACrF,SAAO"}
|
|
1
|
+
{"version":3,"file":"app-registry-parser.service.js","names":[],"sources":["../../src/services/app-registry-parser.service.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n AppPermissions,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppStoredDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport type { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { AppPlatformTargetService } from \"#app-runtime/services/app-platform-target.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n AppSecretBinding,\n AppSecretBindingMap,\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]+)*$/;\nconst SAFE_SECRET_SLOT_ID_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;\n\nexport class AppRegistryParserService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly platformTargetService = new AppPlatformTargetService();\n\n constructor(private readonly appHomeService: AppHomeService) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n parse = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1)\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\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 const app = this.assertRecord(\n rawApp,\n `registry.apps.${appId}`,\n ) as Partial<AppRegistryAppRecord>;\n const installedVersions = this.parseInstalledVersions(\n appId,\n app.installedVersions,\n );\n const activeVersion = this.requireString(\n app.activeVersion,\n `registry.apps.${appId}.activeVersion`,\n );\n if (!installedVersions[activeVersion])\n throw new Error(\n `registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`,\n );\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt =\n 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: this.parseDocumentGrants(\n app.grants,\n installedVersions[activeVersion]?.permissions ?? {},\n `registry.apps.${appId}.grants`,\n ),\n secretBindings: this.parseSecretBindings(\n app.secretBindings,\n `registry.apps.${appId}.secretBindings`,\n ),\n };\n }\n const suppressedBuiltIns =\n candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(\n Object.entries(candidate.suppressedBuiltIns).flatMap(\n ([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw))\n return [];\n const suppressedAt = (raw as { suppressedAt?: unknown })\n .suppressedAt;\n return typeof suppressedAt === \"string\"\n ? [[appId, { suppressedAt }]]\n : [];\n },\n ),\n )\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n parseSecretSlotId = (slotId: string, field: string): string => {\n if (!SAFE_SECRET_SLOT_ID_PATTERN.test(slotId))\n throw new Error(`${field} 必须是稳定的 lowercase slot id。`);\n return slotId;\n };\n\n parseSecretBinding = (\n rawBinding: unknown,\n field: string,\n ): AppSecretBinding => {\n const binding = this.assertRecord(rawBinding, field);\n if (Object.prototype.hasOwnProperty.call(binding, \"value\"))\n throw new Error(`${field} 不能保存 Secret 明文。`);\n const unsupportedField = Object.keys(binding).find(\n (key) => ![\"source\", \"provider\", \"id\"].includes(key),\n );\n if (unsupportedField)\n throw new Error(`${field} 只允许保存 source、provider 和 id。`);\n const source = this.requireString(binding.source, `${field}.source`);\n if (source !== \"env\" && source !== \"file\" && source !== \"exec\") {\n throw new Error(`${field}.source 只支持 env、file 或 exec。`);\n }\n return {\n source,\n provider:\n binding.provider === undefined\n ? undefined\n : this.requireString(binding.provider, `${field}.provider`),\n id: this.requireString(binding.id, `${field}.id`),\n };\n };\n\n retainDeclaredSecretBindings = (\n bindings: AppSecretBindingMap,\n permissions: AppPermissions,\n ): AppSecretBindingMap => {\n const declared = new Set(\n (permissions.secrets ?? []).map((slot) => slot.id),\n );\n return Object.fromEntries(\n Object.entries(bindings).filter(([slotId]) => declared.has(slotId)),\n );\n };\n\n retainCompatibleDocumentGrants = (\n grants: AppStoredDocumentGrantMap,\n permissions: AppPermissions,\n ): AppStoredDocumentGrantMap => {\n const declared = new Map(\n (permissions.documentAccess ?? []).map((scope) => [scope.id, scope]),\n );\n return Object.fromEntries(\n Object.entries(grants).filter(([scopeId, grant]) => {\n const scope = declared.get(scopeId);\n return scope && (scope.mode === \"read-write\" || grant.mode === \"read\");\n }),\n );\n };\n\n private parseDocumentGrants = (\n raw: unknown,\n permissions: AppPermissions,\n field: string,\n ): AppStoredDocumentGrantMap => {\n if (raw === undefined) return {};\n const declared = new Map(\n (permissions.documentAccess ?? []).map((scope) => [scope.id, scope]),\n );\n return Object.fromEntries(\n Object.entries(this.assertRecord(raw, field)).flatMap(\n ([scopeId, value]) => {\n const scope = declared.get(scopeId);\n if (!scope) return [];\n if (typeof value === \"string\") {\n return [\n [\n scopeId,\n {\n path: value,\n mode: scope.mode,\n grantedAt: new Date(0).toISOString(),\n },\n ],\n ];\n }\n const grant = this.assertRecord(value, `${field}.${scopeId}`);\n const mode =\n grant.mode === \"read-write\"\n ? \"read-write\"\n : grant.mode === \"read\"\n ? \"read\"\n : undefined;\n if (!mode || (mode === \"read-write\" && scope.mode !== \"read-write\"))\n return [];\n return [\n [\n scopeId,\n {\n path: this.requireString(\n grant.path,\n `${field}.${scopeId}.path`,\n ),\n mode,\n grantedAt:\n typeof grant.grantedAt === \"string\"\n ? grant.grantedAt\n : new Date(0).toISOString(),\n },\n ],\n ];\n },\n ),\n );\n };\n\n private parseInstalledVersions = (\n appId: string,\n raw: unknown,\n ): Record<string, AppRegistryInstalledVersion> => {\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n raw,\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 const field = `registry.apps.${appId}.installedVersions.${version}`;\n const versionRecord = this.assertRecord(\n rawVersion,\n field,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `${field}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `${field}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion:\n versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n target:\n versionRecord.target === undefined\n ? undefined\n : this.platformTargetService.parseArtifactTarget(\n versionRecord.target,\n `${field}.target`,\n ),\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `${field}.components`,\n ),\n dataSchemaVersion:\n typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n return installedVersions;\n };\n\n private parseDefaultInstance = (\n raw: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\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 = raw as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" &&\n instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (\n instance.publisherId !== undefined &&\n typeof instance.publisherId !== \"string\"\n ) {\n throw new Error(\n `registry.apps.${appId}.defaultInstance.publisherId 无效。`,\n );\n }\n const dataSchemaVersion =\n typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt =\n typeof instance.createdAt === \"string\" ? instance.createdAt : 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 legacy = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n legacy,\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(legacy),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(legacy),\n };\n }\n for (const [field, expectedPath] of Object.entries(expectedStorage).filter(\n ([key]) => key.endsWith(\"Directory\"),\n )) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage],\n expectedPath as string,\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 parseSecretBindings = (\n raw: unknown,\n field: string,\n ): AppSecretBindingMap => {\n if (raw === undefined) return {};\n return Object.fromEntries(\n Object.entries(this.assertRecord(raw, field)).map(([slotId, binding]) => {\n const normalized = this.parseSecretSlotId(slotId, `${field} slot id`);\n return [\n normalized,\n this.parseSecretBinding(binding, `${field}.${normalized}`),\n ];\n }),\n );\n };\n\n private parseComponents = (\n raw: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (raw === undefined) return undefined;\n if (!Array.isArray(raw)) throw new Error(`${field} 必须是数组。`);\n return raw.map((item, index) => {\n const component = this.assertRecord(item, `${field}[${index}]`);\n if (component.kind !== \"panel\" && component.kind !== \"service\")\n throw new Error(`${field}[${index}].kind 无效。`);\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 const relativePath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const directory = path.resolve(installDirectory, relativePath);\n const relative = path.relative(path.resolve(installDirectory), directory);\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative))\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n const manifestPath = path.join(\n directory,\n component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n directory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n manifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind: component.kind,\n id,\n path: relativePath,\n componentDirectory: directory,\n manifestPath,\n };\n });\n };\n\n private assertExactPath = (\n actual: unknown,\n expected: string,\n field: string,\n ): void => {\n if (\n typeof actual !== \"string\" ||\n path.resolve(actual) !== path.resolve(expected)\n )\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n };\n\n private assertRecord = (\n value: unknown,\n field: string,\n ): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n throw new Error(`${field} 必须是对象。`);\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 return value;\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent } from "../types/app-manifest.types.js";
|
|
2
|
-
import { AppDocumentGrantMap } from "../types/app-permissions.types.js";
|
|
2
|
+
import { AppDocumentGrantMap, AppStoredDocumentGrant } from "../types/app-permissions.types.js";
|
|
3
3
|
import { AppHomeService } from "./app-home.service.js";
|
|
4
4
|
import { AppInstanceRecord } from "../types/app-storage.types.js";
|
|
5
5
|
import { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion, AppSecretBinding } from "../types/app-registry.types.js";
|
|
@@ -46,7 +46,8 @@ declare class AppRegistryService {
|
|
|
46
46
|
activateVersion: (appId: string, version: string) => Promise<AppRegistryAppRecord>;
|
|
47
47
|
setVersionContentDigest: (appId: string, version: string, contentSha256: string) => Promise<AppRegistryAppRecord>;
|
|
48
48
|
updateGrants: (appId: string, grants: AppDocumentGrantMap) => Promise<AppRegistryAppRecord>;
|
|
49
|
-
setDocumentGrant: (appId: string, scopeId: string, directoryPath: string) => Promise<AppRegistryAppRecord>;
|
|
49
|
+
setDocumentGrant: (appId: string, scopeId: string, directoryPath: string, mode?: "read" | "read-write") => Promise<AppRegistryAppRecord>;
|
|
50
|
+
restoreDocumentGrant: (appId: string, scopeId: string, grant: AppStoredDocumentGrant | undefined) => Promise<void>;
|
|
50
51
|
removeDocumentGrant: (appId: string, scopeId: string) => Promise<boolean>;
|
|
51
52
|
bindSecret: (appId: string, slotId: string, binding: AppSecretBinding) => Promise<AppRegistryAppRecord>;
|
|
52
53
|
unbindSecret: (appId: string, slotId: string) => Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;cAuBa,kBAAA;EAAA,iBAKQ,cAAA;EAAA,iBAJF,eAAA;EAAA,iBACA,MAAA;cAGE,cAAA,GAAgB,cAAA;EAKnC,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAezB,IAAA,GAAc,QAAA,EAAU,WAAA,KAAc,OAAA;EAAA,QAI9B,YAAA;EAsBR,QAAA,QAAqB,OAAA,CAAQ,oBAAA;EAO7B,MAAA,GAAgB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAKxC,gBAAA,GACE,KAAA,aACC,OAAA,CAAQ,2BAAA;EAKX,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,MAAA,GAAS,2BAAA;IACT,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;EA2EZ,UAAA,GACE,KAAA,UACA,OAAA,cACC,OAAA,CAAQ,oBAAA;EAIX,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EAoBX,uBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAwBX,YAAA,GACE,KAAA,UACA,MAAA,EAAQ,mBAAA,KACP,OAAA,CAAQ,oBAAA;EAyBX,gBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,UACA,IAAA,6BACC,OAAA,CAAQ,oBAAA;EA6BX,oBAAA,GACE,KAAA,UACA,OAAA,UACA,KAAA,EAAO,sBAAA,iBACN,OAAA;EASH,mBAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA;EAcH,UAAA,GACE,KAAA,UACA,MAAA,UACA,OAAA,EAAS,gBAAA,KACR,OAAA,CAAQ,oBAAA;EA2BX,YAAA,GAAsB,KAAA,UAAe,MAAA,aAAiB,OAAA;EAkBtD,SAAA,GACE,KAAA,aACC,OAAA,CAAQ,oBAAA;EAaX,mBAAA,GAA6B,KAAA,aAAgB,OAAA;EAK7C,oBAAA,GACE,KAAA,UACA,UAAA,cACC,OAAA;EAAA,QAcK,SAAA;EAAA,QAiBA,YAAA;EAAA,QAQA,kBAAA;AAAA"}
|
|
@@ -96,7 +96,7 @@ var AppRegistryService = class {
|
|
|
96
96
|
contentSha256: params.contentSha256
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
|
-
grants: currentRecord?.grants ?? {},
|
|
99
|
+
grants: this.parser.retainCompatibleDocumentGrants(currentRecord?.grants ?? {}, activePermissions),
|
|
100
100
|
secretBindings: this.parser.retainDeclaredSecretBindings(currentRecord?.secretBindings ?? {}, activePermissions)
|
|
101
101
|
};
|
|
102
102
|
registry.apps[params.appId] = nextRecord;
|
|
@@ -116,6 +116,7 @@ var AppRegistryService = class {
|
|
|
116
116
|
return {
|
|
117
117
|
...record,
|
|
118
118
|
activeVersion: version,
|
|
119
|
+
grants: this.parser.retainCompatibleDocumentGrants(record.grants, record.installedVersions[version].permissions),
|
|
119
120
|
secretBindings: this.parser.retainDeclaredSecretBindings(record.secretBindings, record.installedVersions[version].permissions)
|
|
120
121
|
};
|
|
121
122
|
});
|
|
@@ -138,16 +139,57 @@ var AppRegistryService = class {
|
|
|
138
139
|
});
|
|
139
140
|
};
|
|
140
141
|
updateGrants = async (appId, grants) => {
|
|
141
|
-
return await this.updateApp(appId, (record) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
return await this.updateApp(appId, (record) => {
|
|
143
|
+
const permissions = record.installedVersions[record.activeVersion]?.permissions ?? {};
|
|
144
|
+
const scopes = new Map((permissions.documentAccess ?? []).map((scope) => [scope.id, scope]));
|
|
145
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
146
|
+
const stored = Object.fromEntries(Object.entries(grants).map(([scopeId, directoryPath]) => {
|
|
147
|
+
const scope = scopes.get(scopeId);
|
|
148
|
+
if (!scope) throw new Error(`应用 ${appId} 未声明 documentAccess scope:${scopeId}`);
|
|
149
|
+
return [scopeId, {
|
|
150
|
+
path: directoryPath,
|
|
151
|
+
mode: scope.mode,
|
|
152
|
+
grantedAt: now
|
|
153
|
+
}];
|
|
154
|
+
}));
|
|
155
|
+
return {
|
|
156
|
+
...record,
|
|
157
|
+
grants: {
|
|
158
|
+
...record.grants,
|
|
159
|
+
...stored
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
setDocumentGrant = async (appId, scopeId, directoryPath, mode) => {
|
|
165
|
+
return await this.updateApp(appId, (record) => {
|
|
166
|
+
const scope = record.installedVersions[record.activeVersion]?.permissions.documentAccess?.find((entry) => entry.id === scopeId);
|
|
167
|
+
if (!scope) throw new Error(`应用 ${appId} 未声明 documentAccess scope:${scopeId}`);
|
|
168
|
+
const effectiveMode = mode ?? scope.mode;
|
|
169
|
+
if (effectiveMode === "read-write" && scope.mode !== "read-write") throw new Error(`应用 ${appId} 的 documentAccess scope ${scopeId} 只声明了 read。`);
|
|
170
|
+
return {
|
|
171
|
+
...record,
|
|
172
|
+
grants: {
|
|
173
|
+
...record.grants,
|
|
174
|
+
[scopeId]: {
|
|
175
|
+
path: directoryPath,
|
|
176
|
+
mode: effectiveMode,
|
|
177
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
});
|
|
148
182
|
};
|
|
149
|
-
|
|
150
|
-
|
|
183
|
+
restoreDocumentGrant = async (appId, scopeId, grant) => {
|
|
184
|
+
await this.updateApp(appId, (record) => {
|
|
185
|
+
const grants = { ...record.grants };
|
|
186
|
+
if (grant) grants[scopeId] = grant;
|
|
187
|
+
else delete grants[scopeId];
|
|
188
|
+
return {
|
|
189
|
+
...record,
|
|
190
|
+
grants
|
|
191
|
+
};
|
|
192
|
+
});
|
|
151
193
|
};
|
|
152
194
|
removeDocumentGrant = async (appId, scopeId) => {
|
|
153
195
|
let removed = false;
|
|
@@ -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 { AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent } 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 { AppRegistryParserService } from \"#app-runtime/services/app-registry-parser.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 AppSecretBinding,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppRegistryService {\n private readonly fileLockService = new FileLockService();\n private readonly parser: AppRegistryParserService;\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.parser = new AppRegistryParserService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parser.parse(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 target?: AppRegistryInstalledVersion[\"target\"];\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 activeVersion = params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version;\n const activePermissions = activeVersion === params.version\n ? params.permissions\n : currentRecord?.installedVersions[activeVersion]?.permissions ?? {};\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion,\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 target: params.target,\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 // A SecretRef is an active permission, not retained App data. Keep only\n // bindings declared by the active version so an update cannot leave an\n // undeclared credential reachable by a later runtime snapshot.\n secretBindings: this.parser.retainDeclaredSecretBindings(\n currentRecord?.secretBindings ?? {},\n activePermissions,\n ),\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 {\n ...record,\n activeVersion: version,\n secretBindings: this.parser.retainDeclaredSecretBindings(\n record.secretBindings,\n record.installedVersions[version].permissions,\n ),\n };\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 bindSecret = async (\n appId: string,\n slotId: string,\n binding: AppSecretBinding,\n ): Promise<AppRegistryAppRecord> => {\n const normalizedSlotId = this.parser.parseSecretSlotId(slotId, \"secret slot id\");\n const normalizedBinding = this.parser.parseSecretBinding(binding, `secret binding ${normalizedSlotId}`);\n return await this.updateApp(appId, (record) => {\n const activeVersion = record.installedVersions[record.activeVersion];\n const declaredSlots = activeVersion?.permissions.secrets ?? [];\n if (!declaredSlots.some((slot) => slot.id === normalizedSlotId)) {\n throw new Error(`应用 ${appId} 未声明 Secret slot:${normalizedSlotId}`);\n }\n return {\n ...record,\n secretBindings: {\n ...record.secretBindings,\n [normalizedSlotId]: normalizedBinding,\n },\n };\n });\n };\n\n unbindSecret = async (appId: string, slotId: string): Promise<boolean> => {\n const normalizedSlotId = this.parser.parseSecretSlotId(slotId, \"secret slot id\");\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(normalizedSlotId in record.secretBindings)) {\n return record;\n }\n const secretBindings = { ...record.secretBindings };\n delete secretBindings[normalizedSlotId];\n removed = true;\n return { ...record, secretBindings };\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\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 {\n AppDocumentGrantMap,\n AppStoredDocumentGrant,\n} from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppRegistryParserService } from \"#app-runtime/services/app-registry-parser.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 AppSecretBinding,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppRegistryService {\n private readonly fileLockService = new FileLockService();\n private readonly parser: AppRegistryParserService;\n\n constructor(\n private readonly appHomeService: AppHomeService = new AppHomeService(),\n ) {\n this.parser = new AppRegistryParserService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n try {\n const raw = await readFile(\n this.appHomeService.getRegistryPath(),\n \"utf-8\",\n );\n return this.parser.parse(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) =>\n left.appId.localeCompare(right.appId),\n );\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 (\n appId: string,\n ): 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 target?: AppRegistryInstalledVersion[\"target\"];\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 =\n currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]\n ?.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 activeVersion =\n params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version;\n const activePermissions =\n activeVersion === params.version\n ? params.permissions\n : (currentRecord?.installedVersions[activeVersion]?.permissions ??\n {});\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion,\n enabled:\n params.enabled ??\n currentRecord?.enabled ??\n 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 target: params.target,\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: this.parser.retainCompatibleDocumentGrants(\n currentRecord?.grants ?? {},\n activePermissions,\n ),\n // A SecretRef is an active permission, not retained App data. Keep only\n // bindings declared by the active version so an update cannot leave an\n // undeclared credential reachable by a later runtime snapshot.\n secretBindings: this.parser.retainDeclaredSecretBindings(\n currentRecord?.secretBindings ?? {},\n activePermissions,\n ),\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (\n appId: string,\n enabled: boolean,\n ): 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 {\n ...record,\n activeVersion: version,\n grants: this.parser.retainCompatibleDocumentGrants(\n record.grants,\n record.installedVersions[version].permissions,\n ),\n secretBindings: this.parser.retainDeclaredSecretBindings(\n record.secretBindings,\n record.installedVersions[version].permissions,\n ),\n };\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 (\n installedVersion.contentSha256 &&\n installedVersion.contentSha256 !== contentSha256\n ) {\n throw new Error(\n `应用 ${appId}@${version} 已存在不同的代码完整性摘要。`,\n );\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 const permissions =\n record.installedVersions[record.activeVersion]?.permissions ?? {};\n const scopes = new Map(\n (permissions.documentAccess ?? []).map((scope) => [scope.id, scope]),\n );\n const now = new Date().toISOString();\n const stored = Object.fromEntries(\n Object.entries(grants).map(([scopeId, directoryPath]) => {\n const scope = scopes.get(scopeId);\n if (!scope)\n throw new Error(\n `应用 ${appId} 未声明 documentAccess scope:${scopeId}`,\n );\n return [\n scopeId,\n { path: directoryPath, mode: scope.mode, grantedAt: now },\n ];\n }),\n );\n return { ...record, grants: { ...record.grants, ...stored } };\n });\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n mode?: \"read\" | \"read-write\",\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const scope = record.installedVersions[\n record.activeVersion\n ]?.permissions.documentAccess?.find((entry) => entry.id === scopeId);\n if (!scope)\n throw new Error(\n `应用 ${appId} 未声明 documentAccess scope:${scopeId}`,\n );\n const effectiveMode = mode ?? scope.mode;\n if (effectiveMode === \"read-write\" && scope.mode !== \"read-write\") {\n throw new Error(\n `应用 ${appId} 的 documentAccess scope ${scopeId} 只声明了 read。`,\n );\n }\n return {\n ...record,\n grants: {\n ...record.grants,\n [scopeId]: {\n path: directoryPath,\n mode: effectiveMode,\n grantedAt: new Date().toISOString(),\n },\n },\n };\n });\n };\n\n restoreDocumentGrant = async (\n appId: string,\n scopeId: string,\n grant: AppStoredDocumentGrant | undefined,\n ): Promise<void> => {\n await this.updateApp(appId, (record) => {\n const grants = { ...record.grants };\n if (grant) grants[scopeId] = grant;\n else delete grants[scopeId];\n return { ...record, grants };\n });\n };\n\n removeDocumentGrant = async (\n appId: string,\n scopeId: string,\n ): 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 bindSecret = async (\n appId: string,\n slotId: string,\n binding: AppSecretBinding,\n ): Promise<AppRegistryAppRecord> => {\n const normalizedSlotId = this.parser.parseSecretSlotId(\n slotId,\n \"secret slot id\",\n );\n const normalizedBinding = this.parser.parseSecretBinding(\n binding,\n `secret binding ${normalizedSlotId}`,\n );\n return await this.updateApp(appId, (record) => {\n const activeVersion = record.installedVersions[record.activeVersion];\n const declaredSlots = activeVersion?.permissions.secrets ?? [];\n if (!declaredSlots.some((slot) => slot.id === normalizedSlotId)) {\n throw new Error(\n `应用 ${appId} 未声明 Secret slot:${normalizedSlotId}`,\n );\n }\n return {\n ...record,\n secretBindings: {\n ...record.secretBindings,\n [normalizedSlotId]: normalizedBinding,\n },\n };\n });\n };\n\n unbindSecret = async (appId: string, slotId: string): Promise<boolean> => {\n const normalizedSlotId = this.parser.parseSecretSlotId(\n slotId,\n \"secret slot id\",\n );\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(normalizedSlotId in record.secretBindings)) {\n return record;\n }\n const secretBindings = { ...record.secretBindings };\n delete secretBindings[normalizedSlotId];\n removed = true;\n return { ...record, secretBindings };\n });\n return removed;\n };\n\n removeApp = async (\n appId: string,\n ): 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 (\n appId: string,\n suppressed: boolean,\n ): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = {\n suppressedAt: new Date().toISOString(),\n };\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(\n `${registryPath}.lock`,\n operation,\n );\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":";;;;;;AAuBA,IAAa,qBAAb,MAAgC;CAC9B,kBAAmC,IAAI,iBAAiB;CACxD;CAEA,YACE,iBAAkD,IAAI,gBAAgB,EACtE;AADiB,OAAA,iBAAA;AAEjB,OAAK,SAAS,IAAI,yBAAyB,eAAe;;CAG5D,OAAO,YAAkC;AACvC,MAAI;GACF,MAAM,MAAM,MAAM,SAChB,KAAK,eAAe,iBAAiB,EACrC,QACD;AACD,UAAO,KAAK,OAAO,MAAM,KAAK,MAAM,IAAI,CAAY;WAC7C,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO;IAAE,eAAe;IAAG,MAAM,EAAE;IAAE,oBAAoB,EAAE;IAAE;AAE/D,SAAM;;;CAIV,OAAO,OAAO,aAAyC;AACrD,QAAM,KAAK,aAAa,YAAY,MAAM,KAAK,aAAa,SAAS,CAAC;;CAGxE,eAAuB,OAAO,aAAyC;AACrE,QAAM,KAAK,eAAe,uBAAuB;EACjD,MAAM,eAAe,KAAK,eAAe,iBAAiB;EAC1D,MAAM,gBAAgB,KAAK,KACzB,KAAK,QAAQ,aAAa,EAC1B,IAAI,KAAK,SAAS,aAAa,CAAC,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC,MAC9D;EACD,MAAM,SAAS,MAAM,KAAK,eAAe,MAAM,IAAM;AACrD,MAAI;AACF,SAAM,OAAO,UAAU,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,KAAK,QAAQ;AACzE,SAAM,OAAO,MAAM;YACX;AACR,SAAM,OAAO,OAAO;;AAEtB,MAAI;AACF,SAAM,OAAO,eAAe,aAAa;WAClC,OAAO;AACd,SAAM,GAAG,eAAe,EAAE,OAAO,MAAM,CAAC;AACxC,SAAM;;;CAIV,WAAW,YAA6C;EACtD,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,OAAO,OAAO,SAAS,KAAK,CAAC,MAAM,MAAM,UAC9C,KAAK,MAAM,cAAc,MAAM,MAAM,CACtC;;CAGH,SAAS,OAAO,UAA6D;AAE3E,UADiB,MAAM,KAAK,MAAM,EAClB,KAAK;;CAGvB,mBAAmB,OACjB,UACqD;EACrD,MAAM,YAAY,MAAM,KAAK,OAAO,MAAM;AAC1C,SAAO,WAAW,kBAAkB,UAAU;;CAGhD,qBAAqB,OAAO,WAyBS;AACnC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,gBAAgB,SAAS,KAAK,OAAO;GAC3C,MAAM,mBACJ,eAAe,aACf,eAAe,kBAAkB,cAAc,gBAC3C;AACN,OAAI,oBAAoB,iBAAiB,OAAO,OAAO,WAAW,GAChE,OAAM,IAAI,MACR,MAAM,OAAO,MAAM,UAAU,iBAAiB,GAAG,OAAO,OAAO,WAAW,MAAM,UAAU,MAC3F;GAEH,MAAM,gBACJ,OAAO,aAAa,SAAS,gBACzB,cAAc,gBACd,OAAO;GACb,MAAM,oBACJ,kBAAkB,OAAO,UACrB,OAAO,cACN,eAAe,kBAAkB,gBAAgB,eAClD,EAAE;GACR,MAAM,aAAmC;IACvC,OAAO,OAAO;IACd,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,WAAW,oBAAoB,OAAO;IACtC;IACA,SACE,OAAO,WACP,eAAe,WACf,OAAO,0BAA0B;IACnC,eAAe,OAAO,gBAAgB,QAAQ;IAC9C,iBAAiB,OAAO;IACxB,mBAAmB;KACjB,GAAI,eAAe,qBAAqB,EAAE;MACzC,OAAO,UAAU;MAChB,SAAS,OAAO;MAChB,kBAAkB,OAAO;MACzB,YAAY,OAAO;MACnB,WAAW,OAAO;MAClB,aAAa,OAAO;MACpB,kBAAkB,OAAO;MACzB,aAAa,OAAO;MACpB,aAAa,OAAO;MACpB,WAAW,OAAO;MAClB,QAAQ,OAAO;MACf,QAAQ,OAAO;MACf,WAAW,OAAO;MAClB,uBAAuB,OAAO;MAC9B,YAAY,OAAO;MACnB,gBAAgB,OAAO;MACvB,UAAU,OAAO;MACjB,mBAAmB,OAAO;MAC1B,eAAe,OAAO;MACvB;KACF;IACD,QAAQ,KAAK,OAAO,+BAClB,eAAe,UAAU,EAAE,EAC3B,kBACD;IAID,gBAAgB,KAAK,OAAO,6BAC1B,eAAe,kBAAkB,EAAE,EACnC,kBACD;IACF;AACD,YAAS,KAAK,OAAO,SAAS;AAC9B,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,aAAa,OACX,OACA,YACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,YAAY;GAAE,GAAG;GAAQ;GAAS,EAAE;;CAG1E,kBAAkB,OAChB,OACA,YACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;AAC7C,OAAI,CAAC,OAAO,kBAAkB,SAC5B,OAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,GAAG;AAElD,UAAO;IACL,GAAG;IACH,eAAe;IACf,QAAQ,KAAK,OAAO,+BAClB,OAAO,QACP,OAAO,kBAAkB,SAAS,YACnC;IACD,gBAAgB,KAAK,OAAO,6BAC1B,OAAO,gBACP,OAAO,kBAAkB,SAAS,YACnC;IACF;IACD;;CAGJ,0BAA0B,OACxB,OACA,SACA,kBACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;GAC7C,MAAM,mBAAmB,OAAO,kBAAkB;AAClD,OAAI,CAAC,iBACH,OAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,GAAG;AAElD,OACE,iBAAiB,iBACjB,iBAAiB,kBAAkB,cAEnC,OAAM,IAAI,MACR,MAAM,MAAM,GAAG,QAAQ,iBACxB;AAEH,UAAO;IACL,GAAG;IACH,mBAAmB;KACjB,GAAG,OAAO;MACT,UAAU;MAAE,GAAG;MAAkB;MAAe;KAClD;IACF;IACD;;CAGJ,eAAe,OACb,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;GAC7C,MAAM,cACJ,OAAO,kBAAkB,OAAO,gBAAgB,eAAe,EAAE;GACnE,MAAM,SAAS,IAAI,KAChB,YAAY,kBAAkB,EAAE,EAAE,KAAK,UAAU,CAAC,MAAM,IAAI,MAAM,CAAC,CACrE;GACD,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;GACpC,MAAM,SAAS,OAAO,YACpB,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,SAAS,mBAAmB;IACvD,MAAM,QAAQ,OAAO,IAAI,QAAQ;AACjC,QAAI,CAAC,MACH,OAAM,IAAI,MACR,MAAM,MAAM,4BAA4B,UACzC;AACH,WAAO,CACL,SACA;KAAE,MAAM;KAAe,MAAM,MAAM;KAAM,WAAW;KAAK,CAC1D;KACD,CACH;AACD,UAAO;IAAE,GAAG;IAAQ,QAAQ;KAAE,GAAG,OAAO;KAAQ,GAAG;KAAQ;IAAE;IAC7D;;CAGJ,mBAAmB,OACjB,OACA,SACA,eACA,SACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;GAC7C,MAAM,QAAQ,OAAO,kBACnB,OAAO,gBACN,YAAY,gBAAgB,MAAM,UAAU,MAAM,OAAO,QAAQ;AACpE,OAAI,CAAC,MACH,OAAM,IAAI,MACR,MAAM,MAAM,4BAA4B,UACzC;GACH,MAAM,gBAAgB,QAAQ,MAAM;AACpC,OAAI,kBAAkB,gBAAgB,MAAM,SAAS,aACnD,OAAM,IAAI,MACR,MAAM,MAAM,0BAA0B,QAAQ,aAC/C;AAEH,UAAO;IACL,GAAG;IACH,QAAQ;KACN,GAAG,OAAO;MACT,UAAU;MACT,MAAM;MACN,MAAM;MACN,4BAAW,IAAI,MAAM,EAAC,aAAa;MACpC;KACF;IACF;IACD;;CAGJ,uBAAuB,OACrB,OACA,SACA,UACkB;AAClB,QAAM,KAAK,UAAU,QAAQ,WAAW;GACtC,MAAM,SAAS,EAAE,GAAG,OAAO,QAAQ;AACnC,OAAI,MAAO,QAAO,WAAW;OACxB,QAAO,OAAO;AACnB,UAAO;IAAE,GAAG;IAAQ;IAAQ;IAC5B;;CAGJ,sBAAsB,OACpB,OACA,YACqB;EACrB,IAAI,UAAU;AACd,QAAM,KAAK,UAAU,QAAQ,WAAW;AACtC,OAAI,EAAE,WAAW,OAAO,QACtB,QAAO;GAET,MAAM,SAAS,EAAE,GAAG,OAAO,QAAQ;AACnC,UAAO,OAAO;AACd,aAAU;AACV,UAAO;IAAE,GAAG;IAAQ;IAAQ;IAC5B;AACF,SAAO;;CAGT,aAAa,OACX,OACA,QACA,YACkC;EAClC,MAAM,mBAAmB,KAAK,OAAO,kBACnC,QACA,iBACD;EACD,MAAM,oBAAoB,KAAK,OAAO,mBACpC,SACA,kBAAkB,mBACnB;AACD,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;AAG7C,OAAI,EAFkB,OAAO,kBAAkB,OAAO,gBACjB,YAAY,WAAW,EAAE,EAC3C,MAAM,SAAS,KAAK,OAAO,iBAAiB,CAC7D,OAAM,IAAI,MACR,MAAM,MAAM,mBAAmB,mBAChC;AAEH,UAAO;IACL,GAAG;IACH,gBAAgB;KACd,GAAG,OAAO;MACT,mBAAmB;KACrB;IACF;IACD;;CAGJ,eAAe,OAAO,OAAe,WAAqC;EACxE,MAAM,mBAAmB,KAAK,OAAO,kBACnC,QACA,iBACD;EACD,IAAI,UAAU;AACd,QAAM,KAAK,UAAU,QAAQ,WAAW;AACtC,OAAI,EAAE,oBAAoB,OAAO,gBAC/B,QAAO;GAET,MAAM,iBAAiB,EAAE,GAAG,OAAO,gBAAgB;AACnD,UAAO,eAAe;AACtB,aAAU;AACV,UAAO;IAAE,GAAG;IAAQ;IAAgB;IACpC;AACF,SAAO;;CAGT,YAAY,OACV,UAC8C;AAC9C,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH;AAEF,UAAO,SAAS,KAAK;AACrB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,sBAAsB,OAAO,UAAoC;EAC/D,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,QAAQ,SAAS,mBAAmB,OAAO;;CAGpD,uBAAuB,OACrB,OACA,eACkB;AAClB,QAAM,KAAK,aAAa,YAAY;GAClC,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,OAAI,WACF,UAAS,mBAAmB,SAAS,EACnC,+BAAc,IAAI,MAAM,EAAC,aAAa,EACvC;OAED,QAAO,SAAS,mBAAmB;AAErC,SAAM,KAAK,aAAa,SAAS;IACjC;;CAGJ,YAAoB,OAClB,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;GAEtC,MAAM,aAAa,OAAO,UAAU;AACpC,YAAS,KAAK,SAAS;AACvB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,eAAuB,OAAU,cAA4C;EAC3E,MAAM,eAAe,KAAK,QAAQ,KAAK,eAAe,iBAAiB,CAAC;AACxE,SAAO,MAAM,KAAK,gBAAgB,SAChC,GAAG,aAAa,QAChB,UACD;;CAGH,sBAA8B,UAC5B,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAA6B,SAAS"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppArtifactTarget, AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
|
|
2
2
|
import { AppDistributionMode } from "./app-bundle.types.js";
|
|
3
|
-
import { AppDocumentGrantMap } from "./app-permissions.types.js";
|
|
3
|
+
import { AppDocumentGrantMap, AppStoredDocumentGrantMap } from "./app-permissions.types.js";
|
|
4
4
|
import { AppInstanceRecord, AppStorageContext, AppStorageUsage } from "./app-storage.types.js";
|
|
5
5
|
import { AppPublisher } from "./app-remote-registry.types.js";
|
|
6
6
|
import { AppInstallSourceKind } from "./app-registry.types.js";
|
|
@@ -57,7 +57,7 @@ type AppInfoResult = {
|
|
|
57
57
|
primaryPanelId?: string;
|
|
58
58
|
contentSha256?: string;
|
|
59
59
|
}>;
|
|
60
|
-
grants:
|
|
60
|
+
grants: AppStoredDocumentGrantMap;
|
|
61
61
|
};
|
|
62
62
|
type InstalledAppListItem = {
|
|
63
63
|
appId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;;KAkBY,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,MAAA,GAAS,iBAAA;EACT,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,MAAA,GAAS,iBAAA;IACT,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,aAAA;EAAA;EAEF,MAAA,EAAQ,yBAAA;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"}
|
|
@@ -2,6 +2,12 @@ import { AppPermissions } from "./app-manifest.types.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/types/app-permissions.types.d.ts
|
|
4
4
|
type AppDocumentGrantMap = Record<string, string>;
|
|
5
|
+
type AppStoredDocumentGrant = {
|
|
6
|
+
path: string;
|
|
7
|
+
mode: "read" | "read-write";
|
|
8
|
+
grantedAt: string;
|
|
9
|
+
};
|
|
10
|
+
type AppStoredDocumentGrantMap = Record<string, AppStoredDocumentGrant>;
|
|
5
11
|
type ResolvedDocumentGrant = {
|
|
6
12
|
id: string;
|
|
7
13
|
mode: "read" | "read-write";
|
|
@@ -28,6 +34,10 @@ type AppDocumentGrantState = {
|
|
|
28
34
|
description?: string;
|
|
29
35
|
granted: boolean;
|
|
30
36
|
grantedPath?: string;
|
|
37
|
+
effectiveMode?: "read" | "read-write";
|
|
38
|
+
grantedAt?: string;
|
|
39
|
+
status: "ungranted" | "granted" | "insufficient" | "unavailable";
|
|
40
|
+
availableActions: Array<"grant" | "replace" | "upgrade" | "downgrade" | "revoke">;
|
|
31
41
|
};
|
|
32
42
|
type AppInstalledPermissionState = {
|
|
33
43
|
appId: string;
|
|
@@ -47,8 +57,9 @@ type AppDocumentGrantMutationResult = {
|
|
|
47
57
|
appId: string;
|
|
48
58
|
scopeId: string;
|
|
49
59
|
grantedPath?: string;
|
|
60
|
+
effectiveMode?: "read" | "read-write";
|
|
50
61
|
removed?: boolean;
|
|
51
62
|
};
|
|
52
63
|
//#endregion
|
|
53
|
-
export { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions };
|
|
64
|
+
export { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, AppStoredDocumentGrant, AppStoredDocumentGrantMap, ResolvedDocumentGrant, ResolvedPermissions };
|
|
54
65
|
//# sourceMappingURL=app-permissions.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-permissions.types.d.ts","names":[],"sources":["../../src/types/app-permissions.types.ts"],"mappings":";;;KAEY,mBAAA,GAAsB,MAAA;AAAA,KAEtB,qBAAA;EACV,EAAA;EACA,IAAA;EACA,WAAA;EACA,IAAA;AAAA;AAAA,KAGU,mBAAA;EACV,cAAA,EAAgB,qBAAA;EAChB,cAAA;EACA,OAAA;IACE,OAAA;IACA,SAAA;EAAA;EAEF,YAAA;IACE,UAAA;EAAA;AAAA;AAAA,KAIQ,oBAAA,GAAuB,IAAA,CACjC,mBAAA;EAGA,SAAA,EAAW,cAAA;AAAA;AAAA,KAGD,qBAAA;EACV,EAAA;EACA,IAAA;EACA,WAAA;EACA,OAAA;EACA,WAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"app-permissions.types.d.ts","names":[],"sources":["../../src/types/app-permissions.types.ts"],"mappings":";;;KAEY,mBAAA,GAAsB,MAAA;AAAA,KAEtB,sBAAA;EACV,IAAA;EACA,IAAA;EACA,SAAA;AAAA;AAAA,KAGU,yBAAA,GAA4B,MAAA,SAAe,sBAAA;AAAA,KAE3C,qBAAA;EACV,EAAA;EACA,IAAA;EACA,WAAA;EACA,IAAA;AAAA;AAAA,KAGU,mBAAA;EACV,cAAA,EAAgB,qBAAA;EAChB,cAAA;EACA,OAAA;IACE,OAAA;IACA,SAAA;EAAA;EAEF,YAAA;IACE,UAAA;EAAA;AAAA;AAAA,KAIQ,oBAAA,GAAuB,IAAA,CACjC,mBAAA;EAGA,SAAA,EAAW,cAAA;AAAA;AAAA,KAGD,qBAAA;EACV,EAAA;EACA,IAAA;EACA,WAAA;EACA,OAAA;EACA,WAAA;EACA,aAAA;EACA,SAAA;EACA,MAAA;EACA,gBAAA,EAAkB,KAAA;AAAA;AAAA,KAKR,2BAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,cAAA,EAAgB,qBAAA;EAChB,cAAA;EACA,OAAA;IACE,OAAA;IACA,SAAA;EAAA;EAEF,YAAA;IACE,UAAA;EAAA;AAAA;AAAA,KAIQ,8BAAA;EACV,KAAA;EACA,OAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;AAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppArtifactTarget, AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent } from "./app-manifest.types.js";
|
|
2
2
|
import { AppDistributionMode } from "./app-bundle.types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { AppStoredDocumentGrantMap } from "./app-permissions.types.js";
|
|
4
4
|
import { AppInstanceRecord } from "./app-storage.types.js";
|
|
5
5
|
import { AppPublisher } from "./app-remote-registry.types.js";
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ type AppRegistryAppRecord = {
|
|
|
42
42
|
dataDirectory: string;
|
|
43
43
|
defaultInstance: AppInstanceRecord;
|
|
44
44
|
installedVersions: Record<string, AppRegistryInstalledVersion>;
|
|
45
|
-
grants:
|
|
45
|
+
grants: AppStoredDocumentGrantMap;
|
|
46
46
|
secretBindings: AppSecretBindingMap;
|
|
47
47
|
};
|
|
48
48
|
type AppRegistry = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;;KAWY,gBAAA;EACV,MAAA;EACA,QAAA;EACA,EAAA;AAAA;AAAA,KAGU,mBAAA,GAAsB,MAAA,SAAe,gBAAA;AAAA,KAErC,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,MAAA,GAAS,iBAAA;EACT,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;EACA,QAAA,GAAW,0BAAA;EACX,iBAAA;EACA,aAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA,GAAY,YAAA;EACZ,aAAA;EACA,OAAA;EACA,aAAA;EACA,eAAA,EAAiB,iBAAA;EACjB,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,
|
|
1
|
+
{"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;;KAWY,gBAAA;EACV,MAAA;EACA,QAAA;EACA,EAAA;AAAA;AAAA,KAGU,mBAAA,GAAsB,MAAA,SAAe,gBAAA;AAAA,KAErC,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,MAAA,GAAS,iBAAA;EACT,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;EACA,QAAA,GAAW,0BAAA;EACX,iBAAA;EACA,aAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA,GAAY,YAAA;EACZ,aAAA;EACA,OAAA;EACA,aAAA;EACA,eAAA,EAAiB,iBAAA;EACjB,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,yBAAA;EACR,cAAA,EAAgB,mBAAA;AAAA;AAAA,KAGN,WAAA;EACV,aAAA;EACA,IAAA,EAAM,MAAA,SAAe,oBAAA;EACrB,kBAAA,EAAoB,MAAA;IAGhB,YAAA;EAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/app-runtime",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Standalone micro app runtime and CLI for NextClaw apps.",
|
|
6
6
|
"type": "module",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"fflate": "^0.8.2",
|
|
61
|
-
"@nextclaw/shared": "0.
|
|
61
|
+
"@nextclaw/shared": "0.5.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^20.17.6",
|