From a3df5c788074f3fa747a796637ab73ea524ce78b Mon Sep 17 00:00:00 2001 From: Petr Pavlik Date: Sat, 28 Sep 2024 17:26:16 +0100 Subject: [PATCH] Initial commit --- .dockerignore | 2 + .github/workflows/ci.yml | 26 ++++ .gitignore | 11 ++ .vscode/hummingbird.code-snippets | 45 +++++++ Dockerfile | 86 ++++++++++++ LICENSE | 201 ++++++++++++++++++++++++++++ Package.swift | 32 +++++ README.md | 31 +++++ Sources/App/App.swift | 27 ++++ Sources/App/Application+build.swift | 54 ++++++++ Tests/AppTests/AppTests.swift | 24 ++++ configure.sh | 152 +++++++++++++++++++++ scripts/download.sh | 11 ++ 13 files changed, 702 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .vscode/hummingbird.code-snippets create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/App/App.swift create mode 100644 Sources/App/Application+build.swift create mode 100644 Tests/AppTests/AppTests.swift create mode 100755 configure.sh create mode 100755 scripts/download.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2fb3343 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.build +.git \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9501259 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: + - main + paths: + - '**.swift' + - '**.yml' + pull_request: + workflow_dispatch: + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + image: + - 'swift:6.0' + container: + image: ${{ matrix.image }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Test + run: swift test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa95c56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +/.build +/.swiftpm +/.devContainer +/Packages +/*.xcodeproj +xcuserdata/ +/.vscode/* +{{#HB_VSCODE_SNIPPETS}} +!/.vscode/hummingbird.code-snippets +{{/HB_VSCODE_SNIPPETS}} diff --git a/.vscode/hummingbird.code-snippets b/.vscode/hummingbird.code-snippets new file mode 100644 index 0000000..6cbac60 --- /dev/null +++ b/.vscode/hummingbird.code-snippets @@ -0,0 +1,45 @@ +{{#HB_VSCODE_SNIPPETS}} +{ + "Endpoint": { + "prefix": "endpoint", + "body": [ + "@Sendable func ${1:functionName}(request: Request, context: some RequestContext) async throws -> ${2:returnValue} {", + "\t${3:statements}", + "}" + ], + "description": "Hummingbird: Endpoint function" + }, + "RouterMiddleware": { + "prefix": "middleware", + "body": [ + "struct ${1:Name}Middleware: RouterMiddleware {", + "\tfunc handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response {", + "\t\t${2:try await next(request, context)}", + "\t}", + "}" + ], + "description": "Hummingbird: RouterMiddleware" + }, + "try context.parameters.require": { + "prefix": "parameter", + "body": [ + "try context.parameters.require(\"${2:parameterName}\")" + ], + "description": "Hummingbird: Extract parameter from request path" + }, + "try await request.decode": { + "prefix": "decode", + "body": [ + "try await request.decode(as: ${1:Type}.self, context: context)" + ], + "description": "Hummingbird: Decode request" + }, + "throw HTTPError": { + "prefix": "httperror", + "body": [ + "throw HTTPError(${code})" + ], + "description": "Hummingbird: Decode request" + } +} +{{/HB_VSCODE_SNIPPETS}} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9bd4eb1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,86 @@ +# ================================ +# Build image +# ================================ +FROM swift:6.0-noble as build + +# Install OS updates +RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ + && apt-get -q update \ + && apt-get -q dist-upgrade -y \ + && apt-get install -y libjemalloc-dev \ + && rm -rf /var/lib/apt/lists/* + +# Set up a build area +WORKDIR /build + +# 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 + +# Copy entire repo into container +COPY . . + +# Build everything, with optimizations, with static linking, and using jemalloc +RUN swift build -c release \ + --static-swift-stdlib \ + -Xlinker -ljemalloc + +# Switch to the staging area +WORKDIR /staging + +# Copy main executable to staging area +RUN cp "$(swift build --package-path /build -c release --show-bin-path)/{{HB_EXECUTABLE_NAME}}" ./ + +# Copy static swift backtracer binary to staging area +RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./ + +# Copy resources bundled by SPM to staging area +RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \; + +# Copy any resouces from the public directory and views directory if the directories exist +# Ensure that by default, neither the directory nor any of its contents are writable. +RUN [ -d /build/public ] && { mv /build/public ./public && chmod -R a-w ./public; } || true + +# ================================ +# Run image +# ================================ +FROM ubuntu: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 \ + && rm -r /var/lib/apt/lists/* + +# Create a hummingbird user and group with /app as its home directory +RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app hummingbird + +# Switch to the new home directory +WORKDIR /app + +# Copy built executable and any staged resources from builder +COPY --from=build --chown=hummingbird:hummingbird /staging /app + +# Provide configuration needed by the built-in crash reporter and some sensible default behaviors. +ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static + +# Ensure all further commands run as the hummingbird user +USER hummingbird:hummingbird + +# Let Docker bind to port 8080 +EXPOSE 8080 + +# Start the Hummingbird service when the image is run, default to listening on 8080 in production environment +ENTRYPOINT ["./{{HB_EXECUTABLE_NAME}}"] +CMD ["--hostname", "0.0.0.0", "--port", "8080"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bea052c --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Adam Fowler + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..a20c638 --- /dev/null +++ b/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version:6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "{{HB_PACKAGE_NAME}}", + platforms: [.macOS(.v14), .iOS(.v17), .tvOS(.v17)], + products: [ + .executable(name: "{{HB_EXECUTABLE_NAME}}", targets: ["{{HB_EXECUTABLE_NAME}}"]), + ], + dependencies: [ + .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0") + ], + targets: [ + .executableTarget(name: "{{HB_EXECUTABLE_NAME}}", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Hummingbird", package: "hummingbird"), + ], + path: "Sources/App" + ), + .testTarget(name: "{{HB_EXECUTABLE_NAME}}Tests", + dependencies: [ + .byName(name: "{{HB_EXECUTABLE_NAME}}"), + .product(name: "HummingbirdTesting", package: "hummingbird") + ], + path: "Tests/AppTests" + ) + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..53baae1 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +

+ + + + +

+ +# Hummingbird Template + +Template for Hummingbird App + +Either click on `Use this template` or run the following to clone locally. +```bash +curl -L https://raw.githubusercontent.com/hummingbird-project/template/main/scripts/download.sh | bash -s +``` + +Then enter the folder created, run `./configure.sh` and follow the instructions. + +## Alternative method + +Clone this repository locally + +```bash +git clone https://github.com/hummingbird-project/template +``` + +And then call the configure script with a path argument. This will create a new project at the specified path + +```bash +./configure.sh +``` diff --git a/Sources/App/App.swift b/Sources/App/App.swift new file mode 100644 index 0000000..eb8581e --- /dev/null +++ b/Sources/App/App.swift @@ -0,0 +1,27 @@ +import ArgumentParser +import Hummingbird +import Logging + +@main +struct AppCommand: AsyncParsableCommand, AppArguments { + @Option(name: .shortAndLong) + var hostname: String = "127.0.0.1" + + @Option(name: .shortAndLong) + var port: Int = 8080 + + @Option(name: .shortAndLong) + var logLevel: Logger.Level? + + func run() async throws { + let app = try await buildApplication(self) + try await app.runService() + } +} + +/// Extend `Logger.Level` so it can be used as an argument +#if hasFeature(RetroactiveAttribute) + extension Logger.Level: @retroactive ExpressibleByArgument {} +#else + extension Logger.Level: ExpressibleByArgument {} +#endif diff --git a/Sources/App/Application+build.swift b/Sources/App/Application+build.swift new file mode 100644 index 0000000..404dce4 --- /dev/null +++ b/Sources/App/Application+build.swift @@ -0,0 +1,54 @@ +import Hummingbird +import Logging + +/// Application arguments protocol. We use a protocol so we can call +/// `buildApplication` inside Tests as well as in the App executable. +/// Any variables added here also have to be added to `App` in App.swift and +/// `TestArguments` in AppTest.swift +public protocol AppArguments { + var hostname: String { get } + var port: Int { get } + var logLevel: Logger.Level? { get } +} + +// Request context used by application +typealias AppRequestContext = BasicRequestContext + +/// Build application +/// - Parameter arguments: application arguments +public func buildApplication(_ arguments: some AppArguments) async throws -> some ApplicationProtocol { + let environment = Environment() + let logger = { + var logger = Logger(label: "{{HB_PACKAGE_NAME}}") + logger.logLevel = + arguments.logLevel ?? + environment.get("LOG_LEVEL").flatMap { Logger.Level(rawValue: $0) } ?? + .info + return logger + }() + let router = buildRouter() + let app = Application( + router: router, + configuration: .init( + address: .hostname(arguments.hostname, port: arguments.port), + serverName: "{{HB_PACKAGE_NAME}}" + ), + logger: logger + ) + return app +} + +/// Build router +func buildRouter() -> Router { + let router = Router(context: AppRequestContext.self) + // Add middleware + router.addMiddleware { + // logging middleware + LogRequestsMiddleware(.info) + } + // Add default endpoint + router.get("/") { _,_ in + return "Hello!" + } + return router +} diff --git a/Tests/AppTests/AppTests.swift b/Tests/AppTests/AppTests.swift new file mode 100644 index 0000000..56df622 --- /dev/null +++ b/Tests/AppTests/AppTests.swift @@ -0,0 +1,24 @@ +import Hummingbird +import HummingbirdTesting +import Logging +import XCTest + +@testable import {{HB_EXECUTABLE_NAME}} + +final class AppTests: XCTestCase { + struct TestArguments: AppArguments { + let hostname = "127.0.0.1" + let port = 0 + let logLevel: Logger.Level? = .trace + } + + func testApp() async throws { + let args = TestArguments() + let app = try await buildApplication(args) + try await app.test(.router) { client in + try await client.execute(uri: "/", method: .get) { response in + XCTAssertEqual(response.body, ByteBuffer(string: "Hello!")) + } + } + } +} diff --git a/configure.sh b/configure.sh new file mode 100755 index 0000000..163c3d5 --- /dev/null +++ b/configure.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +PWD=$(pwd) +TEMPLATE_FOLDER=$(dirname $0) +RELATIVE_TARGET_FOLDER=${1:-} +TARGET_FOLDER=$(cd "$(dirname "$RELATIVE_TARGET_FOLDER")"; pwd -P)/$(basename "$RELATIVE_TARGET_FOLDER") +BASE_FOLDER=$(basename "$TARGET_FOLDER") +CLEAN_BASE_FOLDER=$(echo "$BASE_FOLDER" | sed -e 's/[^a-zA-Z0-9_]/_/g') + +TEMP_FOLDER=$(mktemp -d) +MO="$TEMP_FOLDER"/mo + +cleanup() +{ + rm -rf "$TEMP_FOLDER" +} + +# Download MO bash mustache renderer +download_mo() +{ + # v3.0.6 of mo is broken + curl -sSL https://raw.githubusercontent.com/tests-always-included/mo/3.0.5/mo -o "$MO" + chmod a+x "$MO" +} + +read_input_with_default () { + echo -n "[$1] > " + read -r READ_INPUT_RETURN + + if [ -z "$READ_INPUT_RETURN" ]; then + READ_INPUT_RETURN="$1" + fi +} + +yn_prompt () { + if [[ "$1" == "yes" ]]; then + echo "Y/n" + else + echo "y/N" + fi +} + +read_yes_no () { + while [[ true ]]; do + echo -n "[$(yn_prompt $1)] > " + read -r READ_INPUT_RETURN + + case "$READ_INPUT_RETURN" in + "y" | "Y") + READ_INPUT_RETURN="yes" + return + ;; + + "n" | "N") + READ_INPUT_RETURN="no" + return + ;; + + "") + READ_INPUT_RETURN="$1" + return + ;; + + *) + echo "Please input either \"y\" or \"n\", or press ENTER to use the default." + ;; + esac + done +} + +run_mustache() +{ + FILES=$1 + TEMP_FILE="$TEMP_FOLDER"/tempfile + for FILE in $FILES; do + $MO "$FILE" > "$TEMP_FILE" + # delete file if it is empty or only contains spaces + if ! grep -q '[^[:space:]]' "$TEMP_FILE" ; then + echo "Remove $FILE" + rm "$TEMP_FILE" + rm "$TARGET_FOLDER/$FILE" + else + echo "Copy $FILE" + mv -f "$TEMP_FILE" "$TARGET_FOLDER/$FILE" + fi + done +} + +exitWithError() +{ + echo "Error: $1" + exit 1 +} + +check_valid() { + if [[ "$HB_PACKAGE_NAME" =~ [^a-zA-Z0-9_] ]]; then + exitWithError "Invalid package name: $HB_PACKAGE_NAME" + fi +} +trap cleanup EXIT $? + +echo "Configuring your Hummingbird project" + +# Download Bash Mustache +download_mo + +if [[ "$TARGET_FOLDER" != "$PWD" ]]; then + echo "Outputting to $TARGET_FOLDER" + mkdir -p "$TARGET_FOLDER"/Sources/App + mkdir -p "$TARGET_FOLDER"/Tests/AppTests + mkdir -p "$TARGET_FOLDER"/.vscode +else + echo "Outputting to current folder" +fi + +echo -n "Enter your package name: " +read_input_with_default "$CLEAN_BASE_FOLDER" +export HB_PACKAGE_NAME=$READ_INPUT_RETURN +if [[ "$HB_PACKAGE_NAME" =~ [^a-zA-Z0-9_-] ]]; then + exitWithError "Invalid package name: $HB_PACKAGE_NAME" +fi + +echo -n "Enter your executable name: " +read_input_with_default "App" +export HB_EXECUTABLE_NAME=$READ_INPUT_RETURN +if [[ "$HB_EXECUTABLE_NAME" =~ [^a-zA-Z0-9_] ]]; then + exitWithError "Invalid executable name: $HB_EXECUTABLE_NAME" +fi + +echo -n "Include Visual Studio Code snippets: " +read_yes_no "yes" +if [[ "$READ_INPUT_RETURN" == "yes" ]]; then + export HB_VSCODE_SNIPPETS="yes" +fi + +pushd $TEMPLATE_FOLDER + +# Root level files +FILES=$(find . -maxdepth 1 ! -type d ! -name "*.sh") +run_mustache "$FILES" +# Files in Sources and Tests folder +FILES=$(find Sources Tests .vscode/hummingbird.code-snippets ! -type d) +run_mustache "$FILES" + +# README file +cat < "$TARGET_FOLDER"/README.md +# $HB_PACKAGE_NAME +Hummingbird server framework project +EOF + +popd + +echo "Enter the folder created and run 'swift run' to build and run your server. Then open 'localhost:8080' on your web browser." diff --git a/scripts/download.sh b/scripts/download.sh new file mode 100755 index 0000000..7a16477 --- /dev/null +++ b/scripts/download.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +FOLDER=${1:-} +TEMPLATE_VERSION=2.0.2 + +if [[ -z "$FOLDER" ]]; then + echo "Missing folder name" + echo "Usage: download.sh " + exit 1 +fi + +curl -sSL https://github.com/hummingbird-project/template/archive/refs/tags/"$TEMPLATE_VERSION".tar.gz | tar xvz -s /template-"$TEMPLATE_VERSION"/"$FOLDER"/