diff --git a/create-quasar/CONTRIBUTING.md b/create-quasar/CONTRIBUTING.md new file mode 100644 index 00000000000..a585f721a6c --- /dev/null +++ b/create-quasar/CONTRIBUTING.md @@ -0,0 +1,139 @@ +# Contributing to Quasar Create CLI + +Thank you for your interest in contributing to the Quasar Create CLI tool! This guide will help you understand how the CLI parameter system works and how to extend it when adding or modifying templates and prompts. + +## CLI Parameter System Overview + +The Quasar Create CLI supports both interactive and non-interactive modes. The parameter system is designed to be: + +1. **Modular**: Each template type manages its own parameters +2. **Extensible**: Easy to add new parameters or templates +3. **Dynamic**: Help text is generated based on template type +4. **Default-friendly**: Supports falling back to default values + +## How Parameters Work + +When a user runs the CLI with parameters, the following happens: + +1. Command-line arguments are parsed by `minimist` in `index.js` +2. Arguments are passed to the `utils` module via `setCliArgs()` +3. Template-specific parameter mappings are loaded from `templates//params.js` +4. Parameters are mapped to prompt names and used to answer prompts +5. For non-interactive mode (`--yes` flag), default values are used for missing parameters + +## Adding Parameters for New Templates or Prompts + +If you're adding a new template or modifying an existing one with new prompts, follow these steps: + +### 1. Update the Template's `params.js` File + +Each template has a `params.js` file in its directory that defines: +- Parameter mappings (CLI parameter → prompt name) +- Help text for each parameter + +```js +// Example: /templates/your-template/params.js +export default { + // Parameter name: prompt name + paramMap: { + 'type': 'type', + 'folder': 'folder', + 'your-param': 'yourPromptName', + // Add your new parameters here + }, + + // Help text for the parameters + helpText: { + 'type': 'Project type (your-template)', + 'folder': 'Project folder name', + 'your-param': 'Description of your parameter', + // Add help text for your parameters here + } +} +``` + +### 2. Handle Special Parameter Types + +Different prompt types need special handling: + +#### Text and Select Prompts + +These work automatically. For `select` prompts, validation against choices is handled for you. + +#### Multiselect Prompts + +For multiselect prompts, values are passed as comma-separated strings: + +```js +// CLI usage: --preset value1,value2,value3 +``` + +The system automatically converts these to the format expected by the prompts. + +#### Boolean Prompts + +For boolean prompts (confirm type), you can pass `true`, `false`, or just the flag name: + +```js +// All of these set the flag to true: +--your-boolean-flag +--your-boolean-flag true +--your-boolean-flag=true + +// To set it to false explicitly: +--your-boolean-flag false +``` + +### 3. Conditional Prompts + +If your prompt is conditional based on previous answers, the parameter system will handle this correctly. Just ensure your prompt's `type` function properly evaluates both interactive and non-interactive inputs: + +```js +{ + type: (_, { previousAnswer } = {}) => (previousAnswer ? 'text' : null), + name: 'conditionalPrompt', + message: 'This only appears if previousAnswer is true' +} +``` + +### 4. Default Values + +When using non-interactive mode with the `--yes` flag, the system uses default values for missing parameters: + +- For `select` prompts, it uses the item with `selected: true` or the first item +- For `text` prompts, it uses the `initial` value +- For function-based values, it calls the function to get the default + +Make sure your prompts have appropriate default values! + +## Testing Your Parameters + +Always test your parameters in both interactive and non-interactive modes: + +```bash +# Test help for your template +node create-quasar/index.js --help --type your-template + +# Test with specific parameters +node create-quasar/index.js --type your-template --your-param value + +# Test non-interactive mode +node create-quasar/index.js --type your-template --yes +``` + +## Common Issues and Solutions + +- **Parameter not working**: Check the mapping in your template's `params.js` file +- **Wrong default used**: Ensure default values are correctly set in the prompt definition +- **Conditional prompt not working**: Make sure the condition function handles both interactive and CLI inputs +- **Parameter validation failing**: Ensure CLI validation matches the interactive prompt validation + +## Best Practices + +1. **Keep help text clear and concise**, including expected values or format +2. **Document all parameters** in the template's `params.js` file +3. **Use consistent naming** between CLI parameters and prompt names +4. **Test all parameters** in both interactive and non-interactive modes +5. **Include example usage** in comments or documentation + +By following these guidelines, you'll ensure that Quasar CLI maintains its excellent developer experience while being extensible for future needs. diff --git a/create-quasar/README.md b/create-quasar/README.md index 6878c88180f..c956276db89 100644 --- a/create-quasar/README.md +++ b/create-quasar/README.md @@ -22,6 +22,62 @@ pnpm create quasar bun create quasar ``` +## Command-Line Parameters + +The create-quasar tool supports both interactive and non-interactive modes. By default, it runs in interactive mode, prompting you for each option. However, you can also provide command-line parameters to automate the project creation process. + +### Basic Usage + +```bash +npm init quasar -- --type app --folder my-project +# or +yarn create quasar --type app --folder my-project +``` + +### Non-Interactive Mode + +To run in fully non-interactive mode, use the `--yes` flag. This will use default values for any parameters not explicitly specified: + +```bash +npm init quasar -- --type app --folder my-project --yes +``` + +### Template-Specific Options + +Each template type (app, app-extension, ui-kit) has its own set of specific options. To see the available options for a particular template type, use: + +```bash +npm init quasar -- --help --type +``` + +For example: + +```bash +npm init quasar -- --help --type app +npm init quasar -- --help --type app-extension +npm init quasar -- --help --type ui-kit +``` + +### Examples + +#### Creating a standard app with specific options: + +```bash +npm init quasar -- --type app --folder my-app --script-type js --engine vite-2 --preset eslint,pinia,axios --css scss --yes +``` + +#### Creating an app extension: + +```bash +npm init quasar -- --type app-extension --folder my-extension --name my-ext --description "My Quasar Extension" --preset prompts,install --yes +``` + +#### Creating a UI kit: + +```bash +npm init quasar -- --type ui-kit --folder my-ui-kit --name my-component --features component,ae --yes +``` + ## Supporting Quasar Quasar Framework is an MIT-licensed open source project. Its ongoing development is made possible thanks to the support by these awesome [backers](https://github.com/rstoenescu/quasar-framework/blob/dev/backers.md). diff --git a/create-quasar/index.js b/create-quasar/index.js index 40a2e328f94..6c3d70af1de 100755 --- a/create-quasar/index.js +++ b/create-quasar/index.js @@ -21,15 +21,97 @@ import parseArgs from 'minimist' const argv = parseArgs(process.argv.slice(2), { alias: { - n: 'nogit', + h: 'help', + y: 'yes' }, - boolean: [ 'n' ], + boolean: [ 'help', 'nogit', 'prettier', 'yes' ], + string: [ 'type', 'folder', 'preset', 'package-manager', 'script-type', 'engine', + 'name', 'product-name', 'description', 'sfc-style', 'css' ], }) +// Show help if requested +if (argv.help) { + const validTypes = ['app', 'app-extension', 'ui-kit'] + + // Dynamic help text loading function + const showHelp = async () => { + // Common options for all templates + const baseOptions = [ + ['type', 'Project type (app, app-extension, ui-kit)'], + ['folder', 'Project folder name'], + ['nogit', 'Skip git initialization'], + ['yes, -y', 'Non-interactive mode, use default values for missing options'], + ['help, -h', 'Show this help message'] + ] + + // If a specific type is selected, show detailed help for that type + if (argv.type && validTypes.includes(argv.type)) { + const selectedType = argv.type + let templateSpecificOptions = [] + + try { + // Load params from the selected template + const { default: params } = await import(`./templates/${selectedType}/params.js`) + if (params && params.helpText) { + templateSpecificOptions = Object.entries(params.helpText).map(([key, desc]) => [key, desc]) + } + } catch (err) { + console.error(`Could not load help text for template type: ${selectedType}`) + } + + // Print header for specific template + console.log(` + Usage: create-quasar [options] + + Options for ${selectedType} projects:`) + + // Print all options for the specific template + const allOptions = [...baseOptions, ...templateSpecificOptions] + allOptions.forEach(([key, desc]) => { + console.log(` --${key.padEnd(20)} ${desc}`) + }) + } else { + // General help without a specific template selected + console.log(` + Usage: create-quasar [options] + + Basic Options:`) + + // Print basic options + baseOptions.forEach(([key, desc]) => { + console.log(` --${key.padEnd(20)} ${desc}`) + }) + + // Show template selection information + console.log(` + Available Template Types: + app Standard Quasar application + app-extension Quasar App Extension + ui-kit UI component library + + To see template-specific options, use: + create-quasar --help --type + + Examples: + create-quasar --help --type app + create-quasar --help --type app-extension + create-quasar --help --type ui-kit`) + } + + console.log() // Empty line for better formatting + process.exit(0) + } + + showHelp() +} + const defaultProjectFolder = 'quasar-project' const scope = {} +// Pass command-line arguments to utils module +utils.setCliArgs(argv) + await utils.prompts(scope, [ { type: 'select', diff --git a/create-quasar/templates/app-extension/params.js b/create-quasar/templates/app-extension/params.js new file mode 100644 index 00000000000..2d739e0c4f5 --- /dev/null +++ b/create-quasar/templates/app-extension/params.js @@ -0,0 +1,41 @@ +/** + * Parameter mappings for app-extension template + * + * CONTRIBUTOR GUIDE: + * ---------------- + * When adding new prompts to this template, follow these steps: + * 1. Add a mapping from CLI parameter to prompt name in the paramMap object + * 2. Add help text for the parameter in the helpText object + * 3. Make sure the parameter name is kebab-case (e.g., 'need-org-name') for CLI + * 4. Ensure the prompt name matches what's used in your prompts function + * 5. Test both interactive and non-interactive modes + * + * See /create-quasar/CONTRIBUTING.md for more details on the parameter system + */ +export default { + // Parameter name: prompt name + paramMap: { + 'type': 'type', + 'folder': 'folder', + 'name': 'name', + 'need-org-name': 'needOrgName', + 'org-name': 'orgName', + 'description': 'description', + 'license': 'license', + 'code-format': 'codeFormat', + 'preset': 'preset' + }, + + // Help text for the parameters + helpText: { + 'type': 'Project type (app-extension)', + 'folder': 'Project folder name', + 'name': 'Quasar App Extension ext-id (without "quasar-app-extension" prefix)', + 'need-org-name': 'Use organization name (boolean)', + 'org-name': 'Organization name', + 'description': 'Project description', + 'license': 'License type (MIT, Apache-2.0, etc.)', + 'code-format': 'Code format (esm, commonjs)', + 'preset': 'Features preset (comma-separated: prompts,install,uninstall)' + } +} diff --git a/create-quasar/templates/app/params.js b/create-quasar/templates/app/params.js new file mode 100644 index 00000000000..871228ecf5f --- /dev/null +++ b/create-quasar/templates/app/params.js @@ -0,0 +1,47 @@ +/** + * Parameter mappings for app template + * + * CONTRIBUTOR GUIDE: + * ---------------- + * When adding new prompts to this template, follow these steps: + * 1. Add a mapping from CLI parameter to prompt name in the paramMap object + * 2. Add help text for the parameter in the helpText object + * 3. Make sure the parameter name is kebab-case (e.g., 'script-type') for CLI + * 4. Ensure the prompt name matches what's used in your prompts function + * 5. Test both interactive and non-interactive modes + * + * See /create-quasar/CONTRIBUTING.md for more details on the parameter system + */ +export default { + // Parameter name: prompt name + paramMap: { + 'type': 'type', + 'folder': 'folder', + 'package-manager': 'package-manager', + 'script-type': 'scriptType', + 'engine': 'engine', + 'name': 'name', + 'product-name': 'productName', + 'description': 'description', + 'sfc-style': 'sfcStyle', + 'css': 'css', + 'preset': 'preset', + 'prettier': 'prettier' + }, + + // Help text for the parameters + helpText: { + 'type': 'Project type (app)', + 'folder': 'Project folder name', + 'script-type': 'Script type (js, ts)', + 'engine': 'Engine variant (vite-2, webpack-4)', + 'name': 'Package name', + 'product-name': 'Product name', + 'description': 'Project description', + 'preset': 'Features preset (comma-separated: eslint,pinia,axios,i18n)', + 'prettier': 'Add Prettier for code formatting (boolean)', + 'sfc-style': 'Vue component style (composition-setup, composition, options)', + 'css': 'CSS preprocessor (scss, sass, css)', + 'package-manager': 'Package manager to use (yarn, npm, pnpm, bun)' + } +} diff --git a/create-quasar/templates/app/quasar-v2/js-vite-2/index.js b/create-quasar/templates/app/quasar-v2/js-vite-2/index.js index 4fc069129c6..7a78c9779ed 100644 --- a/create-quasar/templates/app/quasar-v2/js-vite-2/index.js +++ b/create-quasar/templates/app/quasar-v2/js-vite-2/index.js @@ -1,4 +1,9 @@ export async function script ({ scope, utils }) { + // Initialize preset if it doesn't exist yet (for command-line usage) + if (!scope.preset) { + scope.preset = {} + } + await utils.prompts(scope, [ { type: 'multiselect', @@ -13,7 +18,7 @@ export async function script ({ scope, utils }) { format: utils.convertArrayToObject }, { - type: (_, { preset }) => (preset.eslint ? 'confirm' : null), + type: (_, { preset }) => (preset && preset.eslint ? 'confirm' : null), name: 'prettier', initial: true, message: 'Add Prettier for code formatting?' diff --git a/create-quasar/templates/ui-kit/params.js b/create-quasar/templates/ui-kit/params.js new file mode 100644 index 00000000000..6743fbcde0a --- /dev/null +++ b/create-quasar/templates/ui-kit/params.js @@ -0,0 +1,45 @@ +/** + * Parameter mappings for ui-kit template + * + * CONTRIBUTOR GUIDE: + * ---------------- + * When adding new prompts to this template, follow these steps: + * 1. Add a mapping from CLI parameter to prompt name in the paramMap object + * 2. Add help text for the parameter in the helpText object + * 3. Make sure the parameter name is kebab-case (e.g., 'features') for CLI + * 4. Ensure the prompt name matches what's used in your prompts function + * 5. Test both interactive and non-interactive modes + * + * See /create-quasar/CONTRIBUTING.md for more details on the parameter system + */ +export default { + // Parameter name: prompt name + paramMap: { + 'type': 'type', + 'folder': 'folder', + 'name': 'name', + 'license': 'license', + 'features': 'features', + 'package-description': 'packageDescription', + 'umd-export-name': 'umdExportName', + 'component-name': 'componentName', + 'directive-name': 'directiveName', + 'ae-description': 'aeDescription', + 'preset': 'preset' + }, + + // Help text for the parameters + helpText: { + 'type': 'Project type (ui-kit)', + 'folder': 'Project folder name', + 'name': 'Project name (npm name, without "quasar-ui" prefix)', + 'license': 'License type (MIT, Apache-2.0, etc.)', + 'features': 'Features (comma-separated: component,directive,ae)', + 'package-description': 'Package description', + 'umd-export-name': 'UMD export name (global variable, camelCased)', + 'component-name': 'Component name (PascalCase)', + 'directive-name': 'Directive name (kebab-case, without "v-" prefix)', + 'ae-description': 'App Extension description', + 'preset': 'App Extension scripts (comma-separated: prompts,install,uninstall)' + } +} diff --git a/create-quasar/utils/index.js b/create-quasar/utils/index.js index 1ca0516be88..4576b1205de 100644 --- a/create-quasar/utils/index.js +++ b/create-quasar/utils/index.js @@ -13,15 +13,190 @@ import logger from './logger.js' const TEMPLATING_FILE_EXTENSIONS = [ '', '.json', '.js', '.cjs', '.ts', '.vue', '.md', '.html', '.sass' ] +// Global argv reference for non-interactive mode +let cliArgs = null + +// Global parameter mappings cache +let templateParamMaps = {} + +// Function to set CLI args from outside +function setCliArgs(args) { + cliArgs = args +} + +// Function to load parameter mappings for a specific template type +async function loadTemplateParams(templateType) { + if (templateParamMaps[templateType]) { + return templateParamMaps[templateType] + } + + try { + const { default: params } = await import(`../templates/${templateType}/params.js`) + templateParamMaps[templateType] = params + return params + } catch (err) { + logger.warn(`Could not load parameters for template type: ${templateType}`) + return { paramMap: {} } + } +} + async function prompts (scope, questions, opts) { const options = opts || { onCancel: () => { logger.fatal('Scaffolding cancelled') } } - - const answers = await promptUser(questions, options) - Object.assign(scope, answers) + + // Get template-specific parameter mappings + const templateType = scope.projectType || (cliArgs && cliArgs.type) || 'app' + const { paramMap } = await loadTemplateParams(templateType) + + // Check if we have command-line arguments and if we're in non-interactive mode + const nonInteractive = cliArgs && cliArgs.yes === true + + if (cliArgs) { + const answers = {} + + // Process each question to see if we have a CLI arg for it + for (const question of questions) { + const { name, type, choices, initial } = question + + // Skip conditional questions that would not be asked + if (typeof type === 'function') { + const shouldAsk = type(cliArgs, scope) + if (!shouldAsk) continue + } + + // Handle CLI arguments based on question name + let answered = false + + // Check if we have a direct mapping for this parameter + const cliArgName = paramMap[name] || name + + if (cliArgs[cliArgName] !== undefined) { + // We have a CLI argument for this parameter + if (type === 'multiselect') { + const values = cliArgs[cliArgName].split(',') + answers[name] = convertArrayToObject(values) + } else if (type === 'select' && choices) { + // Validate that the provided value is in the choices + // Handle both array choices and function choices + let choicesArray = choices + if (typeof choices === 'function') { + try { + choicesArray = choices() + } catch (e) { + // If we can't evaluate the choices function, just accept the provided value + answers[name] = cliArgs[cliArgName] + answered = true + continue + } + } + + // Make sure choicesArray is actually an array + if (Array.isArray(choicesArray)) { + const validValues = choicesArray.map(choice => choice.value) + if (validValues.includes(cliArgs[cliArgName])) { + answers[name] = cliArgs[cliArgName] + } else { + // Use default value if provided value is not valid + const defaultChoice = choicesArray.find(choice => choice.selected) || choicesArray[0] + answers[name] = defaultChoice.value + logger.warn(`Invalid value for ${cliArgName}: ${cliArgs[cliArgName]}. Using default: ${defaultChoice.value}`) + } + } else { + // If choices isn't an array, just accept the provided value + answers[name] = cliArgs[cliArgName] + } + } else if (type === 'confirm') { + // Handle boolean values + answers[name] = cliArgs[cliArgName] === true || cliArgs[cliArgName] === 'true' + } else if (name === 'projectFolder') { + // Special handling for project folder + const folderName = (cliArgs[cliArgName] && cliArgs[cliArgName].trim()) || 'quasar-project' + scope.projectFolderName = folderName.split('/').pop() + answers[name] = join(process.cwd(), folderName) + } else { + // For all other types, use the value directly + answers[name] = cliArgs[cliArgName] + } + answered = true + } + + // For non-interactive mode, use default values if no CLI argument is provided + if (!answered && nonInteractive) { + // Use default values in non-interactive mode + if (type === 'select' || type === 'text') { + let defaultValue; + + // Get the default value from the choices or initial + if (type === 'select' && choices) { + let choicesArray = choices; + if (typeof choices === 'function') { + try { + choicesArray = choices(); + } catch (e) { + choicesArray = []; + } + } + + if (Array.isArray(choicesArray)) { + const defaultChoice = choicesArray.find(choice => choice.selected) || choicesArray[0]; + defaultValue = defaultChoice ? defaultChoice.value : undefined; + } + } else if (initial !== undefined) { + defaultValue = typeof initial === 'function' ? initial() : initial; + } + + if (defaultValue !== undefined) { + answers[name] = defaultValue; + answered = true; + logger.info(`Using default value for ${name}: ${defaultValue}`); + } + } else if (type === 'multiselect' && name === 'preset') { + // For preset in non-interactive mode, include all recommended options by default + if (Array.isArray(choices)) { + const recommendedChoices = choices + .filter(choice => choice.selected || choice.description === 'recommended') + .map(choice => choice.value); + + answers[name] = convertArrayToObject(recommendedChoices); + answered = true; + logger.info(`Using recommended options for ${name}: ${recommendedChoices.join(', ')}`); + } + } else if (type === 'confirm') { + // Default to true for confirmation questions in non-interactive mode + answers[name] = initial !== undefined ? initial : true; + answered = true; + logger.info(`Using default value for ${name}: ${answers[name]}`); + } + } + + // If we still haven't answered this question and we're not in non-interactive mode, + // we need to prompt the user + if (!answered && !nonInteractive) { + // Fallback to interactive mode for this question + const singleQuestion = { + ...question, + initial: question.initial instanceof Function ? question.initial() : question.initial + } + + const singleAnswer = await promptUser(singleQuestion, options) + Object.assign(answers, singleAnswer) + } else if (!answered && nonInteractive) { + // If we're in non-interactive mode and don't have an answer or default, + // log a warning and use a reasonable default + logger.warn(`No value or default for ${name} in non-interactive mode. Using empty string.`); + answers[name] = ''; + } + } + + Object.assign(scope, answers) + } else { + // Original behavior - fully interactive mode + const answers = await promptUser(questions, options) + Object.assign(scope, answers) + } } function createTargetDir (scope) { @@ -353,6 +528,7 @@ export async function injectAuthor (scope) { export default { logger, + setCliArgs, prompts, createTargetDir, convertArrayToObject, diff --git a/docs/src/pages/start/quasar-cli.md b/docs/src/pages/start/quasar-cli.md index 0289bae7004..53503754a44 100644 --- a/docs/src/pages/start/quasar-cli.md +++ b/docs/src/pages/start/quasar-cli.md @@ -92,7 +92,9 @@ Pick `Quasar CLI with Vite` if you want: 4. Answer the rest of the questions and you're almost done. -5. Now, do you want to be able to run Quasar CLI commands directly (eg. `$ quasar dev/build`) or through yarn or npx (`$ yarn quasar dev/build` / `npx quasar dev/build`)? +5. You can also use command-line parameters to create a project without interactive prompts. See the [Non-Interactive Mode](#non-interactive-mode) section below for details. + +6. Now, do you want to be able to run Quasar CLI commands directly (eg. `$ quasar dev/build`) or through yarn or npx (`$ yarn quasar dev/build` / `npx quasar dev/build`)? We strongly recommend to pick the first choice and be able to run commands directly. Actually, you'll be able to do even more than just this (eg. "quasar upgrade" or "quasar serve" commands). For this, you'll need to globally install the `@quasar/cli` package: @@ -181,6 +183,97 @@ Here are the options: # ..etc ``` +## Non-Interactive Mode + +Quasar CLI supports creating projects in non-interactive mode by providing command-line parameters. This is particularly useful for automation, CI/CD pipelines, or when you want to quickly scaffold projects with predefined settings. + +### Basic Usage + +```bash +# Using Yarn +yarn create quasar --type app --folder my-project + +# Using NPM +npm init quasar -- --type app --folder my-project + +# Using PNPM +pnpm create quasar --type app --folder my-project + +# Using Bun +bun create quasar --type app --folder my-project +``` + +### Fully Non-Interactive Mode + +To run in fully non-interactive mode, use the `--yes` flag. This will use default values for any parameters not explicitly specified: + +```bash +yarn create quasar --type app --folder my-project --yes +``` + +### Template-Specific Options + +Quasar CLI supports three main template types: `app`, `app-extension`, and `ui-kit`. Each template type has its own set of specific options. + +To see the available options for a particular template type, use: + +```bash +yarn create quasar --help --type +``` + +For example: + +```bash +yarn create quasar --help --type app +yarn create quasar --help --type app-extension +yarn create quasar --help --type ui-kit +``` + +### Common Options + +| Option | Description | +| --- | --- | +| `--type` | Project type (app, app-extension, ui-kit) | +| `--folder` | Project folder name | +| `--nogit` | Skip git initialization | +| `--yes, -y` | Non-interactive mode, use default values for missing options | +| `--help, -h` | Show help message | + +### App Template Options + +| Option | Description | +| --- | --- | +| `--script-type` | Script type (js, ts) | +| `--engine` | Engine variant (vite-2, webpack-4) | +| `--name` | Package name | +| `--product-name` | Product name | +| `--description` | Project description | +| `--preset` | Features preset (comma-separated: eslint,pinia,axios,i18n) | +| `--prettier` | Add Prettier for code formatting (boolean) | +| `--sfc-style` | Vue component style (composition-setup, composition, options) | +| `--css` | CSS preprocessor (scss, sass, css) | +| `--package-manager` | Package manager to use (yarn, npm, pnpm, bun) | + +### Examples + +#### Creating a standard app with specific options: + +```bash +yarn create quasar --type app --folder my-app --script-type js --engine vite-2 --preset eslint,pinia,axios --css scss --yes +``` + +#### Creating an app extension: + +```bash +yarn create quasar --type app-extension --folder my-extension --name my-ext --description "My Quasar Extension" --preset prompts,install --yes +``` + +#### Creating a UI kit: + +```bash +yarn create quasar --type ui-kit --folder my-ui-kit --name my-component --features component,ae --yes +``` + ## What next?