├── .dockerignore ├── .github ├── renovate.json └── workflows │ ├── deploy.yml │ ├── spm.yml │ └── test.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Resources ├── ProjectTemplate │ ├── Package.swift │ └── Sources │ │ └── App │ │ └── main.swift └── formatter │ ├── Package.resolved │ └── Package.swift ├── SECURITY.md ├── Sources └── App │ ├── Controllers │ └── LanguageServer.swift │ ├── configure.swift │ ├── entrypoint.swift │ └── routes.swift └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "packageRules": [ 4 | { 5 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 6 | "automerge": true 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: [main] 5 | schedule: 6 | - cron: "0 */4 * * *" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: docker/login-action@v3 17 | with: 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 20 | 21 | - uses: docker/build-push-action@v6 22 | with: 23 | context: . 24 | push: true 25 | tags: swiftfiddle/swiftfiddle-lsp:latest 26 | 27 | - uses: appleboy/ssh-action@v1.2.2 28 | with: 29 | host: ${{ secrets.SSH_HOST }} 30 | username: ${{ secrets.SSH_USER }} 31 | key: ${{ secrets.SSH_KEY }} 32 | script: | 33 | cd swiftfiddle-lsp 34 | git pull --rebase origin main 35 | 36 | docker compose down 37 | docker compose --compatibility up --force-recreate --build --detach 38 | docker image prune --force 39 | -------------------------------------------------------------------------------- /.github/workflows/spm.yml: -------------------------------------------------------------------------------- 1 | name: Update Package.resolved 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 1" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | container: swift:latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Update Package.resolved 14 | run: | 15 | set -ex 16 | 17 | swift package update 18 | swift build 19 | 20 | git config --global --add safe.directory /__w/swiftfiddle-lsp/swiftfiddle-lsp 21 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 22 | git config --global user.name "github-actions[bot]" 23 | 24 | git add Package.resolved 25 | git diff-index --quiet HEAD || git commit -m "Update Package.resolved" 26 | git push origin main 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | branches: [main] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Build 13 | run: | 14 | set -ex 15 | docker build --rm --no-cache . 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "sourceLanguages": [ 7 | "swift" 8 | ], 9 | "name": "Debug App", 10 | "program": "${workspaceFolder:swiftfiddle-lsp}/.build/debug/App", 11 | "args": [], 12 | "cwd": "${workspaceFolder:swiftfiddle-lsp}", 13 | "preLaunchTask": "swift: Build Debug App" 14 | }, 15 | { 16 | "type": "lldb", 17 | "request": "launch", 18 | "sourceLanguages": [ 19 | "swift" 20 | ], 21 | "name": "Release App", 22 | "program": "${workspaceFolder:swiftfiddle-lsp}/.build/release/App", 23 | "args": [], 24 | "cwd": "${workspaceFolder:swiftfiddle-lsp}", 25 | "preLaunchTask": "swift: Build Release App" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB", 3 | "lldb.launch.expressions": "native" 4 | } 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:6.1-jammy as build 2 | 3 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 4 | && apt-get -q update \ 5 | && apt-get -q dist-upgrade -y \ 6 | && apt-get install -y libsqlite3-dev rsync \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /build 10 | COPY ./Package.* ./ 11 | RUN swift package resolve 12 | COPY . . 13 | RUN swift build -c release --static-swift-stdlib \ 14 | && (cd Resources/ProjectTemplate && swift build -c debug) \ 15 | && (cd Resources/formatter && swift build --product swift-format -c release) 16 | 17 | WORKDIR /staging 18 | RUN cp "$(swift build --package-path /build -c release --show-bin-path)/App" ./ \ 19 | && rsync -a --delete --include=".build" --include="App/" \ 20 | --exclude="artifacts" --exclude="checkouts" --exclude="plugins" --exclude="repositories" \ 21 | --exclude="ModuleCache" --exclude="index" \ 22 | --exclude="*.build" --exclude="*.bundle" --exclude="*.product" \ 23 | --exclude="*.json" --exclude="*.o" --exclude="*.swiftsourceinfo" \ 24 | --exclude="App" --exclude=".DS_Store" \ 25 | /build/Resources/ ./Resources/ 26 | 27 | FROM swift:6.1-jammy 28 | 29 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 30 | && apt-get -q update \ 31 | && apt-get -q dist-upgrade -y \ 32 | && apt-get -q install -y \ 33 | ca-certificates \ 34 | tzdata \ 35 | libcurl4 \ 36 | libxml2 \ 37 | rsync \ 38 | && rm -r /var/lib/apt/lists/* 39 | 40 | RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor 41 | 42 | WORKDIR /app 43 | COPY --from=build --chown=vapor:vapor /staging /app 44 | 45 | USER vapor:vapor 46 | EXPOSE 8080 47 | 48 | ENTRYPOINT ["./App"] 49 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kishikawa Katsumi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "3e9d22359f9463840a5a80d6f8fcb939f75e55a04affd5320faf9fb96610ba81", 3 | "pins" : [ 4 | { 5 | "identity" : "async-http-client", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swift-server/async-http-client.git", 8 | "state" : { 9 | "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", 10 | "version" : "1.26.1" 11 | } 12 | }, 13 | { 14 | "identity" : "async-kit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/vapor/async-kit.git", 17 | "state" : { 18 | "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", 19 | "version" : "1.20.0" 20 | } 21 | }, 22 | { 23 | "identity" : "console-kit", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/vapor/console-kit.git", 26 | "state" : { 27 | "revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", 28 | "version" : "4.15.2" 29 | } 30 | }, 31 | { 32 | "identity" : "indexstore-db", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/swiftlang/indexstore-db.git", 35 | "state" : { 36 | "branch" : "main", 37 | "revision" : "e9f63092c300caef0500b6a8142c069eadc88e85" 38 | } 39 | }, 40 | { 41 | "identity" : "multipart-kit", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/vapor/multipart-kit.git", 44 | "state" : { 45 | "revision" : "3498e60218e6003894ff95192d756e238c01f44e", 46 | "version" : "4.7.1" 47 | } 48 | }, 49 | { 50 | "identity" : "routing-kit", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/vapor/routing-kit.git", 53 | "state" : { 54 | "revision" : "93f7222c8e195cbad39fafb5a0e4cc85a8def7ea", 55 | "version" : "4.9.2" 56 | } 57 | }, 58 | { 59 | "identity" : "sourcekit-lsp", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/sourcekit-lsp", 62 | "state" : { 63 | "branch" : "main", 64 | "revision" : "12da8e5f54809b642701dd0dd6e145d3e0c67bc4" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-algorithms", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/apple/swift-algorithms.git", 71 | "state" : { 72 | "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", 73 | "version" : "1.2.1" 74 | } 75 | }, 76 | { 77 | "identity" : "swift-argument-parser", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/apple/swift-argument-parser.git", 80 | "state" : { 81 | "revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b", 82 | "version" : "1.5.1" 83 | } 84 | }, 85 | { 86 | "identity" : "swift-asn1", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/apple/swift-asn1.git", 89 | "state" : { 90 | "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", 91 | "version" : "1.3.2" 92 | } 93 | }, 94 | { 95 | "identity" : "swift-atomics", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/apple/swift-atomics.git", 98 | "state" : { 99 | "revision" : "cd142fd2f64be2100422d658e7411e39489da985", 100 | "version" : "1.2.0" 101 | } 102 | }, 103 | { 104 | "identity" : "swift-certificates", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/apple/swift-certificates.git", 107 | "state" : { 108 | "revision" : "2f797305c1b5b982acaa6005d8a9f970cc4e97ff", 109 | "version" : "1.5.0" 110 | } 111 | }, 112 | { 113 | "identity" : "swift-cmark", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/swiftlang/swift-cmark.git", 116 | "state" : { 117 | "branch" : "gfm", 118 | "revision" : "b97d09472e847a416629f026eceae0e2afcfad65" 119 | } 120 | }, 121 | { 122 | "identity" : "swift-collections", 123 | "kind" : "remoteSourceControl", 124 | "location" : "https://github.com/apple/swift-collections.git", 125 | "state" : { 126 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", 127 | "version" : "1.1.4" 128 | } 129 | }, 130 | { 131 | "identity" : "swift-crypto", 132 | "kind" : "remoteSourceControl", 133 | "location" : "https://github.com/apple/swift-crypto.git", 134 | "state" : { 135 | "revision" : "629f0b679d0fd0a6ae823d7f750b9ab032c00b80", 136 | "version" : "3.0.0" 137 | } 138 | }, 139 | { 140 | "identity" : "swift-distributed-tracing", 141 | "kind" : "remoteSourceControl", 142 | "location" : "https://github.com/apple/swift-distributed-tracing.git", 143 | "state" : { 144 | "revision" : "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", 145 | "version" : "1.2.0" 146 | } 147 | }, 148 | { 149 | "identity" : "swift-docc", 150 | "kind" : "remoteSourceControl", 151 | "location" : "https://github.com/swiftlang/swift-docc.git", 152 | "state" : { 153 | "branch" : "main", 154 | "revision" : "a0c57b817b226f871a9be60543131065de85ff26" 155 | } 156 | }, 157 | { 158 | "identity" : "swift-docc-symbolkit", 159 | "kind" : "remoteSourceControl", 160 | "location" : "https://github.com/swiftlang/swift-docc-symbolkit.git", 161 | "state" : { 162 | "branch" : "main", 163 | "revision" : "ddacb655e2087fd722cb5ca2bcc49953ee267e37" 164 | } 165 | }, 166 | { 167 | "identity" : "swift-driver", 168 | "kind" : "remoteSourceControl", 169 | "location" : "https://github.com/swiftlang/swift-driver.git", 170 | "state" : { 171 | "branch" : "main", 172 | "revision" : "77b128dfa36cffd1dc74935429f283bdf81486d7" 173 | } 174 | }, 175 | { 176 | "identity" : "swift-http-structured-headers", 177 | "kind" : "remoteSourceControl", 178 | "location" : "https://github.com/apple/swift-http-structured-headers.git", 179 | "state" : { 180 | "revision" : "db6eea3692638a65e2124990155cd220c2915903", 181 | "version" : "1.3.0" 182 | } 183 | }, 184 | { 185 | "identity" : "swift-http-types", 186 | "kind" : "remoteSourceControl", 187 | "location" : "https://github.com/apple/swift-http-types.git", 188 | "state" : { 189 | "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", 190 | "version" : "1.4.0" 191 | } 192 | }, 193 | { 194 | "identity" : "swift-llbuild", 195 | "kind" : "remoteSourceControl", 196 | "location" : "https://github.com/swiftlang/swift-llbuild.git", 197 | "state" : { 198 | "branch" : "main", 199 | "revision" : "1e8a570d565148bc08ca05e2bdb273d1362c5f9e" 200 | } 201 | }, 202 | { 203 | "identity" : "swift-lmdb", 204 | "kind" : "remoteSourceControl", 205 | "location" : "https://github.com/swiftlang/swift-lmdb.git", 206 | "state" : { 207 | "branch" : "main", 208 | "revision" : "1ad9a2d80b6fcde498c2242f509bd1be7d667ff8" 209 | } 210 | }, 211 | { 212 | "identity" : "swift-log", 213 | "kind" : "remoteSourceControl", 214 | "location" : "https://github.com/apple/swift-log.git", 215 | "state" : { 216 | "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", 217 | "version" : "1.6.3" 218 | } 219 | }, 220 | { 221 | "identity" : "swift-markdown", 222 | "kind" : "remoteSourceControl", 223 | "location" : "https://github.com/swiftlang/swift-markdown.git", 224 | "state" : { 225 | "branch" : "main", 226 | "revision" : "5ad49ed3219261b085e86da832d0657752bba63c" 227 | } 228 | }, 229 | { 230 | "identity" : "swift-metrics", 231 | "kind" : "remoteSourceControl", 232 | "location" : "https://github.com/apple/swift-metrics.git", 233 | "state" : { 234 | "revision" : "4c83e1cdf4ba538ef6e43a9bbd0bcc33a0ca46e3", 235 | "version" : "2.7.0" 236 | } 237 | }, 238 | { 239 | "identity" : "swift-nio", 240 | "kind" : "remoteSourceControl", 241 | "location" : "https://github.com/apple/swift-nio.git", 242 | "state" : { 243 | "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", 244 | "version" : "2.83.0" 245 | } 246 | }, 247 | { 248 | "identity" : "swift-nio-extras", 249 | "kind" : "remoteSourceControl", 250 | "location" : "https://github.com/apple/swift-nio-extras.git", 251 | "state" : { 252 | "revision" : "f1f6f772198bee35d99dd145f1513d8581a54f2c", 253 | "version" : "1.26.0" 254 | } 255 | }, 256 | { 257 | "identity" : "swift-nio-http2", 258 | "kind" : "remoteSourceControl", 259 | "location" : "https://github.com/apple/swift-nio-http2.git", 260 | "state" : { 261 | "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", 262 | "version" : "1.36.0" 263 | } 264 | }, 265 | { 266 | "identity" : "swift-nio-ssl", 267 | "kind" : "remoteSourceControl", 268 | "location" : "https://github.com/apple/swift-nio-ssl.git", 269 | "state" : { 270 | "revision" : "4b38f35946d00d8f6176fe58f96d83aba64b36c7", 271 | "version" : "2.31.0" 272 | } 273 | }, 274 | { 275 | "identity" : "swift-nio-transport-services", 276 | "kind" : "remoteSourceControl", 277 | "location" : "https://github.com/apple/swift-nio-transport-services.git", 278 | "state" : { 279 | "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", 280 | "version" : "1.24.0" 281 | } 282 | }, 283 | { 284 | "identity" : "swift-numerics", 285 | "kind" : "remoteSourceControl", 286 | "location" : "https://github.com/apple/swift-numerics.git", 287 | "state" : { 288 | "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", 289 | "version" : "1.0.3" 290 | } 291 | }, 292 | { 293 | "identity" : "swift-package-manager", 294 | "kind" : "remoteSourceControl", 295 | "location" : "https://github.com/swiftlang/swift-package-manager.git", 296 | "state" : { 297 | "branch" : "main", 298 | "revision" : "c5d77e55a07aa036aab2cbbcd951c4aeb854068c" 299 | } 300 | }, 301 | { 302 | "identity" : "swift-service-context", 303 | "kind" : "remoteSourceControl", 304 | "location" : "https://github.com/apple/swift-service-context.git", 305 | "state" : { 306 | "revision" : "8946c930cae601452149e45d31d8ddfac973c3c7", 307 | "version" : "1.2.0" 308 | } 309 | }, 310 | { 311 | "identity" : "swift-syntax", 312 | "kind" : "remoteSourceControl", 313 | "location" : "https://github.com/swiftlang/swift-syntax.git", 314 | "state" : { 315 | "branch" : "main", 316 | "revision" : "8d2c20fec97eaea0b843b000f25e83723e265d60" 317 | } 318 | }, 319 | { 320 | "identity" : "swift-system", 321 | "kind" : "remoteSourceControl", 322 | "location" : "https://github.com/apple/swift-system.git", 323 | "state" : { 324 | "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", 325 | "version" : "1.5.0" 326 | } 327 | }, 328 | { 329 | "identity" : "swift-toolchain-sqlite", 330 | "kind" : "remoteSourceControl", 331 | "location" : "https://github.com/swiftlang/swift-toolchain-sqlite", 332 | "state" : { 333 | "revision" : "4ee66b3ab1c40d20176045e61d8276242e73b01d", 334 | "version" : "1.0.3" 335 | } 336 | }, 337 | { 338 | "identity" : "swift-tools-support-core", 339 | "kind" : "remoteSourceControl", 340 | "location" : "https://github.com/swiftlang/swift-tools-support-core.git", 341 | "state" : { 342 | "branch" : "main", 343 | "revision" : "743626410bab5f2a07994349e79c6bfc0ccc5b70" 344 | } 345 | }, 346 | { 347 | "identity" : "vapor", 348 | "kind" : "remoteSourceControl", 349 | "location" : "https://github.com/vapor/vapor.git", 350 | "state" : { 351 | "revision" : "4014016aad591a120f244f9b9e8a57252b7e62b4", 352 | "version" : "4.115.0" 353 | } 354 | }, 355 | { 356 | "identity" : "websocket-kit", 357 | "kind" : "remoteSourceControl", 358 | "location" : "https://github.com/vapor/websocket-kit.git", 359 | "state" : { 360 | "revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167", 361 | "version" : "2.16.1" 362 | } 363 | } 364 | ], 365 | "version" : 3 366 | } 367 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "swiftfiddle-lsp", 6 | platforms: [ 7 | .macOS(.v13) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.115.0"), 11 | .package(url: "https://github.com/apple/sourcekit-lsp", branch: "main") 12 | ], 13 | targets: [ 14 | .executableTarget( 15 | name: "App", 16 | dependencies: [ 17 | .product(name: "Vapor", package: "vapor"), 18 | .product(name: "LSPBindings", package: "sourcekit-lsp"), 19 | ], 20 | swiftSettings: [ 21 | .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) 22 | ] 23 | ), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftfiddle-lsp 2 | 3 | Provide code completion and type hint for [swiftfiddle.com](https://swiftfiddle.com) powered by [SourceKit-LSP](https://github.com/apple/sourcekit-lsp). 4 | -------------------------------------------------------------------------------- /Resources/ProjectTemplate/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "App", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.1"), 11 | .package(url: "https://github.com/apple/swift-atomics", from: "1.3.0"), 12 | .package(url: "https://github.com/apple/swift-collections", from: "1.2.0"), 13 | .package(url: "https://github.com/apple/swift-crypto", from: "3.12.3"), 14 | .package(url: "https://github.com/apple/swift-numerics", from: "1.0.3"), 15 | .package(url: "https://github.com/apple/swift-system", from: "1.5.0"), 16 | ], 17 | targets: [ 18 | .executableTarget( 19 | name: "App", 20 | dependencies: [ 21 | .product(name: "Algorithms", package: "swift-algorithms"), 22 | .product(name: "Atomics", package: "swift-atomics"), 23 | .product(name: "Collections", package: "swift-collections"), 24 | .product(name: "Crypto", package: "swift-crypto"), 25 | .product(name: "Numerics", package: "swift-numerics"), 26 | .product(name: "SystemPackage", package: "swift-system"), 27 | ] 28 | ), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /Resources/ProjectTemplate/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | -------------------------------------------------------------------------------- /Resources/formatter/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1", 10 | "version": "1.1.4" 11 | } 12 | }, 13 | { 14 | "package": "swift-format", 15 | "repositoryURL": "https://github.com/apple/swift-format.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "3dd9b517b9e9846435aa782d769ef5825e7c2d65", 19 | "version": "0.50700.0" 20 | } 21 | }, 22 | { 23 | "package": "SwiftSyntax", 24 | "repositoryURL": "https://github.com/apple/swift-syntax", 25 | "state": { 26 | "branch": null, 27 | "revision": "04d4497be6b88e524a71778d828790e9589ae1c4", 28 | "version": "0.50700.0" 29 | } 30 | }, 31 | { 32 | "package": "swift-system", 33 | "repositoryURL": "https://github.com/apple/swift-system.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "836bc4557b74fe6d2660218d56e3ce96aff76574", 37 | "version": "1.1.1" 38 | } 39 | }, 40 | { 41 | "package": "swift-tools-support-core", 42 | "repositoryURL": "https://github.com/apple/swift-tools-support-core.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "4f07be3dc201f6e2ee85b6942d0c220a16926811", 46 | "version": "0.2.7" 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Resources/formatter/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "formatter", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/apple/swift-format.git", from: "600.0.0"), 11 | ] 12 | ) 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /Sources/App/Controllers/LanguageServer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LanguageServerProtocol 3 | import LanguageServerProtocolJSONRPC 4 | 5 | final class LanguageServer { 6 | let diagnosticsPublisher: @Sendable (PublishDiagnosticsNotification) -> Void 7 | 8 | private let serverProcess = Process() 9 | private let clientToServer = Pipe() 10 | private let serverToClient = Pipe() 11 | 12 | private var instance: LanguageServer? 13 | 14 | private let serverPath: String? 15 | 16 | private lazy var connection = JSONRPCConnection( 17 | name: "clangd", 18 | protocol: .lspProtocol, 19 | inFD: serverToClient.fileHandleForReading, 20 | outFD: clientToServer.fileHandleForWriting 21 | ) 22 | 23 | init(serverPath: String? = nil, diagnosticsPublisher: @Sendable @escaping (PublishDiagnosticsNotification) -> Void = { _ in }) { 24 | self.serverPath = serverPath 25 | self.diagnosticsPublisher = diagnosticsPublisher 26 | instance = self 27 | } 28 | 29 | func start() throws { 30 | let launchPath: String 31 | if let serverPath = serverPath { 32 | launchPath = serverPath 33 | } else { 34 | #if os(macOS) 35 | launchPath = 36 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp" 37 | #else 38 | launchPath = "/usr/bin/sourcekit-lsp" 39 | #endif 40 | } 41 | 42 | connection.start(receiveHandler: Client(diagnosticsPublisher: diagnosticsPublisher)) 43 | 44 | serverProcess.executableURL = URL(fileURLWithPath: launchPath) 45 | serverProcess.arguments = [ 46 | "-Xswiftc", "-enable-bare-slash-regex", 47 | ] 48 | 49 | serverProcess.standardOutput = serverToClient 50 | serverProcess.standardInput = clientToServer 51 | serverProcess.terminationHandler = { [weak self] process in 52 | self?.connection.close() 53 | } 54 | 55 | try serverProcess.run() 56 | } 57 | 58 | func stop() { 59 | sendShutdownRequest { [weak self] _ in 60 | self?.sendExitNotification() 61 | self?.instance = nil 62 | } 63 | } 64 | 65 | func sendInitializeRequest(workspacePath: String, completion: @escaping (Result) -> Void) { 66 | let rootURI = URL(fileURLWithPath: workspacePath) 67 | 68 | let request = InitializeRequest( 69 | rootURI: DocumentURI(rootURI), 70 | capabilities: ClientCapabilities( 71 | textDocument: TextDocumentClientCapabilities( 72 | completion: TextDocumentClientCapabilities.Completion( 73 | completionItem: TextDocumentClientCapabilities.Completion.CompletionItem( 74 | snippetSupport: true 75 | ) 76 | ) 77 | ) 78 | ), 79 | workspaceFolders: [WorkspaceFolder(uri: DocumentURI(rootURI))] 80 | ) 81 | _ = connection.send(request) { 82 | completion($0) 83 | } 84 | } 85 | 86 | func sendInitializedNotification() { 87 | connection.send(InitializedNotification()) 88 | } 89 | 90 | func sendDidOpenNotification(documentPath: String, text: String) { 91 | let identifier = URL(fileURLWithPath: documentPath) 92 | let document = TextDocumentItem( 93 | uri: DocumentURI(identifier), 94 | language: .swift, 95 | version: 1, 96 | text: text 97 | ) 98 | connection.send(DidOpenTextDocumentNotification(textDocument: document)) 99 | } 100 | 101 | func sendDidChangeNotification(documentPath: String, text: String, version: Int) { 102 | let identifier = URL(fileURLWithPath: documentPath) 103 | connection.send( 104 | DidChangeTextDocumentNotification( 105 | textDocument: VersionedTextDocumentIdentifier(DocumentURI(identifier), version: version), 106 | contentChanges: [TextDocumentContentChangeEvent(range: nil, rangeLength: nil, text: text)], 107 | forceRebuild: nil 108 | ) 109 | ) 110 | } 111 | 112 | func sendDidCloseNotification(documentPath: String) { 113 | let identifier = URL(fileURLWithPath: documentPath) 114 | connection.send( 115 | DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(DocumentURI(identifier))) 116 | ) 117 | } 118 | 119 | func sendDocumentSymbolRequest(documentPath: String, completion: @escaping (Result) -> Void) { 120 | let identifier = URL(fileURLWithPath: documentPath) 121 | 122 | let documentSymbolRequest = DocumentSymbolRequest( 123 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)) 124 | ) 125 | _ = connection.send(documentSymbolRequest) { 126 | completion($0) 127 | } 128 | } 129 | 130 | func sendCompletionRequest(documentPath: String, line: Int, character: Int, prefix: String? = nil, completion: @escaping (Result) -> Void) { 131 | let identifier = URL(fileURLWithPath: documentPath) 132 | 133 | let completionRequest = CompletionRequest( 134 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)), 135 | position: Position(line: line, utf16index: character), 136 | context:CompletionContext(triggerKind: .invoked) 137 | ) 138 | _ = connection.send(completionRequest) { 139 | completion($0) 140 | } 141 | } 142 | 143 | func sendHoverRequest(documentPath: String, line: Int, character: Int, completion: @escaping (Result) -> Void) { 144 | let identifier = URL(fileURLWithPath: documentPath) 145 | 146 | let hoverRequest = HoverRequest( 147 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)), 148 | position: Position(line: line, utf16index: character) 149 | ) 150 | _ = connection.send(hoverRequest) { 151 | completion($0) 152 | } 153 | } 154 | 155 | func sendDefinitionRequest(documentPath: String, line: Int, character: Int, completion: @escaping (Result) -> Void) { 156 | let identifier = URL(fileURLWithPath: documentPath) 157 | 158 | let definitionRequest = DefinitionRequest( 159 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)), 160 | position: Position(line: line, utf16index: character) 161 | ) 162 | _ = connection.send(definitionRequest) { 163 | completion($0) 164 | } 165 | } 166 | 167 | func sendReferencesRequest(documentPath: String, line: Int, character: Int, completion: @escaping (Result) -> Void) { 168 | let identifier = URL(fileURLWithPath: documentPath) 169 | 170 | let referencesRequest = ReferencesRequest( 171 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)), 172 | position: Position(line: line, utf16index: character), 173 | context: ReferencesContext(includeDeclaration: false) 174 | ) 175 | _ = connection.send(referencesRequest) { 176 | completion($0) 177 | } 178 | } 179 | 180 | func sendDocumentHighlightRequest(documentPath: String, line: Int, character: Int, completion: @escaping (Result) -> Void) { 181 | let identifier = URL(fileURLWithPath: documentPath) 182 | 183 | let documentHighlightRequest = DocumentHighlightRequest( 184 | textDocument: TextDocumentIdentifier(DocumentURI(identifier)), 185 | position: Position(line: line, utf16index: character) 186 | ) 187 | _ = connection.send(documentHighlightRequest) { 188 | completion($0) 189 | } 190 | } 191 | 192 | func sendShutdownRequest(completion: @escaping (Result) -> Void) { 193 | let request = ShutdownRequest() 194 | _ = connection.send(request) { 195 | completion($0) 196 | } 197 | } 198 | 199 | func sendExitNotification() { 200 | connection.send(ExitNotification()) 201 | serverProcess.terminate() 202 | } 203 | } 204 | 205 | private final class Client: MessageHandler { 206 | let diagnosticsPublisher: @Sendable (PublishDiagnosticsNotification) -> Void 207 | 208 | init(diagnosticsPublisher: @Sendable @escaping (PublishDiagnosticsNotification) -> Void) { 209 | self.diagnosticsPublisher = diagnosticsPublisher 210 | } 211 | 212 | func handle(_ notification: some LanguageServerProtocol.NotificationType) { 213 | if let notification = notification as? PublishDiagnosticsNotification { 214 | diagnosticsPublisher(notification) 215 | } 216 | } 217 | 218 | func handle(_ request: Request, id: LanguageServerProtocol.RequestID, reply: @escaping (LanguageServerProtocol.LSPResult) -> Void) where Request : LanguageServerProtocol.RequestType {} 219 | } 220 | -------------------------------------------------------------------------------- /Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | public func configure(_ app: Application) throws { 4 | app.http.server.configuration.requestDecompression = .enabled 5 | app.http.server.configuration.responseCompression = .enabled 6 | app.http.server.configuration.supportPipelining = true 7 | try routes(app) 8 | } 9 | -------------------------------------------------------------------------------- /Sources/App/entrypoint.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Dispatch 3 | import Logging 4 | 5 | /// This extension is temporary and can be removed once Vapor gets this support. 6 | private extension Vapor.Application { 7 | static let baseExecutionQueue = DispatchQueue(label: "vapor.codes.entrypoint") 8 | 9 | func runFromAsyncMainEntrypoint() async throws { 10 | try await withCheckedThrowingContinuation { continuation in 11 | Vapor.Application.baseExecutionQueue.async { [self] in 12 | do { 13 | try self.run() 14 | continuation.resume() 15 | } catch { 16 | continuation.resume(throwing: error) 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | @main 24 | enum Entrypoint { 25 | static func main() async throws { 26 | var env = try Environment.detect() 27 | try LoggingSystem.bootstrap(from: &env) 28 | 29 | let app = try await Application.make(env) 30 | defer { app.shutdown() } 31 | 32 | try configure(app) 33 | try await app.runFromAsyncMainEntrypoint() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LanguageServerProtocol 3 | import Vapor 4 | 5 | func routes(_ app: Application) throws { 6 | app.get { _ in healthCheck() } 7 | app.get("health") { _ in healthCheck() } 8 | app.get("lang-server") { _ in healthCheck() } 9 | app.get("lang-server", "health") { _ in healthCheck() } 10 | 11 | func healthCheck() -> [String: String] { ["status": "pass"] } 12 | 13 | app.webSocket("lang-server", "api") { (req, ws) in 14 | languageServer(req, ws) 15 | } 16 | 17 | func languageServer(_ req: Vapor.Request, _ ws: WebSocket) { 18 | let uuid = UUID().uuidString 19 | 20 | struct DidOpenRequest: Codable { 21 | let method: String 22 | let code: String 23 | let sessionId: String 24 | } 25 | struct DidCloseRequest: Codable { 26 | let method: String 27 | let sessionId: String 28 | } 29 | typealias DidChangeRequest = DidOpenRequest 30 | 31 | struct HoverRequest: Codable { 32 | let method: String 33 | let id: Int 34 | let row: Int 35 | let column: Int 36 | let sessionId: String 37 | } 38 | struct HoverResponse: Codable { 39 | let method: String 40 | let id: Int 41 | let position: Position 42 | let value: LanguageServerProtocol.HoverRequest.Response 43 | } 44 | 45 | typealias CompletionRequest = HoverRequest 46 | struct CompletionResponse: Codable { 47 | let method: String 48 | let id: Int 49 | let position: Position 50 | let value: LanguageServerProtocol.CompletionRequest.Response? 51 | } 52 | 53 | struct DiagnosticsNotification: Codable { 54 | let method: String 55 | let value: PublishDiagnosticsNotification 56 | } 57 | 58 | struct FormatRequest: Codable { 59 | let method: String 60 | let code: String 61 | } 62 | struct FormatResponse: Codable { 63 | let method: String 64 | let value: String 65 | } 66 | 67 | let encoder = JSONEncoder() 68 | let decoder = JSONDecoder() 69 | 70 | let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) 71 | let workspacePath = temporaryDirectory.appendingPathComponent(uuid, isDirectory: true).path 72 | do { 73 | try copyWorkspace( 74 | atPath: "\(app.directory.resourcesDirectory)ProjectTemplate", 75 | toPath: workspacePath 76 | ) 77 | } catch { 78 | req.logger.error("\(error.localizedDescription)") 79 | _ = ws.close(code: .unexpectedServerError) 80 | return 81 | } 82 | 83 | do { 84 | let metadata = try String( 85 | contentsOf: URL(fileURLWithPath: "\(workspacePath)/.build/debug.yaml"), encoding: .utf8 86 | ) 87 | .replacingOccurrences( 88 | of: "/build/Resources/ProjectTemplate/.build", 89 | with: workspacePath 90 | ) 91 | try metadata.write(toFile: "\(workspacePath)/.build/debug.yaml", atomically: false, encoding: .utf8) 92 | } catch { 93 | req.logger.error("\(error.localizedDescription)") 94 | _ = ws.close(code: .unexpectedServerError) 95 | return 96 | } 97 | 98 | let sourceRoot = "\(workspacePath)/Sources/App/" 99 | let documentPath = "\(sourceRoot)main.swift" 100 | var documentVersion = 0 101 | 102 | let diagnosticsPublisher = { @Sendable (notification: PublishDiagnosticsNotification) in 103 | guard notification.uri.fileURL?.path == documentPath else { return } 104 | 105 | let diagnosticsNotification = DiagnosticsNotification( 106 | method: "diagnostics", 107 | value: notification 108 | ) 109 | guard let data = try? encoder.encode(diagnosticsNotification) else { return } 110 | guard let json = String(data: data, encoding: .utf8) else { return } 111 | ws.send(json) 112 | } 113 | let languageServer = LanguageServer(diagnosticsPublisher: diagnosticsPublisher) 114 | 115 | do { 116 | try languageServer.start() 117 | } catch { 118 | req.logger.error("\(error.localizedDescription)") 119 | _ = ws.close(code: .unexpectedServerError) 120 | return 121 | } 122 | 123 | _ = ws.onClose.always { [weak languageServer] _ in 124 | do { 125 | languageServer?.sendDidCloseNotification(documentPath: documentPath) 126 | languageServer?.stop() 127 | try FileManager().removeItem(atPath: workspacePath) 128 | } catch { 129 | req.logger.error("\(error.localizedDescription)") 130 | } 131 | } 132 | 133 | ws.onText { [weak languageServer] (ws, text) in 134 | guard let data = text.data(using: .utf8) else { return } 135 | 136 | switch text { 137 | case _ where text.hasPrefix(#"{"method":"didOpen""#): 138 | guard let request = try? decoder.decode(DidOpenRequest.self, from: data) else { return } 139 | languageServer?.sendInitializeRequest(workspacePath: workspacePath) { [weak languageServer] (result) in 140 | switch result { 141 | case .success: 142 | languageServer?.sendDidOpenNotification(documentPath: documentPath, text: request.code) 143 | case .failure: 144 | break 145 | } 146 | } 147 | case _ where text.hasPrefix(#"{"method":"didChange""#): 148 | guard let request = try? decoder.decode(DidChangeRequest.self, from: data) else { return } 149 | documentVersion += 1 150 | languageServer?.sendDidChangeNotification(documentPath: documentPath, text: request.code, version: documentVersion) 151 | case _ where text.hasPrefix(#"{"method":"didClose""#): 152 | break 153 | case _ where text.hasPrefix(#"{"method":"hover""#): 154 | guard let request = try? decoder.decode(HoverRequest.self, from: data) else { return } 155 | languageServer?.sendHoverRequest( 156 | documentPath: documentPath, line: request.row, character: request.column 157 | ) { (result) in 158 | let value: LanguageServerProtocol.HoverRequest.Response 159 | switch result { 160 | case .success(let response): 161 | if let response = response { 162 | value = response 163 | } else { 164 | value = nil 165 | } 166 | case .failure: 167 | value = nil 168 | } 169 | let hoverResponse = HoverResponse( 170 | method: "hover", 171 | id: request.id, 172 | position: Position(line: request.row + 1, utf16index: request.column + 1), 173 | value: value 174 | ) 175 | guard let data = try? encoder.encode(hoverResponse) else { return } 176 | guard let json = String(data: data, encoding: .utf8) else { return } 177 | ws.send(json) 178 | } 179 | case _ where text.hasPrefix(#"{"method":"completion""#): 180 | guard let request = try? decoder.decode(CompletionRequest.self, from: data) else { return } 181 | languageServer?.sendCompletionRequest( 182 | documentPath: documentPath, line: request.row, character: request.column 183 | ) { (result) in 184 | let value: LanguageServerProtocol.CompletionRequest.Response? 185 | switch result { 186 | case .success(let response): 187 | value = response 188 | case .failure: 189 | value = nil 190 | } 191 | let completionResponse = CompletionResponse( 192 | method: "completion", 193 | id: request.id, 194 | position: Position(line: request.row + 1, utf16index: request.column + 1), 195 | value: value 196 | ) 197 | guard let data = try? encoder.encode(completionResponse) else { return } 198 | guard let json = String(data: data, encoding: .utf8) else { return } 199 | ws.send(json) 200 | } 201 | case _ where text.hasPrefix(#"{"method":"format""#): 202 | guard let request = try? decoder.decode(FormatRequest.self, from: data) else { return } 203 | let source = request.code 204 | let output = format(source: source) 205 | let formatResponse = FormatResponse(method: "format", value: output) 206 | guard let data = try? encoder.encode(formatResponse) else { return } 207 | guard let json = String(data: data, encoding: .utf8) else { return } 208 | ws.send(json) 209 | default: 210 | break 211 | } 212 | } 213 | } 214 | 215 | func format(source: String) -> String { 216 | guard let input = source.data(using: .utf8) else { 217 | return source 218 | } 219 | 220 | let standardInput = Pipe() 221 | let standardOutput = Pipe() 222 | let standardError = Pipe() 223 | 224 | let fileHandle = standardInput.fileHandleForWriting 225 | fileHandle.write(input) 226 | do { 227 | try fileHandle.close() 228 | } catch { 229 | return source 230 | } 231 | 232 | let process = Process() 233 | let executableURL = URL( 234 | fileURLWithPath: app.directory.resourcesDirectory 235 | ) 236 | .appendingPathComponent("formatter/.build/release/swift-format") 237 | process.executableURL = executableURL 238 | 239 | process.standardInput = standardInput 240 | process.standardOutput = standardOutput 241 | process.standardError = standardError 242 | 243 | process.launch() 244 | process.waitUntilExit() 245 | 246 | let data = standardOutput.fileHandleForReading.readDataToEndOfFile() 247 | guard let output = String(data: data, encoding: .utf8) else { 248 | return source 249 | } 250 | 251 | return output 252 | } 253 | } 254 | 255 | private func copyWorkspace(atPath sourcePath: String, toPath destPath: String) throws { 256 | let fileManager = FileManager() 257 | try fileManager.copyItem(atPath: sourcePath, toPath: destPath) 258 | } 259 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: swiftfiddle/swiftfiddle-lsp:latest 4 | container_name: swiftfiddle-lsp 5 | ports: 6 | - 80:8080 7 | restart: always 8 | healthcheck: 9 | test: ["CMD", "curl", "-f", "http://localhost:8080/health"] 10 | interval: 5m 11 | timeout: 30s 12 | retries: 3 13 | start_period: 40s 14 | deploy: 15 | resources: 16 | limits: 17 | memory: 2g 18 | autoheal: 19 | image: willfarrell/autoheal:latest 20 | container_name: autoheal 21 | environment: 22 | AUTOHEAL_INTERVAL: 60 23 | AUTOHEAL_START_PERIOD: 60 24 | restart: always 25 | volumes: 26 | - /var/run/docker.sock:/var/run/docker.sock 27 | --------------------------------------------------------------------------------