๐Ÿ‡ฎ๐Ÿ‡น
Account created on 7 May 2011, about 13 years ago
#

Merge Requests

More

Recent comments

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

D9 and Sophron 1.x are both EOL.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

2.1.x is D11 compatible.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

FYI currently two projects I maintain, Imagemagick and File Metadata Manager, are running tests with this setup

variables:
  _PHPUNIT_CONCURRENT: '0'
  _PHPUNIT_EXTRA: '-c $CI_PROJECT_DIR/$_WEB_ROOT/core --testdox --colors=always'
  OPT_IN_TEST_PREVIOUS_MINOR: '0'
  OPT_IN_TEST_NEXT_MINOR: '1'
  OPT_IN_TEST_NEXT_MAJOR: '1'

phpunit:
  extends: .phpunit-base
  variables:
    SYMFONY_DEPRECATIONS_HELPER: "ignoreFile=$CI_PROJECT_DIR/$_WEB_ROOT/core/.deprecation-ignore.txt"

and results are really nice, for example

PHPUnit 9.6.19 by Sebastian Bergmann and contributors.

Testing /builds/project/file_mdm/web/modules/custom/file_mdm

File Metadata Exif (Drupal\Tests\file_mdm_exif\Kernel\FileMetadataExif)
 โœ” Exif plugin
 โœ” Jpeg exif save to file
 โœ” Tiff exif save to file

File Metadata Font (Drupal\Tests\file_mdm_font\Kernel\FileMetadataFont)
 โœ” Font plugin
 โœ” Supported keys

Settings Form (Drupal\Tests\file_mdm\Functional\SettingsForm)
 โœ” Missing file logging

File Metadata Manager (Drupal\Tests\file_mdm\Kernel\FileMetadataManager)
 โœ” File metadata
 โœ” File metadata caching
 โœ” Remote file set local path
 โœ” Remote file copy
 โœ” Sanitized uri

Time: 00:09.371, Memory: 6.00 MB

OK (11 tests, 436 assertions)
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

As spotted over in #3417066-120: Upgrade PHPUnit to 10, drop Symfony PHPUnit-bridge dependency โ†’ , we can't mix PHPUnit attributes and annotations, we will have to convert them all at once unfortunately. So we need to proceed by group of files and not by annotation.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Can I ask, please, to get a review of the two options and guidance on which one (or others) to follow? Not very keen in keeeping two alternative approaches rebased just to keep the NR; the second option would require rebasing anytime a dependency is bumped.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

MR!8054 uses PHP reflection.

MR!8125 implements #7, uses BetterReflection (static reflection, no class loading in memory), and requires downgrading nikic/php-parser to 4, for the reasons discussed in ๐Ÿ“Œ Downgrade (temporarily) nikic/php-parser to ^4 Closed: won't fix .

We need to decide on which horse to bet.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

FWIW, I am pretty close to have an alternative implementation using PHPStan's BetterReflection.

This would have the benefit of not loading all test classes via PHP's Reflection.

But two drawbacks:
1) it will be much slower
2) it would require, at least for now, ๐Ÿ“Œ Downgrade (temporarily) nikic/php-parser to ^4 Closed: won't fix

I do not think that's worth doing. In the future, I'd suggest to switch TestDiscovery to get the test list by executing PHPUnit's CLI with the --list-tests-xml option and parsing the resulting XML. That would effectively mean delegating 'how' test classes are discovered entirely to PHPUnit, in a separate process so that PHP's own allocated memory allocated would not be a constraint (if it is right now, today, I doubt anyway).

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Adding _PHPUNIT_EXTRA: '-c $CI_PROJECT_DIR/$_WEB_ROOT/core' works. This enforces PHPUnit to read core's configuration phpunit.dist.xml, which itself differs per version so it's accurate to the context. See PoC in ๐Ÿ“Œ [CI] PHPUnit CLI testing Active .

This would mean:

- for D10 (=PHPUnit 9)

  • specifiying the --printer in the cmd line arguments is redundant, the entry is in the xml already
  • the listener is already in the xml

- for D11 (=PHPUnit 10+)

  • ignoreSuppressionOfDeprecations is already in the xml
  • --fail-on-deprecation is needed, since it's not in the xml (but if you do, alas, PHPUnit 9 fails...)
  • reporting in testdox format is not colliding anymore with the HTML output logger

Obviously one can have its own phpunit.xml in the contrib root and map the -c cmd line argument to it, and do whatever needed according to PHPUnit docs https://docs.phpunit.de/en/11.0/configuration.html. But then it's needed at least two xmls, one for PHPUnit 9 and one for PHPUnit 10, so the value in _PHPUNIT_EXTRA needs to be variable to $PHPUNIT_VERSION (is that possible?)

Setting to NW because I feel we need some more thinking here.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ“Œ | Sophron | Map update
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ“Œ | Sophron | Map update
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Whoa. This is harder than I thought. Looks like phpunit.xml MUST exist, both in D10 and in D11, to get deprecations reported when running PHPUnit straight. In D10 it must include the listener, in D11 the ignoreSuppressionOfDeprecations attribute in the source section. That means version dependent phpunit.xml. Will try on a module and report back here.

