diff --git a/.changeset/sixty-terms-decide.md b/.changeset/sixty-terms-decide.md new file mode 100644 index 000000000000..b28a15626322 --- /dev/null +++ b/.changeset/sixty-terms-decide.md @@ -0,0 +1,5 @@ +--- +'create-svelte': patch +--- + +add eslint and prettier setup options diff --git a/packages/create-svelte/cli/global.d.ts b/packages/create-svelte/cli/global.d.ts new file mode 100644 index 000000000000..639895aac9bc --- /dev/null +++ b/packages/create-svelte/cli/global.d.ts @@ -0,0 +1,12 @@ +declare module '*.gitignore'; + +declare module 'gitignore-parser'; + +declare module 'prompts/lib/index'; + +declare module 'tiny-glob/sync'; + +declare module '*.json'; + +// TODO make this depend on the real types from the kit package +declare module '@sveltejs/kit/filesystem'; diff --git a/packages/create-svelte/cli/index.js b/packages/create-svelte/cli/index.js index 50df87f7db00..f17d7e881cac 100644 --- a/packages/create-svelte/cli/index.js +++ b/packages/create-svelte/cli/index.js @@ -11,28 +11,14 @@ import add_css from './modifications/add_css'; import add_typescript from './modifications/add_typescript'; // import versions from './versions'; import { version } from '../package.json'; +import add_prettier from './modifications/add_prettier'; +import add_eslint from './modifications/add_eslint'; const disclaimer = ` -█████████ ███████████ ███████ ███████████ ███ -███░░░░░███░█░░░███░░░█ ███░░░░░███ ░░███░░░░░███░███ -░███ ░░░ ░ ░███ ░ ███ ░░███ ░███ ░███░███ -░░█████████ ░███ ░███ ░███ ░██████████ ░███ -░░░░░░░░███ ░███ ░███ ░███ ░███░░░░░░ ░███ -███ ░███ ░███ ░░███ ███ ░███ ░░░ -░░█████████ █████ ░░░███████░ █████ ███ -░░░░░░░░░ ░░░░░ ░░░░░░░ ░░░░░ ░░░ - -Pump the brakes! A little disclaimer... - -svelte@next is not ready for use yet. It definitely can't -run your apps, and it might not run at all. - -We haven't yet started accepting community contributions, -and we don't need people to start raising issues yet. - -Given these warnings, please feel free to experiment, but -you're on your own for now. We'll have something to show -soon. +Welcome to the SvelteKit setup wizard! + +SvelteKit is in public beta now. There are definitely bugs and some feature might not work yet. +If you encounter an issue, have a look at https://github.com/sveltejs/kit/issues and open a new one, if it is not already tracked. `; async function main() { @@ -90,6 +76,12 @@ async function main() { await prompt_modifications(target); + console.log( + '\nWant to add other parts to your code base? ' + + 'Visit https://github.com/svelte-add/svelte-adders, a community project of commands ' + + 'to add particular functionality to Svelte projects\n' + ); + console.log('\nNext steps:'); let i = 1; @@ -105,6 +97,11 @@ async function main() { console.log('\nStuck? Visit us at https://svelte.dev/chat\n'); } +/** + * Go through the prompts to let the user setup his project. + * + * @param {string} target + */ async function prompt_modifications(target) { const ts_response = await prompts({ type: 'confirm', @@ -125,6 +122,22 @@ async function prompt_modifications(target) { ] }); await add_css(target, css_response.value); + + const eslint_response = await prompts({ + type: 'confirm', + name: 'value', + message: 'Add ESLint for code linting?', + initial: false + }); + await add_eslint(target, eslint_response.value, ts_response.value); + + const prettier_response = await prompts({ + type: 'confirm', + name: 'value', + message: 'Add Prettier for code formatting?', + initial: false + }); + await add_prettier(target, prettier_response.value, eslint_response.value); } main(); diff --git a/packages/create-svelte/cli/modifications/add_css.js b/packages/create-svelte/cli/modifications/add_css.js index 5b73da8bfdf4..0213e7070b34 100644 --- a/packages/create-svelte/cli/modifications/add_css.js +++ b/packages/create-svelte/cli/modifications/add_css.js @@ -1,11 +1,21 @@ import { bold, green } from 'kleur/colors'; -import { add_svelte_preprocess_to_config, update_component, update_package_json } from './utils'; +import { + add_svelte_preprocess_to_config, + update_component, + update_package_json_dev_deps +} from './utils'; +/** + * Add chosen CSS language to the project. + * + * @param {string} cwd + * @param {'css' | 'scss' | 'less'} which + */ export default async function add_css(cwd, which) { if (which === 'css') { console.log('You can add support for CSS preprocessors like SCSS/Less/PostCSS later.'); } else if (which === 'less') { - update_package_json(cwd, { + update_package_json_dev_deps(cwd, { less: '^3.0.0', 'svelte-preprocess': '^4.0.0' }); @@ -21,7 +31,7 @@ export default async function add_css(cwd, which) { ) ); } else if (which === 'scss') { - update_package_json(cwd, { + update_package_json_dev_deps(cwd, { sass: '^1.0.0', 'svelte-preprocess': '^4.0.0' }); diff --git a/packages/create-svelte/cli/modifications/add_eslint.js b/packages/create-svelte/cli/modifications/add_eslint.js new file mode 100644 index 000000000000..0018c042fe25 --- /dev/null +++ b/packages/create-svelte/cli/modifications/add_eslint.js @@ -0,0 +1,48 @@ +import { bold, green } from 'kleur/colors'; +import { + copy_from_template_additions, + update_package_json_dev_deps, + upsert_package_json_scripts +} from './utils'; + +/** + * Add ESLint if user wants it. + * + * @param {string} cwd + * @param {boolean} yes + * @param {boolean} use_typescript + */ +export default async function add_eslint(cwd, yes, use_typescript) { + if (!yes) { + return; + } + + upsert_package_json_scripts(cwd, { + lint: 'eslint .' + }); + + if (use_typescript) { + update_package_json_dev_deps(cwd, { + '@typescript-eslint/eslint-plugin': '^4.19.0', + '@typescript-eslint/parser': '^4.19.0', + eslint: '^7.22.0', + 'eslint-plugin-svelte3': '^3.1.0' + }); + copy_from_template_additions(cwd, { from: ['.eslintrc.ts.cjs'], to: ['.eslintrc.cjs'] }); + } else { + update_package_json_dev_deps(cwd, { + eslint: '^7.22.0', + 'eslint-plugin-svelte3': '^3.1.0' + }); + copy_from_template_additions(cwd, ['.eslintrc.cjs']); + } + + console.log( + bold( + green( + '✔ Added ESLint.\n' + + 'Readme for ESLint and Svelte: https://github.com/sveltejs/eslint-plugin-svelte3' + ) + ) + ); +} diff --git a/packages/create-svelte/cli/modifications/add_prettier.js b/packages/create-svelte/cli/modifications/add_prettier.js new file mode 100644 index 000000000000..d68e7d0fd634 --- /dev/null +++ b/packages/create-svelte/cli/modifications/add_prettier.js @@ -0,0 +1,67 @@ +import fs from 'fs'; +import { bold, green } from 'kleur/colors'; +import { join } from 'path'; +import { + copy_from_template_additions, + update_package_json_dev_deps, + upsert_package_json_scripts +} from './utils'; + +/** + * Add TypeScript if user wants it. + * + * @param {string} cwd + * @param {boolean} yes + * @param {boolean} usesEslint + */ +export default async function add_prettier(cwd, yes, usesEslint) { + if (!yes) { + return; + } + + update_package_json_dev_deps(cwd, { + prettier: '~2.2.1', + 'prettier-plugin-svelte': '^2.2.0' + }); + copy_from_template_additions(cwd, ['.prettierrc']); + copy_from_template_additions(cwd, ['.prettierignore']); + + if (usesEslint) { + update_package_json_dev_deps(cwd, { + 'eslint-config-prettier': '^8.1.0' + }); + add_prettier_to_eslint_extends(cwd); + upsert_package_json_scripts(cwd, { + lint: 'prettier --check . && eslint .', + format: 'prettier --write .' + }); + } else { + upsert_package_json_scripts(cwd, { + lint: 'prettier --check .', + format: 'prettier --write .' + }); + } + + console.log( + bold( + green( + '✔ Added Prettier.\n' + + 'General formatting options: https://prettier.io/docs/en/options.html\n' + + 'Svelte-specific formatting options: https://github.com/sveltejs/prettier-plugin-svelte#options' + ) + ) + ); +} + +/** + * @param {string} cwd + */ +function add_prettier_to_eslint_extends(cwd) { + const file = join(cwd, '.eslintrc.cjs'); + let code = fs.readFileSync(file, 'utf-8'); + + const insertIdx = code.indexOf(']', code.indexOf('extends: [')); + code = code.substring(0, insertIdx) + ", 'prettier'" + code.substring(insertIdx); + + fs.writeFileSync(file, code, 'utf-8'); +} diff --git a/packages/create-svelte/cli/modifications/add_typescript.js b/packages/create-svelte/cli/modifications/add_typescript.js index 17187c8b3a71..e37e7a9f4c54 100644 --- a/packages/create-svelte/cli/modifications/add_typescript.js +++ b/packages/create-svelte/cli/modifications/add_typescript.js @@ -1,11 +1,22 @@ import fs from 'fs'; import { bold, green } from 'kleur/colors'; import { join } from 'path'; -import { add_svelte_preprocess_to_config, update_component, update_package_json } from './utils'; +import { + add_svelte_preprocess_to_config, + copy_from_template_additions, + update_component, + update_package_json_dev_deps +} from './utils'; +/** + * Add TypeScript if user wants it. + * + * @param {string} cwd + * @param {boolean} yes + */ export default async function add_typescript(cwd, yes) { if (yes) { - update_package_json(cwd, { + update_package_json_dev_deps(cwd, { typescript: '^4.0.0', tslib: '^2.0.0', 'svelte-preprocess': '^4.0.0' @@ -31,11 +42,10 @@ export default async function add_typescript(cwd, yes) { } } +/** + * @param {string} cwd + */ function add_tsconfig(cwd) { fs.unlinkSync(join(cwd, 'jsconfig.json')); - copy_from_ts_template(cwd, 'tsconfig.json'); -} - -function copy_from_ts_template(cwd, ...path) { - fs.copyFileSync(join(__dirname, 'ts-template', ...path), join(cwd, ...path)); + copy_from_template_additions(cwd, ['tsconfig.json']); } diff --git a/packages/create-svelte/cli/modifications/utils.js b/packages/create-svelte/cli/modifications/utils.js index 68e21cc18474..aeca8620c431 100644 --- a/packages/create-svelte/cli/modifications/utils.js +++ b/packages/create-svelte/cli/modifications/utils.js @@ -1,27 +1,31 @@ import fs from 'fs'; -import path from 'path'; +import { join } from 'path'; /** * Updates package.json with given devDependencies + * + * @param {string} cwd + * @param {Record} newDevDeps */ -export function update_package_json(cwd, newDevDeps) { - const pkg_file = path.join(cwd, 'package.json'); - const pkg_json = fs.readFileSync(pkg_file, 'utf-8'); - const pkg = JSON.parse(pkg_json); - - pkg.devDependencies = sortObj({ - ...pkg.devDependencies, - ...newDevDeps +export function update_package_json_dev_deps(cwd, newDevDeps) { + update_package_json(cwd, (pkg) => { + pkg.devDependencies = sortObj({ + ...pkg.devDependencies, + ...newDevDeps + }); + return pkg; }); - - fs.writeFileSync(pkg_file, JSON.stringify(pkg, null, '\t')); } /** * Updates a Svelte component, doing all given replacements. + * + * @param {string} cwd + * @param {string} filepath + * @param {[string, string][]} replacements */ export function update_component(cwd, filepath, replacements) { - const file = path.join(cwd, filepath); + const file = join(cwd, filepath); let code = fs.readFileSync(file, 'utf-8'); replacements.forEach((replacement) => (code = code.replace(replacement[0], replacement[1]))); @@ -31,9 +35,11 @@ export function update_component(cwd, filepath, replacements) { /** * Adds `svelte-preprocess` to `svelte.config.cjs`, if there's no preprocessor already. + * + * @param {string} cwd */ export function add_svelte_preprocess_to_config(cwd) { - const file = path.join(cwd, 'svelte.config.cjs'); + const file = join(cwd, 'svelte.config.cjs'); let config = fs.readFileSync(file, 'utf-8'); if (config.includes('preprocess:')) { @@ -52,8 +58,52 @@ export function add_svelte_preprocess_to_config(cwd) { fs.writeFileSync(file, config); } +/** + * Copy over a specific file from the template-additions folder. + * + * @param {string} cwd + * @param {string[] | {from: string[], to: string[]}} path + */ +export function copy_from_template_additions(cwd, path) { + const from = Array.isArray(path) ? path : path.from; + const to = Array.isArray(path) ? path : path.to; + fs.copyFileSync(join(__dirname, 'template-additions', ...from), join(cwd, ...to)); +} + +/** + * Update or insert package.json scripts + * + * @param {string} cwd + * @param {Record} newScripts + */ +export function upsert_package_json_scripts(cwd, newScripts) { + update_package_json(cwd, (pkg) => { + pkg.scripts = sortObj({ + ...pkg.scripts, + ...newScripts + }); + return pkg; + }); +} + +/** + * + * @param {string} cwd + * @param {(pkg: any) => any} modify + */ +function update_package_json(cwd, modify) { + const pkg_file = join(cwd, 'package.json'); + const pkg_json = fs.readFileSync(pkg_file, 'utf-8'); + const pkg = modify(JSON.parse(pkg_json)); + fs.writeFileSync(pkg_file, JSON.stringify(pkg, null, '\t') + '\n', 'utf-8'); +} + +/** + * @param {Record} obj + * @returns Record + */ function sortObj(obj) { - return Object.keys(obj).reduce((newObj, key) => { + return Object.keys(obj).reduce((/** @type {Record} */ newObj, key) => { newObj[key] = obj[key]; return newObj; }, {}); diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json index 8dda3d82b1bb..e6fd56f82f2a 100644 --- a/packages/create-svelte/package.json +++ b/packages/create-svelte/package.json @@ -22,6 +22,6 @@ }, "files": [ "template", - "ts-template" + "template-additions" ] } diff --git a/packages/create-svelte/template-additions/.eslintrc.cjs b/packages/create-svelte/template-additions/.eslintrc.cjs new file mode 100644 index 000000000000..3db3e4438566 --- /dev/null +++ b/packages/create-svelte/template-additions/.eslintrc.cjs @@ -0,0 +1,10 @@ +module.exports = { + root: true, + extends: ['eslint:recommended'], + plugins: ['svelte3'], + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018 + } +}; diff --git a/packages/create-svelte/template-additions/.eslintrc.ts.cjs b/packages/create-svelte/template-additions/.eslintrc.ts.cjs new file mode 100644 index 000000000000..7fbac8a375c6 --- /dev/null +++ b/packages/create-svelte/template-additions/.eslintrc.ts.cjs @@ -0,0 +1,15 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + plugins: ['svelte3', '@typescript-eslint'], + ignorePatterns: ['.eslintrc.js'], + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + settings: { + 'svelte3/typescript': require('typescript') + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018 + } +}; diff --git a/packages/create-svelte/template-additions/.prettierignore b/packages/create-svelte/template-additions/.prettierignore new file mode 100644 index 000000000000..25e94f70353b --- /dev/null +++ b/packages/create-svelte/template-additions/.prettierignore @@ -0,0 +1,3 @@ +.svelte/** +static/** +node_modules/** diff --git a/packages/create-svelte/template-additions/.prettierrc b/packages/create-svelte/template-additions/.prettierrc new file mode 100644 index 000000000000..ff2677efde55 --- /dev/null +++ b/packages/create-svelte/template-additions/.prettierrc @@ -0,0 +1,6 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/packages/create-svelte/ts-template/tsconfig.json b/packages/create-svelte/template-additions/tsconfig.json similarity index 100% rename from packages/create-svelte/ts-template/tsconfig.json rename to packages/create-svelte/template-additions/tsconfig.json diff --git a/packages/create-svelte/tsconfig.json b/packages/create-svelte/tsconfig.json new file mode 100644 index 000000000000..a2f4a9f54e08 --- /dev/null +++ b/packages/create-svelte/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "noImplicitAny": true, + "target": "es2020", + "module": "es2020", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true + }, + "include": ["cli/**/*"] +}