Skip to content

Commit 5889c93

Browse files
toiroakrclaude
andcommitted
fix(apply): apply IdP/Auth before TailorDB in migration mode
Migration scripts require machine users for authentication, but in tailorDBOnly mode, IdP/Auth were skipped entirely. This caused errors like "invalid auth name" when executing migration scripts. Fix: In migration mode, change apply order to IdP → Auth → TailorDB so machine users are available when migration scripts run. Platform confirmed Auth service creation doesn't require TailorDB type to exist (validation happens at login time, not creation time). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0180437 commit 5889c93

1 file changed

Lines changed: 72 additions & 109 deletions

File tree

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

Lines changed: 72 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,17 @@ export async function apply(options?: ApplyOptions) {
7777
const yes = options?.yes ?? false;
7878
const buildOnly = options?.buildOnly ?? process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY === "true";
7979

80-
// TailorDB only mode: skip other services when migration version is specified
81-
const tailorDBOnlyMode = !!process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
80+
// Migration only mode: skip other services when migration version is specified
81+
const migrationOnlyMode = !!process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
8282

8383
// Generate user types from loaded config
8484
await generateUserTypes(config, config.path);
8585
const application = defineApplication(config);
8686

87-
// Build services (skipped in TailorDB only mode)
87+
// Build services (skipped in migration only mode)
8888
const { workflowResult, workflowBuildResult } = await buildServices(
8989
application,
90-
tailorDBOnlyMode,
90+
migrationOnlyMode,
9191
);
9292
if (buildOnly) return;
9393

@@ -102,13 +102,13 @@ export async function apply(options?: ApplyOptions) {
102102
profile: options?.profile,
103103
});
104104

105-
// Load services (non-TailorDB services skipped in TailorDB only mode)
106-
await loadServices(application, workflowResult, tailorDBOnlyMode);
105+
// Load services (non-TailorDB services skipped in migration only mode)
106+
await loadServices(application, workflowResult, migrationOnlyMode);
107107
logger.newline();
108108