Using _PHPUNIT_CONCURRENCY itโ€™s much simplerโ€ฆ you turn it on and run-tests.sh takes care of all the sherpa work.

๐Ÿ“Œ | Sophron | Map update
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Looks good to me, thanks - I'll leave RTBC to someone that sees this from the outside, I'm too much an insider here :)

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

#18 this issue is about moving AWAY from ::commitAllOnShutdown() (and in general from committing during object destruction which has drawbacks).

IMHO, ::commitAllOnShutdown() should only be transitional towards such goal.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

MR merged with ๐Ÿ“Œ TestDiscovery expects @group annotations, fails with #[Group()] attributes Active to check with TestDiscovery suppoorting #[Group] attributes.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Tests are green now.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

I think the --process-isolation argument can be removed, too, since the final solution in core was to enable process isolation on the base test classes. Maybe this could go in docs as the --fail-on-deprecation argument, but should not be necessary unless in edge cases, truly.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

@fgm see this for the template adjustments: #3444792: Prepare for PHPUnit 10 โ†’ .

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Tests need fixing, but I think the concept is reviewable.

Getting rid of custom code and just relying on PHPUnit to discover tests is a bit too far and actually PHPUnit itself only recently changed format for --list-tests-xml output, and modified test list filtering options, in PHPUnit 11.1. So better wait to be on that before trying it.

For now just extended TestDiscovery to look into attributes of test classes, falling back to annotations if they do not exist (like PHPUnit itself is doing).

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

The @medium annotations can no longer be associated to methods, as attributes.
Only to classes.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

There are just few more outside of Drupal/Tests, we can do them all.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

I agree with #9 btw, and think in core we should just remove the annotations, and document the possible double standard for contrib.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Note sure about #11. It doesn't seem to be an issue in the project I referenced. Maybe if the class is missing yes, me might have a problem with PHPStan reporting it, but runtime the attribute is just handled like a comment?

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

AFAICS we cannot ignore deprecations thrown by PHPUnit itself, anymore. PHPUnit is not using trigger_error for deprecations, but it's own event system. But we could apparently have both annotations and attributes and being compatible with PHPUnit all the way from 9 to 11 - see for instance https://github.com/FileEye/MimeMap/blob/master/tests/src/TypeTest.php#L7... in a separate project. AFAICS PHPUnit will throw a deprecation when no attributes exists AND annotation exist; if the test has attributes, PHPUnit 11 will rely on those and not check about existing annotations.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Given test results, the only big thing to get is onto PHPUnit 11 is only the replacement of test annotations with PHP attributes, that throw thousands of deprecations. Actual errors and failures are just a few (functional ones seem actually error-free)

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Note that the Performance Test is throwing deprecations, even if the setup is such that the test job does not fail. That's because the deprecation handler that replaced Symfony's in PHPUnit 10 is stricter with deprecations thrown by the DebugClassloader. In this case it's the OpenTelemetry classes that are not typehinted.

PHPUnit 10.5.20 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.6
Configuration: /builds/project/drupal/core/phpunit.xml.dist

DDDDDDDD                                                            8 / 8 (100%)

HTML output was generated, 49 page(s).

Time: 12:00.036, Memory: 186.00 MB

8 tests triggered 8 deprecations:

1) /builds/project/drupal/vendor/symfony/error-handler/DebugClassLoader.php:341
Method "ArrayAccess::offsetGet()" might add "mixed" as a native return type declaration in the future. Do the same in implementation "OpenTelemetry\SDK\Common\Attribute\AttributesBuilder" now to avoid errors or add an explicit @return annotation to suppress this message.
Triggered by:
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryAuthenticatedPerformanceTest::testFrontPageAuthenticatedWarmCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php:32
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformanceTest::testFrontPageColdCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php:26
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformanceTest::testFrontPageCoolCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php:79
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformanceTest::testFrontPageHotCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php:43
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformanceTest::testNodePageColdCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:26
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformanceTest::testNodePageCoolCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:67
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformanceTest::testNodePageHotCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:43
* Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformanceTest::testNodePageWarmCache
  /builds/project/drupal/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:85
2) ....

In this case if this were a test run via run-tests.sh, it would fail. But here it's run via PHPUnit CLI without the --fail-on-deprecation command line argument, so deprecations are displayed but do not lead to test failure. If desirable, the indirect deprecation can be silenced like in https://git.drupalcode.org/project/drupal/-/blob/11.x/core/.deprecation-...

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

#11 is this won't fix then? RTBC to get a core committer decide.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

mondrake โ†’ created an issue.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Note that WARNINGS and DEPRECATIONS in PHPUnit 10 do make the test job fail, but are not reported in the GitLab CI test summary.
You need to look at the raw log of the job failure to get details of the failure.

For example, I see in the MR failure here

PHPUnit 10.5.20 by Sebastian
Bergmann and contributors.

Runtime: PHP 8.3.6
Configuration: /builds/issue/drupal-3446164/core/phpunit.xml.dist

