diff --git a/cli/src/commands/app/app_metadata.ts b/cli/src/commands/app/app_metadata.ts index 78a3019566..253dc99f3b 100644 --- a/cli/src/commands/app/app_metadata.ts +++ b/cli/src/commands/app/app_metadata.ts @@ -93,7 +93,16 @@ async function generateAppHash( } /** - * Updates locks for inline scripts in an app + * Result of generating app locks, including which scripts were updated + */ +export interface AppLocksResult { + path: string; + updatedScripts: string[]; +} + +/** + * Updates locks for inline scripts in an app. + * Returns the path if dry-run, or AppLocksResult with updated scripts if actual update occurred. */ export async function generateAppLocksInternal( appFolder: string, @@ -105,7 +114,7 @@ export async function generateAppLocksInternal( }, justUpdateMetadataLock?: boolean, noStaleMessage?: boolean -): Promise { +): Promise { if (appFolder.endsWith(SEP)) { appFolder = appFolder.substring(0, appFolder.length - 1); } @@ -167,6 +176,8 @@ export async function generateAppLocksInternal( ); } + let updatedScripts: string[] = []; + if (!justUpdateMetadataLock) { const changedScripts = []; // Find hashes that do not correspond to previous hashes @@ -201,13 +212,14 @@ export async function generateAppLocksInternal( replaceInlineScripts(runnables, runnablesPath + SEP, false); // Update the app runnables with new locks (writes to separate files) - await updateRawAppRunnables( + updatedScripts = await updateRawAppRunnables( workspace, runnables, remote_path, appFolder, filteredDeps, - opts.defaultTs + opts.defaultTs, + noStaleMessage ); // Note: updateRawAppRunnables now writes each runnable to its own file } else { @@ -217,14 +229,17 @@ export async function generateAppLocksInternal( replaceInlineScripts(normalAppFile.value, appFolder + SEP, false); // Update the app value with new locks - normalAppFile.value = await updateAppInlineScripts( + const result = await updateAppInlineScripts( workspace, normalAppFile.value, remote_path, appFolder, filteredDeps, - opts.defaultTs + opts.defaultTs, + noStaleMessage ); + normalAppFile.value = result.value; + updatedScripts = result.updatedScripts; // Write the updated app file (only for normal apps, raw apps use separate files) writeIfChanged( @@ -251,6 +266,8 @@ export async function generateAppLocksInternal( if (!noStaleMessage) { log.info(colors.green(`App ${remote_path} lockfiles updated`)); } + + return { path: remote_path, updatedScripts }; } /** @@ -340,6 +357,7 @@ async function traverseAndProcessInlineScripts( * Updates locks for all runnables in a raw app, generating locks inline script by inline script. * Writes each runnable to its own YAML file in the backend folder (new format). * Also writes content and lock files to the runnables folder. + * Returns the list of runnable IDs that had their locks updated. */ async function updateRawAppRunnables( workspace: Workspace, @@ -347,8 +365,10 @@ async function updateRawAppRunnables( remotePath: string, appFolder: string, rawDeps?: Record, - defaultTs: "bun" | "deno" = "bun" -): Promise { + defaultTs: "bun" | "deno" = "bun", + noStaleMessage?: boolean +): Promise { + const updatedRunnables: string[] = []; const runnablesFolder = path.join(appFolder, APP_BACKEND_FOLDER); // Ensure runnables folder exists @@ -414,12 +434,11 @@ async function updateRawAppRunnables( continue; } - log.info( - colors.gray( - `Generating lock for runnable ${runnableId} (${language}) - }` - ) - ); + if (!noStaleMessage) { + log.info( + colors.gray(`Generating lock for runnable ${runnableId} (${language})`) + ); + } try { const lock = await generateInlineScriptLock( @@ -459,11 +478,15 @@ async function updateRawAppRunnables( // Write the runnable to its own YAML file writeRunnableToBackend(runnablesFolder, runnableId, simplifiedRunnable); - log.info( - colors.gray( - ` Written ${runnableId}.yaml, ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}` - ) - ); + updatedRunnables.push(runnableId); + + if (!noStaleMessage) { + log.info( + colors.gray( + ` Written ${runnableId}.yaml, ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}` + ) + ); + } } catch (error: any) { log.error( colors.red( @@ -474,11 +497,14 @@ async function updateRawAppRunnables( writeRunnableToBackend(runnablesFolder, runnableId, runnable); } } + + return updatedRunnables; } /** * Updates locks for all inline scripts in a normal app, similar to updateRawAppRunnables - * but for the app.value structure instead of app.runnables + * but for the app.value structure instead of app.runnables. + * Returns a tuple of [updated app value, list of script names that were updated]. */ async function updateAppInlineScripts( workspace: Workspace, @@ -486,9 +512,11 @@ async function updateAppInlineScripts( remotePath: string, appFolder: string, rawDeps?: Record, - defaultTs: "bun" | "deno" = "bun" -): Promise { + defaultTs: "bun" | "deno" = "bun", + noStaleMessage?: boolean +): Promise<{ value: any; updatedScripts: string[] }> { const pathAssigner = newPathAssigner(defaultTs, { skipInlineScriptSuffix: getNonDottedPaths() }); + const updatedScripts: string[] = []; const processor: InlineScriptProcessor = async (inlineScript, context) => { const language = inlineScript.language as SupportedLanguage; @@ -518,13 +546,15 @@ async function updateAppInlineScripts( try { let lock: string | undefined; if (language !== "frontend") { - log.info( - colors.gray( - `Generating lock for inline script "${scriptName}" at ${context.path.join( - "." - )} (${language})` - ) - ); + if (!noStaleMessage) { + log.info( + colors.gray( + `Generating lock for inline script "${scriptName}" at ${context.path.join( + "." + )} (${language})` + ) + ); + } lock = await generateInlineScriptLock( workspace, @@ -553,11 +583,18 @@ async function updateAppInlineScripts( const inlineLockRef = lock && lock !== "" ? `!inline ${basePath}lock` : ""; - log.info( - colors.gray( - ` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}` - ) - ); + if (!noStaleMessage) { + log.info( + colors.gray( + ` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}` + ) + ); + } + + // Track that this script was updated (only for non-frontend scripts that needed locks) + if (language !== "frontend") { + updatedScripts.push(scriptName); + } return { ...inlineScript, @@ -577,7 +614,8 @@ async function updateAppInlineScripts( } }; - return await traverseAndProcessInlineScripts(appValue, processor); + const updatedValue = await traverseAndProcessInlineScripts(appValue, processor); + return { value: updatedValue, updatedScripts }; } /** diff --git a/cli/src/commands/flow/flow_metadata.ts b/cli/src/commands/flow/flow_metadata.ts index 2e48efaa10..908e3c63dc 100644 --- a/cli/src/commands/flow/flow_metadata.ts +++ b/cli/src/commands/flow/flow_metadata.ts @@ -51,6 +51,14 @@ async function generateFlowHash( } return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; } +/** + * Result of generating flow locks, including which scripts were updated + */ +export interface FlowLocksResult { + path: string; + updatedScripts: string[]; +} + export async function generateFlowLockInternal( folder: string, dryRun: boolean, @@ -60,7 +68,7 @@ export async function generateFlowLockInternal( }, justUpdateMetadataLock?: boolean, noStaleMessage?: boolean -): Promise { +): Promise { if (folder.endsWith(SEP)) { folder = folder.substring(0, folder.length - 1); } @@ -109,8 +117,9 @@ export async function generateFlowLockInternal( } + let changedScripts: string[] = []; + if (!justUpdateMetadataLock) { - const changedScripts = []; //find hashes that do not correspond to previous hashes for (const [path, hash] of Object.entries(hashes)) { if (path == TOP_HASH) { @@ -185,6 +194,13 @@ export async function generateFlowLockInternal( if (!noStaleMessage) { log.info(colors.green(`Flow ${remote_path} lockfiles updated`)); } + + // Return the list of updated scripts (extract just the filename from the path) + const updatedScripts = changedScripts.map(p => { + const parts = p.split(SEP); + return parts[parts.length - 1].replace(/\.[^.]+$/, ""); // Remove extension + }); + return { path: remote_path, updatedScripts }; } /** diff --git a/cli/src/commands/generate-metadata/generate-metadata.ts b/cli/src/commands/generate-metadata/generate-metadata.ts index b8c3534e85..77fc4c4f86 100644 --- a/cli/src/commands/generate-metadata/generate-metadata.ts +++ b/cli/src/commands/generate-metadata/generate-metadata.ts @@ -11,8 +11,8 @@ import { generateScriptMetadataInternal, getRawWorkspaceDependencies, } from "../../utils/metadata.ts"; -import { generateFlowLockInternal } from "../flow/flow_metadata.ts"; -import { generateAppLocksInternal, getAppFolders } from "../app/app_metadata.ts"; +import { generateFlowLockInternal, FlowLocksResult } from "../flow/flow_metadata.ts"; +import { generateAppLocksInternal, getAppFolders, AppLocksResult } from "../app/app_metadata.ts"; import { elementsToMap, FSFSElement, @@ -286,21 +286,23 @@ async function generateMetadata( // Process flows for (const item of flows) { current++; - log.info(`${formatProgress(current)} flow ${colors.cyan(item.path)}`); - await generateFlowLockInternal( + const result = await generateFlowLockInternal( item.folder, false, // dryRun workspace, opts, false, true // noStaleMessage - we handle output - ); + ) as FlowLocksResult | void; + const scriptsInfo = result?.updatedScripts?.length + ? `: ${colors.gray(result.updatedScripts.join(", "))}` + : ""; + log.info(`${formatProgress(current)} flow ${colors.cyan(item.path)}${scriptsInfo}`); } // Process apps for (const item of apps) { current++; - log.info(`${formatProgress(current)} app ${colors.cyan(item.path)}`); - await generateAppLocksInternal( + const result = await generateAppLocksInternal( item.folder, item.isRawApp!, // rawApp false, // dryRun @@ -308,7 +310,11 @@ async function generateMetadata( opts, false, true // noStaleMessage - we handle output - ); + ) as AppLocksResult | void; + const scriptsInfo = result?.updatedScripts?.length + ? `: ${colors.gray(result.updatedScripts.join(", "))}` + : ""; + log.info(`${formatProgress(current)} app ${colors.cyan(item.path)}${scriptsInfo}`); } log.info("");