diff --git a/.all-contributorsrc b/.all-contributorsrc index 70a4a37b4..4db49050a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3,9 +3,7 @@ "projectOwner": "webiny", "repoType": "github", "repoHost": "https://github.com", - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": true, "commitConvention": "none", @@ -15,65 +13,49 @@ "name": "Sven", "avatar_url": "https://avatars3.githubusercontent.com/u/3808420?v=4", "profile": "http://www.webiny.com/", - "contributions": [ - "doc", - "code" - ] + "contributions": ["doc", "code"] }, { "login": "Pavel910", "name": "Pavel Denisjuk", "avatar_url": "https://avatars1.githubusercontent.com/u/3920893?v=4", "profile": "http://webiny.com/", - "contributions": [ - "doc", - "code" - ] + "contributions": ["doc", "code"] }, { "login": "doitadrian", "name": "Adrian Smijulj", "avatar_url": "https://avatars0.githubusercontent.com/u/5121148?v=4", "profile": "https://www.webiny.com", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "12vanblart", "name": "Tyler VanBlargan", "avatar_url": "https://avatars2.githubusercontent.com/u/16465776?v=4", "profile": "https://tyler.vanblargan.dev", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "llwp", "name": "Adam John", "avatar_url": "https://avatars2.githubusercontent.com/u/9566542?v=4", "profile": "https://github.com/llwp", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "dorelljames", "name": "Dorell James", "avatar_url": "https://avatars3.githubusercontent.com/u/977413?v=4", "profile": "https://dorelljames.com", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "danruss", "name": "danruss", "avatar_url": "https://avatars2.githubusercontent.com/u/815250?v=4", "profile": "https://github.com/danruss", - "contributions": [ - "doc" - ] + "contributions": ["doc"] } ], "contributorsPerLine": 7 diff --git a/.eslintrc.js b/.eslintrc.js index 08717161d..54e726e89 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,10 @@ module.exports = { - extends: 'react-app', - rules: { - 'react/react-in-jsx-scope': 'off', - 'jsx-a11y/anchor-is-valid': 'off', - 'no-unused-vars': 'off', - 'react/jsx-pascal-case': 'off', - 'jsx-a11y/accessible-emoji': 'off', - }, -} + extends: "react-app", + rules: { + "react/react-in-jsx-scope": "off", + "jsx-a11y/anchor-is-valid": "off", + "no-unused-vars": "off", + "react/jsx-pascal-case": "off", + "jsx-a11y/accessible-emoji": "off" + } +}; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2b556909d..1c2a77eb2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,25 @@ ## Short Description + ## Relevant Links + + - [A change on X page](#) - [Update list of libraries](#) - [A new diagram with updated resources](#) ## Checklist + - [ ] I added page metadata (description, keywords) - [ ] I've added "Can I Use This?" section (if needed, e.g. if documenting a new feature) - [ ] I added `What You'll Learn` at the top of the page and every item in the list starts with a lower case letter - [ ] I used title case for titles and subtitles (in the main menu and in the page content) - [ ] I checked for typos and grammar mistakes - [ ] I added `alt` / `title` attributes for inserted images (if any) -- [ ] When linking code from GitHub, I did it via a specific tag (and not `next` / `v5` branch) +- [ ] When linking code from GitHub, I did it via a specific tag (and not `next` / `v5` branch) ## Screenshots (if relevant): + N/A diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..ee9cf7588 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,54 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "trailingComma": "none", + "tabWidth": 2, + "arrowParens": "avoid", + "endOfLine": "lf", + "useTabs": false, + "semi": true, + "singleQuote": false, + "jsxSingleQuote": false, + "bracketSpacing": true, + "bracketSameLine": false, + "proseWrap": "preserve", + "htmlWhitespaceSensitivity": "css", + "vueIndentScriptAndStyle": false, + "singleAttributePerLine": false, + "embeddedLanguageFormatting": "auto", + "quoteProps": "as-needed", + "requirePragma": false, + "insertPragma": false, + "rangeStart": 0, + "rangeEnd": null, + "sortPackageJson": false, + "ignorePatterns": [ + ".nx/", + ".webiny/", + ".verdaccio/", + "docs/", + "**/build/", + "**/dist/", + "**/.out/", + "**/.pulumi/", + ".webiny/**", + "packages/admin-ui/scripts/importFromFigma/exports/Alias tokens.json", + "packages/project-aws/_templates/appTemplates/", + "lerna.json", + "coverage/**", + "packages/cli/files/**/*.json", + "packages/create-webiny-project/utils/binaries/**", + ".github/workflows/**/*.yml", + "./nextjs/", + "packages/admin-ui/storybook-static/**", + "**/*.hbs" + ], + "overrides": [ + { + "files": ["*.js", "*.ts", "*.tsx"], + "options": { + "tabWidth": 4 + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md index 9bad132dd..fdede7515 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,15 @@ - All script execution: `yarn ` - All package operations: `yarn add`, `yarn install`, etc. +### Release Notes / Changelog Editing + +When editing a generated `changelog.mdx` file, always keep the sibling `changelog.ai.txt` in sync — without being asked: + +- **Removed entry** → add the PR number(s) to the `## Skipped PRs` section with a short reason +- **Rewritten entry** → add a note to the `## Manual Rewrites` section + +The `generate-changelog.ts` script reads `## Skipped PRs` to avoid re-adding manually removed entries on the next run. If `changelog.ai.txt` is not updated, removed PRs will reappear. + ### Validation and Quality - **MDX/.ai.txt Pairing**: Every `.mdx` file must have a corresponding `.ai.txt` companion file @@ -33,7 +42,7 @@ ``` docs/developer-docs/6.0.x/ ├── admin/ # Admin area customization (whitelabeling) -├── webiny-api/ # API customization (custom domain, extend GraphQL schema, universal API keys) +├── webiny-api/ # API customization (custom domain, extend GraphQL schema, custom routes, universal API keys) ├── cli/ # CLI commands reference (deploy, destroy, watch, etc.) ├── core-concepts/ # Foundational knowledge: architecture, applications, project structure, DI, Result pattern, multi-tenancy, local dev, env vars ├── get-started/ # Welcome, installation, upgrade to Business @@ -47,7 +56,7 @@ docs/developer-docs/6.0.x/ ├── infrastructure/ # Cloud infrastructure (database setups, deployment modes, diagrams) ├── overview/ # Pricing, security features │ └── features/ -├── reference/ # Auto-generated API reference (extensions, admin, API) +├── reference/ # API reference (webiny.config.tsx extensions, SDK docs) ├── tasks/ # Background task system (about, reference, management) ├── security/ # Security (universal API keys, programmatic roles and teams) ├── tenant-manager/ # Multi-tenancy management (GraphQL API, model extension) diff --git a/CLAUDE.md b/CLAUDE.md index 80cca7430..eb635989b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,4 @@ ## Startup + - Always read `~/.claude/settings.json` at the start of every conversation. - Read `./AGENTS.md" diff --git a/announcements/6.1.0.txt b/announcements/6.1.0.txt deleted file mode 100644 index 520454935..000000000 --- a/announcements/6.1.0.txt +++ /dev/null @@ -1,17 +0,0 @@ -Hello @channel! :wave: - -Webiny 6.1.0 is out! :rocket: - -This is a feature-packed release for Website Builder! You can now recover deleted pages thanks to the new trash bin, schedule pages to publish or unpublish at specific times, and customize element overlays with your own actions. The editor also gained a readonly mode, which pairs nicely with the Workflows app for review scenarios. - -On the Headless CMS side, we squashed an annoying bug where DynamicZone and Object fields would collapse after saving. ACO folders are now properly accessible via API tokens too — no more unexpected authorization errors when working programmatically. - -For developers, we've extracted MCP into a standalone `webiny-mcp` binary that works in any project, added new architect skills for better AI-assisted code generation, and introduced a handy `webiny upgrade` command to simplify version upgrades. Check out the changelog to learn more. -— - -As usual, the release notes can be found on the following links:point_down::skin-tone-2:: -Changelog: https://www.webiny.com/docs/release-notes/6.1.0/changelog -How To Upgrade: https://www.webiny.com/docs/release-notes/6.1.0/upgrade-guide -— - -Lots of goodies in this one — enjoy building! :tada: diff --git a/docs.config.ts b/docs.config.ts index 26569e2ff..552eee77d 100644 --- a/docs.config.ts +++ b/docs.config.ts @@ -1,14 +1,14 @@ import fs from "fs-extra"; import path from "path"; import { - Version, - MdxData, - NonVersionedDocumentRootConfig, - VersionedDocumentRootConfig, - LinkValidator, - MdxFileFilter, - VersionsProvider, - NonVersionedVariableProcessor + Version, + MdxData, + NonVersionedDocumentRootConfig, + VersionedDocumentRootConfig, + LinkValidator, + MdxFileFilter, + VersionsProvider, + NonVersionedVariableProcessor } from "@webiny/docs-generator"; import { DeveloperDocsMdxFile } from "./docs/developer-docs/DeveloperDocsMdxFile"; import { HandbookMdxFile } from "./docs/handbook/HandbookMdxFile"; @@ -42,134 +42,146 @@ const whitelistedVersions: string[] = []; const minVersionToBuild = process.env.MIN_VERSION || ""; const filterByEnvironment = (version: Version) => { - // In `preview`, if there are specific versions whitelisted for deployment, those are the only ones we'll output. - if (preview && whitelistedVersions.length > 0) { - return whitelistedVersions.includes(version.getValue()); - } - - // If minVersionToBuild is set, only build versions >= minVersion or `latest`. - if (minVersionToBuild) { - if (minVersionToBuild === "latest") { - return version.isLatest(); + // In `preview`, if there are specific versions whitelisted for deployment, those are the only ones we'll output. + if (preview && whitelistedVersions.length > 0) { + return whitelistedVersions.includes(version.getValue()); } - const versionNum = parseFloat(version.getValue().replace(/[^\d.]/g, "")); - const minVersionNum = parseFloat(minVersionToBuild.replace(/[^\d.]/g, "")); - return versionNum >= minVersionNum; - } - // Build everything. - return true; + // If minVersionToBuild is set, only build versions >= minVersion or `latest`. + if (minVersionToBuild) { + if (minVersionToBuild === "latest") { + return version.isLatest(); + } + const versionNum = parseFloat(version.getValue().replace(/[^\d.]/g, "")); + const minVersionNum = parseFloat(minVersionToBuild.replace(/[^\d.]/g, "")); + return versionNum >= minVersionNum; + } + + // Build everything. + return true; }; const filterFilePathByVersion = (filePath: string): boolean => { - // Extract version from file path (e.g., /docs/developer-docs/5.40.x/... or /docs/user-guides/5.40.x/...) - const versionMatch = filePath.match(/\/(\d+(?:\.\d+)?\.x)\//); + // Extract version from file path (e.g., /docs/developer-docs/5.40.x/... or /docs/user-guides/5.40.x/...) + const versionMatch = filePath.match(/\/(\d+(?:\.\d+)?\.x)\//); - if (!versionMatch) { - // If no version in path, include the file (e.g., non-versioned docs) - return true; - } + if (!versionMatch) { + // If no version in path, include the file (e.g., non-versioned docs) + return true; + } - const versionString = versionMatch[1]; + const versionString = versionMatch[1]; - // Use the same filtering logic as filterByEnvironment - if (preview && whitelistedVersions.length > 0) { - return whitelistedVersions.includes(versionString); - } + // Use the same filtering logic as filterByEnvironment + if (preview && whitelistedVersions.length > 0) { + return whitelistedVersions.includes(versionString); + } - if (minVersionToBuild) { - if (minVersionToBuild === "latest") { - // For file paths, we can't determine if it's "latest" without more context - // So we'll include all versioned files when minVersionToBuild is "latest" - return true; + if (minVersionToBuild) { + if (minVersionToBuild === "latest") { + // For file paths, we can't determine if it's "latest" without more context + // So we'll include all versioned files when minVersionToBuild is "latest" + return true; + } + const versionNum = parseFloat(versionString.replace(/[^\d.]/g, "")); + const minVersionNum = parseFloat(minVersionToBuild.replace(/[^\d.]/g, "")); + return versionNum >= minVersionNum; } - const versionNum = parseFloat(versionString.replace(/[^\d.]/g, "")); - const minVersionNum = parseFloat(minVersionToBuild.replace(/[^\d.]/g, "")); - return versionNum >= minVersionNum; - } - return true; + return true; }; const existsInDocs = (link: string) => { - // With basePath: "/docs", generated pages are at src/pages/{slug}.js (no /docs/ prefix in filesystem). - // Links from MDX source files may still contain /docs/ prefix (absolute links). - // Strip it before checking the filesystem. - const stripped = link.startsWith("/docs/") ? link.slice("/docs".length) : link; - return fs.pathExists(path.join(__dirname, `src/pages${stripped}.js`)); + // With basePath: "/docs", generated pages are at src/pages/{slug}.js (no /docs/ prefix in filesystem). + // Links from MDX source files may still contain /docs/ prefix (absolute links). + // Strip it before checking the filesystem. + const stripped = link.startsWith("/docs/") ? link.slice("/docs".length) : link; + return fs.pathExists(path.join(__dirname, `src/pages${stripped}.js`)); }; export default { - projectRootDir: __dirname, - cleanOutputDir: [ - // Clean the old src/pages/docs directory (pre-basePath migration) and any - // generated directories that the generator creates at the src/pages root. - // Next.js internal files (_app.js, _document.js, _error.js) are not in subdirectories. - path.resolve("src/pages/docs"), - ...["admin", "build-with-ai", "cli", "core-concepts", "get-started", - "handbook", "infrastructure", "overview", "reference", "release-notes", - "user-guides", "website-builder" - ].map(dir => path.resolve(`src/pages/${dir}`)), - path.resolve("public/docs-static/raw") - ], - sitemapOutputPath: path.resolve("public/algolia/sitemap.xml"), - linkValidator: new LinkValidator( - linkWhitelist, - link => { - return existsInDocs(link); - }, - filterFilePathByVersion - ), - documentRoots: [ - /* Developer Docs */ - new VersionedDocumentRootConfig({ - rootDir: path.resolve("docs/developer-docs"), - linkPrefix: "", - outputDir: path.resolve("src/pages"), - pageLayout: "@/layouts/DocumentationLayout", - mdxFileFactory: (data: MdxData, version: Version) => new DeveloperDocsMdxFile(data, version), - mdxFileOutputFilter: new MdxFileFilter(mdxFile => { - return filterByEnvironment(mdxFile.getVersion()); - }) - }), - - /* User Guides */ - new VersionedDocumentRootConfig({ - rootDir: path.resolve("docs/user-guides"), - linkPrefix: "", - outputDir: path.resolve("src/pages"), - pageLayout: "@/layouts/DocumentationLayout", - mdxFileFactory: (data: MdxData, version: Version) => new UserGuideMdxFile(data, version), - mdxFileOutputFilter: new MdxFileFilter(mdxFile => { - return filterByEnvironment(mdxFile.getVersion()); - }), - versionsProvider: new UserGuidesVersionProvider( - path.resolve("docs/developer-docs"), - path.resolve("docs/user-guides") - ) - }), - - /* Release Notes */ - new NonVersionedDocumentRootConfig({ - rootDir: path.resolve("docs/release-notes"), - linkPrefix: "", - outputDir: path.resolve("src/pages"), - pageLayout: "@/layouts/ReleaseNotesLayout", - mdxFileFactory: (data: MdxData) => new ReleaseNotesMdxFile(data), - mdxFileProcessors: [ - new NonVersionedVariableProcessor( - new VersionsProvider(path.resolve("docs/developer-docs")).getVersions() - ) - ] - }), - - /* Handbook */ - new NonVersionedDocumentRootConfig({ - rootDir: path.resolve("docs/handbook"), - linkPrefix: "/handbook", - outputDir: path.resolve("src/pages"), - pageLayout: "@/layouts/HandbookLayout", - mdxFileFactory: (data: MdxData) => new HandbookMdxFile(data) - }) - ] + projectRootDir: __dirname, + cleanOutputDir: [ + // Clean the old src/pages/docs directory (pre-basePath migration) and any + // generated directories that the generator creates at the src/pages root. + // Next.js internal files (_app.js, _document.js, _error.js) are not in subdirectories. + path.resolve("src/pages/docs"), + ...[ + "admin", + "build-with-ai", + "cli", + "core-concepts", + "get-started", + "handbook", + "infrastructure", + "overview", + "reference", + "release-notes", + "user-guides", + "website-builder" + ].map(dir => path.resolve(`src/pages/${dir}`)), + path.resolve("public/docs-static/raw") + ], + sitemapOutputPath: path.resolve("public/algolia/sitemap.xml"), + linkValidator: new LinkValidator( + linkWhitelist, + link => { + return existsInDocs(link); + }, + filterFilePathByVersion + ), + documentRoots: [ + /* Developer Docs */ + new VersionedDocumentRootConfig({ + rootDir: path.resolve("docs/developer-docs"), + linkPrefix: "", + outputDir: path.resolve("src/pages"), + pageLayout: "@/layouts/DocumentationLayout", + mdxFileFactory: (data: MdxData, version: Version) => + new DeveloperDocsMdxFile(data, version), + mdxFileOutputFilter: new MdxFileFilter(mdxFile => { + return filterByEnvironment(mdxFile.getVersion()); + }) + }), + + /* User Guides */ + new VersionedDocumentRootConfig({ + rootDir: path.resolve("docs/user-guides"), + linkPrefix: "", + outputDir: path.resolve("src/pages"), + pageLayout: "@/layouts/DocumentationLayout", + mdxFileFactory: (data: MdxData, version: Version) => + new UserGuideMdxFile(data, version), + mdxFileOutputFilter: new MdxFileFilter(mdxFile => { + return filterByEnvironment(mdxFile.getVersion()); + }), + versionsProvider: new UserGuidesVersionProvider( + path.resolve("docs/developer-docs"), + path.resolve("docs/user-guides") + ) + }), + + /* Release Notes */ + new NonVersionedDocumentRootConfig({ + rootDir: path.resolve("docs/release-notes"), + linkPrefix: "", + outputDir: path.resolve("src/pages"), + pageLayout: "@/layouts/ReleaseNotesLayout", + mdxFileFactory: (data: MdxData) => new ReleaseNotesMdxFile(data), + mdxFileProcessors: [ + new NonVersionedVariableProcessor( + new VersionsProvider(path.resolve("docs/developer-docs")).getVersions() + ) + ] + }), + + /* Handbook */ + new NonVersionedDocumentRootConfig({ + rootDir: path.resolve("docs/handbook"), + linkPrefix: "/handbook", + outputDir: path.resolve("src/pages"), + pageLayout: "@/layouts/HandbookLayout", + mdxFileFactory: (data: MdxData) => new HandbookMdxFile(data) + }) + ] }; diff --git a/docs/developer-docs/5.x/admin-area/basics/framework.mdx b/docs/developer-docs/5.x/admin-area/basics/framework.mdx index 7e2231b09..d9b70741b 100644 --- a/docs/developer-docs/5.x/admin-area/basics/framework.mdx +++ b/docs/developer-docs/5.x/admin-area/basics/framework.mdx @@ -120,7 +120,7 @@ export const App = () => { - We highly recommend using our [Extensions](/{version}/core-development-concepts/basics/extensions) to organize your code in a scalable and portable manner. +We highly recommend using our [Extensions](/{version}/core-development-concepts/basics/extensions) to organize your code in a scalable and portable manner. diff --git a/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx b/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx index 67cfd34bf..f6b68952e 100644 --- a/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx +++ b/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx @@ -7,8 +7,8 @@ description: Learn about environment-related utility functions available within import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; - - what are environment-related utility functions - - what environment-related utility functions are available + - what are environment-related utility functions - what environment-related utility functions are + available ## Overview @@ -17,7 +17,7 @@ This article covers a couple of environment-related utility functions that can b - To learn more about extensions in general, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. +To learn more about extensions in general, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -32,7 +32,7 @@ import { getHeadlessCmsGqlApiUrl, getLocaleCode, getTenantId, - isLocalhost, + isLocalhost } from "@webiny/app-admin"; // Use `@webiny/app` for versions 5.41.3 or older. // Returns URL of Webiny's backend API. @@ -56,6 +56,6 @@ isLocalhost(); // true - For versions 5.41.3 or older, instead of the `@webiny/app-admin` package, please use the `@webiny/app` package. +For versions 5.41.3 or older, instead of the `@webiny/app-admin` package, please use the `@webiny/app` package. diff --git a/docs/developer-docs/5.x/architecture/api/overview.mdx b/docs/developer-docs/5.x/architecture/api/overview.mdx index 6c8aab3a1..f0824d9b6 100644 --- a/docs/developer-docs/5.x/architecture/api/overview.mdx +++ b/docs/developer-docs/5.x/architecture/api/overview.mdx @@ -100,9 +100,9 @@ Learn more about the Headless CMS API in the [Headless CMS GraphQL API Overview] For background tasks (jobs)-related requirements, Webiny relies on [AWS Step Functions](https://aws.amazon.com/step-functions/) and AWS Lambda. The Step Functions are used to orchestrate the execution of the background tasks, while a single AWS Lambda function is used to execute the tasks themselves. -Note that background tasks are triggered via an Amazon EventBridge event. Amazon EventBridge is deployed as part of the **Core** project application (not shown on the diagram). +Note that background tasks are triggered via an Amazon EventBridge event. Amazon EventBridge is deployed as part of the **Core** project application (not shown on the diagram). -For more information on how to create and execute background tasks, check out the [Background Tasks](/{version}/core-development-concepts/background-tasks/about-background-tasks) article. +For more information on how to create and execute background tasks, check out the [Background Tasks](/{version}/core-development-concepts/background-tasks/about-background-tasks) article. ## Additional Information diff --git a/docs/developer-docs/5.x/architecture/introduction.mdx b/docs/developer-docs/5.x/architecture/introduction.mdx index e7944e7ed..37a1b37f5 100644 --- a/docs/developer-docs/5.x/architecture/introduction.mdx +++ b/docs/developer-docs/5.x/architecture/introduction.mdx @@ -59,12 +59,12 @@ By default, the development mode is used when deploying into any environment, ex - The **Admin Area** and **Website** project applications do not posses the ability to be deployed in development and production deployment modes, as it's simply not needed. +The **Admin Area** and **Website** project applications do not posses the ability to be deployed in development and production deployment modes, as it's simply not needed. - If needed, Webiny can also be deployed into a preexisting [Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/). For more information, please refer to the [Use Existing Amazon VPC](/{version}/enterprise/use-existing-amazon-vpc) guide. +If needed, Webiny can also be deployed into a preexisting [Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/). For more information, please refer to the [Use Existing Amazon VPC](/{version}/enterprise/use-existing-amazon-vpc) guide. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx index 64b7083f6..8fbc7f616 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx @@ -4,9 +4,9 @@ title: About Background Tasks description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -27,12 +27,13 @@ The Background Task run limit is `1 year`, from the time when the task is trigge This functionality uses the `AWS Event Bridge`, `AWS Step Function` and `AWS Lambda`. ## AWS Services Used in the Background Tasks + To find out more about these services, please visit the following links: + - [`AWS Event Bridge`](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html) - [`AWS Step Function`](https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html) - [`AWS Lambda`](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) - Currently, Background Tasks are not using the Task Token, so they are not resumable. We will be working on the solution for this in the future. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx index 484d79c73..9489d6dc4 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx @@ -4,12 +4,11 @@ title: Background Task Management description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - - + @@ -18,7 +17,6 @@ import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - ## GraphQL API The Background Tasks do not have a section in the Admin UI yet, so they are accessible only via the GraphQL API. @@ -50,6 +48,7 @@ query ListTaskDefinitions { ``` #### List All Background Task Runs + ```graphql query ListTasks { backgroundTasks { @@ -85,10 +84,12 @@ query ListTasks { ```graphql query ListBackgroundTaskLogs { backgroundTasks { - listLogs(where: { - # you can list logs from a certain task if you like - task: "yourTaskId" - }) { + listLogs( + where: { + # you can list logs from a certain task if you like + task: "yourTaskId" + } + ) { data { id createdOn @@ -108,15 +109,19 @@ query ListBackgroundTaskLogs { ``` ### Mutations + #### Trigger a Background Task ```graphql mutation TriggerATask { backgroundTasks { - triggerTask(definition: testingRun, input: { - someVariableForTestingRunTaskToReceive: "someValue", - yetAnotherVariableForTestingRunTaskToReceive: "anotherValue" - }) { + triggerTask( + definition: testingRun + input: { + someVariableForTestingRunTaskToReceive: "someValue" + yetAnotherVariableForTestingRunTaskToReceive: "anotherValue" + } + ) { data { id definitionId @@ -186,11 +191,11 @@ const results = await context.tasks.listDefinitions(); ```typescript const results = await context.tasks.listTasks({ - // all properties are optional - where: {}, - sort: [], - limit: 100, - after: null + // all properties are optional + where: {}, + sort: [], + limit: 100, + after: null }); ``` @@ -198,13 +203,13 @@ const results = await context.tasks.listTasks({ ```typescript const result = await context.tasks.trigger({ - definition: "myTaskDefinition", - // optional input for the task run - input: { - variableToPassAsInput: "someValue" - }, - // optional name for the task run - name: "My Custom Task Name" + definition: "myTaskDefinition", + // optional input for the task run + input: { + variableToPassAsInput: "someValue" + }, + // optional name for the task run + name: "My Custom Task Name" }); ``` @@ -212,9 +217,9 @@ const result = await context.tasks.trigger({ ```typescript const result = await context.tasks.trigger({ - id: "yourTaskId", - // optional message - message: "My Reason for aborting the task" + id: "yourTaskId", + // optional message + message: "My Reason for aborting the task" }); ``` @@ -222,10 +227,10 @@ const result = await context.tasks.trigger({ ```typescript const results = await context.tasks.listLogs({ - // all properties are optional - where: {}, - sort: [], - limit: 100, - after: null + // all properties are optional + where: {}, + sort: [], + limit: 100, + after: null }); ``` diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx index 3c2bc043e..9877b831c 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx @@ -25,8 +25,8 @@ The following is a list of background tasks that are included in Webiny by defau - This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. - +This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. + The **Reindexing Task** is a background task that scans through the DynamoDB Elasticsearch table and pushes the data into the specific Elasticsearch/OpenSearch index. @@ -82,7 +82,7 @@ This is done to reduce the strain on the Elasticsearch/OpenSearch cluster as the - This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. +This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx index 7a0b2aa90..c31f99413 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx @@ -4,9 +4,9 @@ title: How Background Tasks Work? description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; import backgroundTaskStepFunction from "./assets/bg-task-step-function.png"; @@ -19,7 +19,6 @@ import backgroundTaskStepFunction from "./assets/bg-task-step-function.png"; - ## Overview A Background Task lifecycle is as follows: diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx index d7c25e3d1..1285b0720 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx @@ -4,9 +4,9 @@ title: How to Define a Background Task description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -18,94 +18,96 @@ import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - ## Overview First, you need to know that there are two types of Background Tasks: + - public - can be called via GraphQL API or the programmatic API - private - can be called only through the programmatic API Definition of the task is the same for both types of tasks, with a difference of the method name used to define the task. - ## Basic Definition To see all available properties, and information about the properties, for the task definition, check the [`ITaskDefinition`](https://github.com/webiny/webiny-js/blob/abb442c6d67c97980b8053c5e53db7fb4fec88b4/packages/tasks/src/types.ts#L328) interface. There are few properties, which should not be used: - - `fields` - not implemented yet - - `isPrivate` - set automatically when a user defines a task via `createPrivateTaskDefinition` + +- `fields` - not implemented yet +- `isPrivate` - set automatically when a user defines a task via `createPrivateTaskDefinition` ### Public Task + ```typescript import { createTaskDefinition } from "@webiny/tasks"; const myPublicTaskDefinition = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - // your code here - return response.done(); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + // your code here + return response.done(); + } }); ``` ### Private Task + ```typescript import { createPrivateTaskDefinition } from "@webiny/tasks"; const myPrivateTaskDefinition = createPrivateTaskDefinition({ - id: "myPrivateTask", - title: "A Task Accessible Only Via The Code", - async run({response}) { - // your code here - return response.done(); - } + id: "myPrivateTask", + title: "A Task Accessible Only Via The Code", + async run({ response }) { + // your code here + return response.done(); + } }); ``` ## Advanced Definition - ```typescript const syncArticles = createTaskDefinition({ - id: "syncArticles", - title: "Sync Articles", - description: "A task which syncs Webiny articles with another system.", - // optional when defined via the createTaskDefinition or createPrivateTaskDefinition method - // default value is 500 - maxIterations: 1000, - async run({response}) { - // your code which syncs articles with another system - return response.done(); - }, - onBeforeTrigger: async({context, input}) => { - // check if articles are already syncing - // if yes, throw an error - }, - onDone: async({context, task}) => { - // notify another system that articles are synced - }, - onError: async({context, task}) => { - // notify another system that articles are not synced due to an error - }, - onAbort: async({context, task}) => { - // notify another system that articles are not synced due to user aborting the task - }, - onMaxIterations: async({context, task}) => { - // notify another system that articles are not synced due to reaching max iterations of the task - }, + id: "syncArticles", + title: "Sync Articles", + description: "A task which syncs Webiny articles with another system.", + // optional when defined via the createTaskDefinition or createPrivateTaskDefinition method + // default value is 500 + maxIterations: 1000, + async run({ response }) { + // your code which syncs articles with another system + return response.done(); + }, + onBeforeTrigger: async ({ context, input }) => { + // check if articles are already syncing + // if yes, throw an error + }, + onDone: async ({ context, task }) => { + // notify another system that articles are synced + }, + onError: async ({ context, task }) => { + // notify another system that articles are not synced due to an error + }, + onAbort: async ({ context, task }) => { + // notify another system that articles are not synced due to user aborting the task + }, + onMaxIterations: async ({ context, task }) => { + // notify another system that articles are not synced due to reaching max iterations of the task + } }); ``` ## Registering the Task Of course, as all other Webiny plugins, users must register the task definition in the `plugins` array of the `createHandler` function. + ```typescript export const handler = createHandler({ - plugins: [ - // ...rest of plugins - syncArticles - ] + plugins: [ + // ...rest of plugins + syncArticles + ] }); ``` diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx index a517285fa..5e1b748f2 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx @@ -4,9 +4,9 @@ title: How to Handle a Background Task description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -36,17 +36,18 @@ Via the `context` object, you can access whole Webiny system. The base interface of the `context` object is [Context](https://github.com/webiny/webiny-js/blob/abb442c6d67c97980b8053c5e53db7fb4fec88b4/packages/tasks/src/types.ts#L261), but you can pass your own, which must extend the base interface. You can pass your own Ge interface/type of the `context` variable when defining the task: + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; -import {MyCustomContext} from "./types"; +import { createTaskDefinition } from "@webiny/tasks"; +import { MyCustomContext } from "./types"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({context}) { - // context is of type MyCustomContext - await context.yourCustomMethod(); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ context }) { + // context is of type MyCustomContext + await context.yourCustomMethod(); + } }); ``` @@ -55,21 +56,22 @@ const myTask = createTaskDefinition({ The `input` object is the input which was sent to the task when it was triggered. By default, it is of plain object type `Record`, but you can pass your own type/interface when defining the task: + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; -import {MyCustomContext} from "./types"; +import { createTaskDefinition } from "@webiny/tasks"; +import { MyCustomContext } from "./types"; interface MyCustomInput { - myCustomProperty: string; + myCustomProperty: string; } const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({input}) { - // input is of type MyCustomInput - const {myCustomProperty} = input; - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ input }) { + // input is of type MyCustomInput + const { myCustomProperty } = input; + } }); ``` @@ -77,6 +79,7 @@ const myTask = createTaskDefinition({ The `response` object handles the output from the task run. Available methods are: + - done - continue - error @@ -86,23 +89,24 @@ Available methods are: This method signalizes that the task has finished its job and that the Step Function will finish as well. It also signalizes that task status should be set to `success`. Optional message will be stored as well. + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - return response.done("Optional message about the task getting done."); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + return response.done("Optional message about the task getting done."); + } }); ``` Interface for the response object is defined [here](https://github.com/webiny/webiny-js/blob/84d28b3e100a0317a0c3d265d06efaea50971cd3/packages/tasks/src/response/abstractions/TaskResponse.ts#L62). + - `message` - optional message you want to store when the task is done - `output` - optional output object you want to store when the task is done - #### The `continue` Method This method signalizes that the task has not finished its job and that the Step Function should continue running the Lambda handler. @@ -115,50 +119,54 @@ The `continue` method accepts a second, optional, parameter via which you can se You can either send a `seconds` property, which takes a number, or a `date` property, which takes a `Date` object. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({input, response}) { - return response.continue({ - ...input, - aChangedInputProperty: 1, - }, - // optional options - { - seconds: 30, // wait 30 seconds before the next task run - date: new Date("2024-02-25T00:00:00Z") // wait until the specified date before the next task run - }); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ input, response }) { + return response.continue( + { + ...input, + aChangedInputProperty: 1 + }, + // optional options + { + seconds: 30, // wait 30 seconds before the next task run + date: new Date("2024-02-25T00:00:00Z") // wait until the specified date before the next task run + } + ); + } }); ``` - If you are setting waiting time for the `continue` method, note that the maximum waiting time is 355 days, which is almost the maximum life time for the AWS Step Function. + If you are setting waiting time for the `continue` method, note that the maximum waiting time is + 355 days, which is almost the maximum life time for the AWS Step Function. #### The `error` Method This method signalizes that the task has finished its job with an error and that the Step Function will finish as well, in an error state. It also signalizes that task status should be set to `error`. + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - return response.error({ - message: "Error message", - code: "ERROR_CODE", - data: { - // optional data - } - }); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + return response.error({ + message: "Error message", + code: "ERROR_CODE", + data: { + // optional data + } + }); + } }); -```` +``` #### The `abort` Method @@ -168,26 +176,26 @@ When you write your code, you must check if the task was aborted via the `isAbor This is meant to be used while the task has some code which loops continuously, like reading a lot of records from the database, with paginating through the records. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isAborted, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse | null = null; - while ((result = await listFromDb(dbReadParams))) { - if (isAborted()) { - return response.abort(); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isAborted, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse | null = null; + while ((result = await listFromDb(dbReadParams))) { + if (isAborted()) { + return response.abort(); + } + // continue with the loop } + return response.done(); + } }); -```` +``` ### The `isCloseToTimeout` Method @@ -195,33 +203,33 @@ The `isCloseToTimeout` method is a method which returns a boolean value, which t This is useful when you have a lot of code to run, or you have some idea that the code will take some time to execute, and you want to check if you have enough time to finish the code execution. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; interface MyInput { - after?: null | undefined; + after?: null | undefined; } const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isCloseToTimeout, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse; - while ((result = await listFromDb(dbReadParams))) { - // do your magic... - - // assign the cursor to the after property of the dbReadParams - dbReadParams.after = result.cursor; - if (isCloseToTimeout()) { - // on next iteration, we want to continue with new dbReadParams - return response.continue(dbReadParams); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isCloseToTimeout, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse; + while ((result = await listFromDb(dbReadParams))) { + // do your magic... + + // assign the cursor to the after property of the dbReadParams + dbReadParams.after = result.cursor; + if (isCloseToTimeout()) { + // on next iteration, we want to continue with new dbReadParams + return response.continue(dbReadParams); + } + // continue with the loop } + return response.done(); + } }); ``` @@ -229,9 +237,9 @@ The `isCloseToTimeout` method accepts an optional parameter, which is a number o - As a developer, you are responsible for checking if the task is close to the timeout, and for handling the task run accordingly. +As a developer, you are responsible for checking if the task is close to the timeout, and for handling the task run accordingly. - Webiny provides you with the background task mechanism, but you must handle the task run, and timeouts, yourself. +Webiny provides you with the background task mechanism, but you must handle the task run, and timeouts, yourself. @@ -240,26 +248,26 @@ The `isCloseToTimeout` method accepts an optional parameter, which is a number o The `isAborted` method is a method which returns a boolean value, which tells you if the task was aborted by the user. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isAborted, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse | null = null; - while ((result = await listFromDb(dbReadParams))) { - if (isAborted()) { - return response.abort(); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isAborted, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse | null = null; + while ((result = await listFromDb(dbReadParams))) { + if (isAborted()) { + return response.abort(); + } + // continue with the loop } + return response.done(); + } }); -```` +``` ### The `store` Object @@ -268,6 +276,7 @@ The `store` object is of `ITaskManagerStore` interface type, which is defined [h It is used for storing and retrieving data from the task run, including task logs. Available methods on the object are: + - `getTask` - `getStatus` - `updateTask` @@ -286,10 +295,10 @@ It means that each of the execution iteration gets its own record, which contain - Do not use the `addInfoLog` and `addErrorLog` methods for a lot of logging, or logging large amounts of data. - - The logs are stored in the database as you send them, and if you send a lot of logs, or large logs, you will hammer the database. - - Use the built-in logs only for important information, like starting or finishing a part of a task. +Do not use the `addInfoLog` and `addErrorLog` methods for a lot of logging, or logging large amounts of data. + +The logs are stored in the database as you send them, and if you send a lot of logs, or large logs, you will hammer the database. + +Use the built-in logs only for important information, like starting or finishing a part of a task. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx index 5a0d20b06..2c3f92728 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx @@ -4,9 +4,9 @@ title: What to be careful about? description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -43,7 +43,6 @@ Default limit: `500`. To change the limit, see [`maxIterations`](#advanced-definition) parameter when defining the Background Task. - Currently, Background Tasks are not using the Task Token, so they are not resumable. We will be working on the solution for this in the future. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx index 80022f603..6d8d7e1df 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx @@ -171,7 +171,7 @@ To manage how long deleted entries are retained within Webiny, developers can co For example, setting `WEBINY_TRASH_BIN_RETENTION_PERIOD_DAYS=7` will preserve deleted entries for 7 days. -Adjusting this variable allows developers to customize the retention period according to their needs. +Adjusting this variable allows developers to customize the retention period according to their needs. Setting `WEBINY_TRASH_BIN_RETENTION_PERIOD_DAYS=0` will apply the default retention period of 90 days. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx index 915cf7463..ffa51761d 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx @@ -9,7 +9,7 @@ import webinyExtensionCommand from "./assets/webiny-extension-command.png"; - - what extensions are and how they can be used to extend Webiny's functionality. +- what extensions are and how they can be used to extend Webiny's functionality. @@ -41,7 +41,7 @@ Once all the questions are answered, the command creates the base code for the n - There are a couple of cases where the extension code is placed outside of the `extensions` folder. For example, when [modifying cloud infrastructure](/{version}/infrastructure/basics/modify-cloud-infrastructure), the code is placed in different `webiny.application.ts` files, located in the `apps` folder. +There are a couple of cases where the extension code is placed outside of the `extensions` folder. For example, when [modifying cloud infrastructure](/{version}/infrastructure/basics/modify-cloud-infrastructure), the code is placed in different `webiny.application.ts` files, located in the `apps` folder. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx index c142a9101..7ab18ff9a 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx @@ -4,7 +4,7 @@ title: Logger description: Learn about Logger which we internally use --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -14,7 +14,8 @@ import {Alert} from "@/components/Alert"; - This feature will change in the future. We cannot guarantee the data integrity after the system upgrade (in minor version, eg. 5.42.x to 5.43.0) + This feature will change in the future. We cannot guarantee the data integrity after the system + upgrade (in minor version, eg. 5.42.x to 5.43.0) ## Overview @@ -28,33 +29,34 @@ For Logger to work, we deploy a new DynamoDB table called `webiny-logs`. To use the Logger in your project, you can access it from the Webiny context. There are multiple levels of logging available: - - `debug` - - `notice` - - `info` - - `warn` - - `error` + +- `debug` +- `notice` +- `info` +- `warn` +- `error` When you want to log something, you can use the following code: ```typescript -import type {Context} from "./types"; -import {ContextPlugin} from "@webiny/handler-aws"; +import type { Context } from "./types"; +import { ContextPlugin } from "@webiny/handler-aws"; const myCustomPlugin = new ContextPlugin(async context => { - // some custom code - try { - await someAsyncFunction(); - } catch(ex) { - const log = context.logger.withSource("where-did-the-log-come-from"); - // it will log the message with the source "where-did-the-log-come-from" - // and the level "error" - log.error({ - message: "Something is wrong with my custom code!", - error: ex - }); - // maybe rethrow it? - throw ex; - } + // some custom code + try { + await someAsyncFunction(); + } catch (ex) { + const log = context.logger.withSource("where-did-the-log-come-from"); + // it will log the message with the source "where-did-the-log-come-from" + // and the level "error" + log.error({ + message: "Something is wrong with my custom code!", + error: ex + }); + // maybe rethrow it? + throw ex; + } }); ``` @@ -65,8 +67,9 @@ This is something you can use to filter logs later. ## How to Access Logger Logs? There are two ways to access the logs: - - directly from the DynamoDB table `webiny-logs` - - using the GraphQL API + +- directly from the DynamoDB table `webiny-logs` +- using the GraphQL API ### DynamoDB Table @@ -82,8 +85,8 @@ interface Log { type: string; // this is the data that was logged - it's always compressed data: { - compression: "GZIP", - value: string// compressed value + compression: "GZIP"; + value: string; // compressed value }; } ``` @@ -94,16 +97,15 @@ The data is always compressed using GZIP, so you must decompress it before you c You can also access the logs using the GraphQL API on `/graphql` endpoint. There are multiple queries and mutations available: - - `listLogs` - to list all logs - - `getLog` - to get a single log - - `deleteLog` - to delete a single - - `deleteLogs` - to delete multiple logs - - `pruneLogs` - to delete all logs older than 60 seconds +- `listLogs` - to list all logs +- `getLog` - to get a single log +- `deleteLog` - to delete a single +- `deleteLogs` - to delete multiple logs +- `pruneLogs` - to delete all logs older than 60 seconds - - For detailed information on how to use the GraphQL API, check out the API Playground in your Webiny Admin UI. - + For detailed information on how to use the GraphQL API, check out the API Playground in your + Webiny Admin UI. #### List Logs @@ -114,15 +116,11 @@ To list all logs, you can use the `listLogs` query. Here is an example of the qu query ListLogs { logs { listLogs( - where: { - tenant: "root", - source:"myCustomSource", - type: error - }, - sort: DESC, - limit: 100, - after: "cursorFromPreviousRequest" -) { + where: { tenant: "root", source: "myCustomSource", type: error } + sort: DESC + limit: 100 + after: "cursorFromPreviousRequest" + ) { data { id type @@ -144,6 +142,7 @@ query ListLogs { } } ``` + All arguments in `listTags` query are optional. You can filter the logs by `tenant`, `source` and `type`. Or you can just list all logs, without any filtering applied. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx index 8b007f949..02ceb831d 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx @@ -247,7 +247,7 @@ C:\my-new-project\.webiny\pulumi-cli\win32\pulumi\credentials.json ### Can I deploy Webiny on personal server or platforms like Docker or Digital Ocean? -The short answer is no. +The short answer is no. Webiny cannot be deployed on a personal server or platforms like Docker or Digital Ocean because Webiny is a serverless application, meaning it doesn't need a server for installation. Instead, it's built on top of serverless infrastructure like AWS Lambda and DynamoDB to enable scalability and reliability. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx index 6908071d0..0ab7d09e8 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx @@ -4,10 +4,9 @@ title: Routes and Events description: You will learn about new handlers for routes and events, and how to add new routes and event definitions. --- - -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx index 8cd5152ae..46a4cc7cb 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx @@ -13,7 +13,7 @@ import logos from "./assets/tools-libraries/logos.png"; - + Webiny is an open-source project, and as such, it is also relying on other open-source software and tools, to bring the best possible experience and increase development productivity. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx index 778420d49..3a5a531a3 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx @@ -4,7 +4,7 @@ title: User Project Upgrade description: Learn about how to do a project upgrade --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -20,7 +20,8 @@ As a part of our upgrade process, we provide a way to update your codebase on ea To start the project upgrade use the `yarn webiny upgrade` command. - The upgrade process can only update one minor version at a time. So you can upgrade from 5.40.x to 5.41.x, but not from 5.40.x to 5.42.x. + The upgrade process can only update one minor version at a time. So you can upgrade from 5.40.x to + 5.41.x, but not from 5.40.x to 5.42.x. ## Steps @@ -32,6 +33,7 @@ There might even be some breaking changes, so we need to notify the user about t ### 1. Notify the User about Breaking Changes The first step is to notify the user about breaking changes: + ```text webiny warning: Note that Webiny 5.xx.x introduces potential breaking changes! Before continuing, please review the upgrade guide located at https://webiny.link/upgrade/5.xx.x. @@ -52,7 +54,7 @@ Next step, which will always be executed, is to check if the user project depend This does not mean that we use newer versions of the dependencies than our users do in their projects. We ship Webiny with the dependencies that are working with our code. If it is possible, our users should keep their dependencies in sync with Webiny ones to avoid any unexpected problems. - + ```text diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx index b857a243c..8d5fb35cb 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx @@ -45,7 +45,7 @@ yarn webiny watch website --env dev - Extensions are the primary way to develop on top Webiny and extend it. To learn more, check out the [Extensions](/{version}/core-development-concepts/basics/extensions) article. +Extensions are the primary way to develop on top Webiny and extend it. To learn more, check out the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -56,30 +56,26 @@ yarn webiny watch website --env dev When it comes to frontend development (**Admin** and **Website** project applications), the watch command offers an experience similar to other existing frontend development solutions out there. Once started, the watch command: 1. spins up a local development server that serves your application -2. the application is automatically rebuilt and refreshed in the browser whenever a code change is detected +2. the application is automatically rebuilt and refreshed in the browser whenever a code change is detected - Note that you must have the **API** project application already deployed before watching **Admin** and **Website** project applications. This is because of the fact that these applications depend on Webiny's backend APIs to work as expected. +Note that you must have the **API** project application already deployed before watching **Admin** and **Website** project applications. This is because of the fact that these applications depend on Webiny's backend APIs to work as expected. - ### Backend Development When it comes to backend development (**API** project application), the watch command doesn't spin up a local development server, but it watches for changes and continuously deploys them the cloud (AWS Lambda). This approach sort of emulates the local development server experience, because changes are automatically reflected in the cloud (as soon as they are deployed). - With the 5.41.0 release and with the introduction of the New Watch Command (Local AWS Lambda Development), we've made significant improvements to the way backend development is done. The feature is still in beta, but we encourage you to try it out and provide feedback. - +With the 5.41.0 release and with the introduction of the New Watch Command (Local AWS Lambda Development), we've made significant improvements to the way backend development is done. The feature is still in beta, but we encourage you to try it out and provide feedback. + ## FAQ ### Can I run Webiny fully locally, without deploying it to AWS? -Because Webiny is built on top of AWS and its proprietary services, it's not possible to run Webiny fully locally. At the very least, you need to deploy the **API** project application to AWS, because it's the backbone of the entire system. The **Admin** and **Website** project applications can be run locally, but they depend on the **API** project application to work as expected. - - - +Because Webiny is built on top of AWS and its proprietary services, it's not possible to run Webiny fully locally. At the very least, you need to deploy the **API** project application to AWS, because it's the backbone of the entire system. The **Admin** and **Website** project applications can be run locally, but they depend on the **API** project application to work as expected. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx index 266edbaaf..69e904e8b 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx @@ -10,8 +10,8 @@ import webinyAboutCommand from "./assets/webiny-about-command.png"; - - what is the Webiny CLI - - what are the commonly used commands +- what is the Webiny CLI +- what are the commonly used commands @@ -25,10 +25,11 @@ Additionally, the Webiny CLI is pluggable, meaning you can easily create your ow - For a full list of commands, in your terminal of choice, make sure to run: - ```bash - yarn webiny --help - ``` +For a full list of commands, in your terminal of choice, make sure to run: + +```bash +yarn webiny --help +``` @@ -56,7 +57,7 @@ The `--env` argument is required. - For more hands-on information on the above listed commands, please visit the [Deploy Your Project](/{version}/core-development-concepts/basics/project-deployment) and [Destroy Cloud Infrastructure](/{version}/infrastructure/basics/destroy-cloud-infrastructure) guides. +For more hands-on information on the above listed commands, please visit the [Deploy Your Project](/{version}/core-development-concepts/basics/project-deployment) and [Destroy Cloud Infrastructure](/{version}/infrastructure/basics/destroy-cloud-infrastructure) guides. @@ -66,7 +67,7 @@ Provides a way to execute Pulumi specific commands directly via the Pulumi CLI. - For more information, please visit the [Execute Pulumi Commands](/{version}/infrastructure/pulumi-iac/execute-pulumi-commands) guide. +For more information, please visit the [Execute Pulumi Commands](/{version}/infrastructure/pulumi-iac/execute-pulumi-commands) guide. @@ -78,13 +79,13 @@ Returns Pulumi stack output for the specified project application and environmen Here are the most commonly used development-related commands. -#### `yarn webiny watch APP --env ENV` +#### `yarn webiny watch APP --env ENV` Starts local development session for the specified project application, in the specified environment. - For more information, please visit the [Use Watch Command](/{version}/core-development-concepts/basics/watch-command) guide. +For more information, please visit the [Use Watch Command](/{version}/core-development-concepts/basics/watch-command) guide. @@ -92,7 +93,7 @@ Starts local development session for the specified project application, in the s Builds the specified project application, for the specified environment. -#### `yarn webiny extension` +#### `yarn webiny extension` Makes it easy to start developing new Webiny extensions. To learn more about extensions, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -118,7 +119,7 @@ Completely disables collection of anonymous usage information. - By default, Webiny collects anonymous usage information, which is exclusively used for improving the product and understanding usage patterns. Please take a look at our [Telemetry](https://www.webiny.com/telemetry/) page for more information on this subject. +By default, Webiny collects anonymous usage information, which is exclusively used for improving the product and understanding usage patterns. Please take a look at our [Telemetry](https://www.webiny.com/telemetry/) page for more information on this subject. diff --git a/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx b/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx index d76327c35..f3f23d450 100644 --- a/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx @@ -61,6 +61,7 @@ Static tests are used to check issues in our code, without actually executing it All of the mentioned tools (and some others) are already included and configured for you in every Webiny project. + Usually, these tests are reasonably fast to run, and sometimes, even the fastest. diff --git a/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx b/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx index 989305db4..3382e9aa6 100644 --- a/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx @@ -15,7 +15,7 @@ import { Alert } from "@/components/Alert"; ## Overview -Serverless development has slightly different principles from traditional application development. In the traditional development process, developers typically develop and test their project locally before deploying it to a server. However, in the serverless world, this process is slightly different. +Serverless development has slightly different principles from traditional application development. In the traditional development process, developers typically develop and test their project locally before deploying it to a server. However, in the serverless world, this process is slightly different. In this article, we will look at how to do local development in Webiny, a serverless CMS. If you are absolutely beginner to serverless, we will recommend you to read [this article](https://www.webiny.com/knowledge-base/serverless). @@ -37,7 +37,7 @@ As previously mentioned, for the API and infrastructure related portion of the d Webiny utilizes a variety of AWS serverless services, such as AWS Lambda, Amazon DynamoDB, Amazon S3, etc. -And while simulating Lambda locally is not that hard (although it's still not trivial), it's still hard to emulate cloud native mechanisms, like reacting to Amazon S3 objects-related events, Amazon Event Bridge events, emulating services like Amazon Cognito, Amazon SQS, and more. +And while simulating Lambda locally is not that hard (although it's still not trivial), it's still hard to emulate cloud native mechanisms, like reacting to Amazon S3 objects-related events, Amazon Event Bridge events, emulating services like Amazon Cognito, Amazon SQS, and more. With that in mind, we don't recommend using tools like [LocalStack](https://www.localstack.cloud/). Still, if you find it challenging to work with any part of local development, get in touch with us on our [Community Slack](https://www.webiny.com/slack). It will help us offer some advice or improve the local development experience. @@ -48,7 +48,7 @@ Webiny supports [two database setups](/{version}/architecture/introduction#diffe 1. Amazon DynamoDB 2. Amazon DynamoDB + Amazon OpenSearch -The Amazon DynamoDB database setup fully utilizes serverless services, which means it fully follows pay-per-use pricing. This means that for development, the cost is typically minimal or free. +The Amazon DynamoDB database setup fully utilizes serverless services, which means it fully follows pay-per-use pricing. This means that for development, the cost is typically minimal or free. On the other hand, with the Amazon DynamoDB + Amazon OpenSearch version, [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service/) is the only non-serverless service used by Webiny, which is not priced on a pay-per-use basis. This means that you will be charged for the time the service is running, regardless of whether you are using it or not. diff --git a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx index d657d4b97..21732cdd5 100644 --- a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx @@ -21,7 +21,7 @@ In this article, we explain how to extend Webiny's main GraphQL API. Note that W - To learn more about the Headless CMS GraphQL API, different API types, support for multiple locales, and more, make sure to check out the [Headless CMS GraphQL API](/{version}/headless-cms/basics/graphql-api) key topic. +To learn more about the Headless CMS GraphQL API, different API types, support for multiple locales, and more, make sure to check out the [Headless CMS GraphQL API](/{version}/headless-cms/basics/graphql-api) key topic. @@ -33,14 +33,16 @@ In this article, we explain how to extend Webiny's main GraphQL API. Note that W dependencies={"@webiny/api-serverless-cms"} scaffoldCommandExtraInfo={ <> - In the example below, we'll be using the createGraphQLSchemaPlugin plugin factory from the @webiny/api-serverless-cms package, so we've included it in the list of dependencies. + In the example below, we'll be using the createGraphQLSchemaPlugin plugin factory + from the @webiny/api-serverless-cms package, so we've included it in the list of + dependencies. } /> ## Extending the Main GraphQL API -Let's extend Webiny's main GraphQL API with the new `listBooks` query. +Let's extend Webiny's main GraphQL API with the new `listBooks` query. To do this, we use the `createGraphQLSchemaPlugin` plugin factory from the `@webiny/api-serverless-cms` package: @@ -48,37 +50,37 @@ To do this, we use the `createGraphQLSchemaPlugin` plugin factory from the `@web import { createGraphQLSchemaPlugin } from "@webiny/api-serverless-cms"; export const createExtension = () => { - return [ - createGraphQLSchemaPlugin({ - typeDefs: /* GraphQL */ ` - type Book { - title: String - description: String - } - extend type Query { - # Returns a list of all users. - listBooks: [Book] - } - `, - resolvers: { - Query: { - listBooks: async (_, args, context) => { - // In a real life application, these would be loaded from a database. - const books = [ - { title: "First book", description: "This is the first book." }, - { title: "Second book", description: "This is the second book." } - ]; - - return books; - } - } - } - }) - ]; + return [ + createGraphQLSchemaPlugin({ + typeDefs: /* GraphQL */ ` + type Book { + title: String + description: String + } + extend type Query { + # Returns a list of all users. + listBooks: [Book] + } + `, + resolvers: { + Query: { + listBooks: async (_, args, context) => { + // In a real life application, these would be loaded from a database. + const books = [ + { title: "First book", description: "This is the first book." }, + { title: "Second book", description: "This is the second book." } + ]; + + return books; + } + } + } + }) + ]; }; ``` -With this code in place, we should be able to run the following GraphQL query: +With this code in place, we should be able to run the following GraphQL query: ```graphql { @@ -89,19 +91,23 @@ With this code in place, we should be able to run the following GraphQL query: } ``` - - The easiest way to test it is by using the [API Playground](https://www.webiny.com/docs/{version}/admin-area/basics/api-playground) in the Admin app: -Testing the listBooks query in the API Playground} /> + + Testing the listBooks query in the API Playground + + } +/> - Run `yarn webiny watch admin --env ENVIRONMENT_NAME` to start the Admin app in the development mode. Replace `ENVIRONMENT_NAME` with the name of the environment you're working on. +Run `yarn webiny watch admin --env ENVIRONMENT_NAME` to start the Admin app in the development mode. Replace `ENVIRONMENT_NAME` with the name of the environment you're working on. - ## Additional Examples - [Headless CMS - Extend the GraphQL API](/{version}/headless-cms/extending/extend-graphql-api) diff --git a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx index fecd8c080..a216915b0 100644 --- a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx @@ -101,9 +101,8 @@ We intended it to be used to check if the index configuration can be applied giv Index name can contain dashes (-), underscores (\_), numbers (0-9) and english letters (a-z). It is created by our configuration method, so you do not need to worry about that. - Note that each tenant, locale and CMS model combination creates it's own index. - - Page Builder, Form Builder, and some other apps, always create a single index per tenant and locale combination. + Note that each tenant, locale and CMS model combination creates it's own index. Page Builder, Form + Builder, and some other apps, always create a single index per tenant and locale combination. For example, creating an Article model in `root` tenant, `en-US` locale will create an index with the name: `root-headless-cms-en-us-articles`. @@ -127,57 +126,58 @@ This is due to the nature of the Elasticsearch/OpenSearch field mapping and conf ###### **OpenSearch** To make the `OpenSearch` share indexes, you need to add the following configuration to your `webiny.project.ts` file: + ```typescript import { createCoreApp } from "@webiny/serverless-cms-aws"; export default createCoreApp({ - pulumiResourceNamePrefix: "wby-", - openSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + openSearch: { + sharedIndexes: true + } }); ``` - ```typescript import { createApiApp } from "@webiny/serverless-cms-aws"; export default createApiApp({ - pulumiResourceNamePrefix: "wby-", - openSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + openSearch: { + sharedIndexes: true + } }); ``` ###### **Elasticsearch** To make the `Elasticesearch` share indexes, you need to add the following configuration to your `webiny.project.ts` file: + ```typescript import { createCoreApp } from "@webiny/serverless-cms-aws"; export default createCoreApp({ - pulumiResourceNamePrefix: "wby-", - elasticSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + elasticSearch: { + sharedIndexes: true + } }); ``` - ```typescript import { createApiApp } from "@webiny/serverless-cms-aws"; export default createApiApp({ - pulumiResourceNamePrefix: "wby-", - elasticSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + elasticSearch: { + sharedIndexes: true + } }); ``` - You should `NOT` change the `sharedIndexes` configuration after the initial deployment. If you do, you will lose all the data. + You should `NOT` change the `sharedIndexes` configuration after the initial deployment. If you do, + you will lose all the data. ##### Index Prefix diff --git a/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx b/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx index 80213cae6..5582fbc4a 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx @@ -87,5 +87,3 @@ This means that existing security roles and security teams are still the first t ### Can I Use Folder Level Permissions with API Tokens? At the moment, the answer is no. API tokens are not subject to folder level permissions. They will always have access to all folders. - - diff --git a/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx b/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx index 33cdbad84..13368ebee 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx @@ -17,20 +17,19 @@ import { Alert } from "@/components/Alert"; ## Overview -With the 5.39.0 release, Webiny received a feature that enables you to control who can see and access files inside File Manager. This feature is designed to protect highly sensitive files from leaking or being publicly shared. +With the 5.39.0 release, Webiny received a feature that enables you to control who can see and access files inside File Manager. This feature is designed to protect highly sensitive files from leaking or being publicly shared. {"Private -With this feature, after uploading a file inside the File Manager, users can set the Access Control setting on the newly uploaded file. The Access Control setting can take two values: +With this feature, after uploading a file inside the File Manager, users can set the Access Control setting on the newly uploaded file. The Access Control setting can take two values: + - **Public** -> Anyone on the public internet can access the file given the link to the file - **Private** -> Only registered Webiny Admin users can access and view the file Once a file is marked as `Private` even if a direct link to the file is shared with 3rd party users, they will not be able to access the file. The feature works regardless of the file type. You can protect images, documents, videos or any other file type. -It's important to note that public files are automatically cached on the CDN and in the browser. In case you switch a file from public to private, the CDN cache will be flushed, but users who have previously accessed the file might still have it in their browser cache. + It's important to note that public files are automatically cached on the CDN and in the browser. + In case you switch a file from public to private, the CDN cache will be flushed, but users who + have previously accessed the file might still have it in their browser cache. - - - - diff --git a/docs/developer-docs/5.x/enterprise/aacl/teams.mdx b/docs/developer-docs/5.x/enterprise/aacl/teams.mdx index 3e53f6f3f..e25d940b5 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/teams.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/teams.mdx @@ -33,7 +33,7 @@ And although this approach might work for some users, it can quickly become cumb This feature is especially useful for larger organizations, where it's common to have multiple teams working on different projects. Also, it's a great way to simplify the process of managing permissions for multiple users, as you can simply assign a role to a team, instead of assigning it to each individual user. -Additionally, the Teams feature can be used in conjunction with [Folder Level Permissions (FLP)](/{version}/enterprise/aacl/folder-level-permissions) to further enhance the security of your Webiny project. Instead of just being able to define folder level permissions for individual users, you can now define them for teams as well. Make sure to check out the FLP documentation to learn more about this feature. +Additionally, the Teams feature can be used in conjunction with [Folder Level Permissions (FLP)](/{version}/enterprise/aacl/folder-level-permissions) to further enhance the security of your Webiny project. Instead of just being able to define folder level permissions for individual users, you can now define them for teams as well. Make sure to check out the FLP documentation to learn more about this feature. ## Enabling Teams and Feature Overview diff --git a/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx b/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx index 0bb29101a..ba220430b 100644 --- a/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx +++ b/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx @@ -111,18 +111,14 @@ The way we do it is, we decorate the `usePermission` hook, and we always return - This customization is optional. If you _do_ want to allow Companies to be published, you will have to make some changes on the API side of things. There are pros and cons to both approaches, so do reach out to us on our [community slack](https://www.webiny.com/slack) and we can discuss the best way forward for your project. +This customization is optional. If you _do_ want to allow Companies to be published, you will have to make some changes on the API side of things. There are pros and cons to both approaches, so do reach out to us on our [community slack](https://www.webiny.com/slack) and we can discuss the best way forward for your project. ## Tenant Theming -Each Company is allowed to have its own brand attributes, like colors, logo, etc. The [ApplyTenantTheme](https://github.com/webiny/webiny-examples/blob/master/cms-tenant-management/5.40.x/extensions/admin/src/ApplyCompanyTheme.tsx) component demonstrates how you can programmatically modify the Admin app theme, when a user is accessing the Admin app for a particular Company. +Each Company is allowed to have its own brand attributes, like colors, logo, etc. The [ApplyTenantTheme](https://github.com/webiny/webiny-examples/blob/master/cms-tenant-management/5.40.x/extensions/admin/src/ApplyCompanyTheme.tsx) component demonstrates how you can programmatically modify the Admin app theme, when a user is accessing the Admin app for a particular Company. In our demo, we're only showing customization of colors and logo, but following this approach, you can customize pretty much anything related to the UI: navigation menu items, dashboard, columns, even functional plugins. - - - - diff --git a/docs/developer-docs/5.x/enterprise/auth0-integration.mdx b/docs/developer-docs/5.x/enterprise/auth0-integration.mdx index 0ddafb76b..a6b40e9ce 100644 --- a/docs/developer-docs/5.x/enterprise/auth0-integration.mdx +++ b/docs/developer-docs/5.x/enterprise/auth0-integration.mdx @@ -289,15 +289,14 @@ export const App = () => { }; ``` -Next time the system renders the **NotAuthorizedError** component, your decorator will intercept the output, and render your custom UI. +Next time the system renders the **NotAuthorizedError** component, your decorator will intercept the output, and render your custom UI. ## 8) Tenant Access Control (Optional) -If you want to restrict which identity can access individual tenants, you can do it in an optional `canAccessTenant` function. +If you want to restrict which identity can access individual tenants, you can do it in an optional `canAccessTenant` function. Let's imagine you want to restrict tenant access using the Auth0 client ID (it is stored in the idToken, in the `aud` claim). First you would assign the client ID value to your identity, and then, in the `canAccessTenant`, you would perform your checks: - ```diff-ts apps/api/graphql/src/security.ts createAuth0({ domain: String(process.env.AUTH0_DOMAIN), diff --git a/docs/developer-docs/5.x/enterprise/cognito-federation.mdx b/docs/developer-docs/5.x/enterprise/cognito-federation.mdx index 21ff08c8c..056332b2e 100644 --- a/docs/developer-docs/5.x/enterprise/cognito-federation.mdx +++ b/docs/developer-docs/5.x/enterprise/cognito-federation.mdx @@ -339,7 +339,7 @@ Open your `apps/api/graphql/src/security.ts` file, and update the existing `cogn identityType: "admin", + getIdentity({ identity, token }) { + const federatedIdentity = Boolean(token.identities); -+ ++ + return { + ...identity, + group: federatedIdentity ? "full-access" : undefined @@ -412,7 +412,7 @@ To pull this value from the Core app, and inject it into the Admin app, there's createAdminAppConfig, + configureAdminCognitoUserPoolDomain } from "@webiny/serverless-cms-aws"; - + export default createAdminAppConfig(modifier => { + configureAdminCognitoUserPoolDomain(modifier); }); @@ -504,7 +504,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit } from "@webiny/app-admin-users-cognito"; + import { ButtonPrimary } from "@webiny/ui/Button"; import "./App.scss"; - + + const cognitoConfig: CreateAuthenticationConfig = { + oauth: { + domain: String(process.env.REACT_APP_USER_POOL_DOMAIN), @@ -520,7 +520,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit + } + ] + }; - + + // Decorate the SignIn view, and customize props. + const SignInDecorator = Components.SignIn.createDecorator(Original => { + return function SignIn(props) { @@ -534,7 +534,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit + ); + }; + }); - + export const App = () => { return ( diff --git a/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx b/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx index 0e28a6a38..a45550e09 100644 --- a/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx +++ b/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx @@ -8,7 +8,6 @@ import { Alert } from "@/components/Alert"; - - how to enable multi-tenancy in the existing Webiny project diff --git a/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx b/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx index 59614f2e2..885824bf7 100644 --- a/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx @@ -10,12 +10,11 @@ import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; import folderDropConfirmation from "./assets/fm-folder-drop-confirmation.gif"; - - - how to prevent accidental folder moves +- how to prevent accidental folder moves @@ -28,13 +27,13 @@ import { FileManagerViewConfig } from "@webiny/app-file-manager"; const { Browser } = FileManagerViewConfig; - - + +; ``` -When active, this feature prompts users to confirm their action before moving a folder from one location to another. - - - - +When active, this feature prompts users to confirm their action before moving a folder from one location to another. + diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx index 332d5f3e7..89ef689b4 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx @@ -20,19 +20,23 @@ import fmFilesGridDetailsActionPlay from "./assets/fm-files-details-action-play. ## Overview -Files in File Manager can have different actions associated with them. These actions are displayed in the file browser (when hovering over a file) and file details panel. +Files in File Manager can have different actions associated with them. These actions are displayed in the file browser (when hovering over a file) and file details panel. -On top of the built-in actions, it's also possible to add custom actions, which can be displayed for all types of files (global actions) or for specific file types. +On top of the built-in actions, it's also possible to add custom actions, which can be displayed for all types of files (global actions) or for specific file types. In this guide, we show how to add custom actions for both scenarios. ## Getting Started - + ## Global File Actions @@ -47,37 +51,37 @@ import { ReactComponent as PlayIcon } from "@material-design-icons/svg/round/pla const { Browser, FileDetails } = FileManagerViewConfig; const GridItemAction = () => { - return ( - } - onAction={() => { - alert("In the future, this will be useful."); - }} - /> - ); + return ( + } + onAction={() => { + alert("In the future, this will be useful."); + }} + /> + ); }; const FileDetailsAction = () => { - return ( - } - onAction={() => { - alert("In the future, this will be useful."); - }} - /> - ); + return ( + } + onAction={() => { + alert("In the future, this will be useful."); + }} + /> + ); }; export const Extension = () => { - return ( - <> - - } /> - } /> - - - ); + return ( + <> + + } /> + } /> + + + ); }; ``` @@ -91,7 +95,7 @@ With this code in place, the **Play Video** action will be displayed for all typ ## File Type Specific File Actions -Besides global actions, we can also introduce actions for specific file types. +Besides global actions, we can also introduce actions for specific file types. To do that, in the `GridItemAction` and `FileDetailsAction` components, we use the `useFile` hook to get the current file and check its `type` property. If the file type is not `video/quicktime`, we return `null` and the action is not displayed. diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx index 34ce56829..eb4d81c02 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx @@ -100,19 +100,15 @@ export const CopyFolderData = () => { You can pass the custom component to the folder action definition using the `element` prop. ```tsx - - } - /> - + + } /> + ``` This is the whole process of registering a new folder action element. - ### Discover Folder Actions This section demonstrates how you can discover the names of existing folder actions. This is important for further sections on positioning, removing, and replacing existing actions. @@ -127,11 +123,7 @@ To position your custom folder action before or after an existing action, you ca ```tsx - } - before={"delete"} - /> + } before={"delete"} /> ``` @@ -153,10 +145,7 @@ To replace an existing action with a new action element, you need to reference a ```tsx - A new action!} - /> + A new action!} /> ``` @@ -200,19 +189,15 @@ export const CopyFileData = () => { You can pass the custom component to the file action definition using the `element` prop. ```tsx - - } - /> - + + } /> + ``` This is the whole process of registering a new file action element. - ### Discover File Actions This section demonstrates how you can discover the names of existing file actions. This is important for further sections on positioning, removing, and replacing existing actions. @@ -227,11 +212,7 @@ To position your custom file action before or after an existing action, you can ```tsx - } - before={"delete"} - /> + } before={"delete"} /> ``` @@ -253,9 +234,6 @@ To replace an existing action with a new action element, you need to reference a ```tsx - A new action!} - /> + A new action!} /> ``` diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx index 5976c1d9f..a43316f98 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx @@ -38,7 +38,6 @@ File Manager allows you to view files and folders in a table or grid format. The - **Created**: presenting the `createdOn` field - **Modified**: presenting the `savedOn` field - ## Using the code examples @@ -79,7 +78,7 @@ To add a new column, use the `Browser.Table.Column` component and mount it withi ### Simple Column -Here is an example of creating a column to show the `copyright` field data within the table. +Here is an example of creating a column to show the `copyright` field data within the table. The `Browser.Table.Column` component receives the following mandatory props: - `name` used to target the field you want to show and serves as a unique identifier @@ -87,11 +86,8 @@ The `Browser.Table.Column` component receives the following mandatory props: ```tsx - - + + ``` This is the whole process of registering a column. @@ -106,8 +102,7 @@ For example, you could create a `CellCopyright` component that displays: - a dash for folder rows - a dash in case the copyright is not set -- the `copyright` field value prepended by the `©` symbol - +- the `copyright` field value prepended by the `©` symbol ```tsx export const CellCopyright = () => { @@ -139,13 +134,14 @@ Using the `cell` prop, you can pass the custom component to the column definitio name={"extensions.copyright"} header={"Copyright"} + cell={} - /> - + /> + ``` ### Custom column size + To set the initial size of a column, you can use the `size` property. By default, the size is set to `100`. However, this is not a value in pixels but more of a proportion with the other columns within the table. If you want to double the size of a specific column, you can pass `200` as the value. @@ -158,9 +154,9 @@ In addition, you can allow or disallow users to adjust the column width accordin name={"extensions.copyright"} header={"Copyright"} + size={75} -+ resizable={false} // The column is not resizable by the user. - /> - ++ resizable={false} // The column is not resizable by the user. + /> + ``` ### Custom column visibility @@ -175,8 +171,8 @@ Users have the ability to show/hide columns by using the column settings menu. name={"extensions.copyright"} header={"Copyright"} + visible={false} - /> - + /> + ``` @@ -192,8 +188,8 @@ When the `hideable` property is set to false, users are restricted from dynamica header={"Price"} modelIds={["property"]} + hideable={false} - /> - + /> + ``` @@ -208,8 +204,8 @@ You can easily add custom CSS class names to columns using the `className` prope name={"extensions.copyright"} header={"Copyright"} + className={"custom-copyright-className"} - /> - + /> + ``` ## Discover Columns @@ -226,11 +222,7 @@ To position your column before or after an existing column, you can use the `bef ```tsx - + ``` @@ -250,10 +242,10 @@ To replace an existing column with a new cell renderer, you need to reference an ```tsx - {"Custom Type Cell"}} + {"Custom Type Cell"}} /> ``` diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx index adb38e950..851ab0b7a 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx @@ -18,7 +18,7 @@ import fmFilesPreviewsVideo2 from "./assets/fm-files-previews-video-2.png"; ## Overview -In File Manager, every file has a preview that is shown to the user when browsing files. This preview is a small thumbnail that gives the user a quick overview of the file content. +In File Manager, every file has a preview that is shown to the user when browsing files. This preview is a small thumbnail that gives the user a quick overview of the file content. For example, in the following screenshot, we can see that the image file on the left has a small thumbnail immediately shown to the user. This makes it easier to quickly identity the file the user is looking for. @@ -30,7 +30,11 @@ But, if required, this can be implemented, which is what we cover in this articl ## Getting Started - + ## File Preview For Specific File Type diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx index ede1621b8..9059635fe 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx @@ -113,11 +113,11 @@ import "./App.scss"; + const ExtensionFieldDecorator = Browser.Folder.ExtensionField.createDecorator(Original => { + return function FieldDecorator(props) { + const form = useForm(); -+ ++ + if (form.data.id) { + return null; + } -+ ++ + return ; + }; + }); @@ -135,6 +135,7 @@ export const App = () => { ``` ### Understanding the Implementation + - `useForm` **Hook**: This hook provides access to the current form inside the New/Edit Folder dialog. By leveraging `useForm`, you can dynamically control field visibility and behavior based on the form properties. - `ExtensionFieldDecorator`: This decorator ensures the field is only rendered when specific conditions are met. In this case, the field is hidden if an existing folder is being edited. - `FileManagerViewConfig`: This component acts as a wrapper, enabling access to configuration utilities for the File Manager List view. It must be mounted as a parent of other config components to ensure the configurations are applied correctly. @@ -149,7 +150,4 @@ Using this approach, you can: Once you run your Admin app and open the File Manager, edit an existing folder to see the custom renderer in action. While this example hides the field, you can extend it to include more advanced field customizations. - + diff --git a/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx b/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx index 4307d64c9..a39c5699d 100644 --- a/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx +++ b/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx @@ -26,7 +26,6 @@ You can ensure your AI tools have current Webiny knowledge through the Webiny MC - ### Server Details - **Name**: Webiny Docs @@ -69,7 +68,10 @@ To manually connect to the Webiny MCP server in VS Code, add the following to yo Learn more in the [VS Code documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers). - Sometimes, in VS Code, you may need to perform a one-time setup. When you ask a question and include a phrase like “use Webiny Docs”, then VS Code will prompt you to enable the Webiny MCP server for all sessions. Allow it, and the setup will be complete. This step is required only once. Please refer to the image below for reference. + Sometimes, in VS Code, you may need to perform a one-time setup. When you ask a question and + include a phrase like “use Webiny Docs”, then VS Code will prompt you to enable the Webiny MCP + server for all sessions. Allow it, and the setup will be complete. This step is required only + once. Please refer to the image below for reference. ![VS Code Webiny MCP Server](./assets/vs-code-webiny-mcp.png) @@ -87,10 +89,7 @@ To connect to the Webiny MCP server in Claude Desktop, add the following to your "mcpServers": { "webiny": { "command": "npx", - "args": [ - "@modelcontextprotocol/server-fetch", - "https://mcp.docs.webiny.com/mcp" - ] + "args": ["@modelcontextprotocol/server-fetch", "https://mcp.docs.webiny.com/mcp"] } } } @@ -103,6 +102,7 @@ Learn more in the [Claude Desktop documentation](https://docs.anthropic.com/en/d Many tools support a common JSON configuration format for MCP servers. If there are not specific instructions for your chosen tool, you may be able to add the Webiny Docs MCP server by including the following configuration in your tool's MCP settings: **Streamable HTTP:** + ```json { "mcpServers": { @@ -115,6 +115,7 @@ Many tools support a common JSON configuration format for MCP servers. If there ``` **Local Proxy:** + ```json { "mcpServers": { @@ -275,7 +276,6 @@ Refer to the [OpenAI MCP documentation](https://platform.openai.com/docs/mcp#tes Once configured, you can ask your AI tool questions about Webiny, and it will retrieve information directly from the latest docs. Coding agents will be able to consult the latest documentation when performing coding tasks, and chatbots will be able to accurately answer questions about Webiny features, APIs, and best practices. - ## Troubleshooting If you encounter issues: @@ -287,7 +287,6 @@ If you encounter issues: If you are still having problems, please reach out to our [community support channels](https://www.webiny.com/slack) for assistance. - ## AI Assistant in Documentation The Webiny documentation is equipped with an AI Assistant that can answer your questions and help you build customizations with Webiny. To open the AI Assistant, Click the "Ask AI" button in the bottom right corner of the documentation. diff --git a/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx b/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx index aa34c6bcb..316d8dce7 100644 --- a/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx +++ b/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx @@ -1,5 +1,5 @@ --- -id: +id: title: Smart SEO - OpenAI description: Learn how to integrate OpenAI with Webiny to build a Smart SEO tool for Headless CMS. This tool automatically generates SEO titles, descriptions, and tags for your articles. --- @@ -72,6 +72,7 @@ yarn webiny watch admin --env ENVIRONMENT_NAME ``` ## All Set! + You’re now ready to generate SEO titles and tags for your articles using OpenAI. Simply navigate to the **Article - Smart SEO** Model, create a new article, and click the “AI-Optimized SEO” button to generate SEO titles and tags effortlessly.