W 1 / 1
(100%)

HTML output was generated, 305 page(s).

Time: 01:06.504, Memory: 10.00 MB

1 test triggered 1 PHP warning:

1) /builds/issue/drupal-3446164/core/includes/install.core.inc:1805
Undefined array key "major"

Triggered by:

*
Drupal\FunctionalTests\Asset\AssetOptimizationTestUmami::testAssetAggregation

/builds/issue/drupal-3446164/core/tests/Drupal/FunctionalTests/Asset/AssetOptimizationTest.php:37

OK, but there were issues!
Tests: 1, Assertions: 240, Warnings: 1.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Is it possible to have a link to a failing pipeline? What is actually failing?

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

As noted over in #3417066-120: Upgrade PHPUnit to 10, drop Symfony PHPUnit-bridge dependency โ†’ , we can't mix PHPUnit attributes and annotations, we will have to convert them all at once.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

#6 - ouch that's painful.. however we could keep annotations as well as attributes in the same test file, so for BC to have PHPUnit 9 compatiblity (using annotations) and PHPUnit 10/11 compatibility (using attributes) at the same time. See for example these tests in a library I maintain on GitHub: https://github.com/FileEye/MimeMap/blob/ba0b04e179976e7d6a487fdb166c5f1f...

However, we can't proceed by annotation. Do we need an issue to build a comprehensive Rector that would address all annotations and then use that to proceed by test suite/core-module?

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Some comments inline.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Here I would like to check what is the error handler at that point: if itโ€™s already set to PHPUnitโ€™s one we probably do not need to create a nee instance.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Re-added setRunTestInSeparateProcess(TRUE) in base classes.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Started a section in the IS with a list of things to request upstream

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Isolation is a hard requirement for our kernel and functional tests.

How about adding a PHPStan rule checking the attribute exists on those tests?

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Great find in #112 BTW!

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

I am scared about doing that in the extended constructor - being marked @internal by PHPUnit I can see troubles in the future. But if thatโ€™s the way to get through, fine for now I suppose.

I could also argue that adding the attribute now to all tests would be a sort of a baseline vs D9 where all tests were run in isolation. Doing that and leaving additions to be thoughtfully marked with the attribute (or not), would drive focused attention by developers. They would have options: add the attribute to the test, or add the command line option when running PHPUnit, or even add the configuration in the XML file if they have their own. I am not sure we need to baby sit here.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Now FunctionalTestDebugHtmlOutputTest passes, so hopefully we solved the problem. At this stage, I would suggest to do ๐Ÿ“Œ Add #[RunTestsInSeparateProcesses] attribute to all Kernel and Functional tests Needs review first, then come back to this and hopefully see all the process isolation issue solved purely with test attributes.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Per #102, I think it's right we should use a file instead of the static variable, which BTW was the original implementation from the removed listener. I had to go back a bit to find out. On that.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

So my hypothesis in #107 is wrong. The HTML logger works on the subprocess but is not reported by the main process, per #102. Still a mystery for me why the test was passing earlier.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Will revert a bit of the last changes and try to see if the logger works.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

I have an hypotesis i.e. that the subprocess run by PHPUnit shares static info with the parent. This is different from run-tests.sh, whose process isolation is at OS level i.e. separate *nix process. That would explain why i.e. deprecation bridge works for Kernel tests. In that case, since the HTML logger is a singleton, the subprocess would accumulate links in the single instance even if opened by the parent. Somehow this works though only if the process isolation is set before (or separately) the TestCase construction. We would likely not have a public setRunTestInSeparateProcess() method if it was possible to do stuff just in the constructor, there is likely some other config that we are missing from the case of the annotated/attributed method.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Only manual changes:

  • one addition to PHPStan ignoreErrors, to be removed once PHPUnit 10 is in
  • fixes for two functional tests that were hardcoding the line number of their scripts in a check
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Given ๐Ÿ“Œ Add #[RunTestsInSeparateProcesses] attribute to all Kernel and Functional tests Needs review is almost green, we might do that first (it touches 3200+ files!), and then come back here. That one takes away the need of both the CLI flag, AND of the hacking of process isolation in the constructor.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Uploading latest version of the rector script

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

So, chicken and egg - we can't do this until we have PHPUnit 10 in place, otherwise PHPStan complains about the fact that the attribute class does not exist.

๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Attached file is the Rector config file (rector.php) that conveniently has a Rule class to do what is in the MR.

To run this:

  • install rector via composer require -dev rector/rector
  • run rector with the config file via vendor/bin/rector
  • run PHPCBF via vendor/bin/phpcbf --filter=GitModified --standard=SlevomatCodingStandard --sniffs=SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses core (this reorders alphabetically the use imports for cleanliness
  • your diff will be what to put in the MR
๐Ÿ‡ฎ๐Ÿ‡นItaly mondrake ๐Ÿ‡ฎ๐Ÿ‡น

Yeahโ€ฆ wonder why the test was passing before the change thenโ€ฆ

Production build 0.69.0 2024