UNPKG

@salesforce/source-tracking

Version:

API for tracking local and remote Salesforce metadata changes

649 lines 31.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SourceTracking = void 0; /* * Copyright 2025, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const fs = __importStar(require("node:fs")); const node_path_1 = require("node:path"); const core_1 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve"); // this is not exported by SDR (see the comments in SDR regarding its limitations) const filePathGenerator_1 = require("@salesforce/source-deploy-retrieve/lib/src/utils/filePathGenerator"); const remoteSourceTrackingService_1 = require("./shared/remote/remoteSourceTrackingService"); const localShadowRepo_1 = require("./shared/local/localShadowRepo"); const conflicts_1 = require("./shared/conflicts"); const guards_1 = require("./shared/guards"); const remoteChangeIgnoring_1 = require("./shared/remoteChangeIgnoring"); const functions_1 = require("./shared/functions"); const functions_2 = require("./shared/functions"); const metadataKeys_1 = require("./shared/metadataKeys"); const populateFilePaths_1 = require("./shared/populateFilePaths"); const populateTypesAndNames_1 = require("./shared/populateTypesAndNames"); const localComponentSetArray_1 = require("./shared/localComponentSetArray"); const functions_3 = require("./shared/functions"); /** * Manages source tracking files (remote and local) * * const tracking = await SourceTracking.create({org: this.org, project: this.project}); * */ class SourceTracking extends kit_1.AsyncCreatable { registry; projectPath; org; project; packagesDirs; logger; // remote and local tracking may not exist if not initialized localRepo; remoteSourceTrackingService; forceIgnore; ignoreConflicts; subscribeSDREvents; ignoreLocalCache; orgId; constructor(options) { super(options); this.org = options.org; this.orgId = this.org.getOrgId(); this.projectPath = options.project.getPath(); this.packagesDirs = options.project.getPackageDirectories(); this.logger = core_1.Logger.childFromRoot('SourceTracking'); this.project = options.project; this.ignoreConflicts = options.ignoreConflicts ?? false; this.ignoreLocalCache = options.ignoreLocalCache ?? false; this.subscribeSDREvents = options.subscribeSDREvents ?? false; this.registry = options.registry ?? new source_deploy_retrieve_1.RegistryAccess(undefined, this.projectPath); } async init() { await this.maybeSubscribeLifecycleEvents(); } /** * * @param byPackageDir if true, returns a ComponentSet for each packageDir that has any changes * * if false, returns an array containing one ComponentSet with all changes * * if not specified, this method will follow what sfdx-project.json says * @returns ComponentSet[] */ async localChangesAsComponentSet(byPackageDir) { const [projectConfig] = await Promise.all([ this.project.resolveProjectConfig(), this.ensureLocalTracking(), ]); const sourceApiVersion = projectConfig.sourceApiVersion; const [nonDeletes, deletes] = await Promise.all([ this.localRepo.getNonDeleteFilenames(), this.localRepo.getDeleteFilenames(), ]); // it'll be easier to filter filenames and work with smaller component sets than to filter SourceComponents const groupings = (0, localComponentSetArray_1.getGroupedFiles)({ packageDirs: this.packagesDirs, nonDeletes, deletes, }, byPackageDir ?? Boolean(projectConfig.pushPackageDirectoriesSequentially)); // if the users specified true or false for the param, that overrides the project config this.logger.debug(`will build array of ${groupings.length} componentSet(s)`); return (0, localComponentSetArray_1.getComponentSets)({ groupings, sourceApiVersion, registry: this.registry }); } /** reads tracking files for remote changes. It DOES NOT consider the effects of .forceignore unless told to */ async remoteNonDeletesAsComponentSet({ applyIgnore = false, } = {}) { if (applyIgnore) { this.forceIgnore ??= source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path); } const [changeResults, sourceBackedComponents, projectConfig] = await Promise.all([ // all changes based on remote tracking this.getChanges({ origin: 'remote', state: 'nondelete', format: 'ChangeResult', }), // only returns source-backed components (SBC) this.getChanges({ origin: 'remote', state: 'nondelete', format: 'SourceComponent', }), this.project.resolveProjectConfig(), ]); const componentSet = new source_deploy_retrieve_1.ComponentSet(applyIgnore ? sourceBackedComponents.filter(noFileIsIgnored(this.forceIgnore)) : sourceBackedComponents, this.registry); // there may be remote adds not in the SBC. So we add those manually (applyIgnore ? (0, remoteChangeIgnoring_1.removeIgnored)(changeResults, this.forceIgnore, this.project.getDefaultPackage().fullPath, this.registry) : changeResults.map(functions_1.remoteChangeToMetadataMember)).map((mm) => { componentSet.add(mm); }); if (projectConfig.sourceApiVersion) { componentSet.sourceApiVersion = projectConfig.sourceApiVersion; } return componentSet; } /** * Does most of the work for the force:source:status command. * Outputs need a bit of massage since this aims to provide nice json. * * @param local you want local status * @param remote you want remote status * @returns StatusOutputRow[] */ async getStatus({ local, remote }) { let results = []; if (local) { results = results.concat(await this.getLocalStatusRows()); } if (remote) { await this.ensureRemoteTracking(true); const [remoteDeletes, remoteModifies] = await Promise.all([ this.getChanges({ origin: 'remote', state: 'delete', format: 'ChangeResult' }), this.getChanges({ origin: 'remote', state: 'nondelete', format: 'ChangeResultWithPaths' }), ]); results = results.concat((await Promise.all(remoteDeletes.concat(remoteModifies).map((item) => this.remoteChangesToOutputRows(item)))).flat(1)); } if (local && remote) { // keys like ApexClass__MyClass.cls const conflictFiles = new Set((await this.getConflicts()).flatMap((conflict) => conflict.filenames).filter(ts_types_1.isString)); results = results.map((row) => ({ ...row, conflict: !!row.filePath && conflictFiles.has(row.filePath), })); } return results; } async getChanges(options) { if (options?.origin === 'local') { await this.ensureLocalTracking(); const filenames = await getLocalChangesAsFilenames(this.localRepo)(options.state); if (options.format === 'string') { return filenames; } if (options.format === 'ChangeResult' || options.format === 'ChangeResultWithPaths') { return filenames.map((filename) => ({ filenames: [filename], origin: 'local', })); } if (options.format === 'SourceComponent') { const resolver = new source_deploy_retrieve_1.MetadataResolver(this.registry, options.state === 'delete' ? source_deploy_retrieve_1.VirtualTreeContainer.fromFilePaths(filenames) : undefined); return filenames .flatMap((filename) => { try { return resolver.getComponentsFromPath(filename); } catch (e) { this.logger.warn(`unable to resolve ${filename}`); return undefined; } }) .filter(guards_1.isDefined); } } if (options?.origin === 'remote') { await this.ensureRemoteTracking(); const remoteChanges = await this.remoteSourceTrackingService.retrieveUpdates(); this.logger.debug('remoteChanges', remoteChanges); const filteredChanges = remoteChanges .filter(remoteFilterByState[options.state]) // skip any remote types not in the registry. Will emit warnings .filter((rce) => (0, metadataKeys_1.registrySupportsType)(this.registry)(rce.type)); if (options.format === 'ChangeResult') { return filteredChanges.map((0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(this.registry)); } if (options.format === 'ChangeResultWithPaths') { return (0, populateFilePaths_1.populateFilePaths)({ elements: filteredChanges.map((0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(this.registry)), packageDirPaths: this.project.getPackageDirectories().map((pkgDir) => pkgDir.fullPath), registry: this.registry, }); } // turn it into a componentSet to resolve filenames const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(filteredChanges.map((element) => ({ type: element?.type, fullName: element?.name, })), this.registry); const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({ fsPaths: this.packagesDirs.map((dir) => (0, node_path_1.resolve)(dir.fullPath)), include: remoteChangesAsComponentSet, registry: this.registry, }); if (options.format === 'string') { return matchingLocalSourceComponentsSet.getSourceComponents().toArray().flatMap(functions_2.getAllFiles); } else if (options.format === 'SourceComponent') { return matchingLocalSourceComponentsSet.getSourceComponents().toArray(); } } throw new Error(`unsupported options: ${JSON.stringify(options)}`); } async maybeApplyRemoteDeletesToLocal(returnDeleteFileResponses) { const changesToDelete = await this.getChanges({ origin: 'remote', state: 'delete', format: 'SourceComponent' }); const fileResponsesFromDelete = await this.deleteFilesAndUpdateTracking(changesToDelete); return returnDeleteFileResponses ? { componentSetFromNonDeletes: await this.remoteNonDeletesAsComponentSet({ applyIgnore: true }), fileResponsesFromDelete, } : this.remoteNonDeletesAsComponentSet({ applyIgnore: true }); } /** * * returns immediately if there are no changesToDelete * * @param changesToDelete array of SourceComponent */ async deleteFilesAndUpdateTracking(changesToDelete) { if (changesToDelete.length === 0) { return []; } const sourceComponentByFileName = new Map(changesToDelete.flatMap((component) => (0, functions_2.getAllFiles)(component).map((filename) => [filename, component]))); // calculate what to return before we delete any files and .walkContent is no longer valid const changedToBeDeleted = changesToDelete.flatMap((component) => (0, functions_2.getAllFiles)(component).map((file) => ({ state: source_deploy_retrieve_1.ComponentStatus.Deleted, filePath: file, type: component.type.name, fullName: component.fullName, }))); // original CustomLabels behavior const nonDecomposedLabels = this.registry.getTypeByName('customlabels').strategies?.transformer === 'nonDecomposed'; const filenames = Array.from(sourceComponentByFileName.keys()); // delete the files await Promise.all(filenames.map((filename) => sourceComponentByFileName.get(filename)?.type.id === 'customlabel' && nonDecomposedLabels ? (0, functions_2.deleteCustomLabels)(filename, changesToDelete.filter(functions_3.sourceComponentIsCustomLabel)) : fs.promises.unlink(filename))); // update the tracking files. We're simulating SDR-style fileResponse await Promise.all([ this.updateLocalTracking({ deletedFiles: filenames }), this.updateRemoteTracking(changesToDelete.map((component) => ({ type: component.type.name, fullName: component.fullName, state: source_deploy_retrieve_1.ComponentStatus.Deleted, })), true // skip polling because it's a pull ), ]); return changedToBeDeleted; } /** * Update tracking for the options passed. * * @param options the files to update */ async updateLocalTracking(options) { this.logger.trace('start: updateLocalTracking', options); await this.ensureLocalTracking(); this.logger.trace('files', options.files); // relative paths make smaller trees AND isogit wants them relative const relativeOptions = { files: (options.files ?? []).map((0, functions_2.ensureRelative)(this.projectPath)), deletedFiles: (options.deletedFiles ?? []).map((0, functions_2.ensureRelative)(this.projectPath)), }; // plot twist: if you delete a member of a bundle (ex: lwc/foo/foo.css) and push, it'll not be in the fileResponses (deployedFiles) or deletedFiles // what got deleted? Any local changes NOT in the fileResponses but part of a successfully deployed bundle const deployedFilesAsVirtualComponentSet = source_deploy_retrieve_1.ComponentSet.fromSource({ // resolve from highest possible level. TODO: can we use [.] fsPaths: relativeOptions.files.length ? [relativeOptions.files[0].split(node_path_1.sep)[0]] : [], tree: source_deploy_retrieve_1.VirtualTreeContainer.fromFilePaths(relativeOptions.files), registry: this.registry, }); // these are top-level bundle paths like lwc/foo const bundlesWithDeletedFiles = (await this.getChanges({ origin: 'local', state: 'delete', format: 'SourceComponent' })) .filter(functions_2.supportsPartialDelete) .filter((cmp) => deployedFilesAsVirtualComponentSet.has({ type: cmp.type, fullName: cmp.fullName })) .map((cmp) => cmp.content) .filter(ts_types_1.isString); await this.localRepo.commitChanges({ deployedFiles: relativeOptions.files, deletedFiles: relativeOptions.deletedFiles.concat((await this.localRepo.getDeleteFilenames()).filter((deployedFile) => bundlesWithDeletedFiles.some((0, functions_2.folderContainsPath)(deployedFile)) && !relativeOptions.files.includes(deployedFile))), }); this.logger.trace('done: updateLocalTracking', options); } /** * Mark remote source tracking files so say that we have received the latest version from the server * Optional skip polling for the SourceMembers to exist on the server and be updated in local files */ async updateRemoteTracking(fileResponses, skipPolling = false) { // false to explicitly NOT query until we do the polling await this.ensureRemoteTracking(false); if (!skipPolling) { // poll to make sure we have the updates before syncing the ones from metadataKeys await this.remoteSourceTrackingService.pollForSourceTracking(this.registry, fileResponses); } await this.remoteSourceTrackingService.syncSpecifiedElements(this.registry, fileResponses); } async reReadLocalTrackingCache() { await this.localRepo.getStatus(true); } /** * If the local tracking shadowRepo doesn't exist, it will be created. * Does nothing if it already exists, unless you've instantiate SourceTracking to not cache local status, in which case it'll re-read your files * Useful before parallel operations */ async ensureLocalTracking() { if (this.localRepo) { if (this.ignoreLocalCache) { await this.localRepo.getStatus(true); } return; } this.localRepo = await localShadowRepo_1.ShadowRepo.getInstance({ orgId: this.orgId, projectPath: (0, node_path_1.normalize)(this.projectPath), packageDirs: this.packagesDirs, registry: this.registry, }); // loads the status from file so that it's cached await this.localRepo.getStatus(); } /** * If the remote tracking shadowRepo doesn't exist, it will be created. * Does nothing if it already exists. * Useful before parallel operations */ async ensureRemoteTracking(initializeWithQuery = false) { if (this.remoteSourceTrackingService) { this.logger.debug('ensureRemoteTracking: remote tracking already exists'); return; } this.logger.debug('ensureRemoteTracking: remote tracking does not exist yet; getting instance'); this.remoteSourceTrackingService = await remoteSourceTrackingService_1.RemoteSourceTrackingService.getInstance({ org: this.org, projectPath: this.projectPath, }); if (initializeWithQuery) { await this.remoteSourceTrackingService.retrieveUpdates(); } } /** * Deletes the local tracking shadowRepo * return the list of files that were in it */ async clearLocalTracking() { await this.ensureLocalTracking(); return this.localRepo.delete(); } /** * Commits all the local changes so that no changes are present in status */ async resetLocalTracking() { await this.ensureLocalTracking(); const [deletes, nonDeletes] = await Promise.all([ this.localRepo.getDeleteFilenames(), this.localRepo.getNonDeleteFilenames(), ]); await this.localRepo.commitChanges({ deletedFiles: deletes, deployedFiles: nonDeletes, message: 'via resetLocalTracking', }); return [...deletes, ...nonDeletes]; } /** * Deletes the remote tracking files */ async clearRemoteTracking() { return remoteSourceTrackingService_1.RemoteSourceTrackingService.delete(this.orgId); } /** * Sets the files to max revision so that no changes appear */ async resetRemoteTracking(serverRevision) { await this.ensureRemoteTracking(); const resetMembers = await this.remoteSourceTrackingService.reset(serverRevision); return resetMembers.length; } /** * Compares local and remote changes to detect conflicts */ async getConflicts() { // we're going to need have both initialized await Promise.all([this.ensureRemoteTracking(), this.ensureLocalTracking()]); // Strategy: check local changes first (since it'll be faster) to avoid callout // early return if either local or remote is empty const localChanges = await this.getChanges({ state: 'nondelete', origin: 'local', format: 'ChangeResult', }); if (localChanges.length === 0) { return []; } const remoteChanges = await this.getChanges({ origin: 'remote', state: 'nondelete', // remote adds won't have a filename, so we ask for it to be resolved format: 'ChangeResultWithPaths', }); if (remoteChanges.length === 0) { return []; } this.forceIgnore ??= source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path); const result = (0, conflicts_1.getDedupedConflictsFromChanges)({ localChanges, remoteChanges, projectPath: this.projectPath, forceIgnore: this.forceIgnore, registry: this.registry, }); return result; } /** * handles both remote and local tracking * * @param result FileResponse[] */ async updateTrackingFromDeploy(deployResult) { const successes = deployResult.getFileResponses().filter(guards_1.isSdrSuccess).filter(guards_1.FileResponseHasPath); if (!successes.length) { return; } await Promise.all([ this.updateLocalTracking({ // assertions allowed because filtered above files: successes.filter(guards_1.FileResponseIsNotDeleted).map(filePathFromFileResponse), deletedFiles: successes.filter(guards_1.FileResponseIsDeleted).map(filePathFromFileResponse), }), this.updateRemoteTracking(successes.map(functions_1.FileResponseSuccessToRemoteSyncInput)), ]); } /** * handles both remote and local tracking * * @param result FileResponse[] */ async updateTrackingFromRetrieve(retrieveResult) { const successes = retrieveResult.getFileResponses().filter(guards_1.isSdrSuccess); if (!successes.length) { return; } await Promise.all([ this.updateLocalTracking({ // assertion allowed because it's filtering out undefined files: successes.filter(guards_1.FileResponseIsNotDeleted).filter(guards_1.FileResponseHasPath).map(filePathFromFileResponse), deletedFiles: successes.filter(guards_1.FileResponseIsDeleted).filter(guards_1.FileResponseHasPath).map(filePathFromFileResponse), }), this.updateRemoteTracking(successes.map(functions_1.FileResponseSuccessToRemoteSyncInput), true // retrieves don't need to poll for SourceMembers ), ]); } /** * If you've already got an instance of STL, but need to change the conflicts setting * normally you set this on instantiation * * @param value true/false */ setIgnoreConflicts(value) { this.ignoreConflicts = value; } async maybeSubscribeLifecycleEvents() { if (this.subscribeSDREvents && (await this.org.tracksSource())) { const lifecycle = core_1.Lifecycle.getInstance(); // the only thing STL uses pre events for is to check conflicts. So if you don't care about conflicts, don't listen! if (!this.ignoreConflicts) { this.logger.debug('subscribing to predeploy/retrieve events'); // subscribe to SDR `pre` events to handle conflicts before deploy/retrieve lifecycle.on('scopedPreDeploy', async (e) => { this.logger.debug('received scopedPreDeploy event'); if (e.orgId === this.orgId) { (0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts())); } }, `stl#scopedPreDeploy-${this.orgId}`); lifecycle.on('scopedPreRetrieve', async (e) => { this.logger.debug('received scopedPreRetrieve event'); if (e.orgId === this.orgId) { (0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts())); } }, `stl#scopedPreRetrieve-${this.orgId}`); } // subscribe to SDR post-deploy event this.logger.debug('subscribing to postdeploy/retrieve events'); // yes, the post hooks really have different payloads! lifecycle.on('scopedPostDeploy', async (e) => { this.logger.debug('received scopedPostDeploy event'); if (e.orgId === this.orgId && e.deployResult.response.success) { await this.updateTrackingFromDeploy(e.deployResult); } }, `stl#scopedPostDeploy-${this.orgId}`); lifecycle.on('scopedPostRetrieve', async (e) => { this.logger.debug('received scopedPostRetrieve event'); if (e.orgId === this.orgId && e.retrieveResult.response.success) { await this.updateTrackingFromRetrieve(e.retrieveResult); } }, `stl#scopedPostRetrieve-${this.orgId}`); } } async getLocalStatusRows() { await this.ensureLocalTracking(); this.forceIgnore ??= source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path); // ensure forceignore is initialized const [adds, modifies, deletes] = await Promise.all(['add', 'modify', 'delete'].map((state) => this.getChanges({ origin: 'local', state, format: 'ChangeResult' }))); const base = { projectPath: this.projectPath, registry: this.registry, excludeUnresolvable: true }; const toOutput = localChangesToOutputRow(this.logger)(this.forceIgnore); return [ ...(0, populateTypesAndNames_1.populateTypesAndNames)(base)(adds).flatMap(toOutput('add')), ...(0, populateTypesAndNames_1.populateTypesAndNames)(base)(modifies).flatMap(toOutput('modify')), ...(0, populateTypesAndNames_1.populateTypesAndNames)({ ...base, resolveDeleted: true })(deletes).flatMap(toOutput('delete')), ]; } // reserve the right to do something more sophisticated in the future // via async for figuring out hypothetical filenames (ex: getting default packageDir) // eslint-disable-next-line @typescript-eslint/require-await async remoteChangesToOutputRows(input) { this.logger.debug('converting ChangeResult to a row', input); this.forceIgnore ??= source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path); const baseObject = { type: input.type ?? '', origin: input.origin, state: stateFromChangeResult(input), fullName: input.name ?? '', }; // it's easy to check ignores if the filePaths exist locally if (input.filenames?.length) { return input.filenames.map((filename) => ({ ...baseObject, filePath: filename, ignored: this.forceIgnore.denies(filename), })); } // when the file doesn't exist locally, there are no filePaths // SDR can generate the hypothetical place it *would* go and check that if ((0, guards_1.isChangeResultWithNameAndType)(input)) { const ignored = (0, filePathGenerator_1.filePathsFromMetadataComponent)((0, functions_1.changeResultToMetadataComponent)(this.registry)(input)).some((0, functions_2.forceIgnoreDenies)(this.forceIgnore)); return [ { ...baseObject, ignored, }, ]; } return [baseObject]; } } exports.SourceTracking = SourceTracking; const remoteFilterByState = { add: (change) => !change.deleted && !change.modified, modify: (change) => change.modified === true, delete: (change) => change.deleted === true, nondelete: (change) => !change.deleted, }; const stateFromChangeResult = (input) => { if (input.deleted) { return 'delete'; } if (input.modified) { return 'modify'; } return 'add'; }; const getLocalChangesAsFilenames = (localRepo) => async (state) => { switch (state) { case 'modify': return localRepo.getModifyFilenames(); case 'nondelete': return localRepo.getNonDeleteFilenames(); case 'delete': return localRepo.getDeleteFilenames(); case 'add': return localRepo.getAddFilenames(); } }; const filePathFromFileResponse = (input) => input.filePath; const noFileIsIgnored = (forceIgnore) => (cmp) => !(0, functions_2.getAllFiles)(cmp).some((0, functions_2.forceIgnoreDenies)(forceIgnore)); const localChangesToOutputRow = (logger) => (forceIgnore) => (localType) => (input) => { logger.debug('converting ChangeResult to a row', input); if (input.filenames) { return input.filenames.map((filename) => ({ type: input.type ?? '', state: localType, fullName: input.name ?? '', filePath: filename, origin: 'local', ignored: forceIgnore.denies(filename), })); } throw new Error('no filenames found for local ChangeResult'); }; //# sourceMappingURL=sourceTracking.js.map