@nextclaw/app-runtime 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/controllers/publish.controller.js +7 -0
  2. package/dist/controllers/publish.controller.js.map +1 -1
  3. package/dist/index.d.ts +5 -4
  4. package/dist/index.js +2 -1
  5. package/dist/package.js +1 -1
  6. package/dist/services/app-artifact-validation.service.d.ts +46 -0
  7. package/dist/services/app-artifact-validation.service.d.ts.map +1 -0
  8. package/dist/services/app-artifact-validation.service.js +250 -0
  9. package/dist/services/app-artifact-validation.service.js.map +1 -0
  10. package/dist/services/app-bundle.service.d.ts +4 -8
  11. package/dist/services/app-bundle.service.d.ts.map +1 -1
  12. package/dist/services/app-bundle.service.js +7 -66
  13. package/dist/services/app-bundle.service.js.map +1 -1
  14. package/dist/services/app-home.service.d.ts +1 -0
  15. package/dist/services/app-home.service.d.ts.map +1 -1
  16. package/dist/services/app-home.service.js +3 -0
  17. package/dist/services/app-home.service.js.map +1 -1
  18. package/dist/services/app-installation.service.d.ts +7 -1
  19. package/dist/services/app-installation.service.d.ts.map +1 -1
  20. package/dist/services/app-installation.service.js +84 -5
  21. package/dist/services/app-installation.service.js.map +1 -1
  22. package/dist/services/app-marketplace-metadata.service.d.ts +5 -1
  23. package/dist/services/app-marketplace-metadata.service.d.ts.map +1 -1
  24. package/dist/services/app-marketplace-metadata.service.js +44 -2
  25. package/dist/services/app-marketplace-metadata.service.js.map +1 -1
  26. package/dist/services/app-publish-validation.service.d.ts +1 -1
  27. package/dist/services/app-publish.service.d.ts +2 -1
  28. package/dist/services/app-publish.service.d.ts.map +1 -1
  29. package/dist/services/app-publish.service.js +6 -2
  30. package/dist/services/app-publish.service.js.map +1 -1
  31. package/dist/services/app-registry.service.d.ts +2 -0
  32. package/dist/services/app-registry.service.d.ts.map +1 -1
  33. package/dist/services/app-registry.service.js +20 -2
  34. package/dist/services/app-registry.service.js.map +1 -1
  35. package/dist/types/app-installation.types.d.ts +4 -2
  36. package/dist/types/app-installation.types.d.ts.map +1 -1
  37. package/dist/types/app-publish.types.d.ts +11 -2
  38. package/dist/types/app-publish.types.d.ts.map +1 -1
  39. package/dist/types/app-publish.types.js.map +1 -1
  40. package/dist/types/app-registry.types.d.ts +4 -1
  41. package/dist/types/app-registry.types.d.ts.map +1 -1
  42. package/dist/types/app-remote-registry.types.d.ts +1 -1
  43. package/package.json +6 -1
@@ -20,6 +20,13 @@ var PublishCommand = class {
20
20
  }, null, 2)}\n`);
21
21
  return;
22
22
  }
23
+ if (result.item.publishStatus === "pending") {
24
+ write(`Submitted ${result.item.name} (${result.item.appId}) ${result.item.latestVersion} for review.\n`);
25
+ write("Status: pending\n");
26
+ write("The app will appear in the App Marketplace after approval.\n");
27
+ write("Manage submissions: https://platform.nextclaw.io/apps\n");
28
+ return;
29
+ }
23
30
  write(`${result.created ? "Published" : "Updated"} ${result.item.name} (${result.item.appId}) ${result.item.latestVersion}\n`);
24
31
  write(`Distribution: ${result.distribution.path} (${result.distribution.mode})\n`);
25
32
  if (result.item.webUrl) write(`Details: ${result.item.webUrl}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"publish.controller.js","names":[],"sources":["../../src/controllers/publish.controller.ts"],"sourcesContent":["import { AppPublishService } from \"#app-runtime/services/app-publish.service.js\";\n\nexport class PublishCommand {\n constructor(\n private readonly publishService: AppPublishService = new AppPublishService(),\n ) {}\n\n run = async (params: {\n appDirectory: string;\n metadataPath?: string;\n apiBaseUrl?: string;\n token?: string;\n mode?: \"source\" | \"bundle\";\n json: boolean;\n write: (text: string) => void;\n }): Promise<void> => {\n const { appDirectory, metadataPath, apiBaseUrl, token, mode, json, write } = params;\n const result = await this.publishService.publish({\n appDirectory,\n metadataPath,\n apiBaseUrl,\n token,\n mode,\n });\n if (json) {\n write(`${JSON.stringify({ ok: true, publish: result }, null, 2)}\\n`);\n return;\n }\n write(\n `${result.created ? \"Published\" : \"Updated\"} ${result.item.name} (${result.item.appId}) ${result.item.latestVersion}\\n`,\n );\n write(`Distribution: ${result.distribution.path} (${result.distribution.mode})\\n`);\n if (result.item.webUrl) {\n write(`Details: ${result.item.webUrl}\\n`);\n }\n write(`Install: ${result.item.install.command}\\n`);\n };\n}\n"],"mappings":";;AAEA,IAAa,iBAAb,MAA4B;CAC1B,YACE,iBAAqD,IAAI,mBAAmB,EAC5E;AADiB,OAAA,iBAAA;;CAGnB,MAAM,OAAO,WAQQ;EACnB,MAAM,EAAE,cAAc,cAAc,YAAY,OAAO,MAAM,MAAM,UAAU;EAC7E,MAAM,SAAS,MAAM,KAAK,eAAe,QAAQ;GAC/C;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,MAAI,MAAM;AACR,SAAM,GAAG,KAAK,UAAU;IAAE,IAAI;IAAM,SAAS;IAAQ,EAAE,MAAM,EAAE,CAAC,IAAI;AACpE;;AAEF,QACE,GAAG,OAAO,UAAU,cAAc,UAAU,GAAG,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,cAAc,IACrH;AACD,QAAM,iBAAiB,OAAO,aAAa,KAAK,IAAI,OAAO,aAAa,KAAK,KAAK;AAClF,MAAI,OAAO,KAAK,OACd,OAAM,YAAY,OAAO,KAAK,OAAO,IAAI;AAE3C,QAAM,YAAY,OAAO,KAAK,QAAQ,QAAQ,IAAI"}
