Skip to content

Instantly share code, notes, and snippets.

@nevillco
Created September 27, 2024 15:27
Show Gist options
  • Save nevillco/f4abe97b579a6cb86382404243305c8b to your computer and use it in GitHub Desktop.
Save nevillco/f4abe97b579a6cb86382404243305c8b to your computer and use it in GitHub Desktop.
Scaffold TCA Feature in Tuist Project script
#!/bin/bash
set -e
# This script does the following, for a given ComposableArchitecture feature.
# Suppose we are running `./Scripts/scaffold_tca_feature.sh LoginFeature` as an example.
#
# - Create intermediate folders and boilerplate files for 4 targets:
# LoginFeatureKit, LoginFeatureUI, LoginFeatureKitTests, LoginFeatureUITests
# - Create a Tuist manifest file representing the targets at:
# ./Tuist/ProjectDescriptionHelpers/Targets/Features/LoginFeature.swift
# - Insert the new targets into Tuist’s `Project.swift`, maintaining an alphabetical array of targets.
# - Commit the changes and regenerate the project.
#
# A bunch of aspects of this script are opinionated based on my own infrastructure.
# For example, a `CFTargetGroup` is an abstraction bundling a bunch of `ProjectDescription.Target`s.
# Therefore, don’t expect to just "drop in" this script to your project - it is
# posted as a gist only as a reference/inspiration to others using Tuist as a way to improve automation and velocity.
NEW_FEATURE_NAME=$1
if [[ -z "$NEW_FEATURE_NAME" ]]; then
echo "No feature name provided. Provide a feature name, capitalized, like: './Scripts/scaffold_tca_feature.sh MyNewFeature'"
exit 1
fi
echo "Scaffolding ${NEW_FEATURE_NAME}Kit target."
mkdir -p "Targets/Features/${NEW_FEATURE_NAME}Kit/Sources"
cat <<EOF > "Targets/Features/${NEW_FEATURE_NAME}Kit/Sources/${NEW_FEATURE_NAME}.swift"
import ComposableArchitecture
@Reducer public struct $NEW_FEATURE_NAME {
@ObservableState public struct State {
public init() { }
}
public init() { }
}
EOF
echo "Scaffolding ${NEW_FEATURE_NAME}UI target."
mkdir -p "Targets/Features/${NEW_FEATURE_NAME}UI/Sources"
cat <<EOF > "Targets/Features/${NEW_FEATURE_NAME}UI/Sources/${NEW_FEATURE_NAME}View.swift"
import ComposableArchitecture
import ${NEW_FEATURE_NAME}Kit
import SwiftUI
public struct ${NEW_FEATURE_NAME}View: View {
@Bindable var store: StoreOf<${NEW_FEATURE_NAME}>
public init(store: StoreOf<${NEW_FEATURE_NAME}>) {
self.store = store
}
public var body: some View {
Text("Hello, world!")
}
}
EOF
echo "Scaffolding (empty) test targets."
mkdir -p "Targets/Features/${NEW_FEATURE_NAME}Kit/Tests"
touch "Targets/Features/${NEW_FEATURE_NAME}Kit/Tests/${NEW_FEATURE_NAME}Tests.swift"
mkdir -p "Targets/Features/${NEW_FEATURE_NAME}UI/Tests"
touch "Targets/Features/${NEW_FEATURE_NAME}UI/Tests/${NEW_FEATURE_NAME}ViewTests.swift"
echo "Scaffolding Tuist manifest file."
TUIST_FILE="Tuist/ProjectDescriptionHelpers/Targets/Features/${NEW_FEATURE_NAME}.swift"
lowercase_first_letter() {
input="$1"
echo "$(tr '[:upper:]' '[:lower:]' <<< "${input:0:1}")${input:1}"
}
NEW_FEATURE_NAME_LOWER=$(lowercase_first_letter "$NEW_FEATURE_NAME")
cat <<EOF > $TUIST_FILE
import CFTuist
public extension CFTargetGroup {
static let ${NEW_FEATURE_NAME_LOWER} = Self.projectDefault.feature(
kitTarget: .init(
name: .${NEW_FEATURE_NAME_LOWER}Kit
),
uiTarget: .init(
name: .${NEW_FEATURE_NAME_LOWER}UI
)
)
}
public extension CFTargetDependency {
static let ${NEW_FEATURE_NAME_LOWER}Kit: Self = .target(.${NEW_FEATURE_NAME_LOWER}Kit)
static let ${NEW_FEATURE_NAME_LOWER}UI: Self = .target(.${NEW_FEATURE_NAME_LOWER}UI)
}
public extension CFTarget.Name {
static let ${NEW_FEATURE_NAME_LOWER}Kit: Self = "${NEW_FEATURE_NAME}Kit"
static let ${NEW_FEATURE_NAME_LOWER}UI: Self = "${NEW_FEATURE_NAME}UI"
}
EOF
echo "Adding new target group .${NEW_FEATURE_NAME_LOWER} to Tuist Project.swift file."
# File to modify
FILE="Project.swift"
# Find the line number where 'targetGroups: [' starts
START_LINE=$(grep -n 'targetGroups: \[' "$FILE" | cut -d: -f1)
if [ -z "$START_LINE" ]; then
echo "Error: 'targetGroups: [' not found in $FILE"
exit 1
fi
# Find the line number where the 'targetGroups' array ends
END_LINE=$(awk "NR>$START_LINE-1 && /],/ {print NR; exit}" "$FILE")
if [ -z "$END_LINE" ]; then
echo "Error: Closing bracket for 'targetGroups' not found."
exit 1
fi
# Extract the existing items
sed -n "${START_LINE},${END_LINE}p" "$FILE" | sed '1d;$d' | sed 's/^[[:space:]]*\.//;s/,$//' > tmp_targetGroups.txt
# Add the new item
echo "$NEW_FEATURE_NAME_LOWER" | sed 's/^[[:space:]]*\.//' >> tmp_targetGroups.txt
# Sort the items alphabetically
SORTED_ITEMS=$(sort tmp_targetGroups.txt)
# Reconstruct the 'targetGroups' section
{
sed -n "1,$((START_LINE - 1))p" "$FILE"
echo " targetGroups: ["
echo "$SORTED_ITEMS" | sed 's/^/ ./' | sed 's/$/,/'
echo " ],"
sed -n "$((END_LINE + 1)),\$p" "$FILE"
} > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
# Clean up
rm tmp_targetGroups.txt
echo "Successfully added .$NEW_FEATURE_NAME_LOWER to Tuist Project.swift file."
echo "Committing changes."
git add . && git commit -m "Scaffold ${NEW_FEATURE_NAME}"
echo "Done scaffolding! Regenerating the project."
tuist generate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment