Skip to content

Commit 748d915

Browse files
leogdionclaude
andcommitted
Refactor PackageDSLManager: split into focused extension files
- Split 545-line PackageDSLManager.swift into smaller, manageable files - Create separate extensions for different responsibilities: - PackageDSLManager+ProductManagement.swift - Product operations - PackageDSLManager+DependencyManagement.swift - Dependency operations - PackageDSLManager+FluentAPI.swift - Convenience methods - PackageDSLManager+TargetManagement.swift - Target and package creation - Change fileprivate(set) to internal(set) for extension access - Use explicit internal import Foundation statements - Fix critical file length linting error (545 → 121 lines in main file) - Maintain all existing functionality and API compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6bd6a6f commit 748d915

File tree

7 files changed

+574
-430
lines changed

7 files changed

+574
-430
lines changed

Sources/PackageDSLKit/Types/PackageDSLManager+CodeGeneration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
// OTHER DEALINGS IN THE SOFTWARE.
2828
//
2929

30-
public import Foundation
30+
internal import Foundation
3131

3232
// MARK: - Code Generation
3333

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//
2+
// PackageDSLManager+DependencyManagement.swift
3+
// MistKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
internal import Foundation
31+
32+
// MARK: - Dependency Management
33+
34+
extension PackageDSLManager {
35+
/// Add a URL-based package dependency
36+
/// - Parameters:
37+
/// - url: The URL of the package repository
38+
/// - requirement: Version requirement for the dependency
39+
/// - Returns: Self for method chaining
40+
/// - Throws: PackageError if dependency already exists
41+
@discardableResult
42+
public func addDependency(url: String, requirement: VersionRequirement) throws
43+
-> PackageDSLManager
44+
{
45+
let name = extractPackageName(from: url)
46+
47+
// Check for duplicate dependency names
48+
guard !dependencies.contains(where: { $0.typeName == name }) else {
49+
throw PackageError.duplicateDependencyName(name)
50+
}
51+
52+
let dependency = Dependency(
53+
typeName: name,
54+
type: .package,
55+
dependency: "\".package(url: \"\(url)\", \(requirement.asSPMString()))\"",
56+
package: DependencyRef(name: name)
57+
)
58+
59+
dependencies.append(dependency)
60+
return self
61+
}
62+
63+
/// Add a local path-based package dependency
64+
/// - Parameter path: The local file system path to the package
65+
/// - Returns: Self for method chaining
66+
/// - Throws: PackageError if dependency already exists
67+
@discardableResult
68+
public func addDependency(path: String) throws -> PackageDSLManager {
69+
let name = URL(fileURLWithPath: path).lastPathComponent
70+
71+
// Check for duplicate dependency names
72+
guard !dependencies.contains(where: { $0.typeName == name }) else {
73+
throw PackageError.duplicateDependencyName(name)
74+
}
75+
76+
let dependency = Dependency(
77+
typeName: name,
78+
type: .package,
79+
dependency: "\".package(path: \"\(path)\")\"",
80+
package: DependencyRef(name: name)
81+
)
82+
83+
dependencies.append(dependency)
84+
return self
85+
}
86+
87+
/// Add a registry-based package dependency
88+
/// - Parameters:
89+
/// - identity: The package identity in the registry
90+
/// - requirement: Version requirement for the dependency
91+
/// - Returns: Self for method chaining
92+
/// - Throws: PackageError if dependency already exists
93+
@discardableResult
94+
public func addDependency(identity: String, requirement: VersionRequirement) throws
95+
-> PackageDSLManager
96+
{
97+
// Check for duplicate dependency names
98+
guard !dependencies.contains(where: { $0.typeName == identity }) else {
99+
throw PackageError.duplicateDependencyName(identity)
100+
}
101+
102+
let dependency = Dependency(
103+
typeName: identity,
104+
type: .package,
105+
dependency: "\".package(id: \"\(identity)\", \(requirement.asSPMString()))\"",
106+
package: DependencyRef(name: identity)
107+
)
108+
109+
dependencies.append(dependency)
110+
return self
111+
}
112+
113+
/// Remove a package dependency
114+
/// - Parameters:
115+
/// - name: The dependency name to remove
116+
/// - force: If true, remove even if targets depend on it
117+
/// - Returns: Self for method chaining
118+
/// - Throws: PackageError.dependencyNotFound if dependency doesn't exist, or cascadeRemovalRequired if targets depend on it and force is false
119+
@discardableResult
120+
public func removeDependency(name: String, force: Bool = false) throws -> PackageDSLManager {
121+
// Check if dependency exists
122+
guard dependencies.contains(where: { $0.typeName == name }) else {
123+
throw PackageError.dependencyNotFound(name)
124+
}
125+
126+
let dependentTargets = findDependencyDependentTargets(name)
127+
try validateDependencyRemoval(name: name, dependents: dependentTargets, force: force)
128+
129+
removeDependencyFromCollection(name)
130+
cleanupDependencyReferences(name)
131+
132+
return self
133+
}
134+
135+
private func findDependencyDependentTargets(_ name: String) -> [String] {
136+
let dependentRegularTargets = targets.filter { target in
137+
target.dependencies.contains { $0.name == name }
138+
}
139+
let dependentTestTargets = testTargets.filter { testTarget in
140+
testTarget.dependencies.contains { $0.name == name }
141+
}
142+
143+
return dependentRegularTargets.map(\.typeName) + dependentTestTargets.map(\.typeName)
144+
}
145+
146+
private func validateDependencyRemoval(name: String, dependents: [String], force: Bool) throws {
147+
if !dependents.isEmpty && !force {
148+
throw PackageError.cascadeRemovalRequired(name, dependents)
149+
}
150+
}
151+
152+
private func removeDependencyFromCollection(_ name: String) {
153+
dependencies.removeAll { $0.typeName == name }
154+
}
155+
156+
private func cleanupDependencyReferences(_ name: String) {
157+
// Remove dependency references from targets (always clean up references)
158+
targets = targets.map { target in
159+
let filteredDependencies = target.dependencies.filter { $0.name != name }
160+
return Target(
161+
typeName: target.typeName,
162+
dependencies: filteredDependencies
163+
)
164+
}
165+
166+
// Remove dependency references from test targets
167+
testTargets = testTargets.map { testTarget in
168+
let filteredDependencies = testTarget.dependencies.filter { $0.name != name }
169+
return TestTarget(
170+
typeName: testTarget.typeName,
171+
dependencies: filteredDependencies
172+
)
173+
}
174+
}
175+
176+
/// Helper method to extract package name from URL
177+
private func extractPackageName(from url: String) -> String {
178+
guard let urlObject = URL(string: url) else {
179+
return url.components(separatedBy: "/").last ?? url
180+
}
181+
182+
let pathComponent = urlObject.lastPathComponent
183+
184+
// Remove .git suffix if present
185+
if pathComponent.hasSuffix(".git") {
186+
return String(pathComponent.dropLast(4))
187+
}
188+
189+
return pathComponent
190+
}
191+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//
2+
// PackageDSLManager+FluentAPI.swift
3+
// MistKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
internal import Foundation
31+
32+
// MARK: - Fluent API Extensions
33+
34+
extension PackageDSLManager {
35+
/// Fluent API method to add multiple dependencies at once
36+
/// - Parameter dependencies: Array of (url, requirement) tuples
37+
/// - Returns: Self for method chaining
38+
@discardableResult
39+
public func addDependencies(_ dependencies: [(url: String, requirement: VersionRequirement)])
40+
throws -> PackageDSLManager
41+
{
42+
for (url, requirement) in dependencies {
43+
try addDependency(url: url, requirement: requirement)
44+
}
45+
return self
46+
}
47+
48+
/// Fluent API method to add multiple path dependencies at once
49+
/// - Parameter paths: Array of local paths
50+
/// - Returns: Self for method chaining
51+
@discardableResult
52+
public func addPathDependencies(_ paths: [String]) throws -> PackageDSLManager {
53+
for path in paths {
54+
try addDependency(path: path)
55+
}
56+
return self
57+
}
58+
59+
/// Convenience method to add dependency with string version requirement
60+
/// - Parameters:
61+
/// - url: The URL of the package repository
62+
/// - versionString: String representation of version requirement
63+
/// - Returns: Self for method chaining
64+
/// - Throws: PackageError if dependency already exists or version string is invalid
65+
@discardableResult
66+
public func addDependency(url: String, version versionString: String) throws -> PackageDSLManager
67+
{
68+
guard let requirement = VersionRequirement.parse(versionString) else {
69+
throw PackageError.invalidConfiguration("Invalid version requirement: \(versionString)")
70+
}
71+
return try addDependency(url: url, requirement: requirement)
72+
}
73+
74+
/// Convenience method to add registry dependency with string version requirement
75+
/// - Parameters:
76+
/// - identity: The package identity in the registry
77+
/// - versionString: String representation of version requirement
78+
/// - Returns: Self for method chaining
79+
/// - Throws: PackageError if dependency already exists or version string is invalid
80+
@discardableResult
81+
public func addDependency(identity: String, version versionString: String) throws
82+
-> PackageDSLManager
83+
{
84+
guard let requirement = VersionRequirement.parse(versionString) else {
85+
throw PackageError.invalidConfiguration("Invalid version requirement: \(versionString)")
86+
}
87+
return try addDependency(identity: identity, requirement: requirement)
88+
}
89+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// PackageDSLManager+ProductManagement.swift
3+
// MistKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
internal import Foundation
31+
32+
// MARK: - Product Management
33+
34+
extension PackageDSLManager {
35+
/// Add a product to the package
36+
/// - Parameters:
37+
/// - name: The product name
38+
/// - type: The product type (.library or .executable)
39+
/// - targets: Target names that make up this product
40+
/// - Returns: Self for method chaining
41+
/// - Throws: PackageError if product name already exists or targets don't exist
42+
@discardableResult
43+
public func addProduct(name: String, type: ProductType, targets: [String]) throws
44+
-> PackageDSLManager
45+
{
46+
// Check for duplicate product names
47+
guard !products.contains(where: { $0.typeName == name }) else {
48+
throw PackageError.duplicateProductName(name)
49+
}
50+
51+
// Verify all target names exist
52+
let allTargetNames = self.targets.map(\.typeName) + testTargets.map(\.typeName)
53+
for targetName in targets {
54+
guard allTargetNames.contains(targetName) else {
55+
throw PackageError.targetNotFound(targetName)
56+
}
57+
}
58+
59+
// Create dependency references for the targets
60+
let targetDependencies = targets.map { DependencyRef(name: $0) }
61+
62+
let product = Product(
63+
typeName: name,
64+
name: name,
65+
dependencies: targetDependencies,
66+
productType: type
67+
)
68+
69+
products.append(product)
70+
return self
71+
}
72+
73+
/// Remove a product from the package
74+
/// - Parameter name: The product name to remove
75+
/// - Returns: Self for method chaining
76+
/// - Throws: PackageError.productNotFound if product doesn't exist
77+
@discardableResult
78+
public func removeProduct(name: String) throws -> PackageDSLManager {
79+
// Check if product exists
80+
guard products.contains(where: { $0.typeName == name }) else {
81+
throw PackageError.productNotFound(name)
82+
}
83+
84+
// Remove the product
85+
products.removeAll { $0.typeName == name }
86+
return self
87+
}
88+
}

0 commit comments

Comments
 (0)