Skip to content

Commit 0180437

Browse files
toiroakrclaude
andcommitted
refactor(apply): extract build/load/plan into functions with tailorDBOnly check
When TAILOR_INTERNAL_APPLY_MIGRATION_VERSION is set, skip non-TailorDB services to avoid SDL composition errors from toResolverOutput(). Extract processing into dedicated functions with early return pattern: - buildServices: skip builds if tailorDBOnly - loadServices: always load TailorDB, skip others if tailorDBOnly - planServices: always plan TailorDB, return empty plans for others if tailorDBOnly Empty plans cause apply functions to do nothing, eliminating need for conditional branching in the apply phase. Fixes: tailor-inc/platform-planning#687 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4c8cef4 commit 0180437

1 file changed

Lines changed: 225 additions & 58 deletions

File tree

packages/sdk/src/cli/apply/index.ts

Lines changed: 225 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from "./services/confirm";
3333
import { applyExecutor, planExecutor } from "./services/executor";
3434
import { applyIdP, planIdP } from "./services/idp";
35+
import { createChangeSet } from "./services/index";
3536
import { applyPipeline, planPipeline } from "./services/resolver";
3637
import { applyStaticWebsite, planStaticWebsite } from "./services/staticwebsite";
3738
import { applyTailorDB, planTailorDB } from "./services/tailordb";
@@ -76,39 +77,18 @@ export async function apply(options?: ApplyOptions) {
7677
const yes = options?.yes ?? false;
7778
const buildOnly = options?.buildOnly ?? process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY === "true";
7879

80+
// TailorDB only mode: skip other services when migration version is specified
81+
const tailorDBOnlyMode = !!process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
82+
7983
// Generate user types from loaded config
8084
await generateUserTypes(config, config.path);
8185
const application = defineApplication(config);
8286

83-
// Load files first (before building)
84-
// Load workflows first and collect jobs for bundling
85-
let workflowResult: WorkflowLoadResult | undefined;
86-
if (application.workflowConfig) {
87-
workflowResult = await loadAndCollectJobs(application.workflowConfig);
88-
}
89-
90-
// Build trigger context for workflow/job trigger transformation
91-
const triggerContext = await buildTriggerContext(application.workflowConfig);
92-
93-
// Build functions (using already loaded data)
94-
for (const app of application.applications) {
95-
for (const pipeline of app.resolverServices) {
96-
await buildPipeline(pipeline.namespace, pipeline.config, triggerContext);
97-
}
98-
}
99-
if (application.executorService) {
100-
await buildExecutor(application.executorService.config, triggerContext);
101-
}
102-
let workflowBuildResult: BundleWorkflowJobsResult | undefined;
103-
if (workflowResult && workflowResult.jobs.length > 0) {
104-
const mainJobNames = workflowResult.workflowSources.map((ws) => ws.workflow.mainJob.name);
105-
workflowBuildResult = await buildWorkflow(
106-
workflowResult.jobs,
107-
mainJobNames,
108-
application.env,
109-
triggerContext,
110-
);
111-
}
87+
// Build services (skipped in TailorDB only mode)
88+
const { workflowResult, workflowBuildResult } = await buildServices(
89+
application,
90+
tailorDBOnlyMode,
91+
);
11292
if (buildOnly) return;
11393

11494
// Initialize client
@@ -122,23 +102,15 @@ export async function apply(options?: ApplyOptions) {
122102
profile: options?.profile,
123103
});
124104

125-
// Load remaining files and print logs
126-
// Order: TailorDB → Resolver → Executor → Workflow
127-
for (const tailordb of application.tailorDBServices) {
128-
await tailordb.loadTypes();
129-
}
105+
// Load services (non-TailorDB services skipped in TailorDB only mode)
106+
await loadServices(application, workflowResult, tailorDBOnlyMode);
107+
logger.newline();
130108

