Skip to content

Commit d680855

Browse files
committed
V1.0.0 beta.1 (#11)
feat: Swift App Template v1.0.0-beta.1 (#11) - Copy and restructure MonthBar infrastructure into template layout - Replace all hardcoded values with `{{TEMPLATE_*}}` placeholder tokens - Add `Packages/TemplateGenerator` — Swift executable that generates app skeleton files using SyntaxKit - Add `Scripts/setup.sh` — interactive/automated setup with 19 token substitutions, Swift file generation, CI activation, full-stack mode, cleanup, and git reset - Add `README.md`, `README.template.md`, and `CLAUDE.template.md` template documentation - Move `workflows/app.yml` → `.github/app.workflow.yml` so GitHub skips parsing it until `setup.sh` activates it - Add `.github/workflows/validate-template.yml` — CI that confirms placeholder tokens haven't been replaced on the template repo - Replace placeholder-specific icon assets with a generic gradient placeholder - Sync `_reference/MonthBar` to latest upstream - Add `_reference/AtLeast` — iOS/watchOS companion app reference with multi-platform Fastlane and dynamic CI matrix - Extract `private_lane :setup_api_key` in Fastfile; add `sync_build_number`, `sync_last_release`, `upload_metadata`, `upload_privacy_details`, `submit_for_review` lanes - Add `pull_request` trigger, concurrency group, and dynamic matrix to CI workflow - Split `build-macos` into always-run and full-matrix jobs; gate Windows/Android on cross-platform condition - Add conditional server test jobs wrapped in `FULL_STACK_ONLY` markers for `setup.sh` processing - Bump actions: `checkout@v6`, `swift-build@v1.5.2`, `swift-coverage-action@v5`, `codecov-action@v6`, `mise-action@v4` - Prefix all Makefile `fastlane` calls with `mise exec --`; add screenshot targets - Expand CLAUDE.md Commands and Code Signing sections with new Fastlane lanes and make targets - Add automation guide TODO tracker linked to issues #28#50 - Fix `bundle install` and git section in `setup.sh`; harden with `printf -v`, required-field validation, and `swift` availability check - Replace `keywords.txt` with lorem ipsum placeholder to force users to update before App Store submission - Bump Dockerfile base image from `swift:6.0-jammy` to `swift:6.3-noble` - Add `client` to `openapi-generator-config.yaml` generate list - Remove LFS exclusion section from `.gitattributes` --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 18c1e25 commit d680855

File tree

18 files changed

+544
-19
lines changed

18 files changed

+544
-19
lines changed

Sources/SyntaxKit/Declarations/Class.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public struct Class: CodeBlock, Sendable {
3737
private var genericParameters: [String] = []
3838
private var isFinal: Bool = false
3939
private var attributes: [AttributeInfo] = []
40+
private var accessModifier: AccessModifier?
4041

4142
/// The SwiftSyntax representation of this class declaration.
4243
public var syntax: any SyntaxProtocol {
@@ -107,11 +108,16 @@ public struct Class: CodeBlock, Sendable {
107108

108109
// Modifiers
109110
var modifiers: DeclModifierListSyntax = []
110-
if isFinal {
111+
if let access = accessModifier {
111112
modifiers = DeclModifierListSyntax([
112-
DeclModifierSyntax(name: .keyword(.final, trailingTrivia: .space))
113+
DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space))
113114
])
114115
}
116+
if isFinal {
117+
modifiers = DeclModifierListSyntax(
118+
modifiers + [DeclModifierSyntax(name: .keyword(.final, trailingTrivia: .space))]
119+
)
120+
}
115121

116122
return ClassDeclSyntax(
117123
attributes: attributeList,
@@ -161,6 +167,15 @@ public struct Class: CodeBlock, Sendable {
161167
return copy
162168
}
163169

170+
/// Sets the access modifier for the class declaration.
171+
/// - Parameter access: The access modifier.
172+
/// - Returns: A copy of the class with the access modifier set.
173+
public func access(_ access: AccessModifier) -> Self {
174+
var copy = self
175+
copy.accessModifier = access
176+
return copy
177+
}
178+
164179
/// Adds an attribute to the class declaration.
165180
/// - Parameters:
166181
/// - attribute: The attribute name (without the @ symbol).
@@ -189,13 +204,13 @@ public struct Class: CodeBlock, Sendable {
189204
rightParen = .rightParenToken()
190205

191206
let argumentList = arguments.map { argument in
192-
DeclReferenceExprSyntax(baseName: .identifier(argument))
207+
buildAttributeArgumentExpr(from: argument)
193208
}
194209

195210
argumentsSyntax = .argumentList(
196211
LabeledExprListSyntax(
197212
argumentList.enumerated().map { index, expr in
198-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
213+
var element = LabeledExprSyntax(expression: expr)
199214
if index < argumentList.count - 1 {
200215
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
201216
}
@@ -213,6 +228,7 @@ public struct Class: CodeBlock, Sendable {
213228
arguments: argumentsSyntax,
214229
rightParen: rightParen
215230
)
231+
.with(\.trailingTrivia, .newline)
216232
)
217233
}
218234

Sources/SyntaxKit/Declarations/Enum.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@ public struct Enum: CodeBlock, Sendable {
134134
rightParen = .rightParenToken()
135135

136136
let argumentList = arguments.map { argument in
137-
DeclReferenceExprSyntax(baseName: .identifier(argument))
137+
buildAttributeArgumentExpr(from: argument)
138138
}
139139

140140
argumentsSyntax = .argumentList(
141141
LabeledExprListSyntax(
142142
argumentList.enumerated().map { index, expr in
143-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
143+
var element = LabeledExprSyntax(expression: expr)
144144
if index < argumentList.count - 1 {
145145
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
146146
}
@@ -158,6 +158,7 @@ public struct Enum: CodeBlock, Sendable {
158158
arguments: argumentsSyntax,
159159
rightParen: rightParen
160160
)
161+
.with(\.trailingTrivia, .newline)
161162
)
162163
}
163164
return AttributeListSyntax(attributeElements)

Sources/SyntaxKit/Declarations/Extension.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,13 @@ public struct Extension: CodeBlock, Sendable {
130130
rightParen = .rightParenToken()
131131

132132
let argumentList = arguments.map { argument in
133-
DeclReferenceExprSyntax(baseName: .identifier(argument))
133+
buildAttributeArgumentExpr(from: argument)
134134
}
135135

136136
argumentsSyntax = .argumentList(
137137
LabeledExprListSyntax(
138138
argumentList.enumerated().map { index, expr in
139-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
139+
var element = LabeledExprSyntax(expression: expr)
140140
if index < argumentList.count - 1 {
141141
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
142142
}
@@ -154,6 +154,7 @@ public struct Extension: CodeBlock, Sendable {
154154
arguments: argumentsSyntax,
155155
rightParen: rightParen
156156
)
157+
.with(\.trailingTrivia, .newline)
157158
)
158159
}
159160
return AttributeListSyntax(attributeElements)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// IfCanImport.swift
3+
// SyntaxKit
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+
public import SwiftSyntax
31+
32+
/// A `#if canImport(Module)` … `#endif` conditional compilation block.
33+
public struct IfCanImport: CodeBlock, Sendable {
34+
private let moduleName: String
35+
private let content: [any CodeBlock]
36+
37+
/// Creates a `#if canImport(moduleName)` block wrapping the given content.
38+
/// - Parameters:
39+
/// - moduleName: The module name passed to `canImport`.
40+
/// - content: The code blocks to emit when the module is available.
41+
public init(_ moduleName: String, @CodeBlockBuilderResult _ content: () -> [any CodeBlock]) {
42+
self.moduleName = moduleName
43+
self.content = content()
44+
}
45+
46+
public var syntax: any SyntaxProtocol {
47+
let condition = FunctionCallExprSyntax(
48+
calledExpression: ExprSyntax(DeclReferenceExprSyntax(
49+
baseName: .identifier("canImport")
50+
)),
51+
leftParen: .leftParenToken(),
52+
arguments: LabeledExprListSyntax([
53+
LabeledExprSyntax(
54+
expression: ExprSyntax(DeclReferenceExprSyntax(
55+
baseName: .identifier(moduleName)
56+
))
57+
)
58+
]),
59+
rightParen: .rightParenToken()
60+
)
61+
62+
let items = CodeBlockItemListSyntax(
63+
content.compactMap { block -> CodeBlockItemSyntax? in
64+
CodeBlockItemSyntax.Item.create(from: block.syntax).map {
65+
CodeBlockItemSyntax(item: $0, trailingTrivia: .newline)
66+
}
67+
}
68+
)
69+
70+
let clause = IfConfigClauseSyntax(
71+
poundKeyword: .poundIfToken(trailingTrivia: .space),
72+
condition: ExprSyntax(condition).with(\.trailingTrivia, .newline),
73+
elements: .statements(items)
74+
)
75+
76+
return IfConfigDeclSyntax(
77+
clauses: IfConfigClauseListSyntax([clause]),
78+
poundEndif: .poundEndifToken(leadingTrivia: .newline)
79+
)
80+
}
81+
}

Sources/SyntaxKit/Declarations/Import.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ public struct Import: CodeBlock, Sendable {
100100
rightParen = .rightParenToken()
101101

102102
let argumentList = arguments.map { argument in
103-
DeclReferenceExprSyntax(baseName: .identifier(argument))
103+
buildAttributeArgumentExpr(from: argument)
104104
}
105105

106106
argumentsSyntax = .argumentList(
107107
LabeledExprListSyntax(
108108
argumentList.enumerated().map { index, expr in
109-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
109+
var element = LabeledExprSyntax(expression: expr)
110110
if index < argumentList.count - 1 {
111111
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
112112
}
@@ -124,6 +124,7 @@ public struct Import: CodeBlock, Sendable {
124124
arguments: argumentsSyntax,
125125
rightParen: rightParen
126126
)
127+
.with(\.trailingTrivia, .space)
127128
)
128129
}
129130
return AttributeListSyntax(attributeElements)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//
2+
// Initializer.swift
3+
// SyntaxKit
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+
public import SwiftSyntax
31+
32+
/// A Swift `init` declaration.
33+
public struct Initializer: CodeBlock, Sendable {
34+
private let body: [any CodeBlock]
35+
private var accessModifier: AccessModifier?
36+
private var isAsync: Bool = false
37+
private var isThrowing: Bool = false
38+
39+
/// The SwiftSyntax representation of this initializer declaration.
40+
public var syntax: any SyntaxProtocol {
41+
var modifiers: DeclModifierListSyntax = []
42+
if let access = accessModifier {
43+
modifiers = DeclModifierListSyntax([
44+
DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space))
45+
])
46+
}
47+
48+
var effectSpecifiers: FunctionEffectSpecifiersSyntax?
49+
if isAsync || isThrowing {
50+
effectSpecifiers = FunctionEffectSpecifiersSyntax(
51+
asyncSpecifier: isAsync
52+
? .keyword(.async, leadingTrivia: .space, trailingTrivia: .space)
53+
: nil,
54+
throwsSpecifier: isThrowing ? .keyword(.throws, leadingTrivia: .space) : nil
55+
)
56+
}
57+
58+
let bodyBlock = CodeBlockSyntax(
59+
leftBrace: .leftBraceToken(leadingTrivia: .space, trailingTrivia: .newline),
60+
statements: CodeBlockItemListSyntax(
61+
body.compactMap { item in
62+
var codeBlockItem: CodeBlockItemSyntax?
63+
if let decl = item.syntax.as(DeclSyntax.self) {
64+
codeBlockItem = CodeBlockItemSyntax(item: .decl(decl))
65+
} else if let expr = item.syntax.as(ExprSyntax.self) {
66+
codeBlockItem = CodeBlockItemSyntax(item: .expr(expr))
67+
} else if let stmt = item.syntax.as(StmtSyntax.self) {
68+
codeBlockItem = CodeBlockItemSyntax(item: .stmt(stmt))
69+
}
70+
return codeBlockItem?.with(\.trailingTrivia, .newline)
71+
}
72+
),
73+
rightBrace: .rightBraceToken(leadingTrivia: .newline)
74+
)
75+
76+
return InitializerDeclSyntax(
77+
modifiers: modifiers,
78+
initKeyword: .keyword(.`init`),
79+
signature: FunctionSignatureSyntax(
80+
parameterClause: FunctionParameterClauseSyntax(
81+
leftParen: .leftParenToken(),
82+
parameters: FunctionParameterListSyntax([]),
83+
rightParen: .rightParenToken()
84+
),
85+
effectSpecifiers: effectSpecifiers
86+
),
87+
body: bodyBlock
88+
)
89+
}
90+
91+
/// Creates an `init` declaration.
92+
/// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the initializer.
93+
public init(@CodeBlockBuilderResult _ content: () throws -> [any CodeBlock]) rethrows {
94+
self.body = try content()
95+
}
96+
97+
/// Sets the access modifier for the initializer declaration.
98+
/// - Parameter access: The access modifier.
99+
/// - Returns: A copy of the initializer with the access modifier set.
100+
public func access(_ access: AccessModifier) -> Self {
101+
var copy = self
102+
copy.accessModifier = access
103+
return copy
104+
}
105+
106+
/// Marks the initializer as `throws`.
107+
/// - Returns: A copy of the initializer marked as `throws`.
108+
public func throwing() -> Self {
109+
var copy = self
110+
copy.isThrowing = true
111+
return copy
112+
}
113+
114+
/// Marks the initializer as `async`.
115+
/// - Returns: A copy of the initializer marked as `async`.
116+
public func async() -> Self {
117+
var copy = self
118+
copy.isAsync = true
119+
return copy
120+
}
121+
}

Sources/SyntaxKit/Declarations/Protocol.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,13 @@ public struct Protocol: CodeBlock, Sendable {
135135
rightParen = .rightParenToken()
136136

137137
let argumentList = arguments.map { argument in
138-
DeclReferenceExprSyntax(baseName: .identifier(argument))
138+
buildAttributeArgumentExpr(from: argument)
139139
}
140140

141141
argumentsSyntax = .argumentList(
142142
LabeledExprListSyntax(
143143
argumentList.enumerated().map { index, expr in
144-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
144+
var element = LabeledExprSyntax(expression: expr)
145145
if index < argumentList.count - 1 {
146146
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
147147
}
@@ -159,6 +159,7 @@ public struct Protocol: CodeBlock, Sendable {
159159
arguments: argumentsSyntax,
160160
rightParen: rightParen
161161
)
162+
.with(\.trailingTrivia, .newline)
162163
)
163164
}
164165
return AttributeListSyntax(attributeElements)

Sources/SyntaxKit/Declarations/Struct.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ public struct Struct: CodeBlock, Sendable {
7070
rightParen = .rightParenToken()
7171

7272
let argumentList = arguments.map { argument in
73-
DeclReferenceExprSyntax(baseName: .identifier(argument))
73+
buildAttributeArgumentExpr(from: argument)
7474
}
7575

7676
argumentsSyntax = .argumentList(
7777
LabeledExprListSyntax(
7878
argumentList.enumerated().map { index, expr in
79-
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
79+
var element = LabeledExprSyntax(expression: expr)
8080
if index < argumentList.count - 1 {
8181
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
8282
}
@@ -94,6 +94,7 @@ public struct Struct: CodeBlock, Sendable {
9494
arguments: argumentsSyntax,
9595
rightParen: rightParen
9696
)
97+
.with(\.trailingTrivia, .newline)
9798
)
9899
}
99100
return AttributeListSyntax(attributeElements)

0 commit comments

Comments
 (0)