Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
556800c
Try adding ArgumentParser to template
fpseverino Sep 28, 2026
e79f369
Add `Migrate` subcommand
fpseverino Sep 28, 2026
23a4e69
Hide Migrate subcommand
fpseverino Sep 28, 2026
06d2179
Capture all unrecognized command-line arguments
fpseverino Sep 28, 2026
3799b95
Fix warnings
fpseverino Oct 14, 2026
f42ac50
Fix CI
fpseverino Oct 14, 2026
7b63e35
Fix CI for real
fpseverino Oct 14, 2026
d517d19
Use ConfigReader instead of Environment
fpseverino Oct 14, 2026
f8c3373
Add Swift Configuration dependency and fix tests
fpseverino Oct 14, 2026
fd24989
Fix compilation error
fpseverino Oct 14, 2026
d6136f3
Fix warning
fpseverino Oct 14, 2026
47dae80
Fix database tests
fpseverino Oct 14, 2026
d8ffbe0
Fix errors
fpseverino Oct 14, 2026
96e15c7
Improve tests structure
fpseverino Oct 14, 2026
85de5ba
Code formatting
fpseverino Oct 14, 2026
64cab7f
Add linting check
fpseverino Oct 15, 2026
1b1bb0c
Move linting before testing
fpseverino Oct 15, 2026
4037aa1
Fix linting
fpseverino Oct 15, 2026
3f5bb33
Format template
fpseverino Oct 15, 2026
5075093
Actually add devcontainer to template
fpseverino Oct 15, 2026
7c43a01
Remove subcommands
fpseverino Oct 15, 2026
f74145e
Add subcommands
fpseverino Oct 15, 2026
1ecb16f
Merge branch 'vapor5' into subcommands
fpseverino Oct 16, 2026
f00ad3b
Move each command to its own file
fpseverino Oct 16, 2026
3615a76
Handle db config as secret
fpseverino Oct 16, 2026
a2fac5c
Fix secret parameter
fpseverino Oct 16, 2026
1a2b894
Don't follow Swift Configuration guide
fpseverino Oct 16, 2026
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
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ let package = Package(
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "5.0.0-beta.4"),
// 📝 Customizable logging to the console.
.package(url: "https://github.com/vapor/console-kit.git", from: "5.0.0-beta"),{{#fluent}}
.package(url: "https://github.com/vapor/console-kit.git", from: "5.0.0-beta"),
// 🖥️ Straightforward, type-safe argument parsing for Swift.
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.8.2"),{{#fluent}}
// 📖 API package for reading configuration.
.package(url: "https://github.com/apple/swift-configuration.git", from: "1.2.2"),
// 🗄 An ORM for SQL and NoSQL databases.
Expand All @@ -27,6 +29,7 @@ let package = Package(
.product(name: "Fluent{{fluent.db.module}}Driver", package: "fluent-{{fluent.db.url}}-driver"),{{/fluent}}
.product(name: "Vapor", package: "vapor"),
.product(name: "ConsoleLogger", package: "console-kit"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
swiftSettings: swiftSettings
),
Expand Down
32 changes: 32 additions & 0 deletions Sources/App/Commands/Migrate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import ArgumentParser
import Configuration
import ConsoleLogger
import Logging
import Vapor

struct Migrate: AsyncParsableCommand {
static let configuration = CommandConfiguration(abstract: "Prepare or revert your database migrations.")

@Flag var revert = false

/// Captures all unrecognized command-line arguments
/// for compatibility with other libraries that use Swift Configuration.
/// You don't need to pass them to your `ConfigReader`, it will automatically pick them up.
@Argument(parsing: .allUnrecognized, help: .private)
var other: [String] = []

mutating func run() async throws {
try await withLogger(.init(label: "vapor.logger.migrate")) { _ in
// `@Flag`s from ArgumentParser are ignored,
// they are used only for the `--help` screen.
let config = ConfigReader(providers: [
CommandLineArgumentsProvider(),
EnvironmentVariablesProvider(),
])
ConsoleLogger.bootstrapWithConfigReader(config: config)
let app = try Application(configReader: config)
print("🚧 Work in progress 🚧")
try await app.shutdown()
}
}
}
29 changes: 29 additions & 0 deletions Sources/App/Commands/Routes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ArgumentParser
import Configuration
import ConsoleLogger
import Logging
import Vapor

struct Routes: AsyncParsableCommand {
static let configuration = CommandConfiguration(abstract: "Displays all registered routes.")

/// Captures all unrecognized command-line arguments
/// for compatibility with other libraries that use Swift Configuration.
/// You don't need to pass them to your `ConfigReader`, it will automatically pick them up.
@Argument(parsing: .allUnrecognized, help: .private)
var other: [String] = []

mutating func run() async throws {
try await withLogger(.init(label: "vapor.logger.routes")) { _ in
let config = ConfigReader(providers: [
CommandLineArgumentsProvider(),
EnvironmentVariablesProvider(),
])
ConsoleLogger.bootstrapWithConfigReader(config: config)
let app = try Application(configReader: config)
try await configure(app)
print(app.routesASCIITable())
try await app.shutdown()
}
}
}
49 changes: 49 additions & 0 deletions Sources/App/Commands/Serve.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ArgumentParser
import Configuration
import ConsoleLogger
import Logging
import Vapor

struct Serve: AsyncParsableCommand {
static let configuration = CommandConfiguration(abstract: "Begins serving the app over HTTP.")

@Option(help: "The hostname the server will run on.")
var hostname: String?

@Option(help: "The port the server will run on.")
var port: Int?

@Option(help: "Convenience for setting hostname and port together, in the format `hostname:port`.")
var bind: String?

@Option(help: "The path for the Unix domain socket file the server will bind to.")
var socketPath: String?

/// Captures all unrecognized command-line arguments
/// for compatibility with other libraries that use Swift Configuration.
/// You don't need to pass them to your `ConfigReader`, it will automatically pick them up.
@Argument(parsing: .allUnrecognized, help: .private)
var other: [String] = []

mutating func run() async throws {
try await withLogger(.init(label: "vapor.logger")) { _ in
// `@Option`s from ArgumentParser are ignored,
// they are used only for the `--help` screen.
let config = ConfigReader(providers: [
CommandLineArgumentsProvider(),
EnvironmentVariablesProvider(),
])
ConsoleLogger.bootstrapWithConfigReader(config: config)

let app = try Application(configReader: config)
do {
try await configure(app)
try await app.run()
try await app.shutdown()
} catch {
try? await app.shutdown()
throw error
}
}
}
}
20 changes: 10 additions & 10 deletions Sources/App/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ func configure(_ app: Application) async throws {
databases.use(
DatabaseConfigurationFactory.postgres(
configuration: .init(
hostname: databaseConfig.string(forKey: "host", default: "localhost"),
port: databaseConfig.int(forKey: "port", default: SQLPostgresConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", default: "vapor_username"),
password: databaseConfig.string(forKey: "password", default: "vapor_password"),
database: databaseConfig.string(forKey: "name", default: "vapor_database"),
hostname: databaseConfig.string(forKey: "host", isSecret: true, default: "localhost"),
port: databaseConfig.int(forKey: "port", isSecret: true, default: SQLPostgresConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", isSecret: true, default: "vapor_username"),
password: databaseConfig.string(forKey: "password", isSecret: true, default: "vapor_password"),
database: databaseConfig.string(forKey: "name", isSecret: true, default: "vapor_database"),
tls: .prefer(try .init(configuration: .clientDefault))
)
),
Expand All @@ -29,11 +29,11 @@ func configure(_ app: Application) async throws {

databases.use(
DatabaseConfigurationFactory.mysql(
hostname: databaseConfig.string(forKey: "host", default: "localhost"),
port: databaseConfig.int(forKey: "port", default: MySQLConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", default: "vapor_username"),
password: databaseConfig.string(forKey: "password", default: "vapor_password"),
database: databaseConfig.string(forKey: "name", default: "vapor_database")
hostname: databaseConfig.string(forKey: "host", isSecret: true, default: "localhost"),
port: databaseConfig.int(forKey: "port", isSecret: true, default: MySQLConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", isSecret: true, default: "vapor_username"),
password: databaseConfig.string(forKey: "password", isSecret: true, default: "vapor_password"),
database: databaseConfig.string(forKey: "name", isSecret: true, default: "vapor_database")
),
as: .mysql
){{/fluent.db.is_mysql}}{{#fluent.db.is_sqlite}}databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite){{/fluent.db.is_sqlite}}
Expand Down
31 changes: 11 additions & 20 deletions Sources/App/entrypoint.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import ArgumentParser
import Configuration
import ConsoleLogger
import Logging
import Vapor

@main
enum Entrypoint {
static func main() async throws {
try await withLogger(.init(label: "vapor5.logger")) { _ in
let config = ConfigReader(providers: [
CommandLineArgumentsProvider(),
EnvironmentVariablesProvider(),
])
ConsoleLogger.bootstrapWithConfigReader(config: config)

let app = try Application(configReader: config)
do {
try await configure(app)
try await app.run()
try await app.shutdown()
} catch {
try? await app.shutdown()
throw error
}
}
}
struct Entrypoint: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "{{name}}",
subcommands: [
Serve.self,
Routes.self,{{#fluent}}
Migrate.self,{{/fluent}}
],
defaultSubcommand: Serve.self
)
}
20 changes: 10 additions & 10 deletions Tests/AppTests/AppTrait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ struct AppTrait: TestTrait, SuiteTrait, TestScoping {
databases.use(
DatabaseConfigurationFactory.postgres(
configuration: .init(
hostname: databaseConfig.string(forKey: "host", default: "localhost"),
port: databaseConfig.int(forKey: "port", default: SQLPostgresConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", default: "vapor_username"),
password: databaseConfig.string(forKey: "password", default: "vapor_password"),
database: databaseConfig.string(forKey: "name", default: "vapor_database"),
hostname: databaseConfig.string(forKey: "host", isSecret: true, default: "localhost"),
port: databaseConfig.int(forKey: "port", isSecret: true, default: SQLPostgresConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", isSecret: true, default: "vapor_username"),
password: databaseConfig.string(forKey: "password", isSecret: true, default: "vapor_password"),
database: databaseConfig.string(forKey: "name", isSecret: true, default: "vapor_database"),
tls: .prefer(try .init(configuration: .clientDefault))
)
),
as: .psql
){{/fluent.db.is_postgres}}{{#fluent.db.is_mysql}}let databaseConfig = application.configReader.scoped(to: "database")
databases.use(
DatabaseConfigurationFactory.mysql(
hostname: databaseConfig.string(forKey: "host", default: "localhost"),
port: databaseConfig.int(forKey: "port", default: MySQLConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", default: "vapor_username"),
password: databaseConfig.string(forKey: "password", default: "vapor_password"),
database: databaseConfig.string(forKey: "name", default: "vapor_database")
hostname: databaseConfig.string(forKey: "host", isSecret: true, default: "localhost"),
port: databaseConfig.int(forKey: "port", isSecret: true, default: MySQLConfiguration.ianaPortNumber),
username: databaseConfig.string(forKey: "username", isSecret: true, default: "vapor_username"),
password: databaseConfig.string(forKey: "password", isSecret: true, default: "vapor_password"),
database: databaseConfig.string(forKey: "name", isSecret: true, default: "vapor_database")
),
as: .mysql
){{/fluent.db.is_mysql}}{{#fluent.db.is_sqlite}}databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite){{/fluent.db.is_sqlite}}
Expand Down
6 changes: 6 additions & 0 deletions manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ files:
if: fluent
files:
- Databases+Vapor.swift
- folder: Commands
files:
- Serve.swift
- Routes.swift
- file: Migrate.swift
if: fluent
- folder: Tests
files:
- folder: AppTests
Expand Down
Loading