@nextclaw/app-runtime 0.12.1 → 0.13.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 (65) 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-lifecycle.service.js +1 -0
  13. package/dist/services/app-installation-lifecycle.service.js.map +1 -1
  14. package/dist/services/app-installation.service.d.ts +1 -0
  15. package/dist/services/app-installation.service.d.ts.map +1 -1
  16. package/dist/services/app-installation.service.js +10 -12
  17. package/dist/services/app-installation.service.js.map +1 -1
  18. package/dist/services/app-manifest.service.d.ts +3 -0
  19. package/dist/services/app-manifest.service.d.ts.map +1 -1
  20. package/dist/services/app-manifest.service.js +7 -0
  21. package/dist/services/app-manifest.service.js.map +1 -1
  22. package/dist/services/app-platform-target.service.d.ts +43 -0
  23. package/dist/services/app-platform-target.service.d.ts.map +1 -0
  24. package/dist/services/app-platform-target.service.js +164 -0
  25. package/dist/services/app-platform-target.service.js.map +1 -0
  26. package/dist/services/app-publish-artifact.service.d.ts +26 -0
  27. package/dist/services/app-publish-artifact.service.d.ts.map +1 -0
  28. package/dist/services/app-publish-artifact.service.js +55 -0
  29. package/dist/services/app-publish-artifact.service.js.map +1 -0
  30. package/dist/services/app-publish-validation.service.d.ts +15 -1
  31. package/dist/services/app-publish-validation.service.d.ts.map +1 -1
  32. package/dist/services/app-publish-validation.service.js +39 -1
  33. package/dist/services/app-publish-validation.service.js.map +1 -1
  34. package/dist/services/app-publish.service.d.ts +6 -1
  35. package/dist/services/app-publish.service.d.ts.map +1 -1
  36. package/dist/services/app-publish.service.js +43 -11
  37. package/dist/services/app-publish.service.js.map +1 -1
  38. package/dist/services/app-registry.service.d.ts +2 -0
  39. package/dist/services/app-registry.service.d.ts.map +1 -1
  40. package/dist/services/app-registry.service.js +4 -0
  41. package/dist/services/app-registry.service.js.map +1 -1
  42. package/dist/services/app-remote-registry-client.service.d.ts +7 -1
  43. package/dist/services/app-remote-registry-client.service.d.ts.map +1 -1
  44. package/dist/services/app-remote-registry-client.service.js +74 -7
  45. package/dist/services/app-remote-registry-client.service.js.map +1 -1
  46. package/dist/services/app-service-launch.service.d.ts +19 -0
  47. package/dist/services/app-service-launch.service.d.ts.map +1 -0
  48. package/dist/services/app-service-launch.service.js +54 -0
  49. package/dist/services/app-service-launch.service.js.map +1 -0
  50. package/dist/types/app-bundle.types.d.ts +3 -0
  51. package/dist/types/app-bundle.types.d.ts.map +1 -1
  52. package/dist/types/app-installation.types.d.ts +3 -1
  53. package/dist/types/app-installation.types.d.ts.map +1 -1
  54. package/dist/types/app-manifest.types.d.ts +32 -1
  55. package/dist/types/app-manifest.types.d.ts.map +1 -1
  56. package/dist/types/app-manifest.types.js.map +1 -1
  57. package/dist/types/app-publish.types.d.ts +26 -7
  58. package/dist/types/app-publish.types.d.ts.map +1 -1
  59. package/dist/types/app-publish.types.js.map +1 -1
  60. package/dist/types/app-registry.types.d.ts +2 -1
  61. package/dist/types/app-registry.types.d.ts.map +1 -1
  62. package/dist/types/app-remote-registry.types.d.ts +12 -2
  63. package/dist/types/app-remote-registry.types.d.ts.map +1 -1
  64. package/dist/types/app-remote-registry.types.js.map +1 -1
  65. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.service.js","names":[],"sources":["../../src/services/app-publish.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { AppMarketplaceClientService } from \"./app-marketplace-client.service.js\";\nimport { AppMarketplaceMetadataService } from \"./app-marketplace-metadata.service.js\";\nimport type { AppPublishPayload, AppPublishResult } from \"#app-runtime/types/app-publish.types.js\";\nimport { PlatformAuthStateService } from \"./platform-auth-state.service.js\";\n\nconst DEFAULT_PLATFORM_API_BASE = \"https://ai-gateway-api.nextclaw.io\";\n\ntype ResolvedPublishActor = {\n token: string;\n publisher: NonNullable<AppPublishPayload[\"publisher\"]>;\n};\n\nexport class AppPublishService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly bundleService: AppBundleService = new AppBundleService(),\n private readonly metadataService: AppMarketplaceMetadataService = new AppMarketplaceMetadataService(),\n private readonly marketplaceClient: AppMarketplaceClientService = new AppMarketplaceClientService(),\n private readonly authStateService: PlatformAuthStateService = new PlatformAuthStateService(),\n ) {}\n\n publish = async (params: {\n appDirectory: string;\n metadataPath?: string;\n bundleOutputPath?: string;\n apiBaseUrl?: string;\n token?: string;\n mode?: AppDistributionMode;\n }): Promise<AppPublishResult> => {\n const {\n appDirectory: inputAppDirectory,\n metadataPath,\n bundleOutputPath,\n apiBaseUrl,\n token,\n } = params;\n const appDirectory = path.resolve(inputAppDirectory);\n const manifestBundle = await this.manifestService.load(appDirectory);\n const distributionMode = params.mode ??\n (manifestBundle.manifest.schemaVersion === 2 ? \"bundle\" : \"source\");\n const metadata = await this.metadataService.load({\n appDirectory,\n manifest: manifestBundle.manifest,\n metadataPath,\n });\n const actor = await this.resolvePublishActor({\n apiBaseUrl,\n explicitToken: token,\n appId: manifestBundle.manifest.id,\n });\n const bundle = await this.bundleService.packAppDirectory({\n appDirectory,\n outputPath: bundleOutputPath,\n mode: distributionMode,\n });\n const bundleBytes = Buffer.from(await readFile(bundle.bundlePath));\n const bundleSha256 = createHash(\"sha256\").update(bundleBytes).digest(\"hex\");\n const publishFiles = await this.metadataService.collectPublishFiles({\n appDirectory,\n iconPath: manifestBundle.manifest.icon,\n metadataPath,\n visuals: metadata.visuals,\n });\n const payload: AppPublishPayload = {\n slug: metadata.slug,\n appId: manifestBundle.manifest.id,\n name: manifestBundle.manifest.name,\n version: manifestBundle.manifest.version,\n summary: metadata.summary,\n summaryI18n: metadata.summaryI18n,\n description: metadata.description ?? manifestBundle.manifest.description,\n descriptionI18n: metadata.descriptionI18n,\n author: metadata.author,\n tags: metadata.tags,\n sourceRepo: metadata.sourceRepo,\n homepage: metadata.homepage,\n featured: metadata.featured ?? false,\n publisher: actor.publisher,\n visuals: metadata.visuals,\n distributionMode,\n manifest: manifestBundle.manifest,\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n bundleBase64: bundleBytes.toString(\"base64\"),\n bundleSha256,\n files: publishFiles.map((file) => ({\n path: file.path,\n contentBase64: file.bytes.toString(\"base64\"),\n })),\n };\n const result = await this.marketplaceClient.publish({\n payload,\n apiBaseUrl,\n token: actor.token,\n });\n return {\n ...result,\n distribution: {\n path: bundle.bundlePath,\n sha256: bundleSha256,\n mode: distributionMode,\n },\n };\n };\n\n private resolvePublishActor = async (params: {\n apiBaseUrl?: string;\n explicitToken?: string;\n appId: string;\n }): Promise<ResolvedPublishActor> => {\n const explicitToken = params.explicitToken?.trim();\n const envAdminToken = process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();\n if (explicitToken) {\n const token = explicitToken;\n if (!token) {\n throw new Error(\"缺少 publish token。\");\n }\n const me = await this.fetchCurrentPlatformUser({\n token,\n platformApiBase: this.resolvePlatformApiBase(),\n });\n return {\n token,\n publisher: this.buildUserPublisher(me, params.appId),\n };\n }\n\n const authState = this.authStateService.readCurrentAuthState();\n const platformToken = authState.token?.trim();\n if (platformToken) {\n const me = await this.fetchCurrentPlatformUser({\n token: platformToken,\n platformApiBase: this.resolvePlatformApiBase(authState.apiBaseUrl),\n });\n return {\n token: platformToken,\n publisher: this.buildUserPublisher(me, params.appId),\n };\n }\n\n if (envAdminToken) {\n return {\n token: envAdminToken,\n publisher: this.buildOfficialPublisher(),\n };\n }\n\n throw new Error(\"发布需要 NextClaw 平台登录态。请先运行 nextclaw login,或传入 --token。\");\n };\n\n private fetchCurrentPlatformUser = async (params: {\n token: string;\n platformApiBase: string;\n }): Promise<{\n id: string;\n username: string | null;\n role: \"admin\" | \"user\";\n }> => {\n const response = await fetch(`${params.platformApiBase}/platform/auth/me`, {\n headers: {\n authorization: `Bearer ${params.token}`,\n accept: \"application/json\",\n },\n });\n const payload = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n typeof payload === \"object\" &&\n payload &&\n \"error\" in payload &&\n typeof (payload as { error?: { message?: unknown } }).error?.message === \"string\"\n ? (payload as { error: { message: string } }).error.message\n : `${response.status} ${response.statusText}`;\n throw new Error(`读取 NextClaw 登录态失败:${message}`);\n }\n const user = typeof payload === \"object\" &&\n payload &&\n \"data\" in payload &&\n typeof (payload as { data?: { user?: unknown } }).data?.user === \"object\" &&\n (payload as { data: { user: Record<string, unknown> } }).data.user\n ? (payload as { data: { user: Record<string, unknown> } }).data.user\n : null;\n const id = typeof user?.id === \"string\" ? user.id.trim() : \"\";\n const username = typeof user?.username === \"string\" ? user.username.trim() : \"\";\n const role = user?.role === \"admin\" ? \"admin\" : \"user\";\n if (!id) {\n throw new Error(\"平台登录态缺少用户 id。\");\n }\n return {\n id,\n username: username || null,\n role,\n };\n };\n\n private resolvePlatformApiBase = (configuredApiBase?: string): string => {\n const source = configuredApiBase?.trim() || process.env.NEXTCLAW_PLATFORM_API_BASE?.trim() || DEFAULT_PLATFORM_API_BASE;\n const normalized = new URL(source);\n normalized.pathname = normalized.pathname.replace(/\\/v1\\/?$/, \"/\");\n return normalized.toString().replace(/\\/+$/, \"\");\n };\n\n private buildUserPublisher = (\n user: { id: string; username: string | null; role: \"admin\" | \"user\" },\n appId: string,\n ): NonNullable<AppPublishPayload[\"publisher\"]> => {\n const isOfficialScope = appId.startsWith(\"nextclaw.\");\n if (isOfficialScope && user.role === \"admin\") {\n return this.buildOfficialPublisher();\n }\n if (!user.username) {\n throw new Error(\"当前 NextClaw 账号还没有 username,无法发布个人 scope app。请先在平台账号页设置用户名。\");\n }\n return {\n id: user.username,\n name: user.username,\n url: `https://platform.nextclaw.io/account`,\n };\n };\n\n private buildOfficialPublisher = (): NonNullable<AppPublishPayload[\"publisher\"]> => {\n return {\n id: \"nextclaw\",\n name: \"NextClaw\",\n url: \"https://nextclaw.io\",\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-publish.service.js","names":[],"sources":["../../src/services/app-publish.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { AppMarketplaceClientService } from \"./app-marketplace-client.service.js\";\nimport { AppMarketplaceMetadataService } from \"./app-marketplace-metadata.service.js\";\nimport { AppPlatformTargetService } from \"./app-platform-target.service.js\";\nimport { AppPublishArtifactService } from \"./app-publish-artifact.service.js\";\nimport type { AppPublishPayload, AppPublishResult } from \"#app-runtime/types/app-publish.types.js\";\nimport { PlatformAuthStateService } from \"./platform-auth-state.service.js\";\n\nconst DEFAULT_PLATFORM_API_BASE = \"https://ai-gateway-api.nextclaw.io\";\n\ntype ResolvedPublishActor = {\n token: string;\n publisher: NonNullable<AppPublishPayload[\"publisher\"]>;\n};\n\nexport class AppPublishService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly bundleService: AppBundleService = new AppBundleService(),\n private readonly metadataService: AppMarketplaceMetadataService = new AppMarketplaceMetadataService(),\n private readonly marketplaceClient: AppMarketplaceClientService = new AppMarketplaceClientService(),\n private readonly authStateService: PlatformAuthStateService = new PlatformAuthStateService(),\n private readonly publishArtifactService: AppPublishArtifactService = new AppPublishArtifactService(),\n private readonly platformTargetService: AppPlatformTargetService = new AppPlatformTargetService(),\n ) {}\n\n publish = async (params: {\n appDirectory: string;\n metadataPath?: string;\n bundleOutputPath?: string;\n apiBaseUrl?: string;\n token?: string;\n mode?: AppDistributionMode;\n artifactsDirectory?: string;\n }): Promise<AppPublishResult> => {\n const {\n appDirectory: inputAppDirectory,\n metadataPath,\n bundleOutputPath,\n apiBaseUrl,\n token,\n } = params;\n const appDirectory = path.resolve(inputAppDirectory);\n const manifestBundle = await this.manifestService.load(appDirectory);\n const distributionMode = params.mode ??\n (manifestBundle.manifest.schemaVersion === 2 ? \"bundle\" : \"source\");\n const metadata = await this.metadataService.load({\n appDirectory,\n manifest: manifestBundle.manifest,\n metadataPath,\n });\n const actor = await this.resolvePublishActor({\n apiBaseUrl,\n explicitToken: token,\n appId: manifestBundle.manifest.id,\n });\n const componentManifest = manifestBundle.manifest.schemaVersion === 2\n ? manifestBundle.manifest\n : undefined;\n const distribution = componentManifest\n ? this.platformTargetService.resolveDistribution(\n componentManifest.distribution,\n )\n : { mode: \"universal\" as const };\n if (distribution.mode === \"targeted\" && !params.artifactsDirectory?.trim()) {\n throw new Error(\"targeted App 发布需要通过 --artifacts 指定平台 artifact 目录。\");\n }\n const preparedArtifacts = distribution.mode === \"targeted\" && componentManifest\n ? await this.publishArtifactService.collect({\n manifest: componentManifest,\n artifactsDirectory: params.artifactsDirectory!,\n })\n : [];\n const bundle = distribution.mode === \"universal\"\n ? await this.bundleService.packAppDirectory({\n appDirectory,\n outputPath: bundleOutputPath,\n mode: distributionMode,\n })\n : undefined;\n const bundleBytes = bundle\n ? Buffer.from(await readFile(bundle.bundlePath))\n : undefined;\n const bundleSha256 = bundleBytes\n ? createHash(\"sha256\").update(bundleBytes).digest(\"hex\")\n : undefined;\n const publishFiles = await this.metadataService.collectPublishFiles({\n appDirectory,\n iconPath: manifestBundle.manifest.icon,\n metadataPath,\n visuals: metadata.visuals,\n });\n const payloadBase = {\n slug: metadata.slug,\n appId: manifestBundle.manifest.id,\n name: manifestBundle.manifest.name,\n version: manifestBundle.manifest.version,\n summary: metadata.summary,\n summaryI18n: metadata.summaryI18n,\n description: metadata.description ?? manifestBundle.manifest.description,\n descriptionI18n: metadata.descriptionI18n,\n author: metadata.author,\n tags: metadata.tags,\n sourceRepo: metadata.sourceRepo,\n homepage: metadata.homepage,\n featured: metadata.featured ?? false,\n publisher: actor.publisher,\n visuals: metadata.visuals,\n distributionMode,\n manifest: manifestBundle.manifest,\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,\n files: publishFiles.map((file) => ({\n path: file.path,\n contentBase64: file.bytes.toString(\"base64\"),\n })),\n };\n const payload: AppPublishPayload = bundleBytes && bundleSha256\n ? {\n ...payloadBase,\n bundleBase64: bundleBytes.toString(\"base64\"),\n bundleSha256,\n }\n : {\n ...payloadBase,\n artifacts: preparedArtifacts.map((artifact) => ({\n target: artifact.target,\n bundleBase64: artifact.bytes.toString(\"base64\"),\n bundleSha256: artifact.sha256,\n sizeBytes: artifact.sizeBytes,\n })),\n };\n const result = await this.marketplaceClient.publish({\n payload,\n apiBaseUrl,\n token: actor.token,\n });\n return {\n ...result,\n distribution: {\n mode: distributionMode,\n ...(bundle && bundleSha256\n ? {\n path: bundle.bundlePath,\n sha256: bundleSha256,\n }\n : {\n path: path.resolve(params.artifactsDirectory!),\n artifacts: preparedArtifacts.map((artifact) => ({\n target: artifact.target,\n path: artifact.bundlePath,\n sha256: artifact.sha256,\n sizeBytes: artifact.sizeBytes,\n })),\n }),\n },\n };\n };\n\n private resolvePublishActor = async (params: {\n apiBaseUrl?: string;\n explicitToken?: string;\n appId: string;\n }): Promise<ResolvedPublishActor> => {\n const explicitToken = params.explicitToken?.trim();\n const envAdminToken = process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();\n if (explicitToken) {\n const token = explicitToken;\n if (!token) {\n throw new Error(\"缺少 publish token。\");\n }\n const me = await this.fetchCurrentPlatformUser({\n token,\n platformApiBase: this.resolvePlatformApiBase(),\n });\n return {\n token,\n publisher: this.buildUserPublisher(me, params.appId),\n };\n }\n\n const authState = this.authStateService.readCurrentAuthState();\n const platformToken = authState.token?.trim();\n if (platformToken) {\n const me = await this.fetchCurrentPlatformUser({\n token: platformToken,\n platformApiBase: this.resolvePlatformApiBase(authState.apiBaseUrl),\n });\n return {\n token: platformToken,\n publisher: this.buildUserPublisher(me, params.appId),\n };\n }\n\n if (envAdminToken) {\n return {\n token: envAdminToken,\n publisher: this.buildOfficialPublisher(),\n };\n }\n\n throw new Error(\"发布需要 NextClaw 平台登录态。请先运行 nextclaw login,或传入 --token。\");\n };\n\n private fetchCurrentPlatformUser = async (params: {\n token: string;\n platformApiBase: string;\n }): Promise<{\n id: string;\n username: string | null;\n role: \"admin\" | \"user\";\n }> => {\n const response = await fetch(`${params.platformApiBase}/platform/auth/me`, {\n headers: {\n authorization: `Bearer ${params.token}`,\n accept: \"application/json\",\n },\n });\n const payload = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n typeof payload === \"object\" &&\n payload &&\n \"error\" in payload &&\n typeof (payload as { error?: { message?: unknown } }).error?.message === \"string\"\n ? (payload as { error: { message: string } }).error.message\n : `${response.status} ${response.statusText}`;\n throw new Error(`读取 NextClaw 登录态失败:${message}`);\n }\n const user = typeof payload === \"object\" &&\n payload &&\n \"data\" in payload &&\n typeof (payload as { data?: { user?: unknown } }).data?.user === \"object\" &&\n (payload as { data: { user: Record<string, unknown> } }).data.user\n ? (payload as { data: { user: Record<string, unknown> } }).data.user\n : null;\n const id = typeof user?.id === \"string\" ? user.id.trim() : \"\";\n const username = typeof user?.username === \"string\" ? user.username.trim() : \"\";\n const role = user?.role === \"admin\" ? \"admin\" : \"user\";\n if (!id) {\n throw new Error(\"平台登录态缺少用户 id。\");\n }\n return {\n id,\n username: username || null,\n role,\n };\n };\n\n private resolvePlatformApiBase = (configuredApiBase?: string): string => {\n const source = configuredApiBase?.trim() || process.env.NEXTCLAW_PLATFORM_API_BASE?.trim() || DEFAULT_PLATFORM_API_BASE;\n const normalized = new URL(source);\n normalized.pathname = normalized.pathname.replace(/\\/v1\\/?$/, \"/\");\n return normalized.toString().replace(/\\/+$/, \"\");\n };\n\n private buildUserPublisher = (\n user: { id: string; username: string | null; role: \"admin\" | \"user\" },\n appId: string,\n ): NonNullable<AppPublishPayload[\"publisher\"]> => {\n const isOfficialScope = appId.startsWith(\"nextclaw.\");\n if (isOfficialScope && user.role === \"admin\") {\n return this.buildOfficialPublisher();\n }\n if (!user.username) {\n throw new Error(\"当前 NextClaw 账号还没有 username,无法发布个人 scope app。请先在平台账号页设置用户名。\");\n }\n return {\n id: user.username,\n name: user.username,\n url: `https://platform.nextclaw.io/account`,\n };\n };\n\n private buildOfficialPublisher = (): NonNullable<AppPublishPayload[\"publisher\"]> => {\n return {\n id: \"nextclaw\",\n name: \"NextClaw\",\n url: \"https://nextclaw.io\",\n };\n };\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ declare class AppRegistryService {
9
9
  private readonly appHomeService;
10
10
  private readonly instanceStorageService;
11
11
  private readonly fileLockService;
12
+ private readonly platformTargetService;
12
13
  constructor(appHomeService?: AppHomeService);
13
14
  load: () => Promise<AppRegistry>;
14
15
  save: (registry: AppRegistry) => Promise<void>;
@@ -31,6 +32,7 @@ declare class AppRegistryService {
31
32
  registryUrl?: string;
32
33
  bundleUrl?: string;
33
34
  sha256?: string;
35
+ target?: AppRegistryInstalledVersion["target"];
34
36
  publisher?: AppRegistryInstalledVersion["publisher"];
35
37
  manifestSchemaVersion: 1 | 2;
36
38
  components?: AppResolvedComponent[];
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;cAuBa,kBAAA;EAAA,iBAIkB,cAAA;EAAA,iBAHZ,sBAAA;EAAA,iBACA,eAAA;cAEY,cAAA,GAAgB,cAAA;EAI7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAYzB,IAAA,GAAc,QAAA,EAAU,WAAA,KAAc,OAAA;EAAA,QAI9B,YAAA;EAsBR,QAAA,QAAqB,OAAA,CAAQ,oBAAA;EAK7B,MAAA,GAAgB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAKxC,gBAAA,GAA0B,KAAA,aAAgB,OAAA,CAAQ,2BAAA;EAKlD,kBAAA,GAA4B,MAAA;IAC1B,KAAA;IACA,IAAA;IACA,WAAA;IACA,OAAA;IACA,gBAAA;IACA,eAAA,EAAiB,iBAAA;IACjB,UAAA,EAAY,oBAAA;IACZ,SAAA;IACA,WAAA;IACA,gBAAA,GAAmB,2BAAA;IACnB,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,2BAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,QAAA,GAAW,0BAAA;IACX,iBAAA;IACA,aAAA;IACA,OAAA;IACA,QAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAoDZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,uBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAmBX,YAAA,GACE,KAAA,UACA,MAAA,EAAQ,mBAAA,KACP,OAAA,CAAQ,oBAAA;EAOX,gBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAIX,mBAAA,GAA6B,KAAA,UAAe,OAAA,aAAkB,OAAA;EAc9D,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAa3C,mBAAA,GAA6B,KAAA,aAAgB,OAAA;EAK7C,oBAAA,GAA8B,KAAA,UAAe,UAAA,cAAsB,OAAA;EAAA,QAY3D,SAAA;EAAA,QAiBA,YAAA;EAAA,QAKA,aAAA;EAAA,QA+FA,oBAAA;EAAA,QAyGA,eAAA;EAAA,QAMA,eAAA;EAAA,QA6DA,YAAA;EAAA,QAOA,aAAA;EAAA,QAOA,kBAAA;AAAA"}
1
+ {"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;;cAwBa,kBAAA;EAAA,iBAKkB,cAAA;EAAA,iBAJZ,sBAAA;EAAA,iBACA,eAAA;EAAA,iBACA,qBAAA;cAEY,cAAA,GAAgB,cAAA;EAI7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAYzB,IAAA,GAAc,QAAA,EAAU,WAAA,KAAc,OAAA;EAAA,QAI9B,YAAA;EAsBR,QAAA,QAAqB,OAAA,CAAQ,oBAAA;EAK7B,MAAA,GAAgB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAKxC,gBAAA,GAA0B,KAAA,aAAgB,OAAA,CAAQ,2BAAA;EAKlD,kBAAA,GAA4B,MAAA;IAC1B,KAAA;IACA,IAAA;IACA,WAAA;IACA,OAAA;IACA,gBAAA;IACA,eAAA,EAAiB,iBAAA;IACjB,UAAA,EAAY,oBAAA;IACZ,SAAA;IACA,WAAA;IACA,gBAAA,GAAmB,2BAAA;IACnB,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,MAAA,GAAS,2BAAA;IACT,SAAA,GAAY,2BAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,QAAA,GAAW,0BAAA;IACX,iBAAA;IACA,aAAA;IACA,OAAA;IACA,QAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAqDZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,uBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAmBX,YAAA,GACE,KAAA,UACA,MAAA,EAAQ,mBAAA,KACP,OAAA,CAAQ,oBAAA;EAOX,gBAAA,GACE,KAAA,UACA,OAAA,UACA,aAAA,aACC,OAAA,CAAQ,oBAAA;EAIX,mBAAA,GAA6B,KAAA,UAAe,OAAA,aAAkB,OAAA;EAc9D,SAAA,GAAmB,KAAA,aAAgB,OAAA,CAAQ,oBAAA;EAa3C,mBAAA,GAA6B,KAAA,aAAgB,OAAA;EAK7C,oBAAA,GAA8B,KAAA,UAAe,UAAA,cAAsB,OAAA;EAAA,QAY3D,SAAA;EAAA,QAiBA,YAAA;EAAA,QAKA,aAAA;EAAA,QAqGA,oBAAA;EAAA,QAyGA,eAAA;EAAA,QAMA,eAAA;EAAA,QA6DA,YAAA;EAAA,QAOA,aAAA;EAAA,QAOA,kBAAA;AAAA"}
@@ -1,3 +1,4 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
1
2
  import { AppHomeService } from "./app-home.service.js";
2
3
  import { FileLockService } from "./file-lock.service.js";
3
4
  import { AppInstanceStorageService } from "./app-instance-storage.service.js";
@@ -10,6 +11,7 @@ const SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10
11
  var AppRegistryService = class {
11
12
  instanceStorageService;
12
13
  fileLockService = new FileLockService();
14
+ platformTargetService = new AppPlatformTargetService();
13
15
  constructor(appHomeService = new AppHomeService()) {
14
16
  this.appHomeService = appHomeService;
15
17
  this.instanceStorageService = new AppInstanceStorageService(appHomeService);
@@ -87,6 +89,7 @@ var AppRegistryService = class {
87
89
  registryUrl: params.registryUrl,
88
90
  bundleUrl: params.bundleUrl,
89
91
  sha256: params.sha256,
92
+ target: params.target,
90
93
  publisher: params.publisher,
91
94
  manifestSchemaVersion: params.manifestSchemaVersion,
92
95
  components: params.components,
@@ -218,6 +221,7 @@ var AppRegistryService = class {
218
221
  version,
219
222
  installDirectory: path.resolve(installDirectory),
220
223
  manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,
224
+ target: versionRecord.target === void 0 ? void 0 : this.platformTargetService.parseArtifactTarget(versionRecord.target, `registry.apps.${appId}.installedVersions.${version}.target`),
221
225
  components: this.parseComponents(versionRecord.components, installDirectory, `registry.apps.${appId}.installedVersions.${version}.components`),
222
226
  dataSchemaVersion: typeof versionRecord.dataSchemaVersion === "number" && Number.isSafeInteger(versionRecord.dataSchemaVersion) && versionRecord.dataSchemaVersion > 0 ? versionRecord.dataSchemaVersion : 1
223
227
  };
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst SAFE_VERSION_PATTERN = /^[0-9A-Za-z]+(?:[._+-][0-9A-Za-z]+)*$/;\nconst SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppRegistryService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n defaultInstance: AppInstanceRecord;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n security?: AppPlatformSecuritySummary;\n dataSchemaVersion: number;\n contentSha256?: string;\n enabled?: boolean;\n activate?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const currentPublisher = currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== params.publisher?.id) {\n throw new Error(\n `应用 ${params.appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${params.publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion: params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.defaultInstance.storage.dataDirectory,\n defaultInstance: params.defaultInstance,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n security: params.security,\n dataSchemaVersion: params.dataSchemaVersion,\n contentSha256: params.contentSha256,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n setVersionContentDigest = async (\n appId: string,\n version: string,\n contentSha256: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const installedVersion = record.installedVersions[version];\n if (!installedVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (installedVersion.contentSha256 && installedVersion.contentSha256 !== contentSha256) {\n throw new Error(`应用 ${appId}@${version} 已存在不同的代码完整性摘要。`);\n }\n return {\n ...record,\n installedVersions: {\n ...record.installedVersions,\n [version]: { ...installedVersion, contentSha256 },\n },\n };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n return await this.fileLockService.withLock(`${registryPath}.lock`, operation);\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n }\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) {\n throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n }\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.components`,\n ),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt = Object.values(installedVersions)\n .map((version) => version.installedAt)\n .filter((value): value is string => typeof value === \"string\")\n .sort()[0] ?? new Date(0).toISOString();\n const defaultInstance = this.parseDefaultInstance(\n app.defaultInstance,\n appId,\n dataDirectory,\n firstInstalledAt,\n );\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n publisher: app.publisher ?? installedVersions[activeVersion]?.publisher,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n dataDirectory: defaultInstance.storage.dataDirectory,\n defaultInstance,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private parseDefaultInstance = (\n rawInstance: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!rawInstance || typeof rawInstance !== \"object\" || Array.isArray(rawInstance)) {\n this.assertExactPath(\n dataDirectory,\n this.appHomeService.getAppDataDirectory(appId),\n `registry.apps.${appId}.dataDirectory`,\n );\n return this.instanceStorageService.buildLegacyDefaultInstance({\n appId,\n dataDirectory,\n createdAt,\n });\n }\n const instance = rawInstance as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (instance.publisherId !== undefined && typeof instance.publisherId !== \"string\") {\n throw new Error(`registry.apps.${appId}.defaultInstance.publisherId 无效。`);\n }\n const dataSchemaVersion = typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\"\n ? instance.createdAt\n : createdAt;\n const instanceDirectory = path.resolve(\n this.appHomeService.getAppInstanceDirectory(appId, \"default\"),\n );\n const expectedStorage = {\n layout: \"instance-v1\" as const,\n layoutVersion: 1 as const,\n instanceId: \"default\",\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n };\n if (instance.storage.layout === \"legacy\") {\n const expectedLegacyDataDirectory = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n expectedLegacyDataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: {\n ...expectedStorage,\n layout: \"legacy\",\n dataDirectory: path.resolve(expectedLegacyDataDirectory),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(expectedLegacyDataDirectory),\n };\n }\n for (const [field, expectedPath] of Object.entries({\n instanceDirectory: expectedStorage.instanceDirectory,\n dataDirectory: expectedStorage.dataDirectory,\n configDirectory: expectedStorage.configDirectory,\n stateDirectory: expectedStorage.stateDirectory,\n cacheDirectory: expectedStorage.cacheDirectory,\n temporaryDirectory: expectedStorage.temporaryDirectory,\n logsDirectory: expectedStorage.logsDirectory,\n })) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage] as string,\n expectedPath,\n `registry.apps.${appId}.defaultInstance.storage.${field}`,\n );\n }\n this.assertExactPath(\n dataDirectory,\n expectedStorage.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: expectedStorage,\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: instance.legacyDataDirectory,\n };\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) {\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n }\n };\n\n private parseComponents = (\n rawComponents: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (rawComponents === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawComponents)) {\n throw new Error(`${field} 必须是数组。`);\n }\n return rawComponents.map((rawComponent, index) => {\n const component = this.assertRecord(rawComponent, `${field}[${index}]`);\n const kind = component.kind;\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`${field}[${index}].kind 无效。`);\n }\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`${field}[${index}].id 不安全。`);\n }\n const relativeComponentPath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const expectedComponentDirectory = path.resolve(installDirectory, relativeComponentPath);\n const relativeToInstall = path.relative(\n path.resolve(installDirectory),\n expectedComponentDirectory,\n );\n if (\n !relativeToInstall ||\n relativeToInstall.startsWith(\"..\") ||\n path.isAbsolute(relativeToInstall)\n ) {\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n }\n const expectedManifestPath = path.join(\n expectedComponentDirectory,\n kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n expectedComponentDirectory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n expectedManifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind,\n id,\n path: relativeComponentPath,\n componentDirectory: expectedComponentDirectory,\n manifestPath: expectedManifestPath,\n };\n });\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { AppPlatformTargetService } from \"#app-runtime/services/app-platform-target.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst SAFE_VERSION_PATTERN = /^[0-9A-Za-z]+(?:[._+-][0-9A-Za-z]+)*$/;\nconst SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppRegistryService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n private readonly platformTargetService = new AppPlatformTargetService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n defaultInstance: AppInstanceRecord;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n target?: AppRegistryInstalledVersion[\"target\"];\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n security?: AppPlatformSecuritySummary;\n dataSchemaVersion: number;\n contentSha256?: string;\n enabled?: boolean;\n activate?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const currentPublisher = currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== params.publisher?.id) {\n throw new Error(\n `应用 ${params.appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${params.publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion: params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.defaultInstance.storage.dataDirectory,\n defaultInstance: params.defaultInstance,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n target: params.target,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n security: params.security,\n dataSchemaVersion: params.dataSchemaVersion,\n contentSha256: params.contentSha256,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n setVersionContentDigest = async (\n appId: string,\n version: string,\n contentSha256: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const installedVersion = record.installedVersions[version];\n if (!installedVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (installedVersion.contentSha256 && installedVersion.contentSha256 !== contentSha256) {\n throw new Error(`应用 ${appId}@${version} 已存在不同的代码完整性摘要。`);\n }\n return {\n ...record,\n installedVersions: {\n ...record.installedVersions,\n [version]: { ...installedVersion, contentSha256 },\n },\n };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n return await this.fileLockService.withLock(`${registryPath}.lock`, operation);\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n }\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) {\n throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n }\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n target: versionRecord.target === undefined\n ? undefined\n : this.platformTargetService.parseArtifactTarget(\n versionRecord.target,\n `registry.apps.${appId}.installedVersions.${version}.target`,\n ),\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.components`,\n ),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt = Object.values(installedVersions)\n .map((version) => version.installedAt)\n .filter((value): value is string => typeof value === \"string\")\n .sort()[0] ?? new Date(0).toISOString();\n const defaultInstance = this.parseDefaultInstance(\n app.defaultInstance,\n appId,\n dataDirectory,\n firstInstalledAt,\n );\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n publisher: app.publisher ?? installedVersions[activeVersion]?.publisher,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n dataDirectory: defaultInstance.storage.dataDirectory,\n defaultInstance,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private parseDefaultInstance = (\n rawInstance: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!rawInstance || typeof rawInstance !== \"object\" || Array.isArray(rawInstance)) {\n this.assertExactPath(\n dataDirectory,\n this.appHomeService.getAppDataDirectory(appId),\n `registry.apps.${appId}.dataDirectory`,\n );\n return this.instanceStorageService.buildLegacyDefaultInstance({\n appId,\n dataDirectory,\n createdAt,\n });\n }\n const instance = rawInstance as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (instance.publisherId !== undefined && typeof instance.publisherId !== \"string\") {\n throw new Error(`registry.apps.${appId}.defaultInstance.publisherId 无效。`);\n }\n const dataSchemaVersion = typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\"\n ? instance.createdAt\n : createdAt;\n const instanceDirectory = path.resolve(\n this.appHomeService.getAppInstanceDirectory(appId, \"default\"),\n );\n const expectedStorage = {\n layout: \"instance-v1\" as const,\n layoutVersion: 1 as const,\n instanceId: \"default\",\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n };\n if (instance.storage.layout === \"legacy\") {\n const expectedLegacyDataDirectory = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n expectedLegacyDataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: {\n ...expectedStorage,\n layout: \"legacy\",\n dataDirectory: path.resolve(expectedLegacyDataDirectory),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(expectedLegacyDataDirectory),\n };\n }\n for (const [field, expectedPath] of Object.entries({\n instanceDirectory: expectedStorage.instanceDirectory,\n dataDirectory: expectedStorage.dataDirectory,\n configDirectory: expectedStorage.configDirectory,\n stateDirectory: expectedStorage.stateDirectory,\n cacheDirectory: expectedStorage.cacheDirectory,\n temporaryDirectory: expectedStorage.temporaryDirectory,\n logsDirectory: expectedStorage.logsDirectory,\n })) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage] as string,\n expectedPath,\n `registry.apps.${appId}.defaultInstance.storage.${field}`,\n );\n }\n this.assertExactPath(\n dataDirectory,\n expectedStorage.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: expectedStorage,\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: instance.legacyDataDirectory,\n };\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) {\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n }\n };\n\n private parseComponents = (\n rawComponents: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (rawComponents === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawComponents)) {\n throw new Error(`${field} 必须是数组。`);\n }\n return rawComponents.map((rawComponent, index) => {\n const component = this.assertRecord(rawComponent, `${field}[${index}]`);\n const kind = component.kind;\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`${field}[${index}].kind 无效。`);\n }\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`${field}[${index}].id 不安全。`);\n }\n const relativeComponentPath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const expectedComponentDirectory = path.resolve(installDirectory, relativeComponentPath);\n const relativeToInstall = path.relative(\n path.resolve(installDirectory),\n expectedComponentDirectory,\n );\n if (\n !relativeToInstall ||\n relativeToInstall.startsWith(\"..\") ||\n path.isAbsolute(relativeToInstall)\n ) {\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n }\n const expectedManifestPath = path.join(\n expectedComponentDirectory,\n kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n expectedComponentDirectory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n expectedManifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind,\n id,\n path: relativeComponentPath,\n componentDirectory: expectedComponentDirectory,\n manifestPath: expectedManifestPath,\n };\n });\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
@@ -1,10 +1,12 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
1
2
  import { AppRemoteRegistryResolution } from "../types/app-remote-registry.types.js";
2
3
  import { AppRegistryConfigService } from "./app-registry-config.service.js";
3
4
 
4
5
  //#region src/services/app-remote-registry-client.service.d.ts
5
6
  declare class AppRemoteRegistryClientService {
6
7
  private readonly configService;
7
- constructor(configService?: AppRegistryConfigService);
8
+ private readonly platformTargetService;
9
+ constructor(configService?: AppRegistryConfigService, platformTargetService?: AppPlatformTargetService);
8
10
  resolve: (params: {
9
11
  appId: string;
10
12
  version?: string;
@@ -18,6 +20,10 @@ declare class AppRemoteRegistryClientService {
18
20
  }>;
19
21
  private parseDocument;
20
22
  private parseVersion;
23
+ private parseTargetedArtifact;
24
+ private readVersionArtifacts;
25
+ private readRequestedVersion;
26
+ private selectDefaultVersion;
21
27
  private parsePublisher;
22
28
  private readRequiredString;
23
29
  private readOptionalString;
@@ -1 +1 @@
1
- {"version":3,"file":"app-remote-registry-client.service.d.ts","names":[],"sources":["../../src/services/app-remote-registry-client.service.ts"],"mappings":";;;;cAaa,8BAAA;EAAA,iBAEQ,aAAA;cAAA,aAAA,GAAe,wBAAA;EAGlC,OAAA,GAAiB,MAAA;IACf,KAAA;IACA,OAAA;IACA,WAAA;EAAA,MACE,OAAA,CAAQ,2BAAA;EAoCZ,cAAA,GAAwB,MAAA;IACtB,UAAA,EAAY,2BAAA;IACZ,eAAA;EAAA,MACE,OAAA;IACF,UAAA;EAAA;EAAA,QAkCM,aAAA;EAAA,QAsCA,YAAA;EAAA,QAwDA,cAAA;EAAA,QAkBA,kBAAA;EAAA,QAOA,kBAAA;EAAA,QAUA,uBAAA;EAAA,QAIA,eAAA;EAAA,QA6CA,qBAAA;EAAA,QAIA,uBAAA;EAAA,QAIA,eAAA;EAAA,QAMA,oBAAA;AAAA"}
1
+ {"version":3,"file":"app-remote-registry-client.service.d.ts","names":[],"sources":["../../src/services/app-remote-registry-client.service.ts"],"mappings":";;;;;cAea,8BAAA;EAAA,iBAEQ,aAAA;EAAA,iBACA,qBAAA;cADA,aAAA,GAAe,wBAAA,EACf,qBAAA,GAAuB,wBAAA;EAG1C,OAAA,GAAiB,MAAA;IACf,KAAA;IACA,OAAA;IACA,WAAA;EAAA,MACE,OAAA,CAAQ,2BAAA;EAuDZ,cAAA,GAAwB,MAAA;IACtB,UAAA,EAAY,2BAAA;IACZ,eAAA;EAAA,MACE,OAAA;IACF,UAAA;EAAA;EAAA,QAkCM,aAAA;EAAA,QAsCA,YAAA;EAAA,QAsFA,qBAAA;EAAA,QAqCA,oBAAA;EAAA,QAcA,oBAAA;EAAA,QAaA,oBAAA;EAAA,QA4BA,cAAA;EAAA,QAkBA,kBAAA;EAAA,QAOA,kBAAA;EAAA,QAUA,uBAAA;EAAA,QAIA,eAAA;EAAA,QA6CA,qBAAA;EAAA,QAIA,uBAAA;EAAA,QAIA,eAAA;EAAA,QAMA,oBAAA;AAAA"}
@@ -1,3 +1,4 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
1
2
  import { AppRegistryConfigService } from "./app-registry-config.service.js";
2
3
  import { createHash } from "node:crypto";
3
4
  import { writeFile } from "node:fs/promises";
@@ -6,8 +7,9 @@ import path from "node:path";
6
7
  const MAX_REMOTE_BUNDLE_BYTES = 25 * 1024 * 1024;
7
8
  const MAX_REGISTRY_GET_ATTEMPTS = 3;
8
9
  var AppRemoteRegistryClientService = class {
9
- constructor(configService = new AppRegistryConfigService()) {
10
+ constructor(configService = new AppRegistryConfigService(), platformTargetService = new AppPlatformTargetService()) {
10
11
  this.configService = configService;
12
+ this.platformTargetService = platformTargetService;
11
13
  }
12
14
  resolve = async (params) => {
13
15
  const { appId, version: requestedVersion, registryUrl: overrideRegistryUrl } = params;
@@ -15,9 +17,13 @@ var AppRemoteRegistryClientService = class {
15
17
  const metadataUrl = new URL(encodeURIComponent(appId), registryUrl).toString();
16
18
  const rawDocument = await this.readGetResponse(metadataUrl, `无法从 registry 拉取 ${appId} metadata`, async (response) => await response.json());
17
19
  const document = this.parseDocument(rawDocument, appId);
18
- const version = requestedVersion ?? document["dist-tags"].latest;
19
- const versionRecord = document.versions[version];
20
- if (!versionRecord) throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);
20
+ const hostTarget = this.platformTargetService.readHostTarget();
21
+ const selectedVersion = requestedVersion ? this.readRequestedVersion(document, requestedVersion, registryUrl, appId) : this.selectDefaultVersion(document, hostTarget);
22
+ const versionRecord = selectedVersion ? document.versions[selectedVersion.version] : void 0;
23
+ if (!selectedVersion || !versionRecord) throw new Error(requestedVersion ? `registry ${registryUrl} 未提供 ${appId}@${requestedVersion}。` : `registry ${registryUrl} 没有适用于当前 target ${this.platformTargetService.toTargetKey(hostTarget)} 的 ${appId} 版本。`);
24
+ const version = selectedVersion.version;
25
+ const artifact = this.platformTargetService.selectArtifact(this.readVersionArtifacts(versionRecord), hostTarget);
26
+ if (!artifact) throw new Error(`${appId}@${version} 不支持当前 target ${this.platformTargetService.toTargetKey(hostTarget)}。`);
21
27
  return {
22
28
  registryUrl,
23
29
  metadataUrl,
@@ -27,8 +33,9 @@ var AppRemoteRegistryClientService = class {
27
33
  publisher: versionRecord.publisher,
28
34
  permissions: versionRecord.permissions,
29
35
  distributionMode: versionRecord.dist.kind === "source" ? "source" : "bundle",
30
- bundleUrl: new URL(versionRecord.dist.artifact ?? versionRecord.dist.source ?? versionRecord.dist.bundle, metadataUrl).toString(),
31
- sha256: versionRecord.dist.sha256
36
+ target: artifact.target,
37
+ bundleUrl: new URL(artifact.bundle, metadataUrl).toString(),
38
+ sha256: artifact.sha256
32
39
  };
33
40
  };
34
41
  downloadBundle = async (params) => {
@@ -42,7 +49,7 @@ var AppRemoteRegistryClientService = class {
42
49
  });
43
50
  const actualSha256 = createHash("sha256").update(bundleBytes).digest("hex");
44
51
  if (actualSha256 !== resolution.sha256) throw new Error(`bundle checksum 校验失败:期望 ${resolution.sha256},实际 ${actualSha256}`);
45
- const bundlePath = path.join(targetDirectory, `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}.napp`);
52
+ const bundlePath = path.join(targetDirectory, `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}-${this.platformTargetService.toTargetKey(resolution.target)}.napp`);
46
53
  await writeFile(bundlePath, bundleBytes);
47
54
  return { bundlePath };
48
55
  };
@@ -71,6 +78,24 @@ var AppRemoteRegistryClientService = class {
71
78
  const dist = candidate.dist;
72
79
  if (!dist || typeof dist !== "object" || Array.isArray(dist)) throw new Error(`registry metadata 的 versions.${version}.dist 必须是对象。`);
73
80
  const distCandidate = dist;
81
+ if (distCandidate.kind === "targeted-bundle") {
82
+ if (!Array.isArray(distCandidate.artifacts) || distCandidate.artifacts.length === 0) throw new Error(`registry metadata 的 versions.${version}.dist.artifacts 必须是非空数组。`);
83
+ const artifacts = distCandidate.artifacts.map((artifact, index) => this.parseTargetedArtifact(artifact, version, index));
84
+ const targetKeys = artifacts.map((artifact) => this.platformTargetService.toTargetKey(artifact.target));
85
+ const duplicate = targetKeys.find((targetKey, index) => targetKeys.indexOf(targetKey) !== index);
86
+ if (duplicate) throw new Error(`registry metadata 的 versions.${version}.dist.artifacts 包含重复 target:${duplicate}`);
87
+ return {
88
+ name,
89
+ version: parsedVersion,
90
+ description: this.readOptionalString(candidate.description, `versions.${version}.description`),
91
+ publisher: this.parsePublisher(candidate.publisher, version),
92
+ permissions: candidate.permissions,
93
+ dist: {
94
+ kind: "targeted-bundle",
95
+ artifacts
96
+ }
97
+ };
98
+ }
74
99
  return {
75
100
  name,
76
101
  version: parsedVersion,
@@ -86,6 +111,48 @@ var AppRemoteRegistryClientService = class {
86
111
  }
87
112
  };
88
113
  };
114
+ parseTargetedArtifact = (rawArtifact, version, index) => {
115
+ if (!rawArtifact || typeof rawArtifact !== "object" || Array.isArray(rawArtifact)) throw new Error(`versions.${version}.dist.artifacts[${index}] 必须是对象。`);
116
+ const candidate = rawArtifact;
117
+ const sizeBytes = candidate.sizeBytes;
118
+ if (sizeBytes !== void 0 && (typeof sizeBytes !== "number" || !Number.isSafeInteger(sizeBytes) || sizeBytes < 1)) throw new Error(`versions.${version}.dist.artifacts[${index}].sizeBytes 必须是正整数。`);
119
+ return {
120
+ target: this.platformTargetService.parseArtifactTarget(candidate.target, `versions.${version}.dist.artifacts[${index}].target`),
121
+ bundle: this.readRequiredString(candidate.bundle, `versions.${version}.dist.artifacts[${index}].bundle`),
122
+ sha256: this.readRequiredString(candidate.sha256, `versions.${version}.dist.artifacts[${index}].sha256`),
123
+ sizeBytes
124
+ };
125
+ };
126
+ readVersionArtifacts = (version) => {
127
+ if (version.dist.kind === "targeted-bundle") return version.dist.artifacts;
128
+ return [{
129
+ target: { kind: "universal" },
130
+ bundle: version.dist.artifact ?? version.dist.source ?? version.dist.bundle,
131
+ sha256: version.dist.sha256
132
+ }];
133
+ };
134
+ readRequestedVersion = (document, version, registryUrl, appId) => {
135
+ const versionRecord = document.versions[version];
136
+ if (!versionRecord) throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);
137
+ return {
138
+ version,
139
+ artifacts: this.readVersionArtifacts(versionRecord)
140
+ };
141
+ };
142
+ selectDefaultVersion = (document, hostTarget) => {
143
+ const latestVersion = document["dist-tags"].latest;
144
+ const latestRecord = document.versions[latestVersion];
145
+ if (!latestRecord) throw new Error(`registry metadata 的 dist-tags.latest 指向不存在的版本 ${latestVersion}。`);
146
+ const latest = {
147
+ version: latestVersion,
148
+ artifacts: this.readVersionArtifacts(latestRecord)
149
+ };
150
+ if (this.platformTargetService.selectArtifact(latest.artifacts, hostTarget)) return latest;
151
+ return this.platformTargetService.selectLatestCompatibleVersion(Object.entries(document.versions).filter(([version]) => this.platformTargetService.compareVersions(version, latestVersion) <= 0).map(([version, versionRecord]) => ({
152
+ version,
153
+ artifacts: this.readVersionArtifacts(versionRecord)
154
+ })), hostTarget);
155
+ };
89
156
  parsePublisher = (rawPublisher, version) => {
90
157
  if (rawPublisher === void 0) return;
91
158
  if (!rawPublisher || typeof rawPublisher !== "object" || Array.isArray(rawPublisher)) throw new Error(`versions.${version}.publisher 必须是对象。`);
@@ -1 +1 @@
1
- {"version":3,"file":"app-remote-registry-client.service.js","names":[],"sources":["../../src/services/app-remote-registry-client.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppRegistryConfigService } from \"./app-registry-config.service.js\";\nimport type {\n AppRemoteRegistryDocument,\n AppRemoteRegistryResolution,\n AppRemoteRegistryVersion,\n} from \"#app-runtime/types/app-remote-registry.types.js\";\n\nconst MAX_REMOTE_BUNDLE_BYTES = 25 * 1024 * 1024;\nconst MAX_REGISTRY_GET_ATTEMPTS = 3;\n\nexport class AppRemoteRegistryClientService {\n constructor(\n private readonly configService: AppRegistryConfigService = new AppRegistryConfigService(),\n ) {}\n\n resolve = async (params: {\n appId: string;\n version?: string;\n registryUrl?: string;\n }): Promise<AppRemoteRegistryResolution> => {\n const { appId, version: requestedVersion, registryUrl: overrideRegistryUrl } = params;\n const registryUrl = overrideRegistryUrl\n ? this.normalizeRegistryUrl(overrideRegistryUrl)\n : (await this.configService.getSnapshot()).currentUrl;\n const metadataUrl = new URL(encodeURIComponent(appId), registryUrl).toString();\n const rawDocument = await this.readGetResponse(\n metadataUrl,\n `无法从 registry 拉取 ${appId} metadata`,\n async (response) => await response.json(),\n );\n const document = this.parseDocument(rawDocument, appId);\n const version = requestedVersion ?? document[\"dist-tags\"].latest;\n const versionRecord = document.versions[version];\n if (!versionRecord) {\n throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);\n }\n return {\n registryUrl,\n metadataUrl,\n appId,\n version,\n description: versionRecord.description ?? document.description,\n publisher: versionRecord.publisher,\n permissions: versionRecord.permissions,\n distributionMode: versionRecord.dist.kind === \"source\" ? \"source\" : \"bundle\",\n bundleUrl: new URL(\n versionRecord.dist.artifact ??\n versionRecord.dist.source ??\n versionRecord.dist.bundle,\n metadataUrl,\n ).toString(),\n sha256: versionRecord.dist.sha256,\n };\n };\n\n downloadBundle = async (params: {\n resolution: AppRemoteRegistryResolution;\n targetDirectory: string;\n }): Promise<{\n bundlePath: string;\n }> => {\n const { resolution, targetDirectory } = params;\n const bundleBytes = await this.readGetResponse(\n resolution.bundleUrl,\n `无法下载 bundle:${resolution.bundleUrl}`,\n async (response) => {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > MAX_REMOTE_BUNDLE_BYTES) {\n throw new Error(`远程 bundle 超过 ${MAX_REMOTE_BUNDLE_BYTES} bytes 上限。`);\n }\n const bytes = Buffer.from(await response.arrayBuffer());\n if (bytes.byteLength > MAX_REMOTE_BUNDLE_BYTES) {\n throw new Error(`远程 bundle 超过 ${MAX_REMOTE_BUNDLE_BYTES} bytes 上限。`);\n }\n return bytes;\n },\n );\n const actualSha256 = createHash(\"sha256\").update(bundleBytes).digest(\"hex\");\n if (actualSha256 !== resolution.sha256) {\n throw new Error(\n `bundle checksum 校验失败:期望 ${resolution.sha256},实际 ${actualSha256}`,\n );\n }\n const bundlePath = path.join(\n targetDirectory,\n `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}.napp`,\n );\n await writeFile(bundlePath, bundleBytes);\n return {\n bundlePath,\n };\n };\n\n private parseDocument = (\n rawDocument: unknown,\n appId: string,\n ): AppRemoteRegistryDocument => {\n if (!rawDocument || typeof rawDocument !== \"object\" || Array.isArray(rawDocument)) {\n throw new Error(`registry ${appId} metadata 必须是对象。`);\n }\n const candidate = rawDocument as Record<string, unknown>;\n const name = this.readRequiredString(candidate.name, \"name\");\n if (name !== appId) {\n throw new Error(`registry 返回的包名与请求不一致:请求 ${appId},收到 ${name}`);\n }\n const distTags = candidate[\"dist-tags\"];\n if (!distTags || typeof distTags !== \"object\" || Array.isArray(distTags)) {\n throw new Error(\"registry metadata 缺少 dist-tags。\");\n }\n const versions = candidate.versions;\n if (!versions || typeof versions !== \"object\" || Array.isArray(versions)) {\n throw new Error(\"registry metadata 缺少 versions。\");\n }\n return {\n name,\n description: this.readOptionalString(candidate.description, \"description\"),\n \"dist-tags\": {\n latest: this.readRequiredString(\n (distTags as Record<string, unknown>).latest,\n \"dist-tags.latest\",\n ),\n },\n versions: Object.fromEntries(\n Object.entries(versions).map(([version, rawVersion]) => [\n version,\n this.parseVersion(rawVersion, name, version),\n ]),\n ),\n };\n };\n\n private parseVersion = (\n rawVersion: unknown,\n appId: string,\n version: string,\n ): AppRemoteRegistryVersion => {\n if (!rawVersion || typeof rawVersion !== \"object\" || Array.isArray(rawVersion)) {\n throw new Error(`registry metadata 的 versions.${version} 必须是对象。`);\n }\n const candidate = rawVersion as Record<string, unknown>;\n const name = this.readRequiredString(candidate.name, `versions.${version}.name`);\n const parsedVersion = this.readRequiredString(\n candidate.version,\n `versions.${version}.version`,\n );\n if (name !== appId || parsedVersion !== version) {\n throw new Error(`registry metadata 的 versions.${version} 与 app id/version 不一致。`);\n }\n const dist = candidate.dist;\n if (!dist || typeof dist !== \"object\" || Array.isArray(dist)) {\n throw new Error(`registry metadata 的 versions.${version}.dist 必须是对象。`);\n }\n const distCandidate = dist as Record<string, unknown>;\n return {\n name,\n version: parsedVersion,\n description: this.readOptionalString(\n candidate.description,\n `versions.${version}.description`,\n ),\n publisher: this.parsePublisher(candidate.publisher, version),\n permissions: candidate.permissions as AppRemoteRegistryVersion[\"permissions\"],\n dist: {\n kind:\n distCandidate.kind === \"source\" || distCandidate.kind === \"bundle\"\n ? distCandidate.kind\n : undefined,\n artifact: this.readOptionalString(\n distCandidate.artifact,\n `versions.${version}.dist.artifact`,\n ),\n bundle: this.readRequiredString(\n distCandidate.bundle,\n `versions.${version}.dist.bundle`,\n ),\n source: this.readOptionalString(\n distCandidate.source,\n `versions.${version}.dist.source`,\n ),\n sha256: this.readRequiredString(\n distCandidate.sha256,\n `versions.${version}.dist.sha256`,\n ),\n },\n };\n };\n\n private parsePublisher = (\n rawPublisher: unknown,\n version: string,\n ): AppRemoteRegistryVersion[\"publisher\"] => {\n if (rawPublisher === undefined) {\n return undefined;\n }\n if (!rawPublisher || typeof rawPublisher !== \"object\" || Array.isArray(rawPublisher)) {\n throw new Error(`versions.${version}.publisher 必须是对象。`);\n }\n const candidate = rawPublisher as Record<string, unknown>;\n return {\n id: this.readRequiredString(candidate.id, `versions.${version}.publisher.id`),\n name: this.readRequiredString(candidate.name, `versions.${version}.publisher.name`),\n url: this.readOptionalString(candidate.url, `versions.${version}.publisher.url`),\n };\n };\n\n private readRequiredString = (value: unknown, fieldName: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${fieldName} 必须是非空字符串。`);\n }\n return value.trim();\n };\n\n private readOptionalString = (\n value: unknown,\n fieldName: string,\n ): string | undefined => {\n if (value === undefined) {\n return undefined;\n }\n return this.readRequiredString(value, fieldName);\n };\n\n private normalizeBundleFileName = (appId: string): string => {\n return appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n };\n\n private readGetResponse = async <T>(\n url: string,\n operation: string,\n read: (response: Response) => Promise<T>,\n ): Promise<T> => {\n let lastError: unknown;\n for (let attempt = 1; attempt <= MAX_REGISTRY_GET_ATTEMPTS; attempt += 1) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n if (this.isTransientHttpStatus(response.status) && attempt < MAX_REGISTRY_GET_ATTEMPTS) {\n await response.body?.cancel();\n await this.waitBeforeRetry(attempt);\n continue;\n }\n throw new Error(`${operation}:${response.status} ${response.statusText}`);\n }\n try {\n return await read(response);\n } catch (error) {\n if (!this.isTransientNetworkError(error) || attempt === MAX_REGISTRY_GET_ATTEMPTS) {\n throw error;\n }\n lastError = error;\n }\n } catch (error) {\n if (!this.isTransientNetworkError(error) || attempt === MAX_REGISTRY_GET_ATTEMPTS) {\n if (this.isTransientNetworkError(error)) {\n throw new Error(\n `${operation}失败(已尝试 ${MAX_REGISTRY_GET_ATTEMPTS} 次)。`,\n { cause: error },\n );\n }\n throw error;\n }\n lastError = error;\n }\n await this.waitBeforeRetry(attempt);\n }\n throw new Error(\n `${operation}失败(已尝试 ${MAX_REGISTRY_GET_ATTEMPTS} 次)。`,\n { cause: lastError },\n );\n };\n\n private isTransientHttpStatus = (status: number): boolean => {\n return status === 408 || status === 429 || status >= 500;\n };\n\n private isTransientNetworkError = (error: unknown): boolean => {\n return error instanceof TypeError;\n };\n\n private waitBeforeRetry = async (attempt: number): Promise<void> => {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, attempt * 100);\n });\n };\n\n private normalizeRegistryUrl = (registryUrl: string): string => {\n let normalized: URL;\n try {\n normalized = new URL(registryUrl);\n } catch {\n throw new Error(`非法 registry URL:${registryUrl}`);\n }\n if (normalized.protocol !== \"http:\" && normalized.protocol !== \"https:\") {\n throw new Error(`registry URL 只支持 http/https:${registryUrl}`);\n }\n const text = normalized.toString();\n return text.endsWith(\"/\") ? text : `${text}/`;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-remote-registry-client.service.js","names":[],"sources":["../../src/services/app-remote-registry-client.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppRegistryConfigService } from \"./app-registry-config.service.js\";\nimport { AppPlatformTargetService } from \"./app-platform-target.service.js\";\nimport type {\n AppRemoteRegistryArtifact,\n AppRemoteRegistryDocument,\n AppRemoteRegistryResolution,\n AppRemoteRegistryVersion,\n} from \"#app-runtime/types/app-remote-registry.types.js\";\n\nconst MAX_REMOTE_BUNDLE_BYTES = 25 * 1024 * 1024;\nconst MAX_REGISTRY_GET_ATTEMPTS = 3;\n\nexport class AppRemoteRegistryClientService {\n constructor(\n private readonly configService: AppRegistryConfigService = new AppRegistryConfigService(),\n private readonly platformTargetService: AppPlatformTargetService = new AppPlatformTargetService(),\n ) {}\n\n resolve = async (params: {\n appId: string;\n version?: string;\n registryUrl?: string;\n }): Promise<AppRemoteRegistryResolution> => {\n const { appId, version: requestedVersion, registryUrl: overrideRegistryUrl } = params;\n const registryUrl = overrideRegistryUrl\n ? this.normalizeRegistryUrl(overrideRegistryUrl)\n : (await this.configService.getSnapshot()).currentUrl;\n const metadataUrl = new URL(encodeURIComponent(appId), registryUrl).toString();\n const rawDocument = await this.readGetResponse(\n metadataUrl,\n `无法从 registry 拉取 ${appId} metadata`,\n async (response) => await response.json(),\n );\n const document = this.parseDocument(rawDocument, appId);\n const hostTarget = this.platformTargetService.readHostTarget();\n const selectedVersion = requestedVersion\n ? this.readRequestedVersion(document, requestedVersion, registryUrl, appId)\n : this.selectDefaultVersion(document, hostTarget);\n const versionRecord = selectedVersion\n ? document.versions[selectedVersion.version]\n : undefined;\n if (!selectedVersion || !versionRecord) {\n throw new Error(\n requestedVersion\n ? `registry ${registryUrl} 未提供 ${appId}@${requestedVersion}。`\n : `registry ${registryUrl} 没有适用于当前 target ${this.platformTargetService.toTargetKey(hostTarget)} 的 ${appId} 版本。`,\n );\n }\n const version = selectedVersion.version;\n const artifact = this.platformTargetService.selectArtifact(\n this.readVersionArtifacts(versionRecord),\n hostTarget,\n );\n if (!artifact) {\n throw new Error(\n `${appId}@${version} 不支持当前 target ${this.platformTargetService.toTargetKey(hostTarget)}。`,\n );\n }\n return {\n registryUrl,\n metadataUrl,\n appId,\n version,\n description: versionRecord.description ?? document.description,\n publisher: versionRecord.publisher,\n permissions: versionRecord.permissions,\n distributionMode:\n versionRecord.dist.kind === \"source\" ? \"source\" : \"bundle\",\n target: artifact.target,\n bundleUrl: new URL(\n artifact.bundle,\n metadataUrl,\n ).toString(),\n sha256: artifact.sha256,\n };\n };\n\n downloadBundle = async (params: {\n resolution: AppRemoteRegistryResolution;\n targetDirectory: string;\n }): Promise<{\n bundlePath: string;\n }> => {\n const { resolution, targetDirectory } = params;\n const bundleBytes = await this.readGetResponse(\n resolution.bundleUrl,\n `无法下载 bundle:${resolution.bundleUrl}`,\n async (response) => {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > MAX_REMOTE_BUNDLE_BYTES) {\n throw new Error(`远程 bundle 超过 ${MAX_REMOTE_BUNDLE_BYTES} bytes 上限。`);\n }\n const bytes = Buffer.from(await response.arrayBuffer());\n if (bytes.byteLength > MAX_REMOTE_BUNDLE_BYTES) {\n throw new Error(`远程 bundle 超过 ${MAX_REMOTE_BUNDLE_BYTES} bytes 上限。`);\n }\n return bytes;\n },\n );\n const actualSha256 = createHash(\"sha256\").update(bundleBytes).digest(\"hex\");\n if (actualSha256 !== resolution.sha256) {\n throw new Error(\n `bundle checksum 校验失败:期望 ${resolution.sha256},实际 ${actualSha256}`,\n );\n }\n const bundlePath = path.join(\n targetDirectory,\n `${this.normalizeBundleFileName(resolution.appId)}-${resolution.version}-${this.platformTargetService.toTargetKey(resolution.target)}.napp`,\n );\n await writeFile(bundlePath, bundleBytes);\n return {\n bundlePath,\n };\n };\n\n private parseDocument = (\n rawDocument: unknown,\n appId: string,\n ): AppRemoteRegistryDocument => {\n if (!rawDocument || typeof rawDocument !== \"object\" || Array.isArray(rawDocument)) {\n throw new Error(`registry ${appId} metadata 必须是对象。`);\n }\n const candidate = rawDocument as Record<string, unknown>;\n const name = this.readRequiredString(candidate.name, \"name\");\n if (name !== appId) {\n throw new Error(`registry 返回的包名与请求不一致:请求 ${appId},收到 ${name}`);\n }\n const distTags = candidate[\"dist-tags\"];\n if (!distTags || typeof distTags !== \"object\" || Array.isArray(distTags)) {\n throw new Error(\"registry metadata 缺少 dist-tags。\");\n }\n const versions = candidate.versions;\n if (!versions || typeof versions !== \"object\" || Array.isArray(versions)) {\n throw new Error(\"registry metadata 缺少 versions。\");\n }\n return {\n name,\n description: this.readOptionalString(candidate.description, \"description\"),\n \"dist-tags\": {\n latest: this.readRequiredString(\n (distTags as Record<string, unknown>).latest,\n \"dist-tags.latest\",\n ),\n },\n versions: Object.fromEntries(\n Object.entries(versions).map(([version, rawVersion]) => [\n version,\n this.parseVersion(rawVersion, name, version),\n ]),\n ),\n };\n };\n\n private parseVersion = (\n rawVersion: unknown,\n appId: string,\n version: string,\n ): AppRemoteRegistryVersion => {\n if (!rawVersion || typeof rawVersion !== \"object\" || Array.isArray(rawVersion)) {\n throw new Error(`registry metadata 的 versions.${version} 必须是对象。`);\n }\n const candidate = rawVersion as Record<string, unknown>;\n const name = this.readRequiredString(candidate.name, `versions.${version}.name`);\n const parsedVersion = this.readRequiredString(\n candidate.version,\n `versions.${version}.version`,\n );\n if (name !== appId || parsedVersion !== version) {\n throw new Error(`registry metadata 的 versions.${version} 与 app id/version 不一致。`);\n }\n const dist = candidate.dist;\n if (!dist || typeof dist !== \"object\" || Array.isArray(dist)) {\n throw new Error(`registry metadata 的 versions.${version}.dist 必须是对象。`);\n }\n const distCandidate = dist as Record<string, unknown>;\n if (distCandidate.kind === \"targeted-bundle\") {\n if (!Array.isArray(distCandidate.artifacts) || distCandidate.artifacts.length === 0) {\n throw new Error(\n `registry metadata 的 versions.${version}.dist.artifacts 必须是非空数组。`,\n );\n }\n const artifacts = distCandidate.artifacts.map((artifact, index) =>\n this.parseTargetedArtifact(artifact, version, index));\n const targetKeys = artifacts.map((artifact) =>\n this.platformTargetService.toTargetKey(artifact.target));\n const duplicate = targetKeys.find(\n (targetKey, index) => targetKeys.indexOf(targetKey) !== index,\n );\n if (duplicate) {\n throw new Error(\n `registry metadata 的 versions.${version}.dist.artifacts 包含重复 target:${duplicate}`,\n );\n }\n return {\n name,\n version: parsedVersion,\n description: this.readOptionalString(\n candidate.description,\n `versions.${version}.description`,\n ),\n publisher: this.parsePublisher(candidate.publisher, version),\n permissions: candidate.permissions as AppRemoteRegistryVersion[\"permissions\"],\n dist: { kind: \"targeted-bundle\", artifacts },\n };\n }\n return {\n name,\n version: parsedVersion,\n description: this.readOptionalString(\n candidate.description,\n `versions.${version}.description`,\n ),\n publisher: this.parsePublisher(candidate.publisher, version),\n permissions: candidate.permissions as AppRemoteRegistryVersion[\"permissions\"],\n dist: {\n kind:\n distCandidate.kind === \"source\" || distCandidate.kind === \"bundle\"\n ? distCandidate.kind\n : undefined,\n artifact: this.readOptionalString(\n distCandidate.artifact,\n `versions.${version}.dist.artifact`,\n ),\n bundle: this.readRequiredString(\n distCandidate.bundle,\n `versions.${version}.dist.bundle`,\n ),\n source: this.readOptionalString(\n distCandidate.source,\n `versions.${version}.dist.source`,\n ),\n sha256: this.readRequiredString(\n distCandidate.sha256,\n `versions.${version}.dist.sha256`,\n ),\n },\n };\n };\n\n private parseTargetedArtifact = (\n rawArtifact: unknown,\n version: string,\n index: number,\n ): AppRemoteRegistryArtifact => {\n if (!rawArtifact || typeof rawArtifact !== \"object\" || Array.isArray(rawArtifact)) {\n throw new Error(\n `versions.${version}.dist.artifacts[${index}] 必须是对象。`,\n );\n }\n const candidate = rawArtifact as Record<string, unknown>;\n const sizeBytes = candidate.sizeBytes;\n if (\n sizeBytes !== undefined &&\n (typeof sizeBytes !== \"number\" || !Number.isSafeInteger(sizeBytes) || sizeBytes < 1)\n ) {\n throw new Error(\n `versions.${version}.dist.artifacts[${index}].sizeBytes 必须是正整数。`,\n );\n }\n return {\n target: this.platformTargetService.parseArtifactTarget(\n candidate.target,\n `versions.${version}.dist.artifacts[${index}].target`,\n ),\n bundle: this.readRequiredString(\n candidate.bundle,\n `versions.${version}.dist.artifacts[${index}].bundle`,\n ),\n sha256: this.readRequiredString(\n candidate.sha256,\n `versions.${version}.dist.artifacts[${index}].sha256`,\n ),\n sizeBytes: sizeBytes as number | undefined,\n };\n };\n\n private readVersionArtifacts = (\n version: AppRemoteRegistryVersion,\n ): AppRemoteRegistryArtifact[] => {\n if (version.dist.kind === \"targeted-bundle\") {\n return version.dist.artifacts;\n }\n return [{\n target: { kind: \"universal\" },\n bundle:\n version.dist.artifact ?? version.dist.source ?? version.dist.bundle,\n sha256: version.dist.sha256,\n }];\n };\n\n private readRequestedVersion = (\n document: AppRemoteRegistryDocument,\n version: string,\n registryUrl: string,\n appId: string,\n ): { version: string; artifacts: AppRemoteRegistryArtifact[] } => {\n const versionRecord = document.versions[version];\n if (!versionRecord) {\n throw new Error(`registry ${registryUrl} 未提供 ${appId}@${version}。`);\n }\n return { version, artifacts: this.readVersionArtifacts(versionRecord) };\n };\n\n private selectDefaultVersion = (\n document: AppRemoteRegistryDocument,\n hostTarget: ReturnType<AppPlatformTargetService[\"readHostTarget\"]>,\n ): { version: string; artifacts: AppRemoteRegistryArtifact[] } | undefined => {\n const latestVersion = document[\"dist-tags\"].latest;\n const latestRecord = document.versions[latestVersion];\n if (!latestRecord) {\n throw new Error(`registry metadata 的 dist-tags.latest 指向不存在的版本 ${latestVersion}。`);\n }\n const latest = {\n version: latestVersion,\n artifacts: this.readVersionArtifacts(latestRecord),\n };\n if (this.platformTargetService.selectArtifact(latest.artifacts, hostTarget)) {\n return latest;\n }\n return this.platformTargetService.selectLatestCompatibleVersion(\n Object.entries(document.versions)\n .filter(([version]) =>\n this.platformTargetService.compareVersions(version, latestVersion) <= 0)\n .map(([version, versionRecord]) => ({\n version,\n artifacts: this.readVersionArtifacts(versionRecord),\n })),\n hostTarget,\n );\n };\n\n private parsePublisher = (\n rawPublisher: unknown,\n version: string,\n ): AppRemoteRegistryVersion[\"publisher\"] => {\n if (rawPublisher === undefined) {\n return undefined;\n }\n if (!rawPublisher || typeof rawPublisher !== \"object\" || Array.isArray(rawPublisher)) {\n throw new Error(`versions.${version}.publisher 必须是对象。`);\n }\n const candidate = rawPublisher as Record<string, unknown>;\n return {\n id: this.readRequiredString(candidate.id, `versions.${version}.publisher.id`),\n name: this.readRequiredString(candidate.name, `versions.${version}.publisher.name`),\n url: this.readOptionalString(candidate.url, `versions.${version}.publisher.url`),\n };\n };\n\n private readRequiredString = (value: unknown, fieldName: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${fieldName} 必须是非空字符串。`);\n }\n return value.trim();\n };\n\n private readOptionalString = (\n value: unknown,\n fieldName: string,\n ): string | undefined => {\n if (value === undefined) {\n return undefined;\n }\n return this.readRequiredString(value, fieldName);\n };\n\n private normalizeBundleFileName = (appId: string): string => {\n return appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n };\n\n private readGetResponse = async <T>(\n url: string,\n operation: string,\n read: (response: Response) => Promise<T>,\n ): Promise<T> => {\n let lastError: unknown;\n for (let attempt = 1; attempt <= MAX_REGISTRY_GET_ATTEMPTS; attempt += 1) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n if (this.isTransientHttpStatus(response.status) && attempt < MAX_REGISTRY_GET_ATTEMPTS) {\n await response.body?.cancel();\n await this.waitBeforeRetry(attempt);\n continue;\n }\n throw new Error(`${operation}:${response.status} ${response.statusText}`);\n }\n try {\n return await read(response);\n } catch (error) {\n if (!this.isTransientNetworkError(error) || attempt === MAX_REGISTRY_GET_ATTEMPTS) {\n throw error;\n }\n lastError = error;\n }\n } catch (error) {\n if (!this.isTransientNetworkError(error) || attempt === MAX_REGISTRY_GET_ATTEMPTS) {\n if (this.isTransientNetworkError(error)) {\n throw new Error(\n `${operation}失败(已尝试 ${MAX_REGISTRY_GET_ATTEMPTS} 次)。`,\n { cause: error },\n );\n }\n throw error;\n }\n lastError = error;\n }\n await this.waitBeforeRetry(attempt);\n }\n throw new Error(\n `${operation}失败(已尝试 ${MAX_REGISTRY_GET_ATTEMPTS} 次)。`,\n { cause: lastError },\n );\n };\n\n private isTransientHttpStatus = (status: number): boolean => {\n return status === 408 || status === 429 || status >= 500;\n };\n\n private isTransientNetworkError = (error: unknown): boolean => {\n return error instanceof TypeError;\n };\n\n private waitBeforeRetry = async (attempt: number): Promise<void> => {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, attempt * 100);\n });\n };\n\n private normalizeRegistryUrl = (registryUrl: string): string => {\n let normalized: URL;\n try {\n normalized = new URL(registryUrl);\n } catch {\n throw new Error(`非法 registry URL:${registryUrl}`);\n }\n if (normalized.protocol !== \"http:\" && normalized.protocol !== \"https:\") {\n throw new Error(`registry URL 只支持 http/https:${registryUrl}`);\n }\n const text = normalized.toString();\n return text.endsWith(\"/\") ? text : `${text}/`;\n };\n}\n"],"mappings":";;;;;;AAYA,MAAM,0BAA0B,KAAK,OAAO;AAC5C,MAAM,4BAA4B;AAElC,IAAa,iCAAb,MAA4C;CAC1C,YACE,gBAA2D,IAAI,0BAA0B,EACzF,wBAAmE,IAAI,0BAA0B,EACjG;AAFiB,OAAA,gBAAA;AACA,OAAA,wBAAA;;CAGnB,UAAU,OAAO,WAI2B;EAC1C,MAAM,EAAE,OAAO,SAAS,kBAAkB,aAAa,wBAAwB;EAC/E,MAAM,cAAc,sBAChB,KAAK,qBAAqB,oBAAoB,IAC7C,MAAM,KAAK,cAAc,aAAa,EAAE;EAC7C,MAAM,cAAc,IAAI,IAAI,mBAAmB,MAAM,EAAE,YAAY,CAAC,UAAU;EAC9E,MAAM,cAAc,MAAM,KAAK,gBAC7B,aACA,mBAAmB,MAAM,YACzB,OAAO,aAAa,MAAM,SAAS,MAAM,CAC1C;EACD,MAAM,WAAW,KAAK,cAAc,aAAa,MAAM;EACvD,MAAM,aAAa,KAAK,sBAAsB,gBAAgB;EAC9D,MAAM,kBAAkB,mBACpB,KAAK,qBAAqB,UAAU,kBAAkB,aAAa,MAAM,GACzE,KAAK,qBAAqB,UAAU,WAAW;EACnD,MAAM,gBAAgB,kBAClB,SAAS,SAAS,gBAAgB,WAClC,KAAA;AACJ,MAAI,CAAC,mBAAmB,CAAC,cACvB,OAAM,IAAI,MACR,mBACI,YAAY,YAAY,OAAO,MAAM,GAAG,iBAAiB,KACzD,YAAY,YAAY,kBAAkB,KAAK,sBAAsB,YAAY,WAAW,CAAC,KAAK,MAAM,MAC7G;EAEH,MAAM,UAAU,gBAAgB;EAChC,MAAM,WAAW,KAAK,sBAAsB,eAC1C,KAAK,qBAAqB,cAAc,EACxC,WACD;AACD,MAAI,CAAC,SACH,OAAM,IAAI,MACR,GAAG,MAAM,GAAG,QAAQ,gBAAgB,KAAK,sBAAsB,YAAY,WAAW,CAAC,GACxF;AAEH,SAAO;GACL;GACA;GACA;GACA;GACA,aAAa,cAAc,eAAe,SAAS;GACnD,WAAW,cAAc;GACzB,aAAa,cAAc;GAC3B,kBACE,cAAc,KAAK,SAAS,WAAW,WAAW;GACpD,QAAQ,SAAS;GACjB,WAAW,IAAI,IACb,SAAS,QACT,YACD,CAAC,UAAU;GACZ,QAAQ,SAAS;GAClB;;CAGH,iBAAiB,OAAO,WAKlB;EACJ,MAAM,EAAE,YAAY,oBAAoB;EACxC,MAAM,cAAc,MAAM,KAAK,gBAC7B,WAAW,WACX,eAAe,WAAW,aAC1B,OAAO,aAAa;GAClB,MAAM,gBAAgB,OAAO,SAAS,QAAQ,IAAI,iBAAiB,CAAC;AACpE,OAAI,OAAO,SAAS,cAAc,IAAI,gBAAgB,wBACpD,OAAM,IAAI,MAAM,gBAAgB,wBAAwB,YAAY;GAEtE,MAAM,QAAQ,OAAO,KAAK,MAAM,SAAS,aAAa,CAAC;AACvD,OAAI,MAAM,aAAa,wBACrB,OAAM,IAAI,MAAM,gBAAgB,wBAAwB,YAAY;AAEtE,UAAO;IAEV;EACD,MAAM,eAAe,WAAW,SAAS,CAAC,OAAO,YAAY,CAAC,OAAO,MAAM;AAC3E,MAAI,iBAAiB,WAAW,OAC9B,OAAM,IAAI,MACR,2BAA2B,WAAW,OAAO,MAAM,eACpD;EAEH,MAAM,aAAa,KAAK,KACtB,iBACA,GAAG,KAAK,wBAAwB,WAAW,MAAM,CAAC,GAAG,WAAW,QAAQ,GAAG,KAAK,sBAAsB,YAAY,WAAW,OAAO,CAAC,OACtI;AACD,QAAM,UAAU,YAAY,YAAY;AACxC,SAAO,EACL,YACD;;CAGH,iBACE,aACA,UAC8B;AAC9B,MAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY,MAAM,QAAQ,YAAY,CAC/E,OAAM,IAAI,MAAM,YAAY,MAAM,kBAAkB;EAEtD,MAAM,YAAY;EAClB,MAAM,OAAO,KAAK,mBAAmB,UAAU,MAAM,OAAO;AAC5D,MAAI,SAAS,MACX,OAAM,IAAI,MAAM,2BAA2B,MAAM,MAAM,OAAO;EAEhE,MAAM,WAAW,UAAU;AAC3B,MAAI,CAAC,YAAY,OAAO,aAAa,YAAY,MAAM,QAAQ,SAAS,CACtE,OAAM,IAAI,MAAM,kCAAkC;EAEpD,MAAM,WAAW,UAAU;AAC3B,MAAI,CAAC,YAAY,OAAO,aAAa,YAAY,MAAM,QAAQ,SAAS,CACtE,OAAM,IAAI,MAAM,iCAAiC;AAEnD,SAAO;GACL;GACA,aAAa,KAAK,mBAAmB,UAAU,aAAa,cAAc;GAC1E,aAAa,EACX,QAAQ,KAAK,mBACV,SAAqC,QACtC,mBACD,EACF;GACD,UAAU,OAAO,YACf,OAAO,QAAQ,SAAS,CAAC,KAAK,CAAC,SAAS,gBAAgB,CACtD,SACA,KAAK,aAAa,YAAY,MAAM,QAAQ,CAC7C,CAAC,CACH;GACF;;CAGH,gBACE,YACA,OACA,YAC6B;AAC7B,MAAI,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,WAAW,CAC5E,OAAM,IAAI,MAAM,gCAAgC,QAAQ,SAAS;EAEnE,MAAM,YAAY;EAClB,MAAM,OAAO,KAAK,mBAAmB,UAAU,MAAM,YAAY,QAAQ,OAAO;EAChF,MAAM,gBAAgB,KAAK,mBACzB,UAAU,SACV,YAAY,QAAQ,UACrB;AACD,MAAI,SAAS,SAAS,kBAAkB,QACtC,OAAM,IAAI,MAAM,gCAAgC,QAAQ,wBAAwB;EAElF,MAAM,OAAO,UAAU;AACvB,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAC1D,OAAM,IAAI,MAAM,gCAAgC,QAAQ,cAAc;EAExE,MAAM,gBAAgB;AACtB,MAAI,cAAc,SAAS,mBAAmB;AAC5C,OAAI,CAAC,MAAM,QAAQ,cAAc,UAAU,IAAI,cAAc,UAAU,WAAW,EAChF,OAAM,IAAI,MACR,gCAAgC,QAAQ,0BACzC;GAEH,MAAM,YAAY,cAAc,UAAU,KAAK,UAAU,UACvD,KAAK,sBAAsB,UAAU,SAAS,MAAM,CAAC;GACvD,MAAM,aAAa,UAAU,KAAK,aAChC,KAAK,sBAAsB,YAAY,SAAS,OAAO,CAAC;GAC1D,MAAM,YAAY,WAAW,MAC1B,WAAW,UAAU,WAAW,QAAQ,UAAU,KAAK,MACzD;AACD,OAAI,UACF,OAAM,IAAI,MACR,gCAAgC,QAAQ,8BAA8B,YACvE;AAEH,UAAO;IACL;IACA,SAAS;IACT,aAAa,KAAK,mBAChB,UAAU,aACV,YAAY,QAAQ,cACrB;IACD,WAAW,KAAK,eAAe,UAAU,WAAW,QAAQ;IAC5D,aAAa,UAAU;IACvB,MAAM;KAAE,MAAM;KAAmB;KAAW;IAC7C;;AAEH,SAAO;GACL;GACA,SAAS;GACT,aAAa,KAAK,mBAChB,UAAU,aACV,YAAY,QAAQ,cACrB;GACD,WAAW,KAAK,eAAe,UAAU,WAAW,QAAQ;GAC5D,aAAa,UAAU;GACvB,MAAM;IACJ,MACE,cAAc,SAAS,YAAY,cAAc,SAAS,WACtD,cAAc,OACd,KAAA;IACN,UAAU,KAAK,mBACb,cAAc,UACd,YAAY,QAAQ,gBACrB;IACD,QAAQ,KAAK,mBACX,cAAc,QACd,YAAY,QAAQ,cACrB;IACD,QAAQ,KAAK,mBACX,cAAc,QACd,YAAY,QAAQ,cACrB;IACD,QAAQ,KAAK,mBACX,cAAc,QACd,YAAY,QAAQ,cACrB;IACF;GACF;;CAGH,yBACE,aACA,SACA,UAC8B;AAC9B,MAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY,MAAM,QAAQ,YAAY,CAC/E,OAAM,IAAI,MACR,YAAY,QAAQ,kBAAkB,MAAM,UAC7C;EAEH,MAAM,YAAY;EAClB,MAAM,YAAY,UAAU;AAC5B,MACE,cAAc,KAAA,MACb,OAAO,cAAc,YAAY,CAAC,OAAO,cAAc,UAAU,IAAI,YAAY,GAElF,OAAM,IAAI,MACR,YAAY,QAAQ,kBAAkB,MAAM,qBAC7C;AAEH,SAAO;GACL,QAAQ,KAAK,sBAAsB,oBACjC,UAAU,QACV,YAAY,QAAQ,kBAAkB,MAAM,UAC7C;GACD,QAAQ,KAAK,mBACX,UAAU,QACV,YAAY,QAAQ,kBAAkB,MAAM,UAC7C;GACD,QAAQ,KAAK,mBACX,UAAU,QACV,YAAY,QAAQ,kBAAkB,MAAM,UAC7C;GACU;GACZ;;CAGH,wBACE,YACgC;AAChC,MAAI,QAAQ,KAAK,SAAS,kBACxB,QAAO,QAAQ,KAAK;AAEtB,SAAO,CAAC;GACN,QAAQ,EAAE,MAAM,aAAa;GAC7B,QACE,QAAQ,KAAK,YAAY,QAAQ,KAAK,UAAU,QAAQ,KAAK;GAC/D,QAAQ,QAAQ,KAAK;GACtB,CAAC;;CAGJ,wBACE,UACA,SACA,aACA,UACgE;EAChE,MAAM,gBAAgB,SAAS,SAAS;AACxC,MAAI,CAAC,cACH,OAAM,IAAI,MAAM,YAAY,YAAY,OAAO,MAAM,GAAG,QAAQ,GAAG;AAErE,SAAO;GAAE;GAAS,WAAW,KAAK,qBAAqB,cAAc;GAAE;;CAGzE,wBACE,UACA,eAC4E;EAC5E,MAAM,gBAAgB,SAAS,aAAa;EAC5C,MAAM,eAAe,SAAS,SAAS;AACvC,MAAI,CAAC,aACH,OAAM,IAAI,MAAM,iDAAiD,cAAc,GAAG;EAEpF,MAAM,SAAS;GACb,SAAS;GACT,WAAW,KAAK,qBAAqB,aAAa;GACnD;AACD,MAAI,KAAK,sBAAsB,eAAe,OAAO,WAAW,WAAW,CACzE,QAAO;AAET,SAAO,KAAK,sBAAsB,8BAChC,OAAO,QAAQ,SAAS,SAAS,CAC9B,QAAQ,CAAC,aACR,KAAK,sBAAsB,gBAAgB,SAAS,cAAc,IAAI,EAAE,CACzE,KAAK,CAAC,SAAS,oBAAoB;GAClC;GACA,WAAW,KAAK,qBAAqB,cAAc;GACpD,EAAE,EACL,WACD;;CAGH,kBACE,cACA,YAC0C;AAC1C,MAAI,iBAAiB,KAAA,EACnB;AAEF,MAAI,CAAC,gBAAgB,OAAO,iBAAiB,YAAY,MAAM,QAAQ,aAAa,CAClF,OAAM,IAAI,MAAM,YAAY,QAAQ,mBAAmB;EAEzD,MAAM,YAAY;AAClB,SAAO;GACL,IAAI,KAAK,mBAAmB,UAAU,IAAI,YAAY,QAAQ,eAAe;GAC7E,MAAM,KAAK,mBAAmB,UAAU,MAAM,YAAY,QAAQ,iBAAiB;GACnF,KAAK,KAAK,mBAAmB,UAAU,KAAK,YAAY,QAAQ,gBAAgB;GACjF;;CAGH,sBAA8B,OAAgB,cAA8B;AAC1E,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,CAC5C,OAAM,IAAI,MAAM,GAAG,UAAU,YAAY;AAE3C,SAAO,MAAM,MAAM;;CAGrB,sBACE,OACA,cACuB;AACvB,MAAI,UAAU,KAAA,EACZ;AAEF,SAAO,KAAK,mBAAmB,OAAO,UAAU;;CAGlD,2BAAmC,UAA0B;AAC3D,SAAO,MAAM,QAAQ,qBAAqB,IAAI;;CAGhD,kBAA0B,OACxB,KACA,WACA,SACe;EACf,IAAI;AACJ,OAAK,IAAI,UAAU,GAAG,WAAW,2BAA2B,WAAW,GAAG;AACxE,OAAI;IACF,MAAM,WAAW,MAAM,MAAM,IAAI;AACjC,QAAI,CAAC,SAAS,IAAI;AAChB,SAAI,KAAK,sBAAsB,SAAS,OAAO,IAAI,UAAU,2BAA2B;AACtF,YAAM,SAAS,MAAM,QAAQ;AAC7B,YAAM,KAAK,gBAAgB,QAAQ;AACnC;;AAEF,WAAM,IAAI,MAAM,GAAG,UAAU,GAAG,SAAS,OAAO,GAAG,SAAS,aAAa;;AAE3E,QAAI;AACF,YAAO,MAAM,KAAK,SAAS;aACpB,OAAO;AACd,SAAI,CAAC,KAAK,wBAAwB,MAAM,IAAI,YAAY,0BACtD,OAAM;AAER,iBAAY;;YAEP,OAAO;AACd,QAAI,CAAC,KAAK,wBAAwB,MAAM,IAAI,YAAY,2BAA2B;AACjF,SAAI,KAAK,wBAAwB,MAAM,CACrC,OAAM,IAAI,MACR,GAAG,UAAU,SAAS,0BAA0B,OAChD,EAAE,OAAO,OAAO,CACjB;AAEH,WAAM;;AAER,gBAAY;;AAEd,SAAM,KAAK,gBAAgB,QAAQ;;AAErC,QAAM,IAAI,MACR,GAAG,UAAU,SAAS,0BAA0B,OAChD,EAAE,OAAO,WAAW,CACrB;;CAGH,yBAAiC,WAA4B;AAC3D,SAAO,WAAW,OAAO,WAAW,OAAO,UAAU;;CAGvD,2BAAmC,UAA4B;AAC7D,SAAO,iBAAiB;;CAG1B,kBAA0B,OAAO,YAAmC;AAClE,QAAM,IAAI,SAAe,YAAY;AACnC,cAAW,SAAS,UAAU,IAAI;IAClC;;CAGJ,wBAAgC,gBAAgC;EAC9D,IAAI;AACJ,MAAI;AACF,gBAAa,IAAI,IAAI,YAAY;UAC3B;AACN,SAAM,IAAI,MAAM,mBAAmB,cAAc;;AAEnD,MAAI,WAAW,aAAa,WAAW,WAAW,aAAa,SAC7D,OAAM,IAAI,MAAM,+BAA+B,cAAc;EAE/D,MAAM,OAAO,WAAW,UAAU;AAClC,SAAO,KAAK,SAAS,IAAI,GAAG,OAAO,GAAG,KAAK"}
@@ -0,0 +1,19 @@
1
+ import { AppNativeArtifactTarget } from "../types/app-manifest.types.js";
2
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
3
+
4
+ //#region src/services/app-service-launch.service.d.ts
5
+ type AppServiceLaunch = {
6
+ command: string;
7
+ args: string[];
8
+ };
9
+ declare class AppServiceLaunchService {
10
+ private readonly platformTargetService;
11
+ constructor(platformTargetService?: AppPlatformTargetService);
12
+ resolve: (manifest: Record<string, unknown>, target?: AppNativeArtifactTarget) => AppServiceLaunch;
13
+ private readRequiredString;
14
+ private readStringArray;
15
+ private assertObject;
16
+ }
17
+ //#endregion
18
+ export { AppServiceLaunch, AppServiceLaunchService };
19
+ //# sourceMappingURL=app-service-launch.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-service-launch.service.d.ts","names":[],"sources":["../../src/services/app-service-launch.service.ts"],"mappings":";;;;KAGY,gBAAA;EACV,OAAA;EACA,IAAA;AAAA;AAAA,cAGW,uBAAA;EAAA,iBAEQ,qBAAA;cAAA,qBAAA,GAAqB,wBAAA;EAGxC,OAAA,GACE,QAAA,EAAU,MAAA,mBACV,MAAA,GAAS,uBAAA,KACR,gBAAA;EAAA,QAgEK,kBAAA;EAAA,QAOA,eAAA;EAAA,QAUA,YAAA;AAAA"}
@@ -0,0 +1,54 @@
1
+ import { AppPlatformTargetService } from "./app-platform-target.service.js";
2
+ //#region src/services/app-service-launch.service.ts
3
+ var AppServiceLaunchService = class {
4
+ constructor(platformTargetService = new AppPlatformTargetService()) {
5
+ this.platformTargetService = platformTargetService;
6
+ }
7
+ resolve = (manifest, target) => {
8
+ const hasCommand = manifest.command !== void 0;
9
+ if (hasCommand === (manifest.launch !== void 0)) throw new Error("service app 必须且只能声明 command/args 或 launch.targets 其中一种启动方式。");
10
+ if (hasCommand) return {
11
+ command: this.readRequiredString(manifest.command, "service app command"),
12
+ args: this.readStringArray(manifest.args, "service app args")
13
+ };
14
+ const launch = this.assertObject(manifest.launch, "service app launch");
15
+ if (!Array.isArray(launch.targets) || launch.targets.length === 0) throw new Error("service app launch.targets 必须是非空数组。");
16
+ const targets = launch.targets.map((rawTarget, index) => {
17
+ const candidate = this.assertObject(rawTarget, `service app launch.targets[${index}]`);
18
+ const parsedTarget = this.platformTargetService.parseArtifactTarget(candidate.target, `service app launch.targets[${index}].target`);
19
+ if (parsedTarget.kind !== "native") throw new Error(`service app launch.targets[${index}].target 必须是 native target。`);
20
+ return {
21
+ target: parsedTarget,
22
+ command: this.readRequiredString(candidate.command, `service app launch.targets[${index}].command`),
23
+ args: this.readStringArray(candidate.args, `service app launch.targets[${index}].args`)
24
+ };
25
+ });
26
+ const targetKeys = targets.map((entry) => this.platformTargetService.toTargetKey(entry.target));
27
+ const duplicate = targetKeys.find((targetKey, index) => targetKeys.indexOf(targetKey) !== index);
28
+ if (duplicate) throw new Error(`service app launch.targets 包含重复 target:${duplicate}`);
29
+ const selectedTarget = target ?? this.platformTargetService.readHostTarget();
30
+ const selected = this.platformTargetService.selectArtifact(targets, selectedTarget);
31
+ if (!selected) throw new Error(`service app 不支持当前 target:${this.platformTargetService.toTargetKey(selectedTarget)}。`);
32
+ return {
33
+ command: selected.command,
34
+ args: selected.args
35
+ };
36
+ };
37
+ readRequiredString = (value, fieldName) => {
38
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${fieldName} 必须是非空字符串。`);
39
+ return value.trim();
40
+ };
41
+ readStringArray = (value, fieldName) => {
42
+ if (value === void 0) return [];
43
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${fieldName} 必须是字符串数组。`);
44
+ return value;
45
+ };
46
+ assertObject = (value, fieldName) => {
47
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${fieldName} 必须是对象。`);
48
+ return value;
49
+ };
50
+ };
51
+ //#endregion
52
+ export { AppServiceLaunchService };
53
+
54
+ //# sourceMappingURL=app-service-launch.service.js.map