diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..df6ad4fe --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster +ARG VARIANT=8-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT} + +# Install MariaDB client +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get install -y mariadb-client zlib1g-dev libpng-dev libzip-dev libwebp-dev \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Install php-mysql driver +RUN docker-php-ext-install mysqli pdo pdo_mysql gd zip + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..4119a0d0 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/php-mariadb +// Update the VARIANT arg in docker-compose.yml to pick a PHP version +{ + "name": "PHP & MariaDB (Community)", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + + // Set *default* container specific settings.json values on container create. + "settings": { }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "xdebug.php-debug", + "bmewburn.vscode-intelephense-client", + "mrmlnc.vscode-apache" + ], + + // For use with PHP or Apache (e.g.php -S localhost:8080 or apache2ctl start) + "forwardPorts": [8080, 3306], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "cp .env.example .env && sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)/public\" /var/www/html && composer install && yarn install", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "powershell": "latest" + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..4af89ce7 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + # Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4 + # Append -bullseye or -buster to pin to an OS version. + # Use -bullseye variants on local arm64/Apple Silicon. + VARIANT: "8-bullseye" + # Optional Node.js version + NODE_VERSION: "lts/*" + + + volumes: + - ..:/workspace:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + + # Uncomment the next line to use a non-root user for all processes. + # user: vscode + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: mariadb:10.4 + restart: unless-stopped + volumes: + - mariadb-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: mariadb + MYSQL_DATABASE: blessingskin + MYSQL_USER: username + MYSQL_PASSWORD: secret + + # Add "forwardPorts": ["3306"] to **devcontainer.json** to forward MariaDB locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + +volumes: + mariadb-data: diff --git a/.dockerignore b/.dockerignore index 10e12b8f..05f55362 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,13 +1,25 @@ .git/ .github/ .vscode/ +.idea/ +.cache/ +.cache-loader/ coverage/ node_modules/ plugins/** +public/sw.js +public/meta.js public/app/* public/lang/* public/plugins/** resources/assets/tests/ +storage/*.db +storage/*.sqlite +storage/insane-profile-cache +storage/oauth-public.key +storage/oauth-private.key +storage/install.lock +storage/options.php storage/debugbar storage/framework/cache/** storage/framework/sessions/** @@ -16,25 +28,34 @@ storage/framework/views/** storage/logs/** storage/packages/** storage/textures/* +storage/update_cache/* +target/ tests/ -tools/ -vendor/ +vendor/* _ide_helper.php .dockerignore .editorconfig .env .env.testing .eslintignore +.eslintrc.yml .gitignore .php_cs.* +.php-cs-fixer.cache +.php-cs-fixer.dist.php .phpstorm.meta.php .phpunit.result.cache +.sass-cache +.uini azure-pipelines.yml crowdin.yml docker-compose.yml -*.Dockerfile +Dockerfile* index.html +junit.xml phpunit.xml README*.md server.php +tsconfig.dev.json +tsconfig.eslint.json yarn-error.log diff --git a/.gitignore b/.gitignore index 2d2b1e0d..70dee6c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.DS_Store .env .sass-cache coverage diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..ea556e90 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,39 @@ +tasks: + - init: yarn install + command: yarn dev --config webpack.gitpod.config.ts + - init: composer install + command: | + cp .env.example .env + touch storage/database.db + sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i .env + sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/workspace\/blessing-skin-server\/storage\/database\.db/' -i .env + php artisan key:generate + php artisan serve --host=0.0.0.0 + - command: gp await-port 8080 && gp preview $(gp url 8000) + +github: + prebuilds: + # enable for the master/default branch (defaults to true) + master: true + # enable for all branches in this repo (defaults to false) + branches: false + # enable for pull requests coming from this repo (defaults to true) + pullRequests: true + # add a check to pull requests (defaults to true) + addCheck: true + # add a "Review in Gitpod" button as a comment to pull requests (defaults to false) + addComment: false + +vscode: + extensions: + - "editorconfig.editorconfig" + - "eamodio.gitlens" + - "bmewburn.vscode-intelephense-client" + - "esbenp.prettier-vscode" + - "jpoissonnier.vscode-styled-components" + - "mblode.twig-language-2" + - "felixfbecker.php-debug" + +ports: + - port: 8080 + visibility: public diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..35fcbe2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,82 @@ +FROM composer:latest as vendor + +WORKDIR /app + +COPY composer.json composer.lock ./ + +RUN composer install \ + --prefer-dist \ + --no-dev \ + --no-suggest \ + --no-progress \ + --no-autoloader \ + --no-scripts \ + --no-interaction \ + --ignore-platform-reqs + +FROM node:alpine as frontend + +WORKDIR /app + +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +COPY postcss.config.js tsconfig.build.json tsconfig.json webpack.config.ts ./ +COPY tools/*Plugin.ts ./tools/ + +COPY resources ./resources + +RUN yarn build && \ + cp resources/assets/src/images/bg.webp public/app/ && \ + cp resources/assets/src/images/favicon.ico public/app/ && \ + # Strip unused files + rm -rf *.config.js *.config.ts tsconfig.* \ + package.json yarn.lock node_modules/ \ + resources/assets/ resources/lang resources/misc resources/misc/backgrounds/ \ + tools/ + +FROM composer:latest as builder + +WORKDIR /app + +COPY . ./ + +COPY --from=vendor /app ./ +COPY --from=frontend /app/public ./public +COPY --from=frontend /app/resources/views/assets ./resources/views/assets + +RUN composer dump-autoload -o --no-dev -n && \ + rm -rf *.config.js *.config.ts tsconfig.* \ + package.json yarn.lock node_modules/ \ + resources/assets/ resources/misc resources/misc/backgrounds/ \ + tools/ && \ + mv .env.example .env && \ + php artisan key:generate && \ + mv .env storage/ && \ + ln -s storage/.env .env && \ + touch storage/database.db && \ + mkdir storage/plugins && \ + sed 's/PLUGINS_DIR=null/PLUGINS_DIR=\/app\/storage\/plugins/' -i storage/.env && \ + sed 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' -i storage/.env && \ + sed 's/DB_DATABASE=blessingskin/DB_DATABASE=\/app\/storage\/database\.db/' -i storage/.env + +FROM php:8-apache + +ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ + +RUN chmod +x /usr/local/bin/install-php-extensions && \ + install-php-extensions gd zip + +WORKDIR /app + +COPY --from=builder /app ./ + +ENV APACHE_DOCUMENT_ROOT /app/public +RUN chown -R www-data:www-data . && \ + sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf && \ + sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf && \ + a2enmod rewrite headers + +EXPOSE 80 + +VOLUME ["/app/storage"] diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 7cbc269d..8455c654 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -4,6 +4,6 @@ "resources/assets", "tools", "webpack.config.ts", - "webpack.meta.config.ts" + "webpack.gitpod.config.ts" ] } diff --git a/tsconfig.json b/tsconfig.json index 788549d0..c0273a59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "resources/assets/webpack.d.ts", "tools", "webpack.config.ts", - "webpack.meta.config.ts" + "webpack.gitpod.config.ts" ], "ts-node": { "compilerOptions": { diff --git a/webpack.gitpod.config.ts b/webpack.gitpod.config.ts new file mode 100644 index 00000000..4c8a675b --- /dev/null +++ b/webpack.gitpod.config.ts @@ -0,0 +1,191 @@ +import * as path from 'path' +import * as webpack from 'webpack' +import { execSync } from 'child_process' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +import CssMinimizerPlugin from 'css-minimizer-webpack-plugin' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import HtmlWebpackEnhancementPlugin from './tools/HtmlWebpackEnhancementPlugin' +import SyncMetaJsPlugin from './tools/SyncMetaJsPlugin' + +interface Env { + production?: boolean +} + +export default function (env?: Env): webpack.Configuration[] { + const isDev = !env?.production + const htmlPublicPath = isDev ? `${execSync('gp url 8080')}/app/` : '{{ cdn_base }}/app/' + + return [ + { + name: 'app', + mode: isDev ? 'development' : 'production', + entry: { + app: ['react-hot-loader/patch', '@/index.tsx'], + style: ['@/styles/common.css'], + home: '@/scripts/homePage.ts', + 'home-css': '@/styles/home.css', + spectre: [ + 'spectre.css/dist/spectre.min.css', + '@/fonts/minecraft.css', + '@/styles/spectre.css', + ], + }, + output: { + path: `${__dirname}/public/app`, + publicPath: '/app/', + filename: isDev ? '[name].js' : '[name].[contenthash:7].js', + chunkFilename: isDev ? '[id].js' : '[id].[contenthash:7].js', + crossOriginLoading: 'anonymous', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: 'ts-loader', + options: { + configFile: isDev ? 'tsconfig.dev.json' : 'tsconfig.build.json', + transpileOnly: true, + }, + }, + { + test: /\.css$/, + use: [ + isDev ? 'style-loader' : MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + importLoaders: 1, + }, + }, + 'postcss-loader', + ], + }, + { + test: /\.(png|webp|svg|woff2?|eot|ttf)$/, + type: 'asset', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: isDev ? '[name].css' : '[name].[contenthash:7].css', + chunkFilename: isDev ? '[id].css' : '[id].[contenthash:7].css', + }), + new HtmlWebpackPlugin({ + templateContent: isDev + ? '' + : ` + +`, + chunks: ['app'], + scriptLoading: 'blocking', + filename: 'app.twig', + publicPath: htmlPublicPath, + }), + new HtmlWebpackPlugin({ + templateContent: '', + chunks: ['style'], + filename: 'style.twig', + publicPath: htmlPublicPath, + }), + new HtmlWebpackPlugin({ + templateContent: '', + chunks: ['home'], + scriptLoading: 'blocking', + filename: 'home.twig', + publicPath: htmlPublicPath, + }), + new HtmlWebpackPlugin({ + templateContent: '', + chunks: ['home-css'], + filename: 'home-css.twig', + publicPath: htmlPublicPath, + }), + new HtmlWebpackPlugin({ + templateContent: '', + chunks: ['spectre'], + filename: 'spectre.twig', + publicPath: htmlPublicPath, + }), + new HtmlWebpackEnhancementPlugin(), + new webpack.DefinePlugin({ + 'window.Deno': 'true', + Deno: { + args: [], + build: {}, + version: {}, + }, + 'process.platform': '"browser"', + }), + ].concat(isDev ? [new webpack.HotModuleReplacementPlugin()] : []), + resolve: { + extensions: ['.js', '.ts', '.tsx'], + alias: { + 'react-dom': '@hot-loader/react-dom', + '@': path.resolve(__dirname, 'resources/assets/src'), + readline: '@/scripts/cli/readline.ts', + prompts: 'prompts/lib/index.js', + assert: false, + }, + }, + externals: Object.assign( + { jquery: 'jQuery', bootstrap: 'bootstrap', 'admin-lte': 'adminlte' }, + isDev + ? {} + : { + react: 'React', + 'react-dom': 'ReactDOM', + }, + ) as Record, + optimization: { + // @ts-ignore + minimizer: [new CssMinimizerPlugin({}), '...'], + }, + experiments: { + syncWebAssembly: true, + }, + devtool: isDev ? 'eval-source-map' : false, + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + }, + host: '0.0.0.0', + hot: true, + hotOnly: true, + stats: 'errors-warnings', + allowedHosts: ['localhost', '.gitpod.io'], + }, + stats: 'errors-warnings', + ignoreWarnings: [/size limit/i], + }, + { + name: 'meta', + mode: isDev ? 'development' : 'production', + entry: { + meta: './resources/assets/src/scripts/meta.ts', + sw: './resources/assets/src/scripts/sw.ts', + }, + output: { + path: `${__dirname}/public`, + filename: '[name].js', + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: 'ts-loader', + options: { + configFile: 'tsconfig.build.json', + transpileOnly: true, + }, + }, + ], + }, + plugins: [new SyncMetaJsPlugin()], + resolve: { + extensions: ['.js', '.ts'], + }, + stats: 'errors-warnings', + }, + ] +}