UNPKG

sf-decomposer

Version:

Decompose Salesforce metadata into granular, VCS-friendly files; recompose for deployment.

116 lines 6.02 kB
'use strict'; import { readdir, stat } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; import { CONCURRENCY_LIMITS, CUSTOM_LABELS_FILE } from '../helpers/constants.js'; import { pLimit } from '../helpers/pLimit.js'; /** * One-level `readdir` of `dir`, filtered to files ending in `.${metaSuffix}-meta.xml`. * Same glob `perFileHandler` (`decomposeFileHandler.ts`) already uses. Returns `[]` * when `dir` doesn't exist, matching the swallow-errors convention used elsewhere for * directory walks (e.g. `getPackageDirectories.searchRecursively`). */ export async function listSuffixFiles(dir, metaSuffix) { const metaEnding = `.${metaSuffix}-meta.xml`; let entries; try { entries = await readdir(dir, { withFileTypes: true }); } catch { return []; } return entries .filter((entry) => entry.isFile() && entry.name.endsWith(metaEnding)) .map((entry) => ({ filePath: join(dir, entry.name), fullName: entry.name.slice(0, -metaEnding.length), })); } async function pathExists(path) { try { await stat(path); return true; } catch { return false; } } /** * Every parent XML file inside a single `strictDirectoryName`/`folderType` component directory * (e.g. `bots/MyBot` or `reports/MyFolder`), with `fullName` set uniformly to the *directory's* * basename for every file found. This matches `subDirectoryHandler` * (`decomposeFileHandler.ts`): it resolves component-scope config overrides exactly once per * component directory (keyed by that directory's own name) and applies the result to every file * the crate's directory-mode disassemble finds inside — not per individual file. A folder-typed * directory holding several reports resolves overrides once for the whole folder, not once per * report. * * Bots are a special case worth calling out: a bot's component directory holds both * `<name>.bot-meta.xml` and one or more sibling `<version>.botVersion-meta.xml` files * (confirmed against `fixtures/package-dir-2/bots/*`). Both are real decomposable content that * the crate's directory-mode disassemble processes, so both must be discovered here too, or * bot version files are silently never verified at all. */ async function listComponentDirFiles(componentDir, metaSuffix) { const files = await listSuffixFiles(componentDir, metaSuffix); const versionFiles = metaSuffix === 'bot' ? await listSuffixFiles(componentDir, 'botVersion') : []; const fullName = basename(componentDir); return [...files, ...versionFiles].map((file) => ({ ...file, fullName })); } /** * List every parent metadata XML file on disk for a resolved metadata type, mirroring the file * shapes `decomposeFileHandler.ts` discovers during a real decompose — but read-only, and without * the override-driven `perFileHandler` vs. whole-directory split (that split only changes *how* * real decompose disassembles a file, not *which* files exist; every file this function finds * would eventually be visited by a real decompose run one way or another). * * `fullName` on each result is the same component identity `decomposeFileHandler.ts` uses to * resolve component-scope config overrides (directory basename for `strictDirectoryName`/ * `folderType` types, filename minus the meta suffix ending otherwise). */ export async function listParentXmlFilesForType(metaAttributes, manifestXmlPaths) { const { metaSuffix, strictDirectoryName, folderType, metadataPaths } = metaAttributes; if (manifestXmlPaths && manifestXmlPaths.size > 0) { if (strictDirectoryName || folderType) { // Mirrors decomposeFromManifest: dedupe by parent component directory and process the // whole directory, since a folder-typed manifest entry may share its directory with // sibling components the manifest itself never mentioned by path. const componentDirs = new Set(Array.from(manifestXmlPaths).map((xmlPath) => dirname(xmlPath))); const results = await Promise.all(Array.from(componentDirs).map((componentDir) => listComponentDirFiles(componentDir, metaSuffix))); return results.flat(); } const metaEnding = `.${metaSuffix}-meta.xml`; return Array.from(manifestXmlPaths).map((filePath) => ({ filePath, fullName: basename(filePath).endsWith(metaEnding) ? basename(filePath).slice(0, -metaEnding.length) : basename(filePath), })); } const dirLimit = pLimit(CONCURRENCY_LIMITS.PACKAGE_DIRS); if (metaSuffix === 'labels') { const results = await Promise.all(metadataPaths.map((metadataPath) => dirLimit(async () => { const filePath = join(metadataPath, CUSTOM_LABELS_FILE); return (await pathExists(filePath)) ? [{ filePath, fullName: 'CustomLabels' }] : []; }))); return results.flat(); } if (strictDirectoryName || folderType) { const perPathResults = await Promise.all(metadataPaths.map((metadataPath) => dirLimit(async () => { let entries; try { entries = await readdir(metadataPath, { withFileTypes: true }); } catch { return []; } const componentDirNames = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name); const subLimit = pLimit(CONCURRENCY_LIMITS.SUBDIRECTORIES); const perComponent = await Promise.all(componentDirNames.map((componentDirName) => subLimit(() => listComponentDirFiles(join(metadataPath, componentDirName), metaSuffix)))); return perComponent.flat(); }))); return perPathResults.flat(); } const perPathResults = await Promise.all(metadataPaths.map((metadataPath) => dirLimit(() => listSuffixFiles(metadataPath, metaSuffix)))); return perPathResults.flat(); } //# sourceMappingURL=listParentXmlFiles.js.map