├── .editorconfig ├── .gitignore ├── .prettierignore ├── README.md ├── adonisrc.ts ├── assets ├── app.css └── app.js ├── bin ├── build.ts ├── download_sponsors.ts └── serve.ts ├── content ├── .DS_Store ├── config.json └── docs │ ├── db.json │ ├── introduction.md │ ├── legacy_docs │ ├── encore.md │ └── validator │ │ ├── custom_messages.md │ │ ├── custom_validation_rules.md │ │ ├── error_reporters.md │ │ ├── introduction.md │ │ └── schema_caching.md │ ├── migrate_package.md │ ├── migration │ ├── 10_upgrade_commands_options.md │ ├── 11_upgrade_adonisrc_file.md │ ├── 1_upgrade_packages.md │ ├── 2_update_module_system.md │ ├── 3_update_eslint_prettier_setup.md │ ├── 4_update_env_validations_code.md │ ├── 5_upgrade_aliases.md │ ├── 6_migrate_to_newer_imports.md │ ├── 7_fix_relative_imports.md │ ├── 8_upgrade_entrypoints.md │ ├── 9_upgrade_config_files.md │ ├── next_steps.md │ └── upgrade_kit.md │ └── other │ ├── other_breaking_changes.md │ ├── stick_to_v5.md │ └── vite_migration.md ├── package.json ├── public └── _redirects ├── src ├── bootstrap.ts └── collections.ts ├── templates ├── docs.edge ├── elements │ └── img.edge ├── layouts │ └── main.edge └── partials │ ├── detect_color_mode.edge │ ├── logo.edge │ ├── logo_mobile.edge │ └── sponsors.edge ├── tsconfig.json ├── vite.config.js └── vscode_grammars ├── dotenv.tmLanguage.json ├── edge.tmLanguage.json └── main.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | insert_final_newline = ignore 13 | 14 | [**.min.js] 15 | indent_style = ignore 16 | insert_final_newline = ignore 17 | 18 | [MakeFile] 19 | indent_style = space 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies and AdonisJS build 2 | node_modules 3 | build 4 | 5 | # Secrets 6 | .env 7 | .env.local 8 | .env.production.local 9 | .env.development.local 10 | 11 | # Frontend assets compiled code 12 | public/hot 13 | public/build 14 | public/assets 15 | 16 | # Build tools specific 17 | npm-debug.log 18 | yarn-error.log 19 | 20 | # Editors specific 21 | .fleet 22 | .idea 23 | .vscode 24 | 25 | # Static export 26 | dist 27 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migration Guide to AdonisJS v6 2 | 3 | This repo holds the codebase for the website that contains the migration guide for AdonisJS v6. It is built using [AdonisJS](https://adonisjs.com/) and [Dimer](https://github.com/dimerapp/docs-boilerplate) 4 | 5 | See the website at [v6-migration.adonisjs.com](https://v6-migration.adonisjs.com/) 6 | -------------------------------------------------------------------------------- /adonisrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/core/app' 2 | 3 | export default defineConfig({ 4 | typescript: true, 5 | directories: { 6 | views: 'templates', 7 | }, 8 | providers: [ 9 | () => import('@adonisjs/core/providers/app_provider'), 10 | () => import('@adonisjs/core/providers/edge_provider'), 11 | () => import('@adonisjs/vite/vite_provider'), 12 | () => import('@adonisjs/static/static_provider'), 13 | ], 14 | metaFiles: [ 15 | { 16 | pattern: './public/**/*', 17 | reloadServer: false, 18 | }, 19 | ], 20 | }) 21 | -------------------------------------------------------------------------------- /assets/app.css: -------------------------------------------------------------------------------- 1 | @import 'unpoly'; 2 | @import '@docsearch/css'; 3 | 4 | /* Gray colors */ 5 | @import '@radix-ui/colors/mauve.css'; 6 | @import '@radix-ui/colors/mauve-alpha.css'; 7 | @import '@radix-ui/colors/mauve-dark.css'; 8 | @import '@radix-ui/colors/mauve-dark-alpha.css'; 9 | 10 | /* Accent colors */ 11 | @import '@radix-ui/colors/violet.css'; 12 | @import '@radix-ui/colors/violet-alpha.css'; 13 | @import '@radix-ui/colors/violet-dark.css'; 14 | @import '@radix-ui/colors/violet-dark-alpha.css'; 15 | 16 | /* Interface colors */ 17 | @import '@radix-ui/colors/orange-alpha.css'; 18 | @import '@radix-ui/colors/iris-alpha.css'; 19 | @import '@radix-ui/colors/orange-dark-alpha.css'; 20 | @import '@radix-ui/colors/iris-dark-alpha.css'; 21 | 22 | @import '@dimerapp/docs-theme/styles'; 23 | 24 | :root { 25 | --logo-width: 100px; 26 | --font-mono: 'Roboto Mono'; 27 | --sidebar-width: 280px; 28 | --sidebar-anchor-padding-left: 25px; 29 | --sidebar-anchor-padding-right: 25px; 30 | --sidebar-title-padding-left: 25px; 31 | --sidebar-title-padding-right: 25px; 32 | 33 | --accent-1: var(--violet-1); 34 | --accent-2: var(--violet-2); 35 | --accent-3: var(--violet-3); 36 | --accent-4: var(--violet-4); 37 | --accent-5: var(--violet-5); 38 | --accent-6: var(--violet-6); 39 | --accent-7: var(--violet-7); 40 | --accent-8: var(--violet-8); 41 | --accent-9: var(--violet-9); 42 | --accent-10: var(--violet-10); 43 | --accent-11: var(--violet-11); 44 | --accent-12: var(--violet-12); 45 | 46 | --accent-a1: var(--violet-a1); 47 | --accent-a2: var(--violet-a2); 48 | --accent-a3: var(--violet-a3); 49 | --accent-a4: var(--violet-a4); 50 | --accent-a5: var(--violet-a5); 51 | --accent-a6: var(--violet-a6); 52 | --accent-a7: var(--violet-a7); 53 | --accent-a8: var(--violet-a8); 54 | --accent-a9: var(--violet-a9); 55 | --accent-a10: var(--violet-a10); 56 | --accent-a11: var(--violet-a11); 57 | --accent-a12: var(--violet-a12); 58 | 59 | --sidebar-active-link-text-color: var(--iris-12); 60 | } 61 | 62 | html.dark { 63 | --accent-1: var(--violet-1); 64 | --accent-2: var(--violet-2); 65 | --accent-3: var(--violet-3); 66 | --accent-4: var(--violet-4); 67 | --accent-5: var(--violet-5); 68 | --accent-6: var(--violet-6); 69 | --accent-7: var(--violet-7); 70 | --accent-8: var(--violet-8); 71 | --accent-9: var(--violet-9); 72 | --accent-10: var(--violet-10); 73 | --accent-11: var(--violet-11); 74 | --accent-12: var(--violet-12); 75 | 76 | --accent-a1: var(--violet-a1); 77 | --accent-a2: var(--violet-a2); 78 | --accent-a3: var(--violet-a3); 79 | --accent-a4: var(--violet-a4); 80 | --accent-a5: var(--violet-a5); 81 | --accent-a6: var(--violet-a6); 82 | --accent-a7: var(--violet-a7); 83 | --accent-a8: var(--violet-a8); 84 | --accent-a9: var(--violet-a9); 85 | --accent-a10: var(--violet-a10); 86 | --accent-a11: var(--violet-a11); 87 | --accent-a12: var(--violet-a12); 88 | } 89 | 90 | .docs_sidebar_container nav { 91 | padding-left: 0; 92 | padding-right: 0; 93 | } 94 | 95 | .sidebar_section_item a { 96 | border-radius: 0; 97 | } 98 | 99 | .markdown pre span[style='color: #545454;'] { 100 | color: var(--code-comment-color) !important; 101 | } 102 | 103 | .markdown .badge { 104 | background-color: var(--neutral-gray-5); 105 | border-radius: 9999px; 106 | font-size: 14px; 107 | top: -2px; 108 | padding: 2px 8px; 109 | position: relative; 110 | margin-left: 7px; 111 | } 112 | 113 | .toc_container .badge { 114 | display: none; 115 | } 116 | 117 | .markdown h1 .badge { 118 | top: -6px; 119 | font-size: 14px; 120 | } 121 | 122 | .table_container + blockquote { 123 | border: 1px solid var(--prose-table-border-color); 124 | margin-top: -25px; 125 | text-align: center; 126 | font-size: 13px; 127 | padding: 2px 0; 128 | background-color: var(--mauve-4); 129 | } 130 | 131 | .table_container + blockquote *:last-child { 132 | margin: 0; 133 | } 134 | 135 | .markdown .table_container { 136 | margin: var(--prose-elements-margin) 0; 137 | overflow: auto; 138 | } 139 | 140 | .markdown table { 141 | margin: 0; 142 | --code-font-size: var(--prose-table-font-size); 143 | } 144 | 145 | .disclosure_wrapper { 146 | border-radius: var(--rounded-md); 147 | margin: var(--prose-elements-margin) 0; 148 | background-color: var(--disclosure-wrapper-bg-color); 149 | border: 1px solid var(--disclosure-wrapper-border-color); 150 | } 151 | 152 | .disclosure_wrapper .disclosure { 153 | border-radius: 0; 154 | margin: 0; 155 | border: none; 156 | border-bottom: 1px solid var(--disclosure-wrapper-border-color); 157 | background: transparent; 158 | } 159 | 160 | .disclosure_wrapper .disclosure_panel { 161 | border-radius: 0; 162 | } 163 | 164 | .disclosure_wrapper .disclosure:last-child { 165 | border-bottom: none; 166 | } 167 | 168 | .disclosure_wrapper .disclosure:last-child .disclosure_panel { 169 | border-radius: 0 0 7px 7px; 170 | } 171 | 172 | .markdown p.lede { 173 | font-size: 18px; 174 | } 175 | 176 | @media (min-width: 1440px) { 177 | .markdown .table_expanded { 178 | max-width: 120%; 179 | width: 120%; 180 | } 181 | } 182 | 183 | .logo_wrapper { 184 | display: flex; 185 | align-items: center; 186 | gap: 6px; 187 | } 188 | 189 | .logo_suffix { 190 | font-size: 14px; 191 | } 192 | -------------------------------------------------------------------------------- /assets/app.js: -------------------------------------------------------------------------------- 1 | import 'unpoly' 2 | import Alpine from 'alpinejs' 3 | import mediumZoom from 'medium-zoom' 4 | import docsearch from '@docsearch/js' 5 | import { tabs } from 'edge-uikit/tabs' 6 | import Persist from '@alpinejs/persist' 7 | import collapse from '@alpinejs/collapse' 8 | 9 | import { 10 | initZoomComponent, 11 | initBaseComponents, 12 | initSearchComponent, 13 | } from '@dimerapp/docs-theme/scripts' 14 | 15 | import.meta.glob(['../content/**/*.png', '../content/**/*.jpeg', '../content/**/*.jpg']) 16 | 17 | Alpine.plugin(tabs) 18 | Alpine.plugin(Persist) 19 | Alpine.plugin(collapse) 20 | Alpine.plugin(initBaseComponents) 21 | Alpine.plugin(initSearchComponent(docsearch)) 22 | Alpine.plugin(initZoomComponent(mediumZoom)) 23 | Alpine.start() 24 | -------------------------------------------------------------------------------- /bin/build.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Development server entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "server.ts" file is the entrypoint for starting the AdonisJS HTTP 7 | | server. Either you can run this file directly or use the "serve" 8 | | command to run this file and monitor file changes 9 | | 10 | */ 11 | 12 | import 'reflect-metadata' 13 | import { Ignitor } from '@adonisjs/core' 14 | import { defineConfig } from '@adonisjs/vite' 15 | 16 | /** 17 | * URL to the application root. AdonisJS need it to resolve 18 | * paths to file and directories for scaffolding commands 19 | */ 20 | const APP_ROOT = new URL('../', import.meta.url) 21 | 22 | /** 23 | * The importer is used to import files in context of the 24 | * application. 25 | */ 26 | const IMPORTER = (filePath: string) => { 27 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 28 | return import(new URL(filePath, APP_ROOT).href) 29 | } 30 | return import(filePath) 31 | } 32 | 33 | /** 34 | * Exports collection to HTML files 35 | */ 36 | async function exportHTML() { 37 | const { collections } = await import('#src/collections') 38 | const { default: ace } = await import('@adonisjs/core/services/ace') 39 | const { default: app } = await import('@adonisjs/core/services/app') 40 | 41 | for (let collection of collections) { 42 | for (let entry of collection.all()) { 43 | try { 44 | const output = await entry.writeToDisk(app.makePath('dist'), { 45 | collection, 46 | entry, 47 | }) 48 | ace.ui.logger.action(`create ${output.filePath}`).succeeded() 49 | } catch (error) { 50 | ace.ui.logger.action(`create ${entry.permalink}`).failed(error) 51 | } 52 | } 53 | } 54 | } 55 | 56 | const application = new Ignitor(APP_ROOT, { importer: IMPORTER }) 57 | .tap((app) => { 58 | app.initiating(() => { 59 | app.useConfig({ 60 | appUrl: process.env.APP_URL || '', 61 | app: { 62 | appKey: 'zKXHe-Ahdb7aPK1ylAJlRgTefktEaACi', 63 | http: {}, 64 | }, 65 | logger: { 66 | default: 'app', 67 | loggers: { 68 | app: { 69 | enabled: true, 70 | }, 71 | }, 72 | }, 73 | vite: defineConfig({}), 74 | }) 75 | }) 76 | }) 77 | .createApp('console') 78 | 79 | await application.init() 80 | await application.boot() 81 | await application.start(exportHTML) 82 | -------------------------------------------------------------------------------- /bin/download_sponsors.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Script to download sponsors 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This script downloads the sponsors JSON from the pre-configured URLs 7 | | configured inside the "content/config.json" file. 8 | | 9 | */ 10 | 11 | import { request } from 'undici' 12 | import { readFile, writeFile } from 'node:fs/promises' 13 | 14 | /** 15 | * The file path to the config.json file 16 | */ 17 | const CONFIG_FILE_PATH = new URL('../content/config.json', import.meta.url) 18 | 19 | /** 20 | * The file path to the sponsors.json file. The output will be written 21 | * here. 22 | */ 23 | const SPONSORS_FILE_PATH = new URL('../content/sponsors.json', import.meta.url) 24 | 25 | export async function downloadSponsors() { 26 | console.log('starting to download sponsors...') 27 | 28 | try { 29 | const fileContents = await readFile(CONFIG_FILE_PATH, 'utf-8') 30 | const sources = JSON.parse(fileContents).sponsors_sources 31 | let sponsorsList: any[] = [] 32 | 33 | /** 34 | * No sources configured. So going to create an empty 35 | * sponsors.json file. 36 | */ 37 | if (sources.length === 0) { 38 | console.log('skipping download. No sources found...') 39 | await writeFile(SPONSORS_FILE_PATH, JSON.stringify(sponsorsList)) 40 | return 41 | } 42 | 43 | /** 44 | * Processing sponsors 45 | */ 46 | for (let source of sources) { 47 | const { body } = await request(source) 48 | const sponsors = await body.json() 49 | if (Array.isArray(sponsors)) { 50 | sponsorsList = sponsorsList.concat(sponsors) 51 | console.log(`Downloaded "${sponsors.length} sponsors" from "${source}"`) 52 | } 53 | } 54 | 55 | await writeFile(SPONSORS_FILE_PATH, JSON.stringify(sponsorsList)) 56 | } catch (error) { 57 | if (error.code === 'ENOENT') { 58 | console.warn('Cannot download sponsors list. Unable to find "content/config.json" file') 59 | return 60 | } 61 | 62 | console.error(error) 63 | } 64 | } 65 | 66 | await downloadSponsors() 67 | -------------------------------------------------------------------------------- /bin/serve.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Development server entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "server.ts" file is the entrypoint for starting the AdonisJS HTTP 7 | | server. Either you can run this file directly or use the "serve" 8 | | command to run this file and monitor file changes 9 | | 10 | */ 11 | 12 | import 'reflect-metadata' 13 | import { Ignitor } from '@adonisjs/core' 14 | import { readFile } from 'node:fs/promises' 15 | import { defineConfig } from '@adonisjs/vite' 16 | import type { ApplicationService } from '@adonisjs/core/types' 17 | import { defineConfig as defineHttpConfig } from '@adonisjs/core/http' 18 | 19 | /** 20 | * URL to the application root. AdonisJS need it to resolve 21 | * paths to file and directories for scaffolding commands 22 | */ 23 | const APP_ROOT = new URL('../', import.meta.url) 24 | 25 | /** 26 | * The importer is used to import files in context of the 27 | * application. 28 | */ 29 | const IMPORTER = (filePath: string) => { 30 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 31 | return import(new URL(filePath, APP_ROOT).href) 32 | } 33 | return import(filePath) 34 | } 35 | 36 | /** 37 | * Defining routes for development server 38 | */ 39 | async function defineRoutes(app: ApplicationService) { 40 | const { default: server } = await import('@adonisjs/core/services/server') 41 | const { collections } = await import('#src/collections') 42 | const { default: router } = await import('@adonisjs/core/services/router') 43 | 44 | server.use([() => import('@adonisjs/static/static_middleware')]) 45 | const redirects = await readFile(app.publicPath('_redirects'), 'utf-8') 46 | const redirectsCollection = redirects.split('\n').reduce( 47 | (result, line) => { 48 | const [from, to] = line.split(' ') 49 | result[from] = to 50 | return result 51 | }, 52 | {} as Record 53 | ) 54 | 55 | router.get('*', async ({ request, response }) => { 56 | if (redirectsCollection[request.url()]) { 57 | return response.redirect(redirectsCollection[request.url()]) 58 | } 59 | 60 | for (let collection of collections) { 61 | await collection.refresh() 62 | const entry = collection.findByPermalink(request.url()) 63 | if (entry) { 64 | return entry.render({ collection, entry }).catch((error) => { 65 | console.log(error) 66 | }) 67 | } 68 | } 69 | 70 | return response.notFound('Page not found') 71 | }) 72 | } 73 | 74 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 75 | .tap((app) => { 76 | app.initiating(() => { 77 | app.useConfig({ 78 | appUrl: process.env.APP_URL || '', 79 | app: { 80 | appKey: 'zKXHe-Ahdb7aPK1ylAJlRgTefktEaACi', 81 | http: defineHttpConfig({}), 82 | }, 83 | static: { 84 | enabled: true, 85 | etag: true, 86 | lastModified: true, 87 | dotFiles: 'ignore', 88 | }, 89 | logger: { 90 | default: 'app', 91 | loggers: { 92 | app: { 93 | enabled: true, 94 | }, 95 | }, 96 | }, 97 | vite: defineConfig({}), 98 | }) 99 | }) 100 | 101 | app.starting(defineRoutes) 102 | }) 103 | .httpServer() 104 | .start() 105 | .catch(console.error) 106 | -------------------------------------------------------------------------------- /content/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adonisjs/v5_to_v6_upgrade_guide/d3feac9c03dab21f439fc8ded00833337b773303/content/.DS_Store -------------------------------------------------------------------------------- /content/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "home": { 4 | "title": "Adonis V6 Migration", 5 | "href": "/" 6 | }, 7 | "github": { 8 | "title": "AdonisJS on Github", 9 | "href": "https://github.com/adonisjs/v5_to_v6_upgrade_guide" 10 | } 11 | }, 12 | "sponsors_sources": [], 13 | "search": {}, 14 | "fileEditBaseUrl": "https://github.com/adonisjs/v5_to_v6_upgrade_guide/blob/develop", 15 | "copyright": "AdonisJS" 16 | } 17 | -------------------------------------------------------------------------------- /content/docs/db.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "permalink": "introduction", 4 | "title": "Introduction", 5 | "contentPath": "./introduction.md", 6 | "category": "Guides" 7 | }, 8 | { 9 | "permalink": "upgrade-kit", 10 | "title": "Introduction", 11 | "category": "Migration guide", 12 | "contentPath": "./migration/upgrade_kit.md" 13 | }, 14 | { 15 | "permalink": "upgrade-packages", 16 | "title": "1. Upgrade Packages", 17 | "category": "Migration guide", 18 | "contentPath": "./migration/1_upgrade_packages.md" 19 | }, 20 | { 21 | "permalink": "upgrade-module-system", 22 | "title": "2. Upgrade Module System", 23 | "category": "Migration guide", 24 | "contentPath": "./migration/2_update_module_system.md" 25 | }, 26 | { 27 | "permalink": "upgrade-eslint-prettier", 28 | "title": "3. Upgrade ESLint and Prettier", 29 | "category": "Migration guide", 30 | "contentPath": "./migration/3_update_eslint_prettier_setup.md" 31 | }, 32 | { 33 | "permalink": "upgrade-env-config", 34 | "title": "4. Upgrade Env Config", 35 | "category": "Migration guide", 36 | "contentPath": "./migration/4_update_env_validations_code.md" 37 | }, 38 | { 39 | "permalink": "upgrade-aliases", 40 | "title": "5. Upgrade Aliases", 41 | "category": "Migration guide", 42 | "contentPath": "./migration/5_upgrade_aliases.md" 43 | }, 44 | { 45 | "permalink": "migrate-ioc-imports", 46 | "title": "6. Migrate IOC Imports", 47 | "category": "Migration guide", 48 | "contentPath": "./migration/6_migrate_to_newer_imports.md" 49 | }, 50 | { 51 | "permalink": "fix-relative-imports", 52 | "title": "7. Fix Relative Imports", 53 | "category": "Migration guide", 54 | "contentPath": "./migration/7_fix_relative_imports.md" 55 | }, 56 | { 57 | "permalink": "upgrade-entrypoints", 58 | "title": "8. Upgrade Entrypoints", 59 | "category": "Migration guide", 60 | "contentPath": "./migration/8_upgrade_entrypoints.md" 61 | }, 62 | { 63 | "permalink": "upgrade-config-files", 64 | "title": "9. Upgrade Config Files", 65 | "category": "Migration guide", 66 | "contentPath": "./migration/9_upgrade_config_files.md" 67 | }, 68 | { 69 | "permalink": "upgrade-command-options", 70 | "title": "10. Upgrade Command Options", 71 | "category": "Migration guide", 72 | "contentPath": "./migration/10_upgrade_commands_options.md" 73 | }, 74 | { 75 | "permalink": "upgrade-rcfile", 76 | "title": "11. Upgrade RC File", 77 | "category": "Migration guide", 78 | "contentPath": "./migration/11_upgrade_adonisrc_file.md" 79 | }, 80 | { 81 | "permalink": "next-steps", 82 | "title": "Next Steps", 83 | "category": "Migration guide", 84 | "contentPath": "./migration/next_steps.md" 85 | }, 86 | { 87 | "permalink": "other-breaking-changes", 88 | "title": "Other breaking changes", 89 | "category": "Other", 90 | "contentPath": "./other/other_breaking_changes.md" 91 | }, 92 | { 93 | "permalink": "webpack-encore-docs", 94 | "title": "Webpack Encore", 95 | "category": "Legacy docs", 96 | "contentPath": "./legacy_docs/encore.md" 97 | }, 98 | { 99 | "permalink": "validator-introduction", 100 | "title": "Introduction", 101 | "category": "Legacy Validator Docs", 102 | "contentPath": "./legacy_docs/validator/introduction.md" 103 | }, 104 | { 105 | "permalink": "validator-custom-messages", 106 | "title": "Custom Messages", 107 | "category": "Legacy Validator Docs", 108 | "contentPath": "./legacy_docs/validator/custom_messages.md" 109 | }, 110 | { 111 | "permalink": "validator-error-reporters", 112 | "title": "Error reporters", 113 | "category": "Legacy Validator Docs", 114 | "contentPath": "./legacy_docs/validator/error_reporters.md" 115 | }, 116 | { 117 | "permalink": "validator-schema-caching", 118 | "title": "Schema Caching", 119 | "category": "Legacy Validator Docs", 120 | "contentPath": "./legacy_docs/validator/schema_caching.md" 121 | }, 122 | { 123 | "permalink": "validator-custom-validation-rules", 124 | "title": "Custom validation rules", 125 | "category": "Legacy Validator Docs", 126 | "contentPath": "./legacy_docs/validator/custom_validation_rules.md" 127 | }, 128 | { 129 | "permalink": "migrate-to-vite", 130 | "title": "Migrate to Vite", 131 | "category": "Other", 132 | "contentPath": "./other/vite_migration.md" 133 | }, 134 | { 135 | "permalink": "package-migration", 136 | "title": "Package migration", 137 | "category": "Other", 138 | "contentPath": "./migrate_package.md" 139 | }, 140 | { 141 | "permalink": "stick-to-v5", 142 | "title": "Stick to v5", 143 | "category": "Other", 144 | "contentPath": "./other/stick_to_v5.md" 145 | } 146 | ] 147 | -------------------------------------------------------------------------------- /content/docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AdonisJS Migration Guide 3 | summary: The migration guide for migrating from AdonisJS v5 to v6. 4 | --- 5 | 6 | # Migration from AdonisJS v5 to v6 7 | 8 | This guide is a **work in progress** to help you migrate your existing (v5) AdonisJS applications to v6. In order to make the migration process a little easier, we have created a `upgrade-kit` CLI tool that applies patches on an existing codebase. 9 | 10 | ## What's not ready? 11 | 12 | The core of the framework is in great shape to create new applications and migrate over existing one's. However, not all the official and the community packages are compatible with v6 yet. This process might take another few weeks, as we and authors of other packages take out time to make their packages compatible with v6. 13 | 14 | So, we recommend creating new apps with v6 right away. But wait a little (few more weeks) as the community picks up the new release and starting moving their packages to v6. 15 | 16 | If you maintain a package and need help with migrating your package to be compatible with v6, then please open an issue with [adonisjs/v5_to_v6_upgrade_guide](https://github.com/adonisjs/v5_to_v6_upgrade_guide) repo. 17 | 18 | ### Official packages not yet compatible with v6 19 | 20 | If your applications relies on the following packages, then please wait for another few weeks as we get these packages compatible with v6. 21 | 22 | - [Attachment lite](https://github.com/adonisjs/attachment-lite) 23 | - [Lucid Slugify](https://github.com/adonisjs/lucid-slugify) 24 | - [Route model binding](https://github.com/adonisjs/route-model-binding) 25 | 26 | ## What's changed? 27 | 28 | Apart of individual breaking changes across different packages, AdonisJS has moved to ESM and changed the internals of how its IoC container works. Both of these changes forces you update the imports of your application in almost every single file. For example: 29 | 30 | - With ESM, you cannot import an `index.js` file its parent directory name. You will have to type the complete path. 31 | - The imports must end with `.js` extension. 32 | - Next, we remove the `@ioc` prefixed imports that were rewritten to IoC container lookup calls using a TypeScript compiler hook. So, these imports have to be replaced as well. [Learn more about it here](https://github.com/adonisjs/core/discussions/4184#:~:text=Changes%20to%20the%20import%20module%20names) 33 | - Next, we remove the aliasing system of AdonisJS in favor of [Node.js subpath imports](https://nodejs.org/api/packages.html#subpath-imports). So instead of importing a model from `'App/Models/User'` path, you will have to import it from `'#models/User'` path. 34 | - And finally, we use the Node.js [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) to control the public modules API of your packages. This might lead to a value that was earlier exported from the main entrypoint now has been moved to a submodule. 35 | 36 | All these changes combined does not break the internal APIs of existing modules. However, they do require manual labour to replace existing import paths with new import paths. 37 | 38 | To make this journey simpler, you should use the [upgrade-kit CLI](./migration/upgrade_kit.md). 39 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/encore.md: -------------------------------------------------------------------------------- 1 | # Encore documentation 2 | 3 | :::warning 4 | We recommend migrating to Vite as soon as possible. See [migration guide](../other/vite_migration.md) 5 | ::: 6 | 7 | ## Installation 8 | 9 | Install the package from the npm packages registry using one of the following commands. 10 | 11 | :::codegroup 12 | ```sh 13 | // title: npm 14 | npm i @adonisjs/encore 15 | ``` 16 | 17 | ```sh 18 | // title: yarn 19 | yarn add @adonisjs/encore 20 | ``` 21 | 22 | ```sh 23 | // title: pnpm 24 | pnpm add @adonisjs/encore 25 | ``` 26 | ::: 27 | 28 | 29 | Once done, you must run the following command to configure the mail package. 30 | 31 | ```sh 32 | node ace configure @adonisjs/encore 33 | ``` 34 | 35 | 36 | :::disclosure{title="See steps performed by the configure command"} 37 | 38 | 1. Registers the following service provider and command inside the `adonisrc.ts` file. 39 | 40 | ```ts 41 | { 42 | providers: [ 43 | // ...other providers 44 | () => import('@adonisjs/encore/encore_provider') 45 | ] 46 | } 47 | ``` 48 | 49 | 2. Create a `webpack.config.cjs` file 50 | 51 | 3. Install needed dependencies (`@babel/core`, `@babel/preset-env`, `@symfony/webpack-encore`, `webpack`, `webpack-cli` ) 52 | 53 | ::: 54 | 55 | ## Compiling frontend assets 56 | 57 | Once Encore has been configured, the pre-existing commands of AdonisJS will detect it and process your frontend assets as part of the following commands. 58 | 59 | --- 60 | 61 | #### node ace serve --watch 62 | 63 | The `node ace serve --watch` command will also run the [Webpack dev server](https://github.com/webpack/webpack-dev-server) within the same process to compile and serve the frontend assets. 64 | 65 | --- 66 | 67 | #### node ace build 68 | 69 | Similarly, the `node ace build` command will also run the `encore production` command to bundle the frontend assets alongside your AdonisJS build. 70 | 71 | --- 72 | 73 | ### Customizing output directory 74 | 75 | By default, the compiled assets are written to the `./public/assets` directory so that AdonisJS static file server can serve them. 76 | 77 | However, you can customize and define any output directory by updating the `webpack.config.js` file. 78 | 79 | The `setOutputPath` method accepts a path relative to the project root. Also, make sure to update the public URL prefix using the `setPublicPath` method. 80 | 81 | ```ts 82 | // Write file to this directory 83 | Encore.setOutputPath('./public/assets') 84 | 85 | // Prefix the following to the output URL 86 | Encore.setPublicPath('/assets') 87 | ``` 88 | 89 | ### Disable assets compilation 90 | 91 | You can disable Webpack assets compilation by defining the `--no-assets` flag to the `serve` and the `build` commands. 92 | 93 | ```sh 94 | node ace serve --watch --no-assets 95 | node ace build --no-assets 96 | ``` 97 | 98 | ## Customize dev server port and host 99 | Webpack dev server runs on `localhost:8080` by default. If the port is in use, AdonisJS will find a random port to start the Webpack dev server. However, you can also define a custom port using the `--assets-args` flag. 100 | 101 | ```sh 102 | node ace serve --watch --assets-args="--port 5000" 103 | ``` 104 | 105 | As of now, you cannot define the port for the Webpack dev server inside the `webpack.config.js` file. This is the limitation enforced by the [Symfony Encore package](https://github.com/symfony/webpack-encore/issues/941#issuecomment-787568811). 106 | 107 | ## Assets view helpers 108 | 109 | Depending upon your Webpack config, the output files may not have the same name as the input file. For example, The `Encore.enableVersioning()` method appends the file hash to the output file name. 110 | 111 | Hence, it is recommended to never hardcode the file names in your templates and always use the `asset` helper. 112 | 113 | :::caption{for="error"} 114 | Do not reference files by name 115 | ::: 116 | 117 | ```edge 118 | 119 | 120 | 121 | // highlight-start 122 | 123 | 124 | // highlight-end 125 | 126 | 127 | 128 | 129 | ``` 130 | 131 | :::caption{for="success"} 132 | Use the `asset` helper 133 | ::: 134 | 135 | ```edge 136 | 137 | 138 | 139 | // highlight-start 140 | 141 | 142 | // highlight-end 143 | 144 | 145 | 146 | 147 | ``` 148 | 149 | The `asset` helper relies on the `manifest.json` file generated by the Encore to resolve the actual URL. You can use it for all the assets, including JavaScript, CSS, fonts, images, and so on. 150 | 151 | ## Manifest file 152 | 153 | Encore generates the `manifest.json` file inside the `public/assets` directory. This file contains a key-value pair of the file identifier and its URL. 154 | 155 | ```json 156 | { 157 | "assets/app.css": "http://localhost:8080/assets/app.css", 158 | "assets/app.js": "http://localhost:8080/assets/app.js" 159 | } 160 | ``` 161 | 162 | The `asset` view helper resolves the URL from this file itself. 163 | 164 | ## Entrypoints 165 | 166 | Every Webpack bundle always has one or more [entrypoints](https://webpack.js.org/guides/code-splitting/#entry-points). Any other imports inside the entry point file are part of the same bundle. 167 | 168 | For example, if you have registered the `./resources/js/app.js` file as an entry point with the following contents, all the internal imports will be bundled together to form a single output. 169 | 170 | ```ts 171 | import '../css/app.css' 172 | import 'normalize.css' 173 | import 'alpinejs' 174 | ``` 175 | 176 | You can define these entry points inside the `webpack.config.js` file using the `Encore.addEntry` method. The first argument is the entry point name, and 2nd is the path to the entry point file. 177 | 178 | ```ts 179 | Encore.addEntry('app', './resources/js/app.js') 180 | ``` 181 | 182 | ### Multiple entry points 183 | 184 | Most applications need a single entry point unless you are building multiple interfaces in a single codebase. For example: Creating a public website + an admin panel may require different entry points as they will usually have different frontend dependencies and styling altogether. 185 | 186 | You can define multiple entry points by calling the `Encore.addEntry` method multiple times. 187 | 188 | ### Reference entry points inside the template files 189 | 190 | You can make use of the `@entryPointStyles` and the `@entryPointScripts` tags to render the script and the style tags for a given entry point. 191 | 192 | The tags will output the HTML with the correct `href` and `src` attributes. The `./public/assets/entrypoints.json` file is used to look up the URLs for a given entry point. 193 | 194 | ```edge 195 | 196 | 197 | 198 | @entryPointScripts('app') 199 | @entryPointStyles('app') 200 | 201 | 202 | 203 | 204 | ``` 205 | 206 | ## Setup PostCSS 207 | 208 | The first step is to install the [postcss-loader](https://github.com/postcss/postcss-loader) from the npm registry as follows: 209 | 210 | ```sh 211 | npm i -D postcss-loader 212 | ``` 213 | 214 | Next, create the `postcss.config.js` file to configure PostCSS. 215 | 216 | ```ts 217 | // title: postcss.config.js 218 | module.exports = { 219 | plugins: {} 220 | } 221 | ``` 222 | 223 | And finally, enable the PostCSS loader inside the `webpack.config.js` file. 224 | 225 | ```ts 226 | Encore.enablePostCssLoader() 227 | 228 | // Pass options 229 | Encore.enablePostCssLoader((options) => { 230 | options.postcssOptions = { 231 | config: path.resolve(__dirname, 'custom.config.js') 232 | } 233 | }) 234 | ``` 235 | 236 | ## Setup SASS, Less, and Stylus 237 | 238 | To configure the CSS pre-processors, you must uncomment the following lines of code inside the `webpack.config.js` 239 | 240 | ```ts 241 | // Enables SASS 242 | Encore.enableSassLoader() 243 | 244 | // Enables Less 245 | Encore.enableLessLoader() 246 | 247 | // Enables Stylus 248 | Encore.enableStylusLoader() 249 | ``` 250 | 251 | Also, make sure to install the appropriate loaders for them. 252 | 253 | ```sh 254 | # For SASS 255 | npm i -D sass-loader sass 256 | 257 | # For Less 258 | npm i -D less-loader less 259 | 260 | # For Stylus 261 | npm i -D stylus-loader stylus 262 | ``` 263 | 264 | ## Copying & referencing images 265 | Webpack cannot automatically scan/process the images referenced inside an Edge template. Hence, you have to tell the Webpack in advance to copy the images from a specific directory. 266 | 267 | You can use the `copyFiles` method to copy the images to the build output. 268 | 269 | ```ts 270 | Encore.copyFiles({ 271 | from: './resources/images', 272 | to: 'images/[path][name].[hash:8].[ext]', 273 | }) 274 | ``` 275 | 276 | Also, make sure to use the `asset` helper to reference the image inside an `img` tag. 277 | 278 | ```edge 279 | 280 | ``` 281 | 282 | ## Configuring Babel 283 | 284 | Babel is pre-configured for all files with `.js` and `.jsx` extensions using [babel-loader](https://github.com/babel/babel-loader). 285 | 286 | You can further configure Babel using the `Encore.configureBabel` method. 287 | 288 | ```ts 289 | // title: webpack.config.js 290 | Encore.configureBabel((babelConfig) => { 291 | babelConfig.plugins.push('styled-jsx/babel') 292 | babelConfig.presets.push('@babel/preset-flow') 293 | }, { 294 | exclude: /node_modules/ 295 | }) 296 | ``` 297 | 298 | ### Configuring browser targets 299 | 300 | You can configure the browser targets for [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) inside the `package.json`. 301 | 302 | ```json 303 | { 304 | "browserslist": [ 305 | "> 0.5%", 306 | "last 2 versions", 307 | "Firefox ESR", 308 | "not dead" 309 | ] 310 | } 311 | ``` 312 | 313 | ### Using the `.babelrc` file 314 | 315 | Instead of calling `configureBabel()`, you can also use the standard `.babelrc` file. However, this approach has a downside: as soon as a `.babelrc` file is present, Encore can longer configure babel for you, and the `.babelrc` file becomes the single source of truth. 316 | 317 | 318 | ## Configuring React 319 | 320 | You can configure React by installing React preset for Babel from the npm registry. 321 | 322 | ```sh 323 | npm i -D @babel/preset-react 324 | ``` 325 | 326 | Next, enable the React preset inside the `webpack.config.js` file. 327 | 328 | ```ts 329 | Encore.enableReactPreset() 330 | ``` 331 | 332 | :::warning 333 | 334 | If you are using the `.babelrc` file, you must enable the React preset inside it, as Encore can no longer configure Babel. 335 | 336 | ::: 337 | 338 | ## Configuring Vue 339 | You can configure Vue by first enabling the Vue loader inside the `webpack.config.js` file. 340 | 341 | ```ts 342 | // title: Vue 3 343 | Encore.enableVueLoader(() => {}, { 344 | version: 3 345 | }) 346 | ``` 347 | 348 | Next, install the following required dependencies for Vue 349 | 350 | ```sh 351 | // title: Vue 3 352 | npm i vue@next vue-loader@next @vue/compiler-sfc 353 | ``` 354 | 355 | You can define the [vue-loader options](https://vue-loader.vuejs.org/options.html) by passing a callback to the `enableVueLoader` method. 356 | 357 | ```ts 358 | Encore.enableVueLoader((options) => { 359 | options.transformAssetUrls = { 360 | video: ['src', 'poster'], 361 | source: 'src', 362 | img: 'src', 363 | image: ['xlink:href', 'href'], 364 | use: ['xlink:href', 'href'] 365 | } 366 | }) 367 | ``` 368 | 369 | The Encore-specific options can be defined as the third argument. 370 | 371 | ```ts 372 | Encore.enableVueLoader(() => {}, { 373 | version: 3, 374 | runtimeCompilerBuild: false, 375 | useJsx: true 376 | }) 377 | ``` 378 | 379 | #### version 380 | The VueJS version to use. You can opt between `2` and `3`. 381 | 382 | --- 383 | 384 | #### runtimeCompilerBuild 385 | You must disable runtime compiler build when using single-file components and do not want to use the string-based templates. 386 | 387 | --- 388 | 389 | #### useJsx 390 | Enable/disable the support for JSX inside your Vue templates. 391 | 392 | - You cannot enable the option with Vue3. 393 | - Also, you need to install `@vue/babel-preset-jsx` and `@vue/babel-helper-vue-jsx-merge-props` dependencies when using JSX. 394 | 395 | ## Adding custom Webpack loaders 396 | Encore does a pretty good job in encapsulating the setup for the most common use cases. It also allows you to set up custom loaders using the `addLoader` method. 397 | 398 | ```ts 399 | Encore 400 | .addLoader({ 401 | test: /\.handlebars$/, 402 | loader: 'cson', 403 | }) 404 | ``` 405 | 406 | Similarly, you can also add plugins using the `addPlugin` method. 407 | 408 | ```ts 409 | const NpmInstallPlugin = require('npm-install-webpack-plugin') 410 | Encore.addPlugin(new NpmInstallPlugin()) 411 | ``` 412 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/validator/custom_messages.md: -------------------------------------------------------------------------------- 1 | # Custom Messages 2 | 3 | The `validate` method accepts the custom messages alongside the validation schema object. You can define messages just for the validation rules, or you can specify them for individual fields as well. 4 | 5 | ```ts 6 | await request.validate({ 7 | schema: schema.create({ 8 | // ... 9 | }), 10 | // highlight-start 11 | messages: { 12 | required: 'The {{ field }} is required to create a new account', 13 | 'username.unique': 'Username not available' 14 | } 15 | // highlight-end 16 | }) 17 | ``` 18 | 19 | - The custom message for the `required` rule will be used by all the fields that fail the required validation. 20 | - The `username.unique` combination applies only to the `username` field for the `unique` validation rule. 21 | 22 | Messages for nested objects and arrays can be defined using the dot separator. 23 | 24 | ```ts 25 | { 26 | messages: { 27 | 'user.username.required': 'Missing value for username', 28 | 'tags.*.number': 'Tags must be an array of numbers', 29 | 'products.*.title.required': 'Each product must have a title' 30 | } 31 | } 32 | ``` 33 | 34 | ## Dynamic placeholders 35 | You can make use of the following placeholders to reference runtime values inside your custom messages. 36 | 37 | ```ts 38 | { 39 | messages: { 40 | required: '{{ field }} is required to sign up', 41 | enum: 'The value of {{ field }} must be in {{ options.choices }}' 42 | } 43 | } 44 | ``` 45 | 46 | | Placeholder | Description | 47 | |-------------|-------------| 48 | | `{{ field }}` | Name of the field under validation. Nested object paths are represented with a dot separator. For example: `user.profile.username` | 49 | | `{{ rule }}` | Name of the validation rule | 50 | | `{{ options }}` | The options passed by the validation methods. For example, The `enum` rule will pass an array of `choices`, and some rules may not pass any options at all | 51 | 52 | ## Wildcard callback 53 | You can also define a callback function to construct the message at runtime. The callback can only be defined as a fallback using the wildcard `*` expression. 54 | 55 | The callback will be invoked for all the fields in the following example, except for the `username` field only when it fails the `required` validation. 56 | 57 | ```ts 58 | { 59 | messages: { 60 | '*': (field, rule, arrayExpressionPointer, options) => { 61 | return `${rule} validation error on ${field}` 62 | }, 63 | 'username.required': 'Username is required to sign up', 64 | } 65 | } 66 | ``` 67 | 68 | ## Options passed to the message string 69 | Following is the list of options passed by the different validation methods to the message string. 70 | 71 | ### date 72 | The `date` validation rule will pass the `options.format`. 73 | 74 | ```ts 75 | { 76 | 'date.format': '{{ field }} must be formatted as {{ options.format }}', 77 | } 78 | ``` 79 | 80 | --- 81 | 82 | ### distinct 83 | The `distinct` validation rule will pass the `field` on which the distinct rule is applied, along with the `index` at which the duplicate value was found. 84 | 85 | ```ts 86 | { 87 | 'products.distinct': 'The product at {{ options.index + 1 }} position has already been added earlier' 88 | } 89 | ``` 90 | 91 | --- 92 | 93 | ### enum / enumSet 94 | The `enum` and `enumSet` validation rules will pass an array of `options.choices`. 95 | 96 | ```ts 97 | { 98 | 'enum': 'The value must be one of {{ options.choices }}', 99 | 'enumSet': 'The values must be one of {{ options.choices }}', 100 | } 101 | ``` 102 | 103 | --- 104 | 105 | ### file 106 | The file validation allows defining custom messages for the sub-rules. For example: 107 | 108 | ```ts 109 | { 110 | 'file.size': 'The file size must be under {{ options.size }}', 111 | 'file.extname': 'The file must have one of {{ options.extnames }} extension names', 112 | } 113 | ``` 114 | 115 | --- 116 | 117 | ### minLength / maxLength 118 | The `minLength` and `maxLength` validation rules will pass the following options to custom messages. 119 | 120 | ```ts 121 | { 122 | 'minLength': 'The array must have minimum of {{ options.minLength }} items', 123 | 'maxLength': 'The array can contain maximum of {{ options.maxLength }} items', 124 | } 125 | ``` 126 | 127 | --- 128 | 129 | ### range 130 | The `range` validation rule passes the `start` and the `stop` options to custom messages. 131 | 132 | ```ts 133 | { 134 | 'range': 'Candidate age must be between {{ options.start }} and {{ options.stop }} years', 135 | } 136 | ``` 137 | 138 | --- 139 | 140 | ### requiredIfExists / requiredIfNotExists 141 | The `requiredIfExists` and `requiredIfNotExists` validation rules will pass the `options.otherField` as a string. 142 | 143 | ```ts 144 | { 145 | 'requiredIfExists': '{{ options.otherField }} requires {{ field }}', 146 | } 147 | ``` 148 | 149 | --- 150 | 151 | ### Conditional required rules 152 | The following `requiredIf*` rules will pass the `options.otherFields` as an array of strings. 153 | 154 | - requiredIfExistsAll 155 | - requiredIfExistsAny 156 | - requiredIfNotExistsAll 157 | - requiredIfNotExistsAny 158 | 159 | ```ts 160 | { 161 | 'requiredIfExistsAll': '{{ options.otherFields }} requires {{ field }}', 162 | } 163 | ``` 164 | 165 | --- 166 | 167 | ### requiredWhen 168 | The `requiredWhen` validation rule will pass the following options. 169 | 170 | - `options.otherField` 171 | - `options.operator` 172 | - `options.values` 173 | 174 | 175 | ```ts 176 | { 177 | 'requiredWhen': '{{ field }} is required when {{ otherField }}{{ operator }}{{ values }}' 178 | } 179 | ``` 180 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/validator/custom_validation_rules.md: -------------------------------------------------------------------------------- 1 | # Custom validation rules 2 | 3 | You can add custom rules to the validator using the `validator.rule` method. Rules should be registered only once. Hence we recommend you register them inside a service provider or a preload file 4 | 5 | Throughout this guide, we will keep them inside the `start/validator.ts` file. You can create this file by running the following Ace command and select the environment as **"During HTTP server"**. 6 | 7 | ```sh 8 | node ace make:preload validator 9 | ``` 10 | 11 | Open the newly created file and paste the following code inside it. 12 | 13 | ```ts 14 | // title: start/validator.ts 15 | import string from '@adonisjs/core/helpers/string' 16 | import { validator } from '@adonisjs/validator' 17 | 18 | validator.rule('camelCase', (value, _, options) => { 19 | if (typeof value !== 'string') { 20 | return 21 | } 22 | 23 | if (value !== string.camelCase(value)) { 24 | options.errorReporter.report( 25 | options.pointer, 26 | 'camelCase', 27 | 'camelCase validation failed', 28 | options.arrayExpressionPointer 29 | ) 30 | } 31 | }) 32 | ``` 33 | 34 | - The `validator.rule` method accepts the rule name as the first argument. 35 | - The second argument is the rule implementation. The function receives the field's value under validation, the rule options, and an object representing the schema tree. 36 | 37 | In the above example, we create a `camelCase` rule that checks if the field value is the same as its camelCase version or not. If not, we will report an error using the [errorReporter](https://github.com/adonisjs/validator/blob/develop/src/ErrorReporter/Vanilla.ts#L39) class instance. 38 | 39 | ## Using the rule 40 | Before using your custom rules, you will have to inform the TypeScript compiler about the same. Otherwise, it will complain that the rule does not exist. 41 | 42 | To inform TypeScript, we will use [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces) and add the property to the `Rules` interface. 43 | 44 | Create a new file at path `contracts/validator.ts` (the filename is not important) and paste the following contents inside it. 45 | 46 | ```ts 47 | // title: contracts/validator.ts 48 | declare module '@adonisjs/validator/types' { 49 | interface Rules { 50 | camelCase(): Rule 51 | } 52 | } 53 | ``` 54 | 55 | Once done, you can access the `camelCase` rule from the `rules` object. 56 | 57 | ```ts 58 | // highlight-start 59 | import { rules, schema, validator } from '@adonisjs/validator' 60 | // highlight-end 61 | 62 | await validator.validate({ 63 | schema: schema.create({ 64 | fileName: schema.string([ 65 | // highlight-start 66 | rules.camelCase() 67 | // highlight-end 68 | ]), 69 | }), 70 | data: {}, 71 | }) 72 | ``` 73 | 74 | ## Passing options to the rule 75 | Rules can also accept options, and they will be available to the validation callback as the second argument. 76 | 77 | This time let's start from the TypeScript interface and define the options we expect from the rule consumer. 78 | 79 | ```ts 80 | // title: contracts/validator.ts 81 | declare module '@adonisjs/validator/types' { 82 | interface Rules { 83 | camelCase(maxLength?: number): Rule 84 | } 85 | } 86 | ``` 87 | 88 | All the arguments passed to the rule function are available as an array to the rule implementation. So, for example, You can access the `maxLength` option as follows. 89 | 90 | ```ts 91 | validator.rule('camelCase', ( 92 | value, 93 | // highlight-start 94 | [maxLength], 95 | // highlight-end 96 | options 97 | ) => { 98 | // Rest of the validation 99 | if (maxLength && value.length > maxLength) { 100 | options.errorReporter.report( 101 | options.pointer, 102 | // highlight-start 103 | 'camelCase.maxLength', // 👈 Keep an eye on this 104 | // highlight-end 105 | 'camelCase.maxLength validation failed', 106 | options.arrayExpressionPointer, 107 | { maxLength } 108 | ) 109 | } 110 | }) 111 | ``` 112 | 113 | Finally, if you notice, we are passing the rule name as `camelCase.maxLength` to the error reporter. This will allow the users to define a custom validation message just for the `maxLength`. 114 | 115 | ```ts 116 | messages: { 117 | 'camelCase.maxLength': 'Only {{ options.maxLength }} characters are allowed' 118 | } 119 | ``` 120 | 121 | ### Normalizing options 122 | Many times you would want to normalize the options passed to a validation rule. For example: Using a default `maxLength` when not provided by the user. 123 | 124 | Instead of normalizing the options inside the validation callback, we recommend you normalize them only once during the compile phase. 125 | 126 | The `validator.rule` method accepts a callback function as the third argument and runs it during the compile phase. 127 | 128 | ```ts 129 | validator.rule( 130 | 'camelCase', // rule name 131 | () => {}, // validation callback 132 | // highlight-start 133 | ([maxLength]) => { 134 | return { 135 | compiledOptions: { 136 | maxLength: maxLength || 10, 137 | }, 138 | } 139 | } 140 | // highlight-end 141 | ) 142 | ``` 143 | 144 | The `compiledOptions` value is then passed to the validation callback as the second argument. As per the above example, the validation callback will receive the `maxLength` as an object. 145 | 146 | ```ts 147 | validator.rule( 148 | 'camelCase', // rule name 149 | // highlight-start 150 | (value, { maxLength }) => {}, // validation callback 151 | // highlight-end 152 | ([maxLength]) => { 153 | return { 154 | compiledOptions: { 155 | maxLength: maxLength || 10, 156 | }, 157 | } 158 | } 159 | ) 160 | ``` 161 | 162 | ## Async rules 163 | To optimize the validation process, you will have to explicitly inform the validator that your validation rule is async in nature. Just return `async: true` from the compile callback, and then you will be able to use `async/await` inside the validation callback. 164 | 165 | ```ts 166 | validator.rule( 167 | 'camelCase', // rule name 168 | // highlight-start 169 | async () => {}, // validation callback 170 | // highlight-end 171 | () => { 172 | return { 173 | // highlight-start 174 | async: true, 175 | // highlight-end 176 | compiledOptions: {}, 177 | } 178 | } 179 | ) 180 | ``` 181 | 182 | ## Restrict rules to work on a specific data type 183 | Within the compile callback, you can access the **schema type/subtype** of the field on which the validation rule is applied and then conditionally allow it to be used on specific types only. 184 | 185 | Following is an example of restricting the `camelCase` rule to a string schema type only. 186 | 187 | ```ts 188 | validator.rule( 189 | 'camelCase', // rule name 190 | async () => {}, // validation callback 191 | (options, type, subtype) => { 192 | if (subtype !== 'string') { 193 | throw new Error('"camelCase" rule can only be used with a string schema type') 194 | } 195 | 196 | return { 197 | compiledOptions: {}, 198 | } 199 | } 200 | ) 201 | ``` 202 | 203 | An exception will be raised if someone attempts to use the `camelCase` rule on a non-string field. 204 | 205 | ```ts 206 | schema: schema.create({ 207 | fileName: schema.number([ 208 | rules.camelCase() // will result in an error at runtime 209 | ]), 210 | }), 211 | ``` 212 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/validator/error_reporters.md: -------------------------------------------------------------------------------- 1 | # Error Reporters 2 | 3 | Error formatters are helpful when you are writing an API server following a pre-defined spec like [JSON\:API](https://jsonapi.org/) 4 | 5 | Without error formatters, you have to manually loop over the error messages and re-shape them as per the spec followed by your API team. At the same time, error formatters expose an API to collect and structure error messages within the validation lifecycle (without any extra performance overhead). 6 | 7 | ## Using error reporters 8 | The validations performed using the `request.validate` method uses content negotiation to [find the best possible error reporter](./introduction.md#server-rendered-app) for a given HTTP request. 9 | 10 | However, you can also define the error reporter explicitly, which will turn off the content negotiation checks. 11 | 12 | Both the `validator.validate` and `request.validate` method accepts a reporter to use. Either you can use one of the [pre-existing reporters](https://github.com/adonisjs/validator/blob/develop/src/Validator/index.ts#L219-L222) or create/use a custom reporter. 13 | 14 | ```ts 15 | // highlight-start 16 | import { schema, validator } from '@adonisjs/validator' 17 | // highlight-end 18 | 19 | validator.validate({ 20 | schema: schema.create({}), 21 | // highlight-start 22 | reporter: validator.reporters.api, 23 | // highlight-end 24 | }) 25 | ``` 26 | 27 | Inside validator classes, you can define the reporter as an instance property. 28 | 29 | ```ts 30 | // highlight-start 31 | import { schema, validator } from '@adonisjs/validator' 32 | // highlight-end 33 | import { HttpContext } from '@adonisjs/core/http' 34 | 35 | export default class CreateUserValidator { 36 | constructor (protected ctx: HttpContext) {} 37 | 38 | // highlight-start 39 | public reporter = validator.reporters.api 40 | // highlight-end 41 | 42 | public schema = schema.create({ 43 | // ... schema properties 44 | }) 45 | } 46 | ``` 47 | 48 | ## Creating your error reporter 49 | Every reporter report must adhere to the [ErrorReporterContract](https://github.com/adonisjs/validator/blob/develop/adonis-typings/validator.ts#L168) interface and define the following properties/methods on it. 50 | 51 | ```ts 52 | export interface ErrorReporterContract { 53 | hasErrors: boolean 54 | 55 | report( 56 | pointer: string, 57 | rule: string, 58 | message: string, 59 | arrayExpressionPointer?: string, 60 | args?: any 61 | ): void 62 | 63 | toError(): any 64 | 65 | toJSON(): Messages 66 | } 67 | ``` 68 | 69 | #### report 70 | The `report` method is called by the validator when validation fails. It receives the following arguments. 71 | 72 | | Argument | Description | 73 | |-----------|-------------| 74 | | pointer | The path to the field name. Nested properties are represented with a dot notation. `user.profile.username` | 75 | | rule | The name of the validation rule | 76 | | message | The failure message | 77 | | arrayExpressionPointer | This property exists when the current field under validation is nested inside an array. For example: `users.*.username` is the array expression pointer, and `users.0.pointer` is the pointer. | 78 | | args | Arguments passed by the failed validation rule. | 79 | 80 | #### toError 81 | The `toError` method must return an instance of the error class, and the validator will throw this exception. 82 | 83 | --- 84 | 85 | #### toJSON 86 | The `toJSON` method must return the collection of errors reported by the validator so far. 87 | 88 | --- 89 | 90 | #### hasErrors 91 | A boolean to know if the error reporter has received any errors so far. 92 | 93 | Create a new file `app/Validators/Reporters/MyReporter.ts` and paste the following contents inside it. 94 | 95 | --- 96 | 97 | ### Dummy implementation 98 | Following is a dummy implementation of a custom error reporter. Feel free to tweak it further to match your needs. 99 | 100 | ```ts 101 | // title: app/Validators/Reporters/MyReporter.ts 102 | import { 103 | ValidationException, 104 | MessagesBagContract, 105 | ErrorReporterContract, 106 | } from '@adonisjs/validator/types' 107 | 108 | /** 109 | * The shape of an individual error 110 | */ 111 | type ErrorNode = { 112 | message: string, 113 | field: string, 114 | } 115 | 116 | export class MyReporter implements ErrorReporterContract<{ errors: ErrorNode[] }> { 117 | public hasErrors = false 118 | 119 | /** 120 | * Tracking reported errors 121 | */ 122 | private errors: ErrorNode[] = [] 123 | 124 | constructor ( 125 | private messages: MessagesBagContract, 126 | private bail: boolean, 127 | ) { 128 | } 129 | 130 | /** 131 | * Invoked by the validation rules to 132 | * report the error 133 | */ 134 | public report ( 135 | pointer: string, 136 | rule: string, 137 | message: string, 138 | arrayExpressionPointer?: string, 139 | args?: any 140 | ) { 141 | /** 142 | * Turn on the flag 143 | */ 144 | this.hasErrors = true 145 | 146 | /** 147 | * Use messages bag to get the error message. The messages 148 | * bag also checks for the user-defined error messages and 149 | * hence must always be used 150 | */ 151 | const errorMessage = this.messages.get( 152 | pointer, 153 | rule, 154 | message, 155 | arrayExpressionPointer, 156 | args, 157 | ) 158 | 159 | /** 160 | * Track error message 161 | */ 162 | this.errors.push({ message: errorMessage, field: pointer }) 163 | 164 | /** 165 | * Bail mode means stop validation on the first 166 | * error itself 167 | */ 168 | if (this.bail) { 169 | throw this.toError() 170 | } 171 | } 172 | 173 | /** 174 | * Converts validation failures to an exception 175 | */ 176 | public toError () { 177 | throw new ValidationException(false, this.toJSON()) 178 | } 179 | 180 | /** 181 | * Get error messages as JSON 182 | */ 183 | public toJSON () { 184 | return { 185 | errors: this.errors, 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | #### Points to note 192 | 193 | - You must always use the [MessagesBag](https://github.com/adonisjs/validator/blob/develop/src/MessagesBag/index.ts) to retrieve the error. It checks the user-defined custom error messages and returns the best match for a given field and validation rule. 194 | - You should always raise an exception within the `report` method when `this.bail` is set to true. 195 | - When in confusion, do check the implementation of [existing error reporters](https://github.com/adonisjs/validator/tree/develop/src/ErrorReporter). 196 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/validator/introduction.md: -------------------------------------------------------------------------------- 1 | # Legacy Validator docs 2 | 3 | This is the documentation for the legacy version of the validator. For new apps we recommend using [VineJS](vinejs.dev). However, for smoother upgrade experience, we maintain the legacy validator which brings the v5 validation module to v6 apps. 4 | 5 | First, make sure to install `@adonisjs/validator@next` to use the legacy validator. 6 | 7 | :::codegroup 8 | 9 | ```sh 10 | // title: npm 11 | npm install @adonisjs/validator@next 12 | ``` 13 | 14 | ```sh 15 | // title: yarn 16 | yarn add @adonisjs/validator@next 17 | ``` 18 | 19 | ```sh 20 | // title: pnpm 21 | pnpm add @adonisjs/validator@next 22 | ``` 23 | 24 | ::: 25 | 26 | Also, make sure to add the following entry to your `adonisrc.ts` file. 27 | 28 | ```ts 29 | providers: [ 30 | // ...other providers 31 | () => import('@adonisjs/validator/validator_provider') 32 | ] 33 | ``` 34 | 35 | ## Introduction 36 | 37 | ```ts 38 | import { schema } from '@adonisjs/validator' 39 | import router from '@adonisjs/core/services/router' 40 | 41 | router.post('posts', async ({ request }) => { 42 | /** 43 | * Schema definition 44 | */ 45 | const newPostSchema = schema.create({ 46 | title: schema.string(), 47 | body: schema.string(), 48 | categories: schema.array().members(schema.number()), 49 | }) 50 | 51 | /** 52 | * Validate request body against the schema 53 | */ 54 | const payload = await request.validate({ schema: newPostSchema }) 55 | }) 56 | ``` 57 | 58 | The validator also **extracts the static types** from the schema definition. You get the runtime validations and the static type safety from a single schema definition. 59 | 60 | ![](https://res.cloudinary.com/adonis-js/image/upload/q_auto,f_auto/v1611685370/v5/validator-static-types.jpg) 61 | 62 | ## Schema composition 63 | The schema definition is divided into three main parts. 64 | 65 | - The `schema.create` method defines the shape of the data you expect. 66 | - The `schema.string`, `schema.number`, and other similar methods define the data type for an individual field. 67 | - Finally, you use the `rules` object to apply additional validation constraints on a given field. For example: Validating a string to be a valid email is unique inside the database. 68 | 69 | ![](https://res.cloudinary.com/adonis-js/image/upload/q_auto,f_auto/v1617601990/v5/schema-101.png) 70 | 71 | :::note 72 | The `rules` object is imported from `@adonisjs/validator` 73 | 74 | ```ts 75 | import { schema, rules } from '@adonisjs/validator' 76 | ``` 77 | ::: 78 | 79 | If you look carefully, we have separated the **format validations** from **core data types**. So, for example, there is no data type called `schema.email`. Instead, we use the `rules.email` method to ensure a string is formatted as an email. 80 | 81 | This separation helps extend the validator with custom rules without creating unnecessary schema types that have no meaning. For example, there is no thing called **email type**; it is just a string, formatted as an email. 82 | 83 | ## Working with optional and null values 84 | All the fields are **required** by default. However, you can make use of the `optional`, `nullable`, and `nullableAndOptional` modifiers to mark fields as optional. 85 | 86 | All of these modifiers serve different purposes. Let's take a closer look at them. 87 | 88 | | Modifier | Validation behavior | Return payload | 89 | |------------|---------------------|---------------| 90 | | `optional` | Allows both `null` and `undefined` values to exist | Removes the key from the return payload is not is non-existing | 91 | | `nullable` | Allows `null` values to exists. However, the field must be defined in the validation data | Returns the field value including null. | 92 | | `nullableAndOptional` | Allows both `null` and `undefined` values to exist. (Same as modifier 1) | Only removes the key when the value is undefined, otherwise returns the field value | 93 | 94 | ### Use case for `nullable` modifier 95 | 96 | You will often find yourself using the `nullable` modifier to allow optional fields within your application forms. 97 | 98 | In the following example, when the user submits an empty value for the `fullName` field, the server will receive `null,` and hence you can update their existing full name inside the database to null. 99 | 100 | ```ts 101 | schema: schema.create({ 102 | fullName: schema.string.nullable(), 103 | }) 104 | ``` 105 | 106 | ### Use case for `nullableAndOptional` modifier 107 | 108 | If you create an API server that accepts PATCH requests and allows the client to update a portion of a resource, you must use the `nullableAndOptional` modifier. 109 | 110 | In the following example, if the `fullName` is undefined, you can assume that the client does not want to update this property, and if it is `null`, they want to set the property value of `null`. 111 | 112 | ```ts 113 | const payload = await request.validate({ 114 | schema: schema.create({ 115 | fullName: schema.string.nullableAndOptional(), 116 | }) 117 | }) 118 | 119 | const user = await User.findOrFail(1) 120 | user.merge(payload) 121 | await user.save() 122 | ``` 123 | 124 | ### Use case for `optional` modifier 125 | The `optional` modifier is helpful if you want to update a portion of a resource without any optional fields. 126 | 127 | The `email` property may or may not exist in the following example. But the user cannot set it `null`. If the property is not in the request, you will not update the email. 128 | 129 | ```ts 130 | const payload = await request.validate({ 131 | schema: schema.create({ 132 | email: schema.string.optional(), 133 | }) 134 | }) 135 | 136 | const user = await User.findOrFail(1) 137 | user.merge(payload) 138 | await user.save() 139 | ``` 140 | 141 | ## Validating HTTP requests 142 | You can validate the request body, query-string, and route parameters for a given HTTP request using the `request.validate` method. In case of a failure, the `validate` method will raise an exception. 143 | 144 | ```ts 145 | import router from '@adonisjs/core/services/router' 146 | import { schema, rules } from '@adonisjs/validator' 147 | 148 | router.post('users', async ({ request, response }) => { 149 | /** 150 | * Step 1 - Define schema 151 | */ 152 | const newUserSchema = schema.create({ 153 | username: schema.string(), 154 | email: schema.string([ 155 | rules.email() 156 | ]), 157 | password: schema.string([ 158 | rules.confirmed(), 159 | rules.minLength(4) 160 | ]) 161 | }) 162 | 163 | try { 164 | /** 165 | * Step 2 - Validate request body against 166 | * the schema 167 | */ 168 | const payload = await request.validate({ 169 | schema: newUserSchema 170 | }) 171 | } catch (error) { 172 | /** 173 | * Step 3 - Handle errors 174 | */ 175 | response.badRequest(error.messages) 176 | } 177 | }) 178 | ``` 179 | 180 | We recommend **NOT self-handling** the exception and let AdonisJS [convert the exception](https://github.com/adonisjs/validator/blob/develop/src/ValidationException/index.ts#L25-L49) to a response using content negotiation. 181 | 182 | Following is an explanation of how content negotiation works. 183 | 184 | ### Server rendered app 185 | 186 | If you build a standard web application with server-side templating, we will redirect the client back to the form and pass the errors as session flash messages. 187 | 188 | Following is the structure of error messages inside the session's flash store. 189 | 190 | ```ts 191 | { 192 | errors: { 193 | username: ['username is required'] 194 | } 195 | } 196 | ``` 197 | 198 | You can access them using the `flashMessages` global helper. 199 | 200 | ```edge 201 | @if(flashMessages.has('errors.username')) 202 |

