1- import { existsSync , readFileSync , writeFileSync } from 'fs' ;
2- import { join } from 'path' ;
1+ import { existsSync } from 'fs' ;
32import { Command , Option } from '@commander-js/extra-typings' ;
43import {
54 ProtoMessage ,
@@ -8,39 +7,9 @@ import {
87} from './types' ;
98import { parseProtoFile } from './parser' ;
109import { mergeMessage , mergeEnum } from './CompatibilityMerger' ;
11- import { generateMessage , generateEnum } from './writer' ;
10+ import { writeProtoFile , CUSTOM_MESSAGE_NAMES , CUSTOM_ENUM_NAMES } from './writer' ;
1211import logger from '../utils/logger' ;
1312
14- const TEMPLATE_DIR = join ( __dirname , '../config/protobuf-schema-template' ) ;
15-
16- // Load fixed header and custom messages from templates
17- const PROTO_HEADER = readFileSync ( join ( TEMPLATE_DIR , 'partial_header.mustache' ) , 'utf-8' ) ;
18- const CUSTOM_MESSAGES = readFileSync ( join ( TEMPLATE_DIR , 'custom_message.mustache' ) , 'utf-8' ) ;
19-
20- // ==================== CLI ====================
21-
22- const command = new Command ( )
23- . description ( 'Merge incoming proto files into existing proto while maintaining backward compatibility.' )
24- . addOption ( new Option ( '-e, --existing <path>' , 'existing proto file (source of truth)' ) . default ( 'protos/schemas/common.proto' ) )
25- . addOption ( new Option ( '-i, --incoming <paths>' , 'incoming proto files (comma-separated)' )
26- . argParser ( ( val : string ) => val . split ( ',' ) . map ( s => s . trim ( ) ) )
27- . default ( [ 'protos/generated/models/aggregated_models.proto' , 'protos/generated/services/default_service.proto' ] ) )
28- . addOption ( new Option ( '-o, --output <path>' , 'output proto file' ) . default ( 'protos/schemas/common.proto' ) )
29- . allowExcessArguments ( false )
30- . parse ( ) ;
31-
32- type BackwardCompatOpts = {
33- existing : string ;
34- incoming : string [ ] ;
35- output : string ;
36- } ;
37-
38- const opts = command . opts ( ) as BackwardCompatOpts ;
39-
40- // Messages defined in custom_message.mustache - skip and use template instead
41- const CUSTOM_MESSAGE_NAMES = new Set ( [ 'ObjectMap' , 'GeneralNumber' ] ) ;
42- const CUSTOM_ENUM_NAMES = new Set ( [ 'NullValue' ] ) ;
43-
4413export class BackwardCompatibleWriter {
4514 private existingMessages : ProtoMessage [ ] ;
4615 private existingEnums : ProtoEnum [ ] ;
@@ -78,63 +47,55 @@ export class BackwardCompatibleWriter {
7847 }
7948
8049 process ( ) : void {
81- const outputParts : string [ ] = [ ] ;
82-
83- // Use fixed header from template
84- outputParts . push ( PROTO_HEADER . trim ( ) ) ;
50+ const finalMessages : ProtoMessage [ ] = [ ] ;
51+ const finalEnums : ProtoEnum [ ] = [ ] ;
8552
86- // Process messages
53+ // Process existing messages (merge with incoming if present)
8754 for ( const existingMsg of this . existingMessages ) {
8855 if ( CUSTOM_MESSAGE_NAMES . has ( existingMsg . name ) ) {
8956 this . incomingMessageMap . delete ( existingMsg . name ) ;
9057 continue ;
9158 }
9259
9360 const incomingMsg = this . incomingMessageMap . get ( existingMsg . name ) ;
94-
9561 if ( incomingMsg ) {
96- const mergedMsg = mergeMessage ( existingMsg , incomingMsg , this . errors ) ;
97- outputParts . push ( generateMessage ( mergedMsg ) ) ;
62+ finalMessages . push ( mergeMessage ( existingMsg , incomingMsg , this . errors ) ) ;
9863 this . incomingMessageMap . delete ( existingMsg . name ) ;
9964 } else {
100- outputParts . push ( generateMessage ( existingMsg ) ) ;
65+ finalMessages . push ( existingMsg ) ;
10166 }
10267 }
10368
104- // Process enums
69+ // Process existing enums (merge with incoming if present)
10570 for ( const existingEnum of this . existingEnums ) {
10671 if ( CUSTOM_ENUM_NAMES . has ( existingEnum . name ) ) {
10772 this . incomingEnumMap . delete ( existingEnum . name ) ;
10873 continue ;
10974 }
11075
11176 const incomingEnum = this . incomingEnumMap . get ( existingEnum . name ) ;
112-
11377 if ( incomingEnum ) {
114- const mergedEnum = mergeEnum ( existingEnum , incomingEnum ) ;
115- outputParts . push ( generateEnum ( mergedEnum ) ) ;
78+ finalEnums . push ( mergeEnum ( existingEnum , incomingEnum ) ) ;
11679 this . incomingEnumMap . delete ( existingEnum . name ) ;
11780 } else {
118- outputParts . push ( generateEnum ( existingEnum ) ) ;
81+ finalEnums . push ( existingEnum ) ;
11982 }
12083 }
12184
122- // Append new messages from incoming proto files
85+ // Add new messages from incoming (not in existing)
12386 for ( const [ , msg ] of this . incomingMessageMap ) {
124- outputParts . push ( '' ) ;
125- outputParts . push ( generateMessage ( msg ) ) ;
87+ if ( ! CUSTOM_MESSAGE_NAMES . has ( msg . name ) ) {
88+ finalMessages . push ( msg ) ;
89+ }
12690 }
12791
128- // Append new enums from incoming proto files
92+ // Add new enums from incoming (not in existing)
12993 for ( const [ , protoEnum ] of this . incomingEnumMap ) {
130- outputParts . push ( '' ) ;
131- outputParts . push ( generateEnum ( protoEnum ) ) ;
94+ if ( ! CUSTOM_ENUM_NAMES . has ( protoEnum . name ) ) {
95+ finalEnums . push ( protoEnum ) ;
96+ }
13297 }
13398
134- // Append custom messages from template (ObjectMap, GeneralNumber, NullValue)
135- outputParts . push ( '' ) ;
136- outputParts . push ( CUSTOM_MESSAGES . trim ( ) ) ;
137-
13899 // Check for errors before writing
139100 if ( this . errors . length > 0 ) {
140101 logger . error ( 'Backward compatibility errors:' ) ;
@@ -146,36 +107,56 @@ export class BackwardCompatibleWriter {
146107 ) ;
147108 }
148109
149- writeFileSync ( this . outputPath , outputParts . join ( '\n' ) ) ;
110+ // Write output using shared function
111+ writeProtoFile ( finalMessages , finalEnums , this . outputPath ) ;
150112 logger . info ( `Updated: ${ this . outputPath } ` ) ;
151113 }
152114}
153115
154- // ==================== RUN ====================
155-
156- if ( ! existsSync ( opts . existing ) ) {
157- logger . error ( `Existing file not found: ${ opts . existing } ` ) ;
158- process . exit ( 1 ) ;
159- }
116+ // ==================== CLI ====================
160117
161- const existingIncoming = opts . incoming . filter ( p => existsSync ( p ) ) ;
162- if ( existingIncoming . length === 0 ) {
163- logger . error ( `No incoming proto files found.` ) ;
164- process . exit ( 1 ) ;
165- }
118+ /* istanbul ignore next -- CLI entry point */
119+ if ( require . main === module ) {
120+ const command = new Command ( )
121+ . description ( 'Merge incoming proto files into existing proto while maintaining backward compatibility.' )
122+ . addOption ( new Option ( '-e, --existing <path>' , 'existing proto file (source of truth)' ) . default ( 'protos/schemas/common.proto' ) )
123+ . addOption ( new Option ( '-i, --incoming <paths>' , 'incoming proto files (comma-separated)' )
124+ . argParser ( ( val : string ) => val . split ( ',' ) . map ( s => s . trim ( ) ) )
125+ . default ( [ 'protos/generated/models/aggregated_models.proto' , 'protos/generated/services/default_service.proto' ] ) )
126+ . addOption ( new Option ( '-o, --output <path>' , 'output proto file' ) . default ( 'protos/schemas/common.proto' ) )
127+ . allowExcessArguments ( false )
128+ . parse ( ) ;
129+
130+ type BackwardCompatOpts = {
131+ existing : string ;
132+ incoming : string [ ] ;
133+ output : string ;
134+ } ;
135+
136+ const opts = command . opts ( ) as BackwardCompatOpts ;
137+
138+ if ( ! existsSync ( opts . existing ) ) {
139+ logger . error ( `Existing file not found: ${ opts . existing } ` ) ;
140+ process . exit ( 1 ) ;
141+ }
166142
167- try {
168- const writer = new BackwardCompatibleWriter (
169- opts . existing ,
170- opts . incoming ,
171- opts . output
172- ) ;
173- writer . process ( ) ;
174- } catch ( error ) {
175- if ( error instanceof BackwardCompatibilityError ) {
143+ const existingIncoming = opts . incoming . filter ( p => existsSync ( p ) ) ;
144+ if ( existingIncoming . length === 0 ) {
145+ logger . error ( `No incoming proto files found.` ) ;
176146 process . exit ( 1 ) ;
177147 }
178- throw error ;
179- }
180148
181- export { BackwardCompatibilityError } ;
149+ try {
150+ const writer = new BackwardCompatibleWriter (
151+ opts . existing ,
152+ opts . incoming ,
153+ opts . output
154+ ) ;
155+ writer . process ( ) ;
156+ } catch ( error ) {
157+ if ( error instanceof BackwardCompatibilityError ) {
158+ process . exit ( 1 ) ;
159+ }
160+ throw error ;
161+ }
162+ }
0 commit comments