109-
// Log TailorDB only mode
110-
if (tailorDBOnlyMode) {
111-
logger.info("TailorDB only mode: other services will be skipped");
109+
// Log migration mode
110+
if (migrationOnlyMode) {
111+
logger.info("Migration mode: only TailorDB, IdP, and Auth will be applied");
112112
logger.newline();
113113
}
114114

@@ -129,7 +129,7 @@ export async function apply(options?: ApplyOptions) {
129129
application.name,
130130
workflowResult,
131131
workflowBuildResult,
132-
tailorDBOnlyMode,
132+
migrationOnlyMode,
133133
);
134134

135135
// Confirm conflicts
@@ -198,6 +198,26 @@ export async function apply(options?: ApplyOptions) {
198198
return;
199199
}
200200

201+
// Migration only mode: Apply IdP → Auth → TailorDB (different order for migration scripts)
202+
// Migration scripts require machine users, so IdP/Auth must be created first
203+
if (migrationOnlyMode) {
204+
// Phase 1: Create IdP/Auth first (machine users needed for migration scripts)
205+
await applyIdP(client, idp, "create-update");
206+
await applyAuth(client, auth, "create-update");
207+
208+
// Phase 2: TailorDB migration (machine users are now available)
209+
await applyTailorDB(client, tailorDB, "create-update");
210+
211+
// Phase 3: Delete services
212+
await applyAuth(client, auth, "delete-services");
213+
await applyIdP(client, idp, "delete-services");
214+
await applyTailorDB(client, tailorDB, "delete-services");
215+
216+
logger.success("Successfully applied TailorDB + Auth + IdP changes.");
217+
return;
218+
}
219+
220+
// Normal mode: Apply in standard order
201221
// Phase 2: Create/Update services that Application depends on
202222
// - Subgraph services (for GraphQL SDL composition): TailorDB, IdP, Auth, Pipeline
203223
// - StaticWebsite (for CORS and OAuth2 redirect URI resolution)
@@ -206,7 +226,6 @@ export async function apply(options?: ApplyOptions) {
206226
await applyTailorDB(client, tailorDB, "create-update");
207227

208228
// Other services: Apply after TailorDB migrations complete
209-
// (empty plans in TailorDB only mode - apply functions do nothing)
210229
await applyStaticWebsite(client, staticWebsite, "create-update");
211230
await applyIdP(client, idp, "create-update");
212231
await applyAuth(client, auth, "create-update");
@@ -279,9 +298,9 @@ interface BuildServicesResult {
279298

280299
async function buildServices(
281300
application: Readonly<Application>,
282-
tailorDBOnly: boolean,
301+
migrationOnly: boolean,
283302
): Promise<BuildServicesResult> {
284-
if (tailorDBOnly) {
303+
if (migrationOnly) {
285304
return { workflowResult: undefined, workflowBuildResult: undefined };
286305
}
287306

@@ -318,14 +337,14 @@ async function buildServices(
318337
async function loadServices(
319338
application: Readonly<Application>,
320339
workflowResult: WorkflowLoadResult | undefined,
321-
tailorDBOnly: boolean,
340+
migrationOnly: boolean,
322341
) {
323342
// Always load TailorDB types
324343
for (const tailordb of application.tailorDBServices) {
325344
await tailordb.loadTypes();
326345
}
327346

328-
if (tailorDBOnly) {
347+
if (migrationOnly) {
329348
return;
330349
}
331350

@@ -341,118 +360,62 @@ async function loadServices(
341360
}
342361
}
343362

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-
427363
async function planServices(
428364
ctx: PlanContext,
429365
client: OperatorClient,
430366
workspaceId: string,
431367
appName: string,
432368
workflowResult: WorkflowLoadResult | undefined,
433369
workflowBuildResult: BundleWorkflowJobsResult | undefined,
434-
tailorDBOnly: boolean,
370+
migrationOnly: boolean,
435371
) {
436372
const tailorDB = await planTailorDB(ctx);
373+
const idp = await planIdP(ctx);
374+
const auth = await planAuth(ctx);
437375

438-
if (tailorDBOnly) {
376+
if (migrationOnly) {
377+
// Other services use empty plans (type assertions since they won't be used)
439378
return {
440379
tailorDB,
441-
staticWebsite: emptyStaticWebsitePlan(),
442-
idp: emptyIdPPlan(),
443-
auth: emptyAuthPlan(),
444-
pipeline: emptyPipelinePlan(),
445-
app: emptyApplicationPlan(),
446-
executor: emptyExecutorPlan(),
447-
workflow: emptyWorkflowPlan(),
380+
idp,
381+
auth,
382+
staticWebsite: {
383+
changeSet: createChangeSet("StaticWebsites"),
384+
conflicts: [],
385+
unmanaged: [],
386+
resourceOwners: new Set<string>(),
387+
} as Awaited<ReturnType<typeof planStaticWebsite>>,
388+
pipeline: {
389+
changeSet: {
390+
service: createChangeSet("Pipeline services"),
391+
resolver: createChangeSet("Resolvers"),
392+
},
393+
conflicts: [],
394+
unmanaged: [],
395+
resourceOwners: new Set<string>(),
396+
} as Awaited<ReturnType<typeof planPipeline>>,
397+
app: createChangeSet("Applications") as Awaited<ReturnType<typeof planApplication>>,
398+
executor: {
399+
changeSet: createChangeSet("Executors"),
400+
conflicts: [],
401+
unmanaged: [],
402+
resourceOwners: new Set<string>(),
403+
} as Awaited<ReturnType<typeof planExecutor>>,
404+
workflow: {
405+
changeSet: createChangeSet("Workflows"),
406+
conflicts: [],
407+
unmanaged: [],
408+
resourceOwners: new Set<string>(),
409+
appName: "",
410+
} as Awaited<ReturnType<typeof planWorkflow>>,
448411
};
449412
}
450413

451414
return {
452415
tailorDB,
416+
idp,
417+
auth,
453418
staticWebsite: await planStaticWebsite(ctx),
454-
idp: await planIdP(ctx),
455-
auth: await planAuth(ctx),
456419
pipeline: await planPipeline(ctx),
457420
app: await planApplication(ctx),
458421
executor: await planExecutor(ctx),

0 commit comments

Comments
 (0)