diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1da138601..31171c845 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,40 @@ jobs: fail-fast: false matrix: php-version: ['8.2'] - sdk: [Android5Java17, Android14Java17, CLINode16, CLINode18, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node16, Node18, Node20, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift56, Swift56, WebChromium, WebNode] + sdk: [ + Android5Java17, + Android14Java17, + CLINode16, + CLINode18, + DartBeta, + DartStable, + Deno1193, + Deno1303, + DotNet60, + DotNet80, + FlutterStable, + FlutterBeta, + Go112, + Go118, + KotlinJava8, + KotlinJava11, + KotlinJava17, + Node16, + Node18, + Node20, + PHP74, + PHP80, + Python38, + Python39, + Python310, + Ruby27, + Ruby30, + Ruby31, + AppleSwift56, + Swift56, + WebChromium, + WebNode + ] steps: - name: Checkout repository diff --git a/mock-server/docker-compose.yml b/mock-server/docker-compose.yml index 35227488b..842bcf8e3 100644 --- a/mock-server/docker-compose.yml +++ b/mock-server/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: mockapi: container_name: mockapi diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index b61380a01..eb0052fb6 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -475,8 +475,13 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'dart/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'dart/.github/workflows/publish.yml.twig', + ], + [ + 'scope' => 'default', + 'destination' => '.github/workflows/format.yml', + 'template' => 'dart/.github/workflows/format.yml.twig', ], [ 'scope' => 'default', diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php index 33fcea410..a76ef40dd 100644 --- a/src/SDK/Language/DotNet.php +++ b/src/SDK/Language/DotNet.php @@ -303,8 +303,8 @@ public function getFiles(): array return [ [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'dotnet/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'dotnet/.github/workflows/publish.yml.twig', ], [ 'scope' => 'default', @@ -333,93 +333,93 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}.sln', - 'template' => 'dotnet/src/Appwrite.sln', + 'destination' => '{{ spec.title | caseUcfirst }}.sln', + 'template' => 'dotnet/Package.sln', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}.csproj', - 'template' => 'dotnet/src/Appwrite/Appwrite.csproj.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}.csproj', + 'template' => 'dotnet/Package/Package.csproj.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Client.cs', - 'template' => 'dotnet/src/Appwrite/Client.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Client.cs', + 'template' => 'dotnet/Package/Client.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}Exception.cs', - 'template' => 'dotnet/src/Appwrite/Exception.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/{{ spec.title | caseUcfirst }}Exception.cs', + 'template' => 'dotnet/Package/Exception.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/ID.cs', - 'template' => 'dotnet/src/Appwrite/ID.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/ID.cs', + 'template' => 'dotnet/Package/ID.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Permission.cs', - 'template' => 'dotnet/src/Appwrite/Permission.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Permission.cs', + 'template' => 'dotnet/Package/Permission.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Query.cs', - 'template' => 'dotnet/src/Appwrite/Query.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Query.cs', + 'template' => 'dotnet/Package/Query.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Role.cs', - 'template' => 'dotnet/src/Appwrite/Role.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Role.cs', + 'template' => 'dotnet/Package/Role.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Converters/ValueClassConverter.cs', - 'template' => 'dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Converters/ValueClassConverter.cs', + 'template' => 'dotnet/Package/Converters/ValueClassConverter.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Extensions/Extensions.cs', - 'template' => 'dotnet/src/Appwrite/Extensions/Extensions.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Extensions/Extensions.cs', + 'template' => 'dotnet/Package/Extensions/Extensions.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/OrderType.cs', - 'template' => 'dotnet/src/Appwrite/Models/OrderType.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Models/OrderType.cs', + 'template' => 'dotnet/Package/Models/OrderType.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/UploadProgress.cs', - 'template' => 'dotnet/src/Appwrite/Models/UploadProgress.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Models/UploadProgress.cs', + 'template' => 'dotnet/Package/Models/UploadProgress.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/InputFile.cs', - 'template' => 'dotnet/src/Appwrite/Models/InputFile.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Models/InputFile.cs', + 'template' => 'dotnet/Package/Models/InputFile.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Services/Service.cs', - 'template' => 'dotnet/src/Appwrite/Services/Service.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Services/Service.cs', + 'template' => 'dotnet/Package/Services/Service.cs.twig', ], [ 'scope' => 'service', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Services/{{service.name | caseUcfirst}}.cs', - 'template' => 'dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Services/{{service.name | caseUcfirst}}.cs', + 'template' => 'dotnet/Package/Services/ServiceTemplate.cs.twig', ], [ 'scope' => 'definition', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/{{ definition.name | caseUcfirst | overrideIdentifier }}.cs', - 'template' => 'dotnet/src/Appwrite/Models/Model.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Models/{{ definition.name | caseUcfirst | overrideIdentifier }}.cs', + 'template' => 'dotnet/Package/Models/Model.cs.twig', ], [ 'scope' => 'enum', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Enums/{{ enum.name | caseUcfirst | overrideIdentifier }}.cs', - 'template' => 'dotnet/src/Appwrite/Enums/Enum.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Enums/{{ enum.name | caseUcfirst | overrideIdentifier }}.cs', + 'template' => 'dotnet/Package/Enums/Enum.cs.twig', ], [ 'scope' => 'default', - 'destination' => '/src/{{ spec.title | caseUcfirst }}/Enums/IEnum.cs', - 'template' => 'dotnet/src/Appwrite/Enums/IEnum.cs.twig', + 'destination' => '{{ spec.title | caseUcfirst }}/Enums/IEnum.cs', + 'template' => 'dotnet/Package/Enums/IEnum.cs.twig', ] ]; } diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index 77f5c66bf..6c6fd8f17 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -332,8 +332,13 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'flutter/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'flutter/.github/workflows/publish.yml.twig', + ], + [ + 'scope' => 'default', + 'destination' => '.github/workflows/format.yml', + 'template' => 'flutter/.github/workflows/format.yml.twig', ], [ 'scope' => 'enum', diff --git a/src/SDK/Language/Node.php b/src/SDK/Language/Node.php index 45fdb72f7..d6e0a072f 100644 --- a/src/SDK/Language/Node.php +++ b/src/SDK/Language/Node.php @@ -234,8 +234,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'node/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'node/.github/workflows/publish.yml.twig', ], [ 'scope' => 'enum', diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index 0af3c053c..72d87b664 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -202,8 +202,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'python/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'python/.github/workflows/publish.yml.twig', ], [ 'scope' => 'enum', diff --git a/src/SDK/Language/Ruby.php b/src/SDK/Language/Ruby.php index 04f4869a7..16125d52c 100644 --- a/src/SDK/Language/Ruby.php +++ b/src/SDK/Language/Ruby.php @@ -174,8 +174,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'ruby/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'ruby/.github/workflows/publish.yml.twig', ], [ 'scope' => 'definition', diff --git a/src/SDK/Language/Web.php b/src/SDK/Language/Web.php index e29d0d0f1..105e86cd1 100644 --- a/src/SDK/Language/Web.php +++ b/src/SDK/Language/Web.php @@ -112,8 +112,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'web/.travis.yml.twig', + 'destination' => '.github/workflows/publish.yml', + 'template' => 'web/.github/workflows/publish.yml.twig', ], [ 'scope' => 'enum', diff --git a/templates/apple/Package.swift.twig b/templates/apple/Package.swift.twig index 132ae387f..a8cd6504e 100644 --- a/templates/apple/Package.swift.twig +++ b/templates/apple/Package.swift.twig @@ -22,7 +22,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.17.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), ], targets: [ diff --git a/templates/cli/lib/commands/push.js.twig b/templates/cli/lib/commands/push.js.twig index 8e269cdd4..a9052a651 100644 --- a/templates/cli/lib/commands/push.js.twig +++ b/templates/cli/lib/commands/push.js.twig @@ -38,9 +38,10 @@ const { } = require("./messaging"); const { teamsGet, - teamsUpdate, + teamsUpdateName, teamsCreate } = require("./teams"); +const { checkDeployConditions } = require('../utils'); const STEP_SIZE = 100; // Resources const POLL_DEBOUNCE = 2000; // Milliseconds @@ -262,6 +263,7 @@ const pushFunction = async ({ functionId, all, yes, async } = {}) => { if (functionId) { functionIds.push(functionId); } else if (all) { + checkDeployConditions(localConfig); const functions = localConfig.getFunctions(); if (functions.length === 0) { throw new Error("No functions found in the current directory."); @@ -651,6 +653,7 @@ const pushCollection = async ({ all, yes } = {}) => { const collections = []; if (all) { + checkDeployConditions(localConfig); if (localConfig.getCollections().length === 0) { throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} pull collection` to fetch all your collections."); } @@ -873,6 +876,7 @@ const pushBucket = async ({ all, yes } = {}) => { const configBuckets = localConfig.getBuckets(); if (all) { + checkDeployConditions(localConfig); if (configBuckets.length === 0) { throw new Error("No buckets found in the current directory. Run `appwrite pull bucket` to fetch all your buckets."); } @@ -960,6 +964,7 @@ const pushTeam = async ({ all, yes } = {}) => { const configTeams = localConfig.getTeams(); if (all) { + checkDeployConditions(localConfig); if (configTeams.length === 0) { throw new Error("No teams found in the current directory. Run `appwrite pull team` to fetch all your teams."); } @@ -1031,6 +1036,7 @@ const pushMessagingTopic = async ({ all, yes } = {}) => { let overrideExisting = yes; if (all) { + checkDeployConditions(localConfig); if (configTopics.length === 0) { throw new Error("No topics found in the current directory. Run `appwrite pull topics` to pull all your messaging topics."); } diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 67b5fd8ed..76b2d49da 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -8,6 +8,7 @@ const { validateRequired } = require("./validations"); const { paginate } = require('./paginate'); const { databasesList } = require('./commands/databases'); +const { checkDeployConditions } = require('./utils'); const JSONbig = require("json-bigint")({ storeAsString: false }); const getIgnores = (runtime) => { @@ -328,6 +329,7 @@ const questionsPushFunctions = [ validate: (value) => validateRequired('function', value), choices: () => { let functions = localConfig.getFunctions(); + checkDeployConditions(localConfig) if (functions.length === 0) { throw new Error("No functions found in the current directory."); } @@ -355,6 +357,8 @@ const questionsPushCollections = [ validate: (value) => validateRequired('collection', value), choices: () => { let collections = localConfig.getCollections(); + checkDeployConditions(localConfig) + if (collections.length === 0) { throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} pull collection` to fetch all your collections."); } @@ -381,6 +385,7 @@ const questionsPushBuckets = [ validate: (value) => validateRequired('bucket', value), choices: () => { let buckets = localConfig.getBuckets(); + checkDeployConditions(localConfig) if (buckets.length === 0) { throw new Error("No buckets found in the current directory. Run `appwrite pull bucket` to fetch all your buckets."); } @@ -447,6 +452,7 @@ const questionsPushTeams = [ validate: (value) => validateRequired('team', value), choices: () => { let teams = localConfig.getTeams(); + checkDeployConditions(localConfig); if (teams.length === 0) { throw new Error("No teams found in the current directory. Run `appwrite pull team` to fetch all your teams."); } diff --git a/templates/cli/lib/utils.js.twig b/templates/cli/lib/utils.js.twig index 289b1fa6e..851689c84 100644 --- a/templates/cli/lib/utils.js.twig +++ b/templates/cli/lib/utils.js.twig @@ -14,6 +14,13 @@ function getAllFiles(folder) { return files; } +const checkDeployConditions = (localConfig) => { + if (Object.keys(localConfig.data).length === 0) { + throw new Error("No appwrite.json file found in the current directory. This command must be run in the folder holding your appwrite.json file. Please run this command again in the folder containing your appwrite.json file, or run appwrite init project."); + } +} + module.exports = { - getAllFiles + getAllFiles, + checkDeployConditions }; diff --git a/templates/dart/.github/workflows/format.yml.twig b/templates/dart/.github/workflows/format.yml.twig new file mode 100644 index 000000000..3926b5ab0 --- /dev/null +++ b/templates/dart/.github/workflows/format.yml.twig @@ -0,0 +1,33 @@ +name: Format and push + +# Github action will require permission to write to repo +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + format: + runs-on: ubuntu-latest + container: + image: dart:stable + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: true + ref: ${{ '{{'}} github.event.pull_request.head.ref {{ '}}' }} + + - name: Format Dart code + run: dart format . + + - name: git config + run: git config --global --add safe.directory /__w/sdk-for-dart/sdk-for-dart # required to fix dubious ownership + + - name: Add & Commit + uses: EndBug/add-and-commit@v9.1.4 + with: + add: lib + diff --git a/templates/dart/.github/workflows/publish.yml.twig b/templates/dart/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..b40c5074b --- /dev/null +++ b/templates/dart/.github/workflows/publish.yml.twig @@ -0,0 +1,14 @@ +name: Publish to pub.dev + +on: + push: + tags: + - '[0-9]+\.[0-9]+\.[0-9]+.*' + +jobs: + publish: + permissions: + id-token: write + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: pub.dev \ No newline at end of file diff --git a/templates/dart/.travis.yml.twig b/templates/dart/.travis.yml.twig deleted file mode 100644 index 38001f43e..000000000 --- a/templates/dart/.travis.yml.twig +++ /dev/null @@ -1,24 +0,0 @@ -language: dart - -dart: stable - -os: linux - -install: -- mkdir -p ~/.config/dart -- | - cat < ~/.config/dart/pub-credentials.json - { - "accessToken":"$PUB_ACCESS_TOKEN", - "refreshToken":"$PUB_REFRESH_TOKEN", - "tokenEndpoint":"$PUB_TOKEN_EDNPOINT", - "scopes":["https://www.googleapis.com/auth/plus.me","https://www.googleapis.com/auth/userinfo.email"], - "expiration":$PUB_EXPIRATION - } - -deploy: - provider: script - skip_cleanup: true - script: dart format ./lib/ && dart pub publish -f - on: - tags: true \ No newline at end of file diff --git a/templates/dotnet/.github/workflows/publish.yml.twig b/templates/dotnet/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..509f5c655 --- /dev/null +++ b/templates/dotnet/.github/workflows/publish.yml.twig @@ -0,0 +1,31 @@ +name: Publish to RubyGems +on: + release: + types: [published] + +jobs: + publish: + name: Release build and publish + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6' + + - name: Install dependencies + run: dotnet restore + + - name: Build nuget + run: | + dotnet build -c Release + dotnet pack -c Release + + - name: Publish nuget + run: | + dotnet nuget push ./Appwrite/bin/Release/*.nupkg \ + --api-key {{ '${{ secrets.NUGET_TOKEN }}' }} \ + --source https://api.nuget.org/v3/index.json diff --git a/templates/dotnet/.travis.yml.twig b/templates/dotnet/.travis.yml.twig deleted file mode 100644 index c74d0a72e..000000000 --- a/templates/dotnet/.travis.yml.twig +++ /dev/null @@ -1,24 +0,0 @@ -language: csharp - -mono: none - -dotnet: 5.0 - -before_install: -- sudo apt-get -y install libpam0g-dev - -install: -- dotnet restore ./src - -script: -- dotnet build -c Release ./src - -before_deploy: -- dotnet pack -c Release ./src - -deploy: - skip_cleanup: true - provider: script - script: dotnet nuget push ./src/Appwrite/bin/Release/Appwrite.*.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json - on: - tags: true diff --git a/templates/dotnet/src/Appwrite.sln b/templates/dotnet/Package.sln similarity index 100% rename from templates/dotnet/src/Appwrite.sln rename to templates/dotnet/Package.sln diff --git a/templates/dotnet/src/Appwrite/Client.cs.twig b/templates/dotnet/Package/Client.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Client.cs.twig rename to templates/dotnet/Package/Client.cs.twig diff --git a/templates/dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig b/templates/dotnet/Package/Converters/ValueClassConverter.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig rename to templates/dotnet/Package/Converters/ValueClassConverter.cs.twig diff --git a/templates/dotnet/src/Appwrite/Enums/Enum.cs.twig b/templates/dotnet/Package/Enums/Enum.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Enums/Enum.cs.twig rename to templates/dotnet/Package/Enums/Enum.cs.twig diff --git a/templates/dotnet/src/Appwrite/Enums/IEnum.cs.twig b/templates/dotnet/Package/Enums/IEnum.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Enums/IEnum.cs.twig rename to templates/dotnet/Package/Enums/IEnum.cs.twig diff --git a/templates/dotnet/src/Appwrite/Exception.cs.twig b/templates/dotnet/Package/Exception.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Exception.cs.twig rename to templates/dotnet/Package/Exception.cs.twig diff --git a/templates/dotnet/src/Appwrite/Extensions/Extensions.cs.twig b/templates/dotnet/Package/Extensions/Extensions.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Extensions/Extensions.cs.twig rename to templates/dotnet/Package/Extensions/Extensions.cs.twig diff --git a/templates/dotnet/src/Appwrite/ID.cs.twig b/templates/dotnet/Package/ID.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/ID.cs.twig rename to templates/dotnet/Package/ID.cs.twig diff --git a/templates/dotnet/src/Appwrite/Models/InputFile.cs.twig b/templates/dotnet/Package/Models/InputFile.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Models/InputFile.cs.twig rename to templates/dotnet/Package/Models/InputFile.cs.twig diff --git a/templates/dotnet/src/Appwrite/Models/Model.cs.twig b/templates/dotnet/Package/Models/Model.cs.twig similarity index 92% rename from templates/dotnet/src/Appwrite/Models/Model.cs.twig rename to templates/dotnet/Package/Models/Model.cs.twig index fe3fc19e7..add819fe0 100644 --- a/templates/dotnet/src/Appwrite/Models/Model.cs.twig +++ b/templates/dotnet/Package/Models/Model.cs.twig @@ -40,7 +40,7 @@ namespace {{ spec.title | caseUcfirst }}.Models public static {{ definition.name | caseUcfirst | overrideIdentifier}} From(Dictionary map) => new {{ definition.name | caseUcfirst | overrideIdentifier}}( {%~ for property in definition.properties %} - {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject>>().Select(it => {{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: it)).ToList(){% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: ((JObject)map["{{ property.name }}"]).ToObject>()!){% endif %}{% else %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject<{{ property | typeName }}>(){% else %}{% if property.type == "integer" or property.type == "number" %}{% if not property.required %}map["{{ property.name }}"] == null ? null : {% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]){% else %}{% if property.type == "boolean" %}({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]{% else %}map{% if not property.required %}.TryGetValue("{{ property.name }}", out var {{ property.name }}) ? {{ property.name }}.ToString() : null{% else %}["{{ property.name }}"]{% if not property.required %}?{% endif %}.ToString(){% endif %}{% endif %}{% endif %}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject>>().Select(it => {{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: it)).ToList(){% else %}{{property.sub_schema | caseUcfirst | overrideIdentifier}}.From(map: ((JObject)map["{{ property.name }}"]).ToObject>()!){% endif %}{% else %}{% if property.type == 'array' %}((JArray)map["{{ property.name }}"]).ToObject<{{ property | typeName }}>(){% else %}{% if property.type == "integer" or property.type == "number" %}{% if not property.required %}map["{{ property.name }}"] == null ? null : {% endif %}Convert.To{% if property.type == "integer" %}Int64{% else %}Double{% endif %}(map["{{ property.name }}"]){% else %}{% if property.type == "boolean" %}({{ property | typeName }}{% if not property.required %}?{% endif %})map["{{ property.name }}"]{% else %}map{% if not property.required %}.TryGetValue("{{ property.name }}", out var {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}) ? {{ property.name | caseCamel | escapeKeyword | removeDollarSign }}.ToString() : null{% else %}["{{ property.name }}"]{% if not property.required %}?{% endif %}.ToString(){% endif %}{% endif %}{% endif %}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} {%~ endfor %} {%~ if definition.additionalProperties %} diff --git a/templates/dotnet/src/Appwrite/Models/OrderType.cs.twig b/templates/dotnet/Package/Models/OrderType.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Models/OrderType.cs.twig rename to templates/dotnet/Package/Models/OrderType.cs.twig diff --git a/templates/dotnet/src/Appwrite/Models/UploadProgress.cs.twig b/templates/dotnet/Package/Models/UploadProgress.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Models/UploadProgress.cs.twig rename to templates/dotnet/Package/Models/UploadProgress.cs.twig diff --git a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig b/templates/dotnet/Package/Package.csproj.twig similarity index 85% rename from templates/dotnet/src/Appwrite/Appwrite.csproj.twig rename to templates/dotnet/Package/Package.csproj.twig index 9d5539f32..0134308a5 100644 --- a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig +++ b/templates/dotnet/Package/Package.csproj.twig @@ -22,8 +22,8 @@ - - + + diff --git a/templates/dotnet/src/Appwrite/Permission.cs.twig b/templates/dotnet/Package/Permission.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Permission.cs.twig rename to templates/dotnet/Package/Permission.cs.twig diff --git a/templates/dotnet/src/Appwrite/Query.cs.twig b/templates/dotnet/Package/Query.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Query.cs.twig rename to templates/dotnet/Package/Query.cs.twig diff --git a/templates/dotnet/src/Appwrite/Role.cs.twig b/templates/dotnet/Package/Role.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Role.cs.twig rename to templates/dotnet/Package/Role.cs.twig diff --git a/templates/dotnet/src/Appwrite/Services/Service.cs.twig b/templates/dotnet/Package/Services/Service.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Services/Service.cs.twig rename to templates/dotnet/Package/Services/Service.cs.twig diff --git a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig b/templates/dotnet/Package/Services/ServiceTemplate.cs.twig similarity index 100% rename from templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig rename to templates/dotnet/Package/Services/ServiceTemplate.cs.twig diff --git a/templates/flutter/.github/workflows/format.yml.twig b/templates/flutter/.github/workflows/format.yml.twig new file mode 100644 index 000000000..ae216a5fc --- /dev/null +++ b/templates/flutter/.github/workflows/format.yml.twig @@ -0,0 +1,33 @@ +name: Format and push + +# Github action will require permission to write to repo +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + format: + runs-on: ubuntu-latest + container: + image: dart:stable + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: true + ref: ${{ '{{'}} github.event.pull_request.head.ref {{ '}}' }} + + - name: Format Dart code + run: dart format . + + - name: git config + run: git config --global --add safe.directory /__w/sdk-for-flutter/sdk-for-flutter # required to fix dubious ownership + + - name: Add & Commit + uses: EndBug/add-and-commit@v9.1.4 + with: + add: lib + diff --git a/templates/flutter/.github/workflows/publish.yml.twig b/templates/flutter/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..a8584faaa --- /dev/null +++ b/templates/flutter/.github/workflows/publish.yml.twig @@ -0,0 +1,14 @@ +name: Publish to pub.dev + +on: + push: + tags: + - '[0-9]+\.[0-9]+\.[0-9]+.*' + +jobs: + publish: + permissions: + id-token: write + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: pub.dev diff --git a/templates/flutter/.travis.yml.twig b/templates/flutter/.travis.yml.twig deleted file mode 100644 index 0e2d21508..000000000 --- a/templates/flutter/.travis.yml.twig +++ /dev/null @@ -1,29 +0,0 @@ -language: dart - -dart: stable - -os: linux - -install: -- cd ~ -- git clone https://github.com/flutter/flutter.git -b stable --depth 1 -- export PATH="$PATH:$(pwd)/flutter/bin/cache/dart-sdk/bin" -- export PATH="$PATH:$(pwd)/flutter/bin" -- flutter doctor -- mkdir -p ~/.config/dart -- | - cat < ~/.config/dart/pub-credentials.json - { - "accessToken":"$PUB_ACCESS_TOKEN", - "refreshToken":"$PUB_REFRESH_TOKEN", - "tokenEndpoint":"$PUB_TOKEN_EDNPOINT", - "scopes":["https://www.googleapis.com/auth/plus.me","https://www.googleapis.com/auth/userinfo.email"], - "expiration":$PUB_EXPIRATION - } - -deploy: - provider: script - skip_cleanup: true - script: cd $TRAVIS_BUILD_DIR && dart format ./lib/ && dart format ./test/ && flutter pub publish -f - on: - tags: true diff --git a/templates/node/.github/workflows/publish.yml.twig b/templates/node/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..3dda7aa9b --- /dev/null +++ b/templates/node/.github/workflows/publish.yml.twig @@ -0,0 +1,42 @@ +name: Publish to NPM + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # Setup Node.js environment + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + # Determine release tag based on the tag name + - name: Determine release tag + id: release_tag + run: | + if [[ "${{ '{{' }} github.ref {{ '}}' }}" == *"-rc"* ]] || [[ "${{ '{{' }} github.ref {{ '}}' }}" == *"-RC"* ]]; then + echo "tag=next" >> "$GITHUB_OUTPUT" + else + echo "tag=latest" >> "$GITHUB_OUTPUT" + fi + + # Install dependencies (if any) and build your project (if necessary) + - name: Install dependencies and build + run: | + npm install + npm run build + + # Publish to NPM with the appropriate tag + - name: Publish + run: npm publish --tag ${{ '{{' }} steps.release_tag.outputs.tag {{ '}}' }} + env: + NODE_AUTH_TOKEN: ${{ '{{' }} secrets.NPM_TOKEN {{ '}}' }} diff --git a/templates/node/.travis.yml.twig b/templates/node/.travis.yml.twig deleted file mode 100644 index 614269a65..000000000 --- a/templates/node/.travis.yml.twig +++ /dev/null @@ -1,32 +0,0 @@ -language: node_js -node_js: - - "16" - -jobs: - include: - - stage: NPM RC Release - if: tag =~ /-(rc|RC)/ - node_js: "16" - script: - - npm install - - npm run build - - echo "Deploying RC to NPM..." - deploy: - provider: npm - email: $NPM_EMAIL - api_key: $NPM_API_KEY - tag: next - - stage: NPM Release - if: not tag =~ /-(rc|RC)/ - node_js: "16" - script: - - npm install - - npm run build - - echo "Deploying to NPM..." - deploy: - provider: npm - email: $NPM_EMAIL - api_key: $NPM_API_KEY - skip_cleanup: true - on: - tags: true \ No newline at end of file diff --git a/templates/python/.github/workflows/publish.yml.twig b/templates/python/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..25660e35b --- /dev/null +++ b/templates/python/.github/workflows/publish.yml.twig @@ -0,0 +1,31 @@ +name: Publish to PyPI +on: + release: + types: [published] + +jobs: + publish: + name: Release build and publish + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + + - name: Build package + run: | + python -m pip install setuptools wheel build + python setup.py sdist bdist_wheel + + - name: Publish package + run: | + python -m pip install twine + python -m twine upload -r pypi dist/* + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: {{ '${{ secrets.PYPI_TOKEN }}' }} + TWINE_NON_INTERACTIVE: true diff --git a/templates/python/.travis.yml.twig b/templates/python/.travis.yml.twig deleted file mode 100644 index bf9a9dffd..000000000 --- a/templates/python/.travis.yml.twig +++ /dev/null @@ -1,18 +0,0 @@ -language: python - -dist: bionic - -python: - - "3.8" - -jobs: - include: - - stage: pypi release - python: "3.8" - script: echo "Deploying to pypi ..." - deploy: - provider: pypi - username: "__token__" - password: $PYPI_TOKEN - on: - tags: true \ No newline at end of file diff --git a/templates/ruby/.github/workflows/publish.yml.twig b/templates/ruby/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..3e8f6b8c5 --- /dev/null +++ b/templates/ruby/.github/workflows/publish.yml.twig @@ -0,0 +1,29 @@ +name: Publish to RubyGems +on: + release: + types: [published] + +jobs: + publish: + name: Release build and publish + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Set up RubyGems + run: | + cat "---\n:rubygems_api_key: {{ '${{ secrets.RUBYGEMS_TOKEN }}' }}\n" > ~/.gem/credentials + chmod 0600 ~/.gem/credentials + + - name: Build gem + run: gem build {{ spec.title | caseLower }}.gemspec + + - name: Publish gem + run: gem push {{ spec.title | caseLower }}-*.gem diff --git a/templates/ruby/.travis.yml.twig b/templates/ruby/.travis.yml.twig deleted file mode 100644 index d7a0fcb6d..000000000 --- a/templates/ruby/.travis.yml.twig +++ /dev/null @@ -1,16 +0,0 @@ -language: ruby - -rvm: - - 2.7 - -jobs: - include: - - stage: ruby gems release - rvm: "2.7" - script: echo "Deploying to ruby gems ..." - deploy: - provider: rubygems - api_key: $RUBYGEMS_TOKEN - gem: {{spec.title | caseLower | caseSnake}} - on: - tags: true \ No newline at end of file diff --git a/templates/swift/Sources/Models/RealtimeModels.swift.twig b/templates/swift/Sources/Models/RealtimeModels.swift.twig index 3a9c3e298..5129b4cd8 100644 --- a/templates/swift/Sources/Models/RealtimeModels.swift.twig +++ b/templates/swift/Sources/Models/RealtimeModels.swift.twig @@ -1,11 +1,15 @@ import Foundation public class RealtimeSubscription { - public var close: () -> Void + private var close: () async throws -> Void - init(close: @escaping () -> Void) { + init(close: @escaping () async throws-> Void) { self.close = close } + + public func close() async throws { + try await self.close() + } } public class RealtimeCallback { @@ -14,7 +18,7 @@ public class RealtimeCallback { init( for channels: Set, - and callback: @escaping (RealtimeResponseEvent) -> Void + with callback: @escaping (RealtimeResponseEvent) -> Void ) { self.channels = channels self.callback = callback diff --git a/templates/swift/Sources/Services/Realtime.swift.twig b/templates/swift/Sources/Services/Realtime.swift.twig index 543e1ffc8..7838fc9fa 100644 --- a/templates/swift/Sources/Services/Realtime.swift.twig +++ b/templates/swift/Sources/Services/Realtime.swift.twig @@ -7,24 +7,23 @@ open class Realtime : Service { private let TYPE_ERROR = "error" private let TYPE_EVENT = "event" - private let DEBOUNCE_MILLIS = 1 + private let DEBOUNCE_NANOS = 1_000_000 private var socketClient: WebSocketClient? = nil private var activeChannels = Set() private var activeSubscriptions = [Int: RealtimeCallback]() let connectSync = DispatchQueue(label: "ConnectSync") - let callbackSync = DispatchQueue(label: "CallbackSync") private var subCallDepth = 0 private var reconnectAttempts = 0 private var subscriptionsCounter = 0 private var reconnect = true - private func createSocket() { + private func createSocket() async throws { guard activeChannels.count > 0 else { reconnect = false - closeSocket() + try await closeSocket() return } @@ -38,17 +37,31 @@ open class Realtime : Service { if (socketClient != nil) { reconnect = false - closeSocket() - } else { - socketClient = WebSocketClient(url, tlsEnabled: !client.selfSigned, delegate: self)! + try await closeSocket() } - try! socketClient?.connect() + socketClient = WebSocketClient( + url, + tlsEnabled: !client.selfSigned, + delegate: self + ) + + try await socketClient?.connect() } - private func closeSocket() { - socketClient?.close() - //socket?.close(RealtimeCode.POLICY_VIOLATION.value, null) + private func closeSocket() async throws { + guard let client = socketClient, + let group = client.threadGroup else { + return + } + + if (client.isConnected) { + let promise = group.any().makePromise(of: Void.self) + client.close(promise: promise) + try await promise.futureResult.get() + } + + try await group.shutdownGracefully() } private func getTimeout() -> Int { @@ -63,8 +76,8 @@ open class Realtime : Service { public func subscribe( channel: String, callback: @escaping (RealtimeResponseEvent) -> Void - ) -> RealtimeSubscription { - return subscribe( + ) async throws -> RealtimeSubscription { + return try await subscribe( channels: [channel], payloadType: String.self, callback: callback @@ -74,8 +87,8 @@ open class Realtime : Service { public func subscribe( channels: Set, callback: @escaping (RealtimeResponseEvent) -> Void - ) -> RealtimeSubscription { - return subscribe( + ) async throws -> RealtimeSubscription { + return try await subscribe( channels: channels, payloadType: String.self, callback: callback @@ -86,8 +99,8 @@ open class Realtime : Service { channel: String, payloadType: T.Type, callback: @escaping (RealtimeResponseEvent) -> Void - ) -> RealtimeSubscription { - return subscribe( + ) async throws -> RealtimeSubscription { + return try await subscribe( channels: [channel], payloadType: T.self, callback: callback @@ -98,36 +111,38 @@ open class Realtime : Service { channels: Set, payloadType: T.Type, callback: @escaping (RealtimeResponseEvent) -> Void - ) -> RealtimeSubscription { + ) async throws -> RealtimeSubscription { subscriptionsCounter += 1 - let counter = subscriptionsCounter + + let count = subscriptionsCounter channels.forEach { activeChannels.insert($0) } - activeSubscriptions[counter] = RealtimeCallback( + activeSubscriptions[count] = RealtimeCallback( for: Set(channels), - and: callback + with: callback ) connectSync.sync { subCallDepth+=1 } - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(DEBOUNCE_MILLIS)) { - if (self.subCallDepth == 1) { - self.createSocket() - } - self.connectSync.sync { - self.subCallDepth-=1 - } + try await Task.sleep(nanoseconds: UInt64(DEBOUNCE_NANOS)) + + if self.subCallDepth == 1 { + try await self.createSocket() + } + + connectSync.sync { + self.subCallDepth -= 1 } return RealtimeSubscription { - self.activeSubscriptions[counter] = nil + self.activeSubscriptions[count] = nil self.cleanUp(channels: channels) - self.createSocket() + try await self.createSocket() } } @@ -163,7 +178,7 @@ extension Realtime: WebSocketClientDelegate { } } - public func onClose(channel: Channel, data: Data) { + public func onClose(channel: Channel, data: Data) async throws { if (!reconnect) { reconnect = true return @@ -173,10 +188,11 @@ extension Realtime: WebSocketClientDelegate { print("Realtime disconnected. Re-connecting in \(timeout / 1000) seconds.") - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(timeout)) { - self.reconnectAttempts += 1 - self.createSocket() - } + try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000)) + + self.reconnectAttempts += 1 + + try await self.createSocket() } public func onError(error: Swift.Error?, status: HTTPResponseStatus?) { @@ -188,16 +204,10 @@ extension Realtime: WebSocketClientDelegate { } func handleResponseEvent(from json: [String: Any]) { - guard let data = json["data"] as? [String: Any] else { - return - } - guard let channels = data["channels"] as? Array else { - return - } - guard let events = data["events"] as? Array else { - return - } - guard let payload = data["payload"] as? [String: Any] else { + guard let data = json["data"] as? [String: Any], + let channels = data["channels"] as? [String], + let events = data["events"] as? [String], + let payload = data["payload"] as? [String: Any] else { return } guard channels.contains(where: { channel in diff --git a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig index 7e8e26fa1..72322b784 100644 --- a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig +++ b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig @@ -7,6 +7,8 @@ import NIOFoundationCompat import NIOSSL public let WEBSOCKET_LOCKER_QUEUE = "SyncLocker" +public let WEBSOCKET_THREAD_QUEUE = "ThreadLocker" +public let WEBSOCKET_CHANNEL_QUEUE = "ChannelLocker" /// Creates and manages connections to a WebSocket server. /// @@ -20,16 +22,35 @@ public class WebSocketClient { let query: String let headers: HTTPHeaders let frameKey: String - + public private(set) var maxFrameSize: Int - - var channel: Channel? = nil + var tlsEnabled: Bool = false var closeSent: Bool = false - let locker = DispatchQueue(label: WEBSOCKET_LOCKER_QUEUE, qos: .background) + private let locker = DispatchQueue(label: WEBSOCKET_LOCKER_QUEUE, qos: .background) + private let channelQueue = DispatchQueue(label: WEBSOCKET_CHANNEL_QUEUE) + private let threadGroupQueue = DispatchQueue(label: WEBSOCKET_THREAD_QUEUE) - var threadGroup: MultiThreadedEventLoopGroup? = nil + var channel: Channel? { + get { + return channelQueue.sync { _channel } + } + set { + channelQueue.sync { _channel = newValue } + } + } + private var _channel: Channel? = nil + + var threadGroup: MultiThreadedEventLoopGroup? { + get { + return threadGroupQueue.sync { _threadGroup } + } + set { + threadGroupQueue.sync { _threadGroup = newValue } + } + } + private var _threadGroup: MultiThreadedEventLoopGroup? weak var delegate: WebSocketClientDelegate? = nil @@ -216,45 +237,45 @@ public class WebSocketClient { self.threadGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) } } - - deinit { - try! threadGroup!.syncShutdownGracefully() - } // MARK: - Open connection - + /// Open a connection to the configured host and attempt to upgrade the connection to a WebSocket. If successful the `onOpen` callback will fire, otherwise a connection error will be thrown from here. - public func connect() throws { + public func connect() async throws { let socketOptions = ChannelOptions.socket( SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT ) - while(threadGroup == nil) {} - + while(threadGroup == nil) { + try? await Task.sleep(nanoseconds: 10_000_000) + } + let bootstrap = ClientBootstrap(group: threadGroup!) .channelOption(socketOptions, value: 1) - .channelInitializer(self.openChannel) - - _ = try bootstrap - .connect(host: self.host, port: self.port) - .wait() + .channelInitializer { + self.openChannel(channel: $0) + } + + _ = try await bootstrap + .connect(host: self.host,port: self.port) + .get() } private func openChannel(channel: Channel) -> EventLoopFuture { let httpHandler = HTTPHandler(client: self, headers: headers) - + let basicUpgrader = NIOWebSocketClientUpgrader( requestKey: self.frameKey, upgradePipelineHandler: { channel, response in self.upgradePipelineHandler(channel: channel, response: response) } ) - + let config: NIOHTTPClientUpgradeConfiguration = (upgraders: [basicUpgrader], completionHandler: { context in context.channel.pipeline.removeHandler(httpHandler, promise: nil) }) - + return channel.pipeline.addHTTPClientHandlers(withClientUpgrade: config).flatMap { _ in return channel.pipeline.addHandler(httpHandler).flatMap { _ in if self.tlsEnabled { @@ -269,39 +290,43 @@ public class WebSocketClient { } } - @Sendable private func upgradePipelineHandler(channel: Channel, response: HTTPResponseHead) -> EventLoopFuture { + private func upgradePipelineHandler(channel: Channel, response: HTTPResponseHead) -> EventLoopFuture { let handler = MessageHandler(client: self) - + if response.status == .switchingProtocols { self.channel = channel } - + return channel.pipeline.addHandler(handler) } // MARK: - Close connection - + /// Closes the connection /// /// - parameters: /// - data: Close frame payload - public func close(data: Data = Data()) { + public func close( + data: Data = Data(), + promise: EventLoopPromise? = nil + ) { closeSent = true - + var buffer = ByteBufferAllocator() .buffer(capacity: data.count) - + buffer.writeBytes(data) - + send( data: buffer, opcode: .connectionClose, - finalFrame: true + finalFrame: true, + promise: promise ) } - + // MARK: - Send data - + /// Sends binary-formatted data to the connected server in multiple frames. /// /// - parameters: @@ -311,21 +336,23 @@ public class WebSocketClient { public func send( data: Data, opcode: WebSocketOpcode, - finalFrame: Bool = true + finalFrame: Bool = true, + promise: EventLoopPromise? = nil ) { var buffer = ByteBufferAllocator() .buffer(capacity: data.count) - + buffer.writeBytes(data) - + if opcode == .connectionClose { self.closeSent = true } - + send( data: buffer, opcode: opcode, - finalFrame: finalFrame + finalFrame: finalFrame, + promise: promise ) } @@ -338,21 +365,23 @@ public class WebSocketClient { public func send( text: String, opcode: WebSocketOpcode = .text, - finalFrame: Bool = true + finalFrame: Bool = true, + promise: EventLoopPromise? = nil ) { var buffer = ByteBufferAllocator() .buffer(capacity: text.count) - + buffer.writeString(text) - + send( data: buffer, opcode: opcode, - finalFrame: finalFrame + finalFrame: finalFrame, + promise: promise ) } - + /// Sends the JSON representation of the given model to the connected server in multiple frames. /// /// - parameters: @@ -362,7 +391,8 @@ public class WebSocketClient { public func send( model: T, opcode: WebSocketOpcode = .text, - finalFrame: Bool = true + finalFrame: Bool = true, + promise: EventLoopPromise? = nil ) { let jsonEncoder = JSONEncoder() do { @@ -370,13 +400,14 @@ public class WebSocketClient { let string = String(data: jsonData, encoding: .utf8)! var buffer = ByteBufferAllocator() .buffer(capacity: string.count) - + buffer.writeString(string) - + send( data: buffer, opcode: opcode, - finalFrame: finalFrame + finalFrame: finalFrame, + promise: promise ) } catch let error { print(error) @@ -392,7 +423,8 @@ public class WebSocketClient { public func send( data: ByteBuffer, opcode: WebSocketOpcode, - finalFrame: Bool + finalFrame: Bool, + promise: EventLoopPromise? = nil ) { let frame = WebSocketFrame( fin: finalFrame, @@ -400,17 +432,19 @@ public class WebSocketClient { maskKey: nil, data: data ) + guard let channel = channel else { return } + if finalFrame { - channel.writeAndFlush(frame, promise: nil) + channel.writeAndFlush(frame, promise: promise) } else { - channel.write(frame, promise: nil) + channel.write(frame, promise: promise) } - + if opcode == .connectionClose { - channel.close(mode: .all, promise: nil) + channel.close(mode: .all, promise: promise) } } } diff --git a/templates/swift/example-swiftui/Shared/ExampleView.swift b/templates/swift/example-swiftui/Shared/ExampleView.swift index 792f3b308..0c3d2de7a 100644 --- a/templates/swift/example-swiftui/Shared/ExampleView.swift +++ b/templates/swift/example-swiftui/Shared/ExampleView.swift @@ -20,6 +20,9 @@ struct ExampleView: View { TextField("", text: $viewModel.response, axis: .vertical) .padding() + TextField("", text: $viewModel.response2, axis: .vertical) + .padding() + Button("Login") { Task { await viewModel.login() } } @@ -41,7 +44,7 @@ struct ExampleView: View { } Button("Subscribe") { - viewModel.subscribe() + Task { await viewModel.subscribe() } } } #if os(macOS) diff --git a/templates/swift/example-swiftui/Shared/ExampleViewModel.swift b/templates/swift/example-swiftui/Shared/ExampleViewModel.swift index 817a9064f..3c6de090c 100644 --- a/templates/swift/example-swiftui/Shared/ExampleViewModel.swift +++ b/templates/swift/example-swiftui/Shared/ExampleViewModel.swift @@ -15,8 +15,10 @@ extension ExampleView { @Published public var fileId: String = "test" @Published public var databaseId: String = "test" @Published public var collectionId: String = "test" + @Published public var collectionId2: String = "test2" @Published public var isShowPhotoLibrary = false @Published public var response: String = "" + @Published public var response2: String = "" func register() async { do { @@ -127,13 +129,25 @@ extension ExampleView { } } } - - func subscribe() { - _ = realtime.subscribe(channels: ["databases.\(databaseId).collections.\(collectionId).documents"]) { event in + + func subscribe() async { + let sub1 = try? await realtime.subscribe(channels: ["databases.\(databaseId).collections.\(collectionId).documents"]) { event in DispatchQueue.main.async { self.response = String(describing: event.payload!) } } + + try? await Task.sleep(nanoseconds: UInt64(500_000_000)) + + _ = try? await realtime.subscribe(channels: ["databases.\(databaseId).collections.\(collectionId2).documents"]) { event in + DispatchQueue.main.async { + self.response2 = String(describing: event.payload!) + } + } + + try? await Task.sleep(nanoseconds: UInt64(500_000_000)) + + try? await sub1?.close() } } } diff --git a/templates/web/.github/workflows/publish.yml.twig b/templates/web/.github/workflows/publish.yml.twig new file mode 100644 index 000000000..3dda7aa9b --- /dev/null +++ b/templates/web/.github/workflows/publish.yml.twig @@ -0,0 +1,42 @@ +name: Publish to NPM + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # Setup Node.js environment + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + # Determine release tag based on the tag name + - name: Determine release tag + id: release_tag + run: | + if [[ "${{ '{{' }} github.ref {{ '}}' }}" == *"-rc"* ]] || [[ "${{ '{{' }} github.ref {{ '}}' }}" == *"-RC"* ]]; then + echo "tag=next" >> "$GITHUB_OUTPUT" + else + echo "tag=latest" >> "$GITHUB_OUTPUT" + fi + + # Install dependencies (if any) and build your project (if necessary) + - name: Install dependencies and build + run: | + npm install + npm run build + + # Publish to NPM with the appropriate tag + - name: Publish + run: npm publish --tag ${{ '{{' }} steps.release_tag.outputs.tag {{ '}}' }} + env: + NODE_AUTH_TOKEN: ${{ '{{' }} secrets.NPM_TOKEN {{ '}}' }} diff --git a/templates/web/.travis.yml.twig b/templates/web/.travis.yml.twig deleted file mode 100644 index 564159b38..000000000 --- a/templates/web/.travis.yml.twig +++ /dev/null @@ -1,32 +0,0 @@ -language: node_js -node_js: - - "14.16" - -jobs: - include: - - stage: NPM RC Release - if: tag =~ /-(rc|RC)/ - node_js: "14.16" - script: - - npm install - - npm run build - - echo "Deploying RC to NPM..." - deploy: - provider: npm - email: $NPM_EMAIL - api_key: $NPM_API_KEY - tag: next - - stage: NPM Release - if: not tag =~ /-(rc|RC)/ - node_js: "14.16" - script: - - npm install - - npm run build - - echo "Deploying to NPM..." - deploy: - provider: npm - email: $NPM_EMAIL - api_key: $NPM_API_KEY - skip_cleanup: true - on: - tags: true \ No newline at end of file diff --git a/tests/DotNet60Test.php b/tests/DotNet60Test.php index fe572d18d..c8833f802 100644 --- a/tests/DotNet60Test.php +++ b/tests/DotNet60Test.php @@ -12,12 +12,12 @@ class DotNet60Test extends Base protected string $language = 'dotnet'; protected string $class = 'Appwrite\SDK\Language\DotNet'; protected array $build = [ - 'mkdir -p tests/sdks/dotnet/src/test', - 'cp tests/languages/dotnet/Tests.cs tests/sdks/dotnet/src/test/Tests.cs', - 'cp tests/languages/dotnet/Tests60.csproj tests/sdks/dotnet/src/test/Tests.csproj', + 'mkdir -p tests/sdks/dotnet/test', + 'cp tests/languages/dotnet/Tests.cs tests/sdks/dotnet/test/Tests.cs', + 'cp tests/languages/dotnet/Tests60.csproj tests/sdks/dotnet/test/Tests.csproj', ]; protected string $command = - 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:6.0-alpine3.17 dotnet test --verbosity normal --framework net6.0'; + 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/test mcr.microsoft.com/dotnet/sdk:6.0-alpine3.17 dotnet test --verbosity normal --framework net6.0'; protected array $expectedOutput = [ ...Base::FOO_RESPONSES, diff --git a/tests/DotNet70Test.php b/tests/DotNet80Test.php similarity index 69% rename from tests/DotNet70Test.php rename to tests/DotNet80Test.php index 35c4a5696..52a01d4cc 100644 --- a/tests/DotNet70Test.php +++ b/tests/DotNet80Test.php @@ -2,7 +2,7 @@ namespace Tests; -class DotNet70Test extends Base +class DotNet80Test extends Base { protected string $sdkName = 'dotnet'; protected string $sdkPlatform = 'server'; @@ -12,12 +12,12 @@ class DotNet70Test extends Base protected string $language = 'dotnet'; protected string $class = 'Appwrite\SDK\Language\DotNet'; protected array $build = [ - 'mkdir -p tests/sdks/dotnet/src/test', - 'cp tests/languages/dotnet/Tests.cs tests/sdks/dotnet/src/test/Tests.cs', - 'cp tests/languages/dotnet/Tests70.csproj tests/sdks/dotnet/src/test/Tests.csproj', + 'mkdir -p tests/sdks/dotnet/test', + 'cp tests/languages/dotnet/Tests.cs tests/sdks/dotnet/test/Tests.cs', + 'cp tests/languages/dotnet/Tests80.csproj tests/sdks/dotnet/test/Tests.csproj', ]; protected string $command = - 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/src/test/ mcr.microsoft.com/dotnet/sdk:7.0-alpine3.17 dotnet test --verbosity normal --framework net7.0'; + 'docker run --network="mockapi" --rm -v $(pwd):/app -w /app/tests/sdks/dotnet/test mcr.microsoft.com/dotnet/sdk:8.0-alpine3.19 dotnet test --verbosity normal --framework net8.0'; protected array $expectedOutput = [ ...Base::FOO_RESPONSES, diff --git a/tests/languages/apple/Tests.swift b/tests/languages/apple/Tests.swift index 86dde4c46..b7c09c7e4 100644 --- a/tests/languages/apple/Tests.swift +++ b/tests/languages/apple/Tests.swift @@ -34,7 +34,7 @@ class Tests: XCTestCase { let expectation = XCTestExpectation(description: "realtime server") - realtime.subscribe(channels: ["tests"]) { message in + try await realtime.subscribe(channels: ["tests"]) { message in realtimeResponse = message.payload!["response"] as! String expectation.fulfill() } diff --git a/tests/languages/dotnet/Tests.cs b/tests/languages/dotnet/Tests.cs index 7007ac3bd..41fa28886 100644 --- a/tests/languages/dotnet/Tests.cs +++ b/tests/languages/dotnet/Tests.cs @@ -67,17 +67,17 @@ public async Task Test1() var result = await general.Redirect(); TestContext.WriteLine((result as Dictionary)["result"]); - mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../../resources/file.png")); + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../resources/file.png")); TestContext.WriteLine(mock.Result); - mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../../resources/large_file.mp4")); + mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromPath("../../../../../../resources/large_file.mp4")); TestContext.WriteLine(mock.Result); - var info = new FileInfo("../../../../../../../resources/file.png"); + var info = new FileInfo("../../../../../../resources/file.png"); mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromStream(info.OpenRead(), "file.png", "image/png")); TestContext.WriteLine(mock.Result); - info = new FileInfo("../../../../../../../resources/large_file.mp4"); + info = new FileInfo("../../../../../../resources/large_file.mp4"); mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromStream(info.OpenRead(), "large_file.mp4", "video/mp4")); TestContext.WriteLine(mock.Result); diff --git a/tests/languages/dotnet/Tests70.csproj b/tests/languages/dotnet/Tests80.csproj similarity index 91% rename from tests/languages/dotnet/Tests70.csproj rename to tests/languages/dotnet/Tests80.csproj index 392e78b36..3518e8986 100644 --- a/tests/languages/dotnet/Tests70.csproj +++ b/tests/languages/dotnet/Tests80.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 latest false