@@ -21,6 +21,10 @@ import { ArtifactStore } from '../clients/artifact-store.js';
2121import { IArtifactStore } from '../clients/iartifact-store.js' ;
2222import { getCloudConfig , buildArmBaseUrl } from '../lib/cloud-config.js' ;
2323import { EXIT_FATAL , EXIT_SUCCESS } from '../lib/exit-codes.js' ;
24+ import { getResourceTier , TIER_LABELS } from '../lib/dependency-graph.js' ;
25+ import { formatDuration } from '../lib/format-duration.js' ;
26+ import { ResourceType } from '../models/resource-types.js' ;
27+ import { getNamePart } from '../lib/resource-path.js' ;
2428
2529/**
2630 * Interface for extract command options (from CLI flags).
@@ -135,6 +139,7 @@ export async function executeExtract(
135139 await fs . mkdir ( outputParent , { recursive : true } ) ;
136140 const stagingDir = await fs . mkdtemp ( stagingPrefix ) ;
137141
142+ const startedAt = Date . now ( ) ;
138143 let result : ExtractionResult ;
139144 try {
140145 const extractConfig : ExtractConfig = {
@@ -159,11 +164,13 @@ export async function executeExtract(
159164 await fs . rm ( stagingDir , { recursive : true , force : true } ) ;
160165 }
161166
167+ const elapsedMs = Date . now ( ) - startedAt ;
168+
162169 // Output results
163170 if ( globalOpts . format === 'json' ) {
164- outputJson ( result ) ;
171+ outputJson ( result , elapsedMs ) ;
165172 } else {
166- outputText ( result ) ;
173+ outputText ( result , elapsedMs ) ;
167174 }
168175
169176 dependencies . exit ( result . exitCode ) ;
@@ -180,13 +187,14 @@ export function shouldRemoveStaleArtifacts(
180187 * JSON output mode for extract.
181188 * Machine-readable JSON to stdout with resource counts and file paths.
182189 */
183- function outputJson ( result : ExtractionResult ) : void {
190+ function outputJson ( result : ExtractionResult , elapsedMs : number ) : void {
184191 const output = {
185192 status : result . exitCode === 0 ? 'success' : result . exitCode === 1 ? 'partial' : 'error' ,
186193 exitCode : result . exitCode ,
187194 summary : {
188195 totalExtracted : result . totalExtracted ,
189196 totalErrors : result . totalErrors ,
197+ elapsedMs,
190198 typeBreakdown : result . typeResults . map ( ( tr ) => ( {
191199 type : tr . type ,
192200 extracted : tr . extracted . filter ( ( r ) => r . status === 'success' ) . length ,
@@ -216,30 +224,72 @@ function outputJson(result: ExtractionResult): void {
216224 process . stdout . write ( JSON . stringify ( output , null , 2 ) + '\n' ) ;
217225}
218226
227+
228+ function tierOf ( type : ResourceType ) : number {
229+ try {
230+ return getResourceTier ( type ) ;
231+ } catch {
232+ return Number . MAX_SAFE_INTEGER ;
233+ }
234+ }
235+
219236/**
220237 * Text output mode (default) — per-resource status lines.
221238 */
222- function outputText ( result : ExtractionResult ) : void {
223- // Per-type summary
239+ export function outputText ( result : ExtractionResult , elapsedMs : number ) : void {
240+ // Per-type summary, grouped by dependency tier
241+ const linesByTier = new Map < number , string [ ] > ( ) ;
224242 for ( const tr of result . typeResults ) {
243+ const lines : string [ ] = [ ] ;
225244 const successCount = tr . extracted . filter ( ( r ) => r . status === 'success' ) . length ;
226245 if ( successCount > 0 ) {
227- process . stdout . write ( ` Extracted ${ successCount } ${ tr . type } (s)\n `) ;
246+ lines . push ( ` Extracted ${ successCount } ${ tr . type } (s)`) ;
228247 }
229248 if ( tr . errorCount > 0 ) {
230- process . stdout . write ( `Failed ${ tr . errorCount } ${ tr . type } (s)\n` ) ;
249+ lines . push ( ` Failed ${ tr . errorCount } ${ tr . type } (s)` ) ;
250+ }
251+ if ( lines . length === 0 ) continue ;
252+
253+ const tier = tierOf ( tr . type ) ;
254+ const tierLines = linesByTier . get ( tier ) ?? [ ] ;
255+ tierLines . push ( ...lines ) ;
256+ linesByTier . set ( tier , tierLines ) ;
257+ }
258+
259+ for ( const tier of [ ...linesByTier . keys ( ) ] . sort ( ( a , b ) => a - b ) ) {
260+ const label = TIER_LABELS [ tier ] ;
261+ process . stdout . write ( label ? `Tier ${ tier } : ${ label } \n` : 'Other resources\n' ) ;
262+ process . stdout . write ( `${ linesByTier . get ( tier ) ! . join ( '\n' ) } \n\n` ) ;
263+ }
264+
265+ // API details — list every extracted API, even those without sub-resources
266+ // or whose sub-resource extraction failed
267+ const apiDetails = new Map ( result . apiResults . map ( ( ar ) => [ ar . apiName , ar ] ) ) ;
268+ const apiNames = new Set < string > ( ) ;
269+ for ( const tr of result . typeResults ) {
270+ if ( tr . type !== ResourceType . Api ) continue ;
271+ for ( const r of tr . extracted ) {
272+ if ( r . status === 'success' ) apiNames . add ( getNamePart ( r . descriptor . nameParts , 0 ) ) ;
231273 }
232274 }
275+ for ( const name of apiDetails . keys ( ) ) apiNames . add ( name ) ;
233276
234- // API details
235- for ( const ar of result . apiResults ) {
236- const details : string [ ] = [ ] ;
237- if ( ar . specification ) details . push ( 'spec' ) ;
238- if ( ar . operations . length > 0 ) details . push ( `${ ar . operations . length } ops` ) ;
239- if ( ar . revisions . length > 0 ) details . push ( `${ ar . revisions . length } revisions` ) ;
240- if ( details . length > 0 ) {
241- process . stdout . write ( ` API "${ ar . apiName } ": ${ details . join ( ', ' ) } \n` ) ;
277+ if ( apiNames . size > 0 ) {
278+ process . stdout . write ( 'APIs:\n' ) ;
279+ }
280+ for ( const name of apiNames ) {
281+ const ar = apiDetails . get ( name ) ;
282+ let summary : string ;
283+ if ( ! ar ) {
284+ summary = 'sub-resource extraction failed' ;
285+ } else {
286+ const details : string [ ] = [ ] ;
287+ if ( ar . specification ) details . push ( 'spec' ) ;
288+ if ( ar . operations . length > 0 ) details . push ( `${ ar . operations . length } ops` ) ;
289+ if ( ar . revisions . length > 0 ) details . push ( `${ ar . revisions . length } revisions` ) ;
290+ summary = details . length > 0 ? details . join ( ', ' ) : 'definition only' ;
242291 }
292+ process . stdout . write ( ` API "${ name } ": ${ summary } \n` ) ;
243293 }
244294
245295 // Workspace details
@@ -249,6 +299,7 @@ function outputText(result: ExtractionResult): void {
249299
250300 // Summary
251301 process . stdout . write (
252- `\nTotal: ${ result . totalExtracted } resources extracted, ${ result . totalErrors } errors\n`
302+ `\nTotal: ${ result . totalExtracted } resources extracted, ${ result . totalErrors } errors ` +
303+ `in ${ formatDuration ( elapsedMs ) } \n`
253304 ) ;
254305}
0 commit comments