Memor-i is a memory card game for blind people. Memor-i Studio is the online repository where people create their own Memor-i games: they upload card images and sounds, an administrator builds and approves each game, and the Memor-i desktop game downloads the published ones.
- Tech stack
- Requirements
- Getting started
- Tests
- Code quality
- Continuous integration
- Releasing
- Deployment
- License
- Laravel 12 on PHP 8.4, MySQL 8.0
- Vite 7 with jQuery 3.7, Bootstrap 3.4 and the "Pleasure" admin theme (
resources/assets) - Node 24 (version in
.nvmrc), npm with supply-chain settings in.npmrc - External tools on the server: ffmpeg (audio), ImageMagick (game icons), zip/unzip (game packs), and the Inno Setup exe builder service for Windows installers
- DDEV with Docker. DDEV provides PHP 8.4, MySQL 8.0, Node (version from
.nvmrc), Composer, ImageMagick and ffmpeg.
-
Clone the repository and start DDEV:
git clone https://github.com/scify/Memor-i-Studio.git cd Memor-i-Studio ddev startEvery
ddev startrunscomposer setupand then the database migrations.composer setupcreates.envfrom.env.example(only if.envis missing), installs the PHP dependencies and, whenpackage-lock.jsonchanged, the npm dependencies (npm ci), generatesAPP_KEY(only if it is empty) and builds the frontend. It is safe to run again at any time, also by hand:ddev composer setup. DDEV writes the database andAPP_URLsettings into.env. -
Edit
.env:DEFAULT_ADMIN_USER_PASSWORD_FOR_SEED: the password of the admin users that the seeders create (required fordb:seed; it has no default)- the external services if you need them (
WINDOWS_SETUP_SERVICE_URL, Sentry, Firebase). DDEV catches all mail in Mailpit (ddev mailpit).
-
Create the starter data:
ddev artisan db:seed
-
For frontend work, start the Vite dev server (hot reload through port 5176):
ddev npm run dev
ddev composer setup(and everyddev start) builds the assets for production withnpm run build.VITE_*values in.envare read at build time, so set them before you build.
| Task | Command |
|---|---|
| Start or stop the environment | ddev start, ddev stop |
| Run Artisan, Composer or npm in the container | ddev artisan ..., ddev composer ..., ddev npm ... |
| Open a MySQL shell | ddev mysql |
| Open Mailpit (all outgoing mail) | ddev mailpit |
| Show URLs and services | ddev describe |
.npmrc hardens npm for this project. CI checks these settings:
ignore-scripts=true: npm does not run install scripts of dependenciesmin-release-age=7: npm installs only package versions that are at least 7 days oldengine-strict=true: npm refuses to install with a Node or npm version outsideenginesinpackage.json
Use npm ci to install exactly what package-lock.json specifies (composer setup does this).
ddev composer test # run the test suite
ddev coverage # the same, with a coverage summary and an HTML report in coverage/The tests run on SQLite in memory (see the <server> values in phpunit.xml). They never use the DDEV MySQL database:
<server> values override the database variables that DDEV sets in the container, tests/TestCase.php refuses any
database except SQLite in memory, and the tests refuse to start with a cached configuration (composer test clears it
first). ddev coverage turns Xdebug on for the run and then restores its previous state (also when a test fails),
because Xdebug slows down every request.
The project uses three tools:
- Laravel Pint for the PHP code style (rules in
pint.json) - Larastan (PHPStan) for static analysis of
app/(settings inphpstan.neon; known errors are inphpstan-baseline.neon, and new code must not add entries) - ESLint for
resources/assets/js,vite.config.jsandeslint.config.js(rules ineslint.config.js; the vendored theme JavaScript is not linted)
ddev composer lint:check # run all three tools; report problems, change nothing
ddev composer lint:fix # fix the PHP style and the JavaScript, then report what PHPStan still finds.git-blame-ignore-revs lists the format-only Pint commit; GitHub skips it in blame views, and locally
git config blame.ignoreRevsFile .git-blame-ignore-revs does the same.
GitHub Actions run the shared SciFY workflows:
ci.yml(on pushes tomasterand on pull requests): Pint, PHPStan, ESLint, the Vite build and the tests on PHP 8.4security.yml(on pull requests and weekly):composer audit,npm audit, the npm supply-chain settings and Gitleaks
The master branch should require the check ci / CI; dependabot_auto_merge.yml relies on it.
Releases follow Semantic Versioning: increase the major version for changes that break
users or deployments without a migration path, the minor version for new features and compatible changes, and the
patch version for bug fixes only. The version is defined only in package.json; the app shows it in the footer.
Releases are tagged vX.Y.Z. All changes are recorded in CHANGELOG.md.
Changes reach master through pull requests with a passing ci / CI check, so a release goes through a pull request
too. To release version X.Y.Z from an up-to-date master with green CI:
-
Create a branch, for example
git switch -c chore/release-vX.Y.Z. InCHANGELOG.md, rename## [Unreleased]to## [X.Y.Z] - YYYY-MM-DD, add a new empty## [Unreleased]above it, and update the compare links at the end of the file. -
Set the version (this updates
package.jsonandpackage-lock.json, without a commit or tag):ddev npm version X.Y.Z --no-git-tag-version
-
Commit, push and open a pull request; merge it with a merge commit when CI passes:
git add CHANGELOG.md package.json package-lock.json git commit -m "chore: release vX.Y.Z" git push -u origin chore/release-vX.Y.Z gh pr create --title "chore: release vX.Y.Z" --body "Release vX.Y.Z. See CHANGELOG.md."
-
Tag the merge commit on
masterand push the tag:git switch master && git pull --ff-only git tag -a vX.Y.Z -m "vX.Y.Z" git push origin vX.Y.Z
-
Create the GitHub release, with the changelog section as the release notes:
awk '/^## \[X.Y.Z\]/{found=1; next} /^## \[/{found=0} found' CHANGELOG.md > release-notes.md gh release create vX.Y.Z --title "vX.Y.Z" --notes-file release-notes.md --verify-tag rm release-notes.md
-
Deploy the tag, not the branch (see below).
Deploy a release tag (git checkout vX.Y.Z), not master. Read the upgrade notes of every release in
CHANGELOG.md since the deployed version.
- PHP 8.4 or newer (
composer.lockrequires it) with php-fpm, MySQL 8.0, and Node with the version from.nvmrc. - The web server points to the
publicdirectory. - ffmpeg (
apt-get install ffmpeg): uploaded audio is converted to MP3 with a constant bit rate (CBR), which the desktop game needs (CBR and VBR). The command is inpublic/convert_file_to_mp3.sh; it uses the olderavconvwhen that is installed. - ImageMagick (
apt-get install imagemagick): the cover image of a game becomes the.icofile of its Windows executable. - The Inno Setup exe builder
service builds the Windows installers; set its address in
WINDOWS_SETUP_SERVICE_URL. - Mailgun sends the mail; the server's IP address must be on the Mailgun allow list.
Large uploads need these limits:
- nginx (the site file, for example
/etc/nginx/sites-enabled/memoristudio.scify.org):fastcgi_read_timeout 300; - php-fpm (
/etc/php/8.4/fpm/php.ini):max_input_time = 300,post_max_size = 200M,upload_max_filesize = 200M
File permissions: the deploy user must belong to the web server group (for example www-data). The permissions script
adds the user to that group, sets the owner and the modes (775 folders, 664 files) of storage, and sets the group and
the setgid bit on storage and bootstrap/cache:
sudo ./set-file-permissions.sh www-data project_memori .project_memori is the name of the server user.
APP_ENV=productionandAPP_DEBUG=false(.env.examplehas local values; debug mode shows stack traces with configuration values).APP_URLis the public address (https://memoristudio.scify.org). The app answers only requests for this host and its subdomains; the desktop game uses the same host.
After every git pull, install the PHP dependencies with Composer scripts enabled:
composer install --no-dev --optimize-autoloaderThe post-autoload-dump script publishes the cookie-consent assets into public/vendor/scify, which git does not
track. Without them every page answers 500, so do not use --no-scripts, and give the deploy user write access to
public/vendor.
Then build the frontend and run the migrations:
npm ci
npm run build
php artisan migrate --force
php artisan optimize:clearoptimize:clear removes cached routes and config, so a release's route changes take effect. To cache again:
php artisan config:cache && php artisan route:cache && php artisan view:cache.
npm cimust install the devDependencies (do not use--omit=dev): Vite and Sass are devDependencies.- Every page answers 500 until
public/build/manifest.jsonexists, so build before the site serves the new code, or use maintenance mode (php artisan down/php artisan up). - Vite reads
VITE_*values from.envat build time. Set them beforenpm run build; for exampleVITE_SENTRY_DSN_PUBLICenables browser error reports to Sentry.
This project is open-sourced software licensed under the Apache License, Version 2.0.
Memor-i Studio has been created by Science For You (SciFY), a Greek not-for-profit organization.
The Memor-i Studio project has been funded
by Public Benefit Foundation John S. Latsis

