From 2085638900fcdad55d246a57d792efb75450e3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Birm=C3=A9?= Date: Wed, 26 Aug 2026 14:17:51 +0200 Subject: [PATCH] feat: add --wait to osc admin sync-fork Adds a -w/--wait option to `osc admin sync-fork` that polls the sync-fork job to completion and then polls for the auto-triggered remake order, waiting for that to finish too. - packages/core/src/platform/maker.ts: new getOrderIdByRepositoryUrl(), reusing the existing Order/OutputOrder shapes and error handling style from getOrderIdByName. Prefers an order in an active phase (NEW/PENDING/RUNNING), falls back to the first returned order. - packages/cli/src/admin/syncfork.ts: new waitForSyncFork() that polls GET /sync-fork/by-repo until the job leaves pending/running. - packages/cli/src/admin/cmd.ts: sync-fork command gains --wait; when set, waits for the sync-fork job, then polls (bounded, ~2 minutes) for the remake order maker-manager's service-builder creates after a successful sync, then waits for that order via the existing waitForOrder(). Co-Authored-By: Claude --- packages/cli/src/admin/cmd.ts | 54 ++++++++++++++++++++++++++++- packages/cli/src/admin/syncfork.ts | 22 ++++++++++++ packages/core/src/platform/maker.ts | 37 ++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/admin/cmd.ts b/packages/cli/src/admin/cmd.ts index a080266..b380005 100644 --- a/packages/cli/src/admin/cmd.ts +++ b/packages/cli/src/admin/cmd.ts @@ -4,13 +4,15 @@ import { Log, Platform, getOrderIdByName, + getOrderIdByRepositoryUrl, remakeOrder, waitForOrder } from '@osaas/client-core'; import { getServiceRepoUrl, getSyncForkStatus, - triggerSyncFork + triggerSyncFork, + waitForSyncFork } from './syncfork'; import { getInstancesToRemove, @@ -25,6 +27,8 @@ import { } from './subscription'; import { getTenantPlanMap, getTenantTokenCounts } from './money'; +const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + export default function cmdAdmin() { const admin = new Command('admin'); admin @@ -267,6 +271,7 @@ export default function cmdAdmin() { admin .command('sync-fork') .description('Trigger a fork sync for an OSC service') + .option('-w, --wait', 'Wait for sync fork and remake to complete') .argument('', 'The OSC service ID (e.g. eyevinn-web-runner)') .action(async (serviceId, options, command) => { try { @@ -304,6 +309,53 @@ export default function cmdAdmin() { return; } } + + if (options.wait) { + Log().info('Waiting for sync fork to complete...'); + const finalSyncStatus = await waitForSyncFork(repoUrl, platform); + if (finalSyncStatus?.status === 'failed') { + console.log(`Sync fork status: ${finalSyncStatus.status}`); + if (finalSyncStatus.error) { + console.log( + `Sync fork failed: [${finalSyncStatus.error.type}] ${finalSyncStatus.error.message}` + ); + if (finalSyncStatus.error.conflictFiles?.length) { + console.log( + `Conflict files: ${finalSyncStatus.error.conflictFiles.join( + ', ' + )}` + ); + } + } + return; + } + + const maxAttempts = 24; + const pollIntervalMs = 5000; + let orderId: string | undefined; + for (let attempt = 0; attempt < maxAttempts; attempt++) { + orderId = await getOrderIdByRepositoryUrl(platform, repoUrl); + if (orderId) { + break; + } + await delay(pollIntervalMs); + } + + if (!orderId) { + Log().error( + `No remake order found for ${repoUrl} after waiting ${ + (maxAttempts * pollIntervalMs) / 1000 + }s — it may not have started yet; check with 'osc admin remake-order' once it appears` + ); + return; + } + + Log().info('Waiting for remake to complete...'); + await waitForOrder(platform, orderId); + Log().info( + `Sync fork and remake for ${serviceId} completed successfully` + ); + } } catch (err) { console.log((err as Error).message); } diff --git a/packages/cli/src/admin/syncfork.ts b/packages/cli/src/admin/syncfork.ts index 9070939..61d1d38 100644 --- a/packages/cli/src/admin/syncfork.ts +++ b/packages/cli/src/admin/syncfork.ts @@ -89,3 +89,25 @@ export async function getSyncForkStatus( throw err; } } + +const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +export async function waitForSyncFork( + repositoryUrl: string, + platform: Platform +): Promise { + let running = true; + while (running) { + const status = await getSyncForkStatus(repositoryUrl, platform); + if (!status) { + return undefined; + } + if (status.status === 'pending' || status.status === 'running') { + await delay(5000); + } else { + running = false; + return status; + } + } + return undefined; +} diff --git a/packages/core/src/platform/maker.ts b/packages/core/src/platform/maker.ts index 2259e31..8e245e6 100644 --- a/packages/core/src/platform/maker.ts +++ b/packages/core/src/platform/maker.ts @@ -42,6 +42,43 @@ export async function getOrderIdByName(platform: Platform, orderName: string) { } } +const ACTIVE_ORDER_PHASES = new Set(['NEW', 'PENDING', 'RUNNING']); + +export async function getOrderIdByRepositoryUrl( + platform: Platform, + repositoryUrl: string +) { + try { + const makerUrl = new URL( + `https://maker.svc.${platform.getEnvironment()}.osaas.io/maker` + ); + makerUrl.searchParams.append('repositoryUrl', repositoryUrl); + const res = await createFetch(makerUrl, { + headers: { + Authorization: `Bearer ${platform.getApiKey()}`, + 'Content-Type': 'application/json' + } + }); + const activeOrder = res.find((o) => + ACTIVE_ORDER_PHASES.has(o.order.status?.phase || '') + ); + if (activeOrder) { + return activeOrder.order.orderId; + } + if (res[0]) { + return res[0].order.orderId; + } + return undefined; + } catch (err) { + Log().debug(err); + if (err instanceof FetchError && err.httpCode === 401) { + throw new UnauthorizedError(); + } else if (err instanceof FetchError && err.httpCode === 404) { + return undefined; + } + } +} + export async function waitForOrder(platform: Platform, orderId: string) { try { let running = true;