1
+ {"version":3,"file":"publish.controller.js","names":[],"sources":["../../src/controllers/publish.controller.ts"],"sourcesContent":["import { AppPublishService } from \"#app-runtime/services/app-publish.service.js\";\n\nexport class PublishCommand {\n constructor(\n private readonly publishService: AppPublishService = new AppPublishService(),\n ) {}\n\n run = async (params: {\n appDirectory: string;\n metadataPath?: string;\n apiBaseUrl?: string;\n token?: string;\n mode?: \"source\" | \"bundle\";\n json: boolean;\n write: (text: string) => void;\n }): Promise<void> => {\n const { appDirectory, metadataPath, apiBaseUrl, token, mode, json, write } = params;\n const result = await this.publishService.publish({\n appDirectory,\n metadataPath,\n apiBaseUrl,\n token,\n mode,\n });\n if (json) {\n write(`${JSON.stringify({ ok: true, publish: result }, null, 2)}\\n`);\n return;\n }\n if (result.item.publishStatus === \"pending\") {\n write(\n `Submitted ${result.item.name} (${result.item.appId}) ${result.item.latestVersion} for review.\\n`,\n );\n write(\"Status: pending\\n\");\n write(\"The app will appear in the App Marketplace after approval.\\n\");\n write(\"Manage submissions: https://platform.nextclaw.io/apps\\n\");\n return;\n }\n write(\n `${result.created ? \"Published\" : \"Updated\"} ${result.item.name} (${result.item.appId}) ${result.item.latestVersion}\\n`,\n );\n write(`Distribution: ${result.distribution.path} (${result.distribution.mode})\\n`);\n if (result.item.webUrl) {\n write(`Details: ${result.item.webUrl}\\n`);\n }\n write(`Install: ${result.item.install.command}\\n`);\n };\n}\n"],"mappings":";;AAEA,IAAa,iBAAb,MAA4B;CAC1B,YACE,iBAAqD,IAAI,mBAAmB,EAC5E;AADiB,OAAA,iBAAA;;CAGnB,MAAM,OAAO,WAQQ;EACnB,MAAM,EAAE,cAAc,cAAc,YAAY,OAAO,MAAM,MAAM,UAAU;EAC7E,MAAM,SAAS,MAAM,KAAK,eAAe,QAAQ;GAC/C;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,MAAI,MAAM;AACR,SAAM,GAAG,KAAK,UAAU;IAAE,IAAI;IAAM,SAAS;IAAQ,EAAE,MAAM,EAAE,CAAC,IAAI;AACpE;;AAEF,MAAI,OAAO,KAAK,kBAAkB,WAAW;AAC3C,SACE,aAAa,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,cAAc,gBACnF;AACD,SAAM,oBAAoB;AAC1B,SAAM,+DAA+D;AACrE,SAAM,0DAA0D;AAChE;;AAEF,QACE,GAAG,OAAO,UAAU,cAAc,UAAU,GAAG,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,cAAc,IACrH;AACD,QAAM,iBAAiB,OAAO,aAAa,KAAK,IAAI,OAAO,aAAa,KAAK,KAAK;AAClF,MAAI,OAAO,KAAK,OACd,OAAM,YAAY,OAAO,KAAK,OAAO,IAAI;AAE3C,QAAM,YAAY,OAAO,KAAK,QAAQ,QAAQ,IAAI"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ import { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppDistributionMode } from "./types/app-bundle.types.js";
2
+ import { AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService } from "./services/app-artifact-validation.service.js";
1
3
  import { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppResolvedComponent, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
2
4
  import { AppManifestService } from "./services/app-manifest.service.js";
3
- import { AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppDistributionMode } from "./types/app-bundle.types.js";
4
5
  import { AppBundleService } from "./services/app-bundle.service.js";
5
6
  import { AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppInstalledPermissionState, AppPermissionSummary, ResolvedDocumentGrant, ResolvedPermissions } from "./types/app-permissions.types.js";
6
7
  import { AppPermissionsService } from "./services/app-permissions.service.js";
@@ -26,14 +27,14 @@ import { AppRuntimeCommandResult, AppRuntimeDoctorResult, AppRuntimeToolStatus,
26
27
  import { AppBuildResult, AppBuildService } from "./services/app-build.service.js";
27
28
  import { AppRegistryConfigService } from "./services/app-registry-config.service.js";
28
29
  import { AppRemoteRegistryClientService } from "./services/app-remote-registry-client.service.js";
29
- import { AppActivationResult, AppInfoResult, AppInstallResult, AppLaunchResolution, AppRollbackResult, AppUninstallResult, AppUpdateResult, InstalledAppListItem } from "./types/app-installation.types.js";
30
+ import { AppActivationResult, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppLaunchResolution, AppRollbackResult, AppUninstallResult, AppUpdateResult, InstalledAppListItem } from "./types/app-installation.types.js";
30
31
  import { AppInstallationService } from "./services/app-installation.service.js";
31
32
  import { InfoCommand } from "./controllers/info.controller.js";
32
33
  import { InstallCommand } from "./controllers/install.controller.js";
33
34
  import { ListCommand } from "./controllers/list.controller.js";
34
35
  import { PackCommand } from "./controllers/pack.controller.js";
35
36
  import { PermissionsCommand } from "./controllers/permissions.controller.js";
36
- import { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./types/app-publish.types.js";
37
+ import { AppMarketplaceMetadata, AppMarketplaceVisuals, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE } from "./types/app-publish.types.js";
37
38
  import { AppMarketplaceClientService } from "./services/app-marketplace-client.service.js";
38
39
  import { AppMarketplaceMetadataService } from "./services/app-marketplace-metadata.service.js";
39
40
  import { PlatformAuthStateService, PlatformPublishAuthState } from "./services/platform-auth-state.service.js";
@@ -48,4 +49,4 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
48
49
  import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./services/wasmtime-wasi-http-component.service.js";
49
50
  import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/app-host.service.js";
50
51
  import { UiServerService } from "./services/ui-server.service.js";
51
- export { AppActivationResult, 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, AppInstallResult, AppInstallSourceKind, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppPermissionSummary, AppPermissions, AppPermissionsService, 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, AppRuntimeDoctorResult, AppRuntimeOptionsService, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, 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 };
52
+ export { 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, AppInstallationService, AppInstalledPermissionState, AppInstanceService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, 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, AppRuntimeDoctorResult, AppRuntimeOptionsService, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, 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,4 @@
1
+ import { AppArtifactValidationService } from "./services/app-artifact-validation.service.js";
1
2
  import { AppManifestService } from "./services/app-manifest.service.js";
2
3
  import { isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "./types/app-manifest.types.js";
3
4
  import { AppBundleService } from "./services/app-bundle.service.js";
@@ -43,4 +44,4 @@ import { AppPublishValidationService } from "./services/app-publish-validation.s
43
44
  import { ValidatePublishCommand } from "./controllers/validate-publish.controller.js";
44
45
  import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
45
46
  import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
46
- export { AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
47
+ export { AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationService, AppInstanceService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, 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.9.14";
2
+ var version = "0.10.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -0,0 +1,46 @@
1
+ import { AppBundleChecksums, AppBundleMetadata, AppDistributionMode } from "../types/app-bundle.types.js";
2
+
3
+ //#region src/services/app-artifact-validation.service.d.ts
4
+ type AppArtifactValidationExpectation = {
5
+ appId: string;
6
+ name: string;
7
+ version: string;
8
+ distributionMode: AppDistributionMode;
9
+ manifest?: unknown;
10
+ };
11
+ type AppArtifactValidationResult = {
12
+ archive: Record<string, Uint8Array>;
13
+ artifactSha256: string;
14
+ metadata: AppBundleMetadata;
15
+ checksums: AppBundleChecksums;
16
+ manifest: Record<string, unknown>;
17
+ };
18
+ declare class AppArtifactValidationService {
19
+ validate: (params: {
20
+ bytes: Uint8Array;
21
+ expected?: AppArtifactValidationExpectation;
22
+ }) => Promise<AppArtifactValidationResult>;
23
+ private readCentralEntries;
24
+ private readCentralDirectory;
25
+ private readCentralEntry;
26
+ private findEocdOffset;
27
+ private assertRegularFile;
28
+ private normalizeArchive;
29
+ private normalizeEntry;
30
+ private assertCentralEntriesMatchArchive;
31
+ private readMetadata;
32
+ private readChecksums;
33
+ private verifyChecksums;
34
+ private assertManifestContract;
35
+ private assertStandaloneEntry;
36
+ private assertExpectedContract;
37
+ private readJsonEntry;
38
+ private decodeUtf8;
39
+ private sha256;
40
+ private toJsonValue;
41
+ private isJsonSubset;
42
+ private isRecord;
43
+ }
44
+ //#endregion
45
+ export { AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService };
46
+ //# sourceMappingURL=app-artifact-validation.service.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,250 @@
1
+ import { strFromU8, unzipSync } from "fflate";
2
+ //#region src/services/app-artifact-validation.service.ts
3
+ const MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;
4
+ const MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
5
+ const MAX_FILE_BYTES = 25 * 1024 * 1024;
6
+ const MAX_FILE_COUNT = 2e3;
7
+ const MAX_COMPRESSION_RATIO = 200;
8
+ const CHECKSUMS_PATH = ".napp/checksums.json";
9
+ const BUNDLE_METADATA_PATH = ".napp/bundle.json";
10
+ const ENTRY_MANIFEST_PATH = "manifest.json";
11
+ const ZIP_EOCD_SIGNATURE = 101010256;
12
+ const ZIP_CENTRAL_FILE_SIGNATURE = 33639248;
13
+ const ZIP_EOCD_MIN_BYTES = 22;
14
+ const ZIP_MAX_COMMENT_BYTES = 65535;
15
+ const ZIP_CENTRAL_FILE_HEADER_BYTES = 46;
16
+ var AppArtifactValidationService = class {
17
+ validate = async (params) => {
18
+ const { bytes, expected } = params;
19
+ if (bytes.byteLength === 0 || bytes.byteLength > MAX_COMPRESSED_BYTES) throw new Error(`bundle 压缩体积必须在 1 到 ${MAX_COMPRESSED_BYTES} bytes 之间。`);
20
+ const artifactSha256 = await this.sha256(bytes);
21
+ const centralEntries = this.readCentralEntries(bytes);
22
+ const archive = this.normalizeArchive(unzipSync(bytes));
23
+ this.assertCentralEntriesMatchArchive(centralEntries, archive);
24
+ const metadata = this.readMetadata(archive);
25
+ const checksums = this.readChecksums(archive);
26
+ await this.verifyChecksums(archive, checksums);
27
+ const manifest = this.readJsonEntry(archive, ENTRY_MANIFEST_PATH, "manifest.json");
28
+ this.assertManifestContract({
29
+ archive,
30
+ manifest,
31
+ metadata
32
+ });
33
+ if (expected) this.assertExpectedContract({
34
+ expected,
35
+ manifest,
36
+ metadata
37
+ });
38
+ return {
39
+ archive,
40
+ artifactSha256,
41
+ metadata,
42
+ checksums,
43
+ manifest
44
+ };
45
+ };
46
+ readCentralEntries = (bytes) => {
47
+ const { centralDirectoryOffset, entryCount, eocdOffset, view } = this.readCentralDirectory(bytes);
48
+ const entries = [];
49
+ const names = /* @__PURE__ */ new Set();
50
+ let cursor = centralDirectoryOffset;
51
+ let totalUncompressedBytes = 0;
52
+ for (let index = 0; index < entryCount; index += 1) {
53
+ const { entry, entryEnd } = this.readCentralEntry({
54
+ bytes,
55
+ cursor,
56
+ eocdOffset,
57
+ view
58
+ });
59
+ if (names.has(entry.name)) throw new Error(`bundle 包含重复路径:${entry.name}`);
60
+ names.add(entry.name);
61
+ totalUncompressedBytes += entry.uncompressedSize;
62
+ if (totalUncompressedBytes > MAX_UNCOMPRESSED_BYTES) throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);
63
+ entries.push(entry);
64
+ cursor = entryEnd;
65
+ }
66
+ if (cursor !== eocdOffset) throw new Error("bundle central directory 未被精确消费。");
67
+ return entries;
68
+ };
69
+ readCentralDirectory = (bytes) => {
70
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
71
+ const eocdOffset = this.findEocdOffset(view);
72
+ const diskNumber = view.getUint16(eocdOffset + 4, true);
73
+ const centralDirectoryDisk = view.getUint16(eocdOffset + 6, true);
74
+ const entryCountOnDisk = view.getUint16(eocdOffset + 8, true);
75
+ const entryCount = view.getUint16(eocdOffset + 10, true);
76
+ const centralDirectorySize = view.getUint32(eocdOffset + 12, true);
77
+ const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
78
+ const commentLength = view.getUint16(eocdOffset + 20, true);
79
+ if (diskNumber !== 0 || centralDirectoryDisk !== 0 || entryCountOnDisk !== entryCount) throw new Error("bundle 不支持跨磁盘 ZIP。");
80
+ if (entryCount === 65535 || centralDirectorySize === 4294967295 || centralDirectoryOffset === 4294967295) throw new Error("bundle 不支持 Zip64。");
81
+ if (entryCount === 0 || entryCount > MAX_FILE_COUNT || eocdOffset + ZIP_EOCD_MIN_BYTES + commentLength !== bytes.byteLength || centralDirectoryOffset + centralDirectorySize !== eocdOffset) throw new Error("bundle central directory 无效。");
82
+ return {
83
+ centralDirectoryOffset,
84
+ entryCount,
85
+ eocdOffset,
86
+ view
87
+ };
88
+ };
89
+ readCentralEntry = (params) => {
90
+ const { bytes, cursor, eocdOffset, view } = params;
91
+ if (cursor + ZIP_CENTRAL_FILE_HEADER_BYTES > eocdOffset || view.getUint32(cursor, true) !== ZIP_CENTRAL_FILE_SIGNATURE) throw new Error("bundle central directory entry 无效。");
92
+ const versionMadeBy = view.getUint16(cursor + 4, true);
93
+ const flags = view.getUint16(cursor + 8, true);
94
+ const compressionMethod = view.getUint16(cursor + 10, true);
95
+ const compressedSize = view.getUint32(cursor + 20, true);
96
+ const uncompressedSize = view.getUint32(cursor + 24, true);
97
+ const nameLength = view.getUint16(cursor + 28, true);
98
+ const extraLength = view.getUint16(cursor + 30, true);
99
+ const entryCommentLength = view.getUint16(cursor + 32, true);
100
+ const externalAttributes = view.getUint32(cursor + 38, true);
101
+ const entryEnd = cursor + ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength + extraLength + entryCommentLength;
102
+ if (entryEnd > eocdOffset || nameLength === 0) throw new Error("bundle central directory entry 长度无效。");
103
+ if ((flags & 1) !== 0 || (flags & 64) !== 0) throw new Error("bundle 不允许加密文件。");
104
+ if (compressionMethod !== 0 && compressionMethod !== 8) throw new Error(`bundle 不支持压缩方法 ${compressionMethod}。`);
105
+ const rawName = bytes.subarray(cursor + ZIP_CENTRAL_FILE_HEADER_BYTES, cursor + ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength);
106
+ const name = this.normalizeEntry(this.decodeUtf8(rawName));
107
+ this.assertRegularFile({
108
+ externalAttributes,
109
+ hostSystem: versionMadeBy >>> 8,
110
+ name
111
+ });
112
+ if (uncompressedSize > MAX_FILE_BYTES) throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${name}`);
113
+ if (uncompressedSize > 1024 * 1024 && (compressedSize === 0 || uncompressedSize / compressedSize > MAX_COMPRESSION_RATIO)) throw new Error(`bundle 文件压缩比超过 ${MAX_COMPRESSION_RATIO}:1 上限:${name}`);
114
+ return {
115
+ entry: {
116
+ name,
117
+ uncompressedSize
118
+ },
119
+ entryEnd
120
+ };
121
+ };
122
+ findEocdOffset = (view) => {
123
+ const minimumOffset = Math.max(0, view.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_MAX_COMMENT_BYTES);
124
+ for (let offset = view.byteLength - ZIP_EOCD_MIN_BYTES; offset >= minimumOffset; offset -= 1) if (view.getUint32(offset, true) === ZIP_EOCD_SIGNATURE) return offset;
125
+ throw new Error("bundle 缺少有效 ZIP end-of-central-directory。");
126
+ };
127
+ assertRegularFile = (params) => {
128
+ const { externalAttributes, hostSystem, name } = params;
129
+ if (name.endsWith("/")) throw new Error(`bundle 不允许目录 entry:${name}`);
130
+ if (hostSystem === 3) {
131
+ const fileType = externalAttributes >>> 16 & 61440;
132
+ if (fileType !== 0 && fileType !== 32768) throw new Error(`bundle 只允许普通文件:${name}`);
133
+ return;
134
+ }
135
+ if ((externalAttributes & 16) !== 0) throw new Error(`bundle 只允许普通文件:${name}`);
136
+ };
137
+ normalizeArchive = (archive) => {
138
+ const normalized = {};
139
+ for (const [rawName, fileBytes] of Object.entries(archive)) {
140
+ const name = this.normalizeEntry(rawName);
141
+ if (Object.hasOwn(normalized, name)) throw new Error(`bundle 包含重复路径:${name}`);
142
+ normalized[name] = fileBytes;
143
+ }
144
+ return normalized;
145
+ };
146
+ normalizeEntry = (rawName) => {
147
+ const normalized = rawName.replace(/\\/g, "/");
148
+ const segments = normalized.split("/");
149
+ if (!normalized || normalized.startsWith("/") || normalized.includes("\0") || /^[A-Za-z]:/.test(normalized) || segments.some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`bundle 内包含非法路径:${rawName}`);
150
+ return segments.join("/");
151
+ };
152
+ assertCentralEntriesMatchArchive = (entries, archive) => {
153
+ const centralPaths = entries.map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
154
+ const archivePaths = Object.keys(archive).sort((left, right) => left.localeCompare(right));
155
+ if (centralPaths.length !== archivePaths.length || centralPaths.some((entry, index) => entry !== archivePaths[index])) throw new Error("bundle 解压结果与 central directory 不一致。");
156
+ for (const entry of entries) if (archive[entry.name]?.byteLength !== entry.uncompressedSize) throw new Error(`bundle 解压后文件大小不一致:${entry.name}`);
157
+ };
158
+ readMetadata = (archive) => {
159
+ const raw = this.readJsonEntry(archive, BUNDLE_METADATA_PATH, "bundle metadata");
160
+ const distributionMode = raw.distributionMode ?? "bundle";
161
+ 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
+ return {
163
+ ...raw,
164
+ distributionMode
165
+ };
166
+ };
167
+ readChecksums = (archive) => {
168
+ const raw = this.readJsonEntry(archive, CHECKSUMS_PATH, "bundle checksums");
169
+ if (raw.algorithm !== "sha256" || !this.isRecord(raw.files)) throw new Error("bundle checksums 无效。");
170
+ return raw;
171
+ };
172
+ verifyChecksums = async (archive, checksums) => {
173
+ const actualPaths = Object.keys(archive).filter((entry) => entry !== CHECKSUMS_PATH).sort((left, right) => left.localeCompare(right));
174
+ const checksumPaths = Object.keys(checksums.files).map((entry) => this.normalizeEntry(entry)).sort((left, right) => left.localeCompare(right));
175
+ if (actualPaths.length !== checksumPaths.length || actualPaths.some((entry, index) => entry !== checksumPaths[index])) throw new Error("bundle checksums 必须精确覆盖所有 artifact 文件。");
176
+ for (const relativePath of actualPaths) {
177
+ const expectedHash = checksums.files[relativePath];
178
+ if (!expectedHash || !/^[a-f0-9]{64}$/.test(expectedHash)) throw new Error(`bundle checksum 格式无效:${relativePath}`);
179
+ if (await this.sha256(archive[relativePath]) !== expectedHash) throw new Error(`bundle checksum 校验失败:${relativePath}`);
180
+ }
181
+ };
182
+ assertManifestContract = (params) => {
183
+ const { archive, manifest, metadata } = params;
184
+ const identity = manifest;
185
+ if (identity.id !== metadata.appId || identity.name !== metadata.name || identity.version !== metadata.version) throw new Error("bundle metadata 与 manifest.json 身份不一致。");
186
+ if (identity.schemaVersion === 2) {
187
+ if (metadata.distributionMode !== "bundle") throw new Error("schema v2 组合包只支持 bundle 分发。");
188
+ if (!Array.isArray(identity.components) || identity.components.length === 0) throw new Error("schema v2 manifest.components 必须是非空数组。");
189
+ for (const [index, rawComponent] of identity.components.entries()) {
190
+ if (!this.isRecord(rawComponent)) throw new Error(`manifest.components[${index}] 必须是对象。`);
191
+ const kind = rawComponent.kind;
192
+ const componentPath = rawComponent.path;
193
+ if (kind !== "panel" && kind !== "service" || typeof componentPath !== "string") throw new Error(`manifest.components[${index}] 无效。`);
194
+ const manifestPath = `${this.normalizeEntry(componentPath)}/${kind === "panel" ? "panel-app.json" : "service-app.json"}`;
195
+ if (!archive[manifestPath]) throw new Error(`bundle 缺少组件 manifest:${manifestPath}`);
196
+ }
197
+ if (!archive["marketplace.json"]) throw new Error("schema v2 bundle 缺少 marketplace.json。");
198
+ } else if (identity.schemaVersion === 1) {
199
+ this.assertStandaloneEntry(archive, identity.main, "main.entry");
200
+ this.assertStandaloneEntry(archive, identity.ui, "ui.entry");
201
+ } else throw new Error("manifest.schemaVersion 必须是 1 或 2。");
202
+ if (identity.icon !== void 0) {
203
+ if (typeof identity.icon !== "string" || !archive[this.normalizeEntry(identity.icon)]) throw new Error("bundle 缺少 manifest.icon 声明的文件。");
204
+ }
205
+ };
206
+ assertStandaloneEntry = (archive, rawContainer, pathLabel) => {
207
+ if (!this.isRecord(rawContainer) || typeof rawContainer.entry !== "string") throw new Error(`manifest.${pathLabel} 无效。`);
208
+ const entry = this.normalizeEntry(rawContainer.entry);
209
+ if (!archive[entry]) throw new Error(`bundle 缺少 manifest.${pathLabel} 文件:${entry}`);
210
+ };
211
+ assertExpectedContract = (params) => {
212
+ const { expected, manifest, metadata } = params;
213
+ if (expected.appId !== metadata.appId || expected.name !== metadata.name || expected.version !== metadata.version || expected.distributionMode !== metadata.distributionMode) throw new Error("artifact metadata 与 publish payload 不一致。");
214
+ if (expected.manifest !== void 0 && !this.isJsonSubset(this.toJsonValue(expected.manifest), manifest)) throw new Error("artifact manifest.json 与 publish payload.manifest 不一致。");
215
+ };
216
+ readJsonEntry = (archive, entryPath, label) => {
217
+ const bytes = archive[entryPath];
218
+ if (!bytes) throw new Error(`bundle 缺少 ${label}。`);
219
+ try {
220
+ return JSON.parse(strFromU8(bytes));
221
+ } catch (error) {
222
+ throw new Error(`无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`);
223
+ }
224
+ };
225
+ decodeUtf8 = (bytes) => {
226
+ try {
227
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
228
+ } catch {
229
+ throw new Error("bundle 文件名不是有效 UTF-8。");
230
+ }
231
+ };
232
+ sha256 = async (bytes) => {
233
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
234
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
235
+ };
236
+ toJsonValue = (value) => {
237
+ const serialized = JSON.stringify(value);
238
+ return serialized === void 0 ? null : JSON.parse(serialized);
239
+ };
240
+ isJsonSubset = (expected, actual) => {
241
+ if (Array.isArray(expected)) return Array.isArray(actual) && expected.length === actual.length && expected.every((entry, index) => this.isJsonSubset(entry, actual[index]));
242
+ if (this.isRecord(expected)) return this.isRecord(actual) && Object.entries(expected).every(([key, value]) => Object.hasOwn(actual, key) && this.isJsonSubset(value, actual[key]));
243
+ return Object.is(expected, actual);
244
+ };
245
+ isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
246
+ };
247
+ //#endregion
248
+ export { AppArtifactValidationService };
249
+
250
+ //# sourceMappingURL=app-artifact-validation.service.js.map
@@ -0,0 +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,10 +1,12 @@
1
- import { AppManifestService } from "./app-manifest.service.js";
2
1
  import { AppBundleExtractResult, AppBundlePackResult, AppDistributionMode } from "../types/app-bundle.types.js";
2
+ import { AppArtifactValidationService } from "./app-artifact-validation.service.js";
3
+ import { AppManifestService } from "./app-manifest.service.js";
3
4
 
4
5
  //#region src/services/app-bundle.service.d.ts
5
6
  declare class AppBundleService {
6
7
  private readonly manifestService;
7
- constructor(manifestService?: AppManifestService);
8
+ private readonly artifactValidationService;
9
+ constructor(manifestService?: AppManifestService, artifactValidationService?: AppArtifactValidationService);
8
10
  packAppDirectory: (params: {
9
11
  appDirectory: string;
10
12
  outputPath?: string;
@@ -27,16 +29,10 @@ declare class AppBundleService {
27
29
  private shouldExcludeRuntimePath;
28
30
  private shouldExcludeSourcePath;
29
31
  private assertFileBudgets;
30
- private normalizeArchive;
31
- private readMetadataFromArchive;
32
- private readChecksumsFromArchive;
33
- private verifyArchiveChecksums;
34
- private readJsonArchiveEntry;
35
32
  private buildMetadata;
36
33
  private buildChecksums;
37
34
  private computeSha256;
38
35
  private normalizeBundleFileName;
39
- private normalizeArchiveEntry;
40
36
  private isMissingFileError;
41
37
  private pathExists;
42
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"app-bundle.service.d.ts","names":[],"sources":["../../src/services/app-bundle.service.ts"],"mappings":";;;;cAyBa,gBAAA;EAAA,iBAEQ,eAAA;cAAA,eAAA,GAAiB,kBAAA;EAGpC,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,QAkCA,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,QAYA,uBAAA;EAAA,QAOA,iBAAA;EAAA,QAiBA,gBAAA;EAAA,QAcA,uBAAA;EAAA,QA4BA,wBAAA;EAAA,QAcA,sBAAA;EAAA,QA4BA,oBAAA;EAAA,QAgBA,aAAA;EAAA,QAeA,cAAA;EAAA,QASA,aAAA;EAAA,QAGA,uBAAA;EAAA,QAGA,qBAAA;EAAA,QAaA,kBAAA;EAAA,QAIA,UAAA;AAAA"}
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,9 +1,10 @@
1
+ import { AppArtifactValidationService } from "./app-artifact-validation.service.js";
1
2
  import { AppManifestService } from "./app-manifest.service.js";
2
3
  import { isAppComponentManifestBundle, isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
3
4
  import { createHash, randomUUID } from "node:crypto";
4
5
  import { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
6
  import path from "node:path";
6
- import { strToU8, unzipSync, zipSync } from "fflate";
7
+ import { strToU8, zipSync } from "fflate";
7
8
  //#region src/services/app-bundle.service.ts
8
9
  const MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;
9
10
  const MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
@@ -12,8 +13,9 @@ const MAX_FILE_COUNT = 2e3;
12
13
  const CHECKSUMS_PATH = ".napp/checksums.json";
13
14
  const BUNDLE_METADATA_PATH = ".napp/bundle.json";
14
15
  var AppBundleService = class {
15
- constructor(manifestService = new AppManifestService()) {
16
+ constructor(manifestService = new AppManifestService(), artifactValidationService = new AppArtifactValidationService()) {
16
17
  this.manifestService = manifestService;
18
+ this.artifactValidationService = artifactValidationService;
17
19
  }
18
20
  packAppDirectory = async (params) => {
19
21
  const { appDirectory, outputPath, mode: requestedMode } = params;
@@ -63,22 +65,9 @@ var AppBundleService = class {
63
65
  readValidatedArchive = async (bundlePath) => {
64
66
  const bundleStats = await stat(bundlePath);
65
67
  if (!bundleStats.isFile() || bundleStats.size > MAX_COMPRESSED_BYTES) throw new Error(`bundle 压缩体积超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);
66
- let fileCount = 0;
67
- let totalBytes = 0;
68
- const archive = unzipSync(new Uint8Array(await readFile(bundlePath)), { filter: (file) => {
69
- fileCount += 1;
70
- totalBytes += file.originalSize;
71
- if (fileCount > MAX_FILE_COUNT) throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);
72
- if (file.originalSize > MAX_FILE_BYTES) throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${file.name}`);
73
- if (totalBytes > MAX_UNCOMPRESSED_BYTES) throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);
74
- return true;
75
- } });
76
- const normalizedArchive = this.normalizeArchive(archive);
77
- const metadata = this.readMetadataFromArchive(normalizedArchive);
78
- const checksums = this.readChecksumsFromArchive(normalizedArchive);
79
- this.verifyArchiveChecksums(normalizedArchive, checksums);
68
+ const { archive, metadata, checksums } = await this.artifactValidationService.validate({ bytes: new Uint8Array(await readFile(bundlePath)) });
80
69
  return {
81
- archive: normalizedArchive,
70
+ archive,
82
71
  metadata,
83
72
  checksums
84
73
  };
@@ -210,7 +199,7 @@ var AppBundleService = class {
210
199
  }
211
200
  };
212
201
  shouldExcludeRuntimePath = (relativePath) => {
213
- return relativePath.split("/").some((segment) => segment === "node_modules" || segment === ".git" || segment === "coverage" || segment === "tests" || segment === "__tests__" || segment === "fixtures") || /(?:^|\/)\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(".map");
202
+ return relativePath.split("/").some((segment) => segment === "node_modules" || segment === ".git" || segment === "marketplace-assets" || segment === "coverage" || segment === "tests" || segment === "__tests__" || segment === "fixtures") || /(?:^|\/)\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(".map");
214
203
  };
215
204
  shouldExcludeSourcePath = (relativePath) => {
216
205
  return this.shouldExcludeRuntimePath(relativePath) || relativePath === ".napp" || relativePath.startsWith(".napp/") || relativePath === "main/dist" || relativePath.startsWith("main/dist/") || relativePath === "main/generated" || relativePath.startsWith("main/generated/");
@@ -225,48 +214,6 @@ var AppBundleService = class {
225
214
  }
226
215
  if (totalBytes > MAX_UNCOMPRESSED_BYTES) throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);
227
216
  };
228
- normalizeArchive = (archive) => {
229
- const normalizedArchive = {};
230
- for (const [entryName, bytes] of Object.entries(archive)) {
231
- const normalizedEntry = this.normalizeArchiveEntry(entryName);
232
- if (Object.hasOwn(normalizedArchive, normalizedEntry)) throw new Error(`bundle 包含重复路径:${normalizedEntry}`);
233
- normalizedArchive[normalizedEntry] = bytes;
234
- }
235
- return normalizedArchive;
236
- };
237
- readMetadataFromArchive = (archive) => {
238
- const raw = this.readJsonArchiveEntry(archive, BUNDLE_METADATA_PATH, "bundle metadata");
239
- const distributionMode = raw.distributionMode === void 0 ? "bundle" : raw.distributionMode;
240
- 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 !== "manifest.json" || raw.checksumsFile !== CHECKSUMS_PATH) throw new Error("bundle metadata 无效。");
241
- return {
242
- ...raw,
243
- distributionMode
244
- };
245
- };
246
- readChecksumsFromArchive = (archive) => {
247
- const raw = this.readJsonArchiveEntry(archive, CHECKSUMS_PATH, "bundle checksums");
248
- if (raw.algorithm !== "sha256" || !raw.files || typeof raw.files !== "object") throw new Error("bundle checksums 无效。");
249
- return raw;
250
- };
251
- verifyArchiveChecksums = (archive, checksums) => {
252
- const actualPaths = Object.keys(archive).filter((entry) => entry !== CHECKSUMS_PATH).sort((left, right) => left.localeCompare(right));
253
- const checksumPaths = Object.keys(checksums.files).map((entry) => this.normalizeArchiveEntry(entry)).sort((left, right) => left.localeCompare(right));
254
- if (actualPaths.length !== checksumPaths.length || actualPaths.some((entry, index) => entry !== checksumPaths[index])) throw new Error("bundle checksums 必须精确覆盖所有 artifact 文件。");
255
- for (const relativePath of actualPaths) {
256
- const expectedHash = checksums.files[relativePath];
257
- if (!expectedHash || !/^[a-f0-9]{64}$/.test(expectedHash)) throw new Error(`bundle checksum 格式无效:${relativePath}`);
258
- if (this.computeSha256(archive[relativePath]) !== expectedHash) throw new Error(`bundle checksum 校验失败:${relativePath}`);
259
- }
260
- };
261
- readJsonArchiveEntry = (archive, entryPath, label) => {
262
- const bytes = archive[entryPath];
263
- if (!bytes) throw new Error(`bundle 缺少 ${label}。`);
264
- try {
265
- return JSON.parse(Buffer.from(bytes).toString("utf-8"));
266
- } catch (error) {
267
- throw new Error(`无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`);
268
- }
269
- };
270
217
  buildMetadata = (appId, name, version, distributionMode) => ({
271
218
  bundleFormatVersion: 1,
272
219
  distributionMode,
@@ -282,12 +229,6 @@ var AppBundleService = class {
282
229
  });
283
230
  computeSha256 = (fileBytes) => createHash("sha256").update(Buffer.from(fileBytes)).digest("hex");
284
231
  normalizeBundleFileName = (appId) => appId.replace(/[^a-zA-Z0-9._-]+/g, "-");
285
- normalizeArchiveEntry = (entryName) => {
286
- const normalized = entryName.replace(/\\/g, "/");
287
- const segments = normalized.split("/");
288
- if (!normalized || normalized.startsWith("/") || normalized.includes("\0") || /^[A-Za-z]:/.test(normalized) || segments.some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`bundle 内包含非法路径:${entryName}`);
289
- return segments.join("/");
290
- };
291
232
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
292
233
  pathExists = async (targetPath) => {
293
234
  try {