@@ -63,27 +63,74 @@ static char *strip_resolved_ext(char *path) {
6363 return path ;
6464}
6565
66- /* If target starts with "./" and dir_prefix is non-empty, prepend dir_prefix.
67- * Returns heap-allocated repo-relative target. */
66+ /* Join dir_prefix with target, collapsing "." and ".." segments so aliases
67+ * that climb out of their tsconfig's directory (the common monorepo
68+ * pattern: a tsconfig at apps/web/tsconfig.json pointing an alias at a
69+ * wildcard target like "../../packages/shared/src/" + wildcard) resolve
70+ * to a real repo-relative path. Naive concatenation left literal ".."
71+ * components in the target, which never match a module's FQN since
72+ * cbm_pipeline_fqn_module tokenizes on '/' without collapsing them
73+ * (#730). A trailing '/' on target (the usual case right before a
74+ * wildcard) is preserved so the caller's later wildcard-substring
75+ * concat still lines up. Returns heap-allocated
76+ * repo-relative target. */
6877static char * resolve_target_relative (const char * dir_prefix , const char * target ) {
6978 if (!target ) {
7079 return NULL ;
7180 }
72- const char * t = target ;
73- if (t [0 ] == '.' && t [1 ] == '/' ) {
74- t += 2 ;
81+ size_t dp_len = (dir_prefix && dir_prefix [0 ] != '\0' ) ? strlen (dir_prefix ) : 0 ;
82+ size_t t_len = strlen (target );
83+ char * buf = malloc (dp_len + t_len + 2 );
84+ if (!buf ) {
85+ return NULL ;
7586 }
76- if (!dir_prefix || dir_prefix [0 ] == '\0' ) {
77- return strdup (t );
87+ buf [0 ] = '\0' ;
88+ if (dp_len > 0 ) {
89+ memcpy (buf , dir_prefix , dp_len );
90+ buf [dp_len ] = '\0' ;
7891 }
79- size_t dp_len = strlen (dir_prefix );
80- size_t t_len = strlen (t );
81- char * result = malloc (dp_len + 1 + t_len + 1 );
82- if (!result ) {
83- return NULL ;
92+
93+ bool trailing_slash = t_len > 0 && target [t_len - 1 ] == '/' ;
94+
95+ const char * p = target ;
96+ while (* p ) {
97+ while (* p == '/' ) {
98+ p ++ ;
99+ }
100+ if (!* p ) {
101+ break ;
102+ }
103+ const char * seg_start = p ;
104+ while (* p && * p != '/' ) {
105+ p ++ ;
106+ }
107+ size_t seg_len = (size_t )(p - seg_start );
108+ if (seg_len == 1 && seg_start [0 ] == '.' ) {
109+ continue ;
110+ }
111+ if (seg_len == 2 && seg_start [0 ] == '.' && seg_start [1 ] == '.' ) {
112+ char * last = strrchr (buf , '/' );
113+ if (last ) {
114+ * last = '\0' ;
115+ } else {
116+ buf [0 ] = '\0' ;
117+ }
118+ continue ;
119+ }
120+ size_t cur = strlen (buf );
121+ if (cur > 0 ) {
122+ buf [cur ++ ] = '/' ;
123+ }
124+ memcpy (buf + cur , seg_start , seg_len );
125+ buf [cur + seg_len ] = '\0' ;
126+ }
127+
128+ if (trailing_slash ) {
129+ size_t cur = strlen (buf );
130+ buf [cur ] = '/' ;
131+ buf [cur + 1 ] = '\0' ;
84132 }
85- snprintf (result , dp_len + 1 + t_len + 1 , "%s/%s" , dir_prefix , t );
86- return result ;
133+ return buf ;
87134}
88135
89136/* qsort comparator: alias entries by alias_prefix length, descending. */
0 commit comments