@nextclaw/app-runtime 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/controllers/publish.controller.js +7 -0
  2. package/dist/controllers/publish.controller.js.map +1 -1
  3. package/dist/index.d.ts +5 -4
  4. package/dist/index.js +2 -1
  5. package/dist/package.js +1 -1
  6. package/dist/services/app-artifact-validation.service.d.ts +46 -0
  7. package/dist/services/app-artifact-validation.service.d.ts.map +1 -0
  8. package/dist/services/app-artifact-validation.service.js +250 -0
  9. package/dist/services/app-artifact-validation.service.js.map +1 -0
  10. package/dist/services/app-bundle.service.d.ts +4 -8
  11. package/dist/services/app-bundle.service.d.ts.map +1 -1
  12. package/dist/services/app-bundle.service.js +7 -66
  13. package/dist/services/app-bundle.service.js.map +1 -1
  14. package/dist/services/app-home.service.d.ts +1 -0
  15. package/dist/services/app-home.service.d.ts.map +1 -1
  16. package/dist/services/app-home.service.js +3 -0
  17. package/dist/services/app-home.service.js.map +1 -1
  18. package/dist/services/app-installation.service.d.ts +7 -1
  19. package/dist/services/app-installation.service.d.ts.map +1 -1
  20. package/dist/services/app-installation.service.js +84 -5
  21. package/dist/services/app-installation.service.js.map +1 -1
  22. package/dist/services/app-marketplace-metadata.service.d.ts +5 -1
  23. package/dist/services/app-marketplace-metadata.service.d.ts.map +1 -1
  24. package/dist/services/app-marketplace-metadata.service.js +44 -2
  25. package/dist/services/app-marketplace-metadata.service.js.map +1 -1
  26. package/dist/services/app-publish-validation.service.d.ts +1 -1
  27. package/dist/services/app-publish.service.d.ts +2 -1
  28. package/dist/services/app-publish.service.d.ts.map +1 -1
  29. package/dist/services/app-publish.service.js +6 -2
  30. package/dist/services/app-publish.service.js.map +1 -1
  31. package/dist/services/app-registry.service.d.ts +2 -0
  32. package/dist/services/app-registry.service.d.ts.map +1 -1
  33. package/dist/services/app-registry.service.js +20 -2
  34. package/dist/services/app-registry.service.js.map +1 -1
  35. package/dist/types/app-installation.types.d.ts +4 -2
  36. package/dist/types/app-installation.types.d.ts.map +1 -1
  37. package/dist/types/app-publish.types.d.ts +11 -2
  38. package/dist/types/app-publish.types.d.ts.map +1 -1
  39. package/dist/types/app-publish.types.js.map +1 -1
  40. package/dist/types/app-registry.types.d.ts +4 -1
  41. package/dist/types/app-registry.types.d.ts.map +1 -1
  42. package/dist/types/app-remote-registry.types.d.ts +1 -1
  43. package/package.json +6 -1
@@ -2,6 +2,14 @@ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { existsSync } from "node:fs";
4
4
  //#region src/services/app-marketplace-metadata.service.ts
