Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ on:
branches: [ main ]
# Trigger the workflow on any pull request
pull_request:
# Allow manual trigger
workflow_dispatch:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not particularly a fan of that as the use cases are really rare, but it doesn't hurt either I guess


concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,27 @@ open class SvelteSource(project: Project) : Source<SvelteConfig>(project, Svelte
fileManager.getFileContentsAtPath(configFileName)
?: throw NoSuchFileException("Cannot get $configFileName after sync")
}
val aliasPath = parseTsConfig(tsConfig)
// Handle scoped package aliases (e.g. @repo/ui/components).
// For @-prefixed aliases, the package name includes a slash (@scope/name),
// so we split into at most 3 parts to correctly separate the prefix from the rest.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first glance, splitting with exact numbers feels wonky, CodeRabbit review seems right on the kind of problem that could arise.

But to be honest, I'm not really sure what's the best way to handle this: maybe looking at what the original shadcn implementation looks like could help?

val (aliasPrefix, suffix) = if (alias.startsWith("@")) {
val parts = alias.split("/", limit = 3)
val prefix = if (parts.size >= 2) "${parts[0]}/${parts[1]}" else alias
val rest = if (parts.size >= 3) parts[2] else ""
prefix to rest
} else {
alias.substringBefore("/") to alias.substringAfter("/", "")
}

val paths = parseTsConfig(tsConfig)
.asJsonObject?.get("compilerOptions")
?.asJsonObject?.get("paths")
?.asJsonObject?.get(alias.substringBefore("/"))

val aliasPath = (paths?.asJsonObject?.get(aliasPrefix)
?: paths?.asJsonObject?.get("$aliasPrefix/*"))
?.asJsonArray?.get(0)
?.asJsonPrimitive?.content ?: throw Exception("Cannot find alias $alias in $tsConfig")
val normalized = aliasPath.replace(Regex("^\\.+/"), "")
val suffix = alias.substringAfter("/", "")
val normalized = aliasPath.replace(Regex("^\\.+/"), "").removeSuffix("/*")
val resolved = if (suffix.isEmpty()) normalized else "$normalized/$suffix"
Comment on lines +78 to 96
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This regresses single-segment @ aliases.

The new branch treats every @... import as @scope/package/.... That breaks previously supported aliases like @/components/ui/button and @components/foo, because the resolver now looks for @/components/* or @components/foo/* instead of falling back to the original single-segment key (@/*, @components/*, etc.).

🛠️ One way to preserve both scoped and non-scoped forms
-        val (aliasPrefix, suffix) = if (alias.startsWith("@")) {
-            val parts = alias.split("/", limit = 3)
-            val prefix = if (parts.size >= 2) "${parts[0]}/${parts[1]}" else alias
-            val rest = if (parts.size >= 3) parts[2] else ""
-            prefix to rest
-        } else {
-            alias.substringBefore("/") to alias.substringAfter("/", "")
-        }
+        val defaultCandidate = alias.substringBefore("/") to alias.substringAfter("/", "")
+        val scopedCandidate = alias
+            .takeIf { it.startsWith("@") }
+            ?.split("/", limit = 3)
+            ?.takeIf { it.size >= 2 && it[0] != "@" }
+            ?.let { "${it[0]}/${it[1]}" to it.getOrElse(2) { "" } }
 
         val paths = parseTsConfig(tsConfig)
             .asJsonObject?.get("compilerOptions")
             ?.asJsonObject?.get("paths")
 
-        val aliasPath = (paths?.asJsonObject?.get(aliasPrefix)
-            ?: paths?.asJsonObject?.get("$aliasPrefix/*"))
-            ?.asJsonArray?.get(0)
-            ?.asJsonPrimitive?.content ?: throw Exception("Cannot find alias $alias in $tsConfig")
-        val normalized = aliasPath.replace(Regex("^\\.+/"), "").removeSuffix("/*")
-        val resolved = if (suffix.isEmpty()) normalized else "$normalized/$suffix"
+        fun resolveCandidate(aliasPrefix: String, suffix: String): String? {
+            val aliasPath = (paths?.asJsonObject?.get(aliasPrefix)
+                ?: paths?.asJsonObject?.get("$aliasPrefix/*"))
+                ?.asJsonArray?.get(0)
+                ?.asJsonPrimitive?.content ?: return null
+
+            val normalized = aliasPath.replace(Regex("^\\.+/"), "").removeSuffix("/*")
+            return if (suffix.isEmpty()) normalized else "$normalized/$suffix"
+        }
+
+        val resolved = (scopedCandidate?.let { (aliasPrefix, suffix) ->
+            resolveCandidate(aliasPrefix, suffix)
+        } ?: resolveCandidate(defaultCandidate.first, defaultCandidate.second))
+            ?: throw Exception("Cannot find alias $alias in $tsConfig")

return resolved.also { log.debug("Resolved alias $alias to $it") }
}
Expand Down