feat: Sets up ci workflows
This commit is contained in:
20
.gitea/workflows/ci.yml
Normal file
20
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu:
|
||||||
|
name: Ubuntu
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup just.
|
||||||
|
uses: https://git.housh.dev/actions/setup-just.git@v1
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Setup buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Run tests.
|
||||||
|
run: just test-docker
|
||||||
63
.gitea/workflows/release.yml
Normal file
63
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: Create and publish a Docker image
|
||||||
|
|
||||||
|
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['main']
|
||||||
|
tags:
|
||||||
|
- '*.*.*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Defines two custom environment variables for the workflow. These are used for the Container registry domain,
|
||||||
|
# and a name for the Docker image that this workflow builds.
|
||||||
|
env:
|
||||||
|
REGISTRY: git.housh.dev
|
||||||
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
|
||||||
|
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||||
|
jobs:
|
||||||
|
build-and-push-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
# Uses the `docker/login-action` action to log in to the Container registry registry using the account
|
||||||
|
# and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.CONTAINER_TOKEN }}
|
||||||
|
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels
|
||||||
|
# that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a
|
||||||
|
# subsequent step. The `images` value provides the base name for the tags and labels.
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=sha
|
||||||
|
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If
|
||||||
|
# the build succeeds, it pushes the image to GitHub Packages. It uses the `context` parameter to define the build's context
|
||||||
|
# as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)"
|
||||||
|
# in the README of the `docker/build-push-action` repository.
|
||||||
|
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// import Dependencies
|
|
||||||
// import Foundation
|
|
||||||
//
|
|
||||||
// public extension DependencyValues {
|
|
||||||
// var dateFormatter: DateFormatter {
|
|
||||||
// get { self[DateFormatter.self] }
|
|
||||||
// set { self[DateFormatter.self] = newValue }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #if hasFeature(RetroactiveAttribute)
|
|
||||||
// extension DateFormatter: @retroactive DependencyKey {
|
|
||||||
//
|
|
||||||
// public static var liveValue: DateFormatter {
|
|
||||||
// let formatter = DateFormatter()
|
|
||||||
// formatter.dateStyle = .short
|
|
||||||
// return formatter
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// #else
|
|
||||||
// extension DateFormatter: DependencyKey {
|
|
||||||
// public static var liveValue: DateFormatter {
|
|
||||||
// let formatter = DateFormatter()
|
|
||||||
// formatter.dateStyle = .short
|
|
||||||
// return formatter
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
@@ -127,10 +127,10 @@ final class EmployeeModel: Model, @unchecked Sendable {
|
|||||||
@Field(key: "is_active")
|
@Field(key: "is_active")
|
||||||
var active: Bool
|
var active: Bool
|
||||||
|
|
||||||
@Timestamp(key: "created_at", on: .create)
|
@Timestamp(key: "created_at", on: .create, format: .iso8601)
|
||||||
var createdAt: Date?
|
var createdAt: Date?
|
||||||
|
|
||||||
@Timestamp(key: "updated_at", on: .update)
|
@Timestamp(key: "updated_at", on: .update, format: .iso8601)
|
||||||
var updatedAt: Date?
|
var updatedAt: Date?
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|||||||
@@ -142,10 +142,10 @@ final class PurchaseOrderModel: Model, Codable, @unchecked Sendable {
|
|||||||
@Parent(key: "vendor_branch_id")
|
@Parent(key: "vendor_branch_id")
|
||||||
var vendorBranch: VendorBranchModel
|
var vendorBranch: VendorBranchModel
|
||||||
|
|
||||||
@Timestamp(key: "created_at", on: .create)
|
@Timestamp(key: "created_at", on: .create, format: .iso8601)
|
||||||
var createdAt: Date?
|
var createdAt: Date?
|
||||||
|
|
||||||
@Timestamp(key: "updated_at", on: .update)
|
@Timestamp(key: "updated_at", on: .update, format: .iso8601)
|
||||||
var updatedAt: Date?
|
var updatedAt: Date?
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|||||||
@@ -182,10 +182,10 @@ final class UserModel: Model, @unchecked Sendable {
|
|||||||
@Field(key: "password_hash")
|
@Field(key: "password_hash")
|
||||||
var passwordHash: String
|
var passwordHash: String
|
||||||
|
|
||||||
@Timestamp(key: "created_at", on: .create)
|
@Timestamp(key: "created_at", on: .create, format: .iso8601)
|
||||||
var createdAt: Date?
|
var createdAt: Date?
|
||||||
|
|
||||||
@Timestamp(key: "updated_at", on: .update)
|
@Timestamp(key: "updated_at", on: .update, format: .iso8601)
|
||||||
var updatedAt: Date?
|
var updatedAt: Date?
|
||||||
|
|
||||||
@OptionalChild(for: \.$user)
|
@OptionalChild(for: \.$user)
|
||||||
|
|||||||
@@ -125,10 +125,10 @@ final class VendorBranchModel: Model, @unchecked Sendable {
|
|||||||
@Field(key: .string(FieldKey.name))
|
@Field(key: .string(FieldKey.name))
|
||||||
var name: String
|
var name: String
|
||||||
|
|
||||||
@Timestamp(key: .string(FieldKey.createdAt), on: .create)
|
@Timestamp(key: .string(FieldKey.createdAt), on: .create, format: .iso8601)
|
||||||
var createdAt: Date?
|
var createdAt: Date?
|
||||||
|
|
||||||
@Timestamp(key: .string(FieldKey.updatedAt), on: .update)
|
@Timestamp(key: .string(FieldKey.updatedAt), on: .update, format: .iso8601)
|
||||||
var updatedAt: Date?
|
var updatedAt: Date?
|
||||||
|
|
||||||
@Parent(key: .string(FieldKey.vendorID))
|
@Parent(key: .string(FieldKey.vendorID))
|
||||||
|
|||||||
@@ -109,10 +109,10 @@ final class VendorModel: Model, @unchecked Sendable {
|
|||||||
@Field(key: "name")
|
@Field(key: "name")
|
||||||
var name: String
|
var name: String
|
||||||
|
|
||||||
@Timestamp(key: "created_at", on: .create)
|
@Timestamp(key: "created_at", on: .create, format: .iso8601)
|
||||||
var createdAt: Date?
|
var createdAt: Date?
|
||||||
|
|
||||||
@Timestamp(key: "updated_at", on: .update)
|
@Timestamp(key: "updated_at", on: .update, format: .iso8601)
|
||||||
var updatedAt: Date?
|
var updatedAt: Date?
|
||||||
|
|
||||||
@Children(for: \.$vendor)
|
@Children(for: \.$vendor)
|
||||||
|
|||||||
@@ -1,28 +1,53 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public extension DependencyValues {
|
public extension DependencyValues {
|
||||||
var dateFormatter: DateFormatter {
|
var dateFormatter: DateFormatterKey {
|
||||||
get { self[DateFormatter.self] }
|
get { self[DateFormatterKey.self] }
|
||||||
set { self[DateFormatter.self] = newValue }
|
set { self[DateFormatterKey.self] = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if hasFeature(RetroactiveAttribute)
|
@DependencyClient
|
||||||
extension DateFormatter: @retroactive DependencyKey {
|
public struct DateFormatterKey: Sendable {
|
||||||
|
public var string: @Sendable (Date?) -> String = { _ in "N/A" }
|
||||||
|
}
|
||||||
|
|
||||||
public static var liveValue: DateFormatter {
|
extension DateFormatterKey: DependencyKey {
|
||||||
let formatter = DateFormatter()
|
public static let testValue = Self()
|
||||||
formatter.dateStyle = .short
|
|
||||||
return formatter
|
public static var liveValue: DateFormatterKey {
|
||||||
}
|
.init(
|
||||||
|
string: { date in
|
||||||
|
guard let date else { return "N/A" }
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.timeZone = TimeZone(identifier: "UTC")
|
||||||
|
formatter.formatOptions = [.withFullDate]
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
#else
|
}
|
||||||
extension DateFormatter: DependencyKey {
|
|
||||||
public static var liveValue: DateFormatter {
|
// #if hasFeature(RetroactiveAttribute)
|
||||||
let formatter = DateFormatter()
|
// extension DateFormatter: @retroactive DependencyKey {
|
||||||
formatter.dateStyle = .short
|
//
|
||||||
return formatter
|
// public static var liveValue: DateFormatter {
|
||||||
}
|
// let formatter = DateFormatter()
|
||||||
}
|
// formatter.dateStyle = .short
|
||||||
#endif
|
// return formatter
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// #else
|
||||||
|
// extension DateFormatter: DependencyKey {
|
||||||
|
// public static var liveValue: DateFormatter {
|
||||||
|
// let formatter = ISO8601DateFormatter()
|
||||||
|
// formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
// // formatter.dateFormat = "mm/dd/yyyy"
|
||||||
|
// formatter.formatOptions = [.withFullDate]
|
||||||
|
// formatter.timeZone = TimeZone(identifier: "UTC")
|
||||||
|
// return formatter
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// #endif
|
||||||
|
|||||||
@@ -104,11 +104,11 @@ struct PurchaseOrderForm: HTML, Sendable {
|
|||||||
if let purchaseOrder, let createdAt = purchaseOrder.createdAt {
|
if let purchaseOrder, let createdAt = purchaseOrder.createdAt {
|
||||||
div(.class("row")) {
|
div(.class("row")) {
|
||||||
label(.class("label col-2")) { "Created:" }
|
label(.class("label col-2")) { "Created:" }
|
||||||
h3(.class("col-2")) { dateFormatter.string(from: createdAt) }
|
h3(.class("col-2")) { dateFormatter.string(createdAt) }
|
||||||
if let updatedAt = purchaseOrder.updatedAt {
|
if let updatedAt = purchaseOrder.updatedAt {
|
||||||
div(.class("col-1")) {}
|
div(.class("col-1")) {}
|
||||||
label(.class("label col-2")) { "Updated:" }
|
label(.class("label col-2")) { "Updated:" }
|
||||||
h3(.class("col-2")) { dateFormatter.string(from: updatedAt) }
|
h3(.class("col-2")) { dateFormatter.string(updatedAt) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ struct UserDetail: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
div(.class("row")) {
|
div(.class("row")) {
|
||||||
span(.class("label col-2")) { "Created:" }
|
span(.class("label col-2")) { "Created:" }
|
||||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.createdAt) }
|
span(.class("date col-4")) { dateFormatter.string(user.createdAt) }
|
||||||
span(.class("label col-2")) { "Updated:" }
|
span(.class("label col-2")) { "Updated:" }
|
||||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.updatedAt) }
|
span(.class("date col-4")) { dateFormatter.string(user.updatedAt) }
|
||||||
}
|
}
|
||||||
div(.class("btn-row user-buttons")) {
|
div(.class("btn-row user-buttons")) {
|
||||||
button(
|
button(
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct ViewControllerTests {
|
|||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
$0.database.employees = .mock
|
$0.database.employees = .mock
|
||||||
|
$0.dateFormatter = .mock
|
||||||
} operation: {
|
} operation: {
|
||||||
@Dependency(\.viewController) var viewController
|
@Dependency(\.viewController) var viewController
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
@@ -54,6 +55,7 @@ struct ViewControllerTests {
|
|||||||
try await withSnapshotTesting(record: record) {
|
try await withSnapshotTesting(record: record) {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
|
$0.dateFormatter = .mock
|
||||||
} operation: {
|
} operation: {
|
||||||
@Dependency(\.viewController) var viewController
|
@Dependency(\.viewController) var viewController
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ struct ViewControllerTests {
|
|||||||
func purchaseOrderViews() async throws {
|
func purchaseOrderViews() async throws {
|
||||||
try await withSnapshotTesting(record: record) {
|
try await withSnapshotTesting(record: record) {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.dateFormatter = .liveValue
|
$0.dateFormatter = .mock
|
||||||
$0.database.purchaseOrders = .mock
|
$0.database.purchaseOrders = .mock
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
@@ -103,7 +105,7 @@ struct ViewControllerTests {
|
|||||||
func userViews() async throws {
|
func userViews() async throws {
|
||||||
try await withSnapshotTesting(record: record) {
|
try await withSnapshotTesting(record: record) {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.dateFormatter = .liveValue
|
$0.dateFormatter = .mock
|
||||||
$0.database.users = .mock
|
$0.database.users = .mock
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
@@ -132,7 +134,7 @@ struct ViewControllerTests {
|
|||||||
func vendorViews() async throws {
|
func vendorViews() async throws {
|
||||||
try await withSnapshotTesting(record: record) {
|
try await withSnapshotTesting(record: record) {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.dateFormatter = .liveValue
|
$0.dateFormatter = .mock
|
||||||
$0.database.vendors = .mock
|
$0.database.vendors = .mock
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
@@ -161,7 +163,7 @@ struct ViewControllerTests {
|
|||||||
func vendorBranchViews() async throws {
|
func vendorBranchViews() async throws {
|
||||||
try await withSnapshotTesting(record: record) {
|
try await withSnapshotTesting(record: record) {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.dateFormatter = .liveValue
|
$0.dateFormatter = .mock
|
||||||
$0.database.vendors = .mock
|
$0.database.vendors = .mock
|
||||||
$0.database.vendorBranches = .mock
|
$0.database.vendorBranches = .mock
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
@@ -271,7 +273,14 @@ extension DatabaseClient.VendorBranches {
|
|||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
static var mock: Self {
|
static var mock: Self {
|
||||||
Date(timeIntervalSince1970: 1_234_567_890)
|
let formatter = ISO8601DateFormatter()
|
||||||
|
return formatter.date(from: "2025-01-31T02:22:40Z")!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DateFormatterKey {
|
||||||
|
static var mock: Self {
|
||||||
|
.init(string: { _ in "01/31/2025" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,10 +288,10 @@ extension Employee {
|
|||||||
static var mock: Self {
|
static var mock: Self {
|
||||||
Employee(
|
Employee(
|
||||||
id: UUID(0),
|
id: UUID(0),
|
||||||
createdAt: Date(timeIntervalSince1970: 1_234_567_890),
|
createdAt: .mock,
|
||||||
firstName: "Testy",
|
firstName: "Testy",
|
||||||
lastName: "McTestface",
|
lastName: "McTestface",
|
||||||
updatedAt: Date(timeIntervalSince1970: 1_234_567_890)
|
updatedAt: .mock
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,10 +305,10 @@ extension Employee.Create {
|
|||||||
@Dependency(\.date.now) var now
|
@Dependency(\.date.now) var now
|
||||||
return .init(
|
return .init(
|
||||||
id: UUID(0),
|
id: UUID(0),
|
||||||
createdAt: Date(timeIntervalSince1970: 1_234_567_890),
|
createdAt: .mock,
|
||||||
firstName: firstName,
|
firstName: firstName,
|
||||||
lastName: lastName,
|
lastName: lastName,
|
||||||
updatedAt: Date(timeIntervalSince1970: 1_234_567_890)
|
updatedAt: .mock
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="label col-2">Created:</label>
|
<label class="label col-2">Created:</label>
|
||||||
<h3 class="col-2">2/13/09</h3>
|
<h3 class="col-2">01/31/2025</h3>
|
||||||
<div class="col-1"></div>
|
<div class="col-1"></div>
|
||||||
Updated:<label class="label col-2"></label>
|
Updated:<label class="label col-2"></label>
|
||||||
<h3 class="col-2">2/13/09</h3>
|
<h3 class="col-2">01/31/2025</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-primary" type="submit">Update</button>
|
<button class="btn-primary" type="submit">Update</button>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Email:<label for="email" class="col-2"><span class="label"></span></label>
|
Email:<label for="email" class="col-2"><span class="label"></span></label>
|
||||||
<input class="col-4" type="email" id="email" name="email" value="test@example.com" required>
|
<input class="col-4" type="email" id="email" name="email" value="test@example.com" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="row"><span class="label col-2">Created:</span><span class="date col-4"></span><span class="label col-2">Updated:</span><span class="date col-4"></span></div>
|
<div class="row"><span class="label col-2">Created:</span><span class="date col-4">01/31/2025</span><span class="label col-2">Updated:</span><span class="date col-4">01/31/2025</span></div>
|
||||||
<div class="btn-row user-buttons">
|
<div class="btn-row user-buttons">
|
||||||
<button type="submit" class="btn-secondary">Update</button>
|
<button type="submit" class="btn-secondary">Update</button>
|
||||||
<button class="danger" hx-delete="/api/v1/users/00000000-0000-0000-0000-000000000000" hx-trigger="click" hx-swap="outerHTML" hx-target="#user-00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this user?" hx-on::after-request="toggleContent('float'); window.location.href='/users';">Delete</button>
|
<button class="danger" hx-delete="/api/v1/users/00000000-0000-0000-0000-000000000000" hx-trigger="click" hx-swap="outerHTML" hx-target="#user-00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this user?" hx-on::after-request="toggleContent('float'); window.location.href='/users';">Delete</button>
|
||||||
|
|||||||
32
dev.Dockerfile
Normal file
32
dev.Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM swift:6.0-noble
|
||||||
|
|
||||||
|
# Make sure all system packages are up to date, and install only essential packages.
|
||||||
|
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||||
|
&& apt-get -q update \
|
||||||
|
&& apt-get -q dist-upgrade -y \
|
||||||
|
&& apt-get -q install -y \
|
||||||
|
libjemalloc2 \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
|
||||||
|
libcurl4 \
|
||||||
|
# If your app or its dependencies import FoundationXML, also install `libxml2`.
|
||||||
|
# libxml2 \
|
||||||
|
sqlite3 \
|
||||||
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set up a build area
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# First just resolve dependencies.
|
||||||
|
# This creates a cached layer that can be reused
|
||||||
|
# as long as your Package.swift/Package.resolved
|
||||||
|
# files do not change.
|
||||||
|
COPY ./Package.* ./
|
||||||
|
RUN swift package resolve \
|
||||||
|
$([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)
|
||||||
|
|
||||||
|
# Copy entire repo into container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["swift", "test"]
|
||||||
Reference in New Issue
Block a user