From fb19c24cc81fed09b650ee889658ef9fd291915c Mon Sep 17 00:00:00 2001 From: Himanshu Shekhar Date: Wed, 5 Aug 2026 11:06:19 +0530 Subject: [PATCH 1/2] feat: expand dataspace-scoped dependency closure at deploy time Data Cloud dataspace-scoped types (CalculatedInsight, DataModelObject) declare dependencies inline (a CI dependsOn its DataModelObject(s)). When deploying such a component, its referenced dependencies must ride along in the same deploy, but nothing else in the project should. executeDeploy now expands the requested ComponentSet with just the transitive dataspace-scoped dependency closure via SDR's expandDataspaceScopedComponentSet. Requests with no dataspace-scoped components return untouched (cheap no-op for every other deploy). Depends on forcedotcom/source-deploy-retrieve#1816, which adds the dataspaceScoped adapter/transformer and expandDataspaceScopedComponentSet. --- src/utils/deploy.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/utils/deploy.ts b/src/utils/deploy.ts index 8fdd80e4..75f354f3 100644 --- a/src/utils/deploy.ts +++ b/src/utils/deploy.ts @@ -24,6 +24,7 @@ import { ComponentStatus, DeployResult, DestructiveChangesType, + expandDataspaceScopedComponentSet, FileResponseSuccess, MetadataApiDeploy, MetadataApiDeployOptions, @@ -119,6 +120,29 @@ export async function buildComponentSet(opts: Partial, stl?: Sour }); } +/** + * Data Cloud dataspace-scoped types (CalculatedInsight, DataModelObject) declare their + * dependencies inline (a CI `dependsOn` its DataModelObject(s)). When the user asks to deploy such a + * component, the referenced dependencies must ride along in the same deploy, but nothing else in the + * project should. This resolves the whole project, then returns the requested set expanded with just + * the transitive dataspace-scoped dependency closure. If the requested set has no dataspace-scoped + * components, the original set is returned untouched (cheap no-op for every other deploy). + */ +async function expandDataspaceScopedDependencies( + requested: ComponentSet, + registry?: RegistryAccess +): Promise { + const hasDataspaceScoped = [...requested.getSourceComponents()].some( + (c) => c.type.strategies?.adapter === 'dataspaceScoped' + ); + if (!hasDataspaceScoped) { + return requested; + } + // Resolve the full project so dependsOn references (by entityPayload.name) can be located. + const full = await ComponentSetBuilder.build({ sourcepath: await getPackageDirs() }); + return expandDataspaceScopedComponentSet(full, requested, registry); +} + export async function executeDeploy( opts: Partial, project?: SfProject, @@ -166,6 +190,7 @@ export async function executeDeploy( registry = stl.registry; componentSet = await buildComponentSet(opts, stl); + componentSet = await expandDataspaceScopedDependencies(componentSet, registry); if (componentSet.size === 0) { if (opts['source-dir'] ?? opts.manifest ?? opts.metadata ?? throwOnEmpty) { // the user specified something to deploy, but there isn't anything From 51215cf80c92849c20082f1dce0b93f170d75b53 Mon Sep 17 00:00:00 2001 From: Himanshu Shekhar Date: Wed, 5 Aug 2026 15:50:24 +0530 Subject: [PATCH 2/2] refactor: gate dataspace-scoped expansion behind explicit branch, off the default deploy path --- src/utils/deploy.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/utils/deploy.ts b/src/utils/deploy.ts index 75f354f3..b777119b 100644 --- a/src/utils/deploy.ts +++ b/src/utils/deploy.ts @@ -120,24 +120,28 @@ export async function buildComponentSet(opts: Partial, stl?: Sour }); } +/** + * Whether any requested component uses the Data Cloud dataspace-scoped adapter strategy + * (CalculatedInsight, DataModelObject). This is the gate for the dataspace-only dependency + * expansion below — every other deploy short-circuits before touching that code path. + */ +const hasDataspaceScopedComponents = (requested: ComponentSet): boolean => + [...requested.getSourceComponents()].some((c) => c.type.strategies?.adapter === 'dataspaceScoped'); + /** * Data Cloud dataspace-scoped types (CalculatedInsight, DataModelObject) declare their * dependencies inline (a CI `dependsOn` its DataModelObject(s)). When the user asks to deploy such a * component, the referenced dependencies must ride along in the same deploy, but nothing else in the * project should. This resolves the whole project, then returns the requested set expanded with just - * the transitive dataspace-scoped dependency closure. If the requested set has no dataspace-scoped - * components, the original set is returned untouched (cheap no-op for every other deploy). + * the transitive dataspace-scoped dependency closure. + * + * Only call this when {@link hasDataspaceScopedComponents} is true — it is not a no-op for ordinary + * deploys (it resolves the entire project). */ async function expandDataspaceScopedDependencies( requested: ComponentSet, registry?: RegistryAccess ): Promise { - const hasDataspaceScoped = [...requested.getSourceComponents()].some( - (c) => c.type.strategies?.adapter === 'dataspaceScoped' - ); - if (!hasDataspaceScoped) { - return requested; - } // Resolve the full project so dependsOn references (by entityPayload.name) can be located. const full = await ComponentSetBuilder.build({ sourcepath: await getPackageDirs() }); return expandDataspaceScopedComponentSet(full, requested, registry); @@ -190,7 +194,11 @@ export async function executeDeploy( registry = stl.registry; componentSet = await buildComponentSet(opts, stl); - componentSet = await expandDataspaceScopedDependencies(componentSet, registry); + // Data Cloud only: dataspace-scoped components (CI/DMO) pull their inline dependency closure + // into the same deploy. Every other deploy skips this entirely and leaves componentSet as-is. + if (hasDataspaceScopedComponents(componentSet)) { + componentSet = await expandDataspaceScopedDependencies(componentSet, registry); + } if (componentSet.size === 0) { if (opts['source-dir'] ?? opts.manifest ?? opts.metadata ?? throwOnEmpty) { // the user specified something to deploy, but there isn't anything