@nextclaw/app-runtime 0.13.0 → 0.13.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/package.js +1 -1
- package/dist/services/app-installation-filesystem.service.js +21 -3
- package/dist/services/app-installation-filesystem.service.js.map +1 -1
- package/dist/services/app-installation.service.d.ts.map +1 -1
- package/dist/services/app-installation.service.js +2 -1
- package/dist/services/app-installation.service.js.map +1 -1
- package/dist/services/app-platform-target.service.d.ts +4 -2
- package/dist/services/app-platform-target.service.d.ts.map +1 -1
- package/dist/services/app-platform-target.service.js +19 -7
- package/dist/services/app-platform-target.service.js.map +1 -1
- package/dist/services/file-lock.service.d.ts +11 -1
- package/dist/services/file-lock.service.d.ts.map +1 -1
- package/dist/services/file-lock.service.js +11 -2
- package/dist/services/file-lock.service.js.map +1 -1
- package/package.json +6 -1
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,6 @@ import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/a
|
|
|
55
55
|
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
56
56
|
import { AppServiceLaunch, AppServiceLaunchService } from "./services/app-service-launch.service.js";
|
|
57
57
|
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
58
|
-
import { FileLockService } from "./services/file-lock.service.js";
|
|
58
|
+
import { FileLockBusyError, FileLockLease, FileLockService } from "./services/file-lock.service.js";
|
|
59
59
|
import { UiServerService } from "./services/ui-server.service.js";
|
|
60
|
-
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, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, 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, 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 };
|
|
60
|
+
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, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, 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 };
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { WasmSidecarClientService } from "./services/wasm-sidecar-client.service
|
|
|
17
17
|
import { WasmMainRunnerService } from "./services/wasm-main-runner.service.js";
|
|
18
18
|
import { AppInstanceService } from "./services/app-instance.service.js";
|
|
19
19
|
import { AppHomeService } from "./services/app-home.service.js";
|
|
20
|
-
import { FileLockService } from "./services/file-lock.service.js";
|
|
20
|
+
import { FileLockBusyError, FileLockService } from "./services/file-lock.service.js";
|
|
21
21
|
import { APP_STORAGE_LAYOUT_VERSION, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
22
22
|
import { AppInstanceStorageService } from "./services/app-instance-storage.service.js";
|
|
23
23
|
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
@@ -52,4 +52,4 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
|
|
|
52
52
|
import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
|
|
53
53
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
54
54
|
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
55
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPlatformTargetService, AppPublishArtifactService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppServiceLaunchService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
55
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPlatformTargetService, AppPublishArtifactService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppServiceLaunchService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockBusyError, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/package.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppServiceLaunchService } from "./app-service-launch.service.js";
|
|
2
|
+
import { isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { cp, mkdir, readdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import { chmod, cp, lstat, mkdir, readFile, readdir, rename, rm } from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
//#region src/services/app-installation-filesystem.service.ts
|
|
6
7
|
var AppInstallationFilesystemService = class {
|
|
@@ -8,7 +9,7 @@ var AppInstallationFilesystemService = class {
|
|
|
8
9
|
this.params = params;
|
|
9
10
|
}
|
|
10
11
|
copyToImmutableInstallDirectory = async (params) => {
|
|
11
|
-
const { appId, appVersion, extractedDirectory, installDirectory } = params;
|
|
12
|
+
const { appId, appVersion, extractedDirectory, installDirectory, target } = params;
|
|
12
13
|
if (await this.params.integrityService.pathExists(installDirectory)) throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);
|
|
13
14
|
await mkdir(path.dirname(installDirectory), { recursive: true });
|
|
14
15
|
const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;
|
|
@@ -16,6 +17,7 @@ var AppInstallationFilesystemService = class {
|
|
|
16
17
|
await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });
|
|
17
18
|
const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);
|
|
18
19
|
if (stagedManifest.manifest.id !== appId || stagedManifest.manifest.version !== appVersion) throw new Error("staging manifest 与已验证 bundle 身份不一致。");
|
|
20
|
+
await this.restoreServiceCommandPermissions(stagedManifest, target);
|
|
19
21
|
const contentSha256 = await this.params.integrityService.calculateDigest(stagedInstallDirectory);
|
|
20
22
|
await rename(stagedInstallDirectory, installDirectory);
|
|
21
23
|
await this.params.integrityService.protectDirectory(installDirectory);
|
|
@@ -28,6 +30,22 @@ var AppInstallationFilesystemService = class {
|
|
|
28
30
|
throw error;
|
|
29
31
|
}
|
|
30
32
|
};
|
|
33
|
+
restoreServiceCommandPermissions = async (manifestBundle, target) => {
|
|
34
|
+
if (!isAppComponentManifestBundle(manifestBundle)) return;
|
|
35
|
+
const launchService = new AppServiceLaunchService();
|
|
36
|
+
for (const component of manifestBundle.components) {
|
|
37
|
+
if (component.kind !== "service") continue;
|
|
38
|
+
const serviceManifest = JSON.parse(await readFile(component.manifestPath, "utf8"));
|
|
39
|
+
const { command } = launchService.resolve(serviceManifest, target);
|
|
40
|
+
if (path.isAbsolute(command) || !command.includes("/")) continue;
|
|
41
|
+
const commandPath = path.resolve(component.componentDirectory, command);
|
|
42
|
+
const relativeCommandPath = path.relative(component.componentDirectory, commandPath);
|
|
43
|
+
if (!relativeCommandPath || relativeCommandPath.startsWith("..") || path.isAbsolute(relativeCommandPath)) throw new Error(`service app command 必须位于组件目录内:${command}`);
|
|
44
|
+
const stats = await lstat(commandPath);
|
|
45
|
+
if (!stats.isFile()) throw new Error(`service app command 必须是普通文件:${command}`);
|
|
46
|
+
await chmod(commandPath, stats.mode | 64);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
31
49
|
materializeDistribution = async (params) => {
|
|
32
50
|
if (params.distributionMode !== "source") return;
|
|
33
51
|
const manifestBundle = await this.params.manifestService.load(params.appDirectory);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-installation-filesystem.service.js","names":[],"sources":["../../src/services/app-installation-filesystem.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { cp, mkdir, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { isAppStandaloneManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\n\nexport class AppInstallationFilesystemService {\n constructor(private readonly params: {\n buildService: AppBuildService;\n integrityService: AppInstallationIntegrityService;\n manifestService: AppManifestService;\n }) {}\n\n copyToImmutableInstallDirectory = async (params: {\n appId: string;\n appVersion: string;\n extractedDirectory: string;\n installDirectory: string;\n }): Promise<string> => {\n const { appId, appVersion, extractedDirectory, installDirectory } = params;\n if (await this.params.integrityService.pathExists(installDirectory)) {\n throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);\n }\n await mkdir(path.dirname(installDirectory), { recursive: true });\n const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;\n try {\n await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });\n const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);\n if (\n stagedManifest.manifest.id !== appId ||\n stagedManifest.manifest.version !== appVersion\n ) {\n throw new Error(\"staging manifest 与已验证 bundle 身份不一致。\");\n }\n const contentSha256 = await this.params.integrityService.calculateDigest(\n stagedInstallDirectory,\n );\n await rename(stagedInstallDirectory, installDirectory);\n await this.params.integrityService.protectDirectory(installDirectory);\n return contentSha256;\n } catch (error) {\n await rm(stagedInstallDirectory, { recursive: true, force: true });\n throw error;\n }\n };\n\n materializeDistribution = async (params: {\n appDirectory: string;\n distributionMode: AppDistributionMode;\n }): Promise<void> => {\n if (params.distributionMode !== \"source\") {\n return;\n }\n const manifestBundle = await this.params.manifestService.load(params.appDirectory);\n if (!isAppStandaloneManifestBundle(manifestBundle)) {\n throw new Error(\"schema v2 组合包不允许运行安装期 build。\");\n }\n if (manifestBundle.manifest.main.kind !== \"wasi-http-component\") {\n return;\n }\n await this.params.buildService.build({\n appDirectory: params.appDirectory,\n install: true,\n });\n };\n\n reconcileGeneratedSiblings = async (\n parentDirectory: string,\n referencedPaths: Set<string>,\n ): Promise<void> => {\n for (const entry of await this.readDirectories(parentDirectory)) {\n const entryName = path.basename(entry);\n if (entryName.includes(\".staging-\")) {\n await this.params.integrityService.removeDirectory(entry);\n continue;\n }\n const markerIndex = entryName.indexOf(\".uninstalling-\");\n if (markerIndex < 0) {\n continue;\n }\n const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));\n if (\n referencedPaths.has(path.resolve(originalPath)) &&\n !await this.params.integrityService.pathExists(originalPath)\n ) {\n await rename(entry, originalPath);\n } else {\n await this.params.integrityService.removeDirectory(entry);\n }\n }\n };\n\n readDirectories = async (directory: string): Promise<string[]> => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(directory, entry.name));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-installation-filesystem.service.js","names":[],"sources":["../../src/services/app-installation-filesystem.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmod, cp, lstat, mkdir, readFile, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { AppServiceLaunchService } from \"#app-runtime/services/app-service-launch.service.js\";\nimport {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n type AppManifestBundle,\n type AppNativeArtifactTarget,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\n\nexport class AppInstallationFilesystemService {\n constructor(private readonly params: {\n buildService: AppBuildService;\n integrityService: AppInstallationIntegrityService;\n manifestService: AppManifestService;\n }) {}\n\n copyToImmutableInstallDirectory = async (params: {\n appId: string;\n appVersion: string;\n extractedDirectory: string;\n installDirectory: string;\n target?: AppNativeArtifactTarget;\n }): Promise<string> => {\n const { appId, appVersion, extractedDirectory, installDirectory, target } = params;\n if (await this.params.integrityService.pathExists(installDirectory)) {\n throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);\n }\n await mkdir(path.dirname(installDirectory), { recursive: true });\n const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;\n try {\n await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });\n const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);\n if (\n stagedManifest.manifest.id !== appId ||\n stagedManifest.manifest.version !== appVersion\n ) {\n throw new Error(\"staging manifest 与已验证 bundle 身份不一致。\");\n }\n await this.restoreServiceCommandPermissions(stagedManifest, target);\n const contentSha256 = await this.params.integrityService.calculateDigest(\n stagedInstallDirectory,\n );\n await rename(stagedInstallDirectory, installDirectory);\n await this.params.integrityService.protectDirectory(installDirectory);\n return contentSha256;\n } catch (error) {\n await rm(stagedInstallDirectory, { recursive: true, force: true });\n throw error;\n }\n };\n\n private restoreServiceCommandPermissions = async (\n manifestBundle: AppManifestBundle,\n target?: AppNativeArtifactTarget,\n ): Promise<void> => {\n if (!isAppComponentManifestBundle(manifestBundle)) {\n return;\n }\n const launchService = new AppServiceLaunchService();\n for (const component of manifestBundle.components) {\n if (component.kind !== \"service\") {\n continue;\n }\n const serviceManifest = JSON.parse(\n await readFile(component.manifestPath, \"utf8\"),\n ) as Record<string, unknown>;\n const { command } = launchService.resolve(serviceManifest, target);\n if (path.isAbsolute(command) || !command.includes(\"/\")) {\n continue;\n }\n const commandPath = path.resolve(component.componentDirectory, command);\n const relativeCommandPath = path.relative(component.componentDirectory, commandPath);\n if (\n !relativeCommandPath ||\n relativeCommandPath.startsWith(\"..\") ||\n path.isAbsolute(relativeCommandPath)\n ) {\n throw new Error(`service app command 必须位于组件目录内:${command}`);\n }\n const stats = await lstat(commandPath);\n if (!stats.isFile()) {\n throw new Error(`service app command 必须是普通文件:${command}`);\n }\n await chmod(commandPath, stats.mode | 0o100);\n }\n };\n\n materializeDistribution = async (params: {\n appDirectory: string;\n distributionMode: AppDistributionMode;\n }): Promise<void> => {\n if (params.distributionMode !== \"source\") {\n return;\n }\n const manifestBundle = await this.params.manifestService.load(params.appDirectory);\n if (!isAppStandaloneManifestBundle(manifestBundle)) {\n throw new Error(\"schema v2 组合包不允许运行安装期 build。\");\n }\n if (manifestBundle.manifest.main.kind !== \"wasi-http-component\") {\n return;\n }\n await this.params.buildService.build({\n appDirectory: params.appDirectory,\n install: true,\n });\n };\n\n reconcileGeneratedSiblings = async (\n parentDirectory: string,\n referencedPaths: Set<string>,\n ): Promise<void> => {\n for (const entry of await this.readDirectories(parentDirectory)) {\n const entryName = path.basename(entry);\n if (entryName.includes(\".staging-\")) {\n await this.params.integrityService.removeDirectory(entry);\n continue;\n }\n const markerIndex = entryName.indexOf(\".uninstalling-\");\n if (markerIndex < 0) {\n continue;\n }\n const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));\n if (\n referencedPaths.has(path.resolve(originalPath)) &&\n !await this.params.integrityService.pathExists(originalPath)\n ) {\n await rename(entry, originalPath);\n } else {\n await this.params.integrityService.removeDirectory(entry);\n }\n }\n };\n\n readDirectories = async (directory: string): Promise<string[]> => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(directory, entry.name));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;KAuCK,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,iBAGA,oBAAA;EAAA,iBAjBF,oBAAA;EAAA,iBACA,sBAAA;EAAA,iBACA,eAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,mBAAA;cAGE,cAAA,GAAgB,cAAA,EAChB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,kBAAA,EACjB,YAAA,GAAc,eAAA,EACd,eAAA,GAAiB,kBAAA,EACjB,qBAAA,GAAuB,wBAAA,EAGvB,oBAAA,GAAsB,8BAAA,EAGvC,sBAAA,GAAyB,yBAAA;EAyB3B,OAAA,GACE,SAAA,UACA,OAAA,GAAS,iBAAA,KACR,OAAA,CAAQ,gBAAA;EAAA,QAkCH,qBAAA;EAAA,QAUA,qBAAA;
|
|
1
|
+
{"version":3,"file":"app-installation.service.d.ts","names":[],"sources":["../../src/services/app-installation.service.ts"],"mappings":";;;;;;;;;;;;;KAuCK,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,iBAGA,oBAAA;EAAA,iBAjBF,oBAAA;EAAA,iBACA,sBAAA;EAAA,iBACA,eAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,mBAAA;cAGE,cAAA,GAAgB,cAAA,EAChB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,kBAAA,EACjB,YAAA,GAAc,eAAA,EACd,eAAA,GAAiB,kBAAA,EACjB,qBAAA,GAAuB,wBAAA,EAGvB,oBAAA,GAAsB,8BAAA,EAGvC,sBAAA,GAAyB,yBAAA;EAyB3B,OAAA,GACE,SAAA,UACA,OAAA,GAAS,iBAAA,KACR,OAAA,CAAQ,gBAAA;EAAA,QAkCH,qBAAA;EAAA,QAUA,qBAAA;EA8HR,MAAA,GACE,KAAA,UACA,OAAA;IACE,OAAA;IACA,WAAA;IACA,UAAA,GAAa,yBAAA;IACb,QAAA;EAAA,MAED,OAAA,CAAQ,eAAA;EAOX,SAAA,GACE,KAAA,UACA,SAAA,cACC,OAAA,CAAQ,kBAAA;EAOX,IAAA,QAAiB,OAAA,CAAQ,oBAAA;EAkBzB,mBAAA,QAAgC,OAAA;EAwGhC,IAAA,GACE,KAAA,UACA,OAAA;IAAW,mBAAA;EAAA,MACV,OAAA,CAAQ,aAAA;EAwCX,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,mBAAA;EAW9D,QAAA,GAAkB,KAAA,UAAe,OAAA,aAAkB,OAAA,CAAQ,iBAAA;EAoC3D,sBAAA,GAAgC,KAAA,UAAe,OAAA,cAAmB,OAAA;EAqBlE,gBAAA,MAA6B,KAAA,UAAe,SAAA,QAAiB,OAAA,CAAQ,CAAA,MAAK,OAAA,CAAQ,CAAA;EAgBlF,aAAA,GACE,YAAA,UACA,wBAAA,EAA0B,mBAAA,KACzB,OAAA,CAAQ,mBAAA;EAAA,QA6BH,sBAAA;EAOR,aAAA,GACE,KAAA,sBACA,gBAAA,EAAkB,mBAAA,KACjB,OAAA;AAAA"}
|
|
@@ -106,7 +106,8 @@ var AppInstallationService = class {
|
|
|
106
106
|
appId,
|
|
107
107
|
appVersion: version,
|
|
108
108
|
extractedDirectory,
|
|
109
|
-
installDirectory
|
|
109
|
+
installDirectory,
|
|
110
|
+
target: extractedMetadata.metadata.target?.kind === "native" ? extractedMetadata.metadata.target : void 0
|
|
110
111
|
});
|
|
111
112
|
const defaultInstance = await this.instanceStorageService.materializeDefaultInstance({
|
|
112
113
|
appId,
|
|
@@ -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 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 publisher = source.kind === \"registry\"\n ? source.registryResolution.publisher\n : options.trustedPublisher;\n const currentPublisher = existingRecord?.publisher ??\n existingRecord?.installedVersions[existingRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== publisher?.id) {\n throw new Error(\n `应用 ${appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const dataSchemaVersion = manifestBundle.manifest.schemaVersion === 2\n ? manifestBundle.manifest.storage?.schemaVersion ?? 1\n : 1;\n if (existingRecord?.defaultInstance.dataSchemaVersion !== undefined &&\n existingRecord.defaultInstance.dataSchemaVersion !== dataSchemaVersion) {\n throw new Error(\n `应用 ${appId} 数据 schema 从 ${existingRecord.defaultInstance.dataSchemaVersion} 升级到 ${dataSchemaVersion},但包内没有受支持的迁移合同。更新已停止,现有数据和版本保持不变。`,\n );\n }\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, \"default\");\n const instanceExisted = await this.integrityService.pathExists(instanceDirectory);\n const contentSha256 = await this.filesystemService.copyToImmutableInstallDirectory({\n appId,\n appVersion: version,\n extractedDirectory,\n installDirectory,\n });\n const defaultInstance = await this.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: (currentPublisher ?? publisher)?.id,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n dataSchemaVersion,\n });\n await onProgress?.(\"finalizing\");\n let registryRecord;\n try {\n registryRecord = await this.registryService.upsertInstallation({\n appId,\n name: manifestBundle.manifest.name,\n description: manifestBundle.manifest.description,\n version,\n installDirectory,\n defaultInstance,\n sourceKind: source.kind,\n distributionMode: source.kind === \"registry\"\n ? source.registryResolution.distributionMode\n : extractedMetadata.metadata.distributionMode,\n sourceRef: source.sourceRef,\n installedAt: new Date().toISOString(),\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n ...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) {\n await this.instanceStorageService.rollbackNewInstance({\n instance: defaultInstance,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n });\n }\n throw error;\n }\n const installedVersion = registryRecord.installedVersions[version];\n return {\n appId: registryRecord.appId,\n name: registryRecord.name,\n version,\n installDirectory,\n dataDirectory: defaultInstance.storage.dataDirectory,\n instance: registryRecord.defaultInstance,\n sourceKind: source.kind,\n sourceRef: source.sourceRef,\n distributionMode: installedVersion?.distributionMode,\n permissions: installedVersion?.permissions ?? {},\n ...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 {\n AppInstallSourceService,\n type ResolvedAppInstallSource,\n} from \"#app-runtime/services/app-install-source.service.js\";\nimport type {\n AppInfoResult,\n AppInstallProgressHandler,\n AppActivationResult,\n AppInstallResult,\n AppLaunchResolution,\n AppUpdateResult,\n AppRollbackResult,\n AppUninstallResult,\n InstalledAppListItem,\n} from \"#app-runtime/types/app-installation.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\ntype AppInstallOptions = {\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n trustedPublisher?: AppPublisher;\n};\n\ntype PreparedAppInstall = {\n source: ResolvedAppInstallSource;\n extractedDirectory: string;\n extractedMetadata: AppBundleExtractResult;\n manifestBundle: AppManifestBundle;\n options: AppInstallOptions;\n};\n\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 publisher = source.kind === \"registry\"\n ? source.registryResolution.publisher\n : options.trustedPublisher;\n const currentPublisher = existingRecord?.publisher ??\n existingRecord?.installedVersions[existingRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== publisher?.id) {\n throw new Error(\n `应用 ${appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const dataSchemaVersion = manifestBundle.manifest.schemaVersion === 2\n ? manifestBundle.manifest.storage?.schemaVersion ?? 1\n : 1;\n if (existingRecord?.defaultInstance.dataSchemaVersion !== undefined &&\n existingRecord.defaultInstance.dataSchemaVersion !== dataSchemaVersion) {\n throw new Error(\n `应用 ${appId} 数据 schema 从 ${existingRecord.defaultInstance.dataSchemaVersion} 升级到 ${dataSchemaVersion},但包内没有受支持的迁移合同。更新已停止,现有数据和版本保持不变。`,\n );\n }\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, \"default\");\n const instanceExisted = await this.integrityService.pathExists(instanceDirectory);\n const contentSha256 = await this.filesystemService.copyToImmutableInstallDirectory({\n appId,\n appVersion: version,\n extractedDirectory,\n installDirectory,\n target: extractedMetadata.metadata.target?.kind === \"native\" ? extractedMetadata.metadata.target : undefined,\n });\n const defaultInstance = await this.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: (currentPublisher ?? publisher)?.id,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n dataSchemaVersion,\n });\n await onProgress?.(\"finalizing\");\n let registryRecord;\n try {\n registryRecord = await this.registryService.upsertInstallation({\n appId,\n name: manifestBundle.manifest.name,\n description: manifestBundle.manifest.description,\n version,\n installDirectory,\n defaultInstance,\n sourceKind: source.kind,\n distributionMode: source.kind === \"registry\"\n ? source.registryResolution.distributionMode\n : extractedMetadata.metadata.distributionMode,\n sourceRef: source.sourceRef,\n installedAt: new Date().toISOString(),\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n ...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) {\n await this.instanceStorageService.rollbackNewInstance({\n instance: defaultInstance,\n legacyDataDirectory: existingRecord?.defaultInstance.storage.layout === \"legacy\"\n ? existingRecord.dataDirectory\n : undefined,\n });\n }\n throw error;\n }\n const installedVersion = registryRecord.installedVersions[version];\n return {\n appId: registryRecord.appId,\n name: registryRecord.name,\n version,\n installDirectory,\n dataDirectory: defaultInstance.storage.dataDirectory,\n instance: registryRecord.defaultInstance,\n sourceKind: source.kind,\n sourceRef: source.sourceRef,\n distributionMode: installedVersion?.distributionMode,\n permissions: installedVersion?.permissions ?? {},\n ...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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"}
|
|
@@ -14,8 +14,8 @@ type AppPlatformTargetServiceOptions = {
|
|
|
14
14
|
linuxAbi?: "gnu" | "musl";
|
|
15
15
|
};
|
|
16
16
|
declare class AppPlatformTargetService {
|
|
17
|
-
private readonly platform
|
|
18
|
-
private readonly arch
|
|
17
|
+
private readonly platform?;
|
|
18
|
+
private readonly arch?;
|
|
19
19
|
private readonly linuxAbi?;
|
|
20
20
|
constructor(options?: AppPlatformTargetServiceOptions);
|
|
21
21
|
resolveDistribution: (distribution: AppDistributionDeclaration | undefined) => AppDistributionDeclaration;
|
|
@@ -34,6 +34,8 @@ declare class AppPlatformTargetService {
|
|
|
34
34
|
private parseNativeTarget;
|
|
35
35
|
private readArchitecture;
|
|
36
36
|
private detectLinuxAbi;
|
|
37
|
+
private readHostPlatform;
|
|
38
|
+
private readHostArchitecture;
|
|
37
39
|
compareVersions: (left: string, right: string) => number;
|
|
38
40
|
private parseVersion;
|
|
39
41
|
private assertObject;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-platform-target.service.d.ts","names":[],"sources":["../../src/services/app-platform-target.service.ts"],"mappings":";;;KAOK,iBAAA;EACH,MAAA,EAAQ,iBAAA;AAAA;AAAA,KAGL,gBAAA,mBAAmC,iBAAA;EACtC,OAAA;EACA,SAAA,EAAW,SAAA;AAAA;AAAA,KAGR,+BAAA;EACH,QAAA,GAAW,MAAA,CAAO,QAAA;EAClB,IAAA;EACA,QAAA;AAAA;AAAA,cAGW,wBAAA;EAAA,iBACM,QAAA;EAAA,iBACA,IAAA;EAAA,iBACA,QAAA;cAEL,OAAA,GAAS,+BAAA;EAMrB,mBAAA,GACE,YAAA,EAAc,0BAAA,iBACb,0BAAA;EAEH,iBAAA,GAAqB,eAAA,cAA2B,0BAAA;EA6BhD,mBAAA,GAAuB,SAAA,WAAoB,SAAA,cAAuB,iBAAA;EAQlE,cAAA,GAAkB,SAAA,aAAoB,iBAAA;EAetC,WAAA,GAAe,MAAA,EAAQ,iBAAA;EASvB,cAAA,QAAqB,uBAAA;
|
|
1
|
+
{"version":3,"file":"app-platform-target.service.d.ts","names":[],"sources":["../../src/services/app-platform-target.service.ts"],"mappings":";;;KAOK,iBAAA;EACH,MAAA,EAAQ,iBAAA;AAAA;AAAA,KAGL,gBAAA,mBAAmC,iBAAA;EACtC,OAAA;EACA,SAAA,EAAW,SAAA;AAAA;AAAA,KAGR,+BAAA;EACH,QAAA,GAAW,MAAA,CAAO,QAAA;EAClB,IAAA;EACA,QAAA;AAAA;AAAA,cAGW,wBAAA;EAAA,iBACM,QAAA;EAAA,iBACA,IAAA;EAAA,iBACA,QAAA;cAEL,OAAA,GAAS,+BAAA;EAMrB,mBAAA,GACE,YAAA,EAAc,0BAAA,iBACb,0BAAA;EAEH,iBAAA,GAAqB,eAAA,cAA2B,0BAAA;EA6BhD,mBAAA,GAAuB,SAAA,WAAoB,SAAA,cAAuB,iBAAA;EAQlE,cAAA,GAAkB,SAAA,aAAoB,iBAAA;EAetC,WAAA,GAAe,MAAA,EAAQ,iBAAA;EASvB,cAAA,QAAqB,uBAAA;EAoBrB,cAAA,qBAAoC,iBAAA,EAClC,SAAA,WAAoB,SAAA,IACpB,UAAA,GAAY,uBAAA,KACX,SAAA;EAWH,6BAAA,qBAAmD,iBAAA,EACjD,QAAA,WAAmB,gBAAA,CAAiB,SAAA,KACpC,UAAA,GAAY,uBAAA,KACX,gBAAA,CAAiB,SAAA;EAIpB,oBAAA,GAAwB,MAAA;IACtB,QAAA,WAAmB,iBAAA;IACnB,MAAA,WAAiB,iBAAA;IACjB,WAAA;EAAA;EAAA,QAoBM,iBAAA;EAAA,QA8BA,gBAAA;EAAA,QAUA,cAAA;EAAA,QAaA,gBAAA;EAAA,QAUA,oBAAA;EAUR,eAAA,GAAmB,IAAA,UAAc,KAAA;EAAA,QA2BzB,YAAA;EAAA,QAeA,YAAA;AAAA"}
|
|
@@ -4,8 +4,8 @@ var AppPlatformTargetService = class {
|
|
|
4
4
|
arch;
|
|
5
5
|
linuxAbi;
|
|
6
6
|
constructor(options = {}) {
|
|
7
|
-
this.platform = options.platform
|
|
8
|
-
this.arch = options.arch
|
|
7
|
+
this.platform = options.platform;
|
|
8
|
+
this.arch = options.arch;
|
|
9
9
|
this.linuxAbi = options.linuxAbi;
|
|
10
10
|
}
|
|
11
11
|
resolveDistribution = (distribution) => distribution ?? { mode: "universal" };
|
|
@@ -53,25 +53,26 @@ var AppPlatformTargetService = class {
|
|
|
53
53
|
].filter(Boolean).join("-");
|
|
54
54
|
};
|
|
55
55
|
readHostTarget = () => {
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
const platform = this.readHostPlatform();
|
|
57
|
+
const arch = this.readArchitecture(this.readHostArchitecture(), "host architecture");
|
|
58
|
+
if (platform === "darwin") return {
|
|
58
59
|
kind: "native",
|
|
59
60
|
os: "darwin",
|
|
60
61
|
arch
|
|
61
62
|
};
|
|
62
|
-
if (
|
|
63
|
+
if (platform === "linux") return {
|
|
63
64
|
kind: "native",
|
|
64
65
|
os: "linux",
|
|
65
66
|
arch,
|
|
66
67
|
abi: this.linuxAbi ?? this.detectLinuxAbi()
|
|
67
68
|
};
|
|
68
|
-
if (
|
|
69
|
+
if (platform === "win32") return {
|
|
69
70
|
kind: "native",
|
|
70
71
|
os: "win32",
|
|
71
72
|
arch,
|
|
72
73
|
abi: "msvc"
|
|
73
74
|
};
|
|
74
|
-
throw new Error(`当前平台不支持原生 App artifact:${
|
|
75
|
+
throw new Error(`当前平台不支持原生 App artifact:${platform}-${arch}`);
|
|
75
76
|
};
|
|
76
77
|
selectArtifact = (artifacts, hostTarget = this.readHostTarget()) => {
|
|
77
78
|
const universal = artifacts.find((artifact) => artifact.target.kind === "universal");
|
|
@@ -123,9 +124,20 @@ var AppPlatformTargetService = class {
|
|
|
123
124
|
return value;
|
|
124
125
|
};
|
|
125
126
|
detectLinuxAbi = () => {
|
|
127
|
+
if (typeof process === "undefined") throw new Error("当前环境无法自动检测 Linux ABI,请显式提供 linuxAbi。");
|
|
126
128
|
const report = process.report?.getReport();
|
|
127
129
|
return typeof report?.header?.glibcVersionRuntime === "string" && report.header.glibcVersionRuntime.trim() ? "gnu" : "musl";
|
|
128
130
|
};
|
|
131
|
+
readHostPlatform = () => {
|
|
132
|
+
if (this.platform) return this.platform;
|
|
133
|
+
if (typeof process === "undefined") throw new Error("当前环境无法自动检测宿主平台,请显式提供 platform。");
|
|
134
|
+
return process.platform;
|
|
135
|
+
};
|
|
136
|
+
readHostArchitecture = () => {
|
|
137
|
+
if (this.arch) return this.arch;
|
|
138
|
+
if (typeof process === "undefined") throw new Error("当前环境无法自动检测宿主架构,请显式提供 arch。");
|
|
139
|
+
return process.arch;
|
|
140
|
+
};
|
|
129
141
|
compareVersions = (left, right) => {
|
|
130
142
|
const leftParts = this.parseVersion(left);
|
|
131
143
|
const rightParts = this.parseVersion(right);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-platform-target.service.js","names":[],"sources":["../../src/services/app-platform-target.service.ts"],"sourcesContent":["import type {\n AppArtifactArchitecture,\n AppArtifactTarget,\n AppDistributionDeclaration,\n AppNativeArtifactTarget,\n} from \"#app-runtime/types/app-manifest.types.js\";\n\ntype AppTargetArtifact = {\n target: AppArtifactTarget;\n};\n\ntype AppTargetVersion<TArtifact extends AppTargetArtifact> = {\n version: string;\n artifacts: TArtifact[];\n};\n\ntype AppPlatformTargetServiceOptions = {\n platform?: NodeJS.Platform;\n arch?: string;\n linuxAbi?: \"gnu\" | \"musl\";\n};\n\nexport class AppPlatformTargetService {\n private readonly platform: NodeJS.Platform;\n private readonly arch: string;\n private readonly linuxAbi?: \"gnu\" | \"musl\";\n\n constructor(options: AppPlatformTargetServiceOptions = {}) {\n this.platform = options.platform ?? process.platform;\n this.arch = options.arch ?? process.arch;\n this.linuxAbi = options.linuxAbi;\n }\n\n resolveDistribution = (\n distribution: AppDistributionDeclaration | undefined,\n ): AppDistributionDeclaration => distribution ?? { mode: \"universal\" };\n\n parseDistribution = (rawDistribution: unknown): AppDistributionDeclaration | undefined => {\n if (rawDistribution === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawDistribution, \"distribution\");\n if (candidate.mode === \"universal\") {\n if (candidate.targets !== undefined) {\n throw new Error(\"distribution.mode=universal 不能声明 targets。\");\n }\n return { mode: \"universal\" };\n }\n if (candidate.mode !== \"targeted\") {\n throw new Error(\"distribution.mode 只支持 universal 或 targeted。\");\n }\n if (!Array.isArray(candidate.targets) || candidate.targets.length === 0) {\n throw new Error(\"distribution.mode=targeted 必须声明至少一个 target。\");\n }\n const targets = candidate.targets.map((target, index) =>\n this.parseNativeTarget(target, `distribution.targets[${index}]`));\n const targetKeys = targets.map(this.toTargetKey);\n const duplicate = targetKeys.find(\n (targetKey, index) => targetKeys.indexOf(targetKey) !== index,\n );\n if (duplicate) {\n throw new Error(`distribution.targets 包含重复 target:${duplicate}`);\n }\n return { mode: \"targeted\", targets };\n };\n\n parseArtifactTarget = (rawTarget: unknown, fieldName = \"target\"): AppArtifactTarget => {\n const candidate = this.assertObject(rawTarget, fieldName);\n if (candidate.kind === \"universal\") {\n return { kind: \"universal\" };\n }\n return this.parseNativeTarget(candidate, fieldName);\n };\n\n parseTargetKey = (targetKey: string): AppArtifactTarget => {\n const normalized = targetKey.trim();\n if (normalized === \"universal\") {\n return { kind: \"universal\" };\n }\n const [os, arch, abi, ...extra] = normalized.split(\"-\");\n if (extra.length > 0 || !os || !arch) {\n throw new Error(`非法 App target key:${targetKey}`);\n }\n return this.parseNativeTarget(\n { kind: \"native\", os, arch, ...(abi ? { abi } : {}) },\n \"target\",\n );\n };\n\n toTargetKey = (target: AppArtifactTarget): string => {\n if (target.kind === \"universal\") {\n return \"universal\";\n }\n return [target.os, target.arch, \"abi\" in target ? target.abi : undefined]\n .filter(Boolean)\n .join(\"-\");\n };\n\n readHostTarget = (): AppNativeArtifactTarget => {\n const arch = this.readArchitecture(this.arch, \"host architecture\");\n if (this.platform === \"darwin\") {\n return { kind: \"native\", os: \"darwin\", arch };\n }\n if (this.platform === \"linux\") {\n return {\n kind: \"native\",\n os: \"linux\",\n arch,\n abi: this.linuxAbi ?? this.detectLinuxAbi(),\n };\n }\n if (this.platform === \"win32\") {\n return { kind: \"native\", os: \"win32\", arch, abi: \"msvc\" };\n }\n throw new Error(`当前平台不支持原生 App artifact:${this.platform}-${this.arch}`);\n };\n\n selectArtifact = <TArtifact extends AppTargetArtifact>(\n artifacts: readonly TArtifact[],\n hostTarget: AppNativeArtifactTarget = this.readHostTarget(),\n ): TArtifact | undefined => {\n const universal = artifacts.find((artifact) => artifact.target.kind === \"universal\");\n if (universal) {\n return universal;\n }\n const hostTargetKey = this.toTargetKey(hostTarget);\n return artifacts.find(\n (artifact) => this.toTargetKey(artifact.target) === hostTargetKey,\n );\n };\n\n selectLatestCompatibleVersion = <TArtifact extends AppTargetArtifact>(\n versions: readonly AppTargetVersion<TArtifact>[],\n hostTarget: AppNativeArtifactTarget = this.readHostTarget(),\n ): AppTargetVersion<TArtifact> | undefined => versions\n .filter((version) => this.selectArtifact(version.artifacts, hostTarget))\n .sort((left, right) => this.compareVersions(right.version, left.version))[0];\n\n assertExactTargetSet = (params: {\n declared: readonly AppArtifactTarget[];\n actual: readonly AppArtifactTarget[];\n actualLabel?: string;\n }): void => {\n const declared = [...new Set(params.declared.map(this.toTargetKey))].sort();\n const actual = [...new Set(params.actual.map(this.toTargetKey))].sort();\n if (\n declared.length !== params.declared.length ||\n actual.length !== params.actual.length\n ) {\n throw new Error(\"App target 集合不能包含重复项。\");\n }\n if (\n declared.length !== actual.length ||\n declared.some((targetKey, index) => targetKey !== actual[index])\n ) {\n throw new Error(\n `声明 targets 与${params.actualLabel ?? \"实际 artifacts\"} 不一致:声明 [${declared.join(\", \")}], 实际 [${actual.join(\", \")}].`,\n );\n }\n };\n\n private parseNativeTarget = (\n rawTarget: unknown,\n fieldName: string,\n ): AppNativeArtifactTarget => {\n const candidate = this.assertObject(rawTarget, fieldName);\n if (candidate.kind !== \"native\") {\n throw new Error(`${fieldName}.kind 必须是 native。`);\n }\n const arch = this.readArchitecture(candidate.arch, `${fieldName}.arch`);\n if (candidate.os === \"darwin\") {\n if (candidate.abi !== undefined) {\n throw new Error(`${fieldName} 的 darwin target 不能声明 abi。`);\n }\n return { kind: \"native\", os: \"darwin\", arch };\n }\n if (candidate.os === \"linux\") {\n if (candidate.abi !== \"gnu\" && candidate.abi !== \"musl\") {\n throw new Error(`${fieldName} 的 linux target 必须声明 gnu 或 musl abi。`);\n }\n return { kind: \"native\", os: \"linux\", arch, abi: candidate.abi };\n }\n if (candidate.os === \"win32\") {\n if (candidate.abi !== \"msvc\") {\n throw new Error(`${fieldName} 的 win32 target 必须声明 msvc abi。`);\n }\n return { kind: \"native\", os: \"win32\", arch, abi: \"msvc\" };\n }\n throw new Error(`${fieldName}.os 只支持 darwin、linux 或 win32。`);\n };\n\n private readArchitecture = (\n value: unknown,\n fieldName: string,\n ): AppArtifactArchitecture => {\n if (value !== \"x64\" && value !== \"arm64\") {\n throw new Error(`${fieldName} 只支持 x64 或 arm64。`);\n }\n return value;\n };\n\n private detectLinuxAbi = (): \"gnu\" | \"musl\" => {\n const report = process.report?.getReport() as {\n header?: { glibcVersionRuntime?: unknown };\n } | undefined;\n return typeof report?.header?.glibcVersionRuntime === \"string\" &&\n report.header.glibcVersionRuntime.trim()\n ? \"gnu\"\n : \"musl\";\n };\n\n compareVersions = (left: string, right: string): number => {\n const leftParts = this.parseVersion(left);\n const rightParts = this.parseVersion(right);\n if (leftParts && rightParts) {\n for (let index = 0; index < 3; index += 1) {\n const difference = (leftParts.numbers[index] ?? 0) -\n (rightParts.numbers[index] ?? 0);\n if (difference !== 0) {\n return difference;\n }\n }\n if (leftParts.prerelease === rightParts.prerelease) {\n return 0;\n }\n if (!leftParts.prerelease) {\n return 1;\n }\n if (!rightParts.prerelease) {\n return -1;\n }\n return leftParts.prerelease.localeCompare(rightParts.prerelease, undefined, {\n numeric: true,\n });\n }\n return left.localeCompare(right, undefined, { numeric: true });\n };\n\n private parseVersion = (\n version: string,\n ): { numbers: [number, number, number]; prerelease?: string } | undefined => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/.exec(\n version,\n );\n if (!match) {\n return undefined;\n }\n return {\n numbers: [Number(match[1]), Number(match[2]), Number(match[3])],\n prerelease: match[4],\n };\n };\n\n private assertObject = (\n value: unknown,\n fieldName: string,\n ): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-platform-target.service.js","names":[],"sources":["../../src/services/app-platform-target.service.ts"],"sourcesContent":["import type {\n AppArtifactArchitecture,\n AppArtifactTarget,\n AppDistributionDeclaration,\n AppNativeArtifactTarget,\n} from \"#app-runtime/types/app-manifest.types.js\";\n\ntype AppTargetArtifact = {\n target: AppArtifactTarget;\n};\n\ntype AppTargetVersion<TArtifact extends AppTargetArtifact> = {\n version: string;\n artifacts: TArtifact[];\n};\n\ntype AppPlatformTargetServiceOptions = {\n platform?: NodeJS.Platform;\n arch?: string;\n linuxAbi?: \"gnu\" | \"musl\";\n};\n\nexport class AppPlatformTargetService {\n private readonly platform?: NodeJS.Platform;\n private readonly arch?: string;\n private readonly linuxAbi?: \"gnu\" | \"musl\";\n\n constructor(options: AppPlatformTargetServiceOptions = {}) {\n this.platform = options.platform;\n this.arch = options.arch;\n this.linuxAbi = options.linuxAbi;\n }\n\n resolveDistribution = (\n distribution: AppDistributionDeclaration | undefined,\n ): AppDistributionDeclaration => distribution ?? { mode: \"universal\" };\n\n parseDistribution = (rawDistribution: unknown): AppDistributionDeclaration | undefined => {\n if (rawDistribution === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawDistribution, \"distribution\");\n if (candidate.mode === \"universal\") {\n if (candidate.targets !== undefined) {\n throw new Error(\"distribution.mode=universal 不能声明 targets。\");\n }\n return { mode: \"universal\" };\n }\n if (candidate.mode !== \"targeted\") {\n throw new Error(\"distribution.mode 只支持 universal 或 targeted。\");\n }\n if (!Array.isArray(candidate.targets) || candidate.targets.length === 0) {\n throw new Error(\"distribution.mode=targeted 必须声明至少一个 target。\");\n }\n const targets = candidate.targets.map((target, index) =>\n this.parseNativeTarget(target, `distribution.targets[${index}]`));\n const targetKeys = targets.map(this.toTargetKey);\n const duplicate = targetKeys.find(\n (targetKey, index) => targetKeys.indexOf(targetKey) !== index,\n );\n if (duplicate) {\n throw new Error(`distribution.targets 包含重复 target:${duplicate}`);\n }\n return { mode: \"targeted\", targets };\n };\n\n parseArtifactTarget = (rawTarget: unknown, fieldName = \"target\"): AppArtifactTarget => {\n const candidate = this.assertObject(rawTarget, fieldName);\n if (candidate.kind === \"universal\") {\n return { kind: \"universal\" };\n }\n return this.parseNativeTarget(candidate, fieldName);\n };\n\n parseTargetKey = (targetKey: string): AppArtifactTarget => {\n const normalized = targetKey.trim();\n if (normalized === \"universal\") {\n return { kind: \"universal\" };\n }\n const [os, arch, abi, ...extra] = normalized.split(\"-\");\n if (extra.length > 0 || !os || !arch) {\n throw new Error(`非法 App target key:${targetKey}`);\n }\n return this.parseNativeTarget(\n { kind: \"native\", os, arch, ...(abi ? { abi } : {}) },\n \"target\",\n );\n };\n\n toTargetKey = (target: AppArtifactTarget): string => {\n if (target.kind === \"universal\") {\n return \"universal\";\n }\n return [target.os, target.arch, \"abi\" in target ? target.abi : undefined]\n .filter(Boolean)\n .join(\"-\");\n };\n\n readHostTarget = (): AppNativeArtifactTarget => {\n const platform = this.readHostPlatform();\n const arch = this.readArchitecture(this.readHostArchitecture(), \"host architecture\");\n if (platform === \"darwin\") {\n return { kind: \"native\", os: \"darwin\", arch };\n }\n if (platform === \"linux\") {\n return {\n kind: \"native\",\n os: \"linux\",\n arch,\n abi: this.linuxAbi ?? this.detectLinuxAbi(),\n };\n }\n if (platform === \"win32\") {\n return { kind: \"native\", os: \"win32\", arch, abi: \"msvc\" };\n }\n throw new Error(`当前平台不支持原生 App artifact:${platform}-${arch}`);\n };\n\n selectArtifact = <TArtifact extends AppTargetArtifact>(\n artifacts: readonly TArtifact[],\n hostTarget: AppNativeArtifactTarget = this.readHostTarget(),\n ): TArtifact | undefined => {\n const universal = artifacts.find((artifact) => artifact.target.kind === \"universal\");\n if (universal) {\n return universal;\n }\n const hostTargetKey = this.toTargetKey(hostTarget);\n return artifacts.find(\n (artifact) => this.toTargetKey(artifact.target) === hostTargetKey,\n );\n };\n\n selectLatestCompatibleVersion = <TArtifact extends AppTargetArtifact>(\n versions: readonly AppTargetVersion<TArtifact>[],\n hostTarget: AppNativeArtifactTarget = this.readHostTarget(),\n ): AppTargetVersion<TArtifact> | undefined => versions\n .filter((version) => this.selectArtifact(version.artifacts, hostTarget))\n .sort((left, right) => this.compareVersions(right.version, left.version))[0];\n\n assertExactTargetSet = (params: {\n declared: readonly AppArtifactTarget[];\n actual: readonly AppArtifactTarget[];\n actualLabel?: string;\n }): void => {\n const declared = [...new Set(params.declared.map(this.toTargetKey))].sort();\n const actual = [...new Set(params.actual.map(this.toTargetKey))].sort();\n if (\n declared.length !== params.declared.length ||\n actual.length !== params.actual.length\n ) {\n throw new Error(\"App target 集合不能包含重复项。\");\n }\n if (\n declared.length !== actual.length ||\n declared.some((targetKey, index) => targetKey !== actual[index])\n ) {\n throw new Error(\n `声明 targets 与${params.actualLabel ?? \"实际 artifacts\"} 不一致:声明 [${declared.join(\", \")}], 实际 [${actual.join(\", \")}].`,\n );\n }\n };\n\n private parseNativeTarget = (\n rawTarget: unknown,\n fieldName: string,\n ): AppNativeArtifactTarget => {\n const candidate = this.assertObject(rawTarget, fieldName);\n if (candidate.kind !== \"native\") {\n throw new Error(`${fieldName}.kind 必须是 native。`);\n }\n const arch = this.readArchitecture(candidate.arch, `${fieldName}.arch`);\n if (candidate.os === \"darwin\") {\n if (candidate.abi !== undefined) {\n throw new Error(`${fieldName} 的 darwin target 不能声明 abi。`);\n }\n return { kind: \"native\", os: \"darwin\", arch };\n }\n if (candidate.os === \"linux\") {\n if (candidate.abi !== \"gnu\" && candidate.abi !== \"musl\") {\n throw new Error(`${fieldName} 的 linux target 必须声明 gnu 或 musl abi。`);\n }\n return { kind: \"native\", os: \"linux\", arch, abi: candidate.abi };\n }\n if (candidate.os === \"win32\") {\n if (candidate.abi !== \"msvc\") {\n throw new Error(`${fieldName} 的 win32 target 必须声明 msvc abi。`);\n }\n return { kind: \"native\", os: \"win32\", arch, abi: \"msvc\" };\n }\n throw new Error(`${fieldName}.os 只支持 darwin、linux 或 win32。`);\n };\n\n private readArchitecture = (\n value: unknown,\n fieldName: string,\n ): AppArtifactArchitecture => {\n if (value !== \"x64\" && value !== \"arm64\") {\n throw new Error(`${fieldName} 只支持 x64 或 arm64。`);\n }\n return value;\n };\n\n private detectLinuxAbi = (): \"gnu\" | \"musl\" => {\n if (typeof process === \"undefined\") {\n throw new Error(\"当前环境无法自动检测 Linux ABI,请显式提供 linuxAbi。\");\n }\n const report = process.report?.getReport() as {\n header?: { glibcVersionRuntime?: unknown };\n } | undefined;\n return typeof report?.header?.glibcVersionRuntime === \"string\" &&\n report.header.glibcVersionRuntime.trim()\n ? \"gnu\"\n : \"musl\";\n };\n\n private readHostPlatform = (): NodeJS.Platform => {\n if (this.platform) {\n return this.platform;\n }\n if (typeof process === \"undefined\") {\n throw new Error(\"当前环境无法自动检测宿主平台,请显式提供 platform。\");\n }\n return process.platform;\n };\n\n private readHostArchitecture = (): string => {\n if (this.arch) {\n return this.arch;\n }\n if (typeof process === \"undefined\") {\n throw new Error(\"当前环境无法自动检测宿主架构,请显式提供 arch。\");\n }\n return process.arch;\n };\n\n compareVersions = (left: string, right: string): number => {\n const leftParts = this.parseVersion(left);\n const rightParts = this.parseVersion(right);\n if (leftParts && rightParts) {\n for (let index = 0; index < 3; index += 1) {\n const difference = (leftParts.numbers[index] ?? 0) -\n (rightParts.numbers[index] ?? 0);\n if (difference !== 0) {\n return difference;\n }\n }\n if (leftParts.prerelease === rightParts.prerelease) {\n return 0;\n }\n if (!leftParts.prerelease) {\n return 1;\n }\n if (!rightParts.prerelease) {\n return -1;\n }\n return leftParts.prerelease.localeCompare(rightParts.prerelease, undefined, {\n numeric: true,\n });\n }\n return left.localeCompare(right, undefined, { numeric: true });\n };\n\n private parseVersion = (\n version: string,\n ): { numbers: [number, number, number]; prerelease?: string } | undefined => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/.exec(\n version,\n );\n if (!match) {\n return undefined;\n }\n return {\n numbers: [Number(match[1]), Number(match[2]), Number(match[3])],\n prerelease: match[4],\n };\n };\n\n private assertObject = (\n value: unknown,\n fieldName: string,\n ): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n}\n"],"mappings":";AAsBA,IAAa,2BAAb,MAAsC;CACpC;CACA;CACA;CAEA,YAAY,UAA2C,EAAE,EAAE;AACzD,OAAK,WAAW,QAAQ;AACxB,OAAK,OAAO,QAAQ;AACpB,OAAK,WAAW,QAAQ;;CAG1B,uBACE,iBAC+B,gBAAgB,EAAE,MAAM,aAAa;CAEtE,qBAAqB,oBAAqE;AACxF,MAAI,oBAAoB,KAAA,EACtB;EAEF,MAAM,YAAY,KAAK,aAAa,iBAAiB,eAAe;AACpE,MAAI,UAAU,SAAS,aAAa;AAClC,OAAI,UAAU,YAAY,KAAA,EACxB,OAAM,IAAI,MAAM,4CAA4C;AAE9D,UAAO,EAAE,MAAM,aAAa;;AAE9B,MAAI,UAAU,SAAS,WACrB,OAAM,IAAI,MAAM,8CAA8C;AAEhE,MAAI,CAAC,MAAM,QAAQ,UAAU,QAAQ,IAAI,UAAU,QAAQ,WAAW,EACpE,OAAM,IAAI,MAAM,8CAA8C;EAEhE,MAAM,UAAU,UAAU,QAAQ,KAAK,QAAQ,UAC7C,KAAK,kBAAkB,QAAQ,wBAAwB,MAAM,GAAG,CAAC;EACnE,MAAM,aAAa,QAAQ,IAAI,KAAK,YAAY;EAChD,MAAM,YAAY,WAAW,MAC1B,WAAW,UAAU,WAAW,QAAQ,UAAU,KAAK,MACzD;AACD,MAAI,UACF,OAAM,IAAI,MAAM,oCAAoC,YAAY;AAElE,SAAO;GAAE,MAAM;GAAY;GAAS;;CAGtC,uBAAuB,WAAoB,YAAY,aAAgC;EACrF,MAAM,YAAY,KAAK,aAAa,WAAW,UAAU;AACzD,MAAI,UAAU,SAAS,YACrB,QAAO,EAAE,MAAM,aAAa;AAE9B,SAAO,KAAK,kBAAkB,WAAW,UAAU;;CAGrD,kBAAkB,cAAyC;EACzD,MAAM,aAAa,UAAU,MAAM;AACnC,MAAI,eAAe,YACjB,QAAO,EAAE,MAAM,aAAa;EAE9B,MAAM,CAAC,IAAI,MAAM,KAAK,GAAG,SAAS,WAAW,MAAM,IAAI;AACvD,MAAI,MAAM,SAAS,KAAK,CAAC,MAAM,CAAC,KAC9B,OAAM,IAAI,MAAM,qBAAqB,YAAY;AAEnD,SAAO,KAAK,kBACV;GAAE,MAAM;GAAU;GAAI;GAAM,GAAI,MAAM,EAAE,KAAK,GAAG,EAAE;GAAG,EACrD,SACD;;CAGH,eAAe,WAAsC;AACnD,MAAI,OAAO,SAAS,YAClB,QAAO;AAET,SAAO;GAAC,OAAO;GAAI,OAAO;GAAM,SAAS,SAAS,OAAO,MAAM,KAAA;GAAU,CACtE,OAAO,QAAQ,CACf,KAAK,IAAI;;CAGd,uBAAgD;EAC9C,MAAM,WAAW,KAAK,kBAAkB;EACxC,MAAM,OAAO,KAAK,iBAAiB,KAAK,sBAAsB,EAAE,oBAAoB;AACpF,MAAI,aAAa,SACf,QAAO;GAAE,MAAM;GAAU,IAAI;GAAU;GAAM;AAE/C,MAAI,aAAa,QACf,QAAO;GACL,MAAM;GACN,IAAI;GACJ;GACA,KAAK,KAAK,YAAY,KAAK,gBAAgB;GAC5C;AAEH,MAAI,aAAa,QACf,QAAO;GAAE,MAAM;GAAU,IAAI;GAAS;GAAM,KAAK;GAAQ;AAE3D,QAAM,IAAI,MAAM,0BAA0B,SAAS,GAAG,OAAO;;CAG/D,kBACE,WACA,aAAsC,KAAK,gBAAgB,KACjC;EAC1B,MAAM,YAAY,UAAU,MAAM,aAAa,SAAS,OAAO,SAAS,YAAY;AACpF,MAAI,UACF,QAAO;EAET,MAAM,gBAAgB,KAAK,YAAY,WAAW;AAClD,SAAO,UAAU,MACd,aAAa,KAAK,YAAY,SAAS,OAAO,KAAK,cACrD;;CAGH,iCACE,UACA,aAAsC,KAAK,gBAAgB,KACf,SAC3C,QAAQ,YAAY,KAAK,eAAe,QAAQ,WAAW,WAAW,CAAC,CACvE,MAAM,MAAM,UAAU,KAAK,gBAAgB,MAAM,SAAS,KAAK,QAAQ,CAAC,CAAC;CAE5E,wBAAwB,WAIZ;EACV,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,SAAS,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM;EAC3E,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM;AACvE,MACE,SAAS,WAAW,OAAO,SAAS,UACpC,OAAO,WAAW,OAAO,OAAO,OAEhC,OAAM,IAAI,MAAM,wBAAwB;AAE1C,MACE,SAAS,WAAW,OAAO,UAC3B,SAAS,MAAM,WAAW,UAAU,cAAc,OAAO,OAAO,CAEhE,OAAM,IAAI,MACR,eAAe,OAAO,eAAe,eAAe,WAAW,SAAS,KAAK,KAAK,CAAC,SAAS,OAAO,KAAK,KAAK,CAAC,IAC/G;;CAIL,qBACE,WACA,cAC4B;EAC5B,MAAM,YAAY,KAAK,aAAa,WAAW,UAAU;AACzD,MAAI,UAAU,SAAS,SACrB,OAAM,IAAI,MAAM,GAAG,UAAU,mBAAmB;EAElD,MAAM,OAAO,KAAK,iBAAiB,UAAU,MAAM,GAAG,UAAU,OAAO;AACvE,MAAI,UAAU,OAAO,UAAU;AAC7B,OAAI,UAAU,QAAQ,KAAA,EACpB,OAAM,IAAI,MAAM,GAAG,UAAU,4BAA4B;AAE3D,UAAO;IAAE,MAAM;IAAU,IAAI;IAAU;IAAM;;AAE/C,MAAI,UAAU,OAAO,SAAS;AAC5B,OAAI,UAAU,QAAQ,SAAS,UAAU,QAAQ,OAC/C,OAAM,IAAI,MAAM,GAAG,UAAU,sCAAsC;AAErE,UAAO;IAAE,MAAM;IAAU,IAAI;IAAS;IAAM,KAAK,UAAU;IAAK;;AAElE,MAAI,UAAU,OAAO,SAAS;AAC5B,OAAI,UAAU,QAAQ,OACpB,OAAM,IAAI,MAAM,GAAG,UAAU,gCAAgC;AAE/D,UAAO;IAAE,MAAM;IAAU,IAAI;IAAS;IAAM,KAAK;IAAQ;;AAE3D,QAAM,IAAI,MAAM,GAAG,UAAU,+BAA+B;;CAG9D,oBACE,OACA,cAC4B;AAC5B,MAAI,UAAU,SAAS,UAAU,QAC/B,OAAM,IAAI,MAAM,GAAG,UAAU,mBAAmB;AAElD,SAAO;;CAGT,uBAA+C;AAC7C,MAAI,OAAO,YAAY,YACrB,OAAM,IAAI,MAAM,uCAAuC;EAEzD,MAAM,SAAS,QAAQ,QAAQ,WAAW;AAG1C,SAAO,OAAO,QAAQ,QAAQ,wBAAwB,YACpD,OAAO,OAAO,oBAAoB,MAAM,GACtC,QACA;;CAGN,yBAAkD;AAChD,MAAI,KAAK,SACP,QAAO,KAAK;AAEd,MAAI,OAAO,YAAY,YACrB,OAAM,IAAI,MAAM,iCAAiC;AAEnD,SAAO,QAAQ;;CAGjB,6BAA6C;AAC3C,MAAI,KAAK,KACP,QAAO,KAAK;AAEd,MAAI,OAAO,YAAY,YACrB,OAAM,IAAI,MAAM,6BAA6B;AAE/C,SAAO,QAAQ;;CAGjB,mBAAmB,MAAc,UAA0B;EACzD,MAAM,YAAY,KAAK,aAAa,KAAK;EACzC,MAAM,aAAa,KAAK,aAAa,MAAM;AAC3C,MAAI,aAAa,YAAY;AAC3B,QAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG;IACzC,MAAM,cAAc,UAAU,QAAQ,UAAU,MAC7C,WAAW,QAAQ,UAAU;AAChC,QAAI,eAAe,EACjB,QAAO;;AAGX,OAAI,UAAU,eAAe,WAAW,WACtC,QAAO;AAET,OAAI,CAAC,UAAU,WACb,QAAO;AAET,OAAI,CAAC,WAAW,WACd,QAAO;AAET,UAAO,UAAU,WAAW,cAAc,WAAW,YAAY,KAAA,GAAW,EAC1E,SAAS,MACV,CAAC;;AAEJ,SAAO,KAAK,cAAc,OAAO,KAAA,GAAW,EAAE,SAAS,MAAM,CAAC;;CAGhE,gBACE,YAC2E;EAC3E,MAAM,QAAQ,mEAAmE,KAC/E,QACD;AACD,MAAI,CAAC,MACH;AAEF,SAAO;GACL,SAAS;IAAC,OAAO,MAAM,GAAG;IAAE,OAAO,MAAM,GAAG;IAAE,OAAO,MAAM,GAAG;IAAC;GAC/D,YAAY,MAAM;GACnB;;CAGH,gBACE,OACA,cAC4B;AAC5B,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,OAAM,IAAI,MAAM,GAAG,UAAU,SAAS;AAExC,SAAO"}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
//#region src/services/file-lock.service.d.ts
|
|
2
|
+
declare class FileLockBusyError extends Error {
|
|
3
|
+
readonly lockPath: string;
|
|
4
|
+
constructor(lockPath: string);
|
|
5
|
+
}
|
|
6
|
+
type FileLockLease = {
|
|
7
|
+
release(): Promise<void>;
|
|
8
|
+
};
|
|
2
9
|
declare class FileLockService {
|
|
3
10
|
private static readonly localQueues;
|
|
4
11
|
withLock: <T>(lockPath: string, operation: () => Promise<T>, options?: {
|
|
5
12
|
waitTimeoutMs?: number;
|
|
6
13
|
}) => Promise<T>;
|
|
14
|
+
acquireLease: (lockPath: string, options?: {
|
|
15
|
+
waitTimeoutMs?: number;
|
|
16
|
+
}) => Promise<FileLockLease>;
|
|
7
17
|
private acquire;
|
|
8
18
|
private recoverAndAcquire;
|
|
9
19
|
private waitOrThrow;
|
|
@@ -16,5 +26,5 @@ declare class FileLockService {
|
|
|
16
26
|
private isMissingFileError;
|
|
17
27
|
}
|
|
18
28
|
//#endregion
|
|
19
|
-
export { FileLockService };
|
|
29
|
+
export { FileLockBusyError, FileLockLease, FileLockService };
|
|
20
30
|
//# sourceMappingURL=file-lock.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.service.d.ts","names":[],"sources":["../../src/services/file-lock.service.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"file-lock.service.d.ts","names":[],"sources":["../../src/services/file-lock.service.ts"],"mappings":";cAWa,iBAAA,SAA0B,KAAA;EAAA,SAC5B,QAAA;cAEG,QAAA;AAAA;AAAA,KAOF,aAAA;EACV,OAAA,IAAW,OAAA;AAAA;AAAA,cAOA,eAAA;EAAA,wBACa,WAAA;EAExB,QAAA,MACE,QAAA,UACA,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,OAAA;IAAW,aAAA;EAAA,MACV,OAAA,CAAQ,CAAA;EAqBX,YAAA,GACE,QAAA,UACA,OAAA;IAAW,aAAA;EAAA,MACV,OAAA,CAAQ,aAAA;EAAA,QAIH,OAAA;EAAA,QA8CA,iBAAA;EAAA,QA6CA,WAAA;EAAA,QAWA,eAAA;EAAA,QAcA,cAAA;EAAA,QAgBA,aAAA;EAAA,QAmBA,cAAA;EAAA,QAUA,UAAA;EAAA,QASA,oBAAA;EAAA,QAIA,kBAAA;AAAA"}
|
|
@@ -3,6 +3,14 @@ import { access, mkdir, open, readFile, rm, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { setTimeout } from "node:timers/promises";
|
|
5
5
|
//#region src/services/file-lock.service.ts
|
|
6
|
+
var FileLockBusyError = class extends Error {
|
|
7
|
+
lockPath;
|
|
8
|
+
constructor(lockPath) {
|
|
9
|
+
super(`等待文件锁超时:${lockPath}`);
|
|
10
|
+
this.name = "FileLockBusyError";
|
|
11
|
+
this.lockPath = lockPath;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
6
14
|
const DEFAULT_WAIT_TIMEOUT_MS = 15e3;
|
|
7
15
|
const MALFORMED_LOCK_STALE_AFTER_MS = 3e4;
|
|
8
16
|
const RETRY_DELAY_MS = 25;
|
|
@@ -25,6 +33,7 @@ var FileLockService = class FileLockService {
|
|
|
25
33
|
if (FileLockService.localQueues.get(resolvedLockPath) === current) FileLockService.localQueues.delete(resolvedLockPath);
|
|
26
34
|
}
|
|
27
35
|
};
|
|
36
|
+
acquireLease = async (lockPath, options = {}) => ({ release: await this.acquire(path.resolve(lockPath), options.waitTimeoutMs ?? 0) });
|
|
28
37
|
acquire = async (lockPath, waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS) => {
|
|
29
38
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
30
39
|
const startedAt = Date.now();
|
|
@@ -101,7 +110,7 @@ var FileLockService = class FileLockService {
|
|
|
101
110
|
}
|
|
102
111
|
};
|
|
103
112
|
waitOrThrow = async (lockPath, startedAt, waitTimeoutMs) => {
|
|
104
|
-
if (Date.now() - startedAt >= waitTimeoutMs) throw new
|
|
113
|
+
if (Date.now() - startedAt >= waitTimeoutMs) throw new FileLockBusyError(lockPath);
|
|
105
114
|
await setTimeout(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));
|
|
106
115
|
};
|
|
107
116
|
removeOwnedLock = async (lockPath, token) => {
|
|
@@ -151,6 +160,6 @@ var FileLockService = class FileLockService {
|
|
|
151
160
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
152
161
|
};
|
|
153
162
|
//#endregion
|
|
154
|
-
export { FileLockService };
|
|
163
|
+
export { FileLockBusyError, FileLockService };
|
|
155
164
|
|
|
156
165
|
//# sourceMappingURL=file-lock.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.service.js","names":["delay"],"sources":["../../src/services/file-lock.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, open, readFile, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\ntype FileLockMetadata = {\n token: string;\n pid: number;\n createdAt: string;\n};\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 15_000;\nconst MALFORMED_LOCK_STALE_AFTER_MS = 30_000;\nconst RETRY_DELAY_MS = 25;\n\nexport class FileLockService {\n private static readonly localQueues = new Map<string, Promise<unknown>>();\n\n withLock = async <T>(\n lockPath: string,\n operation: () => Promise<T>,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<T> => {\n const resolvedLockPath = path.resolve(lockPath);\n const previous = FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(async () => {\n const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);\n try {\n return await operation();\n } finally {\n await release();\n }\n });\n FileLockService.localQueues.set(resolvedLockPath, current);\n try {\n return await current;\n } finally {\n if (FileLockService.localQueues.get(resolvedLockPath) === current) {\n FileLockService.localQueues.delete(resolvedLockPath);\n }\n }\n };\n\n private acquire = async (\n lockPath: string,\n waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS,\n ): Promise<() => Promise<void>> => {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const startedAt = Date.now();\n const recoveryLockPath = `${lockPath}.recovery`;\n while (true) {\n if (await this.pathExists(recoveryLockPath)) {\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n continue;\n }\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } catch (error) {\n if (!this.isAlreadyExistsError(error)) {\n throw error;\n }\n if (await this.canRecoverLock(lockPath)) {\n const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);\n if (recovered) {\n return recovered;\n }\n }\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n }\n }\n };\n\n private recoverAndAcquire = async (\n lockPath: string,\n recoveryLockPath: string,\n ): Promise<(() => Promise<void>) | undefined> => {\n let recoveryHandle;\n try {\n recoveryHandle = await open(recoveryLockPath, \"wx\", 0o600);\n await recoveryHandle.writeFile(`${process.pid}\\n`, \"utf8\");\n await recoveryHandle.sync();\n } catch (error) {\n await recoveryHandle?.close().catch(() => undefined);\n if (this.isAlreadyExistsError(error)) {\n return undefined;\n }\n throw error;\n }\n try {\n if (!await this.canRecoverLock(lockPath)) {\n return undefined;\n }\n await rm(lockPath, { force: true });\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } finally {\n await recoveryHandle.close();\n await rm(recoveryLockPath, { force: true });\n }\n };\n\n private waitOrThrow = async (\n lockPath: string,\n startedAt: number,\n waitTimeoutMs: number,\n ): Promise<void> => {\n if (Date.now() - startedAt >= waitTimeoutMs) {\n throw new Error(`等待文件锁超时:${lockPath}`);\n }\n await delay(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));\n };\n\n private removeOwnedLock = async (lockPath: string, token: string): Promise<void> => {\n try {\n const metadata = this.parseMetadata(await readFile(lockPath, \"utf8\"));\n if (metadata?.token !== token) {\n throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);\n }\n await rm(lockPath);\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private canRecoverLock = async (lockPath: string): Promise<boolean> => {\n try {\n const [raw, lockStats] = await Promise.all([\n readFile(lockPath, \"utf8\"),\n stat(lockPath),\n ]);\n const metadata = this.parseMetadata(raw);\n if (metadata) {\n return !this.isProcessAlive(metadata.pid);\n }\n return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;\n } catch (error) {\n return this.isMissingFileError(error);\n }\n };\n\n private parseMetadata = (raw: string): FileLockMetadata | undefined => {\n try {\n const candidate = JSON.parse(raw) as Partial<FileLockMetadata>;\n if (\n typeof candidate.token !== \"string\" ||\n !candidate.token ||\n typeof candidate.pid !== \"number\" ||\n !Number.isSafeInteger(candidate.pid) ||\n candidate.pid <= 0 ||\n typeof candidate.createdAt !== \"string\"\n ) {\n return undefined;\n }\n return candidate as FileLockMetadata;\n } catch {\n return undefined;\n }\n };\n\n private isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EPERM\";\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isAlreadyExistsError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EEXIST\";\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":"file-lock.service.js","names":["delay"],"sources":["../../src/services/file-lock.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, open, readFile, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\ntype FileLockMetadata = {\n token: string;\n pid: number;\n createdAt: string;\n};\n\nexport class FileLockBusyError extends Error {\n readonly lockPath: string;\n\n constructor(lockPath: string) {\n super(`等待文件锁超时:${lockPath}`);\n this.name = \"FileLockBusyError\";\n this.lockPath = lockPath;\n }\n}\n\nexport type FileLockLease = {\n release(): Promise<void>;\n};\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 15_000;\nconst MALFORMED_LOCK_STALE_AFTER_MS = 30_000;\nconst RETRY_DELAY_MS = 25;\n\nexport class FileLockService {\n private static readonly localQueues = new Map<string, Promise<unknown>>();\n\n withLock = async <T>(\n lockPath: string,\n operation: () => Promise<T>,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<T> => {\n const resolvedLockPath = path.resolve(lockPath);\n const previous = FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(async () => {\n const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);\n try {\n return await operation();\n } finally {\n await release();\n }\n });\n FileLockService.localQueues.set(resolvedLockPath, current);\n try {\n return await current;\n } finally {\n if (FileLockService.localQueues.get(resolvedLockPath) === current) {\n FileLockService.localQueues.delete(resolvedLockPath);\n }\n }\n };\n\n acquireLease = async (\n lockPath: string,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<FileLockLease> => ({\n release: await this.acquire(path.resolve(lockPath), options.waitTimeoutMs ?? 0),\n });\n\n private acquire = async (\n lockPath: string,\n waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS,\n ): Promise<() => Promise<void>> => {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const startedAt = Date.now();\n const recoveryLockPath = `${lockPath}.recovery`;\n while (true) {\n if (await this.pathExists(recoveryLockPath)) {\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n continue;\n }\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } catch (error) {\n if (!this.isAlreadyExistsError(error)) {\n throw error;\n }\n if (await this.canRecoverLock(lockPath)) {\n const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);\n if (recovered) {\n return recovered;\n }\n }\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n }\n }\n };\n\n private recoverAndAcquire = async (\n lockPath: string,\n recoveryLockPath: string,\n ): Promise<(() => Promise<void>) | undefined> => {\n let recoveryHandle;\n try {\n recoveryHandle = await open(recoveryLockPath, \"wx\", 0o600);\n await recoveryHandle.writeFile(`${process.pid}\\n`, \"utf8\");\n await recoveryHandle.sync();\n } catch (error) {\n await recoveryHandle?.close().catch(() => undefined);\n if (this.isAlreadyExistsError(error)) {\n return undefined;\n }\n throw error;\n }\n try {\n if (!await this.canRecoverLock(lockPath)) {\n return undefined;\n }\n await rm(lockPath, { force: true });\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } finally {\n await recoveryHandle.close();\n await rm(recoveryLockPath, { force: true });\n }\n };\n\n private waitOrThrow = async (\n lockPath: string,\n startedAt: number,\n waitTimeoutMs: number,\n ): Promise<void> => {\n if (Date.now() - startedAt >= waitTimeoutMs) {\n throw new FileLockBusyError(lockPath);\n }\n await delay(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));\n };\n\n private removeOwnedLock = async (lockPath: string, token: string): Promise<void> => {\n try {\n const metadata = this.parseMetadata(await readFile(lockPath, \"utf8\"));\n if (metadata?.token !== token) {\n throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);\n }\n await rm(lockPath);\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private canRecoverLock = async (lockPath: string): Promise<boolean> => {\n try {\n const [raw, lockStats] = await Promise.all([\n readFile(lockPath, \"utf8\"),\n stat(lockPath),\n ]);\n const metadata = this.parseMetadata(raw);\n if (metadata) {\n return !this.isProcessAlive(metadata.pid);\n }\n return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;\n } catch (error) {\n return this.isMissingFileError(error);\n }\n };\n\n private parseMetadata = (raw: string): FileLockMetadata | undefined => {\n try {\n const candidate = JSON.parse(raw) as Partial<FileLockMetadata>;\n if (\n typeof candidate.token !== \"string\" ||\n !candidate.token ||\n typeof candidate.pid !== \"number\" ||\n !Number.isSafeInteger(candidate.pid) ||\n candidate.pid <= 0 ||\n typeof candidate.createdAt !== \"string\"\n ) {\n return undefined;\n }\n return candidate as FileLockMetadata;\n } catch {\n return undefined;\n }\n };\n\n private isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EPERM\";\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isAlreadyExistsError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EEXIST\";\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/app-runtime",
|
|
3
|
-
"version": "0.13.0",
|
|
3
|
+
"version": "0.13.2-beta.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Standalone micro app runtime and CLI for NextClaw apps.",
|
|
6
6
|
"type": "module",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"development": "./src/services/app-artifact-validation.service.ts",
|
|
38
38
|
"types": "./dist/services/app-artifact-validation.service.d.ts",
|
|
39
39
|
"default": "./dist/services/app-artifact-validation.service.js"
|
|
40
|
+
},
|
|
41
|
+
"./platform-target": {
|
|
42
|
+
"development": "./src/services/app-platform-target.service.ts",
|
|
43
|
+
"types": "./dist/services/app-platform-target.service.d.ts",
|
|
44
|
+
"default": "./dist/services/app-platform-target.service.js"
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
"imports": {
|