5
+ const COVER_EXTENSIONS = new Set([
6
+ ".avif",
7
+ ".jpg",
8
+ ".jpeg",
9
+ ".png",
10
+ ".webp"
11
+ ]);
12
+ const MAX_COVER_BYTES = 512 * 1024;
5
13
  var AppMarketplaceMetadataService = class {
6
14
  load = async (params) => {
7
15
  const { appDirectory, manifest, metadataPath: customMetadataPath } = params;
@@ -10,9 +18,10 @@ var AppMarketplaceMetadataService = class {
10
18
  return this.parseMetadata(raw, manifest);
11
19
  };
12
20
  collectPublishFiles = async (params) => {
21
+ const { iconPath, metadataPath: customMetadataPath, visuals } = params;
13
22
  const appDirectory = path.resolve(params.appDirectory);
14
23
  const publishFiles = [];
15
- const metadataPath = params.metadataPath ? path.resolve(params.metadataPath) : path.join(appDirectory, "marketplace.json");
24
+ const metadataPath = customMetadataPath ? path.resolve(customMetadataPath) : path.join(appDirectory, "marketplace.json");
16
25
  publishFiles.push({
17
26
  path: "marketplace.json",
18
27
  bytes: Buffer.from(await readFile(metadataPath))
@@ -22,6 +31,19 @@ var AppMarketplaceMetadataService = class {
22
31
  path: "README.md",
23
32
  bytes: Buffer.from(await readFile(readmePath))
24
33
  });
34
+ if (iconPath) publishFiles.push({
35
+ path: iconPath,
36
+ bytes: Buffer.from(await readFile(path.join(appDirectory, iconPath)))
37
+ });
38
+ if (visuals) {
39
+ const coverPath = path.join(appDirectory, visuals.cover);
40
+ const bytes = Buffer.from(await readFile(coverPath));
41
+ if (bytes.byteLength > MAX_COVER_BYTES) throw new Error(`visuals.cover 不能超过 ${MAX_COVER_BYTES} bytes。`);
42
+ publishFiles.push({
43
+ path: visuals.cover,
44
+ bytes
45
+ });
46
+ }
25
47
  return publishFiles;
26
48
  };
27
49
  parseMetadata = (rawMetadata, manifest) => {
@@ -43,7 +65,21 @@ var AppMarketplaceMetadataService = class {
43
65
  sourceRepo: this.readOptionalString(candidate.sourceRepo, "sourceRepo"),
44
66
  homepage: this.readOptionalString(candidate.homepage, "homepage"),
45
67
  featured: this.readOptionalBoolean(candidate.featured, "featured") ?? false,
46
- publisher
68
+ publisher,
69
+ visuals: this.parseVisuals(candidate.visuals)
70
+ };
71
+ };
72
+ parseVisuals = (rawVisuals) => {
73
+ if (rawVisuals === void 0) return;
74
+ if (!rawVisuals || typeof rawVisuals !== "object" || Array.isArray(rawVisuals)) throw new Error("visuals 必须是对象。");
75
+ const candidate = rawVisuals;
76
+ const cover = this.readSafeRelativePath(candidate.cover, "visuals.cover");
77
+ if (!COVER_EXTENSIONS.has(path.extname(cover).toLowerCase())) throw new Error("visuals.cover 必须是 AVIF、JPEG、PNG 或 WebP 图片。");
78
+ const accentColor = this.readRequiredString(candidate.accentColor, "visuals.accentColor");
79
+ if (!/^#[0-9a-f]{6}$/i.test(accentColor)) throw new Error("visuals.accentColor 必须是六位十六进制颜色。");
80
+ return {
81
+ cover,
82
+ accentColor: accentColor.toUpperCase()
47
83
  };
48
84
  };
49
85
  parsePublisher = (rawPublisher) => {
@@ -64,6 +100,12 @@ var AppMarketplaceMetadataService = class {
64
100
  if (value === void 0) return;
65
101
  return this.readRequiredString(value, fieldName);
66
102
  };
103
+ readSafeRelativePath = (value, fieldName) => {
104
+ const relativePath = this.readRequiredString(value, fieldName).replace(/\\/g, "/");
105
+ const segments = relativePath.split("/");
106
+ if (relativePath.startsWith("/") || /^[A-Za-z]:/.test(relativePath) || segments.some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`${fieldName} 必须是安全的相对路径。`);
107
+ return segments.join("/");
108
+ };
67
109
  readStringArray = (value, fieldName) => {
68
110
  if (!Array.isArray(value) || value.length === 0) throw new Error(`${fieldName} 必须是非空字符串数组。`);
69
111
  return value.map((item, index) => this.readRequiredString(item, `${fieldName}[${index}]`));
@@ -1 +1 @@
1
- {"version":3,"file":"app-marketplace-metadata.service.js","names":[],"sources":["../../src/services/app-marketplace-metadata.service.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifest } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppMarketplaceMetadata } from \"#app-runtime/types/app-publish.types.js\";\n\nexport class AppMarketplaceMetadataService {\n load = async (params: {\n appDirectory: string;\n manifest: AppManifest;\n metadataPath?: string;\n }): Promise<AppMarketplaceMetadata> => {\n const { appDirectory, manifest, metadataPath: customMetadataPath } = params;\n const metadataPath = customMetadataPath\n ? path.resolve(customMetadataPath)\n : path.join(path.resolve(appDirectory), \"marketplace.json\");\n const raw = JSON.parse(await readFile(metadataPath, \"utf-8\")) as unknown;\n return this.parseMetadata(raw, manifest);\n };\n\n collectPublishFiles = async (params: {\n appDirectory: string;\n metadataPath?: string;\n }): Promise<Array<{ path: string; bytes: Buffer }>> => {\n const appDirectory = path.resolve(params.appDirectory);\n const publishFiles: Array<{ path: string; bytes: Buffer }> = [];\n const metadataPath = params.metadataPath\n ? path.resolve(params.metadataPath)\n : path.join(appDirectory, \"marketplace.json\");\n publishFiles.push({\n path: \"marketplace.json\",\n bytes: Buffer.from(await readFile(metadataPath)),\n });\n const readmePath = path.join(appDirectory, \"README.md\");\n if (existsSync(readmePath)) {\n publishFiles.push({\n path: \"README.md\",\n bytes: Buffer.from(await readFile(readmePath)),\n });\n }\n return publishFiles;\n };\n\n private parseMetadata = (\n rawMetadata: unknown,\n manifest: AppManifest,\n ): AppMarketplaceMetadata => {\n if (!rawMetadata || typeof rawMetadata !== \"object\" || Array.isArray(rawMetadata)) {\n throw new Error(\"marketplace.json 必须是对象。\");\n }\n const candidate = rawMetadata as Record<string, unknown>;\n const slug = this.readRequiredString(candidate.slug, \"slug\");\n const summary = this.readRequiredString(candidate.summary, \"summary\");\n const description = this.readOptionalString(candidate.description, \"description\");\n const author =\n this.readOptionalString(candidate.author, \"author\") ?? manifest.name;\n const publisher = this.parsePublisher(candidate.publisher);\n return {\n slug,\n summary,\n summaryI18n: this.readLocalizedTextMap(candidate.summaryI18n, \"summaryI18n\", summary),\n description,\n descriptionI18n: description\n ? this.readLocalizedTextMap(candidate.descriptionI18n, \"descriptionI18n\", description)\n : undefined,\n author,\n tags: this.readStringArray(candidate.tags, \"tags\"),\n sourceRepo: this.readOptionalString(candidate.sourceRepo, \"sourceRepo\"),\n homepage: this.readOptionalString(candidate.homepage, \"homepage\"),\n featured: this.readOptionalBoolean(candidate.featured, \"featured\") ?? false,\n publisher,\n };\n };\n\n private parsePublisher = (\n rawPublisher: unknown,\n ): AppMarketplaceMetadata[\"publisher\"] => {\n if (rawPublisher === undefined) {\n return undefined;\n }\n if (!rawPublisher || typeof rawPublisher !== \"object\" || Array.isArray(rawPublisher)) {\n throw new Error(\"publisher 必须是对象。\");\n }\n const candidate = rawPublisher as Record<string, unknown>;\n return {\n id: this.readRequiredString(candidate.id, \"publisher.id\"),\n name: this.readRequiredString(candidate.name, \"publisher.name\"),\n url: this.readOptionalString(candidate.url, \"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 readStringArray = (value: unknown, fieldName: string): string[] => {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(`${fieldName} 必须是非空字符串数组。`);\n }\n return value.map((item, index) =>\n this.readRequiredString(item, `${fieldName}[${index}]`),\n );\n };\n\n private readOptionalBoolean = (\n value: unknown,\n fieldName: string,\n ): boolean | undefined => {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== \"boolean\") {\n throw new Error(`${fieldName} 必须是布尔值。`);\n }\n return value;\n };\n\n private readLocalizedTextMap = (\n value: unknown,\n fieldName: string,\n fallbackEn: string,\n ): Record<string, string> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n const candidate = value as Record<string, unknown>;\n const normalized = Object.fromEntries(\n Object.entries(candidate).map(([locale, localeValue]) => [\n locale,\n this.readRequiredString(localeValue, `${fieldName}.${locale}`),\n ]),\n );\n if (!normalized.en) {\n normalized.en = fallbackEn;\n }\n return normalized;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-marketplace-metadata.service.js","names":[],"sources":["../../src/services/app-marketplace-metadata.service.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifest } from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppMarketplaceMetadata,\n AppMarketplaceVisuals,\n} from \"#app-runtime/types/app-publish.types.js\";\n\nconst COVER_EXTENSIONS = new Set([\".avif\", \".jpg\", \".jpeg\", \".png\", \".webp\"]);\nconst MAX_COVER_BYTES = 512 * 1024;\n\nexport class AppMarketplaceMetadataService {\n load = async (params: {\n appDirectory: string;\n manifest: AppManifest;\n metadataPath?: string;\n }): Promise<AppMarketplaceMetadata> => {\n const { appDirectory, manifest, metadataPath: customMetadataPath } = params;\n const metadataPath = customMetadataPath\n ? path.resolve(customMetadataPath)\n : path.join(path.resolve(appDirectory), \"marketplace.json\");\n const raw = JSON.parse(await readFile(metadataPath, \"utf-8\")) as unknown;\n return this.parseMetadata(raw, manifest);\n };\n\n collectPublishFiles = async (params: {\n appDirectory: string;\n iconPath?: string;\n metadataPath?: string;\n visuals?: AppMarketplaceVisuals;\n }): Promise<Array<{ path: string; bytes: Buffer }>> => {\n const { iconPath, metadataPath: customMetadataPath, visuals } = params;\n const appDirectory = path.resolve(params.appDirectory);\n const publishFiles: Array<{ path: string; bytes: Buffer }> = [];\n const metadataPath = customMetadataPath\n ? path.resolve(customMetadataPath)\n : path.join(appDirectory, \"marketplace.json\");\n publishFiles.push({\n path: \"marketplace.json\",\n bytes: Buffer.from(await readFile(metadataPath)),\n });\n const readmePath = path.join(appDirectory, \"README.md\");\n if (existsSync(readmePath)) {\n publishFiles.push({\n path: \"README.md\",\n bytes: Buffer.from(await readFile(readmePath)),\n });\n }\n if (iconPath) {\n publishFiles.push({\n path: iconPath,\n bytes: Buffer.from(await readFile(path.join(appDirectory, iconPath))),\n });\n }\n if (visuals) {\n const coverPath = path.join(appDirectory, visuals.cover);\n const bytes = Buffer.from(await readFile(coverPath));\n if (bytes.byteLength > MAX_COVER_BYTES) {\n throw new Error(`visuals.cover 不能超过 ${MAX_COVER_BYTES} bytes。`);\n }\n publishFiles.push({ path: visuals.cover, bytes });\n }\n return publishFiles;\n };\n\n private parseMetadata = (\n rawMetadata: unknown,\n manifest: AppManifest,\n ): AppMarketplaceMetadata => {\n if (!rawMetadata || typeof rawMetadata !== \"object\" || Array.isArray(rawMetadata)) {\n throw new Error(\"marketplace.json 必须是对象。\");\n }\n const candidate = rawMetadata as Record<string, unknown>;\n const slug = this.readRequiredString(candidate.slug, \"slug\");\n const summary = this.readRequiredString(candidate.summary, \"summary\");\n const description = this.readOptionalString(candidate.description, \"description\");\n const author =\n this.readOptionalString(candidate.author, \"author\") ?? manifest.name;\n const publisher = this.parsePublisher(candidate.publisher);\n return {\n slug,\n summary,\n summaryI18n: this.readLocalizedTextMap(candidate.summaryI18n, \"summaryI18n\", summary),\n description,\n descriptionI18n: description\n ? this.readLocalizedTextMap(candidate.descriptionI18n, \"descriptionI18n\", description)\n : undefined,\n author,\n tags: this.readStringArray(candidate.tags, \"tags\"),\n sourceRepo: this.readOptionalString(candidate.sourceRepo, \"sourceRepo\"),\n homepage: this.readOptionalString(candidate.homepage, \"homepage\"),\n featured: this.readOptionalBoolean(candidate.featured, \"featured\") ?? false,\n publisher,\n visuals: this.parseVisuals(candidate.visuals),\n };\n };\n\n private parseVisuals = (rawVisuals: unknown): AppMarketplaceVisuals | undefined => {\n if (rawVisuals === undefined) {\n return undefined;\n }\n if (!rawVisuals || typeof rawVisuals !== \"object\" || Array.isArray(rawVisuals)) {\n throw new Error(\"visuals 必须是对象。\");\n }\n const candidate = rawVisuals as Record<string, unknown>;\n const cover = this.readSafeRelativePath(candidate.cover, \"visuals.cover\");\n if (!COVER_EXTENSIONS.has(path.extname(cover).toLowerCase())) {\n throw new Error(\"visuals.cover 必须是 AVIF、JPEG、PNG 或 WebP 图片。\");\n }\n const accentColor = this.readRequiredString(candidate.accentColor, \"visuals.accentColor\");\n if (!/^#[0-9a-f]{6}$/i.test(accentColor)) {\n throw new Error(\"visuals.accentColor 必须是六位十六进制颜色。\");\n }\n return { cover, accentColor: accentColor.toUpperCase() };\n };\n\n private parsePublisher = (\n rawPublisher: unknown,\n ): AppMarketplaceMetadata[\"publisher\"] => {\n if (rawPublisher === undefined) {\n return undefined;\n }\n if (!rawPublisher || typeof rawPublisher !== \"object\" || Array.isArray(rawPublisher)) {\n throw new Error(\"publisher 必须是对象。\");\n }\n const candidate = rawPublisher as Record<string, unknown>;\n return {\n id: this.readRequiredString(candidate.id, \"publisher.id\"),\n name: this.readRequiredString(candidate.name, \"publisher.name\"),\n url: this.readOptionalString(candidate.url, \"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 readSafeRelativePath = (value: unknown, fieldName: string): string => {\n const relativePath = this.readRequiredString(value, fieldName).replace(/\\\\/g, \"/\");\n const segments = relativePath.split(\"/\");\n if (\n relativePath.startsWith(\"/\") ||\n /^[A-Za-z]:/.test(relativePath) ||\n segments.some((segment) => !segment || segment === \".\" || segment === \"..\")\n ) {\n throw new Error(`${fieldName} 必须是安全的相对路径。`);\n }\n return segments.join(\"/\");\n };\n\n private readStringArray = (value: unknown, fieldName: string): string[] => {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(`${fieldName} 必须是非空字符串数组。`);\n }\n return value.map((item, index) =>\n this.readRequiredString(item, `${fieldName}[${index}]`),\n );\n };\n\n private readOptionalBoolean = (\n value: unknown,\n fieldName: string,\n ): boolean | undefined => {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== \"boolean\") {\n throw new Error(`${fieldName} 必须是布尔值。`);\n }\n return value;\n };\n\n private readLocalizedTextMap = (\n value: unknown,\n fieldName: string,\n fallbackEn: string,\n ): Record<string, string> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n const candidate = value as Record<string, unknown>;\n const normalized = Object.fromEntries(\n Object.entries(candidate).map(([locale, localeValue]) => [\n locale,\n this.readRequiredString(localeValue, `${fieldName}.${locale}`),\n ]),\n );\n if (!normalized.en) {\n normalized.en = fallbackEn;\n }\n return normalized;\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { AppManifestService } from "./app-manifest.service.js";
2
1
  import { AppDistributionMode } from "../types/app-bundle.types.js";
2
+ import { AppManifestService } from "./app-manifest.service.js";
3
3
  import { AppBundleService } from "./app-bundle.service.js";
4
4
  import { AppMarketplaceMetadataService } from "./app-marketplace-metadata.service.js";
5
5
 
@@ -1,5 +1,5 @@
1
- import { AppManifestService } from "./app-manifest.service.js";
2
1
  import { AppDistributionMode } from "../types/app-bundle.types.js";
2
+ import { AppManifestService } from "./app-manifest.service.js";
3
3
  import { AppBundleService } from "./app-bundle.service.js";
4
4
  import { AppPublishResult } from "../types/app-publish.types.js";
5
5
  import { AppMarketplaceClientService } from "./app-marketplace-client.service.js";
@@ -17,6 +17,7 @@ declare class AppPublishService {
17
17
  publish: (params: {
18
18
  appDirectory: string;
19
19
  metadataPath?: string;
20
+ bundleOutputPath?: string;
20
21
  apiBaseUrl?: string;
21
22
  token?: string;
22
23
  mode?: AppDistributionMode;
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.service.d.ts","names":[],"sources":["../../src/services/app-publish.service.ts"],"mappings":";;;;;;;;;cAkBa,iBAAA;EAAA,iBAEQ,eAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;cAJA,eAAA,GAAiB,kBAAA,EACjB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,6BAAA,EACjB,iBAAA,GAAmB,2BAAA,EACnB,gBAAA,GAAkB,wBAAA;EAGrC,OAAA,GAAiB,MAAA;IACf,YAAA;IACA,YAAA;IACA,UAAA;IACA,KAAA;IACA,IAAA,GAAO,mBAAA;EAAA,MACL,OAAA,CAAQ,gBAAA;EAAA,QAoEJ,mBAAA;EAAA,QA6CA,wBAAA;EAAA,QA6CA,sBAAA;EAAA,QAOA,kBAAA;EAAA,QAkBA,sBAAA;AAAA"}
1
+ {"version":3,"file":"app-publish.service.d.ts","names":[],"sources":["../../src/services/app-publish.service.ts"],"mappings":";;;;;;;;;cAkBa,iBAAA;EAAA,iBAEQ,eAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;cAJA,eAAA,GAAiB,kBAAA,EACjB,aAAA,GAAe,gBAAA,EACf,eAAA,GAAiB,6BAAA,EACjB,iBAAA,GAAmB,2BAAA,EACnB,gBAAA,GAAkB,wBAAA;EAGrC,OAAA,GAAiB,MAAA;IACf,YAAA;IACA,YAAA;IACA,gBAAA;IACA,UAAA;IACA,KAAA;IACA,IAAA,GAAO,mBAAA;EAAA,MACL,OAAA,CAAQ,gBAAA;EAAA,QA8EJ,mBAAA;EAAA,QA6CA,wBAAA;EAAA,QA6CA,sBAAA;EAAA,QAOA,kBAAA;EAAA,QAkBA,sBAAA;AAAA"}
@@ -17,7 +17,7 @@ var AppPublishService = class {
17
17
  this.authStateService = authStateService;
18
18
  }
19
19
  publish = async (params) => {
20
- const { appDirectory: inputAppDirectory, metadataPath, apiBaseUrl, token } = params;
20
+ const { appDirectory: inputAppDirectory, metadataPath, bundleOutputPath, apiBaseUrl, token } = params;
21
21
  const appDirectory = path.resolve(inputAppDirectory);
22
22
  const manifestBundle = await this.manifestService.load(appDirectory);
23
23
  const distributionMode = params.mode ?? (manifestBundle.manifest.schemaVersion === 2 ? "bundle" : "source");
@@ -33,13 +33,16 @@ var AppPublishService = class {
33
33
  });
34
34
  const bundle = await this.bundleService.packAppDirectory({
35
35
  appDirectory,
36
+ outputPath: bundleOutputPath,
36
37
  mode: distributionMode
37
38
  });
38
39
  const bundleBytes = Buffer.from(await readFile(bundle.bundlePath));
39
40
  const bundleSha256 = createHash("sha256").update(bundleBytes).digest("hex");
40
41
  const publishFiles = await this.metadataService.collectPublishFiles({
41
42
  appDirectory,
42
- metadataPath
43
+ iconPath: manifestBundle.manifest.icon,
44
+ metadataPath,
45
+ visuals: metadata.visuals
43
46
  });
44
47
  const payload = {
45
48
  slug: metadata.slug,
@@ -56,6 +59,7 @@ var AppPublishService = class {
56
59
  homepage: metadata.homepage,
57
60
  featured: metadata.featured ?? false,
58
61
  publisher: actor.publisher,
62
+ visuals: metadata.visuals,
59
63
  distributionMode,
60
64
  manifest: manifestBundle.manifest,
61
65
  permissions: manifestBundle.manifest.schemaVersion === 1 ? manifestBundle.manifest.permissions ?? {} : {},
@@ -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 apiBaseUrl?: string;\n token?: string;\n mode?: AppDistributionMode;\n }): Promise<AppPublishResult> => {\n const { appDirectory: inputAppDirectory, metadataPath, apiBaseUrl, token } = 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 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 metadataPath,\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 distributionMode,\n manifest: manifestBundle.manifest,\n permissions: manifestBundle.manifest.schemaVersion === 1\n ? manifestBundle.manifest.permissions ?? {}\n : {},\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 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 : {},\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":";;;;;;;;;AAWA,MAAM,4BAA4B;AAOlC,IAAa,oBAAb,MAA+B;CAC7B,YACE,kBAAuD,IAAI,oBAAoB,EAC/E,gBAAmD,IAAI,kBAAkB,EACzE,kBAAkE,IAAI,+BAA+B,EACrG,oBAAkE,IAAI,6BAA6B,EACnG,mBAA8D,IAAI,0BAA0B,EAC5F;AALiB,OAAA,kBAAA;AACA,OAAA,gBAAA;AACA,OAAA,kBAAA;AACA,OAAA,oBAAA;AACA,OAAA,mBAAA;;CAGnB,UAAU,OAAO,WAOgB;EAC/B,MAAM,EACJ,cAAc,mBACd,cACA,kBACA,YACA,UACE;EACJ,MAAM,eAAe,KAAK,QAAQ,kBAAkB;EACpD,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,KAAK,aAAa;EACpE,MAAM,mBAAmB,OAAO,SAC7B,eAAe,SAAS,kBAAkB,IAAI,WAAW;EAC5D,MAAM,WAAW,MAAM,KAAK,gBAAgB,KAAK;GAC/C;GACA,UAAU,eAAe;GACzB;GACD,CAAC;EACF,MAAM,QAAQ,MAAM,KAAK,oBAAoB;GAC3C;GACA,eAAe;GACf,OAAO,eAAe,SAAS;GAChC,CAAC;EACF,MAAM,SAAS,MAAM,KAAK,cAAc,iBAAiB;GACvD;GACA,YAAY;GACZ,MAAM;GACP,CAAC;EACF,MAAM,cAAc,OAAO,KAAK,MAAM,SAAS,OAAO,WAAW,CAAC;EAClE,MAAM,eAAe,WAAW,SAAS,CAAC,OAAO,YAAY,CAAC,OAAO,MAAM;EAC3E,MAAM,eAAe,MAAM,KAAK,gBAAgB,oBAAoB;GAClE;GACA,UAAU,eAAe,SAAS;GAClC;GACA,SAAS,SAAS;GACnB,CAAC;EACF,MAAM,UAA6B;GACjC,MAAM,SAAS;GACf,OAAO,eAAe,SAAS;GAC/B,MAAM,eAAe,SAAS;GAC9B,SAAS,eAAe,SAAS;GACjC,SAAS,SAAS;GAClB,aAAa,SAAS;GACtB,aAAa,SAAS,eAAe,eAAe,SAAS;GAC7D,iBAAiB,SAAS;GAC1B,QAAQ,SAAS;GACjB,MAAM,SAAS;GACf,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,UAAU,SAAS,YAAY;GAC/B,WAAW,MAAM;GACjB,SAAS,SAAS;GAClB;GACA,UAAU,eAAe;GACzB,aAAa,eAAe,SAAS,kBAAkB,IACnD,eAAe,SAAS,eAAe,EAAE,GACzC,EAAE;GACN,cAAc,YAAY,SAAS,SAAS;GAC5C;GACA,OAAO,aAAa,KAAK,UAAU;IACjC,MAAM,KAAK;IACX,eAAe,KAAK,MAAM,SAAS,SAAS;IAC7C,EAAE;GACJ;AAMD,SAAO;GACL,GANa,MAAM,KAAK,kBAAkB,QAAQ;IAClD;IACA;IACA,OAAO,MAAM;IACd,CAAC;GAGA,cAAc;IACZ,MAAM,OAAO;IACb,QAAQ;IACR,MAAM;IACP;GACF;;CAGH,sBAA8B,OAAO,WAIA;EACnC,MAAM,gBAAgB,OAAO,eAAe,MAAM;EAClD,MAAM,gBAAgB,QAAQ,IAAI,kCAAkC,MAAM;AAC1E,MAAI,eAAe;GACjB,MAAM,QAAQ;AACd,OAAI,CAAC,MACH,OAAM,IAAI,MAAM,oBAAoB;GAEtC,MAAM,KAAK,MAAM,KAAK,yBAAyB;IAC7C;IACA,iBAAiB,KAAK,wBAAwB;IAC/C,CAAC;AACF,UAAO;IACL;IACA,WAAW,KAAK,mBAAmB,IAAI,OAAO,MAAM;IACrD;;EAGH,MAAM,YAAY,KAAK,iBAAiB,sBAAsB;EAC9D,MAAM,gBAAgB,UAAU,OAAO,MAAM;AAC7C,MAAI,eAAe;GACjB,MAAM,KAAK,MAAM,KAAK,yBAAyB;IAC7C,OAAO;IACP,iBAAiB,KAAK,uBAAuB,UAAU,WAAW;IACnE,CAAC;AACF,UAAO;IACL,OAAO;IACP,WAAW,KAAK,mBAAmB,IAAI,OAAO,MAAM;IACrD;;AAGH,MAAI,cACF,QAAO;GACL,OAAO;GACP,WAAW,KAAK,wBAAwB;GACzC;AAGH,QAAM,IAAI,MAAM,uDAAuD;;CAGzE,2BAAmC,OAAO,WAOpC;EACJ,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,gBAAgB,oBAAoB,EACzE,SAAS;GACP,eAAe,UAAU,OAAO;GAChC,QAAQ;GACT,EACF,CAAC;EACF,MAAM,UAAU,MAAM,SAAS,MAAM,CAAC,YAAY,KAAK;AACvD,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,UACJ,OAAO,YAAY,YACnB,WACA,WAAW,WACX,OAAQ,QAA8C,OAAO,YAAY,WACpE,QAA2C,MAAM,UAClD,GAAG,SAAS,OAAO,GAAG,SAAS;AACrC,SAAM,IAAI,MAAM,qBAAqB,UAAU;;EAEjD,MAAM,OAAO,OAAO,YAAY,YAC9B,WACA,UAAU,WACV,OAAQ,QAA0C,MAAM,SAAS,YAChE,QAAwD,KAAK,OAC3D,QAAwD,KAAK,OAC9D;EACJ,MAAM,KAAK,OAAO,MAAM,OAAO,WAAW,KAAK,GAAG,MAAM,GAAG;EAC3D,MAAM,WAAW,OAAO,MAAM,aAAa,WAAW,KAAK,SAAS,MAAM,GAAG;EAC7E,MAAM,OAAO,MAAM,SAAS,UAAU,UAAU;AAChD,MAAI,CAAC,GACH,OAAM,IAAI,MAAM,gBAAgB;AAElC,SAAO;GACL;GACA,UAAU,YAAY;GACtB;GACD;;CAGH,0BAAkC,sBAAuC;EACvE,MAAM,SAAS,mBAAmB,MAAM,IAAI,QAAQ,IAAI,4BAA4B,MAAM,IAAI;EAC9F,MAAM,aAAa,IAAI,IAAI,OAAO;AAClC,aAAW,WAAW,WAAW,SAAS,QAAQ,YAAY,IAAI;AAClE,SAAO,WAAW,UAAU,CAAC,QAAQ,QAAQ,GAAG;;CAGlD,sBACE,MACA,UACgD;AAEhD,MADwB,MAAM,WAAW,YAAY,IAC9B,KAAK,SAAS,QACnC,QAAO,KAAK,wBAAwB;AAEtC,MAAI,CAAC,KAAK,SACR,OAAM,IAAI,MAAM,6DAA6D;AAE/E,SAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,KAAK;GACN;;CAGH,+BAAoF;AAClF,SAAO;GACL,IAAI;GACJ,MAAM;GACN,KAAK;GACN"}
@@ -41,6 +41,8 @@ declare class AppRegistryService {
41
41
  setDocumentGrant: (appId: string, scopeId: string, directoryPath: string) => Promise<AppRegistryAppRecord>;
42
42
  removeDocumentGrant: (appId: string, scopeId: string) => Promise<boolean>;
43
43
  removeApp: (appId: string) => Promise<AppRegistryAppRecord | undefined>;
44
+ isBuiltInSuppressed: (appId: string) => Promise<boolean>;
45
+ setBuiltInSuppressed: (appId: string, suppressed: boolean) => Promise<void>;
44
46
  private updateApp;
45
47
  private withMutation;
46
48
  private parseRegistry;
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.d.ts","names":[],"sources":["../../src/services/app-registry.service.ts"],"mappings":";;;;;;cAYa,kBAAA;EAAA,iBAGkB,cAAA;EAAA,wBAFL,cAAA;cAEK,cAAA,GAAgB,cAAA;EAE7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAazB,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,aAAA;IACA,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,OAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAsCZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,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;EAAA,QAanC,SAAA;EAAA,QAiBA,YAAA;EAAA,QAcA,aAAA;EAAA,QAyCA,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":";;;;;;cAYa,kBAAA;EAAA,iBAGkB,cAAA;EAAA,wBAFL,cAAA;cAEK,cAAA,GAAgB,cAAA;EAE7C,IAAA,QAAiB,OAAA,CAAQ,WAAA;EAazB,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,aAAA;IACA,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,OAAA;EAAA,MACE,OAAA,CAAQ,oBAAA;EAsCZ,UAAA,GAAoB,KAAA,UAAe,OAAA,cAAmB,OAAA,CAAQ,oBAAA;EAI9D,eAAA,GACE,KAAA,UACA,OAAA,aACC,OAAA,CAAQ,oBAAA;EASX,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,QAcA,aAAA;EAAA,QAoDA,YAAA;EAAA,QAOA,aAAA;EAAA,QAOA,kBAAA;AAAA"}
@@ -15,7 +15,8 @@ var AppRegistryService = class AppRegistryService {
15
15
  } catch (error) {
16
16
  if (this.isMissingFileError(error)) return {
17
17
  schemaVersion: 1,
18
- apps: {}
18
+ apps: {},
19
+ suppressedBuiltIns: {}
19
20
  };
20
21
  throw error;
21
22
  }
@@ -140,6 +141,18 @@ var AppRegistryService = class AppRegistryService {
140
141
  return appRecord;
141
142
  });
142
143
  };
144
+ isBuiltInSuppressed = async (appId) => {
145
+ const registry = await this.load();
146
+ return Boolean(registry.suppressedBuiltIns[appId]);
147
+ };
148
+ setBuiltInSuppressed = async (appId, suppressed) => {
149
+ await this.withMutation(async () => {
150
+ const registry = await this.load();
151
+ if (suppressed) registry.suppressedBuiltIns[appId] = { suppressedAt: (/* @__PURE__ */ new Date()).toISOString() };
152
+ else delete registry.suppressedBuiltIns[appId];
153
+ await this.saveUnlocked(registry);
154
+ });
155
+ };
143
156
  updateApp = async (appId, update) => {
144
157
  return await this.withMutation(async () => {
145
158
  const registry = await this.load();
@@ -191,7 +204,12 @@ var AppRegistryService = class AppRegistryService {
191
204
  }
192
205
  return {
193
206
  schemaVersion: 1,
194
- apps
207
+ apps,
208
+ suppressedBuiltIns: candidate.suppressedBuiltIns && typeof candidate.suppressedBuiltIns === "object" && !Array.isArray(candidate.suppressedBuiltIns) ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {
209
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
210
+ const suppressedAt = raw.suppressedAt;
211
+ return typeof suppressedAt === "string" ? [[appId, { suppressedAt }]] : [];
212
+ })) : {}
195
213
  };
196
214
  };
197
215
  assertRecord = (value, field) => {
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppPermissions, AppResolvedComponent } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppRegistryService {\n private static readonly mutationQueues = new Map<string, Promise<unknown>>();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n load = async (): Promise<AppRegistry> => {\n await this.appHomeService.ensureBaseDirectories();\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: {} };\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 dataDirectory: string;\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 enabled?: 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 nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n activeVersion: params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.dataDirectory,\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 },\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 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 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 const previous = AppRegistryService.mutationQueues.get(registryPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(operation);\n AppRegistryService.mutationQueues.set(registryPath, current);\n try {\n return await current;\n } finally {\n if (AppRegistryService.mutationQueues.get(registryPath) === current) {\n AppRegistryService.mutationQueues.delete(registryPath);\n }\n }\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 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 const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 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 apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n return { schemaVersion: 1, apps };\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 { AppPermissions, AppResolvedComponent } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppRegistryService {\n private static readonly mutationQueues = new Map<string, Promise<unknown>>();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n load = async (): Promise<AppRegistry> => {\n await this.appHomeService.ensureBaseDirectories();\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 dataDirectory: string;\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 enabled?: 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 nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n activeVersion: params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.dataDirectory,\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 },\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 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 const previous = AppRegistryService.mutationQueues.get(registryPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(operation);\n AppRegistryService.mutationQueues.set(registryPath, current);\n try {\n return await current;\n } finally {\n if (AppRegistryService.mutationQueues.get(registryPath) === current) {\n AppRegistryService.mutationQueues.delete(registryPath);\n }\n }\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 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 const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 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 apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\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 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":";;;;AAYA,IAAa,qBAAb,MAAa,mBAAmB;CAC9B,OAAwB,iCAAiB,IAAI,KAA+B;CAE5E,YAAY,iBAAkD,IAAI,gBAAgB,EAAE;AAAvD,OAAA,iBAAA;;CAE7B,OAAO,YAAkC;AACvC,QAAM,KAAK,eAAe,uBAAuB;AACjD,MAAI;GACF,MAAM,MAAM,MAAM,SAAS,KAAK,eAAe,iBAAiB,EAAE,QAAQ;AAC1E,UAAO,KAAK,cAAc,KAAK,MAAM,IAAI,CAAY;WAC9C,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO;IAAE,eAAe;IAAG,MAAM,EAAE;IAAE,oBAAoB,EAAE;IAAE;AAE/D,SAAM;;;CAIV,OAAO,OAAO,aAAyC;AACrD,QAAM,KAAK,aAAa,YAAY,MAAM,KAAK,aAAa,SAAS,CAAC;;CAGxE,eAAuB,OAAO,aAAyC;AACrE,QAAM,KAAK,eAAe,uBAAuB;EACjD,MAAM,eAAe,KAAK,eAAe,iBAAiB;EAC1D,MAAM,gBAAgB,KAAK,KACzB,KAAK,QAAQ,aAAa,EAC1B,IAAI,KAAK,SAAS,aAAa,CAAC,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC,MAC9D;EACD,MAAM,SAAS,MAAM,KAAK,eAAe,MAAM,IAAM;AACrD,MAAI;AACF,SAAM,OAAO,UAAU,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,KAAK,QAAQ;AACzE,SAAM,OAAO,MAAM;YACX;AACR,SAAM,OAAO,OAAO;;AAEtB,MAAI;AACF,SAAM,OAAO,eAAe,aAAa;WAClC,OAAO;AACd,SAAM,GAAG,eAAe,EAAE,OAAO,MAAM,CAAC;AACxC,SAAM;;;CAIV,WAAW,YAA6C;EACtD,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,OAAO,OAAO,SAAS,KAAK,CAAC,MAAM,MAAM,UAAU,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC;;CAGlG,SAAS,OAAO,UAA6D;AAE3E,UADiB,MAAM,KAAK,MAAM,EAClB,KAAK;;CAGvB,mBAAmB,OAAO,UAAoE;EAC5F,MAAM,YAAY,MAAM,KAAK,OAAO,MAAM;AAC1C,SAAO,WAAW,kBAAkB,UAAU;;CAGhD,qBAAqB,OAAO,WAoBS;AACnC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,gBAAgB,SAAS,KAAK,OAAO;GAC3C,MAAM,aAAmC;IACvC,OAAO,OAAO;IACd,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,eAAe,OAAO;IACtB,SAAS,OAAO,WAAW,eAAe,WAAW,OAAO,0BAA0B;IACtF,eAAe,OAAO;IACtB,mBAAmB;KACjB,GAAI,eAAe,qBAAqB,EAAE;MACzC,OAAO,UAAU;MAChB,SAAS,OAAO;MAChB,kBAAkB,OAAO;MACzB,YAAY,OAAO;MACnB,WAAW,OAAO;MAClB,aAAa,OAAO;MACpB,kBAAkB,OAAO;MACzB,aAAa,OAAO;MACpB,aAAa,OAAO;MACpB,WAAW,OAAO;MAClB,QAAQ,OAAO;MACf,WAAW,OAAO;MAClB,uBAAuB,OAAO;MAC9B,YAAY,OAAO;MACnB,gBAAgB,OAAO;MACxB;KACF;IACD,QAAQ,eAAe,UAAU,EAAE;IACpC;AACD,YAAS,KAAK,OAAO,SAAS;AAC9B,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,aAAa,OAAO,OAAe,YAAoD;AACrF,SAAO,MAAM,KAAK,UAAU,QAAQ,YAAY;GAAE,GAAG;GAAQ;GAAS,EAAE;;CAG1E,kBAAkB,OAChB,OACA,YACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;AAC7C,OAAI,CAAC,OAAO,kBAAkB,SAC5B,OAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,GAAG;AAElD,UAAO;IAAE,GAAG;IAAQ,eAAe;IAAS;IAC5C;;CAGJ,eAAe,OACb,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,YAAY;GAC9C,GAAG;GACH,QAAQ;IAAE,GAAG,OAAO;IAAQ,GAAG;IAAQ;GACxC,EAAE;;CAGL,mBAAmB,OACjB,OACA,SACA,kBACkC;AAClC,SAAO,MAAM,KAAK,aAAa,OAAO,GAAG,UAAU,eAAe,CAAC;;CAGrE,sBAAsB,OAAO,OAAe,YAAsC;EAChF,IAAI,UAAU;AACd,QAAM,KAAK,UAAU,QAAQ,WAAW;AACtC,OAAI,EAAE,WAAW,OAAO,QACtB,QAAO;GAET,MAAM,SAAS,EAAE,GAAG,OAAO,QAAQ;AACnC,UAAO,OAAO;AACd,aAAU;AACV,UAAO;IAAE,GAAG;IAAQ;IAAQ;IAC5B;AACF,SAAO;;CAGT,YAAY,OAAO,UAA6D;AAC9E,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH;AAEF,UAAO,SAAS,KAAK;AACrB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,sBAAsB,OAAO,UAAoC;EAC/D,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,QAAQ,SAAS,mBAAmB,OAAO;;CAGpD,uBAAuB,OAAO,OAAe,eAAuC;AAClF,QAAM,KAAK,aAAa,YAAY;GAClC,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,OAAI,WACF,UAAS,mBAAmB,SAAS,EAAE,+BAAc,IAAI,MAAM,EAAC,aAAa,EAAE;OAE/E,QAAO,SAAS,mBAAmB;AAErC,SAAM,KAAK,aAAa,SAAS;IACjC;;CAGJ,YAAoB,OAClB,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;GAEtC,MAAM,aAAa,OAAO,UAAU;AACpC,YAAS,KAAK,SAAS;AACvB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,eAAuB,OAAU,cAA4C;EAC3E,MAAM,eAAe,KAAK,QAAQ,KAAK,eAAe,iBAAiB,CAAC;EAExE,MAAM,WADW,mBAAmB,eAAe,IAAI,aAAa,IAAI,QAAQ,SAAS,EAChE,YAAY,KAAA,EAAU,CAAC,KAAK,UAAU;AAC/D,qBAAmB,eAAe,IAAI,cAAc,QAAQ;AAC5D,MAAI;AACF,UAAO,MAAM;YACL;AACR,OAAI,mBAAmB,eAAe,IAAI,aAAa,KAAK,QAC1D,oBAAmB,eAAe,OAAO,aAAa;;;CAK5D,iBAAyB,gBAAsC;EAC7D,MAAM,YAAY,KAAK,aAAa,aAAa,gBAAgB;AACjE,MAAI,UAAU,kBAAkB,EAC9B,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,KAAK,aAAa,UAAU,MAAM,gBAAgB;EAClE,MAAM,OAA6C,EAAE;AACrD,OAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,QAAQ,EAAE;GACrD,MAAM,MAAM,KAAK,aAAa,QAAQ,iBAAiB,QAAQ;GAC/D,MAAM,oBAAiE,EAAE;GACzE,MAAM,cAAc,KAAK,aACvB,IAAI,mBACJ,iBAAiB,MAAM,oBACxB;AACD,QAAK,MAAM,CAAC,SAAS,eAAe,OAAO,QAAQ,YAAY,EAAE;IAC/D,MAAM,gBAAgB,KAAK,aACzB,YACA,iBAAiB,MAAM,qBAAqB,UAC7C;AACD,sBAAkB,WAAW;KAC3B,GAAI;KACJ;KACA,uBAAuB,cAAc,0BAA0B,IAAI,IAAI;KACxE;;GAEH,MAAM,gBAAgB,KAAK,cAAc,IAAI,eAAe,iBAAiB,MAAM,gBAAgB;AACnG,OAAI,CAAC,kBAAkB,eACrB,OAAM,IAAI,MAAM,iBAAiB,MAAM,oBAAoB,cAAc,GAAG;AAE9E,QAAK,SAAS;IACZ,GAAI;IACJ;IACA,SAAS,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU;IAC1D;IACA;IACA,QAAQ,IAAI,UAAU,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,EAAE;IACvE;;AAaH,SAAO;GAAE,eAAe;GAAG;GAAM,oBAXN,UAAU,sBACnC,OAAO,UAAU,uBAAuB,YACxC,CAAC,MAAM,QAAQ,UAAU,mBAAmB,GAC1C,OAAO,YAAY,OAAO,QAAQ,UAAU,mBAAmB,CAAC,SAAS,CAAC,OAAO,SAAS;AACxF,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CACvD,QAAO,EAAE;IAEX,MAAM,eAAgB,IAAmC;AACzD,WAAO,OAAO,iBAAiB,WAAW,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE;KAC1E,CAAC,GACH,EAAE;GAC+C;;CAGvD,gBAAwB,OAAgB,UAA2C;AACjF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,OAAM,IAAI,MAAM,GAAG,MAAM,SAAS;AAEpC,SAAO;;CAGT,iBAAyB,OAAgB,UAA0B;AACjE,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,CAC5C,OAAM,IAAI,MAAM,GAAG,MAAM,YAAY;AAEvC,SAAO;;CAGT,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
@@ -1,10 +1,12 @@
1
- import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
3
3
  import { AppDocumentGrantMap } from "./app-permissions.types.js";
4
4
  import { AppPublisher } from "./app-remote-registry.types.js";
5
5
  import { AppInstallSourceKind } from "./app-registry.types.js";
6
6
 
7
7
  //#region src/types/app-installation.types.d.ts
8
+ type AppInstallProgressPhase = "resolving" | "downloading" | "verifying" | "installing" | "finalizing";
9
+ type AppInstallProgressHandler = (phase: AppInstallProgressPhase) => void | Promise<void>;
8
10
  type AppInstallResult = {
9
11
  appId: string;
10
12
  name: string;
@@ -84,5 +86,5 @@ type AppRollbackResult = AppActivationResult & {
84
86
  rolledBack: boolean;
85
87
  };
86
88
  //#endregion
87
- export { AppActivationResult, AppInfoResult, AppInstallResult, AppLaunchResolution, AppRollbackResult, AppUninstallResult, AppUpdateResult, InstalledAppListItem };
89
+ export { AppActivationResult, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppLaunchResolution, AppRollbackResult, AppUninstallResult, AppUpdateResult, InstalledAppListItem };
88
90
  //# sourceMappingURL=app-installation.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;KAMY,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
1
+ {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;KAMY,uBAAA;AAAA,KAOA,yBAAA,IACV,KAAA,EAAO,uBAAA,YACG,OAAA;AAAA,KAEA,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
@@ -1,9 +1,13 @@
1
- import { AppManifest, AppPermissions } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppManifest, AppPermissions } from "./app-manifest.types.js";
3
3
  import { AppPublisher } from "./app-remote-registry.types.js";
4
4
 
5
5
  //#region src/types/app-publish.types.d.ts
6
6
  declare const DEFAULT_APP_MARKETPLACE_API_BASE = "https://apps-registry.nextclaw.io";
7
+ type AppMarketplaceVisuals = {
8
+ cover: string;
9
+ accentColor: string;
10
+ };
7
11
  type AppMarketplaceMetadata = {
8
12
  slug: string;
9
13
  summary: string;
@@ -16,6 +20,7 @@ type AppMarketplaceMetadata = {
16
20
  homepage?: string;
17
21
  featured?: boolean;
18
22
  publisher?: AppPublisher;
23
+ visuals?: AppMarketplaceVisuals;
19
24
  };
20
25
  type AppPublishFile = {
21
26
  path: string;
@@ -36,6 +41,7 @@ type AppPublishPayload = {
36
41
  homepage?: string;
37
42
  featured: boolean;
38
43
  publisher: AppPublisher;
44
+ visuals?: AppMarketplaceVisuals;
39
45
  distributionMode: AppDistributionMode;
40
46
  manifest: AppManifest;
41
47
  permissions: AppPermissions;
@@ -48,6 +54,9 @@ type AppPublishResult = {
48
54
  item: {
49
55
  slug: string;
50
56
  appId: string;
57
+ ownerScope: string;
58
+ appName: string;
59
+ publishStatus: "pending" | "published";
51
60
  name: string;
52
61
  latestVersion: string;
53
62
  webUrl?: string;
@@ -66,5 +75,5 @@ type AppPublishResult = {
66
75
  fileCount: number;
67
76
  };
68
77
  //#endregion
69
- export { AppMarketplaceMetadata, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE };
78
+ export { AppMarketplaceMetadata, AppMarketplaceVisuals, AppPublishFile, AppPublishPayload, AppPublishResult, DEFAULT_APP_MARKETPLACE_API_BASE };
70
79
  //# sourceMappingURL=app-publish.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.types.d.ts","names":[],"sources":["../../src/types/app-publish.types.ts"],"mappings":";;;;;cAIa,gCAAA;AAAA,KAED,sBAAA;EACV,IAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,GAAY,YAAA;AAAA;AAAA,KAGF,cAAA;EACV,IAAA;EACA,aAAA;AAAA;AAAA,KAGU,iBAAA;EACV,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,EAAW,YAAA;EACX,gBAAA,EAAkB,mBAAA;EAClB,QAAA,EAAU,WAAA;EACV,WAAA,EAAa,cAAA;EACb,YAAA;EACA,YAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,KAGG,gBAAA;EACV,OAAA;EACA,IAAA;IACE,IAAA;IACA,KAAA;IACA,IAAA;IACA,aAAA;IACA,MAAA;IACA,OAAA;MACE,IAAA;MACA,IAAA;MACA,OAAA;MACA,QAAA;IAAA;EAAA;EAGJ,YAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA,EAAM,mBAAA;EAAA;EAER,SAAA;AAAA"}
1
+ {"version":3,"file":"app-publish.types.d.ts","names":[],"sources":["../../src/types/app-publish.types.ts"],"mappings":";;;;;cAOa,gCAAA;AAAA,KAGD,qBAAA;EACV,KAAA;EACA,WAAA;AAAA;AAAA,KAGU,sBAAA;EACV,IAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA,GAAU,qBAAA;AAAA;AAAA,KAGA,cAAA;EACV,IAAA;EACA,aAAA;AAAA;AAAA,KAGU,iBAAA;EACV,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,EAAW,YAAA;EACX,OAAA,GAAU,qBAAA;EACV,gBAAA,EAAkB,mBAAA;EAClB,QAAA,EAAU,WAAA;EACV,WAAA,EAAa,cAAA;EACb,YAAA;EACA,YAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,KAGG,gBAAA;EACV,OAAA;EACA,IAAA;IACE,IAAA;IACA,KAAA;IACA,UAAA;IACA,OAAA;IACA,aAAA;IACA,IAAA;IACA,aAAA;IACA,MAAA;IACA,OAAA;MACE,IAAA;MACA,IAAA;MACA,OAAA;MACA,QAAA;IAAA;EAAA;EAGJ,YAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA,EAAM,mBAAA;EAAA;EAER,SAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.types.js","names":[],"sources":["../../src/types/app-publish.types.ts"],"sourcesContent":["import type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport type { AppManifest, AppPermissions } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\n\nexport const DEFAULT_APP_MARKETPLACE_API_BASE = \"https://apps-registry.nextclaw.io\";\n\nexport type AppMarketplaceMetadata = {\n slug: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured?: boolean;\n publisher?: AppPublisher;\n};\n\nexport type AppPublishFile = {\n path: string;\n contentBase64: string;\n};\n\nexport type AppPublishPayload = {\n slug: string;\n appId: string;\n name: string;\n version: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured: boolean;\n publisher: AppPublisher;\n distributionMode: AppDistributionMode;\n manifest: AppManifest;\n permissions: AppPermissions;\n bundleBase64: string;\n bundleSha256: string;\n files: AppPublishFile[];\n};\n\nexport type AppPublishResult = {\n created: boolean;\n item: {\n slug: string;\n appId: string;\n name: string;\n latestVersion: string;\n webUrl?: string;\n install: {\n kind: \"registry\";\n spec: string;\n command: string;\n registry: string;\n };\n };\n distribution: {\n path: string;\n sha256: string;\n mode: AppDistributionMode;\n };\n fileCount: number;\n};\n"],"mappings":";AAIA,MAAa,mCAAmC"}
1
+ {"version":3,"file":"app-publish.types.js","names":[],"sources":["../../src/types/app-publish.types.ts"],"sourcesContent":["import type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport type {\n AppManifest,\n AppPermissions,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\n\nexport const DEFAULT_APP_MARKETPLACE_API_BASE =\n \"https://apps-registry.nextclaw.io\";\n\nexport type AppMarketplaceVisuals = {\n cover: string;\n accentColor: string;\n};\n\nexport type AppMarketplaceMetadata = {\n slug: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured?: boolean;\n publisher?: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n};\n\nexport type AppPublishFile = {\n path: string;\n contentBase64: string;\n};\n\nexport type AppPublishPayload = {\n slug: string;\n appId: string;\n name: string;\n version: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured: boolean;\n publisher: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n distributionMode: AppDistributionMode;\n manifest: AppManifest;\n permissions: AppPermissions;\n bundleBase64: string;\n bundleSha256: string;\n files: AppPublishFile[];\n};\n\nexport type AppPublishResult = {\n created: boolean;\n item: {\n slug: string;\n appId: string;\n ownerScope: string;\n appName: string;\n publishStatus: \"pending\" | \"published\";\n name: string;\n latestVersion: string;\n webUrl?: string;\n install: {\n kind: \"registry\";\n spec: string;\n command: string;\n registry: string;\n };\n };\n distribution: {\n path: string;\n sha256: string;\n mode: AppDistributionMode;\n };\n fileCount: number;\n};\n"],"mappings":";AAOA,MAAa,mCACX"}
@@ -1,5 +1,5 @@
1
- import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
3
3
  import { AppDocumentGrantMap } from "./app-permissions.types.js";
4
4
  import { AppPublisher } from "./app-remote-registry.types.js";
5
5
 
@@ -34,6 +34,9 @@ type AppRegistryAppRecord = {
34
34
  type AppRegistry = {
35
35
  schemaVersion: 1;
36
36
  apps: Record<string, AppRegistryAppRecord>;
37
+ suppressedBuiltIns: Record<string, {
38
+ suppressedAt: string;
39
+ }>;
37
40
  };
38
41
  //#endregion
39
42
  export { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion };
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;KAKY,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,WAAA;EACV,aAAA;EACA,IAAA,EAAM,MAAA,SAAe,oBAAA;AAAA"}
1
+ {"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;KAKY,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,WAAA;EACV,aAAA;EACA,IAAA,EAAM,MAAA,SAAe,oBAAA;EACrB,kBAAA,EAAoB,MAAA;IAClB,YAAA;EAAA;AAAA"}
@@ -1,5 +1,5 @@
1
- import { AppPermissions } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions } from "./app-manifest.types.js";
3
3
 
4
4
  //#region src/types/app-remote-registry.types.d.ts
5
5
  declare const DEFAULT_APP_REGISTRY_URL = "https://apps-registry.nextclaw.io/api/v1/apps/registry/";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.9.14",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",
@@ -32,6 +32,11 @@
32
32
  "development": "./src/index.ts",
33
33
  "types": "./dist/index.d.ts",
34
34
  "default": "./dist/index.js"
35
+ },
36
+ "./artifact-validation": {
37
+ "development": "./src/services/app-artifact-validation.service.ts",
38
+ "types": "./dist/services/app-artifact-validation.service.d.ts",
39
+ "default": "./dist/services/app-artifact-validation.service.js"
35
40
  }
36
41
  },
37
42
  "imports": {