1- // Browser-compatible version of commit-from-branch core logic
2- import { renderTemplate } from './tokens ' ;
1+ // Browser-compatible wrapper for commit-from-branch core logic
2+ import type { ProcessingState , CommitFromBranchConfig as OriginalConfig } from '@253eosam/commit-from-branch ' ;
33
4- // =============================================================================
5- // Types (adapted from original types.ts)
6- // =============================================================================
7-
8- export type CommitFromBranchConfig = {
9- includePattern ?: string | string [ ] ;
10- format ?: string ;
11- fallbackFormat ?: string ;
12- exclude ?: string [ ] ;
13- } ;
4+ // Re-export types for compatibility
5+ export type CommitFromBranchConfig = OriginalConfig ;
146
157export type Context = {
168 branch : string ;
@@ -27,33 +19,10 @@ export type ProcessedConfig = CommitFromBranchConfig & {
2719 exclude : string [ ] ;
2820} ;
2921
30- export type PreviewState = {
31- config : ProcessedConfig ;
32- branch : string ;
33- ticket : string ;
34- originalMessage : string ;
35- lines : string [ ] ;
36- context : Context ;
37- template : string ;
38- renderedMessage : string ;
39- shouldSkip : boolean ;
40- skipReason ?: string ;
41- } ;
42-
43- export type ValidationRule = {
44- name : string ;
45- check : ( state : PreviewState ) => boolean ;
46- reason : string ;
47- } ;
48-
49- export type MessageProcessor = {
50- name : string ;
51- shouldApply : ( state : PreviewState ) => boolean ;
52- process : ( state : PreviewState ) => PreviewState ;
53- } ;
22+ export type PreviewState = ProcessingState ;
5423
5524// =============================================================================
56- // Utility Functions (adapted for browser)
25+ // Browser-specific adaptations
5726// =============================================================================
5827
5928const createRegexPattern = ( pattern : string ) : RegExp =>
@@ -68,183 +37,131 @@ const extractTicketFromBranch = (branch: string): string =>
6837const escapeRegexSpecialChars = ( str : string ) : string =>
6938 str . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
7039
71- const normalizeConfig = ( config : CommitFromBranchConfig ) : ProcessedConfig => {
72- const includePattern = config . includePattern || [ '*' ] ;
73- const includePatterns = Array . isArray ( includePattern ) ? includePattern : [ includePattern ] ;
74-
75- return {
76- ...config ,
77- includePatterns,
78- format : config . format || '${ticket}: ${msg}' ,
79- fallbackFormat : config . fallbackFormat || '${segs[0]}: ${msg}' ,
80- exclude : config . exclude || [ ]
81- } ;
40+ // Simple template renderer (extracted from main module logic)
41+ export const renderTemplate = ( tpl : string , ctx : Context ) : string => {
42+ let out = String ( tpl ) ;
43+
44+ // ${prefix:n} → first n segments joined with '/'
45+ out = out . replace ( / \$ \{ p r e f i x : ( \d + ) \} / g, ( _m , n ) => {
46+ const k = Math . max ( 0 , parseInt ( n , 10 ) || 0 ) ;
47+ return ctx . segs . slice ( 0 , k ) . join ( '/' ) || '' ;
48+ } ) ;
49+
50+ // ${seg0}, ${seg1}, ...
51+ out = out . replace ( / \$ \{ s e g ( \d + ) \} / g, ( _m , i ) => {
52+ const idx = parseInt ( i , 10 ) || 0 ;
53+ return ctx . segs [ idx ] || '' ;
54+ } ) ;
55+
56+ return out
57+ . replace ( / \$ \{ t i c k e t \} / g, ctx . ticket || '' )
58+ . replace ( / \$ \{ b r a n c h \} / g, ctx . branch || '' )
59+ . replace ( / \$ \{ s e g m e n t s \} / g, ctx . segs . join ( '/' ) )
60+ . replace ( / \$ \{ m s g \} / g, ctx . msg || '' )
61+ . replace ( / \$ \{ b o d y \} / g, ctx . body || '' ) ;
8262} ;
8363
8464// =============================================================================
85- // State Creation
65+ // Preview State Creation (browser-adapted)
8666// =============================================================================
8767
8868export const createPreviewState = (
8969 config : CommitFromBranchConfig ,
9070 branch : string ,
9171 originalMessage : string = ''
92- ) : PreviewState => {
93- const normalizedConfig = normalizeConfig ( config ) ;
72+ ) : ProcessingState => {
9473 const ticket = extractTicketFromBranch ( branch ) ;
95- const lines = originalMessage . split ( '\n' ) ;
9674 const segs = branch . split ( '/' ) ;
97-
98- const template = ticket ? normalizedConfig . format : normalizedConfig . fallbackFormat ;
99- const context : Context = {
100- branch,
101- segs,
102- ticket,
103- msg : originalMessage ,
104- body : lines . join ( '\n' )
75+ const lines = originalMessage . split ( '\n' ) ;
76+
77+ const template = ticket ? config . format || '[${ticket}] ${msg}' : config . fallbackFormat || '[${seg0}] ${msg}' ;
78+ const context : Context = {
79+ branch,
80+ segs,
81+ ticket,
82+ msg : originalMessage ,
83+ body : lines . join ( '\n' )
10584 } ;
85+
10686 const renderedMessage = renderTemplate ( template , context ) ;
10787
10888 return {
109- config : normalizedConfig ,
89+ config : config as any , // Type compatibility
90+ commitMsgPath : '/mock/path' , // Browser mock
91+ source : undefined ,
11092 branch,
11193 ticket,
11294 originalMessage,
11395 lines,
114- context,
96+ context : context as any , // Type compatibility
11597 template,
11698 renderedMessage,
117- shouldSkip : false
99+ shouldSkip : false ,
100+ isDryRun : false ,
101+ debug : false
118102 } ;
119103} ;
120104
121105// =============================================================================
122- // Validation Rules (adapted from original)
106+ // Main Preview Function
123107// =============================================================================
124108
125- export const validationRules : ValidationRule [ ] = [
126- {
127- name : 'branch-existence' ,
128- check : ( state ) => Boolean ( state . branch && state . branch !== 'HEAD' ) ,
129- reason : 'no branch or detached HEAD'
130- } ,
131- {
132- name : 'include-pattern-match' ,
133- check : ( state ) => matchesAnyPattern ( state . branch , state . config . includePatterns ) ,
134- reason : 'includePattern mismatch'
109+ export const generatePreview = (
110+ config : CommitFromBranchConfig ,
111+ branch : string ,
112+ originalMessage : string = ''
113+ ) : ProcessingState => {
114+ // Create browser-compatible preview state
115+ const state = createPreviewState ( config , branch , originalMessage ) ;
116+
117+ // Apply basic validation
118+ if ( ! branch || branch === 'HEAD' ) {
119+ return { ...state , shouldSkip : true , skipReason : 'no branch or detached HEAD' } ;
135120 }
136- ] ;
137121
138- // =============================================================================
139- // Message Processors (adapted from original)
140- // =============================================================================
122+ const includePatterns = Array . isArray ( config . includePattern )
123+ ? config . includePattern
124+ : [ config . includePattern || '*' ] ;
141125
142- export const messageProcessors : MessageProcessor [ ] = [
143- {
144- name : 'template-replacement' ,
145- shouldApply : ( state ) => / \$ \{ m s g \} | \$ \{ b o d y \} / . test ( state . template ) ,
146- process : ( state ) => {
147- if ( state . originalMessage === state . renderedMessage ) {
148- return { ...state , shouldSkip : true , skipReason : 'message already matches template' } ;
149- }
150- return {
151- ...state ,
152- lines : [ state . renderedMessage , ...state . lines . slice ( 1 ) ]
153- } ;
154- }
155- } ,
156- {
157- name : 'prefix-addition' ,
158- shouldApply : ( state ) => ! / \$ \{ m s g \} | \$ \{ b o d y \} / . test ( state . template ) ,
159- process : ( state ) => {
160- const escaped = escapeRegexSpecialChars ( state . renderedMessage ) ;
161-
162- // Check if prefix already exists
163- if ( new RegExp ( '^\\s*' + escaped , 'i' ) . test ( state . originalMessage ) ) {
164- return { ...state , shouldSkip : true , skipReason : 'prefix already exists' } ;
165- }
166-
167- // Check if ticket is already in message
168- if ( state . ticket ) {
169- const ticketRegex = new RegExp ( `\\b${ escapeRegexSpecialChars ( state . ticket ) } \\b` , 'i' ) ;
170- if ( ticketRegex . test ( state . originalMessage ) ) {
171- return { ...state , shouldSkip : true , skipReason : 'ticket already in message' } ;
172- }
173- }
174-
175- // Check if branch segment is already in message
176- const firstSeg = state . context . segs [ 0 ] ;
177- if ( firstSeg && firstSeg !== 'HEAD' ) {
178- const segRegex = new RegExp ( `\\b${ escapeRegexSpecialChars ( firstSeg ) } \\b` , 'i' ) ;
179- if ( segRegex . test ( state . originalMessage ) ) {
180- return { ...state , shouldSkip : true , skipReason : 'branch segment already in message' } ;
181- }
182- }
183-
184- return {
185- ...state ,
186- lines : [ state . renderedMessage + state . originalMessage , ...state . lines . slice ( 1 ) ]
187- } ;
188- }
126+ if ( ! matchesAnyPattern ( branch , includePatterns ) ) {
127+ return { ...state , shouldSkip : true , skipReason : 'includePattern mismatch' } ;
189128 }
190- ] ;
191129
192- // =============================================================================
193- // Processing Pipeline
194- // =============================================================================
130+ // Apply message processing logic
131+ const hasMessageToken = / \$ \{ m s g \} | \$ \{ b o d y \} / . test ( state . template ) ;
195132
196- const applyValidationRules = ( state : PreviewState ) : PreviewState => {
197- for ( const rule of validationRules ) {
198- if ( ! rule . check ( state ) ) {
199- return { ...state , shouldSkip : true , skipReason : rule . reason } ;
133+ if ( hasMessageToken ) {
134+ if ( originalMessage === state . renderedMessage ) {
135+ return { ...state , shouldSkip : true , skipReason : 'message already matches template' } ;
200136 }
201- }
202- return state ;
203- } ;
137+ return { ...state , lines : [ state . renderedMessage , ...state . lines . slice ( 1 ) ] } ;
138+ } else {
139+ // Prefix mode - check for duplicates
140+ const escaped = escapeRegexSpecialChars ( state . renderedMessage ) ;
204141
205- const processMessage = ( state : PreviewState ) : PreviewState => {
206- if ( state . shouldSkip ) return state ;
207-
208- const applicableProcessor = messageProcessors . find ( processor =>
209- processor . shouldApply ( state )
210- ) ;
211-
212- if ( ! applicableProcessor ) {
213- return { ...state , shouldSkip : true , skipReason : 'no applicable processor' } ;
214- }
215-
216- return applicableProcessor . process ( state ) ;
217- } ;
142+ if ( new RegExp ( '^\\s*' + escaped , 'i' ) . test ( originalMessage ) ) {
143+ return { ...state , shouldSkip : true , skipReason : 'prefix already exists' } ;
144+ }
218145
219- const pipe = < T > ( ...functions : Array < ( arg : T ) => T > ) => ( value : T ) : T =>
220- functions . reduce ( ( acc , fn ) => fn ( acc ) , value ) ;
146+ if ( state . ticket && new RegExp ( `\\b${ escapeRegexSpecialChars ( state . ticket ) } \\b` , 'i' ) . test ( originalMessage ) ) {
147+ return { ...state , shouldSkip : true , skipReason : 'ticket already in message' } ;
148+ }
221149
222- // =============================================================================
223- // Main Preview Function
224- // =============================================================================
150+ const firstSeg = state . context . segs [ 0 ] ;
151+ if ( firstSeg && firstSeg !== 'HEAD' &&
152+ new RegExp ( `\\b${ escapeRegexSpecialChars ( firstSeg ) } \\b` , 'i' ) . test ( originalMessage ) ) {
153+ return { ...state , shouldSkip : true , skipReason : 'branch segment already in message' } ;
154+ }
225155
226- export const generatePreview = (
227- config : CommitFromBranchConfig ,
228- branch : string ,
229- originalMessage : string = ''
230- ) : PreviewState => {
231- const initialState = createPreviewState ( config , branch , originalMessage ) ;
232-
233- const pipeline = pipe (
234- applyValidationRules ,
235- processMessage
236- ) ;
237-
238- return pipeline ( initialState ) ;
156+ return {
157+ ...state ,
158+ lines : [ state . renderedMessage + originalMessage , ...state . lines . slice ( 1 ) ]
159+ } ;
160+ }
239161} ;
240162
241- // =============================================================================
242- // Exports
243- // =============================================================================
244-
163+ // Re-export for compatibility
245164export {
246- applyValidationRules ,
247- processMessage ,
248165 createRegexPattern ,
249166 matchesAnyPattern ,
250167 extractTicketFromBranch
0 commit comments