diff --git a/.claude/settings.json b/.claude/settings.json index bf5e6d6..4dedac6 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,53 +1,75 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", - "env": { - "NODE_ENV": "development" - }, "permissions": { "allow": [ - "Bash(pnpm install)", - "Bash(pnpm test)", - "Bash(pnpm test:watch)", - "Bash(pnpm lint)", - "Bash(pnpm lint:fix)", - "Bash(pnpm typecheck)", - "Bash(pnpm format)", - "Bash(pnpm format:check)", - "Bash(pnpm build)", - "Bash(vitest *)", - "Bash(oxlint *)", - "Bash(oxfmt *)", - "Bash(tsc *)", - "Bash(rslib build *)", + "Bash(ls *)", + "Bash(cat *)", + "Bash(grep *)", + "Bash(find *)", + "Bash(wc *)", + "Bash(head *)", + "Bash(tail *)", "Bash(git status *)", + "Bash(git status)", "Bash(git log *)", "Bash(git diff *)", + "Bash(git diff)", "Bash(git show *)", "Bash(git branch *)", - - "Bash(rtk pnpm install)", - "Bash(rtk vitest *)", - "Bash(rtk lint *)", - "Bash(rtk tsc *)", - "Bash(rtk grep *)", - "Bash(rtk ls *)", - "Bash(rtk read *)", - "Bash(rtk find *)", - "Bash(rtk diff *)", - "Bash(rtk smart *)", - "Bash(rtk json *)", - "Bash(rtk pnpm list *)", - "Bash(rtk git status *)", - "Bash(rtk git log *)", - "Bash(rtk git diff *)", - - "WebSearch", - "WebFetch", - - "mcp__claude_ai_Context7__resolve-library-id", - "mcp__claude_ai_Context7__query-docs", - "mcp__plugin_context7_context7__resolve-library-id", - "mcp__plugin_context7_context7__query-docs" - ] + "Bash(git remote *)", + "Bash(git describe *)", + "Bash(git ls-files *)", + "Bash(git rev-parse *)", + "Bash(pnpm install)", + "Bash(pnpm typecheck*)", + "Bash(pnpm build*)", + "Bash(pnpm test*)", + "Bash(pnpm format*)", + "Bash(pnpm lint*)", + "Bash(pnpm list *)", + "Bash(pnpm why *)", + "Bash(pnpm outdated*)", + "Bash(pnpm view *)", + "Bash(gh pr view *)", + "Bash(gh pr list *)", + "Bash(gh pr checks *)", + "Bash(gh run list *)", + "Bash(gh run view *)", + "Bash(gh release view *)", + "Bash(gh issue view *)" + ], + "ask": [ + "Bash(git push *)", + "Bash(git tag *)", + "Bash(git reset --hard *)", + "Bash(gh pr create *)", + "Bash(gh pr merge *)", + "Bash(gh release create *)", + "Bash(gh release edit *)", + "Bash(gh release delete *)", + "Bash(gh repo edit *)", + "Bash(gh repo delete *)", + "Bash(gh secret *)", + "Bash(gh api *)", + "Bash(npm publish *)", + "Bash(pnpm publish *)" + ], + "deny": ["Read(./.env)", "Read(./.env.local)", "Read(./.env.*.local)"] + }, + "attribution": { + "commit": "", + "pr": "" + }, + "extraKnownMarketplaces": { + "seshuk": { + "source": { "source": "github", "repo": "maximseshuk/payload-plugin-tooling" } + }, + "payload-marketplace": { + "source": { "source": "github", "repo": "payloadcms/payload" } + } + }, + "enabledPlugins": { + "payload-plugin@seshuk": true, + "payload@payload-marketplace": true } } diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml index b7f3586..08a6506 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml @@ -12,7 +12,7 @@ body: options: - label: I have searched existing issues to ensure this bug hasn't been reported already required: true - - label: I have read the [documentation](https://github.com/maximseshuk/payload-plugin-openapi#readme) + - label: I have read the [documentation](https://payload-plugin-openapi.seshuk.im) required: true - type: textarea @@ -31,14 +31,14 @@ body: required: true - type: textarea - id: config + id: options attributes: - label: Plugin Configuration - description: Share your plugin setup + label: Plugin Options + description: Share your plugin setup (**remove API keys and sensitive data**) placeholder: | ```typescript openapi({ - metadata: { + info: { title: 'My API', version: '1.0.0', }, @@ -56,10 +56,11 @@ body: Paste output from `pnpm payload info` or list your versions. Please use specific version numbers instead of "latest" to help us debug faster. placeholder: | - @seshuk/payload-plugin-openapi: 0.1.0 - payload: 3.53.0 - next: 15.4.4 - node: 20.9.0 + @seshuk/payload-plugin-openapi: 1.0.0 + payload: 4.0.0 + next: 16.4.0 + node: 24.15.0 + database adapter: @payloadcms/db-sqlite 4.0.0 render: shell validations: required: true diff --git a/.github/ISSUE_TEMPLATE/02-question.yml b/.github/ISSUE_TEMPLATE/02-question.yml index b2f5d6f..aae5f59 100644 --- a/.github/ISSUE_TEMPLATE/02-question.yml +++ b/.github/ISSUE_TEMPLATE/02-question.yml @@ -8,7 +8,7 @@ body: - type: markdown attributes: value: | - **Before asking:** Please check the [documentation](https://github.com/maximseshuk/payload-plugin-openapi#readme) and [existing issues](https://github.com/maximseshuk/payload-plugin-openapi/issues). + **Before asking:** Please check the [documentation](https://payload-plugin-openapi.seshuk.im) and [existing issues](https://github.com/maximseshuk/payload-plugin-openapi/issues). - type: textarea id: question @@ -25,14 +25,14 @@ body: required: true - type: textarea - id: current-config + id: current-options attributes: - label: Current Configuration (if relevant) - description: Share your plugin setup + label: Current Options (if relevant) + description: Share your plugin setup (**remove API keys and sensitive data**) placeholder: | ```typescript openapi({ - metadata: { + info: { title: 'My API', version: '1.0.0', }, diff --git a/.github/ISSUE_TEMPLATE/03-feature-request.yml b/.github/ISSUE_TEMPLATE/03-feature-request.yml index a058785..f8e30f9 100644 --- a/.github/ISSUE_TEMPLATE/03-feature-request.yml +++ b/.github/ISSUE_TEMPLATE/03-feature-request.yml @@ -12,33 +12,59 @@ body: options: - label: I have searched existing issues to make sure this feature hasn't been requested before required: true - - label: I have checked the [documentation](https://github.com/maximseshuk/payload-plugin-openapi#readme) to confirm this feature doesn't already exist + - label: I have checked the [documentation](https://payload-plugin-openapi.seshuk.im) to confirm this feature doesn't already exist required: true - type: textarea id: description attributes: label: Feature Description - description: What would you like to see, and what problem does it solve? + description: What feature would you like to see implemented, and why? placeholder: | - - What problem does this solve? + Describe the feature you'd like to see: + - What problem does this solve, and who is it for? - How should it work? + - What would the options look like? + - Any current workarounds you're using? validations: required: true - type: textarea - id: config-example + id: options-example attributes: - label: Example Configuration (optional) - description: If this involves config changes, show what you imagine it would look like + label: Example Options (optional) + description: If this involves option changes, show what you imagine it would look like placeholder: | ```typescript openapi({ - metadata: { + info: { title: 'My API', version: '1.0.0', }, - // Your proposed option here + // Your proposed feature here + newFeature: { + // example options + }, }) ``` render: typescript + + - type: checkboxes + id: implementation + attributes: + label: Implementation + description: Are you willing to help implement this feature? + options: + - label: I'm willing to submit a pull request for this feature + - label: I can help with testing the implementation + + - type: textarea + id: additional-context + attributes: + label: Additional Context (optional) + description: Any other information that would help understand this feature request + placeholder: | + Any additional details: + - Alternative solutions you've considered + - Links to similar features in other tools + - Screenshots or examples diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8671f09..cb756ae 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Documentation - url: https://github.com/maximseshuk/payload-plugin-openapi#readme + - name: 📚 Documentation + url: https://payload-plugin-openapi.seshuk.im about: Check the documentation for setup and configuration guides + - name: 🔄 Upgrade Guide + url: https://payload-plugin-openapi.seshuk.im/v1/upgrade-guide + about: How to upgrade from 0.x diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..af5ab39 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,75 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + versioning-strategy: increase-if-necessary + cooldown: + default-days: 2 + commit-message: + prefix: chore + include: scope + ignore: + - dependency-name: payload + update-types: [version-update:semver-major] + - dependency-name: payload + versions: ['>=4.0.0-i <4.0.0-j'] + - dependency-name: '@payloadcms/*' + update-types: [version-update:semver-major] + - dependency-name: '@payloadcms/*' + versions: ['>=4.0.0-i <4.0.0-j'] + - dependency-name: next + update-types: [version-update:semver-major] + - dependency-name: react + update-types: [version-update:semver-major] + - dependency-name: react-dom + update-types: [version-update:semver-major] + groups: + payload: + patterns: + - payload + - '@payloadcms/*' + - next + - '@next/*' + - react + - react-dom + - '@types/react' + - '@types/react-dom' + dev: + dependency-type: development + update-types: [minor, patch] + exclude-patterns: + - payload + - '@payloadcms/*' + - next + - '@next/*' + - react + - react-dom + - '@types/react' + - '@types/react-dom' + runtime: + dependency-type: production + update-types: [minor, patch] + exclude-patterns: + - payload + - '@payloadcms/*' + - next + - '@next/*' + - react + - react-dom + - '@types/react' + - '@types/react-dom' + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + commit-message: + prefix: ci + cooldown: + default-days: 2 + groups: + actions: + patterns: ['*'] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e9e6287 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## What + + + +## Why + + + +## How I tested it + + + +## Checklist + +- [ ] `pnpm typecheck && pnpm lint && pnpm format && pnpm test:unit` pass +- [ ] Tests added or updated for the behavior change +- [ ] `README.md` and `docs/` updated if users will notice the change +- [ ] Commit subjects follow Conventional Commits (`fix: ...`, `feat: ...`) diff --git a/.github/releases/v1.0.0-beta.1.md b/.github/releases/v1.0.0-beta.1.md new file mode 100644 index 0000000..332f373 --- /dev/null +++ b/.github/releases/v1.0.0-beta.1.md @@ -0,0 +1,62 @@ +Payload 4 support. Payload 3 users stay on `0.x`. + +## Breaking changes + +- Requires Payload 4.0.0-canary.38, Next.js 16.4+ and Node.js 24.15+. +- Renamed options: `metadata` → `info`, `specEndpoint` → `path`, `interactiveAuth.endpoint` → `interactiveAuth.path`, docs UI `specEndpoint` → `specURL`. Old names throw at startup. +- `securityWhen` is now `security`. It returns `'public'`, `'secured'`, a security requirement or `undefined`. +- `custom.openapi.security` takes `'public'` or `'secured'` instead of `true` or `false`. +- `filters.excludeWhen` is removed. Pass the function to `filters.excludeOperations` instead. +- `excludeOperations` and `security` now also apply to custom, jobs and system endpoints. +- Extension `transform` gets one object (`{ doc, payload, options }`). If it throws, the spec request fails. +- `servers` no longer comes from the `Host` header. Use `servers`, `serverURL` or `trustedHosts`. +- Payload 4 routes: access is `GET /api/access`, and root endpoints live under `/api` (`/api/health`). +- Payload 4 turns versions on by default, so version endpoints show up unless you set `versions: false`. +- `payload-jobs-stats` is hidden unless `includeSystem` is on. + +See the [upgrade guide](https://payload-plugin-openapi.seshuk.im/v1/upgrade-guide) for details. + +```ts +// 0.x +openapi({ + metadata: { title, version }, + specEndpoint: '/spec.json', + securityWhen: ({ slug }) => (slug === 'feed' ? true : undefined), + filters: { excludeWhen: ({ path }) => path.includes('/internal/') }, +}) + +// v1 +openapi({ + info: { title, version }, + path: '/spec.json', + security: ({ slug }) => (slug === 'feed' ? 'public' : undefined), + filters: { excludeOperations: [({ path }) => path.includes('/internal/')] }, +}) +``` + +## New + +- New Payload 4 endpoints: validate, upload instructions, file download, API key reveal and document access. Validate follows `access.validate`, and `custom.openapi.security` accepts a `validate` key. +- `access` on `openapi()`, `scalar()` and `swaggerUi()` limits who can see the spec and the docs. +- `servers` and `trustedHosts` set the base URLs. +- `info` takes the full OpenAPI Info Object, and `info.description` can be localized. The docs page title is `info.title`. +- `interactiveAuth.collection` picks the login collection. +- `transform` can be async and gets `payload` and `options`. +- `custom.openapi` is typed for collections, globals and fields. +- Scalar and Swagger UI load pinned versions with SRI. +- Folders and tags are documented. +- Anonymous usage telemetry. Turn it off with `telemetry: false` or `DO_NOT_TRACK=1`. +- [Endpoints reference](https://payload-plugin-openapi.seshuk.im/v1/reference/endpoints) in the docs. + +## Fixes + +- After you log in on the docs page, requests sent from it include your token. The docs page also loads the spec from a custom `path`. +- Version, restore, jobs and staged upload endpoints are secured when they should be, not public. +- Request bodies match what Payload accepts: nested relationships are IDs, polymorphic ones are `{ relationTo, value }`, and read-only fields are left out. +- Write operations list every query param Payload reads, such as `depth`, `locale`, `draft`, `autosave` and `overrideLock`. +- Responses match Payload: create returns `201`, global update returns `{ message, result }`, and global restore returns `{ doc, message }`. +- Auth bodies follow `loginWithUsername`, and unlock is documented for every auth collection. With `disableLocalStrategy`, `me`, `logout`, `refresh-token` and `init` stay. +- OpenAPI 3.0 and 3.1 output is valid. +- `payload-kv`, `payload-llm-instructions` and `payload-query-presets` are hidden unless `includeSystem` is on. +- Joins over several collections require `relationTo`. +- Interactive auth `expires_in` is in seconds. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33e2920..bd011d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,29 +4,10 @@ on: branches: [main] pull_request: branches: [main] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - NEXT_TELEMETRY_DISABLED: 1 - +permissions: + contents: read jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6 - with: - version: 11.11.0 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: pnpm - - run: pnpm install - - run: pnpm lint - - run: pnpm format:check - - run: pnpm typecheck - - run: pnpm test - - run: pnpm build + ci: + uses: maximseshuk/payload-plugin-tooling/.github/workflows/ci.yml@v0.2.3 + with: + node-versions: '["24"]' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e290d8c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,37 @@ +name: Docs + +on: + push: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/docs.yml' + pull_request: + paths: + - 'docs/**' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate MDX and navigation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install --frozen-lockfile + - run: pnpm docs:validate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f77584..fcf104b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,46 +2,13 @@ name: Release on: push: tags: ['v*.*.*'] - permissions: - contents: write - id-token: write - + contents: read jobs: release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - uses: pnpm/action-setup@v6 - with: - version: 11.11.0 - - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - registry-url: https://registry.npmjs.org/ - - - run: pnpm install - - run: pnpm lint - - run: pnpm typecheck - - run: pnpm test - - run: pnpm build - - - name: Generate changelog - uses: orhun/git-cliff-action@v4 - id: changelog - with: - config: cliff.toml - args: --latest --strip header --github-repo ${{ github.repository }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create GitHub Release - uses: softprops/action-gh-release@v3 - with: - body: ${{ steps.changelog.outputs.content }} - - - run: pnpm pack --pack-destination "$RUNNER_TEMP/pack" - - run: npm publish "$RUNNER_TEMP"/pack/*.tgz --access public --provenance --tag latest + uses: maximseshuk/payload-plugin-tooling/.github/workflows/release.yml@v0.2.3 + permissions: + contents: write + id-token: write + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index ba47f12..4081475 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ dist docs/.mint node_modules tests/.next +tests/AGENTS.md +tests/CLAUDE.md tests/node_modules diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 99e2f7d..a25e36d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,4 @@ { - "recommendations": ["oxc.oxc-vscode"] + "recommendations": ["oxc.oxc-vscode", "TypeScriptTeam.native-preview"], + "unwantedRecommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index d646692..d700a95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,28 +1,35 @@ { - "[javascript]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[json]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[jsonc]": { - "editor.defaultFormatter": "oxc.oxc-vscode" - }, - "[yaml]": { - "editor.defaultFormatter": "oxc.oxc-vscode" + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/node_modules": true, + "**/dist": true, + "**/coverage": true, + "**/.next": true, + "**/.omc": true, + "**/.playwright-mcp": true, + "**/*.tsbuildinfo": true, + "**/*.db": true, + "**/.claude/**/*.local.json": true }, + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSave": true, "editor.formatOnSaveMode": "file", "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" - } + }, + "editor.tabSize": 2, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "[markdown]": { + "editor.wordWrap": "on" + }, + "[mdx]": { + "editor.wordWrap": "on" + }, + "oxc.lint.run": "onType", + "oxc.fixKind": "safe_fix", + "oxc.unusedDisableDirectives": "deny", + "js/ts.experimental.useTsgo": true, + "js/ts.tsdk.path": "./node_modules/typescript" } diff --git a/.zed/settings.json b/.zed/settings.json index 13879c7..5a64c21 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,19 +1,11 @@ { "file_scan_exclusions": [ "**/.git", - "**/.svn", - "**/.hg", - "**/.jj", - "**/CVS", "**/.DS_Store", - "**/Thumbs.db", - "**/.classpath", - "**/.settings", "**/node_modules", "**/dist", "**/coverage", "**/.next", - "**/.mint", "**/.omc", "**/.playwright-mcp", "**/*.tsbuildinfo", @@ -27,91 +19,39 @@ "tab_size": 2, "languages": { "TypeScript": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - }, - { - "code_action": "source.fixAll.oxc" - } - ] + "language_servers": ["typescript-ls", "!vtsls", "!typescript-language-server", "!eslint", "..."], + "formatter": [{ "language_server": { "name": "oxfmt" } }, { "code_action": "source.fixAll.oxc" }] }, "TSX": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - }, - { - "code_action": "source.fixAll.oxc" - } - ] + "language_servers": ["typescript-ls", "!vtsls", "!typescript-language-server", "!eslint", "..."], + "formatter": [{ "language_server": { "name": "oxfmt" } }, { "code_action": "source.fixAll.oxc" }] }, "JavaScript": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - }, - { - "code_action": "source.fixAll.oxc" - } - ] + "language_servers": ["typescript-ls", "!vtsls", "!typescript-language-server", "!eslint", "..."], + "formatter": [{ "language_server": { "name": "oxfmt" } }, { "code_action": "source.fixAll.oxc" }] }, "JSON": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - } - ] + "formatter": [{ "language_server": { "name": "oxfmt" } }] }, "JSONC": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - } - ] + "formatter": [{ "language_server": { "name": "oxfmt" } }] }, "CSS": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - } - ] + "formatter": [{ "language_server": { "name": "oxfmt" } }] }, "YAML": { - "format_on_save": "on", - "formatter": [ - { - "language_server": { - "name": "oxfmt" - } - } - ] + "formatter": [{ "language_server": { "name": "oxfmt" } }] + }, + "TOML": { + "formatter": [{ "language_server": { "name": "oxfmt" } }] }, "Markdown": { - "format_on_save": "off", - "soft_wrap": "editor_width" + "soft_wrap": "editor_width", + "formatter": [{ "language_server": { "name": "oxfmt" } }] }, "MDX": { - "format_on_save": "off", - "soft_wrap": "editor_width" + "soft_wrap": "editor_width", + "formatter": [{ "language_server": { "name": "oxfmt" } }] } }, "lsp": { diff --git a/AGENTS.md b/AGENTS.md index 4f279c5..367618a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,72 +1,109 @@ -# payload-plugin-openapi Development Guidelines +# OpenAPI Plugin for Payload + +Payload 4 plugin. Builds OpenAPI 3.0/3.1/3.2 document from sanitized Payload config. Serves it at `/api/openapi.json`, docs UI via Scalar or Swagger UI. Features: filters, security marking from access functions, `custom.openapi` metadata on fields/endpoints, i18n (44 locales), interactive auth, cache, CLI command `openapi:generate`. + +## Environment -This document outlines coding standards and practices for **@seshuk/payload-plugin-openapi** — a Payload CMS plugin that builds an OpenAPI 3.0/3.1/3.2 document from a sanitized Payload config and serves it through Scalar or Swagger UI. +- pnpm 12. Node.js 24.15+. Payload 4. +- Shared dev config from `@seshuk/payload-plugin-tooling` (oxlint, oxfmt, tsconfig, tsdown, test DB, CI/release workflows, changelog). +- Secrets from env only. Never hardcode keys. -`CLAUDE.md` and `GEMINI.md` are symlinks to this file. +## Commands -## Core Principles +```bash +pnpm typecheck # tsc --noEmit +pnpm lint # oxlint +pnpm lint:fix +pnpm format # oxfmt write; format:check to verify +pnpm test:unit # vitest, tests/unit: no DB +pnpm test:int # vitest, tests/integration: in-memory DB (TEST_DB=sqlite|postgres|mongodb, default sqlite) +pnpm build # tsdown -> dist/ -**Lazy, Config-Driven Generation**: The document is never built at config time. `openapi()` only registers the `bin` script, stashes `ResolvedOptions` on `config.custom[PLUGIN_NAME]`, merges i18n bundles, and (unless `serve: false`) mounts the endpoints. The actual document is built per request or per CLI run from the fully _sanitized_ config — so every other plugin's collections, globals, fields, and endpoints are visible regardless of plugin order. +pnpm dev # dev Payload + Next app (tests/payload.config.ts) +pnpm docs:dev # Mintlify docs site (docs/) +pnpm docs:validate +``` -**Always Build 3.2, Downconvert Down**: The builder always produces OpenAPI 3.2. `openapiVersion: '3.1'` and `'3.0'` run a downconversion pass (`src/spec/downconvert.ts`) on the finished document. Never branch the builder on version — add to the downconverter instead. +Before every commit: `pnpm typecheck && pnpm lint && pnpm format && pnpm test:unit && pnpm test:int`. -**Native Metadata, No Registry**: Field- and endpoint-level docs ride on Payload's own `custom.openapi` key. The plugin reads that shape; it does not keep a parallel registry. Localizable strings (`description`/`title`/`summary`) may be functions or locale-keyed objects, resolved in `src/spec/entitySchemas.ts`. +## Structure -**Resolve Options Once**: Defaults live in `resolveOptions` (`src/options.ts`), nowhere else. Builders take `ResolvedOptions`, never raw user options. +`src/index.ts` is the only public entry. -**Minimal Surface**: `src/index.ts` is the single public entry point. The runtime dependency footprint is essentially just `@scalar/openapi-types` — keep it that way. +``` +src/ +├── index.ts # openapi() plugin, exports +├── shared/ +│ ├── constants.ts # PLUGIN_NAME, paths +│ ├── types/index.ts # public types (JSDoc = editor hints), ResolvedOptions, BuildContext +│ ├── translations/ # makeT + locales/ (44) +│ └── utils.ts # deepMerge, isPlainObject, … check here first +├── server/ +│ ├── options/resolveOptions.ts # every default lives here +│ ├── endpoints/ # spec.ts (serve doc, ?lang=, cache), interactiveAuth.ts +│ ├── spec/ # generator: build, buildDocument, entitySchemas, fields, params, components, filters, names, tags, security, downconvert, paths/ +│ └── ui/ # createUiPlugin + scalar/swagger/html +└── cli/generateSpec.ts # `payload openapi:generate` (--lang, --out, --server) +tests/ +├── vitest.config.ts # projects: unit, int +├── unit/ # no DB +├── integration/ # *.int.spec.ts, dev config + testDatabase() +├── helpers/ # i18n stub, build context +└── payload.config.ts, app/, collections/, globals/, blocks/ # runnable dev app +``` -## Toolchain +## Architecture -The toolchain is **oxc**: `oxlint` for linting, `oxfmt` for formatting. There is **no eslint or prettier** — don't reach for them. The build is **rslib** (not rsbuild or tsup). The package manager is **pnpm**; don't use npm/yarn or commit a `package-lock.json`/`yarn.lock`. +- `openapi()` builds nothing at config time. Registers CLI command, stashes `ResolvedOptions` on `config.custom[PLUGIN_NAME]`, merges i18n, mounts endpoints (unless `serve: false`). +- Document built per request or per CLI run from sanitized config. Other plugins' collections/endpoints visible regardless of order. +- Builder always makes 3.2. `openapiVersion: '3.1' | '3.0'` downconverts finished doc in `server/spec/downconvert.ts`. Never branch builder on version. +- Field/endpoint docs ride on Payload `custom.openapi`. No own registry. Types augment `CollectionCustom`/`GlobalCustom`/`FieldCustom` in `shared/types`. +- `servers` set per request in `server/endpoints/spec.ts`: `servers` fn > array > `Host` in `trustedHosts` > `serverURL` > `[]`. Never trust `Host` outside allowlist. +- Extension `transform` fails closed. No try/catch around it. +- UI default `cdnBase` pinned + SRI. Bump version = recompute sha384 in `server/ui/scalar.ts`/`swagger.ts`. -## Key Restrictions +## Rules -- Never switch the toolchain (eslint, prettier, tsup, a different package manager) without being asked. -- Never downgrade the TypeScript `lib`/`target` below ES2023 / Node 20 — `.toSorted()` and friends are intentional. -- Never branch the builder on `openapiVersion`; downconvert from 3.2 instead. -- Never scatter option defaults outside `resolveOptions`. -- Never edit `dist/`, `node_modules/`, or other generated output by hand. -- Never commit secrets, push directly to `main`, or add the agent as a commit/PR author. +- No code comments. Only JSDoc on plugin options types (`src/shared/types/index.ts`). +- Defaults only in `resolveOptions`. Builders take `ResolvedOptions`. +- Removed/renamed v0 keys throw via `assertNoRemovedKeys` (`[] was renamed to `). No aliases. +- `excludeOperations` + `security` run for every path group (collection, global, custom, jobs, system) through `finalize` in `buildDocument`. Secured ops get `PayloadLogin` there when `serve && interactiveAuth`. +- Runtime deps: `@scalar/openapi-types` only. Ask before adding one. +- New public option: `shared/types/index.ts` (JSDoc), `server/options/resolveOptions.ts`, `README.md`, docs page, test. +- Match surrounding naming and idiom. Imports end in `.js`. `@/` (= `src/`) for any import that leaves the folder, `./x.js` only in the same folder, no `../`. Tests import source through `@/` too. -## Repository Layout +## Testing -The shipped source is `src/`. `tests/` is both the vitest suite and a runnable Payload + Next dev app. +- vitest in `tests/`: `unit/` (builders, options, plugin factory; no DB), `integration/*.int.spec.ts` (full doc against dev config). +- Test DB from `testDatabase()` (`@seshuk/payload-plugin-tooling/test-database`). +- Plugin factory testable directly: `openapi(opts)(config)`, assert on `endpoints`/`cli`/`custom`/`i18n`. -- `src/index.ts` — public exports: `openapi`, `scalar`/`swaggerUi`, `buildOpenApiDocument`, the downconverters, and all types. -- `src/options.ts` — `resolveOptions`; the home of every default. -- `src/types.ts` — public types plus `ResolvedOptions`/`BuildContext`. -- `src/endpoints/` — runtime handlers: `spec.ts` (serves the doc; `?lang=` + cache), `interactiveAuth.ts` (credential→JWT). -- `src/bin/generateSpec.ts` — the `openapi:generate` CLI (`--lang`, `--out`, `--server`). -- `src/spec/` — the generator: `build.ts`, `buildDocument.ts`, `entitySchemas.ts`, `fields.ts`, `params.ts`, `components.ts`, `filters.ts`, `names.ts`, `tags.ts`, `downconvert.ts`, and `spec/paths/` (collections, globals, auth, versions, jobs, custom). -- `src/ui/` — docs renderers: `createUiPlugin.ts` (shared factory), `scalar.ts`/`swagger.ts` (thin), `html.ts`. -- `src/translations/` — `index.ts` registers locale bundles + `makeT`; `locales/` holds 44 files. -- `src/utils.ts` — shared helpers (`deepMerge`, `isPlainObject`, …); check here before adding a utility. +## Docs -## Code Style +- Mintlify site in `docs/`. User-facing change: update `README.md` and matching docs page. -Internal imports use the `.js` extension under ESM/NodeNext even though the files are `.ts` — match the surrounding imports. TypeScript runs in strict mode; avoid `any` and prefer precise types from `@scalar/openapi-types` and `payload`. Keep comments load-bearing — explain _why_, not _what_, and match the existing low density. When adding a public option, change all four sites in one PR: `types.ts` (with accurate JSDoc — it's the editor-hint source of truth), `options.ts`, the `README.md`, and a test. Format and lint touched files: `pnpm exec oxfmt ` and `pnpm lint`. +## Branches -## Testing Requirements +| Branch | Major | Payload | Node | Takes | +| ------ | ----- | ------- | ------ | ------------------------------ | +| `main` | v1 | 4 | 24.15+ | all work; PRs target `main` | +| `0.x` | v0 | 3 | 20+ | critical fixes only, from main | -Tests are vitest in `tests/` — `unit.spec.ts` (builders, options, downconvert, the plugin factory in isolation) and `int.spec.ts` (the full document built against the dev config). Follow TDD for behavior changes: write the failing test first. The plugin factory is directly testable — call `openapi(opts)(config)` and assert on the returned `endpoints`/`bin`/`custom`/`i18n`. Cover success, failure, and the edge that motivated the change; don't assert on log output. Run `pnpm test` before committing anything under `src/`, and **show the output** — never claim "tested" or "all green" without pasted evidence in the same turn. +## Releases -## Build & Commands +- Run `/payload-plugin:release` skill: version bump in `package.json`, `.github/releases/vX.Y.Z.md`, checks, commit `chore(release): vX.Y.Z`, tag. Never pushes. +- Tag push `vX.Y.Z[-pre.N]` runs tooling release workflow: checks, git-cliff notes, GitHub Release, npm publish (OIDC). Dist-tag: `beta` for `-beta.N`, `latest` for newest major, `latest-N` for older major. +- New major and its first prerelease need `.github/releases/vX.Y.Z.md`. +- Tags `v*` never move. Bad release = new version. -| Task | Command | -| -------------- | -------------------------------------------- | -| Typecheck | `pnpm typecheck` | -| Lint / fix | `pnpm lint` · `pnpm lint:fix` | -| Format / check | `pnpm format` · `pnpm format:check` | -| Test / watch | `pnpm test` · `pnpm test:watch` | -| One test file | `pnpm exec vitest run tests/unit.spec.ts` | -| Build | `pnpm build` | -| Dev app | `pnpm dev` (boots `tests/payload.config.ts`) | +## Commits -## Commits and PRs +- One-line Conventional Commit (`feat: add nestedTags option`). Optional scope = `src/` area (`cli`, `endpoints`, `spec`, `translations`, `ui`). +- No co-authored-by or agent trailers. +- Commit locally. Push only when asked. -Use Conventional Commits (`type: subject`) with the subject focused on user impact, one logical change per commit. The **scope is optional** — there's no commitlint enforcing it. When one helps, use the `src/` folder you touched (`bin`, `endpoints`, `spec`, `translations`, `ui`); `release` is reserved for the `pnpm version` scripts (`chore(release): v%s`). Don't invent scopes beyond those. - -**Never add a `Co-Authored-By` trailer or any agent attribution to commits.** Agents assist; they don't author. The commit message is the subject line and (when needed) a body explaining _why_ — nothing else. - -Branch off `main` and open a PR rather than pushing to it; force-push only with `--force-with-lease`. Keep `README.md` and JSDoc in sync with any option, command, or behavior change in the same PR. Ask first before adding a runtime dependency, changing a public signature, or doing a repo-wide refactor. +## Boundaries + +Ask first: new runtime dependency, public API change, large refactor, commit/push not requested. + +Never: secrets in repo; push, force-push or destructive git without explicit request; edit `dist/` or generated files by hand. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc0a85d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing + +Thanks for helping with the OpenAPI plugin for Payload. This guide covers the setup, the rules the code follows, and how pull requests get reviewed. + +## Before you start + +- For a bug, open an issue with steps to reproduce first, unless the fix is small and obvious. +- For a new feature or a change to the public options, open an issue first and describe the use case. It saves you work if the idea doesn't fit the plugin. +- For security problems, don't open an issue. Follow [SECURITY.md](SECURITY.md). + +## Setup + +You need Node.js 24.15+ and pnpm 12. + +```bash +pnpm install +pnpm test:unit +``` + +## Test setup + +Tests and the dev app need no accounts and no `.env` file. Tests run on an in-memory SQLite database. To run them on Postgres or MongoDB, set `TEST_DB=postgres` (in-memory PGlite) or `TEST_DB=mongodb` (in-memory MongoDB server). `pnpm dev` starts the dev Payload app from `tests/payload.config.ts` and keeps its data in `tests/payload.db`. + +## Commands + +```bash +pnpm typecheck # tsc --noEmit +pnpm lint # oxlint +pnpm format # oxfmt +pnpm test:unit # unit tests, no database +pnpm test:int # integration tests, in-memory database +pnpm build # tsdown +pnpm dev # dev Payload app +pnpm docs:dev # docs site +``` + +Run `pnpm typecheck && pnpm lint && pnpm format && pnpm test:unit && pnpm test:int` before you push. + +## Code rules + +- **Keep defaults in `resolveOptions`.** Every default lives in `src/server/options/resolveOptions.ts`. Builders take `ResolvedOptions`, never the raw options. +- **Document every new public option.** Add it to `src/shared/types/index.ts` (with JSDoc) and `src/server/options/resolveOptions.ts`, then to `README.md` and the docs page, and cover it with a test. +- **Build once, convert after.** The builder always produces OpenAPI 3.2. `openapiVersion: '3.1' | '3.0'` is applied to the finished document in `src/server/spec/downconvert.ts`. Never branch the builder on the version. +- **Describe fields and endpoints through `custom.openapi`.** It is Payload's own `custom` key. Don't add a separate registry. +- **Reuse `src/shared/utils.ts`.** Check it before you write a helper such as `deepMerge` or `isPlainObject`. +- **Comment sparingly.** Name things so the code explains itself. Add a comment only for a workaround or a constraint a reader would miss. The public types in `src/shared/types/index.ts` keep their JSDoc. +- **Match the surrounding code.** Follow the naming and patterns already used in the file you change. Imports end in `.js`. Use `@/` (`src/`) for an import that leaves the folder and `./x.js` only within the same folder. +- **Ask before adding a runtime dependency.** The only one today is `@scalar/openapi-types`. Say why in the issue or PR. + +## Tests + +- Add or update tests for every behavior change. +- Tests live in `tests/`. `tests/unit/` covers builders, options and the plugin factory without a database. `tests/integration/*.int.spec.ts` builds the full document against the dev config. +- Get the database from `testDatabase()` (`@seshuk/payload-plugin-tooling/test-database`), not from a hard-coded adapter. +- Name a `describe` after the function or feature under test (`schemaName`, `spec/fields`). Start an `it` name with a present-tense verb and write it in plain English: `turns a slug into PascalCase`, not `should turn…`. +- A bug fix should come with a test that fails without the fix. +- Don't commit `.only`, `.skip` or placeholder tests. + +## Docs + +If users will notice the change, update `README.md` and the matching page in `docs/`. Preview the site with `pnpm docs:dev`, and run `pnpm docs:validate` and `pnpm docs:check-links` after you edit it. + +## Commits and pull requests + +- Use [Conventional Commits](https://www.conventionalcommits.org/) with a short, one-line subject, for example `fix: keep tags when a filter hides a path` or `feat: add nestedTags option`. The release changelog is built from these. +- Keep each PR focused on one change. Open it against `main`, which holds 1.x for Payload 4. The `0.x` branch (Payload 3) only takes critical and high-severity security fixes, which are cherry-picked from `main` where possible. +- Fill in the PR template: what changed, why, and how you tested it. Link the issue (`Closes #123`). +- CI must pass. The `Lint, typecheck, test, build (24)` check runs lint, format check, typecheck, unit tests and the build on Node 24. +- All review threads need to be resolved before merge. + +## License + +By contributing, you agree that your work is released under the [MIT License](LICENSE). diff --git a/README.md b/README.md index 8fc367e..abdf10a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@
- OpenAPI Plugin for Payload CMS + OpenAPI Plugin for Payload -

OpenAPI Plugin for Payload CMS

+

OpenAPI Plugin for Payload

Generate an OpenAPI 3.0/3.1/3.2 specification from your Payload config and serve it with Scalar or Swagger UI.

@@ -14,13 +14,13 @@ CI Documentation license -Ko-fi +Ko-fi
## Features -- **Full spec from your config** — collections, globals, auth, versions, and jobs are documented with zero annotation. +- **Full spec from your config** — collections, globals, auth, versions, uploads, and jobs are documented with zero annotation. See the [endpoint list](https://payload-plugin-openapi.seshuk.im/v1/reference/endpoints). - **Interactive docs included** — mount Scalar or Swagger UI, or both on different paths. - **Native metadata** — document custom endpoints and refine field schemas through Payload's own `custom.openapi` key. No wrapper, no separate registry. - **Precise filtering** — choose exactly which entities and operations end up in the spec. @@ -31,16 +31,16 @@ ## Quick start -Requires **Payload CMS 3.53.0 or later** and **Node.js 20 or later**. +Requires **Payload 4.0.0-canary.38**, **Next.js 16.4 or later** and **Node.js 24.15 or later**. On Payload 3, use `@seshuk/payload-plugin-openapi@0`. ### Install ```bash -npm install @seshuk/payload-plugin-openapi -yarn add @seshuk/payload-plugin-openapi -pnpm add @seshuk/payload-plugin-openapi +npm install @seshuk/payload-plugin-openapi@beta ``` +v1 is in beta under the `beta` npm tag, and Payload 4 is published under `canary`. + ### Configure Add the plugin to your Payload config, plus a docs UI renderer: @@ -52,7 +52,7 @@ import { openapi, scalar } from '@seshuk/payload-plugin-openapi' export default buildConfig({ plugins: [ openapi({ - metadata: { + info: { title: 'My API', version: '1.0.0', }, @@ -67,38 +67,80 @@ Two endpoints are now live, both relative to your API route (`/api` by default): - `GET /api/openapi.json` — the generated OpenAPI document - `GET /api/docs` — the interactive API reference -`metadata.title` and `metadata.version` are required; the plugin throws at boot without them. Prefer Swagger UI? Swap `scalar()` for `swaggerUi()`, or mount both on different paths. +`info.title` and `info.version` are required; the plugin throws at boot without them. Prefer Swagger UI? Swap `scalar()` for `swaggerUi()`, or mount both on different paths. The document is built lazily from the fully sanitized config, so collections, fields, and endpoints added by other plugins are picked up regardless of plugin order. -Add `filters` to control what is exposed, `interactiveAuth` for a login dialog in the docs UI, `extensions` for anything the generator doesn't produce on its own — see the [configuration reference](https://payload-plugin-openapi.seshuk.im/configuration/overview). +Add `filters` to control what is exposed, `interactiveAuth` for a login dialog in the docs UI, `extensions` for anything the generator doesn't produce on its own — see the [configuration reference](https://payload-plugin-openapi.seshuk.im/v1/configuration/overview). + +## Upgrading from 0.x + +v1 renames a few options. The plugin throws at boot when it finds an old name. + +```ts +// 0.x +openapi({ + metadata: { title, version }, + specEndpoint: '/spec.json', + interactiveAuth: { endpoint: '/login' }, + securityWhen: ({ slug }) => (slug === 'feed' ? true : undefined), + filters: { excludeWhen: ({ path }) => path.includes('/internal/') }, +}) +scalar({ specEndpoint: '/api/spec.json' }) + +// v1 +openapi({ + info: { title, version }, + path: '/spec.json', + interactiveAuth: { path: '/login' }, + security: ({ slug }) => (slug === 'feed' ? 'public' : undefined), + filters: { excludeOperations: [({ path }) => path.includes('/internal/')] }, +}) +scalar() // follows the openapi() path; use specURL for another URL + +// 0.x +custom: { openapi: { security: { read: true, create: false } } } + +// v1 +custom: { openapi: { security: { read: 'public', create: 'secured' } } } +``` + +The spec no longer reads `servers` from the `Host` header. It uses `serverURL`, the new `servers` option, or hosts you list in `trustedHosts`. An extension `transform` that throws now fails the request. The spec and docs page are open by default. Add `access` to `openapi()` and `scalar()` to make them private. See [Upgrading from 0.x](https://payload-plugin-openapi.seshuk.im/v1/upgrade-guide). ## Documentation Full docs are at ****: -- [Quick start](https://payload-plugin-openapi.seshuk.im/quick-start) -- [Configuration reference](https://payload-plugin-openapi.seshuk.im/configuration/overview) -- [Filters](https://payload-plugin-openapi.seshuk.im/configuration/filters) -- [Security marking](https://payload-plugin-openapi.seshuk.im/configuration/security-marking) -- [Docs UI — Scalar & Swagger](https://payload-plugin-openapi.seshuk.im/configuration/docs-ui) -- [Documenting custom endpoints](https://payload-plugin-openapi.seshuk.im/guides/custom-endpoints) -- [Field metadata](https://payload-plugin-openapi.seshuk.im/guides/field-metadata) -- [CLI — `openapi:generate`](https://payload-plugin-openapi.seshuk.im/cli/generate) -- [Examples](https://payload-plugin-openapi.seshuk.im/guides/examples) +- [Quick start](https://payload-plugin-openapi.seshuk.im/v1/quick-start) +- [Configuration reference](https://payload-plugin-openapi.seshuk.im/v1/configuration/overview) +- [Filters](https://payload-plugin-openapi.seshuk.im/v1/configuration/filters) +- [Security marking](https://payload-plugin-openapi.seshuk.im/v1/configuration/security-marking) +- [Docs UI — Scalar & Swagger](https://payload-plugin-openapi.seshuk.im/v1/configuration/docs-ui) +- [Documenting custom endpoints](https://payload-plugin-openapi.seshuk.im/v1/guides/custom-endpoints) +- [Field metadata](https://payload-plugin-openapi.seshuk.im/v1/guides/field-metadata) +- [CLI — `openapi:generate`](https://payload-plugin-openapi.seshuk.im/v1/cli/generate) +- [Examples](https://payload-plugin-openapi.seshuk.im/v1/guides/examples) ## For plugin authors -If you maintain a Payload plugin, attach a `custom.openapi` Operation Object to the endpoints you add and they show up in the generated spec — no dependency on this package, and nothing for your users to wire up. The same key works on fields. If this plugin isn't installed, the metadata is inert. See [For plugin authors](https://payload-plugin-openapi.seshuk.im/guides/plugin-authors). +If you maintain a Payload plugin, attach a `custom.openapi` Operation Object to the endpoints you add and they show up in the generated spec — no dependency on this package, and nothing for your users to wire up. The same key works on fields. If this plugin isn't installed, the metadata is inert. See [For plugin authors](https://payload-plugin-openapi.seshuk.im/v1/guides/plugin-authors). + +## Telemetry + +The plugin sends an anonymous, opt-out usage report once per day: plugin, Payload, and Node versions plus which features are enabled. It never sends secrets, IPs, keys, URLs, paths, hostnames, collection names, or anything from your spec. A one-time notice prints on first run. + +Opt out with `telemetry: false`, `DO_NOT_TRACK=1`, or `OPENAPI_TELEMETRY_DISABLED=1` (also disabled automatically in CI and when `payload.config.telemetry` is `false`). See [Telemetry](https://payload-plugin-openapi.seshuk.im/v1/configuration/telemetry) for the full list of what is and isn't collected. ## Related plugins -- **[@seshuk/payload-storage-bunny](https://github.com/maximseshuk/payload-storage-bunny)** — store files and stream video from Payload CMS on Bunny's global CDN. -- **[@seshuk/payload-plugin-media-preview](https://github.com/maximseshuk/payload-plugin-media-preview)** — preview images, video, audio, and documents directly in the Payload admin panel. +- **[@seshuk/payload-storage-bunny](https://github.com/maximseshuk/payload-storage-bunny)** — store files and stream video from Payload on Bunny.net's global CDN. +- **[@seshuk/payload-plugin-media-preview](https://github.com/maximseshuk/payload-plugin-media-preview)** — preview images, video, audio and documents in the Payload admin panel. +- **[@seshuk/payload-plugin-janitor](https://github.com/maximseshuk/payload-plugin-janitor)** — find orphaned files and unused documents, then delete only what you approve. +- **[@seshuk/payload-plugin-sitemap](https://github.com/maximseshuk/payload-plugin-sitemap)** — sitemaps for Payload, stored with any storage adapter. ## Support -Bug reports, feature requests, and questions go to [GitHub Issues](https://github.com/maximseshuk/payload-plugin-openapi/issues). For Payload itself, see the [Payload CMS docs](https://payloadcms.com/docs) and [Discord](https://discord.gg/payloadcms). +Bug reports, feature requests, and questions go to [GitHub Issues](https://github.com/maximseshuk/payload-plugin-openapi/issues). For Payload itself, see the [Payload docs](https://payloadcms.com/docs) and [Discord](https://discord.gg/payloadcms). ## License @@ -106,6 +148,6 @@ MIT — see [LICENSE](LICENSE). ## Credits -Built by [Maxim Seshuk](https://github.com/maximseshuk) for the Payload CMS community. +Built by [Maxim Seshuk](https://github.com/maximseshuk) for the Payload community. If this plugin saves you time, you can [buy me a coffee](https://ko-fi.com/seshuk) ☕ diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a57bafa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported versions + +| Version | Payload | Support | +| ------- | ------- | ---------------------------------------------- | +| 1.x | 4.x | Security fixes and bug fixes | +| 0.x | 3.x | Critical and high-severity security fixes only | + +Fixes ship in the latest release of each supported major version. Upgrade to it before you report a problem. + +## Reporting a vulnerability + +Please don't open a public issue for security problems. Report them privately through [GitHub security advisories](https://github.com/maximseshuk/payload-plugin-openapi/security/advisories/new). + +Include the plugin version, your Payload version, the affected options, and steps to reproduce. Once a fix is released, the advisory is published with credit to you, unless you'd rather stay anonymous. diff --git a/cliff.toml b/cliff.toml deleted file mode 100644 index 0f38cd5..0000000 --- a/cliff.toml +++ /dev/null @@ -1,72 +0,0 @@ -[changelog] -header = "" -body = """ -{%- if version %} -{%- if previous and previous.version %} -## [{{ version }}]({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }}) -{%- else %} -## [{{ version }}]({{ self::remote_url() }}/releases/tag/{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }}) -{%- endif %} -{%- else %} -## Unreleased -{%- endif %} -{%- for group, commits in commits | group_by(attribute="group") %} - -### {{ group | striptags | trim | upper_first }} -{% for commit in commits %} -- {% if commit.scope %}**{{ commit.scope }}:** {% endif -%} -{{ commit.message | split(pat="\n") | first | upper_first }}\ -{%- if commit.remote.pr_number %} ([#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }})){% endif %} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ -{%- if commit.remote.username %} by @{{ commit.remote.username }}{% endif %}\ -{%- if commit.breaking and commit.breaking_description and commit.breaking_description != commit.message %} - - {{ commit.breaking_description | indent(prefix=" ", first=false) }} -{%- endif %} -{%- endfor %} -{%- endfor %} -{%- if remote.contributors %} - -### 🤝 Contributors - -{% for contributor in remote.contributors -%} -{%- if contributor.username and not contributor.username is matching("\\[bot\\]$") %} -- @{{ contributor.username }}{% if contributor.is_first_time %} (first contribution){% endif %} -{%- endif -%} -{% endfor %} -{%- endif %} - -{% macro remote_url() -%} -https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} -{%- endmacro %} -""" -trim = true -footer = "" - -[git] -conventional_commits = true -filter_unconventional = false -split_commits = false -commit_parsers = [ - { field = "breaking", pattern = "true", group = "⚠️ Breaking Changes" }, - { message = "^feat", group = "🚀 Features" }, - { message = "^fix", group = "🐛 Bug Fixes" }, - { message = "^perf", group = "⚡ Performance" }, - { message = "^refactor", group = "🛠 Refactors" }, - { message = "^docs", group = "📚 Documentation" }, - { message = "^style", group = "🎨 Styles" }, - { message = "^test", group = "🧪 Tests" }, - { message = "^build", group = "🔨 Build" }, - { message = "^ci", group = "⚙️ CI" }, - { message = "^chore\\(release\\)", skip = true }, - { message = "^chore", group = "🏡 Chores" }, - { message = "^Merge", skip = true }, - { message = ".*", group = "Other" }, -] -filter_commits = false -topo_order = false -sort_commits = "oldest" -protect_breaking_commits = true - -[remote.github] -owner = "maximseshuk" -repo = "payload-plugin-openapi" diff --git a/docs/cli/generate.mdx b/docs/cli/generate.mdx index b321d5b..2b52251 100644 --- a/docs/cli/generate.mdx +++ b/docs/cli/generate.mdx @@ -88,13 +88,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: 20 - cache: pnpm - - run: pnpm install --frozen-lockfile - - run: pnpm payload openapi:generate --server https://api.example.com --out openapi.json + cache: npm + - run: npm ci + - run: npx payload openapi:generate --server https://api.example.com --out openapi.json - name: Fail on spec drift run: git diff --exit-code openapi.json ``` diff --git a/docs/custom.css b/docs/custom.css index 1bee3d3..ecadd5a 100644 --- a/docs/custom.css +++ b/docs/custom.css @@ -1,378 +1,327 @@ -/* ============================================================= */ -/* Custom landing page (hero) — ported from the rspress home page */ -/* ============================================================= */ - -:root { - --oa-font-base: 'Geist', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --oa-font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - - --oa-brand: #10b981; - --oa-bg: #ffffff; - --oa-divider: #dcebe3; - --oa-text-3: #9db3a9; - --oa-hero-secondary: #14211c; - --oa-hero-title-bg: linear-gradient(85.19deg, #10b981 0%, #22d3ee 100%); - --oa-home-bg: radial-gradient(120% 90% at 50% 0%, #d3f6ea 0%, #f0fbf6 50%, #ffffff 100%); -} - -.dark { - --oa-brand: #34d399; - --oa-bg: #08100e; - --oa-divider: rgba(255, 255, 255, 0.1); - --oa-text-3: #6b7f77; - --oa-hero-secondary: #f2faf6; - --oa-home-bg: radial-gradient(120% 90% at 50% 0%, #0c2a24 0%, #0a1d18 45%, #08100e 100%); -} - -/* Full-bleed landing: hide the top navbar on the custom home page only */ -body:has(.rp-home) #navbar, -body:has(.rp-home) #navbar-transition, -body:has(.rp-home) #topbar { +body:has(.oa-home) #navbar, +body:has(.oa-home) #navbar-transition, +body:has(.oa-home) #topbar { display: none !important; } -/* Drop any top offset the layout reserved for the (now hidden) navbar */ -body:has(.rp-home) #content-area, -body:has(.rp-home) main { +body:has(.oa-home) #content-area, +body:has(.oa-home) :has(> #content-area), +body:has(.oa-home) main { padding-top: 0 !important; margin-top: 0 !important; } -/* ---------------------------------- */ -/* Home shell + structural grid */ -/* ---------------------------------- */ - -.rp-home { +.oa-home { + --oa-bg: rgb(var(--background-light)); + --oa-text: rgb(var(--gray-900)); + --oa-muted: rgb(var(--gray-700)); + --oa-line: rgb(var(--gray-950) / 0.1); + --oa-cross: rgb(var(--gray-950) / 0.35); + --oa-l: linear-gradient(transparent, var(--oa-line) 96px, var(--oa-line) calc(100% - 64px), transparent); + display: grid; + max-width: 92rem; + margin: 0 auto; + padding: 0 48px; + background: + var(--oa-l) 0 100% / 1px calc(100% - 80px), + var(--oa-l) calc(25% + 0.25px) 100% / 1px calc(100% - 160px), + var(--oa-l) calc(50% + 0.5px) 100% / 1px calc(100% - 200px), + var(--oa-l) calc(75% + 0.75px) 100% / 1px calc(100% - 160px), + var(--oa-l) 100% 100% / 1px calc(100% - 80px); + background-repeat: no-repeat; + background-origin: content-box; + background-clip: content-box; +} + +.dark .oa-home { + --oa-bg: rgb(var(--background-dark)); + --oa-text: rgb(var(--gray-200)); + --oa-muted: rgb(var(--gray-400)); + --oa-line: rgb(255 255 255 / 0.1); + --oa-cross: rgb(255 255 255 / 0.35); +} + +.oa-hero { position: relative; - min-height: 100vh; - min-height: 100dvh; - font-family: var(--oa-font-base); - background: var(--oa-home-bg); - overflow: hidden; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: 1fr auto; + padding: 232px 0 0; + margin-bottom: 96px; } -.rp-home-grid { +.oa-hero::before, +.oa-hero::after { + content: ''; position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - background-image: - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='800'%3E%3Cpath d='M100 96v8M96 100h8M500 296v8M496 300h8M300 696v8M296 700h8' stroke='rgba(8,58,40,0.09)' stroke-width='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cline x1='100' y1='0' x2='100' y2='200' stroke='rgba(8,58,40,0.035)' stroke-width='1'/%3E%3Cline x1='0' y1='100' x2='200' y2='100' stroke='rgba(8,58,40,0.035)' stroke-width='1'/%3E%3C/svg%3E"); - background-size: - 600px 800px, - 200px 200px; - background-position: center top; - -webkit-mask-image: radial-gradient(120% 85% at 50% 0%, #000 0%, #000 42%, transparent 78%); - mask-image: radial-gradient(120% 85% at 50% 0%, #000 0%, #000 42%, transparent 78%); + bottom: -5px; + left: calc(25% - 4px); + width: 9px; + height: 9px; + background: + linear-gradient(var(--oa-cross) 0 0) center / 9px 1px no-repeat, + linear-gradient(var(--oa-cross) 0 0) center / 1px 9px no-repeat; } -.dark .rp-home-grid { - background-image: - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='800'%3E%3Cpath d='M100 96v8M96 100h8M500 296v8M496 300h8M300 696v8M296 700h8' stroke='rgba(255,255,255,0.07)' stroke-width='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cline x1='100' y1='0' x2='100' y2='200' stroke='rgba(255,255,255,0.028)' stroke-width='1'/%3E%3Cline x1='0' y1='100' x2='200' y2='100' stroke='rgba(255,255,255,0.028)' stroke-width='1'/%3E%3C/svg%3E"); +.oa-hero::after { + left: calc(75% - 4px); } -/* ---------------------------------- */ -/* Hero */ -/* ---------------------------------- */ +.oa-copy { + grid-column: 1 / 3; + align-self: end; + padding: 24px 24px 40px; + box-shadow: inset 1px 0 var(--oa-line); + background: var(--oa-bg); +} -@keyframes rp-hero-float { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-14px); - } +.oa-title { + margin: 0; + font-size: clamp(2.5rem, 1.5rem + 2.8vw, 4rem); + font-weight: 600; + line-height: 1.05; + letter-spacing: -0.035em; + color: var(--oa-text); + text-wrap: balance; } -@keyframes rp-hero-glow { - 0%, - 100% { - opacity: 0.5; - transform: scale(1); - } - 50% { - opacity: 0.85; - transform: scale(1.08); - } +.oa-title span { + color: rgb(var(--primary)); } -.rp-hero { - position: relative; - z-index: 1; - min-height: 82vh; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: clamp(28px, 5vh, 64px) 24px clamp(24px, 4vh, 48px); - font-family: var(--oa-font-base); - background: transparent; +.oa-sub { + max-width: 34rem; + margin: 20px 0 0; + font-size: 1.125rem; + line-height: 1.75rem; + color: var(--oa-muted); + text-wrap: pretty; } -.rp-hero-inner { - position: relative; - display: flex; - flex-direction: row-reverse; - align-items: center; - justify-content: center; - gap: 64px; - max-width: 1040px; - flex-wrap: wrap; +.oa-actions { + grid-column: 1 / 3; + display: grid; + grid-template-columns: 1fr 1fr; + border-top: 1px solid var(--oa-line); + box-shadow: inset 1px 0 var(--oa-line); + background: var(--oa-bg); } -.rp-hero-mark { - position: relative; - width: clamp(230px, 30vw, 360px); - height: auto; - aspect-ratio: 340 / 400; +.oa-row { display: flex; align-items: center; - justify-content: center; + justify-content: space-between; + gap: 16px; + min-width: 0; + height: 64px; + padding: 0 24px; + border: 0; + border-bottom: 1px solid var(--oa-line); + font: inherit; + font-weight: 500; + color: var(--oa-text); + text-align: left; + text-decoration: none; + background: none; + cursor: pointer; + transition: background-color 0.15s ease; } -.rp-hero-glow { - position: absolute; - width: 68%; - height: 62%; - border-radius: 50%; - filter: blur(14px); - background: radial-gradient(closest-side, rgba(34, 211, 238, 0.4), rgba(16, 185, 129, 0.16) 60%, transparent 72%); - animation: rp-hero-glow 4.4s ease-in-out infinite; +.oa-row:hover { + background: color-mix(in srgb, var(--oa-text) 4%, transparent); } -.rp-hero-float { - position: relative; - width: 100%; - height: 100%; - animation: rp-hero-float 5s ease-in-out infinite; +.oa-row:focus-visible { + outline: 2px solid rgb(var(--primary)); + outline-offset: -2px; } -.rp-hero-float img { - display: block; - width: 100%; - height: 100%; +.oa-actions a + a { + border-left: 1px solid var(--oa-line); } -.rp-hero-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: left; - gap: clamp(14px, 2.2vh, 22px); - max-width: 520px; +.oa-row-install { + grid-column: 1 / -1; } -.rp-hero-badge { - display: inline-flex; - align-items: center; - font-family: var(--oa-font-mono); - font-size: 12px; - letter-spacing: 0.18em; - text-transform: uppercase; - color: var(--oa-hero-secondary); - border: 1px solid var(--oa-divider); - background: color-mix(in oklab, var(--oa-bg) 55%, transparent); - padding: 6px 14px; - border-radius: 999px; +.oa-row code { + overflow: hidden; + padding: 0; + border: 0; + font-family: var(--font-paper-mono), ui-monospace, monospace; + font-size: 0.875rem; + font-weight: 400; + color: inherit; + white-space: nowrap; + text-overflow: ellipsis; + background: none; } -.rp-hero-title { - margin: 0; - font-size: clamp(40px, 6vw, 64px); - font-weight: 700; - line-height: 1.06; - letter-spacing: -0.03em; - color: var(--oa-hero-secondary); +.oa-row code span { + color: var(--oa-muted); } -.rp-hero-grad { - background: var(--oa-hero-title-bg); - background-clip: text; - -webkit-background-clip: text; - color: transparent; - -webkit-text-fill-color: transparent; +.oa-row-icon { + flex: none; + width: 16px; + height: 16px; + color: var(--oa-muted); + transition: + color 0.15s ease, + transform 0.15s ease; } -.rp-hero-sub { - margin: 0; - max-width: 560px; - font-size: clamp(15px, 1.6vw, 17px); - line-height: 1.65; - color: var(--oa-hero-secondary); - opacity: 0.75; +.oa-row-icon svg { + display: block; + width: 100%; + height: 100%; } -.rp-hero-actions { - display: flex; - gap: 12px; - flex-wrap: wrap; - justify-content: flex-start; - margin-top: 8px; +.oa-row:hover .oa-row-icon { + color: rgb(var(--primary)); } -.rp-hero-btn { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 13px 24px; - border-radius: 10px; - font-size: 15px; - font-weight: 600; - text-decoration: none; - transition: - box-shadow 0.2s ease, - border-color 0.15s ease, - background-color 0.15s ease, - color 0.15s ease; +a.oa-row:hover .oa-row-icon { + transform: translate(2px, -2px); } -.rp-hero-btn-primary { - position: relative; - padding-right: 50px; - color: #fff; - background: var(--oa-hero-title-bg); +.oa-mark { + grid-column: 3 / 5; + grid-row: 1 / 3; + display: grid; + place-items: center; + border: 1px solid var(--oa-line); + background: var(--oa-bg); } -.rp-hero-btn-primary::after { - content: ''; - position: absolute; - top: 50%; - right: 22px; - width: 20px; - height: 13px; - transform: translateY(-50%); - background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSIgdmlld0JveD0iMCAwIDMwIDIwIj48cGF0aCBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMyIgZD0ibTE4LjQ4NSAxLjUzMSA4LjQ1MyA4LjQ1My04LjQ4NSA4LjQ4NU04LjQ4NSAxLjUzMWw4LjQ1MyA4LjQ1My04LjQ4NSA4LjQ4NSIvPjwvc3ZnPg==') - no-repeat center / contain; - transition: transform 0.2s ease; +.oa-mark img { + display: block; + width: min(64%, 320px); + height: auto; + margin: 0; } -.rp-hero-btn-primary:hover { - box-shadow: 0 8px 24px rgba(16, 185, 129, 0.35); +.oa-mark [data-rmiz], +.oa-mark [data-rmiz] *, +.oa-mark img { + pointer-events: none; } -.rp-hero-btn-primary:hover::after { - transform: translateY(-50%) translateX(4px); -} +@media (max-width: 1199px) { + .oa-home { + background: + var(--oa-l) 0 100% / 1px calc(100% - 48px), + var(--oa-l) calc(50% + 0.5px) 100% / 1px calc(100% - 96px), + var(--oa-l) 100% 100% / 1px calc(100% - 48px); + background-repeat: no-repeat; + background-origin: content-box; + background-clip: content-box; + } -.rp-hero-btn-ghost { - color: var(--oa-hero-secondary); - background: color-mix(in oklab, var(--oa-bg) 70%, transparent); - border: 1px solid var(--oa-divider); -} + .oa-hero { + grid-template-columns: repeat(2, 1fr); + padding-top: 128px; + } -.rp-hero-btn-ghost:hover { - border-color: var(--oa-text-3); - background: var(--oa-bg); -} + .oa-hero::before { + left: calc(50% - 4px); + } -.rp-hero-cmd { - position: relative; - display: inline-block; - font-family: var(--oa-font-mono); - font-size: 13.5px; - color: var(--oa-hero-secondary); - background: color-mix(in oklab, var(--oa-bg) 70%, transparent); - border: 1px solid var(--oa-divider); - padding: 10px 16px; - border-radius: 8px; - margin-top: 6px; - cursor: pointer; - transition: border-color 0.15s ease; -} + .oa-hero::after { + display: none; + } -.rp-hero-cmd:hover { - border-color: var(--oa-text-3); -} + .oa-copy, + .oa-actions { + grid-column: 1 / 2; + } -.rp-hero-cmd span { - color: var(--oa-brand); -} + .oa-actions { + grid-template-columns: 1fr; + } -/* On click the command swaps to a confirmation in place. Real elements, not ::after — - Mintlify's prose styles suppress generated content inside . */ -.rp-hero-cmd-text, -.rp-hero-copied { - transition: opacity 0.18s ease; -} + .oa-actions a + a { + border-left: 0; + } -.rp-hero-cmd .rp-hero-copied { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - gap: 7px; - opacity: 0; - pointer-events: none; - white-space: nowrap; + .oa-mark { + grid-column: 2 / 3; + } } -.rp-hero-cmd .rp-hero-copied, -.rp-hero-cmd .rp-hero-copied span { - color: var(--oa-brand); -} +@media (max-width: 1023px) { + .oa-home { + padding: 0 16px; + } -/* Checkmark drawn in CSS so the label stays plain text */ -.rp-hero-cmd .rp-hero-copied::before { - content: ''; - width: 12px; - height: 7px; - border-left: 2px solid currentcolor; - border-bottom: 2px solid currentcolor; - transform: rotate(-45deg) translate(1px, -2px); -} + .oa-hero { + grid-template-rows: auto; + padding: 96px 0 0; + margin-bottom: 64px; + } -.rp-hero-cmd[data-copied] { - border-color: color-mix(in oklab, var(--oa-brand) 55%, var(--oa-divider)); - background: color-mix(in oklab, var(--oa-brand) 7%, var(--oa-bg)); -} + .oa-copy, + .oa-actions, + .oa-mark { + grid-column: 1 / -1; + grid-row: auto; + } -.rp-hero-cmd[data-copied] .rp-hero-cmd-text { - opacity: 0; -} + .oa-copy, + .oa-actions { + box-shadow: + inset 1px 0 var(--oa-line), + inset -1px 0 var(--oa-line); + } -.rp-hero-cmd[data-copied] .rp-hero-copied { - opacity: 1; -} + .oa-copy { + padding: 32px 20px; + } -/* Hero mark is decorative — kill Mintlify's image zoom/lightbox on the home page */ -.rp-hero-float [data-rmiz], -.rp-hero-float [data-rmiz] *, -.rp-hero-float img { - pointer-events: none; + .oa-row { + padding: 0 20px; + } + + .oa-mark { + order: -1; + height: 240px; + border-bottom: 0; + } + + .oa-mark img { + width: auto; + height: 168px; + } } -@media (max-width: 960px) { - .rp-hero-inner { - gap: 32px; +@media (max-width: 767px) { + .oa-row code { + font-size: 0.7rem; } - .rp-hero-copy { - align-items: center; - text-align: center; + .oa-row code span { + display: none; } - .rp-hero-copy .rp-hero-actions { - justify-content: center; + .oa-home { + background: + var(--oa-l) 0 100% / 1px calc(100% - 32px), + var(--oa-l) 100% 100% / 1px calc(100% - 32px); + background-repeat: no-repeat; + background-origin: content-box; + background-clip: content-box; } - .rp-hero-mark { - width: clamp(190px, 36vw, 290px); + .oa-hero { + padding-top: 64px; } -} -@media (max-height: 720px) { - .rp-hero-mark { - width: min(clamp(230px, 30vw, 360px), 42vh); + .oa-hero::before { + left: -4px; } -} -@media (prefers-reduced-motion: reduce) { - .rp-hero-float, - .rp-hero-glow { - animation: none; + .oa-hero::after { + display: block; + left: calc(100% - 5px); } } diff --git a/docs/docs.json b/docs/docs.json index 44f8b30..48eaf28 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -4,9 +4,9 @@ "name": "OpenAPI Plugin for Payload CMS", "description": "OpenAPI 3.0/3.1/3.2 specs generated from your Payload CMS config, with Scalar or Swagger UI", "colors": { - "primary": "#10b981", - "light": "#34d399", - "dark": "#059669" + "primary": "#0e9f6e", + "light": "#26eca9", + "dark": "#0a7551" }, "favicon": "/favicon.svg", "logo": { @@ -38,47 +38,103 @@ "display": "header" }, "navigation": { - "tabs": [ + "versions": [ { - "tab": "Documentation", - "groups": [ + "version": "v0", + "default": true, + "tabs": [ { - "group": "Documentation", - "pages": ["introduction", "quick-start", "changelog"] - }, - { - "group": "Configuration", - "pages": [ - "configuration/overview", - "configuration/metadata", - "configuration/openapi-version", - "configuration/filters", - "configuration/security-marking", - "configuration/interactive-auth", - "configuration/caching", - "configuration/extensions", - "configuration/docs-ui" + "tab": "Documentation", + "groups": [ + { + "group": "Documentation", + "pages": ["introduction", "quick-start", "changelog"] + }, + { + "group": "Configuration", + "pages": [ + "configuration/overview", + "configuration/metadata", + "configuration/openapi-version", + "configuration/filters", + "configuration/security-marking", + "configuration/interactive-auth", + "configuration/caching", + "configuration/extensions", + "configuration/docs-ui" + ] + }, + { + "group": "CLI", + "pages": ["cli/generate"] + }, + { + "group": "Guides", + "pages": [ + "guides/custom-endpoints", + "guides/field-metadata", + "guides/plugin-authors", + "guides/static-spec", + "guides/programmatic-use", + "guides/i18n", + "guides/examples" + ] + }, + { + "group": "Reference", + "pages": ["reference/exports"] + } ] - }, - { - "group": "CLI", - "pages": ["cli/generate"] - }, + } + ] + }, + { + "version": "v1", + "tag": "Beta", + "tabs": [ { - "group": "Guides", - "pages": [ - "guides/custom-endpoints", - "guides/field-metadata", - "guides/plugin-authors", - "guides/static-spec", - "guides/programmatic-use", - "guides/i18n", - "guides/examples" + "tab": "Documentation", + "groups": [ + { + "group": "Documentation", + "pages": ["v1/introduction", "v1/quick-start", "v1/upgrade-guide", "v1/changelog"] + }, + { + "group": "Configuration", + "pages": [ + "v1/configuration/overview", + "v1/configuration/info", + "v1/configuration/openapi-version", + "v1/configuration/filters", + "v1/configuration/security-marking", + "v1/configuration/interactive-auth", + "v1/configuration/caching", + "v1/configuration/extensions", + "v1/configuration/docs-ui", + "v1/configuration/telemetry" + ] + }, + { + "group": "CLI", + "pages": ["v1/cli/generate"] + }, + { + "group": "Guides", + "pages": [ + "v1/guides/custom-endpoints", + "v1/guides/field-metadata", + "v1/guides/plugin-authors", + "v1/guides/static-spec", + "v1/guides/programmatic-use", + "v1/guides/i18n", + "v1/guides/examples" + ] + }, + { + "group": "Reference", + "pages": ["v1/reference/endpoints", "v1/reference/exports"] + } ] - }, - { - "group": "Reference", - "pages": ["reference/exports"] } ] } diff --git a/docs/favicon.svg b/docs/favicon.svg index 7c3b1f1..0215b4c 100644 --- a/docs/favicon.svg +++ b/docs/favicon.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - \ No newline at end of file +OpenAPI for Payload \ No newline at end of file diff --git a/docs/guides/field-metadata.mdx b/docs/guides/field-metadata.mdx index 449d06c..c9a878b 100644 --- a/docs/guides/field-metadata.mdx +++ b/docs/guides/field-metadata.mdx @@ -144,9 +144,7 @@ export const Posts: CollectionConfig = { security: { read: true, create: false, update: false, delete: false }, }, }, - fields: [ - /* … */ - ], + fields: [/* … */], } ``` diff --git a/docs/hero-mark.svg b/docs/hero-mark.svg index 7f913b2..94415c8 100644 --- a/docs/hero-mark.svg +++ b/docs/hero-mark.svg @@ -1,24 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/index.mdx b/docs/index.mdx index 159ce14..4ce20d8 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,56 +1,96 @@ --- -title: 'Documentation' -description: 'Generate an OpenAPI 3.0/3.1/3.2 specification from your Payload CMS config and serve it with Scalar or Swagger UI.' +title: 'Payload OpenAPI' +description: 'Generate an OpenAPI 3.0, 3.1 or 3.2 spec from your Payload config and serve Scalar or Swagger UI.' mode: 'custom' --- -
-
-
-
-
-
-
- Payload OpenAPI -
-
-
- Payload plugin -

- Your Payload API, documented -

-

- Generate an OpenAPI 3.0, 3.1, or 3.2 specification straight from your Payload config and serve it through - Scalar or Swagger UI — always in sync with your collections. -

- - { - navigator.clipboard?.writeText('npm install @seshuk/payload-plugin-openapi') - const el = e.currentTarget - el.setAttribute('data-copied', '') - setTimeout(() => el.removeAttribute('data-copied'), 1400) - }} +export const InstallRow = () => { + const command = 'npm i @seshuk/payload-plugin-openapi' + const [copied, setCopied] = useState(false) + const timer = useRef() + useEffect(() => () => clearTimeout(timer.current), []) + const copy = () => { + if (!navigator.clipboard) return + navigator.clipboard.writeText(command).then( + () => { + clearTimeout(timer.current) + setCopied(true) + timer.current = setTimeout(() => setCopied(false), 1500) + }, + () => {}, + ) + } + return ( + + ) +} + +
+
+
+

+ Your {'Payload\u00a0API,'} +
+ documented +

+

+ Generate an OpenAPI 3.0, 3.1 or 3.2 spec from your Payload config and serve it with Scalar or Swagger UI. It + always stays in sync with your collections. +

+
+
+ + + + + Read docs + + +
+
+
-
+
diff --git a/docs/logo.svg b/docs/logo.svg index 2451c00..5be190e 100644 --- a/docs/logo.svg +++ b/docs/logo.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - \ No newline at end of file +OpenAPI for Payload \ No newline at end of file diff --git a/docs/v1/changelog.mdx b/docs/v1/changelog.mdx new file mode 100644 index 0000000..da8ceaa --- /dev/null +++ b/docs/v1/changelog.mdx @@ -0,0 +1,88 @@ +--- +title: 'Changelog' +description: 'Release history for the OpenAPI plugin for Payload CMS.' +--- + +Notable changes per release. For the full commit-level history, see the [GitHub releases](https://github.com/maximseshuk/payload-plugin-openapi/releases). + + + +**Breaking changes** + +- Payload 4.0.0-canary.38, Next.js 16.4+ and Node.js 24.15+ are required. Payload 3 users stay on `0.x`. +- `openapi:generate` is now a Payload 4 CLI command. The flags are the same. +- The access endpoint is now `GET /api/access`, not `GET /api//access`. This is the path Payload serves. It still needs `includeAdminAuth`. +- Top-level `config.endpoints` are now documented under the API route (`/api/health`, not `/health`). This is the path Payload serves. +- Payload 4 turns versions on by default, so version endpoints now appear for every collection and global without `versions: false`. +- The internal `payload-jobs-stats` global is hidden unless `includeSystem` is on. +- Renamed options: `metadata` is now `info`, `specEndpoint` is now `path`, `interactiveAuth.endpoint` is now `interactiveAuth.path`, and the docs UI `specEndpoint` is now `specURL`. The plugin throws at boot on an old name. See [Upgrade guide](/v1/upgrade-guide). +- `custom.openapi.security` on a collection or global takes `'public'` or `'secured'`, not `true` or `false`. The plugin throws at boot on a boolean. +- `servers` no longer comes from the request `Host` header, because a client can fake it. It comes from `servers`, `serverURL`, or a host listed in [`trustedHosts`](/v1/configuration/info#trusted-hosts). +- An extension `transform` that throws now fails the spec request instead of being skipped. +- An extension `transform` gets one object, `({ doc, payload, options, ... })`, not `(doc, ctx)`. The plugin throws at boot on a `transform` with two parameters. +- `securityWhen` is replaced by `security`. It returns `'public'`, `'secured'`, a security requirement array, or `undefined`, and it gets the `detected` marking. +- `filters.excludeWhen` is removed. Put the function in `filters.excludeOperations` instead. A function drops an operation only when it returns `true`, and it can be `async`. +- `excludeOperations` and `security` now also apply to custom, jobs and system endpoints. `OperationContext.kind` can be `'custom'`, `'jobs'` or `'system'`, and `slug` is then not set. A rule without `kind`, such as `{ method: 'delete' }`, now also drops matching custom and system operations. + +**New features** + +- New [endpoints reference](/v1/reference/endpoints) page. +- Payload 4 upload instructions: `POST /api/upload-instructions` and `PUT`/`DELETE /api/upload-instructions/{uploadId}`, when you have an upload collection. +- `GET /api//file/{filename}` for upload collections. +- `POST /api//{id}/api-key/reveal` when `useAPIKey: { reveal: true }`. +- `POST /api//access/{id}` (and without `{id}`) and `POST /api/globals//access` return the current user's permissions for one document. They appear with `includeAdminAuth`, like `GET /api/access`. The response lists `validate` next to `create`, `read`, `update` and `delete`. +- `POST /api//validate`, `POST /api//{id}/validate` and `POST /api/globals//validate` check data without saving it and return `{ valid, errors }`. See [Validate](/v1/reference/endpoints#validate). They are marked from `access.validate`, which follows `update`. `custom.openapi.security` takes a `validate` key, which falls back to `update`. +- `access` on `openapi()`, `scalar()` and `swaggerUi()` limits who can read the spec and the docs page. See [Limiting who can read the spec](/v1/configuration/overview#limiting-who-can-read-the-spec). +- `interactiveAuth.collection` picks the auth collection the docs login uses. +- The docs page title is `info.title`. +- `info` takes the full OpenAPI Info Object (`summary`, `termsOfService`, `contact`, `license`, `x-*`), and `info.description` can be localized. See [Info and servers](/v1/configuration/info). +- `servers` sets the spec base URLs, as a list or a function of the request. +- Extension `transform` hooks can be `async` and get `payload` and the resolved `options`. +- `custom.openapi` on collections, globals and fields is typed. +- The docs UI loads pinned Scalar and Swagger UI versions with Subresource Integrity. See [Pinned versions and SRI](/v1/configuration/docs-ui#pinned-versions-and-sri). +- `openapi()`, `scalar()` and `swaggerUi()` are built with Payload's `definePlugin`, so each one carries a `slug` and its `options`. See [Plugin slugs](/v1/reference/exports#plugin-slugs). +- [Folders and tags](/v1/reference/endpoints#folders-and-tags) from Payload 4. Folder and tag collections are documented like other collections. Their read operations list the `computeHierarchyPaths` query param, and `_h_slugPath` and `_h_titlePath` are read-only and kept out of bodies and `where`. +- Anonymous, opt-out usage [telemetry](/v1/configuration/telemetry): plugin, Payload and Node versions and which features are on. Turn it off with `telemetry: false`, `OPENAPI_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`. + +**Fixes** + +- With `disableLocalStrategy`, `me`, `logout`, `refresh-token` and `init` stay in the spec. Only login, password, registration, unlock and verify endpoints are removed. +- `POST /api//unlock` is documented for every auth collection, as Payload mounts it. Before, it needed `maxLoginAttempts`. +- Login, forgot-password and unlock bodies now follow `loginWithUsername` as Payload does: `email` by default, `username` when email login is off, and `email` or `username` when `allowEmailLogin` is on. Before, forgot-password and unlock always asked for `email`, and `allowEmailLogin` was ignored. +- After you log in on the docs page, requests sent from it include your token: every secured operation now lists the `PayloadLogin` scheme too. With `serve: false` the spec no longer lists `PayloadLogin` with a token URL that does not exist. +- The docs UI loads the spec from the `openapi()` route, including a custom `path`. Before, it always used `/api/openapi.json`. +- Version reads are marked from `readVersions` and version restore from `update`. The jobs endpoints follow `jobs.access.run`, and the staged upload `PUT` and `DELETE` are secured. Before, all of them were marked public. +- `expires_in` from the interactive auth endpoint is the token lifetime in seconds, not the expiry timestamp. +- OpenAPI 3.0 output is now valid when a schema has an empty `required` list (for example upload `variants`) or a field allows several types, such as a `json` field, and 3.0/3.1 output no longer keeps the 3.2 tag fields `summary`, `kind` and `parent`. +- Create, update, delete and duplicate operations now list the query params Payload reads for them: `depth`, `locale`, `fallback-locale`, `select`, `populate`, `draft` and `trash`. The global update lists them too. Bulk update and delete keep `where`, and bulk update also lists `limit` and `sort`. +- Write operations also list `autosave`, `publishAllLocales`, `unpublishAllLocales`, `overrideLock` and `selectedLocales[]` where Payload reads them. `autosave` appears when autosave is on, the two locale params when drafts keep their status per locale, `overrideLock` on collection update and delete unless `lockDocuments` is `false`, and `selectedLocales[]` on duplicate when localization is on. +- A polymorphic relationship (`relationTo` with several collections) is written as `{ relationTo, value }` in create and update bodies. Before, the spec asked for a bare ID, which Payload rejects. +- Relationships and uploads inside groups, named tabs, arrays and blocks are written as IDs in create and update bodies, like top-level ones. Before, they showed the populated document. The bodies now follow the input types Payload generates: join and virtual fields and `createdAt`/`updatedAt` are left out, a field with a `defaultValue` is optional, and an optional relationship accepts `null`. Fields that can never be written over REST, such as Payload's `createdBy` and `updatedBy`, are left out too. +- The internal `payload-kv`, `payload-llm-instructions` and `payload-query-presets` collections are hidden unless `includeSystem` is on. +- A join over several collections lists `relationTo` as required in its items. Before, it listed `collectionSlug`, which the items do not have. This affects every folder and tag join. +- `includeSystem` no longer covers `payload-folders`. Payload 4 does not create that collection. +- The global update response is `{ message, result }` and a restored global version is `{ doc, message }`, as Payload sends them. Before, both showed the bare document. Collection create is documented with status `201`, not `200`. + + + + + +**New features** + +- Per-operation [security marking](/v1/configuration/security-marking) derived from your access functions: each operation is probed as an anonymous request and marked public or secured accordingly. +- Overrides for the detected marking: `custom.openapi.security` per collection or global, and the `securityWhen` option across the whole document. + + + + + +Initial release. + +- OpenAPI 3.0/3.1/3.2 document built from the sanitized Payload config — collections, globals, auth, versions, and jobs. +- [Scalar and Swagger UI](/v1/configuration/docs-ui) renderers. +- [Custom endpoint](/v1/guides/custom-endpoints) and [field metadata](/v1/guides/field-metadata) via `custom.openapi`. +- [Filters](/v1/configuration/filters), [interactive auth](/v1/configuration/interactive-auth), [extensions](/v1/configuration/extensions), and [caching](/v1/configuration/caching). +- [`openapi:generate` CLI](/v1/cli/generate) for writing the spec to a file. +- UI translations for 44 locales. + + diff --git a/docs/v1/cli/generate.mdx b/docs/v1/cli/generate.mdx new file mode 100644 index 0000000..1eb69eb --- /dev/null +++ b/docs/v1/cli/generate.mdx @@ -0,0 +1,107 @@ +--- +title: 'Generate command' +sidebarTitle: 'openapi:generate' +description: 'Write the OpenAPI document to a file with the openapi:generate CLI command — no HTTP request required.' +--- + +The plugin registers a Payload CLI command named `openapi:generate`. It builds the same document the runtime endpoint serves and writes it to disk — no HTTP request, no running server needed. + +```bash +payload openapi:generate +``` + +Writing the spec to a file is useful when you want to: + +- **Commit the spec** to the repo, so API changes show up in code review. +- **Diff schemas in CI** and fail the build on unintended API changes. +- **Feed a client-code generator** (openapi-typescript, Orval, and friends). +- **Host a static spec** with no runtime endpoint at all — see [Serve a pre-generated spec](/v1/guides/static-spec). + + + The command reads the plugin's resolved options from your Payload config, so `openapi()` must be in your `plugins` + array — even with [`serve: false`](/v1/guides/static-spec). + + +## Flags + +| Flag | Description | +| ---------- | --------------------------------------------------------------------------------------------------------------------- | +| `--lang` | Locale for translated descriptions, or `all` to write one file per supported language. Defaults to the i18n fallback. | +| `--out` | Output path for a single-language run. Default `openapi.json`. | +| `--server` | Base URL written to the spec's `servers`. Defaults to the `servers` option, then `serverURL`. | + +Flags accept both `--flag value` and `--flag=value`. + +### `--lang` + +Picks the locale used for translated titles and descriptions. Defaults to your project's `i18n.fallbackLanguage`. + +```bash +payload openapi:generate --lang ru +``` + +Pass `all` to write one file per language in your `i18n.supportedLanguages`, named `openapi..json`: + +```bash +payload openapi:generate --lang all +# → openapi.en.json, openapi.ru.json, … +``` + +When `--lang all` writes multiple files, the `openapi..json` naming applies and `--out` is ignored. + +See [Internationalization](/v1/guides/i18n) for how language resolution works. + +### `--out` + +Output path for a single-language run: + +```bash +payload openapi:generate --lang ru --out ./spec/openapi.ru.json +``` + +### `--server` + +Base URL written to the spec's `servers` array: + +```bash +payload openapi:generate --server https://api.example.com +``` + +A file on disk has no request, so the command does not use `trustedHosts` or a `servers` function. Without `--server` it uses the `servers` option when it is an array, then `serverURL` from your Payload config. With none of them, `servers` is empty. See [Servers](/v1/configuration/info#servers). + + + The generated spec reflects public-access marking only — it never runs per-user access checks. This matches the HTTP + endpoint exactly. See [Security marking](/v1/configuration/security-marking). + + +## CI example + +Commit the spec, regenerate it in CI, and fail the job when the committed file drifts from the config: + +```yaml .github/workflows/openapi.yml +name: OpenAPI spec check + +on: [pull_request] + +jobs: + spec-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npx payload openapi:generate --server https://api.example.com --out openapi.json + - name: Fail on spec drift + run: git diff --exit-code openapi.json +``` + +The diff in the failed job output is a readable summary of what changed in your API. + +## See also + +- [Serve a pre-generated spec](/v1/guides/static-spec) — `serve: false`, static hosting, and pointing the docs UI at a file +- [Internationalization](/v1/guides/i18n) — locales, `?lang=`, and multi-language generation +- [Programmatic use](/v1/guides/programmatic-use) — build the document in your own code instead diff --git a/docs/v1/configuration/caching.mdx b/docs/v1/configuration/caching.mdx new file mode 100644 index 0000000..80dc372 --- /dev/null +++ b/docs/v1/configuration/caching.mdx @@ -0,0 +1,26 @@ +--- +title: 'Caching' +description: 'The built document is cached for the life of the process; disable the cache in development to see config edits without a restart.' +--- + +`cache` is `true` by default: the plugin builds the document once and reuses it for every request until the process restarts. + +## Why this is safe + +The Payload config is static after boot — collections, globals, fields, and endpoints cannot change while the server runs. So the document is identical on every request, with one exception: `servers`, which is set on every request, cached or not. See [Servers](/v1/configuration/info#servers). + +## Disable it in development + +In development your config changes often. Turn the cache off so edits show up without a restart: + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + cache: process.env.NODE_ENV === 'production', +}) +``` + + + If you never want a live spec endpoint at all, `cache` is not the option you're looking for — set `serve: false` and + generate the file with the CLI instead. See [Static spec](/v1/guides/static-spec). + diff --git a/docs/v1/configuration/docs-ui.mdx b/docs/v1/configuration/docs-ui.mdx new file mode 100644 index 0000000..033279c --- /dev/null +++ b/docs/v1/configuration/docs-ui.mdx @@ -0,0 +1,97 @@ +--- +title: 'Docs UI' +description: 'Mount a Scalar or Swagger UI API reference next to the generated spec, or both on different paths.' +sidebarTitle: 'Docs UI' +--- + +The plugin ships two UI renderers: `scalar()` and `swaggerUi()`. Each is a separate Payload plugin you add alongside `openapi()` — mount one, or both on different paths: + +```ts payload.config.ts +import { openapi, scalar, swaggerUi } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' } }), + scalar(), // Scalar at /api/docs + swaggerUi({ path: '/swagger' }), // Swagger UI at /api/swagger +] +``` + +Each renderer serves a single HTML page that loads the UI library from a CDN and points it at the spec endpoint. Paths are relative to the Payload API route (`routes.api`, `/api` by default). + +## Options + +Both renderers accept the same options: + +| Option | Type | Default | Description | +| --------------- | ------------------------- | -------------------------- | ---------------------------------------------------------------------------------------- | +| `path` | `string` | `'/docs'` | Where the docs UI is served, relative to the API route (so `/docs` → `/api/docs`) | +| `specURL` | `string` | the `openapi()` spec route | URL the UI fetches the OpenAPI document from | +| `cdnBase` | `string` | pinned jsDelivr URL | Where the UI's assets load from. See [Pinned versions and SRI](#pinned-versions-and-sri) | +| `configuration` | `Record` | `{}` | Extra config merged into the library's init options | +| `access` | `({ req }) => boolean` | open | Who may open the docs page; any other result answers `403` | +| `enabled` | `boolean` | `true` | Set `false` to skip mounting the UI | + +By default the UI loads the spec from the route `openapi()` serves, including a custom `path`, so `openapi({ path: '/spec.json' })` needs no change here. The page title is `info.title`. To keep private docs private, see [Limiting who can read the spec](/v1/configuration/overview#limiting-who-can-read-the-spec). + +## Pinned versions and SRI + +By default each renderer loads one fixed version of its library from jsDelivr: + +| Renderer | Default `cdnBase` | +| ------------- | -------------------------------------------------------------------------------------- | +| `scalar()` | `https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.4/dist/browser/standalone.js` | +| `swaggerUi()` | `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1` | + +The page adds an `integrity` hash ([Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)) to these files. If the CDN serves a changed file, the browser does not run it. A plugin release updates the versions and the hashes. + +For Scalar, `cdnBase` is the URL of the script. For Swagger UI, it is the folder that holds `swagger-ui-bundle.js` and `swagger-ui.css`. + +### Self-hosting + +To load the UI from your own server, for example with a strict Content Security Policy or without internet access, copy the files to your static assets and set `cdnBase`: + +```bash +npm install -D @scalar/api-reference swagger-ui-dist +cp node_modules/@scalar/api-reference/dist/browser/standalone.js public/scalar.js +mkdir -p public/swagger && cp node_modules/swagger-ui-dist/swagger-ui-bundle.js node_modules/swagger-ui-dist/swagger-ui.css public/swagger/ +``` + +```ts payload.config.ts +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' } }), + scalar({ cdnBase: '/scalar.js' }), + swaggerUi({ path: '/swagger', cdnBase: '/swagger' }), +] +``` + +A custom `cdnBase` loads without an `integrity` hash, because the plugin cannot know the file. You pick the version. + +## Passing UI configuration + +`configuration` is passed straight through to the underlying library — Scalar's `createApiReference` config or Swagger UI's `SwaggerUIBundle` options. It must be JSON-serializable; functions aren't supported here. + +```ts payload.config.ts +scalar({ + configuration: { + theme: 'purple', + hideDownloadButton: true, + }, +}) +``` + +## Serving a static spec + +The UI loads the spec from a URL, and that URL doesn't have to be the plugin's runtime endpoint. Point `specURL` at a pre-generated file and the UI renders it directly: + +```ts payload.config.ts +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' }, serve: false }), + scalar({ specURL: '/openapi.json' }), // loads public/openapi.json +] +``` + +See [Static spec](/v1/guides/static-spec) for generating the file with the CLI. + +## Language switcher + +For multi-language docs, point Scalar's `sources` at the spec endpoint with different `?lang=` values. When `sources` is set, the renderer's own `specURL` is ignored. Swagger UI has no built-in switcher — mount one instance per language instead. See [Internationalization](/v1/guides/i18n) for both setups. diff --git a/docs/v1/configuration/extensions.mdx b/docs/v1/configuration/extensions.mdx new file mode 100644 index 0000000..dab37c1 --- /dev/null +++ b/docs/v1/configuration/extensions.mdx @@ -0,0 +1,75 @@ +--- +title: 'Extensions' +description: 'Inject extra paths, components, and tags, or transform the finished document before it is served.' +--- + +For anything the generator doesn't produce on its own, pass `extensions`. Each extension can add paths, components, and tags, and/or transform the finished document before it's served or written to a file: + +```ts payload.config.ts +import type { OpenApiExtension } from '@seshuk/payload-plugin-openapi' + +const myExtension: OpenApiExtension = { + paths: { + '/webhooks/stripe': { + post: { summary: 'Stripe webhook', responses: { '200': { description: 'OK' } } }, + }, + }, + components: { + securitySchemes: { + apiKey: { type: 'apiKey', in: 'header', name: 'X-API-Key' }, + }, + }, + tags: [{ name: 'Webhooks', description: 'Inbound webhooks' }], + transform: async ({ doc, payload }) => { + const { totalDocs } = await payload.count({ collection: 'posts' }) + doc.info['x-posts'] = totalDocs + return doc + }, +} + +openapi({ + info: { title: 'My API', version: '1.0.0' }, + extensions: [myExtension], +}) +``` + +## The extension shape + +| Option | Type | Default | Description | +| ------------ | ---------------------------------------------------- | ------- | -------------------------------------------------------------- | +| `paths` | `PathsObject` | — | Extra path items merged into the document's `paths` | +| `components` | `ComponentsObject` | — | Extra schemas, security schemes, etc. merged into `components` | +| `tags` | `TagObject[]` | — | Extra tags appended to the document's `tags` | +| `transform` | `({ doc, ...ctx }) => Document \| Promise` | — | Post-build hook over the finished document. It can be `async`. | + +## The `transform` hook + +`transform` receives one object, the `ExtensionContext`: the full built document as `doc` plus the build environment. It **must return the document** to serve. Mutate and return the same object, or return a new one — either works, but a missing `return` drops the document. It can be `async`. + +`transform` fails closed. If it throws or its promise rejects, the spec request fails and the CLI stops with the error. The plugin never serves a document that skipped a transform, so a transform that removes private paths can rely on it. + +The `ExtensionContext` has these properties: + +| Property | Type | Description | +| --------------- | -------------------- | ----------------------------------------------------------------------------- | +| `doc` | `Document` | The finished document | +| `defaultIDType` | `'text' \| 'number'` | The project's ID type | +| `locales` | `string[]` | Locale codes from the project's `localization` config | +| `apiRoute` | `string` | The Payload API route (`routes.api`, `/api` by default) | +| `docLanguages` | `string[]` | Languages the docs are available in (the project's `i18n.supportedLanguages`) | +| `i18n` | `I18n` | The active request i18n — resolves translated descriptions | +| `payload` | `BasePayload` | The Payload instance, for example to read data with the Local API | +| `options` | `ResolvedOptions` | The plugin options with every default filled in | + +## Extension or `custom.openapi`? + +Use the tool that keeps documentation next to what it describes: + +- **`custom.openapi` on an endpoint** — documentation for one specific endpoint your config already defines. It lives on the endpoint itself and needs no plugin option. See [Custom endpoints](/v1/guides/custom-endpoints). +- **An extension** — documentation that isn't tied to a single endpoint or field: shared components, extra tags, paths served outside Payload (webhooks handled elsewhere), or a post-build transform over the whole document. + + + +If you're a plugin author, prefer `custom.openapi` on the endpoints you add — it's picked up automatically without depending on this package. Extensions are an opt-in your users wire into their own `openapi({ extensions: [...] })` call. See [For plugin authors](/v1/guides/plugin-authors). + + diff --git a/docs/v1/configuration/filters.mdx b/docs/v1/configuration/filters.mdx new file mode 100644 index 0000000..5c1a4ac --- /dev/null +++ b/docs/v1/configuration/filters.mdx @@ -0,0 +1,151 @@ +--- +title: 'Filters' +description: 'Control what ends up in the spec: which entities are documented, which internal endpoints appear, and which operations are dropped.' +--- + +`filters` decides what the spec contains: which collections and globals are documented, which Payload-internal collections show up, which extra endpoint groups are generated, and which individual operations are dropped. It's one flat object. + +| Option | Type | Default | Description | +| ------------------- | ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------ | +| `include` | `EntityMatcher[]` | `[]` | Allowlist. When non-empty, only matching entities are documented | +| `exclude` | `EntityMatcher[]` | `[]` | Entities to leave out entirely (schemas and paths) | +| `includeHidden` | `boolean` | `false` | Include collections flagged `hidden` / `admin.hidden` | +| `includeSystem` | `boolean` | `false` | Include Payload-internal collections and globals (`payload-jobs`, `payload-jobs-stats`, …) | +| `includeCustom` | `boolean` | `true` | Document endpoints carrying `custom.openapi` metadata | +| `includeAuth` | `boolean` | `true` | Document auth operations (login / logout / me / …) | +| `includeAdminAuth` | `boolean` | `false` | Document admin endpoints (`/init`, first-register, `/api/access`, document access) | +| `includeVersions` | `boolean` | `true` | Document version operations (`/versions`, `/versions/{id}`) | +| `includeJobs` | `boolean` | `true` | Document jobs endpoints (`/payload-jobs/run`, `/handle-schedules`) | +| `excludeOperations` | `Array boolean>` | `[]` | Rules and functions that drop single operations | + +`includeSystem` covers these collections: `payload-jobs`, `payload-kv`, `payload-llm-instructions`, `payload-locked-documents`, `payload-migrations`, `payload-preferences` and `payload-query-presets`. It also covers the `payload-jobs-stats` global. + +See [Endpoints](/v1/reference/endpoints) for the full list of endpoints and the option that controls each one. + +## Choosing entities with `include` and `exclude` + +These two lists pick which collections and globals are documented. Each entry is an `EntityMatcher` — one of three shapes: + +| Matcher | Matches | Example | +| ---------------- | -------------------------------------------------------------- | --------------------------------- | +| a string | one entity by its exact slug | `'posts'` | +| a `RegExp` | every entity whose slug matches the pattern | `/^marketing-/` | +| `{ kind, slug }` | one entity, when a collection and a global share the same slug | `{ kind: 'global', slug: 'nav' }` | + +Two rules govern how the lists combine: + +- `include` is an allowlist. Leave it empty and everything is documented; add anything and only matching entities survive. +- `exclude` always wins. An entity matching both lists is dropped. + +### Document only a few collections + +Once `include` has entries, everything else stays out of the spec: + +```ts payload.config.ts +filters: { + include: ['posts', 'media', 'categories'], +} +``` + +### Document everything except a few entities + +```ts payload.config.ts +filters: { + exclude: ['audit-log', 'internal-settings'], +} +``` + +### Match a group of slugs with a regular expression + +The pattern is tested against the slug. This keeps every collection whose slug starts with `public-` and drops everything else: + +```ts payload.config.ts +filters: { + // public-posts, public-media, public-authors … all kept; everything else dropped. + include: [/^public-/], +} +``` + +Common patterns, for reference: + +- `/^public-/` — slug starts with `public-` +- `/-draft$/` — slug ends with `-draft` +- `/^(posts|pages)$/` — slug is exactly `posts` or `pages` +- `/internal/i` — slug contains `internal`, case-insensitive + +### Disambiguate a collection from a global + +A plain string matches any entity with that slug. When a collection and a global share one, use `{ kind, slug }` to target just one of them: + +```ts payload.config.ts +filters: { + // Keep the `settings` collection, drop the `settings` global. + exclude: [{ kind: 'global', slug: 'settings' }], +} +``` + +### Mix them + +`include` narrows the set first, then `exclude` removes from what's left: + +```ts payload.config.ts +filters: { + // Document every `public-*` collection, but never the drafts one. + include: [/^public-/], + exclude: ['public-drafts'], +} +``` + + + `include` and `exclude` only apply to your own collections and globals. Hidden and Payload-internal collections are + governed by `includeHidden` and `includeSystem`, which run **first** — adding `payload-jobs` to `include` won't + surface it unless `includeSystem` is on. + + +## Dropping individual operations + +`include` / `exclude` work at the entity level. To remove specific operations — one method on one collection, every `DELETE`, anything under a path prefix — use `excludeOperations`. It applies to every operation in the spec: collection, global, custom, jobs and system (upload instructions, `/api/access`) operations. + +### Rules + +Each rule is a set of conditions. Within one rule, every field you set must match (**AND**). Across rules, an operation is dropped if **any** rule matches it (**OR**). A field you leave out matches anything. + +| Field | Type | Matches | +| -------- | ---------------------------- | ------------------------------------------------------------------------- | +| `method` | `HttpMethod \| HttpMethod[]` | The HTTP method(s); omit to match any | +| `slug` | `string \| RegExp` | The collection or global slug, exact string or pattern; omit to match any | +| `kind` | `OperationKind` | Restrict to one group; omit to match all | +| `path` | `RegExp` | Tested against the final route path (e.g. `/api/posts/{id}`) | + +`kind` is one of `'collection'`, `'global'`, `'custom'` (endpoints with `custom.openapi`), `'jobs'` or `'system'`. Custom, jobs and system operations have no slug, so a rule with `slug` never matches them. + +```ts payload.config.ts +filters: { + excludeOperations: [ + // Drop every DELETE, on any entity. + { method: 'delete' }, + // Drop writes to `posts`, but leave its reads alone. + { slug: 'posts', method: ['post', 'patch', 'put'] }, + // Drop anything matching a path, regardless of method or entity. + { path: /\/preview$/ }, + ], +} +``` + +### Functions + +For logic that doesn't fit a rule, put a function in the same list. It receives the `method`, `path`, `slug` and `kind` of every operation. The operation is dropped only when the function returns `true`. An `async` function is awaited: + +```ts payload.config.ts +filters: { + excludeOperations: [ + { method: 'delete' }, + ({ path, method }) => path.includes('/internal/') || method === 'put', + ], +} +``` + + + Filters only remove things from the document. To change how a kept operation is marked (public vs. secured), see + [Security marking](/v1/configuration/security-marking). + diff --git a/docs/v1/configuration/info.mdx b/docs/v1/configuration/info.mdx new file mode 100644 index 0000000..c93ccf6 --- /dev/null +++ b/docs/v1/configuration/info.mdx @@ -0,0 +1,99 @@ +--- +title: 'Info and servers' +description: 'The info object fills the spec info block. servers and trustedHosts set the base URL clients call.' +--- + +`info` fills the `info` block of the generated document. It is the only required plugin option. + +```ts payload.config.ts +openapi({ + info: { + title: 'My API', + version: '1.0.0', + summary: 'Content API for the example app.', + description: { en: 'Public API for the example app.', de: 'Öffentliche API der Beispiel-App.' }, + termsOfService: 'https://example.com/terms', + contact: { name: 'API team', email: 'api@example.com' }, + license: { name: 'MIT', identifier: 'MIT' }, + }, +}) +``` + +`info` is an OpenAPI [Info Object](https://spec.openapis.org/oas/v3.2.0#info-object). The plugin copies every field into the spec. + +| Field | Type | Default | Description | +| ---------------- | ---------------------------------------- | ------- | -------------------------------------------------- | +| `title` | `string` | — | API title. Also the docs page title. **Required.** | +| `version` | `string` | — | Your API's version. **Required.** | +| `summary` | `string` | — | A short summary. OpenAPI 3.0 output drops it. | +| `description` | `string \| Record \| fn` | — | Description. It can be localized, see below. | +| `termsOfService` | `string` | — | URL of the terms of service. | +| `contact` | `{ name?, url?, email? }` | — | Contact for the API. | +| `license` | `{ name, identifier?, url? }` | — | License. OpenAPI 3.0 output drops `identifier`. | +| `x-*` | `unknown` | — | Any extension field. | + +`info.title` and `info.version` are required. The plugin throws on boot if either is missing. + +## Localized description + +`info.description` works like a Payload label. Give a string, an object keyed by language, or a function that gets `{ t, i18n }`. The plugin resolves it against the [docs language](/v1/guides/i18n): + +```ts payload.config.ts +openapi({ + info: { + title: 'My API', + version: '1.0.0', + description: ({ i18n }) => (i18n.language === 'de' ? 'Öffentliche API.' : 'Public API.'), + }, +}) +``` + +## Servers + +The `servers` field of the spec tells clients and the "Try it" button which base URL to call. The plugin picks it in this order: + +1. `servers` as a function. The plugin calls it on every spec request with `{ req }`. +2. `servers` as an array. +3. The request `Host` header, but only when it is in `trustedHosts`. +4. `serverURL` from your Payload config. +5. Nothing. The spec has an empty `servers` list, and clients use the URL they loaded the spec from. + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + servers: [ + { url: 'https://api.example.com', description: 'Production' }, + { url: 'https://staging.example.com', description: 'Staging' }, + ], +}) +``` + +A function can choose the servers per request: + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + servers: ({ req }) => [ + { url: req.headers.get('x-tenant') === 'eu' ? 'https://eu.example.com' : 'https://example.com' }, + ], +}) +``` + +The document is still cached. Only `servers` is set per request. + +### Trusted hosts + +The `Host` header comes from the client, so the plugin does not trust it by default. If it did, anyone could send a fake `Host` and get a spec that points "Try it" at their own server. To serve one spec under several host names, list them in `trustedHosts`: + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + trustedHosts: ['api.example.com', 'api.example.org', 'localhost:3000'], +}) +``` + +A request whose `Host` is in the list gets that host as its server, with `https`, or `http` for `localhost` and `127.*`. Any other host falls back to `serverURL`. The match ignores case and includes the port. `trustedHosts` has no effect when `servers` is set. + +### Static files + +A file on disk has no request. The CLI uses the `--server` flag, then `servers` as an array, then `serverURL`. See [Generate](/v1/cli/generate). diff --git a/docs/v1/configuration/interactive-auth.mdx b/docs/v1/configuration/interactive-auth.mdx new file mode 100644 index 0000000..37af807 --- /dev/null +++ b/docs/v1/configuration/interactive-auth.mdx @@ -0,0 +1,66 @@ +--- +title: 'Interactive auth' +description: 'Add a token endpoint so Scalar and Swagger UI can show an Authorize dialog with a Payload login.' +--- + +By default the docs UI expects you to paste a bearer token. Turn on `interactiveAuth` to add a token endpoint (`/api/openapi-auth`) plus a matching security scheme, so Scalar and Swagger UI show an **Authorize** dialog where users log in with their Payload credentials: + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + interactiveAuth: true, // token endpoint at /api/openapi-auth +}) +``` + +To change the token endpoint path or the auth collection, pass an object. The path is relative to the Payload API route (`routes.api`, `/api` by default): + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + interactiveAuth: { path: '/login', collection: 'admins' }, // → /api/login, logs in to `admins` +}) +``` + +## How the login works + +The endpoint logs in against `collection`. Without it, it uses your **first auth-enabled collection**, falling back to `users` if none is found. It accepts the username as either `email` or `username`, matching how your auth collection is configured — the same value is passed as both, and Payload uses whichever login field the collection defines. + +## Request and response + +The token endpoint accepts a `POST` with form data (`username` and `password` fields) and returns the Payload JWT: + +```bash Request +curl -X POST https://example.com/api/openapi-auth \ + -F 'username=admin@example.com' \ + -F 'password=secret' +``` + +```json Response (200) +{ + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "token_type": "JWT", + "expires_in": 7200 +} +``` + +On a failed login the endpoint returns `400` with: + +```json Response (400) +{ "error": "invalid_grant" } +``` + +`expires_in` is the number of seconds until the token expires. + +The docs UI wires this up for you — users type credentials into the Authorize dialog, the UI exchanges them at the token endpoint, and the returned JWT is attached to every "try it" request. Every secured operation lists both `PayloadToken` and `PayloadLogin`, so the UI sends the token with either one. + + + +The interactive auth endpoint exchanges credentials for a live JWT. Only enable it on docs real users are meant to authenticate against, and serve them over HTTPS. + + + + + +With `serve: false` the interactive-auth endpoint is not mounted, along with the spec endpoint, and the spec does not list the `PayloadLogin` scheme — see [Static spec](/v1/guides/static-spec). + + diff --git a/docs/v1/configuration/openapi-version.mdx b/docs/v1/configuration/openapi-version.mdx new file mode 100644 index 0000000..4cde8c1 --- /dev/null +++ b/docs/v1/configuration/openapi-version.mdx @@ -0,0 +1,33 @@ +--- +title: 'OpenAPI version' +description: 'The document is always built as OpenAPI 3.2; openapiVersion downconverts the result to 3.1 or 3.0.' +--- + +The builder always produces an OpenAPI 3.2 document. The `openapiVersion` option controls what gets served — `'3.1'` and `'3.0'` each run a downconversion pass on the finished document: + +| Version | Behavior | +| ------- | ----------------------------------------------------------------------------- | +| `'3.2'` | Served as built (default) | +| `'3.1'` | A downconversion pass strips 3.2-only fields | +| `'3.0'` | A downconversion pass to 3.0 (e.g. nullable handling, `examples` → `example`) | + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + openapiVersion: '3.0', +}) +``` + +## Nested tags + + + Scalar and Swagger UI don't render OpenAPI 3.2 nested tags yet. The `nestedTags` option is **off by default**, so + `doc.tags` is a flat list of per-entity tags with descriptions. Turn it on only if your consumer understands the 3.2 + `kind`/`parent` tag tree. + + +When `nestedTags: true`, the plugin emits the 3.2 tag hierarchy: navigation groups via `kind`/`parent`, plus per-entity auth and version sub-tags. + +## Downconverting yourself + +The downconverters are also exported as `toOpenApi30` and `toOpenApi31`, so you can build a 3.2 document programmatically and convert it in your own code. See [Exports](/v1/reference/exports). diff --git a/docs/v1/configuration/overview.mdx b/docs/v1/configuration/overview.mdx new file mode 100644 index 0000000..19870c4 --- /dev/null +++ b/docs/v1/configuration/overview.mdx @@ -0,0 +1,91 @@ +--- +title: 'Overview' +description: 'Configure the plugin with a single openapi() call, plus optional scalar() or swaggerUi() for the docs UI.' +--- + +You configure the plugin with a single `openapi(...)` call in your Payload config. The docs UI is separate — add `scalar()` or `swaggerUi()` alongside it (see [Docs UI](/v1/configuration/docs-ui)): + +```ts payload.config.ts +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' +import { buildConfig } from 'payload' + +export default buildConfig({ + // ... + plugins: [ + openapi({ + info: { + title: 'My API', + version: '1.0.0', + }, + }), + scalar(), + ], +}) +``` + +## Plugin options + +| Option | Type | Default | Description | +| ----------------- | ------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `info` | `OpenApiInfo` | — | The spec `info` block: title, version, description, contact, license. **Required.** See [Info and servers](/v1/configuration/info). | +| `servers` | `ServerObject[] \| ({ req }) => …` | `serverURL` | Base URLs written to the spec `servers`. See [Servers](/v1/configuration/info#servers). | +| `trustedHosts` | `string[]` | `[]` | Hosts whose `Host` header may set `servers`. See [Trusted hosts](/v1/configuration/info#trusted-hosts). | +| `openapiVersion` | `'3.0' \| '3.1' \| '3.2'` | `'3.2'` | Spec version to serve. See [OpenAPI version](/v1/configuration/openapi-version). | +| `path` | `string` | `'/openapi.json'` | Path the spec is served from, relative to the API route. | +| `enabled` | `boolean` | `true` | Set `false` to turn the plugin off. See [Turning the plugin off](#turning-the-plugin-off). | +| `serve` | `boolean` | `true` | Set `false` to register only the CLI generator and serve nothing over HTTP. See [Static spec](/v1/guides/static-spec). | +| `access` | `({ req }) => boolean` | open | Who may read the spec. See [Limiting who can read the spec](#limiting-who-can-read-the-spec). | +| `filters` | `FilterOptions` | see page | Which entities and operations end up in the spec. See [Filters](/v1/configuration/filters). | +| `interactiveAuth` | `boolean \| { path, collection }` | `false` | Username/password login for the docs UI. See [Interactive auth](/v1/configuration/interactive-auth). | +| `nestedTags` | `boolean` | `false` | Emit an OpenAPI 3.2 nested tag hierarchy. See [OpenAPI version](/v1/configuration/openapi-version#nested-tags). | +| `security` | `(ctx) => 'public' \| 'secured' \| …` | — | Override the auto-detected security marking per operation. See [Security marking](/v1/configuration/security-marking). | +| `cache` | `boolean` | `true` | Cache the built document for the life of the process. See [Caching](/v1/configuration/caching). | +| `extensions` | `OpenApiExtension[]` | `[]` | Inject paths, components, tags, or transform the finished document. See [Extensions](/v1/configuration/extensions). | +| `telemetry` | `boolean \| { url }` | `true` | Anonymous usage telemetry. See [Telemetry](/v1/configuration/telemetry). | + +## Paths are relative to the API route + +The plugin registers its endpoints as Payload endpoints, so everything mounts under your Payload API route (`routes.api`, `/api` by default). The `path` you pass to `openapi()` — and the `path` you pass to `scalar()`/`swaggerUi()` — are relative to that route. The defaults resolve to: + +- `GET /api/openapi.json` — the generated OpenAPI document +- `GET /api/docs` — the interactive API reference + +## The lazy build model + +`openapi()` does not build anything at config time. It stashes the resolved options, registers the CLI generator, and mounts the endpoints. The document itself is built per request (or per CLI run) from the fully **sanitized** Payload config. + +That means plugin order does not matter: collections, globals, fields, and endpoints added by any other plugin are visible in the spec, whether that plugin runs before or after `openapi()` in your `plugins` array. + +`info.title` and `info.version` are required. The plugin throws on boot if either is missing. + +## Limiting who can read the spec + +The spec and the docs page are open to everyone by default, because most API docs are public. The spec lists every documented collection, field and endpoint. If your API is private, add `access` to `openapi()` and to the docs UI. It gets the request and returns `true` to serve it. Any other result answers `403`. + +This recipe shows the docs only to users of your admin collection: + +```ts payload.config.ts +const adminOnly = ({ req }) => req.user?.collection === req.payload.config.admin.user + +plugins: [openapi({ info: { title: 'My API', version: '1.0.0' }, access: adminOnly }), scalar({ access: adminOnly })] +``` + +The docs page loads the spec from the same site, so the browser sends the admin login cookie with it. To hide the docs only in production, use `access: () => process.env.NODE_ENV !== 'production'`. + +## Turning the plugin off + +With `enabled: false` the plugin adds no endpoints, no CLI command, no translations and sends no telemetry. It never adds collections or fields, so your database schema is the same either way. It still checks your options, so a removed option still throws. + +## Where to go next + +- [Info and servers](/v1/configuration/info) — title, version, description, and where the base URL comes from +- [OpenAPI version](/v1/configuration/openapi-version) — 3.2 by default, downconversion to 3.1/3.0 +- [Filters](/v1/configuration/filters) — choose entities and drop operations +- [Security marking](/v1/configuration/security-marking) — public vs. secured operations +- [Interactive auth](/v1/configuration/interactive-auth) — Authorize dialog in the docs UI +- [Caching](/v1/configuration/caching) — when the document is rebuilt +- [Extensions](/v1/configuration/extensions) — add paths, components, tags, or transform the document +- [Docs UI](/v1/configuration/docs-ui) — Scalar and Swagger UI options +- [Telemetry](/v1/configuration/telemetry) — what the anonymous usage report contains and how to turn it off + +For copy-ready configurations, see [Examples](/v1/guides/examples). diff --git a/docs/v1/configuration/security-marking.mdx b/docs/v1/configuration/security-marking.mdx new file mode 100644 index 0000000..98696c7 --- /dev/null +++ b/docs/v1/configuration/security-marking.mdx @@ -0,0 +1,84 @@ +--- +title: 'Security marking' +description: 'How each operation in the spec is marked public or secured, and how to override the automatic detection.' +--- + +Every operation in the generated document is marked either **public** or **secured**. A secured operation references the `PayloadToken` security scheme, so the docs UI shows a padlock and tells the reader a bearer token is required. + +The marking is a **static documentation hint**, not a live access check. The plugin never evaluates per-user permissions and never gates any request. It only records, in the spec, which endpoints an anonymous caller can reach — your Payload access control keeps enforcing the real rules at runtime. + +## How detection works + +By default the plugin probes each entity's Payload access functions as an **anonymous request** (`user: null`). An operation is marked public only if its access function settles on `true`. The probe is deliberately conservative: + +- An `async` access function is awaited — `async () => true` is correctly detected as public. +- A function that returns a `Where` query (partial access), throws, or times out is marked secured. +- A function that reaches into the database (`req.payload.find(...)`) is marked secured — the probe never runs live queries, so DB-driven access always errs on the side of a padlock. + +## Operation groups + +Payload access control is defined per operation, so the marking follows the same grouping. One access function decides the marking for every endpoint in its group: + +| Access function | Endpoints covered | +| --------------- | ------------------------------------------ | +| `read` | list, find by ID, count | +| `create` | create, duplicate | +| `update` | update by ID, bulk update, restore version | +| `delete` | delete by ID, bulk delete | +| `readVersions` | list versions, find version by ID | +| `validate` | validate, validate by ID | + +In Payload 4, `readVersions` follows `read` unless you set it, so version reads get the same marking as reads. In the same way, `validate` follows `update`. + +The jobs endpoints follow `jobs.access.run`, which needs a logged-in user by default. The upload instructions endpoints are always secured. Custom endpoints keep the `security` you set in their `custom.openapi`. + +## Overriding the detection + +When the probe guesses wrong — a `Where`-based rule that is effectively public, or a DB lookup that always allows anonymous reads — override it. Two levels, in precedence order. + +### Per entity: `custom.openapi.security` + +Set `security` under `custom.openapi` on a collection or global. `'public'` or `'secured'` covers every operation. An object sets each group. The `read` entry also covers `readVersions`. `validate` follows `update` unless you set it. This override wins over the probe: + +```ts collections/Posts.ts +import type { CollectionConfig } from 'payload' + +export const Posts: CollectionConfig = { + slug: 'posts', + custom: { + openapi: { + // 'public' or 'secured' for all operations, or per operation: + security: { read: 'public', create: 'secured', update: 'secured', delete: 'secured' }, + }, + }, + // ... +} +``` + +The type of `custom.openapi` comes with the plugin, so your editor checks it. `true` and `false` were removed in v1. The plugin throws at boot and names the collection. Use `'public'` for `true` and `'secured'` for `false`. + +### Document-wide: `security` + +The `security` option runs **last**, after `custom.openapi.security` and the probe. It runs for every operation in the spec: collection, global, auth, version, custom, jobs and system operations. It can return: + +- `'public'` to mark the operation public; +- `'secured'` to mark it secured with `PayloadToken`; +- an array of OpenAPI security requirements, used as is (for example another scheme you added with [extensions](/v1/configuration/extensions)); +- `undefined` to keep the detected marking. + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + security: ({ slug, method }) => (slug === 'public-feed' && method === 'get' ? 'public' : undefined), +}) +``` + +The function receives `{ method, path, slug, kind, detected }` for each operation. `kind` is `'collection'`, `'global'`, `'custom'`, `'jobs'` or `'system'`, and `slug` is set only for collections and globals. `detected` is the marking the plugin found, `'public'` or `'secured'`. The function can be `async`. + +When [interactive auth](/v1/configuration/interactive-auth) is on, every secured operation also lists the `PayloadLogin` scheme, so the docs UI sends the token it got from the login dialog. + + + +This is public-access marking only — the plugin never runs per-user access checks. The generated spec matches what the HTTP endpoint enforces at runtime; the marking only documents it. + + diff --git a/docs/v1/configuration/telemetry.mdx b/docs/v1/configuration/telemetry.mdx new file mode 100644 index 0000000..3ad222d --- /dev/null +++ b/docs/v1/configuration/telemetry.mdx @@ -0,0 +1,114 @@ +--- +title: 'Telemetry' +description: 'Anonymous, opt-out usage telemetry: what is collected, what is never sent, and how to turn it off.' +--- + +The plugin sends a small, anonymous usage report once per day so the maintainer can see which Payload and plugin versions are in use and which features are worth supporting. It is **opt-out** and **disclosed**: a one-time notice prints to your server logs on first run. + +No secrets, IPs, keys, URLs, paths, hostnames, collection names, or anything from your spec ever leave your server. + +## What is collected + +Every report is a single JSON document with these fields: + +| Field | Example | Notes | +| ----------------- | ------------------------ | -------------------------------------------------------- | +| `product` | `payload-plugin-openapi` | Fixed slug for this plugin. | +| `productVersion` | `1.0.0` | Installed plugin version. | +| `payloadVersion` | `4.0.0` | Installed Payload version. | +| `runtime` | `node` | Always Node for this plugin. | +| `runtimeVersion` | `24` | Node major version only. | +| `os` | `linux` | `process.platform`, lowercased. | +| `projectId` | `b3f1a9c2…` | Anonymous hash — see [below](#how-projectid-is-derived). | +| `projectIdSource` | `git` | Which source produced the hash. | +| `features` | `{ "serve": true, … }` | Booleans only — see [below](#feature-flags). | + +### Feature flags + +`features` is a flat map of booleans derived from your **resolved** options. It records only whether a capability is on, never any value tied to it: + +| Flag | Meaning | +| -------------------------- | ------------------------------------------------------- | +| `serve` | The spec is served over HTTP (`serve` is not `false`). | +| `access` | `access` limits who can read the spec. | +| `cache` | The built document is cached. | +| `servers` | `servers` is set. | +| `trustedHosts` | `trustedHosts` lists at least one host. | +| `openapiVersion30` | The spec is served as OpenAPI 3.0. | +| `openapiVersion31` | The spec is served as OpenAPI 3.1. | +| `filtersEntities` | `filters.include` or `filters.exclude` lists an entity. | +| `filtersExcludeOperations` | `filters.excludeOperations` has a rule or function. | +| `filtersIncludeSystem` | `filters.includeSystem` is on. | +| `interactiveAuth` | The docs UI login is on. | +| `nestedTags` | The OpenAPI 3.2 nested tag hierarchy is on. | +| `security` | A `security` function overrides the marking. | +| `extensions` | At least one extension is set. | +| `extensionTransform` | An extension has a `transform`. | +| `uiScalar` | `scalar()` is in the `plugins` array. | +| `uiSwagger` | `swaggerUi()` is in the `plugins` array. | + +## How `projectId` is derived + +`projectId` is a one-way, irreversible hash: + +``` +projectId = sha256( payload.secret + rawSource ) +``` + +`payload.secret` is used **only as a salt** and is never transmitted — it is high-entropy and private, so the digest cannot be reversed. This mirrors how Payload derives its own telemetry id. + +`rawSource` is the first available of, reported as `projectIdSource`: + +1. `git` — the repository's `remote.origin.url` +2. `packageJSON` — your app's `package.json` `name` +3. `serverURL` — `payload.config.serverURL` +4. `cwd` — the process working directory + +Only the salted hash is sent — never the git URL, package name, or server URL themselves. + +## What is never sent + +No IP address, no `payload.secret`, no API keys, no `info` values, no server URLs or hosts, no paths, no collection or global names, and nothing from the generated document. `features` carries booleans only. + +## Opting out + +Telemetry is disabled automatically if **any** of these is true: + +- `payload.config.telemetry` is `false` (the host's own Payload opt-out) +- the plugin's `telemetry` is `false` +- the `OPENAPI_TELEMETRY_DISABLED` or `DO_NOT_TRACK` environment variable is set to a truthy value +- a `CI` environment is detected +- `NODE_ENV` is `test` + +With `enabled: false` the plugin sends nothing either. + +To opt out explicitly: + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + telemetry: false, +}) +``` + +Or via the environment (respects the cross-tool [Console Do Not Track](https://consoledonottrack.com/) standard): + +```bash +DO_NOT_TRACK=1 +# or, plugin-specific: +OPENAPI_TELEMETRY_DISABLED=1 +``` + +## Sending to your own collector + +Pass an object with a `url` to point reports at a collector you control: + +```ts payload.config.ts +telemetry: { + url: 'https://telemetry.example.com/v1/collect' +} +``` + +## Transport + +Reports are **fire-and-forget**: they run after your own `onInit`, never block boot, never throw, and are capped at a 2-second timeout — a telemetry failure can never affect your application. At most one report is sent per project per UTC day (best-effort, via a timestamp in the OS temp directory). diff --git a/docs/v1/guides/custom-endpoints.mdx b/docs/v1/guides/custom-endpoints.mdx new file mode 100644 index 0000000..7f9b238 --- /dev/null +++ b/docs/v1/guides/custom-endpoints.mdx @@ -0,0 +1,155 @@ +--- +title: 'Custom endpoints' +description: 'Document custom Payload endpoints with a standard OpenAPI Operation Object on custom.openapi — no wrapper, no registry.' +--- + +Any custom Payload endpoint that carries `custom.openapi` metadata is picked up automatically. The metadata is a standard OpenAPI [Operation Object](https://spec.openapis.org/oas/v3.1.0#operation-object) — `summary`, `tags`, `parameters`, `requestBody`, `responses`, and so on. Endpoints without it are skipped, so nothing internal leaks into the spec by accident. + +```ts payload.config.ts +import type { Endpoint } from 'payload' + +const healthEndpoint: Endpoint = { + path: '/health', + method: 'get', + handler: () => Response.json({ ok: true }), + custom: { + openapi: { + summary: 'Health check', + tags: ['System'], + responses: { + '200': { description: 'Service is healthy' }, + }, + }, + }, +} +``` + + + This is the same `custom.openapi` convention that Payload's own [agent + skills](https://github.com/payloadcms/payload/blob/main/tools/claude-plugin/skills/payload/reference/ENDPOINTS.md#openapi-documentation) + document. The plugin reads exactly that shape — no plugin-specific wrapper, no separate registry to keep in sync. + Endpoints already documented this way show up with zero changes. + + +## Where endpoints are collected from + +The plugin walks every endpoint list in the sanitized config: + +| Source | Spec path | +| ---------------------------- | ------------------------- | +| Top-level `config.endpoints` | `/api/...` | +| Collection `endpoints` | `/api//...` | +| Global `endpoints` | `/api/globals//...` | + +The `/api` segment follows your `routes.api` setting. Path params written Express-style (`:id`) are normalized to OpenAPI placeholders (`{id}`), so a collection endpoint at `/:id/tracking` on `posts` is documented as `/api/posts/{id}/tracking`. + + + Custom endpoints are documented only while `filters.includeCustom` stays on. It defaults to `true`; turning it off + drops every `custom.openapi` endpoint from the spec. See [Filters](/v1/configuration/filters). + + +## Examples + +### Request body + +```ts collections/Posts.ts +import type { CollectionConfig } from 'payload' + +export const Posts: CollectionConfig = { + slug: 'posts', + endpoints: [ + { + path: '/import', + method: 'post', + handler: importHandler, + custom: { + openapi: { + summary: 'Bulk import posts', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/PostsCreate' }, + }, + }, + }, + }, + responses: { + '202': { description: 'Import queued' }, + }, + }, + }, + }, + ], + fields: [{ name: 'title', type: 'text', required: true }], +} +``` + +### Path parameters + +Declare params under `parameters` exactly as in any OpenAPI operation. The `:id` in the Payload path and the `{id}` in the parameter refer to the same thing: + +```ts collections/Posts.ts +{ + path: '/:id/tracking', + method: 'get', + handler: trackingHandler, + custom: { + openapi: { + summary: 'Tracking events for a post', + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { type: 'string' }, + }, + ], + responses: { + '200': { description: 'Tracking events' }, + }, + }, + }, +} +``` + +### Tags + +Tags group operations in the docs UI. Reuse an existing entity tag to file the operation alongside the generated CRUD routes, or introduce your own: + +```ts globals/Settings.ts +{ + path: '/refresh', + method: 'post', + handler: refreshHandler, + custom: { + openapi: { + summary: 'Refresh cached settings', + tags: ['Maintenance'], + responses: { + '204': { description: 'Cache refreshed' }, + }, + }, + }, +} +``` + + + `description` and `summary` in the operation object are localizable — pass a translation function or a locale-keyed + object and they resolve against the request language. See [Field + metadata](/v1/guides/field-metadata#localized-strings) for the shape and [i18n](/v1/guides/i18n) for the language + plumbing. + + +## Related + + + + Control which entities and operations end up in the spec, including `includeCustom`. + + + Ship documented endpoints from your own plugin with no dependency on this package. + + diff --git a/docs/v1/guides/examples.mdx b/docs/v1/guides/examples.mdx new file mode 100644 index 0000000..b51af29 --- /dev/null +++ b/docs/v1/guides/examples.mdx @@ -0,0 +1,214 @@ +--- +title: 'Examples' +description: 'Copy-ready openapi() configurations for public APIs, read-only specs, interactive auth, CI generation, multi-language docs, and extensions.' +--- + +Copy-ready configurations for common setups. Each one is a complete `openapi(...)` call — combine pieces as needed for your project. + +For option-by-option reference, see [Configuration](/v1/configuration/overview). + +## Basic setup + +### Minimal + +The smallest working options: `info.title` and `info.version` are required, everything else has a default. + +```ts payload.config.ts +import { buildConfig } from 'payload' +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' + +export default buildConfig({ + plugins: [ + openapi({ + info: { + title: 'My API', + version: '1.0.0', + }, + }), + scalar(), // interactive docs at /api/docs + ], +}) +``` + +### Scalar and Swagger UI side by side + +Each renderer is its own plugin — mount both on different paths against the same spec endpoint. + +```ts payload.config.ts +import { openapi, scalar, swaggerUi } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ + info: { title: 'My API', version: '1.0.0' }, + }), + scalar(), // Scalar at /api/docs + swaggerUi({ path: '/swagger' }), // Swagger UI at /api/swagger +] +``` + +See [Docs UI](/v1/configuration/docs-ui). + +## Shaping the spec + +### Public API only + +Document only the `public-*` collections, and drop one of them by exact slug. `include` is an allowlist: once it's non-empty, only matching entities survive. + +```ts payload.config.ts +openapi({ + info: { title: 'Public API', version: '1.0.0' }, + filters: { + include: [/^public-/], // public-posts, public-media, … all kept + exclude: ['public-drafts'], // …except this one + }, +}) +``` + +See [Filters](/v1/configuration/filters) for all matcher forms (slug, RegExp, `{ kind, slug }`). + +### Read-only spec + +Keep every entity but drop all write operations with `excludeOperations`. + +```ts payload.config.ts +openapi({ + info: { title: 'Read-only API', version: '1.0.0' }, + filters: { + excludeOperations: [{ method: ['post', 'patch', 'put', 'delete'] }], + }, +}) +``` + +Rules combine: add `{ slug: 'posts', method: 'get' }` style entries to target one entity, or `{ path: /\/preview$/ }` to match by route path. + +## Auth and security + +### Interactive auth for a private docs site + +Add a token endpoint and a matching security scheme, so the docs UI shows an **Authorize** dialog where users log in with their Payload credentials instead of pasting a token. + +```ts payload.config.ts +openapi({ + info: { title: 'Internal API', version: '1.0.0' }, + interactiveAuth: true, // token endpoint at /api/openapi-auth + // interactiveAuth: { path: '/login' }, // or a custom path → /api/login +}) +``` + +See [Interactive auth](/v1/configuration/interactive-auth). + +### Security overrides with `security` + +The plugin marks each operation public or secured by probing your access functions. `security` runs last and overrides the detected marking: return `'public'`, `'secured'`, or `undefined` to keep the detection. + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + security: ({ slug, method }) => { + // The feed read is public even though its access function is dynamic. + if (slug === 'public-feed' && method === 'get') return 'public' + // Everything under `internal` always shows a padlock. + if (slug === 'internal') return 'secured' + return undefined // keep the detected marking everywhere else + }, +}) +``` + +See [Security marking](/v1/configuration/security-marking). + +## Environments + +### Development + +Turn the cache off so config edits show up without a restart, and surface hidden and Payload-internal collections while debugging. + +```ts payload.config.ts +openapi({ + info: { title: 'My API (dev)', version: '0.0.0' }, + cache: process.env.NODE_ENV === 'production', + filters: { + includeHidden: true, // collections flagged hidden/admin.hidden + includeSystem: true, // payload-jobs, payload-preferences, … + }, +}) +``` + +See [Caching](/v1/configuration/caching). + +### Generate only, for CI + +No runtime endpoint at all — the plugin registers just the [`openapi:generate` CLI](/v1/cli/generate), and the file is generated at build time. + +```ts payload.config.ts +openapi({ + info: { title: 'My API', version: '1.0.0' }, + serve: false, // CLI only; nothing is served over HTTP +}) +``` + +```bash +payload openapi:generate --server https://api.example.com --out ./public/openapi.json +``` + +See [Serve a pre-generated spec](/v1/guides/static-spec). + +## Multi-language docs site + +One runtime endpoint, resolved per request via `?lang=`; Scalar gets a language switcher through `sources`. Swagger UI has no switcher — mount one instance per language instead. + +```ts payload.config.ts +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ + info: { title: 'My API', version: '1.0.0' }, + }), + scalar({ + configuration: { + sources: [ + { title: 'English', url: '/api/openapi.json?lang=en', default: true }, + { title: 'Русский', url: '/api/openapi.json?lang=ru' }, + ], + }, + }), +] +``` + +See [Internationalization](/v1/guides/i18n). + +## Extensions + +Add a webhook path and an API-key security scheme the generator can't know about, plus a post-build transform. + +```ts payload.config.ts +import type { OpenApiExtension } from '@seshuk/payload-plugin-openapi' + +const webhooks: OpenApiExtension = { + paths: { + '/webhooks/stripe': { + post: { + summary: 'Stripe webhook', + tags: ['Webhooks'], + responses: { '200': { description: 'OK' } }, + }, + }, + }, + components: { + securitySchemes: { + apiKey: { type: 'apiKey', in: 'header', name: 'X-API-Key' }, + }, + }, + tags: [{ name: 'Webhooks', description: 'Inbound webhooks' }], + transform: ({ doc }) => { + doc.info.termsOfService = 'https://example.com/terms' + return doc + }, +} + +openapi({ + info: { title: 'My API', version: '1.0.0' }, + extensions: [webhooks], +}) +``` + +See [Extensions](/v1/configuration/extensions). diff --git a/docs/v1/guides/field-metadata.mdx b/docs/v1/guides/field-metadata.mdx new file mode 100644 index 0000000..c332382 --- /dev/null +++ b/docs/v1/guides/field-metadata.mdx @@ -0,0 +1,164 @@ +--- +title: 'Field metadata' +description: 'Refine inferred field schemas with custom.openapi: descriptions, examples, formats, constraints, and localized strings.' +--- + +The plugin infers a JSON Schema for every field from its Payload type — a `text` field becomes a string, a `number` field a number, a `select` an enum, and so on. You rarely need to touch that. When you do, put a partial Schema Object under the field's `custom.openapi` key. + +Whatever you put there is **deep-merged on top of the inferred schema, and your keys win**. You only change what you name; everything else the plugin inferred stays intact. The merge applies wherever the field appears — the read, create, and update schemas alike. + +## Annotating a field + +```ts collections/Posts.ts +import type { Field } from 'payload' + +const slug: Field = { + name: 'slug', + type: 'text', + custom: { + openapi: { + description: 'URL-safe identifier, lowercase with dashes.', + pattern: '^[a-z0-9-]+$', + example: 'hello-world', + }, + }, +} +``` + +Any Schema Object keyword works — `format`, `deprecated`, length and range constraints, or a full type override: + + + +```ts format + example +{ + name: 'website', + type: 'text', + custom: { + openapi: { format: 'uri', example: 'https://example.com' }, + }, +} +``` + +```ts deprecated +{ + name: 'legacyField', + type: 'text', + custom: { + openapi: { deprecated: true }, + }, +} +``` + +```ts constraints +{ + name: 'phone', + type: 'text', + custom: { + openapi: { + format: 'phone', + pattern: '^\\+[1-9]\\d{1,14}$', + example: '+14155552671', + }, + }, +} +``` + +```ts type override +// The plugin infers `string` for a text field. If your hooks store +// structured data instead, override the inferred type entirely: +{ + name: 'coordinates', + type: 'json', + custom: { + openapi: { + type: 'object', + properties: { + lat: { type: 'number' }, + lng: { type: 'number' }, + }, + required: ['lat', 'lng'], + }, + }, +} +``` + + + + + Fields nested in `row`, `tabs`, and other layout fields are annotated the same way — the plugin flattens layout fields + when it looks for `custom.openapi`. + + +## Localized strings + +`description`, `title`, and `summary` under `custom.openapi` are localizable. Give them a function or a locale-keyed object instead of a plain string, and they resolve against the request language — the same way Payload labels do: + + + +```ts function collections/Posts.ts +const slug: Field = { + name: 'slug', + type: 'text', + custom: { + openapi: { + description: ({ t }) => t('fields:slugHelp'), + }, + }, +} +``` + +```ts locale map collections/Posts.ts +const phone: Field = { + name: 'phone', + type: 'text', + custom: { + openapi: { + description: { + en: 'E.164 formatted phone number', + de: 'Telefonnummer im E.164-Format', + }, + }, + }, +} +``` + + + +The function receives `{ t, i18n }` for the active request language. A locale map is any object whose keys are your project's locales and whose values are strings. Resolution happens per request: `GET /api/openapi.json?lang=de` returns the German descriptions. See [i18n](/v1/guides/i18n) for how the language is picked. + + + Only `description`, `title`, and `summary` are treated this way. Other keys keep their values as-is, so an object + under `properties` named `en` is never mistaken for a locale map. + + +## Entity-level metadata + +Collections and globals accept a `custom.openapi` key too. At the entity level the plugin reads `security` — a per-operation override of the automatic public/secured marking: + +```ts collections/Posts.ts +export const Posts: CollectionConfig = { + slug: 'posts', + custom: { + openapi: { + // Reads are public; every write shows a padlock. + security: { read: 'public', create: 'secured', update: 'secured', delete: 'secured' }, + }, + }, + fields: [/* … */], +} +``` + +Pass `'public'` or `'secured'` to mark every operation at once, or a partial `{ read, create, update, delete }` map of the same values. See [Security marking](/v1/configuration/security-marking) for how this interacts with the access-function probe and the `security` option. + +The title and description shown on an entity's tag in the docs UI come from Payload's own config: the tag description is the collection's or global's `admin.description`, which is also resolved against the request language. + +## Related + + + + How operations get their public/secured marking and every way to override it. + + + The same `custom.openapi` convention on endpoints, as a full Operation Object. + + diff --git a/docs/v1/guides/i18n.mdx b/docs/v1/guides/i18n.mdx new file mode 100644 index 0000000..0252321 --- /dev/null +++ b/docs/v1/guides/i18n.mdx @@ -0,0 +1,100 @@ +--- +title: 'Internationalization' +sidebarTitle: 'i18n' +description: 'UI translations for 44 locales, per-request language resolution, ?lang= on the spec endpoint, and language switchers in the docs UI.' +--- + +The plugin ships UI translations for 44 locales, automatically merged into your Payload i18n configuration under the `@seshuk/payload-plugin-openapi` namespace. Only the languages present in your project's `i18n.supportedLanguages` are merged in — the plugin never adds languages your project doesn't declare. + +## How language resolution works + +Entity titles and descriptions in the generated spec are resolved through the active request locale, the same way Payload resolves its own labels. The spec endpoint also honors an explicit `?lang=` query param: + +```bash +curl https://example.com/api/openapi.json?lang=ru +``` + +returns the document with Russian descriptions. Without `?lang=`, the request language (or the i18n fallback) applies. + +## Language switcher in Scalar + +Scalar supports multiple spec sources natively. Point `configuration.sources` at the same runtime endpoint with different `?lang=` values — nothing is pre-generated, each language resolves on request: + +```ts payload.config.ts +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' } }), + scalar({ + configuration: { + sources: [ + { title: 'English', url: '/api/openapi.json?lang=en', default: true }, + { title: 'Русский', url: '/api/openapi.json?lang=ru' }, + ], + }, + }), +] +``` + + + When `sources` is set, the renderer's own `specURL` is ignored. The first entry is the default unless another entry + sets `default: true`. + + +The same switcher works against static files instead of the runtime endpoint — generate one file per language with [`--lang all`](/v1/cli/generate) and point the `sources` at `/openapi.en.json`, `/openapi.ru.json`, and so on. See [Serve a pre-generated spec](/v1/guides/static-spec). + +## Swagger UI: one instance per language + +Swagger UI has no built-in switcher. Mount one instance per language, each on its own `path`: + +```ts payload.config.ts +import { openapi, swaggerUi } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' } }), + swaggerUi({ path: '/docs/en', specURL: '/api/openapi.json?lang=en' }), + swaggerUi({ path: '/docs/ru', specURL: '/api/openapi.json?lang=ru' }), +] +``` + +## Localizable field metadata + +`description`, `title`, and `summary` in `custom.openapi` metadata are localizable. Give them a function or a locale-keyed object and they resolve against the request language, the same way Payload labels do: + +```ts +import type { Field } from 'payload' + +const slug: Field = { + name: 'slug', + type: 'text', + custom: { + openapi: { + // a function… + description: ({ t }) => t('fields:slugHelp'), + // …or a locale map: + // description: { en: 'URL-safe identifier', ru: 'URL-совместимый идентификатор' }, + }, + }, +} +``` + +See [Field metadata](/v1/guides/field-metadata) for the full merge behavior. + +## Generating translated files + +The [`openapi:generate` CLI](/v1/cli/generate) takes `--lang` for a single locale, or `--lang all` to write one file per supported language (`openapi..json`): + +```bash +payload openapi:generate --lang all +``` + +## Supported locales + +`ar`, `az`, `bg`, `bn` (BD/IN), `ca`, `cs`, `da`, `de`, `en`, `es`, `et`, `fa`, `fr`, `he`, `hr`, `hu`, `hy`, `id`, `is`, `it`, `ja`, `ko`, `lt`, `lv`, `my`, `nb`, `nl`, `pl`, `pt`, `ro`, `rs` (Cyrillic/Latin), `ru`, `sk`, `sl`, `sv`, `ta`, `th`, `tr`, `uk`, `vi`, `zh`, `zhTw` + +## See also + +- [Generate command](/v1/cli/generate) — `--lang` and `--lang all` +- [Serve a pre-generated spec](/v1/guides/static-spec) — static multi-language files +- [Field metadata](/v1/guides/field-metadata) — localizable `custom.openapi` strings +- [Docs UI](/v1/configuration/docs-ui) — Scalar and Swagger UI options diff --git a/docs/v1/guides/plugin-authors.mdx b/docs/v1/guides/plugin-authors.mdx new file mode 100644 index 0000000..fcb3334 --- /dev/null +++ b/docs/v1/guides/plugin-authors.mdx @@ -0,0 +1,91 @@ +--- +title: 'For plugin authors' +description: 'Make your Payload plugin OpenAPI-ready with custom.openapi metadata — no dependency on this package, no wiring for your users.' +--- + +If you maintain a Payload plugin that adds endpoints, collections, or fields, you can make them show up in the generated spec **without depending on this package** — and without your users having to wire anything up. Attach a `custom.openapi` Operation Object to each endpoint you add, and annotate the fields you add the same way. + + + `custom.openapi` is a plain convention on Payload's own `custom` key — the same one Payload's agent skills document. + Your plugin does not import anything from `@seshuk/payload-plugin-openapi`, so it adds zero dependencies and zero + bundle weight. + + +## A documented plugin + +```ts src/index.ts +import type { Config, Plugin } from 'payload' + +import { syncHandler } from './handlers.js' + +export const myPlugin = + (): Plugin => + (config: Config): Config => ({ + ...config, + endpoints: [ + ...(config.endpoints ?? []), + { + path: '/my-feature/sync', + method: 'post', + handler: syncHandler, + custom: { + // Picked up automatically if the OpenAPI plugin is installed; ignored otherwise. + openapi: { + summary: 'Trigger a sync', + tags: ['My Feature'], + responses: { '202': { description: 'Sync queued' } }, + }, + }, + }, + ], + collections: [ + ...(config.collections ?? []), + { + slug: 'sync-log', + fields: [ + { + name: 'status', + type: 'select', + options: ['queued', 'running', 'done'], + custom: { + openapi: { + description: 'Lifecycle state of the sync run.', + example: 'queued', + }, + }, + }, + ], + }, + ], + }) +``` + +That is the whole integration. When a user has the OpenAPI plugin installed, your endpoint appears in their spec under its own tag, and your field carries its description and example into the generated schemas. See [Custom endpoints](/v1/guides/custom-endpoints) for the operation shape and [Field metadata](/v1/guides/field-metadata) for everything fields support. + +## Why plugin order does not matter + +The OpenAPI plugin does not read your config when it runs. It builds the document **lazily, on request, from the fully sanitized config**. By the time a request hits `/api/openapi.json`, every plugin in the `plugins` array has already run — so your endpoints, collections, and fields are visible no matter where either plugin sits in the list. There is no ordering constraint to document and nothing for your users to get wrong. + +## The key is inert without the plugin + +`custom.openapi` is data on Payload's `custom` key, nothing more. If the OpenAPI plugin is not installed, the metadata sits there unused — no runtime cost, no behavior change, no errors. You can ship it unconditionally in your plugin and let each user decide whether they generate a spec. + + + Users can still drop your endpoints from their spec with `filters.includeCustom: false` or entity/operation + [filters](/v1/configuration/filters) — the metadata is an offer, not an obligation. + + +## Beyond single endpoints and fields + +Anything not tied to one endpoint or field — shared components, extra tags, a post-build transform — belongs in an [extension](/v1/configuration/extensions). Extensions are an opt-in that **users** wire into their own `openapi({ extensions: [...] })` call; there is no implicit cross-plugin channel to maintain. Export an `OpenApiExtension` object from your package if you want to offer one, and document that users pass it themselves. + +## Related + + + + The full Operation Object convention, path normalization, and collection sources. + + + Shared components, extra tags, and document transforms. + + diff --git a/docs/v1/guides/programmatic-use.mdx b/docs/v1/guides/programmatic-use.mdx new file mode 100644 index 0000000..9f760af --- /dev/null +++ b/docs/v1/guides/programmatic-use.mdx @@ -0,0 +1,98 @@ +--- +title: 'Programmatic use' +description: 'Build the OpenAPI document in your own code with buildOpenApiDocument and downconvert it with toOpenApi30 / toOpenApi31.' +--- + +Need the document in your own code — a custom build step, a test, a deploy script? `buildOpenApiDocument` builds it from a live Payload instance, and the downconverters are exported alongside it. Everything comes from the main entry point (see [Exports](/v1/reference/exports)). + +## Signature + +```ts +import type { BasePayload } from 'payload' +import type { I18n } from '@payloadcms/translations' +import type { Document, ServerObject } from '@scalar/openapi-types/3.2' + +interface BuildOpenApiInput { + payload: BasePayload + options: ResolvedOptions + language?: string + i18n?: I18n + servers?: ServerObject[] +} + +declare const buildOpenApiDocument: (input: BuildOpenApiInput) => Promise +``` + +| Property | Type | Description | +| ---------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ | +| `payload` | `BasePayload` | A live Payload instance (from `getPayload`). | +| `options` | `ResolvedOptions` | The plugin's resolved options — read them from `config.custom` (below). | +| `language` | `string` | Optional locale for translated descriptions. Defaults to `i18n.fallbackLanguage`. | +| `i18n` | `I18n` | Optional pre-built i18n instance. When set, `language` is not used to create one. | +| `servers` | `ServerObject[]` | Base URLs written to the spec's `servers`. Defaults to the `servers` option when it is an array, then `serverURL`. | + +The returned document already honors `options.openapiVersion`: `'3.0'` and `'3.1'` are downconverted before the promise resolves, so you only call `toOpenApi30` / `toOpenApi31` yourself when you need an extra version of a 3.2 document. + +## Where `options` comes from + +`buildOpenApiDocument` takes **resolved** options — the internal shape with every default filled in — not the raw object you pass to `openapi()`. You never construct it by hand. The plugin resolves your options once at config build time and stashes the result on `config.custom` under the package name; read it from there: + +```ts +const options = payload.config.custom?.['@seshuk/payload-plugin-openapi'] +``` + +This is exactly what the [`openapi:generate` CLI](/v1/cli/generate) does internally, and it means your script always builds with the same options the runtime endpoint uses — one source of truth. + + + `ResolvedOptions` is an internal type and is not exported. Keep the `config.custom` value opaque and pass it straight + through to `buildOpenApiDocument` — its parameter type checks the shape for you. + + +## Example script + +```ts scripts/build-spec.ts +import { writeFile } from 'node:fs/promises' +import { getPayload } from 'payload' +import { buildOpenApiDocument, toOpenApi30 } from '@seshuk/payload-plugin-openapi' +import config from '../src/payload.config.js' + +const payload = await getPayload({ config: await config }) + +const options = payload.config.custom?.['@seshuk/payload-plugin-openapi'] +if (!options) throw new Error('openapi() is not registered in payload.config') + +const doc = await buildOpenApiDocument({ + payload, + options, + language: 'en', // optional + servers: [{ url: 'https://api.example.com' }], +}) + +// Need an extra 3.0 variant of a 3.2 document? Downconvert it yourself. +const v30 = toOpenApi30(doc) + +await writeFile('openapi.json', JSON.stringify(doc, null, 2)) +await writeFile('openapi.3.0.json', JSON.stringify(v30, null, 2)) + +await payload.destroy() +``` + + + If all you want is the document on disk, you don't need a script — the [`openapi:generate` CLI](/v1/cli/generate) + covers that, including one file per language with `--lang all`. + + +## Downconverters + +| Function | Signature | Description | +| ------------- | ----------------------------- | --------------------------------- | +| `toOpenApi30` | `(doc: Document) => Document` | Downconvert a 3.2 document to 3.0 | +| `toOpenApi31` | `(doc: Document) => Document` | Downconvert a 3.2 document to 3.1 | + +The plugin always builds 3.2 and downconverts from there — the same passes run when you set [`openapiVersion`](/v1/configuration/openapi-version). + +## See also + +- [Generate command](/v1/cli/generate) — the no-code path to a spec file +- [Exports](/v1/reference/exports) — the full public API surface +- [OpenAPI version](/v1/configuration/openapi-version) — how the 3.2-first build works diff --git a/docs/v1/guides/static-spec.mdx b/docs/v1/guides/static-spec.mdx new file mode 100644 index 0000000..5d0ac2b --- /dev/null +++ b/docs/v1/guides/static-spec.mdx @@ -0,0 +1,91 @@ +--- +title: 'Serve a pre-generated spec' +description: 'Generate the OpenAPI document to a static file with serve: false and host it yourself — no runtime endpoint, no per-request work.' +--- + +By default the plugin both registers the [`openapi:generate` CLI](/v1/cli/generate) **and** serves the spec at `/api/openapi.json`. If you only want the file — generated at build time, hosted like any other static asset — turn the runtime endpoint off with `serve: false`. + + + Even with the default runtime endpoint, you are not re-generating on every request. With `cache: true` (the default) + the document is built once on first hit and reused for the life of the process — only `servers` is set per request. + See [Caching](/v1/configuration/caching). `serve: false` is for when you want **no** runtime endpoint at all, not + merely to avoid rebuild cost. + + +## Turn off the runtime endpoint + +```ts payload.config.ts +import { openapi } from '@seshuk/payload-plugin-openapi' +import { buildConfig } from 'payload' + +export default buildConfig({ + plugins: [ + openapi({ + info: { title: 'My API', version: '1.0.0' }, + serve: false, // register the CLI generator only; nothing is served over HTTP + }), + ], +}) +``` + +With `serve: false` the plugin mounts neither the spec endpoint nor the [interactive-auth](/v1/configuration/interactive-auth) endpoint. + + + The plugin still must stay in your `plugins` array. That call is what registers the `openapi:generate` CLI command and + stashes the resolved options it reads — remove it and the CLI stops working too. + + +## Generate into your static assets + +Write the file wherever your app serves static files — `public/` in a Next.js project: + +```bash +payload openapi:generate --server https://api.example.com --out ./public/openapi.json +``` + +`--server` sets the base URL in the file. Without it, the command uses the `servers` option or `serverURL` (see [the CLI reference](/v1/cli/generate)). + +Now `public/openapi.json` is served at `/openapi.json` like any other static file — through Next.js, a CDN, nginx, or committed to the repo. There is no per-request work and no way to hit a stale or unauthenticated spec at runtime. + +## Point the docs UI at the file + +The docs UI plugins load the spec from a URL, and that URL does not have to be the plugin's own endpoint. Point `specURL` at your static file and the UI renders it directly: + +```ts payload.config.ts +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' + +plugins: [ + openapi({ info: { title: 'My API', version: '1.0.0' }, serve: false }), + scalar({ specURL: '/openapi.json' }), // loads public/openapi.json +] +``` + +The same works for `swaggerUi()`. See [Docs UI](/v1/configuration/docs-ui) for the renderer options. + +## Multiple languages + +Generate one file per supported language with `--lang all`: + +```bash +payload openapi:generate --server https://api.example.com --lang all +# → openapi.en.json, openapi.ru.json, … +``` + +Move the files into `public/` and wire a language switcher exactly as in [Internationalization](/v1/guides/i18n) — just point Scalar's `sources` at the static files instead of the runtime endpoint: + +```ts payload.config.ts +scalar({ + configuration: { + sources: [ + { title: 'English', url: '/openapi.en.json', default: true }, + { title: 'Русский', url: '/openapi.ru.json' }, + ], + }, +}) +``` + +## See also + +- [Generate command](/v1/cli/generate) — all flags, plus a CI diff example +- [Docs UI](/v1/configuration/docs-ui) — Scalar and Swagger UI options +- [Caching](/v1/configuration/caching) — what the default runtime endpoint actually costs diff --git a/docs/v1/introduction.mdx b/docs/v1/introduction.mdx new file mode 100644 index 0000000..58c6df3 --- /dev/null +++ b/docs/v1/introduction.mdx @@ -0,0 +1,52 @@ +--- +title: 'Introduction' +description: 'Generate OpenAPI 3.0/3.1/3.2 documents from your Payload CMS config and serve them with Scalar or Swagger UI.' +--- + +`@seshuk/payload-plugin-openapi` builds an OpenAPI document straight from your Payload CMS config. Add it to your `plugins` array and you get a complete spec — collections, globals, auth, versions, and jobs — plus an interactive API reference, with zero annotation needed. + +The document is built lazily from the fully sanitized config, so endpoints and fields added by other plugins show up too, regardless of plugin order. + +## Key features + +- **Full spec from your config** — collections, globals, auth, versions, and jobs are documented automatically. +- **Native metadata** — document custom endpoints and refine field schemas with Payload's own `custom.openapi` key. No wrapper, no separate registry. +- **Interactive docs out of the box** — mount Scalar or Swagger UI, or both. +- **Localized** — descriptions resolve through your Payload i18n; the UI ships translations for 44 locales. +- **Precise filtering** — control exactly which entities and operations are exposed. +- **Security marking** — operations are marked public or secured by probing your access functions, with per-entity and per-operation overrides. +- **File generation** — write the spec to disk for CI, schema diffs, or client codegen. +- **One option for the spec version** — OpenAPI 3.0, 3.1, or 3.2 from the same config. + +## Requirements + +Payload `4.0.0-canary.38`, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the `beta` npm tag, and Payload 4 is published under `canary`. For Payload 3, use the `0.x` versions. + +## Where to go next + + + + Install the plugin and get a live spec and docs UI in one config change. + + + Every plugin option, from metadata to caching and extensions. + + + Serve Scalar or Swagger UI, and pass configuration through to either. + + + Choose which entities and operations end up in the spec. + + + How operations are marked public or secured, and how to override it. + + + Document your own endpoints with a plain OpenAPI Operation Object. + + + Write the spec to a file with `payload openapi:generate`. + + + Copy-ready configurations for common setups. + + diff --git a/docs/v1/quick-start.mdx b/docs/v1/quick-start.mdx new file mode 100644 index 0000000..d5b06af --- /dev/null +++ b/docs/v1/quick-start.mdx @@ -0,0 +1,81 @@ +--- +title: 'Quick start' +description: 'Install the plugin, add openapi() and scalar() to your Payload config, and get a live spec with interactive docs.' +--- + + + + + + + +```bash npm +npm install @seshuk/payload-plugin-openapi@beta +``` + +```bash pnpm +pnpm add @seshuk/payload-plugin-openapi@beta +``` + +```bash yarn +yarn add @seshuk/payload-plugin-openapi@beta +``` + + + + + Requires Payload `4.0.0-canary.38`, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the `beta` + npm tag, and Payload 4 is published under `canary`. For Payload 3, use the `0.x` versions. + + + + + + +Add `openapi()` for the spec and `scalar()` for the docs UI: + +```ts payload.config.ts +import { openapi, scalar } from '@seshuk/payload-plugin-openapi' +import { buildConfig } from 'payload' + +export default buildConfig({ + // ... + plugins: [ + openapi({ + info: { + title: 'My API', + version: '1.0.0', + }, + }), + scalar(), + ], +}) +``` + +`info.title` and `info.version` are required. The plugin throws on boot if either is missing. + + + + + +Two endpoints are now live: + +- `GET /api/openapi.json` — the generated OpenAPI document +- `GET /api/docs` — interactive API reference (Scalar) + +Both are mounted under your Payload API route (`routes.api`, `/api` by default), since the plugin registers them as Payload endpoints. The `path` options you pass to `openapi()`, `scalar()` and `swaggerUi()` are relative to that route. + +Prefer Swagger UI? Swap `scalar()` for `swaggerUi()` — or mount both on different paths. See [Docs UI](/v1/configuration/docs-ui). + + + + + +## Next steps + +- [Configuration overview](/v1/configuration/overview) — every plugin option +- [Filters](/v1/configuration/filters) — choose which entities and operations are documented +- [Security marking](/v1/configuration/security-marking) — how operations get their public/secured padlock +- [Custom endpoints](/v1/guides/custom-endpoints) — document your own endpoints with `custom.openapi` +- [CLI generation](/v1/cli/generate) — write the spec to a file for CI or codegen +- [Examples](/v1/guides/examples) — complete configurations for common setups diff --git a/docs/v1/reference/endpoints.mdx b/docs/v1/reference/endpoints.mdx new file mode 100644 index 0000000..20c0f67 --- /dev/null +++ b/docs/v1/reference/endpoints.mdx @@ -0,0 +1,135 @@ +--- +title: 'Endpoints' +description: 'Every Payload REST endpoint the plugin documents, when it appears, and the filter that controls it.' +--- + +The plugin documents the REST endpoints that Payload 4 mounts for your config. An endpoint is in the spec only when Payload serves it. Paths below use the default API route `/api`. `` is a collection slug and `` is a global slug. + +## Collections + +| Endpoint | Method | When | Filter | +| ----------------------------- | ------------------- | ---------------------------------------------------- | ---------------------------------- | +| `/api/` | GET, POST | Always | — | +| `/api/` | PATCH, DELETE | `disableBulkEdit` is not set | — | +| `/api//count` | GET | Always | — | +| `/api//{id}` | GET, PATCH, DELETE | Always | — | +| `/api//validate` | POST | Always | — | +| `/api//{id}/validate` | POST | Always | — | +| `/api//{id}/duplicate` | POST | `disableDuplicate` is not set (auth: off by default) | — | +| `/api//file/{filename}` | GET | Upload collection | — | +| `/api//versions` | GET | `versions` is not `false` | `includeVersions` | +| `/api//versions/{id}` | GET, POST (restore) | `versions` is not `false` | `includeVersions` | +| `/api//access` | POST | Always | `includeAuth` + `includeAdminAuth` | +| `/api//access/{id}` | POST | Always | `includeAuth` + `includeAdminAuth` | + +## Auth collections + +| Endpoint | Method | When | Filter | +| --------------------------------- | ------ | ------------------------------- | ---------------------------------- | +| `/api//me` | GET | Always | `includeAuth` | +| `/api//logout` | POST | Always | `includeAuth` | +| `/api//refresh-token` | POST | Always | `includeAuth` | +| `/api//login` | POST | Local strategy on | `includeAuth` | +| `/api//forgot-password` | POST | Local strategy on | `includeAuth` | +| `/api//reset-password` | POST | Local strategy on | `includeAuth` | +| `/api//unlock` | POST | Local strategy on | `includeAuth` | +| `/api//verify/{id}` | POST | Local strategy on, `verify` set | `includeAuth` | +| `/api//{id}/api-key/reveal` | POST | `useAPIKey: { reveal: true }` | `includeAuth` | +| `/api//init` | GET | Always | `includeAuth` + `includeAdminAuth` | +| `/api//first-register` | POST | Local strategy on | `includeAuth` + `includeAdminAuth` | + +"Local strategy on" means `auth.disableLocalStrategy` is not set. Login, forgot-password and unlock take `email` by default. With `loginWithUsername` they take `username`, or either one when `allowEmailLogin` is on. + +## Globals + +| Endpoint | Method | When | Filter | +| ------------------------------------- | ------------------- | ------------------------- | ---------------------------------- | +| `/api/globals/` | GET, POST | Always | — | +| `/api/globals//validate` | POST | Always | — | +| `/api/globals//versions` | GET | `versions` is not `false` | `includeVersions` | +| `/api/globals//versions/{id}` | GET, POST (restore) | `versions` is not `false` | `includeVersions` | +| `/api/globals//access` | POST | Always | `includeAuth` + `includeAdminAuth` | + +## System + +| Endpoint | Method | When | Filter | +| ------------------------------------- | ----------- | --------------------------------- | ---------------------------------- | +| `/api/access` | GET | Always | `includeAuth` + `includeAdminAuth` | +| `/api/upload-instructions` | POST | At least one upload collection | — | +| `/api/upload-instructions/{uploadId}` | PUT, DELETE | At least one upload collection | — | +| `/api/payload-jobs/run` | GET | Tasks or workflows are configured | `includeJobs` | +| `/api/payload-jobs/handle-schedules` | GET | Tasks or workflows are configured | `includeJobs` | + +## Request and response bodies + +Each collection gets a read schema (`Posts`), a create body (`PostsCreate`) and an update body (`PostsUpdate`). A global gets a read schema (`GlobalSettings`) and an update body (`GlobalSettingsUpdate`). + +The read schema shows a relationship or upload as an ID or the populated document. Create and update bodies take only the ID, at any depth: top level, groups, tabs, arrays and blocks. A polymorphic relationship takes `{ relationTo, value }`, and `hasMany` takes an array of them. The bodies follow the input types Payload generates, so they leave out join and virtual fields and `createdAt`/`updatedAt`, and a field with a `defaultValue` is optional. A field whose `access.create` or `access.update` is a function with no parameters that returns `false`, like `() => false`, is left out of that body, because Payload ignores it over REST. This covers the `createdBy` and `updatedBy` fields Payload adds. In an update body every field is optional. + +The responses follow what Payload sends. Create returns `201` with `{ doc, message }`. Update, delete and duplicate by ID return `{ doc, message }`, and bulk update and delete return `{ docs, errors, message }`. The global update returns `{ message, result }`. A restored version is the document plus `message` for a collection, and `{ doc, message }` for a global. + +## Write params + +Create, update, delete and duplicate list the query params Payload reads for them: `depth`, `locale`, `fallback-locale`, `select`, `populate`, `draft` and `trash`. Bulk update and delete also take `where`. Bulk update also takes `limit` and `sort`, which pick how many matching documents it updates and in what order. These params depend on the config: + +| Param | Operations | When | +| --------------------- | --------------------------------------------------- | ------------------------------------------------------ | +| `autosave` | Create, update by ID, global update | Drafts with `autosave` on | +| `publishAllLocales` | Create, update, update by ID, global update | Drafts keep their status per locale (`localizeStatus`) | +| `unpublishAllLocales` | Update, update by ID, global update | Drafts keep their status per locale (`localizeStatus`) | +| `overrideLock` | Update and delete, bulk or by ID (collections only) | `lockDocuments` is not `false` | +| `selectedLocales[]` | Duplicate | Localization is on | + +Payload sets `localizeStatus` itself when localization is on and the collection or global has localized fields. Send `selectedLocales[]` once per locale, for example `?selectedLocales[]=en&selectedLocales[]=de`. + +## Validate + +`POST /api//validate` checks data for a new document. The body is required and uses the create body (`PostsCreate`). `POST /api//{id}/validate` checks changes to a saved document. Its body is optional and uses the update body (`PostsUpdate`). Payload merges it over the newest draft or the saved document. `POST /api/globals//validate` does the same for a global. Nothing is saved. + +The response is `200` with `{ valid, errors }`, also when a field is not valid. Each error has `message` and `path`, and can have `label`, `locale` and `tableName`. Payload returns `400` for a bad body or locale, `403` without access and `404` when the document does not exist. + +The only query param is `locale`, and it is listed only when localization is on. It takes one or more locales or `all`. Send it once per locale, for example `?locale=en&locale=de`. The [security marking](/v1/configuration/security-marking) follows `access.validate`, which is `access.update` unless you set it. + +## Document access + +`POST /api//access/{id}` returns what the current user may do with one document: `create`, `read`, `update`, `delete`, `validate`, plus `readVersions` when the collection has versions and `unlock` when it sets `auth.maxLoginAttempts`. Each one is `true` or `{ permission, where }`. `fields` is `true` or the permissions per field. Without `{id}`, Payload checks the collection, not a saved document. `POST /api/globals//access` returns `read`, `update`, `validate` and `readVersions` for a global. The body is optional: send document data to check access against it instead of the saved document. Payload leaves out an operation the user may not do. + +## Your own endpoints + +Your own endpoints with `custom.openapi` are added too. See [Custom endpoints](/v1/guides/custom-endpoints). + +To drop a single endpoint, use `excludeOperations` in [Filters](/v1/configuration/filters). It works on every endpoint on this page, your own endpoints included. + +## New in Payload 4 + +- **Upload instructions.** A client can ask Payload where to send the file bytes before it saves the document. Call `POST /api/upload-instructions` with `collectionSlug`, `filename`, `filesize` and `mimeType`. The response tells you where to send the bytes (`type: 'http'`) or which client handler to run (`type: 'dispatch'`). It also returns a `file` object. Send that object as the `file` field of the create or update request. For staged uploads, send the bytes with `PUT /api/upload-instructions/{uploadId}`. Remove them with `DELETE`. You must be logged in and have create or update access. +- **Versions are on by default.** Collections and globals have versions unless you set `versions: false`. The version endpoints appear in the spec for each of them. +- **Folders and tags.** See [Folders and tags](#folders-and-tags). + +## Folders and tags + +In Payload 4, folders and tags are ordinary collections with `folders`, `tags` or `hierarchy` set. There is no `payload-folders` system collection and no extra REST endpoint. The plugin documents a folder or tag collection like any other collection, with the standard [collection endpoints](#collections), and `includeSystem` does not hide it. + +The spec also covers the fields that Payload adds: + +| Field | On | In the spec | +| -------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------- | +| `_h_` | The folder or tag collection | The parent, as a relationship. You can filter on it in `where`. | +| `_h_slugPath`, `_h_titlePath` | The folder or tag collection | Read-only. Not in create or update bodies, and not in `where`, which rejects them. | +| `hierarchyType` | With `collectionSpecific` | The collections a folder may hold, as an enum. | +| The `joinField` | When `joinField` is set | A join that lists child folders and related documents as `{ relationTo, value }`. | +| `createFolderField` / `createTagField` | Collections that use a folder | A relationship to the folder or tag collection. | + +Payload fills `_h_slugPath` and `_h_titlePath` only when you ask for them. The `GET /api/` and `GET /api//{id}` operations of a folder or tag collection list a `computeHierarchyPaths` query param for this. Selecting either field with `select` computes them too. Both fields work in `sort`. If you rename them with `slugPathFieldName` or `titlePathFieldName`, the spec uses your names. + +## Not documented + +These endpoints exist but are not in the spec. They serve the admin panel or are internal. + +| Endpoint | Reason | +| -------------------------------- | ----------------------------------------- | +| `GET /api//paste-url/{id}` | Admin panel helper for `pasteURL` uploads | +| `POST /api/reorder` | Admin panel drag-and-drop for `orderable` | +| `GET /api/og` | Admin panel Open Graph image (Next.js) | +| `POST /api/graphql` | GraphQL, not REST | +| `/api/payload-preferences/{key}` | Admin panel preferences | diff --git a/docs/v1/reference/exports.mdx b/docs/v1/reference/exports.mdx new file mode 100644 index 0000000..871a055 --- /dev/null +++ b/docs/v1/reference/exports.mdx @@ -0,0 +1,72 @@ +--- +title: 'Exports' +description: 'The full public API surface of @seshuk/payload-plugin-openapi — plugins, functions, and types from the single entry point.' +--- + +Everything is exported from the single main entry point: + +```ts +import { + openapi, + scalar, + swaggerUi, + buildOpenApiDocument, + toOpenApi30, + toOpenApi31, +} from '@seshuk/payload-plugin-openapi' +``` + +## Values + +| Export | Kind | Description | +| ---------------------- | -------- | ------------------------------------------------------------------------------------------------- | +| `openapi` | plugin | The main plugin — generates and serves the spec, registers the [CLI](/v1/cli/generate) | +| `scalar` | plugin | Mounts a Scalar API reference UI ([Docs UI](/v1/configuration/docs-ui)) | +| `swaggerUi` | plugin | Mounts a Swagger UI ([Docs UI](/v1/configuration/docs-ui)) | +| `buildOpenApiDocument` | function | Build the document from a live Payload instance ([Programmatic use](/v1/guides/programmatic-use)) | +| `toOpenApi30` | function | Downconvert a 3.2 document to 3.0 | +| `toOpenApi31` | function | Downconvert a 3.2 document to 3.1 | + +## Plugin slugs + +The three plugins are built with Payload's `definePlugin`, so each one carries a `slug` and the `options` you passed. Another plugin can find them through the `plugins` map that `definePlugin` hands it, or in `config.plugins`: + +| Plugin | Slug | +| ----------- | ------------------------------------------- | +| `openapi` | `@seshuk/payload-plugin-openapi` | +| `scalar` | `@seshuk/payload-plugin-openapi/scalar` | +| `swaggerUi` | `@seshuk/payload-plugin-openapi/swagger-ui` | + +## Types + +All public types come from the same entry point, so editor autocomplete picks them up on `import type { … } from '@seshuk/payload-plugin-openapi'`. + +| Type | Description | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `OpenApiPluginOptions` | The options object accepted by `openapi()` | +| `OpenApiInfo` | The `info` option: an OpenAPI Info Object with a localizable `description` ([Info and servers](/v1/configuration/info)) | +| `ServersOption` | The `servers` option: a server list or a function of `{ req }` ([Servers](/v1/configuration/info#servers)) | +| `LocalizedText` | A string, a language-keyed object or a `({ t, i18n }) => string` function | +| `OpenApiVersion` | `'3.0' \| '3.1' \| '3.2'` | +| `FilterOptions` | The `filters` object — entity include/exclude, endpoint-group toggles, operation rules ([Filters](/v1/configuration/filters)) | +| `OperationContext` | `{ method, path, slug, kind }` passed to `filters.excludeOperations` functions and `security` | +| `OperationRule` | One `filters.excludeOperations` rule — `method`, `slug`, `kind`, `path` | +| `OperationFilter` | One `filters.excludeOperations` entry: an `OperationRule` or a function | +| `OperationKind` | `'collection' \| 'global' \| 'custom' \| 'jobs' \| 'system'` | +| `SecurityOption` | The `security` function ([Security marking](/v1/configuration/security-marking)) | +| `SecurityMarking` | `'public' \| 'secured'` | +| `AccessOption` | The `access` function of `openapi()`, `scalar()` and `swaggerUi()` | +| `EntityOperation` | `'read' \| 'create' \| 'update' \| 'delete' \| 'validate'` | +| `EntitySecurityOverride` | Shape of `custom.openapi.security` on an entity — a `SecurityMarking` or a per-operation map ([Security marking](/v1/configuration/security-marking)) | +| `EntityOpenApiMeta` | Type of `custom.openapi` on a collection or global. The plugin adds it to Payload's `CollectionCustom` and `GlobalCustom` | +| `FieldOpenApiMeta` | Type of `custom.openapi` on a field. The plugin adds it to Payload's `FieldCustom` | +| `OpenApiExtension` | Extra `paths`/`components`/`tags` and a `transform` hook ([Extensions](/v1/configuration/extensions)) | +| `BuildContext` | Build-time context — locales, API route, doc languages, active i18n | +| `ExtensionContext` | `BuildContext` plus `doc`, `payload` and the resolved `options`, passed to extension `transform` hooks as one object | +| `BuildOpenApiInput` | The input object of `buildOpenApiDocument` ([Programmatic use](/v1/guides/programmatic-use)) | +| `UiPluginOptions` | Options shared by `scalar()` and `swaggerUi()` — `path`, `specURL`, `cdnBase`, `configuration`, `access`, `enabled` | + + + Document types (the return type of `buildOpenApiDocument` and the downconverters) come from + `@scalar/openapi-types/3.2` — the plugin's only runtime dependency. + diff --git a/docs/v1/upgrade-guide.mdx b/docs/v1/upgrade-guide.mdx new file mode 100644 index 0000000..7284905 --- /dev/null +++ b/docs/v1/upgrade-guide.mdx @@ -0,0 +1,67 @@ +--- +title: 'Upgrade guide' +description: 'Upgrade the OpenAPI plugin from 0.x to v1: renamed options, entity security values and changed defaults.' +--- + +This guide covers upgrading from 0.x to v1. v1 needs Payload `4.0.0-canary.38` and Next.js 16.4 or later. + +## 0.x to v1 + +Some options were renamed in v1. The plugin throws at boot when it finds an old name, and the error names the new one. + +| 0.x | v1 | +| ------------------------------------------------------------------- | ----------------------------------------------- | +| `specEndpoint` | `path` | +| `securityWhen` | `security`, returning `'public'` or `'secured'` | +| `filters.excludeWhen` | a function in `filters.excludeOperations` | +| `interactiveAuth.endpoint` | `interactiveAuth.path` | +| `scalar({ specEndpoint })` | `scalar({ specURL })` (same for `swaggerUi`) | +| `metadata` | `info` | +| `custom.openapi.security: true` / `false` on a collection or global | `'public'` / `'secured'` | + +```ts Before (0.x) +openapi({ + metadata: { title: 'My API', version: '1.0.0' }, + specEndpoint: '/spec.json', + interactiveAuth: { endpoint: '/login' }, + securityWhen: ({ slug }) => (slug === 'feed' ? true : undefined), + filters: { excludeWhen: ({ path }) => path.includes('/internal/') }, +}) +``` + +```ts After (v1) +openapi({ + info: { title: 'My API', version: '1.0.0' }, + path: '/spec.json', + interactiveAuth: { path: '/login' }, + security: ({ slug }) => (slug === 'feed' ? 'public' : undefined), + filters: { excludeOperations: [({ path }) => path.includes('/internal/')] }, +}) +``` + +Entity security now uses the same words as the `security` option. A `true` or `false` value throws at boot: + +```ts Before (0.x) +custom: { openapi: { security: { read: true, create: false } } } +``` + +```ts After (v1) +custom: { openapi: { security: { read: 'public', create: 'secured' } } } +``` + +An extension `transform` now gets one object with `doc` and the build context. A `transform` with two parameters throws at boot. A `transform` that still treats its argument as the document fails the spec request, because it returns the object, not a document. Check every `transform`: + +```ts Before (0.x) +transform: (doc, ctx) => doc +``` + +```ts After (v1) +transform: ({ doc, payload }) => doc +``` + +Some defaults changed too: + +- The spec no longer takes `servers` from the request `Host` header, because a client can fake it. It uses `serverURL` from your Payload config, or `servers`. To keep the old behavior for known hosts, list them in [`trustedHosts`](/v1/configuration/info#trusted-hosts). +- An extension `transform` that throws now fails the spec request. Before, the plugin logged the error and served the document without that transform. +- The docs UI loads a pinned library version with [Subresource Integrity](/v1/configuration/docs-ui#pinned-versions-and-sri). +- Upload read schemas show `variants` instead of `sizes`, because Payload renamed image sizes to variants. See the [Payload release notes](https://github.com/payloadcms/payload/releases/tag/v4.0.0-canary.38). diff --git a/media/og.png b/media/og.png new file mode 100644 index 0000000..8ee4af1 Binary files /dev/null and b/media/og.png differ diff --git a/oxfmt.config.ts b/oxfmt.config.ts index 316fe48..38880a9 100644 --- a/oxfmt.config.ts +++ b/oxfmt.config.ts @@ -1,16 +1,7 @@ +import { oxfmtBase } from '@seshuk/payload-plugin-tooling/oxfmt' import { defineConfig } from 'oxfmt' export default defineConfig({ - printWidth: 120, - semi: false, - singleQuote: true, - trailingComma: 'all', - ignorePatterns: [ - '**/node_modules/**', - '**/dist/**', - '**/.next/**', - 'pnpm-lock.yaml', - '**/next-env.d.ts', - 'tests/app/(payload)/**', - ], + ...oxfmtBase, + ignorePatterns: [...oxfmtBase.ignorePatterns, 'tests/.next', '**/next-env.d.ts', 'tests/app/(payload)/**'], }) diff --git a/oxlint.config.ts b/oxlint.config.ts index 1652dd4..59a5058 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,27 +1,20 @@ +import { oxlintBase } from '@seshuk/payload-plugin-tooling/oxlint' import { defineConfig } from 'oxlint' export default defineConfig({ - plugins: ['typescript', 'unicorn', 'import', 'oxc'], - categories: { - correctness: 'error', - suspicious: 'warn', - }, - env: { - node: true, - es2024: true, - }, - ignorePatterns: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/next-env.d.ts', 'tests/app/(payload)/**'], + ...oxlintBase, + ignorePatterns: [...oxlintBase.ignorePatterns, 'tests/.next', '**/next-env.d.ts', 'tests/app/(payload)/**'], rules: { + ...oxlintBase.rules, 'import/no-default-export': 'off', - 'import/no-unassigned-import': 'off', 'unicorn/consistent-function-scoping': 'off', }, overrides: [ + ...oxlintBase.overrides, { files: ['tests/**/*.{ts,tsx,mjs}'], rules: { 'no-await-in-loop': 'off', - 'no-underscore-dangle': ['warn', { allow: ['_payload'] }], }, }, ], diff --git a/package.json b/package.json index f657a7e..b788cf2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@seshuk/payload-plugin-openapi", - "version": "0.2.2", - "description": "OpenAPI 3.0/3.1/3.2 spec generator for Payload CMS, with Scalar/Swagger UI.", + "version": "1.0.0-beta.1", + "description": "OpenAPI 3.0/3.1/3.2 spec generator for Payload, with Scalar/Swagger UI.", "keywords": [ "api-docs", "cms", @@ -23,77 +23,93 @@ "type": "git", "url": "git+https://github.com/maximseshuk/payload-plugin-openapi.git" }, - "funding": "https://ko-fi.com/V7V61UCT39", + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/seshuk" + }, "files": [ "dist" ], "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { - ".": { - "import": "./src/index.ts", - "types": "./src/index.ts", - "default": "./src/index.ts" - }, - "./*": "./*" + ".": "./dist/index.js" }, "publishConfig": { "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { - "import": "./dist/index.js", "types": "./dist/index.d.ts", + "import": "./dist/index.js", "default": "./dist/index.js" } - } + }, + "access": "public", + "registry": "https://registry.npmjs.org/" }, "scripts": { - "build": "rslib build", - "build:watch": "rslib build --watch", - "dev": "cross-env PAYLOAD_CONFIG_PATH=./tests/payload.config.ts next dev tests --turbo", - "dev:safe": "rm -rf .next && cross-env PAYLOAD_CONFIG_PATH=./tests/payload.config.ts next dev tests --turbo --port 3001", + "build": "tsdown", + "build:watch": "tsdown --watch", + "dev": "cross-env PAYLOAD_CONFIG_PATH=./tests/payload.config.ts DATABASE_URL=file:./tests/payload.db next dev tests", "dev:generate-importmap": "cd tests && cross-env PAYLOAD_CONFIG_PATH=./payload.config.ts payload generate:importmap", + "dev:safe": "rm -rf tests/.next && cross-env PAYLOAD_CONFIG_PATH=./tests/payload.config.ts DATABASE_URL=file:./tests/payload.db next dev tests --port 3001", "docs:check-links": "pnpm --dir docs check-links", "docs:dev": "pnpm --dir docs dev", "docs:validate": "pnpm --dir docs validate", - "lint": "oxlint src tests", - "lint:fix": "oxlint src tests --fix", - "format": "oxfmt .", - "format:check": "oxfmt --check .", - "test": "vitest run", - "test:watch": "vitest", - "typecheck": "tsc --noEmit", - "release:patch": "pnpm version patch -m \"chore(release): v%s\"", - "release:minor": "pnpm version minor -m \"chore(release): v%s\"", - "release:major": "pnpm version major -m \"chore(release): v%s\"" + "format": "oxfmt", + "format:check": "oxfmt --check", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "test": "vitest run -c tests/vitest.config.ts", + "test:coverage": "vitest run -c tests/vitest.config.ts --coverage", + "test:int": "vitest run -c tests/vitest.config.ts --project int", + "test:unit": "vitest run -c tests/vitest.config.ts --project unit", + "test:watch": "vitest -c tests/vitest.config.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { "@scalar/openapi-types": "^0.9.1" }, "devDependencies": { - "@payloadcms/db-sqlite": "^3.85.1", - "@payloadcms/next": "^3.85.1", - "@payloadcms/translations": "^3.85.1", - "@rslib/core": "^0.22.1", + "@payloadcms/db-sqlite": "4.0.0-canary.38", + "@payloadcms/next": "4.0.0-canary.38", + "@payloadcms/translations": "4.0.0-canary.38", + "@payloadcms/ui": "4.0.0-canary.38", "@seriousme/openapi-schema-validator": "^2.9.0", + "@seshuk/payload-plugin-tooling": "0.2.3", + "@types/node": "catalog:", + "@vitest/coverage-v8": "^5.0.3", "cross-env": "^10.1.0", - "next": "^16.2.9", - "oxfmt": "^0.54.0", - "oxlint": "^1.69.0", - "payload": "^3.85.1", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "typescript": "^6.0.3", - "vite-tsconfig-paths": "7.0.0-alpha.1", - "vitest": "^4.1.8" + "next": "16.4.0", + "oxfmt": "^0.71.0", + "oxlint": "^1.86.0", + "payload": "4.0.0-canary.38", + "react": "catalog:", + "react-dom": "catalog:", + "tsdown": "^0.23.0", + "typescript": "catalog:", + "vitest": "^5.0.3" }, "peerDependencies": { - "@payloadcms/translations": "^3.53.0", - "payload": "^3.53.0" + "@payloadcms/translations": "4.0.0-canary.38", + "payload": "4.0.0-canary.38" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "^12.0.0", + "onFail": "warn" + }, + "runtime": { + "name": "node", + "version": ">=24.15.0", + "onFail": "warn" + } }, "engines": { - "node": ">=20.9.0" + "node": ">=24.15.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ef2818..502df97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,182 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: ^12.0.0 + version: 12.8.1 + +packages: + + '@pnpm/exe.android-arm64@12.8.1': + resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.8.1': + resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.8.1': + resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.8.1': + resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.8.1': + resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.8.1': + resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.8.1': + resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.8.1': + resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.8.1': + resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.8.1': + resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.8.1': + resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.8.1': + resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.8.1': + resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.8.1': + resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==} + cpu: [x64] + os: [win32] + + pnpm@12.8.1: + resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.8.1': + optional: true + + '@pnpm/exe.android-x64@12.8.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.8.1': + optional: true + + '@pnpm/exe.darwin-x64@12.8.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.8.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.8.1': + optional: true + + '@pnpm/exe.linux-arm64@12.8.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.8.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.8.1': + optional: true + + '@pnpm/exe.linux-s390x@12.8.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.8.1': + optional: true + + '@pnpm/exe.linux-x64@12.8.1': + optional: true + + '@pnpm/exe.win32-arm64@12.8.1': + optional: true + + '@pnpm/exe.win32-x64@12.8.1': + optional: true + + pnpm@12.8.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.8.1 + '@pnpm/exe.android-x64': 12.8.1 + '@pnpm/exe.darwin-arm64': 12.8.1 + '@pnpm/exe.darwin-x64': 12.8.1 + '@pnpm/exe.freebsd-x64': 12.8.1 + '@pnpm/exe.linux-arm64': 12.8.1 + '@pnpm/exe.linux-arm64-musl': 12.8.1 + '@pnpm/exe.linux-ppc64': 12.8.1 + '@pnpm/exe.linux-riscv64': 12.8.1 + '@pnpm/exe.linux-s390x': 12.8.1 + '@pnpm/exe.linux-x64': 12.8.1 + '@pnpm/exe.linux-x64-musl': 12.8.1 + '@pnpm/exe.win32-arm64': 12.8.1 + '@pnpm/exe.win32-x64': 12.8.1 + +--- lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@types/node': + specifier: ^26.1.2 + version: 26.6.4 + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0 + typescript: + specifier: 7.0.2 + version: 7.0.2 + importers: .: @@ -13,56 +186,65 @@ importers: version: 0.9.1 devDependencies: '@payloadcms/db-sqlite': - specifier: ^3.85.1 - version: 3.85.1(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3)) + specifier: 4.0.0-canary.38 + version: 4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2)) '@payloadcms/next': - specifier: ^3.85.1 - version: 3.85.1(@types/react@19.2.15)(graphql@16.14.0)(monaco-editor@0.55.1)(next@16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4))(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + specifier: 4.0.0-canary.38 + version: 4.0.0-canary.38(@types/react@19.2.15)(graphql@16.14.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) '@payloadcms/translations': - specifier: ^3.85.1 - version: 3.85.1 - '@rslib/core': - specifier: ^0.22.1 - version: 0.22.1(core-js@3.42.0)(typescript@6.0.3) + specifier: 4.0.0-canary.38 + version: 4.0.0-canary.38 + '@payloadcms/ui': + specifier: 4.0.0-canary.38 + version: 4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) '@seriousme/openapi-schema-validator': specifier: ^2.9.0 version: 2.9.0 + '@seshuk/payload-plugin-tooling': + specifier: 0.2.3 + version: 0.2.3(@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2)))(oxfmt@0.71.0)(oxlint@1.86.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.22.4)(typescript@7.0.2))(vitest@5.0.3) + '@types/node': + specifier: 'catalog:' + version: 26.6.4 + '@vitest/coverage-v8': + specifier: ^5.0.3 + version: 5.0.3(vitest@5.0.3) cross-env: specifier: ^10.1.0 version: 10.1.0 next: - specifier: ^16.2.9 - version: 16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4) + specifier: 16.4.0 + version: 16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) oxfmt: - specifier: ^0.54.0 - version: 0.54.0 + specifier: ^0.71.0 + version: 0.71.0 oxlint: - specifier: ^1.69.0 - version: 1.69.0 + specifier: ^1.86.0 + version: 1.86.0 payload: - specifier: ^3.85.1 - version: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + specifier: 4.0.0-canary.38 + version: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) react: - specifier: ^19.2.7 - version: 19.2.7 + specifier: 'catalog:' + version: 19.3.0 react-dom: - specifier: ^19.2.7 - version: 19.2.7(react@19.2.7) + specifier: 'catalog:' + version: 19.3.0(react@19.3.0) + tsdown: + specifier: ^0.23.0 + version: 0.23.0(oxc-resolver@11.20.0)(tsx@4.22.4)(typescript@7.0.2) typescript: - specifier: ^6.0.3 - version: 6.0.3 - vite-tsconfig-paths: - specifier: 7.0.0-alpha.1 - version: 7.0.0-alpha.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) + specifier: 'catalog:' + version: 7.0.2 vitest: - specifier: ^4.1.8 - version: 4.1.8(@types/node@25.9.1)(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) + specifier: ^5.0.3 + version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) docs: devDependencies: mint: specifier: ^4.2.810 - version: 4.2.810(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/node@25.9.1)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3) + version: 4.2.810(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/node@26.6.4)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2) packages: @@ -84,68 +266,6 @@ packages: '@ark/util@0.56.2': resolution: {integrity: sha512-9kU2sUE38FZEGG7l3hamYMBieLYEJh2L1mrYD2eXpT+78EnQSV1bhjxJhnxGBMSTbtwpBSDNSK+K60WvaI/DTQ==} - '@ast-grep/napi-darwin-arm64@0.37.0': - resolution: {integrity: sha512-QAiIiaAbLvMEg/yBbyKn+p1gX2/FuaC0SMf7D7capm/oG4xGMzdeaQIcSosF4TCxxV+hIH4Bz9e4/u7w6Bnk3Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@ast-grep/napi-darwin-x64@0.37.0': - resolution: {integrity: sha512-zvcvdgekd4ySV3zUbUp8HF5nk5zqwiMXTuVzTUdl/w08O7JjM6XPOIVT+d2o/MqwM9rsXdzdergY5oY2RdhSPA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@ast-grep/napi-linux-arm64-gnu@0.37.0': - resolution: {integrity: sha512-L7Sj0lXy8X+BqSMgr1LB8cCoWk0rericdeu+dC8/c8zpsav5Oo2IQKY1PmiZ7H8IHoFBbURLf8iklY9wsD+cyA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@ast-grep/napi-linux-arm64-musl@0.37.0': - resolution: {integrity: sha512-LF9sAvYy6es/OdyJDO3RwkX3I82Vkfsng1sqUBcoWC1jVb1wX5YVzHtpQox9JrEhGl+bNp7FYxB4Qba9OdA5GA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@ast-grep/napi-linux-x64-gnu@0.37.0': - resolution: {integrity: sha512-TViz5/klqre6aSmJzswEIjApnGjJzstG/SE8VDWsrftMBMYt2PTu3MeluZVwzSqDao8doT/P+6U11dU05UOgxw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@ast-grep/napi-linux-x64-musl@0.37.0': - resolution: {integrity: sha512-/BcCH33S9E3ovOAEoxYngUNXgb+JLg991sdyiNP2bSoYd30a9RHrG7CYwW6fMgua3ijQ474eV6cq9yZO1bCpXg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@ast-grep/napi-win32-arm64-msvc@0.37.0': - resolution: {integrity: sha512-TjQA4cFoIEW2bgjLkaL9yqT4XWuuLa5MCNd0VCDhGRDMNQ9+rhwi9eLOWRaap3xzT7g+nlbcEHL3AkVCD2+b3A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@ast-grep/napi-win32-ia32-msvc@0.37.0': - resolution: {integrity: sha512-uNmVka8fJCdYsyOlF9aZqQMLTatEYBynjChVTzUfFMDfmZ0bihs/YTqJVbkSm8TZM7CUX82apvn50z/dX5iWRA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@ast-grep/napi-win32-x64-msvc@0.37.0': - resolution: {integrity: sha512-vCiFOT3hSCQuHHfZ933GAwnPzmL0G04JxQEsBRfqONywyT8bSdDc/ECpAfr3S9VcS4JZ9/F6tkePKW/Om2Dq2g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@ast-grep/napi@0.37.0': - resolution: {integrity: sha512-Hb4o6h1Pf6yRUAX07DR4JVY7dmQw+RVQMW5/m55GoiAT/VRoKCWBtIUPPOnqDVhbx1Cjfil9b6EDrgJsUAujEQ==} - engines: {node: '>= 10'} - '@asyncapi/parser@3.4.0': resolution: {integrity: sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ==} @@ -224,6 +344,10 @@ packages: '@types/react': optional: true + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} @@ -270,6 +394,9 @@ packages: '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -925,8 +1052,8 @@ packages: cpu: [x64] os: [win32] - '@faceless-ui/modal@3.0.0': - resolution: {integrity: sha512-o3oEFsot99EQ8RJc1kL3s/nNMHX+y+WMXVzSSmca9L0l2MR6ez2QM1z1yIelJX93jqkLXQ9tW+R9tmsYa+O4Qg==} + '@faceless-ui/modal@3.0.2': + resolution: {integrity: sha512-2BK5ipSTIejnpAH4MsZ8aX9g7HxQrFybPpTAPVq8/RDRXxxShfwOuuhuxOx7KyZ1nWHtMGTWFpB0wkCAtg037w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -943,9 +1070,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/core@1.8.0': resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} @@ -955,12 +1079,6 @@ packages: '@floating-ui/dom@1.8.0': resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - '@floating-ui/react-dom@2.1.9': resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: @@ -973,9 +1091,6 @@ packages: react: '>=17.0.0' react-dom: '>=17.0.0' - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} @@ -995,6 +1110,12 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.33.5': resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1007,6 +1128,17 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + '@img/sharp-libvips-darwin-arm64@1.0.4': resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] @@ -1017,6 +1149,11 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] @@ -1027,6 +1164,11 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.4': resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] @@ -1039,6 +1181,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] @@ -1051,18 +1199,36 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] @@ -1075,6 +1241,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] @@ -1087,6 +1259,12 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] @@ -1099,6 +1277,12 @@ packages: os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] @@ -1111,6 +1295,12 @@ packages: os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1125,6 +1315,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1139,6 +1336,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1146,6 +1350,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1153,6 +1364,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1167,6 +1385,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1181,6 +1406,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1195,6 +1427,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1209,6 +1448,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1219,12 +1465,27 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + '@img/sharp-win32-arm64@0.34.5': resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1237,6 +1498,12 @@ packages: cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.33.5': resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1249,6 +1516,12 @@ packages: cpu: [x64] os: [win32] + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -1397,6 +1670,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -1545,60 +1821,57 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - '@next/env@15.5.18': - resolution: {integrity: sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==} + '@next/env@16.4.0': + resolution: {integrity: sha512-pyLlT5oafGlX8X7xjgGNXz5dNIDSxREm+qD6i9VLSZiZFopA/XWl8NM/KPmLx/8EG/++c49iid3gNWSQW1+2dQ==} - '@next/env@16.2.9': - resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==} - - '@next/swc-darwin-arm64@16.2.9': - resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==} + '@next/swc-darwin-arm64@16.4.0': + resolution: {integrity: sha512-yyZ+9MgViAKLLb5l1gPBGjRwnq3teNLb5Zx6JEw8LzNSTjlsnp+baUHFxTgl7PpjCmKf48VQfyipG1eBflb48Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.9': - resolution: {integrity: sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==} + '@next/swc-darwin-x64@16.4.0': + resolution: {integrity: sha512-RePEEejEj1Auf5s9qEcG0JbcDP3ceNUS/ofNX8AhxzgIjTVlZIHMatlp16YplcSMpsVo/ZMQHJTjbMYJznjGUg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.9': - resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==} + '@next/swc-linux-arm64-gnu@16.4.0': + resolution: {integrity: sha512-HhsyCqd+NpmvDDVY/LqkAN24f+QVvXh3ie+PfcwKinWL2Mu826JymQxJXGjAIfmJ4VlHjp4Pe/nZtXtUoEMAqw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.9': - resolution: {integrity: sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==} + '@next/swc-linux-arm64-musl@16.4.0': + resolution: {integrity: sha512-sN9Pf+Zh+RpZAOmbR7d8KAA8T61CVhawtbEJ2Bk6QrRzx6Z/6haaF4UiwBzytM96GRRRWZh9/WALIEO3npMamA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.9': - resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==} + '@next/swc-linux-x64-gnu@16.4.0': + resolution: {integrity: sha512-D5M5xLWQVd1CvR+L7BickM1weRfmM41x3L1NWcnKY/oyJJqr1qwaCgmu3dMAhLBOMdZwSuDCRV6DJRRpiUkbDQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.9': - resolution: {integrity: sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==} + '@next/swc-linux-x64-musl@16.4.0': + resolution: {integrity: sha512-8d5WLS6a9YalqpOgXdlbvonFu+sIgjA+CO6tGzetrpPVcUL56foa7B7664h41rAlbLi5fJwK4vWji3zf4QDy+w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.9': - resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==} + '@next/swc-win32-arm64-msvc@16.4.0': + resolution: {integrity: sha512-sQuOIiW3snb48UwrBcDMvFA/u03qtw2LeuSIxFIQ9AJuW9UG/4Ddsfo+eV73PnZwBhyncMtsKVh7PrzMol2/fA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.9': - resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==} + '@next/swc-win32-x64-msvc@16.4.0': + resolution: {integrity: sha512-6/33lCuxpdHUy27RegyGM/5uTpF7YZd3UR4D0gpkOA/tWY5YR7msbQjqPgCZmSmxrrTLGCXYA4A8vT2H5DPGTQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1621,6 +1894,9 @@ packages: '@openapi-contrib/openapi-schema-to-json-schema@3.2.0': resolution: {integrity: sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw==} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + '@oxc-resolver/binding-android-arm-eabi@11.20.0': resolution: {integrity: sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==} cpu: [arm] @@ -1724,284 +2000,283 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.54.0': - resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==} + '@oxfmt/binding-android-arm-eabi@0.71.0': + resolution: {integrity: sha512-l31EyBfB4egJeFua11aQFEUE9nnDtfpWCadqQovm0WL30t/yUHtdFl/5HyFXvd5xT8Ewh3U4TI9f30GowrQ1fw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.54.0': - resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==} + '@oxfmt/binding-android-arm64@0.71.0': + resolution: {integrity: sha512-E8q39SUZzSXQ08oZoFoTkxRADkdBNgL6De4qgZYr2O4JCunwqrZwAyCebxIF3yVHBafHM30WUkhTkDi+jNYZOw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.54.0': - resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==} + '@oxfmt/binding-darwin-arm64@0.71.0': + resolution: {integrity: sha512-pTteTrN88DicrmJ3DocBmpNDa6Umfh0iveEG8rnAlDFQpclmtDS7hnIvZbIEV9mDO0Ot1U0sjtoJQ1itd/ZMhQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.54.0': - resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==} + '@oxfmt/binding-darwin-x64@0.71.0': + resolution: {integrity: sha512-G5melRC3IiNEgXlNKJhSExJYQGAQIppHFp2cl/wFrqQcEwmc4k4TkrHaFTR29SDudjoW5AZq5cXPGJDqtlH/Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.54.0': - resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==} + '@oxfmt/binding-freebsd-x64@0.71.0': + resolution: {integrity: sha512-Hb1P5yKb9aiH2VSbm3Ml37mKjzqpunYet9krw/YK7V5JsAUJDQlju3Yk/dEqQvJD/usBvpi01Vbk9ZDNo0a8Cw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': - resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==} + '@oxfmt/binding-linux-arm-gnueabihf@0.71.0': + resolution: {integrity: sha512-r7n4JEO+rkp0kaP3ia+0hv6FzK3PQIHqaF0jxGuhjJ4JHVX9ZVRbgpDVDW+OloyuJsRWpWFDKIHe8gfl1p/4mQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.54.0': - resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==} + '@oxfmt/binding-linux-arm-musleabihf@0.71.0': + resolution: {integrity: sha512-EEynTF4nakgqDhVCmd3cHQCmTcw0BM8ea6eHigTdtZ3yxdsBNv+dYkt+OpDDZx1n/jzjI7J+Ht7IgkqFWuJUxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.54.0': - resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==} + '@oxfmt/binding-linux-arm64-gnu@0.71.0': + resolution: {integrity: sha512-7VgJIrywCwR/G6YMv+HqsccUt8Z4q4mbM2xR+Ry/6AeMk4pCmZ9aO4Srd3mmrvVu8PsCZGP7oHEHSXWTmyloaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.54.0': - resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==} + '@oxfmt/binding-linux-arm64-musl@0.71.0': + resolution: {integrity: sha512-AOCaminv/+fhinUXKvtPZT4POhNYmN9GfvaJdgJa1AbvuzClWGwDIXfJg8eu3sYaVL2B68jNvmS7LZGAYa0z3A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.54.0': - resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.71.0': + resolution: {integrity: sha512-0SHxuaa4QRLM7jfsd8wc6L7KdLg+FR6/ah9k1eAvcrK/Sni7oxchMvsjwUsyakVAta+wBVDkjJTr7/RFU0uUGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.54.0': - resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==} + '@oxfmt/binding-linux-riscv64-gnu@0.71.0': + resolution: {integrity: sha512-UNKj1+rPorTK6uSlQzpN96vDNbHbL/63zcpznweo0ImQdkDdtQCS7G3puxX++2cESPKT2B56Y6DICUmFTJde5A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.54.0': - resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==} + '@oxfmt/binding-linux-riscv64-musl@0.71.0': + resolution: {integrity: sha512-5cVXKj7a0mWu7GU6/bZgNyv+KtLgM5S9cnT26Os2/abj8pK/bklHsbdYEHGLLniGjNYkyzwem8mZciPCaDtilw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.54.0': - resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==} + '@oxfmt/binding-linux-s390x-gnu@0.71.0': + resolution: {integrity: sha512-lNpcMuZvyU1V/CY5h19hac03m4E4PLSYRckcxZOynSPojI4Mwt6nEu9wT/gVf0bclH3xGEWjwqApnSTgjkd2oA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.54.0': - resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==} + '@oxfmt/binding-linux-x64-gnu@0.71.0': + resolution: {integrity: sha512-5/Z6pUewQpknXqC4/ykK6Zc6RiteAnPem1Ci7K1RZLVF6w6MMjwHjR4vsjijW4Czidgv7HKeVglGjElADliT9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.54.0': - resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==} + '@oxfmt/binding-linux-x64-musl@0.71.0': + resolution: {integrity: sha512-uVdG2N/4GEbOeljpQ+xv+NeEwJWJGj0WaxSiSYnoiqIYy3RWrWd3rGUmxWXP1A8+ferNvvwFoDAtvgsDUvBuSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.54.0': - resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==} + '@oxfmt/binding-openharmony-arm64@0.71.0': + resolution: {integrity: sha512-1LwEZmSDRFKeBaGd/Hgj2xI3SmgGFpF4YtgkSx0sTYdTiZfHWbcFvRIBjL1YiDNdQd9Khda3Ij8CoqBULjskDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.54.0': - resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==} + '@oxfmt/binding-win32-arm64-msvc@0.71.0': + resolution: {integrity: sha512-5tplmkeMXmza3PUptJQy25wCPJbIAg5g5r7zs+DI+7eEYOqbY+7roMdSH2zbDvv1Pb4oTDJ6NWYx9IRxBxXwhQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.54.0': - resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==} + '@oxfmt/binding-win32-ia32-msvc@0.71.0': + resolution: {integrity: sha512-T2LWgh3Vx4PYDjtcqRRGDyCsVxeF/VE5xTpUQiCm65bNLwMpRnJYj6Hlkf6qRM3+jxCIOqHD1kVHNQCCiAVMNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.54.0': - resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==} + '@oxfmt/binding-win32-x64-msvc@0.71.0': + resolution: {integrity: sha512-L06SVp7Hu1EQTkh3+PHnKK26dxVbEpOG2InMwCooTE4MA437b5FgfTleCCiGn4ykISHnHU6UHaIg7SiMO7rBHA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.69.0': - resolution: {integrity: sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==} + '@oxlint/binding-android-arm-eabi@1.86.0': + resolution: {integrity: sha512-63Ozq6yQn79B2UxjSZ9huJmI+3/ZPI7hxAe6TyFh9oQL447KnIMoffOdtog+h4KILbQr61ZRvFPW5gYbAUgpgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.69.0': - resolution: {integrity: sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==} + '@oxlint/binding-android-arm64@1.86.0': + resolution: {integrity: sha512-JdLGp44phbf/0Du236Dt06yfK6Qw8xhTFeDvHJdjk167r59OfP5+BL31mgMYVnswS8e+j6tFuccSY0QDC/lgCw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.69.0': - resolution: {integrity: sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==} + '@oxlint/binding-darwin-arm64@1.86.0': + resolution: {integrity: sha512-h+vkOr4ik6KLFCXdtNVEj9xfnXfNUpPoF95QjORdhiSpLqQePzcN81kGHjAgsw1/G7WBWK5KsFKbI2UK8EyzMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.69.0': - resolution: {integrity: sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==} + '@oxlint/binding-darwin-x64@1.86.0': + resolution: {integrity: sha512-wbglRuH5eKsp68keTOOeKDHU1astWSRYFsh2bwKQTAPb44rD6kVgzYI1ZrAJRTc6/7PH55ZXFCQx0rQXrj0UdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.69.0': - resolution: {integrity: sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==} + '@oxlint/binding-freebsd-x64@1.86.0': + resolution: {integrity: sha512-yTQ7QSOlNfQsrfbeVweTiegQJLKPjUKgXp/JpGiFzza6Hp29S9LAoH77TM3L0RjK39iQRZB5lk5iq2t241Mfag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.69.0': - resolution: {integrity: sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.86.0': + resolution: {integrity: sha512-QYJGy0E2Tv8GAayyBIeHFTwQb8u+zrvrOYrifuDGZDr9ad5XsDhiMScpZymhS9fwWjqrgG2ZUwT4vpY0ISqp4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.69.0': - resolution: {integrity: sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==} + '@oxlint/binding-linux-arm-musleabihf@1.86.0': + resolution: {integrity: sha512-oQo83CafLT8p3qh0AGkN0tSUIh5Hm63mfDsoqUmBQUBnSZLNBAbyRaAftfGY5a0cKZ34dG6jAtMYgcY5kZqt5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.69.0': - resolution: {integrity: sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==} + '@oxlint/binding-linux-arm64-gnu@1.86.0': + resolution: {integrity: sha512-EM6wy5c2UM12qPb7iDJDBAZvHyfEJGH6iosFgaFiaRZpirz99//EHcUyD2KAskz07x1TULuiAtsojf4nvWWksw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.69.0': - resolution: {integrity: sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==} + '@oxlint/binding-linux-arm64-musl@1.86.0': + resolution: {integrity: sha512-vCiUQb9ZNzZalxYRU4mlZwUqT5f+c7Sk9DwUOplWHtik3Dl4r3DVnUB5tBeRuTAUe6MUVCTylNW7TkkN8J1oTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.69.0': - resolution: {integrity: sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==} + '@oxlint/binding-linux-ppc64-gnu@1.86.0': + resolution: {integrity: sha512-T0AzE89yjTYmOhB/hb6i+rWTpe2mYt+xiguBhs9DEPIJTZ1H8OYqsE4VxJHHw2Wlq8Mztwvp2P8NV87ZOuhPRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.69.0': - resolution: {integrity: sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==} + '@oxlint/binding-linux-riscv64-gnu@1.86.0': + resolution: {integrity: sha512-s49r0K2f5Oc7fJoMUO1WkdhYK5Ob79uYRwWQ/XNoGysaOq3RUGmGUbHSuXiyydTTA+P9xEdDEnnlZVXiaSJhlw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.69.0': - resolution: {integrity: sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==} + '@oxlint/binding-linux-riscv64-musl@1.86.0': + resolution: {integrity: sha512-JIa2tYR8ayb3nvcMSZzn4O/xTy13S7MHllu7cBD+B5qDQXXSRy/Vwk1aNpiTjZmBJGwKCHOy/NvnY5Xcwg27uQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.69.0': - resolution: {integrity: sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==} + '@oxlint/binding-linux-s390x-gnu@1.86.0': + resolution: {integrity: sha512-XwXMyBsAuWZkhZU74UNcfQKr1CJJAKnS80dZzOnjl6A4fKcf91IjFgidegQugMHA5SN8ulwDXEDV1yPntjXgQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.69.0': - resolution: {integrity: sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==} + '@oxlint/binding-linux-x64-gnu@1.86.0': + resolution: {integrity: sha512-C1WjukSyMnr66b+w1/tV8RFVv6d9v0MzDf4p9IxVXknqgmTHBgZh1pccN1eHzFr0b9Tbb3OXoPsAAdAuHAQfeA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.69.0': - resolution: {integrity: sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==} + '@oxlint/binding-linux-x64-musl@1.86.0': + resolution: {integrity: sha512-ap6KLmvC38c6MdYzsIh25cXQupqYvjd37tMNftzrX1DCtkX1Gcf2B+S2B17dD4lKa5c5gJog7DQJyDo82PBKyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.69.0': - resolution: {integrity: sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==} + '@oxlint/binding-openharmony-arm64@1.86.0': + resolution: {integrity: sha512-996Q9w/2GLwNyy5Djs39Jt09aFlr4b7TPhoj7dvo5FHmcyiF68nPz73HNc5UAYKIYeK+M9JzPQ4Wg1r1dXvP6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.69.0': - resolution: {integrity: sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==} + '@oxlint/binding-win32-arm64-msvc@1.86.0': + resolution: {integrity: sha512-9uTCHYTknNkug5+46ViSoUMuOuGWK61D87QnblU6CXaWKJC630Z7dbls61vdHxya/cR2ycQZ6Mm6ckgF6Cag5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.69.0': - resolution: {integrity: sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==} + '@oxlint/binding-win32-ia32-msvc@1.86.0': + resolution: {integrity: sha512-a47ga7EKVfMU8rO0i1m3mGwfNRj3FhQyt1d/ukO/E9u1HHF3KpR3tyb3VhuJBGEVPF8uR4exSyHUyx7xbE+uxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.69.0': - resolution: {integrity: sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==} + '@oxlint/binding-win32-x64-msvc@1.86.0': + resolution: {integrity: sha512-az4dzifCRzES1crwu6pDFXzJTIanrdxXFL9TbiJWrx9LKcuacbR83aPvxDA1LKLNswi/RoqitC7Afcflhu3zBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@payloadcms/db-sqlite@3.85.1': - resolution: {integrity: sha512-+5bVk9ClpwJB9w6zoPeaLABRWbsLAxgfgl5dWGBxo+UwTgc8DYrzDukycvZ3cR/TB/pRnyoA+33rP7mEpeVA7g==} + '@payloadcms/db-sqlite@4.0.0-canary.38': + resolution: {integrity: sha512-4cIMI6XscQaSimiX6J+yEjsIVcdzkaXSGq9HbvyXqoyKWRkDkAqQrJ+aEmQ2PpTQICnFGXeOH1EK57C/5XYhPA==} peerDependencies: - payload: 3.85.1 + payload: 4.0.0-canary.38 - '@payloadcms/drizzle@3.85.1': - resolution: {integrity: sha512-TVvw+tdvINf1y9bhkwjhGYguDybT3BJpTFzftKrsu034HqlN8ecc84ii35PXeiSsPciVWVgfgwAg9TSHQv4DFg==} + '@payloadcms/drizzle@4.0.0-canary.38': + resolution: {integrity: sha512-IntAuQqgNzor1ZvzlhD0Bb9w8XrUWPYH62UKYhDVUcA00XFs6etkggeGt4iULweM7sMZZI82dm0syJ6JfT6eHA==} peerDependencies: - payload: 3.85.1 + payload: 4.0.0-canary.38 - '@payloadcms/graphql@3.85.1': - resolution: {integrity: sha512-jMrSCiGYmsjOUGmZayX5U7Vh3i8IcO0XMmQ9x5jCIZuB+/sNDiBNeHJWArC+H2cfPYh0G+iv9t+RpPg6FemiMg==} + '@payloadcms/graphql@4.0.0-canary.38': + resolution: {integrity: sha512-JZvndTmx1pnVM95HEDEba1zGHVo2SuQwbMz37GlJ+gdDuIVktTLLQBP8gTrRLl9stdf3RL/h6oeA7uxEinkvmw==} hasBin: true peerDependencies: graphql: ^16.8.1 - payload: 3.85.1 + payload: 4.0.0-canary.38 - '@payloadcms/next@3.85.1': - resolution: {integrity: sha512-k/2l2LQNrgfa5r5ibR0o4/nNhLAlQQXtLpwGrJq2/B/yws4Ir6otc+ozTMiprATlvVb6uXcNFT+m1XLYqqSFuQ==} - engines: {node: ^18.20.2 || >=20.9.0} + '@payloadcms/next@4.0.0-canary.38': + resolution: {integrity: sha512-AJrDVOVw4V1gqDvCYCDnEd10quw7rHtRHaCRe6tYWtkyB3s3lcRveuM0mPWxmNFWMeYAgkhp57doDV18Psn66A==} + engines: {node: '>=24.15.0'} peerDependencies: graphql: ^16.8.1 - next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' - payload: 3.85.1 + next: '>=16.4.0 <17.0.0' + payload: 4.0.0-canary.38 - '@payloadcms/translations@3.85.1': - resolution: {integrity: sha512-1DssESAfPjgkp8/f7Xq1ZOCnMjSXp0Xkpbo0I7Zt0DCaA+QY7Go+kZQuSYazKKRF+lgSGzKEHRJ5CWqzhpKh8w==} + '@payloadcms/translations@4.0.0-canary.38': + resolution: {integrity: sha512-7ao8otvtNFYVr13uZvZqr5LiP7ey4DdczL0ikuPi8LvM3QBo7WklCxgK5YrFt8WSAhwZamDNY1V73DGQmAPWjw==} - '@payloadcms/ui@3.85.1': - resolution: {integrity: sha512-WCJBEKAnwIvEi2Hiykl92dLVjGga6sPGsNlkbkssNMDqieEUh6qNAb3HewlJUa0IFrhxVFLoAEyJOzHr1UJXkQ==} - engines: {node: ^18.20.2 || >=20.9.0} + '@payloadcms/ui@4.0.0-canary.38': + resolution: {integrity: sha512-+NIwg3zX+U+O8Bsz1vUhx+raAKn1Dgj437c85Vs/IbOv6wq41Rv2+iAS+yMaB+Uu0kcCiYxAeJC2wbw6oYW47w==} + engines: {node: '>=24.15.0'} peerDependencies: - next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' - payload: 3.85.1 + payload: 4.0.0-canary.38 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 @@ -2016,8 +2291,110 @@ packages: engines: {node: '>=18'} hasBin: true - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@quansync/fs@1.1.0': + resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==} + + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} cpu: [arm] os: [android] @@ -2154,97 +2531,6 @@ packages: cpu: [x64] os: [win32] - '@rsbuild/core@2.0.12': - resolution: {integrity: sha512-6f0M9TO4pAeM/VAshg9PajYK4IbfUB21Xun2kT0q3/SbBQ607KzkQZ6fmReuKUKCLbo26tGnbgg5P77eDDcfaA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - core-js: '>= 3.0.0' - peerDependenciesMeta: - core-js: - optional: true - - '@rslib/core@0.22.1': - resolution: {integrity: sha512-RaqTITHFkpMDJG9fmD7Hu6FLE64hwctCo46asHOD2DipzQJWawg6K0pFGimTAyutYEZysIUfYgCwSYkbctDudg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7 - typescript: ^5 || ^6 - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - typescript: - optional: true - - '@rspack/binding-darwin-arm64@2.0.8': - resolution: {integrity: sha512-vCgbgH7B7qom+uID+RCZsTCOYFb9wC4/4+1U6rMfytrXGVJ72eNQs2tbdjOl0lb18CT3N/n+VkWynUiLk84GwA==} - cpu: [arm64] - os: [darwin] - - '@rspack/binding-darwin-x64@2.0.8': - resolution: {integrity: sha512-satPm2PD4B7jDTVlVAdvMVdUszwLvWUEnUDzLb77mvVkezKNDZmuhb+e8s+FfKs8hJpNbZ9VAejuA2rr8o985w==} - cpu: [x64] - os: [darwin] - - '@rspack/binding-linux-arm64-gnu@2.0.8': - resolution: {integrity: sha512-pSI+npPQE/uDtiboqvcOIRJbEV2+B+H1xffmko/gw50la92oTUW60kVULFwsb6L0+GVCzIcwX3yq60GtYIn+Ug==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-arm64-musl@2.0.8': - resolution: {integrity: sha512-igjJ43yxWQ72GZqjDDZSSHax9/Vg+6rLMmOvFglTJUkQpB4Tyvu/YjW+WRjYj2xRw6blOjLxUSJWASvuSqqlvg==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rspack/binding-linux-x64-gnu@2.0.8': - resolution: {integrity: sha512-zrkoEOnqj1hOEBO5T2I/2Ts2HSJsYFh1qXwMpK4dMJFGGNWDfNeUa6/LF5uq3VINF3JUl7RL47AgrucoSZJXPA==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-x64-musl@2.0.8': - resolution: {integrity: sha512-6CtDaGZjNDvJd9TBp7a9zABbrPORO21W96+3ZcGBn0YNUPUk4ARxIxrTTpeJ/1F41QDM8AYIkGDdqEYMqTYBsA==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rspack/binding-wasm32-wasi@2.0.8': - resolution: {integrity: sha512-Yf4SiqTUroT5Ju+te0YAY2xxKOb35tECsO21v7hYyGa705wrgoAK/MmF7enOvs9GR1iZIqgiLD/wxsIxl8GjJw==} - cpu: [wasm32] - - '@rspack/binding-win32-arm64-msvc@2.0.8': - resolution: {integrity: sha512-8NCuiQsAhXrwRBy57QZoypqrws/zLBkaQVGiB8hksr6v++8hNigNjqpQARLbd0iyMuHsQQ++8+auGk6xlDXmzw==} - cpu: [arm64] - os: [win32] - - '@rspack/binding-win32-ia32-msvc@2.0.8': - resolution: {integrity: sha512-bxiekytbX7V9KFAra+HkwtNWC6pYfHEBBZFpiT0xUs3mCFOmAAFVBsBSQsoCP9AdCEXoMAvNdnrHNw3iov4OZw==} - cpu: [ia32] - os: [win32] - - '@rspack/binding-win32-x64-msvc@2.0.8': - resolution: {integrity: sha512-7zPs8YCe/ZVJTwd+5lpB0CP0tkn2pONf/T1ycmVY76u21Nrwt8mXQGc/2yH2eWP4B7fikYBr3hGr7mpR2fajqQ==} - cpu: [x64] - os: [win32] - - '@rspack/binding@2.0.8': - resolution: {integrity: sha512-3uZ+y8aQxq33ty2srMxg2Nu0XuBI6vVrG50rkDaXqwWqOohfgGUSfFuQK7EnSUNy4aFUQlCG6NHialQHJov0wg==} - - '@rspack/core@2.0.8': - resolution: {integrity: sha512-+NLGJf8gZxihDmMFzjlly3toc2SMjeDmuvz0/Cai9AMdV4F+Pqcnt2BA9V4e3SY2jmhJQtPwgyyLtR1RiJO77g==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 - '@swc/helpers': ^0.5.23 - peerDependenciesMeta: - '@module-federation/runtime-tools': - optional: true - '@swc/helpers': - optional: true - '@scalar/openapi-types@0.9.1': resolution: {integrity: sha512-gkGhSkxSzADaBiNg+ZAbJuwj+ZUmzP2Pg9CWZ7ZP+0fck2WjPeDDM7aAbouAm0aQQMF9xBjSPXSA9a/qTHYaTw==} engines: {node: '>=22'} @@ -2256,6 +2542,45 @@ packages: resolution: {integrity: sha512-bP/48Il7RMxhohq36Vg0c25tnCKOO+oEv8XSxDefuUBj0W8fYzL2Yb6C8tji0WBXykZ1S/79AWbrzGLfCG2dYw==} hasBin: true + '@seshuk/payload-plugin-tooling@0.2.3': + resolution: {integrity: sha512-qlAfCM1iSVSmKQFBfwP3l3yHbJNhaCOofrvqwk5rtvcdE5JAWZCoSx9HsHoNdka9+0eEjBNLeaHYYkChkOdl+Q==} + engines: {node: '>=24.15.0'} + peerDependencies: + '@electric-sql/pglite': '>=0.5.0' + '@electric-sql/pglite-socket': '>=0.2.0' + '@payloadcms/db-mongodb': '>=4.0.0-0' + '@payloadcms/db-postgres': '>=4.0.0-0' + '@payloadcms/db-sqlite': '>=4.0.0-0' + mongodb-memory-server: '>=10.0.0' + oxfmt: '>=0.71.0' + oxlint: '>=1.77.0' + payload: '>=4.0.0-0' + tsdown: '>=0.23.0' + vitest: '>=4.0.0' + peerDependenciesMeta: + '@electric-sql/pglite': + optional: true + '@electric-sql/pglite-socket': + optional: true + '@payloadcms/db-mongodb': + optional: true + '@payloadcms/db-postgres': + optional: true + '@payloadcms/db-sqlite': + optional: true + mongodb-memory-server: + optional: true + oxfmt: + optional: true + oxlint: + optional: true + payload: + optional: true + tsdown: + optional: true + vitest: + optional: true + '@shikijs/core@3.23.0': resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} @@ -2372,9 +2697,6 @@ packages: resolution: {integrity: sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==} engines: {node: '>=10.8'} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -2452,6 +2774,9 @@ packages: '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2481,6 +2806,126 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@typescript/vfs@1.6.4': resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} peerDependencies: @@ -2489,11 +2934,25 @@ packages: '@ungap/structured-clone@1.3.3': resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - '@vitest/expect@4.1.8': - resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + '@vitest/coverage-v8@5.0.3': + resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} + peerDependencies: + '@vitest/browser': 5.0.3 + vitest: 5.0.3 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} + engines: {node: '>=22'} - '@vitest/mocker@4.1.8': - resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2503,20 +2962,143 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.8': - resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + + '@yuku-codegen/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A==} + cpu: [arm] + os: [linux] + libc: [glibc] - '@vitest/runner@4.1.8': - resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q==} + cpu: [arm] + os: [linux] + libc: [musl] - '@vitest/snapshot@4.1.8': - resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw==} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@vitest/spy@4.1.8': - resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==} + cpu: [arm64] + os: [linux] + libc: [musl] - '@vitest/utils@4.1.8': - resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.10.2': + resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -2567,22 +3149,9 @@ packages: ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2658,6 +3227,9 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -2804,6 +3376,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -2962,6 +3538,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2976,8 +3556,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - console-table-printer@2.12.1: - resolution: {integrity: sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==} + console-table-printer@2.15.0: + resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -2990,9 +3570,6 @@ packages: convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - convert-to-spaces@2.0.1: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3004,9 +3581,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - core-js@3.42.0: - resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==} - cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -3074,12 +3648,12 @@ packages: dataloader@2.2.3: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} - date-fns@3.6.0: - resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} - date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -3148,6 +3722,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -3210,6 +3787,14 @@ packages: dompurify@3.2.7: resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + dotenv-expand@11.0.6: + resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + drizzle-kit@0.31.7: resolution: {integrity: sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A==} hasBin: true @@ -3306,6 +3891,15 @@ packages: sqlite3: optional: true + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -3319,6 +3913,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.1.0: + resolution: {integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==} + engines: {node: '>=14'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -3369,8 +3967,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} @@ -3487,8 +4085,8 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} expr-eval-fork@3.0.3: @@ -3507,8 +4105,8 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true - fast-copy@3.0.2: - resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-copy@4.1.1: + resolution: {integrity: sha512-A4QTJmuiztpGtr6AMeJts9R4hbj2ZBUwtOaKrG6rw2y7t6+IaJKjz5M3XDs8BUznxDH43FVc6A0y/gWlMl4UtA==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3565,9 +4163,9 @@ packages: fflate@0.8.3: resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - file-type@21.3.4: - resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} - engines: {node: '>=20'} + file-type@22.0.1: + resolution: {integrity: sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA==} + engines: {node: '>=22'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -3695,8 +4293,12 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + get-tsconfig@5.0.0-beta.6: + resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==} + engines: {node: '>=20.20.0'} get-uri@6.0.5: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} @@ -3848,6 +4450,9 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hookable@6.1.2: + resolution: {integrity: sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -3896,9 +4501,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - image-size@2.0.2: - resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} - engines: {node: '>=16.x'} + image-dimensions@2.5.1: + resolution: {integrity: sha512-It7CkTNp7IYA8jhvGgz8K18OAbHF3/Juk8RNEyTyMFfolJOIU1Y2wGDnzDQPbGCjyxVF/++pwIZE0BKJUEOb1g==} + engines: {node: '>=18'} hasBin: true immer@9.0.21: @@ -3911,6 +4516,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-without-cache@0.4.1: + resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==} + engines: {node: ^22.18.0 || >=24.0.0} + indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} @@ -3970,8 +4579,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-alphabetical@2.0.1: @@ -4155,8 +4764,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@5.10.0: - resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} jose@6.2.9: resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} @@ -4168,6 +4777,9 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4175,10 +4787,6 @@ packages: resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - js-yaml@4.3.1: resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true @@ -4282,8 +4890,11 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} @@ -4566,11 +5177,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4598,8 +5204,8 @@ packages: react: '>= 19.1.0' react-dom: '>= 19.1.0' - next@16.2.9: - resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==} + next@16.4.0: + resolution: {integrity: sha512-N2C+9q89Z/oLrsepSRS4jRMtwmUZAoZuAHU0e9T6+sAB5DdhADmrnOW+zjYijlrYFmVfmQhGVGy9eMFoB7UgKg==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -4698,8 +5304,13 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} @@ -4742,8 +5353,8 @@ packages: oxc-resolver@11.20.0: resolution: {integrity: sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==} - oxfmt@0.54.0: - resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==} + oxfmt@0.71.0: + resolution: {integrity: sha512-lUPUl0d/+Io5pDrsPXWs6rB4N/bpB78oj9CTDpnbulfDz+0r3XXcHPlQ7kRPJ2GjIT4nX+/mcqunOeP9BvsEtg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4755,12 +5366,12 @@ packages: vite-plus: optional: true - oxlint@1.69.0: - resolution: {integrity: sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==} + oxlint@1.86.0: + resolution: {integrity: sha512-og0lhgvZfgGF//gOOmZXvtr+GmBbAGEnbEhv/QUg7UW2Wi4wHMnJbnMD+zuHgPdJxdklfgpPupdlAaAySyxrZg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=7.0.2003' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -4842,12 +5453,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - payload@3.85.1: - resolution: {integrity: sha512-vfnqwwMOru9wpdiFB3gyb6B+gUt7cjE8+YiNn1g+26dPOyGkAZa9U1QmCMSZfgARj5H42llElQHQw9WX3lYbIw==} - engines: {node: ^18.20.2 || >=20.9.0} + payload@4.0.0-canary.38: + resolution: {integrity: sha512-PJMpPqTIsd4DIOyVo3aYVETfvOfsfVd7PfqwJjnbCCqv0Zafzx/H0zACJbhMt1N5CtT5ID4HL6SnNmLWMsgqBQ==} + engines: {node: '>=24.15.0'} hasBin: true peerDependencies: graphql: ^16.8.1 @@ -4866,22 +5474,26 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - pino-pretty@13.1.2: - resolution: {integrity: sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==} + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} hasBin: true pino-std-serializers@7.1.0: resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} hasBin: true pirates@4.0.7: @@ -4937,10 +5549,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.23: resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} engines: {node: ^10 || ^12 || >=14} @@ -5030,6 +5638,9 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -5052,16 +5663,20 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-datepicker@7.6.0: - resolution: {integrity: sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw==} + react-datepicker@9.1.0: + resolution: {integrity: sha512-lOp+m5bc+ttgtB5MHEjwiVu4nlp4CvJLS/PG1OiOe5pmg9kV73pEqO8H0Geqvg2E8gjqTaL9eRhSe+ZpeKP3nA==} peerDependencies: + date-fns-tz: ^3.0.0 react: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + date-fns-tz: + optional: true - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: - react: ^19.2.7 + react: ^19.3.0 react-image-crop@10.1.8: resolution: {integrity: sha512-4rb8XtXNx7ZaOZarKKnckgz4xLMvds/YrU6mpJfGhGAsy2Mg4mIw1x+DCCGngVGq2soTBVVOxx2s/C6mTX9+pA==} @@ -5093,8 +5708,8 @@ packages: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -5112,6 +5727,9 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -5250,26 +5868,37 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rsbuild-plugin-dts@0.22.1: - resolution: {integrity: sha512-6vBfXgwK4j6GkjSRH13tuceBavc6qwSE5IVSoVCZ/wRuM0ZL7dIjdk+yGSWKJt5lNMfc4XwQoTErGIC9zhgztQ==} - engines: {node: ^20.19.0 || >=22.12.0} + rolldown-plugin-dts@0.28.6: + resolution: {integrity: sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} peerDependencies: - '@microsoft/api-extractor': ^7 - '@rsbuild/core': ^1.0.0 || ^2.0.0-0 - '@typescript/native-preview': ^7.0.0-0 - typescript: ^5 || ^6 + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + rolldown: ^1.2.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true '@typescript/native-preview': optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true typescript: optional: true + vue-tsc: + optional: true + + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} @@ -5306,8 +5935,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-filename@1.6.3: - resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + sanitize-filename@1.6.4: + resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} sass@1.77.4: resolution: {integrity: sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==} @@ -5324,8 +5953,8 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} @@ -5335,6 +5964,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -5373,6 +6007,15 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5400,9 +6043,6 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -5498,9 +6138,6 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} @@ -5508,8 +6145,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} @@ -5649,26 +6286,28 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.2.0: + resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} + engines: {node: '>=20.0.0'} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinypool@2.1.0: - resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} engines: {node: '>=14.0.0'} to-data-view@1.1.0: @@ -5698,6 +6337,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -5721,16 +6364,39 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - deprecated: unmaintained + tsdown@0.23.0: + resolution: {integrity: sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: - typescript: ^5.0.0 + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.23.0 + '@tsdown/exe': 0.23.0 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true typescript: optional: true + unplugin-unused: + optional: true + unrun: + optional: true tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -5743,6 +6409,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -5789,9 +6460,9 @@ packages: typed-query-selector@2.12.2: resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true uint8array-extras@1.5.0: @@ -5802,11 +6473,17 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici@7.24.4: - resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + undici@7.29.1: + resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} engines: {node: '>=20.18.1'} unified@11.0.5: @@ -5912,14 +6589,18 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - uuid@13.0.2: - resolution: {integrity: sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==} + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + verkit@0.4.1: + resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==} + engines: {node: '>=18.12.0'} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -5932,12 +6613,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-tsconfig-paths@7.0.0-alpha.1: - resolution: {integrity: sha512-R2xUKqgU3lETsbx66sOoVj6/2hf9J9Aizavs03obMY5TxC99UbF1tM4bSfn1KZ8HxtOEaK3gdm/CEutS3yBvog==} - engines: {node: '>=18'} - peerDependencies: - vite: '>=5.0.0' - vite@7.3.3: resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5978,23 +6653,23 @@ packages: yaml: optional: true - vitest@4.1.8: - resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.8 - '@vitest/browser-preview': 4.1.8 - '@vitest/browser-webdriverio': 4.1.8 - '@vitest/coverage-istanbul': 4.1.8 - '@vitest/coverage-v8': 4.1.8 - '@vitest/ui': 4.1.8 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -6053,9 +6728,9 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true widest-line@5.0.0: @@ -6145,6 +6820,15 @@ packages: yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + yuku-ast@0.10.2: + resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} + + yuku-codegen@0.10.2: + resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==} + + yuku-parser@0.10.2: + resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} + zod-to-json-schema@3.20.4: resolution: {integrity: sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg==} peerDependencies: @@ -6159,6 +6843,9 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -6175,7 +6862,7 @@ snapshots: dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 - js-yaml: 4.1.1 + js-yaml: 4.3.1 '@ark/schema@0.56.2': dependencies: @@ -6183,45 +6870,6 @@ snapshots: '@ark/util@0.56.2': {} - '@ast-grep/napi-darwin-arm64@0.37.0': - optional: true - - '@ast-grep/napi-darwin-x64@0.37.0': - optional: true - - '@ast-grep/napi-linux-arm64-gnu@0.37.0': - optional: true - - '@ast-grep/napi-linux-arm64-musl@0.37.0': - optional: true - - '@ast-grep/napi-linux-x64-gnu@0.37.0': - optional: true - - '@ast-grep/napi-linux-x64-musl@0.37.0': - optional: true - - '@ast-grep/napi-win32-arm64-msvc@0.37.0': - optional: true - - '@ast-grep/napi-win32-ia32-msvc@0.37.0': - optional: true - - '@ast-grep/napi-win32-x64-msvc@0.37.0': - optional: true - - '@ast-grep/napi@0.37.0': - optionalDependencies: - '@ast-grep/napi-darwin-arm64': 0.37.0 - '@ast-grep/napi-darwin-x64': 0.37.0 - '@ast-grep/napi-linux-arm64-gnu': 0.37.0 - '@ast-grep/napi-linux-arm64-musl': 0.37.0 - '@ast-grep/napi-linux-x64-gnu': 0.37.0 - '@ast-grep/napi-linux-x64-musl': 0.37.0 - '@ast-grep/napi-win32-arm64-msvc': 0.37.0 - '@ast-grep/napi-win32-ia32-msvc': 0.37.0 - '@ast-grep/napi-win32-x64-msvc': 0.37.0 - '@asyncapi/parser@3.4.0': dependencies: '@asyncapi/specs': 6.11.1 @@ -6238,7 +6886,7 @@ snapshots: '@types/urijs': 1.19.26 ajv: 8.20.0 ajv-errors: 3.0.0(ajv@8.20.0) - ajv-formats: 2.1.1(ajv@8.20.0) + ajv-formats: 2.1.1 avsc: 5.7.9 js-yaml: 4.3.1 jsonpath-plus: 10.4.0 @@ -6306,67 +6954,69 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)': + '@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.3.2(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3) - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.3))(react@19.2.3) + '@base-ui/utils': 0.3.2(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3) + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.0(react@19.2.3))(react@19.2.3) '@floating-ui/utils': 0.2.12 react: 19.2.3 - react-dom: 19.2.7(react@19.2.3) + react-dom: 19.3.0(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: '@date-fns/tz': 1.2.0 '@types/react': 19.2.15 date-fns: 4.1.0 - '@base-ui/utils@0.3.2(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)': + '@base-ui/utils@0.3.2(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.29.7 '@floating-ui/utils': 0.2.12 react: 19.2.3 - react-dom: 19.2.7(react@19.2.3) + react-dom: 19.3.0(react@19.2.3) reselect: 5.2.0 use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: '@types/react': 19.2.15 + '@bcoe/v8-coverage@1.0.2': {} + '@borewit/text-codec@0.2.2': {} '@canvas/image-data@1.1.0': {} '@date-fns/tz@1.2.0': {} - '@dnd-kit/accessibility@3.1.1(react@19.2.7)': + '@dnd-kit/accessibility@3.1.1(react@19.3.0)': dependencies: - react: 19.2.7 + react: 19.3.0 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + '@dnd-kit/accessibility': 3.1.1(react@19.3.0) + '@dnd-kit/utilities': 3.2.2(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) tslib: 2.8.1 - '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 + '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@dnd-kit/utilities': 3.2.2(react@19.3.0) + react: 19.3.0 tslib: 2.8.1 - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 + '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@dnd-kit/utilities': 3.2.2(react@19.3.0) + react: 19.3.0 tslib: 2.8.1 - '@dnd-kit/utilities@3.2.2(react@19.2.7)': + '@dnd-kit/utilities@3.2.2(react@19.3.0)': dependencies: - react: 19.2.7 + react: 19.3.0 tslib: 2.8.1 '@drizzle-team/brocli@0.10.2': {} @@ -6382,6 +7032,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 @@ -6415,17 +7070,17 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.7)': + '@emotion/react@11.14.0(@types/react@19.2.15)(react@19.3.0)': dependencies: '@babel/runtime': 7.29.7 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.3.0) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 19.2.7 + react: 19.3.0 optionalDependencies: '@types/react': 19.2.15 transitivePeerDependencies: @@ -6443,9 +7098,9 @@ snapshots: '@emotion/unitless@0.10.0': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.7)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.3.0)': dependencies: - react: 19.2.7 + react: 19.3.0 '@emotion/utils@1.4.2': {} @@ -6461,7 +7116,7 @@ snapshots: '@esbuild-kit/esm-loader@2.6.5': dependencies: '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.8.1 + get-tsconfig: 4.14.0 '@esbuild/aix-ppc64@0.25.12': optional: true @@ -6763,27 +7418,23 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@faceless-ui/modal@3.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@faceless-ui/modal@3.0.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: body-scroll-lock: 4.0.0-beta.0 focus-trap: 7.5.4 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-transition-group: 4.4.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@faceless-ui/scroll-info@2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@faceless-ui/scroll-info@2.0.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - '@faceless-ui/window-info@3.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@faceless-ui/window-info@3.0.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@floating-ui/core@1.8.0': dependencies: @@ -6791,36 +7442,34 @@ snapshots: '@floating-ui/dom@1.7.6': dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 '@floating-ui/dom@1.8.0': dependencies: '@floating-ui/core': 1.8.0 '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@floating-ui/react-dom@2.1.9(react-dom@19.3.0(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + '@floating-ui/dom': 1.8.0 + react: 19.2.3 + react-dom: 19.3.0(react@19.2.3) - '@floating-ui/react-dom@2.1.9(react-dom@19.2.7(react@19.2.3))(react@19.2.3)': + '@floating-ui/react-dom@2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@floating-ui/dom': 1.8.0 - react: 19.2.3 - react-dom: 19.2.7(react@19.2.3) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - '@floating-ui/react@0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@floating-ui/react@0.27.19(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/utils': 0.2.11 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@floating-ui/utils': 0.2.12 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) tabbable: 6.4.0 - '@floating-ui/utils@0.2.11': {} - '@floating-ui/utils@0.2.12': {} '@img/colour@1.1.0': {} @@ -6835,6 +7484,11 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true + '@img/sharp-darwin-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.4 + optional: true + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.4 @@ -6845,60 +7499,100 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true + '@img/sharp-darwin-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.4 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true + '@img/sharp-libvips-darwin-arm64@1.3.4': + optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true '@img/sharp-libvips-darwin-x64@1.2.4': optional: true + '@img/sharp-libvips-darwin-x64@1.3.4': + optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true '@img/sharp-libvips-linux-arm64@1.2.4': optional: true + '@img/sharp-libvips-linux-arm64@1.3.4': + optional: true + '@img/sharp-libvips-linux-arm@1.0.5': optional: true '@img/sharp-libvips-linux-arm@1.2.4': optional: true + '@img/sharp-libvips-linux-arm@1.3.4': + optional: true + '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true + '@img/sharp-libvips-linux-ppc64@1.3.4': + optional: true + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true + '@img/sharp-libvips-linux-riscv64@1.3.4': + optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true '@img/sharp-libvips-linux-s390x@1.2.4': optional: true + '@img/sharp-libvips-linux-s390x@1.3.4': + optional: true + '@img/sharp-libvips-linux-x64@1.0.4': optional: true '@img/sharp-libvips-linux-x64@1.2.4': optional: true + '@img/sharp-libvips-linux-x64@1.3.4': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + optional: true + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.4 @@ -6909,6 +7603,11 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true + '@img/sharp-linux-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.4 + optional: true + '@img/sharp-linux-arm@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.5 @@ -6919,16 +7618,31 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.2.4 optional: true + '@img/sharp-linux-arm@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.4 + optional: true + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true + '@img/sharp-linux-ppc64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.4 + optional: true + '@img/sharp-linux-riscv64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true + '@img/sharp-linux-riscv64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.4 + optional: true + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.4 @@ -6939,6 +7653,11 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true + '@img/sharp-linux-s390x@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.4 + optional: true + '@img/sharp-linux-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.4 @@ -6949,6 +7668,11 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.4 optional: true + '@img/sharp-linux-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.4 + optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 @@ -6959,6 +7683,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true + '@img/sharp-linuxmusl-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + optional: true + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 @@ -6969,6 +7698,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true + '@img/sharp-linuxmusl-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + optional: true + '@img/sharp-wasm32@0.33.5': dependencies: '@emnapi/runtime': 1.10.0 @@ -6979,145 +7713,164 @@ snapshots: '@emnapi/runtime': 1.10.0 optional: true + '@img/sharp-wasm32@0.35.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + '@img/sharp-win32-arm64@0.34.5': optional: true + '@img/sharp-win32-arm64@0.35.5': + optional: true + '@img/sharp-win32-ia32@0.33.5': optional: true '@img/sharp-win32-ia32@0.34.5': optional: true + '@img/sharp-win32-ia32@0.35.5': + optional: true + '@img/sharp-win32-x64@0.33.5': optional: true '@img/sharp-win32-x64@0.34.5': optional: true + '@img/sharp-win32-x64@0.35.5': + optional: true + '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.9.1)': + '@inquirer/checkbox@4.3.2(@types/node@26.6.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/type': 3.0.10(@types/node@26.6.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/confirm@5.1.21(@types/node@25.9.1)': + '@inquirer/confirm@5.1.21(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/core@10.3.2(@types/node@25.9.1)': + '@inquirer/core@10.3.2(@types/node@26.6.4)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/type': 3.0.10(@types/node@26.6.4) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/editor@4.2.23(@types/node@25.9.1)': + '@inquirer/editor@4.2.23(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/external-editor': 1.0.3(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/external-editor': 1.0.3(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/expand@4.0.23(@types/node@25.9.1)': + '@inquirer/expand@4.0.23(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/external-editor@1.0.3(@types/node@25.9.1)': + '@inquirer/external-editor@1.0.3(@types/node@26.6.4)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.9.1)': + '@inquirer/input@4.3.1(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/number@3.0.23(@types/node@25.9.1)': + '@inquirer/number@3.0.23(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/password@4.0.23(@types/node@25.9.1)': + '@inquirer/password@4.0.23(@types/node@26.6.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/prompts@7.9.0(@types/node@25.9.1)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.9.1) - '@inquirer/confirm': 5.1.21(@types/node@25.9.1) - '@inquirer/editor': 4.2.23(@types/node@25.9.1) - '@inquirer/expand': 4.0.23(@types/node@25.9.1) - '@inquirer/input': 4.3.1(@types/node@25.9.1) - '@inquirer/number': 3.0.23(@types/node@25.9.1) - '@inquirer/password': 4.0.23(@types/node@25.9.1) - '@inquirer/rawlist': 4.1.11(@types/node@25.9.1) - '@inquirer/search': 3.2.2(@types/node@25.9.1) - '@inquirer/select': 4.4.2(@types/node@25.9.1) + '@types/node': 26.6.4 + + '@inquirer/prompts@7.9.0(@types/node@26.6.4)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@26.6.4) + '@inquirer/confirm': 5.1.21(@types/node@26.6.4) + '@inquirer/editor': 4.2.23(@types/node@26.6.4) + '@inquirer/expand': 4.0.23(@types/node@26.6.4) + '@inquirer/input': 4.3.1(@types/node@26.6.4) + '@inquirer/number': 3.0.23(@types/node@26.6.4) + '@inquirer/password': 4.0.23(@types/node@26.6.4) + '@inquirer/rawlist': 4.1.11(@types/node@26.6.4) + '@inquirer/search': 3.2.2(@types/node@26.6.4) + '@inquirer/select': 4.4.2(@types/node@26.6.4) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/rawlist@4.1.11(@types/node@25.9.1)': + '@inquirer/rawlist@4.1.11(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/search@3.2.2(@types/node@25.9.1)': + '@inquirer/search@3.2.2(@types/node@26.6.4)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/type': 3.0.10(@types/node@26.6.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/select@4.4.2(@types/node@25.9.1)': + '@inquirer/select@4.4.2(@types/node@26.6.4)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.9.1) + '@inquirer/core': 10.3.2(@types/node@26.6.4) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.9.1) + '@inquirer/type': 3.0.10(@types/node@26.6.4) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 - '@inquirer/type@3.0.10(@types/node@25.9.1)': + '@inquirer/type@3.0.10(@types/node@26.6.4)': optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -7132,6 +7885,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -7245,22 +8000,22 @@ snapshots: '@types/react': 19.2.15 react: 19.2.3 - '@mintlify/cli@4.0.1413(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/node@25.9.1)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3)': + '@mintlify/cli@4.0.1413(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/node@26.6.4)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@25.9.1) - '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/link-rot': 3.0.1298(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@inquirer/prompts': 7.9.0(@types/node@26.6.4) + '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/link-rot': 3.0.1298(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) '@mintlify/models': 0.0.350(debug@4.4.3) - '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/previewing': 4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3) - '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/previewing': 4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2) + '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) adm-zip: 0.6.0 chalk: 5.2.0 color: 4.2.3 detect-port: 1.5.1 fs-extra: 11.2.0 ink: 6.3.0(@types/react@19.2.15)(react@19.2.3) - inquirer: 12.3.0(@types/node@25.9.1) + inquirer: 12.3.0(@types/node@26.6.4) js-yaml: 4.3.1 jsonc-parser: 3.3.1 mdast-util-mdx-jsx: 3.2.0 @@ -7299,13 +8054,13 @@ snapshots: - typescript - utf-8-validate - '@mintlify/common@1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/common@1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: '@asyncapi/parser': 3.4.0 - '@mintlify/mdx': 4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/mdx': 4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) '@mintlify/models': 0.0.350(debug@4.4.3) '@mintlify/openapi-parser': 0.0.8 - '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) '@sindresorhus/slugify': 2.2.0 '@types/mdast': 4.0.4 acorn: 8.11.2 @@ -7357,14 +8112,14 @@ snapshots: - ts-node - typescript - '@mintlify/link-rot@3.0.1298(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/link-rot@3.0.1298(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) '@mintlify/models': 0.0.350(debug@4.4.3) - '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/previewing': 4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3) - '@mintlify/scraping': 4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/previewing': 4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2) + '@mintlify/scraping': 4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) fs-extra: 11.1.0 unist-util-visit: 4.1.2 transitivePeerDependencies: @@ -7384,26 +8139,26 @@ snapshots: - typescript - utf-8-validate - '@mintlify/mdx@4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/mdx@4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@base-ui/react': 1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3) + '@base-ui/react': 1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3) '@shikijs/transformers': 3.23.0 - '@shikijs/twoslash': 3.23.0(typescript@6.0.3) + '@shikijs/twoslash': 3.23.0(typescript@7.0.2) arktype: 2.2.3 hast-util-to-string: 3.0.1 mdast-util-from-markdown: 2.0.3 mdast-util-gfm: 3.1.0 mdast-util-mdx-jsx: 3.2.0 mdast-util-to-hast: 13.2.1 - next-mdx-remote-client: 2.1.12(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(unified@11.0.5) + next-mdx-remote-client: 2.1.12(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(unified@11.0.5) react: 19.2.3 - react-dom: 19.2.7(react@19.2.3) + react-dom: 19.3.0(react@19.2.3) rehype-katex: 7.0.1 remark-gfm: 4.0.1 remark-math: 6.0.0 remark-smartypants: 3.0.3 shiki: 3.23.0 - twoslash: 0.3.9(typescript@6.0.3) + twoslash: 0.3.9(typescript@7.0.2) unified: 11.0.5 unist-util-visit: 5.0.0 transitivePeerDependencies: @@ -7423,16 +8178,16 @@ snapshots: dependencies: ajv: 8.20.0 ajv-draft-04: 1.0.0(ajv@8.20.0) - ajv-formats: 3.0.1(ajv@8.20.0) + ajv-formats: 3.0.1 jsonpointer: 5.0.1 leven: 4.1.0 yaml: 2.9.0 - '@mintlify/prebuild@1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/prebuild@1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/scraping': 4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/scraping': 4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) chalk: 5.3.0 favicons: 7.2.0 fflate: 0.8.3 @@ -7458,11 +8213,11 @@ snapshots: - typescript - utf-8-validate - '@mintlify/previewing@4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3)': + '@mintlify/previewing@4.0.1322(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2)': dependencies: - '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/prebuild': 1.0.1250(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@mintlify/validation': 0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) adm-zip: 0.6.0 better-opn: 3.0.2 chalk: 5.2.0 @@ -7494,9 +8249,9 @@ snapshots: - typescript - utf-8-validate - '@mintlify/scraping@4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/scraping@4.0.965(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/common': 1.0.1097(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) fast-xml-parser: 5.7.3 fs-extra: 11.1.1 graphql: 16.11.0 @@ -7504,7 +8259,7 @@ snapshots: js-yaml: 4.3.1 mdast-util-mdx-jsx: 3.1.3 neotraverse: 0.6.18 - puppeteer: 24.3.1(typescript@6.0.3) + puppeteer: 24.3.1(typescript@7.0.2) rehype-parse: 9.0.1 remark-gfm: 4.0.0 remark-mdx: 3.0.1 @@ -7530,9 +8285,9 @@ snapshots: - typescript - utf-8-validate - '@mintlify/validation@0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@mintlify/validation@0.1.832(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@mintlify/mdx': 4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/mdx': 4.0.2(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(typescript@7.0.2) '@mintlify/models': 0.0.350(debug@4.4.3) fractional-indexing: 3.2.0 js-yaml: 4.3.1 @@ -7557,12 +8312,12 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@monaco-editor/loader': 1.7.0 monaco-editor: 0.55.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: @@ -7573,32 +8328,30 @@ snapshots: '@neon-rs/load@0.0.4': {} - '@next/env@15.5.18': {} - - '@next/env@16.2.9': {} + '@next/env@16.4.0': {} - '@next/swc-darwin-arm64@16.2.9': + '@next/swc-darwin-arm64@16.4.0': optional: true - '@next/swc-darwin-x64@16.2.9': + '@next/swc-darwin-x64@16.4.0': optional: true - '@next/swc-linux-arm64-gnu@16.2.9': + '@next/swc-linux-arm64-gnu@16.4.0': optional: true - '@next/swc-linux-arm64-musl@16.2.9': + '@next/swc-linux-arm64-musl@16.4.0': optional: true - '@next/swc-linux-x64-gnu@16.2.9': + '@next/swc-linux-x64-gnu@16.4.0': optional: true - '@next/swc-linux-x64-musl@16.2.9': + '@next/swc-linux-x64-musl@16.4.0': optional: true - '@next/swc-win32-arm64-msvc@16.2.9': + '@next/swc-win32-arm64-msvc@16.4.0': optional: true - '@next/swc-win32-x64-msvc@16.2.9': + '@next/swc-win32-x64-msvc@16.4.0': optional: true '@nodable/entities@2.2.0': {} @@ -7619,6 +8372,8 @@ snapshots: dependencies: fast-deep-equal: 3.1.3 + '@oxc-project/types@0.152.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.20.0': optional: true @@ -7680,131 +8435,131 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.20.0': optional: true - '@oxfmt/binding-android-arm-eabi@0.54.0': + '@oxfmt/binding-android-arm-eabi@0.71.0': optional: true - '@oxfmt/binding-android-arm64@0.54.0': + '@oxfmt/binding-android-arm64@0.71.0': optional: true - '@oxfmt/binding-darwin-arm64@0.54.0': + '@oxfmt/binding-darwin-arm64@0.71.0': optional: true - '@oxfmt/binding-darwin-x64@0.54.0': + '@oxfmt/binding-darwin-x64@0.71.0': optional: true - '@oxfmt/binding-freebsd-x64@0.54.0': + '@oxfmt/binding-freebsd-x64@0.71.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.71.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.54.0': + '@oxfmt/binding-linux-arm-musleabihf@0.71.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.54.0': + '@oxfmt/binding-linux-arm64-gnu@0.71.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.54.0': + '@oxfmt/binding-linux-arm64-musl@0.71.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.54.0': + '@oxfmt/binding-linux-ppc64-gnu@0.71.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.54.0': + '@oxfmt/binding-linux-riscv64-gnu@0.71.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.54.0': + '@oxfmt/binding-linux-riscv64-musl@0.71.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.54.0': + '@oxfmt/binding-linux-s390x-gnu@0.71.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.54.0': + '@oxfmt/binding-linux-x64-gnu@0.71.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.54.0': + '@oxfmt/binding-linux-x64-musl@0.71.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.54.0': + '@oxfmt/binding-openharmony-arm64@0.71.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.54.0': + '@oxfmt/binding-win32-arm64-msvc@0.71.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.54.0': + '@oxfmt/binding-win32-ia32-msvc@0.71.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.54.0': + '@oxfmt/binding-win32-x64-msvc@0.71.0': optional: true - '@oxlint/binding-android-arm-eabi@1.69.0': + '@oxlint/binding-android-arm-eabi@1.86.0': optional: true - '@oxlint/binding-android-arm64@1.69.0': + '@oxlint/binding-android-arm64@1.86.0': optional: true - '@oxlint/binding-darwin-arm64@1.69.0': + '@oxlint/binding-darwin-arm64@1.86.0': optional: true - '@oxlint/binding-darwin-x64@1.69.0': + '@oxlint/binding-darwin-x64@1.86.0': optional: true - '@oxlint/binding-freebsd-x64@1.69.0': + '@oxlint/binding-freebsd-x64@1.86.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.69.0': + '@oxlint/binding-linux-arm-gnueabihf@1.86.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.69.0': + '@oxlint/binding-linux-arm-musleabihf@1.86.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.69.0': + '@oxlint/binding-linux-arm64-gnu@1.86.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.69.0': + '@oxlint/binding-linux-arm64-musl@1.86.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.69.0': + '@oxlint/binding-linux-ppc64-gnu@1.86.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.69.0': + '@oxlint/binding-linux-riscv64-gnu@1.86.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.69.0': + '@oxlint/binding-linux-riscv64-musl@1.86.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.69.0': + '@oxlint/binding-linux-s390x-gnu@1.86.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.69.0': + '@oxlint/binding-linux-x64-gnu@1.86.0': optional: true - '@oxlint/binding-linux-x64-musl@1.69.0': + '@oxlint/binding-linux-x64-musl@1.86.0': optional: true - '@oxlint/binding-openharmony-arm64@1.69.0': + '@oxlint/binding-openharmony-arm64@1.86.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.69.0': + '@oxlint/binding-win32-arm64-msvc@1.86.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.69.0': + '@oxlint/binding-win32-ia32-msvc@1.86.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.69.0': + '@oxlint/binding-win32-x64-msvc@1.86.0': optional: true - '@payloadcms/db-sqlite@3.85.1(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))': + '@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))': dependencies: '@libsql/client': 0.14.0 - '@payloadcms/drizzle': 3.85.1(@libsql/client@0.14.0)(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3)) - console-table-printer: 2.12.1 + '@payloadcms/drizzle': 4.0.0-canary.38(@libsql/client@0.14.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2)) + console-table-printer: 2.15.0 drizzle-kit: 0.31.7 drizzle-orm: 0.45.2(@libsql/client@0.14.0) - payload: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) prompts: 2.4.2 to-snake-case: 1.0.0 - uuid: 13.0.2 + uuid: 14.0.0 transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@cloudflare/workers-types' @@ -7838,15 +8593,15 @@ snapshots: - supports-color - utf-8-validate - '@payloadcms/drizzle@3.85.1(@libsql/client@0.14.0)(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))': + '@payloadcms/drizzle@4.0.0-canary.38(@libsql/client@0.14.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))': dependencies: - console-table-printer: 2.12.1 + console-table-printer: 2.15.0 dequal: 2.0.3 drizzle-orm: 0.45.2(@libsql/client@0.14.0) - payload: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) prompts: 2.4.2 to-snake-case: 1.0.0 - uuid: 13.0.2 + uuid: 14.0.0 transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@cloudflare/workers-types' @@ -7878,81 +8633,86 @@ snapshots: - sql.js - sqlite3 - '@payloadcms/graphql@3.85.1(graphql@16.14.0)(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(typescript@6.0.3)': + '@payloadcms/graphql@4.0.0-canary.38(graphql@16.14.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(typescript@7.0.2)': dependencies: graphql: 16.14.0 graphql-scalars: 1.22.2(graphql@16.14.0) - payload: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) pluralize: 8.0.0 - ts-essentials: 10.0.3(typescript@6.0.3) - tsx: 4.22.4 + ts-essentials: 10.0.3(typescript@7.0.2) + tsx: 4.23.15 transitivePeerDependencies: - typescript - '@payloadcms/next@3.85.1(@types/react@19.2.15)(graphql@16.14.0)(monaco-editor@0.55.1)(next@16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4))(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@payloadcms/next@4.0.0-canary.38(@types/react@19.2.15)(graphql@16.14.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) - '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) - '@payloadcms/graphql': 3.85.1(graphql@16.14.0)(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(typescript@6.0.3) - '@payloadcms/translations': 3.85.1 - '@payloadcms/ui': 3.85.1(@types/react@19.2.15)(monaco-editor@0.55.1)(next@16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4))(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) + '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) + '@payloadcms/graphql': 4.0.0-canary.38(graphql@16.14.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(typescript@7.0.2) + '@payloadcms/translations': 4.0.0-canary.38 + '@payloadcms/ui': 4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) busboy: 1.6.0 dequal: 2.0.3 - file-type: 21.3.4 + file-type: 22.0.1 graphql: 16.14.0 graphql-http: 1.22.4(graphql@16.14.0) graphql-playground-html: 1.6.30 http-status: 2.1.0 - next: 16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4) + next: 16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) path-to-regexp: 6.3.0 - payload: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) qs-esm: 8.0.1 sass: 1.77.4 - uuid: 13.0.2 + uuid: 14.0.0 transitivePeerDependencies: - '@types/react' + - date-fns-tz - monaco-editor - react - react-dom - supports-color - typescript - '@payloadcms/translations@3.85.1': + '@payloadcms/translations@4.0.0-canary.38': dependencies: - date-fns: 4.1.0 + date-fns: 4.4.0 - '@payloadcms/ui@3.85.1(@types/react@19.2.15)(monaco-editor@0.55.1)(next@16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4))(payload@3.85.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@payloadcms/ui@4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': dependencies: '@date-fns/tz': 1.2.0 - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - '@faceless-ui/modal': 3.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@faceless-ui/scroll-info': 2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@faceless-ui/window-info': 3.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@monaco-editor/react': 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@payloadcms/translations': 3.85.1 + '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) + '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) + '@dnd-kit/utilities': 3.2.2(react@19.3.0) + '@faceless-ui/modal': 3.0.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@faceless-ui/scroll-info': 2.0.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@faceless-ui/window-info': 3.0.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@floating-ui/dom': 1.7.6 + '@monaco-editor/react': 4.7.0(monaco-editor@0.55.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@payloadcms/translations': 4.0.0-canary.38 + '@swc/helpers': 0.5.23 bson-objectid: 2.0.4 - date-fns: 4.1.0 + date-fns: 4.4.0 dequal: 2.0.3 md5: 2.3.0 - next: 16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4) object-to-formdata: 4.5.1 - payload: 3.85.1(graphql@16.14.0)(typescript@6.0.3) + path-to-regexp: 6.3.0 + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) qs-esm: 8.0.1 - react: 19.2.7 - react-datepicker: 7.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - react-image-crop: 10.1.8(react@19.2.7) - react-select: 5.9.0(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.3.0 + react-datepicker: 9.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react-dom: 19.3.0(react@19.3.0) + react-image-crop: 10.1.8(react@19.3.0) + react-select: 5.9.0(@types/react@19.2.15)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) scheduler: 0.25.0 - sonner: 1.7.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - ts-essentials: 10.0.3(typescript@6.0.3) - use-context-selector: 2.0.0(react@19.2.7)(scheduler@0.25.0) - uuid: 13.0.2 + sonner: 1.7.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + ts-essentials: 10.0.3(typescript@7.0.2) + use-context-selector: 2.0.0(react@19.3.0)(scheduler@0.25.0) + uuid: 14.0.0 transitivePeerDependencies: - '@types/react' + - date-fns-tz - monaco-editor - supports-color - typescript @@ -7978,153 +8738,131 @@ snapshots: - react-native-b4a - supports-color - '@rollup/rollup-android-arm-eabi@4.60.4': + '@quansync/fs@1.1.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm-eabi@1.2.12': optional: true - '@rollup/rollup-android-arm64@4.60.4': + '@rolldown/binding-android-arm64@1.2.12': optional: true - '@rollup/rollup-darwin-arm64@4.60.4': + '@rolldown/binding-darwin-arm64@1.2.12': optional: true - '@rollup/rollup-darwin-x64@4.60.4': + '@rolldown/binding-darwin-x64@1.2.12': optional: true - '@rollup/rollup-freebsd-arm64@4.60.4': + '@rolldown/binding-freebsd-x64@1.2.12': optional: true - '@rollup/rollup-freebsd-x64@4.60.4': + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + '@rolldown/binding-linux-arm64-gnu@1.2.12': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.4': + '@rolldown/binding-linux-arm64-musl@1.2.12': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.4': + '@rolldown/binding-linux-ppc64-gnu@1.2.12': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.4': + '@rolldown/binding-linux-s390x-gnu@1.2.12': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.4': + '@rolldown/binding-linux-x64-gnu@1.2.12': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.4': + '@rolldown/binding-linux-x64-musl@1.2.12': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.4': + '@rolldown/binding-openharmony-arm64@1.2.12': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.4': + '@rolldown/binding-win32-arm64-msvc@1.2.12': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.4': + '@rolldown/binding-win32-x64-msvc@1.2.12': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.4': + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.4': + '@rollup/rollup-android-arm64@4.60.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.4': + '@rollup/rollup-darwin-arm64@4.60.4': optional: true - '@rollup/rollup-linux-x64-musl@4.60.4': + '@rollup/rollup-darwin-x64@4.60.4': optional: true - '@rollup/rollup-openbsd-x64@4.60.4': + '@rollup/rollup-freebsd-arm64@4.60.4': optional: true - '@rollup/rollup-openharmony-arm64@4.60.4': + '@rollup/rollup-freebsd-x64@4.60.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.4': + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.4': + '@rollup/rollup-linux-arm-musleabihf@4.60.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.4': + '@rollup/rollup-linux-arm64-gnu@4.60.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.4': + '@rollup/rollup-linux-arm64-musl@4.60.4': optional: true - '@rsbuild/core@2.0.12(core-js@3.42.0)': - dependencies: - '@rspack/core': 2.0.8(@swc/helpers@0.5.23) - '@swc/helpers': 0.5.23 - optionalDependencies: - core-js: 3.42.0 - transitivePeerDependencies: - - '@module-federation/runtime-tools' + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true - '@rslib/core@0.22.1(core-js@3.42.0)(typescript@6.0.3)': - dependencies: - '@rsbuild/core': 2.0.12(core-js@3.42.0) - rsbuild-plugin-dts: 0.22.1(@rsbuild/core@2.0.12(core-js@3.42.0))(typescript@6.0.3) - optionalDependencies: - typescript: 6.0.3 - transitivePeerDependencies: - - '@module-federation/runtime-tools' - - '@typescript/native-preview' - - core-js + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true - '@rspack/binding-darwin-arm64@2.0.8': + '@rollup/rollup-linux-ppc64-musl@4.60.4': optional: true - '@rspack/binding-darwin-x64@2.0.8': + '@rollup/rollup-linux-riscv64-gnu@4.60.4': optional: true - '@rspack/binding-linux-arm64-gnu@2.0.8': + '@rollup/rollup-linux-riscv64-musl@4.60.4': optional: true - '@rspack/binding-linux-arm64-musl@2.0.8': + '@rollup/rollup-linux-s390x-gnu@4.60.4': optional: true - '@rspack/binding-linux-x64-gnu@2.0.8': + '@rollup/rollup-linux-x64-gnu@4.60.4': optional: true - '@rspack/binding-linux-x64-musl@2.0.8': + '@rollup/rollup-linux-x64-musl@4.60.4': optional: true - '@rspack/binding-wasm32-wasi@2.0.8': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@rollup/rollup-openbsd-x64@4.60.4': optional: true - '@rspack/binding-win32-arm64-msvc@2.0.8': + '@rollup/rollup-openharmony-arm64@4.60.4': optional: true - '@rspack/binding-win32-ia32-msvc@2.0.8': + '@rollup/rollup-win32-arm64-msvc@4.60.4': optional: true - '@rspack/binding-win32-x64-msvc@2.0.8': + '@rollup/rollup-win32-ia32-msvc@4.60.4': optional: true - '@rspack/binding@2.0.8': - optionalDependencies: - '@rspack/binding-darwin-arm64': 2.0.8 - '@rspack/binding-darwin-x64': 2.0.8 - '@rspack/binding-linux-arm64-gnu': 2.0.8 - '@rspack/binding-linux-arm64-musl': 2.0.8 - '@rspack/binding-linux-x64-gnu': 2.0.8 - '@rspack/binding-linux-x64-musl': 2.0.8 - '@rspack/binding-wasm32-wasi': 2.0.8 - '@rspack/binding-win32-arm64-msvc': 2.0.8 - '@rspack/binding-win32-ia32-msvc': 2.0.8 - '@rspack/binding-win32-x64-msvc': 2.0.8 - - '@rspack/core@2.0.8(@swc/helpers@0.5.23)': - dependencies: - '@rspack/binding': 2.0.8 - optionalDependencies: - '@swc/helpers': 0.5.23 + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true '@scalar/openapi-types@0.9.1': {} @@ -8134,9 +8872,18 @@ snapshots: dependencies: ajv: 8.20.0 ajv-draft-04: 1.0.0(ajv@8.20.0) - ajv-formats: 3.0.1(ajv@8.20.0) + ajv-formats: 3.0.1 yaml: 2.9.0 + '@seshuk/payload-plugin-tooling@0.2.3(@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2)))(oxfmt@0.71.0)(oxlint@1.86.0)(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.22.4)(typescript@7.0.2))(vitest@5.0.3)': + optionalDependencies: + '@payloadcms/db-sqlite': 4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2)) + oxfmt: 0.71.0 + oxlint: 1.86.0 + payload: 4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2) + tsdown: 0.23.0(oxc-resolver@11.20.0)(tsx@4.22.4)(typescript@7.0.2) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) + '@shikijs/core@3.23.0': dependencies: '@shikijs/types': 3.23.0 @@ -8168,12 +8915,12 @@ snapshots: '@shikijs/core': 3.23.0 '@shikijs/types': 3.23.0 - '@shikijs/twoslash@3.23.0(typescript@6.0.3)': + '@shikijs/twoslash@3.23.0(typescript@7.0.2)': dependencies: '@shikijs/core': 3.23.0 '@shikijs/types': 3.23.0 - twoslash: 0.3.9(typescript@6.0.3) - typescript: 6.0.3 + twoslash: 0.3.9(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -8252,7 +8999,7 @@ snapshots: '@types/json-schema': 7.0.15 ajv: 8.20.0 ajv-errors: 3.0.0(ajv@8.20.0) - ajv-formats: 2.1.1(ajv@8.20.0) + ajv-formats: 2.1.1 es-aggregate-error: 1.0.14 expr-eval-fork: 3.0.3 jsonpath-plus: 10.4.0 @@ -8286,7 +9033,7 @@ snapshots: ajv: 8.20.0 ajv-draft-04: 1.0.0(ajv@8.20.0) ajv-errors: 3.0.0(ajv@8.20.0) - ajv-formats: 2.1.1(ajv@8.20.0) + ajv-formats: 2.1.1 lodash: 4.18.1 tslib: 2.8.1 transitivePeerDependencies: @@ -8344,10 +9091,6 @@ snapshots: '@stoplight/yaml-ast-parser': 0.0.50 tslib: 2.8.1 - '@swc/helpers@0.5.15': - dependencies: - tslib: 2.8.1 - '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -8374,7 +9117,7 @@ snapshots: '@types/busboy@1.5.4': dependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 '@types/chai@5.2.3': dependencies: @@ -8431,6 +9174,10 @@ snapshots: dependencies: undici-types: 7.24.6 + '@types/node@26.6.4': + dependencies: + undici-types: 8.9.0 + '@types/parse-json@4.0.2': {} '@types/react-transition-group@4.4.12(@types/react@19.2.15)': @@ -8452,62 +9199,184 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 '@types/yauzl@2.10.3': dependencies: '@types/node': 25.9.1 optional: true - '@typescript/vfs@1.6.4(typescript@6.0.3)': + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@typescript/vfs@1.6.4(typescript@7.0.2)': dependencies: debug: 4.4.3 - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color '@ungap/structured-clone@1.3.3': {} - '@vitest/expect@4.1.8': + '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 - chai: 6.2.2 - tinyrainbow: 3.1.0 + '@bcoe/v8-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 2.2.1 + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) + + '@vitest/istanbul-lib-coverage@1.0.2': {} + + '@vitest/istanbul-lib-report@1.0.2': + dependencies: + '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@4.1.8(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@5.0.3(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.8 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.4.2 optionalDependencies: - vite: 7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/pretty-format@4.1.8': - dependencies: - tinyrainbow: 3.1.0 + '@vitest/spy@5.0.3': {} - '@vitest/runner@4.1.8': - dependencies: - '@vitest/utils': 4.1.8 - pathe: 2.0.3 + '@yuku-codegen/binding-android-arm64@0.10.2': + optional: true - '@vitest/snapshot@4.1.8': - dependencies: - '@vitest/pretty-format': 4.1.8 - '@vitest/utils': 4.1.8 - magic-string: 0.30.21 - pathe: 2.0.3 + '@yuku-codegen/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.10.2': + optional: true - '@vitest/spy@4.1.8': {} + '@yuku-codegen/binding-freebsd-x64@0.10.2': + optional: true - '@vitest/utils@4.1.8': - dependencies: - '@vitest/pretty-format': 4.1.8 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-x64@0.10.2': + optional: true + + '@yuku-parser/binding-android-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-x64@0.10.2': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-win32-x64@0.10.2': + optional: true + + '@yuku-toolchain/types@0.10.2': {} accepts@1.3.8: dependencies: @@ -8545,20 +9414,13 @@ snapshots: dependencies: ajv: 8.20.0 - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: + ajv-formats@2.1.1: + dependencies: ajv: 8.20.0 - ajv@8.18.0: + ajv-formats@3.0.1: dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 + ajv: 8.20.0 ajv@8.20.0: dependencies: @@ -8637,6 +9499,12 @@ snapshots: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@1.0.7: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + astring@1.9.0: {} async-function@1.0.0: {} @@ -8772,6 +9640,8 @@ snapshots: bytes@3.1.2: {} + cac@7.0.0: {} + cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: @@ -8925,6 +9795,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -8933,7 +9805,7 @@ snapshots: concat-map@0.0.1: {} - console-table-printer@2.12.1: + console-table-printer@2.15.0: dependencies: simple-wcswidth: 1.1.2 @@ -8945,17 +9817,12 @@ snapshots: convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} - convert-to-spaces@2.0.1: {} cookie-signature@1.0.7: {} cookie@0.7.2: {} - core-js@3.42.0: - optional: true - cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -8969,14 +9836,14 @@ snapshots: path-type: 4.0.0 yaml: 1.10.3 - cosmiconfig@9.0.2(typescript@6.0.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.3.1 parse-json: 5.2.0 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 croner@10.0.1: {} @@ -9023,10 +9890,10 @@ snapshots: dataloader@2.2.3: {} - date-fns@3.6.0: {} - date-fns@4.1.0: {} + date-fns@4.4.0: {} + dateformat@4.6.3: {} debug@2.6.9: @@ -9081,6 +9948,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defu@6.1.7: {} + degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -9135,6 +10004,12 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dotenv-expand@11.0.6: + dependencies: + dotenv: 16.4.5 + + dotenv@16.4.5: {} + drizzle-kit@0.31.7: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -9148,6 +10023,10 @@ snapshots: optionalDependencies: '@libsql/client': 0.14.0 + dts-resolver@3.0.0(oxc-resolver@11.20.0): + optionalDependencies: + oxc-resolver: 11.20.0 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -9160,6 +10039,8 @@ snapshots: emoji-regex@8.0.0: {} + empathic@2.1.0: {} + encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -9274,7 +10155,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.2: {} es-object-atoms@1.1.2: dependencies: @@ -9499,7 +10380,7 @@ snapshots: expand-template@2.0.3: optional: true - expect-type@1.3.0: {} + expect-type@1.4.0: {} expr-eval-fork@3.0.3: {} @@ -9551,7 +10432,7 @@ snapshots: transitivePeerDependencies: - supports-color - fast-copy@3.0.2: {} + fast-copy@4.1.1: {} fast-deep-equal@3.1.3: {} @@ -9605,6 +10486,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -9612,7 +10497,7 @@ snapshots: fflate@0.8.3: {} - file-type@21.3.4: + file-type@22.0.1: dependencies: '@tokenizer/inflate': 0.4.1 strtok3: 10.3.5 @@ -9757,7 +10642,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.8.1: + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-tsconfig@5.0.0-beta.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -10048,6 +10937,8 @@ snapshots: dependencies: react-is: 16.13.1 + hookable@6.1.2: {} + html-void-elements@3.0.0: {} http-cache-semantics@4.2.0: {} @@ -10102,7 +10993,7 @@ snapshots: ignore@7.0.5: {} - image-size@2.0.2: {} + image-dimensions@2.5.1: {} immer@9.0.21: {} @@ -10113,6 +11004,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-without-cache@0.4.1: {} + indent-string@5.0.0: {} inflight@1.0.6: @@ -10165,12 +11058,12 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@12.3.0(@types/node@25.9.1): + inquirer@12.3.0(@types/node@26.6.4): dependencies: - '@inquirer/core': 10.3.2(@types/node@25.9.1) - '@inquirer/prompts': 7.9.0(@types/node@25.9.1) - '@inquirer/type': 3.0.10(@types/node@25.9.1) - '@types/node': 25.9.1 + '@inquirer/core': 10.3.2(@types/node@26.6.4) + '@inquirer/prompts': 7.9.0(@types/node@26.6.4) + '@inquirer/type': 3.0.10(@types/node@26.6.4) + '@types/node': 26.6.4 ansi-escapes: 4.3.2 mute-stream: 2.0.0 run-async: 3.0.0 @@ -10188,7 +11081,7 @@ snapshots: ipaddr.js@1.9.1: {} - ipaddr.js@2.2.0: {} + ipaddr.js@2.3.0: {} is-alphabetical@2.0.1: {} @@ -10361,7 +11254,7 @@ snapshots: jiti@2.7.0: optional: true - jose@5.10.0: {} + jose@6.2.3: {} jose@6.2.9: {} @@ -10369,6 +11262,8 @@ snapshots: js-base64@3.7.8: {} + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} js-yaml@3.15.1: @@ -10376,10 +11271,6 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - js-yaml@4.3.1: dependencies: argparse: 2.0.1 @@ -10398,11 +11289,11 @@ snapshots: '@types/json-schema': 7.0.15 '@types/lodash': 4.17.24 is-glob: 4.0.3 - js-yaml: 4.1.1 + js-yaml: 4.3.1 lodash: 4.18.1 minimist: 1.2.8 prettier: 3.8.3 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 json-schema-traverse@1.0.0: {} @@ -10479,9 +11370,15 @@ snapshots: lru-cache@7.18.3: {} - magic-string@0.30.21: + magic-string@1.4.2: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.5: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 markdown-extensions@2.0.0: {} @@ -11051,9 +11948,9 @@ snapshots: dependencies: minipass: 7.1.3 - mint@4.2.810(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/node@25.9.1)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3): + mint@4.2.810(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/node@26.6.4)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2): dependencies: - '@mintlify/cli': 4.0.1413(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.2.7(react@19.2.3))(react@19.2.3))(@types/node@25.9.1)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.2.7(react@19.2.3))(typescript@6.0.3) + '@mintlify/cli': 4.0.1413(@base-ui/react@1.7.0(@date-fns/tz@1.2.0)(@types/react@19.2.15)(date-fns@4.1.0)(react-dom@19.3.0(react@19.2.3))(react@19.2.3))(@types/node@26.6.4)(@types/react@19.2.15)(debug@4.4.3)(react-dom@19.3.0(react@19.2.3))(typescript@7.0.2) transitivePeerDependencies: - '@base-ui/react' - '@types/node' @@ -11093,8 +11990,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.12: {} - nanoid@3.3.18: {} napi-build-utils@2.0.0: @@ -11106,14 +12001,14 @@ snapshots: netmask@2.1.1: {} - next-mdx-remote-client@2.1.12(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.3))(react@19.2.3)(unified@11.0.5): + next-mdx-remote-client@2.1.12(@types/react@19.2.15)(react-dom@19.3.0(react@19.2.3))(react@19.2.3)(unified@11.0.5): dependencies: '@babel/code-frame': 7.29.7 '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.15)(react@19.2.3) '@types/mdx': 2.0.14 react: 19.2.3 - react-dom: 19.2.7(react@19.2.3) + react-dom: 19.3.0(react@19.2.3) remark-mdx-remove-esm: 1.3.3(unified@11.0.5) serialize-error: 13.0.1 vfile: 6.0.3 @@ -11123,29 +12018,30 @@ snapshots: - supports-color - unified - next@16.2.9(babel-plugin-macros@3.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.77.4): + next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4): dependencies: - '@next/env': 16.2.9 - '@swc/helpers': 0.5.15 + '@next/env': 16.4.0 + '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.10.32 caniuse-lite: 1.0.30001793 - postcss: 8.4.31 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(babel-plugin-macros@3.1.0)(react@19.2.7) + postcss: 8.5.23 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + styled-jsx: 5.1.6(babel-plugin-macros@3.1.0)(react@19.3.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.9 - '@next/swc-darwin-x64': 16.2.9 - '@next/swc-linux-arm64-gnu': 16.2.9 - '@next/swc-linux-arm64-musl': 16.2.9 - '@next/swc-linux-x64-gnu': 16.2.9 - '@next/swc-linux-x64-musl': 16.2.9 - '@next/swc-win32-arm64-msvc': 16.2.9 - '@next/swc-win32-x64-msvc': 16.2.9 + '@next/swc-darwin-arm64': 16.4.0 + '@next/swc-darwin-x64': 16.4.0 + '@next/swc-linux-arm64-gnu': 16.4.0 + '@next/swc-linux-arm64-musl': 16.4.0 + '@next/swc-linux-x64-gnu': 16.4.0 + '@next/swc-linux-x64-musl': 16.4.0 + '@next/swc-win32-arm64-msvc': 16.4.0 + '@next/swc-win32-x64-msvc': 16.4.0 sass: 1.77.4 - sharp: 0.34.5 + sharp: 0.35.5(@types/node@26.6.4) transitivePeerDependencies: - '@babel/core' + - '@types/node' - babel-plugin-macros nimma@0.2.3: @@ -11213,7 +12109,9 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 - obug@2.1.1: {} + obug@2.2.1: {} + + obug@3.0.0: {} on-exit-leak-free@2.1.2: {} @@ -11280,52 +12178,53 @@ snapshots: '@oxc-resolver/binding-wasm32-wasi': 11.20.0 '@oxc-resolver/binding-win32-arm64-msvc': 11.20.0 '@oxc-resolver/binding-win32-x64-msvc': 11.20.0 + optional: true - oxfmt@0.54.0: + oxfmt@0.71.0: dependencies: - tinypool: 2.1.0 + tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.54.0 - '@oxfmt/binding-android-arm64': 0.54.0 - '@oxfmt/binding-darwin-arm64': 0.54.0 - '@oxfmt/binding-darwin-x64': 0.54.0 - '@oxfmt/binding-freebsd-x64': 0.54.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.54.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.54.0 - '@oxfmt/binding-linux-arm64-gnu': 0.54.0 - '@oxfmt/binding-linux-arm64-musl': 0.54.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.54.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.54.0 - '@oxfmt/binding-linux-riscv64-musl': 0.54.0 - '@oxfmt/binding-linux-s390x-gnu': 0.54.0 - '@oxfmt/binding-linux-x64-gnu': 0.54.0 - '@oxfmt/binding-linux-x64-musl': 0.54.0 - '@oxfmt/binding-openharmony-arm64': 0.54.0 - '@oxfmt/binding-win32-arm64-msvc': 0.54.0 - '@oxfmt/binding-win32-ia32-msvc': 0.54.0 - '@oxfmt/binding-win32-x64-msvc': 0.54.0 - - oxlint@1.69.0: + '@oxfmt/binding-android-arm-eabi': 0.71.0 + '@oxfmt/binding-android-arm64': 0.71.0 + '@oxfmt/binding-darwin-arm64': 0.71.0 + '@oxfmt/binding-darwin-x64': 0.71.0 + '@oxfmt/binding-freebsd-x64': 0.71.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.71.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.71.0 + '@oxfmt/binding-linux-arm64-gnu': 0.71.0 + '@oxfmt/binding-linux-arm64-musl': 0.71.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.71.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.71.0 + '@oxfmt/binding-linux-riscv64-musl': 0.71.0 + '@oxfmt/binding-linux-s390x-gnu': 0.71.0 + '@oxfmt/binding-linux-x64-gnu': 0.71.0 + '@oxfmt/binding-linux-x64-musl': 0.71.0 + '@oxfmt/binding-openharmony-arm64': 0.71.0 + '@oxfmt/binding-win32-arm64-msvc': 0.71.0 + '@oxfmt/binding-win32-ia32-msvc': 0.71.0 + '@oxfmt/binding-win32-x64-msvc': 0.71.0 + + oxlint@1.86.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.69.0 - '@oxlint/binding-android-arm64': 1.69.0 - '@oxlint/binding-darwin-arm64': 1.69.0 - '@oxlint/binding-darwin-x64': 1.69.0 - '@oxlint/binding-freebsd-x64': 1.69.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.69.0 - '@oxlint/binding-linux-arm-musleabihf': 1.69.0 - '@oxlint/binding-linux-arm64-gnu': 1.69.0 - '@oxlint/binding-linux-arm64-musl': 1.69.0 - '@oxlint/binding-linux-ppc64-gnu': 1.69.0 - '@oxlint/binding-linux-riscv64-gnu': 1.69.0 - '@oxlint/binding-linux-riscv64-musl': 1.69.0 - '@oxlint/binding-linux-s390x-gnu': 1.69.0 - '@oxlint/binding-linux-x64-gnu': 1.69.0 - '@oxlint/binding-linux-x64-musl': 1.69.0 - '@oxlint/binding-openharmony-arm64': 1.69.0 - '@oxlint/binding-win32-arm64-msvc': 1.69.0 - '@oxlint/binding-win32-ia32-msvc': 1.69.0 - '@oxlint/binding-win32-x64-msvc': 1.69.0 + '@oxlint/binding-android-arm-eabi': 1.86.0 + '@oxlint/binding-android-arm64': 1.86.0 + '@oxlint/binding-darwin-arm64': 1.86.0 + '@oxlint/binding-darwin-x64': 1.86.0 + '@oxlint/binding-freebsd-x64': 1.86.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.86.0 + '@oxlint/binding-linux-arm-musleabihf': 1.86.0 + '@oxlint/binding-linux-arm64-gnu': 1.86.0 + '@oxlint/binding-linux-arm64-musl': 1.86.0 + '@oxlint/binding-linux-ppc64-gnu': 1.86.0 + '@oxlint/binding-linux-riscv64-gnu': 1.86.0 + '@oxlint/binding-linux-riscv64-musl': 1.86.0 + '@oxlint/binding-linux-s390x-gnu': 1.86.0 + '@oxlint/binding-linux-x64-gnu': 1.86.0 + '@oxlint/binding-linux-x64-musl': 1.86.0 + '@oxlint/binding-openharmony-arm64': 1.86.0 + '@oxlint/binding-win32-arm64-msvc': 1.86.0 + '@oxlint/binding-win32-ia32-msvc': 1.86.0 + '@oxlint/binding-win32-x64-msvc': 1.86.0 p-any@4.0.0: dependencies: @@ -11411,42 +12310,44 @@ snapshots: path-type@4.0.0: {} - pathe@2.0.3: {} - - payload@3.85.1(graphql@16.14.0)(typescript@6.0.3): + payload@4.0.0-canary.38(graphql@16.14.0)(typescript@7.0.2): dependencies: - '@next/env': 15.5.18 - '@payloadcms/translations': 3.85.1 + '@next/env': 16.4.0 + '@payloadcms/translations': 4.0.0-canary.38 + '@standard-schema/spec': 1.1.0 '@types/busboy': 1.5.4 - ajv: 8.18.0 bson-objectid: 2.0.4 busboy: 1.6.0 ci-info: 4.4.0 - console-table-printer: 2.12.1 + commander: 15.0.0 + console-table-printer: 2.15.0 croner: 10.0.1 dataloader: 2.2.3 deepmerge: 4.3.1 - file-type: 21.3.4 - get-tsconfig: 4.8.1 + dotenv: 16.4.5 + dotenv-expand: 11.0.6 + file-type: 22.0.1 + get-tsconfig: 4.14.0 graphql: 16.14.0 http-status: 2.1.0 - image-size: 2.0.2 - ipaddr.js: 2.2.0 - jose: 5.10.0 + image-dimensions: 2.5.1 + ipaddr.js: 2.3.0 + jose: 6.2.3 json-schema-to-typescript: 15.0.3 - minimist: 1.2.8 path-to-regexp: 6.3.0 - pino: 9.14.0 - pino-pretty: 13.1.2 + pino: 10.3.1 + pino-pretty: 13.1.3 pluralize: 8.0.0 qs-esm: 8.0.1 range-parser: 1.2.1 - sanitize-filename: 1.6.3 - ts-essentials: 10.0.3(typescript@6.0.3) - tsx: 4.22.4 - undici: 7.24.4 - uuid: 13.0.2 + sanitize-filename: 1.6.4 + sax: 1.6.1 + ts-essentials: 10.0.3(typescript@7.0.2) + tsx: 4.23.15 + undici: 7.29.1 + uuid: 14.0.0 ws: 8.21.0 + zod: 4.6.5 transitivePeerDependencies: - bufferutil - supports-color @@ -11461,23 +12362,25 @@ snapshots: picomatch@4.0.4: {} + picomatch@4.0.7: {} + pify@2.3.0: {} - pino-abstract-transport@2.0.0: + pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 - pino-pretty@13.1.2: + pino-pretty@13.1.3: dependencies: colorette: 2.0.20 dateformat: 4.6.3 - fast-copy: 3.0.2 + fast-copy: 4.1.1 fast-safe-stringify: 2.1.1 help-me: 5.0.0 joycon: 3.1.1 minimist: 1.2.8 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 + pino-abstract-transport: 3.0.0 pump: 3.0.4 secure-json-parse: 4.1.0 sonic-boom: 4.2.1 @@ -11485,19 +12388,19 @@ snapshots: pino-std-serializers@7.1.0: {} - pino@9.14.0: + pino@10.3.1: dependencies: '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 + pino-abstract-transport: 3.0.0 pino-std-serializers: 7.1.0 process-warning: 5.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 sonic-boom: 4.2.1 - thread-stream: 3.1.0 + thread-stream: 4.2.0 pirates@4.0.7: {} @@ -11538,12 +12441,6 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.31: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.23: dependencies: nanoid: 3.3.18 @@ -11646,11 +12543,11 @@ snapshots: - supports-color - utf-8-validate - puppeteer@24.3.1(typescript@6.0.3): + puppeteer@24.3.1(typescript@7.0.2): dependencies: '@puppeteer/browsers': 2.7.1 chromium-bidi: 2.1.2(devtools-protocol@0.0.1402036) - cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@7.0.2) devtools-protocol: 0.0.1402036 puppeteer-core: 24.3.1 typed-query-selector: 2.12.2 @@ -11674,6 +12571,8 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + quansync@1.0.0: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -11697,27 +12596,27 @@ snapshots: strip-json-comments: 2.0.1 optional: true - react-datepicker@7.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-datepicker@9.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - '@floating-ui/react': 0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@floating-ui/react': 0.27.19(react-dom@19.3.0(react@19.3.0))(react@19.3.0) clsx: 2.1.1 - date-fns: 3.6.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + date-fns: 4.1.0 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - react-dom@19.2.7(react@19.2.3): + react-dom@19.3.0(react@19.2.3): dependencies: react: 19.2.3 - scheduler: 0.27.0 + scheduler: 0.28.0 - react-dom@19.2.7(react@19.2.7): + react-dom@19.3.0(react@19.3.0): dependencies: - react: 19.2.7 - scheduler: 0.27.0 + react: 19.3.0 + scheduler: 0.28.0 - react-image-crop@10.1.8(react@19.2.7): + react-image-crop@10.1.8(react@19.3.0): dependencies: - react: 19.2.7 + react: 19.3.0 react-is@16.13.1: {} @@ -11726,35 +12625,35 @@ snapshots: react: 19.2.3 scheduler: 0.26.0 - react-select@5.9.0(@types/react@19.2.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-select@5.9.0(@types/react@19.2.15)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@babel/runtime': 7.29.7 '@emotion/cache': 11.14.0 - '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.7) + '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.3.0) '@floating-ui/dom': 1.7.6 '@types/react-transition-group': 4.4.12(@types/react@19.2.15) memoize-one: 6.0.0 prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.15)(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-transition-group: 4.4.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.15)(react@19.3.0) transitivePeerDependencies: - '@types/react' - supports-color - react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-transition-group@4.4.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@babel/runtime': 7.29.7 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) react@19.2.3: {} - react@19.2.7: {} + react@19.3.0: {} read-cache@1.0.0: dependencies: @@ -11773,6 +12672,8 @@ snapshots: real-require@0.2.0: {} + real-require@1.0.0: {} + recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.9 @@ -12028,6 +12929,41 @@ snapshots: reusify@1.1.0: {} + rolldown-plugin-dts@0.28.6(oxc-resolver@11.20.0)(rolldown@1.2.12)(typescript@7.0.2): + dependencies: + dts-resolver: 3.0.0(oxc-resolver@11.20.0) + get-tsconfig: 5.0.0-beta.6 + obug: 3.0.0 + rolldown: 1.2.12 + yuku-ast: 0.10.2 + yuku-codegen: 0.10.2 + yuku-parser: 0.10.2 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.2.12: + dependencies: + '@oxc-project/types': 0.152.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.12 + '@rolldown/binding-android-arm64': 1.2.12 + '@rolldown/binding-darwin-arm64': 1.2.12 + '@rolldown/binding-darwin-x64': 1.2.12 + '@rolldown/binding-freebsd-x64': 1.2.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.12 + '@rolldown/binding-linux-arm64-gnu': 1.2.12 + '@rolldown/binding-linux-arm64-musl': 1.2.12 + '@rolldown/binding-linux-ppc64-gnu': 1.2.12 + '@rolldown/binding-linux-s390x-gnu': 1.2.12 + '@rolldown/binding-linux-x64-gnu': 1.2.12 + '@rolldown/binding-linux-x64-musl': 1.2.12 + '@rolldown/binding-openharmony-arm64': 1.2.12 + '@rolldown/binding-win32-arm64-msvc': 1.2.12 + '@rolldown/binding-win32-x64-msvc': 1.2.12 + rollup@4.60.4: dependencies: '@types/estree': 1.0.8 @@ -12059,13 +12995,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 - rsbuild-plugin-dts@0.22.1(@rsbuild/core@2.0.12(core-js@3.42.0))(typescript@6.0.3): - dependencies: - '@ast-grep/napi': 0.37.0 - '@rsbuild/core': 2.0.12(core-js@3.42.0) - optionalDependencies: - typescript: 6.0.3 - run-async@3.0.0: {} run-parallel@1.2.0: @@ -12103,7 +13032,7 @@ snapshots: safer-buffer@2.1.2: {} - sanitize-filename@1.6.3: + sanitize-filename@1.6.4: dependencies: truncate-utf8-bytes: 1.0.2 @@ -12119,12 +13048,15 @@ snapshots: scheduler@0.26.0: {} - scheduler@0.27.0: {} + scheduler@0.28.0: {} secure-json-parse@4.1.0: {} semver@7.8.1: {} + semver@7.8.5: + optional: true + send@0.19.2: dependencies: debug: 2.6.9 @@ -12244,6 +13176,40 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + sharp@0.35.5(@types/node@26.6.4): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 26.6.4 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -12289,8 +13255,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -12372,10 +13336,10 @@ snapshots: dependencies: atomic-sleep: 1.0.0 - sonner@1.7.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + sonner@1.7.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) source-map-js@1.2.1: {} @@ -12400,13 +13364,11 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 - stackback@0.0.2: {} - state-local@1.0.7: {} statuses@2.0.2: {} - std-env@4.1.0: {} + std-env@4.3.0: {} stop-iteration-iterator@1.1.0: dependencies: @@ -12499,10 +13461,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(babel-plugin-macros@3.1.0)(react@19.2.7): + styled-jsx@5.1.6(babel-plugin-macros@3.1.0)(react@19.3.0): dependencies: client-only: 0.0.1 - react: 19.2.7 + react: 19.3.0 optionalDependencies: babel-plugin-macros: 3.1.0 @@ -12525,7 +13487,7 @@ snapshots: lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 supports-preserve-symlinks-flag@1.0.0: {} @@ -12630,22 +13592,22 @@ snapshots: dependencies: any-promise: 1.3.0 - thread-stream@3.1.0: + thread-stream@4.2.0: dependencies: - real-require: 0.2.0 + real-require: 1.0.0 - tinybench@2.9.0: {} + tinybench@6.2.0: {} - tinyexec@1.2.4: {} + tinyexec@1.3.1: {} - tinyglobby@0.2.16: + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinypool@2.1.0: {} + tinypool@2.2.0: {} - tinyrainbow@3.1.0: {} + tinyrainbow@3.2.0: {} to-data-view@1.1.0: {} @@ -12673,6 +13635,8 @@ snapshots: tr46@0.0.3: {} + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trim-trailing-lines@2.1.0: {} @@ -12683,15 +13647,37 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-essentials@10.0.3(typescript@6.0.3): + ts-essentials@10.0.3(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 ts-interface-checker@0.1.13: {} - tsconfck@3.1.6(typescript@6.0.3): + tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.22.4)(typescript@7.0.2): + dependencies: + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.1.0 + hookable: 6.1.2 + import-without-cache: 0.4.1 + obug: 2.2.1 + picomatch: 4.0.7 + rolldown: 1.2.12 + rolldown-plugin-dts: 0.28.6(oxc-resolver@11.20.0)(rolldown@1.2.12)(typescript@7.0.2) + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + verkit: 0.4.1 optionalDependencies: - typescript: 6.0.3 + tsx: 4.22.4 + typescript: 7.0.2 + transitivePeerDependencies: + - '@typescript/native-preview' + - '@volar/typescript' + - '@vue/language-core' + - oxc-resolver + - vue-tsc tslib@1.14.1: {} @@ -12702,6 +13688,13 @@ snapshots: esbuild: 0.28.1 optionalDependencies: fsevents: 2.3.3 + optional: true + + tsx@4.23.15: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 tunnel-agent@0.6.0: dependencies: @@ -12710,11 +13703,11 @@ snapshots: twoslash-protocol@0.3.9: {} - twoslash@0.3.9(typescript@6.0.3): + twoslash@0.3.9(typescript@7.0.2): dependencies: - '@typescript/vfs': 1.6.4(typescript@6.0.3) + '@typescript/vfs': 1.6.4(typescript@7.0.2) twoslash-protocol: 0.3.9 - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -12766,7 +13759,28 @@ snapshots: typed-query-selector@2.12.2: {} - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 uint8array-extras@1.5.0: {} @@ -12777,9 +13791,16 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.1.0 + quansync: 1.0.0 + undici-types@7.24.6: {} - undici@7.24.4: {} + undici-types@8.9.0: {} + + undici@7.29.1: {} unified@11.0.5: dependencies: @@ -12877,14 +13898,14 @@ snapshots: urijs@1.19.11: {} - use-context-selector@2.0.0(react@19.2.7)(scheduler@0.25.0): + use-context-selector@2.0.0(react@19.3.0)(scheduler@0.25.0): dependencies: - react: 19.2.7 + react: 19.3.0 scheduler: 0.25.0 - use-isomorphic-layout-effect@1.2.1(@types/react@19.2.15)(react@19.2.7): + use-isomorphic-layout-effect@1.2.1(@types/react@19.2.15)(react@19.3.0): dependencies: - react: 19.2.7 + react: 19.3.0 optionalDependencies: '@types/react': 19.2.15 @@ -12902,10 +13923,12 @@ snapshots: uuid@11.1.1: {} - uuid@13.0.2: {} + uuid@14.0.0: {} vary@1.1.2: {} + verkit@0.4.1: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -12926,56 +13949,41 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-tsconfig-paths@7.0.0-alpha.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - debug: 4.4.3 - oxc-resolver: 11.20.0 - tsconfck: 3.1.6(typescript@6.0.3) - vite: 7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0) - transitivePeerDependencies: - - supports-color - - typescript - - vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0): + vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 postcss: 8.5.23 rollup: 4.60.4 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.77.4 tsx: 4.22.4 yaml: 2.9.0 - vitest@4.1.8(@types/node@25.9.1)(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.8 - '@vitest/runner': 4.1.8 - '@vitest/snapshot': 4.1.8 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.3(@types/node@25.9.1)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 7.3.3(@types/node@26.6.4)(jiti@2.7.0)(sass@1.77.4)(tsx@4.22.4)(yaml@2.9.0) + why-is-node-running: 3.2.1 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.6.4 + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw @@ -13035,10 +14043,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} widest-line@5.0.0: dependencies: @@ -13119,6 +14124,45 @@ snapshots: yoga-layout@3.2.1: {} + yuku-ast@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + + yuku-codegen@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-x64': 0.10.2 + '@yuku-codegen/binding-freebsd-x64': 0.10.2 + '@yuku-codegen/binding-linux-arm-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm-musl': 0.10.2 + '@yuku-codegen/binding-linux-arm64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm64-musl': 0.10.2 + '@yuku-codegen/binding-linux-x64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-x64-musl': 0.10.2 + '@yuku-codegen/binding-win32-arm64': 0.10.2 + '@yuku-codegen/binding-win32-x64': 0.10.2 + + yuku-parser@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + yuku-ast: 0.10.2 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.10.2 + '@yuku-parser/binding-darwin-arm64': 0.10.2 + '@yuku-parser/binding-darwin-x64': 0.10.2 + '@yuku-parser/binding-freebsd-x64': 0.10.2 + '@yuku-parser/binding-linux-arm-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm-musl': 0.10.2 + '@yuku-parser/binding-linux-arm64-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm64-musl': 0.10.2 + '@yuku-parser/binding-linux-x64-gnu': 0.10.2 + '@yuku-parser/binding-linux-x64-musl': 0.10.2 + '@yuku-parser/binding-win32-arm64': 0.10.2 + '@yuku-parser/binding-win32-x64': 0.10.2 + zod-to-json-schema@3.20.4(zod@3.24.0): dependencies: zod: 3.24.0 @@ -13129,4 +14173,6 @@ snapshots: zod@4.3.6: {} + zod@4.6.5: {} + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6649888..80b7c5a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,10 +1,28 @@ +nodeVersion: 24.15.0 +pmOnFail: download packages: - - . - docs + +catalog: + '@types/node': ^26.1.2 + react: 19.3.0 + react-dom: 19.3.0 + typescript: 7.0.2 + allowBuilds: '@scarf/scarf': false core-js: false esbuild: true keytar: false + oxc-resolver: true puppeteer: false sharp: true +minimumReleaseAgeExclude: + - '@seshuk/payload-plugin-tooling' + - '@payloadcms/db-sqlite@4.0.0-canary.38' + - '@payloadcms/drizzle@4.0.0-canary.38' + - '@payloadcms/graphql@4.0.0-canary.38' + - '@payloadcms/next@4.0.0-canary.38' + - '@payloadcms/translations@4.0.0-canary.38' + - '@payloadcms/ui@4.0.0-canary.38' + - payload@4.0.0-canary.38 diff --git a/rslib.config.ts b/rslib.config.ts deleted file mode 100644 index 36692ee..0000000 --- a/rslib.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from '@rslib/core' - -export default defineConfig({ - source: { - entry: { index: ['./src/**/*.ts'] }, - }, - lib: [ - { - format: 'esm', - bundle: false, - dts: true, - }, - ], - output: { - target: 'node', - }, -}) diff --git a/src/bin/generateSpec.ts b/src/bin/generateSpec.ts deleted file mode 100644 index 6811336..0000000 --- a/src/bin/generateSpec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { writeFile } from 'node:fs/promises' -import path from 'node:path' -import { getPayload } from 'payload' -import type { SanitizedConfig } from 'payload' - -import { PLUGIN_NAME } from '../constants.js' -import type { ResolvedOptions } from '../types.js' -import { buildOpenApiDocument } from '../spec/build.js' - -interface CliArgs { - lang?: string - out?: string - server?: string -} - -export const parseArgs = (argv: string[]): CliArgs => { - const args: CliArgs = {} - for (let i = 0; i < argv.length; i++) { - const arg = argv[i] - if (arg === undefined) continue - const read = (key: keyof CliArgs, flag: string): boolean => { - if (arg === flag) { - args[key] = argv[++i] - return true - } - if (arg.startsWith(`${flag}=`)) { - args[key] = arg.slice(flag.length + 1) - return true - } - return false - } - if (read('lang', '--lang')) continue - if (read('out', '--out')) continue - read('server', '--server') - } - return args -} - -/** - * Bin entrypoint registered automatically by the OpenAPI plugin. Reads resolved - * plugin options from `config.custom[PLUGIN_NAME]` (stashed during config build), - * then writes the OpenAPI document(s) to disk — no HTTP request required. - * - * Flags: - * --lang locale for translated descriptions; `all` writes one file - * per supported language. Defaults to the i18n fallback. - * --out output file for a single-language run (default openapi.json). - * --server base URL written to the document's `servers`. The HTTP - * endpoint derives this per request; on disk there's no - * request, so pass it here or leave `servers` empty. - * - * Note: the spec reflects public-access marking only (no per-user access), - * identical to the HTTP endpoint. - */ -export const script = async (config: SanitizedConfig): Promise => { - const resolved = config.custom?.[PLUGIN_NAME] as ResolvedOptions | undefined - if (!resolved) { - throw new Error( - `${PLUGIN_NAME}: plugin is not registered in payload.config — no resolved options found on config.custom['${PLUGIN_NAME}'].`, - ) - } - - const payload = await getPayload({ config }) - const { lang, out, server } = parseArgs(process.argv) - const servers = server ? [{ url: server }] : undefined - - const fallback = config.i18n?.fallbackLanguage ?? 'en' - const supported = config.i18n?.supportedLanguages ? Object.keys(config.i18n.supportedLanguages) : [fallback] - const langs = lang === 'all' ? supported : [lang ?? fallback] - - for (const language of langs) { - const doc = await buildOpenApiDocument({ payload, options: resolved, language, servers }) - const file = langs.length > 1 ? `openapi.${language}.json` : (out ?? 'openapi.json') - await writeFile(path.resolve(file), `${JSON.stringify(doc, null, 2)}\n`) - payload.logger.info(`Wrote ${file}`) - } - - await payload.destroy() - process.exit(0) -} diff --git a/src/cli/generateSpec.ts b/src/cli/generateSpec.ts new file mode 100644 index 0000000..3701b5b --- /dev/null +++ b/src/cli/generateSpec.ts @@ -0,0 +1,48 @@ +import { writeFile } from 'node:fs/promises' +import path from 'node:path' + +import { strictObject, z } from 'payload' +import { defineCLICommand } from 'payload/cli' + +import { buildOpenApiDocument } from '@/server/spec/build.js' +import { PLUGIN_NAME } from '@/shared/constants.js' +import type { ResolvedOptions } from '@/shared/types/index.js' + +const optionalString = (description: string) => z.optional(z.string()).check(z.describe(description)) + +export const generateSpecCommand = defineCLICommand({ + description: 'Write the OpenAPI document to a file.', + input: strictObject({ + lang: optionalString( + 'Locale for translated descriptions; `all` writes one file per supported language. Defaults to the i18n fallback.', + ), + out: optionalString('Output file for a single-language run (default openapi.json).'), + server: optionalString( + 'Base URL written to the document `servers`. Defaults to the `servers` option, then `serverURL`.', + ), + }), + handler: async ({ args, getConfig, getPayload }) => { + const config = await getConfig() + const resolved = config.custom?.[PLUGIN_NAME] as ResolvedOptions | undefined + if (!resolved) { + throw new Error( + `${PLUGIN_NAME}: plugin is not registered in payload.config — no resolved options found on config.custom['${PLUGIN_NAME}'].`, + ) + } + + const payload = await getPayload() + const { lang, out, server } = args + const servers = server ? [{ url: server }] : undefined + + const fallback = config.i18n?.fallbackLanguage ?? 'en' + const supported = config.i18n?.supportedLanguages ? Object.keys(config.i18n.supportedLanguages) : [fallback] + const langs = lang === 'all' ? supported : [lang ?? fallback] + + for (const language of langs) { + const doc = await buildOpenApiDocument({ payload, options: resolved, language, servers }) + const file = langs.length > 1 ? `openapi.${language}.json` : (out ?? 'openapi.json') + await writeFile(path.resolve(file), `${JSON.stringify(doc, null, 2)}\n`) + payload.logger.info(`Wrote ${file}`) + } + }, +}) diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 31c77de..0000000 --- a/src/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PLUGIN_NAME = '@seshuk/payload-plugin-openapi' diff --git a/src/index.ts b/src/index.ts index 0280f7d..f95f9ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,28 +1,34 @@ import { fileURLToPath } from 'node:url' -import type { Config, Plugin } from 'payload' -import { PLUGIN_NAME } from './constants.js' -import { resolveOptions } from './options.js' -import type { OpenApiPluginOptions } from './types.js' -import { interactiveAuthHandler } from './endpoints/interactiveAuth.js' -import { specHandler } from './endpoints/spec.js' -import { translations } from './translations/index.js' +import { definePlugin } from 'payload' -const BIN_KEY = 'openapi:generate' +import { interactiveAuthHandler } from '@/server/endpoints/interactiveAuth.js' +import { specHandler } from '@/server/endpoints/spec.js' +import { resolveOptions } from '@/server/options/resolveOptions.js' +import { readSecurityOverride } from '@/server/spec/security.js' +import { buildFeatures, reportOpenApiTelemetry } from '@/server/telemetry.js' +import { PLUGIN_NAME } from '@/shared/constants.js' +import { translations } from '@/shared/translations/index.js' +import type { OpenApiPluginOptions } from '@/shared/types/index.js' -const resolveBinScriptPath = (): string => { +const CLI_COMMAND = 'openapi:generate' + +const resolveCommandPath = (): string => { const selfUrl = new URL(import.meta.url) const ext = selfUrl.pathname.endsWith('.ts') ? '.ts' : '.js' - return fileURLToPath(new URL(`./bin/generateSpec${ext}`, selfUrl)) + return `${fileURLToPath(new URL(`./cli/generateSpec${ext}`, selfUrl))}#generateSpecCommand` } -export const openapi = - (options: OpenApiPluginOptions): Plugin => - (config: Config): Config => { +export const openapi = definePlugin({ + slug: PLUGIN_NAME, + plugin: ({ config, options, plugins }) => { + const resolved = resolveOptions(options) + for (const entity of [...(config.collections ?? []), ...(config.globals ?? [])]) readSecurityOverride(entity) if (options.enabled === false) return config - const resolved = resolveOptions(options) - const authCollection = (config.collections ?? []).find((c) => Boolean(c.auth))?.slug ?? 'users' + const authCollection = + resolved.interactiveAuth.collection ?? (config.collections ?? []).find((c) => Boolean(c.auth))?.slug ?? 'users' + const features = buildFeatures({ plugins, resolved }) config.i18n = config.i18n ?? {} config.i18n.translations = config.i18n.translations ?? {} @@ -43,21 +49,26 @@ export const openapi = ...config.custom, [PLUGIN_NAME]: resolved, }, - bin: [...(config.bin ?? []), { key: BIN_KEY, scriptPath: resolveBinScriptPath() }], + cli: + config.cli === false + ? config.cli + : { + ...config.cli, + commands: { [CLI_COMMAND]: resolveCommandPath(), ...config.cli?.commands }, + }, endpoints: [ ...(config.endpoints ?? []), - // `serve: false` registers the CLI bin only — nothing is mounted over HTTP. ...(resolved.serve ? [ { - path: resolved.specEndpoint, + path: resolved.path, method: 'get' as const, handler: specHandler(resolved), }, ...(resolved.interactiveAuth.enabled ? [ { - path: resolved.interactiveAuth.endpoint, + path: resolved.interactiveAuth.path, method: 'post' as const, handler: interactiveAuthHandler(authCollection), }, @@ -66,24 +77,40 @@ export const openapi = ] : []), ], + onInit: async (payload) => { + await config.onInit?.(payload) + void reportOpenApiTelemetry({ features, options, payload }) + }, } - } + }, +}) -export { buildOpenApiDocument } from './spec/build.js' -export type { BuildOpenApiInput } from './spec/build.js' -export { toOpenApi30, toOpenApi31 } from './spec/downconvert.js' -export { scalar } from './ui/scalar.js' -export { swaggerUi } from './ui/swagger.js' +export { buildOpenApiDocument } from '@/server/spec/build.js' +export type { BuildOpenApiInput } from '@/server/spec/build.js' +export { toOpenApi30, toOpenApi31 } from '@/server/spec/downconvert.js' +export { scalar } from '@/server/ui/scalar.js' +export { swaggerUi } from '@/server/ui/swagger.js' export type { + AccessOption, BuildContext, + EntityOpenApiMeta, EntityOperation, EntitySecurityOverride, + ExtensionContext, + FieldOpenApiMeta, FilterOptions, + LocalizedText, OpenApiExtension, - OpenApiMetadata, + OpenApiInfo, OpenApiPluginOptions, OpenApiVersion, OperationContext, + OperationFilter, + OperationKind, + OperationRule, + SecurityMarking, + SecurityOption, + ServersOption, UiPluginOptions, -} from './types.js' +} from '@/shared/types/index.js' diff --git a/src/options.ts b/src/options.ts deleted file mode 100644 index 2f85d50..0000000 --- a/src/options.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PLUGIN_NAME } from './constants.js' -import type { FilterOptions, OpenApiPluginOptions, ResolvedFilters, ResolvedOptions } from './types.js' - -const resolveFilters = (filters: FilterOptions = {}): ResolvedFilters => ({ - include: filters.include ?? [], - exclude: filters.exclude ?? [], - includeHidden: filters.includeHidden ?? false, - includeSystem: filters.includeSystem ?? false, - includeCustom: filters.includeCustom ?? true, - includeAuth: filters.includeAuth ?? true, - includeAdminAuth: filters.includeAdminAuth ?? false, - includeVersions: filters.includeVersions ?? true, - includeJobs: filters.includeJobs ?? true, - excludeOperations: filters.excludeOperations ?? [], - excludeWhen: filters.excludeWhen, -}) - -const DEFAULT_AUTH_ENDPOINT = '/openapi-auth' - -const resolveInteractiveAuth = ( - option: OpenApiPluginOptions['interactiveAuth'], -): ResolvedOptions['interactiveAuth'] => { - if (!option) return { enabled: false, endpoint: DEFAULT_AUTH_ENDPOINT } - if (option === true) return { enabled: true, endpoint: DEFAULT_AUTH_ENDPOINT } - return { enabled: true, endpoint: option.endpoint ?? DEFAULT_AUTH_ENDPOINT } -} - -export const resolveOptions = (options: OpenApiPluginOptions): ResolvedOptions => { - if (!options.metadata?.title || !options.metadata?.version) { - throw new Error(`${PLUGIN_NAME}: metadata.title and metadata.version are required.`) - } - - return { - metadata: options.metadata, - openapiVersion: options.openapiVersion ?? '3.2', - specEndpoint: options.specEndpoint ?? '/openapi.json', - serve: options.serve ?? true, - filters: resolveFilters(options.filters), - interactiveAuth: resolveInteractiveAuth(options.interactiveAuth), - nestedTags: options.nestedTags ?? false, - securityWhen: options.securityWhen, - cache: options.cache ?? true, - extensions: options.extensions ?? [], - } -} diff --git a/src/endpoints/interactiveAuth.ts b/src/server/endpoints/interactiveAuth.ts similarity index 87% rename from src/endpoints/interactiveAuth.ts rename to src/server/endpoints/interactiveAuth.ts index 888fb1f..57d144f 100644 --- a/src/endpoints/interactiveAuth.ts +++ b/src/server/endpoints/interactiveAuth.ts @@ -15,7 +15,7 @@ export const interactiveAuthHandler: InteractiveAuthHandler = (authCollectionSlu return Response.json({ access_token: result.token, token_type: 'JWT', - expires_in: result.exp, + expires_in: result.exp === undefined ? undefined : result.exp - Math.floor(Date.now() / 1000), }) } catch { return Response.json({ error: 'invalid_grant' }, { status: 400 }) diff --git a/src/endpoints/spec.ts b/src/server/endpoints/spec.ts similarity index 51% rename from src/endpoints/spec.ts rename to src/server/endpoints/spec.ts index f7f0441..1f53722 100644 --- a/src/endpoints/spec.ts +++ b/src/server/endpoints/spec.ts @@ -1,14 +1,20 @@ +import type { Document, ServerObject } from '@scalar/openapi-types/3.2' +import { Forbidden } from 'payload' import type { PayloadHandler, PayloadRequest } from 'payload' -import type { Document } from '@scalar/openapi-types/3.2' -import type { ResolvedOptions } from '../types.js' -import { buildOpenApiDocument } from '../spec/build.js' + +import { buildOpenApiDocument, staticServers } from '@/server/spec/build.js' +import type { ResolvedOptions } from '@/shared/types/index.js' type SpecHandler = (options: ResolvedOptions) => PayloadHandler -const deriveServerUrl = (req: PayloadRequest): string => { - const host = req.headers.get('host') ?? 'localhost:3000' - const protocol = host.startsWith('localhost') || host.startsWith('127.') ? 'http' : 'https' - return `${protocol}://${host}` +const requestServers = async (req: PayloadRequest, options: ResolvedOptions): Promise => { + if (typeof options.servers === 'function') return options.servers({ req }) + const host = req.headers.get('host')?.toLowerCase() + if (!options.servers && host && options.trustedHosts.includes(host)) { + const protocol = host.startsWith('localhost') || host.startsWith('127.') ? 'http' : 'https' + return [{ url: `${protocol}://${host}` }] + } + return staticServers(options, req.payload.config) } const requestedLang = (req: PayloadRequest): string | undefined => { @@ -22,6 +28,7 @@ export const specHandler: SpecHandler = (options) => { const cache = new Map() return async (req: PayloadRequest) => { + if (options.access && (await options.access({ req })) !== true) throw new Forbidden(req.t) const requested = requestedLang(req) const lang = requested ?? req.i18n?.language ?? req.payload.config.i18n?.fallbackLanguage ?? 'en' @@ -35,7 +42,6 @@ export const specHandler: SpecHandler = (options) => { }) cache.set(lang, doc) } - const document = { ...doc, servers: [{ url: deriveServerUrl(req) }] } - return Response.json(document) + return Response.json({ ...doc, servers: await requestServers(req, options) }) } } diff --git a/src/server/options/resolveOptions.ts b/src/server/options/resolveOptions.ts new file mode 100644 index 0000000..52f65b4 --- /dev/null +++ b/src/server/options/resolveOptions.ts @@ -0,0 +1,69 @@ +import { PLUGIN_NAME } from '@/shared/constants.js' +import type { FilterOptions, OpenApiPluginOptions, ResolvedFilters, ResolvedOptions } from '@/shared/types/index.js' +import { isPlainObject } from '@/shared/utils.js' + +const resolveFilters = (filters: FilterOptions = {}): ResolvedFilters => ({ + include: filters.include ?? [], + exclude: filters.exclude ?? [], + includeHidden: filters.includeHidden ?? false, + includeSystem: filters.includeSystem ?? false, + includeCustom: filters.includeCustom ?? true, + includeAuth: filters.includeAuth ?? true, + includeAdminAuth: filters.includeAdminAuth ?? false, + includeVersions: filters.includeVersions ?? true, + includeJobs: filters.includeJobs ?? true, + excludeOperations: filters.excludeOperations ?? [], +}) + +const DEFAULT_AUTH_PATH = '/openapi-auth' + +const resolveInteractiveAuth = ( + option: OpenApiPluginOptions['interactiveAuth'], +): ResolvedOptions['interactiveAuth'] => { + if (!option) return { enabled: false, path: DEFAULT_AUTH_PATH } + if (option === true) return { enabled: true, path: DEFAULT_AUTH_PATH } + return { enabled: true, path: option.path ?? DEFAULT_AUTH_PATH, collection: option.collection } +} + +export const assertNoRemovedKeys = (removed: Array<[unknown, string]>): void => { + for (const [value, message] of removed) { + if (value !== undefined) throw new Error(`[${PLUGIN_NAME}] ${message}`) + } +} + +export const resolveOptions = (options: OpenApiPluginOptions): ResolvedOptions => { + const legacy = options as Record + const legacyFilters = isPlainObject(legacy.filters) ? legacy.filters : {} + const legacyAuth = isPlainObject(legacy.interactiveAuth) ? legacy.interactiveAuth : {} + assertNoRemovedKeys([ + [legacy.metadata, 'metadata was renamed to info'], + [legacy.specEndpoint, 'specEndpoint was renamed to path'], + [legacy.securityWhen, "securityWhen was removed, use security (return 'public' or 'secured', not true or false)"], + [legacyFilters.excludeWhen, 'filters.excludeWhen was removed, use a function in filters.excludeOperations'], + [legacyAuth.endpoint, 'interactiveAuth.endpoint was renamed to interactiveAuth.path'], + [ + options.extensions?.some((extension) => (extension.transform?.length ?? 0) > 1) || undefined, + 'extensions[].transform takes one object now, use ({ doc, payload }) instead of (doc, ctx)', + ], + ]) + + if (!options.info?.title || !options.info?.version) { + throw new Error(`[${PLUGIN_NAME}] info.title and info.version are required`) + } + + return { + info: options.info, + openapiVersion: options.openapiVersion ?? '3.2', + path: options.path ?? '/openapi.json', + serve: options.serve ?? true, + access: options.access, + servers: options.servers, + trustedHosts: (options.trustedHosts ?? []).map((host) => host.toLowerCase()), + filters: resolveFilters(options.filters), + interactiveAuth: resolveInteractiveAuth(options.interactiveAuth), + nestedTags: options.nestedTags ?? false, + security: options.security, + cache: options.cache ?? true, + extensions: options.extensions ?? [], + } +} diff --git a/src/spec/build.ts b/src/server/spec/build.ts similarity index 72% rename from src/spec/build.ts rename to src/server/spec/build.ts index b011cb7..40b3d9a 100644 --- a/src/spec/build.ts +++ b/src/server/spec/build.ts @@ -1,9 +1,10 @@ -import { getLocalI18n } from 'payload' -import type { BasePayload } from 'payload' import type { AcceptedLanguages, I18n } from '@payloadcms/translations' -import type { Document } from '@scalar/openapi-types/3.2' +import type { Document, ServerObject } from '@scalar/openapi-types/3.2' +import { getLocalI18n } from 'payload' +import type { BasePayload, SanitizedConfig } from 'payload' + +import type { BuildContext, ResolvedOptions } from '@/shared/types/index.js' -import type { BuildContext, ResolvedOptions } from '../types.js' import { buildDocument } from './buildDocument.js' import { toOpenApi30, toOpenApi31 } from './downconvert.js' @@ -12,7 +13,12 @@ export interface BuildOpenApiInput { options: ResolvedOptions language?: string i18n?: I18n - servers?: { url: string }[] + servers?: ServerObject[] +} + +export const staticServers = (options: ResolvedOptions, config: SanitizedConfig): ServerObject[] => { + if (Array.isArray(options.servers)) return options.servers + return config.serverURL ? [{ url: config.serverURL }] : [] } export const buildOpenApiDocument = async (input: BuildOpenApiInput): Promise => { @@ -37,13 +43,13 @@ export const buildOpenApiDocument = async (input: BuildOpenApiInput): Promise collection.config), globals: config.globals, config, ctx, - logger: payload.logger, }) if (options.openapiVersion === '3.0') return toOpenApi30(doc) diff --git a/src/spec/buildDocument.ts b/src/server/spec/buildDocument.ts similarity index 67% rename from src/spec/buildDocument.ts rename to src/server/spec/buildDocument.ts index ddce4a3..04b0ee3 100644 --- a/src/spec/buildDocument.ts +++ b/src/server/spec/buildDocument.ts @@ -1,4 +1,4 @@ -import type { Field, PayloadLogger, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload' +import { getTranslation } from '@payloadcms/translations' import type { ComponentsObject, Document, @@ -7,12 +7,27 @@ import type { SecuritySchemeObject, TagObject, } from '@scalar/openapi-types/3.2' -import { getTranslation } from '@payloadcms/translations' +import type { + Access, + BasePayload, + Field, + SanitizedCollectionConfig, + SanitizedConfig, + SanitizedGlobalConfig, +} from 'payload' + +import { buildAuthPaths } from '@/server/spec/paths/auth.js' +import { buildCollectionPaths } from '@/server/spec/paths/collections.js' +import { buildCustomEndpointPaths } from '@/server/spec/paths/custom.js' +import { buildGlobalPaths } from '@/server/spec/paths/globals.js' +import { buildJobsPaths } from '@/server/spec/paths/jobs.js' +import { buildSystemPaths } from '@/server/spec/paths/system.js' +import { buildVersionPaths, versionComponentSchemas } from '@/server/spec/paths/versions.js' +import { PLUGIN_NAME } from '@/shared/constants.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext, OperationKind, ResolvedOptions } from '@/shared/types/index.js' +import { deepMerge } from '@/shared/utils.js' -import type { BuildContext, ResolvedOptions } from '../types.js' -import { PLUGIN_NAME } from '../constants.js' -import { deepMerge } from '../utils.js' -import { makeT } from '../translations/index.js' import { ERROR_SCHEMA_NAME, INTERACTIVE_SCHEME_NAME, @@ -24,6 +39,7 @@ import { } from './components.js' import { buildBlockSchema, buildEntitySchemas } from './entitySchemas.js' import { filterOperations, shouldIncludeCollection, shouldIncludeGlobal } from './filters.js' +import { applyHierarchy } from './hierarchy.js' import { blockSchemaName, createSchemaName, @@ -37,14 +53,8 @@ import { updateSchemaName, } from './names.js' import { buildParamSchemas, buildQueryOperationsSchema, collectionHasFilters } from './params.js' -import { applySecurityWhen, resolveEntitySecurity } from './security.js' -import { buildTagHierarchy, type CollectionTagInfo, type GlobalTagInfo } from './tags.js' -import { buildAuthPaths } from './paths/auth.js' -import { buildCollectionPaths } from './paths/collections.js' -import { buildCustomEndpointPaths } from './paths/custom.js' -import { buildGlobalPaths } from './paths/globals.js' -import { buildJobsPaths } from './paths/jobs.js' -import { buildVersionPaths, versionComponentSchemas } from './paths/versions.js' +import { applySecurity, evaluateAccess, resolveEntitySecurity, securedRequirement } from './security.js' +import { buildTagHierarchy, type CollectionTagInfo, type GlobalTagInfo, type SystemTag } from './tags.js' const resolveDescription = (description: unknown, ctx: BuildContext): string | undefined => { if (description == null) return undefined @@ -71,16 +81,27 @@ export interface BuildInput { globals: SanitizedGlobalConfig[] config: SanitizedConfig ctx: BuildContext - logger: PayloadLogger + payload: BasePayload } export const buildDocument = async (input: BuildInput): Promise => { - const { options, ctx, config, logger } = input + const { options, ctx, config, payload } = input + const { logger } = payload const t = makeT(ctx.i18n) const paths: PathsObject = {} const schemas: Record = {} const { filters } = options + const login = options.serve && options.interactiveAuth.enabled + const finalize = async (group: PathsObject, kind: OperationKind, slug?: string): Promise => + applySecurity({ + paths: await filterOperations({ paths: group, slug, kind, filters }), + slug, + kind, + security: options.security, + login, + }) + const access = filters.includeAuth && filters.includeAdminAuth const collections = input.collections.filter((c) => shouldIncludeCollection(c, filters)) const globals = input.globals.filter((g) => shouldIncludeGlobal(g, filters)) @@ -116,10 +137,11 @@ export const buildDocument = async (input: BuildInput): Promise => { const security = await resolveEntitySecurity({ entity: collection, - operations: ['read', 'create', 'update', 'delete'], + operations: ['read', 'create', 'update', 'delete', 'validate', 'readVersions'], locale: ctx.locales[0], + baseAccess: config.baseAccess, }) - const collectionPaths: PathsObject = { ...buildCollectionPaths({ collection, ctx, security }) } + const collectionPaths: PathsObject = { ...buildCollectionPaths({ collection, ctx, security, access }) } if (filters.includeAuth) { Object.assign( @@ -145,25 +167,13 @@ export const buildDocument = async (input: BuildInput): Promise => { ctx, schemaBase: base, nestedTags: options.nestedTags, + security, }), ) } - const filteredCollectionPaths = filterOperations({ - paths: collectionPaths, - slug: collection.slug, - kind: 'collection', - filters, - }) - Object.assign( - paths, - applySecurityWhen({ - paths: filteredCollectionPaths, - slug: collection.slug, - kind: 'collection', - securityWhen: options.securityWhen, - }), - ) + applyHierarchy({ collection, base, paths: collectionPaths, schemas, ctx }) + Object.assign(paths, await finalize(collectionPaths, 'collection', collection.slug)) } catch (error) { logger.warn(`${PLUGIN_NAME}: skipped collection "${collection.slug}": ${(error as Error).message}`) } @@ -184,10 +194,11 @@ export const buildDocument = async (input: BuildInput): Promise => { const security = await resolveEntitySecurity({ entity: global, - operations: ['read', 'update'], + operations: ['read', 'update', 'validate', 'readVersions'], locale: ctx.locales[0], + baseAccess: config.baseAccess, }) - const globalPaths: PathsObject = { ...buildGlobalPaths({ global, ctx, security }) } + const globalPaths: PathsObject = { ...buildGlobalPaths({ global, ctx, security, access }) } if (filters.includeVersions && global.versions) { const { version, versionList } = versionComponentSchemas({ @@ -205,21 +216,13 @@ export const buildDocument = async (input: BuildInput): Promise => { ctx, schemaBase: gbase, nestedTags: options.nestedTags, + security, global: true, }), ) } - const filteredGlobalPaths = filterOperations({ paths: globalPaths, slug: global.slug, kind: 'global', filters }) - Object.assign( - paths, - applySecurityWhen({ - paths: filteredGlobalPaths, - slug: global.slug, - kind: 'global', - securityWhen: options.securityWhen, - }), - ) + Object.assign(paths, await finalize(globalPaths, 'global', global.slug)) } catch (error) { logger.warn(`${PLUGIN_NAME}: skipped global "${global.slug}": ${(error as Error).message}`) } @@ -227,7 +230,7 @@ export const buildDocument = async (input: BuildInput): Promise => { if (filters.includeCustom) { try { - Object.assign(paths, buildCustomEndpointPaths({ config, collections, globals, ctx })) + Object.assign(paths, await finalize(buildCustomEndpointPaths({ config, collections, globals, ctx }), 'custom')) } catch (error) { logger.warn(`${PLUGIN_NAME}: failed to collect custom endpoints: ${(error as Error).message}`) } @@ -240,12 +243,24 @@ export const buildDocument = async (input: BuildInput): Promise => { if (filters.includeJobs) { try { - Object.assign(paths, buildJobsPaths({ config, ctx })) + const runAccess = (jobsConfig?.access as { run?: Access } | undefined)?.run + const jobsSecurity = (await evaluateAccess(runAccess, { baseAccess: config.baseAccess })) + ? undefined + : securedRequirement() + Object.assign(paths, await finalize(buildJobsPaths({ config, ctx, security: jobsSecurity }), 'jobs')) } catch (error) { logger.warn(`${PLUGIN_NAME}: failed to add jobs endpoint: ${(error as Error).message}`) } } + const uploadSlugs = collections.filter((c) => c.upload).map((c) => c.slug) + Object.assign(paths, await finalize(buildSystemPaths({ ctx, access, uploadSlugs }), 'system')) + + const systemTags: SystemTag[] = [] + if (hasJobs) systemTags.push('Jobs') + if (uploadSlugs.length > 0) systemTags.push('Uploads') + if (access) systemTags.push('Access') + const supportedTimezones = config.admin?.timezones?.supportedTimezones if (Array.isArray(supportedTimezones) && supportedTimezones.length > 0) { schemas[schemaName('supportedTimezones')] = { @@ -261,9 +276,9 @@ export const buildDocument = async (input: BuildInput): Promise => { const securitySchemes: Record = { [SECURITY_SCHEME_NAME]: securityScheme({ cookiePrefix, t }), } - if (options.interactiveAuth.enabled) { + if (login) { securitySchemes[INTERACTIVE_SCHEME_NAME] = interactiveSecurityScheme({ - tokenUrl: `${ctx.apiRoute}${options.interactiveAuth.endpoint}`, + tokenUrl: `${ctx.apiRoute}${options.interactiveAuth.path}`, t, }) } @@ -280,8 +295,9 @@ export const buildDocument = async (input: BuildInput): Promise => { ? `\n\n${t('docLanguagesNote', { languages: ctx.docLanguages.map((l) => `\`${l}\``).join(', ') })}` : '' const info = { - ...options.metadata, - description: `${options.metadata.description ?? ''}${localeNote}${docLanguagesNote}`.trim() || undefined, + ...options.info, + description: + `${resolveDescription(options.info.description, ctx) ?? ''}${localeNote}${docLanguagesNote}`.trim() || undefined, } let doc = { @@ -296,7 +312,7 @@ export const buildDocument = async (input: BuildInput): Promise => { tags: buildTagHierarchy({ collections: collectionTagInfos, globals: globalTagInfos, - hasJobs, + systemTags, t, nested: options.nestedTags, }), @@ -304,23 +320,24 @@ export const buildDocument = async (input: BuildInput): Promise => { } as Document for (const ext of options.extensions) { - try { - if (ext.paths) { - doc.paths = deepMerge(doc.paths ?? {}, ext.paths) as PathsObject - } - if (ext.components) { - doc.components = deepMerge( - (doc.components ?? {}) as Record, - ext.components as Record, - ) as ComponentsObject - } - if (ext.tags) { - const seen = new Set((doc.tags ?? []).map((tag: TagObject) => tag.name)) - doc.tags = [...(doc.tags ?? []), ...ext.tags.filter((tag: TagObject) => !seen.has(tag.name))] + if (ext.paths) { + doc.paths = deepMerge(doc.paths ?? {}, ext.paths) as PathsObject + } + if (ext.components) { + doc.components = deepMerge( + (doc.components ?? {}) as Record, + ext.components as Record, + ) as ComponentsObject + } + if (ext.tags) { + const seen = new Set((doc.tags ?? []).map((tag: TagObject) => tag.name)) + doc.tags = [...(doc.tags ?? []), ...ext.tags.filter((tag: TagObject) => !seen.has(tag.name))] + } + if (ext.transform) { + doc = await ext.transform({ ...ctx, doc, payload, options }) + if (typeof doc?.openapi !== 'string' || 'payload' in doc) { + throw new Error(`[${PLUGIN_NAME}] extensions[].transform must return the document, it gets ({ doc, payload })`) } - if (ext.transform) doc = ext.transform(doc, ctx) - } catch (error) { - logger.warn(`${PLUGIN_NAME}: extension failed and was skipped: ${(error as Error).message}`) } } diff --git a/src/spec/components.ts b/src/server/spec/components.ts similarity index 67% rename from src/spec/components.ts rename to src/server/spec/components.ts index d054373..cfba8e8 100644 --- a/src/spec/components.ts +++ b/src/server/spec/components.ts @@ -1,13 +1,17 @@ import type { + OperationObject, + ParameterObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SchemaObject, + SecurityRequirementObject, SecuritySchemeObject, } from '@scalar/openapi-types/3.2' -import type { Translate } from '../translations/types.js' +import type { Translate } from '@/shared/translations/types.js' + import { refTo } from './names.js' type SchemaOrRef = SchemaObject | ReferenceObject @@ -35,6 +39,103 @@ export const messageResponse = (description: string): ResponsesObject => export type ErrorCode = '400' | '401' | '403' | '404' | '500' +const allowed = (): SchemaObject => ({ + oneOf: [ + { type: 'boolean', enum: [true] }, + { + type: 'object', + properties: { permission: { type: 'boolean' }, where: { type: 'object', additionalProperties: true } }, + required: ['permission'], + }, + ], +}) + +export const docAccessOperation = ({ + tag, + operationId, + operations, + bodyRef, + errors, + t, +}: { + tag: string + operationId: string + operations: string[] + bodyRef: ReferenceObject + errors: ErrorCode[] + t: Translate +}): OperationObject => ({ + tags: [tag], + operationId, + requestBody: { description: t('docAccessBody'), content: { 'application/json': { schema: bodyRef } } }, + responses: { + ...jsonOk(t('docAccess'), { + type: 'object', + properties: { + ...Object.fromEntries(operations.map((op) => [op, allowed()])), + fields: { + oneOf: [ + { type: 'boolean', enum: [true] }, + { type: 'object', additionalProperties: true }, + ], + }, + }, + }), + ...errorResponses(errors, t), + }, +}) + +export const validateOperation = ({ + tag, + operationId, + parameters, + requestBody, + errors, + security, + t, +}: { + tag: string + operationId: string + parameters: ParameterObject[] + requestBody: RequestBodyObject + errors: ErrorCode[] + security?: SecurityRequirementObject[] + t: Translate +}): OperationObject => ({ + tags: [tag], + operationId, + parameters, + requestBody: { + ...requestBody, + description: [requestBody.description, t('validateBody')].filter(Boolean).join(' '), + }, + responses: { + ...jsonOk(t('validateResult'), { + type: 'object', + properties: { + valid: { type: 'boolean' }, + errors: { + type: 'array', + items: { + type: 'object', + properties: { + message: { type: 'string' }, + path: { type: 'string' }, + label: { oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: { type: 'string' } }] }, + locale: { type: 'string' }, + tableName: { type: 'string' }, + }, + required: ['message', 'path'], + }, + }, + }, + required: ['valid', 'errors'], + }), + ...errorResponses(errors, t), + }, + security, +}) + export const ERROR_SCHEMA_NAME = 'ErrorResponse' const ERROR_KEYS = { diff --git a/src/spec/downconvert.ts b/src/server/spec/downconvert.ts similarity index 96% rename from src/spec/downconvert.ts rename to src/server/spec/downconvert.ts index 3f4664a..0f991cf 100644 --- a/src/spec/downconvert.ts +++ b/src/server/spec/downconvert.ts @@ -74,5 +74,7 @@ export const toOpenApi31 = (doc: Document): Document => ({ export const toOpenApi30 = (doc: Document): Document => { const converted = downconvertTo30(toOpenApi31(doc)) as Document converted.openapi = '3.0.4' + delete converted.info.summary + delete converted.info.license?.identifier return converted } diff --git a/src/spec/entitySchemas.ts b/src/server/spec/entitySchemas.ts similarity index 72% rename from src/spec/entitySchemas.ts rename to src/server/spec/entitySchemas.ts index 99f7827..ffa254e 100644 --- a/src/spec/entitySchemas.ts +++ b/src/server/spec/entitySchemas.ts @@ -1,20 +1,17 @@ -import { entityToJSONSchema } from 'payload' -import type { Block, Field, SanitizedConfig } from 'payload' -import type { SchemaObject } from '@scalar/openapi-types/3.2' import { getTranslation } from '@payloadcms/translations' +import type { SchemaObject } from '@scalar/openapi-types/3.2' +import { entityToJSONSchema } from 'payload' +import type { Block, Field, SanitizedConfig, SchemaVariant } from 'payload' + +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext, Entity, IDType } from '@/shared/types/index.js' +import { deepMerge, isPlainObject } from '@/shared/utils.js' -import type { BuildContext, Entity, IDType } from '../types.js' -import { deepMerge, isPlainObject } from '../utils.js' -import { makeT } from '../translations/index.js' import { AUTH_FIELDS, flattenFields } from './fields.js' -import { refTo, rewriteRefs, schemaName } from './names.js' +import { rewriteRefs } from './names.js' const READ_HIDDEN_FIELDS = new Set([...AUTH_FIELDS, 'collection']) -const SERVER_FIELDS = ['id', 'createdAt', 'updatedAt'] - -// Drop interfaceName: entityToJSONSchema emits dangling $refs otherwise. -// Shallow copy only, config has functions, structuredClone would throw. const stripInterfaceName = (entity: T): T => { const { interfaceName: _omit, ...rest } = entity as T & { interfaceName?: string } return rest as T @@ -24,18 +21,28 @@ const toComponentSchema = ({ config, source, idType, - write = false, + defs = new Map(), + variant, }: { config: SanitizedConfig source: Entity | Block idType: IDType - write?: boolean + defs?: Map + variant?: SchemaVariant }): SchemaObject => { - const jsonSchema = entityToJSONSchema(config, source as never, new Map(), idType, undefined, undefined, { - forceInlineBlocks: write, - }) + const jsonSchema = entityToJSONSchema( + config, + source as never, + defs as never, + idType, + new Set(), + undefined, + undefined, + undefined, + variant, + ) const blockSlugs = new Set((config.blocks ?? []).map((b) => b.slug)) - const schema = rewriteRefs(jsonSchema as never, blockSlugs) as unknown as SchemaObject + const schema = rewriteRefs(jsonSchema as never, blockSlugs, defs) as unknown as SchemaObject delete schema.title return schema } @@ -55,8 +62,6 @@ const isLocaleKeyed = (value: unknown, ctx: BuildContext): value is Record { @@ -129,6 +134,19 @@ const addBlockDiscriminators = (node: unknown): void => { for (const value of Object.values(node)) addBlockDiscriminators(value) } +const fixPolymorphicJoinRequired = (node: unknown): void => { + if (Array.isArray(node)) { + for (const item of node) fixPolymorphicJoinRequired(item) + return + } + if (!isPlainObject(node)) return + const { properties, required } = node as SchemaObject + if (isPlainObject(properties) && 'relationTo' in properties && Array.isArray(required)) { + ;(node as SchemaObject).required = required.map((r: string) => (r === 'collectionSlug' ? 'relationTo' : r)) + } + for (const value of Object.values(node)) fixPolymorphicJoinRequired(value) +} + const markLocalizedFields = ({ schema, fields, @@ -166,47 +184,19 @@ const removeProps = (schema: SchemaObject, names: Iterable): void => { } } -const toIdType = (type: unknown): unknown => - Array.isArray(type) ? type.map(toIdType) : type === 'number' ? 'integer' : type - -const writeRelationshipsAsIds = (node: unknown, refs: Set): void => { - if (Array.isArray(node)) { - for (const item of node) writeRelationshipsAsIds(item, refs) - return +const isConstantDeny = (access: unknown): boolean => { + if (typeof access !== 'function' || access.length > 0) return false + try { + return access() === false + } catch { + return false } - if (!isPlainObject(node)) return - const oneOf = node.oneOf - if (Array.isArray(oneOf) && oneOf.length === 2 && isRef(oneOf[1]) && refs.has(oneOf[1].$ref)) { - delete node.oneOf - node.type = toIdType((oneOf[0] as SchemaObject).type) - return - } - for (const value of Object.values(node)) writeRelationshipsAsIds(value, refs) } -const buildBaseSchema = ({ - entity, - config, - ctx, - write = false, -}: { - entity: Entity - config: SanitizedConfig - ctx: BuildContext - write?: boolean -}): SchemaObject => { - const idType = ctx.defaultIDType - const schema = toComponentSchema({ config, source: stripInterfaceName(entity), idType, write }) - if (write) { - const refs = new Set((config.collections ?? []).map((c) => refTo(schemaName(c.slug)))) - writeRelationshipsAsIds(schema, refs) - } - const isAuth = 'auth' in entity && Boolean(entity.auth) - if (isAuth) removeProps(schema, READ_HIDDEN_FIELDS) - applyFieldOverrides({ schema, fields: entity.fields, ctx }) - addBlockDiscriminators(schema) - return schema -} +const deniedWrites = (fields: Field[], operation: 'create' | 'update'): string[] => + flattenFields(fields) + .filter((field) => 'name' in field && 'access' in field && isConstantDeny(field.access?.[operation])) + .map((field) => (field as { name: string }).name) const cloneSchema = (schema: SchemaObject): SchemaObject => ({ ...schema, @@ -229,21 +219,30 @@ export const buildEntitySchemas = async ({ config: SanitizedConfig ctx: BuildContext }): Promise => { - const read = buildBaseSchema({ entity, config, ctx }) - const write = buildBaseSchema({ entity, config, ctx, write: true }) + const source = stripInterfaceName(entity) + const defs = new Map() + const idType = ctx.defaultIDType + const read = toComponentSchema({ config, source, idType, defs }) + const write = toComponentSchema({ config, source, idType, defs, variant: 'input' }) const isAuth = 'auth' in entity && Boolean(entity.auth) - const create = cloneSchema(write) - if (Array.isArray(create.required)) { - const optional = new Set(SERVER_FIELDS) - create.required = create.required.filter((r: string) => !optional.has(r)) + for (const schema of [read, write]) { + if (isAuth) removeProps(schema, READ_HIDDEN_FIELDS) + applyFieldOverrides({ schema, fields: entity.fields, ctx }) + addBlockDiscriminators(schema) } + fixPolymorphicJoinRequired(read) + + const create = cloneSchema(write) if (isAuth && create.properties) { create.properties.password = { type: 'string' } create.required = [...new Set([...(create.required ?? []), 'password'])] } + removeProps(create, deniedWrites(entity.fields, 'create')) + const update = cloneSchema(write) + removeProps(update, deniedWrites(entity.fields, 'update')) delete update.required if (update.properties) delete update.properties.id if (isAuth && update.properties) { diff --git a/src/spec/fields.ts b/src/server/spec/fields.ts similarity index 100% rename from src/spec/fields.ts rename to src/server/spec/fields.ts diff --git a/src/spec/filters.ts b/src/server/spec/filters.ts similarity index 75% rename from src/spec/filters.ts rename to src/server/spec/filters.ts index e4eabd1..b3ee782 100644 --- a/src/spec/filters.ts +++ b/src/server/spec/filters.ts @@ -1,24 +1,28 @@ -import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload' import type { PathsObject } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload' + import type { EntityKind, EntityMatcher, HttpMethod, OperationContext, + OperationKind, OperationRule, ResolvedFilters, -} from '../types.js' +} from '@/shared/types/index.js' const SYSTEM_COLLECTIONS = new Set([ 'payload-jobs', 'payload-locked-documents', 'payload-preferences', 'payload-migrations', - 'payload-folders', 'payload-kv', + 'payload-llm-instructions', 'payload-query-presets', ]) +const SYSTEM_GLOBALS = new Set(['payload-jobs-stats']) + const HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete'] interface EntityRef { @@ -61,16 +65,19 @@ export const shouldIncludeCollection = (collection: SanitizedCollectionConfig, f return passesIncludeExclude({ slug, kind: 'collection' }, filters) } -export const shouldIncludeGlobal = (global: SanitizedGlobalConfig, filters: ResolvedFilters): boolean => - passesIncludeExclude({ slug: global.slug, kind: 'global' }, filters) +export const shouldIncludeGlobal = (global: SanitizedGlobalConfig, filters: ResolvedFilters): boolean => { + if (SYSTEM_GLOBALS.has(global.slug) && !filters.includeSystem) return false + return passesIncludeExclude({ slug: global.slug, kind: 'global' }, filters) +} const ruleMatchesMethod = (rule: OperationRule, method: HttpMethod): boolean => { if (rule.method === undefined) return true return Array.isArray(rule.method) ? rule.method.includes(method) : rule.method === method } -const ruleMatchesSlug = (rule: OperationRule, slug: string): boolean => { +const ruleMatchesSlug = (rule: OperationRule, slug: string | undefined): boolean => { if (rule.slug === undefined) return true + if (slug === undefined) return false return rule.slug instanceof RegExp ? rule.slug.test(slug) : rule.slug === slug } @@ -80,23 +87,25 @@ const ruleMatches = (rule: OperationRule, { method, slug, kind, path }: Operatio (rule.kind === undefined || rule.kind === kind) && (rule.path === undefined || rule.path.test(path)) -const isOperationExcluded = (filters: ResolvedFilters, op: OperationContext): boolean => { - if (filters.excludeOperations.some((rule) => ruleMatches(rule, op))) return true - return filters.excludeWhen?.(op) ?? false +const isOperationExcluded = async (filters: ResolvedFilters, op: OperationContext): Promise => { + for (const rule of filters.excludeOperations) { + if (typeof rule === 'function' ? (await rule(op)) === true : ruleMatches(rule, op)) return true + } + return false } -export const filterOperations = ({ +export const filterOperations = async ({ paths, slug, kind, filters, }: { paths: PathsObject - slug: string - kind: EntityKind + slug?: string + kind: OperationKind filters: ResolvedFilters -}): PathsObject => { - if (filters.excludeOperations.length === 0 && !filters.excludeWhen) return paths +}): Promise => { + if (filters.excludeOperations.length === 0) return paths for (const [path, item] of Object.entries(paths)) { if (!item) continue @@ -104,7 +113,7 @@ export const filterOperations = ({ const ops = item as Record for (const method of HTTP_METHODS) { if (!ops[method]) continue - if (isOperationExcluded(filters, { method, slug, kind, path })) { + if (await isOperationExcluded(filters, { method, path, slug, kind })) { delete ops[method] } else { remaining += 1 diff --git a/src/server/spec/hierarchy.ts b/src/server/spec/hierarchy.ts new file mode 100644 index 0000000..66e6fe6 --- /dev/null +++ b/src/server/spec/hierarchy.ts @@ -0,0 +1,50 @@ +import type { OperationObject, ParameterObject, PathsObject, SchemaObject } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig } from 'payload' + +import { createSchemaName, querySchemaName, updateSchemaName } from '@/server/spec/names.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' + +export const applyHierarchy = ({ + collection, + base, + paths, + schemas, + ctx, +}: { + collection: SanitizedCollectionConfig + base: string + paths: PathsObject + schemas: Record + ctx: BuildContext +}): void => { + const { hierarchy } = collection + if (!hierarchy) return + const t = makeT(ctx.i18n) + const pathFields = { + [hierarchy.slugPathFieldName]: t('schemaHierarchySlugPath'), + [hierarchy.titlePathFieldName]: t('schemaHierarchyTitlePath'), + } + + const read = schemas[base]?.properties + for (const [name, description] of Object.entries(pathFields)) { + if (read?.[name]) read[name] = { ...read[name], readOnly: true, description } + for (const other of [createSchemaName(base), updateSchemaName(base), querySchemaName(base)]) { + delete schemas[other]?.properties?.[name] + } + } + + const param: ParameterObject = { + name: 'computeHierarchyPaths', + in: 'query', + description: t('paramComputeHierarchyPaths', { + slugPath: hierarchy.slugPathFieldName, + titlePath: hierarchy.titlePathFieldName, + }), + schema: { type: 'boolean' }, + } + const route = `${ctx.apiRoute}/${collection.slug}` + for (const op of [paths[route]?.get, paths[`${route}/{id}`]?.get] as (OperationObject | undefined)[]) { + op?.parameters?.push(param) + } +} diff --git a/src/spec/names.ts b/src/server/spec/names.ts similarity index 67% rename from src/spec/names.ts rename to src/server/spec/names.ts index 634ea52..15f2c89 100644 --- a/src/spec/names.ts +++ b/src/server/spec/names.ts @@ -1,4 +1,4 @@ -import { upperFirst } from '../utils.js' +import { upperFirst } from '@/shared/utils.js' export const schemaName = (slug: string): string => upperFirst(slug.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())) @@ -24,25 +24,36 @@ type JsonValue = Record | unknown[] | string | number | boolean const REF = /^#\/(?:definitions|\$defs|components\/schemas)\/(.+)$/ +const findBlockSlug = (name: string, blockSlugs: Set): string | undefined => + [...blockSlugs].find((s) => s === name || schemaName(s) === schemaName(name)) + const normalizeRef = (ref: string, blockSlugs: Set): string => { const match = REF.exec(ref) if (!match) return ref const captured = match[1]! - const pascal = schemaName(captured) - const blockSlug = [...blockSlugs].find((s) => s === captured || schemaName(s) === pascal) + const blockSlug = findBlockSlug(captured, blockSlugs) if (blockSlug) return `#/components/schemas/${blockSchemaName(blockSlug)}` - return `#/components/schemas/${pascal}` + return `#/components/schemas/${schemaName(captured)}` } -export const rewriteRefs = (node: JsonValue, blockSlugs: Set = new Set()): JsonValue => { - if (Array.isArray(node)) return node.map((item) => rewriteRefs(item as JsonValue, blockSlugs)) +export const rewriteRefs = ( + node: JsonValue, + blockSlugs: Set = new Set(), + defs: Map = new Map(), +): JsonValue => { + if (Array.isArray(node)) return node.map((item) => rewriteRefs(item as JsonValue, blockSlugs, defs)) if (node && typeof node === 'object') { + const { $ref, ...rest } = node + const name = typeof $ref === 'string' ? REF.exec($ref)?.[1] : undefined + if (name && defs.has(name) && !findBlockSlug(name, blockSlugs)) { + return rewriteRefs({ ...(defs.get(name) as Record), ...rest }, blockSlugs, defs) + } const out: Record = {} for (const [key, value] of Object.entries(node)) { out[key] = key === '$ref' && typeof value === 'string' ? normalizeRef(value, blockSlugs) - : rewriteRefs(value as JsonValue, blockSlugs) + : rewriteRefs(value as JsonValue, blockSlugs, defs) } return out } diff --git a/src/spec/params.ts b/src/server/spec/params.ts similarity index 94% rename from src/spec/params.ts rename to src/server/spec/params.ts index 0177402..ddc952f 100644 --- a/src/spec/params.ts +++ b/src/server/spec/params.ts @@ -1,8 +1,9 @@ -import type { Field } from 'payload' import type { ParameterObject, ReferenceObject, SchemaObject } from '@scalar/openapi-types/3.2' +import type { Field } from 'payload' + +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext, Entity } from '@/shared/types/index.js' -import type { BuildContext, Entity } from '../types.js' -import { makeT } from '../translations/index.js' import { exposableFields } from './fields.js' import { joinsSchemaName, populateSchemaName, querySchemaName, refTo, selectSchemaName } from './names.js' @@ -286,6 +287,20 @@ export const writeParams = ({ WRITE_PARAMS[operation].has(p.name), ) +export const validateParams = (ctx: BuildContext): ParameterObject[] => + ctx.locales.length > 0 + ? [ + { + name: 'locale', + in: 'query', + description: makeT(ctx.i18n)('paramValidateLocale'), + style: 'form', + explode: true, + schema: { type: 'array', items: { type: 'string', enum: [...ctx.locales, 'all'] } }, + }, + ] + : [] + export const whereParam = (base: string): ParameterObject => objectParam('where', refTo(querySchemaName(base))) export const buildListParams = ({ diff --git a/src/spec/paths/auth.ts b/src/server/spec/paths/auth.ts similarity index 78% rename from src/spec/paths/auth.ts rename to src/server/spec/paths/auth.ts index 1ae9677..724926d 100644 --- a/src/spec/paths/auth.ts +++ b/src/server/spec/paths/auth.ts @@ -1,4 +1,3 @@ -import type { SanitizedCollectionConfig } from 'payload' import type { OperationObject, PathsObject, @@ -6,12 +5,23 @@ import type { ResponsesObject, SchemaObject, } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig } from 'payload' + +import { + ERRORS, + type ErrorCode, + SECURITY_SCHEME_NAME, + errorResponses, + jsonBody, + jsonResponse, + messageResponse, +} from '@/server/spec/components.js' +import { refTo, schemaName } from '@/server/spec/names.js' +import { authTagName } from '@/server/spec/tags.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' -import type { BuildContext } from '../../types.js' -import { makeT } from '../../translations/index.js' -import { ERRORS, type ErrorCode, errorResponses, jsonBody, jsonResponse, messageResponse } from '../components.js' -import { refTo, schemaName } from '../names.js' -import { authTagName } from '../tags.js' +const LOCAL_STRATEGY_ROUTES = ['login', 'forgot-password', 'reset-password', 'first-register', 'unlock', 'verify/{id}'] const identifierBody = ( auth: SanitizedCollectionConfig['auth'], @@ -49,7 +59,7 @@ export const buildAuthPaths = ({ nestedTags: boolean }): PathsObject => { const auth = collection.auth - if (!auth || auth.disableLocalStrategy) return {} + if (!auth) return {} const t = makeT(ctx.i18n) const name = schemaName(collection.slug) @@ -173,16 +183,6 @@ export const buildAuthPaths = ({ }, }), } - paths[`${base}/access`] = { - get: op(`access${name}`, ERRORS.session, { - responses: { - '200': jsonResponse(t('authAccess'), { - type: 'object', - additionalProperties: true, - }), - }, - }), - } } paths[`${base}/unlock`] = { @@ -201,5 +201,39 @@ export const buildAuthPaths = ({ } } + if (auth.disableLocalStrategy) { + for (const route of LOCAL_STRATEGY_ROUTES) delete paths[`${base}/${route}`] + } + + if (typeof auth.useAPIKey === 'object' && auth.useAPIKey.reveal === true) { + const errorBody = (description: string) => + jsonResponse(description, { type: 'object', properties: { error: { type: 'string' } } }) + paths[`${base}/{id}/api-key/reveal`] = { + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { type: ctx.defaultIDType === 'number' ? 'integer' : 'string' }, + }, + ], + post: { + tags: tag, + operationId: `revealApiKey${name}`, + responses: { + '200': jsonResponse(t('apiKeyReveal'), { + type: 'object', + properties: { apiKey: { type: 'string' } }, + required: ['apiKey'], + }), + '403': errorBody(t('error403')), + '404': errorBody(t('error404')), + ...errorResponses(['500'], t), + }, + security: [{ [SECURITY_SCHEME_NAME]: [] }], + }, + } + } + return paths } diff --git a/src/spec/paths/collections.ts b/src/server/spec/paths/collections.ts similarity index 71% rename from src/spec/paths/collections.ts rename to src/server/spec/paths/collections.ts index 175411c..48b1fbd 100644 --- a/src/spec/paths/collections.ts +++ b/src/server/spec/paths/collections.ts @@ -1,4 +1,3 @@ -import type { SanitizedCollectionConfig } from 'payload' import type { PathsObject, ReferenceObject, @@ -6,29 +5,41 @@ import type { ResponsesObject, SchemaObject, } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig } from 'payload' -import type { BuildContext } from '../../types.js' -import { makeT } from '../../translations/index.js' -import { ERRORS, errorResponses, jsonOk, jsonResponse, uploadRequestBody } from '../components.js' -import { createSchemaName, listSchemaName, refTo, schemaName, updateSchemaName } from '../names.js' +import { + ERRORS, + docAccessOperation, + errorResponses, + jsonOk, + jsonResponse, + uploadRequestBody, + validateOperation, +} from '@/server/spec/components.js' +import { createSchemaName, listSchemaName, refTo, schemaName, updateSchemaName } from '@/server/spec/names.js' import { buildListParams, buildParamSchemas, commonReadParams, type ReadParamRefs, + validateParams, writeParams, type WriteOperation, -} from '../params.js' -import type { EntitySecurity } from '../security.js' +} from '@/server/spec/params.js' +import type { EntitySecurity } from '@/server/spec/security.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' export const buildCollectionPaths = ({ collection, ctx, security = {}, + access = false, }: { collection: SanitizedCollectionConfig ctx: BuildContext security?: EntitySecurity + access?: boolean }): PathsObject => { const t = makeT(ctx.i18n) const name = schemaName(collection.slug) @@ -38,7 +49,7 @@ export const buildCollectionPaths = ({ const createRef: ReferenceObject = { $ref: refTo(createSchemaName(name)) } const updateRef: ReferenceObject = { $ref: refTo(updateSchemaName(name)) } const idType = ctx.defaultIDType === 'number' ? 'integer' : 'string' - const { read: secRead, create: secCreate, update: secUpdate, delete: secDelete } = security + const { read: secRead, create: secCreate, update: secUpdate, delete: secDelete, validate: secValidate } = security const allowBulk = collection.disableBulkEdit !== true const allowDuplicate = collection.disableDuplicate !== true @@ -166,6 +177,17 @@ export const buildCollectionPaths = ({ security: secRead, }, }, + [`${base}/validate`]: { + post: validateOperation({ + tag: name, + operationId: `validate${name}`, + parameters: validateParams(ctx), + requestBody: { ...requestBody(createRef, false), required: true }, + errors: ERRORS.create, + security: secValidate, + t, + }), + }, [`${base}/{id}`]: { parameters: [{ name: 'id', in: 'path', required: true, schema: { type: idType } }], get: { @@ -193,6 +215,19 @@ export const buildCollectionPaths = ({ }, } + paths[`${base}/{id}/validate`] = { + parameters: [{ name: 'id', in: 'path', required: true, schema: { type: idType } }], + post: validateOperation({ + tag: name, + operationId: `validate${name}ById`, + parameters: validateParams(ctx), + requestBody: requestBody(updateRef, false), + errors: ERRORS.update, + security: secValidate, + t, + }), + } + if (allowDuplicate) { paths[`${base}/{id}/duplicate`] = { parameters: [{ name: 'id', in: 'path', required: true, schema: { type: idType } }], @@ -206,5 +241,42 @@ export const buildCollectionPaths = ({ } } + if (access) { + const auth = collection.auth + const operations = [ + 'create', + 'read', + 'update', + 'delete', + 'validate', + ...(auth && auth.maxLoginAttempts ? ['unlock'] : []), + ...(collection.versions ? ['readVersions'] : []), + ] + const accessOp = (operationId: string, errors: typeof ERRORS.read) => + docAccessOperation({ tag: name, operationId, operations, bodyRef: updateRef, errors, t }) + paths[`${base}/access`] = { post: accessOp(`access${name}`, ['500']) } + paths[`${base}/access/{id}`] = { + parameters: [{ name: 'id', in: 'path', required: true, schema: { type: idType } }], + post: accessOp(`access${name}ById`, ['404', '500']), + } + } + + if (isUpload) { + const file = { content: { '*/*': { schema: { type: 'string', format: 'binary' } } } } as const + paths[`${base}/file/{filename}`] = { + parameters: [{ name: 'filename', in: 'path', required: true, schema: { type: 'string' } }], + get: { + tags: [name], + operationId: `get${name}File`, + responses: { + '200': { description: t('fileServe'), ...file }, + '206': { description: t('filePartial'), ...file }, + ...errorResponses(['400', '403', '404', '500'], t), + }, + security: secRead, + }, + } + } + return paths } diff --git a/src/spec/paths/custom.ts b/src/server/spec/paths/custom.ts similarity index 89% rename from src/spec/paths/custom.ts rename to src/server/spec/paths/custom.ts index 389433a..2bee942 100644 --- a/src/spec/paths/custom.ts +++ b/src/server/spec/paths/custom.ts @@ -1,6 +1,7 @@ -import type { Endpoint, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload' import type { OperationObject, PathsObject } from '@scalar/openapi-types/3.2' -import type { BuildContext } from '../../types.js' +import type { Endpoint, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload' + +import type { BuildContext } from '@/shared/types/index.js' const getOpenapiMeta = (endpoint: Endpoint): OperationObject | undefined => { const meta = endpoint.custom?.openapi @@ -27,7 +28,7 @@ const collect = ({ if (!meta) continue const path = normalizePath(prefix, endpoint.path) const method = endpoint.method.toLowerCase() - out[path] = { ...out[path], [method]: meta } + out[path] = { ...out[path], [method]: { ...meta } } } } @@ -43,7 +44,7 @@ export const buildCustomEndpointPaths = ({ ctx: BuildContext }): PathsObject => { const out: PathsObject = {} - collect({ endpoints: config.endpoints, prefix: '', out }) + collect({ endpoints: config.endpoints, prefix: ctx.apiRoute, out }) for (const collection of collections) { collect({ endpoints: collection.endpoints, prefix: `${ctx.apiRoute}/${collection.slug}`, out }) } diff --git a/src/spec/paths/globals.ts b/src/server/spec/paths/globals.ts similarity index 53% rename from src/spec/paths/globals.ts rename to src/server/spec/paths/globals.ts index affd967..efcc95c 100644 --- a/src/spec/paths/globals.ts +++ b/src/server/spec/paths/globals.ts @@ -1,27 +1,35 @@ -import type { SanitizedGlobalConfig } from 'payload' import type { PathsObject, ReferenceObject } from '@scalar/openapi-types/3.2' +import type { SanitizedGlobalConfig } from 'payload' -import type { BuildContext } from '../../types.js' -import { makeT } from '../../translations/index.js' -import { ERRORS, errorResponses, jsonOk } from '../components.js' -import { globalSchemaName, refTo, updateSchemaName } from '../names.js' -import { buildParamSchemas, commonReadParams, type ReadParamRefs, writeParams } from '../params.js' -import type { EntitySecurity } from '../security.js' +import { ERRORS, docAccessOperation, errorResponses, jsonOk, validateOperation } from '@/server/spec/components.js' +import { globalSchemaName, refTo, updateSchemaName } from '@/server/spec/names.js' +import { + buildParamSchemas, + commonReadParams, + type ReadParamRefs, + validateParams, + writeParams, +} from '@/server/spec/params.js' +import type { EntitySecurity } from '@/server/spec/security.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' export const buildGlobalPaths = ({ global, ctx, security = {}, + access = false, }: { global: SanitizedGlobalConfig ctx: BuildContext security?: EntitySecurity + access?: boolean }): PathsObject => { const t = makeT(ctx.i18n) const name = globalSchemaName(global.slug) const docRef: ReferenceObject = { $ref: refTo(name) } const updateRef: ReferenceObject = { $ref: refTo(updateSchemaName(name)) } - const { read: secRead, update: secUpdate } = security + const { read: secRead, update: secUpdate, validate: secValidate } = security const paramSchemas = buildParamSchemas({ fields: global.fields, ctx }) const refs: ReadParamRefs = { @@ -30,7 +38,7 @@ export const buildGlobalPaths = ({ joins: Boolean(paramSchemas.joins), } - return { + const paths: PathsObject = { [`${ctx.apiRoute}/globals/${global.slug}`]: { get: { tags: [name], @@ -54,5 +62,31 @@ export const buildGlobalPaths = ({ security: secUpdate, }, }, + [`${ctx.apiRoute}/globals/${global.slug}/validate`]: { + post: validateOperation({ + tag: name, + operationId: `validate${name}`, + parameters: validateParams(ctx), + requestBody: { content: { 'application/json': { schema: updateRef } } }, + errors: ERRORS.globalUpdate, + security: secValidate, + t, + }), + }, } + + if (access) { + paths[`${ctx.apiRoute}/globals/${global.slug}/access`] = { + post: docAccessOperation({ + tag: name, + operationId: `access${name}`, + operations: ['read', 'update', 'validate', ...(global.versions ? ['readVersions'] : [])], + bodyRef: updateRef, + errors: ['500'], + t, + }), + } + } + + return paths } diff --git a/src/spec/paths/jobs.ts b/src/server/spec/paths/jobs.ts similarity index 86% rename from src/spec/paths/jobs.ts rename to src/server/spec/paths/jobs.ts index 8bbed9f..7760b05 100644 --- a/src/spec/paths/jobs.ts +++ b/src/server/spec/paths/jobs.ts @@ -1,9 +1,9 @@ +import type { ParameterObject, PathsObject, SecurityRequirementObject } from '@scalar/openapi-types/3.2' import type { SanitizedConfig } from 'payload' -import type { ParameterObject, PathsObject } from '@scalar/openapi-types/3.2' -import type { BuildContext } from '../../types.js' -import { makeT } from '../../translations/index.js' -import { errorResponses, jsonResponse } from '../components.js' +import { errorResponses, jsonResponse } from '@/server/spec/components.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' const boolParam = (name: string, description: string): ParameterObject => ({ name, @@ -48,7 +48,15 @@ const queueParam = (queues: string[], ctx: BuildContext): ParameterObject => ({ schema: { type: 'string', enum: queues }, }) -export const buildJobsPaths = ({ config, ctx }: { config: SanitizedConfig; ctx: BuildContext }): PathsObject => { +export const buildJobsPaths = ({ + config, + ctx, + security, +}: { + config: SanitizedConfig + ctx: BuildContext + security?: SecurityRequirementObject[] +}): PathsObject => { const jobs = config.jobs as Record | undefined const hasJobs = Boolean((jobs?.tasks as unknown[])?.length || (jobs?.workflows as unknown[])?.length) if (!hasJobs) return {} @@ -81,6 +89,7 @@ export const buildJobsPaths = ({ config, ctx }: { config: SanitizedConfig; ctx: }), ...errors, }, + security, }, }, [`${ctx.apiRoute}/payload-jobs/handle-schedules`]: { @@ -101,6 +110,7 @@ export const buildJobsPaths = ({ config, ctx }: { config: SanitizedConfig; ctx: }), ...errors, }, + security, }, }, } diff --git a/src/server/spec/paths/system.ts b/src/server/spec/paths/system.ts new file mode 100644 index 0000000..8364549 --- /dev/null +++ b/src/server/spec/paths/system.ts @@ -0,0 +1,113 @@ +import type { PathsObject, ResponsesObject } from '@scalar/openapi-types/3.2' + +import { SECURITY_SCHEME_NAME, errorResponses, jsonBody, jsonResponse } from '@/server/spec/components.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext } from '@/shared/types/index.js' + +export const buildSystemPaths = ({ + ctx, + access, + uploadSlugs, +}: { + ctx: BuildContext + access: boolean + uploadSlugs: string[] +}): PathsObject => { + const t = makeT(ctx.i18n) + const paths: PathsObject = {} + + if (access) { + paths[`${ctx.apiRoute}/access`] = { + get: { + tags: ['Access'], + operationId: 'access', + responses: { + '200': jsonResponse(t('authAccess'), { type: 'object', additionalProperties: true }), + ...errorResponses(['500'], t), + }, + }, + } + } + + if (uploadSlugs.length > 0) { + const staged: ResponsesObject = { + '204': { description: t('uploadStageResult') }, + ...errorResponses(['400', '500'], t), + } + + paths[`${ctx.apiRoute}/upload-instructions`] = { + post: { + tags: ['Uploads'], + operationId: 'getUploadInstructions', + summary: t('uploadInstructionsSummary'), + requestBody: jsonBody({ + type: 'object', + additionalProperties: false, + properties: { + collectionSlug: { type: 'string', enum: uploadSlugs }, + docPrefix: { type: 'string' }, + filename: { type: 'string' }, + filesize: { type: 'integer', minimum: 0 }, + mimeType: { type: 'string' }, + }, + required: ['collectionSlug', 'filename', 'filesize', 'mimeType'], + }), + responses: { + '200': jsonResponse(t('uploadInstructionsResult'), { + type: 'object', + properties: { + type: { type: 'string', enum: ['http', 'dispatch'] }, + file: { + type: 'object', + properties: { + filename: { type: 'string' }, + mimeType: { type: 'string' }, + size: { type: 'integer' }, + uploadReference: { type: 'object', additionalProperties: true }, + }, + required: ['filename', 'mimeType', 'size', 'uploadReference'], + }, + request: { + type: 'object', + properties: { + method: { type: 'string', enum: ['POST', 'PUT'] }, + url: { type: 'string' }, + headers: { type: 'object', additionalProperties: { type: 'string' } }, + }, + required: ['method', 'url'], + }, + name: { type: 'string' }, + data: {}, + }, + required: ['type', 'file'], + }), + ...errorResponses(['400', '403', '500'], t), + }, + security: [{ [SECURITY_SCHEME_NAME]: [] }], + }, + } + paths[`${ctx.apiRoute}/upload-instructions/{uploadId}`] = { + parameters: [{ name: 'uploadId', in: 'path', required: true, schema: { type: 'string' } }], + put: { + tags: ['Uploads'], + operationId: 'uploadStagedFile', + summary: t('uploadStagePutSummary'), + requestBody: { + required: true, + content: { '*/*': { schema: { type: 'string', format: 'binary' } } }, + }, + responses: staged, + security: [{ [SECURITY_SCHEME_NAME]: [] }], + }, + delete: { + tags: ['Uploads'], + operationId: 'deleteStagedFile', + summary: t('uploadStageDeleteSummary'), + responses: staged, + security: [{ [SECURITY_SCHEME_NAME]: [] }], + }, + } + } + + return paths +} diff --git a/src/spec/paths/versions.ts b/src/server/spec/paths/versions.ts similarity index 90% rename from src/spec/paths/versions.ts rename to src/server/spec/paths/versions.ts index 3d80a1e..cb897b3 100644 --- a/src/spec/paths/versions.ts +++ b/src/server/spec/paths/versions.ts @@ -1,11 +1,12 @@ import type { ParameterObject, PathsObject, ReferenceObject, SchemaObject } from '@scalar/openapi-types/3.2' -import type { BuildContext, Entity, IDType } from '../../types.js' -import { makeT } from '../../translations/index.js' -import { ERRORS, errorResponses, jsonResponse, nullableType } from '../components.js' -import { refTo, schemaName } from '../names.js' -import { buildParamSchemas, commonReadParams, type ReadParamRefs } from '../params.js' -import { versionsTagName } from '../tags.js' +import { ERRORS, errorResponses, jsonResponse, nullableType } from '@/server/spec/components.js' +import { refTo, schemaName } from '@/server/spec/names.js' +import { buildParamSchemas, commonReadParams, type ReadParamRefs } from '@/server/spec/params.js' +import type { EntitySecurity } from '@/server/spec/security.js' +import { versionsTagName } from '@/server/spec/tags.js' +import { makeT } from '@/shared/translations/index.js' +import type { BuildContext, Entity, IDType } from '@/shared/types/index.js' const buildVersionSchema = (base: string, idType: IDType): SchemaObject => { const id: SchemaObject = { type: idType === 'number' ? 'integer' : 'string' } @@ -105,6 +106,7 @@ export const buildVersionPaths = ({ ctx, schemaBase, nestedTags, + security = {}, global = false, }: { entity: Entity @@ -112,6 +114,7 @@ export const buildVersionPaths = ({ ctx: BuildContext schemaBase?: string nestedTags: boolean + security?: EntitySecurity global?: boolean }): PathsObject => { if (!entity.versions) return {} @@ -147,6 +150,7 @@ export const buildVersionPaths = ({ '200': jsonResponse(t('versionList'), versionListRef(name)), ...errorResponses(ERRORS.versionList, t), }, + security: security.readVersions, }, }, [`${pathBase}/versions/{id}`]: { @@ -159,6 +163,7 @@ export const buildVersionPaths = ({ '200': jsonResponse(t('versionSingle'), versionRef(name)), ...errorResponses(ERRORS.versionRead, t), }, + security: security.readVersions, }, post: { tags: versionsTag, @@ -171,6 +176,7 @@ export const buildVersionPaths = ({ '200': jsonResponse(t('versionRestored'), restoreResponse), ...errorResponses(ERRORS.versionRestore, t), }, + security: security.update, }, }, } diff --git a/src/server/spec/security.ts b/src/server/spec/security.ts new file mode 100644 index 0000000..cc21b58 --- /dev/null +++ b/src/server/spec/security.ts @@ -0,0 +1,155 @@ +import type { PathsObject, SecurityRequirementObject } from '@scalar/openapi-types/3.2' +import type { Access, SanitizedConfig } from 'payload' + +import { PLUGIN_NAME } from '@/shared/constants.js' +import type { + Entity, + EntityOperation, + EntitySecurityOverride, + HttpMethod, + OperationKind, + SecurityOption, +} from '@/shared/types/index.js' +import { isPlainObject } from '@/shared/utils.js' + +import { INTERACTIVE_SCHEME_NAME, SECURITY_SCHEME_NAME } from './components.js' + +const HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete'] + +export const securedRequirement = (login = false): SecurityRequirementObject[] => + login ? [{ [SECURITY_SCHEME_NAME]: [] }, { [INTERACTIVE_SCHEME_NAME]: [] }] : [{ [SECURITY_SCHEME_NAME]: [] }] + +type ProbedOperation = EntityOperation | 'readVersions' + +const OVERRIDE_FALLBACK: Partial> = { + readVersions: 'read', + validate: 'update', +} + +export type EntitySecurity = Partial> + +const withTimeout = async (promise: Promise, timeoutMs: number): Promise => { + let timer: ReturnType | undefined + const guard = new Promise((resolve) => { + timer = setTimeout(() => resolve(false), timeoutMs) + }) + try { + return await Promise.race([ + promise.then( + (value) => value === true, + () => false, + ), + guard, + ]) + } finally { + if (timer) clearTimeout(timer) + } +} + +export const evaluateAccess = async ( + access: Access | undefined, + opts: { locale?: string; timeoutMs?: number; baseAccess?: SanitizedConfig['baseAccess'] } = {}, +): Promise => { + if (!access) return false + const { locale, timeoutMs = 250, baseAccess } = opts + const trap = (allowed: PropertyKey, value: unknown): T => + new Proxy({} as T, { + get(_target, key) { + if (key === allowed) return value + throw new Error('req.payload accessed during build') + }, + }) + const payloadTrap = trap('config', trap('baseAccess', baseAccess)) + const req = { user: null, headers: new Headers(), context: {}, locale, payload: payloadTrap } + try { + const result = access({ req } as never) + if (result && typeof (result as Promise).then === 'function') { + return await withTimeout(result as Promise, timeoutMs) + } + return result === true + } catch { + return false + } +} + +export const readSecurityOverride = (entity: { + slug: string + custom?: { openapi?: { security?: unknown } } +}): EntitySecurityOverride | undefined => { + const meta = entity.custom?.openapi?.security + const values = isPlainObject(meta) ? Object.values(meta) : [meta] + if (values.some((value) => typeof value === 'boolean')) { + throw new Error( + `[${PLUGIN_NAME}] custom.openapi.security true/false on "${entity.slug}" was removed, use 'public' or 'secured'`, + ) + } + return meta === undefined ? undefined : (meta as EntitySecurityOverride) +} + +const overrideFor = (override: EntitySecurityOverride | undefined, op: ProbedOperation): boolean | undefined => { + const fallback = OVERRIDE_FALLBACK[op] + const marking = + typeof override === 'string' + ? override + : (override?.[op as EntityOperation] ?? (fallback ? override?.[fallback] : undefined)) + return marking === undefined ? undefined : marking === 'public' +} + +export const resolveEntitySecurity = async ({ + entity, + operations, + locale, + baseAccess, +}: { + entity: Entity + operations: ProbedOperation[] + locale?: string + baseAccess?: SanitizedConfig['baseAccess'] +}): Promise => { + const override = readSecurityOverride(entity) + const access = entity.access as Partial> | undefined + const out: EntitySecurity = {} + for (const op of operations) { + const declared = overrideFor(override, op) + const isPublic = declared ?? (await evaluateAccess(access?.[op], { locale, baseAccess })) + out[op] = isPublic ? undefined : securedRequirement() + } + return out +} + +const withLogin = (security: SecurityRequirementObject[]): SecurityRequirementObject[] => + security.some((r) => SECURITY_SCHEME_NAME in r) && !security.some((r) => INTERACTIVE_SCHEME_NAME in r) + ? [...security, { [INTERACTIVE_SCHEME_NAME]: [] }] + : security + +export const applySecurity = async ({ + paths, + slug, + kind, + security, + login = false, +}: { + paths: PathsObject + slug?: string + kind: OperationKind + security?: SecurityOption + login?: boolean +}): Promise => { + if (!security && !login) return paths + for (const [path, item] of Object.entries(paths)) { + if (!item) continue + const ops = item as Record + for (const method of HTTP_METHODS) { + const op = ops[method] + if (!op) continue + if (login && op.security) op.security = withLogin(op.security) + if (!security) continue + const detected = op.security?.length ? 'secured' : 'public' + const decision = await security({ method, path, slug, kind, detected }) + if (decision === 'public') delete op.security + else if (decision === 'secured') op.security = securedRequirement(login) + else if (Array.isArray(decision)) op.security = decision + } + } + return paths +} diff --git a/src/spec/tags.ts b/src/server/spec/tags.ts similarity index 79% rename from src/spec/tags.ts rename to src/server/spec/tags.ts index 4dd2358..e24f60b 100644 --- a/src/spec/tags.ts +++ b/src/server/spec/tags.ts @@ -1,5 +1,6 @@ import type { TagObject } from '@scalar/openapi-types/3.2' -import type { Translate } from '../translations/types.js' + +import type { Translate } from '@/shared/translations/types.js' export const NAV_COLLECTIONS = 'Collections' export const NAV_GLOBALS = 'Globals' @@ -9,6 +10,10 @@ export const entityTagName = (base: string): string => base export const authTagName = (base: string): string => `${base} Auth` export const versionsTagName = (base: string): string => `${base} Versions` +const SYSTEM_TAG_SUMMARIES = { Jobs: 'tagJobs', Uploads: 'tagUploads', Access: 'tagAccess' } as const + +export type SystemTag = keyof typeof SYSTEM_TAG_SUMMARIES + export interface CollectionTagInfo { base: string hasAuth: boolean @@ -25,12 +30,12 @@ export interface GlobalTagInfo { export interface TagHierarchyInput { collections: CollectionTagInfo[] globals: GlobalTagInfo[] - hasJobs: boolean + systemTags: SystemTag[] t: Translate nested: boolean } -export const buildTagHierarchy = ({ collections, globals, hasJobs, t, nested }: TagHierarchyInput): TagObject[] => { +export const buildTagHierarchy = ({ collections, globals, systemTags, t, nested }: TagHierarchyInput): TagObject[] => { if (!nested) { return [ ...collections.map((c) => ({ name: entityTagName(c.base), description: c.description })), @@ -62,9 +67,9 @@ export const buildTagHierarchy = ({ collections, globals, hasJobs, t, nested }: } } - if (hasJobs) { + if (systemTags.length > 0) { tags.push({ name: NAV_SYSTEM, summary: t('tagSystem'), description: t('tagSystemDesc'), kind: 'nav' }) - tags.push({ name: 'Jobs', parent: NAV_SYSTEM, summary: t('tagJobs') }) + for (const name of systemTags) tags.push({ name, parent: NAV_SYSTEM, summary: t(SYSTEM_TAG_SUMMARIES[name]) }) } return tags diff --git a/src/server/telemetry.ts b/src/server/telemetry.ts new file mode 100644 index 0000000..e8fde2c --- /dev/null +++ b/src/server/telemetry.ts @@ -0,0 +1,70 @@ +import { reportTelemetry } from '@seshuk/payload-plugin-tooling/telemetry' +import type { Payload, PluginsMap } from 'payload' + +import { PLUGIN_NAME, SCALAR_PLUGIN_NAME, SWAGGER_UI_PLUGIN_NAME } from '@/shared/constants.js' +import type { OpenApiPluginOptions, ResolvedOptions } from '@/shared/types/index.js' + +export type TelemetryFeatures = { + access: boolean + cache: boolean + extensions: boolean + extensionTransform: boolean + filtersEntities: boolean + filtersExcludeOperations: boolean + filtersIncludeSystem: boolean + interactiveAuth: boolean + nestedTags: boolean + openapiVersion30: boolean + openapiVersion31: boolean + security: boolean + serve: boolean + servers: boolean + trustedHosts: boolean + uiScalar: boolean + uiSwagger: boolean +} + +export const buildFeatures = ({ + plugins, + resolved, +}: { + plugins: PluginsMap + resolved: ResolvedOptions +}): TelemetryFeatures => ({ + access: Boolean(resolved.access), + cache: resolved.cache, + extensions: resolved.extensions.length > 0, + extensionTransform: resolved.extensions.some((extension) => Boolean(extension.transform)), + filtersEntities: resolved.filters.include.length > 0 || resolved.filters.exclude.length > 0, + filtersExcludeOperations: resolved.filters.excludeOperations.length > 0, + filtersIncludeSystem: resolved.filters.includeSystem, + interactiveAuth: resolved.interactiveAuth.enabled, + nestedTags: resolved.nestedTags, + openapiVersion30: resolved.openapiVersion === '3.0', + openapiVersion31: resolved.openapiVersion === '3.1', + security: Boolean(resolved.security), + serve: resolved.serve, + servers: Boolean(resolved.servers), + trustedHosts: resolved.trustedHosts.length > 0, + uiScalar: Boolean(plugins[SCALAR_PLUGIN_NAME]), + uiSwagger: Boolean(plugins[SWAGGER_UI_PLUGIN_NAME]), +}) + +export const reportOpenApiTelemetry = ({ + features, + options, + payload, +}: { + features: TelemetryFeatures + options: OpenApiPluginOptions + payload: Payload +}): Promise => + reportTelemetry({ + disableEnv: 'OPENAPI_TELEMETRY_DISABLED', + docsUrl: 'https://payload-plugin-openapi.seshuk.im/v1/configuration/telemetry', + features, + option: options.telemetry, + packageName: PLUGIN_NAME, + payload, + product: 'payload-plugin-openapi', + }) diff --git a/src/server/ui/createUiPlugin.ts b/src/server/ui/createUiPlugin.ts new file mode 100644 index 0000000..3aec3e2 --- /dev/null +++ b/src/server/ui/createUiPlugin.ts @@ -0,0 +1,53 @@ +import { definePlugin, Forbidden } from 'payload' + +import { assertNoRemovedKeys } from '@/server/options/resolveOptions.js' +import { PLUGIN_NAME } from '@/shared/constants.js' +import type { ResolvedOptions, UiPluginOptions } from '@/shared/types/index.js' + +export interface UiRenderArgs { + specUrl: string + cdnBase: string + pinned: boolean + title: string + configuration: Record +} + +export interface UiPluginSpec { + slug: string + defaultDocsUrl: string + defaultCdnBase: string + render: (args: UiRenderArgs) => string +} + +export const createUiPlugin = (spec: UiPluginSpec) => + definePlugin({ + slug: spec.slug, + plugin: ({ config, options = {} }) => { + assertNoRemovedKeys([[(options as Record).specEndpoint, 'specEndpoint was renamed to specURL']]) + if (options.enabled === false) return config + + return { + ...config, + endpoints: [ + ...(config.endpoints ?? []), + { + path: options.path ?? spec.defaultDocsUrl, + method: 'get', + handler: async (req) => { + if (options.access && (await options.access({ req })) !== true) throw new Forbidden(req.t) + const { custom, routes } = req.payload.config + const resolved = custom?.[PLUGIN_NAME] as ResolvedOptions | undefined + const html = spec.render({ + specUrl: options.specURL ?? `${routes?.api ?? '/api'}${resolved?.path ?? '/openapi.json'}`, + cdnBase: options.cdnBase ?? spec.defaultCdnBase, + pinned: (options.cdnBase ?? spec.defaultCdnBase) === spec.defaultCdnBase, + title: resolved?.info.title ?? 'API Reference', + configuration: options.configuration ?? {}, + }) + return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } }) + }, + }, + ], + } + }, + }) diff --git a/src/ui/html.ts b/src/server/ui/html.ts similarity index 85% rename from src/ui/html.ts rename to src/server/ui/html.ts index 194aa14..a6539d1 100644 --- a/src/ui/html.ts +++ b/src/server/ui/html.ts @@ -12,3 +12,6 @@ export const specUrlExpr = (specUrl: string): string => { const u = JSON.stringify(specUrl) return `(function(){var l=new URLSearchParams(location.search).get('lang');return ${u}+(l?((${u}.indexOf('?')>-1?'&':'?')+'lang='+encodeURIComponent(l)):'')})()` } + +export const integrityAttrs = (pinned: boolean, integrity: string): string => + pinned ? ` integrity="${integrity}" crossorigin="anonymous"` : '' diff --git a/src/server/ui/scalar.ts b/src/server/ui/scalar.ts new file mode 100644 index 0000000..52ed1a9 --- /dev/null +++ b/src/server/ui/scalar.ts @@ -0,0 +1,22 @@ +import { SCALAR_PLUGIN_NAME } from '@/shared/constants.js' + +import { createUiPlugin } from './createUiPlugin.js' +import { escapeHtml, htmlPage, integrityAttrs, jsonForScript, specUrlExpr } from './html.js' + +const SCRIPT_INTEGRITY = 'sha384-omTRdD9MbjA1vm12DqRUVvqJlr3VzSixvAdF1Jruu9AJOiJKyTKraIB6DyX+m10M' + +export const scalar = createUiPlugin({ + slug: SCALAR_PLUGIN_NAME, + defaultDocsUrl: '/docs', + defaultCdnBase: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.4/dist/browser/standalone.js', + render: ({ specUrl, cdnBase, pinned, title, configuration }) => + htmlPage({ + title, + head: '', + body: + `
` + + `` + + ``, + }), +}) diff --git a/src/server/ui/swagger.ts b/src/server/ui/swagger.ts new file mode 100644 index 0000000..2189d9d --- /dev/null +++ b/src/server/ui/swagger.ts @@ -0,0 +1,23 @@ +import { SWAGGER_UI_PLUGIN_NAME } from '@/shared/constants.js' + +import { createUiPlugin } from './createUiPlugin.js' +import { escapeHtml, htmlPage, integrityAttrs, jsonForScript, specUrlExpr } from './html.js' + +const STYLE_INTEGRITY = 'sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW' +const SCRIPT_INTEGRITY = 'sha384-ZPehFMQommnnuaZ4rpxgkgTT2DKFVp4hZC/7pLit+9Lek9T1YGSo23eHFbvNkXkw' + +export const swaggerUi = createUiPlugin({ + slug: SWAGGER_UI_PLUGIN_NAME, + defaultDocsUrl: '/docs', + defaultCdnBase: 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1', + render: ({ specUrl, cdnBase, pinned, title, configuration }) => + htmlPage({ + title, + head: ``, + body: + `
` + + `` + + ``, + }), +}) diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..021aeb2 --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,3 @@ +export const PLUGIN_NAME = '@seshuk/payload-plugin-openapi' +export const SCALAR_PLUGIN_NAME = `${PLUGIN_NAME}/scalar` +export const SWAGGER_UI_PLUGIN_NAME = `${PLUGIN_NAME}/swagger-ui` diff --git a/src/shared/translations/index.ts b/src/shared/translations/index.ts new file mode 100644 index 0000000..b4f5a2b --- /dev/null +++ b/src/shared/translations/index.ts @@ -0,0 +1,103 @@ +import type { AcceptedLanguages, I18n } from '@payloadcms/translations' + +import { PLUGIN_NAME } from '@/shared/constants.js' +import { ar } from '@/shared/translations/locales/ar.js' +import { az } from '@/shared/translations/locales/az.js' +import { bg } from '@/shared/translations/locales/bg.js' +import { bnBd } from '@/shared/translations/locales/bnBd.js' +import { bnIn } from '@/shared/translations/locales/bnIn.js' +import { ca } from '@/shared/translations/locales/ca.js' +import { cs } from '@/shared/translations/locales/cs.js' +import { da } from '@/shared/translations/locales/da.js' +import { de } from '@/shared/translations/locales/de.js' +import { en } from '@/shared/translations/locales/en.js' +import { es } from '@/shared/translations/locales/es.js' +import { et } from '@/shared/translations/locales/et.js' +import { fa } from '@/shared/translations/locales/fa.js' +import { fr } from '@/shared/translations/locales/fr.js' +import { he } from '@/shared/translations/locales/he.js' +import { hr } from '@/shared/translations/locales/hr.js' +import { hu } from '@/shared/translations/locales/hu.js' +import { hy } from '@/shared/translations/locales/hy.js' +import { id } from '@/shared/translations/locales/id.js' +import { is } from '@/shared/translations/locales/is.js' +import { it } from '@/shared/translations/locales/it.js' +import { ja } from '@/shared/translations/locales/ja.js' +import { ko } from '@/shared/translations/locales/ko.js' +import { lt } from '@/shared/translations/locales/lt.js' +import { lv } from '@/shared/translations/locales/lv.js' +import { my } from '@/shared/translations/locales/my.js' +import { nb } from '@/shared/translations/locales/nb.js' +import { nl } from '@/shared/translations/locales/nl.js' +import { pl } from '@/shared/translations/locales/pl.js' +import { pt } from '@/shared/translations/locales/pt.js' +import { ro } from '@/shared/translations/locales/ro.js' +import { rs } from '@/shared/translations/locales/rs.js' +import { rsLatin } from '@/shared/translations/locales/rsLatin.js' +import { ru } from '@/shared/translations/locales/ru.js' +import { sk } from '@/shared/translations/locales/sk.js' +import { sl } from '@/shared/translations/locales/sl.js' +import { sv } from '@/shared/translations/locales/sv.js' +import { ta } from '@/shared/translations/locales/ta.js' +import { th } from '@/shared/translations/locales/th.js' +import { tr } from '@/shared/translations/locales/tr.js' +import { uk } from '@/shared/translations/locales/uk.js' +import { vi } from '@/shared/translations/locales/vi.js' +import { zh } from '@/shared/translations/locales/zh.js' +import { zhTw } from '@/shared/translations/locales/zhTw.js' + +import type { PluginDefaultTranslationsObject, Translate } from './types.js' + +export const makeT = (i18n: Pick): Translate => { + const t = i18n.t as ((key: string, vars?: Record) => string) | undefined + return (key, vars) => (t ? t(`${PLUGIN_NAME}:${key}`, vars) : key) +} + +export const translations: { + [key in AcceptedLanguages]?: PluginDefaultTranslationsObject +} = { + ar, + az, + bg, + 'bn-BD': bnBd, + 'bn-IN': bnIn, + ca, + cs, + da, + de, + en, + es, + et, + fa, + fr, + he, + hr, + hu, + hy, + id, + is, + it, + ja, + ko, + lt, + lv, + my, + nb, + nl, + pl, + pt, + ro, + rs, + 'rs-latin': rsLatin, + ru, + sk, + sl, + sv, + ta, + th, + tr, + uk, + vi, + zh, + 'zh-TW': zhTw, +} diff --git a/src/translations/locales/ar.ts b/src/shared/translations/locales/ar.ts similarity index 75% rename from src/translations/locales/ar.ts rename to src/shared/translations/locales/ar.ts index 42fabda..61771f2 100644 --- a/src/translations/locales/ar.ts +++ b/src/shared/translations/locales/ar.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ar: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const ar: PluginDefaultTranslationsObject = { 'مع `locale=all`، اضبط على false للإبقاء على الحقول المترجمة ككائنات لكل لغة. القيمة الافتراضية true.', paramLocale: 'اللغة المراد إرجاعها، أو `all` لكل اللغات. راجع قسم الترجمة في وصف الـ API.', paramFallbackLocale: 'اللغة الاحتياطية للقيم المترجمة المفقودة، أو `none` للتعطيل.', + paramValidateLocale: 'اللغات المراد التحقق منها، أو `all` لكل اللغات. كرر المعامل لأكثر من لغة.', + paramComputeHierarchyPaths: + 'اضبطه على true لحساب مسارات `{{slugPath}}` و`{{titlePath}}`. تحديد أي من الحقلين يحسبها أيضًا.', schemaSelect: 'اختر الحقول المراد إرجاعها، مثل `select[title]=true`. اتركه فارغًا لإرجاع الكل.', schemaPopulate: 'جلب المستندات المرتبطة لكل مجموعة، مثل `populate[posts][title]=true`.', @@ -24,6 +27,10 @@ export const ar: PluginDefaultTranslationsObject = { 'مرشح `where` الخاص بـ Payload. ضمّن حقلاً ثم عاملاً: `where[field][equals]=value`. ادمج الجمل باستخدام المصفوفتين `and` / `or`، مثل `where[or][0][field][equals]=value`. يسرد كل حقل فقط العوامل الصالحة لنوعه.', schemaSupportedTimezones: 'المناطق الزمنية المدعومة بتنسيق IANA.', schemaPerLocale: 'القيم لكل لغة، تُرجَع عند استخدام `locale=all`.', + schemaHierarchySlugPath: + 'مسار الأصل بالمعرّفات النصية، مثل `parent/child`. يُحسب عند القراءة مع `computeHierarchyPaths=true` أو عند تحديده. لا يمكن استخدامه في `where`.', + schemaHierarchyTitlePath: + 'مسار الأصل بالعناوين، مثل `Parent/Child`. يُحسب عند القراءة مع `computeHierarchyPaths=true` أو عند تحديده. لا يمكن استخدامه في `where`.', collectionList: 'قائمة مستندات مقسّمة إلى صفحات', collectionDoc: 'مستند واحد', @@ -34,6 +41,10 @@ export const ar: PluginDefaultTranslationsObject = { collectionBulkDelete: 'نتيجة الحذف المجمّع', collectionCount: 'عدد المستندات', collectionDuplicated: 'المستند المُكرَّر', + validateResult: + 'نتيجة التحقق. لا يتم حفظ أي شيء. القيم غير الصالحة للحقول تُرجع `valid: false` مع الأخطاء، وليس رمز حالة خطأ.', + validateBody: + 'بيانات المستند المراد التحقق منها. في المستند المحفوظ أو المستند العام، تُدمج البيانات فوق أحدث مسودة، أو المستند المحفوظ إن لم توجد مسودة.', globalDoc: 'المستند العام', @@ -45,9 +56,12 @@ export const ar: PluginDefaultTranslationsObject = { authResetPassword: 'نتيجة إعادة تعيين كلمة المرور', authFirstRegister: 'المستخدم الأول، تم إنشاؤه برمز مصادقة', authInit: 'ما إذا كان لدى مجموعة المصادقة هذه أي مستخدمين بعد', - authAccess: 'صلاحيات (أذونات) المستخدم الحالي لهذه المجموعة', + authAccess: 'صلاحيات المستخدم الحالي لكل المجموعات والعناصر العامة', + docAccess: 'صلاحيات المستخدم الحالي لهذا المستند', + docAccessBody: 'بيانات المستند التي يتم التحقق من الصلاحيات عليها. بدونها يستخدم Payload المستند المحفوظ إن وجد.', authUnlock: 'نتيجة إلغاء القفل', authVerify: 'نتيجة التحقق', + apiKeyReveal: 'مفتاح API بعد فك تشفيره', versionList: 'قائمة إصدارات مقسّمة إلى صفحات', versionSingle: 'إصدار واحد', @@ -69,6 +83,13 @@ export const ar: PluginDefaultTranslationsObject = { uploadBody: 'أرسِل `multipart/form-data` لرفع ملف (جزء ثنائي `file` بالإضافة إلى جزء `_payload` يحتوي على الحقول بصيغة JSON)، أو `application/json` بالحقول فقط في حال عدم وجود ملف.', uploadPayloadField: 'حقول {{schema}} بصيغة JSON. مثال: `{"alt":"A caption"}`.', + fileServe: 'الملف', + filePartial: 'جزء من الملف، لطلب `Range`', + uploadInstructionsSummary: 'الحصول على تعليمات رفع ملف قبل حفظ المستند', + uploadInstructionsResult: 'مكان إرسال بايتات الملف، وقيمة `file` التي تُرسل مع طلب الإنشاء أو التحديث', + uploadStagePutSummary: 'إرسال بايتات الملف لرفع مؤقت', + uploadStageDeleteSummary: 'حذف رفع مؤقت', + uploadStageResult: 'تم، بدون محتوى', error400: 'خطأ في التحقق أو الاستعلام (ValidationError، QueryError)', error401: 'غير مُصادَق عليه (AuthenticationError)', @@ -90,6 +111,8 @@ export const ar: PluginDefaultTranslationsObject = { tagAuth: 'المصادقة', tagVersions: 'الإصدارات', tagJobs: 'المهام', + tagUploads: 'الرفع', + tagAccess: 'الوصول', localizationHeading: 'الترجمة', localizationNote: diff --git a/src/translations/locales/az.ts b/src/shared/translations/locales/az.ts similarity index 75% rename from src/translations/locales/az.ts rename to src/shared/translations/locales/az.ts index 6d590a7..6bbe406 100644 --- a/src/translations/locales/az.ts +++ b/src/shared/translations/locales/az.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const az: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const az: PluginDefaultTranslationsObject = { '`locale=all` ilə, lokallaşdırılmış sahələri hər lokal üzrə obyekt kimi saxlamaq üçün false təyin edin. Standart olaraq true.', paramLocale: 'Qaytarılacaq lokal, və ya hər lokal üçün `all`. API təsvirindəki Lokallaşdırma bölməsinə baxın.', paramFallbackLocale: 'Çatışmayan lokallaşdırılmış dəyərlər üçün geri dönüləcək lokal, və ya söndürmək üçün `none`.', + paramValidateLocale: + 'Yoxlanılacaq lokallar, və ya hər lokal üçün `all`. Birdən çox lokal üçün parametri təkrarlayın.', + paramComputeHierarchyPaths: + '`{{slugPath}}` və `{{titlePath}}` yollarını hesablamaq üçün true təyin edin. Bu sahələrdən birini seçmək də onları hesablayır.', schemaSelect: 'Qaytarılacaq sahələri seçin, məs. `select[title]=true`. Hamısını qaytarmaq üçün buraxın.', schemaPopulate: 'Hər kolleksiya üzrə əlaqəli sənədləri doldurun, məs. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const az: PluginDefaultTranslationsObject = { 'Payload `where` filtri. Əvvəlcə sahəni, sonra operatoru yuvalandırın: `where[field][equals]=value`. Şərtləri `and` / `or` massivləri ilə birləşdirin, məs. `where[or][0][field][equals]=value`. Hər sahə yalnız öz tipinə uyğun operatorları sadalayır.', schemaSupportedTimezones: 'IANA formatında dəstəklənən saat qurşaqları.', schemaPerLocale: '`locale=all` zamanı qaytarılan hər lokal üzrə dəyərlər.', + schemaHierarchySlugPath: + 'Slug yolu, məs. `parent/child`. Oxuma zamanı `computeHierarchyPaths=true` ilə və ya seçildikdə hesablanır. `where`-də istifadə edilə bilməz.', + schemaHierarchyTitlePath: + 'Başlıq yolu, məs. `Parent/Child`. Oxuma zamanı `computeHierarchyPaths=true` ilə və ya seçildikdə hesablanır. `where`-də istifadə edilə bilməz.', collectionList: 'Səhifələnmiş sənəd siyahısı', collectionDoc: 'Tək sənəd', @@ -35,6 +43,10 @@ export const az: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Toplu silmə nəticəsi', collectionCount: 'Sənəd sayı', collectionDuplicated: 'Dublikat edilmiş sənəd', + validateResult: + 'Yoxlama nəticəsi. Heç nə saxlanılmır. Yanlış sahə dəyərləri xəta statusu əvəzinə xətalarla birlikdə `valid: false` qaytarır.', + validateBody: + 'Yoxlanılacaq sənəd məlumatları. Saxlanmış sənəddə və ya qlobal sənəddə məlumatlar ən son qaralamanın, qaralama yoxdursa saxlanmış sənədin üzərinə birləşdirilir.', globalDoc: 'Qlobal sənəd', @@ -46,9 +58,13 @@ export const az: PluginDefaultTranslationsObject = { authResetPassword: 'Parol sıfırlama nəticəsi', authFirstRegister: 'Autentifikasiya tokeni ilə yaradılmış ilk istifadəçi', authInit: 'Bu autentifikasiya kolleksiyasında hələ hər hansı istifadəçinin olub-olmaması', - authAccess: 'Cari istifadəçinin bu kolleksiya üçün girişi (icazələri)', + authAccess: 'Cari istifadəçinin bütün kolleksiyalar və qloballar üçün icazələri', + docAccess: 'Cari istifadəçinin bu sənəd üçün icazələri', + docAccessBody: + 'Girişin yoxlanılacağı sənəd məlumatları. Olmadıqda Payload saxlanmış sənədi istifadə edir (əgər varsa).', authUnlock: 'Kiliddən çıxarma nəticəsi', authVerify: 'Doğrulama nəticəsi', + apiKeyReveal: 'Deşifrə edilmiş API açarı', versionList: 'Səhifələnmiş versiya siyahısı', versionSingle: 'Tək versiya', @@ -70,6 +86,14 @@ export const az: PluginDefaultTranslationsObject = { uploadBody: 'Fayl yükləmək üçün `multipart/form-data` göndərin (binar `file` hissəsi və JSON-a çevrilmiş sahələri olan `_payload` hissəsi), və ya fayl olmadıqda yalnız sahələrlə `application/json` göndərin.', uploadPayloadField: 'JSON-a çevrilmiş {{schema}} sahələri. Nümunə: `{"alt":"A caption"}`.', + fileServe: 'Fayl', + filePartial: 'Faylın bir hissəsi, `Range` sorğusu üçün', + uploadInstructionsSummary: 'Sənədi saxlamazdan əvvəl fayl yükləmək üçün təlimat alın', + uploadInstructionsResult: + 'Fayl baytlarının hara göndəriləcəyi və yaratma və ya yeniləmə sorğusu ilə göndəriləcək `file` dəyəri', + uploadStagePutSummary: 'Mərhələli yükləmə üçün fayl baytlarını göndərin', + uploadStageDeleteSummary: 'Mərhələli yükləməni silin', + uploadStageResult: 'Hazırdır, məzmun yoxdur', error400: 'Doğrulama və ya sorğu xətası (ValidationError, QueryError)', error401: 'Autentifikasiya olunmayıb (AuthenticationError)', @@ -91,6 +115,8 @@ export const az: PluginDefaultTranslationsObject = { tagAuth: 'Autentifikasiya', tagVersions: 'Versiyalar', tagJobs: 'İşlər', + tagUploads: 'Yükləmələr', + tagAccess: 'Giriş', localizationHeading: 'Lokallaşdırma', localizationNote: diff --git a/src/translations/locales/bg.ts b/src/shared/translations/locales/bg.ts similarity index 74% rename from src/translations/locales/bg.ts rename to src/shared/translations/locales/bg.ts index a7416c6..84106cd 100644 --- a/src/translations/locales/bg.ts +++ b/src/shared/translations/locales/bg.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const bg: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const bg: PluginDefaultTranslationsObject = { 'С `locale=all` задайте false, за да запазите локализираните полета като обекти по локал. По подразбиране true.', paramLocale: 'Локал за връщане или `all` за всеки локал. Вижте раздела Localization в описанието на API.', paramFallbackLocale: 'Локал, към който да се връща при липсващи локализирани стойности, или `none` за изключване.', + paramValidateLocale: 'Локали за валидиране или `all` за всеки локал. Повторете параметъра за повече от един локал.', + paramComputeHierarchyPaths: + 'Задайте true, за да се изчислят пътищата `{{slugPath}}` и `{{titlePath}}`. Изборът на някое от полетата също ги изчислява.', schemaSelect: 'Изберете полета за връщане, напр. `select[title]=true`. Пропуснете, за да върнете всички.', schemaPopulate: 'Populate-ване на свързани документи по колекция, напр. `populate[posts][title]=true`.', @@ -24,6 +27,10 @@ export const bg: PluginDefaultTranslationsObject = { 'Payload `where` филтър. Вложете поле, след това оператор: `where[field][equals]=value`. Комбинирайте клаузи с масивите `and` / `or`, напр. `where[or][0][field][equals]=value`. Всяко поле изброява само операторите, валидни за неговия тип.', schemaSupportedTimezones: 'Поддържани часови зони във формат IANA.', schemaPerLocale: 'Стойности по локал, връщани когато `locale=all`.', + schemaHierarchySlugPath: + 'Път от slug стойности, напр. `parent/child`. Изчислява се при четене с `computeHierarchyPaths=true` или когато е избран. Не може да се ползва в `where`.', + schemaHierarchyTitlePath: + 'Път от заглавия, напр. `Parent/Child`. Изчислява се при четене с `computeHierarchyPaths=true` или когато е избран. Не може да се ползва в `where`.', collectionList: 'Списък на документи с пагинация', collectionDoc: 'Един документ', @@ -34,6 +41,10 @@ export const bg: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Резултат от групово изтриване', collectionCount: 'Брой документи', collectionDuplicated: 'Дублираният документ', + validateResult: + 'Резултат от валидирането. Нищо не се записва. Невалидните стойности на полета връщат `valid: false` с грешките, а не статус за грешка.', + validateBody: + 'Данни на документа за валидиране. При записан документ или глобален документ данните се сливат върху последната чернова, а ако няма чернова, върху записания документ.', globalDoc: 'Глобалният документ', @@ -45,9 +56,13 @@ export const bg: PluginDefaultTranslationsObject = { authResetPassword: 'Резултат от нулиране на парола', authFirstRegister: 'Първият потребител, създаден с токен за удостоверяване', authInit: 'Дали тази auth колекция вече има потребители', - authAccess: 'Достъпът (правата) на текущия потребител за тази колекция', + authAccess: 'Правата на текущия потребител за всички колекции и глобални', + docAccess: 'Правата на текущия потребител за този документ', + docAccessBody: + 'Данни на документа, спрямо които се проверява достъпът. Без тях Payload използва записания документ, ако има такъв.', authUnlock: 'Резултат от отключване', authVerify: 'Резултат от верификация', + apiKeyReveal: 'Дешифрираният API ключ', versionList: 'Списък на версии с пагинация', versionSingle: 'Една версия', @@ -69,6 +84,14 @@ export const bg: PluginDefaultTranslationsObject = { uploadBody: 'Изпратете `multipart/form-data`, за да качите файл (двоична `file` част плюс `_payload` част с JSON-сериализираните полета), или `application/json` само с полетата, когато няма файл.', uploadPayloadField: 'JSON-сериализирани {{schema}} полета. Пример: `{"alt":"A caption"}`.', + fileServe: 'Файлът', + filePartial: 'Част от файла при заявка с `Range`', + uploadInstructionsSummary: 'Получаване на инструкции за качване на файл преди запис на документа', + uploadInstructionsResult: + 'Къде да се изпратят байтовете на файла и стойността `file` за заявката за създаване или обновяване', + uploadStagePutSummary: 'Изпращане на байтовете на файла за временно качване', + uploadStageDeleteSummary: 'Изтриване на временно качване', + uploadStageResult: 'Готово, без съдържание', error400: 'Грешка при валидация или заявка (ValidationError, QueryError)', error401: 'Не сте удостоверени (AuthenticationError)', @@ -90,6 +113,8 @@ export const bg: PluginDefaultTranslationsObject = { tagAuth: 'Удостоверяване', tagVersions: 'Версии', tagJobs: 'Задачи', + tagUploads: 'Качвания', + tagAccess: 'Достъп', localizationHeading: 'Локализация', localizationNote: diff --git a/src/translations/locales/bnBd.ts b/src/shared/translations/locales/bnBd.ts similarity index 75% rename from src/translations/locales/bnBd.ts rename to src/shared/translations/locales/bnBd.ts index 28ba45d..6da1c6e 100644 --- a/src/translations/locales/bnBd.ts +++ b/src/shared/translations/locales/bnBd.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const bnBd: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,10 @@ export const bnBd: PluginDefaultTranslationsObject = { '`locale=all` এর সাথে, লোকালাইজড ফিল্ডগুলো প্রতি-লোকেল অবজেক্ট হিসেবে রাখতে false সেট করুন। ডিফল্ট true।', paramLocale: 'যে লোকেল ফেরত দিতে হবে, অথবা প্রতিটি লোকেলের জন্য `all`। API বিবরণের Localization অংশটি দেখুন।', paramFallbackLocale: 'অনুপস্থিত লোকালাইজড মানের জন্য যে লোকেলে ফিরে যাওয়া হবে, অথবা নিষ্ক্রিয় করতে `none`।', + paramValidateLocale: + 'যে লোকেলগুলো যাচাই করতে হবে, অথবা প্রতিটি লোকেলের জন্য `all`। একাধিক লোকেলের জন্য প্যারামিটারটি পুনরাবৃত্তি করুন।', + paramComputeHierarchyPaths: + '`{{slugPath}}` এবং `{{titlePath}}` পাথ গণনা করতে true দিন। যেকোনো একটি ফিল্ড নির্বাচন করলেও সেগুলো গণনা হয়।', schemaSelect: 'কোন ফিল্ডগুলো ফেরত দেওয়া হবে তা নির্বাচন করুন, যেমন `select[title]=true`। সবগুলো ফেরত পেতে বাদ দিন।', schemaPopulate: 'প্রতি কালেকশন অনুযায়ী সম্পর্কিত ডকুমেন্ট পপুলেট করুন, যেমন `populate[posts][title]=true`।', @@ -24,6 +28,10 @@ export const bnBd: PluginDefaultTranslationsObject = { 'Payload `where` ফিল্টার। একটি ফিল্ড তারপর একটি অপারেটর নেস্ট করুন: `where[field][equals]=value`। `and` / `or` অ্যারে দিয়ে শর্তসমূহ একত্র করুন, যেমন `where[or][0][field][equals]=value`। প্রতিটি ফিল্ড শুধু তার টাইপের জন্য বৈধ অপারেটরগুলোই তালিকাভুক্ত করে।', schemaSupportedTimezones: 'IANA ফরম্যাটে সমর্থিত টাইমজোন।', schemaPerLocale: 'প্রতি-লোকেল মান, `locale=all` দেওয়া হলে ফেরত দেওয়া হয়।', + schemaHierarchySlugPath: + 'স্লাগ পাথ, যেমন `parent/child`। পড়ার সময় `computeHierarchyPaths=true` দিলে বা নির্বাচন করলে গণনা হয়। `where`-এ ব্যবহার করা যায় না।', + schemaHierarchyTitlePath: + 'শিরোনাম পাথ, যেমন `Parent/Child`। পড়ার সময় `computeHierarchyPaths=true` দিলে বা নির্বাচন করলে গণনা হয়। `where`-এ ব্যবহার করা যায় না।', collectionList: 'ডকুমেন্টের পেজিনেটেড তালিকা', collectionDoc: 'একটি একক ডকুমেন্ট', @@ -34,6 +42,10 @@ export const bnBd: PluginDefaultTranslationsObject = { collectionBulkDelete: 'বাল্ক ডিলিটের ফলাফল', collectionCount: 'ডকুমেন্ট সংখ্যা', collectionDuplicated: 'ডুপ্লিকেট করা ডকুমেন্ট', + validateResult: + 'যাচাইয়ের ফলাফল। কিছুই সংরক্ষণ হয় না। ফিল্ডের অবৈধ মানের ক্ষেত্রে এরর স্ট্যাটাস নয়, ত্রুটিসহ `valid: false` ফেরত আসে।', + validateBody: + 'যাচাই করার ডকুমেন্ট ডেটা। সংরক্ষিত ডকুমেন্ট বা গ্লোবালে ডেটা সর্বশেষ ড্রাফটের উপর মার্জ হয়, ড্রাফট না থাকলে সংরক্ষিত ডকুমেন্টের উপর।', globalDoc: 'গ্লোবাল ডকুমেন্ট', @@ -45,9 +57,12 @@ export const bnBd: PluginDefaultTranslationsObject = { authResetPassword: 'পাসওয়ার্ড রিসেটের ফলাফল', authFirstRegister: 'প্রথম ব্যবহারকারী, একটি auth টোকেনসহ তৈরি করা হয়েছে', authInit: 'এই auth কালেকশনে এখনও কোনো ব্যবহারকারী আছে কিনা', - authAccess: 'এই কালেকশনের জন্য বর্তমান ব্যবহারকারীর অ্যাক্সেস (অনুমতি)', + authAccess: 'সব কালেকশন ও গ্লোবালের জন্য বর্তমান ব্যবহারকারীর অনুমতি', + docAccess: 'এই ডকুমেন্টের জন্য বর্তমান ব্যবহারকারীর অনুমতি', + docAccessBody: 'যে ডকুমেন্ট ডেটার বিপরীতে অ্যাক্সেস যাচাই হবে। না দিলে Payload সংরক্ষিত ডকুমেন্ট ব্যবহার করে, যদি থাকে।', authUnlock: 'আনলকের ফলাফল', authVerify: 'যাচাইয়ের ফলাফল', + apiKeyReveal: 'ডিক্রিপ্ট করা API কী', versionList: 'সংস্করণের পেজিনেটেড তালিকা', versionSingle: 'একটি একক সংস্করণ', @@ -69,6 +84,13 @@ export const bnBd: PluginDefaultTranslationsObject = { uploadBody: 'একটি ফাইল আপলোড করতে `multipart/form-data` পাঠান (একটি বাইনারি `file` অংশ এবং JSON-স্ট্রিংফাইড ফিল্ডসহ একটি `_payload` অংশ), অথবা কোনো ফাইল না থাকলে শুধু ফিল্ডসহ `application/json` পাঠান।', uploadPayloadField: 'JSON-স্ট্রিংফাইড {{schema}} ফিল্ড। উদাহরণ: `{"alt":"A caption"}`।', + fileServe: 'ফাইলটি', + filePartial: 'ফাইলের একটি অংশ, `Range` রিকোয়েস্টের জন্য', + uploadInstructionsSummary: 'ডকুমেন্ট সেভ করার আগে ফাইল আপলোডের নির্দেশনা নিন', + uploadInstructionsResult: 'ফাইলের বাইট কোথায় পাঠাতে হবে এবং তৈরি বা আপডেট রিকোয়েস্টে কোন `file` মান পাঠাতে হবে', + uploadStagePutSummary: 'অস্থায়ী আপলোডের জন্য ফাইলের বাইট পাঠান', + uploadStageDeleteSummary: 'অস্থায়ী আপলোড মুছুন', + uploadStageResult: 'সম্পন্ন, কোনো কনটেন্ট নেই', error400: 'ভ্যালিডেশন বা কোয়েরি ত্রুটি (ValidationError, QueryError)', error401: 'প্রমাণীকৃত নয় (AuthenticationError)', @@ -89,6 +111,8 @@ export const bnBd: PluginDefaultTranslationsObject = { tagAuth: 'অথেনটিকেশন', tagVersions: 'সংস্করণ', tagJobs: 'জব', + tagUploads: 'আপলোড', + tagAccess: 'অ্যাক্সেস', localizationHeading: 'লোকালাইজেশন', localizationNote: diff --git a/src/translations/locales/bnIn.ts b/src/shared/translations/locales/bnIn.ts similarity index 75% rename from src/translations/locales/bnIn.ts rename to src/shared/translations/locales/bnIn.ts index 5ef1948..f8ecfec 100644 --- a/src/translations/locales/bnIn.ts +++ b/src/shared/translations/locales/bnIn.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const bnIn: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -15,6 +15,10 @@ export const bnIn: PluginDefaultTranslationsObject = { paramFlattenLocales: '`locale=all` সহ, স্থানীয়কৃত ফিল্ডগুলো প্রতি-লোকেল অবজেক্ট হিসেবে রাখতে false সেট করুন। ডিফল্ট true।', paramLocale: 'যে লোকেল ফেরত দেওয়া হবে, অথবা প্রতিটি লোকেলের জন্য `all`। API বিবরণে Localization বিভাগটি দেখুন।', paramFallbackLocale: 'অনুপস্থিত স্থানীয়কৃত মানের জন্য যে লোকেলে ফিরে যাওয়া হবে, অথবা নিষ্ক্রিয় করতে `none`।', + paramValidateLocale: + 'যে লোকেলগুলি যাচাই করা হবে, অথবা প্রতিটি লোকেলের জন্য `all`। একাধিক লোকেলের জন্য প্যারামিটারটি পুনরাবৃত্তি করুন।', + paramComputeHierarchyPaths: + '`{{slugPath}}` এবং `{{titlePath}}` পাথ গণনা করতে true দিন। যেকোনো একটি ফিল্ড নির্বাচন করলেও সেগুলো গণনা হয়।', schemaSelect: 'যে ফিল্ডগুলো ফেরত দেওয়া হবে তা বেছে নিন, যেমন `select[title]=true`। সব ফেরত দিতে বাদ দিন।', schemaPopulate: 'প্রতিটি কালেকশন অনুযায়ী সম্পর্কিত নথি পপুলেট করুন, যেমন `populate[posts][title]=true`।', @@ -23,6 +27,10 @@ export const bnIn: PluginDefaultTranslationsObject = { 'Payload `where` ফিল্টার। একটি ফিল্ড তারপর একটি অপারেটর নেস্ট করুন: `where[field][equals]=value`। `and` / `or` অ্যারে দিয়ে ক্লজগুলো একত্র করুন, যেমন `where[or][0][field][equals]=value`। প্রতিটি ফিল্ড কেবল তার টাইপের জন্য বৈধ অপারেটরগুলোই তালিকাভুক্ত করে।', schemaSupportedTimezones: 'IANA ফরম্যাটে সমর্থিত টাইমজোন।', schemaPerLocale: 'প্রতি-লোকেল মান, `locale=all` দিলে ফেরত দেওয়া হয়।', + schemaHierarchySlugPath: + 'স্লাগ পাথ, যেমন `parent/child`। পড়ার সময় `computeHierarchyPaths=true` দিলে বা নির্বাচন করলে গণনা হয়। `where`-এ ব্যবহার করা যায় না।', + schemaHierarchyTitlePath: + 'শিরোনাম পাথ, যেমন `Parent/Child`। পড়ার সময় `computeHierarchyPaths=true` দিলে বা নির্বাচন করলে গণনা হয়। `where`-এ ব্যবহার করা যায় না।', collectionList: 'নথির পেজিনেটেড তালিকা', collectionDoc: 'একটি একক নথি', @@ -33,6 +41,9 @@ export const bnIn: PluginDefaultTranslationsObject = { collectionBulkDelete: 'বাল্ক ডিলিটের ফলাফল', collectionCount: 'নথির সংখ্যা', collectionDuplicated: 'নকল করা নথি', + validateResult: + 'যাচাইয়ের ফলাফল। কিছুই সংরক্ষিত হয় না। ফিল্ডের অবৈধ মানের ক্ষেত্রে এরর স্ট্যাটাস নয়, ত্রুটিসহ `valid: false` ফেরত আসে।', + validateBody: 'যাচাই করার নথির ডেটা। সংরক্ষিত নথি বা গ্লোবালে ডেটা সর্বশেষ খসড়ার উপর মার্জ হয়, খসড়া না থাকলে সংরক্ষিত নথির উপর।', globalDoc: 'গ্লোবাল নথি', @@ -44,9 +55,12 @@ export const bnIn: PluginDefaultTranslationsObject = { authResetPassword: 'পাসওয়ার্ড রিসেটের ফলাফল', authFirstRegister: 'প্রথম ব্যবহারকারী, একটি auth টোকেন সহ তৈরি করা হয়েছে', authInit: 'এই auth কালেকশনে এখনও কোনো ব্যবহারকারী আছে কিনা', - authAccess: 'এই কালেকশনের জন্য বর্তমান ব্যবহারকারীর অ্যাক্সেস (অনুমতি)', + authAccess: 'সব কালেকশন ও গ্লোবালের জন্য বর্তমান ব্যবহারকারীর অনুমতি', + docAccess: 'এই নথির জন্য বর্তমান ব্যবহারকারীর অনুমতি', + docAccessBody: 'যে নথির ডেটার বিপরীতে অ্যাক্সেস যাচাই হবে। না দিলে Payload সংরক্ষিত নথি ব্যবহার করে, যদি থাকে।', authUnlock: 'আনলকের ফলাফল', authVerify: 'যাচাইয়ের ফলাফল', + apiKeyReveal: 'ডিক্রিপ্ট করা API কী', versionList: 'সংস্করণের পেজিনেটেড তালিকা', versionSingle: 'একটি একক সংস্করণ', @@ -68,6 +82,13 @@ export const bnIn: PluginDefaultTranslationsObject = { uploadBody: 'কোনো ফাইল আপলোড করতে `multipart/form-data` পাঠান (একটি বাইনারি `file` অংশ এবং JSON-স্ট্রিংকৃত ফিল্ডসহ একটি `_payload` অংশ), অথবা যখন কোনো ফাইল নেই তখন শুধু ফিল্ডগুলোসহ `application/json` পাঠান।', uploadPayloadField: 'JSON-স্ট্রিংকৃত {{schema}} ফিল্ড। উদাহরণ: `{"alt":"A caption"}`।', + fileServe: 'ফাইলটি', + filePartial: 'ফাইলের একটি অংশ, `Range` অনুরোধের জন্য', + uploadInstructionsSummary: 'নথি সংরক্ষণের আগে ফাইল আপলোডের নির্দেশনা নিন', + uploadInstructionsResult: 'ফাইলের বাইট কোথায় পাঠাতে হবে এবং তৈরি বা হালনাগাদ অনুরোধে কোন `file` মান পাঠাতে হবে', + uploadStagePutSummary: 'অস্থায়ী আপলোডের জন্য ফাইলের বাইট পাঠান', + uploadStageDeleteSummary: 'অস্থায়ী আপলোড মুছুন', + uploadStageResult: 'সম্পন্ন, কোনো বিষয়বস্তু নেই', error400: 'যাচাই বা কোয়েরি ত্রুটি (ValidationError, QueryError)', error401: 'প্রমাণীকৃত নয় (AuthenticationError)', @@ -88,6 +109,8 @@ export const bnIn: PluginDefaultTranslationsObject = { tagAuth: 'প্রমাণীকরণ', tagVersions: 'সংস্করণ', tagJobs: 'জব', + tagUploads: 'আপলোড', + tagAccess: 'অ্যাক্সেস', localizationHeading: 'স্থানীয়করণ', localizationNote: diff --git a/src/translations/locales/ca.ts b/src/shared/translations/locales/ca.ts similarity index 75% rename from src/translations/locales/ca.ts rename to src/shared/translations/locales/ca.ts index 7a44b97..3a6af26 100644 --- a/src/translations/locales/ca.ts +++ b/src/shared/translations/locales/ca.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ca: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const ca: PluginDefaultTranslationsObject = { paramLocale: "Localització a retornar, o `all` per a totes les localitzacions. Vegeu la secció Localització a la descripció de l'API.", paramFallbackLocale: 'Localització de reserva per als valors localitzats que manquin, o `none` per desactivar-la.', + paramValidateLocale: + "Localitzacions a validar, o `all` per a totes les localitzacions. Repeteix el paràmetre per a més d'una localització.", + paramComputeHierarchyPaths: + 'Posa true per calcular els camins `{{slugPath}}` i `{{titlePath}}`. Seleccionar qualsevol dels dos camps també els calcula.', schemaSelect: 'Trieu els camps a retornar, p. ex. `select[title]=true`. Ometeu-ho per retornar-los tots.', schemaPopulate: 'Ompliu els documents relacionats per col·lecció, p. ex. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const ca: PluginDefaultTranslationsObject = { 'Filtre `where` de Payload. Imbriqueu un camp i després un operador: `where[field][equals]=value`. Combineu clàusules amb els arrays `and` / `or`, p. ex. `where[or][0][field][equals]=value`. Cada camp només llista els operadors vàlids per al seu tipus.', schemaSupportedTimezones: 'Zones horàries admeses en format IANA.', schemaPerLocale: 'Valors per localització, retornats quan `locale=all`.', + schemaHierarchySlugPath: + 'Camí de slugs, p. ex. `parent/child`. Es calcula en llegir amb `computeHierarchyPaths=true` o quan se selecciona. No es pot fer servir a `where`.', + schemaHierarchyTitlePath: + 'Camí de títols, p. ex. `Parent/Child`. Es calcula en llegir amb `computeHierarchyPaths=true` o quan se selecciona. No es pot fer servir a `where`.', collectionList: 'Llista paginada de documents', collectionDoc: 'Un únic document', @@ -36,6 +44,10 @@ export const ca: PluginDefaultTranslationsObject = { collectionBulkDelete: "Resultat de l'eliminació massiva", collectionCount: 'Recompte de documents', collectionDuplicated: 'El document duplicat', + validateResult: + "Resultat de la validació. No es desa res. Els valors de camp no vàlids retornen `valid: false` amb els errors, no un estat d'error.", + validateBody: + "Dades del document a validar. En un document desat o un global, les dades es fusionen sobre l'últim esborrany, o sobre el document desat si no hi ha esborrany.", globalDoc: 'El document global', @@ -47,9 +59,13 @@ export const ca: PluginDefaultTranslationsObject = { authResetPassword: 'Resultat del restabliment de contrasenya', authFirstRegister: "El primer usuari, creat amb un token d'autenticació", authInit: "Si aquesta col·lecció d'autenticació ja té usuaris", - authAccess: "L'accés (permisos) de l'usuari actual per a aquesta col·lecció", + authAccess: "Els permisos de l'usuari actual per a totes les col·leccions i globals", + docAccess: "Els permisos de l'usuari actual per a aquest document", + docAccessBody: + "Dades del document amb què es comprova l'accés. Sense elles, Payload fa servir el document desat, si n'hi ha.", authUnlock: 'Resultat del desbloqueig', authVerify: 'Resultat de la verificació', + apiKeyReveal: "La clau d'API desxifrada", versionList: 'Llista paginada de versions', versionSingle: 'Una única versió', @@ -71,6 +87,14 @@ export const ca: PluginDefaultTranslationsObject = { uploadBody: 'Envieu `multipart/form-data` per pujar un fitxer (una part binària `file` més una part `_payload` amb els camps en format JSON), o `application/json` només amb els camps quan no hi ha cap fitxer.', uploadPayloadField: 'Camps {{schema}} en format JSON. Exemple: `{"alt":"A caption"}`.', + fileServe: 'El fitxer', + filePartial: 'Una part del fitxer, per a una petició `Range`', + uploadInstructionsSummary: 'Obté instruccions per pujar un fitxer abans de desar el document', + uploadInstructionsResult: + "On enviar els bytes del fitxer i el valor `file` que cal enviar amb la petició de creació o d'actualització", + uploadStagePutSummary: 'Envia els bytes del fitxer per a una pujada temporal', + uploadStageDeleteSummary: 'Elimina una pujada temporal', + uploadStageResult: 'Fet, sense contingut', error400: 'Error de validació o de consulta (ValidationError, QueryError)', error401: 'No autenticat (AuthenticationError)', @@ -92,6 +116,8 @@ export const ca: PluginDefaultTranslationsObject = { tagAuth: 'Autenticació', tagVersions: 'Versions', tagJobs: 'Tasques', + tagUploads: 'Pujades', + tagAccess: 'Accés', localizationHeading: 'Localització', localizationNote: diff --git a/src/translations/locales/cs.ts b/src/shared/translations/locales/cs.ts similarity index 75% rename from src/translations/locales/cs.ts rename to src/shared/translations/locales/cs.ts index 466dfba..702655e 100644 --- a/src/translations/locales/cs.ts +++ b/src/shared/translations/locales/cs.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const cs: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,9 @@ export const cs: PluginDefaultTranslationsObject = { 'S `locale=all` nastavte false, aby lokalizovaná pole zůstala jako objekty po jednotlivých jazycích. Výchozí hodnota je true.', paramLocale: 'Jazyk, který se má vrátit, nebo `all` pro všechny jazyky. Viz sekce Lokalizace v popisu API.', paramFallbackLocale: 'Jazyk, na který se přejde u chybějících lokalizovaných hodnot, nebo `none` pro vypnutí.', + paramValidateLocale: 'Jazyky k ověření, nebo `all` pro všechny jazyky. Pro více jazyků parametr zopakujte.', + paramComputeHierarchyPaths: + 'Nastavte true pro výpočet cest `{{slugPath}}` a `{{titlePath}}`. Výběr kteréhokoli z těchto polí je vypočítá také.', schemaSelect: 'Vyberte pole, která se mají vrátit, např. `select[title]=true`. Vynechte pro vrácení všech.', schemaPopulate: 'Naplňte související dokumenty podle kolekce, např. `populate[posts][title]=true`.', @@ -25,6 +28,10 @@ export const cs: PluginDefaultTranslationsObject = { 'Filtr `where` v Payloadu. Vnořte pole a poté operátor: `where[field][equals]=value`. Klauzule kombinujte pomocí polí `and` / `or`, např. `where[or][0][field][equals]=value`. Každé pole uvádí pouze operátory platné pro jeho typ.', schemaSupportedTimezones: 'Podporované časové zóny ve formátu IANA.', schemaPerLocale: 'Hodnoty po jednotlivých jazycích, vrácené při `locale=all`.', + schemaHierarchySlugPath: + 'Cesta ze slugů, např. `parent/child`. Počítá se při čtení s `computeHierarchyPaths=true` nebo při výběru. Nelze použít ve `where`.', + schemaHierarchyTitlePath: + 'Cesta z názvů, např. `Parent/Child`. Počítá se při čtení s `computeHierarchyPaths=true` nebo při výběru. Nelze použít ve `where`.', collectionList: 'Stránkovaný seznam dokumentů', collectionDoc: 'Jeden dokument', @@ -35,6 +42,10 @@ export const cs: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Výsledek hromadného smazání', collectionCount: 'Počet dokumentů', collectionDuplicated: 'Duplikovaný dokument', + validateResult: + 'Výsledek ověření. Nic se neukládá. Neplatné hodnoty polí vrátí `valid: false` s chybami, nikoli chybový stav.', + validateBody: + 'Data dokumentu k ověření. U uloženého dokumentu nebo globálního dokumentu se data sloučí s nejnovějším konceptem, případně s uloženým dokumentem, pokud koncept neexistuje.', globalDoc: 'Globální dokument', @@ -46,9 +57,13 @@ export const cs: PluginDefaultTranslationsObject = { authResetPassword: 'Výsledek obnovení hesla', authFirstRegister: 'První uživatel vytvořený s autentizačním tokenem', authInit: 'Zda tato autentizační kolekce již obsahuje nějaké uživatele', - authAccess: 'Přístup (oprávnění) aktuálního uživatele k této kolekci', + authAccess: 'Oprávnění aktuálního uživatele pro všechny kolekce a globální', + docAccess: 'Oprávnění aktuálního uživatele pro tento dokument', + docAccessBody: + 'Data dokumentu, vůči kterým se ověřuje přístup. Bez nich Payload použije uložený dokument, pokud existuje.', authUnlock: 'Výsledek odemčení', authVerify: 'Výsledek ověření', + apiKeyReveal: 'Dešifrovaný API klíč', versionList: 'Stránkovaný seznam verzí', versionSingle: 'Jedna verze', @@ -70,6 +85,13 @@ export const cs: PluginDefaultTranslationsObject = { uploadBody: 'Pro nahrání souboru odešlete `multipart/form-data` (binární část `file` plus část `_payload` s poli serializovanými do JSON), nebo `application/json` jen s poli, pokud žádný soubor není.', uploadPayloadField: 'Pole {{schema}} serializovaná do JSON. Příklad: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Soubor', + filePartial: 'Část souboru pro požadavek `Range`', + uploadInstructionsSummary: 'Získat pokyny k nahrání souboru před uložením dokumentu', + uploadInstructionsResult: 'Kam poslat bajty souboru a hodnota `file` pro požadavek na vytvoření nebo úpravu', + uploadStagePutSummary: 'Odeslat bajty souboru pro dočasné nahrání', + uploadStageDeleteSummary: 'Smazat dočasné nahrání', + uploadStageResult: 'Hotovo, bez obsahu', error400: 'Chyba validace nebo dotazu (ValidationError, QueryError)', error401: 'Neautentizováno (AuthenticationError)', @@ -91,6 +113,8 @@ export const cs: PluginDefaultTranslationsObject = { tagAuth: 'Autentizace', tagVersions: 'Verze', tagJobs: 'Úlohy', + tagUploads: 'Nahrávání', + tagAccess: 'Přístup', localizationHeading: 'Lokalizace', localizationNote: diff --git a/src/translations/locales/da.ts b/src/shared/translations/locales/da.ts similarity index 75% rename from src/translations/locales/da.ts rename to src/shared/translations/locales/da.ts index eed07ef..85c9a43 100644 --- a/src/translations/locales/da.ts +++ b/src/shared/translations/locales/da.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const da: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const da: PluginDefaultTranslationsObject = { paramLocale: 'Sprog der skal returneres, eller `all` for alle sprog. Se afsnittet Lokalisering i API-beskrivelsen.', paramFallbackLocale: 'Sprog der skal falde tilbage til for manglende lokaliserede værdier, eller `none` for at deaktivere.', + paramValidateLocale: + 'Sprog der skal valideres, eller `all` for alle sprog. Gentag parameteren for mere end ét sprog.', + paramComputeHierarchyPaths: + 'Sæt til true for at beregne stierne `{{slugPath}}` og `{{titlePath}}`. Valg af et af felterne beregner dem også.', schemaSelect: 'Vælg hvilke felter der skal returneres, f.eks. `select[title]=true`. Udelad for at returnere alle.', schemaPopulate: 'Populér relaterede dokumenter pr. collection, f.eks. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const da: PluginDefaultTranslationsObject = { 'Payload `where`-filter. Indlejr et felt og derefter en operator: `where[field][equals]=value`. Kombinér betingelser med `and`- / `or`-arrays, f.eks. `where[or][0][field][equals]=value`. Hvert felt angiver kun de operatorer, der er gyldige for dets type.', schemaSupportedTimezones: 'Understøttede tidszoner i IANA-format.', schemaPerLocale: 'Værdier pr. sprog, returneres når `locale=all`.', + schemaHierarchySlugPath: + 'Sti af slugs, f.eks. `parent/child`. Beregnes ved læsning med `computeHierarchyPaths=true` eller når feltet vælges. Kan ikke bruges i `where`.', + schemaHierarchyTitlePath: + 'Sti af titler, f.eks. `Parent/Child`. Beregnes ved læsning med `computeHierarchyPaths=true` eller når feltet vælges. Kan ikke bruges i `where`.', collectionList: 'Pagineret liste over dokumenter', collectionDoc: 'Et enkelt dokument', @@ -35,6 +43,10 @@ export const da: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultat af masseslettelse', collectionCount: 'Antal dokumenter', collectionDuplicated: 'Det duplikerede dokument', + validateResult: + 'Valideringsresultat. Intet gemmes. Ugyldige feltværdier returnerer `valid: false` med fejlene, ikke en fejlstatus.', + validateBody: + 'Dokumentdata der skal valideres. På et gemt dokument eller en global flettes dataene oven på den seneste kladde, eller det gemte dokument, hvis der ikke er nogen kladde.', globalDoc: 'Det globale dokument', @@ -46,9 +58,13 @@ export const da: PluginDefaultTranslationsObject = { authResetPassword: 'Resultat af nulstilling af adgangskode', authFirstRegister: 'Den første bruger, oprettet med en auth-token', authInit: 'Om denne auth-collection allerede har brugere', - authAccess: 'Den aktuelle brugers adgang (tilladelser) for denne collection', + authAccess: 'Den aktuelle brugers tilladelser for alle collections og globals', + docAccess: 'Den aktuelle brugers tilladelser for dette dokument', + docAccessBody: + 'Dokumentdata, som adgang kontrolleres mod. Uden dem bruger Payload det gemte dokument, hvis der er et.', authUnlock: 'Resultat af oplåsning', authVerify: 'Resultat af verifikation', + apiKeyReveal: 'Den dekrypterede API-nøgle', versionList: 'Pagineret liste over versioner', versionSingle: 'En enkelt version', @@ -70,6 +86,14 @@ export const da: PluginDefaultTranslationsObject = { uploadBody: 'Send `multipart/form-data` for at uploade en fil (en binær `file`-del plus en `_payload`-del med de JSON-strengkodede felter), eller `application/json` med blot felterne, når der ikke er nogen fil.', uploadPayloadField: 'JSON-strengkodede {{schema}}-felter. Eksempel: `{"alt":"A caption"}`.', + fileServe: 'Filen', + filePartial: 'En del af filen ved en `Range`-anmodning', + uploadInstructionsSummary: 'Hent instruktioner til at uploade en fil, før dokumentet gemmes', + uploadInstructionsResult: + 'Hvor filens bytes skal sendes hen, og den `file`-værdi, der skal sendes med opret- eller opdater-anmodningen', + uploadStagePutSummary: 'Send filens bytes til en midlertidig upload', + uploadStageDeleteSummary: 'Slet en midlertidig upload', + uploadStageResult: 'Udført, intet indhold', error400: 'Validerings- eller forespørgselsfejl (ValidationError, QueryError)', error401: 'Ikke godkendt (AuthenticationError)', @@ -91,6 +115,8 @@ export const da: PluginDefaultTranslationsObject = { tagAuth: 'Auth', tagVersions: 'Versioner', tagJobs: 'Jobs', + tagUploads: 'Uploads', + tagAccess: 'Adgang', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/translations/locales/de.ts b/src/shared/translations/locales/de.ts similarity index 75% rename from src/translations/locales/de.ts rename to src/shared/translations/locales/de.ts index 7199fc1..98a2c0e 100644 --- a/src/translations/locales/de.ts +++ b/src/shared/translations/locales/de.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const de: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -19,6 +19,10 @@ export const de: PluginDefaultTranslationsObject = { 'Zurückzugebende Sprache oder `all` für alle Sprachen. Siehe den Abschnitt zur Lokalisierung in der API-Beschreibung.', paramFallbackLocale: 'Sprache, auf die bei fehlenden lokalisierten Werten zurückgegriffen wird, oder `none` zum Deaktivieren.', + paramValidateLocale: + 'Zu validierende Sprachen oder `all` für alle Sprachen. Für mehrere Sprachen den Parameter wiederholen.', + paramComputeHierarchyPaths: + 'Auf true setzen, um die Pfade `{{slugPath}}` und `{{titlePath}}` zu berechnen. Die Auswahl eines der beiden Felder berechnet sie ebenfalls.', schemaSelect: 'Zurückzugebende Felder auswählen, z. B. `select[title]=true`. Weglassen, um alle zurückzugeben.', schemaPopulate: 'Verknüpfte Dokumente je Collection befüllen, z. B. `populate[posts][title]=true`.', @@ -27,6 +31,10 @@ export const de: PluginDefaultTranslationsObject = { 'Payload-`where`-Filter. Verschachteln Sie ein Feld und dann einen Operator: `where[field][equals]=value`. Verknüpfen Sie Klauseln mit den Arrays `and` / `or`, z. B. `where[or][0][field][equals]=value`. Jedes Feld listet nur die für seinen Typ gültigen Operatoren auf.', schemaSupportedTimezones: 'Unterstützte Zeitzonen im IANA-Format.', schemaPerLocale: 'Werte je Sprache, zurückgegeben bei `locale=all`.', + schemaHierarchySlugPath: + 'Slug-Pfad, z. B. `parent/child`. Wird beim Lesen mit `computeHierarchyPaths=true` oder bei Auswahl berechnet. Nicht in `where` verwendbar.', + schemaHierarchyTitlePath: + 'Titel-Pfad, z. B. `Parent/Child`. Wird beim Lesen mit `computeHierarchyPaths=true` oder bei Auswahl berechnet. Nicht in `where` verwendbar.', collectionList: 'Paginierte Liste von Dokumenten', collectionDoc: 'Ein einzelnes Dokument', @@ -37,6 +45,10 @@ export const de: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Ergebnis der Massenlöschung', collectionCount: 'Anzahl der Dokumente', collectionDuplicated: 'Das duplizierte Dokument', + validateResult: + 'Validierungsergebnis. Es wird nichts gespeichert. Ungültige Feldwerte liefern `valid: false` mit den Fehlern, keinen Fehlerstatus.', + validateBody: + 'Zu validierende Dokumentdaten. Bei einem gespeicherten Dokument oder einem Global werden die Daten über den neuesten Entwurf gelegt, oder über das gespeicherte Dokument, falls es keinen Entwurf gibt.', globalDoc: 'Das globale Dokument', @@ -48,9 +60,13 @@ export const de: PluginDefaultTranslationsObject = { authResetPassword: 'Ergebnis des Passwort-Zurücksetzens', authFirstRegister: 'Der erste Benutzer, erstellt mit einem Auth-Token', authInit: 'Ob diese Auth-Collection bereits Benutzer hat', - authAccess: 'Die Zugriffsrechte (Berechtigungen) des aktuellen Benutzers für diese Collection', + authAccess: 'Die Berechtigungen des aktuellen Benutzers für alle Collections und Globals', + docAccess: 'Die Berechtigungen des aktuellen Benutzers für dieses Dokument', + docAccessBody: + 'Dokumentdaten, gegen die der Zugriff geprüft wird. Ohne sie nutzt Payload das gespeicherte Dokument, falls es eines gibt.', authUnlock: 'Ergebnis des Entsperrens', authVerify: 'Verifizierungsergebnis', + apiKeyReveal: 'Der entschlüsselte API-Schlüssel', versionList: 'Paginierte Liste von Versionen', versionSingle: 'Eine einzelne Version', @@ -72,6 +88,14 @@ export const de: PluginDefaultTranslationsObject = { uploadBody: 'Senden Sie `multipart/form-data`, um eine Datei hochzuladen (ein binärer `file`-Teil plus ein `_payload`-Teil mit den als JSON-String serialisierten Feldern), oder `application/json` nur mit den Feldern, wenn keine Datei vorhanden ist.', uploadPayloadField: 'Als JSON-String serialisierte {{schema}}-Felder. Beispiel: `{"alt":"A caption"}`.', + fileServe: 'Die Datei', + filePartial: 'Ein Teil der Datei bei einer `Range`-Anfrage', + uploadInstructionsSummary: 'Anweisungen zum Hochladen einer Datei vor dem Speichern des Dokuments abrufen', + uploadInstructionsResult: + 'Wohin die Bytes der Datei gesendet werden und welcher `file`-Wert mit der Erstellungs- oder Aktualisierungsanfrage gesendet wird', + uploadStagePutSummary: 'Die Bytes der Datei für einen Zwischen-Upload senden', + uploadStageDeleteSummary: 'Einen Zwischen-Upload löschen', + uploadStageResult: 'Erledigt, kein Inhalt', error400: 'Validierungs- oder Abfragefehler (ValidationError, QueryError)', error401: 'Nicht authentifiziert (AuthenticationError)', @@ -93,6 +117,8 @@ export const de: PluginDefaultTranslationsObject = { tagAuth: 'Authentifizierung', tagVersions: 'Versionen', tagJobs: 'Jobs', + tagUploads: 'Uploads', + tagAccess: 'Zugriff', localizationHeading: 'Lokalisierung', localizationNote: diff --git a/src/translations/locales/en.ts b/src/shared/translations/locales/en.ts similarity index 74% rename from src/translations/locales/en.ts rename to src/shared/translations/locales/en.ts index 962bdb8..2e62ad5 100644 --- a/src/translations/locales/en.ts +++ b/src/shared/translations/locales/en.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const en: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -15,6 +15,10 @@ export const en: PluginDefaultTranslationsObject = { paramFlattenLocales: 'With `locale=all`, set false to keep localized fields as per-locale objects. Default true.', paramLocale: 'Locale to return, or `all` for every locale. See the Localization section in the API description.', paramFallbackLocale: 'Locale to fall back to for missing localized values, or `none` to disable.', + paramValidateLocale: + 'Locales to validate, or `all` for every locale. Repeat the parameter for more than one locale.', + paramComputeHierarchyPaths: + 'Set true to compute the `{{slugPath}}` and `{{titlePath}}` paths. Selecting either field computes them too.', schemaSelect: 'Choose fields to return, e.g. `select[title]=true`. Omit to return all.', schemaPopulate: 'Populate related documents per collection, e.g. `populate[posts][title]=true`.', @@ -23,6 +27,10 @@ export const en: PluginDefaultTranslationsObject = { 'Payload `where` filter. Nest a field then an operator: `where[field][equals]=value`. Combine clauses with the `and` / `or` arrays, e.g. `where[or][0][field][equals]=value`. Each field lists only the operators valid for its type.', schemaSupportedTimezones: 'Supported timezones in IANA format.', schemaPerLocale: 'Per-locale values, returned when `locale=all`.', + schemaHierarchySlugPath: + 'Slug path, e.g. `parent/child`. Computed on read with `computeHierarchyPaths=true` or when selected. Cannot be used in `where`.', + schemaHierarchyTitlePath: + 'Title path, e.g. `Parent/Child`. Computed on read with `computeHierarchyPaths=true` or when selected. Cannot be used in `where`.', collectionList: 'Paginated list of documents', collectionDoc: 'A single document', @@ -33,6 +41,10 @@ export const en: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Bulk delete result', collectionCount: 'Document count', collectionDuplicated: 'The duplicated document', + validateResult: + 'Validation result. Nothing is saved. Invalid field values return `valid: false` with the errors, not an error status.', + validateBody: + 'Document data to validate. On a saved document or a global, the data is merged over the latest draft, or the saved document if there is no draft.', globalDoc: 'The global document', @@ -44,9 +56,13 @@ export const en: PluginDefaultTranslationsObject = { authResetPassword: 'Password reset result', authFirstRegister: 'The first user, created with an auth token', authInit: 'Whether this auth collection has any users yet', - authAccess: 'The current user’s access (permissions) for this collection', + authAccess: 'The current user’s permissions for every collection and global', + docAccess: 'The current user’s permissions for this document', + docAccessBody: + 'Document data to check access against. Without it, Payload uses the saved document, if there is one.', authUnlock: 'Unlock result', authVerify: 'Verification result', + apiKeyReveal: 'The decrypted API key', versionList: 'Paginated list of versions', versionSingle: 'A single version', @@ -68,6 +84,14 @@ export const en: PluginDefaultTranslationsObject = { uploadBody: 'Send `multipart/form-data` to upload a file (a binary `file` part plus a `_payload` part with the JSON-stringified fields), or `application/json` with just the fields when there’s no file.', uploadPayloadField: 'JSON-stringified {{schema}} fields. Example: `{"alt":"A caption"}`.', + fileServe: 'The file', + filePartial: 'Part of the file, for a `Range` request', + uploadInstructionsSummary: 'Get instructions to upload a file before saving the document', + uploadInstructionsResult: + 'Where to send the file bytes, and the `file` value to send with the create or update request', + uploadStagePutSummary: 'Send the file bytes for a staged upload', + uploadStageDeleteSummary: 'Delete a staged upload', + uploadStageResult: 'Done, no content', error400: 'Validation or query error (ValidationError, QueryError)', error401: 'Not authenticated (AuthenticationError)', @@ -88,6 +112,8 @@ export const en: PluginDefaultTranslationsObject = { tagAuth: 'Auth', tagVersions: 'Versions', tagJobs: 'Jobs', + tagUploads: 'Uploads', + tagAccess: 'Access', localizationHeading: 'Localization', localizationNote: diff --git a/src/translations/locales/es.ts b/src/shared/translations/locales/es.ts similarity index 75% rename from src/translations/locales/es.ts rename to src/shared/translations/locales/es.ts index 32da51b..04f2174 100644 --- a/src/translations/locales/es.ts +++ b/src/shared/translations/locales/es.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const es: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const es: PluginDefaultTranslationsObject = { paramLocale: 'Idioma a devolver, o `all` para todos los idiomas. Consulta la sección de Localización en la descripción de la API.', paramFallbackLocale: 'Idioma de reserva para los valores localizados que falten, o `none` para desactivarlo.', + paramValidateLocale: + 'Idiomas a validar, o `all` para todos los idiomas. Repite el parámetro para más de un idioma.', + paramComputeHierarchyPaths: + 'Usa true para calcular las rutas `{{slugPath}}` y `{{titlePath}}`. Seleccionar cualquiera de los dos campos también las calcula.', schemaSelect: 'Elige los campos a devolver, p. ej. `select[title]=true`. Omítelo para devolver todos.', schemaPopulate: 'Pobla documentos relacionados por colección, p. ej. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const es: PluginDefaultTranslationsObject = { 'Filtro `where` de Payload. Anida un campo y luego un operador: `where[field][equals]=value`. Combina cláusulas con los arrays `and` / `or`, p. ej. `where[or][0][field][equals]=value`. Cada campo lista únicamente los operadores válidos para su tipo.', schemaSupportedTimezones: 'Zonas horarias admitidas en formato IANA.', schemaPerLocale: 'Valores por idioma, devueltos cuando `locale=all`.', + schemaHierarchySlugPath: + 'Ruta de slugs, p. ej. `parent/child`. Se calcula al leer con `computeHierarchyPaths=true` o al seleccionarla. No se puede usar en `where`.', + schemaHierarchyTitlePath: + 'Ruta de títulos, p. ej. `Parent/Child`. Se calcula al leer con `computeHierarchyPaths=true` o al seleccionarla. No se puede usar en `where`.', collectionList: 'Lista paginada de documentos', collectionDoc: 'Un único documento', @@ -35,6 +43,10 @@ export const es: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultado de la eliminación masiva', collectionCount: 'Recuento de documentos', collectionDuplicated: 'El documento duplicado', + validateResult: + 'Resultado de la validación. No se guarda nada. Los valores de campo no válidos devuelven `valid: false` con los errores, no un estado de error.', + validateBody: + 'Datos del documento a validar. En un documento guardado o un global, los datos se combinan sobre el último borrador, o sobre el documento guardado si no hay borrador.', globalDoc: 'El documento global', @@ -46,9 +58,13 @@ export const es: PluginDefaultTranslationsObject = { authResetPassword: 'Resultado del restablecimiento de contraseña', authFirstRegister: 'El primer usuario, creado con un token de autenticación', authInit: 'Si esta colección de autenticación ya tiene algún usuario', - authAccess: 'El acceso (permisos) del usuario actual para esta colección', + authAccess: 'Los permisos del usuario actual para todas las colecciones y globales', + docAccess: 'Los permisos del usuario actual para este documento', + docAccessBody: + 'Datos del documento con los que se comprueba el acceso. Sin ellos, Payload usa el documento guardado, si existe.', authUnlock: 'Resultado del desbloqueo', authVerify: 'Resultado de la verificación', + apiKeyReveal: 'La clave de API descifrada', versionList: 'Lista paginada de versiones', versionSingle: 'Una única versión', @@ -70,6 +86,14 @@ export const es: PluginDefaultTranslationsObject = { uploadBody: 'Envía `multipart/form-data` para subir un archivo (una parte binaria `file` más una parte `_payload` con los campos serializados como JSON), o `application/json` con solo los campos cuando no hay archivo.', uploadPayloadField: 'Campos {{schema}} serializados como JSON. Ejemplo: `{"alt":"A caption"}`.', + fileServe: 'El archivo', + filePartial: 'Una parte del archivo, para una petición `Range`', + uploadInstructionsSummary: 'Obtener instrucciones para subir un archivo antes de guardar el documento', + uploadInstructionsResult: + 'Dónde enviar los bytes del archivo y el valor `file` que se envía con la petición de creación o actualización', + uploadStagePutSummary: 'Enviar los bytes del archivo para una subida temporal', + uploadStageDeleteSummary: 'Eliminar una subida temporal', + uploadStageResult: 'Hecho, sin contenido', error400: 'Error de validación o de consulta (ValidationError, QueryError)', error401: 'No autenticado (AuthenticationError)', @@ -91,6 +115,8 @@ export const es: PluginDefaultTranslationsObject = { tagAuth: 'Autenticación', tagVersions: 'Versiones', tagJobs: 'Trabajos', + tagUploads: 'Subidas', + tagAccess: 'Acceso', localizationHeading: 'Localización', localizationNote: diff --git a/src/translations/locales/et.ts b/src/shared/translations/locales/et.ts similarity index 75% rename from src/translations/locales/et.ts rename to src/shared/translations/locales/et.ts index 89d5533..174de0e 100644 --- a/src/translations/locales/et.ts +++ b/src/shared/translations/locales/et.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const et: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const et: PluginDefaultTranslationsObject = { paramLocale: 'Tagastatav lokaat või `all` kõigi lokaatide jaoks. Vaata API kirjelduses jaotist Lokaliseerimine.', paramFallbackLocale: 'Lokaat, millele puuduvate lokaliseeritud väärtuste korral tagasi pöörduda, või `none` selle keelamiseks.', + paramValidateLocale: + 'Valideeritavad lokaadid või `all` kõigi lokaatide jaoks. Mitme lokaadi jaoks korda parameetrit.', + paramComputeHierarchyPaths: + 'Määra true, et arvutada teed `{{slugPath}}` ja `{{titlePath}}`. Kumbagi välja valimine arvutab need samuti.', schemaSelect: 'Vali tagastatavad väljad, nt `select[title]=true`. Kõigi tagastamiseks jäta tühjaks.', schemaPopulate: 'Täida seotud dokumendid kollektsiooni kaupa, nt `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const et: PluginDefaultTranslationsObject = { 'Payloadi `where` filter. Pesasta väli ja seejärel operaator: `where[field][equals]=value`. Kombineeri klausleid massiividega `and` / `or`, nt `where[or][0][field][equals]=value`. Iga väli loetleb ainult tema tüübi jaoks sobivad operaatorid.', schemaSupportedTimezones: 'Toetatud ajavööndid IANA vormingus.', schemaPerLocale: 'Lokaadipõhised väärtused, tagastatakse väärtusega `locale=all`.', + schemaHierarchySlugPath: + 'Slugide tee, nt `parent/child`. Arvutatakse lugemisel koos `computeHierarchyPaths=true` või valimisel. Ei saa kasutada `where` sees.', + schemaHierarchyTitlePath: + 'Pealkirjade tee, nt `Parent/Child`. Arvutatakse lugemisel koos `computeHierarchyPaths=true` või valimisel. Ei saa kasutada `where` sees.', collectionList: 'Lehekülgedeks jaotatud dokumentide loend', collectionDoc: 'Üksik dokument', @@ -36,6 +44,10 @@ export const et: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Hulgikustutuse tulemus', collectionCount: 'Dokumentide arv', collectionDuplicated: 'Dubleeritud dokument', + validateResult: + 'Valideerimise tulemus. Midagi ei salvestata. Kehtetud väljaväärtused tagastavad `valid: false` koos vigadega, mitte veastaatuse.', + validateBody: + 'Valideeritavad dokumendi andmed. Salvestatud dokumendi või globaalse dokumendi puhul liidetakse andmed viimase mustandi peale või mustandi puudumisel salvestatud dokumendi peale.', globalDoc: 'Globaalne dokument', @@ -47,9 +59,13 @@ export const et: PluginDefaultTranslationsObject = { authResetPassword: 'Parooli lähtestamise tulemus', authFirstRegister: 'Esimene kasutaja, loodud koos autentimistokeniga', authInit: 'Kas sellel autentimiskollektsioonil on juba kasutajaid', - authAccess: 'Praeguse kasutaja juurdepääs (õigused) selle kollektsiooni jaoks', + authAccess: 'Praeguse kasutaja õigused kõigi kollektsioonide ja globaalide jaoks', + docAccess: 'Praeguse kasutaja õigused selle dokumendi jaoks', + docAccessBody: + 'Dokumendi andmed, mille alusel õigusi kontrollitakse. Ilma nendeta kasutab Payload salvestatud dokumenti, kui see on olemas.', authUnlock: 'Avamise tulemus', authVerify: 'Kinnitamise tulemus', + apiKeyReveal: 'Dekrüpteeritud API-võti', versionList: 'Lehekülgedeks jaotatud versioonide loend', versionSingle: 'Üksik versioon', @@ -71,6 +87,14 @@ export const et: PluginDefaultTranslationsObject = { uploadBody: 'Faili üleslaadimiseks saada `multipart/form-data` (binaarne `file` osa pluss `_payload` osa JSON-stringitud väljadega) või `application/json` ainult väljadega, kui faili pole.', uploadPayloadField: 'JSON-stringitud {{schema}} väljad. Näide: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Fail', + filePartial: 'Osa failist `Range` päringu jaoks', + uploadInstructionsSummary: 'Hangi juhised faili üleslaadimiseks enne dokumendi salvestamist', + uploadInstructionsResult: + 'Kuhu saata faili baidid ja milline `file` väärtus saata loomise või uuendamise päringuga', + uploadStagePutSummary: 'Saada faili baidid ajutise üleslaadimise jaoks', + uploadStageDeleteSummary: 'Kustuta ajutine üleslaadimine', + uploadStageResult: 'Valmis, sisu puudub', error400: 'Valideerimis- või päringuviga (ValidationError, QueryError)', error401: 'Autentimata (AuthenticationError)', @@ -92,6 +116,8 @@ export const et: PluginDefaultTranslationsObject = { tagAuth: 'Autentimine', tagVersions: 'Versioonid', tagJobs: 'Tööd', + tagUploads: 'Üleslaadimised', + tagAccess: 'Juurdepääs', localizationHeading: 'Lokaliseerimine', localizationNote: diff --git a/src/translations/locales/fa.ts b/src/shared/translations/locales/fa.ts similarity index 75% rename from src/translations/locales/fa.ts rename to src/shared/translations/locales/fa.ts index c7ae80c..09c3397 100644 --- a/src/translations/locales/fa.ts +++ b/src/shared/translations/locales/fa.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const fa: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const fa: PluginDefaultTranslationsObject = { 'زبانی که باید بازگردانده شود، یا `all` برای همه زبان‌ها. به بخش بومی‌سازی (Localization) در توضیحات API مراجعه کنید.', paramFallbackLocale: 'زبانی که در صورت نبودن مقادیر محلی‌سازی‌شده به آن بازگشت داده شود، یا `none` برای غیرفعال کردن.', + paramValidateLocale: + 'زبان‌هایی که باید اعتبارسنجی شوند، یا `all` برای همه زبان‌ها. برای بیش از یک زبان، پارامتر را تکرار کنید.', + paramComputeHierarchyPaths: + 'برای محاسبه مسیرهای `{{slugPath}}` و `{{titlePath}}` مقدار true بدهید. انتخاب هر یک از این فیلدها هم آن‌ها را محاسبه می‌کند.', schemaSelect: 'فیلدهایی که باید بازگردانده شوند را انتخاب کنید، مثلاً `select[title]=true`. برای بازگرداندن همه، حذفش کنید.', @@ -27,6 +31,10 @@ export const fa: PluginDefaultTranslationsObject = { 'فیلتر `where` در Payload. ابتدا یک فیلد و سپس یک عملگر را تو در تو بنویسید: `where[field][equals]=value`. شرط‌ها را با آرایه‌های `and` / `or` ترکیب کنید، مثلاً `where[or][0][field][equals]=value`. هر فیلد فقط عملگرهای معتبر برای نوع خود را فهرست می‌کند.', schemaSupportedTimezones: 'منطقه‌های زمانی پشتیبانی‌شده با قالب IANA.', schemaPerLocale: 'مقادیر هر زبان، که هنگام `locale=all` بازگردانده می‌شوند.', + schemaHierarchySlugPath: + 'مسیر اسلاگ، مثلاً `parent/child`. هنگام خواندن با `computeHierarchyPaths=true` یا در صورت انتخاب محاسبه می‌شود. در `where` قابل استفاده نیست.', + schemaHierarchyTitlePath: + 'مسیر عنوان، مثلاً `Parent/Child`. هنگام خواندن با `computeHierarchyPaths=true` یا در صورت انتخاب محاسبه می‌شود. در `where` قابل استفاده نیست.', collectionList: 'فهرست صفحه‌بندی‌شده اسناد', collectionDoc: 'یک سند واحد', @@ -37,6 +45,10 @@ export const fa: PluginDefaultTranslationsObject = { collectionBulkDelete: 'نتیجه حذف گروهی', collectionCount: 'تعداد اسناد', collectionDuplicated: 'سند تکثیرشده', + validateResult: + 'نتیجه اعتبارسنجی. چیزی ذخیره نمی‌شود. مقادیر نامعتبر فیلدها به‌جای وضعیت خطا، `valid: false` را همراه با خطاها برمی‌گردانند.', + validateBody: + 'داده‌های سند برای اعتبارسنجی. در یک سند ذخیره‌شده یا سند سراسری (global)، داده‌ها روی آخرین پیش‌نویس ادغام می‌شوند، و اگر پیش‌نویسی نباشد روی سند ذخیره‌شده.', globalDoc: 'سند سراسری (global)', @@ -48,9 +60,13 @@ export const fa: PluginDefaultTranslationsObject = { authResetPassword: 'نتیجه بازنشانی رمز عبور', authFirstRegister: 'نخستین کاربر، که همراه با یک توکن احراز هویت ایجاد شد', authInit: 'اینکه آیا این مجموعه احراز هویت تاکنون کاربری دارد یا خیر', - authAccess: 'دسترسی (مجوزهای) کاربر فعلی برای این مجموعه', + authAccess: 'مجوزهای کاربر فعلی برای همه مجموعه‌ها و سراسری‌ها', + docAccess: 'مجوزهای کاربر فعلی برای این سند', + docAccessBody: + 'داده‌های سند برای بررسی دسترسی. بدون آن، Payload از سند ذخیره‌شده استفاده می‌کند، اگر وجود داشته باشد.', authUnlock: 'نتیجه باز کردن قفل', authVerify: 'نتیجه تأیید', + apiKeyReveal: 'کلید API رمزگشایی‌شده', versionList: 'فهرست صفحه‌بندی‌شده نسخه‌ها', versionSingle: 'یک نسخه واحد', @@ -72,6 +88,13 @@ export const fa: PluginDefaultTranslationsObject = { uploadBody: 'برای بارگذاری یک فایل، `multipart/form-data` ارسال کنید (یک بخش باینری `file` به‌علاوه یک بخش `_payload` حاوی فیلدهای به‌صورت JSON رشته‌شده)، یا وقتی فایلی وجود ندارد، `application/json` فقط با فیلدها ارسال کنید.', uploadPayloadField: 'فیلدهای {{schema}} به‌صورت JSON رشته‌شده. مثال: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'فایل', + filePartial: 'بخشی از فایل، برای درخواست `Range`', + uploadInstructionsSummary: 'دریافت دستورالعمل بارگذاری فایل پیش از ذخیره سند', + uploadInstructionsResult: 'مقصد ارسال بایت‌های فایل و مقدار `file` که همراه درخواست ایجاد یا به‌روزرسانی ارسال می‌شود', + uploadStagePutSummary: 'ارسال بایت‌های فایل برای بارگذاری موقت', + uploadStageDeleteSummary: 'حذف بارگذاری موقت', + uploadStageResult: 'انجام شد، بدون محتوا', error400: 'خطای اعتبارسنجی یا پرس‌وجو (ValidationError, QueryError)', error401: 'احراز هویت نشده (AuthenticationError)', @@ -93,6 +116,8 @@ export const fa: PluginDefaultTranslationsObject = { tagAuth: 'احراز هویت', tagVersions: 'نسخه‌ها', tagJobs: 'کارها', + tagUploads: 'بارگذاری‌ها', + tagAccess: 'دسترسی', localizationHeading: 'بومی‌سازی', localizationNote: diff --git a/src/translations/locales/fr.ts b/src/shared/translations/locales/fr.ts similarity index 75% rename from src/translations/locales/fr.ts rename to src/shared/translations/locales/fr.ts index d20e2cf..dcb3524 100644 --- a/src/translations/locales/fr.ts +++ b/src/shared/translations/locales/fr.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const fr: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const fr: PluginDefaultTranslationsObject = { paramLocale: "Locale à renvoyer, ou `all` pour toutes les locales. Voir la section Localisation dans la description de l'API.", paramFallbackLocale: 'Locale de repli pour les valeurs localisées manquantes, ou `none` pour désactiver.', + paramValidateLocale: + "Locales à valider, ou `all` pour toutes les locales. Répétez le paramètre pour plus d'une locale.", + paramComputeHierarchyPaths: + 'Mettre à true pour calculer les chemins `{{slugPath}}` et `{{titlePath}}`. Sélectionner l’un de ces champs les calcule aussi.', schemaSelect: 'Choisissez les champs à renvoyer, par ex. `select[title]=true`. Omettez pour tout renvoyer.', schemaPopulate: 'Peuple les documents liés par collection, par ex. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const fr: PluginDefaultTranslationsObject = { 'Filtre `where` de Payload. Imbriquez un champ puis un opérateur : `where[field][equals]=value`. Combinez les clauses avec les tableaux `and` / `or`, par ex. `where[or][0][field][equals]=value`. Chaque champ ne liste que les opérateurs valides pour son type.', schemaSupportedTimezones: 'Fuseaux horaires pris en charge au format IANA.', schemaPerLocale: 'Valeurs par locale, renvoyées lorsque `locale=all`.', + schemaHierarchySlugPath: + 'Chemin de slugs, p. ex. `parent/child`. Calculé à la lecture avec `computeHierarchyPaths=true` ou s’il est sélectionné. Inutilisable dans `where`.', + schemaHierarchyTitlePath: + 'Chemin de titres, p. ex. `Parent/Child`. Calculé à la lecture avec `computeHierarchyPaths=true` ou s’il est sélectionné. Inutilisable dans `where`.', collectionList: 'Liste paginée de documents', collectionDoc: 'Un document unique', @@ -36,6 +44,10 @@ export const fr: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Résultat de la suppression groupée', collectionCount: 'Nombre de documents', collectionDuplicated: 'Le document dupliqué', + validateResult: + "Résultat de la validation. Rien n'est enregistré. Les valeurs de champ invalides renvoient `valid: false` avec les erreurs, et non un statut d'erreur.", + validateBody: + "Données du document à valider. Sur un document enregistré ou un global, les données sont fusionnées sur le dernier brouillon, ou sur le document enregistré s'il n'y a pas de brouillon.", globalDoc: 'Le document global', @@ -47,9 +59,13 @@ export const fr: PluginDefaultTranslationsObject = { authResetPassword: 'Résultat de la réinitialisation du mot de passe', authFirstRegister: "Le premier utilisateur, créé avec un token d'authentification", authInit: "Indique si cette collection d'authentification possède déjà des utilisateurs", - authAccess: "Les accès (permissions) de l'utilisateur actuel pour cette collection", + authAccess: "Les permissions de l'utilisateur actuel pour toutes les collections et tous les globaux", + docAccess: "Les permissions de l'utilisateur actuel pour ce document", + docAccessBody: + "Données du document sur lesquelles vérifier l'accès. Sans elles, Payload utilise le document enregistré, s'il existe.", authUnlock: 'Résultat du déverrouillage', authVerify: 'Résultat de la vérification', + apiKeyReveal: "La clé d'API déchiffrée", versionList: 'Liste paginée de versions', versionSingle: 'Une version unique', @@ -71,6 +87,14 @@ export const fr: PluginDefaultTranslationsObject = { uploadBody: "Envoyez `multipart/form-data` pour téléverser un fichier (une partie binaire `file` plus une partie `_payload` contenant les champs au format JSON), ou `application/json` avec uniquement les champs en l'absence de fichier.", uploadPayloadField: 'Champs {{schema}} au format JSON. Exemple : `{"alt":"A caption"}`.', + fileServe: 'Le fichier', + filePartial: 'Une partie du fichier, pour une requête `Range`', + uploadInstructionsSummary: "Obtenir les instructions pour téléverser un fichier avant d'enregistrer le document", + uploadInstructionsResult: + 'Où envoyer les octets du fichier, et la valeur `file` à envoyer avec la requête de création ou de mise à jour', + uploadStagePutSummary: 'Envoyer les octets du fichier pour un téléversement temporaire', + uploadStageDeleteSummary: 'Supprimer un téléversement temporaire', + uploadStageResult: 'Terminé, aucun contenu', error400: 'Erreur de validation ou de requête (ValidationError, QueryError)', error401: 'Non authentifié (AuthenticationError)', @@ -92,6 +116,8 @@ export const fr: PluginDefaultTranslationsObject = { tagAuth: 'Authentification', tagVersions: 'Versions', tagJobs: 'Jobs', + tagUploads: 'Téléversements', + tagAccess: 'Accès', localizationHeading: 'Localisation', localizationNote: diff --git a/src/translations/locales/he.ts b/src/shared/translations/locales/he.ts similarity index 75% rename from src/translations/locales/he.ts rename to src/shared/translations/locales/he.ts index 084c491..3aa58f8 100644 --- a/src/translations/locales/he.ts +++ b/src/shared/translations/locales/he.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const he: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -15,6 +15,9 @@ export const he: PluginDefaultTranslationsObject = { paramFlattenLocales: 'עם `locale=all`, הגדר false כדי לשמור שדות מתורגמים כאובייקטים לכל לוקאל. ברירת המחדל true.', paramLocale: 'הלוקאל להחזרה, או `all` עבור כל הלוקאלים. ראה את סעיף הלוקליזציה בתיאור ה-API.', paramFallbackLocale: 'לוקאל לחזרה אחורה עבור ערכים מתורגמים חסרים, או `none` כדי להשבית.', + paramValidateLocale: 'לוקאלים לאימות, או `all` עבור כל הלוקאלים. חזור על הפרמטר עבור יותר מלוקאל אחד.', + paramComputeHierarchyPaths: + 'הגדירו true כדי לחשב את הנתיבים `{{slugPath}}` ו-`{{titlePath}}`. בחירה של אחד מהשדות מחשבת אותם גם כן.', schemaSelect: 'בחר אילו שדות להחזיר, למשל `select[title]=true`. השמט כדי להחזיר את הכל.', schemaPopulate: 'אכלוס מסמכים מקושרים לכל אוסף, למשל `populate[posts][title]=true`.', @@ -23,6 +26,10 @@ export const he: PluginDefaultTranslationsObject = { 'מסנן `where` של Payload. קנן שדה ואחריו אופרטור: `where[field][equals]=value`. שלב תנאים באמצעות המערכים `and` / `or`, למשל `where[or][0][field][equals]=value`. כל שדה מפרט רק את האופרטורים התקפים לסוג שלו.', schemaSupportedTimezones: 'אזורי זמן נתמכים בפורמט IANA.', schemaPerLocale: 'ערכים לכל לוקאל, מוחזרים כאשר `locale=all`.', + schemaHierarchySlugPath: + 'נתיב slug, למשל `parent/child`. מחושב בקריאה עם `computeHierarchyPaths=true` או כשהשדה נבחר. לא ניתן להשתמש בו ב-`where`.', + schemaHierarchyTitlePath: + 'נתיב כותרות, למשל `Parent/Child`. מחושב בקריאה עם `computeHierarchyPaths=true` או כשהשדה נבחר. לא ניתן להשתמש בו ב-`where`.', collectionList: 'רשימת מסמכים עם עימוד', collectionDoc: 'מסמך יחיד', @@ -33,6 +40,10 @@ export const he: PluginDefaultTranslationsObject = { collectionBulkDelete: 'תוצאת מחיקה מרובה', collectionCount: 'מספר מסמכים', collectionDuplicated: 'המסמך המשוכפל', + validateResult: + 'תוצאת האימות. דבר אינו נשמר. ערכי שדות לא תקינים מחזירים `valid: false` עם השגיאות, ולא סטטוס שגיאה.', + validateBody: + 'נתוני המסמך לאימות. במסמך שמור או במסמך גלובלי, הנתונים ממוזגים על הטיוטה האחרונה, או על המסמך השמור אם אין טיוטה.', globalDoc: 'המסמך הגלובלי', @@ -44,9 +55,12 @@ export const he: PluginDefaultTranslationsObject = { authResetPassword: 'תוצאת איפוס סיסמה', authFirstRegister: 'המשתמש הראשון, נוצר עם טוקן אימות', authInit: 'האם לאוסף אימות זה כבר יש משתמשים', - authAccess: 'הגישה (ההרשאות) של המשתמש הנוכחי לאוסף זה', + authAccess: 'ההרשאות של המשתמש הנוכחי לכל האוספים והגלובליים', + docAccess: 'ההרשאות של המשתמש הנוכחי למסמך זה', + docAccessBody: 'נתוני המסמך שמולם נבדקת הגישה. בלעדיהם Payload משתמש במסמך השמור, אם יש כזה.', authUnlock: 'תוצאת שחרור נעילה', authVerify: 'תוצאת אימות', + apiKeyReveal: 'מפתח ה-API המפוענח', versionList: 'רשימת גרסאות עם עימוד', versionSingle: 'גרסה יחידה', @@ -68,6 +82,13 @@ export const he: PluginDefaultTranslationsObject = { uploadBody: 'שלח `multipart/form-data` כדי להעלות קובץ (חלק `file` בינארי בתוספת חלק `_payload` עם השדות מקודדים כ-JSON), או `application/json` עם השדות בלבד כאשר אין קובץ.', uploadPayloadField: 'שדות {{schema}} מקודדים כ-JSON. דוגמה: `{"alt":"A caption"}`.', + fileServe: 'הקובץ', + filePartial: 'חלק מהקובץ, עבור בקשת `Range`', + uploadInstructionsSummary: 'קבלת הוראות להעלאת קובץ לפני שמירת המסמך', + uploadInstructionsResult: 'לאן לשלוח את בתי הקובץ, וערך `file` לשליחה עם בקשת היצירה או העדכון', + uploadStagePutSummary: 'שליחת בתי הקובץ להעלאה זמנית', + uploadStageDeleteSummary: 'מחיקת העלאה זמנית', + uploadStageResult: 'בוצע, ללא תוכן', error400: 'שגיאת אימות או שאילתה (ValidationError, QueryError)', error401: 'לא מאומת (AuthenticationError)', @@ -88,6 +109,8 @@ export const he: PluginDefaultTranslationsObject = { tagAuth: 'אימות', tagVersions: 'גרסאות', tagJobs: 'משימות', + tagUploads: 'העלאות', + tagAccess: 'גישה', localizationHeading: 'לוקליזציה', localizationNote: diff --git a/src/translations/locales/hr.ts b/src/shared/translations/locales/hr.ts similarity index 75% rename from src/translations/locales/hr.ts rename to src/shared/translations/locales/hr.ts index 0b0e6c9..0092561 100644 --- a/src/translations/locales/hr.ts +++ b/src/shared/translations/locales/hr.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const hr: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const hr: PluginDefaultTranslationsObject = { paramLocale: 'Lokalizacija koja se vraća ili `all` za svaku lokalizaciju. Pogledajte odjeljak Lokalizacija u opisu API-ja.', paramFallbackLocale: 'Lokalizacija na koju se vraća za vrijednosti koje nedostaju ili `none` za onemogućavanje.', + paramValidateLocale: + 'Lokalizacije za provjeru ili `all` za svaku lokalizaciju. Ponovite parametar za više od jedne lokalizacije.', + paramComputeHierarchyPaths: + 'Postavite true za izračun putanja `{{slugPath}}` i `{{titlePath}}`. Odabir bilo kojeg od tih polja također ih izračunava.', schemaSelect: 'Odaberite polja koja se vraćaju, npr. `select[title]=true`. Izostavite za vraćanje svih.', schemaPopulate: 'Popunite povezane dokumente po kolekciji, npr. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const hr: PluginDefaultTranslationsObject = { 'Payload `where` filtar. Ugnijezdite polje pa operator: `where[field][equals]=value`. Kombinirajte uvjete pomoću nizova `and` / `or`, npr. `where[or][0][field][equals]=value`. Svako polje navodi samo operatore valjane za svoj tip.', schemaSupportedTimezones: 'Podržane vremenske zone u IANA formatu.', schemaPerLocale: 'Vrijednosti po lokalizaciji, vraćaju se uz `locale=all`.', + schemaHierarchySlugPath: + 'Putanja slugova, npr. `parent/child`. Izračunava se pri čitanju s `computeHierarchyPaths=true` ili kad je odabrana. Ne može se koristiti u `where`.', + schemaHierarchyTitlePath: + 'Putanja naslova, npr. `Parent/Child`. Izračunava se pri čitanju s `computeHierarchyPaths=true` ili kad je odabrana. Ne može se koristiti u `where`.', collectionList: 'Paginirani popis dokumenata', collectionDoc: 'Jedan dokument', @@ -36,6 +44,10 @@ export const hr: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Rezultat skupnog brisanja', collectionCount: 'Broj dokumenata', collectionDuplicated: 'Duplicirani dokument', + validateResult: + 'Rezultat provjere. Ništa se ne sprema. Nevažeće vrijednosti polja vraćaju `valid: false` s pogreškama, a ne status pogreške.', + validateBody: + 'Podaci dokumenta za provjeru. Na spremljenom dokumentu ili globalnom dokumentu podaci se spajaju preko najnovije skice, ili spremljenog dokumenta ako skice nema.', globalDoc: 'Globalni dokument', @@ -47,9 +59,13 @@ export const hr: PluginDefaultTranslationsObject = { authResetPassword: 'Rezultat poništavanja lozinke', authFirstRegister: 'Prvi korisnik, stvoren s tokenom za autentikaciju', authInit: 'Ima li ova autentikacijska kolekcija već korisnike', - authAccess: 'Pristup (dozvole) trenutnog korisnika za ovu kolekciju', + authAccess: 'Dozvole trenutnog korisnika za sve kolekcije i globale', + docAccess: 'Dozvole trenutnog korisnika za ovaj dokument', + docAccessBody: + 'Podaci dokumenta prema kojima se provjerava pristup. Bez njih Payload koristi spremljeni dokument, ako postoji.', authUnlock: 'Rezultat otključavanja', authVerify: 'Rezultat verifikacije', + apiKeyReveal: 'Dešifrirani API ključ', versionList: 'Paginirani popis verzija', versionSingle: 'Jedna verzija', @@ -71,6 +87,14 @@ export const hr: PluginDefaultTranslationsObject = { uploadBody: 'Pošaljite `multipart/form-data` za prijenos datoteke (binarni dio `file` plus dio `_payload` s poljima pretvorenima u JSON niz) ili `application/json` samo s poljima kada nema datoteke.', uploadPayloadField: 'Polja {{schema}} pretvorena u JSON niz. Primjer: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Datoteka', + filePartial: 'Dio datoteke za zahtjev `Range`', + uploadInstructionsSummary: 'Dohvati upute za prijenos datoteke prije spremanja dokumenta', + uploadInstructionsResult: + 'Kamo poslati bajtove datoteke i vrijednost `file` koja se šalje sa zahtjevom za stvaranje ili ažuriranje', + uploadStagePutSummary: 'Pošalji bajtove datoteke za privremeni prijenos', + uploadStageDeleteSummary: 'Izbriši privremeni prijenos', + uploadStageResult: 'Gotovo, bez sadržaja', error400: 'Pogreška valjanosti ili upita (ValidationError, QueryError)', error401: 'Nije autenticirano (AuthenticationError)', @@ -92,6 +116,8 @@ export const hr: PluginDefaultTranslationsObject = { tagAuth: 'Autentikacija', tagVersions: 'Verzije', tagJobs: 'Poslovi', + tagUploads: 'Prijenosi', + tagAccess: 'Pristup', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/translations/locales/hu.ts b/src/shared/translations/locales/hu.ts similarity index 75% rename from src/translations/locales/hu.ts rename to src/shared/translations/locales/hu.ts index 2c30323..849e76f 100644 --- a/src/translations/locales/hu.ts +++ b/src/shared/translations/locales/hu.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const hu: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,10 @@ export const hu: PluginDefaultTranslationsObject = { 'A `locale=all` esetén állítsd false értékre, hogy a lokalizált mezők lokálonkénti objektumokként maradjanak meg. Alapértelmezetten true.', paramLocale: 'A visszaadandó lokál, vagy `all` az összes lokálhoz. Lásd az API leírásban a Lokalizáció szakaszt.', paramFallbackLocale: 'A hiányzó lokalizált értékekhez használt tartalék lokál, vagy `none` a letiltáshoz.', + paramValidateLocale: + 'Az ellenőrzendő lokálok, vagy `all` az összes lokálhoz. Több lokálhoz ismételje meg a paramétert.', + paramComputeHierarchyPaths: + 'Állítsa true értékre a(z) `{{slugPath}}` és `{{titlePath}}` útvonalak kiszámításához. Bármelyik mező kiválasztása is kiszámítja őket.', schemaSelect: 'Válaszd ki a visszaadandó mezőket, pl. `select[title]=true`. Hagyd üresen az összes visszaadásához.', schemaPopulate: 'Kapcsolódó dokumentumok betöltése gyűjteményenként, pl. `populate[posts][title]=true`.', @@ -24,6 +28,10 @@ export const hu: PluginDefaultTranslationsObject = { 'Payload `where` szűrő. Ágyazz be egy mezőt, majd egy operátort: `where[field][equals]=value`. A feltételeket az `and` / `or` tömbökkel kombinálhatod, pl. `where[or][0][field][equals]=value`. Minden mezőnél csak az adott típushoz érvényes operátorok szerepelnek.', schemaSupportedTimezones: 'Támogatott időzónák IANA formátumban.', schemaPerLocale: 'Lokálonkénti értékek, amelyeket a `locale=all` esetén ad vissza.', + schemaHierarchySlugPath: + 'Slug útvonal, pl. `parent/child`. Olvasáskor számolódik `computeHierarchyPaths=true` esetén vagy ha ki van választva. A `where` feltételben nem használható.', + schemaHierarchyTitlePath: + 'Cím útvonal, pl. `Parent/Child`. Olvasáskor számolódik `computeHierarchyPaths=true` esetén vagy ha ki van választva. A `where` feltételben nem használható.', collectionList: 'Dokumentumok lapozott listája', collectionDoc: 'Egyetlen dokumentum', @@ -34,6 +42,10 @@ export const hu: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Tömeges törlés eredménye', collectionCount: 'Dokumentumok száma', collectionDuplicated: 'A duplikált dokumentum', + validateResult: + 'Az ellenőrzés eredménye. Semmi sem kerül mentésre. Az érvénytelen mezőértékek `valid: false` értéket adnak vissza a hibákkal, nem hibaállapotot.', + validateBody: + 'Az ellenőrizendő dokumentumadatok. Mentett dokumentum vagy globális dokumentum esetén az adatok a legutóbbi vázlatra, vázlat hiányában a mentett dokumentumra egyesülnek.', globalDoc: 'A globális dokumentum', @@ -45,9 +57,13 @@ export const hu: PluginDefaultTranslationsObject = { authResetPassword: 'Jelszó-visszaállítás eredménye', authFirstRegister: 'Az első felhasználó, hitelesítési tokennel létrehozva', authInit: 'Hogy ennek a hitelesítési gyűjteménynek van-e már bármilyen felhasználója', - authAccess: 'Az aktuális felhasználó hozzáférése (jogosultságai) ehhez a gyűjteményhez', + authAccess: 'Az aktuális felhasználó jogosultságai az összes gyűjteményhez és globálishoz', + docAccess: 'Az aktuális felhasználó jogosultságai ehhez a dokumentumhoz', + docAccessBody: + 'Dokumentumadatok, amelyek alapján a hozzáférés ellenőrizve van. Nélküle a Payload a mentett dokumentumot használja, ha van.', authUnlock: 'Feloldás eredménye', authVerify: 'Ellenőrzés eredménye', + apiKeyReveal: 'A visszafejtett API-kulcs', versionList: 'Verziók lapozott listája', versionSingle: 'Egyetlen verzió', @@ -69,6 +85,14 @@ export const hu: PluginDefaultTranslationsObject = { uploadBody: 'Küldj `multipart/form-data` formátumot fájl feltöltéséhez (egy bináris `file` rész, valamint egy `_payload` rész a JSON-stringgé alakított mezőkkel), vagy `application/json` formátumot csak a mezőkkel, ha nincs fájl.', uploadPayloadField: 'JSON-stringgé alakított {{schema}} mezők. Példa: `{"alt":"A caption"}`.', + fileServe: 'A fájl', + filePartial: 'A fájl egy része `Range` kéréshez', + uploadInstructionsSummary: 'Utasítások lekérése fájl feltöltéséhez a dokumentum mentése előtt', + uploadInstructionsResult: + 'Hová kell küldeni a fájl bájtjait, és milyen `file` értéket kell küldeni a létrehozási vagy frissítési kéréssel', + uploadStagePutSummary: 'A fájl bájtjainak küldése ideiglenes feltöltéshez', + uploadStageDeleteSummary: 'Ideiglenes feltöltés törlése', + uploadStageResult: 'Kész, nincs tartalom', error400: 'Érvényesítési vagy lekérdezési hiba (ValidationError, QueryError)', error401: 'Nincs hitelesítve (AuthenticationError)', @@ -90,6 +114,8 @@ export const hu: PluginDefaultTranslationsObject = { tagAuth: 'Hitelesítés', tagVersions: 'Verziók', tagJobs: 'Feladatok', + tagUploads: 'Feltöltések', + tagAccess: 'Hozzáférés', localizationHeading: 'Lokalizáció', localizationNote: diff --git a/src/translations/locales/hy.ts b/src/shared/translations/locales/hy.ts similarity index 74% rename from src/translations/locales/hy.ts rename to src/shared/translations/locales/hy.ts index b8cf836..7ebd679 100644 --- a/src/translations/locales/hy.ts +++ b/src/shared/translations/locales/hy.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const hy: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -19,6 +19,10 @@ export const hy: PluginDefaultTranslationsObject = { 'Վերադարձվող locale-ը, կամ `all`՝ բոլոր locale-ների համար։ Տես API-ի նկարագրության Տեղայնացման բաժինը։', paramFallbackLocale: 'Locale, որին վերադառնալ բացակայող տեղայնացված արժեքների դեպքում, կամ `none`՝ անջատելու համար։', + paramValidateLocale: + 'Վավերացվող locale-ները, կամ `all`՝ բոլոր locale-ների համար։ Մեկից ավելի locale-ի համար կրկնեք պարամետրը։', + paramComputeHierarchyPaths: + 'Նշեք true՝ `{{slugPath}}` և `{{titlePath}}` ուղիները հաշվարկելու համար։ Դաշտերից որևէ մեկի ընտրությունը նույնպես հաշվարկում է դրանք։', schemaSelect: 'Ընտրեք վերադարձվող դաշտերը, օր. `select[title]=true`։ Բաց թողեք՝ բոլորը վերադարձնելու համար։', schemaPopulate: 'Ներառել կապակցված փաստաթղթերն ըստ կոլեկցիայի, օր. `populate[posts][title]=true`։', @@ -27,6 +31,10 @@ export const hy: PluginDefaultTranslationsObject = { 'Payload-ի `where` ֆիլտր։ Ներդրեք դաշտ, ապա օպերատոր. `where[field][equals]=value`։ Համատեղեք պայմանները `and` / `or` զանգվածներով, օր. `where[or][0][field][equals]=value`։ Յուրաքանչյուր դաշտ ցուցադրում է միայն իր տիպին վավեր օպերատորները։', schemaSupportedTimezones: 'Աջակցվող ժամային գոտիները IANA ձևաչափով։', schemaPerLocale: 'Ըստ-locale-ի արժեքներ, վերադարձվում են `locale=all`-ի դեպքում։', + schemaHierarchySlugPath: + 'Slug ուղի, օր.՝ `parent/child`։ Հաշվարկվում է կարդալիս `computeHierarchyPaths=true`-ով կամ ընտրելիս։ Չի կարող օգտագործվել `where`-ում։', + schemaHierarchyTitlePath: + 'Վերնագրերի ուղի, օր.՝ `Parent/Child`։ Հաշվարկվում է կարդալիս `computeHierarchyPaths=true`-ով կամ ընտրելիս։ Չի կարող օգտագործվել `where`-ում։', collectionList: 'Փաստաթղթերի էջավորված ցանկ', collectionDoc: 'Մեկ փաստաթուղթ', @@ -37,6 +45,10 @@ export const hy: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Զանգվածային ջնջման արդյունք', collectionCount: 'Փաստաթղթերի քանակ', collectionDuplicated: 'Կրկնօրինակված փաստաթուղթը', + validateResult: + 'Վավերացման արդյունքը։ Ոչինչ չի պահպանվում։ Դաշտերի անվավեր արժեքների դեպքում վերադարձվում է `valid: false`՝ սխալներով, ոչ թե սխալի կարգավիճակ։', + validateBody: + 'Վավերացվող փաստաթղթի տվյալները։ Պահպանված փաստաթղթի կամ գլոբալ փաստաթղթի դեպքում տվյալները միավորվում են վերջին սևագրի վրա, իսկ եթե սևագիր չկա՝ պահպանված փաստաթղթի վրա։', globalDoc: 'Գլոբալ փաստաթուղթը', @@ -48,9 +60,13 @@ export const hy: PluginDefaultTranslationsObject = { authResetPassword: 'Գաղտնաբառի վերականգնման արդյունք', authFirstRegister: 'Առաջին օգտատերը, ստեղծված auth token-ով', authInit: 'Արդյոք այս auth կոլեկցիան արդեն ունի օգտատերեր', - authAccess: 'Ընթացիկ օգտատիրոջ մուտքի իրավունքները (թույլտվությունները) այս կոլեկցիայի համար', + authAccess: 'Ընթացիկ օգտատիրոջ թույլտվությունները բոլոր կոլեկցիաների և գլոբալների համար', + docAccess: 'Ընթացիկ օգտատիրոջ թույլտվությունները այս փաստաթղթի համար', + docAccessBody: + 'Փաստաթղթի տվյալներ, որոնց համեմատ ստուգվում է մուտքը։ Առանց դրանց Payload-ը օգտագործում է պահպանված փաստաթուղթը, եթե այն կա։', authUnlock: 'Ապակողպման արդյունք', authVerify: 'Հաստատման արդյունք', + apiKeyReveal: 'Վերծանված API բանալին', versionList: 'Տարբերակների էջավորված ցանկ', versionSingle: 'Մեկ տարբերակ', @@ -72,6 +88,14 @@ export const hy: PluginDefaultTranslationsObject = { uploadBody: 'Ֆայլ վերբեռնելու համար ուղարկեք `multipart/form-data` (երկուական `file` մաս և `_payload` մաս՝ JSON-ի տողի վերածված դաշտերով), կամ `application/json`՝ միայն դաշտերով, երբ ֆայլ չկա։', uploadPayloadField: 'JSON-ի տողի վերածված {{schema}} դաշտեր։ Օրինակ՝ `{"alt":"A caption"}`։', + fileServe: 'Ֆայլը', + filePartial: 'Ֆայլի մի մասը՝ `Range` հարցման համար', + uploadInstructionsSummary: 'Ստանալ ֆայլի վերբեռնման հրահանգներ մինչև փաստաթղթի պահպանումը', + uploadInstructionsResult: + 'Որտեղ ուղարկել ֆայլի բայթերը և `file` արժեքը, որն ուղարկվում է ստեղծման կամ թարմացման հարցման հետ', + uploadStagePutSummary: 'Ուղարկել ֆայլի բայթերը ժամանակավոր վերբեռնման համար', + uploadStageDeleteSummary: 'Ջնջել ժամանակավոր վերբեռնումը', + uploadStageResult: 'Պատրաստ է, առանց բովանդակության', error400: 'Վավերացման կամ հարցման սխալ (ValidationError, QueryError)', error401: 'Չնույնականացված (AuthenticationError)', @@ -93,6 +117,8 @@ export const hy: PluginDefaultTranslationsObject = { tagAuth: 'Նույնականացում', tagVersions: 'Տարբերակներ', tagJobs: 'Առաջադրանքներ', + tagUploads: 'Վերբեռնումներ', + tagAccess: 'Մուտք', localizationHeading: 'Տեղայնացում', localizationNote: diff --git a/src/translations/locales/id.ts b/src/shared/translations/locales/id.ts similarity index 75% rename from src/translations/locales/id.ts rename to src/shared/translations/locales/id.ts index 7a3ba53..1db1790 100644 --- a/src/translations/locales/id.ts +++ b/src/shared/translations/locales/id.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const id: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const id: PluginDefaultTranslationsObject = { paramLocale: 'Lokal yang akan dikembalikan, atau `all` untuk setiap lokal. Lihat bagian Localization pada deskripsi API.', paramFallbackLocale: 'Lokal cadangan untuk nilai terlokalisasi yang hilang, atau `none` untuk menonaktifkan.', + paramValidateLocale: + 'Lokal yang akan divalidasi, atau `all` untuk setiap lokal. Ulangi parameter untuk lebih dari satu lokal.', + paramComputeHierarchyPaths: + 'Atur ke true untuk menghitung jalur `{{slugPath}}` dan `{{titlePath}}`. Memilih salah satu field tersebut juga menghitungnya.', schemaSelect: 'Pilih field yang akan dikembalikan, mis. `select[title]=true`. Kosongkan untuk mengembalikan semua.', schemaPopulate: 'Mengisi dokumen terkait per koleksi, mis. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const id: PluginDefaultTranslationsObject = { 'Filter `where` Payload. Bersarangkan sebuah field lalu sebuah operator: `where[field][equals]=value`. Gabungkan klausa dengan array `and` / `or`, mis. `where[or][0][field][equals]=value`. Setiap field hanya mencantumkan operator yang valid untuk tipenya.', schemaSupportedTimezones: 'Zona waktu yang didukung dalam format IANA.', schemaPerLocale: 'Nilai per-lokal, dikembalikan saat `locale=all`.', + schemaHierarchySlugPath: + 'Jalur slug, mis. `parent/child`. Dihitung saat membaca dengan `computeHierarchyPaths=true` atau saat dipilih. Tidak dapat dipakai di `where`.', + schemaHierarchyTitlePath: + 'Jalur judul, mis. `Parent/Child`. Dihitung saat membaca dengan `computeHierarchyPaths=true` atau saat dipilih. Tidak dapat dipakai di `where`.', collectionList: 'Daftar dokumen berpaginasi', collectionDoc: 'Sebuah dokumen tunggal', @@ -36,6 +44,10 @@ export const id: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Hasil penghapusan massal', collectionCount: 'Jumlah dokumen', collectionDuplicated: 'Dokumen hasil duplikasi', + validateResult: + 'Hasil validasi. Tidak ada yang disimpan. Nilai kolom yang tidak valid mengembalikan `valid: false` beserta kesalahannya, bukan status error.', + validateBody: + 'Data dokumen yang akan divalidasi. Pada dokumen yang tersimpan atau global, data digabungkan di atas draf terbaru, atau dokumen yang tersimpan jika tidak ada draf.', globalDoc: 'Dokumen global', @@ -47,9 +59,12 @@ export const id: PluginDefaultTranslationsObject = { authResetPassword: 'Hasil pengaturan ulang kata sandi', authFirstRegister: 'Pengguna pertama, dibuat dengan token autentikasi', authInit: 'Apakah koleksi autentikasi ini sudah memiliki pengguna', - authAccess: 'Akses (izin) pengguna saat ini untuk koleksi ini', + authAccess: 'Izin pengguna saat ini untuk semua koleksi dan global', + docAccess: 'Izin pengguna saat ini untuk dokumen ini', + docAccessBody: 'Data dokumen untuk memeriksa akses. Tanpanya, Payload memakai dokumen yang tersimpan, jika ada.', authUnlock: 'Hasil pembukaan kunci', authVerify: 'Hasil verifikasi', + apiKeyReveal: 'Kunci API yang sudah didekripsi', versionList: 'Daftar versi berpaginasi', versionSingle: 'Sebuah versi tunggal', @@ -71,6 +86,14 @@ export const id: PluginDefaultTranslationsObject = { uploadBody: 'Kirim `multipart/form-data` untuk mengunggah file (sebuah bagian biner `file` ditambah sebuah bagian `_payload` berisi field yang sudah di-JSON-stringify), atau `application/json` hanya dengan field saja jika tidak ada file.', uploadPayloadField: 'Field {{schema}} yang sudah di-JSON-stringify. Contoh: `{"alt":"A caption"}`.', + fileServe: 'File', + filePartial: 'Sebagian file, untuk permintaan `Range`', + uploadInstructionsSummary: 'Dapatkan instruksi untuk mengunggah file sebelum menyimpan dokumen', + uploadInstructionsResult: + 'Ke mana byte file dikirim, dan nilai `file` yang dikirim bersama permintaan buat atau perbarui', + uploadStagePutSummary: 'Kirim byte file untuk unggahan sementara', + uploadStageDeleteSummary: 'Hapus unggahan sementara', + uploadStageResult: 'Selesai, tanpa konten', error400: 'Galat validasi atau kueri (ValidationError, QueryError)', error401: 'Tidak terautentikasi (AuthenticationError)', @@ -92,6 +115,8 @@ export const id: PluginDefaultTranslationsObject = { tagAuth: 'Autentikasi', tagVersions: 'Versi', tagJobs: 'Job', + tagUploads: 'Unggahan', + tagAccess: 'Akses', localizationHeading: 'Localization', localizationNote: diff --git a/src/translations/locales/is.ts b/src/shared/translations/locales/is.ts similarity index 74% rename from src/translations/locales/is.ts rename to src/shared/translations/locales/is.ts index 2802c56..0bb4b85 100644 --- a/src/translations/locales/is.ts +++ b/src/shared/translations/locales/is.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const is: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const is: PluginDefaultTranslationsObject = { paramLocale: 'Tungumál til að skila, eða `all` fyrir öll tungumál. Sjá Staðfærsla-kaflann í API-lýsingunni.', paramFallbackLocale: 'Tungumál til að nota til vara fyrir staðfærð gildi sem vantar, eða `none` til að slökkva á því.', + paramValidateLocale: + 'Tungumál sem á að sannreyna, eða `all` fyrir öll tungumál. Endurtaktu færibreytuna fyrir fleiri en eitt tungumál.', + paramComputeHierarchyPaths: + 'Stilltu á true til að reikna slóðirnar `{{slugPath}}` og `{{titlePath}}`. Að velja annað hvort svæðið reiknar þær líka.', schemaSelect: 'Veldu reiti til að skila, t.d. `select[title]=true`. Slepptu til að skila öllum.', schemaPopulate: 'Birta tengd skjöl eftir safni, t.d. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const is: PluginDefaultTranslationsObject = { 'Payload `where`-sía. Hreiðraðu reit og svo virkja: `where[field][equals]=value`. Sameinaðu skilyrði með `and` / `or` fylkjunum, t.d. `where[or][0][field][equals]=value`. Hver reitur tilgreinir aðeins þá virkja sem gilda fyrir gerð hans.', schemaSupportedTimezones: 'Studd tímabelti á IANA-sniði.', schemaPerLocale: 'Gildi eftir tungumáli, skilað þegar `locale=all`.', + schemaHierarchySlugPath: + 'Slóð úr slug-gildum, t.d. `parent/child`. Reiknuð við lestur með `computeHierarchyPaths=true` eða þegar hún er valin. Ekki hægt að nota í `where`.', + schemaHierarchyTitlePath: + 'Slóð úr titlum, t.d. `Parent/Child`. Reiknuð við lestur með `computeHierarchyPaths=true` eða þegar hún er valin. Ekki hægt að nota í `where`.', collectionList: 'Síðuskiptur listi yfir skjöl', collectionDoc: 'Eitt skjal', @@ -35,6 +43,10 @@ export const is: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Niðurstaða magneyðingar', collectionCount: 'Fjöldi skjala', collectionDuplicated: 'Afritaða skjalið', + validateResult: + 'Niðurstaða sannprófunar. Ekkert er vistað. Ógild reitagildi skila `valid: false` með villunum, ekki villustöðu.', + validateBody: + 'Skjalagögn sem á að sannreyna. Á vistuðu skjali eða algildu skjali eru gögnin sameinuð yfir nýjustu drög, eða vistaða skjalið ef engin drög eru til.', globalDoc: 'Algilda skjalið', @@ -46,9 +58,13 @@ export const is: PluginDefaultTranslationsObject = { authResetPassword: 'Niðurstaða endurstillingar lykilorðs', authFirstRegister: 'Fyrsti notandinn, stofnaður með auðkennistóka', authInit: 'Hvort þetta auðkenningarsafn hafi þegar einhverja notendur', - authAccess: 'Aðgangur (heimildir) núverandi notanda fyrir þetta safn', + authAccess: 'Heimildir núverandi notanda fyrir öll söfn og algild gögn', + docAccess: 'Heimildir núverandi notanda fyrir þetta skjal', + docAccessBody: + 'Skjalagögn sem aðgangur er athugaður út frá. Án þeirra notar Payload vistaða skjalið, ef það er til.', authUnlock: 'Niðurstaða aflæsingar', authVerify: 'Niðurstaða staðfestingar', + apiKeyReveal: 'Afkóðaði API-lykillinn', versionList: 'Síðuskiptur listi yfir útgáfur', versionSingle: 'Ein útgáfa', @@ -70,6 +86,14 @@ export const is: PluginDefaultTranslationsObject = { uploadBody: 'Sendu `multipart/form-data` til að hlaða upp skrá (tvíundarhluta `file` ásamt `_payload`-hluta með reitunum sem JSON-strengur), eða `application/json` með aðeins reitunum þegar engin skrá er til staðar.', uploadPayloadField: '{{schema}}-reitir sem JSON-strengur. Dæmi: `{"alt":"A caption"}`.', + fileServe: 'Skráin', + filePartial: 'Hluti skrárinnar, fyrir `Range`-beiðni', + uploadInstructionsSummary: 'Sækja leiðbeiningar til að hlaða upp skrá áður en skjalið er vistað', + uploadInstructionsResult: + 'Hvert á að senda bæti skrárinnar og `file`-gildið sem er sent með stofnunar- eða uppfærslubeiðninni', + uploadStagePutSummary: 'Senda bæti skrárinnar fyrir tímabundna upphleðslu', + uploadStageDeleteSummary: 'Eyða tímabundinni upphleðslu', + uploadStageResult: 'Lokið, ekkert efni', error400: 'Staðfestingar- eða fyrirspurnarvilla (ValidationError, QueryError)', error401: 'Ekki auðkenndur (AuthenticationError)', @@ -91,6 +115,8 @@ export const is: PluginDefaultTranslationsObject = { tagAuth: 'Auðkenning', tagVersions: 'Útgáfur', tagJobs: 'Verk', + tagUploads: 'Upphleðslur', + tagAccess: 'Aðgangur', localizationHeading: 'Staðfærsla', localizationNote: diff --git a/src/translations/locales/it.ts b/src/shared/translations/locales/it.ts similarity index 75% rename from src/translations/locales/it.ts rename to src/shared/translations/locales/it.ts index 0fd93b3..97421fa 100644 --- a/src/translations/locales/it.ts +++ b/src/shared/translations/locales/it.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const it: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const it: PluginDefaultTranslationsObject = { paramLocale: "Lingua da restituire, oppure `all` per tutte le lingue. Vedere la sezione Localizzazione nella descrizione dell'API.", paramFallbackLocale: 'Lingua di ripiego per i valori localizzati mancanti, oppure `none` per disabilitarla.', + paramValidateLocale: + 'Lingue da validare, oppure `all` per tutte le lingue. Ripetere il parametro per più di una lingua.', + paramComputeHierarchyPaths: + 'Imposta true per calcolare i percorsi `{{slugPath}}` e `{{titlePath}}`. Anche selezionare uno dei due campi li calcola.', schemaSelect: 'Scegliere i campi da restituire, ad es. `select[title]=true`. Omettere per restituirli tutti.', schemaPopulate: 'Popola i documenti correlati per ciascuna collezione, ad es. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const it: PluginDefaultTranslationsObject = { 'Filtro `where` di Payload. Annidare un campo e poi un operatore: `where[field][equals]=value`. Combinare le clausole con gli array `and` / `or`, ad es. `where[or][0][field][equals]=value`. Ogni campo elenca solo gli operatori validi per il suo tipo.', schemaSupportedTimezones: 'Fusi orari supportati in formato IANA.', schemaPerLocale: 'Valori per singola lingua, restituiti quando `locale=all`.', + schemaHierarchySlugPath: + 'Percorso di slug, es. `parent/child`. Calcolato in lettura con `computeHierarchyPaths=true` o quando selezionato. Non utilizzabile in `where`.', + schemaHierarchyTitlePath: + 'Percorso di titoli, es. `Parent/Child`. Calcolato in lettura con `computeHierarchyPaths=true` o quando selezionato. Non utilizzabile in `where`.', collectionList: 'Elenco paginato di documenti', collectionDoc: 'Un singolo documento', @@ -36,6 +44,10 @@ export const it: PluginDefaultTranslationsObject = { collectionBulkDelete: "Risultato dell'eliminazione di massa", collectionCount: 'Conteggio dei documenti', collectionDuplicated: 'Il documento duplicato', + validateResult: + 'Risultato della validazione. Non viene salvato nulla. I valori di campo non validi restituiscono `valid: false` con gli errori, non uno stato di errore.', + validateBody: + "Dati del documento da validare. Su un documento salvato o un global, i dati vengono uniti all'ultima bozza, oppure al documento salvato se non c'è una bozza.", globalDoc: 'Il documento globale', @@ -47,9 +59,13 @@ export const it: PluginDefaultTranslationsObject = { authResetPassword: 'Risultato della reimpostazione della password', authFirstRegister: 'Il primo utente, creato con un token di autenticazione', authInit: 'Indica se questa collezione di autenticazione ha già degli utenti', - authAccess: "L'accesso (i permessi) dell'utente corrente per questa collezione", + authAccess: "I permessi dell'utente corrente per tutte le collezioni e i globali", + docAccess: "I permessi dell'utente corrente per questo documento", + docAccessBody: + "Dati del documento su cui verificare l'accesso. Senza, Payload usa il documento salvato, se esiste.", authUnlock: 'Risultato dello sblocco', authVerify: 'Risultato della verifica', + apiKeyReveal: 'La chiave API decifrata', versionList: 'Elenco paginato di versioni', versionSingle: 'Una singola versione', @@ -71,6 +87,14 @@ export const it: PluginDefaultTranslationsObject = { uploadBody: "Inviare `multipart/form-data` per caricare un file (una parte binaria `file` più una parte `_payload` con i campi in formato JSON), oppure `application/json` con i soli campi quando non c'è alcun file.", uploadPayloadField: 'Campi {{schema}} in formato JSON. Esempio: `{"alt":"A caption"}`.', + fileServe: 'Il file', + filePartial: 'Una parte del file, per una richiesta `Range`', + uploadInstructionsSummary: 'Ottiene le istruzioni per caricare un file prima di salvare il documento', + uploadInstructionsResult: + 'Dove inviare i byte del file e il valore `file` da inviare con la richiesta di creazione o aggiornamento', + uploadStagePutSummary: 'Invia i byte del file per un caricamento temporaneo', + uploadStageDeleteSummary: 'Elimina un caricamento temporaneo', + uploadStageResult: 'Fatto, nessun contenuto', error400: 'Errore di validazione o di query (ValidationError, QueryError)', error401: 'Non autenticato (AuthenticationError)', @@ -92,6 +116,8 @@ export const it: PluginDefaultTranslationsObject = { tagAuth: 'Autenticazione', tagVersions: 'Versioni', tagJobs: 'Job', + tagUploads: 'Caricamenti', + tagAccess: 'Accesso', localizationHeading: 'Localizzazione', localizationNote: diff --git a/src/translations/locales/ja.ts b/src/shared/translations/locales/ja.ts similarity index 75% rename from src/translations/locales/ja.ts rename to src/shared/translations/locales/ja.ts index 2b043c3..fbdd097 100644 --- a/src/translations/locales/ja.ts +++ b/src/shared/translations/locales/ja.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ja: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const ja: PluginDefaultTranslationsObject = { '返すロケール。すべてのロケールを取得するには `all` を指定します。API 説明のローカライズのセクションを参照してください。', paramFallbackLocale: 'ローカライズされた値が欠落している場合にフォールバックするロケール。無効にするには `none` を指定します。', + paramValidateLocale: + '検証するロケール。すべてのロケールを対象にするには `all` を指定します。複数のロケールを指定するにはパラメーターを繰り返します。', + paramComputeHierarchyPaths: + 'true にすると `{{slugPath}}` と `{{titlePath}}` のパスを計算します。どちらかのフィールドを選択した場合も計算されます。', schemaSelect: '返すフィールドを選択します。例: `select[title]=true`。省略するとすべて返します。', schemaPopulate: 'コレクションごとに関連ドキュメントを展開します。例: `populate[posts][title]=true`。', @@ -26,6 +30,10 @@ export const ja: PluginDefaultTranslationsObject = { 'Payload の `where` フィルター。フィールドの次に演算子をネストします: `where[field][equals]=value`。`and` / `or` 配列で条件を組み合わせます。例: `where[or][0][field][equals]=value`。各フィールドにはその型で有効な演算子のみが列挙されます。', schemaSupportedTimezones: 'IANA 形式でサポートされているタイムゾーン。', schemaPerLocale: '`locale=all` のときに返される、ロケールごとの値。', + schemaHierarchySlugPath: + 'スラッグのパス(例: `parent/child`)。`computeHierarchyPaths=true` を付けて読み取るか、選択したときに計算されます。`where` では使えません。', + schemaHierarchyTitlePath: + 'タイトルのパス(例: `Parent/Child`)。`computeHierarchyPaths=true` を付けて読み取るか、選択したときに計算されます。`where` では使えません。', collectionList: 'ドキュメントのページネーション付き一覧', collectionDoc: '単一のドキュメント', @@ -36,6 +44,10 @@ export const ja: PluginDefaultTranslationsObject = { collectionBulkDelete: '一括削除の結果', collectionCount: 'ドキュメント数', collectionDuplicated: '複製されたドキュメント', + validateResult: + '検証結果。何も保存されません。無効なフィールド値は、エラーステータスではなく、エラー内容を含む `valid: false` を返します。', + validateBody: + '検証するドキュメントデータ。保存済みのドキュメントまたはグローバルでは、データは最新の下書きに、下書きがなければ保存済みのドキュメントにマージされます。', globalDoc: 'グローバルドキュメント', @@ -47,9 +59,13 @@ export const ja: PluginDefaultTranslationsObject = { authResetPassword: 'パスワードリセットの結果', authFirstRegister: '認証トークン付きで作成された最初のユーザー', authInit: 'この認証コレクションにすでにユーザーが存在するかどうか', - authAccess: 'このコレクションに対する現在のユーザーのアクセス権 (パーミッション)', + authAccess: 'すべてのコレクションとグローバルに対する現在のユーザーのパーミッション', + docAccess: 'このドキュメントに対する現在のユーザーのパーミッション', + docAccessBody: + 'アクセスを確認するドキュメントデータ。省略すると、Payload は保存済みのドキュメントがあればそれを使います。', authUnlock: 'ロック解除の結果', authVerify: '検証結果', + apiKeyReveal: '復号された API キー', versionList: 'バージョンのページネーション付き一覧', versionSingle: '単一のバージョン', @@ -71,6 +87,13 @@ export const ja: PluginDefaultTranslationsObject = { uploadBody: 'ファイルをアップロードするには `multipart/form-data` を送信します (バイナリの `file` パートと、JSON 文字列化したフィールドを含む `_payload` パート)。ファイルがない場合は、フィールドのみを含む `application/json` を送信します。', uploadPayloadField: 'JSON 文字列化した {{schema}} フィールド。例: `{"alt":"A caption"}`。', + fileServe: 'ファイル', + filePartial: '`Range` リクエストに対するファイルの一部', + uploadInstructionsSummary: 'ドキュメントを保存する前にファイルをアップロードする手順を取得します', + uploadInstructionsResult: 'ファイルのバイトの送信先と、作成または更新リクエストで送信する `file` の値', + uploadStagePutSummary: '一時アップロードにファイルのバイトを送信します', + uploadStageDeleteSummary: '一時アップロードを削除します', + uploadStageResult: '完了 (コンテンツなし)', error400: '検証エラーまたはクエリエラー (ValidationError, QueryError)', error401: '認証されていません (AuthenticationError)', @@ -92,6 +115,8 @@ export const ja: PluginDefaultTranslationsObject = { tagAuth: '認証', tagVersions: 'バージョン', tagJobs: 'ジョブ', + tagUploads: 'アップロード', + tagAccess: 'アクセス', localizationHeading: 'ローカライズ', localizationNote: diff --git a/src/translations/locales/ko.ts b/src/shared/translations/locales/ko.ts similarity index 75% rename from src/translations/locales/ko.ts rename to src/shared/translations/locales/ko.ts index d9a5af9..b10d0f3 100644 --- a/src/translations/locales/ko.ts +++ b/src/shared/translations/locales/ko.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ko: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const ko: PluginDefaultTranslationsObject = { paramLocale: '반환할 로케일이며, 모든 로케일을 받으려면 `all`을 사용합니다. API 설명의 Localization 섹션을 참고하세요.', paramFallbackLocale: '현지화된 값이 없을 때 대체할 로케일이며, 비활성화하려면 `none`을 사용합니다.', + paramValidateLocale: + '검증할 로케일이며, 모든 로케일을 검증하려면 `all`을 사용합니다. 로케일이 둘 이상이면 파라미터를 반복합니다.', + paramComputeHierarchyPaths: + 'true로 설정하면 `{{slugPath}}` 및 `{{titlePath}}` 경로를 계산합니다. 두 필드 중 하나를 선택해도 계산됩니다.', schemaSelect: '반환할 필드를 선택합니다(예: `select[title]=true`). 생략하면 모두 반환합니다.', schemaPopulate: '컬렉션별로 관련 문서를 채웁니다(예: `populate[posts][title]=true`).', @@ -25,6 +29,10 @@ export const ko: PluginDefaultTranslationsObject = { 'Payload `where` 필터입니다. 필드 다음에 연산자를 중첩합니다: `where[field][equals]=value`. `and` / `or` 배열로 절을 결합합니다(예: `where[or][0][field][equals]=value`). 각 필드는 해당 타입에 유효한 연산자만 나열합니다.', schemaSupportedTimezones: 'IANA 형식으로 지원되는 시간대입니다.', schemaPerLocale: '`locale=all`일 때 반환되는 로케일별 값입니다.', + schemaHierarchySlugPath: + '슬러그 경로(예: `parent/child`). `computeHierarchyPaths=true`로 읽거나 선택하면 계산됩니다. `where`에서는 사용할 수 없습니다.', + schemaHierarchyTitlePath: + '제목 경로(예: `Parent/Child`). `computeHierarchyPaths=true`로 읽거나 선택하면 계산됩니다. `where`에서는 사용할 수 없습니다.', collectionList: '페이지네이션된 문서 목록', collectionDoc: '단일 문서', @@ -35,6 +43,10 @@ export const ko: PluginDefaultTranslationsObject = { collectionBulkDelete: '일괄 삭제 결과', collectionCount: '문서 개수', collectionDuplicated: '복제된 문서', + validateResult: + '검증 결과. 아무것도 저장되지 않습니다. 유효하지 않은 필드 값은 오류 상태가 아니라 오류 내용과 함께 `valid: false`를 반환합니다.', + validateBody: + '검증할 문서 데이터. 저장된 문서나 글로벌에서는 데이터가 최신 초안 위에, 초안이 없으면 저장된 문서 위에 병합됩니다.', globalDoc: '글로벌 문서', @@ -46,9 +58,12 @@ export const ko: PluginDefaultTranslationsObject = { authResetPassword: '비밀번호 재설정 결과', authFirstRegister: '인증 토큰과 함께 생성된 첫 번째 사용자', authInit: '이 인증 컬렉션에 사용자가 있는지 여부', - authAccess: '이 컬렉션에 대한 현재 사용자의 접근 권한(권한)', + authAccess: '모든 컬렉션과 글로벌에 대한 현재 사용자의 권한', + docAccess: '이 문서에 대한 현재 사용자의 권한', + docAccessBody: '접근 권한을 확인할 문서 데이터. 없으면 Payload는 저장된 문서가 있을 경우 그것을 사용합니다.', authUnlock: '잠금 해제 결과', authVerify: '인증 확인 결과', + apiKeyReveal: '복호화된 API 키', versionList: '페이지네이션된 버전 목록', versionSingle: '단일 버전', @@ -70,6 +85,13 @@ export const ko: PluginDefaultTranslationsObject = { uploadBody: '파일을 업로드하려면 `multipart/form-data`를 전송하고(바이너리 `file` 파트와 JSON 문자열로 변환된 필드가 담긴 `_payload` 파트), 파일이 없으면 필드만 담은 `application/json`을 전송합니다.', uploadPayloadField: 'JSON 문자열로 변환된 {{schema}} 필드입니다. 예: `{"alt":"A caption"}`.', + fileServe: '파일', + filePartial: '`Range` 요청에 대한 파일의 일부', + uploadInstructionsSummary: '문서를 저장하기 전에 파일 업로드 안내를 받습니다', + uploadInstructionsResult: '파일 바이트를 보낼 위치와 생성 또는 수정 요청에 함께 보낼 `file` 값', + uploadStagePutSummary: '임시 업로드에 파일 바이트를 보냅니다', + uploadStageDeleteSummary: '임시 업로드를 삭제합니다', + uploadStageResult: '완료, 콘텐츠 없음', error400: '유효성 검사 또는 쿼리 오류 (ValidationError, QueryError)', error401: '인증되지 않음 (AuthenticationError)', @@ -91,6 +113,8 @@ export const ko: PluginDefaultTranslationsObject = { tagAuth: '인증', tagVersions: '버전', tagJobs: '작업', + tagUploads: '업로드', + tagAccess: '접근', localizationHeading: '현지화', localizationNote: diff --git a/src/translations/locales/lt.ts b/src/shared/translations/locales/lt.ts similarity index 76% rename from src/translations/locales/lt.ts rename to src/shared/translations/locales/lt.ts index 10c4019..2c3219d 100644 --- a/src/translations/locales/lt.ts +++ b/src/shared/translations/locales/lt.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const lt: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,9 @@ export const lt: PluginDefaultTranslationsObject = { 'Su `locale=all` nustatykite false, kad lokalizuoti laukai liktų kaip atskirų lokalių objektai. Numatytoji reikšmė true.', paramLocale: 'Lokalė, kurią grąžinti, arba `all` visoms lokalėms. Žr. lokalizacijos skyrių API aprašyme.', paramFallbackLocale: 'Atsarginė lokalė trūkstamoms lokalizuotoms reikšmėms, arba `none` norint išjungti.', + paramValidateLocale: 'Tikrintinos lokalės arba `all` visoms lokalėms. Kelioms lokalėms kartokite parametrą.', + paramComputeHierarchyPaths: + 'Nustatykite true, kad būtų apskaičiuoti keliai `{{slugPath}}` ir `{{titlePath}}`. Pasirinkus bet kurį iš šių laukų jie taip pat apskaičiuojami.', schemaSelect: 'Pasirinkite grąžintinus laukus, pvz. `select[title]=true`. Praleiskite, kad būtų grąžinti visi.', schemaPopulate: 'Užpildykite susijusius dokumentus pagal kolekciją, pvz. `populate[posts][title]=true`.', @@ -25,6 +28,10 @@ export const lt: PluginDefaultTranslationsObject = { 'Payload `where` filtras. Įdėkite lauką, tada operatorių: `where[field][equals]=value`. Sąlygas derinkite naudodami `and` / `or` masyvus, pvz. `where[or][0][field][equals]=value`. Kiekvienas laukas nurodo tik jo tipui tinkamus operatorius.', schemaSupportedTimezones: 'Palaikomos laiko juostos IANA formatu.', schemaPerLocale: 'Atskirų lokalių reikšmės, grąžinamos kai naudojama `locale=all`.', + schemaHierarchySlugPath: + 'Slug kelias, pvz., `parent/child`. Apskaičiuojamas skaitant su `computeHierarchyPaths=true` arba kai pasirinktas. Negalima naudoti `where`.', + schemaHierarchyTitlePath: + 'Pavadinimų kelias, pvz., `Parent/Child`. Apskaičiuojamas skaitant su `computeHierarchyPaths=true` arba kai pasirinktas. Negalima naudoti `where`.', collectionList: 'Puslapiuojamas dokumentų sąrašas', collectionDoc: 'Vienas dokumentas', @@ -35,6 +42,10 @@ export const lt: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Masinio ištrynimo rezultatas', collectionCount: 'Dokumentų skaičius', collectionDuplicated: 'Dublikuotas dokumentas', + validateResult: + 'Patikros rezultatas. Niekas neišsaugoma. Netinkamos laukų reikšmės grąžina `valid: false` su klaidomis, o ne klaidos būseną.', + validateBody: + 'Tikrintini dokumento duomenys. Išsaugotame dokumente ar globaliame dokumente duomenys sujungiami su naujausiu juodraščiu, o jei juodraščio nėra – su išsaugotu dokumentu.', globalDoc: 'Globalus dokumentas', @@ -46,9 +57,13 @@ export const lt: PluginDefaultTranslationsObject = { authResetPassword: 'Slaptažodžio atstatymo rezultatas', authFirstRegister: 'Pirmasis naudotojas, sukurtas su autentifikacijos tokenu', authInit: 'Ar ši autentifikacijos kolekcija jau turi naudotojų', - authAccess: 'Dabartinio naudotojo prieiga (teisės) prie šios kolekcijos', + authAccess: 'Dabartinio naudotojo teisės visoms kolekcijoms ir globaliems objektams', + docAccess: 'Dabartinio naudotojo teisės šiam dokumentui', + docAccessBody: + 'Dokumento duomenys, pagal kuriuos tikrinama prieiga. Be jų Payload naudoja išsaugotą dokumentą, jei toks yra.', authUnlock: 'Atrakinimo rezultatas', authVerify: 'Patvirtinimo rezultatas', + apiKeyReveal: 'Iššifruotas API raktas', versionList: 'Puslapiuojamas versijų sąrašas', versionSingle: 'Viena versija', @@ -70,6 +85,14 @@ export const lt: PluginDefaultTranslationsObject = { uploadBody: 'Siųskite `multipart/form-data`, kad įkeltumėte failą (dvejetainė `file` dalis ir `_payload` dalis su JSON eilute pavaizduotais laukais), arba `application/json` tik su laukais, kai failo nėra.', uploadPayloadField: 'JSON eilute pavaizduoti {{schema}} laukai. Pavyzdys: `{"alt":"A caption"}`.', + fileServe: 'Failas', + filePartial: 'Failo dalis `Range` užklausai', + uploadInstructionsSummary: 'Gauti failo įkėlimo instrukcijas prieš išsaugant dokumentą', + uploadInstructionsResult: + 'Kur siųsti failo baitus ir kokią `file` reikšmę siųsti su kūrimo arba atnaujinimo užklausa', + uploadStagePutSummary: 'Siųsti failo baitus laikinam įkėlimui', + uploadStageDeleteSummary: 'Ištrinti laikiną įkėlimą', + uploadStageResult: 'Atlikta, be turinio', error400: 'Patvirtinimo arba užklausos klaida (ValidationError, QueryError)', error401: 'Neautentifikuota (AuthenticationError)', @@ -91,6 +114,8 @@ export const lt: PluginDefaultTranslationsObject = { tagAuth: 'Autentifikacija', tagVersions: 'Versijos', tagJobs: 'Užduotys', + tagUploads: 'Įkėlimai', + tagAccess: 'Prieiga', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/translations/locales/lv.ts b/src/shared/translations/locales/lv.ts similarity index 76% rename from src/translations/locales/lv.ts rename to src/shared/translations/locales/lv.ts index 3158c88..7b4034f 100644 --- a/src/translations/locales/lv.ts +++ b/src/shared/translations/locales/lv.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const lv: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,9 @@ export const lv: PluginDefaultTranslationsObject = { paramLocale: 'Atgriežamā lokāle vai `all`, lai iegūtu visas lokāles. Skatiet sadaļu Lokalizācija API aprakstā.', paramFallbackLocale: 'Lokāle, uz kuru atkāpties trūkstošu lokalizētu vērtību gadījumā, vai `none`, lai to atspējotu.', + paramValidateLocale: 'Pārbaudāmās lokāles vai `all` visām lokālēm. Vairākām lokālēm atkārtojiet parametru.', + paramComputeHierarchyPaths: + 'Iestatiet true, lai aprēķinātu ceļus `{{slugPath}}` un `{{titlePath}}`. Jebkura no šiem laukiem atlase arī tos aprēķina.', schemaSelect: 'Izvēlieties atgriežamos laukus, piemēram, `select[title]=true`. Izlaidiet, lai atgrieztu visus.', schemaPopulate: 'Aizpildiet saistītos dokumentus pa kolekcijām, piemēram, `populate[posts][title]=true`.', @@ -26,6 +29,10 @@ export const lv: PluginDefaultTranslationsObject = { 'Payload `where` filtrs. Ietveriet lauku un pēc tam operatoru: `where[field][equals]=value`. Apvienojiet nosacījumus ar `and` / `or` masīviem, piemēram, `where[or][0][field][equals]=value`. Katrs lauks norāda tikai tā tipam derīgos operatorus.', schemaSupportedTimezones: 'Atbalstītās laika joslas IANA formātā.', schemaPerLocale: 'Atsevišķu lokāļu vērtības, kas tiek atgrieztas, kad izmantots `locale=all`.', + schemaHierarchySlugPath: + 'Slug ceļš, piem., `parent/child`. Aprēķina, lasot ar `computeHierarchyPaths=true` vai kad atlasīts. Nevar izmantot `where`.', + schemaHierarchyTitlePath: + 'Virsrakstu ceļš, piem., `Parent/Child`. Aprēķina, lasot ar `computeHierarchyPaths=true` vai kad atlasīts. Nevar izmantot `where`.', collectionList: 'Lapotais dokumentu saraksts', collectionDoc: 'Atsevišķs dokuments', @@ -36,6 +43,10 @@ export const lv: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Masveida dzēšanas rezultāts', collectionCount: 'Dokumentu skaits', collectionDuplicated: 'Dublētais dokuments', + validateResult: + 'Pārbaudes rezultāts. Nekas netiek saglabāts. Nederīgas lauku vērtības atgriež `valid: false` ar kļūdām, nevis kļūdas statusu.', + validateBody: + 'Pārbaudāmie dokumenta dati. Saglabātam dokumentam vai globālajam dokumentam dati tiek apvienoti ar jaunāko melnrakstu, bet, ja melnraksta nav, ar saglabāto dokumentu.', globalDoc: 'Globālais dokuments', @@ -47,9 +58,13 @@ export const lv: PluginDefaultTranslationsObject = { authResetPassword: 'Paroles atiestatīšanas rezultāts', authFirstRegister: 'Pirmais lietotājs, izveidots ar autentifikācijas talonu', authInit: 'Vai šajā autentifikācijas kolekcijā jau ir kādi lietotāji', - authAccess: 'Pašreizējā lietotāja piekļuve (atļaujas) šai kolekcijai', + authAccess: 'Pašreizējā lietotāja atļaujas visām kolekcijām un globālajiem objektiem', + docAccess: 'Pašreizējā lietotāja atļaujas šim dokumentam', + docAccessBody: + 'Dokumenta dati, pret kuriem pārbauda piekļuvi. Bez tiem Payload izmanto saglabāto dokumentu, ja tāds ir.', authUnlock: 'Atbloķēšanas rezultāts', authVerify: 'Verifikācijas rezultāts', + apiKeyReveal: 'Atšifrētā API atslēga', versionList: 'Lapotais versiju saraksts', versionSingle: 'Atsevišķa versija', @@ -71,6 +86,14 @@ export const lv: PluginDefaultTranslationsObject = { uploadBody: 'Sūtiet `multipart/form-data`, lai augšupielādētu failu (binārā `file` daļa un `_payload` daļa ar JSON virknē pārveidotajiem laukiem), vai `application/json` tikai ar laukiem, ja faila nav.', uploadPayloadField: 'JSON virknē pārveidotie {{schema}} lauki. Piemērs: `{"alt":"A caption"}`.', + fileServe: 'Fails', + filePartial: 'Faila daļa `Range` pieprasījumam', + uploadInstructionsSummary: 'Saņemt norādījumus faila augšupielādei pirms dokumenta saglabāšanas', + uploadInstructionsResult: + 'Kur sūtīt faila baitus un kāda `file` vērtība jāsūta kopā ar izveides vai atjaunināšanas pieprasījumu', + uploadStagePutSummary: 'Sūtīt faila baitus pagaidu augšupielādei', + uploadStageDeleteSummary: 'Dzēst pagaidu augšupielādi', + uploadStageResult: 'Gatavs, bez satura', error400: 'Validācijas vai vaicājuma kļūda (ValidationError, QueryError)', error401: 'Nav autentificēts (AuthenticationError)', @@ -92,6 +115,8 @@ export const lv: PluginDefaultTranslationsObject = { tagAuth: 'Autentifikācija', tagVersions: 'Versijas', tagJobs: 'Darbi', + tagUploads: 'Augšupielādes', + tagAccess: 'Piekļuve', localizationHeading: 'Lokalizācija', localizationNote: diff --git a/src/translations/locales/my.ts b/src/shared/translations/locales/my.ts similarity index 75% rename from src/translations/locales/my.ts rename to src/shared/translations/locales/my.ts index 200a96e..0377768 100644 --- a/src/translations/locales/my.ts +++ b/src/shared/translations/locales/my.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const my: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const my: PluginDefaultTranslationsObject = { '`locale=all` ဖြင့်အသုံးပြုသောအခါ ဒေသန္တရပြုလုပ်ထားသော အကွက်များကို တစ်ဒေသစီ object အဖြစ် ဆက်ထားရန် false သတ်မှတ်ပါ။ မူရင်းတန်ဖိုးမှာ true ဖြစ်သည်။', paramLocale: 'ပြန်ပေးရမည့် ဒေသ၊ သို့မဟုတ် ဒေသအားလုံးအတွက် `all`။ API ဖော်ပြချက်ရှိ Localization အပိုင်းကို ကြည့်ပါ။', paramFallbackLocale: 'ပျောက်ဆုံးနေသော ဒေသန္တရတန်ဖိုးများအတွက် အစားထိုးပြန်လှည့်မည့် ဒေသ၊ သို့မဟုတ် ပိတ်ရန် `none`။', + paramValidateLocale: 'အတည်ပြုစစ်ဆေးမည့် ဒေသများ၊ သို့မဟုတ် ဒေသအားလုံးအတွက် `all`။ ဒေသတစ်ခုထက်ပိုလျှင် parameter ကို ထပ်ရေးပါ။', + paramComputeHierarchyPaths: + '`{{slugPath}}` နှင့် `{{titlePath}}` လမ်းကြောင်းများကို တွက်ချက်ရန် true သတ်မှတ်ပါ။ ထို field တစ်ခုခုကို ရွေးလျှင်လည်း တွက်ချက်သည်။', schemaSelect: 'ပြန်ပေးမည့် အကွက်များကို ရွေးချယ်ပါ၊ ဥပမာ `select[title]=true`။ အားလုံးပြန်ပေးစေရန် ချန်လှပ်ထားပါ။', schemaPopulate: 'collection အလိုက် ဆက်စပ်စာရွက်စာတမ်းများကို ဖြည့်သွင်းသည်၊ ဥပမာ `populate[posts][title]=true`။', @@ -24,6 +27,10 @@ export const my: PluginDefaultTranslationsObject = { 'Payload ၏ `where` စစ်ထုတ်ချက်။ အကွက်တစ်ခုပြီး operator တစ်ခုကို အလွှာဆင့်ထည့်ပါ: `where[field][equals]=value`။ စကားစုများကို `and` / `or` array များဖြင့် ပေါင်းစပ်ပါ၊ ဥပမာ `where[or][0][field][equals]=value`။ အကွက်တစ်ခုစီသည် ၎င်း၏အမျိုးအစားအတွက် မှန်ကန်သော operator များကိုသာ ဖော်ပြသည်။', schemaSupportedTimezones: 'IANA ပုံစံဖြင့် ပံ့ပိုးထားသော အချိန်ဇုန်များ။', schemaPerLocale: '`locale=all` သုံးသောအခါ ပြန်ပေးသော တစ်ဒေသစီ တန်ဖိုးများ။', + schemaHierarchySlugPath: + 'Slug လမ်းကြောင်း၊ ဥပမာ `parent/child`။ `computeHierarchyPaths=true` ဖြင့် ဖတ်သောအခါ သို့မဟုတ် ရွေးထားသောအခါ တွက်ချက်သည်။ `where` တွင် သုံး၍မရပါ။', + schemaHierarchyTitlePath: + 'ခေါင်းစဉ် လမ်းကြောင်း၊ ဥပမာ `Parent/Child`။ `computeHierarchyPaths=true` ဖြင့် ဖတ်သောအခါ သို့မဟုတ် ရွေးထားသောအခါ တွက်ချက်သည်။ `where` တွင် သုံး၍မရပါ။', collectionList: 'စာရွက်စာတမ်းများ၏ စာမျက်နှာခွဲ စာရင်း', collectionDoc: 'စာရွက်စာတမ်းတစ်ခု', @@ -34,6 +41,10 @@ export const my: PluginDefaultTranslationsObject = { collectionBulkDelete: 'အစုလိုက် ဖျက်မှု ရလဒ်', collectionCount: 'စာရွက်စာတမ်း အရေအတွက်', collectionDuplicated: 'ပွားယူထားသော စာရွက်စာတမ်း', + validateResult: + 'အတည်ပြုစစ်ဆေးမှု ရလဒ်။ မည်သည့်အရာမျှ မသိမ်းပါ။ မှားယွင်းသော ကွင်းဒေတာတန်ဖိုးများသည် error status မဟုတ်ဘဲ အမှားများနှင့်အတူ `valid: false` ကို ပြန်ပေးသည်။', + validateBody: + 'အတည်ပြုစစ်ဆေးမည့် စာရွက်စာတမ်းဒေတာ။ သိမ်းထားသော စာရွက်စာတမ်း သို့မဟုတ် global တွင် ဒေတာကို နောက်ဆုံး မူကြမ်းပေါ်တွင် ပေါင်းစပ်ပြီး မူကြမ်းမရှိလျှင် သိမ်းထားသော စာရွက်စာတမ်းပေါ်တွင် ပေါင်းစပ်သည်။', globalDoc: 'global စာရွက်စာတမ်း', @@ -45,9 +56,12 @@ export const my: PluginDefaultTranslationsObject = { authResetPassword: 'စကားဝှက် ပြန်လည်သတ်မှတ်မှု ရလဒ်', authFirstRegister: 'auth token ဖြင့် ဖန်တီးထားသော ပထမဆုံး အသုံးပြုသူ', authInit: 'ဤ auth collection တွင် အသုံးပြုသူ ရှိပြီးဖြစ်မဖြစ်', - authAccess: 'ဤ collection အတွက် လက်ရှိအသုံးပြုသူ၏ ဝင်ရောက်ခွင့် (ခွင့်ပြုချက်များ)', + authAccess: 'collections နှင့် globals အားလုံးအတွက် လက်ရှိအသုံးပြုသူ၏ ခွင့်ပြုချက်များ', + docAccess: 'ဤစာရွက်စာတမ်းအတွက် လက်ရှိအသုံးပြုသူ၏ ခွင့်ပြုချက်များ', + docAccessBody: 'ဝင်ရောက်ခွင့်ကို စစ်ဆေးမည့် စာရွက်စာတမ်းဒေတာ။ မပါပါက Payload သည် သိမ်းထားသော စာရွက်စာတမ်းရှိလျှင် ၎င်းကို အသုံးပြုသည်။', authUnlock: 'သော့ဖွင့်မှု ရလဒ်', authVerify: 'အတည်ပြုမှု ရလဒ်', + apiKeyReveal: 'ကုဒ်ဖြည်ထားသော API key', versionList: 'ဗားရှင်းများ၏ စာမျက်နှာခွဲ စာရင်း', versionSingle: 'ဗားရှင်းတစ်ခု', @@ -69,6 +83,13 @@ export const my: PluginDefaultTranslationsObject = { uploadBody: 'ဖိုင်တစ်ခု အပ်လုဒ်လုပ်ရန် `multipart/form-data` ပေးပို့ပါ (binary `file` အပိုင်းနှင့် JSON-stringified အကွက်များပါသော `_payload` အပိုင်း)၊ သို့မဟုတ် ဖိုင်မရှိသည့်အခါ အကွက်များသာပါသော `application/json` ကို ပေးပို့ပါ။', uploadPayloadField: 'JSON-stringified {{schema}} အကွက်များ။ ဥပမာ: `{"alt":"A caption"}`။', + fileServe: 'ဖိုင်', + filePartial: '`Range` request အတွက် ဖိုင်၏ အစိတ်အပိုင်း', + uploadInstructionsSummary: 'စာရွက်စာတမ်းကို မသိမ်းမီ ဖိုင်အပ်လုဒ်လုပ်ရန် ညွှန်ကြားချက်များ ရယူသည်', + uploadInstructionsResult: 'ဖိုင် bytes ပို့ရမည့်နေရာနှင့် create သို့မဟုတ် update request နှင့်အတူ ပို့ရမည့် `file` တန်ဖိုး', + uploadStagePutSummary: 'ယာယီအပ်လုဒ်အတွက် ဖိုင် bytes ပို့သည်', + uploadStageDeleteSummary: 'ယာယီအပ်လုဒ်ကို ဖျက်သည်', + uploadStageResult: 'ပြီးပါပြီ၊ အကြောင်းအရာ မရှိပါ', error400: 'အတည်ပြုမှု သို့မဟုတ် query အမှား (ValidationError, QueryError)', error401: 'အထောက်အထား မစိစစ်ရသေးပါ (AuthenticationError)', @@ -90,6 +111,8 @@ export const my: PluginDefaultTranslationsObject = { tagAuth: 'Auth', tagVersions: 'Versions', tagJobs: 'Jobs', + tagUploads: 'Uploads', + tagAccess: 'Access', localizationHeading: 'Localization', localizationNote: diff --git a/src/translations/locales/nb.ts b/src/shared/translations/locales/nb.ts similarity index 75% rename from src/translations/locales/nb.ts rename to src/shared/translations/locales/nb.ts index cb47942..b4fd28a 100644 --- a/src/translations/locales/nb.ts +++ b/src/shared/translations/locales/nb.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const nb: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const nb: PluginDefaultTranslationsObject = { paramLocale: 'Locale som skal returneres, eller `all` for alle locales. Se Lokalisering-seksjonen i API-beskrivelsen.', paramFallbackLocale: 'Locale å falle tilbake til for manglende lokaliserte verdier, eller `none` for å deaktivere.', + paramValidateLocale: + 'Locales som skal valideres, eller `all` for alle locales. Gjenta parameteren for mer enn én locale.', + paramComputeHierarchyPaths: + 'Sett til true for å beregne stiene `{{slugPath}}` og `{{titlePath}}`. Å velge ett av feltene beregner dem også.', schemaSelect: 'Velg felt som skal returneres, f.eks. `select[title]=true`. Utelat for å returnere alle.', schemaPopulate: 'Populer relaterte dokumenter per samling, f.eks. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const nb: PluginDefaultTranslationsObject = { 'Payload `where`-filter. Nest et felt og deretter en operator: `where[field][equals]=value`. Kombiner betingelser med `and`- / `or`-arrayene, f.eks. `where[or][0][field][equals]=value`. Hvert felt lister bare operatorene som er gyldige for typen sin.', schemaSupportedTimezones: 'Støttede tidssoner i IANA-format.', schemaPerLocale: 'Verdier per locale, returnert når `locale=all`.', + schemaHierarchySlugPath: + 'Sti av slugs, f.eks. `parent/child`. Beregnes ved lesing med `computeHierarchyPaths=true` eller når feltet velges. Kan ikke brukes i `where`.', + schemaHierarchyTitlePath: + 'Sti av titler, f.eks. `Parent/Child`. Beregnes ved lesing med `computeHierarchyPaths=true` eller når feltet velges. Kan ikke brukes i `where`.', collectionList: 'Paginert liste over dokumenter', collectionDoc: 'Et enkelt dokument', @@ -35,6 +43,10 @@ export const nb: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultat av massesletting', collectionCount: 'Antall dokumenter', collectionDuplicated: 'Det dupliserte dokumentet', + validateResult: + 'Valideringsresultat. Ingenting lagres. Ugyldige feltverdier returnerer `valid: false` med feilene, ikke en feilstatus.', + validateBody: + 'Dokumentdata som skal valideres. På et lagret dokument eller en global flettes dataene oppå det siste utkastet, eller det lagrede dokumentet hvis det ikke finnes noe utkast.', globalDoc: 'Det globale dokumentet', @@ -46,9 +58,13 @@ export const nb: PluginDefaultTranslationsObject = { authResetPassword: 'Resultat av passordtilbakestilling', authFirstRegister: 'Den første brukeren, opprettet med et auth-token', authInit: 'Om denne auth-samlingen har noen brukere ennå', - authAccess: 'Den nåværende brukerens tilgang (tillatelser) for denne samlingen', + authAccess: 'Den nåværende brukerens tillatelser for alle samlinger og globaler', + docAccess: 'Den nåværende brukerens tillatelser for dette dokumentet', + docAccessBody: + 'Dokumentdata som tilgangen sjekkes mot. Uten dem bruker Payload det lagrede dokumentet, hvis det finnes.', authUnlock: 'Resultat av opplåsing', authVerify: 'Verifiseringsresultat', + apiKeyReveal: 'Den dekrypterte API-nøkkelen', versionList: 'Paginert liste over versjoner', versionSingle: 'En enkelt versjon', @@ -70,6 +86,14 @@ export const nb: PluginDefaultTranslationsObject = { uploadBody: 'Send `multipart/form-data` for å laste opp en fil (en binær `file`-del pluss en `_payload`-del med de JSON-strengkodede feltene), eller `application/json` med bare feltene når det ikke finnes noen fil.', uploadPayloadField: 'JSON-strengkodede {{schema}}-felt. Eksempel: `{"alt":"A caption"}`.', + fileServe: 'Filen', + filePartial: 'En del av filen ved en `Range`-forespørsel', + uploadInstructionsSummary: 'Hent instruksjoner for å laste opp en fil før dokumentet lagres', + uploadInstructionsResult: + 'Hvor filens byte skal sendes, og `file`-verdien som skal sendes med opprett- eller oppdater-forespørselen', + uploadStagePutSummary: 'Send filens byte for en midlertidig opplasting', + uploadStageDeleteSummary: 'Slett en midlertidig opplasting', + uploadStageResult: 'Ferdig, uten innhold', error400: 'Validerings- eller spørringsfeil (ValidationError, QueryError)', error401: 'Ikke autentisert (AuthenticationError)', @@ -91,6 +115,8 @@ export const nb: PluginDefaultTranslationsObject = { tagAuth: 'Autentisering', tagVersions: 'Versjoner', tagJobs: 'Jobber', + tagUploads: 'Opplastinger', + tagAccess: 'Tilgang', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/translations/locales/nl.ts b/src/shared/translations/locales/nl.ts similarity index 74% rename from src/translations/locales/nl.ts rename to src/shared/translations/locales/nl.ts index 4b2a014..4284d17 100644 --- a/src/translations/locales/nl.ts +++ b/src/shared/translations/locales/nl.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const nl: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const nl: PluginDefaultTranslationsObject = { 'De locale die wordt geretourneerd, of `all` voor elke locale. Zie het gedeelte Lokalisatie in de API-beschrijving.', paramFallbackLocale: 'De locale waarop wordt teruggevallen voor ontbrekende gelokaliseerde waarden, of `none` om uit te schakelen.', + paramValidateLocale: + 'Te valideren locales, of `all` voor elke locale. Herhaal de parameter voor meer dan één locale.', + paramComputeHierarchyPaths: + 'Zet op true om de paden `{{slugPath}}` en `{{titlePath}}` te berekenen. Een van beide velden selecteren berekent ze ook.', schemaSelect: 'Kies welke velden worden geretourneerd, bijv. `select[title]=true`. Laat weg om alle velden te retourneren.', @@ -27,6 +31,10 @@ export const nl: PluginDefaultTranslationsObject = { 'Payload `where`-filter. Nest eerst een veld en daarna een operator: `where[field][equals]=value`. Combineer clausules met de arrays `and` / `or`, bijv. `where[or][0][field][equals]=value`. Elk veld toont alleen de operators die geldig zijn voor het bijbehorende type.', schemaSupportedTimezones: 'Ondersteunde tijdzones in IANA-formaat.', schemaPerLocale: 'Waarden per locale, geretourneerd wanneer `locale=all`.', + schemaHierarchySlugPath: + 'Slug-pad, bijv. `parent/child`. Berekend bij lezen met `computeHierarchyPaths=true` of wanneer geselecteerd. Niet bruikbaar in `where`.', + schemaHierarchyTitlePath: + 'Titelpad, bijv. `Parent/Child`. Berekend bij lezen met `computeHierarchyPaths=true` of wanneer geselecteerd. Niet bruikbaar in `where`.', collectionList: 'Gepagineerde lijst met documenten', collectionDoc: 'Eén enkel document', @@ -37,6 +45,10 @@ export const nl: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultaat van bulkverwijdering', collectionCount: 'Aantal documenten', collectionDuplicated: 'Het gedupliceerde document', + validateResult: + 'Validatieresultaat. Er wordt niets opgeslagen. Ongeldige veldwaarden geven `valid: false` met de fouten terug, geen foutstatus.', + validateBody: + 'Te valideren documentgegevens. Bij een opgeslagen document of een global worden de gegevens samengevoegd over het laatste concept, of over het opgeslagen document als er geen concept is.', globalDoc: 'Het globale document', @@ -48,9 +60,13 @@ export const nl: PluginDefaultTranslationsObject = { authResetPassword: 'Resultaat van wachtwoordherstel', authFirstRegister: 'De eerste gebruiker, aangemaakt met een auth-token', authInit: 'Of deze auth-collectie al gebruikers heeft', - authAccess: 'De toegang (rechten) van de huidige gebruiker voor deze collectie', + authAccess: 'De rechten van de huidige gebruiker voor alle collecties en globals', + docAccess: 'De rechten van de huidige gebruiker voor dit document', + docAccessBody: + 'Documentgegevens waartegen toegang wordt gecontroleerd. Zonder gegevens gebruikt Payload het opgeslagen document, als dat er is.', authUnlock: 'Resultaat van ontgrendeling', authVerify: 'Verificatieresultaat', + apiKeyReveal: 'De ontsleutelde API-sleutel', versionList: 'Gepagineerde lijst met versies', versionSingle: 'Eén enkele versie', @@ -72,6 +88,14 @@ export const nl: PluginDefaultTranslationsObject = { uploadBody: 'Verzend `multipart/form-data` om een bestand te uploaden (een binair `file`-deel plus een `_payload`-deel met de velden als JSON-string), of `application/json` met alleen de velden wanneer er geen bestand is.', uploadPayloadField: '{{schema}}-velden als JSON-string. Voorbeeld: `{"alt":"A caption"}`.', + fileServe: 'Het bestand', + filePartial: 'Een deel van het bestand, voor een `Range`-verzoek', + uploadInstructionsSummary: 'Instructies ophalen om een bestand te uploaden voordat het document wordt opgeslagen', + uploadInstructionsResult: + 'Waar de bytes van het bestand naartoe moeten, en de `file`-waarde die met het aanmaak- of bijwerkverzoek mee moet', + uploadStagePutSummary: 'De bytes van het bestand verzenden voor een tijdelijke upload', + uploadStageDeleteSummary: 'Een tijdelijke upload verwijderen', + uploadStageResult: 'Klaar, geen inhoud', error400: 'Validatie- of queryfout (ValidationError, QueryError)', error401: 'Niet geverifieerd (AuthenticationError)', @@ -93,6 +117,8 @@ export const nl: PluginDefaultTranslationsObject = { tagAuth: 'Auth', tagVersions: 'Versies', tagJobs: 'Taken', + tagUploads: 'Uploads', + tagAccess: 'Toegang', localizationHeading: 'Lokalisatie', localizationNote: diff --git a/src/translations/locales/pl.ts b/src/shared/translations/locales/pl.ts similarity index 75% rename from src/translations/locales/pl.ts rename to src/shared/translations/locales/pl.ts index ba8ef26..6ceda1f 100644 --- a/src/translations/locales/pl.ts +++ b/src/shared/translations/locales/pl.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const pl: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const pl: PluginDefaultTranslationsObject = { 'Przy `locale=all` ustaw false, aby zachować pola zlokalizowane jako obiekty z podziałem na języki. Domyślnie true.', paramLocale: 'Język do zwrócenia lub `all`, aby zwrócić wszystkie języki. Zobacz sekcję Lokalizacja w opisie API.', paramFallbackLocale: 'Język zastępczy dla brakujących wartości zlokalizowanych lub `none`, aby wyłączyć.', + paramValidateLocale: + 'Języki do sprawdzenia lub `all` dla wszystkich języków. Dla więcej niż jednego języka powtórz parametr.', + paramComputeHierarchyPaths: + 'Ustaw true, aby obliczyć ścieżki `{{slugPath}}` i `{{titlePath}}`. Wybranie któregokolwiek z tych pól również je oblicza.', schemaSelect: 'Wybierz pola do zwrócenia, np. `select[title]=true`. Pomiń, aby zwrócić wszystkie.', schemaPopulate: 'Wypełnij powiązane dokumenty dla każdej kolekcji, np. `populate[posts][title]=true`.', @@ -25,6 +29,10 @@ export const pl: PluginDefaultTranslationsObject = { 'Filtr `where` Payload. Zagnieźdź pole, a następnie operator: `where[field][equals]=value`. Łącz warunki za pomocą tablic `and` / `or`, np. `where[or][0][field][equals]=value`. Każde pole wymienia tylko operatory właściwe dla jego typu.', schemaSupportedTimezones: 'Obsługiwane strefy czasowe w formacie IANA.', schemaPerLocale: 'Wartości dla poszczególnych języków, zwracane przy `locale=all`.', + schemaHierarchySlugPath: + 'Ścieżka slugów, np. `parent/child`. Obliczana przy odczycie z `computeHierarchyPaths=true` lub po wybraniu. Nie można jej użyć w `where`.', + schemaHierarchyTitlePath: + 'Ścieżka tytułów, np. `Parent/Child`. Obliczana przy odczycie z `computeHierarchyPaths=true` lub po wybraniu. Nie można jej użyć w `where`.', collectionList: 'Stronicowana lista dokumentów', collectionDoc: 'Pojedynczy dokument', @@ -35,6 +43,10 @@ export const pl: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Wynik usuwania zbiorczego', collectionCount: 'Liczba dokumentów', collectionDuplicated: 'Zduplikowany dokument', + validateResult: + 'Wynik walidacji. Nic nie jest zapisywane. Nieprawidłowe wartości pól zwracają `valid: false` wraz z błędami, a nie status błędu.', + validateBody: + 'Dane dokumentu do walidacji. W zapisanym dokumencie lub dokumencie globalnym dane są scalane z najnowszą wersją roboczą, a gdy jej nie ma, z zapisanym dokumentem.', globalDoc: 'Dokument globalny', @@ -46,9 +58,13 @@ export const pl: PluginDefaultTranslationsObject = { authResetPassword: 'Wynik resetowania hasła', authFirstRegister: 'Pierwszy użytkownik utworzony wraz z tokenem uwierzytelniającym', authInit: 'Czy ta kolekcja uwierzytelniania ma już jakichkolwiek użytkowników', - authAccess: 'Uprawnienia (dostęp) bieżącego użytkownika do tej kolekcji', + authAccess: 'Uprawnienia bieżącego użytkownika do wszystkich kolekcji i globalnych', + docAccess: 'Uprawnienia bieżącego użytkownika do tego dokumentu', + docAccessBody: + 'Dane dokumentu, względem których sprawdzany jest dostęp. Bez nich Payload używa zapisanego dokumentu, jeśli istnieje.', authUnlock: 'Wynik odblokowania', authVerify: 'Wynik weryfikacji', + apiKeyReveal: 'Odszyfrowany klucz API', versionList: 'Stronicowana lista wersji', versionSingle: 'Pojedyncza wersja', @@ -70,6 +86,14 @@ export const pl: PluginDefaultTranslationsObject = { uploadBody: 'Wyślij `multipart/form-data`, aby przesłać plik (część binarna `file` oraz część `_payload` z polami w postaci ciągu JSON), albo `application/json` z samymi polami, gdy nie ma pliku.', uploadPayloadField: 'Pola {{schema}} w postaci ciągu JSON. Przykład: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Plik', + filePartial: 'Część pliku dla żądania `Range`', + uploadInstructionsSummary: 'Pobierz instrukcje przesłania pliku przed zapisaniem dokumentu', + uploadInstructionsResult: + 'Dokąd wysłać bajty pliku i jaką wartość `file` wysłać z żądaniem utworzenia lub aktualizacji', + uploadStagePutSummary: 'Wyślij bajty pliku do tymczasowego przesyłania', + uploadStageDeleteSummary: 'Usuń tymczasowe przesyłanie', + uploadStageResult: 'Gotowe, bez treści', error400: 'Błąd walidacji lub zapytania (ValidationError, QueryError)', error401: 'Brak uwierzytelnienia (AuthenticationError)', @@ -91,6 +115,8 @@ export const pl: PluginDefaultTranslationsObject = { tagAuth: 'Uwierzytelnianie', tagVersions: 'Wersje', tagJobs: 'Zadania', + tagUploads: 'Przesyłanie plików', + tagAccess: 'Dostęp', localizationHeading: 'Lokalizacja', localizationNote: diff --git a/src/translations/locales/pt.ts b/src/shared/translations/locales/pt.ts similarity index 75% rename from src/translations/locales/pt.ts rename to src/shared/translations/locales/pt.ts index d845ad7..3e41e64 100644 --- a/src/translations/locales/pt.ts +++ b/src/shared/translations/locales/pt.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const pt: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const pt: PluginDefaultTranslationsObject = { paramLocale: 'Locale a retornar, ou `all` para todos os locales. Consulte a seção de Localização na descrição da API.', paramFallbackLocale: 'Locale alternativo para valores localizados ausentes, ou `none` para desativar.', + paramValidateLocale: + 'Locales a validar, ou `all` para todos os locales. Repita o parâmetro para mais de um locale.', + paramComputeHierarchyPaths: + 'Defina como true para calcular os caminhos `{{slugPath}}` e `{{titlePath}}`. Selecionar qualquer um dos campos também os calcula.', schemaSelect: 'Escolha os campos a retornar, por exemplo `select[title]=true`. Omita para retornar todos.', schemaPopulate: 'Popule documentos relacionados por coleção, por exemplo `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const pt: PluginDefaultTranslationsObject = { 'Filtro `where` do Payload. Aninhe um campo e depois um operador: `where[field][equals]=value`. Combine cláusulas com os arrays `and` / `or`, por exemplo `where[or][0][field][equals]=value`. Cada campo lista apenas os operadores válidos para seu tipo.', schemaSupportedTimezones: 'Fusos horários suportados no formato IANA.', schemaPerLocale: 'Valores por locale, retornados quando `locale=all`.', + schemaHierarchySlugPath: + 'Caminho de slugs, ex. `parent/child`. Calculado na leitura com `computeHierarchyPaths=true` ou quando selecionado. Não pode ser usado em `where`.', + schemaHierarchyTitlePath: + 'Caminho de títulos, ex. `Parent/Child`. Calculado na leitura com `computeHierarchyPaths=true` ou quando selecionado. Não pode ser usado em `where`.', collectionList: 'Lista paginada de documentos', collectionDoc: 'Um único documento', @@ -36,6 +44,10 @@ export const pt: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultado da exclusão em massa', collectionCount: 'Contagem de documentos', collectionDuplicated: 'O documento duplicado', + validateResult: + 'Resultado da validação. Nada é salvo. Valores de campo inválidos retornam `valid: false` com os erros, não um status de erro.', + validateBody: + 'Dados do documento a validar. Em um documento salvo ou em um global, os dados são mesclados sobre o rascunho mais recente, ou sobre o documento salvo se não houver rascunho.', globalDoc: 'O documento global', @@ -47,9 +59,13 @@ export const pt: PluginDefaultTranslationsObject = { authResetPassword: 'Resultado da redefinição de senha', authFirstRegister: 'O primeiro usuário, criado com um token de autenticação', authInit: 'Se esta coleção de autenticação já possui algum usuário', - authAccess: 'O acesso (permissões) do usuário atual para esta coleção', + authAccess: 'As permissões do usuário atual para todas as coleções e globais', + docAccess: 'As permissões do usuário atual para este documento', + docAccessBody: + 'Dados do documento usados para verificar o acesso. Sem eles, o Payload usa o documento salvo, se existir.', authUnlock: 'Resultado do desbloqueio', authVerify: 'Resultado da verificação', + apiKeyReveal: 'A chave de API descriptografada', versionList: 'Lista paginada de versões', versionSingle: 'Uma única versão', @@ -71,6 +87,14 @@ export const pt: PluginDefaultTranslationsObject = { uploadBody: 'Envie `multipart/form-data` para fazer upload de um arquivo (uma parte binária `file` mais uma parte `_payload` com os campos em JSON stringificado), ou `application/json` apenas com os campos quando não houver arquivo.', uploadPayloadField: 'Campos {{schema}} em JSON stringificado. Exemplo: `{"alt":"A caption"}`.', + fileServe: 'O arquivo', + filePartial: 'Uma parte do arquivo, para uma requisição `Range`', + uploadInstructionsSummary: 'Obter instruções para enviar um arquivo antes de salvar o documento', + uploadInstructionsResult: + 'Para onde enviar os bytes do arquivo e o valor `file` a enviar com a requisição de criação ou atualização', + uploadStagePutSummary: 'Enviar os bytes do arquivo para um upload temporário', + uploadStageDeleteSummary: 'Excluir um upload temporário', + uploadStageResult: 'Concluído, sem conteúdo', error400: 'Erro de validação ou de consulta (ValidationError, QueryError)', error401: 'Não autenticado (AuthenticationError)', @@ -92,6 +116,8 @@ export const pt: PluginDefaultTranslationsObject = { tagAuth: 'Autenticação', tagVersions: 'Versões', tagJobs: 'Tarefas', + tagUploads: 'Uploads', + tagAccess: 'Acesso', localizationHeading: 'Localização', localizationNote: diff --git a/src/translations/locales/ro.ts b/src/shared/translations/locales/ro.ts similarity index 74% rename from src/translations/locales/ro.ts rename to src/shared/translations/locales/ro.ts index 9397bf9..2a1e53e 100644 --- a/src/translations/locales/ro.ts +++ b/src/shared/translations/locales/ro.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ro: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const ro: PluginDefaultTranslationsObject = { paramLocale: 'Locale-ul de returnat sau `all` pentru toate locale-urile. Consultați secțiunea Localizare din descrierea API.', paramFallbackLocale: 'Locale-ul de rezervă pentru valorile localizate lipsă sau `none` pentru a dezactiva.', + paramValidateLocale: + 'Locale-uri de validat sau `all` pentru toate locale-urile. Repetați parametrul pentru mai mult de un locale.', + paramComputeHierarchyPaths: + 'Setați true pentru a calcula căile `{{slugPath}}` și `{{titlePath}}`. Selectarea oricăruia dintre câmpuri le calculează și ea.', schemaSelect: 'Alegeți câmpurile de returnat, de ex. `select[title]=true`. Omiteți pentru a le returna pe toate.', schemaPopulate: 'Populați documentele asociate per colecție, de ex. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const ro: PluginDefaultTranslationsObject = { 'Filtru `where` Payload. Imbricați un câmp, apoi un operator: `where[field][equals]=value`. Combinați clauzele cu tablourile `and` / `or`, de ex. `where[or][0][field][equals]=value`. Fiecare câmp listează doar operatorii valizi pentru tipul său.', schemaSupportedTimezones: 'Fusuri orare acceptate în format IANA.', schemaPerLocale: 'Valori per-locale, returnate când `locale=all`.', + schemaHierarchySlugPath: + 'Cale din sluguri, de ex. `parent/child`. Calculată la citire cu `computeHierarchyPaths=true` sau când este selectată. Nu poate fi folosită în `where`.', + schemaHierarchyTitlePath: + 'Cale din titluri, de ex. `Parent/Child`. Calculată la citire cu `computeHierarchyPaths=true` sau când este selectată. Nu poate fi folosită în `where`.', collectionList: 'Listă paginată de documente', collectionDoc: 'Un singur document', @@ -36,6 +44,10 @@ export const ro: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Rezultatul ștergerii în masă', collectionCount: 'Numărul de documente', collectionDuplicated: 'Documentul duplicat', + validateResult: + 'Rezultatul validării. Nu se salvează nimic. Valorile invalide ale câmpurilor returnează `valid: false` cu erorile, nu un status de eroare.', + validateBody: + 'Datele documentului de validat. Pe un document salvat sau un global, datele sunt îmbinate peste cea mai recentă ciornă sau peste documentul salvat, dacă nu există ciornă.', globalDoc: 'Documentul global', @@ -47,9 +59,13 @@ export const ro: PluginDefaultTranslationsObject = { authResetPassword: 'Rezultatul resetării parolei', authFirstRegister: 'Primul utilizator, creat cu un token de autentificare', authInit: 'Dacă această colecție de autentificare are deja utilizatori', - authAccess: 'Accesul (permisiunile) utilizatorului curent pentru această colecție', + authAccess: 'Permisiunile utilizatorului curent pentru toate colecțiile și globalele', + docAccess: 'Permisiunile utilizatorului curent pentru acest document', + docAccessBody: + 'Datele documentului față de care se verifică accesul. Fără ele, Payload folosește documentul salvat, dacă există.', authUnlock: 'Rezultatul deblocării', authVerify: 'Rezultatul verificării', + apiKeyReveal: 'Cheia API decriptată', versionList: 'Listă paginată de versiuni', versionSingle: 'O singură versiune', @@ -71,6 +87,14 @@ export const ro: PluginDefaultTranslationsObject = { uploadBody: 'Trimiteți `multipart/form-data` pentru a încărca un fișier (o parte binară `file` plus o parte `_payload` cu câmpurile serializate în JSON) sau `application/json` doar cu câmpurile când nu există fișier.', uploadPayloadField: 'Câmpuri {{schema}} serializate în JSON. Exemplu: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Fișierul', + filePartial: 'O parte din fișier, pentru o cerere `Range`', + uploadInstructionsSummary: 'Obține instrucțiuni pentru încărcarea unui fișier înainte de salvarea documentului', + uploadInstructionsResult: + 'Unde se trimit octeții fișierului și valoarea `file` de trimis cu cererea de creare sau actualizare', + uploadStagePutSummary: 'Trimite octeții fișierului pentru o încărcare temporară', + uploadStageDeleteSummary: 'Șterge o încărcare temporară', + uploadStageResult: 'Gata, fără conținut', error400: 'Eroare de validare sau de interogare (ValidationError, QueryError)', error401: 'Neautentificat (AuthenticationError)', @@ -92,6 +116,8 @@ export const ro: PluginDefaultTranslationsObject = { tagAuth: 'Autentificare', tagVersions: 'Versiuni', tagJobs: 'Joburi', + tagUploads: 'Încărcări', + tagAccess: 'Acces', localizationHeading: 'Localizare', localizationNote: diff --git a/src/translations/locales/rs.ts b/src/shared/translations/locales/rs.ts similarity index 75% rename from src/translations/locales/rs.ts rename to src/shared/translations/locales/rs.ts index fb55712..3de82bd 100644 --- a/src/translations/locales/rs.ts +++ b/src/shared/translations/locales/rs.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const rs: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,10 @@ export const rs: PluginDefaultTranslationsObject = { 'Уз `locale=all`, поставите на false да би се локализована поља задржала као објекти по локалитету. Подразумевано true.', paramLocale: 'Локалитет који се враћа, или `all` за све локалитете. Погледајте одељак Локализација у опису API-ја.', paramFallbackLocale: 'Резервни локалитет за недостајуће локализоване вредности, или `none` за онемогућавање.', + paramValidateLocale: + 'Локалитети за проверу, или `all` за све локалитете. Поновите параметар за више од једног локалитета.', + paramComputeHierarchyPaths: + 'Поставите true да бисте израчунали путање `{{slugPath}}` и `{{titlePath}}`. Избор било ког од тих поља их такође израчунава.', schemaSelect: 'Изаберите поља која се враћају, нпр. `select[title]=true`. Изоставите да би се вратила сва.', schemaPopulate: 'Попуњавање повезаних докумената по колекцији, нпр. `populate[posts][title]=true`.', @@ -24,6 +28,10 @@ export const rs: PluginDefaultTranslationsObject = { 'Payload `where` филтер. Угнездите поље, па оператор: `where[field][equals]=value`. Комбинујте клаузуле помоћу низова `and` / `or`, нпр. `where[or][0][field][equals]=value`. Свако поље наводи само операторе важеће за његов тип.', schemaSupportedTimezones: 'Подржане временске зоне у IANA формату.', schemaPerLocale: 'Вредности по локалитету, враћају се када је `locale=all`.', + schemaHierarchySlugPath: + 'Путања slug вредности, нпр. `parent/child`. Израчунава се при читању са `computeHierarchyPaths=true` или када је изабрана. Не може се користити у `where`.', + schemaHierarchyTitlePath: + 'Путања наслова, нпр. `Parent/Child`. Израчунава се при читању са `computeHierarchyPaths=true` или када је изабрана. Не може се користити у `where`.', collectionList: 'Страничена листа докумената', collectionDoc: 'Један документ', @@ -34,6 +42,10 @@ export const rs: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Резултат групног брисања', collectionCount: 'Број докумената', collectionDuplicated: 'Дуплирани документ', + validateResult: + 'Резултат провере. Ништа се не чува. Неважеће вредности поља враћају `valid: false` са грешкама, а не статус грешке.', + validateBody: + 'Подаци документа за проверу. На сачуваном документу или глобалном документу подаци се спајају преко најновијег нацрта, или сачуваног документа ако нацрта нема.', globalDoc: 'Глобални документ', @@ -45,9 +57,13 @@ export const rs: PluginDefaultTranslationsObject = { authResetPassword: 'Резултат ресетовања лозинке', authFirstRegister: 'Први корисник, креиран са токеном за аутентификацију', authInit: 'Да ли ова колекција за аутентификацију већ има кориснике', - authAccess: 'Приступ (дозволе) тренутног корисника за ову колекцију', + authAccess: 'Дозволе тренутног корисника за све колекције и глобале', + docAccess: 'Дозволе тренутног корисника за овај документ', + docAccessBody: + 'Подаци документа према којима се проверава приступ. Без њих Payload користи сачувани документ, ако постоји.', authUnlock: 'Резултат откључавања', authVerify: 'Резултат верификације', + apiKeyReveal: 'Дешифровани API кључ', versionList: 'Страничена листа верзија', versionSingle: 'Једна верзија', @@ -69,6 +85,14 @@ export const rs: PluginDefaultTranslationsObject = { uploadBody: 'Пошаљите `multipart/form-data` да отпремите датотеку (бинарни `file` део плус `_payload` део са пољима у JSON стринг формату), или `application/json` само са пољима када нема датотеке.', uploadPayloadField: 'Поља {{schema}} у JSON стринг формату. Пример: `{"alt":"A caption"}`.', + fileServe: 'Датотека', + filePartial: 'Део датотеке за `Range` захтев', + uploadInstructionsSummary: 'Преузимање упутстава за отпремање датотеке пре чувања документа', + uploadInstructionsResult: + 'Где послати бајтове датотеке и вредност `file` која се шаље уз захтев за креирање или ажурирање', + uploadStagePutSummary: 'Слање бајтова датотеке за привремено отпремање', + uploadStageDeleteSummary: 'Брисање привременог отпремања', + uploadStageResult: 'Готово, без садржаја', error400: 'Грешка валидације или упита (ValidationError, QueryError)', error401: 'Није аутентификован (AuthenticationError)', @@ -90,6 +114,8 @@ export const rs: PluginDefaultTranslationsObject = { tagAuth: 'Аутентификација', tagVersions: 'Верзије', tagJobs: 'Послови', + tagUploads: 'Отпремања', + tagAccess: 'Приступ', localizationHeading: 'Локализација', localizationNote: diff --git a/src/translations/locales/rsLatin.ts b/src/shared/translations/locales/rsLatin.ts similarity index 75% rename from src/translations/locales/rsLatin.ts rename to src/shared/translations/locales/rsLatin.ts index b5016a9..a6684a9 100644 --- a/src/translations/locales/rsLatin.ts +++ b/src/shared/translations/locales/rsLatin.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const rsLatin: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -19,6 +19,10 @@ export const rsLatin: PluginDefaultTranslationsObject = { 'Lokalitet koji se vraća, ili `all` za sve lokalitete. Pogledajte odeljak Lokalizacija u opisu API-ja.', paramFallbackLocale: 'Lokalitet na koji se vraća za nedostajuće lokalizovane vrednosti, ili `none` za onemogućavanje.', + paramValidateLocale: + 'Lokaliteti za proveru, ili `all` za sve lokalitete. Ponovite parametar za više od jednog lokaliteta.', + paramComputeHierarchyPaths: + 'Postavite true da biste izračunali putanje `{{slugPath}}` i `{{titlePath}}`. Izbor bilo kog od tih polja ih takođe izračunava.', schemaSelect: 'Izaberite polja koja se vraćaju, npr. `select[title]=true`. Izostavite da biste vratili sva.', schemaPopulate: 'Popunite povezane dokumente po kolekciji, npr. `populate[posts][title]=true`.', @@ -27,6 +31,10 @@ export const rsLatin: PluginDefaultTranslationsObject = { 'Payload `where` filter. Ugnezdite polje pa operator: `where[field][equals]=value`. Kombinujte uslove pomoću nizova `and` / `or`, npr. `where[or][0][field][equals]=value`. Svako polje navodi samo operatore koji važe za njegov tip.', schemaSupportedTimezones: 'Podržane vremenske zone u IANA formatu.', schemaPerLocale: 'Vrednosti po lokalitetu, vraćaju se kada je `locale=all`.', + schemaHierarchySlugPath: + 'Putanja slug vrednosti, npr. `parent/child`. Izračunava se pri čitanju sa `computeHierarchyPaths=true` ili kada je izabrana. Ne može se koristiti u `where`.', + schemaHierarchyTitlePath: + 'Putanja naslova, npr. `Parent/Child`. Izračunava se pri čitanju sa `computeHierarchyPaths=true` ili kada je izabrana. Ne može se koristiti u `where`.', collectionList: 'Stranicama podeljena lista dokumenata', collectionDoc: 'Jedan dokument', @@ -37,6 +45,10 @@ export const rsLatin: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Rezultat grupnog brisanja', collectionCount: 'Broj dokumenata', collectionDuplicated: 'Duplirani dokument', + validateResult: + 'Rezultat provere. Ništa se ne čuva. Nevažeće vrednosti polja vraćaju `valid: false` sa greškama, a ne status greške.', + validateBody: + 'Podaci dokumenta za proveru. Na sačuvanom dokumentu ili globalnom dokumentu podaci se spajaju preko najnovijeg nacrta, ili sačuvanog dokumenta ako nacrta nema.', globalDoc: 'Globalni dokument', @@ -48,9 +60,13 @@ export const rsLatin: PluginDefaultTranslationsObject = { authResetPassword: 'Rezultat resetovanja lozinke', authFirstRegister: 'Prvi korisnik, kreiran sa tokenom za autentifikaciju', authInit: 'Da li ova kolekcija za autentifikaciju već ima korisnike', - authAccess: 'Pristup (dozvole) trenutnog korisnika za ovu kolekciju', + authAccess: 'Dozvole trenutnog korisnika za sve kolekcije i globale', + docAccess: 'Dozvole trenutnog korisnika za ovaj dokument', + docAccessBody: + 'Podaci dokumenta prema kojima se proverava pristup. Bez njih Payload koristi sačuvani dokument, ako postoji.', authUnlock: 'Rezultat otključavanja', authVerify: 'Rezultat verifikacije', + apiKeyReveal: 'Dešifrovani API ključ', versionList: 'Stranicama podeljena lista verzija', versionSingle: 'Jedna verzija', @@ -72,6 +88,14 @@ export const rsLatin: PluginDefaultTranslationsObject = { uploadBody: 'Pošaljite `multipart/form-data` za otpremanje datoteke (binarni `file` deo plus `_payload` deo sa poljima pretvorenim u JSON string), ili `application/json` samo sa poljima kada nema datoteke.', uploadPayloadField: '{{schema}} polja pretvorena u JSON string. Primer: `{"alt":"A caption"}`.', + fileServe: 'Datoteka', + filePartial: 'Deo datoteke za `Range` zahtev', + uploadInstructionsSummary: 'Preuzimanje uputstava za otpremanje datoteke pre čuvanja dokumenta', + uploadInstructionsResult: + 'Gde poslati bajtove datoteke i vrednost `file` koja se šalje uz zahtev za kreiranje ili ažuriranje', + uploadStagePutSummary: 'Slanje bajtova datoteke za privremeno otpremanje', + uploadStageDeleteSummary: 'Brisanje privremenog otpremanja', + uploadStageResult: 'Gotovo, bez sadržaja', error400: 'Greška u validaciji ili upitu (ValidationError, QueryError)', error401: 'Niste autentifikovani (AuthenticationError)', @@ -93,6 +117,8 @@ export const rsLatin: PluginDefaultTranslationsObject = { tagAuth: 'Autentifikacija', tagVersions: 'Verzije', tagJobs: 'Poslovi', + tagUploads: 'Otpremanja', + tagAccess: 'Pristup', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/translations/locales/ru.ts b/src/shared/translations/locales/ru.ts similarity index 74% rename from src/translations/locales/ru.ts rename to src/shared/translations/locales/ru.ts index 27e136c..8919dbb 100644 --- a/src/translations/locales/ru.ts +++ b/src/shared/translations/locales/ru.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ru: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const ru: PluginDefaultTranslationsObject = { 'При `locale=all` установите false, чтобы локализованные поля оставались объектами по локалям. По умолчанию true.', paramLocale: 'Локаль для возврата или `all` для всех локалей. См. раздел «Локализация» в описании API.', paramFallbackLocale: 'Локаль для подстановки при отсутствии локализованных значений или `none`, чтобы отключить.', + paramValidateLocale: 'Локали для проверки или `all` для всех локалей. Для нескольких локалей повторите параметр.', + paramComputeHierarchyPaths: + 'Передайте true, чтобы вычислить пути `{{slugPath}}` и `{{titlePath}}`. Выбор любого из этих полей тоже их вычисляет.', schemaSelect: 'Выберите возвращаемые поля, например `select[title]=true`. Опустите, чтобы вернуть все.', schemaPopulate: 'Подгрузка связанных документов по коллекциям, например `populate[posts][title]=true`.', @@ -24,6 +27,10 @@ export const ru: PluginDefaultTranslationsObject = { 'Фильтр Payload `where`. Вложите поле, затем оператор: `where[field][equals]=value`. Объединяйте условия с помощью массивов `and` / `or`, например `where[or][0][field][equals]=value`. Для каждого поля перечислены только операторы, допустимые для его типа.', schemaSupportedTimezones: 'Поддерживаемые часовые пояса в формате IANA.', schemaPerLocale: 'Значения по локалям, возвращаемые при `locale=all`.', + schemaHierarchySlugPath: + 'Путь из slug, например `parent/child`. Вычисляется при чтении с `computeHierarchyPaths=true` или если поле выбрано. Нельзя использовать в `where`.', + schemaHierarchyTitlePath: + 'Путь из заголовков, например `Parent/Child`. Вычисляется при чтении с `computeHierarchyPaths=true` или если поле выбрано. Нельзя использовать в `where`.', collectionList: 'Постраничный список документов', collectionDoc: 'Один документ', @@ -34,6 +41,10 @@ export const ru: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Результат массового удаления', collectionCount: 'Количество документов', collectionDuplicated: 'Дублированный документ', + validateResult: + 'Результат проверки. Ничего не сохраняется. Недопустимые значения полей возвращают `valid: false` с ошибками, а не статус ошибки.', + validateBody: + 'Данные документа для проверки. Для сохранённого документа или глобального документа данные накладываются на последний черновик, а если черновика нет, на сохранённый документ.', globalDoc: 'Глобальный документ', @@ -45,9 +56,13 @@ export const ru: PluginDefaultTranslationsObject = { authResetPassword: 'Результат сброса пароля', authFirstRegister: 'Первый пользователь, созданный с токеном аутентификации', authInit: 'Есть ли уже пользователи в этой коллекции аутентификации', - authAccess: 'Доступ (права) текущего пользователя к этой коллекции', + authAccess: 'Права текущего пользователя для всех коллекций и глобальных данных', + docAccess: 'Права текущего пользователя для этого документа', + docAccessBody: + 'Данные документа, по которым проверяется доступ. Без них Payload использует сохранённый документ, если он есть.', authUnlock: 'Результат разблокировки', authVerify: 'Результат подтверждения', + apiKeyReveal: 'Расшифрованный API-ключ', versionList: 'Постраничный список версий', versionSingle: 'Одна версия', @@ -69,6 +84,14 @@ export const ru: PluginDefaultTranslationsObject = { uploadBody: 'Отправьте `multipart/form-data` для загрузки файла (бинарная часть `file` плюс часть `_payload` с полями в виде JSON-строки) или `application/json` только с полями, если файла нет.', uploadPayloadField: 'Поля {{schema}} в виде JSON-строки. Пример: `{"alt":"A caption"}`.', + fileServe: 'Файл', + filePartial: 'Часть файла для запроса с `Range`', + uploadInstructionsSummary: 'Получить инструкции для загрузки файла до сохранения документа', + uploadInstructionsResult: + 'Куда отправить байты файла и какое значение `file` передать в запросе на создание или обновление', + uploadStagePutSummary: 'Отправить байты файла для временной загрузки', + uploadStageDeleteSummary: 'Удалить временную загрузку', + uploadStageResult: 'Готово, без содержимого', error400: 'Ошибка валидации или запроса (ValidationError, QueryError)', error401: 'Не аутентифицирован (AuthenticationError)', @@ -90,6 +113,8 @@ export const ru: PluginDefaultTranslationsObject = { tagAuth: 'Аутентификация', tagVersions: 'Версии', tagJobs: 'Задачи', + tagUploads: 'Загрузки', + tagAccess: 'Доступ', localizationHeading: 'Локализация', localizationNote: diff --git a/src/translations/locales/sk.ts b/src/shared/translations/locales/sk.ts similarity index 76% rename from src/translations/locales/sk.ts rename to src/shared/translations/locales/sk.ts index 0955189..5ddd3f3 100644 --- a/src/translations/locales/sk.ts +++ b/src/shared/translations/locales/sk.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const sk: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -19,6 +19,9 @@ export const sk: PluginDefaultTranslationsObject = { 'Lokalita, ktorá sa má vrátiť, alebo `all` pre všetky lokality. Pozri sekciu Lokalizácia v popise API.', paramFallbackLocale: 'Lokalita, na ktorú sa použije záloha pri chýbajúcich lokalizovaných hodnotách, alebo `none` pre vypnutie.', + paramValidateLocale: 'Lokality na overenie, alebo `all` pre všetky lokality. Pre viac lokalít parameter zopakujte.', + paramComputeHierarchyPaths: + 'Nastavte true na výpočet ciest `{{slugPath}}` a `{{titlePath}}`. Výber ktoréhokoľvek z týchto polí ich vypočíta tiež.', schemaSelect: 'Vyberte polia, ktoré sa majú vrátiť, napr. `select[title]=true`. Vynechajte pre vrátenie všetkých.', schemaPopulate: 'Naplniť súvisiace dokumenty podľa kolekcie, napr. `populate[posts][title]=true`.', @@ -28,6 +31,10 @@ export const sk: PluginDefaultTranslationsObject = { 'Filter `where` v Payloade. Vnorte pole a potom operátor: `where[field][equals]=value`. Klauzuly kombinujte pomocou polí `and` / `or`, napr. `where[or][0][field][equals]=value`. Každé pole uvádza iba operátory platné pre jeho typ.', schemaSupportedTimezones: 'Podporované časové pásma vo formáte IANA.', schemaPerLocale: 'Hodnoty podľa jednotlivých lokalít, vrátené pri `locale=all`.', + schemaHierarchySlugPath: + 'Cesta zo slugov, napr. `parent/child`. Počíta sa pri čítaní s `computeHierarchyPaths=true` alebo pri výbere. Nedá sa použiť vo `where`.', + schemaHierarchyTitlePath: + 'Cesta z názvov, napr. `Parent/Child`. Počíta sa pri čítaní s `computeHierarchyPaths=true` alebo pri výbere. Nedá sa použiť vo `where`.', collectionList: 'Stránkovaný zoznam dokumentov', collectionDoc: 'Jeden dokument', @@ -38,6 +45,10 @@ export const sk: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Výsledok hromadného odstránenia', collectionCount: 'Počet dokumentov', collectionDuplicated: 'Duplikovaný dokument', + validateResult: + 'Výsledok overenia. Nič sa neukladá. Neplatné hodnoty polí vrátia `valid: false` s chybami, nie chybový stav.', + validateBody: + 'Údaje dokumentu na overenie. Pri uloženom dokumente alebo globálnom dokumente sa údaje zlúčia s najnovším konceptom, prípadne s uloženým dokumentom, ak koncept neexistuje.', globalDoc: 'Globálny dokument', @@ -49,9 +60,13 @@ export const sk: PluginDefaultTranslationsObject = { authResetPassword: 'Výsledok obnovenia hesla', authFirstRegister: 'Prvý používateľ vytvorený s autentifikačným tokenom', authInit: 'Či táto autentifikačná kolekcia už má nejakých používateľov', - authAccess: 'Prístup (oprávnenia) aktuálneho používateľa pre túto kolekciu', + authAccess: 'Oprávnenia aktuálneho používateľa pre všetky kolekcie a globály', + docAccess: 'Oprávnenia aktuálneho používateľa pre tento dokument', + docAccessBody: + 'Údaje dokumentu, voči ktorým sa overuje prístup. Bez nich Payload použije uložený dokument, ak existuje.', authUnlock: 'Výsledok odomknutia', authVerify: 'Výsledok overenia', + apiKeyReveal: 'Dešifrovaný API kľúč', versionList: 'Stránkovaný zoznam verzií', versionSingle: 'Jedna verzia', @@ -73,6 +88,14 @@ export const sk: PluginDefaultTranslationsObject = { uploadBody: 'Pre nahranie súboru odošlite `multipart/form-data` (binárnu časť `file` plus časť `_payload` s poliami serializovanými do JSON), alebo `application/json` len s poliami, ak súbor nie je.', uploadPayloadField: 'Polia {{schema}} serializované do JSON. Príklad: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Súbor', + filePartial: 'Časť súboru pre požiadavku `Range`', + uploadInstructionsSummary: 'Získať pokyny na nahranie súboru pred uložením dokumentu', + uploadInstructionsResult: + 'Kam poslať bajty súboru a hodnota `file`, ktorá sa pošle s požiadavkou na vytvorenie alebo úpravu', + uploadStagePutSummary: 'Odoslať bajty súboru pre dočasné nahranie', + uploadStageDeleteSummary: 'Odstrániť dočasné nahranie', + uploadStageResult: 'Hotovo, bez obsahu', error400: 'Chyba validácie alebo dotazu (ValidationError, QueryError)', error401: 'Neprihlásený (AuthenticationError)', @@ -94,6 +117,8 @@ export const sk: PluginDefaultTranslationsObject = { tagAuth: 'Autentifikácia', tagVersions: 'Verzie', tagJobs: 'Úlohy', + tagUploads: 'Nahrávanie', + tagAccess: 'Prístup', localizationHeading: 'Lokalizácia', localizationNote: diff --git a/src/translations/locales/sl.ts b/src/shared/translations/locales/sl.ts similarity index 75% rename from src/translations/locales/sl.ts rename to src/shared/translations/locales/sl.ts index 728f24a..823957a 100644 --- a/src/translations/locales/sl.ts +++ b/src/shared/translations/locales/sl.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const sl: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,9 @@ export const sl: PluginDefaultTranslationsObject = { paramLocale: 'Jezik, ki naj se vrne, ali `all` za vse jezike. Glejte razdelek Lokalizacija v opisu API-ja.', paramFallbackLocale: 'Jezik, na katerega naj se zateče pri manjkajočih lokaliziranih vrednostih, ali `none` za onemogočenje.', + paramValidateLocale: 'Jeziki za preverjanje ali `all` za vse jezike. Za več jezikov parameter ponovite.', + paramComputeHierarchyPaths: + 'Nastavite true za izračun poti `{{slugPath}}` in `{{titlePath}}`. Izbira katerega koli od teh polj ju prav tako izračuna.', schemaSelect: 'Izberite polja, ki naj se vrnejo, npr. `select[title]=true`. Izpustite za vrnitev vseh.', schemaPopulate: 'Napolnite povezane dokumente po posamezni zbirki, npr. `populate[posts][title]=true`.', @@ -26,6 +29,10 @@ export const sl: PluginDefaultTranslationsObject = { 'Payloadov filter `where`. Ugnezdite polje, nato operator: `where[field][equals]=value`. Stavke združite s polji `and` / `or`, npr. `where[or][0][field][equals]=value`. Vsako polje navaja le operatorje, veljavne za njegov tip.', schemaSupportedTimezones: 'Podprti časovni pasovi v zapisu IANA.', schemaPerLocale: 'Vrednosti po posameznem jeziku, vrnjene pri `locale=all`.', + schemaHierarchySlugPath: + 'Pot iz slugov, npr. `parent/child`. Izračuna se pri branju z `computeHierarchyPaths=true` ali ko je izbrana. Ni je mogoče uporabiti v `where`.', + schemaHierarchyTitlePath: + 'Pot iz naslovov, npr. `Parent/Child`. Izračuna se pri branju z `computeHierarchyPaths=true` ali ko je izbrana. Ni je mogoče uporabiti v `where`.', collectionList: 'Stranjen seznam dokumentov', collectionDoc: 'Posamezen dokument', @@ -36,6 +43,10 @@ export const sl: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Rezultat množičnega brisanja', collectionCount: 'Število dokumentov', collectionDuplicated: 'Podvojeni dokument', + validateResult: + 'Rezultat preverjanja. Nič se ne shrani. Neveljavne vrednosti polj vrnejo `valid: false` z napakami, ne statusa napake.', + validateBody: + 'Podatki dokumenta za preverjanje. Pri shranjenem dokumentu ali globalnem dokumentu se podatki združijo z zadnjim osnutkom ali s shranjenim dokumentom, če osnutka ni.', globalDoc: 'Globalni dokument', @@ -47,9 +58,13 @@ export const sl: PluginDefaultTranslationsObject = { authResetPassword: 'Rezultat ponastavitve gesla', authFirstRegister: 'Prvi uporabnik, ustvarjen z žetonom za overjanje', authInit: 'Ali ta zbirka za overjanje že vsebuje kakšne uporabnike', - authAccess: 'Dostop (dovoljenja) trenutnega uporabnika za to zbirko', + authAccess: 'Dovoljenja trenutnega uporabnika za vse zbirke in globalne nastavitve', + docAccess: 'Dovoljenja trenutnega uporabnika za ta dokument', + docAccessBody: + 'Podatki dokumenta, glede na katere se preveri dostop. Brez njih Payload uporabi shranjeni dokument, če obstaja.', authUnlock: 'Rezultat odklepanja', authVerify: 'Rezultat preverjanja', + apiKeyReveal: 'Dešifrirani ključ API', versionList: 'Stranjen seznam različic', versionSingle: 'Posamezna različica', @@ -71,6 +86,14 @@ export const sl: PluginDefaultTranslationsObject = { uploadBody: 'Za nalaganje datoteke pošljite `multipart/form-data` (binarni del `file` ter del `_payload` s polji, pretvorjenimi v niz JSON) ali `application/json` zgolj s polji, kadar datoteke ni.', uploadPayloadField: 'Polja {{schema}}, pretvorjena v niz JSON. Primer: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Datoteka', + filePartial: 'Del datoteke za zahtevo `Range`', + uploadInstructionsSummary: 'Pridobi navodila za nalaganje datoteke pred shranjevanjem dokumenta', + uploadInstructionsResult: + 'Kam poslati bajte datoteke in vrednost `file`, ki jo pošljete z zahtevo za ustvarjanje ali posodobitev', + uploadStagePutSummary: 'Pošlji bajte datoteke za začasno nalaganje', + uploadStageDeleteSummary: 'Izbriši začasno nalaganje', + uploadStageResult: 'Končano, brez vsebine', error400: 'Napaka pri preverjanju ali poizvedbi (ValidationError, QueryError)', error401: 'Niste overjeni (AuthenticationError)', @@ -92,6 +115,8 @@ export const sl: PluginDefaultTranslationsObject = { tagAuth: 'Overjanje', tagVersions: 'Različice', tagJobs: 'Opravila', + tagUploads: 'Nalaganja', + tagAccess: 'Dostop', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/translations/locales/sv.ts b/src/shared/translations/locales/sv.ts similarity index 74% rename from src/translations/locales/sv.ts rename to src/shared/translations/locales/sv.ts index 3de7549..e8862b5 100644 --- a/src/translations/locales/sv.ts +++ b/src/shared/translations/locales/sv.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const sv: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const sv: PluginDefaultTranslationsObject = { paramLocale: 'Språk att returnera, eller `all` för alla språk. Se avsnittet Lokalisering i API-beskrivningen.', paramFallbackLocale: 'Språk att falla tillbaka på för saknade lokaliserade värden, eller `none` för att inaktivera.', + paramValidateLocale: + 'Språk som ska valideras, eller `all` för alla språk. Upprepa parametern för fler än ett språk.', + paramComputeHierarchyPaths: + 'Ange true för att beräkna sökvägarna `{{slugPath}}` och `{{titlePath}}`. Att välja något av fälten beräknar dem också.', schemaSelect: 'Välj fält att returnera, t.ex. `select[title]=true`. Utelämna för att returnera alla.', schemaPopulate: 'Populera relaterade dokument per samling, t.ex. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const sv: PluginDefaultTranslationsObject = { 'Payload `where`-filter. Nästla ett fält och sedan en operator: `where[field][equals]=value`. Kombinera villkor med arrayerna `and` / `or`, t.ex. `where[or][0][field][equals]=value`. Varje fält listar endast de operatorer som är giltiga för dess typ.', schemaSupportedTimezones: 'Tidszoner som stöds, i IANA-format.', schemaPerLocale: 'Värden per språk, returneras när `locale=all`.', + schemaHierarchySlugPath: + 'Sökväg av slugs, t.ex. `parent/child`. Beräknas vid läsning med `computeHierarchyPaths=true` eller när fältet väljs. Kan inte användas i `where`.', + schemaHierarchyTitlePath: + 'Sökväg av titlar, t.ex. `Parent/Child`. Beräknas vid läsning med `computeHierarchyPaths=true` eller när fältet väljs. Kan inte användas i `where`.', collectionList: 'Paginerad lista över dokument', collectionDoc: 'Ett enskilt dokument', @@ -36,6 +44,10 @@ export const sv: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Resultat av massborttagning', collectionCount: 'Antal dokument', collectionDuplicated: 'Det duplicerade dokumentet', + validateResult: + 'Valideringsresultat. Inget sparas. Ogiltiga fältvärden returnerar `valid: false` med felen, inte en felstatus.', + validateBody: + 'Dokumentdata som ska valideras. På ett sparat dokument eller en global slås datan samman över det senaste utkastet, eller det sparade dokumentet om det inte finns något utkast.', globalDoc: 'Det globala dokumentet', @@ -47,9 +59,13 @@ export const sv: PluginDefaultTranslationsObject = { authResetPassword: 'Resultat av lösenordsåterställning', authFirstRegister: 'Den första användaren, skapad med en autentiseringstoken', authInit: 'Huruvida den här autentiseringssamlingen har några användare ännu', - authAccess: 'Den aktuella användarens åtkomst (behörigheter) för den här samlingen', + authAccess: 'Den aktuella användarens behörigheter för alla samlingar och globaler', + docAccess: 'Den aktuella användarens behörigheter för detta dokument', + docAccessBody: + 'Dokumentdata som åtkomsten kontrolleras mot. Utan dem använder Payload det sparade dokumentet, om det finns.', authUnlock: 'Resultat av upplåsning', authVerify: 'Verifieringsresultat', + apiKeyReveal: 'Den dekrypterade API-nyckeln', versionList: 'Paginerad lista över versioner', versionSingle: 'En enskild version', @@ -71,6 +87,14 @@ export const sv: PluginDefaultTranslationsObject = { uploadBody: 'Skicka `multipart/form-data` för att ladda upp en fil (en binär `file`-del plus en `_payload`-del med de JSON-strängifierade fälten), eller `application/json` med enbart fälten när det inte finns någon fil.', uploadPayloadField: 'JSON-strängifierade {{schema}}-fält. Exempel: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Filen', + filePartial: 'En del av filen vid en `Range`-begäran', + uploadInstructionsSummary: 'Hämta instruktioner för att ladda upp en fil innan dokumentet sparas', + uploadInstructionsResult: + 'Vart filens byte ska skickas och vilket `file`-värde som ska skickas med skapa- eller uppdatera-begäran', + uploadStagePutSummary: 'Skicka filens byte för en tillfällig uppladdning', + uploadStageDeleteSummary: 'Ta bort en tillfällig uppladdning', + uploadStageResult: 'Klart, inget innehåll', error400: 'Validerings- eller frågefel (ValidationError, QueryError)', error401: 'Inte autentiserad (AuthenticationError)', @@ -92,6 +116,8 @@ export const sv: PluginDefaultTranslationsObject = { tagAuth: 'Autentisering', tagVersions: 'Versioner', tagJobs: 'Jobb', + tagUploads: 'Uppladdningar', + tagAccess: 'Åtkomst', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/translations/locales/ta.ts b/src/shared/translations/locales/ta.ts similarity index 73% rename from src/translations/locales/ta.ts rename to src/shared/translations/locales/ta.ts index 193a42f..014a2d3 100644 --- a/src/translations/locales/ta.ts +++ b/src/shared/translations/locales/ta.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const ta: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,10 @@ export const ta: PluginDefaultTranslationsObject = { paramLocale: 'திருப்பி அளிக்க வேண்டிய மொழியிடம், அல்லது ஒவ்வொரு மொழியிடத்திற்கும் `all`. API விளக்கத்தில் உள்ளூராக்கம் (Localization) பகுதியைப் பார்க்கவும்.', paramFallbackLocale: 'விடுபட்ட உள்ளூராக்கப்பட்ட மதிப்புகளுக்குப் பின்வாங்கப் பயன்படும் மொழியிடம், அல்லது முடக்க `none`.', + paramValidateLocale: + 'சரிபார்க்க வேண்டிய மொழியிடங்கள், அல்லது ஒவ்வொரு மொழியிடத்திற்கும் `all`. ஒன்றுக்கு மேற்பட்ட மொழியிடங்களுக்கு அளவுருவை மீண்டும் குறிப்பிடவும்.', + paramComputeHierarchyPaths: + '`{{slugPath}}` மற்றும் `{{titlePath}}` பாதைகளைக் கணக்கிட true என அமைக்கவும். இந்தப் புலங்களில் ஒன்றைத் தேர்ந்தெடுத்தாலும் அவை கணக்கிடப்படும்.', schemaSelect: 'திருப்பி அளிக்க வேண்டிய புலங்களைத் தேர்வுசெய்யவும், எ.கா. `select[title]=true`. அனைத்தையும் திருப்பி அளிக்க இதைத் தவிர்க்கவும்.', @@ -26,6 +30,10 @@ export const ta: PluginDefaultTranslationsObject = { 'Payload `where` வடிகட்டி. ஒரு புலத்தை, பின் ஒரு செயற்குறியை உள்ளமைக்கவும்: `where[field][equals]=value`. `and` / `or` வரிசைகளுடன் விதிகளை இணைக்கவும், எ.கா. `where[or][0][field][equals]=value`. ஒவ்வொரு புலமும் அதன் வகைக்குப் பொருந்தும் செயற்குறிகளை மட்டுமே பட்டியலிடுகிறது.', schemaSupportedTimezones: 'IANA வடிவத்தில் ஆதரிக்கப்படும் நேர மண்டலங்கள்.', schemaPerLocale: '`locale=all` எனும்போது திருப்பி அளிக்கப்படும், ஒவ்வொரு மொழியிடத்திற்குமான மதிப்புகள்.', + schemaHierarchySlugPath: + 'ஸ்லக் பாதை, எ.கா. `parent/child`. `computeHierarchyPaths=true` உடன் படிக்கும்போது அல்லது தேர்ந்தெடுக்கும்போது கணக்கிடப்படும். `where`-இல் பயன்படுத்த முடியாது.', + schemaHierarchyTitlePath: + 'தலைப்புப் பாதை, எ.கா. `Parent/Child`. `computeHierarchyPaths=true` உடன் படிக்கும்போது அல்லது தேர்ந்தெடுக்கும்போது கணக்கிடப்படும். `where`-இல் பயன்படுத்த முடியாது.', collectionList: 'ஆவணங்களின் பக்கமிடப்பட்ட பட்டியல்', collectionDoc: 'ஒரு ஆவணம்', @@ -36,6 +44,10 @@ export const ta: PluginDefaultTranslationsObject = { collectionBulkDelete: 'மொத்த நீக்கம் முடிவு', collectionCount: 'ஆவண எண்ணிக்கை', collectionDuplicated: 'நகலெடுக்கப்பட்ட ஆவணம்', + validateResult: + 'சரிபார்ப்பு முடிவு. எதுவும் சேமிக்கப்படாது. தவறான புல மதிப்புகள் பிழை நிலைக்குப் பதிலாக பிழைகளுடன் `valid: false` என்பதைத் திருப்பி அளிக்கும்.', + validateBody: + 'சரிபார்க்க வேண்டிய ஆவணத் தரவு. சேமிக்கப்பட்ட ஆவணம் அல்லது உலகளாவிய ஆவணத்தில், தரவு சமீபத்திய வரைவின் மேல் இணைக்கப்படும்; வரைவு இல்லையெனில் சேமிக்கப்பட்ட ஆவணத்தின் மேல் இணைக்கப்படும்.', globalDoc: 'உலகளாவிய ஆவணம்', @@ -47,9 +59,12 @@ export const ta: PluginDefaultTranslationsObject = { authResetPassword: 'கடவுச்சொல் மீட்டமைப்பு முடிவு', authFirstRegister: 'அங்கீகார டோக்கனுடன் உருவாக்கப்பட்ட முதல் பயனர்', authInit: 'இந்த அங்கீகாரத் தொகுப்பில் இதுவரை ஏதேனும் பயனர்கள் உள்ளனரா என்பது', - authAccess: 'இந்தத் தொகுப்பிற்கான தற்போதைய பயனரின் அணுகல் (அனுமதிகள்)', + authAccess: 'அனைத்து தொகுப்புகள் மற்றும் உலகளாவியவைகளுக்கான தற்போதைய பயனரின் அனுமதிகள்', + docAccess: 'இந்த ஆவணத்துக்கான தற்போதைய பயனரின் அனுமதிகள்', + docAccessBody: 'அணுகலைச் சரிபார்க்க வேண்டிய ஆவணத் தரவு. இல்லையெனில், சேமிக்கப்பட்ட ஆவணம் இருந்தால் Payload அதைப் பயன்படுத்தும்.', authUnlock: 'திறப்பு முடிவு', authVerify: 'சரிபார்ப்பு முடிவு', + apiKeyReveal: 'மறைகுறியாக்கம் நீக்கப்பட்ட API விசை', versionList: 'பதிப்புகளின் பக்கமிடப்பட்ட பட்டியல்', versionSingle: 'ஒரு பதிப்பு', @@ -71,6 +86,14 @@ export const ta: PluginDefaultTranslationsObject = { uploadBody: 'ஒரு கோப்பைப் பதிவேற்ற `multipart/form-data` ஐ அனுப்பவும் (ஒரு பைனரி `file` பகுதி மற்றும் JSON-stringified புலங்களைக் கொண்ட `_payload` பகுதி), அல்லது கோப்பு இல்லாதபோது புலங்களை மட்டும் கொண்ட `application/json` ஐ அனுப்பவும்.', uploadPayloadField: 'JSON-stringified {{schema}} புலங்கள். எடுத்துக்காட்டு: `{"alt":"A caption"}`.', + fileServe: 'கோப்பு', + filePartial: '`Range` கோரிக்கைக்கான கோப்பின் ஒரு பகுதி', + uploadInstructionsSummary: 'ஆவணத்தைச் சேமிக்கும் முன் கோப்பைப் பதிவேற்றுவதற்கான வழிமுறைகளைப் பெறவும்', + uploadInstructionsResult: + 'கோப்பின் பைட்டுகளை எங்கு அனுப்ப வேண்டும் மற்றும் உருவாக்கு அல்லது புதுப்பி கோரிக்கையுடன் அனுப்ப வேண்டிய `file` மதிப்பு', + uploadStagePutSummary: 'தற்காலிகப் பதிவேற்றத்திற்குக் கோப்பின் பைட்டுகளை அனுப்பவும்', + uploadStageDeleteSummary: 'தற்காலிகப் பதிவேற்றத்தை நீக்கவும்', + uploadStageResult: 'முடிந்தது, உள்ளடக்கம் இல்லை', error400: 'சரிபார்ப்பு அல்லது வினவல் பிழை (ValidationError, QueryError)', error401: 'அங்கீகரிக்கப்படவில்லை (AuthenticationError)', @@ -92,6 +115,8 @@ export const ta: PluginDefaultTranslationsObject = { tagAuth: 'அங்கீகாரம்', tagVersions: 'பதிப்புகள்', tagJobs: 'பணிகள்', + tagUploads: 'பதிவேற்றங்கள்', + tagAccess: 'அணுகல்', localizationHeading: 'உள்ளூராக்கம்', localizationNote: diff --git a/src/translations/locales/th.ts b/src/shared/translations/locales/th.ts similarity index 75% rename from src/translations/locales/th.ts rename to src/shared/translations/locales/th.ts index f6c5338..5ae41de 100644 --- a/src/translations/locales/th.ts +++ b/src/shared/translations/locales/th.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const th: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const th: PluginDefaultTranslationsObject = { 'เมื่อใช้ `locale=all` ให้ตั้งค่าเป็น false เพื่อเก็บฟิลด์ที่แปลภาษาไว้เป็นออบเจ็กต์แยกตามแต่ละโลแคล ค่าเริ่มต้นคือ true', paramLocale: 'โลแคลที่จะคืนค่า หรือ `all` เพื่อรับทุกโลแคล ดูที่หัวข้อ Localization ในคำอธิบาย API', paramFallbackLocale: 'โลแคลที่จะใช้แทนเมื่อไม่มีค่าที่แปลภาษาไว้ หรือ `none` เพื่อปิดการใช้งาน', + paramValidateLocale: 'โลแคลที่จะตรวจสอบ หรือ `all` เพื่อรับทุกโลแคล ระบุพารามิเตอร์ซ้ำหากมีมากกว่าหนึ่งโลแคล', + paramComputeHierarchyPaths: + 'ตั้งเป็น true เพื่อคำนวณเส้นทาง `{{slugPath}}` และ `{{titlePath}}` การเลือกฟิลด์ใดฟิลด์หนึ่งก็คำนวณเช่นกัน', schemaSelect: 'เลือกฟิลด์ที่จะคืนค่า เช่น `select[title]=true` หากไม่ระบุจะคืนค่าทั้งหมด', schemaPopulate: 'ดึงข้อมูลเอกสารที่เกี่ยวข้องในแต่ละคอลเลกชัน เช่น `populate[posts][title]=true`', @@ -24,6 +27,10 @@ export const th: PluginDefaultTranslationsObject = { 'ตัวกรอง `where` ของ Payload ซ้อนฟิลด์แล้วตามด้วยตัวดำเนินการ: `where[field][equals]=value` รวมหลายเงื่อนไขด้วยอาเรย์ `and` / `or` เช่น `where[or][0][field][equals]=value` แต่ละฟิลด์จะแสดงเฉพาะตัวดำเนินการที่ใช้ได้กับชนิดของฟิลด์นั้น', schemaSupportedTimezones: 'เขตเวลาที่รองรับในรูปแบบ IANA', schemaPerLocale: 'ค่าแยกตามแต่ละโลแคล จะคืนค่าเมื่อใช้ `locale=all`', + schemaHierarchySlugPath: + 'เส้นทางสลัก เช่น `parent/child` คำนวณเมื่ออ่านพร้อม `computeHierarchyPaths=true` หรือเมื่อถูกเลือก ใช้ใน `where` ไม่ได้', + schemaHierarchyTitlePath: + 'เส้นทางชื่อเรื่อง เช่น `Parent/Child` คำนวณเมื่ออ่านพร้อม `computeHierarchyPaths=true` หรือเมื่อถูกเลือก ใช้ใน `where` ไม่ได้', collectionList: 'รายการเอกสารแบบแบ่งหน้า', collectionDoc: 'เอกสารหนึ่งรายการ', @@ -34,6 +41,9 @@ export const th: PluginDefaultTranslationsObject = { collectionBulkDelete: 'ผลการลบแบบกลุ่ม', collectionCount: 'จำนวนเอกสาร', collectionDuplicated: 'เอกสารที่ทำสำเนา', + validateResult: 'ผลการตรวจสอบ ไม่มีการบันทึกใดๆ ค่าของฟิลด์ที่ไม่ถูกต้องจะคืนค่า `valid: false` พร้อมข้อผิดพลาด ไม่ใช่สถานะข้อผิดพลาด', + validateBody: + 'ข้อมูลเอกสารที่จะตรวจสอบ สำหรับเอกสารที่บันทึกไว้หรือเอกสาร global ข้อมูลจะถูกรวมทับแบบร่างล่าสุด หรือเอกสารที่บันทึกไว้หากไม่มีแบบร่าง', globalDoc: 'เอกสาร global', @@ -45,9 +55,12 @@ export const th: PluginDefaultTranslationsObject = { authResetPassword: 'ผลการรีเซ็ตรหัสผ่าน', authFirstRegister: 'ผู้ใช้คนแรกที่สร้างขึ้นพร้อมกับโทเค็นการยืนยันตัวตน', authInit: 'ระบุว่าคอลเลกชันการยืนยันตัวตนนี้มีผู้ใช้อยู่แล้วหรือยัง', - authAccess: 'สิทธิ์การเข้าถึง (permissions) ของผู้ใช้ปัจจุบันสำหรับคอลเลกชันนี้', + authAccess: 'สิทธิ์ของผู้ใช้ปัจจุบันสำหรับทุกคอลเลกชันและ Globals', + docAccess: 'สิทธิ์ของผู้ใช้ปัจจุบันสำหรับเอกสารนี้', + docAccessBody: 'ข้อมูลเอกสารที่ใช้ตรวจสอบสิทธิ์ หากไม่ระบุ Payload จะใช้เอกสารที่บันทึกไว้ ถ้ามี', authUnlock: 'ผลการปลดล็อก', authVerify: 'ผลการยืนยัน', + apiKeyReveal: 'API key ที่ถอดรหัสแล้ว', versionList: 'รายการเวอร์ชันแบบแบ่งหน้า', versionSingle: 'เวอร์ชันหนึ่งรายการ', @@ -69,6 +82,13 @@ export const th: PluginDefaultTranslationsObject = { uploadBody: 'ส่ง `multipart/form-data` เพื่ออัปโหลดไฟล์ (ส่วน `file` ที่เป็นไบนารี พร้อมส่วน `_payload` ที่มีฟิลด์ในรูปแบบ JSON string) หรือส่ง `application/json` พร้อมเฉพาะฟิลด์เมื่อไม่มีไฟล์', uploadPayloadField: 'ฟิลด์ {{schema}} ในรูปแบบ JSON string ตัวอย่าง: `{"alt":"A caption"}`', + fileServe: 'ไฟล์', + filePartial: 'ส่วนหนึ่งของไฟล์ สำหรับคำขอ `Range`', + uploadInstructionsSummary: 'รับคำแนะนำในการอัปโหลดไฟล์ก่อนบันทึกเอกสาร', + uploadInstructionsResult: 'ปลายทางสำหรับส่งไบต์ของไฟล์ และค่า `file` ที่ต้องส่งไปกับคำขอสร้างหรืออัปเดต', + uploadStagePutSummary: 'ส่งไบต์ของไฟล์สำหรับการอัปโหลดชั่วคราว', + uploadStageDeleteSummary: 'ลบการอัปโหลดชั่วคราว', + uploadStageResult: 'เสร็จแล้ว ไม่มีเนื้อหา', error400: 'ข้อผิดพลาดในการตรวจสอบความถูกต้องหรือการค้นหา (ValidationError, QueryError)', error401: 'ยังไม่ได้ยืนยันตัวตน (AuthenticationError)', @@ -89,6 +109,8 @@ export const th: PluginDefaultTranslationsObject = { tagAuth: 'การยืนยันตัวตน', tagVersions: 'เวอร์ชัน', tagJobs: 'งาน', + tagUploads: 'การอัปโหลด', + tagAccess: 'การเข้าถึง', localizationHeading: 'การแปลภาษา', localizationNote: diff --git a/src/translations/locales/tr.ts b/src/shared/translations/locales/tr.ts similarity index 75% rename from src/translations/locales/tr.ts rename to src/shared/translations/locales/tr.ts index 82ac751..4b7d2d1 100644 --- a/src/translations/locales/tr.ts +++ b/src/shared/translations/locales/tr.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const tr: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -18,6 +18,10 @@ export const tr: PluginDefaultTranslationsObject = { 'Döndürülecek yerel ayar veya her yerel ayar için `all`. API açıklamasındaki Yerelleştirme bölümüne bakın.', paramFallbackLocale: 'Eksik yerelleştirilmiş değerler için geri dönülecek yerel ayar veya devre dışı bırakmak için `none`.', + paramValidateLocale: + 'Doğrulanacak yerel ayarlar veya her yerel ayar için `all`. Birden fazla yerel ayar için parametreyi tekrarlayın.', + paramComputeHierarchyPaths: + '`{{slugPath}}` ve `{{titlePath}}` yollarını hesaplamak için true yapın. Bu alanlardan birini seçmek de bunları hesaplar.', schemaSelect: 'Döndürülecek alanları seçin, örn. `select[title]=true`. Tümünü döndürmek için boş bırakın.', schemaPopulate: 'İlişkili dokümanları koleksiyon başına doldurun, örn. `populate[posts][title]=true`.', @@ -26,6 +30,10 @@ export const tr: PluginDefaultTranslationsObject = { 'Payload `where` filtresi. Önce bir alanı, sonra bir operatörü yuvalayın: `where[field][equals]=value`. Yan tümceleri `and` / `or` dizileriyle birleştirin, örn. `where[or][0][field][equals]=value`. Her alan yalnızca kendi türü için geçerli operatörleri listeler.', schemaSupportedTimezones: 'IANA formatında desteklenen saat dilimleri.', schemaPerLocale: '`locale=all` kullanıldığında döndürülen yerel başına değerler.', + schemaHierarchySlugPath: + 'Slug yolu, örn. `parent/child`. Okurken `computeHierarchyPaths=true` ile ya da seçildiğinde hesaplanır. `where` içinde kullanılamaz.', + schemaHierarchyTitlePath: + 'Başlık yolu, örn. `Parent/Child`. Okurken `computeHierarchyPaths=true` ile ya da seçildiğinde hesaplanır. `where` içinde kullanılamaz.', collectionList: 'Sayfalandırılmış doküman listesi', collectionDoc: 'Tek bir doküman', @@ -36,6 +44,10 @@ export const tr: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Toplu silme sonucu', collectionCount: 'Doküman sayısı', collectionDuplicated: 'Çoğaltılan doküman', + validateResult: + 'Doğrulama sonucu. Hiçbir şey kaydedilmez. Geçersiz alan değerleri hata durumu değil, hatalarla birlikte `valid: false` döndürür.', + validateBody: + 'Doğrulanacak doküman verisi. Kayıtlı bir dokümanda veya genel dokümanda veriler en son taslağın üzerine, taslak yoksa kayıtlı dokümanın üzerine birleştirilir.', globalDoc: 'Genel doküman', @@ -47,9 +59,12 @@ export const tr: PluginDefaultTranslationsObject = { authResetPassword: 'Parola sıfırlama sonucu', authFirstRegister: 'Bir auth token ile oluşturulan ilk kullanıcı', authInit: 'Bu auth koleksiyonunda henüz kullanıcı olup olmadığı', - authAccess: 'Geçerli kullanıcının bu koleksiyon için erişimi (izinleri)', + authAccess: 'Geçerli kullanıcının tüm koleksiyonlar ve globaller için izinleri', + docAccess: 'Geçerli kullanıcının bu doküman için izinleri', + docAccessBody: 'Erişimin kontrol edileceği doküman verisi. Yoksa Payload, varsa kayıtlı dokümanı kullanır.', authUnlock: 'Kilit açma sonucu', authVerify: 'Doğrulama sonucu', + apiKeyReveal: 'Şifresi çözülmüş API anahtarı', versionList: 'Sayfalandırılmış sürüm listesi', versionSingle: 'Tek bir sürüm', @@ -71,6 +86,14 @@ export const tr: PluginDefaultTranslationsObject = { uploadBody: 'Bir dosya yüklemek için `multipart/form-data` gönderin (ikili bir `file` bölümü ile JSON olarak dizgeleştirilmiş alanları içeren bir `_payload` bölümü) veya dosya yoksa yalnızca alanlarla `application/json` gönderin.', uploadPayloadField: 'JSON olarak dizgeleştirilmiş {{schema}} alanları. Örnek: `{\\"alt\\":\\"A caption\\"}`.', + fileServe: 'Dosya', + filePartial: '`Range` isteği için dosyanın bir bölümü', + uploadInstructionsSummary: 'Dokümanı kaydetmeden önce dosya yükleme talimatlarını alın', + uploadInstructionsResult: + 'Dosya baytlarının nereye gönderileceği ve oluşturma ya da güncelleme isteğiyle gönderilecek `file` değeri', + uploadStagePutSummary: 'Geçici yükleme için dosya baytlarını gönderin', + uploadStageDeleteSummary: 'Geçici yüklemeyi silin', + uploadStageResult: 'Tamamlandı, içerik yok', error400: 'Doğrulama veya sorgu hatası (ValidationError, QueryError)', error401: 'Kimlik doğrulanmadı (AuthenticationError)', @@ -92,6 +115,8 @@ export const tr: PluginDefaultTranslationsObject = { tagAuth: 'Kimlik Doğrulama', tagVersions: 'Sürümler', tagJobs: 'İşler', + tagUploads: 'Yüklemeler', + tagAccess: 'Erişim', localizationHeading: 'Yerelleştirme', localizationNote: diff --git a/src/translations/locales/uk.ts b/src/shared/translations/locales/uk.ts similarity index 74% rename from src/translations/locales/uk.ts rename to src/shared/translations/locales/uk.ts index 679c783..68753c9 100644 --- a/src/translations/locales/uk.ts +++ b/src/shared/translations/locales/uk.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const uk: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,9 @@ export const uk: PluginDefaultTranslationsObject = { 'З `locale=all` встановіть false, щоб зберегти локалізовані поля як об’єкти за локалями. За замовчуванням true.', paramLocale: 'Локаль для повернення або `all` для всіх локалей. Див. розділ Localization в описі API.', paramFallbackLocale: 'Локаль для підстановки відсутніх локалізованих значень або `none`, щоб вимкнути.', + paramValidateLocale: 'Локалі для перевірки або `all` для всіх локалей. Для кількох локалей повторіть параметр.', + paramComputeHierarchyPaths: + 'Передайте true, щоб обчислити шляхи `{{slugPath}}` і `{{titlePath}}`. Вибір будь-якого з цих полів теж їх обчислює.', schemaSelect: 'Виберіть поля для повернення, напр. `select[title]=true`. Пропустіть, щоб повернути всі.', schemaPopulate: 'Заповнюйте пов’язані документи для кожної колекції, напр. `populate[posts][title]=true`.', @@ -24,6 +27,10 @@ export const uk: PluginDefaultTranslationsObject = { 'Фільтр Payload `where`. Вкладіть поле, а потім оператор: `where[field][equals]=value`. Комбінуйте умови за допомогою масивів `and` / `or`, напр. `where[or][0][field][equals]=value`. Кожне поле перелічує лише оператори, дійсні для його типу.', schemaSupportedTimezones: 'Підтримувані часові пояси у форматі IANA.', schemaPerLocale: 'Значення для кожної локалі, що повертаються, коли `locale=all`.', + schemaHierarchySlugPath: + 'Шлях зі slug, наприклад `parent/child`. Обчислюється під час читання з `computeHierarchyPaths=true` або якщо поле вибрано. Не можна використовувати в `where`.', + schemaHierarchyTitlePath: + 'Шлях із заголовків, наприклад `Parent/Child`. Обчислюється під час читання з `computeHierarchyPaths=true` або якщо поле вибрано. Не можна використовувати в `where`.', collectionList: 'Посторінковий список документів', collectionDoc: 'Окремий документ', @@ -34,6 +41,10 @@ export const uk: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Результат масового видалення', collectionCount: 'Кількість документів', collectionDuplicated: 'Дубльований документ', + validateResult: + 'Результат перевірки. Нічого не зберігається. Недійсні значення полів повертають `valid: false` з помилками, а не статус помилки.', + validateBody: + 'Дані документа для перевірки. Для збереженого документа або глобального документа дані накладаються на останню чернетку, а якщо чернетки немає, на збережений документ.', globalDoc: 'Глобальний документ', @@ -45,9 +56,13 @@ export const uk: PluginDefaultTranslationsObject = { authResetPassword: 'Результат скидання пароля', authFirstRegister: 'Перший користувач, створений із токеном автентифікації', authInit: 'Чи має ця колекція автентифікації хоча б одного користувача', - authAccess: 'Доступ (дозволи) поточного користувача до цієї колекції', + authAccess: 'Дозволи поточного користувача для всіх колекцій і глобальних', + docAccess: 'Дозволи поточного користувача для цього документа', + docAccessBody: + 'Дані документа, за якими перевіряється доступ. Без них Payload використовує збережений документ, якщо він є.', authUnlock: 'Результат розблокування', authVerify: 'Результат підтвердження', + apiKeyReveal: 'Розшифрований API-ключ', versionList: 'Посторінковий список версій', versionSingle: 'Окрема версія', @@ -69,6 +84,14 @@ export const uk: PluginDefaultTranslationsObject = { uploadBody: 'Надішліть `multipart/form-data`, щоб завантажити файл (двійкова частина `file` плюс частина `_payload` з полями у вигляді JSON-рядка), або `application/json` лише з полями, коли файлу немає.', uploadPayloadField: 'Поля {{schema}} у вигляді JSON-рядка. Приклад: `{"alt":"A caption"}`.', + fileServe: 'Файл', + filePartial: 'Частина файлу для запиту з `Range`', + uploadInstructionsSummary: 'Отримати інструкції для завантаження файлу перед збереженням документа', + uploadInstructionsResult: + 'Куди надіслати байти файлу і яке значення `file` передати в запиті на створення чи оновлення', + uploadStagePutSummary: 'Надіслати байти файлу для тимчасового завантаження', + uploadStageDeleteSummary: 'Видалити тимчасове завантаження', + uploadStageResult: 'Готово, без вмісту', error400: 'Помилка валідації або запиту (ValidationError, QueryError)', error401: 'Не автентифіковано (AuthenticationError)', @@ -90,6 +113,8 @@ export const uk: PluginDefaultTranslationsObject = { tagAuth: 'Автентифікація', tagVersions: 'Версії', tagJobs: 'Завдання', + tagUploads: 'Завантаження', + tagAccess: 'Доступ', localizationHeading: 'Локалізація', localizationNote: diff --git a/src/translations/locales/vi.ts b/src/shared/translations/locales/vi.ts similarity index 75% rename from src/translations/locales/vi.ts rename to src/shared/translations/locales/vi.ts index 07d4b68..6fae4c5 100644 --- a/src/translations/locales/vi.ts +++ b/src/shared/translations/locales/vi.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const vi: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -16,6 +16,10 @@ export const vi: PluginDefaultTranslationsObject = { 'Với `locale=all`, đặt false để giữ các trường đa ngôn ngữ dưới dạng đối tượng theo từng ngôn ngữ. Mặc định là true.', paramLocale: 'Ngôn ngữ cần trả về, hoặc `all` để lấy mọi ngôn ngữ. Xem phần Đa ngôn ngữ trong phần mô tả API.', paramFallbackLocale: 'Ngôn ngữ dự phòng cho các giá trị đa ngôn ngữ bị thiếu, hoặc `none` để tắt.', + paramValidateLocale: + 'Các ngôn ngữ cần xác thực, hoặc `all` để lấy mọi ngôn ngữ. Lặp lại tham số nếu có nhiều ngôn ngữ.', + paramComputeHierarchyPaths: + 'Đặt true để tính các đường dẫn `{{slugPath}}` và `{{titlePath}}`. Chọn một trong hai trường cũng sẽ tính chúng.', schemaSelect: 'Chọn các trường cần trả về, ví dụ `select[title]=true`. Bỏ trống để trả về tất cả.', schemaPopulate: 'Điền dữ liệu các tài liệu liên quan theo từng collection, ví dụ `populate[posts][title]=true`.', @@ -24,6 +28,10 @@ export const vi: PluginDefaultTranslationsObject = { 'Bộ lọc `where` của Payload. Lồng một trường rồi đến một toán tử: `where[field][equals]=value`. Kết hợp các mệnh đề bằng mảng `and` / `or`, ví dụ `where[or][0][field][equals]=value`. Mỗi trường chỉ liệt kê các toán tử hợp lệ với kiểu của nó.', schemaSupportedTimezones: 'Các múi giờ được hỗ trợ theo định dạng IANA.', schemaPerLocale: 'Giá trị theo từng ngôn ngữ, được trả về khi dùng `locale=all`.', + schemaHierarchySlugPath: + 'Đường dẫn slug, ví dụ `parent/child`. Được tính khi đọc với `computeHierarchyPaths=true` hoặc khi được chọn. Không dùng được trong `where`.', + schemaHierarchyTitlePath: + 'Đường dẫn tiêu đề, ví dụ `Parent/Child`. Được tính khi đọc với `computeHierarchyPaths=true` hoặc khi được chọn. Không dùng được trong `where`.', collectionList: 'Danh sách tài liệu được phân trang', collectionDoc: 'Một tài liệu đơn lẻ', @@ -34,6 +42,10 @@ export const vi: PluginDefaultTranslationsObject = { collectionBulkDelete: 'Kết quả xóa hàng loạt', collectionCount: 'Số lượng tài liệu', collectionDuplicated: 'Tài liệu đã được nhân bản', + validateResult: + 'Kết quả xác thực. Không có gì được lưu. Giá trị trường không hợp lệ trả về `valid: false` kèm các lỗi, không phải trạng thái lỗi.', + validateBody: + 'Dữ liệu tài liệu cần xác thực. Với tài liệu đã lưu hoặc tài liệu global, dữ liệu được gộp lên bản nháp mới nhất, hoặc tài liệu đã lưu nếu không có bản nháp.', globalDoc: 'Tài liệu global', @@ -45,9 +57,13 @@ export const vi: PluginDefaultTranslationsObject = { authResetPassword: 'Kết quả đặt lại mật khẩu', authFirstRegister: 'Người dùng đầu tiên, được tạo cùng với một token xác thực', authInit: 'Cho biết collection xác thực này đã có người dùng nào hay chưa', - authAccess: 'Quyền truy cập (permissions) của người dùng hiện tại đối với collection này', + authAccess: 'Quyền của người dùng hiện tại đối với mọi collection và global', + docAccess: 'Quyền của người dùng hiện tại đối với tài liệu này', + docAccessBody: + 'Dữ liệu tài liệu dùng để kiểm tra quyền truy cập. Nếu không có, Payload dùng tài liệu đã lưu, nếu có.', authUnlock: 'Kết quả mở khóa', authVerify: 'Kết quả xác minh', + apiKeyReveal: 'API key đã được giải mã', versionList: 'Danh sách phiên bản được phân trang', versionSingle: 'Một phiên bản đơn lẻ', @@ -69,6 +85,13 @@ export const vi: PluginDefaultTranslationsObject = { uploadBody: 'Gửi `multipart/form-data` để tải lên một tệp (một phần `file` nhị phân cùng với một phần `_payload` chứa các trường đã được chuyển thành chuỗi JSON), hoặc gửi `application/json` chỉ với các trường khi không có tệp.', uploadPayloadField: 'Các trường {{schema}} đã được chuyển thành chuỗi JSON. Ví dụ: `{"alt":"A caption"}`.', + fileServe: 'Tệp', + filePartial: 'Một phần của tệp, cho yêu cầu `Range`', + uploadInstructionsSummary: 'Lấy hướng dẫn tải tệp lên trước khi lưu tài liệu', + uploadInstructionsResult: 'Nơi gửi các byte của tệp, và giá trị `file` cần gửi kèm yêu cầu tạo hoặc cập nhật', + uploadStagePutSummary: 'Gửi các byte của tệp cho một lần tải lên tạm thời', + uploadStageDeleteSummary: 'Xóa một lần tải lên tạm thời', + uploadStageResult: 'Xong, không có nội dung', error400: 'Lỗi xác thực hoặc lỗi truy vấn (ValidationError, QueryError)', error401: 'Chưa được xác thực (AuthenticationError)', @@ -90,6 +113,8 @@ export const vi: PluginDefaultTranslationsObject = { tagAuth: 'Xác thực', tagVersions: 'Phiên bản', tagJobs: 'Jobs', + tagUploads: 'Tải lên', + tagAccess: 'Truy cập', localizationHeading: 'Đa ngôn ngữ', localizationNote: diff --git a/src/translations/locales/zh.ts b/src/shared/translations/locales/zh.ts similarity index 75% rename from src/translations/locales/zh.ts rename to src/shared/translations/locales/zh.ts index 959817e..b872511 100644 --- a/src/translations/locales/zh.ts +++ b/src/shared/translations/locales/zh.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const zh: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -15,6 +15,9 @@ export const zh: PluginDefaultTranslationsObject = { paramFlattenLocales: '在使用 `locale=all` 时,设为 false 可将本地化字段保留为按语言区域划分的对象。默认值为 true。', paramLocale: '要返回的语言区域,或使用 `all` 返回所有语言区域。参见 API 说明中的本地化(Localization)部分。', paramFallbackLocale: '缺少本地化值时回退到的语言区域,或使用 `none` 禁用回退。', + paramValidateLocale: '要验证的语言区域,或使用 `all` 验证所有语言区域。多个语言区域请重复该参数。', + paramComputeHierarchyPaths: + '设为 true 以计算 `{{slugPath}}` 和 `{{titlePath}}` 路径。选择其中任一字段也会计算它们。', schemaSelect: '选择要返回的字段,例如 `select[title]=true`。省略则返回所有字段。', schemaPopulate: '按集合填充关联文档,例如 `populate[posts][title]=true`。', @@ -23,6 +26,10 @@ export const zh: PluginDefaultTranslationsObject = { 'Payload 的 `where` 过滤器。先嵌套字段,再嵌套运算符:`where[field][equals]=value`。使用 `and` / `or` 数组组合多个条件,例如 `where[or][0][field][equals]=value`。每个字段仅列出对其类型有效的运算符。', schemaSupportedTimezones: 'IANA 格式的受支持时区。', schemaPerLocale: '按语言区域划分的值,在使用 `locale=all` 时返回。', + schemaHierarchySlugPath: + 'slug 路径,例如 `parent/child`。在读取时带上 `computeHierarchyPaths=true` 或选择该字段时计算。不能用于 `where`。', + schemaHierarchyTitlePath: + '标题路径,例如 `Parent/Child`。在读取时带上 `computeHierarchyPaths=true` 或选择该字段时计算。不能用于 `where`。', collectionList: '文档的分页列表', collectionDoc: '单个文档', @@ -33,6 +40,9 @@ export const zh: PluginDefaultTranslationsObject = { collectionBulkDelete: '批量删除结果', collectionCount: '文档数量', collectionDuplicated: '复制出的文档', + validateResult: '验证结果。不会保存任何内容。无效的字段值会返回带有错误信息的 `valid: false`,而不是错误状态。', + validateBody: + '要验证的文档数据。对于已保存的文档或全局文档,数据会合并到最新草稿上;没有草稿时合并到已保存的文档上。', globalDoc: '该全局文档', @@ -44,9 +54,12 @@ export const zh: PluginDefaultTranslationsObject = { authResetPassword: '密码重置结果', authFirstRegister: '第一个用户,已创建并附带认证令牌', authInit: '该认证集合中是否已存在任何用户', - authAccess: '当前用户对该集合的访问权限(permissions)', + authAccess: '当前用户对所有集合和全局的权限', + docAccess: '当前用户对此文档的权限', + docAccessBody: '用于检查访问权限的文档数据。省略时,Payload 使用已保存的文档(如果有)。', authUnlock: '解锁结果', authVerify: '验证结果', + apiKeyReveal: '解密后的 API 密钥', versionList: '版本的分页列表', versionSingle: '单个版本', @@ -68,6 +81,13 @@ export const zh: PluginDefaultTranslationsObject = { uploadBody: '发送 `multipart/form-data` 以上传文件(一个二进制 `file` 部分,加上一个包含 JSON 字符串化字段的 `_payload` 部分),或在没有文件时仅发送字段并使用 `application/json`。', uploadPayloadField: 'JSON 字符串化的 {{schema}} 字段。示例:`{"alt":"A caption"}`。', + fileServe: '文件', + filePartial: '`Range` 请求返回的部分文件', + uploadInstructionsSummary: '在保存文档前获取上传文件的说明', + uploadInstructionsResult: '文件字节的发送位置,以及在创建或更新请求中发送的 `file` 值', + uploadStagePutSummary: '为临时上传发送文件字节', + uploadStageDeleteSummary: '删除临时上传', + uploadStageResult: '完成,无内容', error400: '验证或查询错误(ValidationError、QueryError)', error401: '未认证(AuthenticationError)', @@ -88,6 +108,8 @@ export const zh: PluginDefaultTranslationsObject = { tagAuth: '认证', tagVersions: '版本', tagJobs: '作业', + tagUploads: '上传', + tagAccess: '访问', localizationHeading: '本地化', localizationNote: diff --git a/src/translations/locales/zhTw.ts b/src/shared/translations/locales/zhTw.ts similarity index 75% rename from src/translations/locales/zhTw.ts rename to src/shared/translations/locales/zhTw.ts index 789c13d..69ecbf0 100644 --- a/src/translations/locales/zhTw.ts +++ b/src/shared/translations/locales/zhTw.ts @@ -1,4 +1,4 @@ -import type { PluginDefaultTranslationsObject } from '../types.js' +import type { PluginDefaultTranslationsObject } from '@/shared/translations/types.js' export const zhTw: PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -15,6 +15,9 @@ export const zhTw: PluginDefaultTranslationsObject = { paramFlattenLocales: '搭配 `locale=all` 使用時,設為 false 可將本地化欄位保留為各語系物件。預設為 true。', paramLocale: '要回傳的語系,或使用 `all` 取得所有語系。請參閱 API 說明中的「本地化」一節。', paramFallbackLocale: '當本地化值缺失時用以回退的語系,或使用 `none` 停用。', + paramValidateLocale: '要驗證的語系,或使用 `all` 驗證所有語系。多個語系請重複該參數。', + paramComputeHierarchyPaths: + '設為 true 以計算 `{{slugPath}}` 與 `{{titlePath}}` 路徑。選擇其中任一欄位也會計算它們。', schemaSelect: '選擇要回傳的欄位,例如 `select[title]=true`。省略則回傳全部。', schemaPopulate: '依集合填充關聯文件,例如 `populate[posts][title]=true`。', @@ -23,6 +26,10 @@ export const zhTw: PluginDefaultTranslationsObject = { 'Payload 的 `where` 篩選器。先巢狀欄位再接運算子:`where[field][equals]=value`。使用 `and` / `or` 陣列組合子句,例如 `where[or][0][field][equals]=value`。每個欄位僅列出其型別適用的運算子。', schemaSupportedTimezones: '採用 IANA 格式的支援時區。', schemaPerLocale: '各語系的值,於 `locale=all` 時回傳。', + schemaHierarchySlugPath: + 'slug 路徑,例如 `parent/child`。在讀取時帶上 `computeHierarchyPaths=true` 或選擇該欄位時計算。不能用於 `where`。', + schemaHierarchyTitlePath: + '標題路徑,例如 `Parent/Child`。在讀取時帶上 `computeHierarchyPaths=true` 或選擇該欄位時計算。不能用於 `where`。', collectionList: '分頁的文件清單', collectionDoc: '單一文件', @@ -33,6 +40,9 @@ export const zhTw: PluginDefaultTranslationsObject = { collectionBulkDelete: '批次刪除結果', collectionCount: '文件數量', collectionDuplicated: '已複製的文件', + validateResult: '驗證結果。不會儲存任何內容。無效的欄位值會回傳含錯誤資訊的 `valid: false`,而不是錯誤狀態。', + validateBody: + '要驗證的文件資料。對於已儲存的文件或全域文件,資料會合併到最新草稿上;沒有草稿時合併到已儲存的文件上。', globalDoc: '全域文件', @@ -44,9 +54,12 @@ export const zhTw: PluginDefaultTranslationsObject = { authResetPassword: '密碼重設結果', authFirstRegister: '第一位使用者,建立時附帶驗證權杖', authInit: '此驗證集合是否已有任何使用者', - authAccess: '目前使用者對此集合的存取(權限)', + authAccess: '目前使用者對所有集合與全域的權限', + docAccess: '目前使用者對此文件的權限', + docAccessBody: '用於檢查存取權限的文件資料。省略時,Payload 會使用已儲存的文件(如果有)。', authUnlock: '解鎖結果', authVerify: '驗證結果', + apiKeyReveal: '解密後的 API 金鑰', versionList: '分頁的版本清單', versionSingle: '單一版本', @@ -68,6 +81,13 @@ export const zhTw: PluginDefaultTranslationsObject = { uploadBody: '傳送 `multipart/form-data` 以上傳檔案(一個二進位的 `file` 部分,加上一個含 JSON 字串化欄位的 `_payload` 部分),或在沒有檔案時僅以 `application/json` 傳送欄位。', uploadPayloadField: 'JSON 字串化的 {{schema}} 欄位。範例:`{"alt":"A caption"}`。', + fileServe: '檔案', + filePartial: '`Range` 請求傳回的部分檔案', + uploadInstructionsSummary: '在儲存文件前取得上傳檔案的說明', + uploadInstructionsResult: '檔案位元組的傳送位置,以及在建立或更新請求中傳送的 `file` 值', + uploadStagePutSummary: '為暫存上傳傳送檔案位元組', + uploadStageDeleteSummary: '刪除暫存上傳', + uploadStageResult: '完成,無內容', error400: '驗證或查詢錯誤 (ValidationError, QueryError)', error401: '未驗證 (AuthenticationError)', @@ -88,6 +108,8 @@ export const zhTw: PluginDefaultTranslationsObject = { tagAuth: '驗證', tagVersions: '版本', tagJobs: '工作', + tagUploads: '上傳', + tagAccess: '存取', localizationHeading: '本地化', localizationNote: diff --git a/src/translations/types.ts b/src/shared/translations/types.ts similarity index 81% rename from src/translations/types.ts rename to src/shared/translations/types.ts index ac198fb..f2fcb3b 100644 --- a/src/translations/types.ts +++ b/src/shared/translations/types.ts @@ -1,6 +1,6 @@ import type { DefaultTranslationKeys, NestedKeysStripped, TFunction } from '@payloadcms/translations' -import type { en } from './locales/en.js' +import type { en } from '@/shared/translations/locales/en.js' export type PluginDefaultTranslationsObject = { '@seshuk/payload-plugin-openapi': { @@ -17,6 +17,8 @@ export type PluginDefaultTranslationsObject = { paramFlattenLocales: string paramLocale: string paramFallbackLocale: string + paramValidateLocale: string + paramComputeHierarchyPaths: string schemaSelect: string schemaPopulate: string @@ -24,6 +26,8 @@ export type PluginDefaultTranslationsObject = { schemaWhere: string schemaSupportedTimezones: string schemaPerLocale: string + schemaHierarchySlugPath: string + schemaHierarchyTitlePath: string collectionList: string collectionDoc: string @@ -34,6 +38,8 @@ export type PluginDefaultTranslationsObject = { collectionBulkDelete: string collectionCount: string collectionDuplicated: string + validateResult: string + validateBody: string globalDoc: string @@ -46,8 +52,11 @@ export type PluginDefaultTranslationsObject = { authFirstRegister: string authInit: string authAccess: string + docAccess: string + docAccessBody: string authUnlock: string authVerify: string + apiKeyReveal: string versionList: string versionSingle: string @@ -68,6 +77,13 @@ export type PluginDefaultTranslationsObject = { uploadFile: string uploadBody: string uploadPayloadField: string + fileServe: string + filePartial: string + uploadInstructionsSummary: string + uploadInstructionsResult: string + uploadStagePutSummary: string + uploadStageDeleteSummary: string + uploadStageResult: string error400: string error401: string @@ -87,6 +103,8 @@ export type PluginDefaultTranslationsObject = { tagAuth: string tagVersions: string tagJobs: string + tagUploads: string + tagAccess: string localizationHeading: string localizationNote: string diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..8a8cc6c --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,432 @@ +import type { I18n } from '@payloadcms/translations' +import type { + ComponentsObject, + Document, + InfoObject, + PathsObject, + SecurityRequirementObject, + ServerObject, + TagObject, +} from '@scalar/openapi-types/3.2' +import type { BasePayload, PayloadRequest, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload' + +// Shared primitives + +export type OpenApiVersion = '3.0' | '3.1' | '3.2' + +export type EntityKind = 'collection' | 'global' + +export type OperationKind = EntityKind | 'custom' | 'jobs' | 'system' + +export type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete' + +export type IDType = 'text' | 'number' + +export type Entity = SanitizedCollectionConfig | SanitizedGlobalConfig + +export type EntityOperation = 'read' | 'create' | 'update' | 'delete' | 'validate' + +export type SecurityMarking = 'public' | 'secured' + +/** + * Marking for a collection or global, set in its `custom.openapi.security`: + * one marking for every operation, or one per operation. Operations you leave + * out keep the marking the access probe found. `read` also marks the version + * reads. `validate` falls back to `update` when you leave it out. + */ +export type EntitySecurityOverride = SecurityMarking | Partial> + +/** + * Text that follows the request language: a string, a map of language to + * string (`{ en: '…', de: '…' }`), or a function that gets `{ t, i18n }`. + */ +export type LocalizedText = + | string + | Record + | ((args: { t: (key: string, vars?: Record) => string; i18n: I18n }) => string) + +/** `custom.openapi` on a collection or global. */ +export interface EntityOpenApiMeta { + /** Overrides the public or secured marking. See {@link EntitySecurityOverride}. */ + security?: EntitySecurityOverride +} + +/** + * `custom.openapi` on a field: a partial Schema Object deep-merged on top of + * the inferred one. `description`, `title` and `summary` follow the + * request language. + */ +export interface FieldOpenApiMeta { + description?: LocalizedText + title?: LocalizedText + summary?: LocalizedText + [keyword: string]: unknown +} + +declare module 'payload' { + interface CollectionCustom { + openapi?: EntityOpenApiMeta + } + interface GlobalCustom { + openapi?: EntityOpenApiMeta + } + interface FieldCustom { + openapi?: FieldOpenApiMeta + } +} + +// Plugin options + +/** + * The OpenAPI `info` object. `title` and `version` are required. `description` + * can follow the request language, see {@link LocalizedText}. + */ +export type OpenApiInfo = Omit & { description?: LocalizedText } + +/** + * Base URLs for the spec's `servers`: a fixed list, or a function that gets + * the request. + */ +export type ServersOption = + | ServerObject[] + | ((args: { req: PayloadRequest }) => ServerObject[] | Promise) + +/** + * Interactive "Authorize" login for the docs UI. When on, the plugin adds a + * token endpoint and a security scheme so Scalar/Swagger can log in with a + * username and password instead of pasting a token. Off by default. + * - `true` — on, at the default path, against the first auth collection. + * - `{ path, collection }` — on, with a custom token endpoint path or auth collection. + */ +export type InteractiveAuthOption = + | boolean + | { + /** + * Token endpoint path, relative to the API route (`/openapi-auth` → `/api/openapi-auth`). + * @default '/openapi-auth' + */ + path?: string + /** + * Auth collection the login runs against. + * @default the first collection with `auth` enabled, or `'users'` + */ + collection?: string + } + +/** + * Gate for a plugin route. Return `true` to serve the request; anything else + * answers 403. + */ +export type AccessOption = (args: { req: PayloadRequest }) => boolean | Promise + +/** Match an entity by slug (any kind), a {kind,slug} pair, or a slug pattern. */ +export type EntityMatcher = string | RegExp | { kind: EntityKind; slug: string } + +/** + * Drops generated operations. Within one rule, every set field must match (AND); + * an operation is removed if any rule matches it. Fields: + * - `method` — HTTP method(s); omit to match any. + * - `slug` — collection or global slug, exact string or RegExp; omit to match any. + * A rule with `slug` never matches custom, jobs or system operations. + * - `kind` — restrict to one operation group; omit to match all. + * - `path` — RegExp tested against the final route path (e.g. `/api/posts/{id}`). + */ +export interface OperationRule { + method?: HttpMethod | HttpMethod[] + slug?: string | RegExp + kind?: OperationKind + path?: RegExp +} + +/** Context passed to `excludeOperations` functions and `security` for each operation. */ +export interface OperationContext { + method: HttpMethod + path: string + /** Collection or global slug. Unset for custom, jobs and system operations. */ + slug?: string + kind: OperationKind +} + +/** A rule, or a function that drops the operation when it returns (or resolves to) `true`. */ +export type OperationFilter = OperationRule | ((ctx: OperationContext) => boolean | Promise) + +type SecurityDecision = SecurityMarking | SecurityRequirementObject[] | undefined + +/** + * Decides the security marking of one operation. `'public'` removes it, + * `'secured'` sets the default requirement, an array sets that exact + * requirement, and `undefined` keeps `detected`. + */ +export type SecurityOption = ( + ctx: OperationContext & { detected: SecurityMarking }, +) => SecurityDecision | Promise + +/** + * Controls what appears in the spec, in one flat object: which entities are + * documented, which Payload-internal collections to show, which extra endpoint + * groups to generate, and which operations to drop. + */ +export interface FilterOptions { + /** + * Allowlist of entities; when non-empty, only matching entities are documented. + * @default [] + */ + include?: EntityMatcher[] + /** + * Entities to omit entirely (schemas + paths). + * @default [] + */ + exclude?: EntityMatcher[] + /** + * Include collections flagged `hidden`/`admin.hidden`. + * @default false + */ + includeHidden?: boolean + /** + * Include Payload-internal collections and globals (payload-jobs, payload-preferences, payload-jobs-stats, ...). + * @default false + */ + includeSystem?: boolean + /** + * Document endpoints carrying `custom.openapi` metadata. + * @default true + */ + includeCustom?: boolean + /** + * Document auth operation endpoints (login/logout/me/...). + * @default true + */ + includeAuth?: boolean + /** + * Document admin/bootstrap auth endpoints (/init, first-register, root /access) and the document access endpoints (/{slug}/access/{id}, /globals/{slug}/access). + * @default false + */ + includeAdminAuth?: boolean + /** + * Document version operation endpoints (/versions, /versions/{id}). + * @default true + */ + includeVersions?: boolean + /** + * Document jobs endpoints (/payload-jobs/run, /handle-schedules). + * @default true + */ + includeJobs?: boolean + /** + * Drops operations from every group: collection, global, custom, jobs and + * system. An operation is removed when any rule matches it or any function + * returns `true`. + * @default [] + */ + excludeOperations?: OperationFilter[] +} + +export interface OpenApiExtension { + /** Paths deep-merged into the generated ones. */ + paths?: PathsObject + /** Components deep-merged into the generated ones. */ + components?: ComponentsObject + /** Tags appended to the list; a tag whose name already exists is skipped. */ + tags?: TagObject[] + /** + * Runs on the finished document and returns the document to use. Gets one + * object: `doc` plus the {@link ExtensionContext}, e.g. `({ doc, payload }) => doc`. + * It can be `async`. If it throws, the spec request fails: the plugin never + * serves a document that skipped a transform. + */ + transform?: (args: ExtensionContext) => Document | Promise +} + +/** What `extensions[].transform` gets: the finished document and the build context. */ +export type ExtensionContext = BuildContext & { doc: Document; payload: BasePayload; options: ResolvedOptions } + +export type OpenApiPluginOptions = { + /** The document's `info` object. `title` and `version` are required. See {@link OpenApiInfo}. */ + info: OpenApiInfo + /** + * OpenAPI version to serve. The document is always built as 3.2; `3.1` and + * `3.0` each run a downconversion pass on the result. + * @default '3.2' + */ + openapiVersion?: OpenApiVersion + /** + * Where the spec is served, relative to the Payload API route (`routes.api`, + * `/api` by default). So the `/openapi.json` default is served at + * `/api/openapi.json`. + * @default '/openapi.json' + */ + path?: string + /** + * Set false to turn the plugin off. It then adds no endpoints, no CLI command + * and no translations. The plugin adds no collections, so the schema stays the same. + * @default true + */ + enabled?: boolean + /** + * Mount the runtime spec (and interactive-auth) endpoints. Set false to + * register only the `openapi:generate` CLI command and skip serving over + * HTTP — generate the spec to a file and host it however you like (static + * asset, CDN, committed to the repo). Point the docs UI plugins' `specURL` + * at wherever the file ends up. + * @default true + */ + serve?: boolean + /** + * Who may read the spec endpoint. Runs on every request. + * @default open to everyone + */ + access?: AccessOption + /** + * The spec's `servers`. A function runs on every request. See {@link ServersOption}. + * @default `[{ url: serverURL }]` when the Payload `serverURL` is set, otherwise `[]` (clients use the spec's own origin) + */ + servers?: ServersOption + /** + * Hosts the spec may echo back as its server URL. When the request `Host` + * header (`host` or `host:port`) is on this list, `servers` is + * `https://` (`http` for localhost and 127.*). The `Host` header comes from + * the client, so the plugin never uses it unless you list it here. A + * `servers` option wins over this list. + * @default [] + */ + trustedHosts?: string[] + /** What appears in the spec. See {@link FilterOptions}. */ + filters?: FilterOptions + /** + * Interactive docs-UI login. See {@link InteractiveAuthOption}. + * @default false + */ + interactiveAuth?: InteractiveAuthOption + /** + * Emit an OpenAPI 3.2 nested tag hierarchy (nav groups via `kind`/`parent`, + * plus per-entity auth/version sub-tags). Off by default: Scalar and Swagger UI + * don't render 3.2 nested tags yet and would show a confusing flat list of group + * names. When off, `doc.tags` is a flat list of per-entity tags with their + * descriptions. + * @default false + */ + nestedTags?: boolean + /** + * Override the security marking per operation. Runs last, after + * `custom.openapi.security` and the access probe, across every operation + * group. See {@link SecurityOption}. + */ + security?: SecurityOption + /** + * Cache the built document for the life of the process. The Payload config is + * static after boot, so the spec is the same every time apart from + * `servers`, which is set per request. Set false in dev to rebuild on + * every request, e.g. `cache: process.env.NODE_ENV === 'production'`. + * @default true + */ + cache?: boolean + /** + * Extra paths, components and tags merged into the document, plus a final `transform`. + * @default [] + */ + extensions?: OpenApiExtension[] + /** + * Anonymous usage telemetry: plugin, Payload and Node versions and the features in use. On by default. + * It never sends secrets, IP addresses, keys, URLs, paths, hostnames or collection names. + * The first run logs a notice. + * + * Off when `payload.config.telemetry` is `false`, when `DO_NOT_TRACK` or `OPENAPI_TELEMETRY_DISABLED` + * is set, in CI or with `NODE_ENV=test`. Set `false` to turn it off. Pass `{ url }` to use your own collector. + * + * @see https://payload-plugin-openapi.seshuk.im/v1/configuration/telemetry + * @default true + */ + telemetry?: + | boolean + | { + /** + * Collector URL that receives the telemetry report. + * @default the plugin's public collector + */ + url?: string + } +} + +export interface UiPluginOptions { + /** + * Where the docs UI is served, relative to the Payload API route + * (`routes.api`, `/api` by default). So the `/docs` default is served at + * `/api/docs`. + * @default '/docs' + */ + path?: string + /** + * URL the UI fetches the OpenAPI document from. Any URL reference works: + * absolute, root-relative, or relative to the docs page. + * @default the `openapi()` spec route, `/openapi.json` unless its `path` is set + */ + specURL?: string + /** + * Where the UI's assets load from: the script URL for Scalar, the folder with + * `swagger-ui-bundle.js` and `swagger-ui.css` for Swagger UI. Set it to + * self-host the assets. The default URL is pinned to one version and loads + * with Subresource Integrity; a custom URL loads without it. + * @default the pinned jsDelivr URL + */ + cdnBase?: string + /** + * Extra UI config, merged into the underlying library's init options (Scalar's + * `createApiReference` config or Swagger UI's `SwaggerUIBundle`). Must be + * JSON-serializable, functions aren't supported here. + * @default {} + */ + configuration?: Record + /** + * Who may open the docs page. Runs on every request. + * @default open to everyone + */ + access?: AccessOption + /** + * Set false to skip mounting the docs UI. + * @default true + */ + enabled?: boolean +} + +// Resolved options + +export interface ResolvedFilters { + include: EntityMatcher[] + exclude: EntityMatcher[] + includeHidden: boolean + includeSystem: boolean + includeCustom: boolean + includeAuth: boolean + includeAdminAuth: boolean + includeVersions: boolean + includeJobs: boolean + excludeOperations: OperationFilter[] +} + +export interface ResolvedOptions { + info: OpenApiInfo + openapiVersion: OpenApiVersion + path: string + serve: boolean + access?: AccessOption + servers?: ServersOption + trustedHosts: string[] + filters: ResolvedFilters + interactiveAuth: { enabled: boolean; path: string; collection?: string } + nestedTags: boolean + security?: SecurityOption + cache: boolean + extensions: OpenApiExtension[] +} + +// Build context + +export interface BuildContext { + defaultIDType: IDType + locales: string[] + apiRoute: string + /** Languages the docs are available in (project `i18n.supportedLanguages`). */ + docLanguages: string[] + /** Request i18n: resolves Payload entity descriptions and, via {@link makeT}, plugin strings. */ + i18n: I18n +} diff --git a/src/utils.ts b/src/shared/utils.ts similarity index 100% rename from src/utils.ts rename to src/shared/utils.ts diff --git a/src/spec/security.ts b/src/spec/security.ts deleted file mode 100644 index 58d45bd..0000000 --- a/src/spec/security.ts +++ /dev/null @@ -1,124 +0,0 @@ -import type { Access } from 'payload' -import type { PathsObject, SecurityRequirementObject } from '@scalar/openapi-types/3.2' - -import type { - Entity, - EntityKind, - EntityOperation, - EntitySecurityOverride, - HttpMethod, - OperationContext, -} from '../types.js' -import { SECURITY_SCHEME_NAME } from './components.js' - -const HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete'] - -const securedRequirement = (): SecurityRequirementObject[] => [{ [SECURITY_SCHEME_NAME]: [] }] - -export type EntitySecurity = Partial> - -const withTimeout = async (promise: Promise, timeoutMs: number): Promise => { - let timer: ReturnType | undefined - const guard = new Promise((resolve) => { - timer = setTimeout(() => resolve(false), timeoutMs) - }) - try { - // Both handlers attached, so a rejected or never-settling fn leaks no unhandled rejection. - return await Promise.race([ - promise.then( - (value) => value === true, - () => false, - ), - guard, - ]) - } finally { - if (timer) clearTimeout(timer) - } -} - -// Probe an access fn as an anonymous request: public only if it settles on `true`; -// a throw, a `Where`, a timeout, or a `req.payload` (DB) read all fall back to secured. -export const evaluateAccess = async ( - access: Access | undefined, - opts: { locale?: string; timeoutMs?: number } = {}, -): Promise => { - if (!access) return false - const { locale, timeoutMs = 250 } = opts - const payloadTrap = new Proxy( - {}, - { - get() { - throw new Error('req.payload accessed during build') - }, - }, - ) - const req = { user: null, headers: new Headers(), context: {}, locale, payload: payloadTrap } - try { - const result = access({ req } as never) - if (result && typeof (result as Promise).then === 'function') { - return await withTimeout(result as Promise, timeoutMs) - } - return result === true - } catch { - return false - } -} - -const readSecurityOverride = (entity: Entity): EntitySecurityOverride | undefined => { - const meta = (entity as { custom?: { openapi?: { security?: unknown } } }).custom?.openapi?.security - if (typeof meta === 'boolean') return meta - if (meta && typeof meta === 'object') return meta as EntitySecurityOverride - return undefined -} - -const overrideFor = (override: EntitySecurityOverride | undefined, op: EntityOperation): boolean | undefined => { - if (override === undefined) return undefined - if (typeof override === 'boolean') return override - return override[op] -} - -export const resolveEntitySecurity = async ({ - entity, - operations, - locale, -}: { - entity: Entity - operations: EntityOperation[] - locale?: string -}): Promise => { - const override = readSecurityOverride(entity) - const access = entity.access as Partial> | undefined - const out: EntitySecurity = {} - for (const op of operations) { - const declared = overrideFor(override, op) - const isPublic = declared ?? (await evaluateAccess(access?.[op], { locale })) - out[op] = isPublic ? undefined : securedRequirement() - } - return out -} - -export const applySecurityWhen = ({ - paths, - slug, - kind, - securityWhen, -}: { - paths: PathsObject - slug: string - kind: EntityKind - securityWhen?: (ctx: OperationContext) => boolean | undefined -}): PathsObject => { - if (!securityWhen) return paths - for (const [path, item] of Object.entries(paths)) { - if (!item) continue - const ops = item as Record - for (const method of HTTP_METHODS) { - const op = ops[method] - if (!op) continue - const decision = securityWhen({ method, path, slug, kind }) - if (decision === true) delete op.security - else if (decision === false) op.security = securedRequirement() - } - } - return paths -} diff --git a/src/translations/index.ts b/src/translations/index.ts deleted file mode 100644 index 6026215..0000000 --- a/src/translations/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { AcceptedLanguages, I18n } from '@payloadcms/translations' - -import type { PluginDefaultTranslationsObject, Translate } from './types.js' - -import { PLUGIN_NAME } from '../constants.js' -import { ar } from './locales/ar.js' -import { az } from './locales/az.js' -import { bg } from './locales/bg.js' -import { bnBd } from './locales/bnBd.js' -import { bnIn } from './locales/bnIn.js' -import { ca } from './locales/ca.js' -import { cs } from './locales/cs.js' -import { da } from './locales/da.js' -import { de } from './locales/de.js' -import { en } from './locales/en.js' -import { es } from './locales/es.js' -import { et } from './locales/et.js' -import { fa } from './locales/fa.js' -import { fr } from './locales/fr.js' -import { he } from './locales/he.js' -import { hr } from './locales/hr.js' -import { hu } from './locales/hu.js' -import { hy } from './locales/hy.js' -import { id } from './locales/id.js' -import { is } from './locales/is.js' -import { it } from './locales/it.js' -import { ja } from './locales/ja.js' -import { ko } from './locales/ko.js' -import { lt } from './locales/lt.js' -import { lv } from './locales/lv.js' -import { my } from './locales/my.js' -import { nb } from './locales/nb.js' -import { nl } from './locales/nl.js' -import { pl } from './locales/pl.js' -import { pt } from './locales/pt.js' -import { ro } from './locales/ro.js' -import { rs } from './locales/rs.js' -import { rsLatin } from './locales/rsLatin.js' -import { ru } from './locales/ru.js' -import { sk } from './locales/sk.js' -import { sl } from './locales/sl.js' -import { sv } from './locales/sv.js' -import { ta } from './locales/ta.js' -import { th } from './locales/th.js' -import { tr } from './locales/tr.js' -import { uk } from './locales/uk.js' -import { vi } from './locales/vi.js' -import { zh } from './locales/zh.js' -import { zhTw } from './locales/zhTw.js' - -export const makeT = (i18n: Pick): Translate => { - const t = i18n.t as ((key: string, vars?: Record) => string) | undefined - return (key, vars) => (t ? t(`${PLUGIN_NAME}:${key}`, vars) : key) -} - -export const translations: { - [key in AcceptedLanguages]?: PluginDefaultTranslationsObject -} = { - ar, - az, - bg, - 'bn-BD': bnBd, - 'bn-IN': bnIn, - ca, - cs, - da, - de, - en, - es, - et, - fa, - fr, - he, - hr, - hu, - hy, - id, - is, - it, - ja, - ko, - lt, - lv, - my, - nb, - nl, - pl, - pt, - ro, - rs, - 'rs-latin': rsLatin, - ru, - sk, - sl, - sv, - ta, - th, - tr, - uk, - vi, - zh, - 'zh-TW': zhTw, -} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 4b2a065..0000000 --- a/src/types.ts +++ /dev/null @@ -1,212 +0,0 @@ -import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload' -import type { ComponentsObject, Document, PathsObject, TagObject } from '@scalar/openapi-types/3.2' - -import type { I18n } from '@payloadcms/translations' - -// Shared primitives - -export type OpenApiVersion = '3.0' | '3.1' | '3.2' - -export type EntityKind = 'collection' | 'global' - -export type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete' - -export type IDType = 'text' | 'number' - -export type Entity = SanitizedCollectionConfig | SanitizedGlobalConfig - -export type EntityOperation = 'read' | 'create' | 'update' | 'delete' - -export type EntitySecurityOverride = boolean | Partial> - -// Plugin options - -export interface OpenApiMetadata { - title: string - version: string - description?: string -} - -/** - * Interactive "Authorize" login for the docs UI. When on, the plugin adds a - * token endpoint and a security scheme so Scalar/Swagger can log in with a - * username and password instead of pasting a token. Off by default. The - * endpoint path is relative to the API route (`/openapi-auth` → `/api/openapi-auth`). - * - `true` — on, at the default endpoint (`/openapi-auth`). - * - `{ endpoint }` — on, at a custom token endpoint path. - */ -export type InteractiveAuthOption = boolean | { endpoint?: string } - -/** Match an entity by slug (any kind), a {kind,slug} pair, or a slug pattern. */ -export type EntityMatcher = string | RegExp | { kind: EntityKind; slug: string } - -/** - * Drops generated operations. Within one rule, every set field must match (AND); - * an operation is removed if any rule matches it. Fields: - * - `method` — HTTP method(s); omit to match any. - * - `slug` — entity slug, exact string or RegExp; omit to match any. - * - `kind` — restrict to collections or globals; omit to match both. - * - `path` — RegExp tested against the final route path (e.g. `/api/posts/{id}`). - */ -export interface OperationRule { - method?: HttpMethod | HttpMethod[] - slug?: string | RegExp - kind?: EntityKind - path?: RegExp -} - -/** Context passed to {@link FilterOptions.excludeWhen} for each operation. */ -export interface OperationContext { - method: HttpMethod - path: string - slug: string - kind: EntityKind -} - -/** - * Controls what appears in the spec, in one flat object: which entities are - * documented, which Payload-internal collections to show, which extra endpoint - * groups to generate, and which operations to drop. - */ -export interface FilterOptions { - /** Allowlist of entities; when non-empty, only matching entities are documented. */ - include?: EntityMatcher[] - /** Entities to omit entirely (schemas + paths). */ - exclude?: EntityMatcher[] - /** Include collections flagged `hidden`/`admin.hidden`. Default false. */ - includeHidden?: boolean - /** Include Payload-internal collections (payload-jobs, payload-preferences, ...). Default false. */ - includeSystem?: boolean - /** Document endpoints carrying `custom.openapi` metadata. Default true. */ - includeCustom?: boolean - /** Document auth operation endpoints (login/logout/me/...). Default true. */ - includeAuth?: boolean - /** Document admin/bootstrap auth endpoints (/init, /access, first-register). Default false. */ - includeAdminAuth?: boolean - /** Document version operation endpoints (/versions, /versions/{id}). Default true. */ - includeVersions?: boolean - /** Document jobs endpoints (/payload-jobs/run, /handle-schedules). Default true. */ - includeJobs?: boolean - /** Per-operation removal rules. */ - excludeOperations?: OperationRule[] - /** Escape hatch: return true to drop an operation. Runs after excludeOperations. */ - excludeWhen?: (ctx: OperationContext) => boolean -} - -export interface OpenApiExtension { - paths?: PathsObject - components?: ComponentsObject - tags?: TagObject[] - transform?: (doc: Document, ctx: BuildContext) => Document -} - -export type OpenApiPluginOptions = { - metadata: OpenApiMetadata - /** - * OpenAPI version to serve. The document is always built as 3.2; `3.1` and - * `3.0` each run a downconversion pass on the result. Default `3.2`. - */ - openapiVersion?: OpenApiVersion - /** - * Where the spec is served, relative to the Payload API route (`routes.api`, - * `/api` by default). So the `/openapi.json` default is served at - * `/api/openapi.json`. Default `/openapi.json`. - */ - specEndpoint?: string - enabled?: boolean - /** - * Mount the runtime spec (and interactive-auth) endpoints. Default true. Set - * false to register only the `openapi:generate` CLI bin and skip serving over - * HTTP — generate the spec to a file and host it however you like (static - * asset, CDN, committed to the repo). The docs UI plugins (`scalar`/`swaggerUi`) - * are unaffected; point their `specEndpoint` at wherever the file ends up. - */ - serve?: boolean - filters?: FilterOptions - /** Interactive docs-UI login. See {@link InteractiveAuthOption}. Default false. */ - interactiveAuth?: InteractiveAuthOption - /** - * Emit an OpenAPI 3.2 nested tag hierarchy (nav groups via `kind`/`parent`, - * plus per-entity auth/version sub-tags). Off by default: Scalar and Swagger UI - * don't render 3.2 nested tags yet and would show a confusing flat list of group - * names. When off, `doc.tags` is a flat list of per-entity tags with their - * descriptions. - */ - nestedTags?: boolean - /** - * Override the security marking per operation: `true` marks it public, `false` - * secured (`PayloadToken`), `undefined` keeps the detected marking. Runs last, - * after `custom.openapi.security` and the probe, across every operation group. - */ - securityWhen?: (ctx: OperationContext) => boolean | undefined - /** - * Cache the built document for the life of the process. The Payload config is - * static after boot, so the spec is the same every time apart from the server - * URL, which is filled in fresh per response. Default true; set false in dev to - * rebuild on every request, e.g. `cache: process.env.NODE_ENV === 'production'`. - */ - cache?: boolean - extensions?: OpenApiExtension[] -} - -export interface UiPluginOptions { - /** - * Where the docs UI is served, relative to the Payload API route - * (`routes.api`, `/api` by default). So the `/docs` default is served at - * `/api/docs`. Default `/docs`. - */ - path?: string - /** Absolute URL the UI fetches the OpenAPI document from. Default `/openapi.json`. */ - specEndpoint?: string - /** CDN base for the UI's assets. Default is the official jsDelivr package. */ - cdnBase?: string - /** - * Extra UI config, merged into the underlying library's init options (Scalar's - * `createApiReference` config or Swagger UI's `SwaggerUIBundle`). Must be - * JSON-serializable, functions aren't supported here. - */ - configuration?: Record - /** Set false to skip mounting the docs UI. */ - enabled?: boolean -} - -// Resolved options - -export interface ResolvedFilters { - include: EntityMatcher[] - exclude: EntityMatcher[] - includeHidden: boolean - includeSystem: boolean - includeCustom: boolean - includeAuth: boolean - includeAdminAuth: boolean - includeVersions: boolean - includeJobs: boolean - excludeOperations: OperationRule[] - excludeWhen?: (ctx: OperationContext) => boolean -} - -export interface ResolvedOptions { - metadata: OpenApiMetadata - openapiVersion: OpenApiVersion - specEndpoint: string - serve: boolean - filters: ResolvedFilters - interactiveAuth: { enabled: boolean; endpoint: string } - nestedTags: boolean - securityWhen?: (ctx: OperationContext) => boolean | undefined - cache: boolean - extensions: OpenApiExtension[] -} - -// Build context - -export interface BuildContext { - defaultIDType: IDType - locales: string[] - apiRoute: string - /** Languages the docs are available in (project `i18n.supportedLanguages`). */ - docLanguages: string[] - /** Request i18n: resolves Payload entity descriptions and, via {@link makeT}, plugin strings. */ - i18n: I18n -} diff --git a/src/ui/createUiPlugin.ts b/src/ui/createUiPlugin.ts deleted file mode 100644 index 06a1ecc..0000000 --- a/src/ui/createUiPlugin.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Config, Plugin } from 'payload' -import type { UiPluginOptions } from '../types.js' - -export interface UiRenderArgs { - specUrl: string - cdnBase: string - title: string - configuration: Record -} - -export interface UiPluginSpec { - defaultDocsUrl: string - defaultCdnBase: string - render: (args: UiRenderArgs) => string -} - -export const createUiPlugin = - (spec: UiPluginSpec) => - (options: UiPluginOptions = {}): Plugin => - (config: Config): Config => { - if (options.enabled === false) return config - - const docsPath = options.path ?? spec.defaultDocsUrl - const specUrl = options.specEndpoint ?? `${config.routes?.api ?? '/api'}/openapi.json` - const cdnBase = options.cdnBase ?? spec.defaultCdnBase - const html = spec.render({ - specUrl, - cdnBase, - title: 'API Reference', - configuration: options.configuration ?? {}, - }) - - return { - ...config, - endpoints: [ - ...(config.endpoints ?? []), - { - path: docsPath, - method: 'get', - handler: () => new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } }), - }, - ], - } - } diff --git a/src/ui/scalar.ts b/src/ui/scalar.ts deleted file mode 100644 index 7e0801e..0000000 --- a/src/ui/scalar.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createUiPlugin } from './createUiPlugin.js' -import { escapeHtml, htmlPage, jsonForScript, specUrlExpr } from './html.js' - -export const scalar = createUiPlugin({ - defaultDocsUrl: '/docs', - defaultCdnBase: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference', - render: ({ specUrl, cdnBase, title, configuration }) => - htmlPage({ - title, - head: '', - body: - `
` + - `` + - ``, - }), -}) diff --git a/src/ui/swagger.ts b/src/ui/swagger.ts deleted file mode 100644 index 52e9220..0000000 --- a/src/ui/swagger.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createUiPlugin } from './createUiPlugin.js' -import { escapeHtml, htmlPage, jsonForScript, specUrlExpr } from './html.js' - -export const swaggerUi = createUiPlugin({ - defaultDocsUrl: '/docs', - defaultCdnBase: 'https://cdn.jsdelivr.net/npm/swagger-ui-dist', - render: ({ specUrl, cdnBase, title, configuration }) => - htmlPage({ - title, - head: ``, - body: - `
` + - `` + - ``, - }), -}) diff --git a/tests/app/(payload)/admin/importMap.js b/tests/app/(payload)/admin/importMap.js index af86423..2efdd32 100644 --- a/tests/app/(payload)/admin/importMap.js +++ b/tests/app/(payload)/admin/importMap.js @@ -1,6 +1,16 @@ -import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' +import { CollectionCards as CollectionCards_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { CollectionQueryWidget as CollectionQueryWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { QueryPresetsWhereField as QueryPresetsWhereField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { CollectionQuerySortField as CollectionQuerySortField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { RecentlyViewedWidget as RecentlyViewedWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { RecentlyViewedCollectionsField as RecentlyViewedCollectionsField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' /** @type import('payload').ImportMap */ export const importMap = { - "@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 + "@payloadcms/ui/rsc#CollectionCards": CollectionCards_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui/rsc#CollectionQueryWidget": CollectionQueryWidget_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui#QueryPresetsWhereField": QueryPresetsWhereField_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui#CollectionQuerySortField": CollectionQuerySortField_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui/rsc#RecentlyViewedWidget": RecentlyViewedWidget_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui#RecentlyViewedCollectionsField": RecentlyViewedCollectionsField_3817bf644402e67bfe6577f60ef982de } diff --git a/tests/collections/Folders.ts b/tests/collections/Folders.ts new file mode 100644 index 0000000..454f427 --- /dev/null +++ b/tests/collections/Folders.ts @@ -0,0 +1,8 @@ +import type { CollectionConfig } from 'payload' + +export const Folders: CollectionConfig = { + slug: 'folders', + admin: { useAsTitle: 'name' }, + folders: { collectionSpecific: true, joinField: { name: 'children' } }, + fields: [{ name: 'name', type: 'text', required: true }], +} diff --git a/tests/collections/Media.ts b/tests/collections/Media.ts index 007a4bb..39b5ed4 100644 --- a/tests/collections/Media.ts +++ b/tests/collections/Media.ts @@ -1,10 +1,10 @@ +import { createFolderField } from 'payload' import type { CollectionConfig } from 'payload' export const Media: CollectionConfig = { slug: 'media', upload: { mimeTypes: ['image/*'], - imageSizes: [{ name: 'thumbnail', width: 400, height: 300 }], }, - fields: [{ name: 'alt', type: 'text' }], + fields: [{ name: 'alt', type: 'text' }, createFolderField({ relationTo: 'folders' })], } diff --git a/tests/collections/Posts.ts b/tests/collections/Posts.ts index f8be009..679c8f9 100644 --- a/tests/collections/Posts.ts +++ b/tests/collections/Posts.ts @@ -3,7 +3,7 @@ import type { CollectionConfig } from 'payload' export const Posts: CollectionConfig = { slug: 'posts', admin: { description: 'Blog posts and articles' }, - versions: { drafts: { localizeStatus: true }, maxPerDoc: 10 }, + versions: { drafts: true, maxPerDoc: 10 }, fields: [ { name: 'title', type: 'text', required: true, localized: true }, { @@ -42,8 +42,7 @@ export const Posts: CollectionConfig = { { name: 'callouts', type: 'blocks', - blockReferences: ['callToAction'], - blocks: [], + blocks: ['callToAction'], }, { name: 'slug', type: 'text', unique: true }, { name: 'legacyField', type: 'text', custom: { openapi: { deprecated: true } } }, @@ -123,7 +122,7 @@ export const Posts: CollectionConfig = { type: 'array', fields: [ { type: 'row', fields: [{ name: 'tags', type: 'relationship', relationTo: 'tags', hasMany: true }] }, - { name: 'content', type: 'blocks', blockReferences: ['mediaBlock', 'callToAction'], blocks: [] }, + { name: 'content', type: 'blocks', blocks: ['mediaBlock', 'callToAction'] }, ], }, ], diff --git a/tests/collections/Tags.ts b/tests/collections/Tags.ts index caf292c..7c567bd 100644 --- a/tests/collections/Tags.ts +++ b/tests/collections/Tags.ts @@ -4,6 +4,7 @@ export const Tags: CollectionConfig = { slug: 'tags', admin: { useAsTitle: 'name' }, lockDocuments: false, + access: { read: () => true }, fields: [ { name: 'name', type: 'text', required: true }, { name: 'color', type: 'text' }, diff --git a/tests/collections/Users.ts b/tests/collections/Users.ts index f2cf8c5..d889659 100644 --- a/tests/collections/Users.ts +++ b/tests/collections/Users.ts @@ -3,6 +3,7 @@ import type { CollectionConfig } from 'payload' export const Users: CollectionConfig = { slug: 'users', auth: true, + versions: false, fields: [ { name: 'name', type: 'text' }, { diff --git a/tests/collections/index.ts b/tests/collections/index.ts index 78bce72..74fa60c 100644 --- a/tests/collections/index.ts +++ b/tests/collections/index.ts @@ -1,3 +1,4 @@ +export { Folders } from './Folders.js' export { Media } from './Media.js' export { Posts } from './Posts.js' export { Tags } from './Tags.js' diff --git a/tests/helpers.ts b/tests/helpers/context.ts similarity index 71% rename from tests/helpers.ts rename to tests/helpers/context.ts index 58e8c61..a212a17 100644 --- a/tests/helpers.ts +++ b/tests/helpers/context.ts @@ -1,10 +1,11 @@ -import { resolveOptions } from '../src/options.js' -import type { BuildContext } from '../src/types.js' -import type { Translate } from '../src/translations/types.js' -import { en } from '../src/translations/locales/en.js' -import { PLUGIN_NAME as NS } from '../src/constants.js' import { I18n, importDateFNSLocale } from '@payloadcms/translations' +import { resolveOptions } from '@/server/options/resolveOptions.js' +import { PLUGIN_NAME as NS } from '@/shared/constants.js' +import { en } from '@/shared/translations/locales/en.js' +import type { Translate } from '@/shared/translations/types.js' +import type { BuildContext } from '@/shared/types/index.js' + export const t: Translate = (key, vars) => { let str = en[NS][key] for (const [k, v] of Object.entries(vars ?? {})) { @@ -37,12 +38,12 @@ export const ctx: BuildContext = { } export const baseInput = (overrides: Record = {}) => ({ - options: resolveOptions({ metadata: { title: 'Test API', version: '1.0.0' } }), + options: resolveOptions({ info: { title: 'Test API', version: '1.0.0' } }), servers: [{ url: 'http://localhost:3000' }], collections: [], globals: [], config: {} as never, ctx, - logger: { warn() {} } as never, + payload: { logger: { warn() {} } } as never, ...overrides, }) diff --git a/tests/int.spec.ts b/tests/integration/spec.int.spec.ts similarity index 75% rename from tests/int.spec.ts rename to tests/integration/spec.int.spec.ts index e651c26..3892876 100644 --- a/tests/int.spec.ts +++ b/tests/integration/spec.int.spec.ts @@ -1,5 +1,3 @@ -import { getPayload } from 'payload' -import type { Access, SanitizedCollectionConfig } from 'payload' import type { Document, ParameterObject, @@ -9,25 +7,28 @@ import type { SchemaObject, TagObject, } from '@scalar/openapi-types/3.2' +import { getPayload } from 'payload' +import type { Access, SanitizedCollectionConfig } from 'payload' import { beforeAll, describe, expect, it } from 'vitest' -import { buildDocument } from '../src/spec/buildDocument.js' -import { toOpenApi30, toOpenApi31 } from '../src/spec/downconvert.js' -import { specHandler } from '../src/endpoints/spec.js' -import { resolveOptions } from '../src/options.js' -import type { BuildContext, FilterOptions } from '../src/types.js' -import { i18nStub } from './helpers.js' -import configPromise from './payload.config.js' +import { specHandler } from '@/server/endpoints/spec.js' +import { resolveOptions } from '@/server/options/resolveOptions.js' +import { buildDocument } from '@/server/spec/buildDocument.js' +import { toOpenApi30, toOpenApi31 } from '@/server/spec/downconvert.js' +import type { BuildContext, FilterOptions } from '@/shared/types/index.js' + +import { i18nStub } from '../helpers/context.js' +import configPromise from '../payload.config.js' let doc: Document let nestedDoc: Document -let idType: 'string' | 'integer' +let adminDoc: Document +let idType: 'string' | 'number' beforeAll(async () => { - process.env.USE_MEMORY_DB = '1' const config = await configPromise const payload = await getPayload({ config }) - idType = payload.db.defaultIDType === 'number' ? 'integer' : 'string' + idType = payload.db.defaultIDType === 'number' ? 'number' : 'string' const ctx: BuildContext = { defaultIDType: payload.db.defaultIDType, @@ -38,22 +39,31 @@ beforeAll(async () => { } const collections = Object.values(payload.collections).map((c) => c.config) doc = await buildDocument({ - options: resolveOptions({ metadata: { title: 'T', version: '1.0.0' } }), + options: resolveOptions({ info: { title: 'T', version: '1.0.0' } }), servers: [{ url: 'http://localhost' }], collections, globals: payload.config.globals, config: payload.config, ctx, - logger: payload.logger, + payload, + }) + adminDoc = await buildDocument({ + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, filters: { includeAdminAuth: true } }), + servers: [{ url: 'http://localhost' }], + collections, + globals: payload.config.globals, + config: payload.config, + ctx, + payload, }) nestedDoc = await buildDocument({ - options: resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, nestedTags: true }), + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, nestedTags: true }), servers: [{ url: 'http://localhost' }], collections, globals: payload.config.globals, config: payload.config, ctx, - logger: payload.logger, + payload, }) }, 60_000) @@ -62,13 +72,20 @@ const schema = (name: string) => doc.components?.schemas?.[name] as SchemaObject describe('generated document', () => { describe('document validity', () => { it.each([ - ['3.2', (d: typeof doc) => d], + ['3.2', (d: Document) => d], ['3.1', toOpenApi31], ['3.0', toOpenApi30], ])('validates as a well-formed OpenAPI %s document', async (_version, convert) => { const { Validator } = await import('@seriousme/openapi-schema-validator') - const result = await new Validator().validate(convert(structuredClone(doc))) - expect(result.valid).toBe(true) + for (const d of [doc, nestedDoc, adminDoc]) { + const result = await new Validator().validate(convert(structuredClone(d))) + expect(result.errors).toBeUndefined() + expect(result.valid).toBe(true) + } + }) + + it('drops empty `required` arrays in 3.0, which needs at least one item', () => { + expect(JSON.stringify(toOpenApi30(structuredClone(doc)))).not.toContain('"required":[]') }) it('leaves no broken `$ref` to a lowercased slug', () => { @@ -88,26 +105,26 @@ describe('generated document', () => { it('adds a localization note to `info.description` when locales are configured', async () => { const payload = await getPayload({ config: await configPromise }) const localized = await buildDocument({ - options: resolveOptions({ metadata: { title: 'T', version: '1.0.0', description: 'Base.' } }), + options: resolveOptions({ info: { title: 'T', version: '1.0.0', description: 'Base.' } }), servers: [{ url: 'http://localhost' }], collections: [], globals: [], config: payload.config, ctx: { defaultIDType: 'text', locales: ['en', 'de'], apiRoute: '/api', docLanguages: ['en'], i18n: i18nStub }, - logger: payload.logger, + payload, }) expect(localized.info.description).toContain('Base.') expect(localized.info.description).toContain('locale=all') expect(localized.info.description).toContain('`en`') const noLocale = await buildDocument({ - options: resolveOptions({ metadata: { title: 'T', version: '1.0.0' } }), + options: resolveOptions({ info: { title: 'T', version: '1.0.0' } }), servers: [{ url: 'http://localhost' }], collections: [], globals: [], config: payload.config, ctx: { defaultIDType: 'text', locales: [], apiRoute: '/api', docLanguages: ['en'], i18n: i18nStub }, - logger: payload.logger, + payload, }) expect(noLocale.info.description ?? '').not.toContain('locale=all') }) @@ -164,7 +181,7 @@ describe('generated document', () => { it('writes relationships as ids in create bodies', () => { const author = schema('PostsCreate').properties?.author as SchemaObject - expect(author.type).toEqual([idType, 'null']) + expect(author).toEqual({ type: [idType, 'null'] }) const tags = schema('PostsCreate').properties?.tags as SchemaObject expect(tags.type).toEqual(['array', 'null']) expect((tags.items as SchemaObject).type).toBe(idType) @@ -183,7 +200,7 @@ describe('generated document', () => { it('writes an upload field as an id in create bodies', () => { const featured = schema('PostsCreate').properties?.featuredImage as SchemaObject - expect(featured.type).toEqual([idType, 'null']) + expect(featured).toEqual({ type: [idType, 'null'] }) }) it('writes relationships nested in groups, named tabs, arrays and blocks as ids', () => { @@ -201,6 +218,7 @@ describe('generated document', () => { expect(row.tags).toEqual({ type: ['array', 'null'], items: { type: idType } }) const blocks = ((row.content as SchemaObject).items as SchemaObject).oneOf as SchemaObject[] expect(blocks[0]?.properties?.media).toEqual({ type: idType }) + expect(blocks[1]).toEqual({ $ref: '#/components/schemas/BlockCallToAction' }) const layout = ((create.layout as SchemaObject).items as SchemaObject).oneOf as SchemaObject[] const gallery = layout.find((b) => (b.properties?.blockType as SchemaObject | undefined)?.const === 'gallery') @@ -211,15 +229,26 @@ describe('generated document', () => { it('keeps the populated document in nested read shapes', () => { const read = schema('Posts').properties ?? {} const hero = (read.hero as SchemaObject).properties ?? {} - expect((hero.image as SchemaObject).oneOf?.[1]).toEqual({ $ref: '#/components/schemas/Media' }) - const related = ((read.related as SchemaObject).items as SchemaObject).oneOf as SchemaObject[] - const value = related[0]?.properties?.value as SchemaObject - expect(value.oneOf?.[1]).toEqual({ $ref: '#/components/schemas/Posts' }) + const image = hero.image as SchemaObject + expect(image.oneOf?.[1]).toEqual({ $ref: '#/components/schemas/Media' }) const row = ((read.sections as SchemaObject).items as SchemaObject).properties ?? {} const blocks = ((row.content as SchemaObject).items as SchemaObject).oneOf as SchemaObject[] expect(blocks[0]).toEqual({ $ref: '#/components/schemas/BlockMedia' }) - const media = schema('BlockMedia').properties?.media as SchemaObject - expect(media.oneOf?.[1]).toEqual({ $ref: '#/components/schemas/Media' }) + }) + + it('leaves join fields and timestamps out of create bodies', () => { + expect(schema('UsersCreate').properties?.posts).toBeUndefined() + expect(schema('PostsCreate').properties?.createdAt).toBeUndefined() + expect(schema('PostsCreate').properties?.updatedAt).toBeUndefined() + }) + + it('leaves `createdBy` and `updatedBy` out of write bodies and keeps them on read', () => { + for (const field of ['createdBy', 'updatedBy']) { + expect(schema('Posts').properties?.[field]).toBeDefined() + expect(schema('PostsCreate').properties?.[field]).toBeUndefined() + expect(schema('PostsUpdate').properties?.[field]).toBeUndefined() + expect(schema('GlobalSettingsUpdate').properties?.[field]).toBeUndefined() + } }) }) @@ -319,6 +348,17 @@ describe('generated document', () => { expect(mpSchema.required).toBeUndefined() expect(body.required).toBe(false) }) + + it('documents the file and upload instructions endpoints for upload collections', () => { + expect(doc.paths['/api/media/file/{filename}']?.get).toBeDefined() + expect(doc.paths['/api/users/file/{filename}']).toBeUndefined() + const body = doc.paths['/api/upload-instructions']?.post?.requestBody as RequestBodyObject + const bodySchema = body.content['application/json'].schema as SchemaObject + const slug = bodySchema.properties?.collectionSlug as SchemaObject + expect(slug.enum).toEqual(['media']) + expect(doc.paths['/api/upload-instructions/{uploadId}']?.put).toBeDefined() + expect(doc.paths['/api/upload-instructions/{uploadId}']?.delete).toBeDefined() + }) }) describe('query params', () => { @@ -418,6 +458,63 @@ describe('generated document', () => { }) }) + describe('hierarchy', () => { + const paramNames = (op?: { parameters?: unknown[] }) => + ((op?.parameters ?? []) as ParameterObject[]).map((p) => p.name) + + it('documents a folders collection with the standard collection endpoints only', () => { + const folderPaths = Object.keys(doc.paths).filter((p) => p.startsWith('/api/folders')) + expect(folderPaths.toSorted()).toEqual([ + '/api/folders', + '/api/folders/count', + '/api/folders/validate', + '/api/folders/versions', + '/api/folders/versions/{id}', + '/api/folders/{id}', + '/api/folders/{id}/duplicate', + '/api/folders/{id}/validate', + ]) + expect(Object.keys(doc.paths).some((p) => p.includes('payload-folders'))).toBe(false) + }) + + it('adds the parent, scope and join fields to the folder read schema', () => { + const props = schema('Folders').properties ?? {} + expect(props._h_folders).toBeDefined() + expect((props.hierarchyType as SchemaObject).items).toEqual({ type: 'string', enum: ['media'] }) + const children = (props.children as SchemaObject).properties?.docs as SchemaObject + for (const branch of ((children.items as SchemaObject).oneOf ?? []) as SchemaObject[]) { + expect(branch.required).toEqual(['relationTo', 'value']) + } + }) + + it('marks the computed path fields read-only and keeps them out of bodies and `where`', () => { + for (const name of ['_h_slugPath', '_h_titlePath']) { + const field = schema('Folders').properties?.[name] as SchemaObject + expect(field.readOnly).toBe(true) + expect(field.description).toContain('computeHierarchyPaths=true') + expect(schema('FoldersCreate').properties?.[name]).toBeUndefined() + expect(schema('FoldersUpdate').properties?.[name]).toBeUndefined() + expect(schema('FoldersQueryOperations').properties?.[name]).toBeUndefined() + expect(schema('FoldersSelect').properties?.[name]).toEqual({ type: 'boolean' }) + } + expect(schema('FoldersQueryOperations').properties?._h_folders).toBeDefined() + }) + + it('lists `computeHierarchyPaths` on the folder reads only', () => { + expect(paramNames(doc.paths['/api/folders']?.get)).toContain('computeHierarchyPaths') + expect(paramNames(doc.paths['/api/folders/{id}']?.get)).toContain('computeHierarchyPaths') + expect(paramNames(doc.paths['/api/folders']?.post)).not.toContain('computeHierarchyPaths') + expect(paramNames(doc.paths['/api/folders/{id}']?.patch)).not.toContain('computeHierarchyPaths') + expect(paramNames(doc.paths['/api/media']?.get)).not.toContain('computeHierarchyPaths') + }) + + it('adds the folder field to a related collection', () => { + expect(schema('Media').properties?._h_folders).toBeDefined() + expect(schema('MediaCreate').properties?._h_folders).toMatchObject({ type: [idType, 'null'] }) + expect(schema('MediaQueryOperations').properties?._h_folders).toBeDefined() + }) + }) + describe('paths/auth', () => { it('documents the auth endpoints for auth collections', () => { expect(doc.paths['/api/users/login']?.post).toBeDefined() @@ -432,26 +529,15 @@ describe('generated document', () => { it('omits the admin and bootstrap auth endpoints by default', () => { expect(doc.paths['/api/users/first-register']).toBeUndefined() expect(doc.paths['/api/users/init']).toBeUndefined() + expect(doc.paths['/api/access']).toBeUndefined() expect(doc.paths['/api/users/access']).toBeUndefined() }) - it('documents the admin and bootstrap auth endpoints when `adminAuthEndpoints` is on', async () => { - const payload = await getPayload({ config: await configPromise }) - const adminDoc = await buildDocument({ - options: resolveOptions({ - metadata: { title: 'T', version: '1.0.0' }, - filters: { includeAdminAuth: true }, - }), - servers: [{ url: 'http://localhost' }], - collections: Object.values(payload.collections).map((c) => c.config), - globals: [], - config: payload.config, - ctx: { defaultIDType: 'text', locales: [], apiRoute: '/api', docLanguages: ['en'], i18n: i18nStub }, - logger: payload.logger, - }) + it('documents the admin and bootstrap auth endpoints when `adminAuthEndpoints` is on', () => { expect(adminDoc.paths['/api/users/first-register']?.post).toBeDefined() expect(adminDoc.paths['/api/users/init']?.get).toBeDefined() - expect(adminDoc.paths['/api/users/access']?.get).toBeDefined() + expect(adminDoc.paths['/api/access']?.get).toBeDefined() + expect(adminDoc.paths['/api/users/access']?.get).toBeUndefined() }) it('does not document auth endpoints for non-auth collections', () => { @@ -459,6 +545,45 @@ describe('generated document', () => { }) }) + describe('paths/access', () => { + it('documents the per-document access endpoints only with `includeAdminAuth`', () => { + expect(doc.paths['/api/posts/access/{id}']).toBeUndefined() + expect(doc.paths['/api/globals/settings/access']).toBeUndefined() + + const props = (op?: { responses?: Record }) => { + const ok = op?.responses?.['200'] as ResponseObject + const body = ok.content?.['application/json']?.schema as SchemaObject + return Object.keys(body.properties ?? {}) + } + const byId = adminDoc.paths['/api/posts/access/{id}']?.post + expect(byId?.operationId).toBe('accessPostsById') + expect(byId?.security).toBeUndefined() + const requestBody = byId?.requestBody as RequestBodyObject + expect(requestBody.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/PostsUpdate', + }) + expect(props(byId)).toEqual(['create', 'read', 'update', 'delete', 'validate', 'readVersions', 'fields']) + expect(byId?.responses?.['404']).toBeDefined() + expect(adminDoc.paths['/api/posts/access']?.post?.operationId).toBe('accessPosts') + expect(props(adminDoc.paths['/api/users/access/{id}']?.post)).toEqual([ + 'create', + 'read', + 'update', + 'delete', + 'validate', + 'unlock', + 'fields', + ]) + expect(props(adminDoc.paths['/api/globals/settings/access']?.post)).toEqual([ + 'read', + 'update', + 'validate', + 'readVersions', + 'fields', + ]) + }) + }) + describe('paths/versions', () => { it('documents the version endpoints for versioned collections', () => { expect(doc.paths['/api/posts/versions']?.get).toBeDefined() @@ -599,7 +724,7 @@ describe('generated document', () => { } const serveSpec = async (url: string): Promise => { const payload = await getPayload({ config: await configPromise }) - const handler = specHandler(resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, cache: false })) + const handler = specHandler(resolveOptions({ info: { title: 'T', version: '1.0.0' }, cache: false })) const res = await handler({ url, payload, headers: new Headers(), i18n: i18nStub } as never) return (await res.json()) as Document } @@ -648,13 +773,13 @@ describe('filters', () => { const build = async (filters: Partial): Promise => { const payload = await getPayload({ config: await configPromise }) return buildDocument({ - options: resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, filters }), + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, filters }), servers: [{ url: 'http://localhost' }], collections: Object.values(payload.collections).map((c) => c.config), globals: payload.config.globals, config: payload.config, ctx: { defaultIDType: 'text', locales: [], apiRoute: '/api', docLanguages: ['en'], i18n: i18nStub }, - logger: payload.logger, + payload, }) } @@ -689,7 +814,7 @@ describe('security marking', () => { const buildWithReadAccess = async ( read: Access, - options = resolveOptions({ metadata: { title: 'T', version: '1.0.0' } }), + options = resolveOptions({ info: { title: 'T', version: '1.0.0' } }), ): Promise => { const payload = await getPayload({ config: await configPromise }) const base = Object.values(payload.collections).find((c) => c.config.slug === 'tags')!.config @@ -701,7 +826,7 @@ describe('security marking', () => { globals: [], config: payload.config, ctx: { defaultIDType: 'text', locales: [], apiRoute: '/api', docLanguages: ['en'], i18n: i18nStub }, - logger: payload.logger, + payload, }) } @@ -714,6 +839,25 @@ describe('security marking', () => { expect(d.paths?.['/api/tags']?.get?.security).toEqual(secured) }) + it('marks a public read from the collection config as public after Payload sanitizes it', () => { + expect(doc.paths['/api/tags']?.get?.security).toBeUndefined() + expect(doc.paths['/api/tags/{id}']?.get?.security).toBeUndefined() + expect(doc.paths['/api/tags']?.post?.security).toEqual(secured) + expect(doc.paths['/api/tags/{id}/validate']?.post?.security).toEqual(secured) + }) + + it('marks version endpoints from `readVersions` and `update`, not as public', () => { + expect(doc.paths['/api/posts/versions']?.get?.security).toEqual(secured) + expect(doc.paths['/api/posts/versions/{id}']?.get?.security).toEqual(secured) + expect(doc.paths['/api/posts/versions/{id}']?.post?.security).toEqual(secured) + expect(doc.paths['/api/globals/settings/versions']?.get?.security).toEqual(secured) + }) + + it('marks the jobs endpoints as secured by default', () => { + expect(doc.paths['/api/payload-jobs/run']?.get?.security).toEqual(secured) + expect(doc.paths['/api/payload-jobs/handle-schedules']?.get?.security).toEqual(secured) + }) + it('marks a plain public read function as public, leaving other operations secured', async () => { const d = await buildWithReadAccess(() => true) expect(d.paths?.['/api/tags']?.get?.security).toBeUndefined() @@ -721,12 +865,12 @@ describe('security marking', () => { expect(d.paths?.['/api/tags/{id}']?.delete?.security).toEqual(secured) }) - it('lets securityWhen override the marking per operation in both directions', async () => { + it('lets `security` override the marking per operation in both directions', async () => { const options = resolveOptions({ - metadata: { title: 'T', version: '1.0.0' }, - securityWhen: ({ method }) => { - if (method === 'get') return false - if (method === 'post') return true + info: { title: 'T', version: '1.0.0' }, + security: ({ method }) => { + if (method === 'get') return 'secured' + if (method === 'post') return 'public' return undefined }, }) diff --git a/tests/next-env.d.ts b/tests/next-env.d.ts index c4b7818..a419cbe 100644 --- a/tests/next-env.d.ts +++ b/tests/next-env.d.ts @@ -1,6 +1,7 @@ /// /// import "./.next/dev/types/routes.d.ts"; +import "./.next/dev/types/root-params.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/tests/next.config.mjs b/tests/next.config.mjs index 0693ca5..ecf0c35 100644 --- a/tests/next.config.mjs +++ b/tests/next.config.mjs @@ -10,7 +10,7 @@ const nextConfig = { } return webpackConfig }, - serverExternalPackages: ['mongodb-memory-server'], + serverExternalPackages: ['@seshuk/payload-plugin-tooling', 'mongodb-memory-server'], } export default withPayload(nextConfig, { devBundleServerPackages: false }) diff --git a/tests/payload.config.ts b/tests/payload.config.ts index 02328dc..060908e 100644 --- a/tests/payload.config.ts +++ b/tests/payload.config.ts @@ -1,28 +1,28 @@ -import { sqliteAdapter } from '@payloadcms/db-sqlite' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + import { de } from '@payloadcms/translations/languages/de' import { en } from '@payloadcms/translations/languages/en' import { fr } from '@payloadcms/translations/languages/fr' -import path from 'node:path' -import { fileURLToPath } from 'node:url' +import { testDatabase } from '@seshuk/payload-plugin-tooling/test-database' import { buildConfig } from 'payload' -import { openapi, scalar, swaggerUi } from '../src/index.js' +import { openapi, scalar, swaggerUi } from '@/index.js' + import { CallToAction } from './blocks/CallToAction.js' import { MediaBlock } from './blocks/MediaBlock.js' -import { Media, Posts, Tags, Users } from './collections/index.js' +import { Folders, Media, Posts, Tags, Users } from './collections/index.js' import { Settings } from './globals/Settings.js' const dirname = path.dirname(fileURLToPath(import.meta.url)) const devUser = { email: 'dev@example.com', password: 'dev' } -const buildConfigAsync = async () => { - const useMemory = process.env.NODE_ENV === 'test' || process.env.USE_MEMORY_DB === '1' - - return buildConfig({ +const buildConfigAsync = async () => + buildConfig({ secret: process.env.PAYLOAD_SECRET || 'dev-secret', + telemetry: false, typescript: { autoGenerate: false }, - experimental: { localizeStatus: true }, admin: { user: 'users', importMap: { baseDir: path.resolve(dirname) } }, i18n: { supportedLanguages: { en, de, fr }, @@ -31,11 +31,7 @@ const buildConfigAsync = async () => { locales: ['en', 'de', 'fr'], defaultLocale: 'en', }, - db: sqliteAdapter({ - client: { - url: useMemory ? ':memory:' : process.env.DATABASE_URI || `file:${path.resolve(dirname, 'payload.db')}`, - }, - }), + db: await testDatabase(), blocks: [CallToAction, MediaBlock], jobs: { tasks: [ @@ -47,11 +43,11 @@ const buildConfigAsync = async () => { }, ], }, - collections: [Users, Posts, Media, Tags], + collections: [Users, Posts, Media, Tags, Folders], globals: [Settings], plugins: [ openapi({ - metadata: { title: 'Dev API', version: '1.0.0' }, + info: { title: 'Dev API', version: '1.0.0' }, filters: { includeAdminAuth: true }, interactiveAuth: true, cache: process.env.NODE_ENV === 'production', @@ -62,13 +58,13 @@ const buildConfigAsync = async () => { onInit: async (payload) => { const existing = await payload.find({ collection: 'users', + overrideAccess: true, where: { email: { equals: devUser.email } }, }) if (existing.docs.length === 0) { - await payload.create({ collection: 'users', data: devUser }) + await payload.create({ collection: 'users', data: devUser, overrideAccess: true }) } }, }) -} export default buildConfigAsync() diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 171eaa3..82a5b59 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -19,7 +19,8 @@ } ], "paths": { - "@payload-config": ["./payload.config.ts"] + "@payload-config": ["./payload.config.ts"], + "@/*": ["../src/*"] } }, "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], diff --git a/tests/unit/hierarchy.spec.ts b/tests/unit/hierarchy.spec.ts new file mode 100644 index 0000000..bd7b945 --- /dev/null +++ b/tests/unit/hierarchy.spec.ts @@ -0,0 +1,61 @@ +import type { ParameterObject, PathsObject, SchemaObject } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig } from 'payload' +import { describe, expect, it } from 'vitest' + +import { applyHierarchy } from '@/server/spec/hierarchy.js' + +import { ctx } from '../helpers/context.js' + +const text: SchemaObject = { type: 'string' } + +const setup = (hierarchy: SanitizedCollectionConfig['hierarchy']) => { + const paths: PathsObject = { + '/api/folders': { get: { parameters: [] }, post: { parameters: [] } }, + '/api/folders/{id}': { get: { parameters: [] }, patch: { parameters: [] } }, + } + const props = () => ({ name: text, crumbs: text, titles: text }) + const schemas: Record = { + Folders: { type: 'object', properties: props() }, + FoldersCreate: { type: 'object', properties: props() }, + FoldersUpdate: { type: 'object', properties: props() }, + FoldersQueryOperations: { type: 'object', properties: props() }, + FoldersSelect: { type: 'object', properties: props() }, + } + const collection = { slug: 'folders', hierarchy } as unknown as SanitizedCollectionConfig + applyHierarchy({ collection, base: 'Folders', paths, schemas, ctx }) + return { paths, schemas } +} + +const names = (op: unknown) => ((op as { parameters: ParameterObject[] }).parameters ?? []).map((p) => p.name) + +describe('applyHierarchy', () => { + it('uses the configured path field names', () => { + const { paths, schemas } = setup({ + slugPathFieldName: 'crumbs', + titlePathFieldName: 'titles', + } as SanitizedCollectionConfig['hierarchy']) + + for (const name of ['crumbs', 'titles']) { + expect(schemas.Folders.properties?.[name]).toMatchObject({ type: 'string', readOnly: true }) + expect(schemas.FoldersCreate.properties?.[name]).toBeUndefined() + expect(schemas.FoldersUpdate.properties?.[name]).toBeUndefined() + expect(schemas.FoldersQueryOperations.properties?.[name]).toBeUndefined() + expect(schemas.FoldersSelect.properties?.[name]).toEqual(text) + } + expect(schemas.FoldersCreate.properties?.name).toEqual(text) + + const param = paths['/api/folders']?.get?.parameters?.[0] as ParameterObject + expect(param).toMatchObject({ name: 'computeHierarchyPaths', in: 'query', schema: { type: 'boolean' } }) + expect(param.description).toContain('`crumbs`') + expect(names(paths['/api/folders/{id}']?.get)).toEqual(['computeHierarchyPaths']) + expect(names(paths['/api/folders']?.post)).toEqual([]) + expect(names(paths['/api/folders/{id}']?.patch)).toEqual([]) + }) + + it('leaves a collection without hierarchy untouched', () => { + const { paths, schemas } = setup(false) + expect(schemas.Folders.properties?.crumbs).toEqual(text) + expect(schemas.FoldersCreate.properties?.crumbs).toEqual(text) + expect(names(paths['/api/folders']?.get)).toEqual([]) + }) +}) diff --git a/tests/unit.spec.ts b/tests/unit/index.spec.ts similarity index 50% rename from tests/unit.spec.ts rename to tests/unit/index.spec.ts index 3a369d4..5b5fad1 100644 --- a/tests/unit.spec.ts +++ b/tests/unit/index.spec.ts @@ -1,24 +1,32 @@ +import type { I18n } from '@payloadcms/translations' +import type { Document, PathsObject, RequestBodyObject, SchemaObject } from '@scalar/openapi-types/3.2' import { Validator } from '@seriousme/openapi-schema-validator' import type { + CollectionConfig, Config, Endpoint, Field, + GlobalConfig, PayloadRequest, Plugin, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig, + TextField, } from 'payload' import { flattenAllFields } from 'payload' -import type { I18n } from '@payloadcms/translations' -import { describe, expect, it } from 'vitest' - -import { resolveOptions } from '../src/options.js' -import { PLUGIN_NAME } from '../src/constants.js' -import { buildDocument } from '../src/spec/buildDocument.js' -import { flattenFields } from '../src/spec/fields.js' -import { filterOperations, shouldIncludeCollection, shouldIncludeGlobal } from '../src/spec/filters.js' -import { rewriteRefs, schemaName } from '../src/spec/names.js' +import { describe, expect, expectTypeOf, it } from 'vitest' + +import { resolveOptions } from '@/server/options/resolveOptions.js' +import { buildDocument } from '@/server/spec/buildDocument.js' +import { flattenFields } from '@/server/spec/fields.js' +import { filterOperations, shouldIncludeCollection, shouldIncludeGlobal } from '@/server/spec/filters.js' +import { rewriteRefs, schemaName } from '@/server/spec/names.js' +import { buildAuthPaths } from '@/server/spec/paths/auth.js' +import { buildCollectionPaths } from '@/server/spec/paths/collections.js' +import { buildCustomEndpointPaths } from '@/server/spec/paths/custom.js' +import { buildSystemPaths } from '@/server/spec/paths/system.js' +import { applySecurity, evaluateAccess, resolveEntitySecurity } from '@/server/spec/security.js' import { NAV_COLLECTIONS, NAV_GLOBALS, @@ -27,15 +35,12 @@ import { buildTagHierarchy, entityTagName, versionsTagName, -} from '../src/spec/tags.js' -import type { Translate } from '../src/translations/types.js' -import { buildCustomEndpointPaths } from '../src/spec/paths/custom.js' -import { buildCollectionPaths } from '../src/spec/paths/collections.js' -import { buildAuthPaths } from '../src/spec/paths/auth.js' -import { applySecurityWhen, evaluateAccess, resolveEntitySecurity } from '../src/spec/security.js' -import type { Document, PathsObject, RequestBodyObject, SchemaObject } from '@scalar/openapi-types/3.2' -import type { OpenApiExtension, ResolvedFilters } from '../src/types.js' -import { baseInput, ctx, i18nStub, t } from './helpers.js' +} from '@/server/spec/tags.js' +import { PLUGIN_NAME } from '@/shared/constants.js' +import type { Translate } from '@/shared/translations/types.js' +import type { EntityOpenApiMeta, FieldOpenApiMeta, OpenApiExtension, ResolvedFilters } from '@/shared/types/index.js' + +import { baseInput, ctx, i18nStub, t } from '../helpers/context.js' describe('spec/names', () => { describe('schemaName', () => { @@ -58,6 +63,20 @@ describe('spec/names', () => { const input = { $ref: '#/components/schemas/X' } expect(rewriteRefs(input)).toEqual(input) }) + + it('inlines collected `$defs` but keeps refs to config-level blocks', () => { + const defs = new Map([ + ['Hero', { type: 'object', properties: { blockType: { const: 'hero' } } }], + ['CallToAction', { type: 'object' }], + ]) + const input = { oneOf: [{ $ref: '#/$defs/Hero' }, { $ref: '#/$defs/CallToAction' }] } + expect(rewriteRefs(input, new Set(['callToAction']), defs)).toEqual({ + oneOf: [ + { type: 'object', properties: { blockType: { const: 'hero' } } }, + { $ref: '#/components/schemas/BlockCallToAction' }, + ], + }) + }) }) }) @@ -115,6 +134,17 @@ describe('spec/filters', () => { expect(shouldIncludeCollection(coll('posts'), f())).toBe(true) }) + it('hides the `payload-kv`, `payload-llm-instructions` and `payload-query-presets` collections unless `includeSystem` is set', () => { + expect(shouldIncludeCollection(coll('payload-kv'), f())).toBe(false) + expect(shouldIncludeCollection(coll('payload-query-presets'), f())).toBe(false) + expect(shouldIncludeCollection(coll('payload-llm-instructions'), f())).toBe(false) + expect(shouldIncludeCollection(coll('payload-kv'), f({ includeSystem: true }))).toBe(true) + }) + + it('documents a `payload-folders` collection, which Payload 4 no longer creates', () => { + expect(shouldIncludeCollection(coll('payload-folders'), f())).toBe(true) + }) + it('hides hidden collections unless `includeHidden` is set', () => { const hidden = coll('drafts', { admin: { hidden: true } }) expect(shouldIncludeCollection(hidden, f())).toBe(false) @@ -129,12 +159,6 @@ describe('spec/filters', () => { expect(shouldIncludeCollection(hiddenFromAnon, f({ includeHidden: true }))).toBe(true) }) - it('hides the `payload-kv` and `payload-query-presets` collections unless `includeSystem` is set', () => { - expect(shouldIncludeCollection(coll('payload-kv'), f())).toBe(false) - expect(shouldIncludeCollection(coll('payload-query-presets'), f())).toBe(false) - expect(shouldIncludeCollection(coll('payload-kv'), f({ includeSystem: true }))).toBe(true) - }) - it('keeps a collection whose `admin.hidden` function throws', () => { const thrower = coll('drafts', { admin: { @@ -162,6 +186,12 @@ describe('spec/filters', () => { ) expect(shouldIncludeCollection(coll('posts'), filters)).toBe(true) }) + + it('hides the `payload-jobs-stats` global unless `includeSystem` is on', () => { + const stats = coll('payload-jobs-stats') as unknown as Parameters[0] + expect(shouldIncludeGlobal(stats, f())).toBe(false) + expect(shouldIncludeGlobal(stats, f({ includeSystem: true }))).toBe(true) + }) }) describe('filterOperations', () => { @@ -170,8 +200,8 @@ describe('spec/filters', () => { '/api/posts/{id}': { get: {}, patch: {}, delete: {} }, }) - it('drops a method across all paths and removes the emptied paths', () => { - const out = filterOperations({ + it('drops a method across all paths and removes the emptied paths', async () => { + const out = await filterOperations({ paths: paths(), slug: 'posts', kind: 'collection', @@ -182,8 +212,8 @@ describe('spec/filters', () => { expect(out['/api/posts']?.get).toBeDefined() }) - it('targets a single entity by slug', () => { - const out = filterOperations({ + it('targets a single entity by slug', async () => { + const out = await filterOperations({ paths: paths(), slug: 'tags', kind: 'collection', @@ -192,8 +222,8 @@ describe('spec/filters', () => { expect(out['/api/posts']?.post).toBeDefined() }) - it('matches a path by regex to tell list from by-id', () => { - const out = filterOperations({ + it('matches a path by regex to tell list from by-id', async () => { + const out = await filterOperations({ paths: paths(), slug: 'posts', kind: 'collection', @@ -203,8 +233,8 @@ describe('spec/filters', () => { expect(out['/api/posts/{id}']?.get).toBeUndefined() }) - it('removes a path entirely when all of its methods are excluded', () => { - const out = filterOperations({ + it('removes a path entirely when all of its methods are excluded', async () => { + const out = await filterOperations({ paths: paths(), slug: 'posts', kind: 'collection', @@ -214,8 +244,8 @@ describe('spec/filters', () => { expect(out['/api/posts']).toBeDefined() }) - it('respects `kind` so globals and collections do not collide', () => { - const out = filterOperations({ + it('respects `kind` so globals and collections do not collide', async () => { + const out = await filterOperations({ paths: paths(), slug: 'posts', kind: 'collection', @@ -224,16 +254,41 @@ describe('spec/filters', () => { expect(out['/api/posts']?.get).toBeDefined() }) - it('applies the `excludeWhen` predicate', () => { - const out = filterOperations({ + it('applies a function entry next to the rules', async () => { + const out = await filterOperations({ paths: paths(), slug: 'posts', kind: 'collection', - filters: f({ excludeWhen: ({ method }) => method === 'patch' }), + filters: f({ excludeOperations: [{ method: 'delete' }, ({ method }) => method === 'patch'] }), }) expect(out['/api/posts']?.patch).toBeUndefined() + expect(out['/api/posts']?.delete).toBeUndefined() expect(out['/api/posts']?.get).toBeDefined() }) + + it('awaits an async function and excludes only on `true`', async () => { + const out = await filterOperations({ + paths: paths(), + slug: 'posts', + kind: 'collection', + filters: f({ + excludeOperations: [async ({ method }) => method === 'patch', (() => 'yes') as unknown as () => boolean], + }), + }) + expect(out['/api/posts']?.patch).toBeUndefined() + expect(out['/api/posts']?.get).toBeDefined() + expect(out['/api/posts/{id}']?.delete).toBeDefined() + }) + + it('never matches a `slug` rule against an operation without a slug', async () => { + const out = await filterOperations({ + paths: paths(), + kind: 'custom', + filters: f({ excludeOperations: [{ slug: /.*/ }, { kind: 'custom', method: 'delete' }] }), + }) + expect(out['/api/posts']?.get).toBeDefined() + expect(out['/api/posts']?.delete).toBeUndefined() + }) }) }) @@ -249,7 +304,7 @@ describe('spec/tags', () => { const tags = buildTagHierarchy({ collections: [{ base: 'Posts', hasAuth: false, hasVersions: true, description: 'Blog posts' }], globals: [{ base: 'GlobalSettings', hasVersions: false }], - hasJobs: true, + systemTags: ['Jobs'], t: tt, nested: true, }) @@ -268,12 +323,28 @@ describe('spec/tags', () => { expect(byName['Jobs']?.parent).toBe(NAV_SYSTEM) }) + it('puts the Uploads and Access tags under the System group', () => { + const tt = ((key: string) => key) as Translate + const tags = buildTagHierarchy({ + collections: [], + globals: [], + systemTags: ['Uploads', 'Access'], + t: tt, + nested: true, + }) + const byName = Object.fromEntries(tags.map((tag) => [tag.name, tag])) + expect(byName['Uploads']?.parent).toBe(NAV_SYSTEM) + expect(byName['Uploads']?.summary).toBe('tagUploads') + expect(byName['Access']?.parent).toBe(NAV_SYSTEM) + expect(byName['Jobs']).toBeUndefined() + }) + it('builds a flat list of entity tags with descriptions when not nested', () => { const tt = ((key: string) => key) as Translate const tags = buildTagHierarchy({ collections: [{ base: 'Posts', hasAuth: true, hasVersions: true, description: 'Blog posts' }], globals: [{ base: 'GlobalSettings', hasVersions: true, description: 'Site config' }], - hasJobs: true, + systemTags: ['Jobs'], t: tt, nested: false, }) @@ -309,10 +380,10 @@ describe('spec/buildDocument', () => { const doc = await buildDocument( baseInput({ options: resolveOptions({ - metadata: { title: 'T', version: '1.0.0' }, + info: { title: 'T', version: '1.0.0' }, extensions: [ { paths: { '/api/custom': { get: { responses: { '200': { description: 'ok' } } } } } }, - { transform: (d) => ({ ...d, info: { ...d.info, title: 'Transformed' } }) }, + { transform: ({ doc: d }) => ({ ...d, info: { ...d.info, title: 'Transformed' } }) }, ], }), }), @@ -321,11 +392,11 @@ describe('spec/buildDocument', () => { expect(doc.info.title).toBe('Transformed') }) - it('keeps building when an extension throws', async () => { - const doc = await buildDocument( + it('fails closed when an extension `transform` throws', async () => { + const build = buildDocument( baseInput({ options: resolveOptions({ - metadata: { title: 'T', version: '1.0.0' }, + info: { title: 'T', version: '1.0.0' }, extensions: [ { transform: () => { @@ -336,15 +407,197 @@ describe('spec/buildDocument', () => { }), }), ) - expect(doc.info.title).toBe('T') + await expect(build).rejects.toThrow('boom') + }) + + it('fails closed when an extension `transform` returns the context, not the document', async () => { + const build = buildDocument( + baseInput({ + options: resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + extensions: [{ transform: (d) => ({ ...d }) as never }], + }), + }), + ) + await expect(build).rejects.toThrow('extensions[].transform must return the document') + }) + + it('awaits an async `transform` and passes it `payload` and the resolved options', async () => { + let seen: { payload: unknown; title: unknown; locales: unknown } | undefined + const input = baseInput({ + options: resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + extensions: [ + { + transform: async ({ doc: d, payload, options, locales }) => { + seen = { payload, title: options.info.title, locales } + return { ...d, info: { ...d.info, title: 'Async' } } + }, + }, + ], + }), + }) + const doc = await buildDocument(input) + expect(doc.info.title).toBe('Async') + expect(seen).toEqual({ payload: input.payload, title: 'T', locales: input.ctx.locales }) + }) + + it('copies every `info` field and resolves a localized `info.description`', async () => { + const info = { + title: 'T', + version: '1.0.0', + summary: 'Short', + termsOfService: 'https://example.com/terms', + contact: { name: 'Team', email: 'team@example.com' }, + license: { name: 'MIT', identifier: 'MIT' }, + } + const doc = await buildDocument( + baseInput({ options: resolveOptions({ info: { ...info, description: { en: 'English', de: 'Deutsch' } } }) }), + ) + expect(doc.info).toMatchObject(info) + expect(doc.info.description).toMatch(/^English\n\n## /) + const fromFn = await buildDocument( + baseInput({ options: resolveOptions({ info: { ...info, description: ({ i18n }) => `Lang ${i18n.language}` } }) }), + ) + expect(fromFn.info.description).toMatch(/^Lang en\n/) + const result = await new Validator().validate(structuredClone(doc)) + expect(result.valid).toBe(true) + }) + + it('drops `info.summary` and `license.identifier` from a 3.0 document', async () => { + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') + const doc = await buildDocument( + baseInput({ + options: resolveOptions({ + info: { title: 'T', version: '1.0.0', summary: 'Short', license: { name: 'MIT', identifier: 'MIT' } }, + }), + }), + ) + const converted = toOpenApi30(doc) + expect(converted.info.summary).toBeUndefined() + expect(converted.info.license).toEqual({ name: 'MIT' }) + expect(doc.info.summary).toBe('Short') + }) + + describe('interactive auth', () => { + const securedPaths = { + endpoints: [ + { + path: '/private', + method: 'get', + handler: () => new Response(), + custom: { openapi: { responses: { '200': { description: 'ok' } }, security: [{ PayloadToken: [] }] } }, + }, + ], + } as unknown as SanitizedConfig + const build = (over: Partial[0]>) => + buildDocument( + baseInput({ + config: securedPaths, + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, ...over }), + }), + ) + + it('references `PayloadLogin` from secured operations so "Try it" sends the token', async () => { + const doc = await build({ interactiveAuth: true }) + expect(doc.components?.securitySchemes?.PayloadLogin).toBeDefined() + expect(doc.paths?.['/api/private']?.get?.security).toEqual([{ PayloadToken: [] }, { PayloadLogin: [] }]) + const result = await new Validator().validate(structuredClone(doc)) + expect(result.valid).toBe(true) + }) + + it('advertises no `tokenUrl` when `serve` is false', async () => { + const doc = await build({ interactiveAuth: true, serve: false }) + expect(doc.components?.securitySchemes?.PayloadLogin).toBeUndefined() + expect(doc.paths?.['/api/private']?.get?.security).toEqual([{ PayloadToken: [] }]) + }) + + it("does not change the user's endpoint metadata", async () => { + await build({ interactiveAuth: true }) + const meta = (securedPaths.endpoints as unknown as Array<{ custom: { openapi: { security: unknown } } }>)[0] + expect(meta?.custom.openapi.security).toEqual([{ PayloadToken: [] }]) + }) + }) + + describe('custom, jobs and system operations', () => { + const config = { + endpoints: [ + { + path: '/health', + method: 'get', + handler: () => new Response(), + custom: { openapi: { responses: { '200': { description: 'ok' } } } }, + }, + ], + jobs: { tasks: [{}] }, + } as unknown as SanitizedConfig + const upload = coll('media', { upload: {} as never, fields: [] }) + + it('applies `excludeOperations` to every group', async () => { + const doc = await buildDocument( + baseInput({ + config, + collections: [upload], + options: resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + filters: { + excludeOperations: [ + { kind: 'custom' }, + ({ kind }) => kind === 'jobs', + { kind: 'system', method: 'delete' }, + ], + }, + }), + }), + ) + expect(doc.paths?.['/api/health']).toBeUndefined() + expect(doc.paths?.['/api/payload-jobs/run']).toBeUndefined() + expect(doc.paths?.['/api/upload-instructions/{uploadId}']?.delete).toBeUndefined() + expect(doc.paths?.['/api/upload-instructions/{uploadId}']?.put).toBeDefined() + }) + + it('passes custom, jobs and system operations to `security`', async () => { + const kinds = new Set() + const doc = await buildDocument( + baseInput({ + config, + collections: [upload], + options: resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + security: ({ kind }) => { + kinds.add(kind) + return kind === 'custom' ? 'secured' : undefined + }, + }), + }), + ) + expect([...kinds].toSorted()).toEqual(['custom', 'jobs', 'system']) + expect(doc.paths?.['/api/health']?.get?.security).toEqual([{ PayloadToken: [] }]) + }) + + it('marks jobs secured unless `jobs.access.run` is public', async () => { + const secured = await buildDocument(baseInput({ config })) + expect(secured.paths?.['/api/payload-jobs/run']?.get?.security).toEqual([{ PayloadToken: [] }]) + expect(secured.paths?.['/api/payload-jobs/handle-schedules']?.get?.security).toEqual([{ PayloadToken: [] }]) + const open = await buildDocument( + baseInput({ config: { ...config, jobs: { tasks: [{}], access: { run: () => true } } } }), + ) + expect(open.paths?.['/api/payload-jobs/run']?.get?.security).toBeUndefined() + }) }) }) describe('spec/paths/jobs', () => { describe('buildJobsPaths', () => { it('adds the run endpoint only when tasks or workflows are set up', async () => { - const { buildJobsPaths } = await import('../src/spec/paths/jobs.js') - const jobsCtx = { defaultIDType: 'text' as const, locales: [], apiRoute: '/api', i18n: i18nStub } + const { buildJobsPaths } = await import('@/server/spec/paths/jobs.js') + const jobsCtx = { + defaultIDType: 'text' as const, + locales: [], + apiRoute: '/api', + docLanguages: ['en'], + i18n: i18nStub, + } const cfg = (jobs: SanitizedConfig['jobs']): SanitizedConfig => ({ jobs }) as SanitizedConfig expect(buildJobsPaths({ config: cfg({} as SanitizedConfig['jobs']), ctx: jobsCtx })).toEqual({}) const withJobs = buildJobsPaths({ @@ -356,6 +609,91 @@ describe('spec/paths/jobs', () => { }) }) +describe('spec/paths/system', () => { + describe('buildSystemPaths', () => { + it('adds the upload instructions endpoints only when an upload collection exists', () => { + expect(buildSystemPaths({ ctx, access: false, uploadSlugs: [] })).toEqual({}) + const paths = buildSystemPaths({ ctx, access: false, uploadSlugs: ['media'] }) + expect(paths['/api/upload-instructions']?.post?.operationId).toBe('getUploadInstructions') + expect(paths['/api/upload-instructions/{uploadId}']?.put?.responses?.['204']).toBeDefined() + expect(paths['/api/upload-instructions/{uploadId}']?.delete?.responses?.['204']).toBeDefined() + expect(paths['/api/access']).toBeUndefined() + }) + + it('marks the staged upload `PUT` and `DELETE` as secured', () => { + const paths = buildSystemPaths({ ctx, access: false, uploadSlugs: ['media'] }) + expect(paths['/api/upload-instructions/{uploadId}']?.put?.security).toEqual([{ PayloadToken: [] }]) + expect(paths['/api/upload-instructions/{uploadId}']?.delete?.security).toEqual([{ PayloadToken: [] }]) + }) + + it('adds the root access endpoint only when `access` is on', () => { + expect(buildSystemPaths({ ctx, access: true, uploadSlugs: [] })['/api/access']?.get).toBeDefined() + }) + }) +}) + +describe('spec/paths/auth', () => { + const users = (auth: Record): SanitizedCollectionConfig => + ({ slug: 'users', fields: [], auth }) as unknown as SanitizedCollectionConfig + const build = (auth: Record): PathsObject => + buildAuthPaths({ collection: users(auth), ctx, includeAdmin: true, nestedTags: true }) + + const body = (paths: PathsObject, route: string): SchemaObject => { + const requestBody = paths[`/api/users/${route}`]?.post?.requestBody as RequestBodyObject + return requestBody.content['application/json'].schema as SchemaObject + } + + it('asks for `email` in login, forgot-password and unlock bodies by default', () => { + const paths = build({}) + for (const route of ['login', 'forgot-password', 'unlock']) { + expect(Object.keys(body(paths, route).properties ?? {})).not.toContain('username') + expect(body(paths, route).required).toContain('email') + } + expect(body(paths, 'login').required).toEqual(['email', 'password']) + }) + + it('asks for `username` only when `loginWithUsername` does not allow email login', () => { + for (const loginWithUsername of [true, { allowEmailLogin: false, requireEmail: true }]) { + const paths = build({ loginWithUsername }) + for (const route of ['login', 'forgot-password', 'unlock']) { + expect(Object.keys(body(paths, route).properties ?? {})).not.toContain('email') + expect(body(paths, route).required).toContain('username') + } + } + }) + + it('accepts `email` or `username` when `allowEmailLogin` is on', () => { + const paths = build({ loginWithUsername: { allowEmailLogin: true, requireEmail: false } }) + for (const route of ['login', 'forgot-password', 'unlock']) { + const schema = body(paths, route) + expect(Object.keys(schema.properties ?? {})).toEqual(expect.arrayContaining(['email', 'username'])) + expect(schema.anyOf).toEqual([{ required: ['email'] }, { required: ['username'] }]) + } + expect(body(paths, 'login').required).toEqual(['password']) + expect(body(paths, 'unlock').required).toBeUndefined() + }) + + it('documents unlock for every auth collection, without `maxLoginAttempts`', () => { + expect(build({})['/api/users/unlock']?.post).toBeDefined() + }) + + it('adds the API key reveal endpoint only when `useAPIKey.reveal` is on', () => { + expect(build({ useAPIKey: true })['/api/users/{id}/api-key/reveal']).toBeUndefined() + expect(build({ useAPIKey: { reveal: true } })['/api/users/{id}/api-key/reveal']?.post).toBeDefined() + }) + + it('keeps me, logout, refresh-token and init but drops login routes when `disableLocalStrategy` is set', () => { + const paths = build({ disableLocalStrategy: true }) + expect(paths['/api/users/me']?.get).toBeDefined() + expect(paths['/api/users/logout']?.post).toBeDefined() + expect(paths['/api/users/refresh-token']?.post).toBeDefined() + expect(paths['/api/users/init']?.get).toBeDefined() + expect(paths['/api/users/login']).toBeUndefined() + expect(paths['/api/users/forgot-password']).toBeUndefined() + expect(paths['/api/users/first-register']).toBeUndefined() + }) +}) + describe('spec/paths/custom', () => { describe('buildCustomEndpointPaths', () => { it('keeps only endpoints with `custom.openapi` and turns params into placeholders', () => { @@ -384,8 +722,8 @@ describe('spec/paths/custom', () => { } as unknown as SanitizedCollectionConfig const paths = buildCustomEndpointPaths({ config, collections: [collection], globals: [], ctx }) - expect(paths['/health']).toBeDefined() - expect(paths['/secret']).toBeUndefined() + expect(paths['/api/health']).toBeDefined() + expect(paths['/api/secret']).toBeUndefined() expect(paths['/api/posts/{id}/tracking']).toBeDefined() }) }) @@ -402,6 +740,21 @@ describe('spec/paths/collections', () => { expect(paths['/api/posts/{id}/duplicate']?.post).toBeDefined() }) + it('lists `limit` and `sort` on bulk update only', () => { + const paths = buildCollectionPaths({ collection: postsColl(), ctx }) + const names = (op?: { parameters?: unknown[] }) => ((op?.parameters ?? []) as { name: string }[]).map((p) => p.name) + expect(names(paths['/api/posts']?.patch)).toEqual(expect.arrayContaining(['limit', 'sort'])) + expect(names(paths['/api/posts']?.patch)).not.toContain('page') + expect(names(paths['/api/posts']?.delete)).not.toContain('limit') + expect(names(paths['/api/posts']?.delete)).not.toContain('sort') + }) + + it('documents the file endpoint only for upload collections', () => { + expect(buildCollectionPaths({ collection: postsColl(), ctx })['/api/posts/file/{filename}']).toBeUndefined() + const paths = buildCollectionPaths({ collection: postsColl({ upload: {} as never }), ctx }) + expect(paths['/api/posts/file/{filename}']?.get?.responses?.['206']).toBeDefined() + }) + it('drops bulk update/delete when `disableBulkEdit` is set, keeping by-id ops', () => { const paths = buildCollectionPaths({ collection: postsColl({ disableBulkEdit: true }), ctx }) expect(paths['/api/posts']?.patch).toBeUndefined() @@ -417,18 +770,9 @@ describe('spec/paths/collections', () => { expect(paths['/api/posts/{id}']?.get).toBeDefined() }) - it('lists `limit` and `sort` on bulk update only', () => { - const paths = buildCollectionPaths({ collection: postsColl(), ctx }) - const names = (op?: { parameters?: unknown[] }) => ((op?.parameters ?? []) as { name: string }[]).map((p) => p.name) - expect(names(paths['/api/posts']?.patch)).toEqual(expect.arrayContaining(['limit', 'sort'])) - expect(names(paths['/api/posts']?.patch)).not.toContain('page') - expect(names(paths['/api/posts']?.delete)).not.toContain('limit') - expect(names(paths['/api/posts']?.delete)).not.toContain('sort') - }) - it('describes the responses as Payload sends them', async () => { - const { buildGlobalPaths } = await import('../src/spec/paths/globals.js') - const { buildVersionPaths } = await import('../src/spec/paths/versions.js') + const { buildGlobalPaths } = await import('@/server/spec/paths/globals.js') + const { buildVersionPaths } = await import('@/server/spec/paths/versions.js') const body = (op?: { responses?: Record }, code = '200') => { const response = op?.responses?.[code] as { content: Record } | undefined return response?.content['application/json']?.schema @@ -458,52 +802,85 @@ describe('spec/paths/collections', () => { expect(restore()?.allOf?.[0]).toEqual(doc) expect(restore(true)?.properties).toEqual({ message: { type: 'string' }, doc }) }) -}) -describe('spec/paths/auth', () => { - const users = (auth: Record): SanitizedCollectionConfig => - ({ slug: 'users', fields: [], auth }) as unknown as SanitizedCollectionConfig - - const build = (auth: Record): PathsObject => - buildAuthPaths({ collection: users(auth), ctx, includeAdmin: true, nestedTags: true }) + it('adds the document access endpoints with the operations Payload checks', async () => { + const { buildGlobalPaths } = await import('@/server/spec/paths/globals.js') + const operations = (op?: { responses?: Record }) => { + const ok = op?.responses?.['200'] as { content: Record } + return Object.keys(ok.content['application/json']?.schema.properties ?? {}) + } + expect(buildCollectionPaths({ collection: postsColl(), ctx })['/api/posts/access/{id}']).toBeUndefined() + + const plain = buildCollectionPaths({ collection: postsColl(), ctx, access: true }) + expect(operations(plain['/api/posts/access']?.post)).toEqual([ + 'create', + 'read', + 'update', + 'delete', + 'validate', + 'fields', + ]) + expect(plain['/api/posts/access/{id}']?.post?.operationId).toBe('accessPostsById') - const body = (paths: PathsObject, route: string): SchemaObject => { - const requestBody = paths[`/api/users/${route}`]?.post?.requestBody as RequestBodyObject - return requestBody.content['application/json'].schema as SchemaObject - } + const users = buildCollectionPaths({ + collection: postsColl({ auth: { maxLoginAttempts: 5 } as never, versions: { drafts: false } as never }), + ctx, + access: true, + }) + expect(operations(users['/api/posts/access/{id}']?.post)).toEqual([ + 'create', + 'read', + 'update', + 'delete', + 'validate', + 'unlock', + 'readVersions', + 'fields', + ]) - it('asks for `email` in login, forgot-password and unlock bodies by default', () => { - const paths = build({}) - for (const route of ['login', 'forgot-password', 'unlock']) { - expect(Object.keys(body(paths, route).properties ?? {})).not.toContain('username') - expect(body(paths, route).required).toContain('email') - } - expect(body(paths, 'login').required).toEqual(['email', 'password']) + const global = { slug: 'site', fields: [], versions: false } as unknown as SanitizedGlobalConfig + expect(buildGlobalPaths({ global, ctx })['/api/globals/site/access']).toBeUndefined() + expect(operations(buildGlobalPaths({ global, ctx, access: true })['/api/globals/site/access']?.post)).toEqual([ + 'read', + 'update', + 'validate', + 'fields', + ]) }) - it('asks for `username` only when `loginWithUsername` does not allow email login', () => { - for (const loginWithUsername of [true, { allowEmailLogin: false, requireEmail: true }]) { - const paths = build({ loginWithUsername }) - for (const route of ['login', 'forgot-password', 'unlock']) { - expect(Object.keys(body(paths, route).properties ?? {})).not.toContain('email') - expect(body(paths, route).required).toContain('username') - } - } - }) + it('adds the validate endpoints with a locale list and the validation result', async () => { + const { buildGlobalPaths } = await import('@/server/spec/paths/globals.js') + const paths = buildCollectionPaths({ collection: postsColl(), ctx }) + const create = paths['/api/posts/validate']?.post + const byId = paths['/api/posts/{id}/validate']?.post + expect(create?.operationId).toBe('validatePosts') + expect(create?.requestBody).toMatchObject({ + required: true, + content: { 'application/json': { schema: { $ref: '#/components/schemas/PostsCreate' } } }, + }) + expect(byId?.operationId).toBe('validatePostsById') + expect(byId?.requestBody).not.toHaveProperty('required') + expect(byId?.requestBody).toMatchObject({ + content: { 'application/json': { schema: { $ref: '#/components/schemas/PostsUpdate' } } }, + }) + expect(paths['/api/posts/{id}/validate']?.parameters?.[0]).toMatchObject({ name: 'id', in: 'path' }) + expect(create?.responses?.['200']).toMatchObject({ + content: { 'application/json': { schema: { required: ['valid', 'errors'] } } }, + }) + expect( + buildCollectionPaths({ collection: postsColl(), ctx: { ...ctx, locales: [] } })['/api/posts/validate']?.post + ?.parameters, + ).toEqual([]) - it('accepts `email` or `username` when `allowEmailLogin` is on', () => { - const paths = build({ loginWithUsername: { allowEmailLogin: true, requireEmail: false } }) - for (const route of ['login', 'forgot-password', 'unlock']) { - const schema = body(paths, route) - expect(Object.keys(schema.properties ?? {})).toEqual(expect.arrayContaining(['email', 'username'])) - expect(schema.anyOf).toEqual([{ required: ['email'] }, { required: ['username'] }]) - } - expect(body(paths, 'login').required).toEqual(['password']) - expect(body(paths, 'unlock').required).toBeUndefined() - }) + const localized = buildCollectionPaths({ collection: postsColl(), ctx: { ...ctx, locales: ['en', 'de'] } }) + expect(localized['/api/posts/validate']?.post?.parameters).toMatchObject([ + { name: 'locale', in: 'query', explode: true, schema: { type: 'array', items: { enum: ['en', 'de', 'all'] } } }, + ]) - it('documents unlock for every auth collection, without `maxLoginAttempts`', () => { - expect(build({})['/api/users/unlock']?.post).toBeDefined() + const global = { slug: 'site', fields: [] } as unknown as SanitizedGlobalConfig + expect(buildGlobalPaths({ global, ctx })['/api/globals/site/validate']?.post?.operationId).toBe( + 'validateGlobalSite', + ) }) }) @@ -555,6 +932,22 @@ describe('spec/security', () => { expect(await evaluateAccess(access as never)).toBe(false) }) + it('reads `baseAccess` from the config the way Payload wraps access', async () => { + const wrapped = async ({ req }: { req: { payload: { config: { baseAccess?: { read?: () => boolean } } } } }) => { + const base = req.payload.config.baseAccess?.read + return base ? base() : true + } + expect(await evaluateAccess(wrapped as never)).toBe(true) + expect(await evaluateAccess(wrapped as never, { baseAccess: { read: () => true } as never })).toBe(true) + expect(await evaluateAccess(wrapped as never, { baseAccess: { read: () => false } as never })).toBe(false) + }) + + it('treats access that reads other config keys as secured', async () => { + const access = ({ req }: { req: { payload: { config: { custom?: { private?: boolean } } } } }) => + !req.payload.config.custom?.private + expect(await evaluateAccess(access as never)).toBe(false) + }) + it('treats a never-settling promise as secured once the timeout elapses', async () => { const access = () => new Promise(() => {}) expect(await evaluateAccess(access as never, { timeoutMs: 10 })).toBe(false) @@ -572,21 +965,32 @@ describe('spec/security', () => { expect(sec.create).toEqual(secured) }) - it('lets a boolean custom.openapi.security override the probe for all operations', async () => { - const entity = { - slug: 'posts', - access: { read: () => true }, - custom: { openapi: { security: false } }, - } as unknown as SanitizedCollectionConfig - const sec = await resolveEntitySecurity({ entity, operations: ['read'] }) - expect(sec.read).toEqual(secured) + it("lets 'public' or 'secured' in custom.openapi.security override the probe for all operations", async () => { + const entity = (security: string, allowed: boolean) => + ({ + slug: 'posts', + access: { read: () => allowed, create: () => allowed }, + custom: { openapi: { security } }, + }) as unknown as SanitizedCollectionConfig + const secured_ = await resolveEntitySecurity({ entity: entity('secured', true), operations: ['read', 'create'] }) + expect(secured_).toEqual({ read: secured, create: secured }) + const open = await resolveEntitySecurity({ entity: entity('public', false), operations: ['read', 'create'] }) + expect(open).toEqual({ read: undefined, create: undefined }) + }) + + it('throws on the removed true/false form, also inside a per-operation object', async () => { + const message = `[${PLUGIN_NAME}] custom.openapi.security true/false on "posts" was removed, use 'public' or 'secured'` + for (const security of [false, true, { read: true }, { read: 'public', create: false }]) { + const entity = { slug: 'posts', custom: { openapi: { security } } } as unknown as SanitizedCollectionConfig + await expect(resolveEntitySecurity({ entity, operations: ['read'] })).rejects.toThrow(message) + } }) it('lets a per-operation override win over the probe in both directions', async () => { const entity = { slug: 'posts', access: { read: () => false, create: () => true }, - custom: { openapi: { security: { read: true, create: false } } }, + custom: { openapi: { security: { read: 'public', create: 'secured' } } }, } as unknown as SanitizedCollectionConfig const sec = await resolveEntitySecurity({ entity, operations: ['read', 'create'] }) expect(sec.read).toBeUndefined() @@ -597,7 +1001,7 @@ describe('spec/security', () => { const entity = { slug: 'posts', access: { read: () => true, create: () => true }, - custom: { openapi: { security: { read: false } } }, + custom: { openapi: { security: { read: 'secured' } } }, } as unknown as SanitizedCollectionConfig const sec = await resolveEntitySecurity({ entity, operations: ['read', 'create'] }) expect(sec.read).toEqual(secured) @@ -605,36 +1009,76 @@ describe('spec/security', () => { }) }) - describe('applySecurityWhen', () => { + it('types `custom.openapi` on collections, globals and fields', () => { + expectTypeOf['openapi']>().toEqualTypeOf() + expectTypeOf['openapi']>().toEqualTypeOf() + expectTypeOf['openapi']>().toEqualTypeOf() + expectTypeOf<{ security: 'public' }>().toExtend() + expectTypeOf<{ security: { read: 'public'; update: 'secured' } }>().toExtend() + expectTypeOf<{ security: true }>().not.toExtend() + }) + + describe('applySecurity', () => { const paths = (): PathsObject => ({ '/api/posts': { get: { security: undefined }, post: { security: [{ PayloadToken: [] }] } }, }) + const withLogin = [{ PayloadToken: [] }, { PayloadLogin: [] }] - it('is a no-op when securityWhen is not set', () => { + it('is a no-op when neither `security` nor login is set', async () => { const p = paths() - expect(applySecurityWhen({ paths: p, slug: 'posts', kind: 'collection' })).toBe(p) + expect(await applySecurity({ paths: p, slug: 'posts', kind: 'collection' })).toBe(p) }) - it('opens an operation when the predicate returns true', () => { - const out = applySecurityWhen({ paths: paths(), slug: 'posts', kind: 'collection', securityWhen: () => true }) + it("opens an operation on 'public'", async () => { + const out = await applySecurity({ paths: paths(), slug: 'posts', kind: 'collection', security: () => 'public' }) expect(out['/api/posts']?.post?.security).toBeUndefined() }) - it('closes an operation when the predicate returns false', () => { - const out = applySecurityWhen({ paths: paths(), slug: 'posts', kind: 'collection', securityWhen: () => false }) + it("closes an operation on 'secured'", async () => { + const out = await applySecurity({ paths: paths(), slug: 'posts', kind: 'collection', security: () => 'secured' }) expect(out['/api/posts']?.get?.security).toEqual(secured) }) - it('leaves an operation untouched when the predicate returns undefined', () => { - const out = applySecurityWhen({ + it('keeps the detected marking on undefined and passes it as `detected`', async () => { + const seen: string[] = [] + const out = await applySecurity({ paths: paths(), slug: 'posts', kind: 'collection', - securityWhen: () => undefined, + security: ({ method, detected }) => { + seen.push(`${method}:${detected}`) + return undefined + }, }) + expect(seen).toEqual(['get:public', 'post:secured']) expect(out['/api/posts']?.get?.security).toBeUndefined() expect(out['/api/posts']?.post?.security).toEqual(secured) }) + + it('sets a returned requirement array as is and awaits a Promise', async () => { + const apiKey = [{ ApiKey: [] }] + const out = await applySecurity({ + paths: paths(), + slug: 'posts', + kind: 'collection', + security: async ({ method }) => (method === 'get' ? apiKey : 'public'), + }) + expect(out['/api/posts']?.get?.security).toEqual(apiKey) + expect(out['/api/posts']?.post?.security).toBeUndefined() + }) + + it('adds the `PayloadLogin` requirement next to `PayloadToken` when login is on', async () => { + const out = await applySecurity({ paths: paths(), slug: 'posts', kind: 'collection', login: true }) + expect(out['/api/posts']?.post?.security).toEqual(withLogin) + expect(out['/api/posts']?.get?.security).toBeUndefined() + const closed = await applySecurity({ + paths: paths(), + kind: 'custom', + login: true, + security: () => 'secured', + }) + expect(closed['/api/posts']?.get?.security).toEqual(withLogin) + }) }) describe('per-operation marking in path builders', () => { @@ -653,8 +1097,28 @@ describe('spec/security', () => { expect(paths['/api/posts']?.post?.security).toEqual(secured) }) + it('marks version reads from `readVersions` and restore from `update`', async () => { + const { buildVersionPaths } = await import('@/server/spec/paths/versions.js') + const entity = { + slug: 'posts', + fields: [], + versions: {}, + access: { read: () => true, readVersions: async () => true, update: () => false }, + } as unknown as SanitizedCollectionConfig + const security = await resolveEntitySecurity({ entity, operations: ['readVersions', 'update'] }) + const paths = buildVersionPaths({ entity, pathBase: '/api/posts', ctx, nestedTags: false, security }) + expect(paths['/api/posts/versions']?.get?.security).toBeUndefined() + expect(paths['/api/posts/versions/{id}']?.get?.security).toBeUndefined() + expect(paths['/api/posts/versions/{id}']?.post?.security).toEqual(secured) + const closed = await resolveEntitySecurity({ + entity: { ...entity, access: {} } as SanitizedCollectionConfig, + operations: ['readVersions'], + }) + expect(closed.readVersions).toEqual(secured) + }) + it('marks global get/post from read/update security', async () => { - const { buildGlobalPaths } = await import('../src/spec/paths/globals.js') + const { buildGlobalPaths } = await import('@/server/spec/paths/globals.js') const global = { slug: 'settings', fields: [], @@ -665,12 +1129,38 @@ describe('spec/security', () => { expect(paths['/api/globals/settings']?.get?.security).toBeUndefined() expect(paths['/api/globals/settings']?.post?.security).toEqual(secured) }) + + it('marks validate from `validate` access, its own override and the `update` override', async () => { + const { buildGlobalPaths } = await import('@/server/spec/paths/globals.js') + const global = { + slug: 'settings', + fields: [], + access: { update: () => false, validate: () => true }, + } as unknown as SanitizedGlobalConfig + const probed = await resolveEntitySecurity({ entity: global, operations: ['update', 'validate'] }) + const paths = buildGlobalPaths({ global, ctx, security: probed }) + expect(paths['/api/globals/settings']?.post?.security).toEqual(secured) + expect(paths['/api/globals/settings/validate']?.post?.security).toBeUndefined() + const overridden = await resolveEntitySecurity({ + entity: { ...global, custom: { openapi: { security: { update: 'secured' } } } } as SanitizedGlobalConfig, + operations: ['validate'], + }) + expect(overridden.validate).toEqual(secured) + const ownKey = await resolveEntitySecurity({ + entity: { + ...global, + custom: { openapi: { security: { update: 'secured', validate: 'public' } } }, + } as SanitizedGlobalConfig, + operations: ['update', 'validate'], + }) + expect(ownKey).toEqual({ update: secured, validate: undefined }) + }) }) }) describe('spec/entitySchemas', () => { it('writes relationships as ids at any depth and keeps the populated shape on read', async () => { - const { buildEntitySchemas } = await import('../src/spec/entitySchemas.js') + const { buildEntitySchemas } = await import('@/server/spec/entitySchemas.js') const fields = [ { name: 'cover', type: 'upload', relationTo: 'media' }, { name: 'meta', type: 'group', fields: [{ name: 'image', type: 'upload', relationTo: 'media' }] }, @@ -697,38 +1187,72 @@ describe('spec/entitySchemas', () => { const config = { collections: [collection('media'), collection('tags')], blocks: [] } as unknown as SanitizedConfig const { read, create, update } = await buildEntitySchemas({ entity, config, ctx }) - const id = { type: 'string' } - const optionalId = { type: ['string', 'null'] } + const id = { type: ['string', 'null'] } const props = create.properties ?? {} - expect(props.cover).toEqual(optionalId) - expect((props.meta as SchemaObject).properties?.image).toEqual(optionalId) + expect(props.cover).toEqual(id) + expect((props.meta as SchemaObject).properties?.image).toEqual(id) const row = ((props.rows as SchemaObject).items as SchemaObject).properties ?? {} - expect(row.tags).toEqual({ type: ['array', 'null'], items: id }) + expect(row.tags).toEqual({ type: ['array', 'null'], items: { type: 'string' } }) const link = (row.link as SchemaObject).oneOf as SchemaObject[] expect(link.map((b) => [b.properties?.relationTo, b.properties?.value])).toEqual([ - [{ const: 'media' }, id], - [{ const: 'tags' }, id], + [{ const: 'media' }, { type: 'string' }], + [{ const: 'tags' }, { type: 'string' }], ]) const quote = ((props.content as SchemaObject).items as SchemaObject).oneOf?.[0] as SchemaObject - expect(quote.properties?.source).toEqual(optionalId) + expect(quote.properties?.source).toEqual(id) expect(update.properties?.meta).toEqual(props.meta) const readMeta = read.properties?.meta as SchemaObject const readImage = readMeta.properties?.image as SchemaObject expect(readImage.oneOf?.[1]).toEqual({ $ref: '#/components/schemas/Media' }) }) + + it('leaves fields whose write access is always false out of the matching write body', async () => { + const { buildEntitySchemas } = await import('@/server/spec/entitySchemas.js') + const fields = [ + { name: 'stamp', type: 'text', access: { create: () => false, update: () => false } }, + { name: 'lockedLater', type: 'text', access: { update: () => false } }, + { + name: 'maybe', + type: 'text', + access: { create: (args?: { req?: { user?: { admin?: boolean } } }) => args?.req?.user?.admin === true }, + }, + { + name: 'adminOnly', + type: 'text', + access: { create: ({ req }: { req: { user: unknown } }) => Boolean(req.user) }, + }, + { type: 'row', fields: [{ name: 'nested', type: 'text', access: { create: () => false } }] }, + ] as Field[] + const entity = { + slug: 'posts', + fields, + flattenedFields: flattenAllFields({ fields }), + } as unknown as SanitizedCollectionConfig + const config = { collections: [entity], blocks: [] } as unknown as SanitizedConfig + const { read, create, update } = await buildEntitySchemas({ entity, config, ctx }) + expect(Object.keys(read.properties ?? {})).toEqual( + expect.arrayContaining(['stamp', 'lockedLater', 'adminOnly', 'nested']), + ) + expect(Object.keys(create.properties ?? {})).toEqual(expect.arrayContaining(['lockedLater', 'adminOnly', 'maybe'])) + expect(create.properties).not.toHaveProperty('stamp') + expect(create.properties).not.toHaveProperty('nested') + expect(update.properties).not.toHaveProperty('stamp') + expect(update.properties).not.toHaveProperty('lockedLater') + expect(update.properties).toHaveProperty('adminOnly') + }) }) describe('spec/params', () => { it('uses `ctx.t` for descriptions so a custom translation flows through', async () => { - const { buildSelectSchema } = await import('../src/spec/params.js') + const { buildSelectSchema } = await import('@/server/spec/params.js') const custom = { ...ctx, i18n: { ...ctx.i18n, t: (() => 'ÜBERSETZT') as unknown as I18n['t'] } } const schema = buildSelectSchema({ fields: [{ name: 'title', type: 'text' }] as Field[], ctx: custom }) expect(schema.description).toBe('ÜBERSETZT') }) it('adds draft, locale and lock params by entity config and operation', async () => { - const { writeParams } = await import('../src/spec/params.js') + const { writeParams } = await import('@/server/spec/params.js') const refs = { select: true, populate: false, joins: false } const entity = (versions: unknown, lockDocuments?: false) => ({ slug: 'x', fields: [], versions, lockDocuments }) as unknown as SanitizedCollectionConfig @@ -770,7 +1294,7 @@ type Loose = Record describe('spec/downconvert', () => { describe('toOpenApi30', () => { it('rewrites the version and nullable type arrays, leaving non-null types alone', async () => { - const { toOpenApi30 } = await import('../src/spec/downconvert.js') + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.1.2', info: { title: 'T', version: '1' }, @@ -798,7 +1322,7 @@ describe('spec/downconvert', () => { }) it('converts content and example keywords and drops the ones 3.0 rejects', async () => { - const { toOpenApi30 } = await import('../src/spec/downconvert.js') + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.1.2', info: { title: 'T', version: '1' }, @@ -831,7 +1355,7 @@ describe('spec/downconvert', () => { }) it('leaves a 3.0 examples map untouched when it is already an object', async () => { - const { toOpenApi30 } = await import('../src/spec/downconvert.js') + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.1.2', info: { title: 'T', version: '1' }, @@ -859,11 +1383,9 @@ describe('spec/downconvert', () => { sample: { value: { ok: true } }, }) }) - }) - describe('toOpenApi30 required', () => { it('drops an empty `required` array and keeps a filled one', async () => { - const { toOpenApi30 } = await import('../src/spec/downconvert.js') + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.2.0', info: { title: 'T', version: '1' }, @@ -881,7 +1403,7 @@ describe('spec/downconvert', () => { }) it('turns several types into `anyOf` and keeps `null` in each branch', async () => { - const { toOpenApi30 } = await import('../src/spec/downconvert.js') + const { toOpenApi30 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.2.0', info: { title: 'T', version: '1' }, @@ -907,7 +1429,7 @@ describe('spec/downconvert', () => { describe('toOpenApi31', () => { it('rewrites the `openapi` version field to 3.1.2', async () => { - const { toOpenApi31 } = await import('../src/spec/downconvert.js') + const { toOpenApi31 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.2.0', info: { title: 'Test', version: '1.0' }, paths: {} } const result = toOpenApi31(doc) expect(result.openapi).toBe('3.1.2') @@ -915,13 +1437,13 @@ describe('spec/downconvert', () => { }) it('returns a new document instead of mutating the input', async () => { - const { toOpenApi31 } = await import('../src/spec/downconvert.js') + const { toOpenApi31 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.2.0', info: { title: 'T', version: '1' }, paths: {} } expect(toOpenApi31(doc)).not.toBe(doc) }) it('strips the 3.2-only `kind`, `parent` and `summary` fields from tags', async () => { - const { toOpenApi31 } = await import('../src/spec/downconvert.js') + const { toOpenApi31 } = await import('@/server/spec/downconvert.js') const doc: Document = { openapi: '3.2.0', info: { title: 'T', version: '1' }, @@ -963,41 +1485,90 @@ describe('endpoints/spec', () => { }, }) as unknown as PayloadRequest - const optionsBase = () => resolveOptions({ metadata: { title: 'T', version: '1.0.0' } }) + const optionsBase = () => resolveOptions({ info: { title: 'T', version: '1.0.0' } }) + const serversFor = async (options: ReturnType, host: string, serverURL?: string) => { + const { specHandler } = await import('@/server/endpoints/spec.js') + const req = makeReq(host) + Object.assign(req.payload.config, { serverURL }) + return (await (await specHandler(options)(req)).json()).servers + } - it('injects a fresh server URL per request from the cached doc', async () => { - const { specHandler } = await import('../src/endpoints/spec.js') - const handler = specHandler(optionsBase()) - const a = await (await handler(makeReq('localhost:3000'))).json() - const b = await (await handler(makeReq('api.example.com'))).json() - expect(a.servers[0].url).toBe('http://localhost:3000') - expect(b.servers[0].url).toBe('https://api.example.com') + it('never reads servers from an untrusted Host header', async () => { + expect(await serversFor(optionsBase(), 'evil.example.com')).toEqual([]) + expect(await serversFor(optionsBase(), 'evil.example.com', 'https://api.example.com')).toEqual([ + { url: 'https://api.example.com' }, + ]) + }) + + it('uses the Host header only when it is in `trustedHosts`', async () => { + const options = resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + trustedHosts: ['API.example.com', 'localhost:3000'], + }) + expect(await serversFor(options, 'api.example.com')).toEqual([{ url: 'https://api.example.com' }]) + expect(await serversFor(options, 'localhost:3000')).toEqual([{ url: 'http://localhost:3000' }]) + expect(await serversFor(options, 'evil.example.com', 'https://api.example.com')).toEqual([ + { url: 'https://api.example.com' }, + ]) + }) + + it('prefers a `servers` array over `trustedHosts` and `serverURL`', async () => { + const servers = [{ url: 'https://a.example.com', description: 'A' }] + const options = resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + servers, + trustedHosts: ['b.example.com'], + }) + expect(await serversFor(options, 'b.example.com', 'https://c.example.com')).toEqual(servers) + }) + + it('calls a `servers` function per request with `req` and keeps the cached doc', async () => { + const options = resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + servers: async ({ req }) => [{ url: `https://${req.headers.get('host')}` }], + }) + const { specHandler } = await import('@/server/endpoints/spec.js') + const handler = specHandler(options) + const a = await (await handler(makeReq('a.example.com'))).json() + const b = await (await handler(makeReq('b.example.com'))).json() + expect(a.servers).toEqual([{ url: 'https://a.example.com' }]) + expect(b.servers).toEqual([{ url: 'https://b.example.com' }]) expect({ ...a, servers: [] }).toEqual({ ...b, servers: [] }) }) + it('answers 403 when `access` denies the request', async () => { + const { specHandler } = await import('@/server/endpoints/spec.js') + const handler = specHandler( + resolveOptions({ info: { title: 'T', version: '1.0.0' }, access: async ({ req }) => Boolean(req.user) }), + ) + await expect(handler(makeReq('localhost:3000'))).rejects.toMatchObject({ status: 403 }) + const req = Object.assign(makeReq('localhost:3000'), { user: { id: 1 } }) + expect((await handler(req)).status).toBe(200) + }) + it('rebuilds on every request when the cache is off', async () => { - const { specHandler } = await import('../src/endpoints/spec.js') - const handler = specHandler(resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, cache: false })) + const { specHandler } = await import('@/server/endpoints/spec.js') + const handler = specHandler(resolveOptions({ info: { title: 'T', version: '1.0.0' }, cache: false })) const res = await (await handler(makeReq('localhost:3000'))).json() expect(res.openapi).toBe('3.2.0') }) it('serves 3.0 when `openapiVersion` is `3.0`', async () => { - const { specHandler } = await import('../src/endpoints/spec.js') - const handler = specHandler(resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, openapiVersion: '3.0' })) + const { specHandler } = await import('@/server/endpoints/spec.js') + const handler = specHandler(resolveOptions({ info: { title: 'T', version: '1.0.0' }, openapiVersion: '3.0' })) const res = await (await handler(makeReq('localhost:3000'))).json() expect(res.openapi).toBe('3.0.4') }) it('serves 3.1 when `openapiVersion` is `3.1`', async () => { - const { specHandler } = await import('../src/endpoints/spec.js') - const handler = specHandler(resolveOptions({ metadata: { title: 'T', version: '1.0.0' }, openapiVersion: '3.1' })) + const { specHandler } = await import('@/server/endpoints/spec.js') + const handler = specHandler(resolveOptions({ info: { title: 'T', version: '1.0.0' }, openapiVersion: '3.1' })) const res = await (await handler(makeReq('localhost:3000'))).json() expect(res.openapi).toBe('3.1.2') }) it('defaults to 3.2 when `openapiVersion` is not set', async () => { - const { specHandler } = await import('../src/endpoints/spec.js') + const { specHandler } = await import('@/server/endpoints/spec.js') const handler = specHandler(optionsBase()) const res = await (await handler(makeReq('localhost:3000'))).json() expect(res.openapi).toBe('3.2.0') @@ -1006,15 +1577,47 @@ describe('endpoints/spec', () => { }) describe('ui/html', () => { - const renderHtml = async (plugin: Plugin): Promise => { + const uiRequest = (custom: Record = {}, user: unknown = null) => + ({ + user, + t: (k: string) => k, + payload: { config: { routes: { api: '/api' }, custom } }, + }) as unknown as PayloadRequest + const renderHtml = async (plugin: Plugin, req = uiRequest()): Promise => { const out = await plugin({ routes: { api: '/api' } } as unknown as Config) const endpoint = (out.endpoints ?? []).at(-1) as Endpoint - const handler = endpoint.handler as () => Response | Promise - return (await handler()).text() + return (await endpoint.handler(req)).text() } + it('loads the spec from the `openapi()` route, including a custom `path`', async () => { + const { scalar } = await import('@/server/ui/scalar.js') + expect(await renderHtml(scalar())).toContain('"/api/openapi.json"') + const resolved = resolveOptions({ info: { title: 'Shop API', version: '1.0.0' }, path: '/spec.json' }) + const html = await renderHtml(scalar(), uiRequest({ [PLUGIN_NAME]: resolved })) + expect(html).toContain('"/api/spec.json"') + expect(html).toContain('Shop API') + }) + + it('uses `specURL` when set', async () => { + const { swaggerUi } = await import('@/server/ui/swagger.js') + expect(await renderHtml(swaggerUi({ specURL: '/static/openapi.json' }))).toContain('"/static/openapi.json"') + }) + + it('answers 403 when `access` denies the request', async () => { + const { scalar } = await import('@/server/ui/scalar.js') + const plugin = scalar({ access: ({ req }) => Boolean(req.user) }) + await expect(renderHtml(plugin)).rejects.toMatchObject({ status: 403 }) + expect(await renderHtml(plugin, uiRequest({}, { id: 1 }))).toContain('Scalar.createApiReference') + }) + + it('throws on the removed `specEndpoint`', async () => { + const { scalar } = await import('@/server/ui/scalar.js') + const plugin = scalar({ specEndpoint: '/x' } as never) + expect(() => plugin({} as Config)).toThrow(`[${PLUGIN_NAME}] specEndpoint was renamed to specURL`) + }) + it('passes Scalar configuration into the init call', async () => { - const { scalar } = await import('../src/ui/scalar.js') + const { scalar } = await import('@/server/ui/scalar.js') const html = await renderHtml(scalar({ configuration: { theme: 'purple', hideModels: true } })) expect(html).toContain('Scalar.createApiReference') expect(html).toContain('"theme":"purple"') @@ -1022,22 +1625,42 @@ describe('ui/html', () => { }) it('passes Swagger UI configuration into `SwaggerUIBundle`', async () => { - const { swaggerUi } = await import('../src/ui/swagger.js') + const { swaggerUi } = await import('@/server/ui/swagger.js') const html = await renderHtml(swaggerUi({ configuration: { docExpansion: 'none' } })) expect(html).toContain('SwaggerUIBundle') expect(html).toContain('"docExpansion":"none"') }) it('escapes `<` in configuration so a string value cannot break out of the script', async () => { - const { swaggerUi } = await import('../src/ui/swagger.js') + const { swaggerUi } = await import('@/server/ui/swagger.js') const html = await renderHtml(swaggerUi({ configuration: { x: '' } })) expect(html).not.toContain('"') expect(html).toContain('\\u003c/script>') }) + it('loads pinned CDN assets with SRI, and a custom `cdnBase` without it', async () => { + const { scalar } = await import('@/server/ui/scalar.js') + const { swaggerUi } = await import('@/server/ui/swagger.js') + const scalarHtml = await renderHtml(scalar()) + expect(scalarHtml).toMatch( + /