22 * Get blueprint build logs command
33 */
44
5+ import chalk from "chalk" ;
6+ import type {
7+ BlueprintBuildLogsListView ,
8+ BlueprintBuildLog ,
9+ } from "@runloop/api-client/resources/blueprints" ;
510import { getClient } from "../../utils/client.js" ;
611import { output , outputError } from "../../utils/output.js" ;
712
@@ -10,13 +15,138 @@ interface BlueprintLogsOptions {
1015 output ?: string ;
1116}
1217
18+ function formatLogLevel ( level : string ) : string {
19+ const normalized = level . toUpperCase ( ) ;
20+ switch ( normalized ) {
21+ case "ERROR" :
22+ case "ERR" :
23+ return chalk . red . bold ( "ERROR" ) ;
24+ case "WARN" :
25+ case "WARNING" :
26+ return chalk . yellow . bold ( "WARN " ) ;
27+ case "INFO" :
28+ return chalk . blue ( "INFO " ) ;
29+ case "DEBUG" :
30+ return chalk . gray ( "DEBUG" ) ;
31+ default :
32+ return chalk . gray ( normalized . padEnd ( 5 ) ) ;
33+ }
34+ }
35+
36+ function formatTimestamp ( timestampMs : number ) : string {
37+ const date = new Date ( timestampMs ) ;
38+ const now = new Date ( ) ;
39+
40+ const isToday = date . toDateString ( ) === now . toDateString ( ) ;
41+ const isThisYear = date . getFullYear ( ) === now . getFullYear ( ) ;
42+
43+ const time = date . toLocaleTimeString ( "en-US" , {
44+ hour12 : false ,
45+ hour : "2-digit" ,
46+ minute : "2-digit" ,
47+ second : "2-digit" ,
48+ } ) ;
49+ const ms = date . getMilliseconds ( ) . toString ( ) . padStart ( 3 , "0" ) ;
50+
51+ if ( isToday ) {
52+ // Today: show time with milliseconds for fine granularity
53+ return chalk . dim ( `${ time } .${ ms } ` ) ;
54+ } else if ( isThisYear ) {
55+ // This year: show "Jan 5 15:44:03"
56+ const monthDay = date . toLocaleDateString ( "en-US" , {
57+ month : "short" ,
58+ day : "numeric" ,
59+ } ) ;
60+ return chalk . dim ( `${ monthDay } ${ time } ` ) ;
61+ } else {
62+ // Older: show "Jan 5, 2024 15:44:03"
63+ const fullDate = date . toLocaleDateString ( "en-US" , {
64+ year : "numeric" ,
65+ month : "short" ,
66+ day : "numeric" ,
67+ } ) ;
68+ return chalk . dim ( `${ fullDate } ${ time } ` ) ;
69+ }
70+ }
71+
72+ function colorizeMessage ( message : string ) : string {
73+ // Colorize common Docker build patterns
74+ if ( message . startsWith ( "Step " ) || message . startsWith ( "---> " ) ) {
75+ return chalk . cyan . bold ( message ) ;
76+ }
77+ if ( message . startsWith ( "Successfully" ) ) {
78+ return chalk . green . bold ( message ) ;
79+ }
80+ if ( message . startsWith ( "Removing intermediate container" ) ) {
81+ return chalk . dim ( message ) ;
82+ }
83+ if (
84+ message . toLowerCase ( ) . includes ( "error" ) ||
85+ message . toLowerCase ( ) . includes ( "failed" )
86+ ) {
87+ return chalk . red ( message ) ;
88+ }
89+ if ( message . toLowerCase ( ) . includes ( "warning" ) ) {
90+ return chalk . yellow ( message ) ;
91+ }
92+ // Dockerfile instructions
93+ if (
94+ message . startsWith ( "RUN " ) ||
95+ message . startsWith ( "COPY " ) ||
96+ message . startsWith ( "ADD " ) ||
97+ message . startsWith ( "FROM " ) ||
98+ message . startsWith ( "WORKDIR " ) ||
99+ message . startsWith ( "ENV " )
100+ ) {
101+ return chalk . yellow ( message ) ;
102+ }
103+ return message ;
104+ }
105+
106+ function formatLogEntry ( log : BlueprintBuildLog ) : string {
107+ const parts : string [ ] = [ ] ;
108+
109+ // Timestamp
110+ parts . push ( formatTimestamp ( log . timestamp_ms ) ) ;
111+
112+ // Level
113+ parts . push ( formatLogLevel ( log . level ) ) ;
114+
115+ // Message with colorization
116+ parts . push ( colorizeMessage ( log . message ) ) ;
117+
118+ return parts . join ( " " ) ;
119+ }
120+
121+ function formatLogs ( response : BlueprintBuildLogsListView ) : void {
122+ const logs = response . logs ;
123+
124+ if ( ! logs || logs . length === 0 ) {
125+ console . log ( chalk . dim ( "No build logs available" ) ) ;
126+ return ;
127+ }
128+
129+ console . log (
130+ chalk . bold . underline ( `Blueprint Build Logs (${ response . blueprint_id } )\n` ) ,
131+ ) ;
132+
133+ for ( const log of logs ) {
134+ console . log ( formatLogEntry ( log ) ) ;
135+ }
136+ }
137+
13138export async function getBlueprintLogs ( options : BlueprintLogsOptions ) {
14139 try {
15140 const client = getClient ( ) ;
16141 const logs = await client . blueprints . logs ( options . id ) ;
17- output ( logs , { format : options . output , defaultFormat : "json" } ) ;
142+
143+ // Pretty print for text output, JSON for others
144+ if ( ! options . output || options . output === "text" ) {
145+ formatLogs ( logs ) ;
146+ } else {
147+ output ( logs , { format : options . output , defaultFormat : "json" } ) ;
148+ }
18149 } catch ( error ) {
19150 outputError ( "Failed to get blueprint logs" , error ) ;
20151 }
21152}
22-
0 commit comments