{{ flashMessages.get('errors.username') }}

203 | @end 204 | ``` 205 | 206 | ### Requests with `Accept=application/json` header 207 | Requests negotiating for the JSON data type receive the error messages as an array of objects. Each error message contains the **field name**, the failed **validation rule**, and the **error message**. 208 | 209 | ```ts 210 | { 211 | errors: [ 212 | { 213 | field: 'title', 214 | rule: 'required', 215 | message: 'required validation failed', 216 | }, 217 | ] 218 | } 219 | ``` 220 | 221 | ### JSON API 222 | Requests negotiating using `Accept=application/vnd.api+json` header, receives the error messages as per the [JSON API spec](https://jsonapi.org/format/#errors). 223 | 224 | ```ts 225 | { 226 | errors: [ 227 | { 228 | code: 'required', 229 | source: { 230 | pointer: 'title', 231 | }, 232 | title: 'required validation failed' 233 | } 234 | ] 235 | } 236 | ``` 237 | 238 | ## Standalone validator usage 239 | You can also use the validator outside of an HTTP request by importing the `validate` method from the Validator module. The functional API remains the same. However, you will have to manually provide the `data` to validate. 240 | 241 | ```ts 242 | import { validator, schema } from '@adonisjs/validator' 243 | 244 | await validator.validate({ 245 | schema: schema.create({ 246 | // ... define schema 247 | }), 248 | data: { 249 | email: 'virk@adonisjs.com', 250 | password: 'secret' 251 | } 252 | }) 253 | ``` 254 | 255 | Also, since you perform the validation outside of an HTTP request, you will have to handle the exception and display the errors manually. 256 | 257 | ## Validator classes 258 | Validator classes allow you to extract the inline schema from your controllers and move them to a dedicated class. 259 | 260 | You can create a new validator by executing the following Ace command. 261 | 262 | ```sh 263 | node ace make:validator CreateUser 264 | 265 | # CREATE: app/Validators/CreateUserValidator.ts 266 | ``` 267 | 268 | All the validation related properties, including the `schema`, `messages` are defined as properties on the class. 269 | 270 | ```ts 271 | // title: app/Validators/CreateUserValidator.ts 272 | import { schema, CustomMessages } from '@adonisjs/validator' 273 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 274 | 275 | export default class CreateUserValidator { 276 | constructor (protected ctx: HttpContextContract) { 277 | } 278 | 279 | public schema = schema.create({ 280 | }) 281 | 282 | public messages: CustomMessages = {} 283 | } 284 | ``` 285 | 286 | ### Using validator 287 | 288 | Instead of passing an object with the `schema` property, you can now pass the class constructor to the `request.validate` method. 289 | 290 | ```ts 291 | import router from '@adonisjs/core/services/router' 292 | // highlight-start 293 | import CreateUser from '#validators/create_user_validator' 294 | // highlight-end 295 | 296 | router.post('users', async ({ request, response }) => { 297 | // highlight-start 298 | const payload = await request.validate(CreateUser) 299 | // highlight-end 300 | }) 301 | ``` 302 | 303 | During validation, a new instance of the validator class is created behind the scenes. Also, the `request.validate` method will pass the current HTTP context as a first constructor argument. 304 | 305 | You can also manually construct the class instance and pass any arguments you like. For example: 306 | 307 | ```ts 308 | router.post('users', async ({ request, response }) => { 309 | const payload = await request.validate( 310 | new CreateUser({ 311 | countries: fetchAllowedCountries(), 312 | states: fetchAllowedStates() 313 | }) 314 | ) 315 | }) 316 | ``` 317 | 318 | Following is an example of using the validator classes outside of the HTTP request. 319 | 320 | ```ts 321 | import { validator } from '@adonisjs/validator' 322 | import CreateUser from '#validators/create_user_validator' 323 | 324 | await validator.validate( 325 | new CreateUser({ 326 | countries: fetchAllowedCountries(), 327 | states: fetchAllowedStates() 328 | }) 329 | ) 330 | ``` 331 | 332 | ## What's next? 333 | 334 | - Learn more about [custom messages](./custom_messages.md) 335 | - Learn more about [error reporters](./error_reporters.md) 336 | -------------------------------------------------------------------------------- /content/docs/legacy_docs/validator/schema_caching.md: -------------------------------------------------------------------------------- 1 | # Schema Caching 2 | 3 | The schema created using the `schema.create` method is first complied to an executable function and then executed to validate the data against the defined rules. 4 | 5 | The compilation process does take a couple of milliseconds before the validation begins. However, based on your performance expectations, you may want to consider caching the compiled schema and hence don't pay the compilation penalty on every request. 6 | 7 | ## Using the `cacheKey` 8 | You can cache a schema by defining a unique `cacheKey`. You can generate this cache key using any approach or rely on the `ctx.routeKey` during an HTTP request. 9 | 10 | ```ts 11 | await request.validate({ 12 | schema: schema.create({...}), 13 | cacheKey: ctx.routeKey, 14 | }) 15 | ``` 16 | 17 | - The first call to `request.validate` will compile the schema and saves the output in reference to the `cacheKey`. 18 | - Until the `cacheKey` is identical, the validator won't recompile the schema. 19 | 20 | ## Caching caveats 21 | Caching in any form is not free, and the same is the case with schema caching. If your schema relies on runtime values, then caching schema will not give the desired outcome. Consider the following example: 22 | 23 | - You are creating a form that accepts the user **state** and their **city**. 24 | - The city options are based upon the value of the selected **state**. 25 | 26 | ```ts 27 | /** 28 | * Assuming the following variables hold data 29 | */ 30 | const STATES = [] 31 | const CITIES = {} 32 | 33 | export default class AddressValidator { 34 | public selectedState = this.ctx.request.input('state') // 👈 35 | 36 | public schema = schema.create({ 37 | state: schema.enum(STATES), 38 | city: schema.enum(CITIES[this.selectedState] || []) 39 | }) 40 | } 41 | ``` 42 | 43 | If you look at the above example, the enum options for the `city` are dependent on the `selectedState` and may vary with every HTTP request. 44 | 45 | However, since we have schema caching turned on. The enum options after the first request will get cached and will not change even if the user selects a different state. 46 | 47 | Now that you understand how caching works. Let's explore some different ways to use dynamic data within your validation schema. 48 | 49 | ### Give up caching 50 | The first option is to give up caching. This will add a delay of a couple of milliseconds to your requests but gives you the most straightforward API to use runtime values within your schema definition. 51 | 52 | ### Create a unique key 53 | Considering the above example, you can append the selected state to the `cacheKey`, and hence each state will have its copy of cached schema. For example: 54 | 55 | ```ts 56 | export default class AddressValidator { 57 | public selectedState = this.ctx.request.input('state') 58 | 59 | public schema = schema.create({ 60 | state: schema.enum(STATES), 61 | city: schema.enum(CITIES[this.selectedState] || []) 62 | }) 63 | 64 | // highlight-start 65 | public cacheKey = `${this.ctx.routeKey}-${selectedState}` 66 | // highlight-end 67 | } 68 | ``` 69 | 70 | The above approach has its own set of downsides. For example, if there are 37 states, there will be 37 cached copies of the same schema with a slight variation. Also, this number will grow exponentially if you need more than one dynamic value. 71 | 72 | Giving up caching is better than caching too many schemas with slight variations. 73 | 74 | ### Using refs 75 | Refs give you the best of both worlds. You can still cache your schema and also reference the runtime values inside them. Following is an example of the same: 76 | 77 | ```ts 78 | export default class AddressValidator { 79 | public selectedState = this.ctx.request.input('state') 80 | 81 | // highlight-start 82 | public refs = schema.refs({ 83 | cities: CITIES[this.selectedState] || [] 84 | }) 85 | // highlight-end 86 | 87 | public schema = schema.create({ 88 | state: schema.enum(STATES), 89 | // highlight-start 90 | city: schema.enum(this.refs.cities) 91 | // highlight-end 92 | }) 93 | } 94 | ``` 95 | 96 | Instead of referencing `CITIES[this.selectedState]` directly, you move it to the `schema.refs` object, and from there on, the cities will be picked up at runtime without recompiling the schema. 97 | 98 | :::note 99 | 100 | Refs only work if the **validation rule** or the **schema type** supports them. 101 | 102 | ::: 103 | -------------------------------------------------------------------------------- /content/docs/migrate_package.md: -------------------------------------------------------------------------------- 1 | # Migrate a package to v6 2 | 3 | If you are the maintainer of an AdonisJS package, this guide is definitely for you. We will try to cover the major changes that you need to make to your package to make it compatible with AdonisJS v6. 4 | 5 | ## Update Tooling 6 | 7 | Make sure to keep an eye on [`pkg-starter-kit`](https://github.com/adonisjs/pkg-starter-kit) repo for the tooling configuration as inspiration for the following steps. 8 | 9 | ### ESM 10 | 11 | Just add `type: module` to your `package.json` 12 | 13 | ```json 14 | { 15 | "type": "module" 16 | } 17 | ``` 18 | 19 | ### `mrm` 20 | 21 | We used to use `mrm` to update configuration files. We've decided to stop using `mrm` as it's no longer really maintained and was causing some problems. So make sure you remove all references to `mrm` from your `package.json`. 22 | 23 | ### Eslint / Prettier 24 | 25 | If you were using the default configuration, you would probably be using `eslint-plugin-adonis` to lint the codebase of your package. From now, we recommend using `@adonisjs/eslint-config` and `@adonisjs/prettier-config`. Make sure you install them as devDependencies. 26 | 27 | ```sh 28 | pnpm add -D @adonisjs/eslint-config @adonisjs/prettier-config 29 | ``` 30 | 31 | Then add this to your `package.json` file 32 | 33 | ```json 34 | { 35 | "eslintConfig": { 36 | "extends": "@adonisjs/eslint-config/package" 37 | }, 38 | "prettier": "@adonisjs/prettier-config" 39 | } 40 | ``` 41 | 42 | Make sure you also remove `.eslintrc.json` and `.eslintignore` from your project if they exist. 43 | 44 | ### Typescript 45 | 46 | Install the following packages: 47 | 48 | ```sh 49 | pnpm add -D ts-node @swc/core @adonisjs/tsconfig 50 | ``` 51 | 52 | You can also delete `@adonisjs/require-ts` from your `package.json`. 53 | 54 | Then update your `tsconfig.json` like this: 55 | 56 | ```json 57 | { 58 | "extends": "@adonisjs/tsconfig/tsconfig.package.json", 59 | "compilerOptions": { 60 | "rootDir": "./", 61 | "outDir": "./build" 62 | } 63 | } 64 | ``` 65 | 66 | Then create a `tsnode.esm.js` file at the root of your project with the following contents: 67 | 68 | ```js 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | TS-Node ESM hook 72 | |-------------------------------------------------------------------------- 73 | | 74 | | Importing this file before any other file will allow you to run TypeScript 75 | | code directly using TS-Node + SWC. For example 76 | | 77 | | node --import="./tsnode.esm.js" bin/test.ts 78 | | node --import="./tsnode.esm.js" index.ts 79 | | 80 | | 81 | | Why not use "--loader=ts-node/esm"? 82 | | Because, loaders have been deprecated. 83 | */ 84 | 85 | import { register } from 'node:module' 86 | register('ts-node/esm', import.meta.url) 87 | ``` 88 | 89 | Then make sure you run your scripts/tests/code with this script rather than `@adonisjs/require-ts`. 90 | 91 | For example, to launch your tests, you can define this script in your `package.json` : 92 | 93 | ```json 94 | { 95 | "scripts": { 96 | "test": "node --import=./tsnode.esm.js --enable-source-maps bin/test.ts" 97 | } 98 | } 99 | ``` 100 | 101 | Another topic, but note the `--enable-source-maps` which allows you to have stack traces with the correct line numbers. This is a [ts-node bug](https://github.com/TypeStrong/ts-node/issues/2053) 102 | 103 | ### Updating dependencies 104 | 105 | Make sure you update all dependencies to @adonis and @japa in your `package.json`. You probably also want to add `@adonisjs/core` as peerDependencies : 106 | 107 | ```json 108 | { 109 | "peerDependencies": { 110 | "@adonisjs/core": "^6.2.0" 111 | } 112 | } 113 | ``` 114 | 115 | ## Package configuration 116 | 117 | Previously, we used an `instructions.ts` to allow the user to configure the package at installation via `node ace configure my-package`. We've changed the way it works. 118 | 119 | You can delete the `adonisjs` field from your `package.json` which is no longer in use, also as the `instructions.ts` and `instructions.md` files. ( Keep instruction.ts though, as part of it can be re-used ) 120 | 121 | Now, to have this configuration logic, you'll need to define a `configure` method, and re-export it from the entrypoint ( `index.ts` ) of your package like this : 122 | 123 | - https://github.com/adonisjs/pkg-starter-kit/blob/main/index.ts 124 | - https://github.com/adonisjs/pkg-starter-kit/blob/main/configure.ts 125 | 126 | This `configure` method will be called by Adonis when the user launches `node ace configure my-package`. 127 | One point to note is that `@adonisjs/sink` is deprecated. Instead, the `configure` method receives a `ConfigureCommand` object as a parameter, which does the same thing as `@adonisjs/sink` but with a more powerful API. 128 | 129 | There's a lot to say about the new API, so I recommend that you: 130 | 131 | - Read the documentation here: https://github.com/adonisjs/assembler?tab=readme-ov-file#codemods 132 | - take some inspiration from the `configure` in the official packages. For example, the `configure` command in `@adonisjs/mail`: https://github.com/adonisjs/mail/blob/develop/configure.ts 133 | 134 | ### Stubs/templates 135 | 136 | Before, we used to use a `templates` folder in which we put `.txt` files which were copied with `sink` via `instructions.ts` file. Now you need to : 137 | 138 | - Create a `stubs` folder at the root of your project. 139 | - Add a `main.ts` to this folder containing this code: 140 | 141 | ```ts 142 | import { dirname } from 'node:path' 143 | import { fileURLToPath } from 'node:url' 144 | 145 | /** 146 | * Path to the root directory where the stubs are stored. We use 147 | * this path within commands and the configure hook 148 | */ 149 | export const stubsRoot = dirname(fileURLToPath(import.meta.url)) 150 | ``` 151 | 152 | - Then you can create a `.stub` file in this folder. Let's imagine a `config.stub` that should be copied into the `config` folder of the user's project. You can do it like this: 153 | 154 | ```ts 155 | { 156 | { 157 | { 158 | exports({ to: app.configPath('my_package_config.ts') }) 159 | } 160 | } 161 | } 162 | 163 | import { defineConfig } from 'my-package' 164 | 165 | export default defineConfig({ 166 | // ... 167 | myProperty: '{{ myProperty }}', 168 | }) 169 | ``` 170 | 171 | You can read more about stub syntax here: https://docs.adonisjs.com/guides/scaffolding#using-stubs 172 | 173 | - Next, make sure you copy your stub files to the final build folder at build time. You can do this as follows: 174 | 175 | ```json 176 | { 177 | "scripts": { 178 | "copy:templates": "copyfiles \"stubs/**/*.stub\" build", 179 | "build": "tsc", 180 | "postbuild": "npm run copy:templates" 181 | } 182 | } 183 | ``` 184 | 185 | And that's it. Now, in your `configure.ts` file, you can do like this to publish the config file in the user's project: 186 | 187 | ```ts 188 | import { stubsRoot } from './stubs/main.js' 189 | import type Configure from '@adonisjs/core/commands/configure' 190 | 191 | export async function configure(command: Configure) { 192 | const codemods = await command.createCodemods() 193 | 194 | await codemods.makeUsingStub(stubsRoot, 'config.stub', { 195 | myProperty: 'foo', // Will be passed to the stub template 196 | }) 197 | } 198 | ``` 199 | 200 | ## Providers / Services 201 | 202 | The major change here will be the way dependencies are injected and resolved via the IoC container. Be sure to read the @adonisjs/fold changelog before continuing. There's a specific section for package maintainers: 203 | 204 | - https://github.com/adonisjs/fold/releases/tag/v9.0.0-0 205 | 206 | Make sure you also read the following documentation: 207 | 208 | - https://docs.adonisjs.com/guides/ioc-container#container-bindings 209 | - https://docs.adonisjs.com/guides/container-services 210 | 211 | If you ever maintain a package that works with a driver system, I also invite you to read this post on `Config Providers` which will be essential for you: 212 | 213 | - https://github.com/adonisjs/road-to-v6/discussions/41 214 | 215 | You should know everything you need to know to migrate your provider. 216 | 217 | ## `adonis-typings` 218 | 219 | If you've been following the previous section, you'll notice that we no longer use `@ioc` imports at all. 220 | The user will import your functions/services/classes directly from your package. **As a result, the `adonis-typings` folder and its contents are no longer needed**, which is a real time-saver for you, and also much simpler to maintain, as there's no additional place to update just for having correct typings. 221 | 222 | ## Commands 223 | 224 | In case your package provides some commands to the user's application, you have a few modifications to make. 225 | 226 | Previously: 227 | 228 | - You defined your commands in `adonisjs.commands` of your `package.json`. As a reminder, the `adonisjs` property in `package.json` is now deprecated. You can delete it. 229 | - You had a `commands/index.ts` which exported an array of commands. This file is no longer used. You can delete it. 230 | 231 | With V6, your package will have to expose a `commands` export path as follows in the `package.json` : 232 | 233 | ```ts 234 | { 235 | "exports": { 236 | ".": "./build/index.js", 237 | "./commands": "./build/commands/main.js", 238 | } 239 | } 240 | ``` 241 | 242 | Then, in your `postbuild`, you now need to use `adonis-kit`, which is a CLI tool exposed by `@adonisjs/core` that will index your commands. You can do it like this: 243 | 244 | ```json 245 | { 246 | "scripts": { 247 | "copy:templates": "copyfiles \"stubs/**/*.stub\" build", 248 | "build": "tsc", 249 | "postbuild": "npm run copy:templates && npm run index:commands", 250 | "index:commands": "adonis-kit index build/commands" 251 | } 252 | } 253 | ``` 254 | 255 | And that's it. 256 | 257 | ## Tests 258 | 259 | Make sure you upgrade to Japa V3. Read the migration guide here: https://japa.dev/docs/uprade-guide 260 | 261 | Otherwise, I invite you to take a look at the tests of different official package to see how they are now written. Here are a few key points: 262 | 263 | - Most packages now export `factories` which allow you to test your code more easily when you need an instance of something. You can access them via `import { xxxFactory } from '@adonisjs/pkg'`. 264 | - In the past, when we needed to start an Adonis application for testing, you'd use big helpers like `setupApp` in which you had to make a bunch of boilerplates, create files on the filesystem... This is no longer necessary. 265 | 266 | - Example on `@adonisjs/mail` for version 5: https://github.com/adonisjs/mail/blob/68addd6bc952b7a4d545459455627652ac21e908/test-helpers/index.ts#L16 267 | - Example on `@adonisjs/mail` for version 6: https://github.com/adonisjs/mail/blob/develop/tests/integration/mail_provider.spec.ts#L25 268 | 269 | So you can create an application via the IgnitorFactory, pass options for each of the different packages, pass adonisrc file options... Much simpler. 270 | -------------------------------------------------------------------------------- /content/docs/migration/10_upgrade_commands_options.md: -------------------------------------------------------------------------------- 1 | # Upgrade Command Options 2 | 3 | This patch will migrate the commands options to the new structure. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-command-options 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-command-options 15 | ``` 16 | 17 | ::: 18 | 19 | With V5, the command options were defined as follows: 20 | 21 | ```ts 22 | export default class TestCommand extends BaseCommand { 23 | public static settings = { 24 | loadApp: false, 25 | stayAlive: false, 26 | } 27 | } 28 | ``` 29 | 30 | With V6, the options are defined like this: 31 | 32 | ```ts 33 | export default class TestCommand extends BaseCommand { 34 | static options: CommandOptions = { 35 | loadApp: false, 36 | staysAlive: false, 37 | } 38 | } 39 | ``` 40 | 41 | The patch will therefore update the options of all commands defined in `commands` directory of your application. 42 | -------------------------------------------------------------------------------- /content/docs/migration/11_upgrade_adonisrc_file.md: -------------------------------------------------------------------------------- 1 | # Upgrade Rc File 2 | 3 | :::codegroup 4 | 5 | ```sh 6 | // title: npm 7 | npx @adonisjs/upgrade-kit@latest upgrade-rcfile 8 | ``` 9 | 10 | ```sh 11 | // title: pnpm 12 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-rcfile 13 | ``` 14 | 15 | ::: 16 | 17 | With V5, the `.adonisrc.json` file was used to a lot of things like defining aliases, commands, providers, etc. 18 | 19 | With V6, the `.adonisrc.json` file is no longer used. Instead, we use the `adonisrc.ts` file to define all these things, for two main reasons: 20 | 21 | - **Type safety**: A TypeScript file provides more type safety. The JSON does not complain if you use an invalid path to a provider or a preload file. 22 | - **Better IDE integration**: In the AdonisJS codebase, service providers extend other modules by adding custom methods and properties. However, the types for these service providers were not picked up by the code editors because the import path of the service provider was inside a JSON file. Once we move to a TypeScript file, this problem disappears. 23 | 24 | So this patch will migrate your `.adonisrc.json` file to `adonisrc.ts` file by keeping the options you've previously defined. 25 | 26 | Make sure to always apply this patch as the last patch, since it will remove the `.adonisrc.json` file, and other patches may still need it. 27 | -------------------------------------------------------------------------------- /content/docs/migration/1_upgrade_packages.md: -------------------------------------------------------------------------------- 1 | # Upgrade packages 2 | 3 | The **"upgrade packages"** patch will update official AdonisJS packages, remove the deprecated one's and install the new one's. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-packages 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-packages 15 | ``` 16 | 17 | ::: 18 | 19 | Following are the steps performed by this patch. 20 | 21 | ## Packages upgraded 22 | 23 | - Every `@adonisjs` and `@japa` packages will be upgraded to the latest version. 24 | - `pino-pretty` 25 | 26 | ## Packages removed 27 | 28 | - `@adonisjs/repl` - The REPL source code has been merged with the AdonisJS core. Therefore the package is no longer required. 29 | - `source-map-support` - No longer need external packages for source maps. Node.js supports source maps as first class citizen now. 30 | 31 | ## Packages swapped 32 | 33 | - `@adonisjs/view` swapped with `edge.js`. The `@adonisjs/view` package was a wrapper on top of `edge.js`. We decided to directly use the `edge.js` package. 34 | - `phc-argon2` swapped with `argon2`. The `phc-argon2` was a wrapper on top of `argon2`. We decided to no longer use the wrapper and rely on the base implementation directly. 35 | - `phc-bcrypt` swapped with `bcrypt`. The `phc-bcrypt` was a wrapper on top of `bcrypt`. We decided to no longer use the wrapper and rely on the base implementation directly. 36 | - `@japa/preset-adonis` is swapped with `@japa/plugin-adonisjs`. 37 | - `adonis-preset-ts` - The base configuration for TypeScript has been moved to `@adonisjs/tsconfig` package. Also, we will install `ts-node`, `@swc/core` packages to run TypeScript code without compiling it during development. 38 | 39 | ## Packages installed 40 | 41 | - `@types/node` to have types for Node.js. 42 | - `@adonisjs/validator` - The validator module from v5 is in legacy mode and we recommend using VineJS for new apps. However, for smoother upgrade experience, we install the `@adonisjs/validator` package, which brings the v5 validation module to v6 apps. 43 | - `@adonisjs/cors` - The CORS source code has been moved to its own package. The patch will install and configure this package if there is an `config/cors.ts` file. 44 | - `@adonisjs/static` - The source code to serve static files has been moved to its own package. The patch will install and configure this package if there is a `config/static.ts` file. 45 | 46 | ## Providers updated 47 | 48 | Since AdonisJS packages exports Service providers, this patch will also update the `.adonisrc.json` file to use the new providers imports. 49 | 50 | ```diff 51 | - "@adonisjs/core" 52 | + "@adonisjs/core/providers/app_provider" 53 | + "@adonisjs/core/providers/hash_provider" 54 | ``` 55 | 56 | ```diff 57 | - "@adonisjs/session" 58 | + "@adonisjs/session/session_provider" 59 | ``` 60 | 61 | ```diff 62 | - "@adonisjs/view" 63 | + "@adonisjs/core/providers/edge_provider" 64 | ``` 65 | 66 | ```diff 67 | - "@adonisjs/shield" 68 | + "@adonisjs/shield/shield_provider" 69 | ``` 70 | 71 | ```diff 72 | - "@adonisjs/lucid" 73 | + "@adonisjs/lucid/database_provider" 74 | ``` 75 | 76 | ```diff 77 | - "@adonisjs/redis" 78 | + "@adonisjs/redis/redis_provider" 79 | ``` 80 | 81 | ```diff 82 | - "@adonisjs/mail" 83 | + "@adonisjs/mail/mail_provider" 84 | ``` 85 | 86 | ```diff 87 | - "@adonisjs/ally" 88 | + "@adonisjs/ally/ally_provider" 89 | ``` 90 | 91 | ```diff 92 | - "@adonisjs/auth" 93 | + "@adonisjs/auth/auth_provider" 94 | ``` 95 | 96 | ```diff 97 | - "@adonisjs/repl" 98 | + { 99 | + "file": "@adonisjs/core/providers/repl_provider", 100 | + "environments": ["repl", "test"] 101 | + } 102 | ``` 103 | 104 | ```diff 105 | - "@japa/preset-adonis/TestsProvider" 106 | ``` 107 | 108 | - The `aceProviders` and `testProviders` have been removed. You can now register providers within the `providers` array and define the environment in which they should be imported. 109 | 110 | ## Commands updated 111 | 112 | Since AdonisJS packages exports Ace commands, this patch will also update the `.adonisrc.json` file to use the new commands imports. 113 | 114 | ```diff 115 | - "@adonisjs/repl/build/commands" 116 | ``` 117 | 118 | ```diff 119 | - "@adonisjs/lucid/build/commands" 120 | + "@adonisjs/lucid/commands" 121 | ``` 122 | 123 | In v6, there is no need to explicitly register commands part of your application codebase. They will be scanned automatically from the `./commands` directory. Also, we will delete the `./commands/index.ts` file as it is no longer needed. 124 | 125 | ```diff 126 | - "./commands" 127 | ``` 128 | -------------------------------------------------------------------------------- /content/docs/migration/2_update_module_system.md: -------------------------------------------------------------------------------- 1 | # Update module system 2 | 3 | The **"update module system"** patch moves your application from CommonJS to ESM. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-module-system 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-module-system 15 | ``` 16 | 17 | ::: 18 | 19 | Following are the steps performed by this patch. 20 | 21 | - Define `type = module` inside the `package.json` file. 22 | - Make `tsconfig.json` file extend the new base config from `@adonisjs/tsconfig` package. 23 | - Remove unnecessary known types from the `compilerOptions.types` array. 24 | 25 | ```json 26 | { 27 | "compilerOptions": { 28 | "types": [ 29 | "@adonisjs/core", 30 | "@adonisjs/repl", 31 | "@adonisjs/session", 32 | "@adonisjs/view", 33 | "@adonisjs/shield", 34 | "@adonisjs/lucid", 35 | "@adonisjs/auth", 36 | "@adonisjs/lucid-slugify", 37 | "@adonisjs/drive-s3", 38 | "@adonisjs/attachment-lite", 39 | "@japa/preset-adonis/build/adonis-typings" 40 | ] 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /content/docs/migration/3_update_eslint_prettier_setup.md: -------------------------------------------------------------------------------- 1 | # Update ESLint and Prettier setup 2 | 3 | The **"Update ESLint and Prettier setup"** patch will update your existing ESLint config and Prettier config files to use the new base configuration shipped with v6 apps. 4 | 5 | :::warning 6 | If you are using a custom setup for both ESLint and Prettier, then you can skip patch. However, then you will have manually update `eslint` and its plugins and ensure they work smoothly with an ESM application. 7 | ::: 8 | 9 | :::codegroup 10 | 11 | ```sh 12 | // title: npm 13 | npx @adonisjs/upgrade-kit@latest upgrade-eslint-prettier 14 | ``` 15 | 16 | ```sh 17 | // title: pnpm 18 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-eslint-prettier 19 | ``` 20 | 21 | ::: 22 | 23 | Following are the steps performed by this patch. 24 | 25 | ## Config files removed 26 | 27 | - Remove `eslintrc.json` and `.eslintignore` files. The new config will be inline within the `package.json` file. 28 | - Remove `.prettierrc` file. The new config will be inline within the `package.json` file. 29 | 30 | ## Packages upgraded 31 | 32 | - `eslint` 33 | - `prettier` 34 | 35 | ## Packages removed 36 | 37 | The following packages have been removed in favour of a single base configuration package `@adonisjs/eslint-config`. 38 | 39 | - `eslint-config-prettier` 40 | - `eslint-plugin-adonis` 41 | - `eslint-plugin-prettier` 42 | 43 | ## Newly added packages 44 | 45 | - `@adonisjs/eslint-config` - Contains base rules for ESLint and ensure it works great with TypeScript, ESM, and Prettier. 46 | - `@adonisjs/prettier-config` - Contains base configuration for prettier rules we are using with v6 apps. 47 | 48 | ## Temporary rules 49 | 50 | We disable the following ESLint rules temporarily to make migration from `v5 -> v6` smoother. 51 | 52 | ```json 53 | "rules": { 54 | "@typescript-eslint/explicit-member-accessibility": "off", 55 | "unicorn/filename-case": "off", 56 | "@typescript-eslint/no-shadow": "off" 57 | } 58 | ``` 59 | 60 | - `@typescript-eslint/explicit-member-accessibility`: In v6 we no longer use TypeScript class property modifiers like `public`, or `private`. Instead we use JavaScript native private properties prefixed with a `#`. 61 | 62 | However, removing property modifiers from an entire codebase can be a tedious task and therefore we turn off this rule temporarily. If needed, you can turn it back on after the migration is completed and your app is in working state. 63 | 64 | - `unicorn/filename-case`: In v6, we opted for a `snake_case` naming convention for the naming folders and files. Again, to make migration smooth, we turn off the ESLint rule. 65 | 66 | - `@typescript-eslint/no-shadow` is a newly added rule and might force you to refactor your application codebase. We turn off this rule to make migration process smooth. 67 | -------------------------------------------------------------------------------- /content/docs/migration/4_update_env_validations_code.md: -------------------------------------------------------------------------------- 1 | # Update Env validations code 2 | 3 | The **"update env validation code"** patch will updates the `env.ts` file to be compatible with the new API. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-env-config 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-env-config 15 | ``` 16 | 17 | ::: 18 | 19 | Following are the steps performed by this patch. 20 | 21 | - Move `env.ts` to `start/env.ts` file. 22 | - Rename `Env.rules` to `Env.create` and await the method call. 23 | 24 | ```diff 25 | - export default Env.rules({ 26 | + export default await Env.create(new URL('../', import.meta.url), { 27 | ``` 28 | 29 | - Delete the `contracts/env.ts` file 30 | 31 | That's all! 32 | -------------------------------------------------------------------------------- /content/docs/migration/5_upgrade_aliases.md: -------------------------------------------------------------------------------- 1 | # Upgrade aliases 2 | 3 | The **"Upgrade aliases"** patch migrates your application from AdonisJS aliases to [Node.js sub-path imports](https://nodejs.org/dist/latest-v20.x/docs/api/packages.html#subpath-imports). Sub-path imports is a platform feature supported natively by Node.js and therefore we will embrace it with v6 applications. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-aliases 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-aliases 15 | ``` 16 | 17 | ::: 18 | 19 | Following are the steps performed by this patch. 20 | 21 | ## Config files updated 22 | 23 | - Scan `.adonisrc.json` file for existing aliases. 24 | - Define a new set of aliases inside `package.json` file. Do note, the Node.js sub-paths import aliases must always start with a `#`. 25 | - Define the same set of aliases inside `tsconfig.json` file as well. 26 | - Remove aliases from the `.adonisrc.json` file. 27 | 28 | ## Source code update 29 | 30 | - Finally, we will update the import statements in your codebase to use new aliases prefixed with a `#`. 31 | 32 | ## Un-effected areas 33 | 34 | - The patch will not update any dynamic imports. 35 | - The patch will not update magic strings based imports referenced within routes file or the events file. 36 | 37 | **What is a magic string?** 38 | 39 | In the following example, the `PostsController.index` is a magic string. Since, the value is a string internally translated to an import by AdonisJS. 40 | 41 | ```ts 42 | import Route from '@ioc:Adonis/Core/Route' 43 | 44 | Route.get('posts', 'PostsController.index') 45 | ``` 46 | -------------------------------------------------------------------------------- /content/docs/migration/6_migrate_to_newer_imports.md: -------------------------------------------------------------------------------- 1 | # Migrate AdonisJS imports 2 | 3 | The **"migrate AdonisJS imports"** patch removes the existing `@ioc` prefixed imports from your application source in favor of new standard ESM imports. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest migrate-ioc-imports 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest migrate-ioc-imports 15 | ``` 16 | 17 | ::: 18 | 19 | Multiple things to note : 20 | 21 | - This patch can be relatively slow for large codebases since it has to traverse all the files and replace the imports. 22 | - This patch will not update dynamic imports. 23 | 24 | ## History of `@ioc` prefixed imports 25 | 26 | AdonisJS uses the IoC Container to distribute pre-configured services like the `router`, `mailer`, `emitter`, and so on. For example, if you want an instance of the mailer, you can write the following code. 27 | 28 | ```ts 29 | const Mail = container.make('Adonis/Addons/Mail') 30 | ``` 31 | 32 | Behind the scenes, we will read the config from the `config/mail.ts` file, create an instance of the `Mailer` class, and return a singleton instance. 33 | 34 | In v5, we also decided to offer an import syntax for the `container.make` function call so you have a unified syntax for importing standard modules and resolving bindings from the container. Following is an example of the same. 35 | 36 | ```ts 37 | import Mail from '@ioc:Adonis/Addons/Mail' 38 | ``` 39 | 40 | Behind the scenes, we use the TypeScript compiler API to rewrite the import as a function call, as shown in the first example. This approach has many drawbacks. 41 | 42 | - The `@ioc` import feels alien since it is particular to AdonisJS. 43 | - We must rely on the TypeScript official compiler API to rewrite the import. Hence, we cannot use faster tools like SWC or ESBuild. 44 | - We have to separately define the `@ioc: Adonis/Addons/Mail` types because the TypeScript compiler cannot resolve this module using its filesystem resolution logic. 45 | 46 | Cut to v6. We are eliminating all this complexity and homegrown style of `@ioc` imports. This patch will rewrite the imports for you, so you do not have to replace them. 47 | 48 | ## What imports have been written 49 | 50 | See this [file](https://github.com/adonisjs/upgrade-kit/blob/main/src/rewrite_maps.ts#L0-L1) where we defined the list of imports to rewrite. 51 | -------------------------------------------------------------------------------- /content/docs/migration/7_fix_relative_imports.md: -------------------------------------------------------------------------------- 1 | # Fix relative imports 2 | 3 | This patch will fix the relative imports in your application code and make them compatible with ESM. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest fix-relative-imports 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest fix-relative-imports 15 | ``` 16 | 17 | ::: 18 | 19 | Since, AdonisJS applications are written in TypeScript, which uses the `import/export` syntax while authoring the code, you are somewhat protected when it comes to the number of breaking changes while moving from CommonJS to ESM. 20 | 21 | For example, you do not have to remove `module.exports`, `require` method calls. 22 | 23 | However, you will have to rewrite the relative imports to match the following rules. 24 | 25 | - All imports must specify the file extension. For example: Instead of writing `import User from './user'`, you will have to write `import User from './user.js'`. 26 | - Even for TypeScript files, the file extension has to be `.js`. 27 | - Only exception to the above rule is when importing a file with a [subpath pattern](https://nodejs.org/api/packages.html#subpath-patterns) we defined through the [`upgrade-aliases`](./5_upgrade_aliases.md) patch. For example, `import User from '#models/user' is valid. 28 | - Index files are no longer imported by specifying the directory name. For example: 29 | 30 | ```ts 31 | // ✅ Right way to import index.js file in ESM 32 | import InvoiceService from './services/invoice/index.js' 33 | 34 | // ❌ Works with CJS, but not ESM 35 | import InvoiceService from './services/invoice' 36 | ``` 37 | 38 | This patch will fix the relative imports in your application code and make them compatible with ESM. 39 | As with previous patches, this patch will **NOT** update dynamic imports. 40 | -------------------------------------------------------------------------------- /content/docs/migration/8_upgrade_entrypoints.md: -------------------------------------------------------------------------------- 1 | # Upgrade Entrypoints 2 | 3 | The **"Upgrade entrypoints"** patch migrates your application entrypoints to use the new ones needed for running AdonisJS v6. 4 | 5 | :::codegroup 6 | 7 | ```sh 8 | // title: npm 9 | npx @adonisjs/upgrade-kit@latest upgrade-entrypoints 10 | ``` 11 | 12 | ```sh 13 | // title: pnpm 14 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-entrypoints 15 | ``` 16 | 17 | ::: 18 | 19 | - We will add 3 new entrypoints files, `bin/console.ts`, `bin/test.ts` and `bin/server.ts`. You can find the source code for these files in the [`web-starter-kit`](https://github.com/adonisjs/web-starter-kit/tree/main/bin) repository. 20 | - We remove the old entrypoint files, `server.ts` and `test.ts`. 21 | - We also remove some of old files which are no longer required : `ace`, `ace-manifest.json` and `commands/index.ts`. 22 | - We add a new `ace.js` file. Code for this file can be found [here](https://github.com/adonisjs/web-starter-kit/blob/main/ace.js). 23 | -------------------------------------------------------------------------------- /content/docs/migration/9_upgrade_config_files.md: -------------------------------------------------------------------------------- 1 | # Upgrade Config Files 2 | 3 | :::codegroup 4 | 5 | ```sh 6 | // title: npm 7 | npx @adonisjs/upgrade-kit@latest upgrade-config-files 8 | ``` 9 | 10 | ```sh 11 | // title: pnpm 12 | pnpm dlx @adonisjs/upgrade-kit@latest upgrade-config-files 13 | ``` 14 | 15 | ::: 16 | 17 | This patch will update the configuration files defined in `config/*.ts` to use the new configuration structure. Some of config files will be left untouched, since they are quite difficult to migrate automatically and can be manually updated easily in some minutes. 18 | 19 | So make sure to read the [breaking changes](../other/other_breaking_changes.md) guide to know about the changes in the config files, and update them manually if required. 20 | 21 | ## Config files updated 22 | 23 | - `config/app.ts` 24 | - `config/bodyparser.ts` 25 | - `config/cors.ts` 26 | - `config/session.ts` 27 | - `config/shield.ts` 28 | - `config/static.ts` 29 | - `config/database.ts` 30 | - `config/redis.ts` 31 | - `config/mail.ts` 32 | - `config/ally.ts` 33 | 34 | ## Config files left untouched 35 | 36 | - `config/auth.ts` 37 | 38 | ## Changes applied 39 | 40 | There are three major changes applied to the config files : 41 | 42 | - Migrate to the new `defineConfig` function 43 | Previously, there were several ways of defining the configuration for a module: 44 | - export const myConfig: MyConfigType = {}` 45 | - export default mailConfig({ /_..._/ })` 46 | - And many other variations. 47 | With V6, every package exports a `defineConfig` function. So what the patch does here is take the options you've previously defined, and pass them to this `defineConfig` function. 48 | - Remove `contracts/*.ts` files associated with core packages. These files used to automatically infer certain types based on your configuration. With V6, these files are removed and types are defined directly in the configuration file. This patch therefore also applies this change. 49 | - Migration to `config providers`. In V5, when you wanted to use one driver or another, you used strings. For example, in the `@adonisjs/mail` configuration, we was doing this : 50 | 51 | ```ts 52 | export default mailConfig({ 53 | mailers: { 54 | smtp: { 55 | driver: 'smtp', // 👈👈 Specify the driver here 56 | }, 57 | }, 58 | }) 59 | ``` 60 | 61 | Keeping this structure with V5 was complicated. You can read more about it [here](https://github.com/adonisjs/road-to-v6/discussions/41). 62 | To continue with @adonisjs/mail example, with V6 we will instead do this : 63 | 64 | ```ts 65 | import { defineConfig, transports } from '@adonisjs/mail' 66 | 67 | export default defineConfig({ 68 | mailers: { 69 | smtp: transports.smtp({ 70 | // ... 71 | }), 72 | 73 | ses: transports.ses({ 74 | // ... 75 | }), 76 | }, 77 | }) 78 | ``` 79 | 80 | So the patch will also apply this modification to most files where it is required. 81 | 82 | If you ever have problems with this patch, feel free to consult the documentation for each package to see how to modify the new package configuration files. The web-starter-kit can also [be a good reference](https://github.com/adonisjs/web-starter-kit/tree/main/config) 83 | -------------------------------------------------------------------------------- /content/docs/migration/next_steps.md: -------------------------------------------------------------------------------- 1 | # Next steps 2 | 3 | Now that you have applied all patches, you are ready to make last manual changes to your application. First, see the [breaking changes](../other/other_breaking_changes.md) list and make sure to fix them. 4 | 5 | After that, here are some things you have to do: 6 | 7 | ## Apply lint fixes 8 | 9 | Since we have changed a lot of files using `upgrade-kit`, you should run your linter/formatter to fix the files. 10 | 11 | ```sh 12 | npm run lint -- --fix 13 | ``` 14 | 15 | ## Migrate the community packages 16 | 17 | Migrate the community packages you are using. Some of them will probably have already been updated. So make sure to consult their respective documentation and changelog and migrate them. 18 | 19 | ## Check the status of your `config/*.ts` files 20 | 21 | Check the state of your `config/*.ts` files. It's more than likely that the upgrade-kit wasn't able to migrate some of them correctly. You'll have to update some of them manually, but this should be very quick. Make sure to consult the V6 documentation to see how to configure the packages. 22 | 23 | ### Move the logger configuration 24 | 25 | Earlier, we defined the logger configuration in `config/app.ts`. We now have moved it to `config/logger.ts` and changed its API. 26 | 27 | See [documentation](https://docs.adonisjs.com/guides/logger#configuration) and the [web-starter-kit](https://github.com/adonisjs/web-starter-kit/blob/main/config/logger.ts) for an example. 28 | 29 | ### Remove the profiler configuration 30 | 31 | The profiler was never documented and is now removed. Remove the profiler configuration from `config/app.ts`. 32 | 33 | ### Remove the Assets bundler configuration 34 | 35 | The assets bundler doesn't need any configuration anymore. Remove the assets bundler configuration from `config/app.ts`. 36 | 37 | ## Update your Exception Handler 38 | 39 | Update your Exception Handler using [the new API](https://docs.adonisjs.com/guides/exception-handling). 40 | 41 | ## Update dynamic imports 42 | 43 | Update all your dynamic imports ( `await import('...')` ) using ESM-friendly imports (e.g. file extensions), and using subpath imports. 44 | 45 | Often, these imports are used in the commands you've created ( `commands/*.ts` ). Make sure you update them. 46 | 47 | Remember to use the new subpath-imports, so if for example you had `await import('App/Models/User')`, you can replace it with `await import('#app/Models/User')`. 48 | 49 | ## Update the `tests/bootstrap.ts` file 50 | 51 | Update the `tests/bootstrap.ts` file using the new configuration system. Take the example of [web-starter-kit](https://github.com/adonisjs/web-starter-kit/blob/main/tests/bootstrap.ts) 52 | 53 | ## ( Optional ) Migrate to the new Adonis 6 routing system 54 | 55 | ( Optional ) Migrate to the new Adonis 6 routing system. Remove all string-based routes and use [real controller imports](https://docs.adonisjs.com/guides/controllers#lazy-loading-controllers). 56 | 57 | ## Update the `start/kernel.ts` file 58 | 59 | Update the `start/kernel.ts` file using the [new API](https://github.com/adonisjs/web-starter-kit/blob/main/start/kernel.ts). 60 | 61 | ## Add container bindings middleware 62 | 63 | Add the middleware `container_bindings.ts` to the `app/middleware` folder and also add it to the kernel. Again, use the [web-starter-kit](https://github.com/adonisjs/web-starter-kit/blob/main/app/middleware/container_bindings_middleware.ts) as an example. 64 | 65 | ## Migrate the @adonisjs/auth middleware 66 | 67 | ## ( Optional ) Move contracts/_ files to config/_ files 68 | 69 | With V5, we defined some typings on the `contracts` folder. We now have moved them directly in the associated `config` file. For example, the `contracts/hash.ts` file on V5 looks like this: 70 | 71 | ```ts 72 | // title: contracts/hash.ts 73 | import type { InferListFromConfig } from '@adonisjs/core/build/config' 74 | import type hashConfig from '../config/hash' 75 | 76 | declare module '@ioc:Adonis/Core/Hash' { 77 | interface HashersList extends InferListFromConfig {} 78 | } 79 | ``` 80 | 81 | Now, with V6, you could only have the `config/hash.ts` file: 82 | 83 | ```ts 84 | // title: config/hash.ts 85 | 86 | import { defineConfig, drivers } from '@adonisjs/core/hash' 87 | 88 | const hashConfig = defineConfig({ 89 | default: 'scrypt', 90 | list: { 91 | scrypt: drivers.scrypt({ 92 | cost: 16384, 93 | blockSize: 8, 94 | parallelization: 1, 95 | maxMemory: 33554432, 96 | }), 97 | }, 98 | }) 99 | 100 | export default hashConfig 101 | 102 | /** 103 | * Inferring types for the list of hashers you have configured 104 | * in your application. 105 | */ 106 | declare module '@adonisjs/core/types' { 107 | export interface HashersList extends InferHashers {} 108 | } 109 | ``` 110 | 111 | ## Migrate edge breaking changes 112 | 113 | AdonisJS 6 comes with a new version of Edge. See the [Edge migration guide](https://edgejs.dev/docs/changelog/upgrading-to-v6) and update your views accordingly. 114 | 115 | ## Webpack Encore 116 | 117 | If you were using Webpack encore, we have created a `@adonisjs/encore` package that adds support for Webpack Encore in AdonisJS 6. But we highly recommend you to migrate to Vite as soon as possible, see the [migration guide](../other/vite_migration.md). 118 | 119 | If you want to stick with Webpack Encore for now, make sure to install `@adonisjs/encore` and run the following command: 120 | 121 | ```sh 122 | node ace configure @adonisjs/encore 123 | ``` 124 | 125 | Also make sure to rename your `webpack.config.js` file to `webpack.config.cjs` and also `postcss.config.js` to `postcss.config.cjs`. 126 | -------------------------------------------------------------------------------- /content/docs/migration/upgrade_kit.md: -------------------------------------------------------------------------------- 1 | # Migration guide 2 | 3 | In this guide we gonna cover everything you need to know to migrate your AdonisJS v5 application to v6. First, let's introduce the `upgrade-kit` CLI tool. 4 | 5 | ## Upgrade Kit 6 | 7 | The `Upgrade Kit` is a CLI tool that will help you migrate your AdonisJS v5 application to v6. It is built on using `ts-morph` so it is able to parse and modify your Typescript files directly. It probably won't be cover all the work needed to migrate your application, but it will take care of the most tedious and boring parts. 8 | 9 | The upgrade kit has multiples patches that **must be applied in a specific order**. After executing a patch, **make sure to review the changes**, commit them, and then execute the next patch. 10 | 11 | Some codemods are difficult to write perfectly, because we have to take into account a lot of different cases of writing code. So, it is possible that some patches will not be able to migrate your code correctly. In this case, you can always fix the code manually, by following the the instructions for each patch just below. Otherwise, feel free to also open an issue on the [Upgrade Kit repository](https://github.com/adonisjs/upgrade-kit) and we will try to fix it as soon as possible. 12 | 13 | ## Installation 14 | 15 | First you need to install the upgrade kit globally : 16 | 17 | :::codegroup 18 | 19 | ```sh 20 | // title: npm 21 | npm i -g @adonisjs/upgrade-kit 22 | ``` 23 | 24 | ```sh 25 | // title: pnpm 26 | pnpm i -g @adonisjs/upgrade-kit 27 | ``` 28 | 29 | ```sh 30 | // title: yarn 31 | yarn global add @adonisjs/upgrade-kit 32 | ``` 33 | 34 | ::: 35 | 36 | ## Usage 37 | 38 | To use the upgrade kit, you can run the following command. 39 | 40 | :::codegroup 41 | 42 | ```sh 43 | // title: npm 44 | npx @adonisjs/upgrade-kit@latest {patchName} --path=/path/to/your/project 45 | ``` 46 | 47 | ```sh 48 | // title: pnpm 49 | pnpm dlx @adonisjs/upgrade-kit@latest {patchName} --path=/path/to/your/project 50 | ``` 51 | 52 | ::: 53 | 54 | - `{patchName}` should be replaced by the name of one of the patches listed below 55 | - `--path` should be replaced by the path to your project. This is optional, if not provided, the current directory will be used. 56 | 57 | ## Patches 58 | 59 | Here is the list of patches. They are ordered by the order in which they should be applied. 60 | 61 | ### Upgrade packages 62 | 63 | Patch name : `upgrade-packages` 64 | 65 | Will update official AdonisJS packages to their new version. 66 | 67 | See [Upgrade packages](./1_upgrade_packages.md) 68 | 69 | ### Update module system 70 | 71 | Patch name : `upgrade-module-system` 72 | 73 | Move from CommonJS to ESM by updating `tsconfig.json` and `package.json`. 74 | 75 | See [Update module system](./2_update_module_system.md) 76 | 77 | ### Update ESLint and Prettier setup 78 | 79 | Patch name : `upgrade-eslint-prettier` 80 | 81 | Update ESLint and Prettier setup. 82 | 83 | See [Update ESLint and Prettier setup](./3_update_eslint_prettier_setup.md) 84 | 85 | ### Update Env validations code 86 | 87 | Patch name : `upgrade-env-config` 88 | 89 | Move to the new Env API. 90 | 91 | See [Update Env validations code](./4_update_env_validations_code.md) 92 | 93 | ### Migrate to Node.js sub-path imports 94 | 95 | Patch name : `upgrade-aliases` 96 | 97 | Move from the old aliases to Node.js subpaths imports. 98 | 99 | See [Migrate to Node.js sub-path imports](./5_upgrade_aliases.md) 100 | 101 | ### Migrate to newer AdonisJS imports 102 | 103 | Patch name : `migrate-ioc-imports` 104 | 105 | Move from `@ioc:` imports to standard imports. 106 | 107 | See [Migrate to newer AdonisJS imports](./6_migrate_to_newer_imports.md) 108 | 109 | ### Fix relative imports to use file extensions 110 | 111 | Patch name : `fix-relative-imports` 112 | 113 | Fix relative imports and add `.js` extensions ( needed for ESM ). 114 | 115 | See [Fix relative imports to use file extensions](./7_fix_relative_imports.md) 116 | 117 | ### Move to newer file structure for entry point files 118 | 119 | Patch name : `upgrade-entrypoints` 120 | 121 | Add new entrypoints needed for Adonis.js v6 122 | 123 | See [Move to newer file structure for entry point files](./8_upgrade_entrypoints.md) 124 | 125 | ### Upgrade config files 126 | 127 | Patch name : `upgrade-config-files` 128 | 129 | Update `config/*.ts` files to use their new APIs. 130 | 131 | See [Upgrade config files](./9_upgrade_config_files.md) 132 | 133 | ### Upgrade command options 134 | 135 | Patch name : `upgrade-command-options` 136 | 137 | Update the command `options` property to use the new API. 138 | 139 | See [Upgrade command options](./10_upgrade_commands_options.md) 140 | 141 | ### Upgrade AdonisRC file (Should be the last patch) 142 | 143 | Patch name : `upgrade-rcfile` 144 | 145 | Move from `.adonisrc.json` to `adonisrc.ts`. 146 | 147 | See [Upgrade AdonisRC file](./11_upgrade_adonisrc_file.md) 148 | 149 | ## Next steps 150 | 151 | After applying all patches, you are ready to make last manual changes. 152 | -------------------------------------------------------------------------------- /content/docs/other/other_breaking_changes.md: -------------------------------------------------------------------------------- 1 | # Other breaking changes 2 | 3 | Here's a list of some breaking changes that are not covered by the patches provided by the upgrade-kit. 4 | 5 | ## Encryption package 6 | 7 | - Zero breaking changes in the functional API 8 | 9 | ## Env 10 | 11 | - Zero breaking changes in the functional API 12 | 13 | ## Config 14 | 15 | - Remove undocumented `Config.merge` method. 16 | 17 | ## Logger 18 | 19 | - Zero breaking changes in the functional API. 20 | - Add support for multiple loggers. 21 | - Upgrade to latest version of Pino. 22 | 23 | ## Events 24 | 25 | - Remove deprecated `trap` and `trapAll` methods. 26 | - Remove `namespace` method. Since we are opting for standard imports and not magic string based imports, the `namespace` method is no longer relevant. 27 | - Add assertion methods like `assertNoneEmitted`, `assertNotEmitted`, `assertEmitted`. 28 | 29 | ## Bodyparser 30 | 31 | - Remove `file.moveToDisk` method from the bodyparser source code. However, you can still use this method if you have `@adonisjs/drive` package installed. 32 | - Removed `queryString` config option from raw bodyparser config block. The option was unused and hence has no breaking change at runtime, but will give static type error. 33 | - Add support for `convertEmptyStringsToNull` for JSON parser as well. 34 | - rename `whitelistedMethods` property to `allowedMethods`. 35 | 36 | ## Session 37 | 38 | - Add `@adonisjs/session/session_middleware` to the middleware list in order to enable sessions. In v5, it was not needed. 39 | 40 | ## Encore 41 | 42 | - No breaking changes 43 | 44 | ## Mail 45 | 46 | - Zero breaking changes in the functional API. 47 | - Add `alwaysTo` and `alwaysFrom` methods. 48 | - Add `assertSent`, `assertNotSent`, `assertNoneSent` to write assertions during testing. 49 | 50 | ## Ally 51 | 52 | - Zero breaking changes 53 | 54 | ## Hash 55 | 56 | - Remove `Hash.isFaked` property since it serves no use case. 57 | - The `Hash.extend` method signature has been changed. 58 | 59 | ```ts 60 | // Earlier 61 | Hash.extend('md5', (manager, mappingName, config) => { 62 | console.log(manager === Hash) // true 63 | }) 64 | 65 | // Now 66 | Hash.extend('md5', (config) => {}) 67 | ``` 68 | 69 | ## Repl 70 | 71 | - Zero functional breaking changes. 72 | - Top-level imports do not work in REPL mode. It is a limitation of Node.js. However, you can use dynamic imports to import ES modules. 73 | 74 | ## Container 75 | 76 | IoC Container has been rewritten from scratch and has received a massive API refactor. Since, the container is the foundation of the framework, there was no easy way to avoid this massive refactor. 77 | 78 | With that said, the API changes will not impact the applications written in AdonisJS. The breaking changes will impact packages though. 79 | 80 | Read the release notes for following releases. 81 | 82 | - https://github.com/adonisjs/fold/releases/tag/v9.0.0-0 83 | 84 | ## Application 85 | 86 | The application module has also received significant breaking changes. Please consult the following release notes. 87 | 88 | - https://github.com/adonisjs/application/releases/tag/v6.0.0-0 89 | - https://github.com/adonisjs/application/releases/tag/v6.3.0-0 90 | - https://github.com/adonisjs/application/releases/tag/v6.8.0-0 91 | 92 | ## Http server 93 | 94 | The Http server module has received significant changes. Please refer to the following release notes. 95 | 96 | - https://github.com/adonisjs/http-server/releases/tag/v6.0.0-0 97 | - https://github.com/adonisjs/http-server/releases/tag/v6.1.0-0 98 | - https://github.com/adonisjs/http-server/releases/tag/v6.2.0-0 99 | 100 | ## Shield 101 | 102 | - Remove DNS prefetching guard, since it is not a standard feature anymore. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control 103 | 104 | ## i18n 105 | 106 | - Rename `i18nManager.getSupportedLocale` to `i18nManager.getSupportedLocaleFor`. 107 | - Rename `i18nManager.getFallbackLocale` to `i18nManager.getFallbackLocaleFor`. 108 | - `provideValidatorMessages` config option was removed, in favor of `app/Middleware/DetectUserLocale`: 109 | ```ts 110 | export default class DetectUserLocaleMiddleware { 111 | static { 112 | RequestValidator.messagesProvider = (ctx) => { 113 | return ctx.i18n.createMessagesProvider() 114 | } 115 | } 116 | } 117 | ``` 118 | - Other config options (located at `config.i18n.ts`) changed: 119 | ```ts 120 | // Earlier 121 | import { I18nConfig } from '@ioc:Adonis/Addons/I18n' 122 | const i18nConfig: I18nConfig = { 123 | translationsFormat: 'icu', 124 | loaders: { 125 | fs: { 126 | enabled: true, 127 | location: app.resourcesPath('lang'), 128 | }, 129 | }, 130 | } 131 | 132 | // Now 133 | import { defineConfig, formatters, loaders } from '@adonisjs/i18n' 134 | const i18nConfig = defineConfig({ 135 | formatter: formatters.icu(), 136 | loaders: [ 137 | loaders.fs({ 138 | location: app.languageFilesPath() 139 | }) 140 | ], 141 | }) 142 | ``` 143 | 144 | ## Redis 145 | 146 | - No longer emit events using the AdonisJS global event emitter. These events were never documented. 147 | 148 | ## Ace 149 | 150 | - https://github.com/adonisjs/ace/releases/tag/v12.0.0-0 151 | 152 | ## Lucid 153 | 154 | - https://github.com/adonisjs/lucid/releases/tag/v19.0.0-0 155 | -------------------------------------------------------------------------------- /content/docs/other/stick_to_v5.md: -------------------------------------------------------------------------------- 1 | # Sticking to V5 2 | 3 | If you are not ready to migrate your application to AdonisJS v6, no worries. Just make sure to pin the following packages to these versions. Major versions above will be for AdonisJS 6 only. 4 | 5 | If you want to stick to v5, you should have the same major versions as the following : 6 | 7 | ## AdonisJS packages 8 | 9 | ```jsonc 10 | { 11 | "@adonisjs/ace": "^11.3.1", 12 | "@adonisjs/ally": "^4.1.5", 13 | "@adonisjs/application": "^5.3.0", 14 | "@adonisjs/assembler": "^5.9.6", 15 | "@adonisjs/attachment-lite": "^1.0.8", 16 | "@adonisjs/auth": "^8.2.3", 17 | "@adonisjs/bodyparser": "^8.1.9", 18 | "@adonisjs/bouncer": "^2.3.0", 19 | "@adonisjs/config": "^3.0.9", 20 | "@adonisjs/core": "^5.9.0", 21 | "@adonisjs/drive-gcs": "^1.1.2", 22 | "@adonisjs/drive-s3": "^1.3.3", 23 | "@adonisjs/drive": "^2.3.0", 24 | "@adonisjs/encryption": "^4.0.8", 25 | "@adonisjs/env": "^3.0.9", 26 | "@adonisjs/events": "^7.2.1", 27 | "@adonisjs/fold": "^8.2.0", 28 | "@adonisjs/hash": "^7.2.2", 29 | "@adonisjs/http-server": "^5.12.0", 30 | "@adonisjs/i18n": "^1.6.0", 31 | "@adonisjs/limiter": "^1.0.2", 32 | "@adonisjs/logger": "^4.1.5", 33 | "@adonisjs/lucid-slugify": "^2.2.1", 34 | "@adonisjs/lucid": "^18.4.2", 35 | "@adonisjs/mail": "^8.2.1", 36 | "@adonisjs/redis": "^7.3.4", 37 | "@adonisjs/repl": "^3.1.11", 38 | "@adonisjs/route-model-binding": "^1.0.1", 39 | "@adonisjs/session": "^6.4.0", 40 | "@adonisjs/shield": "^7.1.1", 41 | "@adonisjs/validator": "^12.6.0", 42 | "@adonisjs/view": "^6.2.0", 43 | } 44 | ``` 45 | 46 | ## Japa Packages 47 | 48 | ```jsonc 49 | { 50 | "@japa/api-client": "1.4.2", 51 | "@japa/browser-client": "^1.2.0", 52 | "@japa/expect": "^2.0.2", 53 | "@japa/file-system": "^1.1.0", 54 | "@japa/preset-adonis": "^1.2.0", 55 | "@japa/run-failed-tests": "^1.1.1", 56 | "@japa/runner": "^2.5.1", 57 | "@japa/snapshot": "1.0.1-3", 58 | "@japa/spec-reporter": "^1.3.3", 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /content/docs/other/vite_migration.md: -------------------------------------------------------------------------------- 1 | # Migrate to Vite 2 | 3 | Vite has become the de facto standard for building frontend applications. With V6 release, we ship an official integration for using Vite inside AdonisJS applications. 4 | 5 | We no longer recommend using Webpack Encore for new projects. However, we will continue to maintain this package for existing v5 applications. 6 | 7 | If you'd like to migrate to Vite later, then you can install the `@adonisjs/encore` package which we maintain to enable you to migrate more easily to V6. However note that no further improvements will be made to Webpack integration. We therefore recommend that you migrate to Vite as soon as possible. 8 | 9 | ## Installation 10 | 11 | First you will need to install and configure `@adonisjs/vite` : 12 | 13 | ```sh 14 | pnpm add @adonisjs/vite 15 | node ace configure @adonisjs/vite 16 | ``` 17 | 18 | This command will : 19 | - Create a `vite.config.ts` file at the root of your project 20 | - Create a basic `resources/js/app.js` file 21 | - Install `vite` as a devDependency 22 | 23 | :::note 24 | Depending on your project, you might need to install other vite plugins. Like `@vitejs/plugin-vue` for Vue.js projects or `@vitejs/plugin-react` for React projects. See below for more details. 25 | ::: 26 | 27 | ## Vite configuration 28 | 29 | You should have a new `vite.config.ts` file at the root of your project with the following content : 30 | 31 | ```ts 32 | // title: vite.config.ts 33 | import { defineConfig } from 'vite' 34 | import adonisjs from '@adonisjs/vite/client' 35 | 36 | export default defineConfig({ 37 | plugins: [ 38 | adonisjs({ 39 | /** 40 | * Entrypoints of your application. Each entrypoint will 41 | * result in a separate bundle. 42 | */ 43 | entrypoints: ['resources/js/app.js'], 44 | 45 | /** 46 | * Paths to watch and reload the browser on file change 47 | */ 48 | reload: ['resources/views/**/*.edge'], 49 | }), 50 | ], 51 | }) 52 | ``` 53 | 54 | Similar to Webpack Encore, you can define multiple entry points. Each entry point will result in a separate bundle. 55 | 56 | So make sure to add all your `Encore.addEntry()` calls to your new Vite configuration. 57 | 58 | ## Vite compatible imports 59 | 60 | Vite only supports ES modules, so you will need to replace any `require()` statements with `import`. 61 | 62 | ## Add `@vite()` tag 63 | 64 | Make sure you to replace your `entryPointStyles` and `entryPointScripts` calls with the `@vite()` tag : 65 | 66 | ```edge 67 | // title: resources/views/welcome.edge 68 | 69 | 70 | 71 | AdonisJS - A fully featured web framework for Node.js 72 | // highlight-start 73 | @vite(['resources/js/app.js']) 74 | // highlight-end 75 | @entryPointStyles('app') 76 | @entryPointScripts('app') 77 | 78 | ``` 79 | 80 | Note that with Webpack Encore you could specify a name for each entrypoint: 81 | 82 | ```ts 83 | Encore.addEntry('app', './resources/js/app.js') 84 | ``` 85 | 86 | With Vite, you only need to specify the file name, and use the same name in your template : 87 | 88 | ```ts 89 | // vite.config.ts 90 | export default defineConfig({ 91 | plugins: [ 92 | adonisjs({ 93 | entrypoints: ['resources/js/app.js'], 94 | }), 95 | ], 96 | }) 97 | ``` 98 | 99 | ```edge 100 | // title: resources/views/welcome.edge 101 | @vite(['resources/js/app.js']) 102 | ``` 103 | 104 | ## Typescript, CSS, Tailwind .. 105 | 106 | Typescript, CSS, Postcss, Less, Sass, Tailwind: these tools should work out of the box. You don't need to configure anything. 107 | 108 | ## Update environment variables 109 | 110 | You will need to update environnement variables that should be exposed to the client code. Vite defaults to `VITE_` prefix, so you will need to update your `.env` file accordingly. 111 | 112 | ```diff 113 | - MY_API_URL=http://localhost:3333 114 | + VITE_MY_API_URL=http://localhost:3333 115 | ``` 116 | 117 | You will also need to update your frontend code to use the new prefix : 118 | 119 | ```diff 120 | - const api = new Ky.create({ prefixUrl: process.env.MY_API_URL }) 121 | + const api = new Ky.create({ prefixUrl: import.meta.env.VITE_MY_API_URL }) 122 | ``` 123 | 124 | ## Vue 125 | 126 | You need to install the `@vitejs/plugin-vue` plugin and add it to your `vite.config.ts` file. 127 | 128 | ```ts 129 | // title: vite.config.ts 130 | import { defineConfig } from 'vite' 131 | import adonis from '@adonisjs/vite' 132 | import vue from '@vitejs/plugin-vue' 133 | 134 | export default defineConfig({ 135 | plugins: [ 136 | adonis({ 137 | entrypoints: ['resources/js/app.ts', 'resources/css/app.css'], 138 | }), 139 | // highlight-start 140 | vue() 141 | // highlight-end 142 | ], 143 | }) 144 | ``` 145 | 146 | 147 | ## React 148 | 149 | You will need to install the `@vitejs/plugin-react` plugin. 150 | 151 | ```ts 152 | // title: vite.config.ts 153 | import { defineConfig } from 'vite' 154 | import adonis from '@adonisjs/vite' 155 | import react from '@vitejs/plugin-react' 156 | 157 | export default defineConfig({ 158 | plugins: [ 159 | adonis({ 160 | entrypoints: ['resources/js/app.ts', 'resources/css/app.css'], 161 | }), 162 | // highlight-start 163 | react() 164 | // highlight-end 165 | ], 166 | }) 167 | ``` 168 | 169 | Also, for the HMR to work, you will need to add the following tag in your `edge` files : 170 | 171 | ```edge 172 | // title: resources/views/welcome.edge 173 | 174 | 175 | 176 | AdonisJS - A fully featured web framework for Node.js 177 | @vite() 178 | // highlight-start 179 | @viteReactRefresh() 180 | // highlight-end 181 | 182 | ``` 183 | 184 | ## Uninstall webpack 185 | 186 | You can remove webpack from your project by removing the following packages : 187 | 188 | ```bash 189 | npm rm @symfony/webpack-encore webpack webpack-cli @babel/core @babel/preset-env @adonisjs/encore 190 | rm webpack.config.js 191 | ``` 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonisjs-web-stater-kit", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "export": "vite build && npm run download:sponsors && node --loader=ts-node/esm bin/build.ts", 9 | "postexport": "copyfiles -u 1 public/* public/**/* dist", 10 | "download:sponsors": "node --loader=ts-node/esm bin/download_sponsors.ts", 11 | "start": "node bin/test.js", 12 | "serve": "node --loader=ts-node/esm bin/serve.ts", 13 | "dev": "concurrently \"vite\" \"npm run serve\"", 14 | "test": "node ace test", 15 | "format": "prettier --write ." 16 | }, 17 | "imports": { 18 | "#src/*": "./src/*.js" 19 | }, 20 | "devDependencies": { 21 | "@adonisjs/assembler": "^7.0.0", 22 | "@adonisjs/prettier-config": "^1.2.1", 23 | "@adonisjs/tsconfig": "^1.2.1", 24 | "@adonisjs/vite": "^2.0.2", 25 | "@alpinejs/collapse": "^3.13.3", 26 | "@alpinejs/persist": "^3.13.3", 27 | "@dimerapp/content": "^5.0.0", 28 | "@dimerapp/docs-theme": "^4.0.1", 29 | "@dimerapp/edge": "^5.0.0", 30 | "@docsearch/css": "^3.5.2", 31 | "@docsearch/js": "^3.5.2", 32 | "@swc/core": "^1.3.103", 33 | "@types/node": "^20.11.4", 34 | "alpinejs": "^3.13.3", 35 | "collect.js": "^4.36.1", 36 | "concurrently": "^8.2.2", 37 | "copyfiles": "^2.4.1", 38 | "edge-uikit": "^1.0.0-0", 39 | "medium-zoom": "^1.1.0", 40 | "pino-pretty": "^10.3.1", 41 | "prettier": "^3.2.4", 42 | "reflect-metadata": "^0.2.1", 43 | "ts-node": "^10.9.2", 44 | "typescript": "^5.3.3", 45 | "undici": "^6.3.0", 46 | "unpoly": "^3.7.2", 47 | "vite": "^5.0.11" 48 | }, 49 | "dependencies": { 50 | "@adonisjs/core": "^6.2.0", 51 | "@adonisjs/static": "^1.1.1", 52 | "@radix-ui/colors": "^3.0.0", 53 | "edge.js": "^6.0.1" 54 | }, 55 | "prettier": "@adonisjs/prettier-config" 56 | } 57 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | / /guides/introduction 2 | /guides /guides/introduction 3 | -------------------------------------------------------------------------------- /src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Bootstrap 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The bootstrap file configures everything needed to render markdown with 7 | | extreme control over the rendering pipeline 8 | | 9 | */ 10 | 11 | import edge from 'edge.js' 12 | import uiKit from 'edge-uikit' 13 | import collect from 'collect.js' 14 | import { dimer } from '@dimerapp/edge' 15 | import { readFile } from 'node:fs/promises' 16 | import { RenderingPipeline } from '@dimerapp/edge' 17 | import { Collection, Renderer } from '@dimerapp/content' 18 | import { docsHook, docsTheme } from '@dimerapp/docs-theme' 19 | 20 | import grammars from '../vscode_grammars/main.js' 21 | 22 | type CollectionEntry = Exclude, undefined> 23 | 24 | edge.use(dimer) 25 | edge.use(docsTheme) 26 | edge.use(uiKit) 27 | 28 | /** 29 | * Globally loads the config file 30 | */ 31 | edge.global('getConfig', async () => 32 | JSON.parse(await readFile(new URL('../content/config.json', import.meta.url), 'utf-8')) 33 | ) 34 | 35 | /** 36 | * Globally loads the sponsors file 37 | */ 38 | edge.global('getSponsors', async () => 39 | JSON.parse(await readFile(new URL('../content/sponsors.json', import.meta.url), 'utf-8')) 40 | ) 41 | 42 | /** 43 | * Returns sections for a collection 44 | */ 45 | edge.global('getSections', function (collection: Collection, entry: CollectionEntry) { 46 | const entries = collection.all() 47 | 48 | return collect(entries) 49 | .groupBy('meta.category') 50 | .map((items, key) => { 51 | return { 52 | title: key, 53 | isActive: entry.meta.category === key, 54 | items: items 55 | .filter((item: CollectionEntry & { draft?: boolean }) => { 56 | return !item.meta.draft 57 | }) 58 | .map((item: CollectionEntry) => { 59 | return { 60 | href: item.permalink, 61 | title: item.title, 62 | isActive: item.permalink === entry.permalink, 63 | } 64 | }) 65 | .all(), 66 | } 67 | }) 68 | .all() 69 | }) 70 | 71 | /** 72 | * Configuring rendering pipeline 73 | */ 74 | const pipeline = new RenderingPipeline() 75 | pipeline.use(docsHook).use((node) => { 76 | const src = node.properties?.src 77 | if (node.tagName === 'img' && (typeof src === 'string' && !src.startsWith('http'))) { 78 | return pipeline.component('elements/img', { node }) 79 | } 80 | }) 81 | 82 | // 'css-variables' | 'dark-plus' | 'dracula-soft' | 'dracula' | 'github-dark-dimmed' | 'github-dark' | 'github-light' | 'hc_light' | 'light-plus' | 'material-theme-darker' | 'material-theme-lighter' | 'material-theme-ocean' | 'material-theme-palenight' | 'material-theme' | 'min-dark' | 'min-light' | 'monokai' | 'nord' | 'one-dark-pro' | 'poimandres' | 'rose-pine-dawn' | 'rose-pine-moon' | 'rose-pine' | 'slack-dark' | 'slack-ochin' | 'solarized-dark' | 'solarized-light' | 'vitesse-dark' | 'vitesse-light'; 83 | 84 | /** 85 | * Configuring renderer 86 | */ 87 | export const renderer = new Renderer(edge, pipeline) 88 | .codeBlocksTheme('material-theme-palenight') 89 | .useTemplate('docs') 90 | 91 | /** 92 | * Adding grammars 93 | */ 94 | grammars.forEach((grammar) => renderer.registerLanguage(grammar)) 95 | -------------------------------------------------------------------------------- /src/collections.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Collections 4 | |-------------------------------------------------------------------------- 5 | | 6 | | Collections represents multiple sources of documentation. For example: 7 | | Guides can be one collection, blog can be another, and API docs can 8 | | be another collection 9 | | 10 | */ 11 | 12 | import { Collection } from '@dimerapp/content' 13 | import { renderer } from './bootstrap.js' 14 | 15 | const docs = new Collection() 16 | .db(new URL('../content/docs/db.json', import.meta.url)) 17 | .useRenderer(renderer) 18 | .urlPrefix('/guides') 19 | 20 | await docs.boot() 21 | 22 | export const collections = [docs] 23 | -------------------------------------------------------------------------------- /templates/docs.edge: -------------------------------------------------------------------------------- 1 | @component('layouts/main', { file }) 2 | @let(siteConfig = await getConfig()) 3 | 4 | @component('docs::header', siteConfig) 5 | @slot('logo') 6 |
7 | @include('partials/logo') 8 | | 9 | Migration guide 10 |
11 | @end 12 | 13 | @slot('logoMobile') 14 | @include('partials/logo_mobile') 15 | @end 16 | @end 17 | 18 |
19 | @!component('docs::sidebar', { 20 | collapsible: false, 21 | sections: getSections(collection, entry) 22 | }) 23 | 24 |
25 | @!component('docs::content_header', { title: file.frontmatter.title }) 26 | 27 | @component('docs::content', { 28 | title: file.frontmatter.title, 29 | copyright: siteConfig.copyright, 30 | fileEditUrl: `${siteConfig.fileEditBaseUrl}/${app.relativePath(file.filePath)}`, 31 | }) 32 | @!component('docs::doc_errors', { messages: file.messages }) 33 | @!component('dimer_contents', { nodes: file.ast.children, pipeline })~ 34 | @end 35 | 36 | @if(file.toc) 37 | @component('docs::toc', { sponsors: siteConfig.advertising_sponsors }) 38 | @!component('dimer_element', { node: file.toc, pipeline })~ 39 | @end 40 | @end 41 |
42 |
43 | @end 44 | -------------------------------------------------------------------------------- /templates/elements/img.edge: -------------------------------------------------------------------------------- 1 |
2 | @if(node.properties.src.startsWith('http://') || node.properties.src.startsWith('https://')) 3 | 4 | @else 5 | 6 | @end 7 |
8 | -------------------------------------------------------------------------------- /templates/layouts/main.edge: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | {{ file.frontmatter.title }} 13 | @if(file.frontmatter.summary) 14 | 15 | @end 16 | 17 | 18 | 19 | 20 | @if(file.frontmatter.summary) 21 | 22 | @end 23 | 24 | 25 | 26 | @if(file.frontmatter.summary) 27 | 28 | @end 29 | 30 | 31 | 32 | 36 | 37 | @vite(['assets/app.css', 'assets/app.js']) 38 | @include('partials/detect_color_mode') 39 | 40 | 41 | 42 | {{{ await $slots.main() }}} 43 | 44 | -------------------------------------------------------------------------------- /templates/partials/detect_color_mode.edge: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /templates/partials/logo.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/partials/logo_mobile.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /templates/partials/sponsors.edge: -------------------------------------------------------------------------------- 1 | @let(sponsors = await getSponsors()) 2 | 3 |
4 |

5 | Explain sponsorship program here 6 |

7 | 8 | {{-- Rendering Github sponsors as per their selected tier --}} 9 | @!component('docs::elements/sponsors', { 10 | sponsors, 11 | tier: 'gold', 12 | title: 'Gold sponsors', 13 | finder: (sponsor) => { 14 | return !sponsor.isOneTime && sponsor.monthlyDollars > 29 15 | } 16 | }) 17 | 18 | @!component('docs::elements/sponsors', { 19 | sponsors, 20 | tier: 'silver', 21 | title: 'Silver sponsors', 22 | finder: (sponsor) => { 23 | return !sponsor.isOneTime && sponsor.monthlyDollars === 29 24 | } 25 | }) 26 | 27 | @!component('docs::elements/sponsors', { 28 | sponsors, 29 | tier: 'basic', 30 | title: 'Sponsors', 31 | finder: (sponsor) => { 32 | return !sponsor.isOneTime && sponsor.monthlyDollars >= 19 && sponsor.monthlyDollars < 29 33 | } 34 | }) 35 | 36 | @!component('docs::elements/sponsors', { 37 | sponsors, 38 | tier: 'basic', 39 | title: 'Backers', 40 | finder: (sponsor) => { 41 | return !sponsor.isOneTime && sponsor.monthlyDollars >= 0 && sponsor.monthlyDollars < 19 42 | } 43 | }) 44 | 45 | @!component('docs::elements/sponsors', { 46 | sponsors, 47 | tier: 'previous', 48 | title: 'Past sponsors', 49 | finder: (sponsor) => { 50 | return sponsor.monthlyDollars === -1 || sponsor.isOneTime 51 | } 52 | }) 53 |
54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@adonisjs/tsconfig/tsconfig.app.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import adonisjs from '@adonisjs/vite/client' 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | adonisjs({ 7 | entrypoints: ['./assets/app.js', './assets/app.css'], 8 | reload: ['content/**/*', 'templates/**/*.edge'], 9 | }), 10 | ], 11 | }) 12 | -------------------------------------------------------------------------------- /vscode_grammars/dotenv.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DotENV", 3 | "scopeName": "source.env", 4 | "fileTypes": [ 5 | ".env", 6 | ".env-sample", 7 | ".env.example", 8 | ".env.local", 9 | ".env.dev", 10 | ".env.test", 11 | ".env.testing", 12 | ".env.production", 13 | ".env.prod" 14 | ], 15 | "uuid": "09d4e117-0975-453d-a74b-c2e525473f97", 16 | "patterns": [ 17 | { 18 | "comment": "Comments - starts with #", 19 | "match": "(#).*$\\n?", 20 | "name": "comment.line.number-sign.env", 21 | "captures": { 22 | "1": { 23 | "name": "punctuation.definition.comment.env" 24 | } 25 | } 26 | }, 27 | { 28 | "comment": "Strings (double)", 29 | "name": "string.quoted.double.env", 30 | "begin": "(\\\")", 31 | "beginCaptures": { 32 | "1": { 33 | "name": "punctuation.definition.string.begin.env" 34 | } 35 | }, 36 | "patterns": [ 37 | { 38 | "include": "#interpolation" 39 | }, 40 | { 41 | "include": "#variable" 42 | }, 43 | { 44 | "include": "#escape-characters" 45 | } 46 | ], 47 | "end": "(\\\")", 48 | "endCaptures": { 49 | "1": { 50 | "name": "punctuation.definition.string.end" 51 | } 52 | } 53 | }, 54 | { 55 | "comment": "Strings (single)", 56 | "name": "string.quoted.single.env", 57 | "begin": "(\\')", 58 | "beginCaptures": { 59 | "1": { 60 | "name": "punctuation.definition.string.begin.env" 61 | } 62 | }, 63 | "end": "(\\')", 64 | "endCaptures": { 65 | "1": { 66 | "name": "punctuation.definition.string.end" 67 | } 68 | } 69 | }, 70 | { 71 | "comment": "Assignment Operator", 72 | "match": "(?<=[\\w])\\s?=", 73 | "name": "keyword.operator.assignment.env" 74 | }, 75 | { 76 | "comment": "Variable", 77 | "match": "([\\w]+)(?=\\s?\\=)", 78 | "name": "variable.other.env" 79 | }, 80 | { 81 | "comment": "Keywords", 82 | "match": "(?i)\\s?(export)", 83 | "name": "keyword.other.env" 84 | }, 85 | { 86 | "comment": "Constants", 87 | "match": "(?i)(?<=\\=)\\s?(true|false|null)", 88 | "name": "constant.language.env" 89 | }, 90 | { 91 | "comment": "Numeric", 92 | "match": "\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)\\b", 93 | "name": "constant.numeric.env" 94 | } 95 | ], 96 | "repository": { 97 | "interpolation": { 98 | "comment": "Template Syntax: \"foo ${bar} {$baz}\"", 99 | "begin": "(\\$\\{|\\{)", 100 | "beginCaptures": { 101 | "1": { 102 | "name": "string.interpolated.env keyword.other.template.begin.env" 103 | } 104 | }, 105 | "patterns": [ 106 | { 107 | "match": "(?x)(\\$+)?([a-zA-Z_\\x{7f}-\\x{ff}][a-zA-Z0-9_\\x{7f}-\\x{ff}]*?\\b)", 108 | "captures": { 109 | "1": { 110 | "name": "punctuation.definition.variable.env variable.other.env" 111 | }, 112 | "2": { 113 | "name": "variable.other.env" 114 | } 115 | } 116 | } 117 | ], 118 | "end": "(\\})", 119 | "endCaptures": { 120 | "1": { 121 | "name": "string.interpolated.env keyword.other.template.end.env" 122 | } 123 | } 124 | }, 125 | "variable": { 126 | "patterns": [ 127 | { 128 | "match": "(?x)(\\$+)([a-zA-Z_\\x{7f}-\\x{ff}][a-zA-Z0-9_\\x{7f}-\\x{ff}]*?\\b)", 129 | "captures": { 130 | "1": { 131 | "name": "punctuation.definition.variable.env variable.other.env" 132 | }, 133 | "2": { 134 | "name": "variable.other.env" 135 | } 136 | } 137 | } 138 | ] 139 | }, 140 | "escape-characters": { 141 | "patterns": [ 142 | { 143 | "match": "\\\\[nrt\\\\\\$\\\"\\']", 144 | "name": "constant.character.escape.env" 145 | } 146 | ] 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /vscode_grammars/edge.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Edge", 4 | "scopeName": "text.html.edge", 5 | "injections": { 6 | "text.html.edge - (meta.embedded | meta.tag | comment.block.edge), L:(text.html.edge meta.tag - (comment.block.edge | meta.embedded.block.edge)), L:(source.js.embedded.html - (comment.block.edge | meta.embedded.block.edge))": { 7 | "patterns": [ 8 | { 9 | "include": "#comment" 10 | }, 11 | { 12 | "include": "#escapedMustache" 13 | }, 14 | { 15 | "include": "#safeMustache" 16 | }, 17 | { 18 | "include": "#mustache" 19 | }, 20 | { 21 | "include": "#nonSeekableTag" 22 | }, 23 | { 24 | "include": "#tag" 25 | } 26 | ] 27 | } 28 | }, 29 | "repository": { 30 | "comment": { 31 | "begin": "\\{{--", 32 | "end": "\\--}}", 33 | "beginCaptures": { 34 | "0": { "name": "punctuation.definition.comment.begin.edge" } 35 | }, 36 | "endCaptures": { 37 | "0": { "name": "punctuation.definition.comment.end.edge" } 38 | }, 39 | "name": "comment.block" 40 | }, 41 | "escapedMustache": { 42 | "begin": "\\@{{", 43 | "end": "\\}}", 44 | "beginCaptures": { 45 | "0": { "name": "punctuation.definition.comment.begin.edge" } 46 | }, 47 | "endCaptures": { 48 | "0": { "name": "punctuation.definition.comment.end.edge" } 49 | }, 50 | "name": "comment.block" 51 | }, 52 | "safeMustache": { 53 | "begin": "\\{{{", 54 | "end": "\\}}}", 55 | "beginCaptures": { 56 | "0": { "name": "punctuation.mustache.begin" } 57 | }, 58 | "endCaptures": { 59 | "0": { "name": "punctuation.mustache.end" } 60 | }, 61 | "name": "meta.embedded.block.javascript", 62 | "patterns": [{ "include": "source.js#expression" }] 63 | }, 64 | "mustache": { 65 | "begin": "\\{{", 66 | "end": "\\}}", 67 | "beginCaptures": { 68 | "0": { "name": "punctuation.mustache.begin" } 69 | }, 70 | "endCaptures": { 71 | "0": { "name": "punctuation.mustache.end" } 72 | }, 73 | "name": "meta.embedded.block.javascript", 74 | "patterns": [{ "include": "source.js#expression" }] 75 | }, 76 | "nonSeekableTag": { 77 | "match": "^(\\s*)((@{1,2})(!)?([a-zA-Z._]+))(~)?$", 78 | "captures": { 79 | "2": { "name": "support.function.edge" } 80 | }, 81 | "name": "meta.embedded.block.javascript", 82 | "patterns": [{ "include": "source.js#expression" }] 83 | }, 84 | "tag": { 85 | "begin": "^(\\s*)((@{1,2})(!)?([a-zA-Z._]+)(\\s{0,2}))(\\()", 86 | "beginCaptures": { 87 | "2": { "name": "support.function.edge" }, 88 | "7": { "name": "punctuation.paren.open" } 89 | }, 90 | "end": "\\)", 91 | "endCaptures": { 92 | "0": { "name": "punctuation.paren.close" } 93 | }, 94 | "name": "meta.embedded.block.javascript", 95 | "patterns": [{ "include": "source.js#expression" }] 96 | } 97 | }, 98 | "patterns": [ 99 | { 100 | "include": "text.html.basic" 101 | }, 102 | { 103 | "include": "text.html.derivative" 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /vscode_grammars/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | VSCode grammars 4 | |-------------------------------------------------------------------------- 5 | | 6 | | Export any custom VSCode languages from this file that you want to 7 | | use inside markdown codeblocks. 8 | | 9 | */ 10 | 11 | import { fileURLToPath } from 'node:url' 12 | import type { ILanguageRegistration } from '@dimerapp/shiki' 13 | 14 | export default [ 15 | { 16 | path: fileURLToPath(new URL('./dotenv.tmLanguage.json', import.meta.url)), 17 | scopeName: 'source.env', 18 | id: 'dotenv', 19 | }, 20 | { 21 | path: fileURLToPath(new URL('./edge.tmLanguage.json', import.meta.url)), 22 | scopeName: 'text.html.edge', 23 | id: 'edge', 24 | }, 25 | ] satisfies ILanguageRegistration[] 26 | --------------------------------------------------------------------------------