mirror of
https://github.com/appwrite/appwrite
synced 2026-05-06 06:48:22 +00:00
Merge branch '1.8.x' into feat-next-js-standalone
This commit is contained in:
commit
3197ebb655
28 changed files with 463 additions and 49 deletions
|
|
@ -60,7 +60,7 @@ return [
|
|||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '20.2.1',
|
||||
'version' => '20.2.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -79,7 +79,7 @@ return [
|
|||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '13.3.0',
|
||||
'version' => '13.3.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
|
|
@ -226,7 +226,7 @@ return [
|
|||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '10.2.3',
|
||||
'version' => '11.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
|
|
@ -300,7 +300,7 @@ return [
|
|||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '13.4.1',
|
||||
'version' => '13.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
|
|||
$subject = $locale->getText("emails.sessionAlert.subject");
|
||||
$preview = $locale->getText("emails.sessionAlert.preview");
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.sessionAlert-' . $locale->default] ?? [];
|
||||
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
|
||||
|
||||
$validator = new FileName();
|
||||
if (!$validator->isValid($smtpBaseTemplate)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path');
|
||||
}
|
||||
|
||||
$bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl';
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-session-alert.tpl');
|
||||
$message
|
||||
|
|
@ -157,12 +165,25 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
|
|||
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
|
||||
];
|
||||
|
||||
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
|
||||
$emailVariables = array_merge($emailVariables, [
|
||||
'accentColor' => APP_EMAIL_ACCENT_COLOR,
|
||||
'logoUrl' => APP_EMAIL_LOGO_URL,
|
||||
'twitterUrl' => APP_SOCIAL_TWITTER,
|
||||
'discordUrl' => APP_SOCIAL_DISCORD,
|
||||
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE,
|
||||
'termsUrl' => APP_EMAIL_TERMS_URL,
|
||||
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
|
||||
]);
|
||||
}
|
||||
|
||||
$email = $user->getAttribute('email');
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient($email)
|
||||
->trigger();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
appwrite migrations create-csv-export \
|
||||
--resource-id <ID1:ID2> \
|
||||
--bucket-id <BUCKET_ID> \
|
||||
--filename <FILENAME>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
appwrite migrations create-csv-import \
|
||||
--bucket-id <BUCKET_ID> \
|
||||
--file-id <FILE_ID> \
|
||||
--resource-id <ID1:ID2>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { Client, Migrations } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
const migrations = new Migrations(client);
|
||||
|
||||
const result = await migrations.createCSVExport({
|
||||
resourceId: '<ID1:ID2>',
|
||||
bucketId: '<BUCKET_ID>',
|
||||
filename: '<FILENAME>',
|
||||
columns: [], // optional
|
||||
queries: [], // optional
|
||||
delimiter: '<DELIMITER>', // optional
|
||||
enclosure: '<ENCLOSURE>', // optional
|
||||
escape: '<ESCAPE>', // optional
|
||||
header: false, // optional
|
||||
notify: false // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Client, Migrations } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
const migrations = new Migrations(client);
|
||||
|
||||
const result = await migrations.createCSVImport({
|
||||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
resourceId: '<ID1:ID2>',
|
||||
internalFile: false // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
|
|
@ -11,7 +11,7 @@ const result = await databases.createCollection({
|
|||
databaseId: '<DATABASE_ID>',
|
||||
collectionId: '<COLLECTION_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
documentSecurity: false, // optional
|
||||
enabled: false // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ const result = await databases.createDocument({
|
|||
"age": 30,
|
||||
"isAdmin": false
|
||||
},
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const result = await databases.updateCollection({
|
|||
databaseId: '<DATABASE_ID>',
|
||||
collectionId: '<COLLECTION_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
documentSecurity: false, // optional
|
||||
enabled: false // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ const result = await databases.updateDocument({
|
|||
collectionId: '<COLLECTION_ID>',
|
||||
documentId: '<DOCUMENT_ID>',
|
||||
data: {}, // optional
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ const result = await databases.upsertDocument({
|
|||
collectionId: '<COLLECTION_ID>',
|
||||
documentId: '<DOCUMENT_ID>',
|
||||
data: {},
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const storage = new sdk.Storage(client);
|
|||
const result = await storage.createBucket({
|
||||
bucketId: '<BUCKET_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
fileSecurity: false, // optional
|
||||
enabled: false, // optional
|
||||
maximumFileSize: 1, // optional
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ const result = await storage.createFile({
|
|||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
file: InputFile.fromPath('/path/to/file', 'filename'),
|
||||
permissions: ["read("any")"] // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())] // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const storage = new sdk.Storage(client);
|
|||
const result = await storage.updateBucket({
|
||||
bucketId: '<BUCKET_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
fileSecurity: false, // optional
|
||||
enabled: false, // optional
|
||||
maximumFileSize: 1, // optional
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ const result = await storage.updateFile({
|
|||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
name: '<NAME>', // optional
|
||||
permissions: ["read("any")"] // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())] // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ const result = await tablesDB.createRow({
|
|||
"age": 30,
|
||||
"isAdmin": false
|
||||
},
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const result = await tablesDB.createTable({
|
|||
databaseId: '<DATABASE_ID>',
|
||||
tableId: '<TABLE_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
rowSecurity: false, // optional
|
||||
enabled: false // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ const result = await tablesDB.updateRow({
|
|||
tableId: '<TABLE_ID>',
|
||||
rowId: '<ROW_ID>',
|
||||
data: {}, // optional
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const result = await tablesDB.updateTable({
|
|||
databaseId: '<DATABASE_ID>',
|
||||
tableId: '<TABLE_ID>',
|
||||
name: '<NAME>',
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
rowSecurity: false, // optional
|
||||
enabled: false // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ const result = await tablesDB.upsertRow({
|
|||
tableId: '<TABLE_ID>',
|
||||
rowId: '<ROW_ID>',
|
||||
data: {}, // optional
|
||||
permissions: ["read("any")"], // optional
|
||||
permissions: [sdk.Permission.read(sdk.Role.any())], // optional
|
||||
transactionId: '<TRANSACTION_ID>' // optional
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# Change Log
|
||||
|
||||
## 13.3.1
|
||||
|
||||
* Fix `onOpen` callback not being called when the websocket connection is established
|
||||
* Fix add missing `scheduled` value to `ExecutionStatus` enum
|
||||
|
||||
## 13.3.0
|
||||
|
||||
* Add `onOpen`, `onClose` and `onError` callbacks to `Realtime` service
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
# Change Log
|
||||
|
||||
## 11.0.0
|
||||
|
||||
* Rename `create-csv-migration` to `create-csv-import` command to create a CSV import of a collection/table
|
||||
* Add `create-csv-export` command to create a CSV export of a collection/table
|
||||
* Add `create-resend-provider` and `update-resend-provider` commands to create and update a Resend Email provider
|
||||
* Fix syncing of tables deleted locally during `push tables` command
|
||||
* Fix added push command support for cli spatial types
|
||||
* Fix attribute changing during push
|
||||
* Replace pkg with @yao-pkg/pkg in dependencies
|
||||
|
||||
## 10.2.3
|
||||
|
||||
* Fix `init tables` command not working
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Change Log
|
||||
|
||||
## 20.2.2
|
||||
|
||||
* Widen `device_info_plus` and `package_info_plus` dependencies to allow for newer versions for Android 15+ support
|
||||
* Fix `CHUNK_SIZE` constant to `chunkSize`
|
||||
* Fix missing `@override` annotation to `toMap` method in all model classes
|
||||
|
||||
## 20.2.1
|
||||
|
||||
* Add transaction support for Databases and TablesDB
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# Change Log
|
||||
|
||||
## 13.5.0
|
||||
|
||||
* Add `create_resend_provider` and `update_resend_provider` methods to `Messaging` service
|
||||
* Improve deprecation warnings
|
||||
* Fix adding `Optional[]` to optional parameters
|
||||
* Fix passing of `None` to nullable parameters
|
||||
|
||||
## 13.4.1
|
||||
|
||||
* Add transaction support for Databases and TablesDB
|
||||
|
|
|
|||
186
docs/tutorials/release-sdks.md
Normal file
186
docs/tutorials/release-sdks.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# Releasing Appwrite SDKs
|
||||
|
||||
This document is part of the Appwrite contributors' guide. Before you continue reading this document, make sure you have read the [Code of Conduct](https://github.com/appwrite/.github/blob/main/CODE_OF_CONDUCT.md) and the [Contributing Guide](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Agenda
|
||||
|
||||
This tutorial will cover how to properly release one or multiple Appwrite SDKs. The SDK release process involves updating the SDK generator, configuring Docker secrets, and running the release script.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before releasing SDKs, you need to:
|
||||
|
||||
1. **Release a new SDK generator version** - Create a PR in the [sdk-generator](https://github.com/appwrite/sdk-generator) repository with your respective sdk's changes. Wait for the PR to get merged and be released.
|
||||
|
||||
2. **Update the SDK generator dependency**
|
||||
- Update composer dependencies to use the new SDK generator version:
|
||||
```bash
|
||||
docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-scripts
|
||||
```
|
||||
|
||||
- Verify that `composer.lock` reflects the new SDK generator version
|
||||
|
||||
### Configure Docker Secrets
|
||||
|
||||
To enable SDK releases via GitHub, you need to mount SSH keys and configure GitHub authentication in your Docker environment.
|
||||
|
||||
#### Update Dockerfile
|
||||
|
||||
Add the following configuration to your `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
ARG GH_TOKEN
|
||||
ENV GH_TOKEN=your_github_token_here
|
||||
RUN git config --global user.email "your-email@example.com"
|
||||
RUN apk add --update --no-cache openssh-client github-cli
|
||||
```
|
||||
|
||||
Replace:
|
||||
- `your_github_token_here` with your GitHub personal access token (with appropriate permissions)
|
||||
- `your-email@example.com` with your Git email address
|
||||
|
||||
#### Update docker-compose.yml
|
||||
|
||||
Add the SSH key volume mount to the `appwrite` service in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
appwrite:
|
||||
volumes:
|
||||
- ~/.ssh:/root/.ssh
|
||||
# ... other volumes
|
||||
```
|
||||
|
||||
This mounts your SSH keys from the host machine, allowing the container to authenticate with GitHub.
|
||||
|
||||
### Updating Specs
|
||||
|
||||
The SDK generator script heavily relies on API specification files (specs). Whenever you are adding a new endpoint, updating parameters, or making any API changes, you need to update the specs.
|
||||
|
||||
Generate specs for the latest version:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite specs
|
||||
```
|
||||
|
||||
Also generate specs for the current stable Appwrite version:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite specs --version=1.8.x
|
||||
```
|
||||
|
||||
### Running the SDK Release Script
|
||||
|
||||
Before running the SDK release script, ensure you update the following for each SDK you plan to release:
|
||||
|
||||
1. **Update the changelog** - Add release notes to the SDK's `CHANGELOG.md` file (located in `docs/sdks/<sdk-name>/CHANGELOG.md`)
|
||||
2. **Bump the version** - Update the version number (patch, minor, or major) in `app/config/platforms.php`
|
||||
|
||||
Once you have completed these updates, run the SDK release script:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite sdks
|
||||
```
|
||||
|
||||
The script will prompt you for:
|
||||
1. **Platform** - Select client, server, console, or `*` for all platforms
|
||||
2. **SDK(s)** - Choose specific SDK(s) or `*` for all
|
||||
3. **Appwrite version** - Specify the version (e.g., `1.8.x`)
|
||||
4. **Git options** - Configure push settings and PR creation
|
||||
|
||||
#### Releasing Multiple SDKs
|
||||
|
||||
If you are releasing multiple SDKs across different platforms, you can specify them directly:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite sdks --sdks=dart,flutter,cli,python
|
||||
```
|
||||
|
||||
#### Pull Request Summary
|
||||
|
||||
After the script completes, you'll receive a summary of created pull requests:
|
||||
|
||||
```text
|
||||
Pull Request Summary
|
||||
Dart: https://github.com/appwrite/sdk-for-dart/pull/123
|
||||
Flutter: https://github.com/appwrite/sdk-for-flutter/pull/124
|
||||
CLI: https://github.com/appwrite/sdk-for-cli/pull/125
|
||||
```
|
||||
|
||||
### Creating GitHub Releases
|
||||
|
||||
> **Note:** This section is for Appwrite maintainers only.
|
||||
|
||||
After the PRs have been reviewed and merged by an Appwrite Lead, you can create GitHub releases automatically.
|
||||
|
||||
#### Dry Run
|
||||
|
||||
First, perform a dry run to preview the releases:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite sdks --release=yes
|
||||
```
|
||||
|
||||
This will display what releases would be created:
|
||||
|
||||
```text
|
||||
[DRY RUN] Would create release for Dart SDK:
|
||||
Repository: appwrite/sdk-for-dart
|
||||
Version: 13.0.0
|
||||
Title: 13.0.0
|
||||
Target Branch: main
|
||||
Previous Version: 12.0.2
|
||||
Release Notes:
|
||||
## What's Changed
|
||||
- Added support for new Users API endpoints
|
||||
- Fixed authentication token handling
|
||||
- Updated dependencies
|
||||
```
|
||||
|
||||
#### Execute Release
|
||||
|
||||
After verifying the dry run output, create the actual releases:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite sdks --release=yes --commit=yes
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Configuration Files
|
||||
|
||||
SDK configurations are defined in the following files:
|
||||
|
||||
- **`app/config/platforms.php`** - Platform and SDK definitions, including metadata, Git repository URLs, versions, and enabled/disabled status
|
||||
- **`src/Appwrite/Platform/Tasks/SDKs.php`** - SDK generation and release logic
|
||||
- **`docs/sdks/<sdk-name>/CHANGELOG.md`** - Changelog files for each SDK
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
If you encounter authentication problems:
|
||||
- **GitHub token** - Verify your token has the correct permissions (repo access, workflow permissions)
|
||||
- **SSH keys** - Ensure your SSH keys are properly configured in `~/.ssh/` and added to your GitHub account
|
||||
- **Git configuration** - Check that the Git email in the Dockerfile matches your GitHub account
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **"Release already exists"** - The script automatically skips releases that already exist for the specified version
|
||||
- **"No changes detected"** - Ensure you've updated the specs and that there are actual API changes to generate
|
||||
- **Permission denied** - Verify that your GitHub token and SSH keys have write access to the SDK repositories
|
||||
|
||||
## Summary
|
||||
|
||||
Congrats! You've successfully learned how to release Appwrite SDKs. Remember to:
|
||||
|
||||
1. Update SDK generator and run `composer update`
|
||||
2. Configure Docker secrets (GitHub token and SSH keys)
|
||||
3. Update specs for both latest and stable versions
|
||||
4. Update changelogs and bump versions in `platforms.php`
|
||||
5. Run the SDK script and create PRs
|
||||
6. (Maintainers only) Create GitHub releases after PR approval
|
||||
|
||||
Happy releasing! 🎉
|
||||
|
|
@ -48,13 +48,18 @@ class SDKs extends Action
|
|||
->param('message', null, new Nullable(new Text(256)), 'Commit Message', optional: true)
|
||||
->param('release', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we create releases?', optional: true)
|
||||
->param('commit', null, new Nullable(new WhiteList(['yes', 'no'])), 'Actually create releases (yes) or dry-run (no)?', optional: true)
|
||||
->param('sdks', null, new Nullable(new Text(256)), 'Selected SDKs', optional: true)
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message, ?string $release, ?string $commit): void
|
||||
public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message, ?string $release, ?string $commit, ?string $sdks): void
|
||||
{
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):');
|
||||
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
if (!$sdks) {
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):');
|
||||
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
} else {
|
||||
$sdks = explode(',', $sdks);
|
||||
}
|
||||
$version ??= Console::confirm('Choose an Appwrite version');
|
||||
|
||||
$createRelease = ($release === 'yes');
|
||||
|
|
@ -104,12 +109,12 @@ class SDKs extends Action
|
|||
|
||||
$platforms = Config::getParam('platforms');
|
||||
foreach ($platforms as $key => $platform) {
|
||||
if ($selectedPlatform !== $key && $selectedPlatform !== '*') {
|
||||
if ($selectedPlatform !== $key && $selectedPlatform !== '*' && ($sdks === null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($platform['sdks'] as $language) {
|
||||
if ($selectedSDK !== $language['key'] && $selectedSDK !== '*') {
|
||||
if ($selectedSDK !== $language['key'] && $selectedSDK !== '*' && ($sdks === null || !\in_array($language['key'], $sdks))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -472,38 +477,60 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
$errorMessage = implode("\n", $prOutput);
|
||||
if (strpos($errorMessage, 'already exists') !== false) {
|
||||
Console::warning("Pull request already exists for {$language['name']} SDK, updating title and body...");
|
||||
|
||||
$updateCommand = 'cd ' . $target . ' && \
|
||||
gh pr edit "' . $gitBranch . '" \
|
||||
$prNumberCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo "' . $repoName . '" \
|
||||
--title "' . $prTitle . '" \
|
||||
--body "' . $prBody . '" \
|
||||
--head "' . $gitBranch . '" \
|
||||
--json number \
|
||||
--jq ".[0].number" \
|
||||
2>&1';
|
||||
|
||||
$updateOutput = [];
|
||||
$updateReturnCode = 0;
|
||||
\exec($updateCommand, $updateOutput, $updateReturnCode);
|
||||
$prNumberOutput = [];
|
||||
$prNumberReturnCode = 0;
|
||||
\exec($prNumberCommand, $prNumberOutput, $prNumberReturnCode);
|
||||
|
||||
if ($updateReturnCode === 0) {
|
||||
Console::success("Successfully updated pull request for {$language['name']} SDK");
|
||||
if ($prNumberReturnCode === 0 && !empty($prNumberOutput[0])) {
|
||||
$prNumber = trim($prNumberOutput[0]);
|
||||
|
||||
$prUrlCommand = 'cd ' . $target . ' && \
|
||||
gh pr view "' . $gitBranch . '" \
|
||||
--repo "' . $repoName . '" \
|
||||
--json url \
|
||||
--jq .url \
|
||||
// Use API directly to update PR to avoid deprecated projectCards field
|
||||
$updateCommand = 'cd ' . $target . ' && \
|
||||
gh api \
|
||||
--method PATCH \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/' . $repoName . '/pulls/' . $prNumber . ' \
|
||||
-f title="' . $prTitle . '" \
|
||||
-f body="' . $prBody . '" \
|
||||
2>&1';
|
||||
|
||||
$prUrlOutput = [];
|
||||
$prUrlReturnCode = 0;
|
||||
\exec($prUrlCommand, $prUrlOutput, $prUrlReturnCode);
|
||||
$updateOutput = [];
|
||||
$updateReturnCode = 0;
|
||||
\exec($updateCommand, $updateOutput, $updateReturnCode);
|
||||
|
||||
if ($prUrlReturnCode === 0 && !empty($prUrlOutput)) {
|
||||
$prUrls[$language['name']] = $prUrlOutput[0];
|
||||
if ($updateReturnCode === 0) {
|
||||
Console::success("Successfully updated pull request for {$language['name']} SDK");
|
||||
|
||||
$prUrlCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo "' . $repoName . '" \
|
||||
--head "' . $gitBranch . '" \
|
||||
--json url \
|
||||
--jq ".[0].url" \
|
||||
2>&1';
|
||||
|
||||
$prUrlOutput = [];
|
||||
$prUrlReturnCode = 0;
|
||||
\exec($prUrlCommand, $prUrlOutput, $prUrlReturnCode);
|
||||
|
||||
if ($prUrlReturnCode === 0 && !empty($prUrlOutput)) {
|
||||
$prUrls[$language['name']] = trim($prUrlOutput[0]);
|
||||
}
|
||||
} else {
|
||||
$updateErrorMessage = implode("\n", $updateOutput);
|
||||
Console::error("Failed to update pull request for {$language['name']} SDK: " . $updateErrorMessage);
|
||||
}
|
||||
} else {
|
||||
$updateErrorMessage = implode("\n", $updateOutput);
|
||||
Console::error("Failed to update pull request for {$language['name']} SDK: " . $updateErrorMessage);
|
||||
Console::error("Failed to get PR number for {$language['name']} SDK");
|
||||
}
|
||||
} else {
|
||||
Console::error("Failed to create pull request for {$language['name']} SDK: " . $errorMessage);
|
||||
|
|
|
|||
|
|
@ -88,4 +88,112 @@ class AccountConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals($response['headers']['status-code'], 204);
|
||||
}
|
||||
|
||||
public function testSessionAlert(): void
|
||||
{
|
||||
$email = uniqid() . 'session-alert@appwrite.io';
|
||||
$password = 'password123';
|
||||
$name = 'Session Alert Tester';
|
||||
|
||||
// Create a new account
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-dev-key' => $this->getProject()['devKey'] ?? ''
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Create first session for the new account
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Create second session for the new account
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
|
||||
// Check the alert email
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
$this->assertEquals($email, $lastEmail['to'][0]['address']);
|
||||
$this->assertStringContainsString('Security alert: new session', $lastEmail['subject']);
|
||||
$this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
|
||||
$this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
|
||||
$this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name
|
||||
$this->assertStringContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']);
|
||||
|
||||
// Verify no alert sent in OTP login
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'otpuser2@appwrite.io'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['$createdAt']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
$this->assertNotEmpty($response['body']['expire']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEmpty($response['body']['phrase']);
|
||||
$this->assertStringContainsStringIgnoringCase('New login detected on '. $this->getProject()['name'], $lastEmail['text']);
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
$this->assertEquals('otpuser2@appwrite.io', $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('OTP for ' . $this->getProject()['name'] . ' Login', $lastEmail['subject']);
|
||||
|
||||
// Find 6 concurrent digits in email text - OTP
|
||||
preg_match_all("/\b\d{6}\b/", $lastEmail['text'], $matches);
|
||||
$code = ($matches[0] ?? [])[0] ?? '';
|
||||
|
||||
$this->assertNotEmpty($code);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => $userId,
|
||||
'secret' => $code
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($userId, $response['body']['userId']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['expire']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
|
||||
$lastEmailId = $lastEmail['id'];
|
||||
$lastEmail = $this->getLastEmail();
|
||||
$this->assertEquals($lastEmailId, $lastEmail['id']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1368,10 +1368,7 @@ class AccountCustomClientTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateAccountSession
|
||||
*/
|
||||
public function testSessionAlert($data): void
|
||||
public function testSessionAlert(): void
|
||||
{
|
||||
$email = uniqid() . 'session-alert@appwrite.io';
|
||||
$password = 'password123';
|
||||
|
|
@ -1437,6 +1434,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
|
||||
$this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
|
||||
$this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name
|
||||
$this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']);
|
||||
|
||||
// Verify no alert sent in OTP login
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/tokens/email', array_merge([
|
||||
|
|
|
|||
Loading…
Reference in a new issue