131-
for (const pipeline of application.resolverServices) {
132-
await pipeline.loadResolvers();
133-
}
134-
if (application.executorService) {
135-
await application.executorService.loadExecutors();
109+
// Log TailorDB only mode
110+
if (tailorDBOnlyMode) {
111+
logger.info("TailorDB only mode: other services will be skipped");
112+
logger.newline();
136113
}
137-
// Print workflow loading logs last (workflows were already loaded for bundling)
138-
if (workflowResult) {
139-
printLoadedWorkflows(workflowResult);
140-
}
141-
logger.newline();
142114

143115
// Phase 1: Plan
144116
const ctx: PlanContext = {
@@ -149,20 +121,16 @@ export async function apply(options?: ApplyOptions) {
149121
config,
150122
noSchemaCheck: options?.noSchemaCheck,
151123
};
152-
const tailorDB = await planTailorDB(ctx);
153-
const staticWebsite = await planStaticWebsite(ctx);
154-
const idp = await planIdP(ctx);
155-
const auth = await planAuth(ctx);
156-
const pipeline = await planPipeline(ctx);
157-
const app = await planApplication(ctx);
158-
const executor = await planExecutor(ctx);
159-
const workflow = await planWorkflow(
160-
client,
161-
workspaceId,
162-
application.name,
163-
workflowResult?.workflows ?? {},
164-
workflowBuildResult?.mainJobDeps ?? {},
165-
);
124+
const { tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow } =
125+
await planServices(
126+
ctx,
127+
client,
128+
workspaceId,
129+
application.name,
130+
workflowResult,
131+
workflowBuildResult,
132+
tailorDBOnlyMode,
133+
);
166134

167135
// Confirm conflicts
168136
const allConflicts: OwnerConflict[] = [
@@ -238,6 +206,7 @@ export async function apply(options?: ApplyOptions) {
238206
await applyTailorDB(client, tailorDB, "create-update");
239207

240208
// Other services: Apply after TailorDB migrations complete
209+
// (empty plans in TailorDB only mode - apply functions do nothing)
241210
await applyStaticWebsite(client, staticWebsite, "create-update");
242211
await applyIdP(client, idp, "create-update");
243212
await applyAuth(client, auth, "create-update");
@@ -299,6 +268,204 @@ async function buildWorkflow(
299268
return bundleWorkflowJobs(collectedJobs, mainJobNames, env, triggerContext);
300269
}
301270

271+
// ============================================================================
272+
// Service orchestration functions
273+
// ============================================================================
274+
275+
interface BuildServicesResult {
276+
workflowResult: WorkflowLoadResult | undefined;
277+
workflowBuildResult: BundleWorkflowJobsResult | undefined;
278+
}
279+
280+
async function buildServices(
281+
application: Readonly<Application>,
282+
tailorDBOnly: boolean,
283+
): Promise<BuildServicesResult> {
284+
if (tailorDBOnly) {
285+
return { workflowResult: undefined, workflowBuildResult: undefined };
286+
}
287+
288+
let workflowResult: WorkflowLoadResult | undefined;
289+
if (application.workflowConfig) {
290+
workflowResult = await loadAndCollectJobs(application.workflowConfig);
291+
}
292+
293+
const triggerContext = await buildTriggerContext(application.workflowConfig);
294+
295+
for (const app of application.applications) {
296+
for (const pipeline of app.resolverServices) {
297+
await buildPipeline(pipeline.namespace, pipeline.config, triggerContext);
298+
}
299+
}
300+
if (application.executorService) {
301+
await buildExecutor(application.executorService.config, triggerContext);
302+
}
303+
304+
let workflowBuildResult: BundleWorkflowJobsResult | undefined;
305+
if (workflowResult && workflowResult.jobs.length > 0) {
306+
const mainJobNames = workflowResult.workflowSources.map((ws) => ws.workflow.mainJob.name);
307+
workflowBuildResult = await buildWorkflow(
308+
workflowResult.jobs,
309+
mainJobNames,
310+
application.env,
311+
triggerContext,
312+
);
313+
}
314+
315+
return { workflowResult, workflowBuildResult };
316+
}
317+
318+
async function loadServices(
319+
application: Readonly<Application>,
320+
workflowResult: WorkflowLoadResult | undefined,
321+
tailorDBOnly: boolean,
322+
) {
323+
// Always load TailorDB types
324+
for (const tailordb of application.tailorDBServices) {
325+
await tailordb.loadTypes();
326+
}
327+
328+
if (tailorDBOnly) {
329+
return;
330+
}
331+
332+
// Load other services
333+
for (const pipeline of application.resolverServices) {
334+
await pipeline.loadResolvers();
335+
}
336+
if (application.executorService) {
337+
await application.executorService.loadExecutors();
338+
}
339+
if (workflowResult) {
340+
printLoadedWorkflows(workflowResult);
341+
}
342+
}
343+
344+
// Empty plan helpers - using type assertions since the plans are empty and won't be used
345+
type StaticWebsitePlanResult = Awaited<ReturnType<typeof planStaticWebsite>>;
346+
type IdPPlanResult = Awaited<ReturnType<typeof planIdP>>;
347+
type AuthPlanResult = Awaited<ReturnType<typeof planAuth>>;
348+
type PipelinePlanResult = Awaited<ReturnType<typeof planPipeline>>;
349+
type ApplicationPlanResult = Awaited<ReturnType<typeof planApplication>>;
350+
type ExecutorPlanResult = Awaited<ReturnType<typeof planExecutor>>;
351+
type WorkflowPlanResult = Awaited<ReturnType<typeof planWorkflow>>;
352+
353+
function emptyStaticWebsitePlan(): StaticWebsitePlanResult {
354+
return {
355+
changeSet: createChangeSet("StaticWebsites"),
356+
conflicts: [],
357+
unmanaged: [],
358+
resourceOwners: new Set<string>(),
359+
} as unknown as StaticWebsitePlanResult;
360+
}
361+
362+
function emptyIdPPlan(): IdPPlanResult {
363+
return {
364+
changeSet: {
365+
service: createChangeSet("IdP services"),
366+
client: createChangeSet("IdP clients"),
367+
},
368+
conflicts: [],
369+
unmanaged: [],
370+
resourceOwners: new Set<string>(),
371+
} as unknown as IdPPlanResult;
372+
}
373+
374+
function emptyAuthPlan(): AuthPlanResult {
375+
return {
376+
changeSet: {
377+
service: createChangeSet("Auth services"),
378+
idpConfig: createChangeSet("IdP configs"),
379+
oauth2Client: createChangeSet("OAuth2 clients"),
380+
machineUser: createChangeSet("Machine users"),
381+
userProfileConfig: createChangeSet("User profile configs"),
382+
tenantConfig: createChangeSet("Tenant configs"),
383+
scim: createChangeSet("SCIM configs"),
384+
scimResource: createChangeSet("SCIM resources"),
385+
},
386+
conflicts: [],
387+
unmanaged: [],
388+
resourceOwners: new Set<string>(),
389+
} as unknown as AuthPlanResult;
390+
}
391+
392+
function emptyPipelinePlan(): PipelinePlanResult {
393+
return {
394+
changeSet: {
395+
service: createChangeSet("Pipeline services"),
396+
resolver: createChangeSet("Resolvers"),
397+
},
398+
conflicts: [],
399+
unmanaged: [],
400+
resourceOwners: new Set<string>(),
401+
} as unknown as PipelinePlanResult;
402+
}
403+
404+
function emptyApplicationPlan(): ApplicationPlanResult {
405+
return createChangeSet("Applications") as unknown as ApplicationPlanResult;
406+
}
407+
408+
function emptyExecutorPlan(): ExecutorPlanResult {
409+
return {
410+
changeSet: createChangeSet("Executors"),
411+
conflicts: [],
412+
unmanaged: [],
413+
resourceOwners: new Set<string>(),
414+
} as unknown as ExecutorPlanResult;
415+
}
416+
417+
function emptyWorkflowPlan(): WorkflowPlanResult {
418+
return {
419+
changeSet: createChangeSet("Workflows"),
420+
conflicts: [],
421+
unmanaged: [],
422+
resourceOwners: new Set<string>(),
423+
appName: "",
424+
} as unknown as WorkflowPlanResult;
425+
}
426+
427+
async function planServices(
428+
ctx: PlanContext,
429+
client: OperatorClient,
430+
workspaceId: string,
431+
appName: string,
432+
workflowResult: WorkflowLoadResult | undefined,
433+
workflowBuildResult: BundleWorkflowJobsResult | undefined,
434+
tailorDBOnly: boolean,
435+
) {
436+
const tailorDB = await planTailorDB(ctx);
437+
438+
if (tailorDBOnly) {
439+
return {
440+
tailorDB,
441+
staticWebsite: emptyStaticWebsitePlan(),
442+
idp: emptyIdPPlan(),
443+
auth: emptyAuthPlan(),
444+
pipeline: emptyPipelinePlan(),
445+
app: emptyApplicationPlan(),
446+
executor: emptyExecutorPlan(),
447+
workflow: emptyWorkflowPlan(),
448+
};
449+
}
450+
451+
return {
452+
tailorDB,
453+
staticWebsite: await planStaticWebsite(ctx),
454+
idp: await planIdP(ctx),
455+
auth: await planAuth(ctx),
456+
pipeline: await planPipeline(ctx),
457+
app: await planApplication(ctx),
458+
executor: await planExecutor(ctx),
459+
workflow: await planWorkflow(
460+
client,
461+
workspaceId,
462+
appName,
463+
workflowResult?.workflows ?? {},
464+
workflowBuildResult?.mainJobDeps ?? {},
465+
),
466+
};
467+
}
468+
302469
export const applyCommand = defineCommand({
303470
name: "apply",
304471
description: "Apply Tailor configuration to deploy your application.",

0 commit comments

Comments
 (0)