@nextclaw/app-runtime 0.9.15 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/publish.controller.js +7 -0
- package/dist/controllers/publish.controller.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +6 -1
- package/dist/package.js +1 -1
- package/dist/services/app-artifact-validation.service.d.ts +46 -0
- package/dist/services/app-artifact-validation.service.d.ts.map +1 -0
- package/dist/services/app-artifact-validation.service.js +250 -0
- package/dist/services/app-artifact-validation.service.js.map +1 -0
- package/dist/services/app-bundle.service.d.ts +4 -8
- package/dist/services/app-bundle.service.d.ts.map +1 -1
- package/dist/services/app-bundle.service.js +6 -65
- package/dist/services/app-bundle.service.js.map +1 -1
- package/dist/services/app-home.service.d.ts +4 -0
- package/dist/services/app-home.service.d.ts.map +1 -1
- package/dist/services/app-home.service.js +15 -1
- package/dist/services/app-home.service.js.map +1 -1
- package/dist/services/app-install-source.service.js +14 -1
- package/dist/services/app-install-source.service.js.map +1 -1
- package/dist/services/app-installation-filesystem.service.js +68 -0
- package/dist/services/app-installation-filesystem.service.js.map +1 -0
- package/dist/services/app-installation-integrity.service.d.ts +20 -0
- package/dist/services/app-installation-integrity.service.d.ts.map +1 -0
- package/dist/services/app-installation-integrity.service.js +84 -0
- package/dist/services/app-installation-integrity.service.js.map +1 -0
- package/dist/services/app-installation-lifecycle.service.js +110 -0
- package/dist/services/app-installation-lifecycle.service.js.map +1 -0
- package/dist/services/app-installation.service.d.ts +21 -11
- package/dist/services/app-installation.service.d.ts.map +1 -1
- package/dist/services/app-installation.service.js +262 -280
- package/dist/services/app-installation.service.js.map +1 -1
- package/dist/services/app-instance-storage.service.d.ts +49 -0
- package/dist/services/app-instance-storage.service.d.ts.map +1 -0
- package/dist/services/app-instance-storage.service.js +256 -0
- package/dist/services/app-instance-storage.service.js.map +1 -0
- package/dist/services/app-manifest.service.d.ts +5 -1
- package/dist/services/app-manifest.service.d.ts.map +1 -1
- package/dist/services/app-manifest.service.js +59 -3
- package/dist/services/app-manifest.service.js.map +1 -1
- package/dist/services/app-publish-validation.service.d.ts +1 -1
- package/dist/services/app-publish.service.d.ts +2 -1
- package/dist/services/app-publish.service.d.ts.map +1 -1
- package/dist/services/app-publish.service.js +3 -2
- package/dist/services/app-publish.service.js.map +1 -1
- package/dist/services/app-registry.service.d.ts +13 -3
- package/dist/services/app-registry.service.d.ts.map +1 -1
- package/dist/services/app-registry.service.js +143 -13
- package/dist/services/app-registry.service.js.map +1 -1
- package/dist/services/file-lock.service.d.ts +20 -0
- package/dist/services/file-lock.service.d.ts.map +1 -0
- package/dist/services/file-lock.service.js +156 -0
- package/dist/services/file-lock.service.js.map +1 -0
- package/dist/types/app-installation.types.d.ts +8 -1
- package/dist/types/app-installation.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.d.ts +22 -1
- package/dist/types/app-manifest.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.js.map +1 -1
- package/dist/types/app-publish.types.d.ts +4 -1
- package/dist/types/app-publish.types.d.ts.map +1 -1
- package/dist/types/app-publish.types.js.map +1 -1
- package/dist/types/app-registry.types.d.ts +7 -1
- package/dist/types/app-registry.types.d.ts.map +1 -1
- package/dist/types/app-remote-registry.types.d.ts +1 -1
- package/dist/types/app-storage.types.d.ts +37 -0
- package/dist/types/app-storage.types.d.ts.map +1 -0
- package/dist/types/app-storage.types.js +7 -0
- package/dist/types/app-storage.types.js.map +1 -0
- package/package.json +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-bundle.service.js","names":[],"sources":["../../src/services/app-bundle.service.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { strToU8, unzipSync, zipSync } from \"fflate\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n type AppManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppBundleChecksums,\n AppBundleExtractResult,\n AppBundleMetadata,\n AppBundlePackResult,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\n\nexport class AppBundleService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n ) {}\n\n packAppDirectory = async (params: {\n appDirectory: string;\n outputPath?: string;\n mode?: AppDistributionMode;\n }): Promise<AppBundlePackResult> => {\n const { appDirectory, outputPath, mode: requestedMode } = params;\n const bundle = await this.manifestService.load(appDirectory);\n const mode = requestedMode ?? \"bundle\";\n if (isAppComponentManifestBundle(bundle) && mode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发,不允许运行安装脚本。\");\n }\n const { appFiles, filePaths } = mode === \"source\"\n ? await this.collectSourceFiles(bundle)\n : await this.collectRuntimeFiles(bundle);\n this.assertFileBudgets(appFiles);\n const metadata = this.buildMetadata(\n bundle.manifest.id,\n bundle.manifest.name,\n bundle.manifest.version,\n mode,\n );\n const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\\n`);\n const checksums = this.buildChecksums({\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n });\n const checksumsJsonBytes = strToU8(`${JSON.stringify(checksums, null, 2)}\\n`);\n const archiveBytes = zipSync(\n {\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n [CHECKSUMS_PATH]: checksumsJsonBytes,\n },\n { level: 9 },\n );\n if (archiveBytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩后超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const resolvedOutputPath = outputPath\n ? path.resolve(outputPath)\n : path.join(\n path.dirname(bundle.appDirectory),\n `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}.napp`,\n );\n await mkdir(path.dirname(resolvedOutputPath), { recursive: true });\n await writeFile(resolvedOutputPath, Buffer.from(archiveBytes));\n return {\n bundlePath: resolvedOutputPath,\n metadata,\n sizeBytes: archiveBytes.byteLength,\n filePaths,\n };\n };\n\n extractBundle = async (params: {\n bundlePath: string;\n targetDirectory: string;\n }): Promise<AppBundleExtractResult> => {\n const bundlePath = path.resolve(params.bundlePath);\n const targetDirectory = path.resolve(params.targetDirectory);\n const { archive, metadata, checksums } = await this.readValidatedArchive(bundlePath);\n await this.replaceTargetWithArchive({ archive, metadata, targetDirectory });\n return { appDirectory: targetDirectory, metadata, checksums };\n };\n\n private readValidatedArchive = async (bundlePath: string): Promise<{\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n }> => {\n const bundleStats = await stat(bundlePath);\n if (!bundleStats.isFile() || bundleStats.size > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩体积超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n let fileCount = 0;\n let totalBytes = 0;\n const archive = unzipSync(new Uint8Array(await readFile(bundlePath)), {\n filter: (file) => {\n fileCount += 1;\n totalBytes += file.originalSize;\n if (fileCount > MAX_FILE_COUNT) {\n throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);\n }\n if (file.originalSize > MAX_FILE_BYTES) {\n throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${file.name}`);\n }\n if (totalBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);\n }\n return true;\n },\n });\n const normalizedArchive = this.normalizeArchive(archive);\n const metadata = this.readMetadataFromArchive(normalizedArchive);\n const checksums = this.readChecksumsFromArchive(normalizedArchive);\n this.verifyArchiveChecksums(normalizedArchive, checksums);\n return { archive: normalizedArchive, metadata, checksums };\n };\n\n private replaceTargetWithArchive = async (params: {\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n targetDirectory: string;\n }): Promise<void> => {\n const { archive, metadata, targetDirectory } = params;\n const targetParent = path.dirname(targetDirectory);\n const operationId = randomUUID();\n const stagedDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.extracting-${operationId}`,\n );\n const backupDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.backup-${operationId}`,\n );\n const targetExists = await this.pathExists(targetDirectory);\n await mkdir(targetParent, { recursive: true });\n try {\n await mkdir(stagedDirectory);\n await this.writeArchiveEntries(archive, stagedDirectory);\n await this.assertExtractedManifest(stagedDirectory, metadata);\n if (targetExists) {\n await rename(targetDirectory, backupDirectory);\n }\n await rename(stagedDirectory, targetDirectory);\n await rm(backupDirectory, { recursive: true, force: true });\n } catch (error) {\n await rm(stagedDirectory, { recursive: true, force: true });\n if (targetExists && await this.pathExists(backupDirectory)) {\n await rm(targetDirectory, { recursive: true, force: true });\n try {\n await rename(backupDirectory, targetDirectory);\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `bundle 解压失败,且无法恢复原目标目录:${targetDirectory}`,\n );\n }\n }\n throw error;\n } finally {\n await rm(stagedDirectory, { recursive: true, force: true });\n await rm(backupDirectory, { recursive: true, force: true });\n }\n };\n\n private writeArchiveEntries = async (\n archive: Record<string, Uint8Array>,\n targetDirectory: string,\n ): Promise<void> => {\n for (const [entryName, bytes] of Object.entries(archive)) {\n const targetPath = path.join(targetDirectory, entryName);\n await mkdir(path.dirname(targetPath), { recursive: true });\n await writeFile(targetPath, Buffer.from(bytes));\n }\n };\n\n private assertExtractedManifest = async (\n appDirectory: string,\n metadata: AppBundleMetadata,\n ): Promise<void> => {\n const manifestBundle = await this.manifestService.load(appDirectory);\n if (\n metadata.appId !== manifestBundle.manifest.id ||\n metadata.name !== manifestBundle.manifest.name ||\n metadata.version !== manifestBundle.manifest.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (manifestBundle.manifest.schemaVersion === 2 && metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n };\n\n private collectRuntimeFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const filePaths = new Set<string>([\n path.relative(bundle.appDirectory, bundle.manifestPath).replace(/\\\\/g, \"/\"),\n ]);\n if (isAppStandaloneManifestBundle(bundle)) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.mainEntryPath).replace(/\\\\/g, \"/\"));\n await this.collectDirectoryPaths(bundle.uiDirectoryPath, bundle.appDirectory, filePaths);\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n } else {\n for (const component of bundle.components) {\n await this.collectDirectoryPaths(component.componentDirectory, bundle.appDirectory, filePaths);\n }\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n await this.addOptionalFile(bundle.appDirectory, \"marketplace.json\", filePaths);\n }\n if (bundle.iconPath) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.iconPath).replace(/\\\\/g, \"/\"));\n }\n return await this.readAppFiles(bundle.appDirectory, filePaths);\n };\n\n private collectSourceFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n if (!isAppStandaloneManifestBundle(bundle)) {\n throw new Error(\"schema v2 组合包不支持 source 分发。\");\n }\n const filePaths = new Set<string>();\n await this.collectSourceDirectoryPaths(bundle.appDirectory, bundle.appDirectory, filePaths);\n const result = await this.readAppFiles(bundle.appDirectory, filePaths);\n if (bundle.manifest.main.kind === \"wasi-http-component\") {\n result.appFiles[bundle.manifest.main.entry] = SOURCE_WASM_PLACEHOLDER_BYTES;\n }\n return result;\n };\n\n private readAppFiles = async (\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));\n const appFiles: Record<string, Uint8Array> = {};\n for (const relativePath of sortedPaths) {\n appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));\n }\n return { appFiles, filePaths: sortedPaths };\n };\n\n private collectDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeRuntimePath(relativePath)) {\n await this.collectDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n continue;\n }\n if (entry.isFile() && !this.shouldExcludeRuntimePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return;\n }\n throw error;\n }\n };\n\n private collectSourceDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeSourcePath(relativePath)) {\n await this.collectSourceDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n } else if (entry.isFile() && !this.shouldExcludeSourcePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n };\n\n private addOptionalFile = async (\n appDirectory: string,\n relativePath: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const stats = await lstat(path.join(appDirectory, relativePath));\n if (stats.isFile() && !stats.isSymbolicLink()) {\n filePaths.add(relativePath);\n }\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private shouldExcludeRuntimePath = (relativePath: string): boolean => {\n const segments = relativePath.split(\"/\");\n return segments.some((segment) =>\n segment === \"node_modules\" ||\n segment === \".git\" ||\n segment === \"marketplace-assets\" ||\n segment === \"coverage\" ||\n segment === \"tests\" ||\n segment === \"__tests__\" ||\n segment === \"fixtures\"\n ) || /(?:^|\\/)\\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(\".map\");\n };\n\n private shouldExcludeSourcePath = (relativePath: string): boolean => {\n return this.shouldExcludeRuntimePath(relativePath) ||\n relativePath === \".napp\" || relativePath.startsWith(\".napp/\") ||\n relativePath === \"main/dist\" || relativePath.startsWith(\"main/dist/\") ||\n relativePath === \"main/generated\" || relativePath.startsWith(\"main/generated/\");\n };\n\n private assertFileBudgets = (files: Record<string, Uint8Array>): void => {\n const entries = Object.entries(files);\n if (entries.length + 2 > MAX_FILE_COUNT) {\n throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);\n }\n let totalBytes = 0;\n for (const [relativePath, bytes] of entries) {\n if (bytes.byteLength > MAX_FILE_BYTES) {\n throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${relativePath}`);\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);\n }\n };\n\n private normalizeArchive = (\n archive: Record<string, Uint8Array>,\n ): Record<string, Uint8Array> => {\n const normalizedArchive: Record<string, Uint8Array> = {};\n for (const [entryName, bytes] of Object.entries(archive)) {\n const normalizedEntry = this.normalizeArchiveEntry(entryName);\n if (Object.hasOwn(normalizedArchive, normalizedEntry)) {\n throw new Error(`bundle 包含重复路径:${normalizedEntry}`);\n }\n normalizedArchive[normalizedEntry] = bytes;\n }\n return normalizedArchive;\n };\n\n private readMetadataFromArchive = (\n archive: Record<string, Uint8Array>,\n ): AppBundleMetadata => {\n const raw = this.readJsonArchiveEntry<Partial<AppBundleMetadata>>(\n archive,\n BUNDLE_METADATA_PATH,\n \"bundle metadata\",\n );\n const distributionMode = raw.distributionMode === undefined\n ? \"bundle\"\n : raw.distributionMode;\n if (\n raw.bundleFormatVersion !== 1 ||\n (distributionMode !== \"bundle\" && distributionMode !== \"source\") ||\n typeof raw.appId !== \"string\" || !raw.appId ||\n typeof raw.name !== \"string\" || !raw.name ||\n typeof raw.version !== \"string\" || !raw.version ||\n raw.entryManifest !== \"manifest.json\" ||\n raw.checksumsFile !== CHECKSUMS_PATH\n ) {\n throw new Error(\"bundle metadata 无效。\");\n }\n return {\n ...raw,\n distributionMode,\n } as AppBundleMetadata;\n };\n\n private readChecksumsFromArchive = (\n archive: Record<string, Uint8Array>,\n ): AppBundleChecksums => {\n const raw = this.readJsonArchiveEntry<Partial<AppBundleChecksums>>(\n archive,\n CHECKSUMS_PATH,\n \"bundle checksums\",\n );\n if (raw.algorithm !== \"sha256\" || !raw.files || typeof raw.files !== \"object\") {\n throw new Error(\"bundle checksums 无效。\");\n }\n return raw as AppBundleChecksums;\n };\n\n private verifyArchiveChecksums = (\n archive: Record<string, Uint8Array>,\n checksums: AppBundleChecksums,\n ): void => {\n const actualPaths = Object.keys(archive)\n .filter((entry) => entry !== CHECKSUMS_PATH)\n .sort((left, right) => left.localeCompare(right));\n const checksumPaths = Object.keys(checksums.files)\n .map((entry) => this.normalizeArchiveEntry(entry))\n .sort((left, right) => left.localeCompare(right));\n if (\n actualPaths.length !== checksumPaths.length ||\n actualPaths.some((entry, index) => entry !== checksumPaths[index])\n ) {\n throw new Error(\"bundle checksums 必须精确覆盖所有 artifact 文件。\");\n }\n for (const relativePath of actualPaths) {\n const expectedHash = checksums.files[relativePath];\n if (!expectedHash || !/^[a-f0-9]{64}$/.test(expectedHash)) {\n throw new Error(`bundle checksum 格式无效:${relativePath}`);\n }\n const actualHash = this.computeSha256(archive[relativePath] as Uint8Array);\n if (actualHash !== expectedHash) {\n throw new Error(`bundle checksum 校验失败:${relativePath}`);\n }\n }\n };\n\n private readJsonArchiveEntry = <T>(\n archive: Record<string, Uint8Array>,\n entryPath: string,\n label: string,\n ): T => {\n const bytes = archive[entryPath];\n if (!bytes) {\n throw new Error(`bundle 缺少 ${label}。`);\n }\n try {\n return JSON.parse(Buffer.from(bytes).toString(\"utf-8\")) as T;\n } catch (error) {\n throw new Error(`无法读取 ${label}:${error instanceof Error ? error.message : String(error)}`);\n }\n };\n\n private buildMetadata = (\n appId: string,\n name: string,\n version: string,\n distributionMode: AppDistributionMode,\n ): AppBundleMetadata => ({\n bundleFormatVersion: 1,\n distributionMode,\n appId,\n name,\n version,\n entryManifest: \"manifest.json\",\n checksumsFile: CHECKSUMS_PATH,\n });\n\n private buildChecksums = (files: Record<string, Uint8Array>): AppBundleChecksums => ({\n algorithm: \"sha256\",\n files: Object.fromEntries(\n Object.entries(files)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]),\n ),\n });\n\n private computeSha256 = (fileBytes: Uint8Array): string =>\n createHash(\"sha256\").update(Buffer.from(fileBytes)).digest(\"hex\");\n\n private normalizeBundleFileName = (appId: string): string =>\n appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n\n private normalizeArchiveEntry = (entryName: string): string => {\n const normalized = entryName.replace(/\\\\/g, \"/\");\n const segments = normalized.split(\"/\");\n if (\n !normalized || normalized.startsWith(\"/\") || normalized.includes(\"\\0\") ||\n /^[A-Za-z]:/.test(normalized) ||\n segments.some((segment) => !segment || segment === \".\" || segment === \"..\")\n ) {\n throw new Error(`bundle 内包含非法路径:${entryName}`);\n }\n return segments.join(\"/\");\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 private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n}\n\nconst SOURCE_WASM_PLACEHOLDER_BYTES = Uint8Array.from(\n Buffer.from(\n \"AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL\",\n \"base64\",\n ),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-bundle.service.js","names":[],"sources":["../../src/services/app-bundle.service.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { access, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { strToU8, zipSync } from \"fflate\";\nimport { AppArtifactValidationService } from \"#app-runtime/services/app-artifact-validation.service.js\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport {\n isAppComponentManifestBundle,\n isAppStandaloneManifestBundle,\n type AppManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type {\n AppBundleChecksums,\n AppBundleExtractResult,\n AppBundleMetadata,\n AppBundlePackResult,\n AppDistributionMode,\n} from \"#app-runtime/types/app-bundle.types.js\";\n\nconst MAX_COMPRESSED_BYTES = 25 * 1024 * 1024;\nconst MAX_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;\nconst MAX_FILE_BYTES = 25 * 1024 * 1024;\nconst MAX_FILE_COUNT = 2_000;\nconst CHECKSUMS_PATH = \".napp/checksums.json\";\nconst BUNDLE_METADATA_PATH = \".napp/bundle.json\";\n\nexport class AppBundleService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n private readonly artifactValidationService: AppArtifactValidationService = new AppArtifactValidationService(),\n ) {}\n\n packAppDirectory = async (params: {\n appDirectory: string;\n outputPath?: string;\n mode?: AppDistributionMode;\n }): Promise<AppBundlePackResult> => {\n const { appDirectory, outputPath, mode: requestedMode } = params;\n const bundle = await this.manifestService.load(appDirectory);\n const mode = requestedMode ?? \"bundle\";\n if (isAppComponentManifestBundle(bundle) && mode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发,不允许运行安装脚本。\");\n }\n const { appFiles, filePaths } = mode === \"source\"\n ? await this.collectSourceFiles(bundle)\n : await this.collectRuntimeFiles(bundle);\n this.assertFileBudgets(appFiles);\n const metadata = this.buildMetadata(\n bundle.manifest.id,\n bundle.manifest.name,\n bundle.manifest.version,\n mode,\n );\n const bundleJsonBytes = strToU8(`${JSON.stringify(metadata, null, 2)}\\n`);\n const checksums = this.buildChecksums({\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n });\n const checksumsJsonBytes = strToU8(`${JSON.stringify(checksums, null, 2)}\\n`);\n const archiveBytes = zipSync(\n {\n ...appFiles,\n [BUNDLE_METADATA_PATH]: bundleJsonBytes,\n [CHECKSUMS_PATH]: checksumsJsonBytes,\n },\n { level: 9 },\n );\n if (archiveBytes.byteLength > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩后超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const resolvedOutputPath = outputPath\n ? path.resolve(outputPath)\n : path.join(\n path.dirname(bundle.appDirectory),\n `${this.normalizeBundleFileName(bundle.manifest.id)}-${bundle.manifest.version}.napp`,\n );\n await mkdir(path.dirname(resolvedOutputPath), { recursive: true });\n await writeFile(resolvedOutputPath, Buffer.from(archiveBytes));\n return {\n bundlePath: resolvedOutputPath,\n metadata,\n sizeBytes: archiveBytes.byteLength,\n filePaths,\n };\n };\n\n extractBundle = async (params: {\n bundlePath: string;\n targetDirectory: string;\n }): Promise<AppBundleExtractResult> => {\n const bundlePath = path.resolve(params.bundlePath);\n const targetDirectory = path.resolve(params.targetDirectory);\n const { archive, metadata, checksums } = await this.readValidatedArchive(bundlePath);\n await this.replaceTargetWithArchive({ archive, metadata, targetDirectory });\n return { appDirectory: targetDirectory, metadata, checksums };\n };\n\n private readValidatedArchive = async (bundlePath: string): Promise<{\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n checksums: AppBundleChecksums;\n }> => {\n const bundleStats = await stat(bundlePath);\n if (!bundleStats.isFile() || bundleStats.size > MAX_COMPRESSED_BYTES) {\n throw new Error(`bundle 压缩体积超过 ${MAX_COMPRESSED_BYTES} bytes 上限。`);\n }\n const { archive, metadata, checksums } = await this.artifactValidationService.validate({\n bytes: new Uint8Array(await readFile(bundlePath)),\n });\n return { archive, metadata, checksums };\n };\n\n private replaceTargetWithArchive = async (params: {\n archive: Record<string, Uint8Array>;\n metadata: AppBundleMetadata;\n targetDirectory: string;\n }): Promise<void> => {\n const { archive, metadata, targetDirectory } = params;\n const targetParent = path.dirname(targetDirectory);\n const operationId = randomUUID();\n const stagedDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.extracting-${operationId}`,\n );\n const backupDirectory = path.join(\n targetParent,\n `.${path.basename(targetDirectory)}.backup-${operationId}`,\n );\n const targetExists = await this.pathExists(targetDirectory);\n await mkdir(targetParent, { recursive: true });\n try {\n await mkdir(stagedDirectory);\n await this.writeArchiveEntries(archive, stagedDirectory);\n await this.assertExtractedManifest(stagedDirectory, metadata);\n if (targetExists) {\n await rename(targetDirectory, backupDirectory);\n }\n await rename(stagedDirectory, targetDirectory);\n await rm(backupDirectory, { recursive: true, force: true });\n } catch (error) {\n await rm(stagedDirectory, { recursive: true, force: true });\n if (targetExists && await this.pathExists(backupDirectory)) {\n await rm(targetDirectory, { recursive: true, force: true });\n try {\n await rename(backupDirectory, targetDirectory);\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `bundle 解压失败,且无法恢复原目标目录:${targetDirectory}`,\n );\n }\n }\n throw error;\n } finally {\n await rm(stagedDirectory, { recursive: true, force: true });\n await rm(backupDirectory, { recursive: true, force: true });\n }\n };\n\n private writeArchiveEntries = async (\n archive: Record<string, Uint8Array>,\n targetDirectory: string,\n ): Promise<void> => {\n for (const [entryName, bytes] of Object.entries(archive)) {\n const targetPath = path.join(targetDirectory, entryName);\n await mkdir(path.dirname(targetPath), { recursive: true });\n await writeFile(targetPath, Buffer.from(bytes));\n }\n };\n\n private assertExtractedManifest = async (\n appDirectory: string,\n metadata: AppBundleMetadata,\n ): Promise<void> => {\n const manifestBundle = await this.manifestService.load(appDirectory);\n if (\n metadata.appId !== manifestBundle.manifest.id ||\n metadata.name !== manifestBundle.manifest.name ||\n metadata.version !== manifestBundle.manifest.version\n ) {\n throw new Error(\"bundle metadata 与 manifest.json 身份不一致。\");\n }\n if (manifestBundle.manifest.schemaVersion === 2 && metadata.distributionMode !== \"bundle\") {\n throw new Error(\"schema v2 组合包只支持 bundle 分发。\");\n }\n };\n\n private collectRuntimeFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const filePaths = new Set<string>([\n path.relative(bundle.appDirectory, bundle.manifestPath).replace(/\\\\/g, \"/\"),\n ]);\n if (isAppStandaloneManifestBundle(bundle)) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.mainEntryPath).replace(/\\\\/g, \"/\"));\n await this.collectDirectoryPaths(bundle.uiDirectoryPath, bundle.appDirectory, filePaths);\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n } else {\n for (const component of bundle.components) {\n await this.collectDirectoryPaths(component.componentDirectory, bundle.appDirectory, filePaths);\n }\n await this.collectDirectoryPaths(bundle.assetsDirectoryPath, bundle.appDirectory, filePaths);\n await this.addOptionalFile(bundle.appDirectory, \"marketplace.json\", filePaths);\n }\n if (bundle.iconPath) {\n filePaths.add(path.relative(bundle.appDirectory, bundle.iconPath).replace(/\\\\/g, \"/\"));\n }\n return await this.readAppFiles(bundle.appDirectory, filePaths);\n };\n\n private collectSourceFiles = async (\n bundle: AppManifestBundle,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n if (!isAppStandaloneManifestBundle(bundle)) {\n throw new Error(\"schema v2 组合包不支持 source 分发。\");\n }\n const filePaths = new Set<string>();\n await this.collectSourceDirectoryPaths(bundle.appDirectory, bundle.appDirectory, filePaths);\n const result = await this.readAppFiles(bundle.appDirectory, filePaths);\n if (bundle.manifest.main.kind === \"wasi-http-component\") {\n result.appFiles[bundle.manifest.main.entry] = SOURCE_WASM_PLACEHOLDER_BYTES;\n }\n return result;\n };\n\n private readAppFiles = async (\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<{ appFiles: Record<string, Uint8Array>; filePaths: string[] }> => {\n const sortedPaths = Array.from(filePaths).sort((left, right) => left.localeCompare(right));\n const appFiles: Record<string, Uint8Array> = {};\n for (const relativePath of sortedPaths) {\n appFiles[relativePath] = new Uint8Array(await readFile(path.join(appDirectory, relativePath)));\n }\n return { appFiles, filePaths: sortedPaths };\n };\n\n private collectDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeRuntimePath(relativePath)) {\n await this.collectDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n continue;\n }\n if (entry.isFile() && !this.shouldExcludeRuntimePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return;\n }\n throw error;\n }\n };\n\n private collectSourceDirectoryPaths = async (\n directoryPath: string,\n appDirectory: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n const entries = await readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n const relativePath = path.relative(appDirectory, entryPath).replace(/\\\\/g, \"/\");\n const entryStats = await lstat(entryPath);\n if (entryStats.isSymbolicLink()) {\n throw new Error(`bundle 不允许包含符号链接:${relativePath}`);\n }\n if (entry.isDirectory()) {\n if (!this.shouldExcludeSourcePath(relativePath)) {\n await this.collectSourceDirectoryPaths(entryPath, appDirectory, filePaths);\n }\n } else if (entry.isFile() && !this.shouldExcludeSourcePath(relativePath)) {\n filePaths.add(relativePath);\n }\n }\n };\n\n private addOptionalFile = async (\n appDirectory: string,\n relativePath: string,\n filePaths: Set<string>,\n ): Promise<void> => {\n try {\n const stats = await lstat(path.join(appDirectory, relativePath));\n if (stats.isFile() && !stats.isSymbolicLink()) {\n filePaths.add(relativePath);\n }\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private shouldExcludeRuntimePath = (relativePath: string): boolean => {\n const segments = relativePath.split(\"/\");\n return segments.some((segment) =>\n segment === \"node_modules\" ||\n segment === \".git\" ||\n segment === \"marketplace-assets\" ||\n segment === \"coverage\" ||\n segment === \"tests\" ||\n segment === \"__tests__\" ||\n segment === \"fixtures\"\n ) || /(?:^|\\/)\\.(?:DS_Store|eslintcache)$/.test(relativePath) || relativePath.endsWith(\".map\");\n };\n\n private shouldExcludeSourcePath = (relativePath: string): boolean => {\n return this.shouldExcludeRuntimePath(relativePath) ||\n relativePath === \".napp\" || relativePath.startsWith(\".napp/\") ||\n relativePath === \"main/dist\" || relativePath.startsWith(\"main/dist/\") ||\n relativePath === \"main/generated\" || relativePath.startsWith(\"main/generated/\");\n };\n\n private assertFileBudgets = (files: Record<string, Uint8Array>): void => {\n const entries = Object.entries(files);\n if (entries.length + 2 > MAX_FILE_COUNT) {\n throw new Error(`bundle 文件数超过 ${MAX_FILE_COUNT} 上限。`);\n }\n let totalBytes = 0;\n for (const [relativePath, bytes] of entries) {\n if (bytes.byteLength > MAX_FILE_BYTES) {\n throw new Error(`bundle 单文件超过 ${MAX_FILE_BYTES} bytes 上限:${relativePath}`);\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > MAX_UNCOMPRESSED_BYTES) {\n throw new Error(`bundle 解压后超过 ${MAX_UNCOMPRESSED_BYTES} bytes 上限。`);\n }\n };\n\n private buildMetadata = (\n appId: string,\n name: string,\n version: string,\n distributionMode: AppDistributionMode,\n ): AppBundleMetadata => ({\n bundleFormatVersion: 1,\n distributionMode,\n appId,\n name,\n version,\n entryManifest: \"manifest.json\",\n checksumsFile: CHECKSUMS_PATH,\n });\n\n private buildChecksums = (files: Record<string, Uint8Array>): AppBundleChecksums => ({\n algorithm: \"sha256\",\n files: Object.fromEntries(\n Object.entries(files)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([relativePath, fileBytes]) => [relativePath, this.computeSha256(fileBytes)]),\n ),\n });\n\n private computeSha256 = (fileBytes: Uint8Array): string =>\n createHash(\"sha256\").update(Buffer.from(fileBytes)).digest(\"hex\");\n\n private normalizeBundleFileName = (appId: string): string =>\n appId.replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n}\n\nconst SOURCE_WASM_PLACEHOLDER_BYTES = Uint8Array.from(\n Buffer.from(\n \"AGFzbQEAAAABBwFgAn9/AX8DAgEABxMBD3N1bW1hcml6ZV9ub3RlcwAACg0BCwAgACABakHIAWoL\",\n \"base64\",\n ),\n);\n"],"mappings":";;;;;;;;AAmBA,MAAM,uBAAuB,KAAK,OAAO;AACzC,MAAM,yBAAyB,MAAM,OAAO;AAC5C,MAAM,iBAAiB,KAAK,OAAO;AACnC,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AACvB,MAAM,uBAAuB;AAE7B,IAAa,mBAAb,MAA8B;CAC5B,YACE,kBAAuD,IAAI,oBAAoB,EAC/E,4BAA2E,IAAI,8BAA8B,EAC7G;AAFiB,OAAA,kBAAA;AACA,OAAA,4BAAA;;CAGnB,mBAAmB,OAAO,WAIU;EAClC,MAAM,EAAE,cAAc,YAAY,MAAM,kBAAkB;EAC1D,MAAM,SAAS,MAAM,KAAK,gBAAgB,KAAK,aAAa;EAC5D,MAAM,OAAO,iBAAiB;AAC9B,MAAI,6BAA6B,OAAO,IAAI,SAAS,SACnD,OAAM,IAAI,MAAM,wCAAwC;EAE1D,MAAM,EAAE,UAAU,cAAc,SAAS,WACrC,MAAM,KAAK,mBAAmB,OAAO,GACrC,MAAM,KAAK,oBAAoB,OAAO;AAC1C,OAAK,kBAAkB,SAAS;EAChC,MAAM,WAAW,KAAK,cACpB,OAAO,SAAS,IAChB,OAAO,SAAS,MAChB,OAAO,SAAS,SAChB,KACD;EACD,MAAM,kBAAkB,QAAQ,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI;EACzE,MAAM,YAAY,KAAK,eAAe;GACpC,GAAG;IACF,uBAAuB;GACzB,CAAC;EACF,MAAM,qBAAqB,QAAQ,GAAG,KAAK,UAAU,WAAW,MAAM,EAAE,CAAC,IAAI;EAC7E,MAAM,eAAe,QACnB;GACE,GAAG;IACF,uBAAuB;IACvB,iBAAiB;GACnB,EACD,EAAE,OAAO,GAAG,CACb;AACD,MAAI,aAAa,aAAa,qBAC5B,OAAM,IAAI,MAAM,gBAAgB,qBAAqB,YAAY;EAEnE,MAAM,qBAAqB,aACvB,KAAK,QAAQ,WAAW,GACxB,KAAK,KACH,KAAK,QAAQ,OAAO,aAAa,EACjC,GAAG,KAAK,wBAAwB,OAAO,SAAS,GAAG,CAAC,GAAG,OAAO,SAAS,QAAQ,OAChF;AACL,QAAM,MAAM,KAAK,QAAQ,mBAAmB,EAAE,EAAE,WAAW,MAAM,CAAC;AAClE,QAAM,UAAU,oBAAoB,OAAO,KAAK,aAAa,CAAC;AAC9D,SAAO;GACL,YAAY;GACZ;GACA,WAAW,aAAa;GACxB;GACD;;CAGH,gBAAgB,OAAO,WAGgB;EACrC,MAAM,aAAa,KAAK,QAAQ,OAAO,WAAW;EAClD,MAAM,kBAAkB,KAAK,QAAQ,OAAO,gBAAgB;EAC5D,MAAM,EAAE,SAAS,UAAU,cAAc,MAAM,KAAK,qBAAqB,WAAW;AACpF,QAAM,KAAK,yBAAyB;GAAE;GAAS;GAAU;GAAiB,CAAC;AAC3E,SAAO;GAAE,cAAc;GAAiB;GAAU;GAAW;;CAG/D,uBAA+B,OAAO,eAIhC;EACJ,MAAM,cAAc,MAAM,KAAK,WAAW;AAC1C,MAAI,CAAC,YAAY,QAAQ,IAAI,YAAY,OAAO,qBAC9C,OAAM,IAAI,MAAM,iBAAiB,qBAAqB,YAAY;EAEpE,MAAM,EAAE,SAAS,UAAU,cAAc,MAAM,KAAK,0BAA0B,SAAS,EACrF,OAAO,IAAI,WAAW,MAAM,SAAS,WAAW,CAAC,EAClD,CAAC;AACF,SAAO;GAAE;GAAS;GAAU;GAAW;;CAGzC,2BAAmC,OAAO,WAIrB;EACnB,MAAM,EAAE,SAAS,UAAU,oBAAoB;EAC/C,MAAM,eAAe,KAAK,QAAQ,gBAAgB;EAClD,MAAM,cAAc,YAAY;EAChC,MAAM,kBAAkB,KAAK,KAC3B,cACA,IAAI,KAAK,SAAS,gBAAgB,CAAC,cAAc,cAClD;EACD,MAAM,kBAAkB,KAAK,KAC3B,cACA,IAAI,KAAK,SAAS,gBAAgB,CAAC,UAAU,cAC9C;EACD,MAAM,eAAe,MAAM,KAAK,WAAW,gBAAgB;AAC3D,QAAM,MAAM,cAAc,EAAE,WAAW,MAAM,CAAC;AAC9C,MAAI;AACF,SAAM,MAAM,gBAAgB;AAC5B,SAAM,KAAK,oBAAoB,SAAS,gBAAgB;AACxD,SAAM,KAAK,wBAAwB,iBAAiB,SAAS;AAC7D,OAAI,aACF,OAAM,OAAO,iBAAiB,gBAAgB;AAEhD,SAAM,OAAO,iBAAiB,gBAAgB;AAC9C,SAAM,GAAG,iBAAiB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;WACpD,OAAO;AACd,SAAM,GAAG,iBAAiB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAC3D,OAAI,gBAAgB,MAAM,KAAK,WAAW,gBAAgB,EAAE;AAC1D,UAAM,GAAG,iBAAiB;KAAE,WAAW;KAAM,OAAO;KAAM,CAAC;AAC3D,QAAI;AACF,WAAM,OAAO,iBAAiB,gBAAgB;aACvC,cAAc;AACrB,WAAM,IAAI,eACR,CAAC,OAAO,aAAa,EACrB,0BAA0B,kBAC3B;;;AAGL,SAAM;YACE;AACR,SAAM,GAAG,iBAAiB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAC3D,SAAM,GAAG,iBAAiB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;;;CAI/D,sBAA8B,OAC5B,SACA,oBACkB;AAClB,OAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,QAAQ,EAAE;GACxD,MAAM,aAAa,KAAK,KAAK,iBAAiB,UAAU;AACxD,SAAM,MAAM,KAAK,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AAC1D,SAAM,UAAU,YAAY,OAAO,KAAK,MAAM,CAAC;;;CAInD,0BAAkC,OAChC,cACA,aACkB;EAClB,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,KAAK,aAAa;AACpE,MACE,SAAS,UAAU,eAAe,SAAS,MAC3C,SAAS,SAAS,eAAe,SAAS,QAC1C,SAAS,YAAY,eAAe,SAAS,QAE7C,OAAM,IAAI,MAAM,yCAAyC;AAE3D,MAAI,eAAe,SAAS,kBAAkB,KAAK,SAAS,qBAAqB,SAC/E,OAAM,IAAI,MAAM,8BAA8B;;CAIlD,sBAA8B,OAC5B,WAC2E;EAC3E,MAAM,YAAY,IAAI,IAAY,CAChC,KAAK,SAAS,OAAO,cAAc,OAAO,aAAa,CAAC,QAAQ,OAAO,IAAI,CAC5E,CAAC;AACF,MAAI,8BAA8B,OAAO,EAAE;AACzC,aAAU,IAAI,KAAK,SAAS,OAAO,cAAc,OAAO,cAAc,CAAC,QAAQ,OAAO,IAAI,CAAC;AAC3F,SAAM,KAAK,sBAAsB,OAAO,iBAAiB,OAAO,cAAc,UAAU;AACxF,SAAM,KAAK,sBAAsB,OAAO,qBAAqB,OAAO,cAAc,UAAU;SACvF;AACL,QAAK,MAAM,aAAa,OAAO,WAC7B,OAAM,KAAK,sBAAsB,UAAU,oBAAoB,OAAO,cAAc,UAAU;AAEhG,SAAM,KAAK,sBAAsB,OAAO,qBAAqB,OAAO,cAAc,UAAU;AAC5F,SAAM,KAAK,gBAAgB,OAAO,cAAc,oBAAoB,UAAU;;AAEhF,MAAI,OAAO,SACT,WAAU,IAAI,KAAK,SAAS,OAAO,cAAc,OAAO,SAAS,CAAC,QAAQ,OAAO,IAAI,CAAC;AAExF,SAAO,MAAM,KAAK,aAAa,OAAO,cAAc,UAAU;;CAGhE,qBAA6B,OAC3B,WAC2E;AAC3E,MAAI,CAAC,8BAA8B,OAAO,CACxC,OAAM,IAAI,MAAM,8BAA8B;EAEhD,MAAM,4BAAY,IAAI,KAAa;AACnC,QAAM,KAAK,4BAA4B,OAAO,cAAc,OAAO,cAAc,UAAU;EAC3F,MAAM,SAAS,MAAM,KAAK,aAAa,OAAO,cAAc,UAAU;AACtE,MAAI,OAAO,SAAS,KAAK,SAAS,sBAChC,QAAO,SAAS,OAAO,SAAS,KAAK,SAAS;AAEhD,SAAO;;CAGT,eAAuB,OACrB,cACA,cAC2E;EAC3E,MAAM,cAAc,MAAM,KAAK,UAAU,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;EAC1F,MAAM,WAAuC,EAAE;AAC/C,OAAK,MAAM,gBAAgB,YACzB,UAAS,gBAAgB,IAAI,WAAW,MAAM,SAAS,KAAK,KAAK,cAAc,aAAa,CAAC,CAAC;AAEhG,SAAO;GAAE;GAAU,WAAW;GAAa;;CAG7C,wBAAgC,OAC9B,eACA,cACA,cACkB;AAClB,MAAI;GACF,MAAM,UAAU,MAAM,QAAQ,eAAe,EAAE,eAAe,MAAM,CAAC;AACrE,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,KAAK,KAAK,eAAe,MAAM,KAAK;IACtD,MAAM,eAAe,KAAK,SAAS,cAAc,UAAU,CAAC,QAAQ,OAAO,IAAI;AAE/E,SADmB,MAAM,MAAM,UAAU,EAC1B,gBAAgB,CAC7B,OAAM,IAAI,MAAM,oBAAoB,eAAe;AAErD,QAAI,MAAM,aAAa,EAAE;AACvB,SAAI,CAAC,KAAK,yBAAyB,aAAa,CAC9C,OAAM,KAAK,sBAAsB,WAAW,cAAc,UAAU;AAEtE;;AAEF,QAAI,MAAM,QAAQ,IAAI,CAAC,KAAK,yBAAyB,aAAa,CAChE,WAAU,IAAI,aAAa;;WAGxB,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC;AAEF,SAAM;;;CAIV,8BAAsC,OACpC,eACA,cACA,cACkB;EAClB,MAAM,UAAU,MAAM,QAAQ,eAAe,EAAE,eAAe,MAAM,CAAC;AACrE,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,YAAY,KAAK,KAAK,eAAe,MAAM,KAAK;GACtD,MAAM,eAAe,KAAK,SAAS,cAAc,UAAU,CAAC,QAAQ,OAAO,IAAI;AAE/E,QADmB,MAAM,MAAM,UAAU,EAC1B,gBAAgB,CAC7B,OAAM,IAAI,MAAM,oBAAoB,eAAe;AAErD,OAAI,MAAM,aAAa;QACjB,CAAC,KAAK,wBAAwB,aAAa,CAC7C,OAAM,KAAK,4BAA4B,WAAW,cAAc,UAAU;cAEnE,MAAM,QAAQ,IAAI,CAAC,KAAK,wBAAwB,aAAa,CACtE,WAAU,IAAI,aAAa;;;CAKjC,kBAA0B,OACxB,cACA,cACA,cACkB;AAClB,MAAI;GACF,MAAM,QAAQ,MAAM,MAAM,KAAK,KAAK,cAAc,aAAa,CAAC;AAChE,OAAI,MAAM,QAAQ,IAAI,CAAC,MAAM,gBAAgB,CAC3C,WAAU,IAAI,aAAa;WAEtB,OAAO;AACd,OAAI,CAAC,KAAK,mBAAmB,MAAM,CACjC,OAAM;;;CAKZ,4BAAoC,iBAAkC;AAEpE,SADiB,aAAa,MAAM,IAAI,CACxB,MAAM,YACpB,YAAY,kBACZ,YAAY,UACZ,YAAY,wBACZ,YAAY,cACZ,YAAY,WACZ,YAAY,eACZ,YAAY,WACb,IAAI,sCAAsC,KAAK,aAAa,IAAI,aAAa,SAAS,OAAO;;CAGhG,2BAAmC,iBAAkC;AACnE,SAAO,KAAK,yBAAyB,aAAa,IAChD,iBAAiB,WAAW,aAAa,WAAW,SAAS,IAC7D,iBAAiB,eAAe,aAAa,WAAW,aAAa,IACrE,iBAAiB,oBAAoB,aAAa,WAAW,kBAAkB;;CAGnF,qBAA6B,UAA4C;EACvE,MAAM,UAAU,OAAO,QAAQ,MAAM;AACrC,MAAI,QAAQ,SAAS,IAAI,eACvB,OAAM,IAAI,MAAM,gBAAgB,eAAe,MAAM;EAEvD,IAAI,aAAa;AACjB,OAAK,MAAM,CAAC,cAAc,UAAU,SAAS;AAC3C,OAAI,MAAM,aAAa,eACrB,OAAM,IAAI,MAAM,gBAAgB,eAAe,YAAY,eAAe;AAE5E,iBAAc,MAAM;;AAEtB,MAAI,aAAa,uBACf,OAAM,IAAI,MAAM,gBAAgB,uBAAuB,YAAY;;CAIvE,iBACE,OACA,MACA,SACA,sBACuB;EACvB,qBAAqB;EACrB;EACA;EACA;EACA;EACA,eAAe;EACf,eAAe;EAChB;CAED,kBAA0B,WAA2D;EACnF,WAAW;EACX,OAAO,OAAO,YACZ,OAAO,QAAQ,MAAM,CAClB,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CACpD,KAAK,CAAC,cAAc,eAAe,CAAC,cAAc,KAAK,cAAc,UAAU,CAAC,CAAC,CACrF;EACF;CAED,iBAAyB,cACvB,WAAW,SAAS,CAAC,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,OAAO,MAAM;CAEnE,2BAAmC,UACjC,MAAM,QAAQ,qBAAqB,IAAI;CAEzC,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS;CAE5D,aAAqB,OAAO,eAAyC;AACnE,MAAI;AACF,SAAM,OAAO,WAAW;AACxB,UAAO;UACD;AACN,UAAO;;;;AAKb,MAAM,gCAAgC,WAAW,KAC/C,OAAO,KACL,gFACA,SACD,CACF"}
|
|
@@ -5,11 +5,15 @@ declare class AppHomeService {
|
|
|
5
5
|
getAppHomeDirectory: () => string;
|
|
6
6
|
getPackagesDirectory: () => string;
|
|
7
7
|
getDataDirectory: () => string;
|
|
8
|
+
getInstancesDirectory: () => string;
|
|
9
|
+
getLocksDirectory: () => string;
|
|
8
10
|
getRegistryPath: () => string;
|
|
9
11
|
getConfigPath: () => string;
|
|
10
12
|
getOperationsPath: () => string;
|
|
11
13
|
getInstallDirectory: (appId: string, version: string) => string;
|
|
12
14
|
getAppDataDirectory: (appId: string) => string;
|
|
15
|
+
getAppInstanceDirectory: (appId: string, instanceId: string) => string;
|
|
16
|
+
getAppOperationLockPath: (appId: string) => string;
|
|
13
17
|
ensureBaseDirectories: () => Promise<void>;
|
|
14
18
|
createTemporaryDirectory: (prefix: string) => Promise<string>;
|
|
15
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-home.service.d.ts","names":[],"sources":["../../src/services/app-home.service.ts"],"mappings":";cAIa,cAAA;EAAA,iBAEQ,gBAAA;cAAA,gBAAA;EAKnB,mBAAA;EAIA,oBAAA;EAIA,gBAAA;EAIA,eAAA;EAIA,aAAA;EAIA,iBAAA;EAIA,mBAAA,GAAuB,KAAA,UAAe,OAAA;EAItC,mBAAA,GAAuB,KAAA;EAIvB,qBAAA,QAAkC,OAAA;
|
|
1
|
+
{"version":3,"file":"app-home.service.d.ts","names":[],"sources":["../../src/services/app-home.service.ts"],"mappings":";cAIa,cAAA;EAAA,iBAEQ,gBAAA;cAAA,gBAAA;EAKnB,mBAAA;EAIA,oBAAA;EAIA,gBAAA;EAIA,qBAAA;EAIA,iBAAA;EAIA,eAAA;EAIA,aAAA;EAIA,iBAAA;EAIA,mBAAA,GAAuB,KAAA,UAAe,OAAA;EAItC,mBAAA,GAAuB,KAAA;EAIvB,uBAAA,GAA2B,KAAA,UAAe,UAAA;EAI1C,uBAAA,GAA2B,KAAA;EAI3B,qBAAA,QAAkC,OAAA;EAUlC,wBAAA,GAAkC,MAAA,aAAiB,OAAA;AAAA"}
|
|
@@ -15,6 +15,12 @@ var AppHomeService = class {
|
|
|
15
15
|
getDataDirectory = () => {
|
|
16
16
|
return path.join(this.appHomeDirectory, "data");
|
|
17
17
|
};
|
|
18
|
+
getInstancesDirectory = () => {
|
|
19
|
+
return path.join(this.appHomeDirectory, "instances");
|
|
20
|
+
};
|
|
21
|
+
getLocksDirectory = () => {
|
|
22
|
+
return path.join(this.appHomeDirectory, "locks");
|
|
23
|
+
};
|
|
18
24
|
getRegistryPath = () => {
|
|
19
25
|
return path.join(this.appHomeDirectory, "registry.json");
|
|
20
26
|
};
|
|
@@ -30,11 +36,19 @@ var AppHomeService = class {
|
|
|
30
36
|
getAppDataDirectory = (appId) => {
|
|
31
37
|
return path.join(this.getDataDirectory(), appId);
|
|
32
38
|
};
|
|
39
|
+
getAppInstanceDirectory = (appId, instanceId) => {
|
|
40
|
+
return path.join(this.getInstancesDirectory(), appId, instanceId);
|
|
41
|
+
};
|
|
42
|
+
getAppOperationLockPath = (appId) => {
|
|
43
|
+
return path.join(this.getLocksDirectory(), "apps", `${appId}.lock`);
|
|
44
|
+
};
|
|
33
45
|
ensureBaseDirectories = async () => {
|
|
34
46
|
await Promise.all([
|
|
35
47
|
mkdir(this.appHomeDirectory, { recursive: true }),
|
|
36
48
|
mkdir(this.getPackagesDirectory(), { recursive: true }),
|
|
37
|
-
mkdir(this.getDataDirectory(), { recursive: true })
|
|
49
|
+
mkdir(this.getDataDirectory(), { recursive: true }),
|
|
50
|
+
mkdir(this.getInstancesDirectory(), { recursive: true }),
|
|
51
|
+
mkdir(this.getLocksDirectory(), { recursive: true })
|
|
38
52
|
]);
|
|
39
53
|
};
|
|
40
54
|
createTemporaryDirectory = async (prefix) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-home.service.js","names":[],"sources":["../../src/services/app-home.service.ts"],"sourcesContent":["import { mkdir, mkdtemp } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nexport class AppHomeService {\n constructor(\n private readonly appHomeDirectory: string = process.env.NEXTCLAW_APP_HOME\n ? path.resolve(process.env.NEXTCLAW_APP_HOME)\n : path.join(process.env.HOME ?? process.cwd(), \".nextclaw\", \"apps\"),\n ) {}\n\n getAppHomeDirectory = (): string => {\n return this.appHomeDirectory;\n };\n\n getPackagesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"packages\");\n };\n\n getDataDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"data\");\n };\n\n getRegistryPath = (): string => {\n return path.join(this.appHomeDirectory, \"registry.json\");\n };\n\n getConfigPath = (): string => {\n return path.join(this.appHomeDirectory, \"config.json\");\n };\n\n getOperationsPath = (): string => {\n return path.join(this.appHomeDirectory, \"operations.json\");\n };\n\n getInstallDirectory = (appId: string, version: string): string => {\n return path.join(this.getPackagesDirectory(), appId, version);\n };\n\n getAppDataDirectory = (appId: string): string => {\n return path.join(this.getDataDirectory(), appId);\n };\n\n ensureBaseDirectories = async (): Promise<void> => {\n await Promise.all([\n mkdir(this.appHomeDirectory, { recursive: true }),\n mkdir(this.getPackagesDirectory(), { recursive: true }),\n mkdir(this.getDataDirectory(), { recursive: true }),\n ]);\n };\n\n createTemporaryDirectory = async (prefix: string): Promise<string> => {\n return mkdtemp(path.join(tmpdir(), prefix));\n };\n}\n"],"mappings":";;;;AAIA,IAAa,iBAAb,MAA4B;CAC1B,YACE,mBAA4C,QAAQ,IAAI,oBACpD,KAAK,QAAQ,QAAQ,IAAI,kBAAkB,GAC3C,KAAK,KAAK,QAAQ,IAAI,QAAQ,QAAQ,KAAK,EAAE,aAAa,OAAO,EACrE;AAHiB,OAAA,mBAAA;;CAKnB,4BAAoC;AAClC,SAAO,KAAK;;CAGd,6BAAqC;AACnC,SAAO,KAAK,KAAK,KAAK,kBAAkB,WAAW;;CAGrD,yBAAiC;AAC/B,SAAO,KAAK,KAAK,KAAK,kBAAkB,OAAO;;CAGjD,wBAAgC;AAC9B,SAAO,KAAK,KAAK,KAAK,kBAAkB,gBAAgB;;CAG1D,sBAA8B;AAC5B,SAAO,KAAK,KAAK,KAAK,kBAAkB,cAAc;;CAGxD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,kBAAkB;;CAG5D,uBAAuB,OAAe,YAA4B;AAChE,SAAO,KAAK,KAAK,KAAK,sBAAsB,EAAE,OAAO,QAAQ;;CAG/D,uBAAuB,UAA0B;AAC/C,SAAO,KAAK,KAAK,KAAK,kBAAkB,EAAE,MAAM;;CAGlD,wBAAwB,YAA2B;AACjD,QAAM,QAAQ,IAAI;GAChB,MAAM,KAAK,kBAAkB,EAAE,WAAW,MAAM,CAAC;GACjD,MAAM,KAAK,sBAAsB,EAAE,EAAE,WAAW,MAAM,CAAC;GACvD,MAAM,KAAK,kBAAkB,EAAE,EAAE,WAAW,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"app-home.service.js","names":[],"sources":["../../src/services/app-home.service.ts"],"sourcesContent":["import { mkdir, mkdtemp } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nexport class AppHomeService {\n constructor(\n private readonly appHomeDirectory: string = process.env.NEXTCLAW_APP_HOME\n ? path.resolve(process.env.NEXTCLAW_APP_HOME)\n : path.join(process.env.HOME ?? process.cwd(), \".nextclaw\", \"apps\"),\n ) {}\n\n getAppHomeDirectory = (): string => {\n return this.appHomeDirectory;\n };\n\n getPackagesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"packages\");\n };\n\n getDataDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"data\");\n };\n\n getInstancesDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"instances\");\n };\n\n getLocksDirectory = (): string => {\n return path.join(this.appHomeDirectory, \"locks\");\n };\n\n getRegistryPath = (): string => {\n return path.join(this.appHomeDirectory, \"registry.json\");\n };\n\n getConfigPath = (): string => {\n return path.join(this.appHomeDirectory, \"config.json\");\n };\n\n getOperationsPath = (): string => {\n return path.join(this.appHomeDirectory, \"operations.json\");\n };\n\n getInstallDirectory = (appId: string, version: string): string => {\n return path.join(this.getPackagesDirectory(), appId, version);\n };\n\n getAppDataDirectory = (appId: string): string => {\n return path.join(this.getDataDirectory(), appId);\n };\n\n getAppInstanceDirectory = (appId: string, instanceId: string): string => {\n return path.join(this.getInstancesDirectory(), appId, instanceId);\n };\n\n getAppOperationLockPath = (appId: string): string => {\n return path.join(this.getLocksDirectory(), \"apps\", `${appId}.lock`);\n };\n\n ensureBaseDirectories = async (): Promise<void> => {\n await Promise.all([\n mkdir(this.appHomeDirectory, { recursive: true }),\n mkdir(this.getPackagesDirectory(), { recursive: true }),\n mkdir(this.getDataDirectory(), { recursive: true }),\n mkdir(this.getInstancesDirectory(), { recursive: true }),\n mkdir(this.getLocksDirectory(), { recursive: true }),\n ]);\n };\n\n createTemporaryDirectory = async (prefix: string): Promise<string> => {\n return mkdtemp(path.join(tmpdir(), prefix));\n };\n}\n"],"mappings":";;;;AAIA,IAAa,iBAAb,MAA4B;CAC1B,YACE,mBAA4C,QAAQ,IAAI,oBACpD,KAAK,QAAQ,QAAQ,IAAI,kBAAkB,GAC3C,KAAK,KAAK,QAAQ,IAAI,QAAQ,QAAQ,KAAK,EAAE,aAAa,OAAO,EACrE;AAHiB,OAAA,mBAAA;;CAKnB,4BAAoC;AAClC,SAAO,KAAK;;CAGd,6BAAqC;AACnC,SAAO,KAAK,KAAK,KAAK,kBAAkB,WAAW;;CAGrD,yBAAiC;AAC/B,SAAO,KAAK,KAAK,KAAK,kBAAkB,OAAO;;CAGjD,8BAAsC;AACpC,SAAO,KAAK,KAAK,KAAK,kBAAkB,YAAY;;CAGtD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,QAAQ;;CAGlD,wBAAgC;AAC9B,SAAO,KAAK,KAAK,KAAK,kBAAkB,gBAAgB;;CAG1D,sBAA8B;AAC5B,SAAO,KAAK,KAAK,KAAK,kBAAkB,cAAc;;CAGxD,0BAAkC;AAChC,SAAO,KAAK,KAAK,KAAK,kBAAkB,kBAAkB;;CAG5D,uBAAuB,OAAe,YAA4B;AAChE,SAAO,KAAK,KAAK,KAAK,sBAAsB,EAAE,OAAO,QAAQ;;CAG/D,uBAAuB,UAA0B;AAC/C,SAAO,KAAK,KAAK,KAAK,kBAAkB,EAAE,MAAM;;CAGlD,2BAA2B,OAAe,eAA+B;AACvE,SAAO,KAAK,KAAK,KAAK,uBAAuB,EAAE,OAAO,WAAW;;CAGnE,2BAA2B,UAA0B;AACnD,SAAO,KAAK,KAAK,KAAK,mBAAmB,EAAE,QAAQ,GAAG,MAAM,OAAO;;CAGrE,wBAAwB,YAA2B;AACjD,QAAM,QAAQ,IAAI;GAChB,MAAM,KAAK,kBAAkB,EAAE,WAAW,MAAM,CAAC;GACjD,MAAM,KAAK,sBAAsB,EAAE,EAAE,WAAW,MAAM,CAAC;GACvD,MAAM,KAAK,kBAAkB,EAAE,EAAE,WAAW,MAAM,CAAC;GACnD,MAAM,KAAK,uBAAuB,EAAE,EAAE,WAAW,MAAM,CAAC;GACxD,MAAM,KAAK,mBAAmB,EAAE,EAAE,WAAW,MAAM,CAAC;GACrD,CAAC;;CAGJ,2BAA2B,OAAO,WAAoC;AACpE,SAAO,QAAQ,KAAK,KAAK,QAAQ,EAAE,OAAO,CAAC"}
|
|
@@ -2,10 +2,23 @@ import { access } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
//#region src/services/app-install-source.service.ts
|
|
4
4
|
var AppInstallSourceService = class {
|
|
5
|
-
constructor(manifestService, remoteRegistryClient) {
|
|
5
|
+
constructor(manifestService, remoteRegistryClient, bundleService) {
|
|
6
6
|
this.manifestService = manifestService;
|
|
7
7
|
this.remoteRegistryClient = remoteRegistryClient;
|
|
8
|
+
this.bundleService = bundleService;
|
|
8
9
|
}
|
|
10
|
+
materializeBundle = async (source, tempDirectory, onProgress) => {
|
|
11
|
+
if (source.kind === "directory") return (await this.bundleService.packAppDirectory({
|
|
12
|
+
appDirectory: source.appDirectory,
|
|
13
|
+
outputPath: path.join(tempDirectory, "app.napp")
|
|
14
|
+
})).bundlePath;
|
|
15
|
+
if (source.kind === "bundle") return source.bundlePath;
|
|
16
|
+
await onProgress?.("downloading");
|
|
17
|
+
return (await this.remoteRegistryClient.downloadBundle({
|
|
18
|
+
resolution: source.registryResolution,
|
|
19
|
+
targetDirectory: tempDirectory
|
|
20
|
+
})).bundlePath;
|
|
21
|
+
};
|
|
9
22
|
resolve = async (appSource, registryUrl) => {
|
|
10
23
|
const localSource = await this.detectLocal(appSource);
|
|
11
24
|
if (localSource.kind === "directory") return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-install-source.service.js","names":[],"sources":["../../src/services/app-install-source.service.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\n\ntype LocalAppSource =\n | { kind: \"directory\"; appDirectory: string }\n | { kind: \"bundle\"; bundlePath: string }\n | { kind: \"missing\" };\n\nexport type ResolvedAppInstallSource =\n | {\n kind: \"directory\";\n appDirectory: string;\n sourceRef: string;\n }\n | {\n kind: \"bundle\";\n bundlePath: string;\n sourceRef: string;\n }\n | {\n kind: \"registry\";\n sourceRef: string;\n registryResolution: Awaited<ReturnType<AppRemoteRegistryClientService[\"resolve\"]>>;\n };\n\nexport class AppInstallSourceService {\n constructor(\n private readonly manifestService: AppManifestService,\n private readonly remoteRegistryClient: AppRemoteRegistryClientService,\n ) {}\n\n resolve = async (\n appSource: string,\n registryUrl?: string,\n ): Promise<ResolvedAppInstallSource> => {\n const localSource = await this.detectLocal(appSource);\n if (localSource.kind === \"directory\") {\n return {\n kind: \"directory\",\n appDirectory: localSource.appDirectory,\n sourceRef: localSource.appDirectory,\n };\n }\n if (localSource.kind === \"bundle\") {\n return {\n kind: \"bundle\",\n bundlePath: localSource.bundlePath,\n sourceRef: localSource.bundlePath,\n };\n }\n const registrySpec = this.parseRegistrySpec(appSource);\n if (!registrySpec) {\n if (this.looksLikePath(appSource)) {\n throw new Error(`本地安装源不存在:${appSource}`);\n }\n throw new Error(`无法识别安装源:${appSource}`);\n }\n const registryResolution = await this.remoteRegistryClient.resolve({\n appId: registrySpec.appId,\n version: registrySpec.version,\n registryUrl,\n });\n return {\n kind: \"registry\",\n sourceRef: `${registryResolution.appId}@${registryResolution.version}`,\n registryResolution,\n };\n };\n\n detectLocal = async (\n sourcePath: string,\n allowBundle: boolean = true,\n ): Promise<LocalAppSource> => {\n const normalizedSource = path.resolve(sourcePath);\n try {\n await access(normalizedSource);\n const manifestBundle = await this.manifestService.load(normalizedSource);\n if (manifestBundle.appDirectory) {\n return {\n kind: \"directory\",\n appDirectory: normalizedSource,\n };\n }\n } catch {\n if (allowBundle && normalizedSource.endsWith(\".napp\")) {\n try {\n await access(normalizedSource);\n return {\n kind: \"bundle\",\n bundlePath: normalizedSource,\n };\n } catch {\n return { kind: \"missing\" };\n }\n }\n return { kind: \"missing\" };\n }\n return { kind: \"missing\" };\n };\n\n private parseRegistrySpec = (\n appSource: string,\n ): { appId: string; version?: string } | undefined => {\n const match = /^(?<appId>[a-z0-9][a-z0-9._-]*)(?:@(?<version>[A-Za-z0-9._+-]+))?$/i.exec(\n appSource.trim(),\n );\n if (!match?.groups?.appId) {\n return undefined;\n }\n return {\n appId: match.groups.appId,\n version: match.groups.version,\n };\n };\n\n private looksLikePath = (appSource: string): boolean =>\n appSource.startsWith(\".\") ||\n appSource.startsWith(\"/\") ||\n appSource.includes(path.sep);\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"app-install-source.service.js","names":[],"sources":["../../src/services/app-install-source.service.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport type { AppBundleService } from \"#app-runtime/services/app-bundle.service.js\";\nimport type { AppInstallProgressHandler } from \"#app-runtime/types/app-installation.types.js\";\n\ntype LocalAppSource =\n | { kind: \"directory\"; appDirectory: string }\n | { kind: \"bundle\"; bundlePath: string }\n | { kind: \"missing\" };\n\nexport type ResolvedAppInstallSource =\n | {\n kind: \"directory\";\n appDirectory: string;\n sourceRef: string;\n }\n | {\n kind: \"bundle\";\n bundlePath: string;\n sourceRef: string;\n }\n | {\n kind: \"registry\";\n sourceRef: string;\n registryResolution: Awaited<ReturnType<AppRemoteRegistryClientService[\"resolve\"]>>;\n };\n\nexport class AppInstallSourceService {\n constructor(\n private readonly manifestService: AppManifestService,\n private readonly remoteRegistryClient: AppRemoteRegistryClientService,\n private readonly bundleService: AppBundleService,\n ) {}\n\n materializeBundle = async (\n source: ResolvedAppInstallSource,\n tempDirectory: string,\n onProgress?: AppInstallProgressHandler,\n ): Promise<string> => {\n if (source.kind === \"directory\") {\n return (await this.bundleService.packAppDirectory({\n appDirectory: source.appDirectory,\n outputPath: path.join(tempDirectory, \"app.napp\"),\n })).bundlePath;\n }\n if (source.kind === \"bundle\") {\n return source.bundlePath;\n }\n await onProgress?.(\"downloading\");\n return (await this.remoteRegistryClient.downloadBundle({\n resolution: source.registryResolution,\n targetDirectory: tempDirectory,\n })).bundlePath;\n };\n\n resolve = async (\n appSource: string,\n registryUrl?: string,\n ): Promise<ResolvedAppInstallSource> => {\n const localSource = await this.detectLocal(appSource);\n if (localSource.kind === \"directory\") {\n return {\n kind: \"directory\",\n appDirectory: localSource.appDirectory,\n sourceRef: localSource.appDirectory,\n };\n }\n if (localSource.kind === \"bundle\") {\n return {\n kind: \"bundle\",\n bundlePath: localSource.bundlePath,\n sourceRef: localSource.bundlePath,\n };\n }\n const registrySpec = this.parseRegistrySpec(appSource);\n if (!registrySpec) {\n if (this.looksLikePath(appSource)) {\n throw new Error(`本地安装源不存在:${appSource}`);\n }\n throw new Error(`无法识别安装源:${appSource}`);\n }\n const registryResolution = await this.remoteRegistryClient.resolve({\n appId: registrySpec.appId,\n version: registrySpec.version,\n registryUrl,\n });\n return {\n kind: \"registry\",\n sourceRef: `${registryResolution.appId}@${registryResolution.version}`,\n registryResolution,\n };\n };\n\n detectLocal = async (\n sourcePath: string,\n allowBundle: boolean = true,\n ): Promise<LocalAppSource> => {\n const normalizedSource = path.resolve(sourcePath);\n try {\n await access(normalizedSource);\n const manifestBundle = await this.manifestService.load(normalizedSource);\n if (manifestBundle.appDirectory) {\n return {\n kind: \"directory\",\n appDirectory: normalizedSource,\n };\n }\n } catch {\n if (allowBundle && normalizedSource.endsWith(\".napp\")) {\n try {\n await access(normalizedSource);\n return {\n kind: \"bundle\",\n bundlePath: normalizedSource,\n };\n } catch {\n return { kind: \"missing\" };\n }\n }\n return { kind: \"missing\" };\n }\n return { kind: \"missing\" };\n };\n\n private parseRegistrySpec = (\n appSource: string,\n ): { appId: string; version?: string } | undefined => {\n const match = /^(?<appId>[a-z0-9][a-z0-9._-]*)(?:@(?<version>[A-Za-z0-9._+-]+))?$/i.exec(\n appSource.trim(),\n );\n if (!match?.groups?.appId) {\n return undefined;\n }\n return {\n appId: match.groups.appId,\n version: match.groups.version,\n };\n };\n\n private looksLikePath = (appSource: string): boolean =>\n appSource.startsWith(\".\") ||\n appSource.startsWith(\"/\") ||\n appSource.includes(path.sep);\n}\n"],"mappings":";;;AA6BA,IAAa,0BAAb,MAAqC;CACnC,YACE,iBACA,sBACA,eACA;AAHiB,OAAA,kBAAA;AACA,OAAA,uBAAA;AACA,OAAA,gBAAA;;CAGnB,oBAAoB,OAClB,QACA,eACA,eACoB;AACpB,MAAI,OAAO,SAAS,YAClB,SAAQ,MAAM,KAAK,cAAc,iBAAiB;GAChD,cAAc,OAAO;GACrB,YAAY,KAAK,KAAK,eAAe,WAAW;GACjD,CAAC,EAAE;AAEN,MAAI,OAAO,SAAS,SAClB,QAAO,OAAO;AAEhB,QAAM,aAAa,cAAc;AACjC,UAAQ,MAAM,KAAK,qBAAqB,eAAe;GACrD,YAAY,OAAO;GACnB,iBAAiB;GAClB,CAAC,EAAE;;CAGN,UAAU,OACR,WACA,gBACsC;EACtC,MAAM,cAAc,MAAM,KAAK,YAAY,UAAU;AACrD,MAAI,YAAY,SAAS,YACvB,QAAO;GACL,MAAM;GACN,cAAc,YAAY;GAC1B,WAAW,YAAY;GACxB;AAEH,MAAI,YAAY,SAAS,SACvB,QAAO;GACL,MAAM;GACN,YAAY,YAAY;GACxB,WAAW,YAAY;GACxB;EAEH,MAAM,eAAe,KAAK,kBAAkB,UAAU;AACtD,MAAI,CAAC,cAAc;AACjB,OAAI,KAAK,cAAc,UAAU,CAC/B,OAAM,IAAI,MAAM,YAAY,YAAY;AAE1C,SAAM,IAAI,MAAM,WAAW,YAAY;;EAEzC,MAAM,qBAAqB,MAAM,KAAK,qBAAqB,QAAQ;GACjE,OAAO,aAAa;GACpB,SAAS,aAAa;GACtB;GACD,CAAC;AACF,SAAO;GACL,MAAM;GACN,WAAW,GAAG,mBAAmB,MAAM,GAAG,mBAAmB;GAC7D;GACD;;CAGH,cAAc,OACZ,YACA,cAAuB,SACK;EAC5B,MAAM,mBAAmB,KAAK,QAAQ,WAAW;AACjD,MAAI;AACF,SAAM,OAAO,iBAAiB;AAE9B,QADuB,MAAM,KAAK,gBAAgB,KAAK,iBAAiB,EACrD,aACjB,QAAO;IACL,MAAM;IACN,cAAc;IACf;UAEG;AACN,OAAI,eAAe,iBAAiB,SAAS,QAAQ,CACnD,KAAI;AACF,UAAM,OAAO,iBAAiB;AAC9B,WAAO;KACL,MAAM;KACN,YAAY;KACb;WACK;AACN,WAAO,EAAE,MAAM,WAAW;;AAG9B,UAAO,EAAE,MAAM,WAAW;;AAE5B,SAAO,EAAE,MAAM,WAAW;;CAG5B,qBACE,cACoD;EACpD,MAAM,QAAQ,sEAAsE,KAClF,UAAU,MAAM,CACjB;AACD,MAAI,CAAC,OAAO,QAAQ,MAClB;AAEF,SAAO;GACL,OAAO,MAAM,OAAO;GACpB,SAAS,MAAM,OAAO;GACvB;;CAGH,iBAAyB,cACvB,UAAU,WAAW,IAAI,IACzB,UAAU,WAAW,IAAI,IACzB,UAAU,SAAS,KAAK,IAAI"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isAppStandaloneManifestBundle } from "../types/app-manifest.types.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { cp, mkdir, readdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/services/app-installation-filesystem.service.ts
|
|
6
|
+
var AppInstallationFilesystemService = class {
|
|
7
|
+
constructor(params) {
|
|
8
|
+
this.params = params;
|
|
9
|
+
}
|
|
10
|
+
copyToImmutableInstallDirectory = async (params) => {
|
|
11
|
+
const { appId, appVersion, extractedDirectory, installDirectory } = params;
|
|
12
|
+
if (await this.params.integrityService.pathExists(installDirectory)) throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);
|
|
13
|
+
await mkdir(path.dirname(installDirectory), { recursive: true });
|
|
14
|
+
const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;
|
|
15
|
+
try {
|
|
16
|
+
await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });
|
|
17
|
+
const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);
|
|
18
|
+
if (stagedManifest.manifest.id !== appId || stagedManifest.manifest.version !== appVersion) throw new Error("staging manifest 与已验证 bundle 身份不一致。");
|
|
19
|
+
const contentSha256 = await this.params.integrityService.calculateDigest(stagedInstallDirectory);
|
|
20
|
+
await rename(stagedInstallDirectory, installDirectory);
|
|
21
|
+
await this.params.integrityService.protectDirectory(installDirectory);
|
|
22
|
+
return contentSha256;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
await rm(stagedInstallDirectory, {
|
|
25
|
+
recursive: true,
|
|
26
|
+
force: true
|
|
27
|
+
});
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
materializeDistribution = async (params) => {
|
|
32
|
+
if (params.distributionMode !== "source") return;
|
|
33
|
+
const manifestBundle = await this.params.manifestService.load(params.appDirectory);
|
|
34
|
+
if (!isAppStandaloneManifestBundle(manifestBundle)) throw new Error("schema v2 组合包不允许运行安装期 build。");
|
|
35
|
+
if (manifestBundle.manifest.main.kind !== "wasi-http-component") return;
|
|
36
|
+
await this.params.buildService.build({
|
|
37
|
+
appDirectory: params.appDirectory,
|
|
38
|
+
install: true
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
reconcileGeneratedSiblings = async (parentDirectory, referencedPaths) => {
|
|
42
|
+
for (const entry of await this.readDirectories(parentDirectory)) {
|
|
43
|
+
const entryName = path.basename(entry);
|
|
44
|
+
if (entryName.includes(".staging-")) {
|
|
45
|
+
await this.params.integrityService.removeDirectory(entry);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const markerIndex = entryName.indexOf(".uninstalling-");
|
|
49
|
+
if (markerIndex < 0) continue;
|
|
50
|
+
const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));
|
|
51
|
+
if (referencedPaths.has(path.resolve(originalPath)) && !await this.params.integrityService.pathExists(originalPath)) await rename(entry, originalPath);
|
|
52
|
+
else await this.params.integrityService.removeDirectory(entry);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
readDirectories = async (directory) => {
|
|
56
|
+
try {
|
|
57
|
+
return (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path.join(directory, entry.name));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (this.isMissingFileError(error)) return [];
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { AppInstallationFilesystemService };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=app-installation-filesystem.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-filesystem.service.js","names":[],"sources":["../../src/services/app-installation-filesystem.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { cp, mkdir, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppBuildService } from \"#app-runtime/services/app-build.service.js\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport { isAppStandaloneManifestBundle } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\n\nexport class AppInstallationFilesystemService {\n constructor(private readonly params: {\n buildService: AppBuildService;\n integrityService: AppInstallationIntegrityService;\n manifestService: AppManifestService;\n }) {}\n\n copyToImmutableInstallDirectory = async (params: {\n appId: string;\n appVersion: string;\n extractedDirectory: string;\n installDirectory: string;\n }): Promise<string> => {\n const { appId, appVersion, extractedDirectory, installDirectory } = params;\n if (await this.params.integrityService.pathExists(installDirectory)) {\n throw new Error(`应用版本目录已存在,不能覆盖不可变版本:${appId}@${appVersion}`);\n }\n await mkdir(path.dirname(installDirectory), { recursive: true });\n const stagedInstallDirectory = `${installDirectory}.staging-${randomUUID()}`;\n try {\n await cp(extractedDirectory, stagedInstallDirectory, { recursive: true });\n const stagedManifest = await this.params.manifestService.load(stagedInstallDirectory);\n if (\n stagedManifest.manifest.id !== appId ||\n stagedManifest.manifest.version !== appVersion\n ) {\n throw new Error(\"staging manifest 与已验证 bundle 身份不一致。\");\n }\n const contentSha256 = await this.params.integrityService.calculateDigest(\n stagedInstallDirectory,\n );\n await rename(stagedInstallDirectory, installDirectory);\n await this.params.integrityService.protectDirectory(installDirectory);\n return contentSha256;\n } catch (error) {\n await rm(stagedInstallDirectory, { recursive: true, force: true });\n throw error;\n }\n };\n\n materializeDistribution = async (params: {\n appDirectory: string;\n distributionMode: AppDistributionMode;\n }): Promise<void> => {\n if (params.distributionMode !== \"source\") {\n return;\n }\n const manifestBundle = await this.params.manifestService.load(params.appDirectory);\n if (!isAppStandaloneManifestBundle(manifestBundle)) {\n throw new Error(\"schema v2 组合包不允许运行安装期 build。\");\n }\n if (manifestBundle.manifest.main.kind !== \"wasi-http-component\") {\n return;\n }\n await this.params.buildService.build({\n appDirectory: params.appDirectory,\n install: true,\n });\n };\n\n reconcileGeneratedSiblings = async (\n parentDirectory: string,\n referencedPaths: Set<string>,\n ): Promise<void> => {\n for (const entry of await this.readDirectories(parentDirectory)) {\n const entryName = path.basename(entry);\n if (entryName.includes(\".staging-\")) {\n await this.params.integrityService.removeDirectory(entry);\n continue;\n }\n const markerIndex = entryName.indexOf(\".uninstalling-\");\n if (markerIndex < 0) {\n continue;\n }\n const originalPath = path.join(parentDirectory, entryName.slice(0, markerIndex));\n if (\n referencedPaths.has(path.resolve(originalPath)) &&\n !await this.params.integrityService.pathExists(originalPath)\n ) {\n await rename(entry, originalPath);\n } else {\n await this.params.integrityService.removeDirectory(entry);\n }\n }\n };\n\n readDirectories = async (directory: string): Promise<string[]> => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(directory, entry.name));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":";;;;;AASA,IAAa,mCAAb,MAA8C;CAC5C,YAAY,QAIT;AAJ0B,OAAA,SAAA;;CAM7B,kCAAkC,OAAO,WAKlB;EACrB,MAAM,EAAE,OAAO,YAAY,oBAAoB,qBAAqB;AACpE,MAAI,MAAM,KAAK,OAAO,iBAAiB,WAAW,iBAAiB,CACjE,OAAM,IAAI,MAAM,uBAAuB,MAAM,GAAG,aAAa;AAE/D,QAAM,MAAM,KAAK,QAAQ,iBAAiB,EAAE,EAAE,WAAW,MAAM,CAAC;EAChE,MAAM,yBAAyB,GAAG,iBAAiB,WAAW,YAAY;AAC1E,MAAI;AACF,SAAM,GAAG,oBAAoB,wBAAwB,EAAE,WAAW,MAAM,CAAC;GACzE,MAAM,iBAAiB,MAAM,KAAK,OAAO,gBAAgB,KAAK,uBAAuB;AACrF,OACE,eAAe,SAAS,OAAO,SAC/B,eAAe,SAAS,YAAY,WAEpC,OAAM,IAAI,MAAM,sCAAsC;GAExD,MAAM,gBAAgB,MAAM,KAAK,OAAO,iBAAiB,gBACvD,uBACD;AACD,SAAM,OAAO,wBAAwB,iBAAiB;AACtD,SAAM,KAAK,OAAO,iBAAiB,iBAAiB,iBAAiB;AACrE,UAAO;WACA,OAAO;AACd,SAAM,GAAG,wBAAwB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAClE,SAAM;;;CAIV,0BAA0B,OAAO,WAGZ;AACnB,MAAI,OAAO,qBAAqB,SAC9B;EAEF,MAAM,iBAAiB,MAAM,KAAK,OAAO,gBAAgB,KAAK,OAAO,aAAa;AAClF,MAAI,CAAC,8BAA8B,eAAe,CAChD,OAAM,IAAI,MAAM,+BAA+B;AAEjD,MAAI,eAAe,SAAS,KAAK,SAAS,sBACxC;AAEF,QAAM,KAAK,OAAO,aAAa,MAAM;GACnC,cAAc,OAAO;GACrB,SAAS;GACV,CAAC;;CAGJ,6BAA6B,OAC3B,iBACA,oBACkB;AAClB,OAAK,MAAM,SAAS,MAAM,KAAK,gBAAgB,gBAAgB,EAAE;GAC/D,MAAM,YAAY,KAAK,SAAS,MAAM;AACtC,OAAI,UAAU,SAAS,YAAY,EAAE;AACnC,UAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM;AACzD;;GAEF,MAAM,cAAc,UAAU,QAAQ,iBAAiB;AACvD,OAAI,cAAc,EAChB;GAEF,MAAM,eAAe,KAAK,KAAK,iBAAiB,UAAU,MAAM,GAAG,YAAY,CAAC;AAChF,OACE,gBAAgB,IAAI,KAAK,QAAQ,aAAa,CAAC,IAC/C,CAAC,MAAM,KAAK,OAAO,iBAAiB,WAAW,aAAa,CAE5D,OAAM,OAAO,OAAO,aAAa;OAEjC,OAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM;;;CAK/D,kBAAkB,OAAO,cAAyC;AAChE,MAAI;AACF,WAAQ,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC,EACtD,QAAQ,UAAU,MAAM,aAAa,CAAC,CACtC,KAAK,UAAU,KAAK,KAAK,WAAW,MAAM,KAAK,CAAC;WAC5C,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO,EAAE;AAEX,SAAM;;;CAIV,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AppManifestService } from "./app-manifest.service.js";
|
|
2
|
+
import { AppRegistryInstalledVersion } from "../types/app-registry.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/app-installation-integrity.service.d.ts
|
|
5
|
+
declare class AppInstallationIntegrityService {
|
|
6
|
+
private readonly manifestService;
|
|
7
|
+
constructor(manifestService?: AppManifestService);
|
|
8
|
+
calculateDigest: (installDirectory: string) => Promise<string>;
|
|
9
|
+
assertVersion: (params: {
|
|
10
|
+
appId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
versionRecord: AppRegistryInstalledVersion;
|
|
13
|
+
}) => Promise<string>;
|
|
14
|
+
protectDirectory: (installDirectory: string) => Promise<void>;
|
|
15
|
+
removeDirectory: (directory: string) => Promise<void>;
|
|
16
|
+
pathExists: (targetPath: string) => Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppInstallationIntegrityService };
|
|
20
|
+
//# sourceMappingURL=app-installation-integrity.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-integrity.service.d.ts","names":[],"sources":["../../src/services/app-installation-integrity.service.ts"],"mappings":";;;;cAMa,+BAAA;EAAA,iBAEQ,eAAA;cAAA,eAAA,GAAiB,kBAAA;EAGpC,eAAA,GAAyB,gBAAA,aAA2B,OAAA;EAgCpD,aAAA,GAAuB,MAAA;IACrB,KAAA;IACA,OAAA;IACA,aAAA,EAAe,2BAAA;EAAA,MACb,OAAA;EAkBJ,gBAAA,GAA0B,gBAAA,aAA2B,OAAA;EAiBrD,eAAA,GAAyB,SAAA,aAAoB,OAAA;EAoB7C,UAAA,GAAoB,UAAA,aAAqB,OAAA;AAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AppManifestService } from "./app-manifest.service.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { access, chmod, lstat, readFile, readdir, readlink, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/services/app-installation-integrity.service.ts
|
|
6
|
+
var AppInstallationIntegrityService = class {
|
|
7
|
+
constructor(manifestService = new AppManifestService()) {
|
|
8
|
+
this.manifestService = manifestService;
|
|
9
|
+
}
|
|
10
|
+
calculateDigest = async (installDirectory) => {
|
|
11
|
+
const digest = createHash("sha256");
|
|
12
|
+
const visit = async (directory) => {
|
|
13
|
+
const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const entryPath = path.join(directory, entry.name);
|
|
16
|
+
const relativePath = path.relative(installDirectory, entryPath).replace(/\\/g, "/");
|
|
17
|
+
digest.update(relativePath);
|
|
18
|
+
digest.update("\0");
|
|
19
|
+
if (entry.isSymbolicLink()) {
|
|
20
|
+
digest.update(`link:${await readlink(entryPath)}`);
|
|
21
|
+
digest.update("\0");
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
digest.update("directory\0");
|
|
26
|
+
await visit(entryPath);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!entry.isFile()) throw new Error(`应用版本目录只允许普通文件、目录或符号链接:${relativePath}`);
|
|
30
|
+
digest.update("file\0");
|
|
31
|
+
digest.update(await readFile(entryPath));
|
|
32
|
+
digest.update("\0");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
await visit(path.resolve(installDirectory));
|
|
36
|
+
return digest.digest("hex");
|
|
37
|
+
};
|
|
38
|
+
assertVersion = async (params) => {
|
|
39
|
+
const { appId, version, versionRecord } = params;
|
|
40
|
+
const targetManifest = await this.manifestService.load(versionRecord.installDirectory);
|
|
41
|
+
if (targetManifest.manifest.id !== appId || targetManifest.manifest.version !== version) throw new Error(`应用 ${appId}@${version} manifest 身份校验失败。`);
|
|
42
|
+
const contentSha256 = await this.calculateDigest(versionRecord.installDirectory);
|
|
43
|
+
if (versionRecord.contentSha256 && contentSha256 !== versionRecord.contentSha256) throw new Error(`应用 ${appId}@${version} 代码完整性校验失败,已阻止激活。请重新安装该版本。`);
|
|
44
|
+
return contentSha256;
|
|
45
|
+
};
|
|
46
|
+
protectDirectory = async (installDirectory) => {
|
|
47
|
+
const protect = async (targetPath) => {
|
|
48
|
+
const stats = await lstat(targetPath);
|
|
49
|
+
if (stats.isSymbolicLink()) return;
|
|
50
|
+
if (stats.isDirectory()) {
|
|
51
|
+
for (const entry of await readdir(targetPath)) await protect(path.join(targetPath, entry));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await chmod(targetPath, stats.mode & -147);
|
|
55
|
+
};
|
|
56
|
+
await protect(path.resolve(installDirectory));
|
|
57
|
+
};
|
|
58
|
+
removeDirectory = async (directory) => {
|
|
59
|
+
if (!await this.pathExists(directory)) return;
|
|
60
|
+
const makeWritable = async (targetPath) => {
|
|
61
|
+
const stats = await lstat(targetPath);
|
|
62
|
+
if (stats.isSymbolicLink()) return;
|
|
63
|
+
await chmod(targetPath, stats.mode | 128 | (stats.isDirectory() ? 448 : 0));
|
|
64
|
+
if (stats.isDirectory()) for (const entry of await readdir(targetPath)) await makeWritable(path.join(targetPath, entry));
|
|
65
|
+
};
|
|
66
|
+
await makeWritable(directory);
|
|
67
|
+
await rm(directory, {
|
|
68
|
+
recursive: true,
|
|
69
|
+
force: true
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
pathExists = async (targetPath) => {
|
|
73
|
+
try {
|
|
74
|
+
await access(targetPath);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { AppInstallationIntegrityService };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=app-installation-integrity.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-integrity.service.js","names":[],"sources":["../../src/services/app-installation-integrity.service.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { access, chmod, lstat, readFile, readdir, readlink, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppManifestService } from \"#app-runtime/services/app-manifest.service.js\";\nimport type { AppRegistryInstalledVersion } from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppInstallationIntegrityService {\n constructor(\n private readonly manifestService: AppManifestService = new AppManifestService(),\n ) {}\n\n calculateDigest = async (installDirectory: string): Promise<string> => {\n const digest = createHash(\"sha256\");\n const visit = async (directory: string): Promise<void> => {\n const entries = (await readdir(directory, { withFileTypes: true }))\n .sort((left, right) => left.name.localeCompare(right.name));\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n const relativePath = path.relative(installDirectory, entryPath).replace(/\\\\/g, \"/\");\n digest.update(relativePath);\n digest.update(\"\\0\");\n if (entry.isSymbolicLink()) {\n digest.update(`link:${await readlink(entryPath)}`);\n digest.update(\"\\0\");\n continue;\n }\n if (entry.isDirectory()) {\n digest.update(\"directory\\0\");\n await visit(entryPath);\n continue;\n }\n if (!entry.isFile()) {\n throw new Error(`应用版本目录只允许普通文件、目录或符号链接:${relativePath}`);\n }\n digest.update(\"file\\0\");\n digest.update(await readFile(entryPath));\n digest.update(\"\\0\");\n }\n };\n await visit(path.resolve(installDirectory));\n return digest.digest(\"hex\");\n };\n\n assertVersion = async (params: {\n appId: string;\n version: string;\n versionRecord: AppRegistryInstalledVersion;\n }): Promise<string> => {\n const { appId, version, versionRecord } = params;\n const targetManifest = await this.manifestService.load(versionRecord.installDirectory);\n if (\n targetManifest.manifest.id !== appId ||\n targetManifest.manifest.version !== version\n ) {\n throw new Error(`应用 ${appId}@${version} manifest 身份校验失败。`);\n }\n const contentSha256 = await this.calculateDigest(versionRecord.installDirectory);\n if (versionRecord.contentSha256 && contentSha256 !== versionRecord.contentSha256) {\n throw new Error(\n `应用 ${appId}@${version} 代码完整性校验失败,已阻止激活。请重新安装该版本。`,\n );\n }\n return contentSha256;\n };\n\n protectDirectory = async (installDirectory: string): Promise<void> => {\n const protect = async (targetPath: string): Promise<void> => {\n const stats = await lstat(targetPath);\n if (stats.isSymbolicLink()) {\n return;\n }\n if (stats.isDirectory()) {\n for (const entry of await readdir(targetPath)) {\n await protect(path.join(targetPath, entry));\n }\n return;\n }\n await chmod(targetPath, stats.mode & ~0o222);\n };\n await protect(path.resolve(installDirectory));\n };\n\n removeDirectory = async (directory: string): Promise<void> => {\n if (!await this.pathExists(directory)) {\n return;\n }\n const makeWritable = async (targetPath: string): Promise<void> => {\n const stats = await lstat(targetPath);\n if (stats.isSymbolicLink()) {\n return;\n }\n await chmod(targetPath, stats.mode | 0o200 | (stats.isDirectory() ? 0o700 : 0));\n if (stats.isDirectory()) {\n for (const entry of await readdir(targetPath)) {\n await makeWritable(path.join(targetPath, entry));\n }\n }\n };\n await makeWritable(directory);\n await rm(directory, { recursive: true, force: true });\n };\n\n pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n}\n"],"mappings":";;;;;AAMA,IAAa,kCAAb,MAA6C;CAC3C,YACE,kBAAuD,IAAI,oBAAoB,EAC/E;AADiB,OAAA,kBAAA;;CAGnB,kBAAkB,OAAO,qBAA8C;EACrE,MAAM,SAAS,WAAW,SAAS;EACnC,MAAM,QAAQ,OAAO,cAAqC;GACxD,MAAM,WAAW,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC,EAC/D,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,KAAK,CAAC;AAC7D,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,KAAK,KAAK,WAAW,MAAM,KAAK;IAClD,MAAM,eAAe,KAAK,SAAS,kBAAkB,UAAU,CAAC,QAAQ,OAAO,IAAI;AACnF,WAAO,OAAO,aAAa;AAC3B,WAAO,OAAO,KAAK;AACnB,QAAI,MAAM,gBAAgB,EAAE;AAC1B,YAAO,OAAO,QAAQ,MAAM,SAAS,UAAU,GAAG;AAClD,YAAO,OAAO,KAAK;AACnB;;AAEF,QAAI,MAAM,aAAa,EAAE;AACvB,YAAO,OAAO,cAAc;AAC5B,WAAM,MAAM,UAAU;AACtB;;AAEF,QAAI,CAAC,MAAM,QAAQ,CACjB,OAAM,IAAI,MAAM,yBAAyB,eAAe;AAE1D,WAAO,OAAO,SAAS;AACvB,WAAO,OAAO,MAAM,SAAS,UAAU,CAAC;AACxC,WAAO,OAAO,KAAK;;;AAGvB,QAAM,MAAM,KAAK,QAAQ,iBAAiB,CAAC;AAC3C,SAAO,OAAO,OAAO,MAAM;;CAG7B,gBAAgB,OAAO,WAIA;EACrB,MAAM,EAAE,OAAO,SAAS,kBAAkB;EAC1C,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,KAAK,cAAc,iBAAiB;AACtF,MACE,eAAe,SAAS,OAAO,SAC/B,eAAe,SAAS,YAAY,QAEpC,OAAM,IAAI,MAAM,MAAM,MAAM,GAAG,QAAQ,mBAAmB;EAE5D,MAAM,gBAAgB,MAAM,KAAK,gBAAgB,cAAc,iBAAiB;AAChF,MAAI,cAAc,iBAAiB,kBAAkB,cAAc,cACjE,OAAM,IAAI,MACR,MAAM,MAAM,GAAG,QAAQ,4BACxB;AAEH,SAAO;;CAGT,mBAAmB,OAAO,qBAA4C;EACpE,MAAM,UAAU,OAAO,eAAsC;GAC3D,MAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,OAAI,MAAM,gBAAgB,CACxB;AAEF,OAAI,MAAM,aAAa,EAAE;AACvB,SAAK,MAAM,SAAS,MAAM,QAAQ,WAAW,CAC3C,OAAM,QAAQ,KAAK,KAAK,YAAY,MAAM,CAAC;AAE7C;;AAEF,SAAM,MAAM,YAAY,MAAM,OAAO,KAAO;;AAE9C,QAAM,QAAQ,KAAK,QAAQ,iBAAiB,CAAC;;CAG/C,kBAAkB,OAAO,cAAqC;AAC5D,MAAI,CAAC,MAAM,KAAK,WAAW,UAAU,CACnC;EAEF,MAAM,eAAe,OAAO,eAAsC;GAChE,MAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,OAAI,MAAM,gBAAgB,CACxB;AAEF,SAAM,MAAM,YAAY,MAAM,OAAO,OAAS,MAAM,aAAa,GAAG,MAAQ,GAAG;AAC/E,OAAI,MAAM,aAAa,CACrB,MAAK,MAAM,SAAS,MAAM,QAAQ,WAAW,CAC3C,OAAM,aAAa,KAAK,KAAK,YAAY,MAAM,CAAC;;AAItD,QAAM,aAAa,UAAU;AAC7B,QAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;CAGvD,aAAa,OAAO,eAAyC;AAC3D,MAAI;AACF,SAAM,OAAO,WAAW;AACxB,UAAO;UACD;AACN,UAAO"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rename } from "node:fs/promises";
|
|
3
|
+
//#region src/services/app-installation-lifecycle.service.ts
|
|
4
|
+
var AppInstallationLifecycleService = class {
|
|
5
|
+
constructor(params) {
|
|
6
|
+
this.params = params;
|
|
7
|
+
}
|
|
8
|
+
update = async (appId, options = {}) => {
|
|
9
|
+
const { onProgress, registryUrl: requestedRegistryUrl, version } = options;
|
|
10
|
+
await onProgress?.("resolving");
|
|
11
|
+
const appRecord = await this.params.registryService.getApp(appId);
|
|
12
|
+
if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
|
|
13
|
+
const activeVersionRecord = appRecord.installedVersions[appRecord.activeVersion];
|
|
14
|
+
const registryUrl = requestedRegistryUrl ?? activeVersionRecord?.registryUrl ?? (await this.params.registryConfigService.getSnapshot()).currentUrl;
|
|
15
|
+
const resolution = await this.params.remoteRegistryClient.resolve({
|
|
16
|
+
appId,
|
|
17
|
+
version,
|
|
18
|
+
registryUrl
|
|
19
|
+
});
|
|
20
|
+
if (resolution.version === appRecord.activeVersion) {
|
|
21
|
+
if (!activeVersionRecord) throw new Error(`已安装应用缺少激活版本:${appId}`);
|
|
22
|
+
return this.toUpdateResult(appRecord, activeVersionRecord, appRecord.activeVersion, false);
|
|
23
|
+
}
|
|
24
|
+
const installedTarget = appRecord.installedVersions[resolution.version];
|
|
25
|
+
if (installedTarget) {
|
|
26
|
+
await onProgress?.("verifying");
|
|
27
|
+
await onProgress?.("installing");
|
|
28
|
+
if (options.activate !== false) await this.params.rollback(appId, resolution.version);
|
|
29
|
+
await onProgress?.("finalizing");
|
|
30
|
+
return this.toUpdateResult(appRecord, installedTarget, resolution.version, true);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...await this.params.install(`${appId}@${resolution.version}`, {
|
|
34
|
+
registryUrl,
|
|
35
|
+
onProgress,
|
|
36
|
+
activate: options.activate
|
|
37
|
+
}),
|
|
38
|
+
previousVersion: appRecord.activeVersion,
|
|
39
|
+
updated: true
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
uninstall = async (appId, purgeData) => {
|
|
43
|
+
const appRecord = await this.params.registryService.getApp(appId);
|
|
44
|
+
if (!appRecord) throw new Error(`未找到已安装应用:${appId}`);
|
|
45
|
+
if (!purgeData) await this.params.instanceStorageService.materializeDefaultInstance({
|
|
46
|
+
appId,
|
|
47
|
+
publisherId: appRecord.publisher?.id,
|
|
48
|
+
legacyDataDirectory: appRecord.defaultInstance.storage.layout === "legacy" ? appRecord.dataDirectory : void 0,
|
|
49
|
+
dataSchemaVersion: appRecord.defaultInstance.dataSchemaVersion
|
|
50
|
+
});
|
|
51
|
+
const removedVersions = Object.keys(appRecord.installedVersions).sort((left, right) => left.localeCompare(right));
|
|
52
|
+
const stagedPaths = [];
|
|
53
|
+
try {
|
|
54
|
+
for (const versionRecord of Object.values(appRecord.installedVersions)) await this.stagePath(versionRecord.installDirectory, stagedPaths);
|
|
55
|
+
if (purgeData) await this.stagePath(appRecord.defaultInstance.storage.layout === "instance-v1" ? appRecord.defaultInstance.storage.instanceDirectory : appRecord.dataDirectory, stagedPaths);
|
|
56
|
+
if (!await this.params.registryService.removeApp(appId)) throw new Error(`卸载过程中应用记录已发生变化:${appId}`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
await this.restoreOrThrow(appId, stagedPaths, error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
await Promise.all(stagedPaths.map(async (entry) => await this.params.integrityService.removeDirectory(entry.stagedPath)));
|
|
62
|
+
return {
|
|
63
|
+
appId,
|
|
64
|
+
removedVersions,
|
|
65
|
+
dataRemoved: purgeData
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
toUpdateResult = (appRecord, versionRecord, version, updated) => ({
|
|
69
|
+
appId: appRecord.appId,
|
|
70
|
+
name: appRecord.name,
|
|
71
|
+
version,
|
|
72
|
+
previousVersion: appRecord.activeVersion,
|
|
73
|
+
installDirectory: versionRecord.installDirectory,
|
|
74
|
+
dataDirectory: appRecord.dataDirectory,
|
|
75
|
+
instance: appRecord.defaultInstance,
|
|
76
|
+
sourceKind: versionRecord.sourceKind,
|
|
77
|
+
distributionMode: versionRecord.distributionMode,
|
|
78
|
+
sourceRef: versionRecord.sourceRef,
|
|
79
|
+
permissions: versionRecord.permissions,
|
|
80
|
+
registryUrl: versionRecord.registryUrl,
|
|
81
|
+
bundleUrl: versionRecord.bundleUrl,
|
|
82
|
+
sha256: versionRecord.sha256,
|
|
83
|
+
publisher: versionRecord.publisher,
|
|
84
|
+
enabled: appRecord.enabled,
|
|
85
|
+
manifestSchemaVersion: versionRecord.manifestSchemaVersion,
|
|
86
|
+
components: versionRecord.components,
|
|
87
|
+
primaryPanelId: versionRecord.primaryPanelId,
|
|
88
|
+
updated
|
|
89
|
+
});
|
|
90
|
+
stagePath = async (originalPath, stagedPaths) => {
|
|
91
|
+
if (!await this.params.integrityService.pathExists(originalPath)) return;
|
|
92
|
+
const stagedPath = `${originalPath}.uninstalling-${randomUUID()}`;
|
|
93
|
+
await rename(originalPath, stagedPath);
|
|
94
|
+
stagedPaths.push({
|
|
95
|
+
originalPath,
|
|
96
|
+
stagedPath
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
restoreOrThrow = async (appId, stagedPaths, operationError) => {
|
|
100
|
+
try {
|
|
101
|
+
for (const entry of [...stagedPaths].reverse()) if (await this.params.integrityService.pathExists(entry.stagedPath)) await rename(entry.stagedPath, entry.originalPath);
|
|
102
|
+
} catch (restoreError) {
|
|
103
|
+
throw new AggregateError([operationError, restoreError], `卸载 ${appId} 失败,且无法完全恢复已暂存文件。`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { AppInstallationLifecycleService };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=app-installation-lifecycle.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-installation-lifecycle.service.js","names":[],"sources":["../../src/services/app-installation-lifecycle.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { rename } from \"node:fs/promises\";\nimport type { AppInstallationIntegrityService } from \"#app-runtime/services/app-installation-integrity.service.js\";\nimport type { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport type { AppRegistryConfigService } from \"#app-runtime/services/app-registry-config.service.js\";\nimport type { AppRegistryService } from \"#app-runtime/services/app-registry.service.js\";\nimport type { AppRemoteRegistryClientService } from \"#app-runtime/services/app-remote-registry-client.service.js\";\nimport type {\n AppInstallProgressHandler,\n AppInstallResult,\n AppRollbackResult,\n AppUninstallResult,\n AppUpdateResult,\n} from \"#app-runtime/types/app-installation.types.js\";\n\ntype AppUpdateOptions = {\n version?: string;\n registryUrl?: string;\n onProgress?: AppInstallProgressHandler;\n activate?: boolean;\n};\n\nexport class AppInstallationLifecycleService {\n constructor(private readonly params: {\n registryService: AppRegistryService;\n registryConfigService: AppRegistryConfigService;\n remoteRegistryClient: AppRemoteRegistryClientService;\n instanceStorageService: AppInstanceStorageService;\n integrityService: AppInstallationIntegrityService;\n install: (source: string, options: AppUpdateOptions) => Promise<AppInstallResult>;\n rollback: (appId: string, version: string) => Promise<AppRollbackResult>;\n }) {}\n\n update = async (appId: string, options: AppUpdateOptions = {}): Promise<AppUpdateResult> => {\n const { onProgress, registryUrl: requestedRegistryUrl, version } = options;\n await onProgress?.(\"resolving\");\n const appRecord = await this.params.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const activeVersionRecord = appRecord.installedVersions[appRecord.activeVersion];\n const registryUrl = requestedRegistryUrl ??\n activeVersionRecord?.registryUrl ??\n (await this.params.registryConfigService.getSnapshot()).currentUrl;\n const resolution = await this.params.remoteRegistryClient.resolve({\n appId,\n version,\n registryUrl,\n });\n if (resolution.version === appRecord.activeVersion) {\n if (!activeVersionRecord) {\n throw new Error(`已安装应用缺少激活版本:${appId}`);\n }\n return this.toUpdateResult(appRecord, activeVersionRecord, appRecord.activeVersion, false);\n }\n const installedTarget = appRecord.installedVersions[resolution.version];\n if (installedTarget) {\n await onProgress?.(\"verifying\");\n await onProgress?.(\"installing\");\n if (options.activate !== false) {\n await this.params.rollback(appId, resolution.version);\n }\n await onProgress?.(\"finalizing\");\n return this.toUpdateResult(appRecord, installedTarget, resolution.version, true);\n }\n const installResult = await this.params.install(`${appId}@${resolution.version}`, {\n registryUrl,\n onProgress,\n activate: options.activate,\n });\n return {\n ...installResult,\n previousVersion: appRecord.activeVersion,\n updated: true,\n };\n };\n\n uninstall = async (appId: string, purgeData: boolean): Promise<AppUninstallResult> => {\n const appRecord = await this.params.registryService.getApp(appId);\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n if (!purgeData) {\n await this.params.instanceStorageService.materializeDefaultInstance({\n appId,\n publisherId: appRecord.publisher?.id,\n legacyDataDirectory: appRecord.defaultInstance.storage.layout === \"legacy\"\n ? appRecord.dataDirectory\n : undefined,\n dataSchemaVersion: appRecord.defaultInstance.dataSchemaVersion,\n });\n }\n const removedVersions = Object.keys(appRecord.installedVersions).sort((left, right) =>\n left.localeCompare(right));\n const stagedPaths: Array<{ originalPath: string; stagedPath: string }> = [];\n try {\n for (const versionRecord of Object.values(appRecord.installedVersions)) {\n await this.stagePath(versionRecord.installDirectory, stagedPaths);\n }\n if (purgeData) {\n await this.stagePath(\n appRecord.defaultInstance.storage.layout === \"instance-v1\"\n ? appRecord.defaultInstance.storage.instanceDirectory\n : appRecord.dataDirectory,\n stagedPaths,\n );\n }\n if (!await this.params.registryService.removeApp(appId)) {\n throw new Error(`卸载过程中应用记录已发生变化:${appId}`);\n }\n } catch (error) {\n await this.restoreOrThrow(appId, stagedPaths, error);\n throw error;\n }\n await Promise.all(stagedPaths.map(async (entry) =>\n await this.params.integrityService.removeDirectory(entry.stagedPath)));\n return { appId, removedVersions, dataRemoved: purgeData };\n };\n\n private toUpdateResult = (\n appRecord: NonNullable<Awaited<ReturnType<AppRegistryService[\"getApp\"]>>>,\n versionRecord: NonNullable<Awaited<ReturnType<AppRegistryService[\"getApp\"]>>>[\"installedVersions\"][string],\n version: string,\n updated: boolean,\n ): AppUpdateResult => ({\n appId: appRecord.appId,\n name: appRecord.name,\n version,\n previousVersion: appRecord.activeVersion,\n installDirectory: versionRecord.installDirectory,\n dataDirectory: appRecord.dataDirectory,\n instance: appRecord.defaultInstance,\n sourceKind: versionRecord.sourceKind,\n distributionMode: versionRecord.distributionMode,\n sourceRef: versionRecord.sourceRef,\n permissions: versionRecord.permissions,\n registryUrl: versionRecord.registryUrl,\n bundleUrl: versionRecord.bundleUrl,\n sha256: versionRecord.sha256,\n publisher: versionRecord.publisher,\n enabled: appRecord.enabled,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion,\n components: versionRecord.components,\n primaryPanelId: versionRecord.primaryPanelId,\n updated,\n });\n\n private stagePath = async (\n originalPath: string,\n stagedPaths: Array<{ originalPath: string; stagedPath: string }>,\n ): Promise<void> => {\n if (!await this.params.integrityService.pathExists(originalPath)) {\n return;\n }\n const stagedPath = `${originalPath}.uninstalling-${randomUUID()}`;\n await rename(originalPath, stagedPath);\n stagedPaths.push({ originalPath, stagedPath });\n };\n\n private restoreOrThrow = async (\n appId: string,\n stagedPaths: Array<{ originalPath: string; stagedPath: string }>,\n operationError: unknown,\n ): Promise<void> => {\n try {\n for (const entry of [...stagedPaths].reverse()) {\n if (await this.params.integrityService.pathExists(entry.stagedPath)) {\n await rename(entry.stagedPath, entry.originalPath);\n }\n }\n } catch (restoreError) {\n throw new AggregateError(\n [operationError, restoreError],\n `卸载 ${appId} 失败,且无法完全恢复已暂存文件。`,\n );\n }\n };\n}\n"],"mappings":";;;AAsBA,IAAa,kCAAb,MAA6C;CAC3C,YAAY,QAQT;AAR0B,OAAA,SAAA;;CAU7B,SAAS,OAAO,OAAe,UAA4B,EAAE,KAA+B;EAC1F,MAAM,EAAE,YAAY,aAAa,sBAAsB,YAAY;AACnE,QAAM,aAAa,YAAY;EAC/B,MAAM,YAAY,MAAM,KAAK,OAAO,gBAAgB,OAAO,MAAM;AACjE,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;EAEtC,MAAM,sBAAsB,UAAU,kBAAkB,UAAU;EAClE,MAAM,cAAc,wBAClB,qBAAqB,gBACpB,MAAM,KAAK,OAAO,sBAAsB,aAAa,EAAE;EAC1D,MAAM,aAAa,MAAM,KAAK,OAAO,qBAAqB,QAAQ;GAChE;GACA;GACA;GACD,CAAC;AACF,MAAI,WAAW,YAAY,UAAU,eAAe;AAClD,OAAI,CAAC,oBACH,OAAM,IAAI,MAAM,eAAe,QAAQ;AAEzC,UAAO,KAAK,eAAe,WAAW,qBAAqB,UAAU,eAAe,MAAM;;EAE5F,MAAM,kBAAkB,UAAU,kBAAkB,WAAW;AAC/D,MAAI,iBAAiB;AACnB,SAAM,aAAa,YAAY;AAC/B,SAAM,aAAa,aAAa;AAChC,OAAI,QAAQ,aAAa,MACvB,OAAM,KAAK,OAAO,SAAS,OAAO,WAAW,QAAQ;AAEvD,SAAM,aAAa,aAAa;AAChC,UAAO,KAAK,eAAe,WAAW,iBAAiB,WAAW,SAAS,KAAK;;AAOlF,SAAO;GACL,GANoB,MAAM,KAAK,OAAO,QAAQ,GAAG,MAAM,GAAG,WAAW,WAAW;IAChF;IACA;IACA,UAAU,QAAQ;IACnB,CAAC;GAGA,iBAAiB,UAAU;GAC3B,SAAS;GACV;;CAGH,YAAY,OAAO,OAAe,cAAoD;EACpF,MAAM,YAAY,MAAM,KAAK,OAAO,gBAAgB,OAAO,MAAM;AACjE,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;AAEtC,MAAI,CAAC,UACH,OAAM,KAAK,OAAO,uBAAuB,2BAA2B;GAClE;GACA,aAAa,UAAU,WAAW;GAClC,qBAAqB,UAAU,gBAAgB,QAAQ,WAAW,WAC9D,UAAU,gBACV,KAAA;GACJ,mBAAmB,UAAU,gBAAgB;GAC9C,CAAC;EAEJ,MAAM,kBAAkB,OAAO,KAAK,UAAU,kBAAkB,CAAC,MAAM,MAAM,UAC3E,KAAK,cAAc,MAAM,CAAC;EAC5B,MAAM,cAAmE,EAAE;AAC3E,MAAI;AACF,QAAK,MAAM,iBAAiB,OAAO,OAAO,UAAU,kBAAkB,CACpE,OAAM,KAAK,UAAU,cAAc,kBAAkB,YAAY;AAEnE,OAAI,UACF,OAAM,KAAK,UACT,UAAU,gBAAgB,QAAQ,WAAW,gBACzC,UAAU,gBAAgB,QAAQ,oBAClC,UAAU,eACd,YACD;AAEH,OAAI,CAAC,MAAM,KAAK,OAAO,gBAAgB,UAAU,MAAM,CACrD,OAAM,IAAI,MAAM,kBAAkB,QAAQ;WAErC,OAAO;AACd,SAAM,KAAK,eAAe,OAAO,aAAa,MAAM;AACpD,SAAM;;AAER,QAAM,QAAQ,IAAI,YAAY,IAAI,OAAO,UACvC,MAAM,KAAK,OAAO,iBAAiB,gBAAgB,MAAM,WAAW,CAAC,CAAC;AACxE,SAAO;GAAE;GAAO;GAAiB,aAAa;GAAW;;CAG3D,kBACE,WACA,eACA,SACA,aACqB;EACrB,OAAO,UAAU;EACjB,MAAM,UAAU;EAChB;EACA,iBAAiB,UAAU;EAC3B,kBAAkB,cAAc;EAChC,eAAe,UAAU;EACzB,UAAU,UAAU;EACpB,YAAY,cAAc;EAC1B,kBAAkB,cAAc;EAChC,WAAW,cAAc;EACzB,aAAa,cAAc;EAC3B,aAAa,cAAc;EAC3B,WAAW,cAAc;EACzB,QAAQ,cAAc;EACtB,WAAW,cAAc;EACzB,SAAS,UAAU;EACnB,uBAAuB,cAAc;EACrC,YAAY,cAAc;EAC1B,gBAAgB,cAAc;EAC9B;EACD;CAED,YAAoB,OAClB,cACA,gBACkB;AAClB,MAAI,CAAC,MAAM,KAAK,OAAO,iBAAiB,WAAW,aAAa,CAC9D;EAEF,MAAM,aAAa,GAAG,aAAa,gBAAgB,YAAY;AAC/D,QAAM,OAAO,cAAc,WAAW;AACtC,cAAY,KAAK;GAAE;GAAc;GAAY,CAAC;;CAGhD,iBAAyB,OACvB,OACA,aACA,mBACkB;AAClB,MAAI;AACF,QAAK,MAAM,SAAS,CAAC,GAAG,YAAY,CAAC,SAAS,CAC5C,KAAI,MAAM,KAAK,OAAO,iBAAiB,WAAW,MAAM,WAAW,CACjE,OAAM,OAAO,MAAM,YAAY,MAAM,aAAa;WAG/C,cAAc;AACrB,SAAM,IAAI,eACR,CAAC,gBAAgB,aAAa,EAC9B,MAAM,MAAM,mBACb"}
|