diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..8273aa1 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,18 @@ +# Informational, not blocking. The blocking gate is jest's coverageThreshold, +# which fails the job locally and in CI without depending on an external +# service being reachable. This adds the per-merge-request diff view on top. +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: 80% + informational: true + +comment: + layout: 'reach, diff, flags, files' + behavior: default diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..85e326d --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# +# CodeRabbit skips any review over 100 changed files, and a Drupal example +# carries hundreds of generated ones. The filters below leave the module, the +# tooling and the pipelines, which is what a reviewer can act on. +language: en-AU +reviews: + path_filters: + # Drupal's own exports, written by drush, never by hand. + - '!example/drupal/config/**' + - '!example/drupal/content/**' + # Composer's scaffolded docroot. + - '!example/drupal/web/**' + # Lock files. + - '!**/package-lock.json' + - '!**/composer.lock' + # Playwright baselines. + - '!test/e2e/__snapshots__/**' + auto_review: + enabled: true + drafts: false diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt new file mode 100644 index 0000000..1f0fe41 --- /dev/null +++ b/.cspell-project-words.txt @@ -0,0 +1,63 @@ +# Words this project uses that the standard dictionaries do not carry. +# Add a term here rather than switching the check off for a file. +actionlint +autobuild +Autobuild +automerge +behaviour +bookworm +cloudflared +Codespaces +colour +commitlint +consola +corepack +cspell +davidanson +dbaeumer +DDEV +devcontainer +devcontainers +druxt +Druxt +druxtjs +esbenp +esbuild +freetype +gitleaks +hverlin +hyperscript +interruptible +jsonapi +knip +Ksection +Lando +libfreetype +libicu +libjpeg +libonig +libpng +libsqlite +libxml +libzip +Licence +lndo +markdownlint +mise +networkidle +nproc +Nuxt +nuxt +nvm +nvmrc +opcache +opencode +pdo +pyyaml +shivammathur +siroc +sqlite +unparseable +vfox +viewports +yamllint diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000..8d0f13a --- /dev/null +++ b/.cspell.json @@ -0,0 +1,45 @@ +{ + "files": ["**/*.{js,mjs,vue,json,jsonc,md,yml,yaml}"], + "dictionaryDefinitions": [ + { + "name": "project-words", + "path": "./.cspell-project-words.txt" + } + ], + "dictionaries": ["project-words", "bash", "misc", "node", "softwareTerms"], + "ignorePaths": [ + ".git/", + "node_modules/", + "**/node_modules/", + "dist/", + "coverage/", + "reports/", + "test-results/", + "playwright-report/", + "example/drupal/", + "**/CHANGELOG.md", + "*.lock", + "package-lock.json", + "*.svg", + "*.png", + "*.snap", + ".vale/", + "example/drupal/vendor/", + "example/drupal/web/", + "example/nuxt/dist/", + "example/nuxt/.nuxt/" + ], + "useGitignore": true, + "words": [ + "coderabbit", + "deasync", + "distutils", + "docroot", + "drush", + "libwebp", + "phpize", + "setuptools", + "trixie", + "webp" + ] +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7eb9f43 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,73 @@ +{ + "name": "Druxt module", + // PHP for the example's Drupal backend, provisioned without Docker by + // example/drupal/.devtools. The PHP feature builds against trixie's + // libsqlite3, which meets Drupal 11's 3.45 minimum; the PHP image is on + // bookworm's 3.40 and cannot install it. Node 16 for the module comes from + // its feature: Nuxt 2 builds on webpack 4, which does not run on a newer Node. + "image": "mcr.microsoft.com/devcontainers/base:trixie", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "16.20.1" + }, + "ghcr.io/devcontainers/features/php:1": { + "version": "8.4", + "installComposer": true + }, + "ghcr.io/devcontainers/features/github-cli:1": {}, + // Installed through gh-release rather than a mise feature: the + // devcontainers-contrib registry that hosted one is gone, and the + // devcontainers-extra one asks for the "latest" jdx/mise release with + // no tag filter, which now resolves to a vfox-* release with no asset. + // The regex keeps this on mise's own v-tags. Same setup as druxt.js. + "ghcr.io/devcontainers-extra/features/gh-release:1": { + "repo": "jdx/mise", + "binaryNames": "mise", + "assetRegex": ".tar.gz$", + "releaseTagRegex": "^v[0-9]+\\.[0-9]+\\.[0-9]+$" + } + }, + "containerEnv": { + // Node and PHP come from the image and features; .mise.toml's pins are + // for host users. Without this, `mise install` builds both from source + // in here. + "MISE_DISABLE_TOOLS": "node,php", + // Pin the example backend's port so the forward below always matches. + "WEBSERVER_PORT": "8888" + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "customizations": { + "vscode": { + "extensions": [ + "hverlin.mise-vscode", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "davidanson.vscode-markdownlint", + "streetsidesoftware.code-spell-checker", + "ms-playwright.playwright", + "editorconfig.editorconfig" + ], + "settings": { + // Where the gh-release feature puts the binary. The extension's + // default bare "mise" is treated as workspace-relative and + // re-prompts for approval on every reload. + "mise.binPath": "/usr/local/bin/mise", + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.validate": ["javascript", "vue"] + } + } + }, + "forwardPorts": [3000, 8888], + "portsAttributes": { + "3000": { + "label": "Example application", + "onAutoForward": "notify" + }, + "8888": { + "label": "Example backend (example/drupal/.devtools)", + "onAutoForward": "silent" + } + }, + "remoteUser": "vscode" +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 0000000..509cf0d --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# One definition for VS Code, Codespaces and DevPod, replacing the Gitpod +# configuration that drifted in every repository still carrying one. +# +# Idempotent: a devcontainer gets rebuilt, and a post-create step that only +# works on a clean container is a step that fails the second time. +set -euo pipefail + +echo "Trusting this repository's .mise.toml..." +mise trust + +# PHP comes from the dev container PHP feature, built from source against +# trixie's libsqlite3: Drupal 11 needs 3.45 or later. The feature leaves gd +# out, and Drupal's installer requires it, so gd is built from PHP's own +# source tree below. sqlite3 is the CLI drush uses to reset the throwaway +# database. +echo "Installing build tooling and image libraries..." +sudo apt-get update -qq > /dev/null +# python3-setuptools: trixie's Python 3.13 has no distutils, and the node-gyp +# bundled with Node 16's npm still imports it when vue-jest's deasync builds. +sudo apt-get install -y -qq python3 python3-setuptools build-essential sqlite3 libjpeg-dev libpng-dev libwebp-dev libfreetype-dev zlib1g-dev > /dev/null + +CONF_DIR=$(php --ini | grep 'Scan for additional .ini files' | sed 's/.*: *//') + +# The feature ships Xdebug active on every request, so each CLI call would +# warn that no debugger is listening. Trigger mode keeps it available on demand. +echo 'xdebug.start_with_request = trigger' | sudo tee "$CONF_DIR/zz-xdebug-trigger.ini" > /dev/null + +echo "Building the gd extension from PHP's source tree..." +PHP_FULL_VERSION=$(php -r 'echo PHP_VERSION;') +PHP_SRC_TMP="$(mktemp -d)" +trap 'rm -rf "$PHP_SRC_TMP"' EXIT +mkdir -p "$PHP_SRC_TMP/gd" +curl -fsSL "https://www.php.net/distributions/php-${PHP_FULL_VERSION}.tar.gz" -o "$PHP_SRC_TMP/php-src.tar.gz" +tar -xzf "$PHP_SRC_TMP/php-src.tar.gz" -C "$PHP_SRC_TMP/gd" --strip-components=3 "php-${PHP_FULL_VERSION}/ext/gd" +( + cd "$PHP_SRC_TMP/gd" + phpize > /dev/null + ./configure --with-jpeg --with-webp --with-freetype > /dev/null + make -j"$(nproc)" > /dev/null + sudo make install > /dev/null +) +echo 'extension=gd' | sudo tee "$CONF_DIR/gd.ini" > /dev/null +php -r "exit(extension_loaded('gd') && extension_loaded('pdo_sqlite') ? 0 : 1);" || { echo "gd or pdo_sqlite is not loaded" >&2; exit 1; } + +echo "Installing dependencies..." +npm install + +# Playwright does not know trixie and falls back to a package list from +# Ubuntu 20.04, whose font packages no longer exist. Its Ubuntu 24.04 list +# uses the same t64 names as trixie and every package in it is available here. +# The platform key carries the architecture. +echo "Installing the Playwright browser for the end-to-end tests..." +case "$(uname -m)" in aarch64 | arm64) PLAYWRIGHT_ARCH=arm64 ;; *) PLAYWRIGHT_ARCH=x64 ;; esac +PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="ubuntu24.04-$PLAYWRIGHT_ARCH" npx playwright install --with-deps chromium > /dev/null + +echo "Building the module, which the example links to by path..." +npm run build + +echo "Provisioning and starting the example backend, then installing the example..." +npm run example:setup + +# npm install enables the hooks via scripts/postinstall.mjs. Repeated here for +# the case where the container was built with install scripts disabled, which +# is a common hardening default. +echo "Enabling git hooks..." +git config core.hooksPath .githooks + +# OpenSSH forwards the host's LANG and LC_*, and bash warns on every start +# when that locale is not generated here. This covers the usual English +# ones before the shell starts; shell-init.sh falls back for anything else. +echo "Generating the English locales hosts commonly send over SSH..." +sudo apt-get update -qq > /dev/null +sudo apt-get install -y -qq locales > /dev/null +sudo sed -i -E 's/^# (en_(AU|CA|GB|IE|NZ|US)\.UTF-8 UTF-8)/\1/' /etc/locale.gen +sudo locale-gen > /dev/null + +# Sourced from ~/.bashrc rather than run once here, so every new terminal +# gets the locale fix and the summary, not only the creation log. +echo "Installing the shell locale fallback and welcome..." +if ! grep -qF '.devcontainer/shell-init.sh' ~/.bashrc; then + printf '\n# Dev container shell setup: locale fallback and welcome.\nexport WORKSPACE_ROOT=%q\n[ -f "$WORKSPACE_ROOT/.devcontainer/shell-init.sh" ] && . "$WORKSPACE_ROOT/.devcontainer/shell-init.sh"\n' "$PWD" >> ~/.bashrc +fi + +echo +echo "Ready. Open a new terminal for the summary of commands." diff --git a/.devcontainer/shell-init.sh b/.devcontainer/shell-init.sh new file mode 100755 index 0000000..c670eca --- /dev/null +++ b/.devcontainer/shell-init.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Sourced by ~/.bashrc inside the dev container. post-create.sh installs the +# line that sources it, so a fresh container gets both halves below. + +# The host's locale arrives over SSH: OpenSSH sends LANG and LC_* by default, +# and DevPod's shell inherits them. When the value names a locale this image +# has not generated, every command warns "setlocale: cannot change locale" +# and manpath gives up. Fall back to the image's UTF-8 locale instead. +# Every forwarded value is checked, not only LANG: a host that sends an +# ungenerated LC_TIME warns just as loudly as one that sends an ungenerated LANG. +available="$(locale -a 2>/dev/null)" +for forwarded in "${LANG:-}" "${LC_ALL:-}" "${LC_ADDRESS:-}" "${LC_COLLATE:-}" \ + "${LC_CTYPE:-}" "${LC_IDENTIFICATION:-}" "${LC_MEASUREMENT:-}" "${LC_MESSAGES:-}" \ + "${LC_MONETARY:-}" "${LC_NAME:-}" "${LC_NUMERIC:-}" "${LC_PAPER:-}" \ + "${LC_TELEPHONE:-}" "${LC_TIME:-}"; do + case "$forwarded" in '' | C | C.* | POSIX) continue ;; esac + if ! printf '%s\n' "$available" | grep -qix "$(printf '%s' "$forwarded" | sed 's/UTF-8$/utf8/')"; then + export LANG=C.UTF-8 + unset LC_ALL LC_ADDRESS LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ + LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ + LC_TELEPHONE LC_TIME + break + fi +done +unset available forwarded + +# The rest is for a person at a prompt. +case $- in *i*) ;; *) return 0 2>/dev/null || exit 0 ;; esac + +backend="not started" +if [ -f "${WORKSPACE_ROOT:-$PWD}/example/.env" ]; then + backend="$(sed -n 's/^BASE_URL=//p' "${WORKSPACE_ROOT:-$PWD}/example/.env" | head -1)" + backend="${backend:-not started}" +fi + +cat < + -- [ ] Bug fix (a non-breaking change which fixes an issue) -- [ ] New feature (a non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) + (): +This repository squash-merges, so the title becomes the commit subject. A prose +title passes review and then breaks the next push to the target branch. +--> -## Description - - - +## What changed +## Why -## Checklist: - - - -- [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] I have added tests to cover my changes (if not applicable, please state why) -- [ ] All new and existing tests are passing. +## How it was checked - -## Screenshots/Media: - +- [ ] `npm run lint` passes +- [ ] `npm test` passes, and coverage did not drop +- [ ] Visual baselines regenerated via the manual `visual:update` job, if the + rendering changed +- [ ] Nothing that resolves only on a private network reached a tracked file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b4d40d..9bc9942 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,174 @@ +--- name: CI +# The same lint and test set as .gitlab-ci.yml. This repository is mirrored and +# releases are cut here, so both hosts have to agree. Nothing on this side lints +# this file: the GitLab pipeline runs actionlint over it in return. + on: push: - branches: [ main ] - + branches: [main] pull_request: - branches: [ main ] + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build: + lint: + runs-on: ubuntu-latest + steps: + # Actions are pinned to commit SHAs, not tags. A tag is a mutable + # reference to code that runs with this repository's credentials. + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + # The attribution and prose checks read the pull request's own + # commits, so its base has to be present. + fetch-depth: 0 + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + cache: npm + + # corepack reads the packageManager field, so the manager the lockfile was + # written by is the one that installs it, and this file never names one. + - run: corepack enable && corepack prepare --activate + + - run: npm ci + + - run: npm run lint:js + + - run: npm run lint:md + + - run: npm run lint:cspell + + - run: npm run lint:knip + + # This repository is public, and its README and example are what people + # copy. A host only the author can reach must never reach a tracked file. + - run: npm run lint:private + + # The work is the author's. No commit, pull request description or + # tracked file may credit an AI tool as author, co-author or generator. + - name: Check for AI attribution + env: + ATTRIBUTION_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + MERGE_REQUEST_DESCRIPTION: ${{ github.event.pull_request.body }} + run: bash .gitlab/scripts/check-attribution.sh + + # Project voice over the markdown a change touches, its commit messages + # and the pull request description. + - name: Lint prose + env: + PROSE_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + MERGE_REQUEST_DESCRIPTION: ${{ github.event.pull_request.body }} + PROSE_LINT_GLOB: "!{.changeset,example/drupal}/**" + run: | + bash .gitlab/scripts/install-vale.sh .vale/bin .vale/styles + bash .gitlab/scripts/lint-prose.sh + + - run: npm run lint:audit + + test: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + cache: npm + + - run: corepack enable && corepack prepare --activate + + - run: npm ci + + - run: npm run build + + # The coverage floor in jest.config.js is what fails here. Codecov below + # adds the per-pull-request view on top and is not the gate. + - run: npm test -- --reporters=default --reporters=jest-junit --runInBand - strategy: - matrix: - node-version: [14.x, 16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: test-results + path: reports/junit/ + - name: Upload coverage to Codecov + if: ${{ env.CODECOV_TOKEN != '' }} + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage + + # The example on a real backend: the module is built, the example installs it + # by path, Drupal 11 is provisioned from scratch on SQLite, the example is + # generated against it and Playwright reads the result. Visual comparisons + # are excluded here on purpose: the committed baselines come from the GitLab + # runner, and this runner renders differently. + e2e: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 + with: + php-version: '8.4' + extensions: pdo_sqlite, gd, intl, zip, opcache + tools: composer:2 + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: | + package-lock.json + example/nuxt/package-lock.json + + - run: corepack enable && corepack prepare --activate + + - run: npm ci + + - run: npm run build + + - name: Provision and start the example backend + env: + WEBSERVER_HOST: 127.0.0.1 + WEBSERVER_PORT: 8888 + run: npm run example:assemble && npm run example:provision && npm run example:start - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} + - name: Backend answers JSON:API + run: . ./example/.env && curl -sf "${BASE_URL}/jsonapi" | grep -q '"jsonapi"' - - run: npm i + - run: npm run example:install - - run: npm run build + - run: npm run example:generate - - run: npm run lint + - run: npx playwright install --with-deps chromium - - run: npm test --reporters=jest-junit --runInBand + - name: End-to-end tests against the generated example + run: | + npm run serve & + bash scripts/wait-for-server.sh + PLAYWRIGHT_BASE_URL=http://127.0.0.1:3000 npm run test:e2e - - name: Codecov - uses: codecov/codecov-action@v3.1.1 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: failure() + with: + name: playwright-results + path: | + test-results/ + playwright-report/ diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5a4d6eb..2806817 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,3 +1,4 @@ +--- # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # @@ -9,14 +10,14 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: 'CodeQL' on: push: - branches: [ develop ] + branches: [develop] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [develop] schedule: - cron: '15 5 * * 2' @@ -32,40 +33,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] + language: ['javascript'] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@4187e74d05793876e9989daffde9c3e66b4acd07 # v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@4187e74d05793876e9989daffde9c3e66b4acd07 # v3 - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + # - run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@4187e74d05793876e9989daffde9c3e66b4acd07 # v3 diff --git a/.gitignore b/.gitignore index c9974fe..3dd69d9 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,18 @@ dist # TernJS port file .tern-port + +# Test and lint output +coverage/ +reports/ +test-results/ +playwright-report/ + +# Playwright's own browser cache +.playwright/ + +# Vale binary and styles, installed by .gitlab/scripts/install-vale.sh +.vale/ + +# Written by example/drupal/.devtools/start with the backend URL +example/.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..90a4f14 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,440 @@ +--- +# CI for the Druxt module template, and the shape every Druxt module repository +# is expected to have. See the repository standard for what each job is for and +# why it exists. +# +# GitHub Actions (.github/workflows/ci.yml) runs the same lint and test set, +# because this repository is mirrored and releases are cut there. Keep the two +# in step: nothing on the GitHub side lints that workflow, so lint:yaml here +# runs actionlint over it in return. +# +# Secret scanning is gitleaks, not GitLab's Security/Secret-Detection template. +# That template is an Ultimate-tier feature: on a non-Ultimate instance the +# include produces no job at all, so the configuration reads as coverage while +# scanning nothing. + +stages: + - lint + - build + - test + - visual + - preview + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +default: + tags: [docker] + timeout: 15m + before_script: + - 'section_start() { local n="$1" t="${2:-$1}"; printf "\e[0Ksection_start:%s:%s\r\e[0K%s\n" "$(date +%s)" "$n" "$t"; }' + - 'section_end() { printf "\e[0Ksection_end:%s:%s\r\e[0K\n" "$(date +%s)" "$1"; }' + +# Shared Node setup. `corepack prepare --activate` reads the packageManager +# field rather than naming a manager here, so this block is identical in every +# repository whatever it installs with. The cache is keyed by the lockfile +# hash: a dependency bump misses once and hits thereafter. +.node: + image: node:16.20.1 + interruptible: true + cache: + key: + files: + - package-lock.json + prefix: node-16.20.1 + paths: + - .npm/ + before_script: + - !reference [default, before_script] + - corepack enable + - corepack prepare --activate + - npm ci --cache .npm --prefer-offline + +# PHP for the example's Drupal 11 backend, with Node 16 alongside for the +# generate step that needs both running at once. +.php-node: + image: php:8.4 + interruptible: true + cache: + key: php-8.4-composer + paths: + - example/drupal/vendor/ + - .npm/ + before_script: + - !reference [default, before_script] + - section_start "toolchain" "Installing PHP extensions, Composer and Node" + # python3-setuptools: this image's Python has no distutils, and the node-gyp + # bundled with Node 16's npm still imports it when vue-jest's deasync builds. + - apt-get update -yqq && apt-get install -yqq git unzip sqlite3 libsqlite3-dev libzip-dev libicu-dev libpng-dev libjpeg62-turbo-dev libfreetype6-dev libonig-dev libxml2-dev python3 python3-setuptools build-essential > /dev/null + - docker-php-ext-configure gd --with-freetype --with-jpeg > /dev/null + - docker-php-ext-install -j"$(nproc)" pdo_sqlite intl gd zip opcache > /dev/null + - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version=2.8.6 > /dev/null + - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash > /dev/null 2>&1 + - export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh" --no-use && nvm install > /dev/null && nvm use > /dev/null + - node --version && php --version | head -1 + - section_end "toolchain" + +# +# --- Lint --- +# + +lint:js: + extends: .node + stage: lint + script: + - section_start "eslint" "Linting sources, tests and scripts" + - npm run lint:js + - section_end "eslint" + +lint:md: + extends: .node + stage: lint + script: + - section_start "markdownlint" "Linting Markdown" + - npm run lint:md + - section_end "markdownlint" + +lint:cspell: + extends: .node + stage: lint + script: + - section_start "cspell" "Spell checking" + - npm run lint:cspell + - section_end "cspell" + +# This repository is public, and its README and example are what people copy to +# start a module. A host only the author can reach must never reach a tracked +# file. +lint:private: + extends: .node + stage: lint + script: + - section_start "private-refs" "Checking for private host references" + - npm run lint:private + - section_end "private-refs" + +lint:knip: + extends: .node + stage: lint + script: + - section_start "knip" "Checking for unused and undeclared dependencies" + - npm run lint:knip + - section_end "knip" + +# Production only. A shipped runtime dependency is what actually reaches a +# consumer; a devDependency advisory is worth knowing about and not worth +# blocking a merge request on. +lint:audit: + extends: .node + stage: lint + script: + - section_start "audit" "Auditing shipped dependencies" + - npm run lint:audit + - section_end "audit" + +# yamllint covers this file; actionlint covers the GitHub workflow, which +# nothing on the GitHub side lints before a push. +lint:yaml: + stage: lint + image: python:3.12-slim + interruptible: true + variables: + ACTIONLINT_VERSION: '1.7.12' + ACTIONLINT_SHA256: '8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8' + script: + - section_start "yamllint" "Linting YAML" + - pip install --quiet yamllint + - yamllint .gitlab-ci.yml .github/workflows/ + - section_end "yamllint" + - section_start "actionlint" "Linting GitHub workflows" + - apt-get update -qq && apt-get install -y -qq curl > /dev/null + - curl -sSLo actionlint.tar.gz "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" + - echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - + - tar -xzf actionlint.tar.gz actionlint + - ./actionlint -color + - section_end "actionlint" + +# The merge request's own commits, not the whole history. These repositories +# squash-merge, so a prose title becomes the commit subject and breaks the next +# push to the target branch. +lint:commit: + extends: .node + stage: lint + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + script: + - section_start "commitlint" "Validating commit messages" + - git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --depth 50 + - npm run lint:commit -- --from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --to HEAD --verbose + - section_end "commitlint" + +# The work is the author's. No commit, merge request description or tracked +# file may credit an AI tool as author, co-author or generator. Full history, +# because the range is the merge request's own and its base has to be present. +lint:attribution: + image: node:16.20.1 + stage: lint + interruptible: true + variables: + GIT_DEPTH: 0 + script: + - section_start "attribution" "Checking for AI attribution" + - bash .gitlab/scripts/check-attribution.sh + - section_end "attribution" + +# Project voice over the markdown a change touches, its commit messages and the +# merge request description. The changesets README and the Drupal scaffold are third-party text. +lint:prose: + image: node:16.20.1 + stage: lint + interruptible: true + variables: + GIT_DEPTH: 0 + PROSE_LINT_GLOB: "!{.changeset,example/drupal}/**" + cache: + key: vale-3.17.1 + paths: + - .vale/ + script: + - section_start "prose" "Linting prose with Vale" + - bash .gitlab/scripts/install-vale.sh .vale/bin .vale/styles + - bash .gitlab/scripts/lint-prose.sh + - section_end "prose" + +# +# --- Test --- +# + +test:unit: + extends: .node + stage: test + needs: [] + script: + - section_start "build" "Building the module" + - npm run build + - section_end "build" + - section_start "jest" "Running unit tests" + - npm test -- --reporters=default --reporters=jest-junit --runInBand + - section_end "jest" + artifacts: + when: always + reports: + junit: reports/junit/junit.xml + paths: + - coverage/ + expire_in: 1 week + # Puts the coverage percentage in the merge request widget, where a drop is + # visible to a reviewer who was never going to open the job log. + coverage: '/All files\s*\|\s*([\d.]+)/' + +# +# --- Example --- +# +# The module is built, the example installs it by path, the backend is +# provisioned from scratch on SQLite, and the example is generated against it. +# The generated example is what the end-to-end, visual and preview jobs read. +# + +example:generate: + extends: .php-node + stage: build + needs: [] + variables: + WEBSERVER_HOST: '127.0.0.1' + WEBSERVER_PORT: '8888' + script: + - section_start "module" "Building the module" + - corepack enable && corepack prepare --activate + - npm ci --cache .npm --prefer-offline + - npm run build + - section_end "module" + - section_start "backend" "Provisioning and starting the example backend" + - npm run example:assemble && npm run example:provision && npm run example:start + - . ./example/.env && curl -sf "${BASE_URL}/jsonapi" | grep -q '"jsonapi"' + - section_end "backend" + - section_start "generate" "Generating the example" + - npm run example:install + - npm run example:generate + - test -f example/nuxt/dist/index.html + - section_end "generate" + artifacts: + paths: + - example/nuxt/dist/ + expire_in: 1 week + +.playwright: + extends: .node + image: mcr.microsoft.com/playwright:v1.49.1-jammy + variables: + PLAYWRIGHT_BASE_URL: http://127.0.0.1:3000 + VISUAL_RESULTS_DIR: test-results + before_script: + - !reference [default, before_script] + # This image ships a newer Node than the project's, and vue-jest's deasync + # cannot build against it. Nothing here needs a native module: the tests + # run Playwright against a static server, so install scripts are skipped. + - npm ci --cache .npm --prefer-offline --ignore-scripts + - npm run serve & + - bash scripts/wait-for-server.sh + +# Functional checks against the generated example, on every pipeline. Visual +# comparisons are the separate job below, because they need committed +# baselines to mean anything. +test:e2e: + extends: .playwright + stage: test + needs: [example:generate] + script: + - section_start "e2e" "Running end-to-end tests against the generated example" + - npm run test:e2e + - section_end "e2e" + artifacts: + when: on_failure + paths: + - test-results/ + - playwright-report/ + expire_in: 1 week + +# +# --- Visual --- +# +# Playwright against the example application. Baselines are committed, and are +# regenerated only by the manual visual:update job below: Chromium renders +# differently on ARM, and a baseline generated there is a permanent false diff +# for everyone else. +# + +# Runs only once baselines are committed. Playwright writes a baseline and +# passes on the first run of any snapshot assertion, so this job with an empty +# test/e2e/__snapshots__ would be green while checking nothing. Absent until +# armed is honest; present and vacuously green is not. +# +# To arm it: run the manual visual:update job below and commit the PNGs it +# produces as an artifact. +visual: + extends: .playwright + stage: visual + needs: [example:generate] + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + exists: + - test/e2e/__snapshots__/**/*.png + script: + - section_start "visual" "Comparing against the committed baselines" + - npm run test:visual + - section_end "visual" + after_script: + # Posts baseline, render and diff for each failure as one sticky note, and + # removes that note when the failure clears. + - bash .gitlab/scripts/post-visual-failure-comment.sh || true + artifacts: + when: always + paths: + - test-results/ + - playwright-report/ + expire_in: 1 week + +# Manual baseline regeneration. Run this rather than regenerating locally: the +# shared runner is the reference architecture, and a baseline committed from a +# developer's ARM machine fails for everyone else. +visual:update: + extends: .playwright + stage: visual + needs: [example:generate] + rules: + - when: manual + allow_failure: true + script: + - section_start "visual-update" "Regenerating visual baselines" + - npm run test:visual:update + - section_end "visual-update" + artifacts: + when: always + paths: + - test/e2e/__snapshots__/ + expire_in: 1 day + +# +# --- Preview --- +# +# Manual. Serves the built example application through a Cloudflare Quick +# Tunnel and posts the URL to the merge request. The tunnel dies with the job. +# + +preview:live: + extends: .node + stage: preview + needs: [example:generate] + timeout: 1h + variables: + PREVIEW_DIR: example/nuxt/dist + PREVIEW_KEEP_ALIVE_MINUTES: '55' + PREVIEW_LABEL: 'Example application' + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + when: manual + allow_failure: true + script: + - section_start "preview-tunnel" "Opening a cloudflared tunnel" + - bash .gitlab/scripts/run-live-preview-tunnel.sh + - section_end "preview-tunnel" + +# +# --- Secret detection --- +# + +secret-detection: + stage: lint + image: alpine:3.21 + interruptible: true + dependencies: [] + variables: + GITLEAKS_VERSION: '8.30.1' + GITLEAKS_SHA256: '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb' + script: + - section_start "gitleaks" "Scanning for committed secrets" + - apk add --no-cache curl tar git > /dev/null + - curl -sSLo gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + - echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum -c - + - tar -xzf gitleaks.tar.gz gitleaks + - ./gitleaks detect --source . --no-banner --redact --verbose + - section_end "gitleaks" + # A scan that finds nothing looks identical to a scan that cannot find + # anything. Plant three credentials outside the repository and assert all + # three are reported, so a bad download, a config change or a widened + # allowlist fails the job instead of passing it quietly. + # + # Each value is split across its own prefix so no fragment in this file + # triggers the rule it is meant to exercise: the repository scan above runs + # over this file too, and a canary that flags itself would need an ignore + # entry, which is the thing being guarded against. + # + # Fixed values, not generated ones. Detection is keyword-driven rather than + # entropy-driven, so a generated credential fires only sometimes and + # presents as flake. A value verified once keeps firing. + # + # Invented, not copied from any vendor's documentation: gitleaks allowlists + # documentation keys, and a canary built from one is inert in exactly the + # way this check exists to catch. + - section_start "canary" "Checking that the scanner still detects" + - | + canary="$(mktemp -d)" + pat="gh""p_8fK2mQ7xR4tV9nB3jH6yW1sZ5cD8gL0aE4uY" + stripe="sk""_live_51aBcD2eFgH3iJkL4mNoP5qR6sTuV7wXyZ8a" + slack="xo""xb-263594753641-2529420432-J1n4h6a9pQrStUvWxYz1AbCd" + printf 'a = "%s"\nb = "%s"\nc = "%s"\n' "$pat" "$stripe" "$slack" > "$canary/canary.txt" + ./gitleaks detect --no-git --source "$canary" --no-banner --redact \ + --report-format json --report-path "$canary/report.json" || true + found=$(grep -c '"RuleID"' "$canary/report.json" || true) + rm -rf "$canary" + if [ "$found" != "3" ]; then + echo "The canary planted 3 credentials and gitleaks reported $found." + echo "The scan above proves nothing while this is true." + exit 1 + fi + echo "Canary detected 3 of 3 planted credentials." + - section_end "canary" diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md new file mode 100644 index 0000000..f4fda6f --- /dev/null +++ b/.gitlab/issue_templates/Default.md @@ -0,0 +1,15 @@ +## What happened + +## What you expected + +## How to reproduce + +## Versions + +| | | +| ------ | --- | +| Module | | +| Druxt | | +| Nuxt | | +| Node | | +| Drupal | | diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 0000000..724f74e --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,20 @@ + + +## What changed + +## Why + +## How it was checked + +- [ ] `npm run lint` passes +- [ ] `npm test` passes, and coverage did not drop +- [ ] Visual baselines regenerated via the manual `visual:update` job, if the + rendering changed +- [ ] Nothing that resolves only on a private network reached a tracked file diff --git a/.gitlab/scripts/check-attribution.sh b/.gitlab/scripts/check-attribution.sh new file mode 100755 index 0000000..857b430 --- /dev/null +++ b/.gitlab/scripts/check-attribution.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Rejects AI authorship claims in commits, the merge request description and +# tracked files. The work is the author's; a tool takes no credit for it. +# +# Usage: check-attribution.sh [--files-only] [] +# +# Commits: the range ..HEAD. Without an argument the base is read from +# ATTRIBUTION_BASE_SHA, then CI_MERGE_REQUEST_DIFF_BASE_SHA, then +# CI_COMMIT_BEFORE_SHA. With none of those only HEAD is read. +# +# Description: MERGE_REQUEST_DESCRIPTION, then CI_MERGE_REQUEST_DESCRIPTION. +# When GITLAB_API_TOKEN, CI_API_V4_URL, CI_PROJECT_ID and CI_MERGE_REQUEST_IID +# are all set the description is fetched instead, because the variable is cut +# at 2700 characters and a footer sits at the end. On a merge request pipeline +# with no description from either source the check refuses: passing over a +# description it never read is not a pass. +# +# Files: every tracked text file, whatever the range. +# +# Exit codes: 0 clean, 1 attribution found, 2 cannot run. +# +# The rejected strings are assembled from fragments below, so this file passes +# its own scan. Keep them that way. + +set -uo pipefail + +files_only=0 +base="" +for arg in "$@"; do + case "$arg" in + --files-only) files_only=1 ;; + -h|--help) sed -n '2,22p' "$0"; exit 0 ;; + *) base="$arg" ;; + esac +done + +if ! git rev-parse --show-toplevel >/dev/null 2>&1; then + echo "[ERROR] not inside a git repository; nothing to check." >&2 + exit 2 +fi + +# Tool names that must not appear as an author, co-author or generator. +tools='claude|anthropic|copilot|chatgpt|openai|gemini|codex|cursor|aider|devin|windsurf|opencode|glm' + +# Each alternative in pieces: the trailer key, the footer, the session link +# and the noreply address are the four shapes seen in practice. +trailer='co-authored'"-by:.*(${tools})" +footer="generated (with|by).*(${tools}|\\bai\\b)" +session='claude-'"session:|claude\\.ai/code/session" +address='noreply'"@anthropic\\.com" +pattern="(${trailer})|(${footer})|(${session})|(${address})" + +failures=0 +report() { + echo "[FAIL] $1" + failures=$((failures + 1)) +} + +# --- commits ----------------------------------------------------------------- + +commits_checked=0 +if [ "$files_only" -eq 0 ]; then + if [ -z "$base" ]; then + base="${ATTRIBUTION_BASE_SHA:-${CI_MERGE_REQUEST_DIFF_BASE_SHA:-}}" + fi + if [ -z "$base" ] && [ -n "${CI_COMMIT_BEFORE_SHA:-}" ] \ + && [ "$CI_COMMIT_BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then + base="$CI_COMMIT_BEFORE_SHA" + fi + + # An all-zero sha is what GitHub passes as the previous commit of a first + # push, and what GitLab passes as CI_COMMIT_BEFORE_SHA for a new branch. + case "$base" in 0000000000000000000000000000000000000000) base="" ;; esac + + if [ -n "$base" ]; then + if ! git cat-file -e "${base}^{commit}" 2>/dev/null; then + echo "[ERROR] base ${base} is not a commit in this repository." >&2 + exit 2 + fi + shas="$(git rev-list "${base}..HEAD")" + else + shas="$(git rev-parse HEAD)" + fi + + for sha in $shas; do + commits_checked=$((commits_checked + 1)) + short="$(git rev-parse --short "$sha")" + while IFS= read -r line; do + [ -n "$line" ] && report "commit ${short}: ${line}" + done < <(git log -1 --format=%B "$sha" | grep -iE "$pattern" || true) + done +fi + +# --- merge request description ----------------------------------------------- + +description_checked=0 +if [ "$files_only" -eq 0 ]; then + description="" + have_description=0 + + if [ -n "${GITLAB_API_TOKEN:-}" ] && [ -n "${CI_API_V4_URL:-}" ] \ + && [ -n "${CI_PROJECT_ID:-}" ] && [ -n "${CI_MERGE_REQUEST_IID:-}" ]; then + url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" + payload="$(curl -sSf --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" "$url" 2>/dev/null || true)" + if [ -n "$payload" ]; then + if command -v python3 >/dev/null 2>&1; then + description="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("description") or "")')" + elif command -v node >/dev/null 2>&1; then + description="$(printf '%s' "$payload" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(JSON.parse(d).description||""))')" + fi + have_description=1 + fi + fi + + if [ "$have_description" -eq 0 ]; then + if [ -n "${MERGE_REQUEST_DESCRIPTION+x}" ]; then + description="$MERGE_REQUEST_DESCRIPTION" + have_description=1 + elif [ -n "${CI_MERGE_REQUEST_DESCRIPTION+x}" ]; then + description="$CI_MERGE_REQUEST_DESCRIPTION" + have_description=1 + if [ "${#description}" -ge 2700 ]; then + echo "[WARN] CI_MERGE_REQUEST_DESCRIPTION is cut at 2700 characters; set GITLAB_API_TOKEN so the full description is read." >&2 + fi + elif [ -n "${CI_MERGE_REQUEST_IID:-}" ]; then + echo "[ERROR] merge request pipeline, but the description is not available." >&2 + echo " GitLab exposes it as CI_MERGE_REQUEST_DESCRIPTION (16.0 and later); other platforms pass MERGE_REQUEST_DESCRIPTION." >&2 + exit 2 + fi + fi + + if [ "$have_description" -eq 1 ]; then + description_checked=1 + while IFS= read -r line; do + [ -n "$line" ] && report "merge request description: ${line}" + done < <(printf '%s\n' "$description" | grep -iE "$pattern" || true) + fi +fi + +# --- tracked files ----------------------------------------------------------- + +files_checked="$(git ls-files | wc -l | tr -d ' ')" +while IFS= read -r line; do + [ -n "$line" ] && report "$line" +done < <(git grep -nIiE "$pattern" -- . || true) + +# --- verdict ----------------------------------------------------------------- + +scope="${files_checked} tracked files" +if [ "$files_only" -eq 0 ]; then + scope="${commits_checked} commits, $([ "$description_checked" -eq 1 ] && echo 'the merge request description' || echo 'no description'), ${scope}" +fi + +if [ "$failures" -gt 0 ]; then + echo "[FAIL] ${failures} attribution line(s) across ${scope}. The work is the author's; remove the credit." + exit 1 +fi +echo "[PASS] no AI attribution in ${scope}." diff --git a/.gitlab/scripts/delete-mr-note.sh b/.gitlab/scripts/delete-mr-note.sh new file mode 100755 index 0000000..10e19f5 --- /dev/null +++ b/.gitlab/scripts/delete-mr-note.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Delete the merge-request note carrying a marker, if one exists. +# +# Usage: delete-mr-note.sh +# +# The counterpart to upsert-mr-note.sh. A failure note that stays after the +# failure is fixed is worse than no note: it reports a problem that no longer +# exists, and a reviewer has no way to tell it is stale. +# +# Absence of a matching note is success, not an error: the common case is a +# pipeline that never failed in the first place. +# +# Environment: see mr-note-lib.sh. + +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scaffold/standards/gitlab-scripts/mr-note-lib.sh +. "$script_dir/mr-note-lib.sh" + +marker="${1:?usage: delete-mr-note.sh }" + +require_token + +url="$(notes_url)" +existing_id="$(find_note_id "$marker")" + +if [ -n "$existing_id" ]; then + api_write --request DELETE "${url}/${existing_id}" > /dev/null + echo "Deleted merge-request note ${existing_id}" +else + echo "No note carries ${marker}; nothing to delete." +fi diff --git a/.gitlab/scripts/install-vale.sh b/.gitlab/scripts/install-vale.sh new file mode 100755 index 0000000..10dd246 --- /dev/null +++ b/.gitlab/scripts/install-vale.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# Installs a pinned Vale and the ai-tells style package, both checksum-verified. +# +# Usage: install-vale.sh [] +# +# Why not `vale sync`: it fetches whatever the package URL serves and verifies +# nothing. Here the version and its checksum move together, so a bump is a +# reviewed change rather than something that happens inside a job. +# +# Environment: +# VALE_VERSION pinned below. Overriding it requires VALE_SHA256 too, +# because the pinned checksums are for the pinned version. +# VALE_SHA256 expected checksum of the Vale archive for this platform. +# AI_TELLS_VERSION pinned below. Same rule: override both or neither. +# AI_TELLS_SHA256 expected checksum of the style package archive. +# +# Exit codes: 0 installed or already present, 1 checksum mismatch, 2 cannot run. + +set -euo pipefail + +bin_dir="${1:?usage: install-vale.sh []}" +styles_dir="${2:-}" + +pinned_vale="3.17.1" +pinned_ai_tells="1.31.0" +pinned_ai_tells_sha256="bc1267248f13e65928475c439ad7ae1bf806a20d09254c08d7d8c4a9c8b811f0" + +vale_version="${VALE_VERSION:-$pinned_vale}" +ai_tells_version="${AI_TELLS_VERSION:-$pinned_ai_tells}" + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) + asset="Linux_64-bit" + pinned_vale_sha256="db947f89f2292e6a0381a61de155f6a5f5cb4cb460ca178ea412ef605559cefd" ;; + Linux-aarch64|Linux-arm64) + asset="Linux_arm64" + pinned_vale_sha256="92d91ebf9ee69ec077379be95cd09e6710ab33d3d5bab66bb482e66ebc80dc23" ;; + Darwin-x86_64) + asset="macOS_64-bit" + pinned_vale_sha256="b37ab999dfd1414d041bd2e94ced103292d634da76f954c385bf789dc7f5f939" ;; + Darwin-arm64) + asset="macOS_arm64" + pinned_vale_sha256="80cacf85ef23f53cfdd77355ec41a6ef99aec136f15dfb3517723482f35593f9" ;; + *) + echo "Unsupported platform: $(uname -s) $(uname -m)" >&2 + exit 2 ;; +esac + +if [ "$vale_version" != "$pinned_vale" ] && [ -z "${VALE_SHA256:-}" ]; then + echo "VALE_VERSION=${vale_version} is not the pinned ${pinned_vale}; set VALE_SHA256 for it." >&2 + exit 2 +fi +if [ "$ai_tells_version" != "$pinned_ai_tells" ] && [ -z "${AI_TELLS_SHA256:-}" ]; then + echo "AI_TELLS_VERSION=${ai_tells_version} is not the pinned ${pinned_ai_tells}; set AI_TELLS_SHA256 for it." >&2 + exit 2 +fi +vale_sha256="${VALE_SHA256:-$pinned_vale_sha256}" +ai_tells_sha256="${AI_TELLS_SHA256:-$pinned_ai_tells_sha256}" + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +# `sha256sum` on Linux, `shasum` on macOS. Either way the comparison is +# exact, and a mismatch removes the download so nothing later can run it. +verify() { + local file="$1" expected="$2" actual + if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$file" | cut -d' ' -f1)" + else + actual="$(shasum -a 256 "$file" | cut -d' ' -f1)" + fi + if [ "$actual" != "$expected" ]; then + echo "checksum mismatch for $(basename "$file"): expected ${expected}, got ${actual}; refusing to install it." >&2 + rm -f "$file" + return 1 + fi +} + +fetch() { + curl -sSfL --retry 3 -o "$2" "$1" +} + +# --- Vale -------------------------------------------------------------------- + +if [ -x "$bin_dir/vale" ] && "$bin_dir/vale" --version 2>/dev/null | grep -q "$vale_version"; then + echo "vale ${vale_version} already installed in ${bin_dir}" +else + archive="$work/vale.tar.gz" + fetch "https://github.com/errata-ai/vale/releases/download/v${vale_version}/vale_${vale_version}_${asset}.tar.gz" "$archive" + verify "$archive" "$vale_sha256" || exit 1 + tar -xzf "$archive" -C "$work" vale + mkdir -p "$bin_dir" + install -m 0755 "$work/vale" "$bin_dir/vale" + echo "vale ${vale_version} installed in ${bin_dir}" +fi + +# --- ai-tells ---------------------------------------------------------------- + +if [ -z "$styles_dir" ]; then + exit 0 +fi + +marker="$styles_dir/ai-tells/.installed-version" +if [ -f "$marker" ] && [ "$(cat "$marker")" = "$ai_tells_version" ]; then + echo "ai-tells ${ai_tells_version} already installed in ${styles_dir}" + exit 0 +fi + +zip="$work/ai-tells.zip" +fetch "https://github.com/tbhb/vale-ai-tells/releases/download/v${ai_tells_version}/ai-tells.zip" "$zip" +verify "$zip" "$ai_tells_sha256" || exit 1 + +# The package is a zip. Not every image has unzip, and every image that runs +# this has one of the others. +if command -v unzip >/dev/null 2>&1; then + unzip -q -o "$zip" -d "$work/unpacked" +elif command -v python3 >/dev/null 2>&1; then + python3 -m zipfile -e "$zip" "$work/unpacked" +elif command -v bsdtar >/dev/null 2>&1; then + mkdir -p "$work/unpacked" && bsdtar -xf "$zip" -C "$work/unpacked" +else + echo "No unzip, python3 or bsdtar available to unpack the style package." >&2 + exit 2 +fi + +if [ ! -d "$work/unpacked/ai-tells/styles/ai-tells" ]; then + echo "The style package did not contain styles/ai-tells; its layout has changed." >&2 + exit 2 +fi +mkdir -p "$styles_dir" +rm -rf "$styles_dir/ai-tells" +cp -R "$work/unpacked/ai-tells/styles/ai-tells" "$styles_dir/ai-tells" +printf '%s\n' "$ai_tells_version" > "$marker" +echo "ai-tells ${ai_tells_version} installed in ${styles_dir}" diff --git a/.gitlab/scripts/lint-prose.sh b/.gitlab/scripts/lint-prose.sh new file mode 100755 index 0000000..1985c91 --- /dev/null +++ b/.gitlab/scripts/lint-prose.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# Lints prose with Vale: the markdown a change touches, the commit messages in +# its range, and the merge request description. All three are content someone +# wrote for this repository, and the project voice applies to all of it. +# +# Usage: lint-prose.sh [--all] [] +# +# Markdown: files added or modified in ..HEAD; with --all, every tracked +# markdown file. Without an argument the base is read from PROSE_BASE_SHA, +# then CI_MERGE_REQUEST_DIFF_BASE_SHA, then CI_COMMIT_BEFORE_SHA. With none of +# those the files and message of HEAD alone are read. +# +# Description: MERGE_REQUEST_DESCRIPTION, then CI_MERGE_REQUEST_DESCRIPTION. +# A merge request pipeline with neither is refused, as check-attribution.sh +# does and for the same reason. +# +# Environment: +# VALE_BIN path to vale; PATH otherwise. See install-vale.sh. +# PROSE_LINT_GLOB passed to `vale --glob` for the repository's markdown, +# to keep fixtures and vendored prose out. Never applied +# to commit messages or the description: those are never +# fixtures. +# +# Before any input is read, an em-dash is written to a file outside the tree +# and linted. If that does not fire, the .vale.ini has no section covering the +# generated inputs and the run would pass by linting nothing, so it refuses. +# +# Exit codes: 0 clean, 1 findings, 2 cannot run. + +set -uo pipefail + +all=0 +base="" +for arg in "$@"; do + case "$arg" in + --all) all=1 ;; + -h|--help) sed -n '2,28p' "$0"; exit 0 ;; + *) base="$arg" ;; + esac +done + +root="$(git rev-parse --show-toplevel 2>/dev/null)" || { + echo "[ERROR] not inside a git repository; nothing to lint." >&2 + exit 2 +} + +# VALE_BIN, then PATH, then the repository's own .vale/bin, which is where +# install-vale.sh puts it when a repository installs it for itself. +vale="${VALE_BIN:-$(command -v vale 2>/dev/null || true)}" +if [ -z "$vale" ] && [ -x "$root/.vale/bin/vale" ]; then + vale="$root/.vale/bin/vale" +fi +if [ -z "$vale" ] || [ ! -x "$vale" ]; then + echo "[ERROR] vale is not installed. Run install-vale.sh .vale/bin .vale/styles in the repository root, or put vale on PATH." >&2 + exit 2 +fi + +ini="$root/.vale.ini" +if [ ! -f "$ini" ]; then + echo "[ERROR] no .vale.ini at ${root}; there is no project voice to apply." >&2 + exit 2 +fi + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +# --- canary ------------------------------------------------------------------ + +mkdir -p "$work/canary" +printf 'Canary text %s with a dash.\n' "$(printf '\xe2\x80\x94')" > "$work/canary/canary.md" +canary_out="$(cd "$work/canary" && "$vale" --config "$ini" --output=line canary.md 2>&1)" +canary_rc=$? +if [ "$canary_rc" -eq 0 ] || ! printf '%s' "$canary_out" | grep -q 'canary.md:'; then + echo "[ERROR] canary did not fire: .vale.ini applies no em-dash rule to files outside the tree, so commit messages and the description would be linted by nothing." >&2 + echo " Add a section such as [*.md] with BasedOnStyles = ai-tells. Vale said:" >&2 + printf '%s\n' "$canary_out" | sed 's/^/ /' >&2 + exit 2 +fi + +# --- range ------------------------------------------------------------------- + +if [ -z "$base" ]; then + base="${PROSE_BASE_SHA:-${CI_MERGE_REQUEST_DIFF_BASE_SHA:-}}" +fi +if [ -z "$base" ] && [ -n "${CI_COMMIT_BEFORE_SHA:-}" ] \ + && [ "$CI_COMMIT_BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then + base="$CI_COMMIT_BEFORE_SHA" +fi +case "$base" in 0000000000000000000000000000000000000000) base="" ;; esac +if [ -n "$base" ] && ! git cat-file -e "${base}^{commit}" 2>/dev/null; then + echo "[ERROR] base ${base} is not a commit in this repository." >&2 + exit 2 +fi + +findings=0 + +# --- commit messages and the description ------------------------------------- + +inputs="$work/inputs" +mkdir -p "$inputs/commits" + +if [ -n "$base" ]; then + shas="$(git rev-list "${base}..HEAD")" +else + shas="$(git rev-parse HEAD)" +fi +commits=0 +for sha in $shas; do + commits=$((commits + 1)) + git log -1 --format=%B "$sha" > "$inputs/commits/$(git rev-parse --short "$sha").md" +done + +description_state="no description" +if [ -n "${MERGE_REQUEST_DESCRIPTION+x}" ]; then + printf '%s\n' "$MERGE_REQUEST_DESCRIPTION" > "$inputs/merge-request.md" + description_state="the merge request description" +elif [ -n "${CI_MERGE_REQUEST_DESCRIPTION+x}" ]; then + printf '%s\n' "$CI_MERGE_REQUEST_DESCRIPTION" > "$inputs/merge-request.md" + description_state="the merge request description" +elif [ -n "${CI_MERGE_REQUEST_IID:-}" ]; then + echo "[ERROR] merge request pipeline, but the description is not available." >&2 + echo " GitLab exposes it as CI_MERGE_REQUEST_DESCRIPTION (16.0 and later); other platforms pass MERGE_REQUEST_DESCRIPTION." >&2 + exit 2 +fi + +generated=() +for f in "$inputs"/commits/*.md "$inputs/merge-request.md"; do + [ -f "$f" ] && generated+=("${f#"$inputs"/}") +done +if [ "${#generated[@]}" -gt 0 ]; then + out="$(cd "$inputs" && "$vale" --config "$ini" --output=line "${generated[@]}" 2>&1)" + rc=$? + if [ -n "$out" ]; then + printf '%s\n' "$out" + fi + if [ "$rc" -ne 0 ]; then + findings=$((findings + 1)) + fi +fi + +# --- the repository's markdown ----------------------------------------------- + +markdown=() +if [ "$all" -eq 1 ]; then + mapfile -d '' -t markdown < <(git -C "$root" ls-files -z -- '*.md') +elif [ -n "$base" ]; then + mapfile -d '' -t markdown < <(git -C "$root" diff -z --name-only --diff-filter=AMR "$base" HEAD -- '*.md') +else + mapfile -d '' -t markdown < <(git -C "$root" diff-tree -z --no-commit-id -r --name-only --diff-filter=AMR HEAD -- '*.md') +fi +existing=() +for f in "${markdown[@]}"; do + [ -f "$root/$f" ] && existing+=("$f") +done + +if [ "${#existing[@]}" -gt 0 ]; then + glob=() + if [ -n "${PROSE_LINT_GLOB:-}" ]; then + glob=(--glob="$PROSE_LINT_GLOB") + fi + out="$(cd "$root" && "$vale" --config "$ini" --output=line "${glob[@]}" "${existing[@]}" 2>&1)" + rc=$? + if [ -n "$out" ]; then + printf '%s\n' "$out" + fi + if [ "$rc" -ne 0 ]; then + findings=$((findings + 1)) + fi +fi + +# --- verdict ----------------------------------------------------------------- + +scope="${commits} commit message(s), ${description_state}, ${#existing[@]} markdown file(s)" +if [ "$findings" -gt 0 ]; then + echo "[FAIL] prose findings across ${scope}." + exit 1 +fi +echo "[PASS] prose clean across ${scope}." diff --git a/.gitlab/scripts/mr-note-lib.sh b/.gitlab/scripts/mr-note-lib.sh new file mode 100755 index 0000000..a1b0f79 --- /dev/null +++ b/.gitlab/scripts/mr-note-lib.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Shared helpers for the merge-request note scripts. Sourced, not executed. +# +# Environment: +# GITLAB_API_TOKEN required by every caller. See require_token. +# CI_API_V4_URL required. Set by GitLab CI. +# CI_PROJECT_ID required. Set by GitLab CI. Never defaulted to a +# literal: that works in one repository and is +# silently wrong in every other. +# CI_MERGE_REQUEST_IID required. Only set on merge-request pipelines. +# MR_NOTE_INTERPRETER optional. Force `python3` or `node` for JSON +# parsing. Auto-detected otherwise; exists so the +# tests can exercise both branches. +# +# Why two interpreters: these scripts run in two different images. The visual +# and preview jobs need node, because Playwright and the application build do. +# The workspace's own pipeline runs on a python image. A script that assumed +# either works in one and fails in the other, and it fails by finding no note +# at all, so every run posts a duplicate instead of erroring. + +# Fail before anything reaches the network. Without this the request goes out +# unauthenticated and comes back as an opaque 401, which reads as an outage +# rather than as a missing variable. +require_token() { + if [ -z "${GITLAB_API_TOKEN:-}" ]; then + echo "GITLAB_API_TOKEN is not set; refusing to call the API." >&2 + return 1 + fi +} + +# Echo the interpreter to parse JSON with, or fail naming both candidates. +json_interpreter() { + local forced="${MR_NOTE_INTERPRETER:-}" + if [ -n "$forced" ]; then + if command -v "$forced" >/dev/null 2>&1; then + printf '%s' "$forced" + return 0 + fi + echo "MR_NOTE_INTERPRETER=${forced} is not executable." >&2 + echo "Set it to python3 or node, or unset it to auto-detect." >&2 + return 1 + fi + if command -v python3 >/dev/null 2>&1; then + printf 'python3' + return 0 + fi + if command -v node >/dev/null 2>&1; then + printf 'node' + return 0 + fi + echo "Neither python3 nor node is available to parse the API response." >&2 + return 1 +} + +# The id of the note carrying $1, read from JSON on stdin. Empty when none. +# +# Matching on the marker rather than the body means another job's note is +# never hijacked. Exit 2 says the payload was not a list of notes, which is a +# different thing from a page that holds no match: treating the two alike let +# a garbled response post a second copy of a note that already existed. +note_id_for() { + local marker="$1" interpreter + interpreter="$(json_interpreter)" || return 1 + + if [ "$interpreter" = "python3" ]; then + python3 -c ' +import json, sys + +marker = sys.argv[1] +try: + notes = json.load(sys.stdin) +except Exception: + sys.exit(2) +if not isinstance(notes, list): + sys.exit(2) +for note in notes: + if isinstance(note, dict) and marker in (note.get("body") or ""): + sys.stdout.write(str(note.get("id", ""))) + break +' "$marker" + else + node -e ' +const fs = require("fs"); +const marker = process.argv[1]; +let notes; +try { + notes = JSON.parse(fs.readFileSync(0, "utf8")); +} catch { process.exit(2); } +if (!Array.isArray(notes)) process.exit(2); +const match = notes.find((n) => n && n.body && n.body.includes(marker)); +process.stdout.write(match ? String(match.id) : ""); +' "$marker" + fi +} + +# Echo the notes endpoint for this merge request. +notes_url() { + local api project iid + api="${CI_API_V4_URL:?CI_API_V4_URL is not set}" + project="${CI_PROJECT_ID:?CI_PROJECT_ID is not set}" + iid="${CI_MERGE_REQUEST_IID:?CI_MERGE_REQUEST_IID is not set; this job needs a merge-request pipeline}" + printf '%s/projects/%s/merge_requests/%s/notes' "$api" "$project" "$iid" +} + +# curl with retries and the token header, as an array to expand at the call +# site. The token is passed as a header value, never on a URL, so it does not +# land in a log line that echoes the request target. +api_curl() { + # --fail: without it curl exits 0 on 403 or 500 and the caller treats the + # error body as a result, so a note that was never posted reports success. + curl -sS --fail --connect-timeout 10 --max-time 30 \ + --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" "$@" +} + +# Reading is safe to repeat, so it retries. +api_read() { + api_curl --retry 3 --retry-delay 2 "$@" +} + +# Writing is not safe to repeat. curl resends the body when a connection drops +# after GitLab accepted the request, which posts the note twice: the one thing +# these scripts exist to prevent. +api_write() { + api_curl "$@" +} + +# Echo the id of the note carrying the marker, or nothing. +# +# The notes endpoint pages, newest first. Reading only the first page misses +# the marker on a busy merge request, and the caller then posts a duplicate +# instead of editing the note that is already there. +find_note_id() { + local marker="$1" url page id body + url="$(notes_url)" + page=1 + while [ "$page" -le 20 ]; do + body="$(api_read "${url}?per_page=100&page=${page}")" || return 1 + case "$body" in '' | '[]') return 0 ;; esac + id="$(printf '%s' "$body" | note_id_for "$marker")" || return 1 + if [ -n "$id" ]; then + printf '%s' "$id" + return 0 + fi + page=$((page + 1)) + done + return 0 +} diff --git a/.gitlab/scripts/post-live-preview-comment.sh b/.gitlab/scripts/post-live-preview-comment.sh new file mode 100755 index 0000000..77c4e8c --- /dev/null +++ b/.gitlab/scripts/post-live-preview-comment.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Post a preview URL to the merge request. +# +# Usage: post-live-preview-comment.sh [keep-alive-minutes] [label] +# +# A tunnel URL that appears only in a job log means the reviewer has to know +# the job exists, run it, and watch its output. Posted to the merge request +# with its expiry, it is a link they can click. +# +# Environment: +# GITLAB_API_TOKEN required. A token with api scope. +# CI_API_V4_URL required. Set by GitLab CI. +# CI_PROJECT_ID required. Set by GitLab CI, never defaulted here. +# CI_MERGE_REQUEST_IID required. Only set on merge-request pipelines. +# CI_COMMIT_SHORT_SHA reported, so a reader knows what is being served. + +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" + +url="${1:?usage: post-live-preview-comment.sh [minutes] [label]}" +minutes="${2:-55}" +label="${3:-Preview}" + +marker="" +body_file="$(mktemp)" +{ + echo "$marker" + echo "### ${label} at \`${CI_COMMIT_SHORT_SHA:-unknown}\`" + echo + echo "| Target | URL |" + echo "| --- | --- |" + echo "| ${label} | ${url} |" + echo + echo "The tunnel closes when the job ends, in about ${minutes} minutes." + echo "Re-run the manual preview job for a fresh one." +} > "$body_file" + +bash "$script_dir/upsert-mr-note.sh" "$marker" "$body_file" +rm -f "$body_file" + +echo "Posted preview URL: ${url}" diff --git a/.gitlab/scripts/post-visual-failure-comment.sh b/.gitlab/scripts/post-visual-failure-comment.sh new file mode 100755 index 0000000..2955098 --- /dev/null +++ b/.gitlab/scripts/post-visual-failure-comment.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# Post failed visual snapshots to the merge request as images. +# +# Usage: post-visual-failure-comment.sh (run as the visual job's after_script) +# +# A visual diff described in a job log is not reviewable. Baseline, render and +# diff side by side in one note is. Run from after_script so it reports whether +# the job passed or failed: on a pass it removes its own previous note, so a +# fixed failure stops being shown. +# +# Environment: +# VISUAL_RESULTS_DIR Playwright's output directory. Default: test-results +# Set it per repository. The original this derives from +# hardcoded `nuxt/test-results`, which works in exactly +# one repository and fails silently everywhere else, by +# finding no screenshots and reporting success. +# GITLAB_API_TOKEN required. A token with api scope. +# CI_API_V4_URL required. Set by GitLab CI. +# CI_PROJECT_ID required. Set by GitLab CI, never defaulted here. +# CI_MERGE_REQUEST_IID required. Only set on merge-request pipelines. +# CI_JOB_NAME used to key the marker, so two visual jobs (say +# viewports and SEO) do not overwrite each other. +# CI_COMMIT_SHORT_SHA reported in the note, so a reader knows which render. + +set -uo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scaffold/standards/gitlab-scripts/mr-note-lib.sh +. "$script_dir/mr-note-lib.sh" + +results_dir="${VISUAL_RESULTS_DIR:-test-results}" +job="${CI_JOB_NAME:-visual}" +marker="" + +require_token || exit 1 + +if [ ! -d "$results_dir" ]; then + echo "No ${results_dir} directory; nothing to report." + exit 0 +fi + +# Playwright writes -actual.png beside the expected and diff images for +# every failing snapshot. +failed_renders=() +while IFS= read -r file; do + failed_renders+=("$file") +done < <(find "$results_dir" -name '*-actual.png' -type f | sort) + +if [ ${#failed_renders[@]} -eq 0 ]; then + echo "No visual failures." + # Remove a note left by an earlier run, so a fixed failure stops showing. + bash "$script_dir/delete-mr-note.sh" "$marker" || true + exit 0 +fi + +api="${CI_API_V4_URL:?CI_API_V4_URL is not set}" +project="${CI_PROJECT_ID:?CI_PROJECT_ID is not set}" +uploads_url="${api}/projects/${project}/uploads" + +# Upload one image and return the Markdown GitLab gives back for it. +# Same two-interpreter reason as mr-note-lib.sh: this runs on a node image in +# the visual job and on a python one in the workspace's own pipeline. +upload() { + local response interpreter + response="$(api_write --request POST --form "file=@$1" "$uploads_url")" + interpreter="$(json_interpreter)" || return 1 + if [ "$interpreter" = "python3" ]; then + printf '%s' "$response" | python3 -c ' +import json, sys +try: + sys.stdout.write(json.load(sys.stdin).get("markdown") or "") +except Exception: + pass +' + else + printf '%s' "$response" | node -e ' +const fs = require("fs"); +let markdown = ""; +try { markdown = JSON.parse(fs.readFileSync(0, "utf8")).markdown || ""; } catch {} +process.stdout.write(markdown); +' + fi +} + +body_file="$(mktemp)" +{ + echo "$marker" + echo "### Visual failures: \`${job}\` at \`${CI_COMMIT_SHORT_SHA:-unknown}\`" + echo + for actual in "${failed_renders[@]}"; do + label="$(basename "$(dirname "$actual")" | sed 's/-retry[0-9]*$//')" + expected="${actual%-actual.png}-expected.png" + diff_image="${actual%-actual.png}-diff.png" + + actual_md="$(upload "$actual")" + + # A new snapshot has no committed baseline, and that is exactly the case a + # reviewer needs to see. Report it as missing rather than failing here. + expected_md="_no committed baseline_" + if [ -f "$expected" ]; then + expected_md="$(upload "$expected")" + fi + + diff_md="_not produced_" + if [ -f "$diff_image" ]; then + diff_md="$(upload "$diff_image")" + fi + + echo "#### \`${label}\`" + echo + echo "| Baseline | Render | Diff |" + echo "| --- | --- | --- |" + echo "| ${expected_md} | ${actual_md} | ${diff_md} |" + echo + done + echo "To accept these renders, run the manual \`visual:update\` job and commit" + echo "the PNGs it produces. Regenerate on x86_64 only: Chromium renders" + echo "differently on ARM, and an ARM baseline is a permanent false diff for" + echo "everyone else." +} > "$body_file" + +bash "$script_dir/upsert-mr-note.sh" "$marker" "$body_file" +rm -f "$body_file" diff --git a/.gitlab/scripts/run-live-preview-tunnel.sh b/.gitlab/scripts/run-live-preview-tunnel.sh new file mode 100755 index 0000000..26a9675 --- /dev/null +++ b/.gitlab/scripts/run-live-preview-tunnel.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Serve a built directory through a Cloudflare Quick Tunnel and post the URL. +# +# Usage: run-live-preview-tunnel.sh (run as a manual job's script) +# +# A Quick Tunnel needs no Cloudflare account and no token, and dies with the +# job that opened it. Manual, because a tunnel on every pipeline is waste and a +# reviewer knows when they want one. +# +# Environment: +# PREVIEW_DIR directory to serve. Default: dist +# Set it per repository. The original this +# derives from ran a pnpm dev server from a +# fixed `nuxt/` path, which is not portable. +# PREVIEW_PORT local port to serve on. Default: 8080 +# PREVIEW_KEEP_ALIVE_MINUTES how long to hold the tunnel. Default: 55 +# Keep it under the job timeout. +# PREVIEW_LABEL what to call it in the note. Default: Preview +# CLOUDFLARED_VERSION pinned release. Default below. +# GITLAB_API_TOKEN et al as required by post-live-preview-comment.sh. +# +# The tunnel and the served directory both terminate with the job: everything +# started here is killed on exit, and the note is removed with it, so the merge +# request never shows a link that no longer resolves. + +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" + +preview_dir="${PREVIEW_DIR:-dist}" +preview_port="${PREVIEW_PORT:-8080}" +keep_alive_minutes="${PREVIEW_KEEP_ALIVE_MINUTES:-55}" +preview_label="${PREVIEW_LABEL:-Preview}" +cloudflared_version="${CLOUDFLARED_VERSION:-2026.7.3}" + +marker="" +serve_pid="" +tunnel_pid="" + +cleanup() { + [ -n "$serve_pid" ] && kill "$serve_pid" 2>/dev/null || true + [ -n "$tunnel_pid" ] && kill "$tunnel_pid" 2>/dev/null || true + # The URL stops resolving the moment this job ends, so the note goes with it. + bash "$script_dir/delete-mr-note.sh" "$marker" 2>/dev/null || true +} +trap cleanup EXIT +trap 'cleanup; exit' INT TERM + +if [ ! -d "$preview_dir" ]; then + echo "Nothing to serve: ${preview_dir} does not exist." >&2 + echo "Build first, or set PREVIEW_DIR to the built output." >&2 + exit 1 +fi + +# Checksums are per architecture and pinned: an unverified binary that opens a +# public tunnel to this build is not something to fetch on trust. The runner +# may be ARM or x86_64, so detect rather than assume. +case "$(uname -m)" in + aarch64|arm64) + cf_arch="arm64" + cf_sha256="65259e652a7bea08bf5df603233ab22b8bf3116af8df9f9206209af6a1b955c0" + ;; + x86_64) + cf_arch="amd64" + cf_sha256="9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17" + ;; + *) + echo "Unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +bin_dir="$(mktemp -d)" +curl -sL --retry 3 \ + "https://github.com/cloudflare/cloudflared/releases/download/${cloudflared_version}/cloudflared-linux-${cf_arch}" \ + -o "${bin_dir}/cloudflared" +echo "${cf_sha256} ${bin_dir}/cloudflared" | sha256sum -c - || { + echo "cloudflared checksum mismatch; refusing to run it." >&2 + exit 1 +} +chmod +x "${bin_dir}/cloudflared" + +echo "Serving ${preview_dir} on :${preview_port}" +npx --yes serve -l "$preview_port" "$preview_dir" > /dev/null 2>&1 & +serve_pid=$! + +# cloudflared self-updates and restarts by default, which kills the tunnel +# whose URL was already posted and quietly opens a new one somewhere else. +export NO_AUTOUPDATE=true + +tunnel_log="$(mktemp)" +"${bin_dir}/cloudflared" tunnel --url "http://localhost:${preview_port}" \ + --no-autoupdate > "$tunnel_log" 2>&1 & +tunnel_pid=$! + +url="" +for _ in $(seq 1 60); do + url="$(grep -om1 'https://[a-z0-9-]*\.trycloudflare\.com' "$tunnel_log" || true)" + [ -n "$url" ] && break + sleep 2 +done + +if [ -z "$url" ]; then + echo "The tunnel did not report a URL within two minutes." >&2 + cat "$tunnel_log" >&2 + exit 1 +fi + +bash "$script_dir/post-live-preview-comment.sh" \ + "$url" "$keep_alive_minutes" "$preview_label" + +echo "Holding the tunnel for ${keep_alive_minutes} minutes." +sleep "$(( keep_alive_minutes * 60 ))" diff --git a/.gitlab/scripts/upsert-mr-note.sh b/.gitlab/scripts/upsert-mr-note.sh new file mode 100755 index 0000000..7345adb --- /dev/null +++ b/.gitlab/scripts/upsert-mr-note.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Create or update a merge-request note identified by a hidden marker. +# +# Usage: upsert-mr-note.sh +# +# The marker is an HTML comment the note carries, so the next run finds its own +# note and edits it rather than adding another. A job that posts on every +# pipeline run buries the merge request within a day otherwise. +# +# Environment: see mr-note-lib.sh. GITLAB_API_TOKEN, CI_API_V4_URL, +# CI_PROJECT_ID and CI_MERGE_REQUEST_IID are all required. + +set -euo pipefail + +script_dir="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scaffold/standards/gitlab-scripts/mr-note-lib.sh +. "$script_dir/mr-note-lib.sh" + +marker="${1:?usage: upsert-mr-note.sh }" +body_file="${2:?usage: upsert-mr-note.sh }" + +require_token + +if [ ! -f "$body_file" ]; then + echo "Body file not found: $body_file" >&2 + exit 1 +fi + +url="$(notes_url)" +existing_id="$(find_note_id "$marker")" + +if [ -n "$existing_id" ]; then + api_write --request PUT --form "body=<${body_file}" "${url}/${existing_id}" > /dev/null + echo "Updated merge-request note ${existing_id}" +else + api_write --request POST --form "body=<${body_file}" "${url}" > /dev/null + echo "Created merge-request note" +fi diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 2e88fe4..0000000 --- a/.gitpod.yml +++ /dev/null @@ -1,76 +0,0 @@ -image: - file: .gitpod/Dockerfile - -tasks: - - init: | - # Installing DDev and Drupal backend. - .gitpod/scripts/ddev-download-images.sh - .gitpod/scripts/ddev-setup.sh - .gitpod/scripts/drupal-setup.sh - - # Installing Nuxt/Druxt frontend. - .gitpod/scripts/nuxt-setup.sh - - # Build module - yarn && yarn build - command: | - .gitpod/scripts/ddev-setup.sh - .gitpod/scripts/nuxt-setup.sh - cd $GITPOD_REPO_ROOT/example/nuxt && NUXT_TELEMETRY_DISABLED=1 yarn dev - -# VScode xdebug extension -vscode: - extensions: - - dbaeumer.vscode-eslint - - editorconfig.editorconfig - - felixfbecker.php-debug - - octref.vetur - -ports: - # Drupal - - port: 8080 - onOpen: ignore - visibility: public - - # Nuxt/Druxt - - port: 3000 - onOpen: ignore - visibility: public - - # Nuxt/Druxt - - port: 3003 - onOpen: ignore - visibility: public - - # Currently un-notified and unsupported mailhog http port - - port: 8025 - onOpen: ignore - # Currently un-notified and unsupported mailhog https port - - port: 8026 - onOpen: ignore - # Currently un-notified and unsupported phpmyadmin http port - - port: 8036 - onOpen: ignore - # Currently un-notified and unsupported phpmyadmin https port - - port: 8037 - onOpen: ignore - # router http port that we're ignoring. - - port: 8888 - onOpen: ignore - # router https port that we're ignoring. - - port: 8889 - onOpen: ignore - # xdebug port - - port: 9000 - onOpen: ignore - -github: - prebuilds: - master: true - branches: true - pullRequests: true - pullRequestsFromForks: true - addCheck: true - addComment: false - addBadge: true - addLabel: true diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile deleted file mode 100644 index 6f32cbf..0000000 --- a/.gitpod/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM gitpod/workspace-full -SHELL ["/bin/bash", "-c"] - -RUN sudo apt-get -qq update - -# Install ddev -RUN brew update && brew install drud/ddev/ddev - -# Install latest composer -RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" -RUN sudo php composer-setup.php --install-dir /usr/bin --filename composer -RUN php -r "unlink('composer-setup.php');" - -# Install latest npm -RUN npm install -g npm diff --git a/.gitpod/scripts/ddev-download-images.sh b/.gitpod/scripts/ddev-download-images.sh deleted file mode 100755 index 52f005b..0000000 --- a/.gitpod/scripts/ddev-download-images.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -ddev version | awk '/(drud|phpmyadmin)/ {print $2;}' >/tmp/images.txt -while IFS= read -r item -do - docker pull "$item" -done < <(cat /tmp/images.txt) diff --git a/.gitpod/scripts/ddev-setup.sh b/.gitpod/scripts/ddev-setup.sh deleted file mode 100755 index 4604be8..0000000 --- a/.gitpod/scripts/ddev-setup.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -DRUPAL_DIR="${GITPOD_REPO_ROOT}/example/drupal" - -# Misc housekeeping before start -ddev config global --instrumentation-opt-in=true - -# Start ddev -cd $DRUPAL_DIR && ddev start diff --git a/.gitpod/scripts/drupal-setup.sh b/.gitpod/scripts/drupal-setup.sh deleted file mode 100755 index feed9a2..0000000 --- a/.gitpod/scripts/drupal-setup.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -eu -o pipefail - -DRUPAL_DIR="${GITPOD_REPO_ROOT}/example/drupal" - -# Set up Drupal website -cd "$DRUPAL_DIR" && ddev drupal-install diff --git a/.gitpod/scripts/nuxt-setup.sh b/.gitpod/scripts/nuxt-setup.sh deleted file mode 100755 index 458040e..0000000 --- a/.gitpod/scripts/nuxt-setup.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -# Set up Druxt for use on gitpod - -set -eu -o pipefail - -DRUXT_DIR="${GITPOD_REPO_ROOT}/example/nuxt" - -# Set up Nuxt -cd "$DRUXT_DIR" && echo "BASE_URL=$(gp url 8080)" > .env -cd "$DRUXT_DIR" && yarn diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..fe97038 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,27 @@ +{ + // MD013 (line length) is off: prose reflows, and a hard wrap in Markdown + // shows up as a diff on every edit to the paragraph rather than the sentence. + // MD033 (inline HTML) is off: the docs site needs it for components. + // MD024 is scoped to siblings so a changelog can repeat "Fixed" per release. + "config": { + "default": true, + "MD013": false, + "MD024": { "siblings_only": true }, + "MD033": false, + "MD041": false + }, + "ignores": [ + "node_modules/**", + "**/node_modules/**", + "dist/**", + "coverage/**", + "example/**/node_modules/**", + "example/drupal/vendor/**", + "example/drupal/web/**", + "example/nuxt/dist/**", + "example/nuxt/.nuxt/**", + ".vale/**", + "CHANGELOG.md", + ".changeset/**" + ] +} diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..a3d2a38 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,69 @@ +# Pinned toolchain. `mise install` gives every contributor and every CI job the +# same Node, so "works on my machine" is a statement about a version anyone can +# reproduce. +# +# The tasks below are the commands CI runs. Keeping them here rather than only +# in .gitlab-ci.yml means a maintainer can run exactly what the pipeline runs, +# and the pipeline stays a thin wrapper rather than a second source of truth. + +[tools] +# PHP for the example backend (example/drupal, Drupal 11); Node for the module. +php = "8.4" +node = "16.20.1" + +[tasks.install] +description = "Install dependencies with the manager package.json declares" +run = "corepack enable && corepack prepare --activate && npm ci" + +[tasks.build] +description = "Build the module with siroc" +run = "npm run build" + +[tasks.lint] +description = "Run every linter" +run = "npm run lint" + +[tasks."lint:js"] +description = "Lint the sources, tests and scripts" +run = "npm run lint:js" + +[tasks."lint:md"] +description = "Lint Markdown" +run = "npm run lint:md" + +[tasks."lint:cspell"] +description = "Spell-check the repository" +run = "npm run lint:cspell" + +[tasks."lint:private"] +description = "Fail if a tracked file points at a host only a private network resolves" +run = "npm run lint:private" + +[tasks."lint:knip"] +description = "Find unused and undeclared dependencies" +run = "npm run lint:knip" + +[tasks."lint:audit"] +description = "Audit shipped dependencies" +run = "npm run lint:audit" + +[tasks.test] +description = "Run the unit tests" +run = "npm test" + +[tasks."test:e2e"] +description = "Run the end-to-end and visual tests (needs the example app running)" +run = "npm run test:e2e" + +[tasks.ci] +description = "Everything the pipelines run, except the end-to-end suite" +depends = ["lint", "test"] + +# --------------------------------------------------------------------------- +# Git hooks. Run once per clone. `npm install` does this for you via +# scripts/postinstall.mjs; this task is here for the case where someone has +# skipped install scripts. +# --------------------------------------------------------------------------- +[tasks."hooks:install"] +description = "Point git at the committed .githooks/ directory" +run = "git config core.hooksPath .githooks" diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..4797941 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16.20.1 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8a6c67d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,21 @@ +node_modules/ +dist/ +coverage/ +reports/ +test-results/ +playwright-report/ +example/drupal/ +package-lock.json +CHANGELOG.md +**/__snapshots__/ + +# Pipeline definitions are owned by yamllint, not prettier. The two disagree on +# comment spacing, and letting both format these files means every run of one +# undoes the other. +.gitlab-ci.yml +.github/workflows/ +.vale/ +example/drupal/vendor/ +example/drupal/web/ +example/nuxt/dist/ +example/nuxt/.nuxt/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..61210b5 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,23 @@ +{ + "printWidth": 80, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "overrides": [ + { + "files": ["*.jsonc"], + "options": { + "parser": "json", + "trailingComma": "none" + } + }, + { + "files": ["*.md"], + "options": { + "printWidth": 100, + "proseWrap": "preserve" + } + } + ] +} diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000..d1e3688 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,11 @@ +# Project voice for everything written here: markdown, commit messages and +# merge request descriptions. .gitlab/scripts/lint-prose.sh feeds all three +# through this configuration. +# +# StylesPath is populated by .gitlab/scripts/install-vale.sh, pinned and +# verified by checksum, rather than by `vale sync`. +StylesPath = .vale/styles +MinAlertLevel = error + +[*.md] +BasedOnStyles = ai-tells diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..cc245ab --- /dev/null +++ b/.yamllint @@ -0,0 +1,10 @@ +--- +extends: default +rules: + # 200, not 80: pipeline scripts carry long commands, and wrapping them makes + # them harder to read rather than easier. + line-length: + max: 200 + level: warning + truthy: + check-keys: false diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..58cda7c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,61 @@ +# Agent instructions + +Starting point for a Druxt module, and the reference module for the +Druxt repository standard. + +## Rules + +- **This repository is public.** Nothing that resolves only on a private + network may reach a tracked file: no internal URLs, hostnames, repository + names or issue links, in any file including comments and patch descriptions. + `npm run lint:private` enforces the URL-shaped half of this and runs in the + pipeline. It cannot catch an internal name written as prose, so that part is + on you. +- **Conventional Commits**, and the same for pull request and merge request + titles. These repositories squash-merge, so the title becomes the commit + subject, and a prose title breaks the next push to the target branch. +- **The coverage floor in `jest.config.js` goes up, never down.** If a change + drops coverage, the change needs a test. +- **No AI tool is credited.** No co-author trailer naming an assistant, no + generated-with footer, no session link, in commits, merge request + descriptions or tracked files. The work is the author's. The commit-msg hook + rejects it locally, and `npm run lint:attribution` and the pipeline check + the rest. +- **Prose is linted with Vale.** The ai-tells style is the minimum, and it + covers the markdown a change touches, its commit messages and the merge + request description. `npm run lint:prose:install` once, then + `npm run lint:prose`. +- **Never regenerate visual baselines locally.** Use the manual `visual:update` + job. Chromium renders differently on ARM and a locally generated baseline is + a permanent false diff for everyone else. + +## Layout + +| Path | Purpose | +| ------------------ | ----------------------------------------------------------------------- | +| `src/` | The module. `index.js` is the Nuxt module, `components/` its components | +| `test/` | Unit tests. `test/e2e/` is Playwright, and is not run by `npm test` | +| `example/` | Drupal 11 on SQLite and a Nuxt application that loads the module | +| `scripts/` | Repository tooling, excluded from the package | +| `.githooks/` | Committed hooks, enabled by `npm install` | +| `.gitlab/scripts/` | Content checks and merge-request automation, copied from the standard | + +## Commands + +```bash +npm install # dependencies, and enables the git hooks +npm run build # siroc +npm test # jest, coverage floor enforced +npm run lint # every linter except prose +npm run lint:prose # Vale, after `npm run lint:prose:install` +npm run example:setup # Drupal 11 backend on SQLite, then the example's dependencies +npm run example:dev # the example on http://localhost:3000 +npm run test:e2e # Playwright against the example, backend up +``` + +## Toolchain + +Pinned in `.mise.toml`. Node is deliberately held at 16.20.1 to match what the +module's own dependencies support; the lint tooling is pinned to versions that +still run there. Bumping either is a deliberate, coordinated change, not a +routine dependency update, so Renovate is configured not to offer it. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a84318b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +Changes to this template are recorded here. The format is +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project +follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Committed git hooks in `.githooks/`, enabled on install, running the linters + and unit tests before a commit and Conventional Commits on the message. +- A GitLab pipeline covering lint, unit tests, end-to-end tests, visual + regression, secret scanning with a detection canary, and a manual preview + tunnel, with a GitHub Actions workflow running lint, unit tests and the + end-to-end suite. +- Spell check, Markdown lint, YAML lint, formatting, dead-code detection, a + production dependency audit, a private-host check, prose linting and a check + that no AI tool is credited as an author. +- An enforced coverage floor, measured from this template's own tests rather + than aspirational. +- A private-host lint, so a URL only the author can reach cannot reach a + tracked file in a public repository. +- An attribution check, in the commit-msg hook and in the pipeline, so that no + commit, merge request description or tracked file credits an AI tool as + author, co-author or generator. +- Prose linting with Vale and the ai-tells style, over the markdown a change + touches, its commit messages and the merge request description. +- Visual regression with Playwright, and merge-request automation that posts + baseline, render and diff for each failure. +- A devcontainer covering VS Code, Codespaces and DevPod. +- The example backend on Drupal 11, provisioned without Docker from a fresh + `site:install` on SQLite, and generated and tested in both pipelines. The + Drupal 9 site with its committed Tome export is gone. +- `AGENTS.md` and `CONTRIBUTING.md`. + +### Changed + +- **BREAKING (for forks).** The toolchain is replaced rather than extended. The + Node 14.x/16.x GitHub Actions matrix and the `npm i` install path are gone, + replaced by a version pinned in `.mise.toml` and an install driven by the + `packageManager` field through corepack. + + A fork that predates this change cannot fast-forward onto it. Rebase your + module's own commits onto the new base instead: + + ```bash + git remote add template https://github.com/druxt/module-template.git + git fetch template + git rebase --onto template/main + ``` + + Then run `npm install` once, which enables the hooks. + +- Renovate no longer extends the stale `@nuxtjs` preset. Updates are grouped + into one pull request, majors are not automated, and GitHub Actions are + pinned to commit SHAs. +- ESLint sets `root: true`, so a checkout inside another workspace no longer + inherits that workspace's rules. + +### Removed + +- The Gitpod configuration. It was provably unmaintained: `.gitpod.yml` + configured prebuilds for a `master` branch this repository has not had, and + its setup scripts drive `yarn` where the repository now uses npm. The + devcontainer replaces it and covers VS Code, Codespaces and DevPod. + +### Fixed + +- `vue-template-compiler` was pinned a patch behind `vue`, which made the test + suite refuse to run with a version-mismatch error. Both are now on 2.7.16. + +[Unreleased]: https://github.com/druxt/module-template/commits/main diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3f07d74 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code + +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9d250f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# Contributing + +Thanks for helping. This template is the reference module for the Druxt +repository standard, so a change here is a change to what every Druxt module +starts from. + +## Getting set up + +```bash +npm install +``` + +That installs dependencies and enables the git hooks. If you skipped install +scripts, run `npm run hooks:install` by hand, or the hooks stay on disk doing +nothing. + +The toolchain is pinned in `.mise.toml`. With [mise](https://mise.jdx.dev) +installed, `mise install` gives you the same Node the pipeline uses. + +## Before you push + +```bash +npm run lint # every check below except prose, which needs Vale installed +npm test # jest, with the coverage floor enforced +``` + +The pre-commit hook runs both, so this is usually already done. The end-to-end +and visual suites are not in the hook, because they need the example +application built and served. + +## Commit messages + +[Conventional Commits](https://www.conventionalcommits.org). The commit-msg +hook checks them, and the pipeline checks the whole merge-request range. + +This matters more than it looks: these repositories squash-merge, so the pull +request or merge request **title** becomes the commit subject. A prose title +passes the commit check and then breaks the next push to the target branch for +everyone. Title your pull request the same way you would title a commit. + +## Coverage + +`jest.config.js` carries a coverage floor measured from the current tests. It +is a floor. Raise it when coverage improves. Do not +lower it to make a change pass: if a change drops coverage, the change needs a +test. + +## Visual baselines + +Visual regression baselines are committed. Regenerate them with the manual +`visual:update` pipeline job, never locally. + +Chromium renders differently on ARM, so a baseline generated on an Apple +silicon machine is a permanent false diff for everyone else. The manual job +runs on the reference architecture and hands back the PNGs to commit. + +## What not to put in a file + +This repository is public. Nothing that only resolves on a private network may +reach a tracked file, whether as a URL, in a comment or in a patch description. +`npm run lint:private` checks this and runs in the pipeline. When you need to +cite something internal, describe it without the URL or point at a public +equivalent. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f63aac4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 DruxtJS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 6c597a9..bd822ca 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,92 @@ -# DruxtModule (template) +# Druxt module template - -[![CI](https://github.com/druxt/module-template/actions/workflows/ci.yml/badge.svg)](https://github.com/druxt/module-template/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/druxt/module-template/branch/main/graph/badge.svg?token=TwCLJOKEjm)](https://codecov.io/gh/druxt/module-template) +Starting point for a [Druxt](https://druxtjs.org) module, and the reference +module for the Druxt repository standard. -> This is a template for creating a DruxtJS module. +Start something new from it, or bring an existing module up +to the same footing. They are different jobs and the second is the more common +one. -## Links +## Starting a new module -- DruxtJS: https://druxtjs.org -- Community Discord server: https://discord.druxtjs.org +1. Create a repository from this template. +2. `npm install`. That installs dependencies and enables the git hooks. +3. Rename things: `package.json`'s `name`, the component in + `src/components/`, and the `@TODO` in it. +4. `npm test` and `npm run lint` should both pass before you change anything. -## Getting started +## Bringing an existing module up -1. **Use this template** +Do not copy this repository over yours. Run the conformance checker against +your module and work the gap list it prints: - Click the **Use this template** button in GitHub and follow the on-screen instructions to **Create a new repository**. - -2. **Build, dev, test, commit** - - This repository is setup with for development using the following tools: - - - [Siroc](https://github.com/unjs/siroc): `npm run build` - - [Jest](https://jestjs.io/): `npm test` - - Linting tools: `npm run lint` - - [Changesets](https://github.com/changesets/changesets): `npm run changesets` - -3. **Contribute** +```bash +python3 check-standards.py --standard standards.yml \ + --repos-root .. --repo +``` - Have a module you want to share with the Druxt community? Let us know via the [Community Discord server](https://discord.druxtjs.org). +It reports every requirement, whether your repository meets it, and why not +when it does not. The gaps are independent, so they can be closed one merge +request at a time rather than in one pass that is impossible to review. Copying +wholesale is worse than it looks, because you inherit this template's coverage floor and its dictionary, and neither describes your module. The floor has to be measured from your own tests. + +## What you get + +| | | +| ------------ | -------------------------------------------------------------------------------------------- | +| Build | siroc, producing ESM and SSR bundles | +| Unit tests | Jest, with an enforced coverage floor | +| Visual tests | Playwright against the example application, three viewports, committed baselines | +| Lint | ESLint, Prettier, markdownlint, cspell, yamllint, knip | +| Secrets | gitleaks, with a canary that proves the scanner still detects | +| Commits | Conventional Commits, checked by a hook and over the merge-request range | +| CI | GitLab and GitHub Actions, running the same set | +| Preview | A manual job serving the example application through a Cloudflare tunnel and posting the URL | +| Releases | Changesets | +| Environment | A devcontainer for VS Code, Codespaces and DevPod | + +## Commands + +```bash +npm install # dependencies, and enables the git hooks +npm run build # build the module +npm test # unit tests, coverage floor enforced +npm run lint # every linter except prose +npm run lint:prose # Vale, after `npm run lint:prose:install` once +npm run example:setup # Drupal 11 backend on SQLite, then the example's dependencies +npm run example:dev # the example on http://localhost:3000 +npm run test:e2e # Playwright against the example, backend up +``` +`.mise.toml` pins the toolchain and defines the same commands as tasks, so with +[mise](https://mise.jdx.dev) installed, `mise run ci` runs what the pipeline +runs. - +[MIT](LICENSE) diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..b7550a1 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,46 @@ +// Conventional Commits, unmodified, plus one project rule. +// +// The type list is deliberately the stock one. A repository-specific vocabulary +// means a contributor who has worked on another Druxt repository guesses wrong, +// and the value of the convention is that it is the same everywhere. +// +// no-ai-attribution: the work is the author's. A co-author trailer naming a +// coding assistant, a generated-with footer or a session link hands ownership +// to a tool. .gitlab/scripts/check-attribution.sh runs the same check over the +// merge request range and every tracked file in CI; this is the local half, +// run by the commit-msg hook. The pattern is assembled from pieces so that this +// file passes its own scan. +const tools = + 'claude|anthropic|copilot|chatgpt|openai|gemini|codex|cursor|aider|devin|windsurf|opencode|glm' +const attribution = new RegExp( + [ + 'co-authored' + `-by:.*(${tools})`, + `generated (with|by).*(${tools}|\\bai\\b)`, + 'claude-' + 'session:', + 'claude\\.ai/code/session', + 'noreply' + '@anthropic\\.com', + ].join('|'), + 'i' +) + +module.exports = { + extends: ['@commitlint/config-conventional'], + plugins: [ + { + rules: { + 'no-ai-attribution': ({ raw }) => { + const hit = raw.match(attribution) + return [ + !hit, + hit + ? `AI attribution is not allowed: "${hit[0].trim()}". The work is the author's.` + : '', + ] + }, + }, + }, + ], + rules: { + 'no-ai-attribution': [2, 'always'], + }, +} diff --git a/example/README.md b/example/README.md index 0f00375..092b981 100644 --- a/example/README.md +++ b/example/README.md @@ -1,8 +1,46 @@ -# DruxtModule example +# Example application -This directory contains the Drupal and Nuxt codebases to demonstrate the DruxtModule (template) module. +A Drupal backend and a Nuxt application that loads the module, so a change to +the module is seen running against a real site. -## Get started +## Run it -1. `cd drupal && ddev start && ddev drupal-install` -2. `cd ../nuxt && yarn && yarn dev` +From the repository root, with PHP 8.3 or later, Composer and Node 16: + +```sh +npm install # the module's dependencies, and the git hooks +npm run build # the example links the module by path, so build first +npm run example:setup # Drupal 11 on SQLite, druxt enabled, then the example's dependencies +npm run example:dev # http://localhost:3000 +``` + +`example:setup` installs a fresh standard-profile Drupal 11 into a throwaway +SQLite database with `druxt` enabled and anonymous read access to its +resources, starts PHP's built-in server on the first free port from 8888, and +writes the URL to `example/.env` as `BASE_URL`. `example/nuxt/nuxt.config.js` +reads that file, so nothing is copied between terminals. `npm run example:info` +shows where the backend is, and `example:stop` and `example:start` control it. + +The dev container does all of this on creation. + +## Tests against it + +`npm run test:e2e` runs Playwright against the example. With no +`PLAYWRIGHT_BASE_URL` it starts the dev server itself, so the backend has to be +up. The pipelines generate the example (`npm run example:generate`) and serve +`example/nuxt/dist` with `npm run serve` instead, which is what a visitor gets. + +## DDEV + +`cd example/drupal && ddev start && ddev drupal-install` builds the same site +on DDEV's MySQL. Set `DRUXT_BASE_URL` to the DDEV URL, or leave `example/.env` +absent and the example falls back to it. + +## What is in here + +| Path | Purpose | +| ---------------------- | ---------------------------------------------------------------- | +| `drupal/composer.json` | Drupal 11 with `drupal/druxt`; the lock is committed | +| `drupal/.devtools/` | Docker-free provisioning: assemble, provision, start, stop, info | +| `nuxt/` | The Nuxt 2 application, with the module as `file:../..` | +| `nuxt/package.json` | Pins `consola` 2: webpack 4 cannot parse the version 3 build | diff --git a/example/drupal/.ddev/commands/web/drupal-install b/example/drupal/.ddev/commands/web/drupal-install index c41c83a..141ff15 100755 --- a/example/drupal/.ddev/commands/web/drupal-install +++ b/example/drupal/.ddev/commands/web/drupal-install @@ -1,8 +1,15 @@ #!/bin/bash -## Description: Install Drupal +## Description: Install the example backend ## Usage: drupal-install ## Example: "ddev drupal-install" - +# +# The same site .devtools/provision builds without Docker, on DDEV's MySQL. +set -euo pipefail composer install -drush -y tome:install +drush -y site:install --site-name='druxt-module-template' standard +drush -y recipe:apply /var/www/html/web/core/recipes/article_content_type +drush -y recipe:apply /var/www/html/web/core/recipes/page_content_type +drush -y cache:rebuild +drush -y pm:enable druxt +drush role:perm:add anonymous 'access druxt resources' diff --git a/example/drupal/.ddev/config.yaml b/example/drupal/.ddev/config.yaml index 857dc06..b598227 100644 --- a/example/drupal/.ddev/config.yaml +++ b/example/drupal/.ddev/config.yaml @@ -1,207 +1,6 @@ name: druxt-module-template -type: drupal9 +type: drupal docroot: web -php_version: "8.0" +php_version: "8.4" webserver_type: nginx-fpm -router_http_port: "80" -router_https_port: "443" xdebug_enabled: false -additional_hostnames: [] -additional_fqdns: [] -database: - type: mariadb - version: "10.3" -nfs_mount_enabled: false -mutagen_enabled: false -use_dns_when_possible: true -composer_version: "2" -web_environment: [] -nodejs_version: "16" - -# Key features of ddev's config.yaml: - -# name: # Name of the project, automatically provides -# http://projectname.ddev.site and https://projectname.ddev.site - -# type: # drupal6/7/8, backdrop, typo3, wordpress, php - -# docroot: # Relative path to the directory containing index.php. - -# php_version: "7.4" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1" - -# You can explicitly specify the webimage but this -# is not recommended, as the images are often closely tied to ddev's' behavior, -# so this can break upgrades. - -# webimage: # nginx/php docker image. - -# database: -# type: # mysql, mariadb -# version: # database version, like "10.3" or "8.0" -# Note that mariadb_version or mysql_version from v1.18 and earlier -# will automatically be converted to this notation with just a "ddev config --auto" - -# router_http_port: # Port to be used for http (defaults to port 80) -# router_https_port: # Port for https (defaults to 443) - -# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart" -# Note that for most people the commands -# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better, -# as leaving xdebug enabled all the time is a big performance hit. - -# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart" -# Note that for most people the commands -# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better, -# as leaving xhprof enabled all the time is a big performance hit. - -# webserver_type: nginx-fpm # or apache-fpm - -# timezone: Europe/Berlin -# This is the timezone used in the containers and by PHP; -# it can be set to any valid timezone, -# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -# For example Europe/Dublin or MST7MDT - -# composer_root: -# Relative path to the composer root directory from the project root. This is -# the directory which contains the composer.json and where all Composer related -# commands are executed. - -# composer_version: "2" -# if composer_version:"2" it will use the most recent composer v2 -# It can also be set to "1", to get most recent composer v1 -# or "" for the default v2 created at release time. -# It can be set to any existing specific composer version. -# After first project 'ddev start' this will not be updated until it changes - -# nodejs_version: "16" -# change from the default system Node.js version to another supported version, like 12, 14, 17. -# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any -# Node.js version, including v6, etc. - -# additional_hostnames: -# - somename -# - someothername -# would provide http and https URLs for "somename.ddev.site" -# and "someothername.ddev.site". - -# additional_fqdns: -# - example.com -# - sub1.example.com -# would provide http and https URLs for "example.com" and "sub1.example.com" -# Please take care with this because it can cause great confusion. - -# upload_dir: custom/upload/dir -# would set the destination path for ddev import-files to /custom/upload/dir - -# working_dir: -# web: /var/www/html -# db: /home -# would set the default working directory for the web and db services. -# These values specify the destination directory for ddev ssh and the -# directory in which commands passed into ddev exec are run. - -# omit_containers: [db, dba, ddev-ssh-agent] -# Currently only these containers are supported. Some containers can also be -# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit -# the "db" container, several standard features of ddev that access the -# database container will be unusable. In the global configuration it is also -# possible to omit ddev-router, but not here. - -# nfs_mount_enabled: false -# Great performance improvement but requires host configuration first. -# See https://ddev.readthedocs.io/en/stable/users/performance/#using-nfs-to-mount-the-project-into-the-container - -# mutagen_enabled: false -# Experimental performance improvement using mutagen asynchronous updates. -# See https://ddev.readthedocs.io/en/latest/users/performance/#using-mutagen - -# fail_on_hook_fail: False -# Decide whether 'ddev start' should be interrupted by a failing hook - -# host_https_port: "59002" -# The host port binding for https can be explicitly specified. It is -# dynamic unless otherwise specified. -# This is not used by most people, most people use the *router* instead -# of the localhost port. - -# host_webserver_port: "59001" -# The host port binding for the ddev-webserver can be explicitly specified. It is -# dynamic unless otherwise specified. -# This is not used by most people, most people use the *router* instead -# of the localhost port. - -# host_db_port: "59002" -# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic -# unless explicitly specified. - -# phpmyadmin_port: "8036" -# phpmyadmin_https_port: "8037" -# The PHPMyAdmin ports can be changed from the default 8036 and 8037 - -# host_phpmyadmin_port: "8036" -# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed -# through ddev-router, but it can be specified and bound. - -# mailhog_port: "8025" -# mailhog_https_port: "8026" -# The MailHog ports can be changed from the default 8025 and 8026 - -# host_mailhog_port: "8025" -# The mailhog port is not normally bound on the host at all, instead being routed -# through ddev-router, but it can be bound directly to localhost if specified here. - -# webimage_extra_packages: [php7.4-tidy, php-bcmath] -# Extra Debian packages that are needed in the webimage can be added here - -# dbimage_extra_packages: [telnet,netcat] -# Extra Debian packages that are needed in the dbimage can be added here - -# use_dns_when_possible: true -# If the host has internet access and the domain configured can -# successfully be looked up, DNS will be used for hostname resolution -# instead of editing /etc/hosts -# Defaults to true - -# project_tld: ddev.site -# The top-level domain used for project URLs -# The default "ddev.site" allows DNS lookup via a wildcard -# If you prefer you can change this to "ddev.local" to preserve -# pre-v1.9 behavior. - -# ngrok_args: --subdomain mysite --auth username:pass -# Provide extra flags to the "ngrok http" command, see -# https://ngrok.com/docs#http or run "ngrok http -h" - -# disable_settings_management: false -# If true, ddev will not create CMS-specific settings files like -# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php -# In this case the user must provide all such settings. - -# You can inject environment variables into the web container with: -# web_environment: -# - SOMEENV=somevalue -# - SOMEOTHERENV=someothervalue - -# no_project_mount: false -# (Experimental) If true, ddev will not mount the project into the web container; -# the user is responsible for mounting it manually or via a script. -# This is to enable experimentation with alternate file mounting strategies. -# For advanced users only! - -# bind_all_interfaces: false -# If true, host ports will be bound on all network interfaces, -# not just the localhost interface. This means that ports -# will be available on the local network if the host firewall -# allows it. - -# Many ddev commands can be extended to run tasks before or after the -# ddev command is executed, for example "post-start", "post-import-db", -# "pre-composer", "post-composer" -# See https://ddev.readthedocs.io/en/stable/users/extending-commands/ for more -# information on the commands that can be extended and the tasks you can define -# for them. Example: -#hooks: -# post-import-db: -# - exec: drush cr -# - exec: drush updb diff --git a/example/drupal/.devtools/assemble b/example/drupal/.devtools/assemble new file mode 100755 index 0000000..6590207 --- /dev/null +++ b/example/drupal/.devtools/assemble @@ -0,0 +1,40 @@ +#!/usr/bin/env php + Next steps:' . PHP_EOL; +echo ' .devtools/provision # Install the site (SQLite + Drupal standard profile)' . PHP_EOL; +echo ' .devtools/start # Start the webserver' . PHP_EOL; +echo PHP_EOL; diff --git a/example/drupal/.devtools/etc/php.ini b/example/drupal/.devtools/etc/php.ini new file mode 100644 index 0000000..01647dd --- /dev/null +++ b/example/drupal/.devtools/etc/php.ini @@ -0,0 +1,6 @@ +; Used via PHPRC by .devtools/helpers.php's drush() wrapper. Drupal installs +; can exceed PHP's default 128M memory_limit, and `-d` overrides on the +; parent process don't reach the child PHP process Drush's SiteProcess +; spawns for `site-install` - PHPRC does, since it's inherited as an +; environment variable rather than passed as a CLI flag. +memory_limit = -1 diff --git a/example/drupal/.devtools/helpers.php b/example/drupal/.devtools/helpers.php new file mode 100644 index 0000000..b3ab2cd --- /dev/null +++ b/example/drupal/.devtools/helpers.php @@ -0,0 +1,483 @@ + + * Associative array of key-value pairs. + */ +function dotenv_read(string $file = '../.env'): array { + if (!file_exists($file)) { + return []; + } + + $contents = file_get_contents($file); + if ($contents === FALSE) { + return []; + } + + $vars = []; + foreach (preg_split('/\r\n|\r|\n/', $contents) ?: [] as $line) { + $trimmed = trim($line); + if ($trimmed === '' || str_starts_with($trimmed, '#') || !str_contains($trimmed, '=')) { + continue; + } + + [$key, $value] = explode('=', $trimmed, 2); + $key = trim($key); + $value = trim($value); + + if ($key === '') { + continue; + } + + if (strlen($value) >= 2) { + $first = $value[0]; + $last = $value[strlen($value) - 1]; + if (($first === '"' && $last === '"') || ($first === "'" && $last === "'")) { + $value = substr($value, 1, -1); + } + } + + $vars[$key] = $value; + } + + return $vars; +} + +/** + * Set or update a variable in a dotenv-style file. + */ +function dotenv_write_var(string $key, string $value, string $file = '../.env'): void { + $assignment = sprintf('%s=%s', $key, $value); + + if (!file_exists($file)) { + if (file_put_contents($file, $assignment . PHP_EOL) === FALSE) { + FAIL('Unable to write %s', $file); + } + + return; + } + + $contents = file_get_contents($file); + if ($contents === FALSE) { + FAIL('Unable to read %s', $file); + + // @codeCoverageIgnoreStart + return; + // @codeCoverageIgnoreEnd + } + + $lines = preg_split('/\r\n|\r|\n/', $contents) ?: []; + $trailing_newline = $contents !== '' && (str_ends_with($contents, "\n") || str_ends_with($contents, "\r")); + if ($trailing_newline && end($lines) === '') { + array_pop($lines); + } + + $replace_index = NULL; + foreach ($lines as $i => $line) { + $trimmed = trim($line); + if ($trimmed === '' || str_starts_with($trimmed, '#') || !str_contains($trimmed, '=')) { + continue; + } + + [$existing_key] = explode('=', $trimmed, 2); + if (trim($existing_key) === $key) { + $replace_index = $i; + } + } + + if ($replace_index === NULL) { + $lines[] = $assignment; + } + else { + $lines[$replace_index] = $assignment; + } + + if (file_put_contents($file, implode(PHP_EOL, $lines) . PHP_EOL) === FALSE) { + FAIL('Unable to write %s', $file); + } +} + +/** + * Resolve an environment value from env, then dotenv, then default. + * + * @return array{0: string, 1: string} + * Tuple of [value, source]. + */ +function resolve_env_value(string $name, string $default, string $dotenv_file = '../.env'): array { + $env = getenv($name); + if ($env !== FALSE && $env !== '') { + return [$env, 'env']; + } + + $dotenv = dotenv_read($dotenv_file); + if (isset($dotenv[$name]) && $dotenv[$name] !== '') { + return [$dotenv[$name], $dotenv_file]; + } + + return [$default, 'default']; +} + +/** + * Resolve the webserver host and port with source tracking. + * + * @return array{host: string, host_source: string, port: string, port_source: string} + */ +function resolve_webserver(bool $auto_discover = FALSE, bool $validate_port = TRUE, string $dotenv_file = '../.env'): array { + [$host, $host_source] = resolve_env_value('WEBSERVER_HOST', '127.0.0.1', $dotenv_file); + + $default_port = $auto_discover ? '' : '8888'; + [$port, $port_source] = resolve_env_value('WEBSERVER_PORT', $default_port, $dotenv_file); + + if ($auto_discover && $port_source === 'default') { + $port = (string) find_free_port(); + dotenv_write_var('WEBSERVER_PORT', $port, $dotenv_file); + $port_source = $dotenv_file; + } + + if ($validate_port) { + validate_port_or_fail($port, 'WEBSERVER_PORT'); + } + + return [ + 'host' => $host, + 'host_source' => $host_source, + 'port' => $port, + 'port_source' => $port_source, + ]; +} + +/** + * Detect the XDebug state of the dev server listening on the given port. + */ +function xdebug_state(string $port): string { + $cmd = sprintf('ps -o command= -p "$(lsof -ti:%s 2>/dev/null | head -1)" 2>/dev/null', escapeshellarg($port)); + $out = trim((string) @shell_exec($cmd)); + if ($out === '') { + return '-'; + } + + return str_contains($out, 'xdebug.mode=debug') ? 'enabled' : 'disabled'; +} + +/** + * Validate that a value is a TCP port in the range 1-65535. + */ +function validate_port_or_fail(string $value, string $source): void { + if (!ctype_digit($value) || (int) $value < 1 || (int) $value > 65535) { + FAIL('Invalid %s "%s". Expected integer in range 1-65535.', $source, $value); + } +} + +/** + * Find a free TCP port by scanning a range of ports. + */ +function find_free_port(int $start = 8888, int $max_attempts = 100): int { + if ($start < 1 || $start > 65535) { + FAIL('Start port must be between 1 and 65535, got %d', $start); + } + if ($max_attempts < 1) { + FAIL('Max attempts must be a positive integer, got %d', $max_attempts); + } + + // Never scan past the maximum valid TCP port. + $last = min(65535, $start + $max_attempts - 1); + for ($port = $start; $port <= $last; $port++) { + $conn = @stream_socket_client(sprintf('tcp://localhost:%d', $port), $errno, $errstr, 0.2); + if ($conn === FALSE) { + return $port; + } + fclose($conn); + } + + FAIL('Unable to find a free port in range %d-%d', $start, $last); + + // @codeCoverageIgnoreStart + return $start; + // @codeCoverageIgnoreEnd +} + +/** + * Stop the dev webserver started by .devtools/start. + * + * Prefers the pidfile written at start time, falling back to whatever is + * bound to the dev port for servers started before the pidfile existed + * (or via WEBSERVER_PORT changes). Every candidate PID is verified to + * still be a PHP dev server before it is signalled - the OS reuses PIDs, + * and the port may hold an unrelated service. + */ +function stop_webserver(string $port): void { + $pid_file = server_pid_file(); + $candidates = []; + + if (is_file($pid_file)) { + $pid = trim((string) file_get_contents($pid_file)); + if ($pid !== '' && ctype_digit($pid)) { + $candidates[] = (int) $pid; + } + @unlink($pid_file); + } + + // The pidfile can go stale - e.g. a server left running from an earlier + // session, or a pidfile write that raced with the process it names. + $port_pids = []; + @exec(sprintf('lsof -ti:%s 2>/dev/null', escapeshellarg($port)), $port_pids); + foreach ($port_pids as $pid) { + $pid = trim((string) $pid); + if ($pid !== '' && ctype_digit($pid)) { + $candidates[] = (int) $pid; + } + } + + $targets = array_filter(array_unique($candidates), function (int $pid): bool { + $command = trim((string) @shell_exec(sprintf('ps -p %d -o command= 2>/dev/null', $pid))); + return $command !== '' && str_contains($command, 'php') && str_contains($command, '-S'); + }); + if ($targets === []) { + return; + } + + // TERM first so the server can exit cleanly; escalate only if it lingers. + foreach ($targets as $pid) { + @exec(sprintf('kill %d 2>/dev/null', $pid)); + } + usleep(500000); + foreach ($targets as $pid) { + if (trim((string) @shell_exec(sprintf('ps -p %d -o pid= 2>/dev/null', $pid))) !== '') { + @exec(sprintf('kill -9 %d 2>/dev/null', $pid)); + } + } +} + +/** + * Path of the pidfile tracking the dev webserver process. + */ +function server_pid_file(): string { + // Unique per checkout: a fixed name is shared by every clone of this + // repo on the machine, letting one checkout's `stop` kill another + // checkout's server (the pidfile written last wins). cwd is stable + // here - every .devtools script runs from drupal/. + return sprintf('/tmp/druxt-module-template-drupal-php-server-%s.pid', hash('sha256', (string) getcwd())); +} + +/** + * Output a note message. + */ +function NOTE(string $format, string|int|float ...$args): void { + echo sprintf(' %s%s', sprintf($format, ...$args), PHP_EOL); +} + +/** + * Output a task message. + */ +function TASK(string $format, string|int|float ...$args): void { + echo term_supports_color() ? + "\033[34m[TASK] " . sprintf($format, ...$args) . "\033[0m\n" : + sprintf('[TASK] %s%s', sprintf($format, ...$args), PHP_EOL); +} + +/** + * Output an info message. + */ +function INFO(string $format, string|int|float ...$args): void { + echo term_supports_color() ? + "\033[36m[INFO] " . sprintf($format, ...$args) . "\033[0m\n" : + sprintf('[INFO] %s%s', sprintf($format, ...$args), PHP_EOL); +} + +/** + * Output a success message. + */ +function PASS(string $format, string|int|float ...$args): void { + echo term_supports_color() ? + "\033[32m[ OK ] " . sprintf($format, ...$args) . "\033[0m\n" : + sprintf('[ OK ] %s%s', sprintf($format, ...$args), PHP_EOL); +} + +/** + * Output a failure message and exit(1). + */ +function FAIL(string $format, string|int|float ...$args): void { + FAIL_NO_EXIT($format, ...$args); + quit(1); +} + +/** + * Output a failure message without exiting. + */ +function FAIL_NO_EXIT(string $format, string|int|float ...$args): void { + echo term_supports_color() ? + "\033[31m[FAIL] " . sprintf($format, ...$args) . "\033[0m\n" : + sprintf('[FAIL] %s%s', sprintf($format, ...$args), PHP_EOL); +} + +/** + * Check if the terminal supports colors. + */ +function term_supports_color(): bool { + return getenv('TERM') === 'dumb' || getenv('TERM') === FALSE ? FALSE : function_exists('posix_isatty') && @posix_isatty(STDOUT); +} + +/** + * Get the path to a command, or FALSE if the command does not exist. + */ +function command_path(string $command): string|false { + if (!preg_match('/^[A-Za-z0-9_\-]+(?: [A-Za-z0-9_\-]+)*$/', $command)) { + return FALSE; + } + exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + return $code === 0 && !empty($output[0]) ? trim($output[0]) : FALSE; +} + +/** + * Require a command to be available, or fail. + */ +function command_must_exist(string $command): void { + if (!command_path($command)) { + FAIL("Command '%s' is not available", $command); + } +} + +/** + * Run a command via passthru, failing if exit code is non-zero. + */ +function passthru_or_fail(string $cmd, string $format = '', string|int|float ...$args): void { + passthru($cmd, $exit_code); + if ($exit_code !== 0) { + if ($format !== '') { + FAIL($format, ...$args); + } + quit($exit_code); + } +} + +/** + * Run a command via passthru, retrying on failure before giving up. + * + * For long network-bound steps (composer install): registries and GitHub + * throw transient 5xx errors, and the step resumes from cache - a retry + * finishes the job instead of failing the whole first run. + */ +function passthru_with_retries(string $cmd, int $attempts = 3, int $delay_seconds = 10): void { + for ($attempt = 1; $attempt <= $attempts; $attempt++) { + $exit_code = 0; + passthru($cmd, $exit_code); + if ($exit_code === 0) { + return; + } + if ($attempt < $attempts) { + NOTE('Attempt %d of %d failed (exit code %d) - retrying in %d seconds.', $attempt, $attempts, $exit_code, $delay_seconds); + sleep($delay_seconds); + } + } + FAIL('Command failed after %d attempts: %s', $attempts, $cmd); +} + +/** + * Run a drush command against this site's `web/` docroot. + * + * @param string $command + * The drush command, optionally with sprintf-style placeholders. + * @param string|string[]|null $args + * Arguments to substitute into the command. Each is escaped with + * escapeshellarg() before substitution. + * @param int|null &$exit_code + * If provided, the exit code is stored here and failures do not exit + * the script. If not provided, non-zero exit codes call FAIL(). + * + * @param-out int $exit_code + */ +function drush(string $command, mixed $args = NULL, ?int &$exit_code = NULL): string { + if (is_string($args)) { + $args = [$args]; + } + + if (is_array($args) && $args !== []) { + $command = sprintf($command, ...array_map(escapeshellarg(...), $args)); + } + + $exit_code_provided = $exit_code !== NULL; + $exit_code = 0; + + // Drupal installs can exceed PHP's default 128M memory_limit. + putenv('PHPRC=' . __DIR__ . '/etc/php.ini'); + $command = 'vendor/bin/drush -r ' . escapeshellarg(getcwd() . '/web') . ' -y ' . $command; + + ob_start(); + passthru($command, $exit_code); + $output = ob_get_clean(); + + if (!$exit_code_provided && $exit_code !== 0) { + FAIL('Drush command failed: %s', $command); + } + + return $output ?: ''; +} + +/** + * Check if debug mode is enabled. + */ +function is_debug(): bool { + return getenv('DEBUG') === '1'; +} + +// Never run the real quit() during tests. +// @codeCoverageIgnoreStart +if (!function_exists('TemplateDevTools\quit') && !class_exists('PHPUnit\\Framework\\TestCase')) { + + /** + * Exit script with given code. + */ + function quit(int $code = 0): void { + exit($code); + } + +} +// @codeCoverageIgnoreEnd diff --git a/example/drupal/.devtools/info b/example/drupal/.devtools/info new file mode 100755 index 0000000..2034f90 --- /dev/null +++ b/example/drupal/.devtools/info @@ -0,0 +1,105 @@ +#!/usr/bin/env php +': print only that field's value on stdout, exit 0. + * Unknown field name prints '-' and exits 1. + */ + +declare(strict_types=1); + +namespace TemplateDevTools; + +require_once __DIR__ . '/helpers.php'; + +$webserver = resolve_webserver(validate_port: FALSE); +$webserver_host = $webserver['host']; +$host_source = $webserver['host_source']; +$webserver_port = $webserver['port']; +$port_source = $webserver['port_source']; + +$db_file = resolve_env_value('DB_FILE', '/tmp/druxt-module-template-drupal-site.sqlite')[0]; +$has_drush = is_file('vendor/bin/drush'); +$site_url = sprintf('http://%s:%s', $webserver_host, $webserver_port); + +$fields = [ + 'drupal-version' => fn(): string => $has_drush ? info_drupal_version() : '-', + 'php-version' => fn(): string => info_php_version(), + 'composer' => fn(): string => info_tool_version('composer --version 2>/dev/null', '/Composer(?:\s+version)?\s+(\S+)/i'), + 'drush' => fn(): string => $has_drush ? info_tool_version('vendor/bin/drush --version 2>/dev/null', '/(\d+\.\d+\.\d+)/') : '-', + 'webserver-host' => fn(): string => $webserver_host, + 'webserver-port' => fn(): string => $webserver_port, + 'site-url' => fn(): string => $site_url, + 'xdebug' => fn(): string => xdebug_state($webserver_port), + 'database' => fn(): string => $db_file, +]; + +$field = isset($argv[1]) && !str_starts_with((string) $argv[1], '-') ? (string) $argv[1] : NULL; +if ($field !== NULL) { + if (isset($fields[$field])) { + echo $fields[$field]() . PHP_EOL; + quit(0); + } + echo '-' . PHP_EOL; + quit(1); +} + +$values = array_map(fn(callable $cb): string => $cb(), $fields); +$php_path = command_path('php'); + +echo PHP_EOL; +echo '===============================' . PHP_EOL; +echo ' ENVIRONMENT INFO ' . PHP_EOL; +echo '===============================' . PHP_EOL; +echo PHP_EOL; +printf("Drupal version: %s%s", $values['drupal-version'], PHP_EOL); +printf("PHP version: %s%s%s", $values['php-version'], $php_path !== FALSE && $values['php-version'] !== '-' ? ' (' . $php_path . ')' : '', PHP_EOL); +printf("Composer: %s%s", $values['composer'], PHP_EOL); +printf("Drush: %s%s", $values['drush'], PHP_EOL); +echo PHP_EOL; +printf("Webserver host: %s (%s)%s", $values['webserver-host'], $host_source, PHP_EOL); +printf("Webserver port: %s (%s)%s", $values['webserver-port'], $port_source, PHP_EOL); +printf("Site URL: %s%s", $values['site-url'], PHP_EOL); +printf("XDebug: %s%s", $values['xdebug'], PHP_EOL); +echo PHP_EOL; +printf("Database: %s%s", $values['database'], PHP_EOL); +echo PHP_EOL; + +/** + * Get the host PHP version, or '-' if it cannot be parsed. + */ +function info_php_version(): string { + $out = (string) @shell_exec('php -v 2>/dev/null'); + if ($out === '' || !preg_match('/^PHP\s+(\S+)/m', $out, $matches)) { + return '-'; + } + + return $matches[1]; +} + +/** + * Run a version probe and extract the first capture group. + */ +function info_tool_version(string $cmd, string $pattern): string { + $out = trim((string) @shell_exec($cmd)); + if ($out === '' || !preg_match($pattern, $out, $matches)) { + return '-'; + } + + return trim($matches[1]); +} + +/** + * Get the installed Drupal version via drush, or '-' if unavailable. + */ +function info_drupal_version(): string { + $cmd = 'vendor/bin/drush -r ' . escapeshellarg(getcwd() . '/web') . ' status --field=drupal-version 2>/dev/null'; + $out = trim((string) @shell_exec($cmd)); + + return $out === '' ? '-' : $out; +} diff --git a/example/drupal/.devtools/provision b/example/drupal/.devtools/provision new file mode 100755 index 0000000..b232665 --- /dev/null +++ b/example/drupal/.devtools/provision @@ -0,0 +1,99 @@ +#!/usr/bin/env php + 'sqlite',\n 'database' => %s,\n];\n", + $db_file_php +)); +PASS('SQLite database configured.'); + +TASK('Running drush site:install standard.'); +drush('site:install standard --site-name=%s', 'druxt-module-template'); +PASS('Site installed.'); + +// Drupal 11's `standard` profile no longer creates content types itself - +// Article and Page now ship as separate core recipes. Without them there +// are no node bundles, so jsonapi_views can't register a route for any +// node-based view (including the front page), and the site can't do +// anything content-shaped. +TASK('Applying Article and Page content-type recipes.'); +foreach (['article_content_type', 'page_content_type'] as $recipe) { + drush('recipe:apply %s', getcwd() . "/web/core/recipes/$recipe"); +} +drush('cache:rebuild'); +PASS('Content types applied.'); + +TASK('Enabling the druxt module.'); +drush('pm:enable druxt'); +drush('role:perm:add anonymous %s', 'access druxt resources'); +PASS('druxt enabled; anonymous can access Druxt resources.'); + +echo PHP_EOL; +echo '===============================' . PHP_EOL; +echo ' 🚀 PROVISION COMPLETE ✅ ' . PHP_EOL; +echo '===============================' . PHP_EOL; +echo PHP_EOL; +echo 'Database: ' . $db_file . PHP_EOL; +echo PHP_EOL; +echo '> Next steps:' . PHP_EOL; +echo ' .devtools/start # Start the webserver' . PHP_EOL; +echo PHP_EOL; + diff --git a/example/drupal/.devtools/start b/example/drupal/.devtools/start new file mode 100755 index 0000000..b4b7a35 --- /dev/null +++ b/example/drupal/.devtools/start @@ -0,0 +1,156 @@ +#!/usr/bin/env php +/dev/null'); + if (stripos($php_v, 'xdebug') === FALSE) { + FAIL('XDEBUG=1 is set but the Xdebug PHP extension is not installed on the host.'); + } + PASS('Xdebug extension is loaded.'); +} + +TASK('Stopping previously started services, if any.'); +stop_webserver($webserver_port); + +TASK('Starting the PHP webserver (docroot: web/).'); +NOTE('Waiting up to %s seconds for the server to be ready.', (string) $webserver_wait_timeout); +$cwd = (string) getcwd(); +// Grouped in a subshell that `exec`s straight into `nohup` (which itself +// execs into `php`, never forking): the backgrounded job ends up as a +// single process whose PID `$!` captures correctly, and which holds no +// inherited copy of this passthru() call's own stdout pipe - without +// this, on some shells the wrapping subshell never exits and passthru() +// blocks forever waiting for a pipe that's still held open, even though +// the actual PHP server started and is serving requests just fine. +passthru(sprintf( + '(cd %s/web && exec nohup php%s -S %s:%s -t . .ht.router.php /tmp/druxt-module-template-drupal-php-server.log 2>&1) & echo $! > %s', + escapeshellarg($cwd), + $xdebug_flags, + escapeshellarg($webserver_host), + escapeshellarg($webserver_port), + escapeshellarg(server_pid_file()) +)); + +$ready = FALSE; +for ($i = 0; $i < $webserver_wait_timeout; $i++) { + sleep(1); + // Liveness: a cold first start (fresh site, no opcache) can take a + // while - dots show the script is waiting, not hung. + echo '.'; + $fp = @fsockopen($webserver_host, (int) $webserver_port, $errno, $errstr, 1); + if ($fp !== FALSE) { + fclose($fp); + $ready = TRUE; + break; + } +} +echo PHP_EOL; +if (!$ready) { + $log = @file_get_contents('/tmp/druxt-module-template-drupal-php-server.log') ?: ''; + FAIL('Unable to start inbuilt PHP server' . ($log !== '' ? "\n" . $log : '')); +} +PASS('Server started successfully.'); + +// A listening socket is not proof that this script's server owns it. When a +// stale server or an unrelated service already holds the port, the checks +// below read that service and report a site this checkout never started. +TASK('Confirming that the server on port %s is ours.', $webserver_port); +$pid = is_file(server_pid_file()) ? trim((string) file_get_contents(server_pid_file())) : ''; +// posix_kill is the liveness test where the extension is compiled in, and +// /proc covers the images where it is not. +$alive = $pid !== '' && ctype_digit($pid) && (function_exists('posix_kill') + ? @posix_kill((int) $pid, 0) + : is_dir('/proc/' . $pid)); +if (!$alive) { + FAIL('The PHP server exited after starting. Check the log at /tmp/druxt-module-template-drupal-php-server.log.'); +} +$port_owners = []; +@exec(sprintf('lsof -ti:%s 2>/dev/null', escapeshellarg($webserver_port)), $port_owners); +$port_owners = array_map('trim', $port_owners); +if ($port_owners !== [] && !in_array($pid, $port_owners, TRUE)) { + FAIL(sprintf('Port %s is held by another process (%s), not by the server this script started.', $webserver_port, implode(', ', $port_owners))); +} +PASS('The server on port %s is ours (pid %s).', $webserver_port, $pid); + +TASK('Checking that the server can serve content.'); +// Generous timeout: the very first request pays for a cold Drupal +// bootstrap (container build, twig compile) with no opcache. +$context = stream_context_create(['http' => ['method' => 'HEAD', 'timeout' => 15, 'ignore_errors' => TRUE]]); +$headers = @get_headers(sprintf('http://%s:%s', $webserver_host, $webserver_port), FALSE, $context); +if ($headers === FALSE || !isset($headers[0]) || (!str_contains($headers[0], '200') && !str_contains($headers[0], '30'))) { + FAIL('Server is started, but site cannot be served.'); +} +PASS('Server can serve content.'); + +$site_url = sprintf('http://%s:%s', $webserver_host, $webserver_port); + +TASK('Writing BASE_URL to ../.env for the Nuxt frontend.'); +dotenv_write_var('BASE_URL', $site_url); +PASS('BASE_URL written.'); + +// Print a one-time login link — non-fatal if the site isn't provisioned yet +// (first run before `.devtools/provision`), since drush() would otherwise +// abort the script. +$login_exit_code = 0; +$login_link = is_file('vendor/bin/drush') ? trim(drush('uli -l %s --no-browser', $site_url, $login_exit_code)) : ''; + +echo PHP_EOL; +echo '===============================' . PHP_EOL; +echo ' đŸ’ģ ENVIRONMENT READY ✅ ' . PHP_EOL; +echo '===============================' . PHP_EOL; +echo PHP_EOL; +echo 'Directory : ' . $cwd . '/web' . PHP_EOL; +echo 'URL : ' . $site_url . PHP_EOL; +echo 'XDebug : ' . ($xdebug_enabled ? 'Enabled' : 'Disabled') . PHP_EOL; +if ($login_exit_code === 0 && $login_link !== '') { + echo 'Login : ' . $login_link . PHP_EOL; +} +echo PHP_EOL; +if ($login_exit_code !== 0) { + echo '> Next steps:' . PHP_EOL; + echo ' .devtools/provision # Provision the site (first run / reset)' . PHP_EOL; + echo PHP_EOL; +} diff --git a/example/drupal/.devtools/stop b/example/drupal/.devtools/stop new file mode 100755 index 0000000..8867b74 --- /dev/null +++ b/example/drupal/.devtools/stop @@ -0,0 +1,31 @@ +#!/usr/bin/env php +=8.3.0", + "psr/event-dispatcher": "^1.0", + "psr/log": "^3.0", + "symfony/console": "^7.1", + "symfony/dependency-injection": "^7.1", + "symfony/filesystem": "^7.1", + "symfony/string": "^7.0", + "twig/twig": "^3.4" + }, + "conflict": { + "nikic/php-parser": "<4.17", + "squizlabs/php_codesniffer": "<3.6" + }, + "require-dev": { + "chi-teck/drupal-coder-extension": "^2.0.0-rc2", + "drupal/coder": "8.3.24", + "drupal/core": "11.x-dev", + "ext-simplexml": "*", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9", + "symfony/var-dumper": "^7.1", + "symfony/yaml": "^7.1", + "vimeo/psalm": "^5.24.0" + }, + "bin": [ + "bin/dcg" + ], + "type": "library", + "autoload": { + "psr-4": { + "DrupalCodeGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Drupal code generator", + "support": { + "issues": "https://github.com/Chi-teck/drupal-code-generator/issues", + "source": "https://github.com/Chi-teck/drupal-code-generator/tree/4.2.0" + }, + "time": "2025-06-01T13:48:30+00:00" + }, + { + "name": "composer/installers", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-24T20:46:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "consolidation/annotated-command", + "version": "4.10.5", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/78abf3b6853d7ff9044babd2b9c002ff433207d8", + "reference": "78abf3b6853d7ff9044babd2b9c002ff433207d8", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^4.3.1", + "php": ">=7.1.3", + "psr/log": "^1 || ^2 || ^3", + "symfony/console": "^4.4.8 || ^5 || ^6 || ^7 || ^8", + "symfony/event-dispatcher": "^4.4.8 || ^5 || ^6 || ^7 || ^8", + "symfony/finder": "^4.4.8 || ^5 || ^6 || ^7 || ^8" + }, + "require-dev": { + "composer-runtime-api": "^2.0", + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "support": { + "issues": "https://github.com/consolidation/annotated-command/issues", + "source": "https://github.com/consolidation/annotated-command/tree/4.10.5" + }, + "time": "2026-03-29T00:50:52+00:00" + }, + { + "name": "consolidation/config", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/adcb989d789f7e0984121492b0377a1def3a6dc8", + "reference": "adcb989d789f7e0984121492b0377a1def3a6dc8", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3", + "grasmash/expander": "^3", + "php": ">=8.2.0", + "symfony/event-dispatcher": "^6 || ^7 || ^8" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3", + "symfony/console": "^7.4 || ^8", + "symfony/yaml": "^7 || ^8", + "yoast/phpunit-polyfills": "^1" + }, + "suggest": { + "symfony/event-dispatcher": "Required to inject configuration into Command options", + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "support": { + "issues": "https://github.com/consolidation/config/issues", + "source": "https://github.com/consolidation/config/tree/3.2.1" + }, + "time": "2026-03-28T23:38:17+00:00" + }, + { + "name": "consolidation/filter-via-dot-access-data", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/filter-via-dot-access-data.git", + "reference": "f9e84bc623d420120028a50dcb9b1d4609ae3b5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/filter-via-dot-access-data/zipball/f9e84bc623d420120028a50dcb9b1d4609ae3b5f", + "reference": "f9e84bc623d420120028a50dcb9b1d4609ae3b5f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0 || ^2.0.0 || ^3.0.0", + "php": ">=7.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Filter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "This project uses dflydev/dot-access-data to provide simple output filtering for applications built with annotated-command / Robo.", + "support": { + "source": "https://github.com/consolidation/filter-via-dot-access-data/tree/2.0.3" + }, + "time": "2025-11-14T21:01:06+00:00" + }, + { + "name": "consolidation/log", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", + "reference": "a0c85d40ca18c22c93fdf78d7e8115cd438ccfe6", + "shasum": "" + }, + "require": { + "php": ">=8.0.0", + "psr/log": "^3", + "symfony/console": "^5 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "type": "library", + "extra": { + "platform": { + "php": "8.2.17" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "support": { + "issues": "https://github.com/consolidation/log/issues", + "source": "https://github.com/consolidation/log/tree/3.1.2" + }, + "time": "2026-03-28T23:36:49+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "4.7.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "a112df9a74854c8438b33b334ed619fa43edf31a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a112df9a74854c8438b33b334ed619fa43edf31a", + "reference": "a112df9a74854c8438b33b334ed619fa43edf31a", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0 || ^2 || ^3", + "php": ">=7.1.3", + "symfony/console": "^4 || ^5 || ^6 || ^7 || ^8", + "symfony/finder": "^4 || ^5 || ^6 || ^7 || ^8" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.4.2", + "phpunit/phpunit": "^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3", + "symfony/var-dumper": "^4 || ^5 || ^6 || ^7 || ^8", + "symfony/yaml": "^4 || ^5 || ^6 || ^7 || ^8", + "yoast/phpunit-polyfills": "^1" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "support": { + "issues": "https://github.com/consolidation/output-formatters/issues", + "source": "https://github.com/consolidation/output-formatters/tree/4.7.1" + }, + "time": "2026-03-28T23:34:39+00:00" + }, + { + "name": "consolidation/robo", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/robo.git", + "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/robo/zipball/6d02c7d800b5e1a3a8977ae74d23bce723486025", + "reference": "6d02c7d800b5e1a3a8977ae74d23bce723486025", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^4.8.1", + "consolidation/config": "^3", + "consolidation/log": "^3", + "consolidation/output-formatters": "^4.1.2", + "league/container": "^3.3.1 || ^4.0", + "php": ">=8.2", + "phpowermove/docblock": "^4.0", + "symfony/console": "^6 || ^7", + "symfony/event-dispatcher": "^6 || ^7", + "symfony/filesystem": "^6 || ^7", + "symfony/finder": "^6 || ^7", + "symfony/process": "^6 || ^7", + "symfony/yaml": "^6 || ^7" + }, + "conflict": { + "codegyre/robo": "*" + }, + "require-dev": { + "natxet/cssmin": "3.0.4", + "patchwork/jsqueeze": "^2", + "pear/archive_tar": "^1.4.4", + "phpunit/phpunit": "^7.5.20 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.6", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "suggest": { + "consolidation/self-update": "For self-updating a phar-based app built with Robo", + "natxet/cssmin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively.", + "totten/lurkerlite": "For monitoring filesystem changes in taskWatch" + }, + "bin": [ + "robo" + ], + "type": "library", + "autoload": { + "psr-4": { + "Robo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "Modern task runner", + "support": { + "issues": "https://github.com/consolidation/robo/issues", + "source": "https://github.com/consolidation/robo/tree/5.1.1" + }, + "time": "2025-11-14T23:30:05+00:00" + }, + { + "name": "consolidation/site-alias", + "version": "4.1.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/site-alias.git", + "reference": "7e1364aec7be0be23bb45799045fd9838a93f2ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/site-alias/zipball/7e1364aec7be0be23bb45799045fd9838a93f2ad", + "reference": "7e1364aec7be0be23bb45799045fd9838a93f2ad", + "shasum": "" + }, + "require": { + "consolidation/config": "^1.2.1 || ^2 || ^3", + "php": ">=7.4", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8", + "symfony/finder": "^5 || ^6 || ^7 || ^8" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.4.2", + "phpunit/phpunit": ">=7", + "squizlabs/php_codesniffer": "^3", + "symfony/var-dumper": "^4 || ^5 || ^6 || ^7 || ^8", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\SiteAlias\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Moshe Weitzman", + "email": "weitzman@tejasa.com" + } + ], + "description": "Manage alias records for local and remote sites.", + "support": { + "issues": "https://github.com/consolidation/site-alias/issues", + "source": "https://github.com/consolidation/site-alias/tree/4.1.3" + }, + "time": "2026-03-28T23:34:58+00:00" + }, + { + "name": "consolidation/site-process", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/site-process.git", + "reference": "4a7438fac393b5127b6a032aa4f593ec917dfab7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/site-process/zipball/4a7438fac393b5127b6a032aa4f593ec917dfab7", + "reference": "4a7438fac393b5127b6a032aa4f593ec917dfab7", + "shasum": "" + }, + "require": { + "consolidation/config": "^2 || ^3", + "consolidation/site-alias": "^3 || ^4", + "php": ">=8.2", + "symfony/console": "^5.4 || ^6 || ^7 || ^8", + "symfony/process": "^5 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\SiteProcess\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Moshe Weitzman", + "email": "weitzman@tejasa.com" + } + ], + "description": "A thin wrapper around the Symfony Process Component that allows applications to use the Site Alias library to specify the target for a remote call.", + "support": { + "issues": "https://github.com/consolidation/site-process/issues", + "source": "https://github.com/consolidation/site-process/tree/6.1.0" + }, + "time": "2026-06-07T03:26:06+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "drupal/core", + "version": "11.4.6", + "source": { + "type": "git", + "url": "https://github.com/drupal/core.git", + "reference": "3fe867f57e51975d4b32243663c7dfd9b92d978a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core/zipball/3fe867f57e51975d4b32243663c7dfd9b92d978a", + "reference": "3fe867f57e51975d4b32243663c7dfd9b92d978a", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "^2.3", + "composer-runtime-api": "^2.1", + "composer/semver": "^3.3", + "doctrine/lexer": "^2 || ^3", + "drupal/core-composer-scaffold": "self.version", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-date": "*", + "ext-dom": "*", + "ext-filter": "*", + "ext-gd": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-session": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "ext-zlib": "*", + "guzzlehttp/guzzle": "^7.10", + "guzzlehttp/psr7": "^2.8.0", + "justinrainbow/json-schema": "^5.2 || ^6.5.2", + "masterminds/html5": "^2.7", + "mck89/peast": "^1.17.4", + "pear/archive_tar": "^1.4.14", + "php": ">=8.3.0", + "php-tuf/composer-stager": "^2.0", + "psr/log": "^3.0", + "revolt/event-loop": "^1.0", + "sebastian/diff": "^4 || ^5 || ^6 || ^7", + "symfony/console": "^7.4", + "symfony/dependency-injection": "^7.4", + "symfony/event-dispatcher": "^7.4", + "symfony/filesystem": "^7.4", + "symfony/finder": "^7.4", + "symfony/http-foundation": "^7.4.13", + "symfony/http-kernel": "^7.4.12", + "symfony/mailer": "^7.4.12", + "symfony/mime": "^7.4.12", + "symfony/polyfill-iconv": "^1.32", + "symfony/polyfill-php84": "^1.32", + "symfony/polyfill-php85": "^1.32", + "symfony/polyfill-php86": "^1.37", + "symfony/process": "^7.4.5", + "symfony/psr-http-message-bridge": "^7.4", + "symfony/routing": "^7.4.13", + "symfony/runtime": "^7.4", + "symfony/serializer": "^7.4", + "symfony/validator": "^7.4", + "symfony/yaml": "^7.4.12", + "twig/html-extra": "^3.23", + "twig/twig": "^3.28.0" + }, + "conflict": { + "dealerdirect/phpcodesniffer-composer-installer": "1.1.0", + "drupal/automatic_updates": "<4", + "drupal/project_browser": "<2.1", + "drush/drush": "<12.4.3" + }, + "replace": { + "drupal/core-annotation": "self.version", + "drupal/core-assertion": "self.version", + "drupal/core-class-finder": "self.version", + "drupal/core-datetime": "self.version", + "drupal/core-dependency-injection": "self.version", + "drupal/core-diff": "self.version", + "drupal/core-discovery": "self.version", + "drupal/core-event-dispatcher": "self.version", + "drupal/core-file-cache": "self.version", + "drupal/core-file-security": "self.version", + "drupal/core-filesystem": "self.version", + "drupal/core-front-matter": "self.version", + "drupal/core-gettext": "self.version", + "drupal/core-graph": "self.version", + "drupal/core-http-foundation": "self.version", + "drupal/core-php-storage": "self.version", + "drupal/core-plugin": "self.version", + "drupal/core-proxy-builder": "self.version", + "drupal/core-render": "self.version", + "drupal/core-serialization": "self.version", + "drupal/core-transliteration": "self.version", + "drupal/core-utility": "self.version", + "drupal/core-uuid": "self.version", + "drupal/core-version": "self.version", + "drupal/search": "*", + "drupal/settings_tray": "*", + "drupal/shortcut": "*", + "drupal/toolbar": "*" + }, + "suggest": { + "ext-zip": "Needed to extend the plugin.manager.archiver service capability with the handling of files in the ZIP format." + }, + "bin": [ + "scripts/dr" + ], + "type": "drupal-core", + "extra": { + "branch-alias": { + "dev-main": "11.x-dev" + }, + "drupal-scaffold": { + "file-mapping": { + "[web-root]/.htaccess": "assets/scaffold/files/htaccess", + "[web-root]/README.md": "assets/scaffold/files/drupal.README.md", + "[web-root]/index.php": "assets/scaffold/files/index.php", + "[web-root]/.csslintrc": "assets/scaffold/files/csslintrc", + "[web-root]/robots.txt": "assets/scaffold/files/robots.txt", + "[web-root]/update.php": "assets/scaffold/files/update.php", + "[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt", + "[web-root]/.eslintignore": "assets/scaffold/files/eslintignore", + "[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json", + "[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php", + "[web-root]/sites/README.txt": "assets/scaffold/files/sites.README.txt", + "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig", + "[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore", + "[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt", + "[project-root]/.gitattributes": "assets/scaffold/files/gitattributes", + "[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt", + "[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt", + "[project-root]/recipes/README.txt": "assets/scaffold/files/recipes.README.txt", + "[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php", + "[web-root]/sites/development.services.yml": "assets/scaffold/files/development.services.yml", + "[web-root]/sites/example.settings.local.php": "assets/scaffold/files/example.settings.local.php", + "[web-root]/sites/default/default.services.yml": "assets/scaffold/files/default.services.yml", + "[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php" + } + } + }, + "autoload": { + "files": [ + "includes/bootstrap.inc" + ], + "psr-4": { + "Drupal\\Core\\": "lib/Drupal/Core", + "Drupal\\Component\\": "lib/Drupal/Component" + }, + "classmap": [ + "lib/Drupal.php", + "lib/Drupal/Component/DependencyInjection/Container.php", + "lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php", + "lib/Drupal/Component/FileCache/FileCacheFactory.php", + "lib/Drupal/Component/Utility/Timer.php", + "lib/Drupal/Component/Utility/Unicode.php", + "lib/Drupal/Core/Cache/Cache.php", + "lib/Drupal/Core/Cache/CacheBackendInterface.php", + "lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php", + "lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php", + "lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php", + "lib/Drupal/Core/Cache/DatabaseBackend.php", + "lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php", + "lib/Drupal/Core/Database/Connection.php", + "lib/Drupal/Core/Database/Database.php", + "lib/Drupal/Core/Database/StatementInterface.php", + "lib/Drupal/Core/DependencyInjection/Container.php", + "lib/Drupal/Core/DrupalKernel.php", + "lib/Drupal/Core/DrupalKernelInterface.php", + "lib/Drupal/Core/Installer/InstallerRedirectTrait.php", + "lib/Drupal/Core/Site/Settings.php", + "lib/Drupal/Component/Datetime/Time.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Drupal is an open source content management platform powering millions of websites and applications.", + "support": { + "source": "https://github.com/drupal/core/tree/11.4.6" + }, + "funding": [ + { + "url": "https://www.drupal.org/composer-fund", + "type": "other" + } + ], + "time": "2026-09-03T08:08:54+00:00" + }, + { + "name": "drupal/core-composer-scaffold", + "version": "11.4.6", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-composer-scaffold.git", + "reference": "678f9ecb912958ebb046d6f9122b84a0cc6401ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/678f9ecb912958ebb046d6f9122b84a0cc6401ff", + "reference": "678f9ecb912958ebb046d6f9122b84a0cc6401ff", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2", + "php": ">=7.3.0" + }, + "conflict": { + "drupal-composer/drupal-scaffold": "*" + }, + "require-dev": { + "composer/composer": "^1.8@stable" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\Scaffold\\Plugin", + "branch-alias": { + "dev-main": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\Scaffold\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "A flexible Composer project scaffold builder.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" + ], + "support": { + "source": "https://github.com/drupal/core-composer-scaffold/tree/11.4.6" + }, + "time": "2026-07-09T23:23:24+00:00" + }, + { + "name": "drupal/core-project-message", + "version": "11.4.6", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-project-message.git", + "reference": "f921f1c88502d559f77a9c86c87ce9bfd8082d07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-project-message/zipball/f921f1c88502d559f77a9c86c87ce9bfd8082d07", + "reference": "f921f1c88502d559f77a9c86c87ce9bfd8082d07", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2", + "php": ">=7.3.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin", + "branch-alias": { + "dev-main": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\ProjectMessage\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Adds a message after Composer installation.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" + ], + "support": { + "source": "https://github.com/drupal/core-project-message/tree/11.4.6" + }, + "time": "2026-01-07T19:00:53+00:00" + }, + { + "name": "drupal/core-recommended", + "version": "11.4.6", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-recommended.git", + "reference": "e1c13e90c77d5ab2c4febe3f6b64a8afcff0ff0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/e1c13e90c77d5ab2c4febe3f6b64a8afcff0ff0e", + "reference": "e1c13e90c77d5ab2c4febe3f6b64a8afcff0ff0e", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "~v2.4.0", + "composer/semver": "~3.4.4", + "doctrine/lexer": "~3.0.1", + "drupal/core": "11.4.6", + "egulias/email-validator": "~4.0.4", + "justinrainbow/json-schema": "~6.8.2", + "marc-mabe/php-enum": "~v4.7.2", + "masterminds/html5": "~2.10.0", + "mck89/peast": "~v1.17.6", + "pear/archive_tar": "~1.6.0", + "pear/console_getopt": "~v1.4.3", + "pear/pear-core-minimal": "~v1.10.18", + "pear/pear_exception": "~v1.0.2", + "php-tuf/composer-stager": "~v2.1.0", + "psr/container": "~2.0.2", + "psr/event-dispatcher": "~1.0.0", + "psr/http-client": "~1.0.3", + "psr/http-factory": "~1.1.0", + "psr/log": "~3.0.2", + "ralouphie/getallheaders": "~3.0.3", + "revolt/event-loop": "~v1.0.9", + "symfony/console": "~v7.4.13", + "symfony/dependency-injection": "~v7.4.13", + "symfony/deprecation-contracts": "~v3.7.0", + "symfony/error-handler": "~v7.4.8", + "symfony/event-dispatcher": "~v7.4.9", + "symfony/event-dispatcher-contracts": "~v3.7.0", + "symfony/filesystem": "~v7.4.11", + "symfony/finder": "~v7.4.8", + "symfony/http-foundation": "~v7.4.13", + "symfony/http-kernel": "~v7.4.13", + "symfony/mailer": "~v7.4.12", + "symfony/mime": "~v7.4.13", + "symfony/process": "~v7.4.13", + "symfony/psr-http-message-bridge": "~v7.4.8", + "symfony/routing": "~v7.4.13", + "symfony/runtime": "~v7.4.13", + "symfony/serializer": "~v7.4.10", + "symfony/service-contracts": "~v3.7.0", + "symfony/string": "~v7.4.13", + "symfony/translation-contracts": "~v3.7.0", + "symfony/validator": "~v7.4.10", + "symfony/var-dumper": "~v7.4.8", + "symfony/var-exporter": "~v7.4.9", + "symfony/yaml": "~v7.4.13" + }, + "conflict": { + "webflo/drupal-core-strict": "*" + }, + "type": "metapackage", + "extra": { + "branch-alias": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.", + "support": { + "source": "https://github.com/drupal/core-recommended/tree/11.4.6" + }, + "time": "2026-09-03T08:08:54+00:00" + }, + { + "name": "drupal/decoupled_router", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/decoupled_router.git", + "reference": "2.0.7" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/decoupled_router-2.0.7.zip", + "reference": "2.0.7", + "shasum": "448bf05bc840b442025aea547a7c3cb0dd600fb9" + }, + "require": { + "drupal/core": "^10.1 || ^11", + "php": ">=8.1" + }, + "conflict": { + "drupal/redirect": "<1.12" + }, + "require-dev": { + "drupal/redirect": "dev-1.x" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.0.7", + "datestamp": "1787382329", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Mateu AguilÃŗ Bosch", + "homepage": "https://www.drupal.org/user/103796", + "email": "mateu.aguilo.bosch@gmail.com" + }, + { + "name": "e0ipso", + "homepage": "https://www.drupal.org/user/550110" + }, + { + "name": "mglaman", + "homepage": "https://www.drupal.org/user/2416470" + } + ], + "description": "Provides an endpoint that will help you resolve path aliases and redirects for entity related routes.", + "homepage": "https://www.drupal.org/project/decoupled_router", + "support": { + "source": "https://git.drupalcode.org/project/decoupled_router" + } + }, + { + "name": "drupal/druxt", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/druxt.git", + "reference": "1.2.2" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/druxt-1.2.2.zip", + "reference": "1.2.2", + "shasum": "44a98ebb2e3d2471446ea609e0dfbc0c648ab99b" + }, + "require": { + "drupal/core": "^10 || ^11 || ^12", + "drupal/decoupled_router": "^2.0", + "drupal/jsonapi_menu_items": "^1.2", + "drupal/jsonapi_views": "^1.1" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "1.2.2", + "datestamp": "1788585538", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Stuart Clark", + "homepage": "https://www.drupal.org/user/103796", + "email": "stuart@realityloop.com" + }, + { + "name": "realityloop", + "homepage": "https://www.drupal.org/user/139189" + } + ], + "description": "A bridge between frameworks, Drupal in the back, Nuxt.js in the front.", + "homepage": "https://www.drupal.org/project/druxt", + "support": { + "source": "https://git.drupalcode.org/project/druxt" + } + }, + { + "name": "drupal/jsonapi_menu_items", + "version": "1.2.8", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/jsonapi_menu_items.git", + "reference": "1.2.8" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/jsonapi_menu_items-1.2.8.zip", + "reference": "1.2.8", + "shasum": "6be93fc2636584ce70c11bb8aef3e5cf82365c1d" + }, + "require": { + "drupal/core": "^9.5 || ^10 || ^11", + "drupal/jsonapi_resources": "^1.0" + }, + "require-dev": { + "drupal/jsonapi_hypermedia": "^1.6", + "drupal/menu_item_extras": "^3.0", + "drupal/menu_link_config": "^1.0" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "1.2.8", + "datestamp": "1748881155", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "acbramley", + "homepage": "https://www.drupal.org/user/1036766" + }, + { + "name": "deciphered", + "homepage": "https://www.drupal.org/user/103796" + }, + { + "name": "larowlan", + "homepage": "https://www.drupal.org/user/395439" + }, + { + "name": "mglaman", + "homepage": "https://www.drupal.org/user/2416470" + } + ], + "description": "Adds a JSON API resource for menu items.", + "homepage": "https://www.drupal.org/project/jsonapi_menu_items", + "keywords": [ + "Drupal", + "JSON API" + ], + "support": { + "source": "https://git.drupalcode.org/project/jsonapi_menu_items" + } + }, + { + "name": "drupal/jsonapi_resources", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/jsonapi_resources.git", + "reference": "8.x-1.8" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/jsonapi_resources-8.x-1.8.zip", + "reference": "8.x-1.8", + "shasum": "66ab9e109e274a0e48bddbce86854554b7eb5098" + }, + "require": { + "drupal/core": "^9.1 || ^10 || ^11" + }, + "require-dev": { + "drupal/jsonapi_extras": "^3.28" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.8", + "datestamp": "1787322568", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "API-First Initiative", + "homepage": "https://www.drupal.org/user/3616626" + }, + { + "name": "bradjones1", + "homepage": "https://www.drupal.org/user/405824" + }, + { + "name": "gabesullice", + "homepage": "https://www.drupal.org/user/2287430" + }, + { + "name": "mglaman", + "homepage": "https://www.drupal.org/user/2416470" + } + ], + "description": "This module let's you define custom resources at routes of your choice that use existing resource types.", + "homepage": "https://www.drupal.org/project/jsonapi_resources", + "keywords": [ + "Drupal", + "JSON API" + ], + "support": { + "source": "https://git.drupalcode.org/project/jsonapi_resources" + } + }, + { + "name": "drupal/jsonapi_views", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/jsonapi_views.git", + "reference": "8.x-1.2" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/jsonapi_views-8.x-1.2.zip", + "reference": "8.x-1.2", + "shasum": "77e8b80b9ab558e24f54705c922be5573caa837b" + }, + "require": { + "drupal/core": ">=8.8", + "drupal/jsonapi_resources": "^1.0", + "php": ">=8.2" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.2", + "datestamp": "1786523971", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "deciphered", + "homepage": "https://www.drupal.org/user/103796" + }, + { + "name": "pixelwhip", + "homepage": "https://www.drupal.org/user/275292" + } + ], + "description": "This module creates custom JSON:API resources from Views.", + "homepage": "https://www.drupal.org/project/jsonapi_views", + "keywords": [ + "Drupal", + "JSON-API" + ], + "support": { + "source": "https://git.drupalcode.org/project/jsonapi_views" + } + }, + { + "name": "drush/drush", + "version": "13.7.7", + "source": { + "type": "git", + "url": "https://github.com/drush-ops/drush.git", + "reference": "1166ab05f297d73e24741f8f5fdcc421e77365b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drush-ops/drush/zipball/1166ab05f297d73e24741f8f5fdcc421e77365b8", + "reference": "1166ab05f297d73e24741f8f5fdcc421e77365b8", + "shasum": "" + }, + "require": { + "chi-teck/drupal-code-generator": "^3.6 || ^4@alpha", + "composer-runtime-api": "^2.2", + "composer/semver": "^1.4 || ^3", + "consolidation/annotated-command": "^4.10.2", + "consolidation/config": "^2.1.2 || ^3.1.1", + "consolidation/filter-via-dot-access-data": "^2.0.2", + "consolidation/output-formatters": "^4.6.1", + "consolidation/robo": "^4.0.6 || ^5.1.0", + "consolidation/site-alias": "^4.1.1", + "consolidation/site-process": "^5.4.2 || ^6.1", + "dflydev/dot-access-data": "^3.0.2", + "ext-dom": "*", + "grasmash/yaml-cli": "^3.2", + "guzzlehttp/guzzle": "^7.0", + "laravel/prompts": "^0.3.5", + "league/container": "^4.2", + "php": ">=8.3", + "psy/psysh": "~0.12", + "symfony/event-dispatcher": "^6 || ^7", + "symfony/filesystem": "^6.1 || ^7", + "symfony/finder": "^6 || ^7", + "symfony/var-dumper": "^6.0 || ^7", + "symfony/yaml": "^6.0 || ^7" + }, + "conflict": { + "drupal/core": "<10.4 <12.0", + "drupal/migrate_run": "*", + "drupal/migrate_tools": "<= 5" + }, + "require-dev": { + "composer/installers": "^2", + "cweagans/composer-patches": "~1.7.3", + "drupal/core-recommended": "^10.4.0 || 11.x-dev", + "drupal/semver_example": "2.3.0", + "jetbrains/phpstorm-attributes": "^1.0", + "mglaman/phpstan-drupal": "^2", + "phpunit/phpunit": "^9 || ^10 || ^11", + "rector/rector": "^2", + "squizlabs/php_codesniffer": "^3.7" + }, + "bin": [ + "drush", + "drush.php" + ], + "type": "library", + "extra": { + "patches-file": "composer.patches.json", + "installer-paths": { + "sut/core": [ + "type:drupal-core" + ], + "sut/libraries/{$name}": [ + "type:drupal-library" + ], + "sut/themes/unish/{$name}": [ + "drupal/empty_theme" + ], + "sut/drush/contrib/{$name}": [ + "type:drupal-drush" + ], + "sut/modules/unish/{$name}": [ + "drupal/devel" + ], + "sut/themes/contrib/{$name}": [ + "type:drupal-theme" + ], + "sut/modules/contrib/{$name}": [ + "type:drupal-module" + ], + "sut/profiles/contrib/{$name}": [ + "type:drupal-profile" + ] + } + }, + "autoload": { + "psr-4": { + "Drush\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Moshe Weitzman", + "email": "weitzman@tejasa.com" + }, + { + "name": "Owen Barton", + "email": "drupal@owenbarton.com" + }, + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Jonathan AraÃąa Cruz", + "email": "jonhattan@faita.net" + }, + { + "name": "Jonathan Hedstrom", + "email": "jhedstrom@gmail.com" + }, + { + "name": "Christopher Gervais", + "email": "chris@ergonlogic.com" + }, + { + "name": "Dave Reid", + "email": "dave@davereid.net" + }, + { + "name": "Damian Lee", + "email": "damiankloip@googlemail.com" + } + ], + "description": "Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.", + "homepage": "http://www.drush.org", + "support": { + "forum": "http://drupal.stackexchange.com/questions/tagged/drush", + "issues": "https://github.com/drush-ops/drush/issues", + "security": "https://github.com/drush-ops/drush/security/advisories", + "slack": "https://drupal.slack.com/messages/C62H9CWQM", + "source": "https://github.com/drush-ops/drush/tree/13.7.7" + }, + "funding": [ + { + "url": "https://github.com/weitzman", + "type": "github" + } + ], + "time": "2026-09-04T03:42:35+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "grasmash/expander", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "reference": "eea11b9afb0c32483b18b9009f4ca07b770e39f4", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.0", + "php": ">=8.0", + "psr/log": "^2 | ^3" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "support": { + "issues": "https://github.com/grasmash/expander/issues", + "source": "https://github.com/grasmash/expander/tree/3.0.1" + }, + "time": "2024-11-25T23:28:05+00:00" + }, + { + "name": "grasmash/yaml-cli", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-cli.git", + "reference": "09a8860566958a1576cc54bbe910a03477e54971" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-cli/zipball/09a8860566958a1576cc54bbe910a03477e54971", + "reference": "09a8860566958a1576cc54bbe910a03477e54971", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3", + "php": ">=8.0", + "symfony/console": "^6 || ^7", + "symfony/filesystem": "^6 || ^7", + "symfony/yaml": "^6 || ^7" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "bin": [ + "bin/yaml-cli" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlCli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "A command line tool for reading and manipulating yaml files.", + "support": { + "issues": "https://github.com/grasmash/yaml-cli/issues", + "source": "https://github.com/grasmash/yaml-cli/tree/3.2.1" + }, + "time": "2024-04-23T02:10:57+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.15.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "MÃĄrk SÃĄgi-KazÃĄr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:21:06+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.5.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:11:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.13.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "95e7828100de18b4e269fb1703be530082d5166d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "MÃĄrk SÃĄgi-KazÃĄr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "MÃĄrk SÃĄgi-KazÃĄr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.13.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:13:11+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.8.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "dev-main", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert SchÃļnthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" + }, + "time": "2026-05-05T05:39:01+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.24", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/5d3cdef29e93ca3b62b1871359db3078cd99908b", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.24" + }, + "time": "2026-08-20T12:55:36+00:00" + }, + { + "name": "league/container", + "version": "4.2.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "nette/php-generator": "^3.4", + "nikic/php-parser": "^4.10", + "phpstan/phpstan": "^0.12.47", + "phpunit/phpunit": "^8.5.17", + "roave/security-advisories": "dev-latest", + "scrutinizer/ocular": "^1.8", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "mail@philbennett.co.uk", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "support": { + "issues": "https://github.com/thephpleague/container/issues", + "source": "https://github.com/thephpleague/container/tree/4.2.5" + }, + "funding": [ + { + "url": "https://github.com/philipobenito", + "type": "github" + } + ], + "time": "2025-05-20T12:55:37+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + }, + "time": "2026-06-23T18:43:15+00:00" + }, + { + "name": "mck89/peast", + "version": "v1.17.7", + "source": { + "type": "git", + "url": "https://github.com/mck89/peast.git", + "reference": "89ee39f81e82cb7ed36cc5abaf2edd43cef71770" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mck89/peast/zipball/89ee39f81e82cb7ed36cc5abaf2edd43cef71770", + "reference": "89ee39f81e82cb7ed36cc5abaf2edd43cef71770", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17.7-dev" + } + }, + "autoload": { + "psr-4": { + "Peast\\": "lib/Peast/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marco MarchiÃ˛", + "email": "marco.mm89@gmail.com" + } + ], + "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.17.7" + }, + "time": "2026-07-21T11:56:06+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "pear/archive_tar", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/pear/Archive_Tar.git", + "reference": "a0d2c458a2ff6ee8943ac4271b7f57d613529ae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/a0d2c458a2ff6ee8943ac4271b7f57d613529ae0", + "reference": "a0d2c458a2ff6ee8943ac4271b7f57d613529ae0", + "shasum": "" + }, + "require": { + "pear/pear-core-minimal": "^1.10.0alpha2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-bz2": "Bz2 compression support.", + "ext-xz": "Lzma2 compression support.", + "ext-zlib": "Gzip compression support." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Archive_Tar": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Vincent Blavet", + "email": "vincent@phpconcept.net" + }, + { + "name": "Greg Beaver", + "email": "greg@chiaraquartet.net" + }, + { + "name": "Michiel Rook", + "email": "mrook@php.net" + } + ], + "description": "Tar file management class with compression support (gzip, bzip2, lzma2)", + "homepage": "https://github.com/pear/Archive_Tar", + "keywords": [ + "archive", + "tar" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar", + "source": "https://github.com/pear/Archive_Tar" + }, + "time": "2026-08-19T17:37:49+00:00" + }, + { + "name": "pear/console_getopt", + "version": "v1.4.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Console_Getopt.git", + "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/a41f8d3e668987609178c7c4a9fe48fecac53fa0", + "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Console": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Andrei Zmievski", + "email": "andrei@php.net", + "role": "Lead" + }, + { + "name": "Stig Bakken", + "email": "stig@php.net", + "role": "Developer" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net", + "role": "Helper" + } + ], + "description": "More info available on: http://pear.php.net/package/Console_Getopt", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt", + "source": "https://github.com/pear/Console_Getopt" + }, + "time": "2019-11-20T18:27:48+00:00" + }, + { + "name": "pear/pear-core-minimal", + "version": "v1.10.18", + "source": { + "type": "git", + "url": "https://github.com/pear/pear-core-minimal.git", + "reference": "c7b55789d01de0ce090d289b73f1bbd6a2f113b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/c7b55789d01de0ce090d289b73f1bbd6a2f113b1", + "reference": "c7b55789d01de0ce090d289b73f1bbd6a2f113b1", + "shasum": "" + }, + "require": { + "pear/console_getopt": "~1.4", + "pear/pear_exception": "~1.0", + "php": ">=5.4" + }, + "replace": { + "rsky/pear-core-min": "self.version" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "src/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@php.net", + "role": "Lead" + } + ], + "description": "Minimal set of PEAR core files to be used as composer dependency", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", + "source": "https://github.com/pear/pear-core-minimal" + }, + "time": "2025-12-14T20:37:07+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + }, + { + "name": "phootwork/collection", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/phootwork/collection.git", + "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phootwork/collection/zipball/46dde20420fba17766c89200bc3ff91d3e58eafa", + "reference": "46dde20420fba17766c89200bc3ff91d3e58eafa", + "shasum": "" + }, + "require": { + "phootwork/lang": "^3.0", + "php": ">=8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "phootwork\\collection\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Gossmann", + "homepage": "http://gos.si" + } + ], + "description": "The phootwork library fills gaps in the php language and provides better solutions than the existing ones php offers.", + "homepage": "https://phootwork.github.io/collection/", + "keywords": [ + "Array object", + "Text object", + "collection", + "collections", + "json", + "list", + "map", + "queue", + "set", + "stack", + "xml" + ], + "support": { + "issues": "https://github.com/phootwork/phootwork/issues", + "source": "https://github.com/phootwork/collection/tree/v3.2.3" + }, + "time": "2022-08-27T12:51:24+00:00" + }, + { + "name": "phootwork/lang", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/phootwork/lang.git", + "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phootwork/lang/zipball/52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", + "reference": "52ec8cce740ce1c424eef02f43b43d5ddfec7b5e", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/polyfill-php81": "^1.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "phootwork\\lang\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Gossmann", + "homepage": "http://gos.si" + } + ], + "description": "Missing PHP language constructs", + "homepage": "https://phootwork.github.io/lang/", + "keywords": [ + "array", + "comparator", + "comparison", + "string" + ], + "support": { + "issues": "https://github.com/phootwork/phootwork/issues", + "source": "https://github.com/phootwork/lang/tree/v3.2.3" + }, + "time": "2024-10-03T13:43:19+00:00" + }, + { + "name": "php-tuf/composer-stager", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-tuf/composer-stager.git", + "reference": "64f3b033736483af81551b0f41ab1126510cfea0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-tuf/composer-stager/zipball/64f3b033736483af81551b0f41ab1126510cfea0", + "reference": "64f3b033736483af81551b0f41ab1126510cfea0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1.0", + "symfony/filesystem": "^6.2 || ^7.0 || ^8.0", + "symfony/process": "^6.4.33 || ^7.3.11 || ^8.0", + "symfony/translation-contracts": "^3.1" + }, + "conflict": { + "symfony/process": ">=6.4 <6.4.33 || >=7.3 <7.3.11 || >=7.4 <7.4.5 || >=8.0 <8.0.5", + "symfony/symfony": ">=6.4 <6.4.33 || >=7.3 <7.3.11 || >=7.4 <7.4.5 || >=8.0 <8.0.5", + "symfony/yaml": ">=2.0.0 <5.4.52 || >=6.0.0 <6.4.40 || >=7.0.0 <7.4.12 || >=8.0.0 <8.0.12" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ext-simplexml": "*", + "phpspec/prophecy": "^1.17", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5.19", + "slevomat/coding-standard": "^8.13", + "squizlabs/php_codesniffer": "^3.7", + "symfony/config": "^6.3 || ^8.0", + "symfony/dependency-injection": "^6.3", + "symfony/yaml": "^6.3", + "thecodingmachine/phpstan-strict-rules": "^1.0" + }, + "suggest": { + "symfony/dependency-injection": "For dependency injection", + "symfony/translation": "For internationalization tools" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpTuf\\ComposerStager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Travis Carden", + "email": "travis.carden@gmail.com", + "role": "Developer" + } + ], + "description": "Stages Composer commands so they can be safely run on a production codebase.", + "homepage": "https://github.com/php-tuf/composer-stager", + "support": { + "issues": "https://github.com/php-tuf/composer-stager/issues", + "source": "https://github.com/php-tuf/composer-stager" + }, + "time": "2026-07-07T14:30:49+00:00" + }, + { + "name": "phpowermove/docblock", + "version": "v4.0", + "source": { + "type": "git", + "url": "https://github.com/phpowermove/docblock.git", + "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpowermove/docblock/zipball/a73f6e17b7d4e1b92ca5378c248c952c9fae7826", + "reference": "a73f6e17b7d4e1b92ca5378c248c952c9fae7826", + "shasum": "" + }, + "require": { + "phootwork/collection": "^3.0", + "phootwork/lang": "^3.0", + "php": ">=8.0" + }, + "require-dev": { + "phootwork/php-cs-fixer-config": "^0.4", + "phpunit/phpunit": "^9.0", + "psalm/phar": "^4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpowermove\\docblock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Gossmann", + "homepage": "http://gos.si" + } + ], + "description": "PHP Docblock parser and generator. An API to read and write Docblocks.", + "keywords": [ + "docblock", + "generator", + "parser" + ], + "support": { + "issues": "https://github.com/phpowermove/docblock/issues", + "source": "https://github.com/phpowermove/docblock/tree/v4.0" + }, + "time": "2021-09-22T16:57:06+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.24", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" + }, + "time": "2026-06-29T15:41:09+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "44061cf513e53c6200372fc935ac42271566295d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", + "reference": "44061cf513e53c6200372fc935ac42271566295d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian LÃŧck", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9" + }, + "time": "2026-05-16T17:55:38+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.33", + "symfony/process": "^7.4.17" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" + } + ], + "time": "2026-08-25T15:35:54+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-25T14:18:37+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f318ac9da5aba0be2cf43ecdd562c05a33bc11c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f318ac9da5aba0be2cf43ecdd562c05a33bc11c0", + "reference": "f318ac9da5aba0be2cf43ecdd562c05a33bc11c0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8373921e231e190a88e2ad526951bbaa791576fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8373921e231e190a88e2ad526951bbaa791576fa", + "reference": "8373921e231e190a88e2ad526951bbaa791576fa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/90d412aa5277c6819db39e7605aa46b1019e3232", + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-23T10:03:40+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T12:09:28+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d070b716a32fbe3bf04204db0f58ace73b86d133", + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T20:10:52+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/275d2d2d24530f2a0eaf17704a3a93860a036351", + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T21:24:29+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b17c9bf3a551d5f635638a3b6c05f06c4dc87584", + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.4.17" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "bf328d82105831db3e409195db0540ff57f27c80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/bf328d82105831db3e409195db0540ff57f27c80", + "reference": "bf328d82105831db3e409195db0540ff57f27c80", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-22T09:04:42+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/2c5729fd241b4b22f6e4b436bc3354a4f262df57", + "reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-iconv": "*" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T08:25:59+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-24T10:51:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-07T06:33:24+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-02T13:42:24+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "058d17fc284cce14efb2385783b55014a461b176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-21T17:40:08+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-24T13:12:05+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/ddd558991e98f693ae6bf5063cc1b0362c6bbec3", + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-17T13:12:36+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/15497f743dd714f3167cb6a56509b9d42e6417b3", + "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4", + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:22:21+00:00" + }, + { + "name": "symfony/serializer", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "1f31d4e902c85b820af5dce27dbe17435524bed0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/1f31d4e902c85b820af5dce27dbe17435524bed0", + "reference": "1f31d4e902c85b820af5dce27dbe17435524bed0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", + "symfony/property-info": "<6.4.43", + "symfony/type-info": "<7.2.5", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", + "symfony/property-info": "^6.4.43|^7.4.15|^8.0.15", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.2.5|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T00:47:26+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-27T15:39:01+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T07:33:02+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/validator", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "976bd879fda78de30ee4688fd38ae87bc4d2dc76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/976bd879fda78de30ee4688fd38ae87bc4d2dc76", + "reference": "976bd879fda78de30ee4688fd38ae87bc4d2dc76", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<7.0", + "symfony/expression-language": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/intl": "<6.4", + "symfony/property-info": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/var-exporter": "<6.4.25|>=7.0,<7.3.3", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/type-info": "^7.1.8", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/bin/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T00:47:26+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "e088da50b813f32473a76871616cbb8fa54653a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e088da50b813f32473a76871616cbb8fa54653a8", + "reference": "e088da50b813f32473a76871616cbb8fa54653a8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T20:10:52+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d6a87acbe48cbc707b9aba7f1a6c2215aab02603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d6a87acbe48cbc707b9aba7f1a6c2215aab02603", + "reference": "d6a87acbe48cbc707b9aba7f1a6c2215aab02603", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-23T10:03:40+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4cef939e55b8a21c5780418de0d98ab00d0736e1", + "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.4.18" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-30T00:47:26+00:00" + }, + { + "name": "twig/html-extra", + "version": "v3.28.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/html-extra.git", + "reference": "760893ed7bdd0a381e4e00004c6f6e26ad3881d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/760893ed7bdd0a381e4e00004c6f6e26ad3881d7", + "reference": "760893ed7bdd0a381e4e00004c6f6e26ad3881d7", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/mime": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Html\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for HTML", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/html-extra/tree/v3.28.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-06-25T06:50:01+00:00" + }, + { + "name": "twig/twig", + "version": "v3.28.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", + "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.28.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-07-03T20:44:34+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "platform-overrides": { + "php": "8.3" + }, + "plugin-api-version": "2.9.0" +} diff --git a/example/drupal/config/sync/.htaccess b/example/drupal/config/sync/.htaccess deleted file mode 100644 index 25776a3..0000000 --- a/example/drupal/config/sync/.htaccess +++ /dev/null @@ -1,24 +0,0 @@ -# Deny all requests from Apache 2.4+. - - Require all denied - - -# Deny all requests from Apache 2.0-2.2. - - Deny from all - - -# Turn off all options we don't need. -Options -Indexes -ExecCGI -Includes -MultiViews - -# Set the catch-all handler to prevent scripts from being executed. -SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 - - # Override the handler again if we're run later in the evaluation list. - SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 - - -# If we know how to do it safely, disable the PHP engine entirely. - - php_flag engine off - \ No newline at end of file diff --git a/example/drupal/config/sync/automated_cron.settings.yml b/example/drupal/config/sync/automated_cron.settings.yml deleted file mode 100644 index 3fc5821..0000000 --- a/example/drupal/config/sync/automated_cron.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -_core: - default_config_hash: fUksROt4FfkAU9BV4hV2XvhTBSS2nTNrZS4U7S-tKrs -interval: 10800 diff --git a/example/drupal/config/sync/block.block.bartik_account_menu.yml b/example/drupal/config/sync/block.block.bartik_account_menu.yml deleted file mode 100644 index 472111b..0000000 --- a/example/drupal/config/sync/block.block.bartik_account_menu.yml +++ /dev/null @@ -1,27 +0,0 @@ -uuid: 0cdf0af8-853d-4c04-9d26-9d44c95a0e07 -langcode: en -status: true -dependencies: - config: - - system.menu.account - module: - - system - theme: - - bartik -_core: - default_config_hash: jzu74Q7R0QWgw8IniA9HZRSb_a8RPak1M0q5J-VGkZI -id: bartik_account_menu -theme: bartik -region: secondary_menu -weight: 0 -provider: null -plugin: 'system_menu_block:account' -settings: - id: 'system_menu_block:account' - label: 'User account menu' - label_display: '0' - provider: system - level: 1 - depth: 1 - expand_all_items: false -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_branding.yml b/example/drupal/config/sync/block.block.bartik_branding.yml deleted file mode 100644 index 4aa36eb..0000000 --- a/example/drupal/config/sync/block.block.bartik_branding.yml +++ /dev/null @@ -1,25 +0,0 @@ -uuid: 3d46aad9-9e9f-4134-9320-4e3afeb7d156 -langcode: en -status: true -dependencies: - module: - - system - theme: - - bartik -_core: - default_config_hash: d-0emr1mEq7sFKDu5RHUJckvNk62hvIZdRwPEfnJyGg -id: bartik_branding -theme: bartik -region: header -weight: 0 -provider: null -plugin: system_branding_block -settings: - id: system_branding_block - label: 'Site branding' - label_display: '0' - provider: system - use_site_logo: true - use_site_name: true - use_site_slogan: true -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_breadcrumbs.yml b/example/drupal/config/sync/block.block.bartik_breadcrumbs.yml deleted file mode 100644 index 77ddcdb..0000000 --- a/example/drupal/config/sync/block.block.bartik_breadcrumbs.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: c609eda1-5739-4e72-bb27-3d9d4678028b -langcode: en -status: true -dependencies: - module: - - system - theme: - - bartik -_core: - default_config_hash: y0bXRvKYQQKkf_RVxA3aOPGpIfKKsDOzzrWYCgjxJ9Y -id: bartik_breadcrumbs -theme: bartik -region: breadcrumb -weight: 0 -provider: null -plugin: system_breadcrumb_block -settings: - id: system_breadcrumb_block - label: Breadcrumbs - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_content.yml b/example/drupal/config/sync/block.block.bartik_content.yml deleted file mode 100644 index 9b695e7..0000000 --- a/example/drupal/config/sync/block.block.bartik_content.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 382cfdd8-1936-4ef4-9416-629e5c4d4842 -langcode: en -status: true -dependencies: - module: - - system - theme: - - bartik -_core: - default_config_hash: kigu8pTxXt1qGR2NJ5HHdC4G2G1wsDrIjLPKBT5wUBc -id: bartik_content -theme: bartik -region: content -weight: 0 -provider: null -plugin: system_main_block -settings: - id: system_main_block - label: 'Main page content' - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_footer.yml b/example/drupal/config/sync/block.block.bartik_footer.yml deleted file mode 100644 index b50b822..0000000 --- a/example/drupal/config/sync/block.block.bartik_footer.yml +++ /dev/null @@ -1,27 +0,0 @@ -uuid: bb705939-021b-476b-9bc6-b52e5f08906e -langcode: en -status: true -dependencies: - config: - - system.menu.footer - module: - - system - theme: - - bartik -_core: - default_config_hash: pnCK21YFjH7iB4XDeokSq3N3XSr6qX8BsqMs9nmip6E -id: bartik_footer -theme: bartik -region: footer_fifth -weight: 0 -provider: null -plugin: 'system_menu_block:footer' -settings: - id: 'system_menu_block:footer' - label: 'Footer menu' - label_display: '0' - provider: system - level: 1 - depth: 0 - expand_all_items: false -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_help.yml b/example/drupal/config/sync/block.block.bartik_help.yml deleted file mode 100644 index 502187b..0000000 --- a/example/drupal/config/sync/block.block.bartik_help.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 045beda0-033a-41e2-9111-aa66ac575f87 -langcode: en -status: true -dependencies: - module: - - help - theme: - - bartik -_core: - default_config_hash: '-laWx8WA-ikiIEH3NJQ0hVbjqk3iEuBms91LB0B4IQU' -id: bartik_help -theme: bartik -region: content -weight: -30 -provider: null -plugin: help_block -settings: - id: help_block - label: Help - label_display: '0' - provider: help -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_local_actions.yml b/example/drupal/config/sync/block.block.bartik_local_actions.yml deleted file mode 100644 index 9d07967..0000000 --- a/example/drupal/config/sync/block.block.bartik_local_actions.yml +++ /dev/null @@ -1,20 +0,0 @@ -uuid: dd8ac186-c398-4dd2-898d-08d9c49182c6 -langcode: en -status: true -dependencies: - theme: - - bartik -_core: - default_config_hash: XRdb8LpbArVA6CpuuEZxzi50JIdjsJaCCZ5o3fH0Blk -id: bartik_local_actions -theme: bartik -region: content -weight: -20 -provider: null -plugin: local_actions_block -settings: - id: local_actions_block - label: 'Primary admin actions' - label_display: '0' - provider: core -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_local_tasks.yml b/example/drupal/config/sync/block.block.bartik_local_tasks.yml deleted file mode 100644 index b9698d6..0000000 --- a/example/drupal/config/sync/block.block.bartik_local_tasks.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: c0b25b9a-5600-4d88-aecb-c9c1bdebfe4c -langcode: en -status: true -dependencies: - theme: - - bartik -_core: - default_config_hash: Qf2BoIzd9py5AYXZj8fJzCYDyVk1-AWXcu-AqQeDZKA -id: bartik_local_tasks -theme: bartik -region: content -weight: -40 -provider: null -plugin: local_tasks_block -settings: - id: local_tasks_block - label: Tabs - label_display: '0' - provider: core - primary: true - secondary: true -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_main_menu.yml b/example/drupal/config/sync/block.block.bartik_main_menu.yml deleted file mode 100644 index b417c45..0000000 --- a/example/drupal/config/sync/block.block.bartik_main_menu.yml +++ /dev/null @@ -1,27 +0,0 @@ -uuid: 4ff45cd4-2584-43d1-809b-6fb13abc87f2 -langcode: en -status: true -dependencies: - config: - - system.menu.main - module: - - system - theme: - - bartik -_core: - default_config_hash: _G1IfGESk7WqvVCi7B3VJxhSY0YICgv1OmuVPJqO8bE -id: bartik_main_menu -theme: bartik -region: primary_menu -weight: 0 -provider: null -plugin: 'system_menu_block:main' -settings: - id: 'system_menu_block:main' - label: 'Main navigation' - label_display: '0' - provider: system - level: 1 - depth: 1 - expand_all_items: false -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_messages.yml b/example/drupal/config/sync/block.block.bartik_messages.yml deleted file mode 100644 index b7333dd..0000000 --- a/example/drupal/config/sync/block.block.bartik_messages.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 3e413c1d-ae92-4c90-a6e3-be29b89a8617 -langcode: en -status: true -dependencies: - module: - - system - theme: - - bartik -_core: - default_config_hash: KC12e0aosUnBBs5bd374JVcQjJaFNOSIHzu0dUGf22o -id: bartik_messages -theme: bartik -region: highlighted -weight: 0 -provider: null -plugin: system_messages_block -settings: - id: system_messages_block - label: 'Status messages' - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_page_title.yml b/example/drupal/config/sync/block.block.bartik_page_title.yml deleted file mode 100644 index 57f9cff..0000000 --- a/example/drupal/config/sync/block.block.bartik_page_title.yml +++ /dev/null @@ -1,20 +0,0 @@ -uuid: a443fc98-d7ce-4cbb-91a9-97797ee0dcfa -langcode: en -status: true -dependencies: - theme: - - bartik -_core: - default_config_hash: ItFhhiUOoeR7oIq73ZaCV7PAYrOwXZ-9fazMzF5DJXM -id: bartik_page_title -theme: bartik -region: content -weight: -50 -provider: null -plugin: page_title_block -settings: - id: page_title_block - label: 'Page title' - label_display: '0' - provider: core -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_powered.yml b/example/drupal/config/sync/block.block.bartik_powered.yml deleted file mode 100644 index be77413..0000000 --- a/example/drupal/config/sync/block.block.bartik_powered.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: a9072fac-7da0-4c23-b22b-c56e1b1a0e9d -langcode: en -status: true -dependencies: - module: - - system - theme: - - bartik -_core: - default_config_hash: I6Esh8cDlPdl2POZg4MOSRUrQNYA1UNCJCxL2yqT_wc -id: bartik_powered -theme: bartik -region: footer_fifth -weight: 10 -provider: null -plugin: system_powered_by_block -settings: - id: system_powered_by_block - label: 'Powered by Drupal' - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_search.yml b/example/drupal/config/sync/block.block.bartik_search.yml deleted file mode 100644 index 2b017dd..0000000 --- a/example/drupal/config/sync/block.block.bartik_search.yml +++ /dev/null @@ -1,23 +0,0 @@ -uuid: d2846a82-479f-4e89-bc37-31b8ec7eb3bb -langcode: en -status: true -dependencies: - module: - - search - theme: - - bartik -_core: - default_config_hash: jQ-C8l3dJPqYCGST1e-kSjR14PDP4wq-iFbp8dMFcRo -id: bartik_search -theme: bartik -region: sidebar_first -weight: -1 -provider: null -plugin: search_form_block -settings: - id: search_form_block - label: Search - label_display: visible - provider: search - page_id: node_search -visibility: { } diff --git a/example/drupal/config/sync/block.block.bartik_tools.yml b/example/drupal/config/sync/block.block.bartik_tools.yml deleted file mode 100644 index a0dc3e8..0000000 --- a/example/drupal/config/sync/block.block.bartik_tools.yml +++ /dev/null @@ -1,27 +0,0 @@ -uuid: bd75f86f-b3c1-4fd5-88b0-699ef1a2fc19 -langcode: en -status: true -dependencies: - config: - - system.menu.tools - module: - - system - theme: - - bartik -_core: - default_config_hash: TszJ7dH_11Qjmo_OpMzqznaED4Bvy1XfVnBzTC9LZXE -id: bartik_tools -theme: bartik -region: sidebar_first -weight: 0 -provider: null -plugin: 'system_menu_block:tools' -settings: - id: 'system_menu_block:tools' - label: Tools - label_display: visible - provider: system - level: 1 - depth: 0 - expand_all_items: false -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_breadcrumbs.yml b/example/drupal/config/sync/block.block.seven_breadcrumbs.yml deleted file mode 100644 index 87b9905..0000000 --- a/example/drupal/config/sync/block.block.seven_breadcrumbs.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: c2ad9d78-1717-411b-bf33-eed677a49c39 -langcode: en -status: true -dependencies: - module: - - system - theme: - - seven -_core: - default_config_hash: 2rIN97Rd7-rjv3P4h0-xrZnAgPJHyXFEwrB--sS2_qE -id: seven_breadcrumbs -theme: seven -region: breadcrumb -weight: 0 -provider: null -plugin: system_breadcrumb_block -settings: - id: system_breadcrumb_block - label: Breadcrumbs - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_content.yml b/example/drupal/config/sync/block.block.seven_content.yml deleted file mode 100644 index 73e6f71..0000000 --- a/example/drupal/config/sync/block.block.seven_content.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: dc6e74a8-283c-4fa1-a48e-ccfe5947387b -langcode: en -status: true -dependencies: - module: - - system - theme: - - seven -_core: - default_config_hash: 6_eJxtyfANKFE_nfriHKgrARRIPHH88sI6jvQ4EHbBc -id: seven_content -theme: seven -region: content -weight: 0 -provider: null -plugin: system_main_block -settings: - id: system_main_block - label: 'Main page content' - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_help.yml b/example/drupal/config/sync/block.block.seven_help.yml deleted file mode 100644 index 95757c9..0000000 --- a/example/drupal/config/sync/block.block.seven_help.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 46cea158-99b6-4773-999a-89b624d0ec51 -langcode: en -status: true -dependencies: - module: - - help - theme: - - seven -_core: - default_config_hash: bYvyEzJ7UcmjXZAYLxb4Qb_J77K9bPbk4ealdap31FY -id: seven_help -theme: seven -region: help -weight: 0 -provider: null -plugin: help_block -settings: - id: help_block - label: Help - label_display: '0' - provider: help -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_local_actions.yml b/example/drupal/config/sync/block.block.seven_local_actions.yml deleted file mode 100644 index dcb326a..0000000 --- a/example/drupal/config/sync/block.block.seven_local_actions.yml +++ /dev/null @@ -1,20 +0,0 @@ -uuid: 484d44ea-82e9-4a08-a277-2cb1df4382c0 -langcode: en -status: true -dependencies: - theme: - - seven -_core: - default_config_hash: gv5JXTvQXjlMY6PXrfxzcLMjMg4074WiEQCy7ZSMvK4 -id: seven_local_actions -theme: seven -region: content -weight: -10 -provider: null -plugin: local_actions_block -settings: - id: local_actions_block - label: 'Primary admin actions' - label_display: '0' - provider: core -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_messages.yml b/example/drupal/config/sync/block.block.seven_messages.yml deleted file mode 100644 index 6a5abcc..0000000 --- a/example/drupal/config/sync/block.block.seven_messages.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: f2ce36f7-5213-432e-b734-183dc0b555f1 -langcode: en -status: true -dependencies: - module: - - system - theme: - - seven -_core: - default_config_hash: bZvFlENVti4PpqT6kqoC86B5_lXp6aE-d4nrvLMU4CY -id: seven_messages -theme: seven -region: highlighted -weight: 0 -provider: null -plugin: system_messages_block -settings: - id: system_messages_block - label: 'Status messages' - label_display: '0' - provider: system -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_page_title.yml b/example/drupal/config/sync/block.block.seven_page_title.yml deleted file mode 100644 index 1db3457..0000000 --- a/example/drupal/config/sync/block.block.seven_page_title.yml +++ /dev/null @@ -1,20 +0,0 @@ -uuid: 9043f691-8135-4da9-a2cd-cc2717e6f1f7 -langcode: en -status: true -dependencies: - theme: - - seven -_core: - default_config_hash: P-2PK2v3yJQRI4Mg1RqKQ0Ki0wP_UUErIvahd_MtMVc -id: seven_page_title -theme: seven -region: header -weight: -30 -provider: null -plugin: page_title_block -settings: - id: page_title_block - label: 'Page title' - label_display: '0' - provider: core -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_primary_local_tasks.yml b/example/drupal/config/sync/block.block.seven_primary_local_tasks.yml deleted file mode 100644 index 822584f..0000000 --- a/example/drupal/config/sync/block.block.seven_primary_local_tasks.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 849876f2-578e-4cc5-a265-3e865e36076b -langcode: en -status: true -dependencies: - theme: - - seven -_core: - default_config_hash: ouwsNHI1V3bwV1G1vl0EcbWy8AB9V62vgLOSjTpoCSI -id: seven_primary_local_tasks -theme: seven -region: header -weight: 0 -provider: null -plugin: local_tasks_block -settings: - id: local_tasks_block - label: 'Primary tabs' - label_display: '0' - provider: core - primary: true - secondary: false -visibility: { } diff --git a/example/drupal/config/sync/block.block.seven_secondary_local_tasks.yml b/example/drupal/config/sync/block.block.seven_secondary_local_tasks.yml deleted file mode 100644 index e8d07e1..0000000 --- a/example/drupal/config/sync/block.block.seven_secondary_local_tasks.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: a2936a6c-03e3-4fd0-bb26-92e5be5c0fbb -langcode: en -status: true -dependencies: - theme: - - seven -_core: - default_config_hash: W8ripr8GQI0o3eaaD7lcd4687nUrcHoYdu4yIjzVN3o -id: seven_secondary_local_tasks -theme: seven -region: pre_content -weight: 0 -provider: null -plugin: local_tasks_block -settings: - id: local_tasks_block - label: 'Secondary tabs' - label_display: '0' - provider: core - primary: false - secondary: true -visibility: { } diff --git a/example/drupal/config/sync/block_content.type.basic.yml b/example/drupal/config/sync/block_content.type.basic.yml deleted file mode 100644 index e28e89f..0000000 --- a/example/drupal/config/sync/block_content.type.basic.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 95121165-8544-4757-9d23-2951bcb00d7f -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: zglzjmYxi0G0ag9MZ02y0LSJOdpWRwJxyP_OvFojFyo -id: basic -label: 'Basic block' -revision: 0 -description: 'A basic block contains a title and a body.' diff --git a/example/drupal/config/sync/comment.settings.yml b/example/drupal/config/sync/comment.settings.yml deleted file mode 100644 index 5b7ad98..0000000 --- a/example/drupal/config/sync/comment.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -_core: - default_config_hash: YNUW2Ij5uE7a4oaXp3i_2lvaFdYM1zNKPPfnEjB0jEc -log_ip_addresses: false diff --git a/example/drupal/config/sync/comment.type.comment.yml b/example/drupal/config/sync/comment.type.comment.yml deleted file mode 100644 index 00b5ad6..0000000 --- a/example/drupal/config/sync/comment.type.comment.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: d8a9fe66-9941-4c6c-a21e-7ba0b75cbe56 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: bqZsN31T2n0UjcbyCpOPi9D2iO0sAOHR7FnEs9qMvaA -id: comment -label: 'Default comments' -target_entity_type_id: node -description: 'Allows commenting on content' diff --git a/example/drupal/config/sync/contact.form.feedback.yml b/example/drupal/config/sync/contact.form.feedback.yml deleted file mode 100644 index 34419f1..0000000 --- a/example/drupal/config/sync/contact.form.feedback.yml +++ /dev/null @@ -1,14 +0,0 @@ -uuid: 8b451fec-3ea4-435c-8eaa-8404304db820 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: vymHlgJy26BuI5GGj9-IXjwR3dRC5C0tij4BpWJnoqw -id: feedback -label: 'Website feedback' -recipients: - - admin@example.com -reply: '' -weight: 0 -message: 'Your message has been sent.' -redirect: '' diff --git a/example/drupal/config/sync/contact.form.personal.yml b/example/drupal/config/sync/contact.form.personal.yml deleted file mode 100644 index 164a354..0000000 --- a/example/drupal/config/sync/contact.form.personal.yml +++ /dev/null @@ -1,13 +0,0 @@ -uuid: 3133c24d-f4a1-4c12-ad47-a4da9be5dec2 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: jonvgt3CkUM2eMLTFwWfHileWWDC4YtXCuIlCahTk_I -id: personal -label: 'Personal contact form' -recipients: { } -reply: '' -weight: 0 -message: 'Your message has been sent.' -redirect: '' diff --git a/example/drupal/config/sync/contact.settings.yml b/example/drupal/config/sync/contact.settings.yml deleted file mode 100644 index 1c949a6..0000000 --- a/example/drupal/config/sync/contact.settings.yml +++ /dev/null @@ -1,7 +0,0 @@ -_core: - default_config_hash: U69DBeuvXuNVOC15rVNaBjDPK2fWFbo9v4takdYSSO8 -default_form: feedback -flood: - limit: 5 - interval: 3600 -user_default_enabled: true diff --git a/example/drupal/config/sync/core.base_field_override.node.page.promote.yml b/example/drupal/config/sync/core.base_field_override.node.page.promote.yml deleted file mode 100644 index 283d198..0000000 --- a/example/drupal/config/sync/core.base_field_override.node.page.promote.yml +++ /dev/null @@ -1,24 +0,0 @@ -uuid: 1a527ff0-ca93-4642-b379-9c6c1bb45d94 -langcode: en -status: true -dependencies: - config: - - node.type.page -_core: - default_config_hash: fPUEnm4T5zfZRr3ttDUqq7yCDd2uW3clWD-pvos4tlQ -id: node.page.promote -field_name: promote -entity_type: node -bundle: page -label: 'Promoted to front page' -description: '' -required: false -translatable: false -default_value: - - - value: 0 -default_value_callback: '' -settings: - on_label: 'On' - off_label: 'Off' -field_type: boolean diff --git a/example/drupal/config/sync/core.date_format.fallback.yml b/example/drupal/config/sync/core.date_format.fallback.yml deleted file mode 100644 index 76c3651..0000000 --- a/example/drupal/config/sync/core.date_format.fallback.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 6290c41f-a2dc-42de-a180-a8ac3edb2abd -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: 7klS5IWXrwzVaPpYZFAs6wcx8U2FF1X73OfrtTsvuvE -id: fallback -label: 'Fallback date format' -locked: true -pattern: 'D, m/d/Y - H:i' diff --git a/example/drupal/config/sync/core.date_format.html_date.yml b/example/drupal/config/sync/core.date_format.html_date.yml deleted file mode 100644 index 4cb2e68..0000000 --- a/example/drupal/config/sync/core.date_format.html_date.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: d084affd-f1a5-4d0c-906a-da3f068a5ab0 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: EOQltUQPmgc6UQ2rcJ4Xi_leCEJj5ui0TR-12duS-Tk -id: html_date -label: 'HTML Date' -locked: true -pattern: Y-m-d diff --git a/example/drupal/config/sync/core.date_format.html_datetime.yml b/example/drupal/config/sync/core.date_format.html_datetime.yml deleted file mode 100644 index fbe929e..0000000 --- a/example/drupal/config/sync/core.date_format.html_datetime.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: ff5026ea-e2ab-4de7-9e95-9793e8289eca -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: jxfClwZIRXIdcvMrE--WkcZxDGUVoOIE3Sm2NRZlFuE -id: html_datetime -label: 'HTML Datetime' -locked: true -pattern: 'Y-m-d\TH:i:sO' diff --git a/example/drupal/config/sync/core.date_format.html_month.yml b/example/drupal/config/sync/core.date_format.html_month.yml deleted file mode 100644 index e1f7328..0000000 --- a/example/drupal/config/sync/core.date_format.html_month.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: d06a66c3-815a-4af6-9aeb-c55f97dc1959 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: Z7KuCUwM_WdTNvLcoltuX3_8d-s-8FZkTN6KgNwF0eM -id: html_month -label: 'HTML Month' -locked: true -pattern: Y-m diff --git a/example/drupal/config/sync/core.date_format.html_time.yml b/example/drupal/config/sync/core.date_format.html_time.yml deleted file mode 100644 index 2a59c87..0000000 --- a/example/drupal/config/sync/core.date_format.html_time.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: f097061d-bafb-4fa9-9509-d7909d005990 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: M7yqicYkU36hRy5p9drAaGBBihhUD1OyujFrAaQ93ZE -id: html_time -label: 'HTML Time' -locked: true -pattern: 'H:i:s' diff --git a/example/drupal/config/sync/core.date_format.html_week.yml b/example/drupal/config/sync/core.date_format.html_week.yml deleted file mode 100644 index d3b6210..0000000 --- a/example/drupal/config/sync/core.date_format.html_week.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 438ddcdd-cc83-4ff8-b45e-4c2f3bcabfd6 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: wKD4WsoV_wFgv2vgI4mcAAFSIzrye17ykzdwrnApkfY -id: html_week -label: 'HTML Week' -locked: true -pattern: Y-\WW diff --git a/example/drupal/config/sync/core.date_format.html_year.yml b/example/drupal/config/sync/core.date_format.html_year.yml deleted file mode 100644 index 89bb344..0000000 --- a/example/drupal/config/sync/core.date_format.html_year.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: d70af53d-a136-4bca-9ee6-54dbf4920dfa -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: OjekiQuX9RbVQ2_8jOHBL94RgYLePqX7wpfNGgcQzrk -id: html_year -label: 'HTML Year' -locked: true -pattern: 'Y' diff --git a/example/drupal/config/sync/core.date_format.html_yearless_date.yml b/example/drupal/config/sync/core.date_format.html_yearless_date.yml deleted file mode 100644 index 7329e5e..0000000 --- a/example/drupal/config/sync/core.date_format.html_yearless_date.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 2f03181d-aaac-4751-a234-d882bedc93c1 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: 5VpawMrKPEPCkoO4YpPa0TDFO2dgiIHfTziJtwlmUxc -id: html_yearless_date -label: 'HTML Yearless date' -locked: true -pattern: m-d diff --git a/example/drupal/config/sync/core.date_format.long.yml b/example/drupal/config/sync/core.date_format.long.yml deleted file mode 100644 index e98fe05..0000000 --- a/example/drupal/config/sync/core.date_format.long.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 4b85ca9a-5c7c-49b2-9352-51cdbab37096 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: og8sWXhBuHbLMw3CoiBEZjgqSyhFBFmcbUW_wLcfNbo -id: long -label: 'Default long date' -locked: false -pattern: 'l, F j, Y - H:i' diff --git a/example/drupal/config/sync/core.date_format.medium.yml b/example/drupal/config/sync/core.date_format.medium.yml deleted file mode 100644 index 634245f..0000000 --- a/example/drupal/config/sync/core.date_format.medium.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: 0cdd0639-0cac-41f6-983d-7ef79df80be6 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: nzL5d024NjXIX_8TlT6uFAu973lmfkmHklJC-2i9rAE -id: medium -label: 'Default medium date' -locked: false -pattern: 'D, m/d/Y - H:i' diff --git a/example/drupal/config/sync/core.date_format.short.yml b/example/drupal/config/sync/core.date_format.short.yml deleted file mode 100644 index c5740d3..0000000 --- a/example/drupal/config/sync/core.date_format.short.yml +++ /dev/null @@ -1,10 +0,0 @@ -uuid: c5039084-f7d6-4559-b166-ebf2dba4cac2 -langcode: en -status: true -dependencies: { } -_core: - default_config_hash: AlzeyytA8InBgxIG9H2UDJYs3CG98Zj6yRsDKmlbZwA -id: short -label: 'Default short date' -locked: false -pattern: 'm/d/Y - H:i' diff --git a/example/drupal/config/sync/core.entity_form_display.block_content.basic.default.yml b/example/drupal/config/sync/core.entity_form_display.block_content.basic.default.yml deleted file mode 100644 index 0ab2174..0000000 --- a/example/drupal/config/sync/core.entity_form_display.block_content.basic.default.yml +++ /dev/null @@ -1,35 +0,0 @@ -uuid: 7703044b-d0c7-474d-89d3-073c93b37ddb -langcode: en -status: true -dependencies: - config: - - block_content.type.basic - - field.field.block_content.basic.body - module: - - text -_core: - default_config_hash: jAps3FCxvKecABS_tgExbhCZrBLQB3bNPWw18WjE3ss -id: block_content.basic.default -targetEntityType: block_content -bundle: basic -mode: default -content: - body: - type: text_textarea_with_summary - weight: -4 - region: content - settings: - rows: 9 - summary_rows: 3 - placeholder: '' - show_summary: false - third_party_settings: { } - info: - type: string_textfield - weight: -5 - region: content - settings: - size: 60 - placeholder: '' - third_party_settings: { } -hidden: { } diff --git a/example/drupal/config/sync/core.entity_form_display.comment.comment.default.yml b/example/drupal/config/sync/core.entity_form_display.comment.comment.default.yml deleted file mode 100644 index d672ad7..0000000 --- a/example/drupal/config/sync/core.entity_form_display.comment.comment.default.yml +++ /dev/null @@ -1,36 +0,0 @@ -uuid: f3fa7917-43a7-4a25-8c37-08332dca9506 -langcode: en -status: true -dependencies: - config: - - comment.type.comment - - field.field.comment.comment.comment_body - module: - - text -_core: - default_config_hash: I0Pa0aQvT_jawlPo9oz4FE3h_ickc55dYKTPl6gILes -id: comment.comment.default -targetEntityType: comment -bundle: comment -mode: default -content: - author: - weight: -2 - region: content - comment_body: - type: text_textarea - weight: 11 - region: content - settings: - rows: 5 - placeholder: '' - third_party_settings: { } - subject: - type: string_textfield - weight: 10 - region: content - settings: - size: 60 - placeholder: '' - third_party_settings: { } -hidden: { } diff --git a/example/drupal/config/sync/core.entity_form_display.node.article.default.yml b/example/drupal/config/sync/core.entity_form_display.node.article.default.yml deleted file mode 100644 index 5b345c5..0000000 --- a/example/drupal/config/sync/core.entity_form_display.node.article.default.yml +++ /dev/null @@ -1,109 +0,0 @@ -uuid: 3718fdf5-0910-4315-ba74-bc4b2542ed31 -langcode: en -status: true -dependencies: - config: - - field.field.node.article.body - - field.field.node.article.comment - - field.field.node.article.field_image - - field.field.node.article.field_tags - - image.style.thumbnail - - node.type.article - module: - - comment - - image - - path - - text -_core: - default_config_hash: Pzq9mzrsfoPf775qgEU_SUbHSro9pv1ga-Euh1Ykd_k -id: node.article.default -targetEntityType: node -bundle: article -mode: default -content: - body: - type: text_textarea_with_summary - weight: 1 - region: content - settings: - rows: 9 - summary_rows: 3 - placeholder: '' - show_summary: false - third_party_settings: { } - comment: - type: comment_default - weight: 20 - region: content - settings: { } - third_party_settings: { } - created: - type: datetime_timestamp - weight: 10 - region: content - settings: { } - third_party_settings: { } - field_image: - type: image_image - weight: 4 - region: content - settings: - progress_indicator: throbber - preview_image_style: thumbnail - third_party_settings: { } - field_tags: - type: entity_reference_autocomplete_tags - weight: 3 - region: content - settings: - match_operator: CONTAINS - match_limit: 10 - size: 60 - placeholder: '' - third_party_settings: { } - path: - type: path - weight: 30 - region: content - settings: { } - third_party_settings: { } - promote: - type: boolean_checkbox - weight: 15 - region: content - settings: - display_label: true - third_party_settings: { } - status: - type: boolean_checkbox - weight: 120 - region: content - settings: - display_label: true - third_party_settings: { } - sticky: - type: boolean_checkbox - weight: 16 - region: content - settings: - display_label: true - third_party_settings: { } - title: - type: string_textfield - weight: 0 - region: content - settings: - size: 60 - placeholder: '' - third_party_settings: { } - uid: - type: entity_reference_autocomplete - weight: 5 - region: content - settings: - match_operator: CONTAINS - match_limit: 10 - size: 60 - placeholder: '' - third_party_settings: { } -hidden: { } diff --git a/example/drupal/config/sync/core.entity_form_display.node.page.default.yml b/example/drupal/config/sync/core.entity_form_display.node.page.default.yml deleted file mode 100644 index 01b6c67..0000000 --- a/example/drupal/config/sync/core.entity_form_display.node.page.default.yml +++ /dev/null @@ -1,79 +0,0 @@ -uuid: 95eba23e-59e7-475d-a41f-550b378ed387 -langcode: en -status: true -dependencies: - config: - - field.field.node.page.body - - node.type.page - module: - - path - - text -_core: - default_config_hash: SfpLhPExzvR0MgFp0Wp7CrmgEnhcqQ-fXIWFhbf4ue0 -id: node.page.default -targetEntityType: node -bundle: page -mode: default -content: - body: - type: text_textarea_with_summary - weight: 31 - region: content - settings: - rows: 9 - summary_rows: 3 - placeholder: '' - show_summary: false - third_party_settings: { } - created: - type: datetime_timestamp - weight: 10 - region: content - settings: { } - third_party_settings: { } - path: - type: path - weight: 30 - region: content - settings: { } - third_party_settings: { } - promote: - type: boolean_checkbox - weight: 15 - region: content - settings: - display_label: true - third_party_settings: { } - status: - type: boolean_checkbox - weight: 120 - region: content - settings: - display_label: true - third_party_settings: { } - sticky: - type: boolean_checkbox - weight: 16 - region: content - settings: - display_label: true - third_party_settings: { } - title: - type: string_textfield - weight: -5 - region: content - settings: - size: 60 - placeholder: '' - third_party_settings: { } - uid: - type: entity_reference_autocomplete - weight: 5 - region: content - settings: - match_operator: CONTAINS - match_limit: 10 - size: 60 - placeholder: '' - third_party_settings: { } -hidden: { } diff --git a/example/drupal/config/sync/core.entity_form_display.user.user.default.yml b/example/drupal/config/sync/core.entity_form_display.user.user.default.yml deleted file mode 100644 index ba62058..0000000 --- a/example/drupal/config/sync/core.entity_form_display.user.user.default.yml +++ /dev/null @@ -1,38 +0,0 @@ -uuid: 8d027283-c383-4cbc-bf25-d8dcf2231966 -langcode: en -status: true -dependencies: - config: - - field.field.user.user.user_picture - - image.style.thumbnail - module: - - image - - user -_core: - default_config_hash: FaQ9Ptcpxpg30AtiqRDtl_8zbJArHP1LPfug_s59TOA -id: user.user.default -targetEntityType: user -bundle: user -mode: default -content: - account: - weight: -10 - region: content - contact: - weight: 5 - region: content - language: - weight: 0 - region: content - timezone: - weight: 6 - region: content - user_picture: - type: image_image - weight: -1 - region: content - settings: - progress_indicator: throbber - preview_image_style: thumbnail - third_party_settings: { } -hidden: { } diff --git a/example/drupal/config/sync/core.entity_form_mode.user.register.yml b/example/drupal/config/sync/core.entity_form_mode.user.register.yml deleted file mode 100644 index 8ee6d8d..0000000 --- a/example/drupal/config/sync/core.entity_form_mode.user.register.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 43e75166-00dc-4a42-a42a-d595dbab8a07 -langcode: en -status: true -dependencies: - module: - - user -_core: - default_config_hash: flXhTcp55yLcyy7ZLOhPGKGZobZQJdkAFVWV3LseiuI -id: user.register -label: Register -targetEntityType: user -cache: true diff --git a/example/drupal/config/sync/core.entity_view_display.block_content.basic.default.yml b/example/drupal/config/sync/core.entity_view_display.block_content.basic.default.yml deleted file mode 100644 index 396b5f2..0000000 --- a/example/drupal/config/sync/core.entity_view_display.block_content.basic.default.yml +++ /dev/null @@ -1,24 +0,0 @@ -uuid: 6b705a61-f979-452f-80fa-596a9d419fb4 -langcode: en -status: true -dependencies: - config: - - block_content.type.basic - - field.field.block_content.basic.body - module: - - text -_core: - default_config_hash: hBNNDTFwakREOTa6GGMqN899Iyrii0hInwSJtQ7Kj30 -id: block_content.basic.default -targetEntityType: block_content -bundle: basic -mode: default -content: - body: - type: text_default - label: hidden - settings: { } - third_party_settings: { } - weight: 0 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_display.comment.comment.default.yml b/example/drupal/config/sync/core.entity_view_display.comment.comment.default.yml deleted file mode 100644 index 30e72b1..0000000 --- a/example/drupal/config/sync/core.entity_view_display.comment.comment.default.yml +++ /dev/null @@ -1,27 +0,0 @@ -uuid: dff97a21-6eff-4ffc-aa34-320dab6e668a -langcode: en -status: true -dependencies: - config: - - comment.type.comment - - field.field.comment.comment.comment_body - module: - - text -_core: - default_config_hash: aBQUGsQ46M4048fIlFuTXwl2zV0j2cJX89CTUobh9hA -id: comment.comment.default -targetEntityType: comment -bundle: comment -mode: default -content: - comment_body: - type: text_default - label: hidden - settings: { } - third_party_settings: { } - weight: 0 - region: content - links: - weight: 100 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_display.node.article.default.yml b/example/drupal/config/sync/core.entity_view_display.node.article.default.yml deleted file mode 100644 index d58f290..0000000 --- a/example/drupal/config/sync/core.entity_view_display.node.article.default.yml +++ /dev/null @@ -1,63 +0,0 @@ -uuid: e78cc92e-a0be-4a20-a01f-6bb116c436d1 -langcode: en -status: true -dependencies: - config: - - core.entity_view_display.comment.comment.default - - field.field.node.article.body - - field.field.node.article.comment - - field.field.node.article.field_image - - field.field.node.article.field_tags - - image.style.large - - node.type.article - module: - - comment - - image - - text - - user -_core: - default_config_hash: b4RQ-Nfz-gOoG_jjftd_qDIz4lf_-OzlxoLHkRXjfrE -id: node.article.default -targetEntityType: node -bundle: article -mode: default -content: - body: - type: text_default - label: hidden - settings: { } - third_party_settings: { } - weight: 0 - region: content - comment: - type: comment_default - label: above - settings: - view_mode: default - pager_id: 0 - third_party_settings: { } - weight: 110 - region: content - field_image: - type: image - label: hidden - settings: - image_link: '' - image_style: large - third_party_settings: { } - weight: -1 - region: content - field_tags: - type: entity_reference_label - label: above - settings: - link: true - third_party_settings: { } - weight: 10 - region: content - links: - settings: { } - third_party_settings: { } - weight: 100 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_display.node.article.rss.yml b/example/drupal/config/sync/core.entity_view_display.node.article.rss.yml deleted file mode 100644 index 4fab7cf..0000000 --- a/example/drupal/config/sync/core.entity_view_display.node.article.rss.yml +++ /dev/null @@ -1,28 +0,0 @@ -uuid: efb8eb05-5ae0-4f78-b708-9d2302730b18 -langcode: en -status: true -dependencies: - config: - - core.entity_view_mode.node.rss - - field.field.node.article.body - - field.field.node.article.comment - - field.field.node.article.field_image - - field.field.node.article.field_tags - - node.type.article - module: - - user -_core: - default_config_hash: 2rIr6K5Q0UQ9khg0zE_CK-PtJH76UL-BDDZcZnZzwCc -id: node.article.rss -targetEntityType: node -bundle: article -mode: rss -content: - links: - weight: 100 - region: content -hidden: - body: true - comment: true - field_image: true - field_tags: true diff --git a/example/drupal/config/sync/core.entity_view_display.node.article.teaser.yml b/example/drupal/config/sync/core.entity_view_display.node.article.teaser.yml deleted file mode 100644 index b5219a4..0000000 --- a/example/drupal/config/sync/core.entity_view_display.node.article.teaser.yml +++ /dev/null @@ -1,55 +0,0 @@ -uuid: 21a64c5e-182a-43bc-9246-0c77f3c1f744 -langcode: en -status: true -dependencies: - config: - - core.entity_view_mode.node.teaser - - field.field.node.article.body - - field.field.node.article.comment - - field.field.node.article.field_image - - field.field.node.article.field_tags - - image.style.medium - - node.type.article - module: - - image - - text - - user -_core: - default_config_hash: 0ur5-yZyR8pJ3lugC_Micbr6V_tpMWz-yHFhtuLcZo8 -id: node.article.teaser -targetEntityType: node -bundle: article -mode: teaser -content: - body: - type: text_summary_or_trimmed - label: hidden - settings: - trim_length: 600 - third_party_settings: { } - weight: 0 - region: content - field_image: - type: image - label: hidden - settings: - image_link: content - image_style: medium - third_party_settings: { } - weight: -1 - region: content - field_tags: - type: entity_reference_label - label: above - settings: - link: true - third_party_settings: { } - weight: 10 - region: content - links: - weight: 100 - region: content -hidden: - comment: true - field_image: true - field_tags: true diff --git a/example/drupal/config/sync/core.entity_view_display.node.page.default.yml b/example/drupal/config/sync/core.entity_view_display.node.page.default.yml deleted file mode 100644 index 71cb2c6..0000000 --- a/example/drupal/config/sync/core.entity_view_display.node.page.default.yml +++ /dev/null @@ -1,28 +0,0 @@ -uuid: 77af42fe-99aa-46a2-8e0a-60212be31065 -langcode: en -status: true -dependencies: - config: - - field.field.node.page.body - - node.type.page - module: - - text - - user -_core: - default_config_hash: M_Y8L5tfmhx7DR143E05YyZSpvgil6VFvqcfBWykalg -id: node.page.default -targetEntityType: node -bundle: page -mode: default -content: - body: - type: text_default - label: hidden - settings: { } - third_party_settings: { } - weight: 100 - region: content - links: - weight: 101 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_display.node.page.teaser.yml b/example/drupal/config/sync/core.entity_view_display.node.page.teaser.yml deleted file mode 100644 index 8ee4cc6..0000000 --- a/example/drupal/config/sync/core.entity_view_display.node.page.teaser.yml +++ /dev/null @@ -1,30 +0,0 @@ -uuid: b59a0aa5-1615-4e8a-b15e-4870dcbb8e2e -langcode: en -status: true -dependencies: - config: - - core.entity_view_mode.node.teaser - - field.field.node.page.body - - node.type.page - module: - - text - - user -_core: - default_config_hash: 8BgdRtLbtQ0F__o0FHSH0Mx5fvXOra9tfT1GmNKbRYw -id: node.page.teaser -targetEntityType: node -bundle: page -mode: teaser -content: - body: - type: text_summary_or_trimmed - label: hidden - settings: - trim_length: 600 - third_party_settings: { } - weight: 100 - region: content - links: - weight: 101 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_display.user.user.compact.yml b/example/drupal/config/sync/core.entity_view_display.user.user.compact.yml deleted file mode 100644 index fd87199..0000000 --- a/example/drupal/config/sync/core.entity_view_display.user.user.compact.yml +++ /dev/null @@ -1,29 +0,0 @@ -uuid: d8d43e49-3900-4c28-bc77-483e2f47e7ab -langcode: en -status: true -dependencies: - config: - - core.entity_view_mode.user.compact - - field.field.user.user.user_picture - - image.style.thumbnail - module: - - image - - user -_core: - default_config_hash: kD2TjItfNDKFw2CFJQfuv4Ys6qg3uYa4hCJYN2jreec -id: user.user.compact -targetEntityType: user -bundle: user -mode: compact -content: - user_picture: - type: image - label: hidden - settings: - image_link: content - image_style: thumbnail - third_party_settings: { } - weight: 0 - region: content -hidden: - member_for: true diff --git a/example/drupal/config/sync/core.entity_view_display.user.user.default.yml b/example/drupal/config/sync/core.entity_view_display.user.user.default.yml deleted file mode 100644 index aa5fe56..0000000 --- a/example/drupal/config/sync/core.entity_view_display.user.user.default.yml +++ /dev/null @@ -1,30 +0,0 @@ -uuid: fde03a05-c267-47df-8f4f-a80c7e05b3ca -langcode: en -status: true -dependencies: - config: - - field.field.user.user.user_picture - - image.style.thumbnail - module: - - image - - user -_core: - default_config_hash: E1qmUI2SlozLlJ308jfMMFKT67l1p6PywE2LTRbeYS4 -id: user.user.default -targetEntityType: user -bundle: user -mode: default -content: - member_for: - weight: 5 - region: content - user_picture: - type: image - label: hidden - settings: - image_link: content - image_style: thumbnail - third_party_settings: { } - weight: 0 - region: content -hidden: { } diff --git a/example/drupal/config/sync/core.entity_view_mode.block_content.full.yml b/example/drupal/config/sync/core.entity_view_mode.block_content.full.yml deleted file mode 100644 index 74194fb..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.block_content.full.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: b24bdce4-85b3-4768-ac74-904b297b3a54 -langcode: en -status: false -dependencies: - module: - - block_content -_core: - default_config_hash: 4tedlMuvQjDOdvHdw86_e-2Rt78aR7TGFMfOK8Ejppg -id: block_content.full -label: Full -targetEntityType: block_content -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.comment.full.yml b/example/drupal/config/sync/core.entity_view_mode.comment.full.yml deleted file mode 100644 index 1d903fd..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.comment.full.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 345782ef-fbcf-4a9e-937c-3d743c6aa731 -langcode: en -status: false -dependencies: - module: - - comment -_core: - default_config_hash: K7eNlfU7NEUajz01wItywZklr2oaPgL6s1_97fmDXLA -id: comment.full -label: 'Full comment' -targetEntityType: comment -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.node.full.yml b/example/drupal/config/sync/core.entity_view_mode.node.full.yml deleted file mode 100644 index 0ea9d88..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.node.full.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: b199ebc7-b223-42e5-9bb2-b9f6e63a4566 -langcode: en -status: false -dependencies: - module: - - node -_core: - default_config_hash: ElrtInxGjZd7GaapJ5O9n-ugi2hG2IxFivtgn0tHOsk -id: node.full -label: 'Full content' -targetEntityType: node -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.node.rss.yml b/example/drupal/config/sync/core.entity_view_mode.node.rss.yml deleted file mode 100644 index c4693bf..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.node.rss.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: d75e80e8-0884-4e7a-a1ec-216f1c6339c9 -langcode: en -status: false -dependencies: - module: - - node -_core: - default_config_hash: vlYzr-rp2f9NMp-Qlr4sFjlqRq-90mco5-afLNGwCrU -id: node.rss -label: RSS -targetEntityType: node -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.node.search_index.yml b/example/drupal/config/sync/core.entity_view_mode.node.search_index.yml deleted file mode 100644 index 6d00277..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.node.search_index.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 5c668dae-1920-4749-95f4-d547b0e59939 -langcode: en -status: false -dependencies: - module: - - node -_core: - default_config_hash: fVFfJv_GzBRE-wpRHbfD5a3VjnhbEOXG6lvRd3uaccY -id: node.search_index -label: 'Search index' -targetEntityType: node -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.node.search_result.yml b/example/drupal/config/sync/core.entity_view_mode.node.search_result.yml deleted file mode 100644 index 4889817..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.node.search_result.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: ca345a4a-9487-4755-8da6-125f3f38155d -langcode: en -status: false -dependencies: - module: - - node -_core: - default_config_hash: 6GCOQ-jP2RbdbHA5YWQ6bT8CfGbqrBYKOSC_XY4E3ZM -id: node.search_result -label: 'Search result highlighting input' -targetEntityType: node -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.node.teaser.yml b/example/drupal/config/sync/core.entity_view_mode.node.teaser.yml deleted file mode 100644 index 0fa1b64..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.node.teaser.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 9084eaee-4abc-42b9-9b85-2fdca4ef3f86 -langcode: en -status: true -dependencies: - module: - - node -_core: - default_config_hash: Mz9qWr1kUYK0mjRAGDsr5XS6PvtZ24en_7ndt-pyWe4 -id: node.teaser -label: Teaser -targetEntityType: node -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.taxonomy_term.full.yml b/example/drupal/config/sync/core.entity_view_mode.taxonomy_term.full.yml deleted file mode 100644 index 5daf9f7..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.taxonomy_term.full.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 3f7f88de-403a-4c03-a5d7-2461d9e8dbe9 -langcode: en -status: true -dependencies: - module: - - taxonomy -_core: - default_config_hash: '-PPKjsNQPvoIDjOuUAvlLocYD976MNjb9Zpgyz5_BWE' -id: taxonomy_term.full -label: 'Taxonomy term page' -targetEntityType: taxonomy_term -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.user.compact.yml b/example/drupal/config/sync/core.entity_view_mode.user.compact.yml deleted file mode 100644 index 76c9c47..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.user.compact.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: 2d054058-16cd-4423-bb1c-8045d5e9306e -langcode: en -status: true -dependencies: - module: - - user -_core: - default_config_hash: 71CSAr_LNPcgu6D6jI4INl1KATkahmeyUFBETAWya8g -id: user.compact -label: Compact -targetEntityType: user -cache: true diff --git a/example/drupal/config/sync/core.entity_view_mode.user.full.yml b/example/drupal/config/sync/core.entity_view_mode.user.full.yml deleted file mode 100644 index 1b8adfa..0000000 --- a/example/drupal/config/sync/core.entity_view_mode.user.full.yml +++ /dev/null @@ -1,12 +0,0 @@ -uuid: a5e351e3-b690-4a56-bd11-eab3d3bedba9 -langcode: en -status: false -dependencies: - module: - - user -_core: - default_config_hash: mQIF_foYjmnVSr9MpcD4CTaJE_FpO1AyDd_DskztGhM -id: user.full -label: 'User account' -targetEntityType: user -cache: true diff --git a/example/drupal/config/sync/core.extension.yml b/example/drupal/config/sync/core.extension.yml deleted file mode 100644 index d5b3e61..0000000 --- a/example/drupal/config/sync/core.extension.yml +++ /dev/null @@ -1,61 +0,0 @@ -_core: - default_config_hash: R4IF-ClDHXxblLcG0L7MgsLvfBIMAvi_skumNFQwkDc -module: - automated_cron: 0 - big_pipe: 0 - block: 0 - block_content: 0 - breakpoint: 0 - ckeditor: 0 - color: 0 - comment: 0 - config: 0 - contact: 0 - contextual: 0 - datetime: 0 - dblog: 0 - decoupled_router: 0 - druxt: 0 - dynamic_page_cache: 0 - editor: 0 - field: 0 - field_ui: 0 - file: 0 - filter: 0 - help: 0 - history: 0 - image: 0 - jsonapi: 0 - jsonapi_menu_items: 0 - jsonapi_resources: 0 - jsonapi_views: 0 - link: 0 - menu_link_content: 0 - menu_ui: 0 - node: 0 - options: 0 - page_cache: 0 - path: 0 - path_alias: 0 - rdf: 0 - search: 0 - serialization: 0 - shortcut: 0 - system: 0 - taxonomy: 0 - text: 0 - tome: 0 - tome_base: 0 - tome_static: 0 - tome_sync: 0 - toolbar: 0 - tour: 0 - update: 0 - user: 0 - views_ui: 0 - views: 10 - standard: 1000 -theme: - bartik: 0 - seven: 0 -profile: standard diff --git a/example/drupal/config/sync/core.menu.static_menu_link_overrides.yml b/example/drupal/config/sync/core.menu.static_menu_link_overrides.yml deleted file mode 100644 index eb6e35e..0000000 --- a/example/drupal/config/sync/core.menu.static_menu_link_overrides.yml +++ /dev/null @@ -1,9 +0,0 @@ -_core: - default_config_hash: CXhei_vpaZk-3f_Mj2cH0YmpK-ZpKHoSzVA3yZrDq0g -definitions: - contact__site_page: - menu_name: footer - parent: '' - weight: 0 - expanded: false - enabled: true diff --git a/example/drupal/config/sync/dblog.settings.yml b/example/drupal/config/sync/dblog.settings.yml deleted file mode 100644 index fbd17ea..0000000 --- a/example/drupal/config/sync/dblog.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -_core: - default_config_hash: e883aGsrt1wFrsydlYU584PZONCSfRy0DtkZ9KzHb58 -row_limit: 1000 diff --git a/example/drupal/config/sync/editor.editor.basic_html.yml b/example/drupal/config/sync/editor.editor.basic_html.yml deleted file mode 100644 index 42d3d7a..0000000 --- a/example/drupal/config/sync/editor.editor.basic_html.yml +++ /dev/null @@ -1,55 +0,0 @@ -uuid: df7c3742-0345-4ddf-9c09-7eed33a09870 -langcode: en -status: true -dependencies: - config: - - filter.format.basic_html - module: - - ckeditor -_core: - default_config_hash: AqlPmO16LvJI4D0Ih6u4GFQIzqr5OnLgAUSjcUGWk2g -format: basic_html -editor: ckeditor -settings: - toolbar: - rows: - - - - - name: Formatting - items: - - Bold - - Italic - - - name: Linking - items: - - DrupalLink - - DrupalUnlink - - - name: Lists - items: - - BulletedList - - NumberedList - - - name: Media - items: - - Blockquote - - DrupalImage - - - name: 'Block Formatting' - items: - - Format - - - name: Tools - items: - - Source - plugins: - stylescombo: - styles: '' -image_upload: - status: true - scheme: public - directory: inline-images - max_size: '' - max_dimensions: - width: 0 - height: 0 diff --git a/example/drupal/config/sync/editor.editor.full_html.yml b/example/drupal/config/sync/editor.editor.full_html.yml deleted file mode 100644 index 745f049..0000000 --- a/example/drupal/config/sync/editor.editor.full_html.yml +++ /dev/null @@ -1,63 +0,0 @@ -uuid: 2d6336e0-63f0-4158-b269-ce239fc55ec3 -langcode: en -status: true -dependencies: - config: - - filter.format.full_html - module: - - ckeditor -_core: - default_config_hash: 967ijj7p6i7rwrYl7r08WQFeCY_c23YAh0h8u-w_CXM -format: full_html -editor: ckeditor -settings: - toolbar: - rows: - - - - - name: Formatting - items: - - Bold - - Italic - - Strike - - Superscript - - Subscript - - '-' - - RemoveFormat - - - name: Linking - items: - - DrupalLink - - DrupalUnlink - - - name: Lists - items: - - BulletedList - - NumberedList - - - name: Media - items: - - Blockquote - - DrupalImage - - Table - - HorizontalRule - - - name: 'Block Formatting' - items: - - Format - - - name: Tools - items: - - ShowBlocks - - Source - plugins: - stylescombo: - styles: '' -image_upload: - status: true - scheme: public - directory: inline-images - max_size: '' - max_dimensions: - width: 0 - height: 0 diff --git a/example/drupal/config/sync/field.field.block_content.basic.body.yml b/example/drupal/config/sync/field.field.block_content.basic.body.yml deleted file mode 100644 index 7c4d348..0000000 --- a/example/drupal/config/sync/field.field.block_content.basic.body.yml +++ /dev/null @@ -1,25 +0,0 @@ -uuid: 944b448f-3347-4345-9cc5-201159346dda -langcode: en -status: true -dependencies: - config: - - block_content.type.basic - - field.storage.block_content.body - module: - - text -_core: - default_config_hash: foetbaMhlB6uLc2bn-PESvaPkbuDWj0MEjpQVwQiP-o -id: block_content.basic.body -field_name: body -entity_type: block_content -bundle: basic -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: false - required_summary: false -field_type: text_with_summary diff --git a/example/drupal/config/sync/field.field.comment.comment.comment_body.yml b/example/drupal/config/sync/field.field.comment.comment.comment_body.yml deleted file mode 100644 index d3af8d0..0000000 --- a/example/drupal/config/sync/field.field.comment.comment.comment_body.yml +++ /dev/null @@ -1,23 +0,0 @@ -uuid: 44ae0ccd-2bc4-4a8c-bb76-84ff5afc5ffd -langcode: en -status: true -dependencies: - config: - - comment.type.comment - - field.storage.comment.comment_body - module: - - text -_core: - default_config_hash: TmAKjNrJ7RR60YpqvJq_QqEewYe_S8Kd23n8VRCqiWs -id: comment.comment.comment_body -field_name: comment_body -entity_type: comment -bundle: comment -label: Comment -description: '' -required: true -translatable: true -default_value: { } -default_value_callback: '' -settings: { } -field_type: text_long diff --git a/example/drupal/config/sync/field.field.node.article.body.yml b/example/drupal/config/sync/field.field.node.article.body.yml deleted file mode 100644 index 70d855b..0000000 --- a/example/drupal/config/sync/field.field.node.article.body.yml +++ /dev/null @@ -1,25 +0,0 @@ -uuid: 695400b3-2149-478a-9732-21767495e101 -langcode: en -status: true -dependencies: - config: - - field.storage.node.body - - node.type.article - module: - - text -_core: - default_config_hash: IjZnOLWk1Pjq3WRg2pLSA1ERh7Po7izCq_p6UztZr2c -id: node.article.body -field_name: body -entity_type: node -bundle: article -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: true - required_summary: false -field_type: text_with_summary diff --git a/example/drupal/config/sync/field.field.node.article.comment.yml b/example/drupal/config/sync/field.field.node.article.comment.yml deleted file mode 100644 index 6934308..0000000 --- a/example/drupal/config/sync/field.field.node.article.comment.yml +++ /dev/null @@ -1,35 +0,0 @@ -uuid: 3ac1dd44-68ae-4130-aaeb-9c8e891fd488 -langcode: en -status: true -dependencies: - config: - - field.storage.node.comment - - node.type.article - module: - - comment -_core: - default_config_hash: UqXlkKC4v2-bDfWx4zcXQrD5YIi3d5byENEmWv-G_Uc -id: node.article.comment -field_name: comment -entity_type: node -bundle: article -label: Comments -description: '' -required: false -translatable: true -default_value: - - - status: 2 - cid: 0 - last_comment_timestamp: 0 - last_comment_name: null - last_comment_uid: 0 - comment_count: 0 -default_value_callback: '' -settings: - default_mode: 1 - per_page: 50 - anonymous: 0 - form_location: true - preview: 1 -field_type: comment diff --git a/example/drupal/config/sync/field.field.node.article.field_image.yml b/example/drupal/config/sync/field.field.node.article.field_image.yml deleted file mode 100644 index 70c3396..0000000 --- a/example/drupal/config/sync/field.field.node.article.field_image.yml +++ /dev/null @@ -1,40 +0,0 @@ -uuid: 3b5c4deb-0861-4665-8ee7-9d907d1c3212 -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_image - - node.type.article - module: - - image -_core: - default_config_hash: PmVL51L3A9QGbPLoeLHw5epAPszeZRasCeC3imeeDRQ -id: node.article.field_image -field_name: field_image -entity_type: node -bundle: article -label: Image -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - handler: 'default:file' - handler_settings: { } - file_directory: '[date:custom:Y]-[date:custom:m]' - file_extensions: 'png gif jpg jpeg' - max_filesize: '' - max_resolution: '' - min_resolution: '' - alt_field: true - alt_field_required: true - title_field: false - title_field_required: false - default_image: - uuid: null - alt: '' - title: '' - width: null - height: null -field_type: image diff --git a/example/drupal/config/sync/field.field.node.article.field_tags.yml b/example/drupal/config/sync/field.field.node.article.field_tags.yml deleted file mode 100644 index 6870d40..0000000 --- a/example/drupal/config/sync/field.field.node.article.field_tags.yml +++ /dev/null @@ -1,29 +0,0 @@ -uuid: fcba4f9d-ad19-4da4-977b-3ad742528441 -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_tags - - node.type.article - - taxonomy.vocabulary.tags -_core: - default_config_hash: QdUgf_beeoaPiyKorFv0q1fcJpWH_uZTqe_xoVJacrw -id: node.article.field_tags -field_name: field_tags -entity_type: node -bundle: article -label: Tags -description: 'Enter a comma-separated list. For example: Amsterdam, Mexico City, "Cleveland, Ohio"' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - handler: 'default:taxonomy_term' - handler_settings: - target_bundles: - tags: tags - sort: - field: _none - auto_create: true -field_type: entity_reference diff --git a/example/drupal/config/sync/field.field.node.page.body.yml b/example/drupal/config/sync/field.field.node.page.body.yml deleted file mode 100644 index b8a168e..0000000 --- a/example/drupal/config/sync/field.field.node.page.body.yml +++ /dev/null @@ -1,25 +0,0 @@ -uuid: e2d861ba-9c46-4616-b82e-aef6d1cdb0de -langcode: en -status: true -dependencies: - config: - - field.storage.node.body - - node.type.page - module: - - text -_core: - default_config_hash: KgVkxLl_K3E3lvN6CEoWQIDT0V8J4Mv-fVYrAIc7-FE -id: node.page.body -field_name: body -entity_type: node -bundle: page -label: Body -description: '' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - display_summary: true - required_summary: false -field_type: text_with_summary diff --git a/example/drupal/config/sync/field.field.user.user.user_picture.yml b/example/drupal/config/sync/field.field.user.user.user_picture.yml deleted file mode 100644 index 2e075ef..0000000 --- a/example/drupal/config/sync/field.field.user.user.user_picture.yml +++ /dev/null @@ -1,40 +0,0 @@ -uuid: 5ee954c4-f68c-4e69-8419-fc7320eaf2cc -langcode: en -status: true -dependencies: - config: - - field.storage.user.user_picture - module: - - image - - user -_core: - default_config_hash: twhm-ZOwSVkUjuLI_pQsaPo_pVLcqFfDmY-ojfqw0yk -id: user.user.user_picture -field_name: user_picture -entity_type: user -bundle: user -label: Picture -description: 'Your virtual face or picture.' -required: false -translatable: true -default_value: { } -default_value_callback: '' -settings: - handler: 'default:file' - handler_settings: { } - file_directory: 'pictures/[date:custom:Y]-[date:custom:m]' - file_extensions: 'png gif jpg jpeg' - max_filesize: '' - max_resolution: '' - min_resolution: '' - alt_field: false - alt_field_required: false - title_field: false - title_field_required: false - default_image: - uuid: null - alt: '' - title: '' - width: null - height: null -field_type: image diff --git a/example/drupal/config/sync/field.settings.yml b/example/drupal/config/sync/field.settings.yml deleted file mode 100644 index 2225b8f..0000000 --- a/example/drupal/config/sync/field.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -_core: - default_config_hash: nJk0TAQBzlNo52ehiHI7bIEPLGi0BYqZvPdEn7Chfu0 -purge_batch_size: 50 diff --git a/example/drupal/config/sync/field.storage.block_content.body.yml b/example/drupal/config/sync/field.storage.block_content.body.yml deleted file mode 100644 index b9885d7..0000000 --- a/example/drupal/config/sync/field.storage.block_content.body.yml +++ /dev/null @@ -1,21 +0,0 @@ -uuid: 97481da7-bacb-40f8-a67f-156f159d5766 -langcode: en -status: true -dependencies: - module: - - block_content - - text -_core: - default_config_hash: eS0snV_L3dx9shtWRTzm5eblwOJ7qKWC9IE-4GMTDFc -id: block_content.body -field_name: body -entity_type: block_content -type: text_with_summary -settings: { } -module: text -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: true -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.comment.comment_body.yml b/example/drupal/config/sync/field.storage.comment.comment_body.yml deleted file mode 100644 index db1ce47..0000000 --- a/example/drupal/config/sync/field.storage.comment.comment_body.yml +++ /dev/null @@ -1,21 +0,0 @@ -uuid: f641de2b-a1de-4c32-9549-ae31acfc8d1a -langcode: en -status: true -dependencies: - module: - - comment - - text -_core: - default_config_hash: swYoCch_hY8QO5uwr4FURplfnUCUlpPB4idF8WGVCpw -id: comment.comment_body -field_name: comment_body -entity_type: comment -type: text_long -settings: { } -module: text -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: true -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.node.body.yml b/example/drupal/config/sync/field.storage.node.body.yml deleted file mode 100644 index a552454..0000000 --- a/example/drupal/config/sync/field.storage.node.body.yml +++ /dev/null @@ -1,21 +0,0 @@ -uuid: 4ca065e4-3e3a-4921-8a5c-b8812cf46185 -langcode: en -status: true -dependencies: - module: - - node - - text -_core: - default_config_hash: EBUo7qOWqaiZaQ_RC9sLY5IoDKphS34v77VIHSACmVY -id: node.body -field_name: body -entity_type: node -type: text_with_summary -settings: { } -module: text -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: true -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.node.comment.yml b/example/drupal/config/sync/field.storage.node.comment.yml deleted file mode 100644 index cab44be..0000000 --- a/example/drupal/config/sync/field.storage.node.comment.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 6210ede5-370d-4081-9cce-e7a4ba693943 -langcode: en -status: true -dependencies: - module: - - comment - - node -_core: - default_config_hash: ktCna9xmWvYZIUfOCUyDQvedn5RtnS4CRmEIwNmvYjc -id: node.comment -field_name: comment -entity_type: node -type: comment -settings: - comment_type: comment -module: comment -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.node.field_image.yml b/example/drupal/config/sync/field.storage.node.field_image.yml deleted file mode 100644 index 0cf0c27..0000000 --- a/example/drupal/config/sync/field.storage.node.field_image.yml +++ /dev/null @@ -1,34 +0,0 @@ -uuid: fc870464-9659-41be-af5b-c9d2fadc48bc -langcode: en -status: true -dependencies: - module: - - file - - image - - node -_core: - default_config_hash: EymokncRIZ7SgQT2IdOQhQJicX4nNc0K89ik-LxmOHE -id: node.field_image -field_name: field_image -entity_type: node -type: image -settings: - target_type: file - display_field: false - display_default: false - uri_scheme: public - default_image: - uuid: null - alt: '' - title: '' - width: null - height: null -module: image -locked: false -cardinality: 1 -translatable: true -indexes: - target_id: - - target_id -persist_with_no_fields: false -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.node.field_tags.yml b/example/drupal/config/sync/field.storage.node.field_tags.yml deleted file mode 100644 index 2b95d18..0000000 --- a/example/drupal/config/sync/field.storage.node.field_tags.yml +++ /dev/null @@ -1,22 +0,0 @@ -uuid: 319cfa4b-2466-4ce1-8116-45ec7d8252e8 -langcode: en -status: true -dependencies: - module: - - node - - taxonomy -_core: - default_config_hash: WpOE_bs8Bs_HY2ns7n2r__de-xno0-Bxkqep5-MsHAs -id: node.field_tags -field_name: field_tags -entity_type: node -type: entity_reference -settings: - target_type: taxonomy_term -module: core -locked: false -cardinality: -1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/example/drupal/config/sync/field.storage.user.user_picture.yml b/example/drupal/config/sync/field.storage.user.user_picture.yml deleted file mode 100644 index c48574d..0000000 --- a/example/drupal/config/sync/field.storage.user.user_picture.yml +++ /dev/null @@ -1,34 +0,0 @@ -uuid: fc495963-30e9-4ed3-a5dc-96dd17d97433 -langcode: en -status: true -dependencies: - module: - - file - - image - - user -_core: - default_config_hash: 0q8-mw8y-Ls5I578sIPQFLp4ljpH7WvVYpzY-I9O-8E -id: user.user_picture -field_name: user_picture -entity_type: user -type: image -settings: - target_type: file - display_field: false - display_default: false - uri_scheme: public - default_image: - uuid: null - alt: '' - title: '' - width: null - height: null -module: image -locked: false -cardinality: 1 -translatable: true -indexes: - target_id: - - target_id -persist_with_no_fields: false -custom_storage: false diff --git a/example/drupal/config/sync/field_ui.settings.yml b/example/drupal/config/sync/field_ui.settings.yml deleted file mode 100644 index 365450f..0000000 --- a/example/drupal/config/sync/field_ui.settings.yml +++ /dev/null @@ -1,3 +0,0 @@ -_core: - default_config_hash: Q1nMi90W6YQxKzZAgJQw7Ag9U4JrsEUwkomF0lhvbIM -field_prefix: field_ diff --git a/example/drupal/config/sync/file.settings.yml b/example/drupal/config/sync/file.settings.yml deleted file mode 100644 index 1ca5bd9..0000000 --- a/example/drupal/config/sync/file.settings.yml +++ /dev/null @@ -1,8 +0,0 @@ -_core: - default_config_hash: 0aMkoXYnax5_tHI9C9zHs-K48KJ6K75PHtD9x-0nbgM -description: - type: textfield - length: 128 -icon: - directory: core/modules/file/icons -make_unused_managed_files_temporary: false diff --git a/example/drupal/config/sync/filter.format.basic_html.yml b/example/drupal/config/sync/filter.format.basic_html.yml deleted file mode 100644 index dc525b1..0000000 --- a/example/drupal/config/sync/filter.format.basic_html.yml +++ /dev/null @@ -1,45 +0,0 @@ -uuid: e895ab43-2681-4eb9-a8a3-151fe76e5b8e -langcode: en -status: true -dependencies: - module: - - editor -_core: - default_config_hash: P8ddpAIKtawJDi5SzOwCzVnnNYqONewSTJ6Xn0dW_aQ -name: 'Basic HTML' -format: basic_html -weight: 0 -filters: - filter_html: - id: filter_html - provider: filter - status: true - weight: -10 - settings: - allowed_html: '