@nextclaw/app-runtime 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/index.d.ts +7 -4
  2. package/dist/index.js +4 -1
  3. package/dist/package.js +1 -1
  4. package/dist/services/app-artifact-validation.service.d.ts +4 -0
  5. package/dist/services/app-artifact-validation.service.d.ts.map +1 -1
  6. package/dist/services/app-artifact-validation.service.js +16 -2
  7. package/dist/services/app-artifact-validation.service.js.map +1 -1
  8. package/dist/services/app-bundle.service.d.ts +4 -0
  9. package/dist/services/app-bundle.service.d.ts.map +1 -1
  10. package/dist/services/app-bundle.service.js +26 -4
  11. package/dist/services/app-bundle.service.js.map +1 -1
  12. package/dist/services/app-installation-filesystem.service.js +21 -3
  13. package/dist/services/app-installation-filesystem.service.js.map +1 -1
  14. package/dist/services/app-installation-lifecycle.service.js +1 -0
  15. package/dist/services/app-installation-lifecycle.service.js.map +1 -1
  16. package/dist/services/app-installation.service.d.ts +1 -0
  17. package/dist/services/app-installation.service.d.ts.map +1 -1
  18. package/dist/services/app-installation.service.js +12 -13
  19. package/dist/services/app-installation.service.js.map +1 -1
  20. package/dist/services/app-manifest.service.d.ts +3 -0
  21. package/dist/services/app-manifest.service.d.ts.map +1 -1
  22. package/dist/services/app-manifest.service.js +6 -0
  23. package/dist/services/app-manifest.service.js.map +1 -1
  24. package/dist/services/app-platform-target.service.d.ts +45 -0
  25. package/dist/services/app-platform-target.service.d.ts.map +1 -0
  26. package/dist/services/app-platform-target.service.js +176 -0
  27. package/dist/services/app-platform-target.service.js.map +1 -0
  28. package/dist/services/app-publish-artifact.service.d.ts +26 -0
  29. package/dist/services/app-publish-artifact.service.d.ts.map +1 -0
  30. package/dist/services/app-publish-artifact.service.js +55 -0
  31. package/dist/services/app-publish-artifact.service.js.map +1 -0
  32. package/dist/services/app-publish-validation.service.d.ts +15 -1
  33. package/dist/services/app-publish-validation.service.d.ts.map +1 -1
  34. package/dist/services/app-publish-validation.service.js +39 -1
  35. package/dist/services/app-publish-validation.service.js.map +1 -1
  36. package/dist/services/app-publish.service.d.ts +6 -1
  37. package/dist/services/app-publish.service.d.ts.map +1 -1
  38. package/dist/services/app-publish.service.js +43 -11
  39. package/dist/services/app-publish.service.js.map +1 -1
  40. package/dist/services/app-registry.service.d.ts +2 -0
  41. package/dist/services/app-registry.service.d.ts.map +1 -1
  42. package/dist/services/app-registry.service.js +4 -0
  43. package/dist/services/app-registry.service.js.map +1 -1
  44. package/dist/services/app-remote-registry-client.service.d.ts +7 -1
  45. package/dist/services/app-remote-registry-client.service.d.ts.map +1 -1
  46. package/dist/services/app-remote-registry-client.service.js +74 -7
  47. package/dist/services/app-remote-registry-client.service.js.map +1 -1
  48. package/dist/services/app-service-launch.service.d.ts +19 -0
  49. package/dist/services/app-service-launch.service.d.ts.map +1 -0
  50. package/dist/services/app-service-launch.service.js +54 -0
  51. package/dist/services/app-service-launch.service.js.map +1 -0
  52. package/dist/types/app-bundle.types.d.ts +3 -0
  53. package/dist/types/app-bundle.types.d.ts.map +1 -1
  54. package/dist/types/app-installation.types.d.ts +3 -1
  55. package/dist/types/app-installation.types.d.ts.map +1 -1
  56. package/dist/types/app-manifest.types.d.ts +32 -1
  57. package/dist/types/app-manifest.types.d.ts.map +1 -1
  58. package/dist/types/app-manifest.types.js.map +1 -1
  59. package/dist/types/app-publish.types.d.ts +26 -7
  60. package/dist/types/app-publish.types.d.ts.map +1 -1
  61. package/dist/types/app-publish.types.js.map +1 -1
  62. package/dist/types/app-registry.types.d.ts +2 -1
  63. package/dist/types/app-registry.types.d.ts.map +1 -1
  64. package/dist/types/app-remote-registry.types.d.ts +12 -2
  65. package/dist/types/app-remote-registry.types.d.ts.map +1 -1
  66. package/dist/types/app-remote-registry.types.js.map +1 -1
  67. package/package.json +6 -1
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ import { AppArtifactArchitecture, AppArtifactTarget, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDarwinArtifactTarget, AppDistributionDeclaration, AppDocumentAccessMode, AppDocumentAccessScope, AppLinuxArtifactTarget, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppNativeArtifactTarget, AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent, AppRuntimeDeclaration, AppRuntimeIsolation, AppRuntimeProfile, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageDeclaration, AppUiManifest, AppUniversalArtifactTarget, AppWasiHttpComponentMainManifest, AppWindowsArtifactTarget, isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
1
2
  import { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppDistributionMode } from "./types/app-bundle.types.js";
2
3
  import { AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService } from "./services/app-artifact-validation.service.js";
3
- import { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent, AppRuntimeDeclaration, AppRuntimeIsolation, AppRuntimeProfile, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageDeclaration, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
4
+ import { AppPlatformTargetService } from "./services/app-platform-target.service.js";
4
5
  import { AppManifestService } from "./services/app-manifest.service.js";
5
6
  import { AppBundleService } from "./services/app-bundle.service.js";
6
7
  import { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions } from "./types/app-permissions.types.js";
@@ -19,7 +20,7 @@ import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
19
20
  import { CreateCommand } from "./controllers/create.controller.js";
20
21
  import { AppHomeService } from "./services/app-home.service.js";
21
22
  import { APP_STORAGE_LAYOUT_VERSION, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceMetadata, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
22
- import { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
23
+ import { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryArtifact, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
23
24
  import { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion } from "./types/app-registry.types.js";
24
25
  import { AppRegistryService } from "./services/app-registry.service.js";
25
26
  import { AppGrantService } from "./services/app-grant.service.js";
@@ -36,9 +37,10 @@ import { InstallCommand } from "./controllers/install.controller.js";
36
37
  import { ListCommand } from "./controllers/list.controller.js";
37
38
  import { PackCommand } from "./controllers/pack.controller.js";
38
39
  import { PermissionsCommand } from "./controllers/permissions.controller.js";
39
- import { AppMarketplaceMetadata, AppMarketplaceVisuals, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./types/app-publish.types.js";
40
+ import { AppMarketplaceMetadata, AppMarketplaceVisuals, AppPublishArtifact, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./types/app-publish.types.js";
40
41
  import { AppMarketplaceClientService } from "./services/app-marketplace-client.service.js";
41
42
  import { AppMarketplaceMetadataService } from "./services/app-marketplace-metadata.service.js";
43
+ import { AppPreparedPublishArtifact, AppPublishArtifactService } from "./services/app-publish-artifact.service.js";
42
44
  import { PlatformAuthStateService, PlatformPublishAuthState } from "./services/platform-auth-state.service.js";
43
45
  import { AppPublishService } from "./services/app-publish.service.js";
44
46
  import { PublishCommand } from "./controllers/publish.controller.js";
@@ -51,7 +53,8 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
51
53
  import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./services/wasmtime-wasi-http-component.service.js";
52
54
  import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/app-host.service.js";
53
55
  import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
56
+ import { AppServiceLaunch, AppServiceLaunchService } from "./services/app-service-launch.service.js";
54
57
  import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
55
58
  import { FileLockService } from "./services/file-lock.service.js";
56
59
  import { UiServerService } from "./services/ui-server.service.js";
57
- export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
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 };
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { AppPlatformTargetService } from "./services/app-platform-target.service.js";
2
+ import { AppServiceLaunchService } from "./services/app-service-launch.service.js";
1
3
  import { AppArtifactValidationService } from "./services/app-artifact-validation.service.js";
2
4
  import { AppManifestService } from "./services/app-manifest.service.js";
3
5
  import { isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
@@ -37,6 +39,7 @@ import { PermissionsCommand } from "./controllers/permissions.controller.js";
37
39
  import { DEFAULT_APP_MARKETPLACE_API_BASE } from "./types/app-publish.types.js";
38
40
  import { AppMarketplaceClientService } from "./services/app-marketplace-client.service.js";
39
41
  import { AppMarketplaceMetadataService } from "./services/app-marketplace-metadata.service.js";
42
+ import { AppPublishArtifactService } from "./services/app-publish-artifact.service.js";
40
43
  import { PlatformAuthStateService } from "./services/platform-auth-state.service.js";
41
44
  import { AppPublishService } from "./services/app-publish.service.js";
42
45
  import { PublishCommand } from "./controllers/publish.controller.js";
@@ -49,4 +52,4 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
49
52
  import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
50
53
  import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
51
54
  import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
52
- export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
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 };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.2";
2
+ var version = "0.13.1";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -1,3 +1,4 @@
1
+ import { AppArtifactTarget } from "../types/app-manifest.types.js";
1
2
  import { AppBundleChecksums, AppBundleMetadata, AppDistributionMode } from "../types/app-bundle.types.js";
2
3
 
3
4
  //#region src/services/app-artifact-validation.service.d.ts
@@ -7,6 +8,7 @@ type AppArtifactValidationExpectation = {
7
8
  version: string;
8
9
  distributionMode: AppDistributionMode;
9
10
  manifest?: unknown;
11
+ target?: AppArtifactTarget;
10
12
  };
11
13
  type AppArtifactValidationResult = {
12
14
  archive: Record<string, Uint8Array>;
@@ -16,6 +18,8 @@ type AppArtifactValidationResult = {
16
18
  manifest: Record<string, unknown>;
17
19
  };
18
20
  declare class AppArtifactValidationService {
21
+ private readonly platformTargetService;
22
+ private readonly serviceLaunchService;
19
23
  validate: (params: {
20
24
  bytes: Uint8Array;
21
25
  expected?: AppArtifactValidationExpectation;
@@ -1 +1 @@
1
- {"version":3,"file":"app-artifact-validation.service.d.ts","names":[],"sources":["../../src/services/app-artifact-validation.service.ts"],"mappings":";;;KAgCY,gCAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA,EAAkB,mBAAA;EAClB,QAAA;AAAA;AAAA,KAGU,2BAAA;EACV,OAAA,EAAS,MAAA,SAAe,UAAA;EACxB,cAAA;EACA,QAAA,EAAU,iBAAA;EACV,SAAA,EAAW,kBAAA;EACX,QAAA,EAAU,MAAA;AAAA;AAAA,cAQC,4BAAA;EACX,QAAA,GAAkB,MAAA;IAChB,KAAA,EAAO,UAAA;IACP,QAAA,GAAW,gCAAA;EAAA,MACT,OAAA,CAAQ,2BAAA;EAAA,QA0BJ,kBAAA;EAAA,QAiCA,oBAAA;EAAA,QAmCA,gBAAA;EAAA,QAgEA,cAAA;EAAA,QAiBA,iBAAA;EAAA,QAsBA,gBAAA;EAAA,QAcA,cAAA;EAAA,QAiBA,gCAAA;EAAA,QAuBA,YAAA;EAAA,QA0BA,aAAA;EAAA,QAcA,eAAA;EAAA,QA4BA,sBAAA;EAAA,QA6DA,qBAAA;EAAA,QAiBA,sBAAA;EAAA,QAwBA,aAAA;EAAA,QAkBA,UAAA;EAAA,QAQA,MAAA;EAAA,QAUA,WAAA;EAAA,QAOA,YAAA;EAAA,QAsBA,QAAA;AAAA"}
1
+ {"version":3,"file":"app-artifact-validation.service.d.ts","names":[],"sources":["../../src/services/app-artifact-validation.service.ts"],"mappings":";;;;KAmCY,gCAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA,EAAkB,mBAAA;EAClB,QAAA;EACA,MAAA,GAAS,iBAAA;AAAA;AAAA,KAGC,2BAAA;EACV,OAAA,EAAS,MAAA,SAAe,UAAA;EACxB,cAAA;EACA,QAAA,EAAU,iBAAA;EACV,SAAA,EAAW,kBAAA;EACX,QAAA,EAAU,MAAA;AAAA;AAAA,cAQC,4BAAA;EAAA,iBACM,qBAAA;EAAA,iBACA,oBAAA;EAIjB,QAAA,GAAkB,MAAA;IAChB,KAAA,EAAO,UAAA;IACP,QAAA,GAAW,gCAAA;EAAA,MACT,OAAA,CAAQ,2BAAA;EAAA,QA0BJ,kBAAA;EAAA,QAiCA,oBAAA;EAAA,QAmCA,gBAAA;EAAA,QAgEA,cAAA;EAAA,QAiBA,iBAAA;EAAA,QAsBA,gBAAA;EAAA,QAcA,cAAA;EAAA,QAiBA,gCAAA;EAAA,QAuBA,YAAA;EAAA,QAmCA,aAAA;EAAA,QAcA,eAAA;EAAA,QA4BA,sBAAA;EAAA,QA6DA,qBAAA;EAAA,QAiBA,sBAAA;EAAA,QA0DA,aAAA;EAAA,QAkBA,UAAA;EAAA,QAQA,MAAA;EAAA,QAUA,WAAA;EAAA,QAOA,YAAA;EAAA,QAsBA,QAAA;AAAA"}
@@ -1,3 +1,5 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
2
+ import { AppServiceLaunchService } from "./app-service-launch.service.js";
1
3
  import { strFromU8, unzipSync } from "fflate";
2
4
  //#region src/services/app-artifact-validation.service.ts
3
5
  const MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;
@@ -14,6 +16,8 @@ const ZIP_EOCD_MIN_BYTES = 22;
14
16
  const ZIP_MAX_COMMENT_BYTES = 65535;
15
17
  const ZIP_CENTRAL_FILE_HEADER_BYTES = 46;
16
18
  var AppArtifactValidationService = class {
19
+ platformTargetService = new AppPlatformTargetService();
20
+ serviceLaunchService = new AppServiceLaunchService(this.platformTargetService);
17
21
  validate = async (params) => {
18
22
  const { bytes, expected } = params;
19
23
  if (bytes.byteLength === 0 || bytes.byteLength > MAX_COMPRESSED_BYTES) throw new Error(`bundle 压缩体积必须在 1 到 ${MAX_COMPRESSED_BYTES} bytes 之间。`);
@@ -31,6 +35,7 @@ var AppArtifactValidationService = class {
31
35
  metadata
32
36
  });
33
37
  if (expected) this.assertExpectedContract({
38
+ archive,
34
39
  expected,
35
40
  manifest,
36
41
  metadata
@@ -161,7 +166,8 @@ var AppArtifactValidationService = class {
161
166
  if (raw.bundleFormatVersion !== 1 || distributionMode !== "bundle" && distributionMode !== "source" || typeof raw.appId !== "string" || !raw.appId || typeof raw.name !== "string" || !raw.name || typeof raw.version !== "string" || !raw.version || raw.entryManifest !== ENTRY_MANIFEST_PATH || raw.checksumsFile !== CHECKSUMS_PATH) throw new Error("bundle metadata 无效。");
162
167
  return {
163
168
  ...raw,
164
- distributionMode
169
+ distributionMode,
170
+ target: raw.target === void 0 ? void 0 : this.platformTargetService.parseArtifactTarget(raw.target, "bundle metadata.target")
165
171
  };
166
172
  };
167
173
  readChecksums = (archive) => {
@@ -209,9 +215,17 @@ var AppArtifactValidationService = class {
209
215
  if (!archive[entry]) throw new Error(`bundle 缺少 manifest.${pathLabel} 文件:${entry}`);
210
216
  };
211
217
  assertExpectedContract = (params) => {
212
- const { expected, manifest, metadata } = params;
218
+ const { archive, expected, manifest, metadata } = params;
213
219
  if (expected.appId !== metadata.appId || expected.name !== metadata.name || expected.version !== metadata.version || expected.distributionMode !== metadata.distributionMode) throw new Error("artifact metadata 与 publish payload 不一致。");
220
+ if (expected.target !== void 0 && (metadata.target === void 0 || this.platformTargetService.toTargetKey(expected.target) !== this.platformTargetService.toTargetKey(metadata.target))) throw new Error("artifact target metadata 与 publish payload 不一致。");
214
221
  if (expected.manifest !== void 0 && !this.isJsonSubset(this.toJsonValue(expected.manifest), manifest)) throw new Error("artifact manifest.json 与 publish payload.manifest 不一致。");
222
+ if (expected.target?.kind === "native" && manifest.schemaVersion === 2 && Array.isArray(manifest.components)) for (const rawComponent of manifest.components) {
223
+ if (!this.isRecord(rawComponent) || rawComponent.kind !== "service") continue;
224
+ const componentPath = typeof rawComponent.path === "string" ? this.normalizeEntry(rawComponent.path) : void 0;
225
+ if (!componentPath) throw new Error("service component path 无效。");
226
+ const serviceManifest = this.readJsonEntry(archive, `${componentPath}/service-app.json`, `${componentPath}/service-app.json`);
227
+ this.serviceLaunchService.resolve(serviceManifest, expected.target);
228
+ }
215
229
  };
216
230
  readJsonEntry = (archive, entryPath, label) => {
217
231
  const bytes = archive[entryPath];
@@ -1 +1 @@
1
- {"version":3,"file":"app-artifact-validation.service.js","names":[],"sources":["../../src/services/app-artifact-validation.service.ts"],"sourcesContent":["import { strFromU8, unzipSync } from \"fflate\";\nimport type {\n AppBundleChecksums,\n AppBundleMetadata,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst MAX_COMPRESSION_RATIO = 200;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\nconst ENTRY_MANIFEST_PATH = \"manifest.json\";\nconst ZIP_EOCD_SIGNATURE = 0x06054b50;\nconst ZIP_CENTRAL_FILE_SIGNATURE = 0x02014b50;\nconst ZIP_EOCD_MIN_BYTES = 22;\nconst ZIP_MAX_COMMENT_BYTES = 0xffff;\nconst ZIP_CENTRAL_FILE_HEADER_BYTES = 46;\n\ntype ArtifactManifestIdentity = {\n schemaVersion?: unknown;\n id?: unknown;\n name?: unknown;\n version?: unknown;\n icon?: unknown;\n main?: unknown;\n ui?: unknown;\n components?: unknown;\n};\n\nexport type AppArtifactValidationExpectation = {\n appId: string;\n name: string;\n version: string;\n distributionMode: AppDistributionMode;\n manifest?: unknown;\n};\n\nexport type AppArtifactValidationResult = {\n archive: Record<string, Uint8Array>;\n artifactSha256: string;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n manifest: Record<string, unknown>;\n};\n\ntype ZipCentralEntry = {\n name: string;\n uncompressedSize: number;\n};\n\nexport class AppArtifactValidationService {\n validate = async (params: {\n bytes: Uint8Array;\n expected?: AppArtifactValidationExpectation;\n }): Promise<AppArtifactValidationResult> => {\n const { bytes, expected } = params;\n if (bytes.byteLength === 0 || bytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(\n `bundle 压缩体积必须在 1 到 ${MAX_COMPRESSED_BYTES} bytes 之间。`,\n );\n }\n const artifactSha256 = await this.sha256(bytes);\n const centralEntries = this.readCentralEntries(bytes);\n const archive = this.normalizeArchive(unzipSync(bytes));\n this.assertCentralEntriesMatchArchive(centralEntries, archive);\n const metadata = this.readMetadata(archive);\n const checksums = this.readChecksums(archive);\n await this.verifyChecksums(archive, checksums);\n const manifest = this.readJsonEntry<Record<string, unknown>>(\n archive,\n ENTRY_MANIFEST_PATH,\n \"manifest.json\",\n );\n this.assertManifestContract({ archive, manifest, metadata });\n if (expected) {\n this.assertExpectedContract({ expected, manifest, metadata });\n }\n return { archive, artifactSha256, metadata, checksums, manifest };\n };\n\n private readCentralEntries = (bytes: Uint8Array): ZipCentralEntry[] => {\n const { centralDirectoryOffset, entryCount, eocdOffset, view } =\n this.readCentralDirectory(bytes);\n const entries: ZipCentralEntry[] = [];\n const names = new Set<string>();\n let cursor = centralDirectoryOffset;\n let totalUncompressedBytes = 0;\n for (let index = 0; index < entryCount; index += 1) {\n const { entry, entryEnd } = this.readCentralEntry({\n bytes,\n cursor,\n eocdOffset,\n view,\n });\n if (names.has(entry.name)) {\n throw new Error(`bundle 包含重复路径:${entry.name}`);\n }\n names.add(entry.name);\n totalUncompressedBytes += entry.uncompressedSize;\n if (totalUncompressedBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(\n `bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`,\n );\n }\n entries.push(entry);\n cursor = entryEnd;\n }\n if (cursor !== eocdOffset) {\n throw new Error(\"bundle central directory 未被精确消费。\");\n }\n return entries;\n };\n\n private readCentralDirectory = (bytes: Uint8Array) => {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const eocdOffset = this.findEocdOffset(view);\n const diskNumber = view.getUint16(eocdOffset + 4, true);\n const centralDirectoryDisk = view.getUint16(eocdOffset + 6, true);\n const entryCountOnDisk = view.getUint16(eocdOffset + 8, true);\n const entryCount = view.getUint16(eocdOffset + 10, true);\n const centralDirectorySize = view.getUint32(eocdOffset + 12, true);\n const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);\n const commentLength = view.getUint16(eocdOffset + 20, true);\n if (\n diskNumber !== 0 ||\n centralDirectoryDisk !== 0 ||\n entryCountOnDisk !== entryCount\n ) {\n throw new Error(\"bundle 不支持跨磁盘 ZIP。\");\n }\n if (\n entryCount === 0xffff ||\n centralDirectorySize === 0xffffffff ||\n centralDirectoryOffset === 0xffffffff\n ) {\n throw new Error(\"bundle 不支持 Zip64。\");\n }\n if (\n entryCount === 0 ||\n entryCount > MAX_FILE_COUNT ||\n eocdOffset + ZIP_EOCD_MIN_BYTES + commentLength !== bytes.byteLength ||\n centralDirectoryOffset + centralDirectorySize !== eocdOffset\n ) {\n throw new Error(\"bundle central directory 无效。\");\n }\n return { centralDirectoryOffset, entryCount, eocdOffset, view };\n };\n\n private readCentralEntry = (params: {\n bytes: Uint8Array;\n cursor: number;\n eocdOffset: number;\n view: DataView;\n }): { entry: ZipCentralEntry; entryEnd: number } => {\n const { bytes, cursor, eocdOffset, view } = params;\n if (\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES > eocdOffset ||\n view.getUint32(cursor, true) !== ZIP_CENTRAL_FILE_SIGNATURE\n ) {\n throw new Error(\"bundle central directory entry 无效。\");\n }\n const versionMadeBy = view.getUint16(cursor + 4, true);\n const flags = view.getUint16(cursor + 8, true);\n const compressionMethod = view.getUint16(cursor + 10, true);\n const compressedSize = view.getUint32(cursor + 20, true);\n const uncompressedSize = view.getUint32(cursor + 24, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const entryCommentLength = view.getUint16(cursor + 32, true);\n const externalAttributes = view.getUint32(cursor + 38, true);\n const entryEnd =\n cursor +\n ZIP_CENTRAL_FILE_HEADER_BYTES +\n nameLength +\n extraLength +\n entryCommentLength;\n if (entryEnd > eocdOffset || nameLength === 0) {\n throw new Error(\"bundle central directory entry 长度无效。\");\n }\n if ((flags & 0x1) !== 0 || (flags & 0x40) !== 0) {\n throw new Error(\"bundle 不允许加密文件。\");\n }\n if (compressionMethod !== 0 && compressionMethod !== 8) {\n throw new Error(`bundle 不支持压缩方法 ${compressionMethod}。`);\n }\n const rawName = bytes.subarray(\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES,\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength,\n );\n const name = this.normalizeEntry(this.decodeUtf8(rawName));\n this.assertRegularFile({\n externalAttributes,\n hostSystem: versionMadeBy >>> 8,\n name,\n });\n if (uncompressedSize > MAX_FILE_BYTES) {\n throw new Error(\n `bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${name}`,\n );\n }\n if (\n uncompressedSize > 1024 * 1024 &&\n (compressedSize === 0 ||\n uncompressedSize / compressedSize > MAX_COMPRESSION_RATIO)\n ) {\n throw new Error(\n `bundle 文件压缩比超过 ${MAX_COMPRESSION_RATIO}:1 上限:${name}`,\n );\n }\n return { entry: { name, uncompressedSize }, entryEnd };\n };\n\n private findEocdOffset = (view: DataView): number => {\n const minimumOffset = Math.max(\n 0,\n view.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_MAX_COMMENT_BYTES,\n );\n for (\n let offset = view.byteLength - ZIP_EOCD_MIN_BYTES;\n offset >= minimumOffset;\n offset -= 1\n ) {\n if (view.getUint32(offset, true) === ZIP_EOCD_SIGNATURE) {\n return offset;\n }\n }\n throw new Error(\"bundle 缺少有效 ZIP end-of-central-directory。\");\n };\n\n private assertRegularFile = (params: {\n externalAttributes: number;\n hostSystem: number;\n name: string;\n }): void => {\n const { externalAttributes, hostSystem, name } = params;\n if (name.endsWith(\"/\")) {\n throw new Error(`bundle 不允许目录 entry:${name}`);\n }\n if (hostSystem === 3) {\n const unixMode = externalAttributes >>> 16;\n const fileType = unixMode & 0xf000;\n if (fileType !== 0 && fileType !== 0x8000) {\n throw new Error(`bundle 只允许普通文件:${name}`);\n }\n return;\n }\n if ((externalAttributes & 0x10) !== 0) {\n throw new Error(`bundle 只允许普通文件:${name}`);\n }\n };\n\n private normalizeArchive = (\n archive: Record<string, Uint8Array>,\n ): Record<string, Uint8Array> => {\n const normalized: Record<string, Uint8Array> = {};\n for (const [rawName, fileBytes] of Object.entries(archive)) {\n const name = this.normalizeEntry(rawName);\n if (Object.hasOwn(normalized, name)) {\n throw new Error(`bundle 包含重复路径:${name}`);\n }\n normalized[name] = fileBytes;\n }\n return normalized;\n };\n\n private normalizeEntry = (rawName: string): string => {\n const normalized = rawName.replace(/\\\\/g, \"/\");\n const segments = normalized.split(\"/\");\n if (\n !normalized ||\n normalized.startsWith(\"/\") ||\n normalized.includes(\"\\0\") ||\n /^[A-Za-z]:/.test(normalized) ||\n segments.some(\n (segment) => !segment || segment === \".\" || segment === \"..\",\n )\n ) {\n throw new Error(`bundle 内包含非法路径:${rawName}`);\n }\n return segments.join(\"/\");\n };\n\n private assertCentralEntriesMatchArchive = (\n entries: ZipCentralEntry[],\n archive: Record<string, Uint8Array>,\n ): void => {\n const centralPaths = entries\n .map((entry) => entry.name)\n .sort((left, right) => left.localeCompare(right));\n const archivePaths = Object.keys(archive).sort((left, right) =>\n left.localeCompare(right),\n );\n if (\n centralPaths.length !== archivePaths.length ||\n centralPaths.some((entry, index) => entry !== archivePaths[index])\n ) {\n throw new Error(\"bundle 解压结果与 central directory 不一致。\");\n }\n for (const entry of entries) {\n if (archive[entry.name]?.byteLength !== entry.uncompressedSize) {\n throw new Error(`bundle 解压后文件大小不一致:${entry.name}`);\n }\n }\n };\n\n private readMetadata = (\n archive: Record<string, Uint8Array>,\n ): AppBundleMetadata => {\n const raw = this.readJsonEntry<Partial<AppBundleMetadata>>(\n archive,\n BUNDLE_METADATA_PATH,\n \"bundle metadata\",\n );\n const distributionMode = raw.distributionMode ?? \"bundle\";\n if (\n raw.bundleFormatVersion !== 1 ||\n (distributionMode !== \"bundle\" && distributionMode !== \"source\") ||\n typeof raw.appId !== \"string\" ||\n !raw.appId ||\n typeof raw.name !== \"string\" ||\n !raw.name ||\n typeof raw.version !== \"string\" ||\n !raw.version ||\n raw.entryManifest !== ENTRY_MANIFEST_PATH ||\n raw.checksumsFile !== CHECKSUMS_PATH\n ) {\n throw new Error(\"bundle metadata 无效。\");\n }\n return { ...raw, distributionMode } as AppBundleMetadata;\n };\n\n private readChecksums = (\n archive: Record<string, Uint8Array>,\n ): AppBundleChecksums => {\n const raw = this.readJsonEntry<Partial<AppBundleChecksums>>(\n archive,\n CHECKSUMS_PATH,\n \"bundle checksums\",\n );\n if (raw.algorithm !== \"sha256\" || !this.isRecord(raw.files)) {\n throw new Error(\"bundle checksums 无效。\");\n }\n return raw as AppBundleChecksums;\n };\n\n private verifyChecksums = async (\n archive: Record<string, Uint8Array>,\n checksums: AppBundleChecksums,\n ): Promise<void> => {\n const actualPaths = Object.keys(archive)\n .filter((entry) => entry !== CHECKSUMS_PATH)\n .sort((left, right) => left.localeCompare(right));\n const checksumPaths = Object.keys(checksums.files)\n .map((entry) => this.normalizeEntry(entry))\n .sort((left, right) => left.localeCompare(right));\n if (\n actualPaths.length !== checksumPaths.length ||\n actualPaths.some((entry, index) => entry !== checksumPaths[index])\n ) {\n throw new Error(\"bundle checksums 必须精确覆盖所有 artifact 文件。\");\n }\n for (const relativePath of actualPaths) {\n const expectedHash = checksums.files[relativePath];\n if (!expectedHash || !/^[a-f0-9]{64}$/.test(expectedHash)) {\n throw new Error(`bundle checksum 格式无效:${relativePath}`);\n }\n const actualHash = await this.sha256(archive[relativePath] as Uint8Array);\n if (actualHash !== expectedHash) {\n throw new Error(`bundle checksum 校验失败:${relativePath}`);\n }\n }\n };\n\n private assertManifestContract = (params: {\n archive: Record<string, Uint8Array>;\n manifest: Record<string, unknown>;\n metadata: AppBundleMetadata;\n }): void => {\n const { archive, manifest, metadata } = params;\n const identity = manifest as ArtifactManifestIdentity;\n if (\n identity.id !== metadata.appId ||\n identity.name !== metadata.name ||\n identity.version !== metadata.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (identity.schemaVersion === 2) {\n if (metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n if (\n !Array.isArray(identity.components) ||\n identity.components.length === 0\n ) {\n throw new Error(\"schema v2 manifest.components 必须是非空数组。\");\n }\n for (const [index, rawComponent] of identity.components.entries()) {\n if (!this.isRecord(rawComponent)) {\n throw new Error(`manifest.components[${index}] 必须是对象。`);\n }\n const kind = rawComponent.kind;\n const componentPath = rawComponent.path;\n if (\n (kind !== \"panel\" && kind !== \"service\") ||\n typeof componentPath !== \"string\"\n ) {\n throw new Error(`manifest.components[${index}] 无效。`);\n }\n const normalizedPath = this.normalizeEntry(componentPath);\n const manifestPath = `${normalizedPath}/${kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\"}`;\n if (!archive[manifestPath]) {\n throw new Error(`bundle 缺少组件 manifest:${manifestPath}`);\n }\n }\n if (!archive[\"marketplace.json\"]) {\n throw new Error(\"schema v2 bundle 缺少 marketplace.json。\");\n }\n } else if (identity.schemaVersion === 1) {\n this.assertStandaloneEntry(archive, identity.main, \"main.entry\");\n this.assertStandaloneEntry(archive, identity.ui, \"ui.entry\");\n } else {\n throw new Error(\"manifest.schemaVersion 必须是 1 或 2。\");\n }\n if (identity.icon !== undefined) {\n if (\n typeof identity.icon !== \"string\" ||\n !archive[this.normalizeEntry(identity.icon)]\n ) {\n throw new Error(\"bundle 缺少 manifest.icon 声明的文件。\");\n }\n }\n };\n\n private assertStandaloneEntry = (\n archive: Record<string, Uint8Array>,\n rawContainer: unknown,\n pathLabel: string,\n ): void => {\n if (\n !this.isRecord(rawContainer) ||\n typeof rawContainer.entry !== \"string\"\n ) {\n throw new Error(`manifest.${pathLabel} 无效。`);\n }\n const entry = this.normalizeEntry(rawContainer.entry);\n if (!archive[entry]) {\n throw new Error(`bundle 缺少 manifest.${pathLabel} 文件:${entry}`);\n }\n };\n\n private assertExpectedContract = (params: {\n expected: AppArtifactValidationExpectation;\n manifest: Record<string, unknown>;\n metadata: AppBundleMetadata;\n }): void => {\n const { expected, manifest, metadata } = params;\n if (\n expected.appId !== metadata.appId ||\n expected.name !== metadata.name ||\n expected.version !== metadata.version ||\n expected.distributionMode !== metadata.distributionMode\n ) {\n throw new Error(\"artifact metadata 与 publish payload 不一致。\");\n }\n if (\n expected.manifest !== undefined &&\n !this.isJsonSubset(this.toJsonValue(expected.manifest), manifest)\n ) {\n throw new Error(\n \"artifact manifest.json 与 publish payload.manifest 不一致。\",\n );\n }\n };\n\n private readJsonEntry = <T>(\n archive: Record<string, Uint8Array>,\n entryPath: string,\n label: string,\n ): T => {\n const bytes = archive[entryPath];\n if (!bytes) {\n throw new Error(`bundle 缺少 ${label}。`);\n }\n try {\n return JSON.parse(strFromU8(bytes)) as T;\n } catch (error) {\n throw new Error(\n `无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n\n private decodeUtf8 = (bytes: Uint8Array): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new Error(\"bundle 文件名不是有效 UTF-8。\");\n }\n };\n\n private sha256 = async (bytes: Uint8Array): Promise<string> => {\n const digest = await globalThis.crypto.subtle.digest(\n \"SHA-256\",\n new Uint8Array(bytes),\n );\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n };\n\n private toJsonValue = (value: unknown): unknown => {\n const serialized = JSON.stringify(value);\n return serialized === undefined\n ? null\n : (JSON.parse(serialized) as unknown);\n };\n\n private isJsonSubset = (expected: unknown, actual: unknown): boolean => {\n if (Array.isArray(expected)) {\n return (\n Array.isArray(actual) &&\n expected.length === actual.length &&\n expected.every((entry, index) =>\n this.isJsonSubset(entry, actual[index]),\n )\n );\n }\n if (this.isRecord(expected)) {\n return (\n this.isRecord(actual) &&\n Object.entries(expected).every(\n ([key, value]) =>\n Object.hasOwn(actual, key) && this.isJsonSubset(value, actual[key]),\n )\n );\n }\n return Object.is(expected, actual);\n };\n\n private isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-artifact-validation.service.js","names":[],"sources":["../../src/services/app-artifact-validation.service.ts"],"sourcesContent":["import { strFromU8, unzipSync } from \"fflate\";\nimport { AppPlatformTargetService } from \"#app-runtime/services/app-platform-target.service.js\";\nimport { AppServiceLaunchService } from \"#app-runtime/services/app-service-launch.service.js\";\nimport type { AppArtifactTarget } from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppBundleChecksums,\n AppBundleMetadata,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst MAX_COMPRESSION_RATIO = 200;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\nconst ENTRY_MANIFEST_PATH = \"manifest.json\";\nconst ZIP_EOCD_SIGNATURE = 0x06054b50;\nconst ZIP_CENTRAL_FILE_SIGNATURE = 0x02014b50;\nconst ZIP_EOCD_MIN_BYTES = 22;\nconst ZIP_MAX_COMMENT_BYTES = 0xffff;\nconst ZIP_CENTRAL_FILE_HEADER_BYTES = 46;\n\ntype ArtifactManifestIdentity = {\n schemaVersion?: unknown;\n id?: unknown;\n name?: unknown;\n version?: unknown;\n icon?: unknown;\n main?: unknown;\n ui?: unknown;\n components?: unknown;\n};\n\nexport type AppArtifactValidationExpectation = {\n appId: string;\n name: string;\n version: string;\n distributionMode: AppDistributionMode;\n manifest?: unknown;\n target?: AppArtifactTarget;\n};\n\nexport type AppArtifactValidationResult = {\n archive: Record<string, Uint8Array>;\n artifactSha256: string;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n manifest: Record<string, unknown>;\n};\n\ntype ZipCentralEntry = {\n name: string;\n uncompressedSize: number;\n};\n\nexport class AppArtifactValidationService {\n private readonly platformTargetService = new AppPlatformTargetService();\n private readonly serviceLaunchService = new AppServiceLaunchService(\n this.platformTargetService,\n );\n\n validate = async (params: {\n bytes: Uint8Array;\n expected?: AppArtifactValidationExpectation;\n }): Promise<AppArtifactValidationResult> => {\n const { bytes, expected } = params;\n if (bytes.byteLength === 0 || bytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(\n `bundle 压缩体积必须在 1 到 ${MAX_COMPRESSED_BYTES} bytes 之间。`,\n );\n }\n const artifactSha256 = await this.sha256(bytes);\n const centralEntries = this.readCentralEntries(bytes);\n const archive = this.normalizeArchive(unzipSync(bytes));\n this.assertCentralEntriesMatchArchive(centralEntries, archive);\n const metadata = this.readMetadata(archive);\n const checksums = this.readChecksums(archive);\n await this.verifyChecksums(archive, checksums);\n const manifest = this.readJsonEntry<Record<string, unknown>>(\n archive,\n ENTRY_MANIFEST_PATH,\n \"manifest.json\",\n );\n this.assertManifestContract({ archive, manifest, metadata });\n if (expected) {\n this.assertExpectedContract({ archive, expected, manifest, metadata });\n }\n return { archive, artifactSha256, metadata, checksums, manifest };\n };\n\n private readCentralEntries = (bytes: Uint8Array): ZipCentralEntry[] => {\n const { centralDirectoryOffset, entryCount, eocdOffset, view } =\n this.readCentralDirectory(bytes);\n const entries: ZipCentralEntry[] = [];\n const names = new Set<string>();\n let cursor = centralDirectoryOffset;\n let totalUncompressedBytes = 0;\n for (let index = 0; index < entryCount; index += 1) {\n const { entry, entryEnd } = this.readCentralEntry({\n bytes,\n cursor,\n eocdOffset,\n view,\n });\n if (names.has(entry.name)) {\n throw new Error(`bundle 包含重复路径:${entry.name}`);\n }\n names.add(entry.name);\n totalUncompressedBytes += entry.uncompressedSize;\n if (totalUncompressedBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(\n `bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`,\n );\n }\n entries.push(entry);\n cursor = entryEnd;\n }\n if (cursor !== eocdOffset) {\n throw new Error(\"bundle central directory 未被精确消费。\");\n }\n return entries;\n };\n\n private readCentralDirectory = (bytes: Uint8Array) => {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const eocdOffset = this.findEocdOffset(view);\n const diskNumber = view.getUint16(eocdOffset + 4, true);\n const centralDirectoryDisk = view.getUint16(eocdOffset + 6, true);\n const entryCountOnDisk = view.getUint16(eocdOffset + 8, true);\n const entryCount = view.getUint16(eocdOffset + 10, true);\n const centralDirectorySize = view.getUint32(eocdOffset + 12, true);\n const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);\n const commentLength = view.getUint16(eocdOffset + 20, true);\n if (\n diskNumber !== 0 ||\n centralDirectoryDisk !== 0 ||\n entryCountOnDisk !== entryCount\n ) {\n throw new Error(\"bundle 不支持跨磁盘 ZIP。\");\n }\n if (\n entryCount === 0xffff ||\n centralDirectorySize === 0xffffffff ||\n centralDirectoryOffset === 0xffffffff\n ) {\n throw new Error(\"bundle 不支持 Zip64。\");\n }\n if (\n entryCount === 0 ||\n entryCount > MAX_FILE_COUNT ||\n eocdOffset + ZIP_EOCD_MIN_BYTES + commentLength !== bytes.byteLength ||\n centralDirectoryOffset + centralDirectorySize !== eocdOffset\n ) {\n throw new Error(\"bundle central directory 无效。\");\n }\n return { centralDirectoryOffset, entryCount, eocdOffset, view };\n };\n\n private readCentralEntry = (params: {\n bytes: Uint8Array;\n cursor: number;\n eocdOffset: number;\n view: DataView;\n }): { entry: ZipCentralEntry; entryEnd: number } => {\n const { bytes, cursor, eocdOffset, view } = params;\n if (\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES > eocdOffset ||\n view.getUint32(cursor, true) !== ZIP_CENTRAL_FILE_SIGNATURE\n ) {\n throw new Error(\"bundle central directory entry 无效。\");\n }\n const versionMadeBy = view.getUint16(cursor + 4, true);\n const flags = view.getUint16(cursor + 8, true);\n const compressionMethod = view.getUint16(cursor + 10, true);\n const compressedSize = view.getUint32(cursor + 20, true);\n const uncompressedSize = view.getUint32(cursor + 24, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const entryCommentLength = view.getUint16(cursor + 32, true);\n const externalAttributes = view.getUint32(cursor + 38, true);\n const entryEnd =\n cursor +\n ZIP_CENTRAL_FILE_HEADER_BYTES +\n nameLength +\n extraLength +\n entryCommentLength;\n if (entryEnd > eocdOffset || nameLength === 0) {\n throw new Error(\"bundle central directory entry 长度无效。\");\n }\n if ((flags & 0x1) !== 0 || (flags & 0x40) !== 0) {\n throw new Error(\"bundle 不允许加密文件。\");\n }\n if (compressionMethod !== 0 && compressionMethod !== 8) {\n throw new Error(`bundle 不支持压缩方法 ${compressionMethod}。`);\n }\n const rawName = bytes.subarray(\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES,\n cursor + ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength,\n );\n const name = this.normalizeEntry(this.decodeUtf8(rawName));\n this.assertRegularFile({\n externalAttributes,\n hostSystem: versionMadeBy >>> 8,\n name,\n });\n if (uncompressedSize > MAX_FILE_BYTES) {\n throw new Error(\n `bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${name}`,\n );\n }\n if (\n uncompressedSize > 1024 * 1024 &&\n (compressedSize === 0 ||\n uncompressedSize / compressedSize > MAX_COMPRESSION_RATIO)\n ) {\n throw new Error(\n `bundle 文件压缩比超过 ${MAX_COMPRESSION_RATIO}:1 上限:${name}`,\n );\n }\n return { entry: { name, uncompressedSize }, entryEnd };\n };\n\n private findEocdOffset = (view: DataView): number => {\n const minimumOffset = Math.max(\n 0,\n view.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_MAX_COMMENT_BYTES,\n );\n for (\n let offset = view.byteLength - ZIP_EOCD_MIN_BYTES;\n offset >= minimumOffset;\n offset -= 1\n ) {\n if (view.getUint32(offset, true) === ZIP_EOCD_SIGNATURE) {\n return offset;\n }\n }\n throw new Error(\"bundle 缺少有效 ZIP end-of-central-directory。\");\n };\n\n private assertRegularFile = (params: {\n externalAttributes: number;\n hostSystem: number;\n name: string;\n }): void => {\n const { externalAttributes, hostSystem, name } = params;\n if (name.endsWith(\"/\")) {\n throw new Error(`bundle 不允许目录 entry:${name}`);\n }\n if (hostSystem === 3) {\n const unixMode = externalAttributes >>> 16;\n const fileType = unixMode & 0xf000;\n if (fileType !== 0 && fileType !== 0x8000) {\n throw new Error(`bundle 只允许普通文件:${name}`);\n }\n return;\n }\n if ((externalAttributes & 0x10) !== 0) {\n throw new Error(`bundle 只允许普通文件:${name}`);\n }\n };\n\n private normalizeArchive = (\n archive: Record<string, Uint8Array>,\n ): Record<string, Uint8Array> => {\n const normalized: Record<string, Uint8Array> = {};\n for (const [rawName, fileBytes] of Object.entries(archive)) {\n const name = this.normalizeEntry(rawName);\n if (Object.hasOwn(normalized, name)) {\n throw new Error(`bundle 包含重复路径:${name}`);\n }\n normalized[name] = fileBytes;\n }\n return normalized;\n };\n\n private normalizeEntry = (rawName: string): string => {\n const normalized = rawName.replace(/\\\\/g, \"/\");\n const segments = normalized.split(\"/\");\n if (\n !normalized ||\n normalized.startsWith(\"/\") ||\n normalized.includes(\"\\0\") ||\n /^[A-Za-z]:/.test(normalized) ||\n segments.some(\n (segment) => !segment || segment === \".\" || segment === \"..\",\n )\n ) {\n throw new Error(`bundle 内包含非法路径:${rawName}`);\n }\n return segments.join(\"/\");\n };\n\n private assertCentralEntriesMatchArchive = (\n entries: ZipCentralEntry[],\n archive: Record<string, Uint8Array>,\n ): void => {\n const centralPaths = entries\n .map((entry) => entry.name)\n .sort((left, right) => left.localeCompare(right));\n const archivePaths = Object.keys(archive).sort((left, right) =>\n left.localeCompare(right),\n );\n if (\n centralPaths.length !== archivePaths.length ||\n centralPaths.some((entry, index) => entry !== archivePaths[index])\n ) {\n throw new Error(\"bundle 解压结果与 central directory 不一致。\");\n }\n for (const entry of entries) {\n if (archive[entry.name]?.byteLength !== entry.uncompressedSize) {\n throw new Error(`bundle 解压后文件大小不一致:${entry.name}`);\n }\n }\n };\n\n private readMetadata = (\n archive: Record<string, Uint8Array>,\n ): AppBundleMetadata => {\n const raw = this.readJsonEntry<Partial<AppBundleMetadata>>(\n archive,\n BUNDLE_METADATA_PATH,\n \"bundle metadata\",\n );\n const distributionMode = raw.distributionMode ?? \"bundle\";\n if (\n raw.bundleFormatVersion !== 1 ||\n (distributionMode !== \"bundle\" && distributionMode !== \"source\") ||\n typeof raw.appId !== \"string\" ||\n !raw.appId ||\n typeof raw.name !== \"string\" ||\n !raw.name ||\n typeof raw.version !== \"string\" ||\n !raw.version ||\n raw.entryManifest !== ENTRY_MANIFEST_PATH ||\n raw.checksumsFile !== CHECKSUMS_PATH\n ) {\n throw new Error(\"bundle metadata 无效。\");\n }\n return {\n ...raw,\n distributionMode,\n target: raw.target === undefined\n ? undefined\n : this.platformTargetService.parseArtifactTarget(\n raw.target,\n \"bundle metadata.target\",\n ),\n } as AppBundleMetadata;\n };\n\n private readChecksums = (\n archive: Record<string, Uint8Array>,\n ): AppBundleChecksums => {\n const raw = this.readJsonEntry<Partial<AppBundleChecksums>>(\n archive,\n CHECKSUMS_PATH,\n \"bundle checksums\",\n );\n if (raw.algorithm !== \"sha256\" || !this.isRecord(raw.files)) {\n throw new Error(\"bundle checksums 无效。\");\n }\n return raw as AppBundleChecksums;\n };\n\n private verifyChecksums = async (\n archive: Record<string, Uint8Array>,\n checksums: AppBundleChecksums,\n ): Promise<void> => {\n const actualPaths = Object.keys(archive)\n .filter((entry) => entry !== CHECKSUMS_PATH)\n .sort((left, right) => left.localeCompare(right));\n const checksumPaths = Object.keys(checksums.files)\n .map((entry) => this.normalizeEntry(entry))\n .sort((left, right) => left.localeCompare(right));\n if (\n actualPaths.length !== checksumPaths.length ||\n actualPaths.some((entry, index) => entry !== checksumPaths[index])\n ) {\n throw new Error(\"bundle checksums 必须精确覆盖所有 artifact 文件。\");\n }\n for (const relativePath of actualPaths) {\n const expectedHash = checksums.files[relativePath];\n if (!expectedHash || !/^[a-f0-9]{64}$/.test(expectedHash)) {\n throw new Error(`bundle checksum 格式无效:${relativePath}`);\n }\n const actualHash = await this.sha256(archive[relativePath] as Uint8Array);\n if (actualHash !== expectedHash) {\n throw new Error(`bundle checksum 校验失败:${relativePath}`);\n }\n }\n };\n\n private assertManifestContract = (params: {\n archive: Record<string, Uint8Array>;\n manifest: Record<string, unknown>;\n metadata: AppBundleMetadata;\n }): void => {\n const { archive, manifest, metadata } = params;\n const identity = manifest as ArtifactManifestIdentity;\n if (\n identity.id !== metadata.appId ||\n identity.name !== metadata.name ||\n identity.version !== metadata.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (identity.schemaVersion === 2) {\n if (metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n if (\n !Array.isArray(identity.components) ||\n identity.components.length === 0\n ) {\n throw new Error(\"schema v2 manifest.components 必须是非空数组。\");\n }\n for (const [index, rawComponent] of identity.components.entries()) {\n if (!this.isRecord(rawComponent)) {\n throw new Error(`manifest.components[${index}] 必须是对象。`);\n }\n const kind = rawComponent.kind;\n const componentPath = rawComponent.path;\n if (\n (kind !== \"panel\" && kind !== \"service\") ||\n typeof componentPath !== \"string\"\n ) {\n throw new Error(`manifest.components[${index}] 无效。`);\n }\n const normalizedPath = this.normalizeEntry(componentPath);\n const manifestPath = `${normalizedPath}/${kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\"}`;\n if (!archive[manifestPath]) {\n throw new Error(`bundle 缺少组件 manifest:${manifestPath}`);\n }\n }\n if (!archive[\"marketplace.json\"]) {\n throw new Error(\"schema v2 bundle 缺少 marketplace.json。\");\n }\n } else if (identity.schemaVersion === 1) {\n this.assertStandaloneEntry(archive, identity.main, \"main.entry\");\n this.assertStandaloneEntry(archive, identity.ui, \"ui.entry\");\n } else {\n throw new Error(\"manifest.schemaVersion 必须是 1 或 2。\");\n }\n if (identity.icon !== undefined) {\n if (\n typeof identity.icon !== \"string\" ||\n !archive[this.normalizeEntry(identity.icon)]\n ) {\n throw new Error(\"bundle 缺少 manifest.icon 声明的文件。\");\n }\n }\n };\n\n private assertStandaloneEntry = (\n archive: Record<string, Uint8Array>,\n rawContainer: unknown,\n pathLabel: string,\n ): void => {\n if (\n !this.isRecord(rawContainer) ||\n typeof rawContainer.entry !== \"string\"\n ) {\n throw new Error(`manifest.${pathLabel} 无效。`);\n }\n const entry = this.normalizeEntry(rawContainer.entry);\n if (!archive[entry]) {\n throw new Error(`bundle 缺少 manifest.${pathLabel} 文件:${entry}`);\n }\n };\n\n private assertExpectedContract = (params: {\n archive: Record<string, Uint8Array>;\n expected: AppArtifactValidationExpectation;\n manifest: Record<string, unknown>;\n metadata: AppBundleMetadata;\n }): void => {\n const { archive, expected, manifest, metadata } = params;\n if (\n expected.appId !== metadata.appId ||\n expected.name !== metadata.name ||\n expected.version !== metadata.version ||\n expected.distributionMode !== metadata.distributionMode\n ) {\n throw new Error(\"artifact metadata 与 publish payload 不一致。\");\n }\n if (\n expected.target !== undefined &&\n (\n metadata.target === undefined ||\n this.platformTargetService.toTargetKey(expected.target) !==\n this.platformTargetService.toTargetKey(metadata.target)\n )\n ) {\n throw new Error(\"artifact target metadata 与 publish payload 不一致。\");\n }\n if (\n expected.manifest !== undefined &&\n !this.isJsonSubset(this.toJsonValue(expected.manifest), manifest)\n ) {\n throw new Error(\n \"artifact manifest.json 与 publish payload.manifest 不一致。\",\n );\n }\n if (\n expected.target?.kind === \"native\" &&\n manifest.schemaVersion === 2 &&\n Array.isArray(manifest.components)\n ) {\n for (const rawComponent of manifest.components) {\n if (!this.isRecord(rawComponent) || rawComponent.kind !== \"service\") {\n continue;\n }\n const componentPath = typeof rawComponent.path === \"string\"\n ? this.normalizeEntry(rawComponent.path)\n : undefined;\n if (!componentPath) {\n throw new Error(\"service component path 无效。\");\n }\n const serviceManifest = this.readJsonEntry<Record<string, unknown>>(\n archive,\n `${componentPath}/service-app.json`,\n `${componentPath}/service-app.json`,\n );\n this.serviceLaunchService.resolve(serviceManifest, expected.target);\n }\n }\n };\n\n private readJsonEntry = <T>(\n archive: Record<string, Uint8Array>,\n entryPath: string,\n label: string,\n ): T => {\n const bytes = archive[entryPath];\n if (!bytes) {\n throw new Error(`bundle 缺少 ${label}。`);\n }\n try {\n return JSON.parse(strFromU8(bytes)) as T;\n } catch (error) {\n throw new Error(\n `无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n\n private decodeUtf8 = (bytes: Uint8Array): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new Error(\"bundle 文件名不是有效 UTF-8。\");\n }\n };\n\n private sha256 = async (bytes: Uint8Array): Promise<string> => {\n const digest = await globalThis.crypto.subtle.digest(\n \"SHA-256\",\n new Uint8Array(bytes),\n );\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n };\n\n private toJsonValue = (value: unknown): unknown => {\n const serialized = JSON.stringify(value);\n return serialized === undefined\n ? null\n : (JSON.parse(serialized) as unknown);\n };\n\n private isJsonSubset = (expected: unknown, actual: unknown): boolean => {\n if (Array.isArray(expected)) {\n return (\n Array.isArray(actual) &&\n expected.length === actual.length &&\n expected.every((entry, index) =>\n this.isJsonSubset(entry, actual[index]),\n )\n );\n }\n if (this.isRecord(expected)) {\n return (\n this.isRecord(actual) &&\n Object.entries(expected).every(\n ([key, value]) =>\n Object.hasOwn(actual, key) && this.isJsonSubset(value, actual[key]),\n )\n );\n }\n return Object.is(expected, actual);\n };\n\n private isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":";;;;AAUA,MAAM,uBAAuB,KAAK,OAAO;AACzC,MAAM,yBAAyB,MAAM,OAAO;AAC5C,MAAM,iBAAiB,KAAK,OAAO;AACnC,MAAM,iBAAiB;AACvB,MAAM,wBAAwB;AAC9B,MAAM,iBAAiB;AACvB,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,6BAA6B;AACnC,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAC9B,MAAM,gCAAgC;AAmCtC,IAAa,+BAAb,MAA0C;CACxC,wBAAyC,IAAI,0BAA0B;CACvE,uBAAwC,IAAI,wBAC1C,KAAK,sBACN;CAED,WAAW,OAAO,WAG0B;EAC1C,MAAM,EAAE,OAAO,aAAa;AAC5B,MAAI,MAAM,eAAe,KAAK,MAAM,aAAa,qBAC/C,OAAM,IAAI,MACR,sBAAsB,qBAAqB,YAC5C;EAEH,MAAM,iBAAiB,MAAM,KAAK,OAAO,MAAM;EAC/C,MAAM,iBAAiB,KAAK,mBAAmB,MAAM;EACrD,MAAM,UAAU,KAAK,iBAAiB,UAAU,MAAM,CAAC;AACvD,OAAK,iCAAiC,gBAAgB,QAAQ;EAC9D,MAAM,WAAW,KAAK,aAAa,QAAQ;EAC3C,MAAM,YAAY,KAAK,cAAc,QAAQ;AAC7C,QAAM,KAAK,gBAAgB,SAAS,UAAU;EAC9C,MAAM,WAAW,KAAK,cACpB,SACA,qBACA,gBACD;AACD,OAAK,uBAAuB;GAAE;GAAS;GAAU;GAAU,CAAC;AAC5D,MAAI,SACF,MAAK,uBAAuB;GAAE;GAAS;GAAU;GAAU;GAAU,CAAC;AAExE,SAAO;GAAE;GAAS;GAAgB;GAAU;GAAW;GAAU;;CAGnE,sBAA8B,UAAyC;EACrE,MAAM,EAAE,wBAAwB,YAAY,YAAY,SACtD,KAAK,qBAAqB,MAAM;EAClC,MAAM,UAA6B,EAAE;EACrC,MAAM,wBAAQ,IAAI,KAAa;EAC/B,IAAI,SAAS;EACb,IAAI,yBAAyB;AAC7B,OAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,SAAS,GAAG;GAClD,MAAM,EAAE,OAAO,aAAa,KAAK,iBAAiB;IAChD;IACA;IACA;IACA;IACD,CAAC;AACF,OAAI,MAAM,IAAI,MAAM,KAAK,CACvB,OAAM,IAAI,MAAM,iBAAiB,MAAM,OAAO;AAEhD,SAAM,IAAI,MAAM,KAAK;AACrB,6BAA0B,MAAM;AAChC,OAAI,yBAAyB,uBAC3B,OAAM,IAAI,MACR,gBAAgB,uBAAuB,YACxC;AAEH,WAAQ,KAAK,MAAM;AACnB,YAAS;;AAEX,MAAI,WAAW,WACb,OAAM,IAAI,MAAM,mCAAmC;AAErD,SAAO;;CAGT,wBAAgC,UAAsB;EACpD,MAAM,OAAO,IAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,WAAW;EAC3E,MAAM,aAAa,KAAK,eAAe,KAAK;EAC5C,MAAM,aAAa,KAAK,UAAU,aAAa,GAAG,KAAK;EACvD,MAAM,uBAAuB,KAAK,UAAU,aAAa,GAAG,KAAK;EACjE,MAAM,mBAAmB,KAAK,UAAU,aAAa,GAAG,KAAK;EAC7D,MAAM,aAAa,KAAK,UAAU,aAAa,IAAI,KAAK;EACxD,MAAM,uBAAuB,KAAK,UAAU,aAAa,IAAI,KAAK;EAClE,MAAM,yBAAyB,KAAK,UAAU,aAAa,IAAI,KAAK;EACpE,MAAM,gBAAgB,KAAK,UAAU,aAAa,IAAI,KAAK;AAC3D,MACE,eAAe,KACf,yBAAyB,KACzB,qBAAqB,WAErB,OAAM,IAAI,MAAM,qBAAqB;AAEvC,MACE,eAAe,SACf,yBAAyB,cACzB,2BAA2B,WAE3B,OAAM,IAAI,MAAM,oBAAoB;AAEtC,MACE,eAAe,KACf,aAAa,kBACb,aAAa,qBAAqB,kBAAkB,MAAM,cAC1D,yBAAyB,yBAAyB,WAElD,OAAM,IAAI,MAAM,+BAA+B;AAEjD,SAAO;GAAE;GAAwB;GAAY;GAAY;GAAM;;CAGjE,oBAA4B,WAKwB;EAClD,MAAM,EAAE,OAAO,QAAQ,YAAY,SAAS;AAC5C,MACE,SAAS,gCAAgC,cACzC,KAAK,UAAU,QAAQ,KAAK,KAAK,2BAEjC,OAAM,IAAI,MAAM,qCAAqC;EAEvD,MAAM,gBAAgB,KAAK,UAAU,SAAS,GAAG,KAAK;EACtD,MAAM,QAAQ,KAAK,UAAU,SAAS,GAAG,KAAK;EAC9C,MAAM,oBAAoB,KAAK,UAAU,SAAS,IAAI,KAAK;EAC3D,MAAM,iBAAiB,KAAK,UAAU,SAAS,IAAI,KAAK;EACxD,MAAM,mBAAmB,KAAK,UAAU,SAAS,IAAI,KAAK;EAC1D,MAAM,aAAa,KAAK,UAAU,SAAS,IAAI,KAAK;EACpD,MAAM,cAAc,KAAK,UAAU,SAAS,IAAI,KAAK;EACrD,MAAM,qBAAqB,KAAK,UAAU,SAAS,IAAI,KAAK;EAC5D,MAAM,qBAAqB,KAAK,UAAU,SAAS,IAAI,KAAK;EAC5D,MAAM,WACJ,SACA,gCACA,aACA,cACA;AACF,MAAI,WAAW,cAAc,eAAe,EAC1C,OAAM,IAAI,MAAM,uCAAuC;AAEzD,OAAK,QAAQ,OAAS,MAAM,QAAQ,QAAU,EAC5C,OAAM,IAAI,MAAM,kBAAkB;AAEpC,MAAI,sBAAsB,KAAK,sBAAsB,EACnD,OAAM,IAAI,MAAM,kBAAkB,kBAAkB,GAAG;EAEzD,MAAM,UAAU,MAAM,SACpB,SAAS,+BACT,SAAS,gCAAgC,WAC1C;EACD,MAAM,OAAO,KAAK,eAAe,KAAK,WAAW,QAAQ,CAAC;AAC1D,OAAK,kBAAkB;GACrB;GACA,YAAY,kBAAkB;GAC9B;GACD,CAAC;AACF,MAAI,mBAAmB,eACrB,OAAM,IAAI,MACR,gBAAgB,eAAe,YAAY,OAC5C;AAEH,MACE,mBAAmB,OAAO,SACzB,mBAAmB,KAClB,mBAAmB,iBAAiB,uBAEtC,OAAM,IAAI,MACR,kBAAkB,sBAAsB,QAAQ,OACjD;AAEH,SAAO;GAAE,OAAO;IAAE;IAAM;IAAkB;GAAE;GAAU;;CAGxD,kBAA0B,SAA2B;EACnD,MAAM,gBAAgB,KAAK,IACzB,GACA,KAAK,aAAa,qBAAqB,sBACxC;AACD,OACE,IAAI,SAAS,KAAK,aAAa,oBAC/B,UAAU,eACV,UAAU,EAEV,KAAI,KAAK,UAAU,QAAQ,KAAK,KAAK,mBACnC,QAAO;AAGX,QAAM,IAAI,MAAM,4CAA4C;;CAG9D,qBAA6B,WAIjB;EACV,MAAM,EAAE,oBAAoB,YAAY,SAAS;AACjD,MAAI,KAAK,SAAS,IAAI,CACpB,OAAM,IAAI,MAAM,sBAAsB,OAAO;AAE/C,MAAI,eAAe,GAAG;GAEpB,MAAM,WADW,uBAAuB,KACZ;AAC5B,OAAI,aAAa,KAAK,aAAa,MACjC,OAAM,IAAI,MAAM,kBAAkB,OAAO;AAE3C;;AAEF,OAAK,qBAAqB,QAAU,EAClC,OAAM,IAAI,MAAM,kBAAkB,OAAO;;CAI7C,oBACE,YAC+B;EAC/B,MAAM,aAAyC,EAAE;AACjD,OAAK,MAAM,CAAC,SAAS,cAAc,OAAO,QAAQ,QAAQ,EAAE;GAC1D,MAAM,OAAO,KAAK,eAAe,QAAQ;AACzC,OAAI,OAAO,OAAO,YAAY,KAAK,CACjC,OAAM,IAAI,MAAM,iBAAiB,OAAO;AAE1C,cAAW,QAAQ;;AAErB,SAAO;;CAGT,kBAA0B,YAA4B;EACpD,MAAM,aAAa,QAAQ,QAAQ,OAAO,IAAI;EAC9C,MAAM,WAAW,WAAW,MAAM,IAAI;AACtC,MACE,CAAC,cACD,WAAW,WAAW,IAAI,IAC1B,WAAW,SAAS,KAAK,IACzB,aAAa,KAAK,WAAW,IAC7B,SAAS,MACN,YAAY,CAAC,WAAW,YAAY,OAAO,YAAY,KACzD,CAED,OAAM,IAAI,MAAM,kBAAkB,UAAU;AAE9C,SAAO,SAAS,KAAK,IAAI;;CAG3B,oCACE,SACA,YACS;EACT,MAAM,eAAe,QAClB,KAAK,UAAU,MAAM,KAAK,CAC1B,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;EACnD,MAAM,eAAe,OAAO,KAAK,QAAQ,CAAC,MAAM,MAAM,UACpD,KAAK,cAAc,MAAM,CAC1B;AACD,MACE,aAAa,WAAW,aAAa,UACrC,aAAa,MAAM,OAAO,UAAU,UAAU,aAAa,OAAO,CAElE,OAAM,IAAI,MAAM,sCAAsC;AAExD,OAAK,MAAM,SAAS,QAClB,KAAI,QAAQ,MAAM,OAAO,eAAe,MAAM,iBAC5C,OAAM,IAAI,MAAM,qBAAqB,MAAM,OAAO;;CAKxD,gBACE,YACsB;EACtB,MAAM,MAAM,KAAK,cACf,SACA,sBACA,kBACD;EACD,MAAM,mBAAmB,IAAI,oBAAoB;AACjD,MACE,IAAI,wBAAwB,KAC3B,qBAAqB,YAAY,qBAAqB,YACvD,OAAO,IAAI,UAAU,YACrB,CAAC,IAAI,SACL,OAAO,IAAI,SAAS,YACpB,CAAC,IAAI,QACL,OAAO,IAAI,YAAY,YACvB,CAAC,IAAI,WACL,IAAI,kBAAkB,uBACtB,IAAI,kBAAkB,eAEtB,OAAM,IAAI,MAAM,sBAAsB;AAExC,SAAO;GACL,GAAG;GACH;GACA,QAAQ,IAAI,WAAW,KAAA,IACnB,KAAA,IACA,KAAK,sBAAsB,oBACzB,IAAI,QACJ,yBACD;GACN;;CAGH,iBACE,YACuB;EACvB,MAAM,MAAM,KAAK,cACf,SACA,gBACA,mBACD;AACD,MAAI,IAAI,cAAc,YAAY,CAAC,KAAK,SAAS,IAAI,MAAM,CACzD,OAAM,IAAI,MAAM,uBAAuB;AAEzC,SAAO;;CAGT,kBAA0B,OACxB,SACA,cACkB;EAClB,MAAM,cAAc,OAAO,KAAK,QAAQ,CACrC,QAAQ,UAAU,UAAU,eAAe,CAC3C,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;EACnD,MAAM,gBAAgB,OAAO,KAAK,UAAU,MAAM,CAC/C,KAAK,UAAU,KAAK,eAAe,MAAM,CAAC,CAC1C,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;AACnD,MACE,YAAY,WAAW,cAAc,UACrC,YAAY,MAAM,OAAO,UAAU,UAAU,cAAc,OAAO,CAElE,OAAM,IAAI,MAAM,yCAAyC;AAE3D,OAAK,MAAM,gBAAgB,aAAa;GACtC,MAAM,eAAe,UAAU,MAAM;AACrC,OAAI,CAAC,gBAAgB,CAAC,iBAAiB,KAAK,aAAa,CACvD,OAAM,IAAI,MAAM,wBAAwB,eAAe;AAGzD,OADmB,MAAM,KAAK,OAAO,QAAQ,cAA4B,KACtD,aACjB,OAAM,IAAI,MAAM,wBAAwB,eAAe;;;CAK7D,0BAAkC,WAItB;EACV,MAAM,EAAE,SAAS,UAAU,aAAa;EACxC,MAAM,WAAW;AACjB,MACE,SAAS,OAAO,SAAS,SACzB,SAAS,SAAS,SAAS,QAC3B,SAAS,YAAY,SAAS,QAE9B,OAAM,IAAI,MAAM,yCAAyC;AAE3D,MAAI,SAAS,kBAAkB,GAAG;AAChC,OAAI,SAAS,qBAAqB,SAChC,OAAM,IAAI,MAAM,8BAA8B;AAEhD,OACE,CAAC,MAAM,QAAQ,SAAS,WAAW,IACnC,SAAS,WAAW,WAAW,EAE/B,OAAM,IAAI,MAAM,yCAAyC;AAE3D,QAAK,MAAM,CAAC,OAAO,iBAAiB,SAAS,WAAW,SAAS,EAAE;AACjE,QAAI,CAAC,KAAK,SAAS,aAAa,CAC9B,OAAM,IAAI,MAAM,uBAAuB,MAAM,UAAU;IAEzD,MAAM,OAAO,aAAa;IAC1B,MAAM,gBAAgB,aAAa;AACnC,QACG,SAAS,WAAW,SAAS,aAC9B,OAAO,kBAAkB,SAEzB,OAAM,IAAI,MAAM,uBAAuB,MAAM,OAAO;IAGtD,MAAM,eAAe,GADE,KAAK,eAAe,cAAc,CAClB,GAAG,SAAS,UAAU,mBAAmB;AAChF,QAAI,CAAC,QAAQ,cACX,OAAM,IAAI,MAAM,wBAAwB,eAAe;;AAG3D,OAAI,CAAC,QAAQ,oBACX,OAAM,IAAI,MAAM,wCAAwC;aAEjD,SAAS,kBAAkB,GAAG;AACvC,QAAK,sBAAsB,SAAS,SAAS,MAAM,aAAa;AAChE,QAAK,sBAAsB,SAAS,SAAS,IAAI,WAAW;QAE5D,OAAM,IAAI,MAAM,oCAAoC;AAEtD,MAAI,SAAS,SAAS,KAAA;OAElB,OAAO,SAAS,SAAS,YACzB,CAAC,QAAQ,KAAK,eAAe,SAAS,KAAK,EAE3C,OAAM,IAAI,MAAM,iCAAiC;;;CAKvD,yBACE,SACA,cACA,cACS;AACT,MACE,CAAC,KAAK,SAAS,aAAa,IAC5B,OAAO,aAAa,UAAU,SAE9B,OAAM,IAAI,MAAM,YAAY,UAAU,MAAM;EAE9C,MAAM,QAAQ,KAAK,eAAe,aAAa,MAAM;AACrD,MAAI,CAAC,QAAQ,OACX,OAAM,IAAI,MAAM,sBAAsB,UAAU,MAAM,QAAQ;;CAIlE,0BAAkC,WAKtB;EACV,MAAM,EAAE,SAAS,UAAU,UAAU,aAAa;AAClD,MACE,SAAS,UAAU,SAAS,SAC5B,SAAS,SAAS,SAAS,QAC3B,SAAS,YAAY,SAAS,WAC9B,SAAS,qBAAqB,SAAS,iBAEvC,OAAM,IAAI,MAAM,2CAA2C;AAE7D,MACE,SAAS,WAAW,KAAA,MAElB,SAAS,WAAW,KAAA,KACpB,KAAK,sBAAsB,YAAY,SAAS,OAAO,KACrD,KAAK,sBAAsB,YAAY,SAAS,OAAO,EAG3D,OAAM,IAAI,MAAM,kDAAkD;AAEpE,MACE,SAAS,aAAa,KAAA,KACtB,CAAC,KAAK,aAAa,KAAK,YAAY,SAAS,SAAS,EAAE,SAAS,CAEjE,OAAM,IAAI,MACR,yDACD;AAEH,MACE,SAAS,QAAQ,SAAS,YAC1B,SAAS,kBAAkB,KAC3B,MAAM,QAAQ,SAAS,WAAW,CAElC,MAAK,MAAM,gBAAgB,SAAS,YAAY;AAC9C,OAAI,CAAC,KAAK,SAAS,aAAa,IAAI,aAAa,SAAS,UACxD;GAEF,MAAM,gBAAgB,OAAO,aAAa,SAAS,WAC/C,KAAK,eAAe,aAAa,KAAK,GACtC,KAAA;AACJ,OAAI,CAAC,cACH,OAAM,IAAI,MAAM,6BAA6B;GAE/C,MAAM,kBAAkB,KAAK,cAC3B,SACA,GAAG,cAAc,oBACjB,GAAG,cAAc,mBAClB;AACD,QAAK,qBAAqB,QAAQ,iBAAiB,SAAS,OAAO;;;CAKzE,iBACE,SACA,WACA,UACM;EACN,MAAM,QAAQ,QAAQ;AACtB,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,aAAa,MAAM,GAAG;AAExC,MAAI;AACF,UAAO,KAAK,MAAM,UAAU,MAAM,CAAC;WAC5B,OAAO;AACd,SAAM,IAAI,MACR,QAAQ,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACxE;;;CAIL,cAAsB,UAA8B;AAClD,MAAI;AACF,UAAO,IAAI,YAAY,SAAS,EAAE,OAAO,MAAM,CAAC,CAAC,OAAO,MAAM;UACxD;AACN,SAAM,IAAI,MAAM,wBAAwB;;;CAI5C,SAAiB,OAAO,UAAuC;EAC7D,MAAM,SAAS,MAAM,WAAW,OAAO,OAAO,OAC5C,WACA,IAAI,WAAW,MAAM,CACtB;AACD,SAAO,CAAC,GAAG,IAAI,WAAW,OAAO,CAAC,CAC/B,KAAK,SAAS,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CACjD,KAAK,GAAG;;CAGb,eAAuB,UAA4B;EACjD,MAAM,aAAa,KAAK,UAAU,MAAM;AACxC,SAAO,eAAe,KAAA,IAClB,OACC,KAAK,MAAM,WAAW;;CAG7B,gBAAwB,UAAmB,WAA6B;AACtE,MAAI,MAAM,QAAQ,SAAS,CACzB,QACE,MAAM,QAAQ,OAAO,IACrB,SAAS,WAAW,OAAO,UAC3B,SAAS,OAAO,OAAO,UACrB,KAAK,aAAa,OAAO,OAAO,OAAO,CACxC;AAGL,MAAI,KAAK,SAAS,SAAS,CACzB,QACE,KAAK,SAAS,OAAO,IACrB,OAAO,QAAQ,SAAS,CAAC,OACtB,CAAC,KAAK,WACL,OAAO,OAAO,QAAQ,IAAI,IAAI,KAAK,aAAa,OAAO,OAAO,KAAK,CACtE;AAGL,SAAO,OAAO,GAAG,UAAU,OAAO;;CAGpC,YAAoB,UAClB,QAAQ,MAAM,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM"}
@@ -1,3 +1,4 @@
1
+ import { AppArtifactTarget } from "../types/app-manifest.types.js";
1
2
  import { AppBundleExtractResult, AppBundlePackResult, AppDistributionMode } from "../types/app-bundle.types.js";
2
3
  import { AppArtifactValidationService } from "./app-artifact-validation.service.js";
3
4
  import { AppManifestService } from "./app-manifest.service.js";
@@ -6,11 +7,13 @@ import { AppManifestService } from "./app-manifest.service.js";
6
7
  declare class AppBundleService {
7
8
  private readonly manifestService;
8
9
  private readonly artifactValidationService;
10
+ private readonly platformTargetService;
9
11
  constructor(manifestService?: AppManifestService, artifactValidationService?: AppArtifactValidationService);
10
12
  packAppDirectory: (params: {
11
13
  appDirectory: string;
12
14
  outputPath?: string;
13
15
  mode?: AppDistributionMode;
16
+ target?: AppArtifactTarget;
14
17
  }) => Promise<AppBundlePackResult>;
15
18
  extractBundle: (params: {
16
19
  bundlePath: string;
@@ -30,6 +33,7 @@ declare class AppBundleService {
30
33
  private shouldExcludeSourcePath;
31
34
  private assertFileBudgets;
32
35
  private buildMetadata;
36
+ private assertTargetAllowed;
33
37
  private buildChecksums;
34
38
  private computeSha256;
35
39
  private normalizeBundleFileName;
@@ -1 +1 @@
1
- {"version":3,"file":"app-bundle.service.d.ts","names":[],"sources":["../../src/services/app-bundle.service.ts"],"mappings":";;;;;cA0Ba,gBAAA;EAAA,iBAEQ,eAAA;EAAA,iBACA,yBAAA;cADA,eAAA,GAAiB,kBAAA,EACjB,yBAAA,GAA2B,4BAAA;EAG9C,gBAAA,GAA0B,MAAA;IACxB,YAAA;IACA,UAAA;IACA,IAAA,GAAO,mBAAA;EAAA,MACL,OAAA,CAAQ,mBAAA;EAkDZ,aAAA,GAAuB,MAAA;IACrB,UAAA;IACA,eAAA;EAAA,MACE,OAAA,CAAQ,sBAAA;EAAA,QAQJ,oBAAA;EAAA,QAeA,wBAAA;EAAA,QA+CA,mBAAA;EAAA,QAWA,uBAAA;EAAA,QAiBA,mBAAA;EAAA,QAuBA,kBAAA;EAAA,QAeA,YAAA;EAAA,QAYA,qBAAA;EAAA,QAgCA,2BAAA;EAAA,QAuBA,eAAA;EAAA,QAiBA,wBAAA;EAAA,QAaA,uBAAA;EAAA,QAOA,iBAAA;EAAA,QAiBA,aAAA;EAAA,QAeA,cAAA;EAAA,QASA,aAAA;EAAA,QAGA,uBAAA;EAAA,QAGA,kBAAA;EAAA,QAIA,UAAA;AAAA"}
1
+ {"version":3,"file":"app-bundle.service.d.ts","names":[],"sources":["../../src/services/app-bundle.service.ts"],"mappings":";;;;;;cA4Ba,gBAAA;EAAA,iBAIQ,eAAA;EAAA,iBACA,yBAAA;EAAA,iBAJF,qBAAA;cAGE,eAAA,GAAiB,kBAAA,EACjB,yBAAA,GAA2B,4BAAA;EAG9C,gBAAA,GAA0B,MAAA;IACxB,YAAA;IACA,UAAA;IACA,IAAA,GAAO,mBAAA;IACP,MAAA,GAAS,iBAAA;EAAA,MACP,OAAA,CAAQ,mBAAA;EAsDZ,aAAA,GAAuB,MAAA;IACrB,UAAA;IACA,eAAA;EAAA,MACE,OAAA,CAAQ,sBAAA;EAAA,QAQJ,oBAAA;EAAA,QAeA,wBAAA;EAAA,QA+CA,mBAAA;EAAA,QAWA,uBAAA;EAAA,QAiBA,mBAAA;EAAA,QAuBA,kBAAA;EAAA,QAeA,YAAA;EAAA,QAYA,qBAAA;EAAA,QAgCA,2BAAA;EAAA,QAuBA,eAAA;EAAA,QAiBA,wBAAA;EAAA,QAaA,uBAAA;EAAA,QAOA,iBAAA;EAAA,QAiBA,aAAA;EAAA,QAiBA,mBAAA;EAAA,QAmCA,cAAA;EAAA,QASA,aAAA;EAAA,QAGA,uBAAA;EAAA,QAGA,kBAAA;EAAA,QAIA,UAAA;AAAA"}
@@ -1,3 +1,4 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
1
2
  import { AppArtifactValidationService } from "./app-artifact-validation.service.js";
2
3
  import { AppManifestService } from "./app-manifest.service.js";
3
4
  import { isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
@@ -13,18 +14,20 @@ const MAX_FILE_COUNT = 2e3;
13
14
  const CHECKSUMS_PATH = ".napp/checksums.json";
14
15
  const BUNDLE_METADATA_PATH = ".napp/bundle.json";
15
16
  var AppBundleService = class {
17
+ platformTargetService = new AppPlatformTargetService();
16
18
  constructor(manifestService = new AppManifestService(), artifactValidationService = new AppArtifactValidationService()) {
17
19
  this.manifestService = manifestService;
18
20
  this.artifactValidationService = artifactValidationService;
19
21
  }
20
22
  packAppDirectory = async (params) => {
21
- const { appDirectory, outputPath, mode: requestedMode } = params;
23
+ const { appDirectory, outputPath, mode: requestedMode, target } = params;
22
24
  const bundle = await this.manifestService.load(appDirectory);
23
25
  const mode = requestedMode ?? "bundle";
24
26
  if (isAppComponentManifestBundle(bundle) && mode !== "bundle") throw new Error("schema v2 组合包只支持 bundle 分发,不允许运行安装脚本。");
27
+ this.assertTargetAllowed(bundle, target);
25
28
  const { appFiles, filePaths } = mode === "source" ? await this.collectSourceFiles(bundle) : await this.collectRuntimeFiles(bundle);
26
29
  this.assertFileBudgets(appFiles);
27
- const metadata = this.buildMetadata(bundle.manifest.id, bundle.manifest.name, bundle.manifest.version, mode);
30
+ const metadata = this.buildMetadata(bundle.manifest.id, bundle.manifest.name, bundle.manifest.version, mode, target);
28
31
  const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\n`);
29
32
  const checksums = this.buildChecksums({
30
33
  ...appFiles,
@@ -37,7 +40,7 @@ var AppBundleService = class {
37
40
  [CHECKSUMS_PATH]: checksumsJsonBytes
38
41
  }, { level: 9 });
39
42
  if (archiveBytes.byteLength > MAX_COMPRESSED_BYTES) throw new Error(`bundle 压缩后超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);
40
- const resolvedOutputPath = outputPath ? path.resolve(outputPath) : path.join(path.dirname(bundle.appDirectory), `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}.napp`);
43
+ const resolvedOutputPath = outputPath ? path.resolve(outputPath) : path.join(path.dirname(bundle.appDirectory), `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}${target ? `-${this.platformTargetService.toTargetKey(target)}` : ""}.napp`);
41
44
  await mkdir(path.dirname(resolvedOutputPath), { recursive: true });
42
45
  await writeFile(resolvedOutputPath, Buffer.from(archiveBytes));
43
46
  return {
@@ -214,15 +217,34 @@ var AppBundleService = class {
214
217
  }
215
218
  if (totalBytes > MAX_UNCOMPRESSED_BYTES) throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);
216
219
  };
217
- buildMetadata = (appId, name, version, distributionMode) => ({
220
+ buildMetadata = (appId, name, version, distributionMode, target) => ({
218
221
  bundleFormatVersion: 1,
219
222
  distributionMode,
220
223
  appId,
221
224
  name,
222
225
  version,
226
+ target,
223
227
  entryManifest: "manifest.json",
224
228
  checksumsFile: CHECKSUMS_PATH
225
229
  });
230
+ assertTargetAllowed = (bundle, target) => {
231
+ if (!isAppComponentManifestBundle(bundle)) {
232
+ if (target && target.kind !== "universal") throw new Error("schema v1 artifact 只支持 universal target。");
233
+ return;
234
+ }
235
+ const distribution = this.platformTargetService.resolveDistribution(bundle.manifest.distribution);
236
+ if (!target) {
237
+ if (distribution.mode === "targeted") throw new Error("targeted App 打包时必须指定 artifact target。");
238
+ return;
239
+ }
240
+ if (distribution.mode === "universal") {
241
+ if (target.kind !== "universal") throw new Error("universal App 不能打包为 native target artifact。");
242
+ return;
243
+ }
244
+ if (target.kind === "universal") throw new Error("targeted App 不能打包为 universal artifact。");
245
+ const targetKey = this.platformTargetService.toTargetKey(target);
246
+ if (!distribution.targets.some((entry) => this.platformTargetService.toTargetKey(entry) === targetKey)) throw new Error(`artifact target 未在 manifest distribution 中声明:${targetKey}`);
247
+ };
226
248
  buildChecksums = (files) => ({
227
249
  algorithm: "sha256",
228
250
  files: Object.fromEntries(Object.entries(files).sort(([left], [right]) => left.localeCompare(right)).map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]))
@@ -1 +1 @@
1
- {"version":3,"file":"app-bundle.service.js","names":[],"sources":["../../src/services/app-bundle.service.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { strToU8, zipSync } from \"fflate\";\nimport { AppArtifactValidationService } from \"#app-runtime/services/app-artifact-validation.service.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n type AppManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppBundleChecksums,\n AppBundleExtractResult,\n AppBundleMetadata,\n AppBundlePackResult,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\n\nexport class AppBundleService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly artifactValidationService: AppArtifactValidationService = new AppArtifactValidationService(),\n ) {}\n\n packAppDirectory = async (params: {\n appDirectory: string;\n outputPath?: string;\n mode?: AppDistributionMode;\n }): Promise<AppBundlePackResult> => {\n const { appDirectory, outputPath, mode: requestedMode } = params;\n const bundle = await this.manifestService.load(appDirectory);\n const mode = requestedMode ?? \"bundle\";\n if (isAppComponentManifestBundle(bundle) && mode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发,不允许运行安装脚本。\");\n }\n const { appFiles, filePaths } = mode === \"source\"\n ? await this.collectSourceFiles(bundle)\n : await this.collectRuntimeFiles(bundle);\n this.assertFileBudgets(appFiles);\n const metadata = this.buildMetadata(\n bundle.manifest.id,\n bundle.manifest.name,\n bundle.manifest.version,\n mode,\n );\n const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\\n`);\n const checksums = this.buildChecksums({\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n });\n const checksumsJsonBytes = strToU8(`${JSON.stringify(checksums, null, 2)}\\n`);\n const archiveBytes = zipSync(\n {\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n [CHECKSUMS_PATH]: checksumsJsonBytes,\n },\n { level: 9 },\n );\n if (archiveBytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩后超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const resolvedOutputPath = outputPath\n ? path.resolve(outputPath)\n : path.join(\n path.dirname(bundle.appDirectory),\n `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}.napp`,\n );\n await mkdir(path.dirname(resolvedOutputPath), { recursive: true });\n await writeFile(resolvedOutputPath, Buffer.from(archiveBytes));\n return {\n bundlePath: resolvedOutputPath,\n metadata,\n sizeBytes: archiveBytes.byteLength,\n filePaths,\n };\n };\n\n extractBundle = async (params: {\n bundlePath: string;\n targetDirectory: string;\n }): Promise<AppBundleExtractResult> => {\n const bundlePath = path.resolve(params.bundlePath);\n const targetDirectory = path.resolve(params.targetDirectory);\n const { archive, metadata, checksums } = await this.readValidatedArchive(bundlePath);\n await this.replaceTargetWithArchive({ archive, metadata, targetDirectory });\n return { appDirectory: targetDirectory, metadata, checksums };\n };\n\n private readValidatedArchive = async (bundlePath: string): Promise<{\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n }> => {\n const bundleStats = await stat(bundlePath);\n if (!bundleStats.isFile() || bundleStats.size > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩体积超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const { archive, metadata, checksums } = await this.artifactValidationService.validate({\n bytes: new Uint8Array(await readFile(bundlePath)),\n });\n return { archive, metadata, checksums };\n };\n\n private replaceTargetWithArchive = async (params: {\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n targetDirectory: string;\n }): Promise<void> => {\n const { archive, metadata, targetDirectory } = params;\n const targetParent = path.dirname(targetDirectory);\n const operationId = randomUUID();\n const stagedDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.extracting-${operationId}`,\n );\n const backupDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.backup-${operationId}`,\n );\n const targetExists = await this.pathExists(targetDirectory);\n await mkdir(targetParent, { recursive: true });\n try {\n await mkdir(stagedDirectory);\n await this.writeArchiveEntries(archive, stagedDirectory);\n await this.assertExtractedManifest(stagedDirectory, metadata);\n if (targetExists) {\n await rename(targetDirectory, backupDirectory);\n }\n await rename(stagedDirectory, targetDirectory);\n await rm(backupDirectory, { recursive: true, force: true });\n } catch (error) {\n await rm(stagedDirectory, { recursive: true, force: true });\n if (targetExists && await this.pathExists(backupDirectory)) {\n await rm(targetDirectory, { recursive: true, force: true });\n try {\n await rename(backupDirectory, targetDirectory);\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `bundle 解压失败,且无法恢复原目标目录:${targetDirectory}`,\n );\n }\n }\n throw error;\n } finally {\n await rm(stagedDirectory, { recursive: true, force: true });\n await rm(backupDirectory, { recursive: true, force: true });\n }\n };\n\n private writeArchiveEntries = async (\n archive: Record<string, Uint8Array>,\n targetDirectory: string,\n ): Promise<void> => {\n for (const [entryName, bytes] of Object.entries(archive)) {\n const targetPath = path.join(targetDirectory, entryName);\n await mkdir(path.dirname(targetPath), { recursive: true });\n await writeFile(targetPath, Buffer.from(bytes));\n }\n };\n\n private assertExtractedManifest = async (\n appDirectory: string,\n metadata: AppBundleMetadata,\n ): Promise<void> => {\n const manifestBundle = await this.manifestService.load(appDirectory);\n if (\n metadata.appId !== manifestBundle.manifest.id ||\n metadata.name !== manifestBundle.manifest.name ||\n metadata.version !== manifestBundle.manifest.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (manifestBundle.manifest.schemaVersion === 2 && metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n };\n\n private collectRuntimeFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const filePaths = new Set<string>([\n path.relative(bundle.appDirectory, bundle.manifestPath).replace(/\\\\/g, \"/\"),\n ]);\n if (isAppStandaloneManifestBundle(bundle)) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.mainEntryPath).replace(/\\\\/g, \"/\"));\n await this.collectDirectoryPaths(bundle.uiDirectoryPath, bundle.appDirectory, filePaths);\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n } else {\n for (const component of bundle.components) {\n await this.collectDirectoryPaths(component.componentDirectory, bundle.appDirectory, filePaths);\n }\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n await this.addOptionalFile(bundle.appDirectory, \"marketplace.json\", filePaths);\n }\n if (bundle.iconPath) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.iconPath).replace(/\\\\/g, \"/\"));\n }\n return await this.readAppFiles(bundle.appDirectory, filePaths);\n };\n\n private collectSourceFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n if (!isAppStandaloneManifestBundle(bundle)) {\n throw new Error(\"schema v2 组合包不支持 source 分发。\");\n }\n const filePaths = new Set<string>();\n await this.collectSourceDirectoryPaths(bundle.appDirectory, bundle.appDirectory, filePaths);\n const result = await this.readAppFiles(bundle.appDirectory, filePaths);\n if (bundle.manifest.main.kind === \"wasi-http-component\") {\n result.appFiles[bundle.manifest.main.entry] = SOURCE_WASM_PLACEHOLDER_BYTES;\n }\n return result;\n };\n\n private readAppFiles = async (\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));\n const appFiles: Record<string, Uint8Array> = {};\n for (const relativePath of sortedPaths) {\n appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));\n }\n return { appFiles, filePaths: sortedPaths };\n };\n\n private collectDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeRuntimePath(relativePath)) {\n await this.collectDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n continue;\n }\n if (entry.isFile() && !this.shouldExcludeRuntimePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return;\n }\n throw error;\n }\n };\n\n private collectSourceDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeSourcePath(relativePath)) {\n await this.collectSourceDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n } else if (entry.isFile() && !this.shouldExcludeSourcePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n };\n\n private addOptionalFile = async (\n appDirectory: string,\n relativePath: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const stats = await lstat(path.join(appDirectory, relativePath));\n if (stats.isFile() && !stats.isSymbolicLink()) {\n filePaths.add(relativePath);\n }\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private shouldExcludeRuntimePath = (relativePath: string): boolean => {\n const segments = relativePath.split(\"/\");\n return segments.some((segment) =>\n segment === \"node_modules\" ||\n segment === \".git\" ||\n segment === \"marketplace-assets\" ||\n segment === \"coverage\" ||\n segment === \"tests\" ||\n segment === \"__tests__\" ||\n segment === \"fixtures\"\n ) || /(?:^|\\/)\\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(\".map\");\n };\n\n private shouldExcludeSourcePath = (relativePath: string): boolean => {\n return this.shouldExcludeRuntimePath(relativePath) ||\n relativePath === \".napp\" || relativePath.startsWith(\".napp/\") ||\n relativePath === \"main/dist\" || relativePath.startsWith(\"main/dist/\") ||\n relativePath === \"main/generated\" || relativePath.startsWith(\"main/generated/\");\n };\n\n private assertFileBudgets = (files: Record<string, Uint8Array>): void => {\n const entries = Object.entries(files);\n if (entries.length + 2 > MAX_FILE_COUNT) {\n throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);\n }\n let totalBytes = 0;\n for (const [relativePath, bytes] of entries) {\n if (bytes.byteLength > MAX_FILE_BYTES) {\n throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${relativePath}`);\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);\n }\n };\n\n private buildMetadata = (\n appId: string,\n name: string,\n version: string,\n distributionMode: AppDistributionMode,\n ): AppBundleMetadata => ({\n bundleFormatVersion: 1,\n distributionMode,\n appId,\n name,\n version,\n entryManifest: \"manifest.json\",\n checksumsFile: CHECKSUMS_PATH,\n });\n\n private buildChecksums = (files: Record<string, Uint8Array>): AppBundleChecksums => ({\n algorithm: \"sha256\",\n files: Object.fromEntries(\n Object.entries(files)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]),\n ),\n });\n\n private computeSha256 = (fileBytes: Uint8Array): string =>\n createHash(\"sha256\").update(Buffer.from(fileBytes)).digest(\"hex\");\n\n private normalizeBundleFileName = (appId: string): string =>\n appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\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 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\nconst SOURCE_WASM_PLACEHOLDER_BYTES = Uint8Array.from(\n Buffer.from(\n \"AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL\",\n \"base64\",\n ),\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-bundle.service.js","names":[],"sources":["../../src/services/app-bundle.service.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { strToU8, zipSync } from \"fflate\";\nimport { AppArtifactValidationService } from \"#app-runtime/services/app-artifact-validation.service.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { AppPlatformTargetService } from \"#app-runtime/services/app-platform-target.service.js\";\nimport {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n type AppArtifactTarget,\n type AppManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppBundleChecksums,\n AppBundleExtractResult,\n AppBundleMetadata,\n AppBundlePackResult,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\n\nexport class AppBundleService {\n private readonly platformTargetService = new AppPlatformTargetService();\n\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly artifactValidationService: AppArtifactValidationService = new AppArtifactValidationService(),\n ) {}\n\n packAppDirectory = async (params: {\n appDirectory: string;\n outputPath?: string;\n mode?: AppDistributionMode;\n target?: AppArtifactTarget;\n }): Promise<AppBundlePackResult> => {\n const { appDirectory, outputPath, mode: requestedMode, target } = params;\n const bundle = await this.manifestService.load(appDirectory);\n const mode = requestedMode ?? \"bundle\";\n if (isAppComponentManifestBundle(bundle) && mode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发,不允许运行安装脚本。\");\n }\n this.assertTargetAllowed(bundle, target);\n const { appFiles, filePaths } = mode === \"source\"\n ? await this.collectSourceFiles(bundle)\n : await this.collectRuntimeFiles(bundle);\n this.assertFileBudgets(appFiles);\n const metadata = this.buildMetadata(\n bundle.manifest.id,\n bundle.manifest.name,\n bundle.manifest.version,\n mode,\n target,\n );\n const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\\n`);\n const checksums = this.buildChecksums({\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n });\n const checksumsJsonBytes = strToU8(`${JSON.stringify(checksums, null, 2)}\\n`);\n const archiveBytes = zipSync(\n {\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n [CHECKSUMS_PATH]: checksumsJsonBytes,\n },\n { level: 9 },\n );\n if (archiveBytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩后超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const resolvedOutputPath = outputPath\n ? path.resolve(outputPath)\n : path.join(\n path.dirname(bundle.appDirectory),\n `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}${\n target ? `-${this.platformTargetService.toTargetKey(target)}` : \"\"\n }.napp`,\n );\n await mkdir(path.dirname(resolvedOutputPath), { recursive: true });\n await writeFile(resolvedOutputPath, Buffer.from(archiveBytes));\n return {\n bundlePath: resolvedOutputPath,\n metadata,\n sizeBytes: archiveBytes.byteLength,\n filePaths,\n };\n };\n\n extractBundle = async (params: {\n bundlePath: string;\n targetDirectory: string;\n }): Promise<AppBundleExtractResult> => {\n const bundlePath = path.resolve(params.bundlePath);\n const targetDirectory = path.resolve(params.targetDirectory);\n const { archive, metadata, checksums } = await this.readValidatedArchive(bundlePath);\n await this.replaceTargetWithArchive({ archive, metadata, targetDirectory });\n return { appDirectory: targetDirectory, metadata, checksums };\n };\n\n private readValidatedArchive = async (bundlePath: string): Promise<{\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n }> => {\n const bundleStats = await stat(bundlePath);\n if (!bundleStats.isFile() || bundleStats.size > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩体积超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const { archive, metadata, checksums } = await this.artifactValidationService.validate({\n bytes: new Uint8Array(await readFile(bundlePath)),\n });\n return { archive, metadata, checksums };\n };\n\n private replaceTargetWithArchive = async (params: {\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n targetDirectory: string;\n }): Promise<void> => {\n const { archive, metadata, targetDirectory } = params;\n const targetParent = path.dirname(targetDirectory);\n const operationId = randomUUID();\n const stagedDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.extracting-${operationId}`,\n );\n const backupDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.backup-${operationId}`,\n );\n const targetExists = await this.pathExists(targetDirectory);\n await mkdir(targetParent, { recursive: true });\n try {\n await mkdir(stagedDirectory);\n await this.writeArchiveEntries(archive, stagedDirectory);\n await this.assertExtractedManifest(stagedDirectory, metadata);\n if (targetExists) {\n await rename(targetDirectory, backupDirectory);\n }\n await rename(stagedDirectory, targetDirectory);\n await rm(backupDirectory, { recursive: true, force: true });\n } catch (error) {\n await rm(stagedDirectory, { recursive: true, force: true });\n if (targetExists && await this.pathExists(backupDirectory)) {\n await rm(targetDirectory, { recursive: true, force: true });\n try {\n await rename(backupDirectory, targetDirectory);\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `bundle 解压失败,且无法恢复原目标目录:${targetDirectory}`,\n );\n }\n }\n throw error;\n } finally {\n await rm(stagedDirectory, { recursive: true, force: true });\n await rm(backupDirectory, { recursive: true, force: true });\n }\n };\n\n private writeArchiveEntries = async (\n archive: Record<string, Uint8Array>,\n targetDirectory: string,\n ): Promise<void> => {\n for (const [entryName, bytes] of Object.entries(archive)) {\n const targetPath = path.join(targetDirectory, entryName);\n await mkdir(path.dirname(targetPath), { recursive: true });\n await writeFile(targetPath, Buffer.from(bytes));\n }\n };\n\n private assertExtractedManifest = async (\n appDirectory: string,\n metadata: AppBundleMetadata,\n ): Promise<void> => {\n const manifestBundle = await this.manifestService.load(appDirectory);\n if (\n metadata.appId !== manifestBundle.manifest.id ||\n metadata.name !== manifestBundle.manifest.name ||\n metadata.version !== manifestBundle.manifest.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (manifestBundle.manifest.schemaVersion === 2 && metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n };\n\n private collectRuntimeFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const filePaths = new Set<string>([\n path.relative(bundle.appDirectory, bundle.manifestPath).replace(/\\\\/g, \"/\"),\n ]);\n if (isAppStandaloneManifestBundle(bundle)) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.mainEntryPath).replace(/\\\\/g, \"/\"));\n await this.collectDirectoryPaths(bundle.uiDirectoryPath, bundle.appDirectory, filePaths);\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n } else {\n for (const component of bundle.components) {\n await this.collectDirectoryPaths(component.componentDirectory, bundle.appDirectory, filePaths);\n }\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n await this.addOptionalFile(bundle.appDirectory, \"marketplace.json\", filePaths);\n }\n if (bundle.iconPath) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.iconPath).replace(/\\\\/g, \"/\"));\n }\n return await this.readAppFiles(bundle.appDirectory, filePaths);\n };\n\n private collectSourceFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n if (!isAppStandaloneManifestBundle(bundle)) {\n throw new Error(\"schema v2 组合包不支持 source 分发。\");\n }\n const filePaths = new Set<string>();\n await this.collectSourceDirectoryPaths(bundle.appDirectory, bundle.appDirectory, filePaths);\n const result = await this.readAppFiles(bundle.appDirectory, filePaths);\n if (bundle.manifest.main.kind === \"wasi-http-component\") {\n result.appFiles[bundle.manifest.main.entry] = SOURCE_WASM_PLACEHOLDER_BYTES;\n }\n return result;\n };\n\n private readAppFiles = async (\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));\n const appFiles: Record<string, Uint8Array> = {};\n for (const relativePath of sortedPaths) {\n appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));\n }\n return { appFiles, filePaths: sortedPaths };\n };\n\n private collectDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeRuntimePath(relativePath)) {\n await this.collectDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n continue;\n }\n if (entry.isFile() && !this.shouldExcludeRuntimePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return;\n }\n throw error;\n }\n };\n\n private collectSourceDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeSourcePath(relativePath)) {\n await this.collectSourceDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n } else if (entry.isFile() && !this.shouldExcludeSourcePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n };\n\n private addOptionalFile = async (\n appDirectory: string,\n relativePath: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const stats = await lstat(path.join(appDirectory, relativePath));\n if (stats.isFile() && !stats.isSymbolicLink()) {\n filePaths.add(relativePath);\n }\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private shouldExcludeRuntimePath = (relativePath: string): boolean => {\n const segments = relativePath.split(\"/\");\n return segments.some((segment) =>\n segment === \"node_modules\" ||\n segment === \".git\" ||\n segment === \"marketplace-assets\" ||\n segment === \"coverage\" ||\n segment === \"tests\" ||\n segment === \"__tests__\" ||\n segment === \"fixtures\"\n ) || /(?:^|\\/)\\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(\".map\");\n };\n\n private shouldExcludeSourcePath = (relativePath: string): boolean => {\n return this.shouldExcludeRuntimePath(relativePath) ||\n relativePath === \".napp\" || relativePath.startsWith(\".napp/\") ||\n relativePath === \"main/dist\" || relativePath.startsWith(\"main/dist/\") ||\n relativePath === \"main/generated\" || relativePath.startsWith(\"main/generated/\");\n };\n\n private assertFileBudgets = (files: Record<string, Uint8Array>): void => {\n const entries = Object.entries(files);\n if (entries.length + 2 > MAX_FILE_COUNT) {\n throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);\n }\n let totalBytes = 0;\n for (const [relativePath, bytes] of entries) {\n if (bytes.byteLength > MAX_FILE_BYTES) {\n throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${relativePath}`);\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);\n }\n };\n\n private buildMetadata = (\n appId: string,\n name: string,\n version: string,\n distributionMode: AppDistributionMode,\n target?: AppArtifactTarget,\n ): AppBundleMetadata => ({\n bundleFormatVersion: 1,\n distributionMode,\n appId,\n name,\n version,\n target,\n entryManifest: \"manifest.json\",\n checksumsFile: CHECKSUMS_PATH,\n });\n\n private assertTargetAllowed = (\n bundle: AppManifestBundle,\n target: AppArtifactTarget | undefined,\n ): void => {\n if (!isAppComponentManifestBundle(bundle)) {\n if (target && target.kind !== \"universal\") {\n throw new Error(\"schema v1 artifact 只支持 universal target。\");\n }\n return;\n }\n const distribution = this.platformTargetService.resolveDistribution(\n bundle.manifest.distribution,\n );\n if (!target) {\n if (distribution.mode === \"targeted\") {\n throw new Error(\"targeted App 打包时必须指定 artifact target。\");\n }\n return;\n }\n if (distribution.mode === \"universal\") {\n if (target.kind !== \"universal\") {\n throw new Error(\"universal App 不能打包为 native target artifact。\");\n }\n return;\n }\n if (target.kind === \"universal\") {\n throw new Error(\"targeted App 不能打包为 universal artifact。\");\n }\n const targetKey = this.platformTargetService.toTargetKey(target);\n if (!distribution.targets.some((entry) =>\n this.platformTargetService.toTargetKey(entry) === targetKey)) {\n throw new Error(`artifact target 未在 manifest distribution 中声明:${targetKey}`);\n }\n };\n\n private buildChecksums = (files: Record<string, Uint8Array>): AppBundleChecksums => ({\n algorithm: \"sha256\",\n files: Object.fromEntries(\n Object.entries(files)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]),\n ),\n });\n\n private computeSha256 = (fileBytes: Uint8Array): string =>\n createHash(\"sha256\").update(Buffer.from(fileBytes)).digest(\"hex\");\n\n private normalizeBundleFileName = (appId: string): string =>\n appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\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 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\nconst SOURCE_WASM_PLACEHOLDER_BYTES = Uint8Array.from(\n Buffer.from(\n \"AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL\",\n \"base64\",\n ),\n);\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import { isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
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);