@modyqyw/fabric
Version:
Opinionated shareable specifications for git-based JavaScript/TypeScript projects.
859 lines (848 loc) • 25.4 kB
JavaScript
#!/usr/bin/env node
import { readFileSync, accessSync, mkdirSync } from 'node:fs';
import { unlink, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { installPackage } from '@antfu/install-pkg';
import { checkbox, confirm, select } from '@inquirer/prompts';
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
import { Command } from 'commander';
import consola from 'consola';
import { defu } from 'defu';
import { globSync } from 'tinyglobby';
import { Listr } from 'listr2';
import { isPackageExists } from 'local-pkg';
import sortObjectKeys from 'sort-object-keys';
import { sortPackageJson } from 'sort-package-json';
import updateNotifier from 'update-notifier';
const name = "@modyqyw/fabric";
const version = "11.8.1";
const description = "Opinionated shareable specifications for git-based JavaScript/TypeScript projects.";
const keywords = [
"fabric",
"specification",
"config",
"front-end",
"frontend",
"prettier",
"biome",
"markdownlint",
"eslint",
"stylelint",
"commitlint",
"husky",
"simple-git-hooks",
"lint-staged",
"javascript",
"typescript",
"js",
"ts",
"react",
"rn",
"react-native",
"taro",
"vue",
"vue2",
"vue3",
"uniapp",
"uni-app",
"css",
"scss",
"git",
"naming"
];
const homepage = "https://github.com/ModyQyW/fabric#readme";
const bugs = {
url: "https://github.com/ModyQyW/fabric/issues"
};
const repository = {
type: "git",
url: "git+https://github.com/ModyQyW/fabric.git"
};
const funding = [
{
type: "individual",
url: "https://github.com/ModyQyW/sponsors"
},
{
type: "tidelift",
url: "https://tidelift.com/funding/github/npm/@modyqyw/fabric"
}
];
const license = "MIT";
const author = {
name: "ModyQyW",
email: "wurui.dev@gmail.com",
url: "https://modyqyw.top"
};
const type = "module";
const exports = {
".": {
"import": {
types: "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
require: {
types: "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./biome.json": "./dist/biome.json",
"./cli": {
"import": {
types: "./dist/cli.d.mts",
"default": "./dist/cli.mjs"
},
require: {
types: "./dist/cli.d.cts",
"default": "./dist/cli.cjs"
}
},
"./commitlint": {
"import": {
types: "./dist/commitlint.d.mts",
"default": "./dist/commitlint.mjs"
},
require: {
types: "./dist/commitlint.d.cts",
"default": "./dist/commitlint.cjs"
}
},
"./eslint": {
"import": {
types: "./dist/eslint.d.mts",
"default": "./dist/eslint.mjs"
},
require: {
types: "./dist/eslint.d.cts",
"default": "./dist/eslint.cjs"
}
},
"./lint-staged": {
"import": {
types: "./dist/lint-staged.d.mts",
"default": "./dist/lint-staged.mjs"
},
require: {
types: "./dist/lint-staged.d.cts",
"default": "./dist/lint-staged.cjs"
}
},
"./markdownlint.json": "./dist/markdownlint.json",
"./prettier": {
"import": {
types: "./dist/prettier.d.mts",
"default": "./dist/prettier.mjs"
},
require: {
types: "./dist/prettier.d.cts",
"default": "./dist/prettier.cjs"
}
},
"./simple-git-hooks": {
"import": {
types: "./dist/simple-git-hooks.d.mts",
"default": "./dist/simple-git-hooks.mjs"
},
require: {
types: "./dist/simple-git-hooks.d.cts",
"default": "./dist/simple-git-hooks.cjs"
}
},
"./stylelint": {
"import": {
types: "./dist/stylelint.d.mts",
"default": "./dist/stylelint.mjs"
},
require: {
types: "./dist/stylelint.d.cts",
"default": "./dist/stylelint.cjs"
}
}
};
const main = "./dist/index.cjs";
const module = "./dist/index.mjs";
const types = "./dist/index.d.ts";
const typesVersions = {
"*": {
"*": [
"./dist/*",
"./dist/cli.d.ts",
"./dist/commitlint.d.ts",
"./dist/eslint.d.ts",
"./dist/index.d.ts",
"./dist/lint-staged.d.ts",
"./dist/prettier.d.ts",
"./dist/simple-git-hooks.d.ts",
"./dist/stylelint.d.ts"
]
}
};
const bin = {
mf: "./dist/cli.mjs",
"modyqyw-fabric": "./dist/cli.mjs"
};
const files = [
"dist"
];
const scripts = {
build: "unbuild",
depupdate: "taze -fw",
dev: "unbuild --stub",
"docs:build": "vitepress build docs",
"docs:dev": "vitepress dev docs",
"docs:preview": "vitepress preview docs",
format: "prettier . \"!**/package-lock.json*\" \"!**/yarn.lock\" \"!**/pnpm-lock.yaml\" --ignore-unknown --write --cache --log-level=warn",
lint: "conc \"pnpm:lint:eslint\" \"pnpm:lint:markdownlint\" \"pnpm:lint:package\"",
"lint:eslint": "eslint . --fix --cache",
"lint:markdownlint": "markdownlint . --fix --ignore-path=.gitignore",
"lint:package": "pnpm run build && publint && attw --pack .",
prepare: "is-ci || simple-git-hooks",
prepublishOnly: "pnpm run build",
preversion: "git-branch-is main && conc \"pnpm:lint\" \"pnpm:type-check\"",
release: "commit-and-tag-version -a",
"type-check": "tsc --noEmit"
};
const dependencies = {
"@antfu/install-pkg": "^1.0.0",
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.26.5",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@commitlint/cli": "^19.6.1",
"@commitlint/config-angular": "^19.7.0",
"@commitlint/config-conventional": "^19.6.0",
"@commitlint/config-lerna-scopes": "^19.7.0",
"@commitlint/config-nx-scopes": "^19.5.0",
"@commitlint/config-pnpm-scopes": "^19.5.0",
"@commitlint/config-rush-scopes": "^19.5.0",
"@commitlint/types": "^19.5.0",
"@eslint-react/shared": "^1.23.2",
"@eslint/compat": "^1.2.5",
"@inquirer/prompts": "^7.2.3",
"@listr2/prompt-adapter-inquirer": "^2.0.18",
"@next/eslint-plugin-next": "^15.1.4",
"@nuxt/eslint-plugin": "^0.7.5",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
"@unocss/eslint-plugin": "^65.4.0",
commander: "^13.0.0",
consola: "^3.4.0",
defu: "^6.1.4",
"eslint-config-flat-gitignore": "^1.0.0",
"eslint-import-resolver-oxc": "^0.8.0",
"eslint-plugin-command": "^2.1.0",
"eslint-plugin-import-x": "^4.6.1",
"eslint-plugin-jsdoc": "^50.6.1",
"eslint-plugin-jsonc": "^2.18.2",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-n": "^17.15.1",
"eslint-plugin-no-barrel-files": "^1.2.0",
"eslint-plugin-nuxt": "^4.0.0",
"eslint-plugin-package-json": "^0.19.0",
"eslint-plugin-perfectionist": "^4.6.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react-dom": "^1.23.2",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-hooks-extra": "^1.23.2",
"eslint-plugin-react-naming-convention": "^1.23.2",
"eslint-plugin-react-native": "^5.0.0",
"eslint-plugin-react-perf": "^3.3.3",
"eslint-plugin-react-refresh": "^0.4.18",
"eslint-plugin-react-web-api": "^1.23.2",
"eslint-plugin-react-x": "^1.23.2",
"eslint-plugin-regexp": "^2.7.0",
"eslint-plugin-tailwindcss": "^3.17.5",
"eslint-plugin-toml": "^0.12.0",
"eslint-plugin-unicorn": "^56.0.1",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.32.0",
"eslint-plugin-vue-scoped-css": "^2.9.0",
"eslint-plugin-yml": "^1.16.0",
globals: "^15.14.0",
"jsonc-eslint-parser": "^2.4.0",
listr2: "^8.2.5",
"local-pkg": "^1.0.0",
multimatch: "^7.0.0",
postcss: "^8.5.0",
"postcss-html": "^1.8.0",
"postcss-scss": "^4.0.9",
"prettier-plugin-curly": "^0.3.1",
"prettier-plugin-jsdoc": "^1.3.2",
"sort-object-keys": "^1.1.3",
"sort-package-json": "^2.13.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-recommended-scss": "^14.1.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-high-performance-animation": "^1.10.0",
"stylelint-order": "^6.0.4",
"stylelint-plugin-defensive-css": "^1.0.4",
"stylelint-plugin-logical-css": "^1.2.1",
tinyglobby: "^0.2.10",
"toml-eslint-parser": "^0.10.0",
typescript: "^5.7.3",
"typescript-eslint": "^8.20.0",
"update-notifier": "^7.3.1",
"vue-eslint-parser": "^9.4.3",
"yaml-eslint-parser": "^1.2.3"
};
const devDependencies = {
"@arethetypeswrong/cli": "^0.17.3",
"@biomejs/biome": "^1.9.4",
"@inquirer/type": "^3.0.2",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^22.10.6",
"@types/sort-object-keys": "^1.1.3",
"@types/update-notifier": "^6.0.8",
"commit-and-tag-version": "^12.5.0",
concurrently: "^9.1.2",
"esbuild-register": "^3.6.0",
eslint: "^9.18.0",
"git-branch-is": "^4.0.0",
"is-ci": "^4.1.0",
"lint-staged": "^15.3.0",
"markdownlint-cli": "^0.43.0",
oxlint: "^0.15.6",
prettier: "^3.4.2",
publint: "^0.3.2",
react: "^19.0.0",
"simple-git-hooks": "^2.11.1",
stylelint: "^16.13.1",
taze: "^18.1.0",
unbuild: "^3.3.1",
vitepress: "^1.5.0",
vue: "^3.5.13"
};
const peerDependencies = {
"@biomejs/biome": "^1.9.0",
eslint: "^9.10.0",
postcss: "^8.0.0",
prettier: "^3.0.0",
react: "^18.0.0",
stylelint: "^16.0.0",
typescript: "^5.0.0",
vue: "^3.0.0"
};
const peerDependenciesMeta = {
"@biomejs/biome": {
optional: true
},
eslint: {
optional: true
},
postcss: {
optional: true
},
prettier: {
optional: true
},
react: {
optional: true
},
stylelint: {
optional: true
},
typescript: {
optional: true
},
vue: {
optional: true
}
};
const packageManager = "pnpm@9.15.4";
const engines = {
node: "^20.11.0 || >=21.2.0"
};
const publishConfig = {
access: "public",
registry: "https://registry.npmjs.org/"
};
const packageJson = {
name: name,
version: version,
description: description,
keywords: keywords,
homepage: homepage,
bugs: bugs,
repository: repository,
funding: funding,
license: license,
author: author,
type: type,
exports: exports,
main: main,
module: module,
types: types,
typesVersions: typesVersions,
bin: bin,
files: files,
scripts: scripts,
dependencies: dependencies,
devDependencies: devDependencies,
peerDependencies: peerDependencies,
peerDependenciesMeta: peerDependenciesMeta,
packageManager: packageManager,
engines: engines,
publishConfig: publishConfig
};
updateNotifier({ pkg: packageJson }).notify();
const program = new Command().name(packageJson.name).description(packageJson.description).version(packageJson.version).argument("[dir]", "dir to setup @modyqyw/fabric", ".").option("--prettier", "setup Prettier").option("--eslint", "setup ESLint").option("--oxlint", "setup oxlint").option("--stylelint", "setup Stylelint").option("--markdownlint", "setup markdownlint").option("--biome", "setup Biome").option("--tsc", "setup tsc").option("--commitlint", "setup commitlint").option("--lint-staged", "setup lint-staged").option("--simple-git-hooks", "setup simple-git-hooks").option("--editor-config", "setup .editorconfig").option("--vscode", "setup .vscode").option("-a, --all", "setup all functions").option("-c, --clean", "clean legacy setup").parse();
const args = program.args;
const opts = program.opts();
const dir = args[0] || ".";
const cwd = process.cwd();
function resolvePath(...paths) {
return resolve(cwd, dir, ...paths);
}
const packageJsonPath = resolvePath("package.json");
const packageJsonContent = readFileSync(packageJsonPath, "utf8");
const packageJsonObject = JSON.parse(packageJsonContent);
const packageJsonEof = "\n";
const packageJsonIndent = " ";
const functionOptions = [
{
description: "EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs.",
name: "EditorConfig",
path: ".editorconfig",
patterns: [".editorconfig"],
template: `root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
`,
value: "editorConfig",
vscodeRecommendations: ["EditorConfig.EditorConfig"]
},
{
description: "Prettier is a widely adopted code formatter with good support for JavaScript / TypeScript / JSX / TSX / CSS / SCSS / Vue, and it's my top pick for formatters.",
field: "prettier",
name: "Prettier",
packages: ["prettier"],
path: "prettier.config.mjs",
patterns: [".prettierrc*", "prettier.config.*"],
scripts: {
format: 'prettier . "!**/package-lock.json*" "!**/yarn.lock" "!**/pnpm-lock.yaml" --ignore-unknown --write --cache --log-level=warn'
},
template: `import { prettier } from '@modyqyw/fabric';
export default prettier();
`,
value: "prettier",
vscodeRecommendations: ["esbenp.prettier-vscode"],
vscodeSettings: {
// 指定默认代码格式化器为 Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 保存自动格式化
"editor.formatOnSave": true,
// 启动 Prettier
"prettier.enable": true
}
},
{
description: "ESLint is a widely adopted linter, mainly for script files.",
field: "eslintConfig",
name: "ESLint",
packages: ["eslint"],
path: "eslint.config.mjs",
patterns: [".eslintrc*", "eslint.config.*"],
scripts: {
"lint:eslint": "eslint . --fix --cache"
},
template: `import { eslint } from '@modyqyw/fabric';
export default eslint();
`,
value: "eslint",
vscodeRecommendations: ["dbaeumer.vscode-eslint"],
vscodeSettings: {
// 启用 ESLint 平面配置
"eslint.experimental.useFlatConfig": true,
// < 3.0.10
"eslint.useFlatConfig": true,
// >= 3.0.10
// ESLint 检查的语言
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"markdown",
"json",
"jsonc",
"yaml",
"toml"
],
// JavaScript、JSX、TypeScript、TypeScript JSX、Vue、markdown、JSON、JSONC、YAML、TOML 手动保存后 ESLint 自动修复
"[javascript][javascriptreact][typescript][typescriptreact][vue][markdown][json][jsonc][yaml][toml]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
}
},
{
description: "oxlint is an emerging linter that requires no configuration by default and is mainly used for script files.",
name: "oxlint",
packages: ["oxlint"],
scripts: {
"lint:oxlint": "oxlint --fix"
},
value: "oxlint"
},
{
description: "Stylelint is a widely adopted linter, mainly for style files.",
field: "stylelint",
name: "Stylelint",
packages: ["stylelint"],
path: "stylelint.config.mjs",
patterns: [".stylelintrc*", "stylelint.config.*"],
scripts: {
"lint:stylelint": 'stylelint "./**/*.{css,scss,vue}" --fix --cache --aei --ignore-path=.gitignore'
},
template: `import { stylelint } from '@modyqyw/fabric';
export default stylelint();
`,
value: "stylelint",
vscodeRecommendations: ["stylelint.vscode-stylelint"],
vscodeSettings: {
// 禁用内置的 CSS 检查
"css.validate": false,
// 禁用内置的 LESS 检查
"less.validate": false,
// 禁用内置的 SCSS 检查
"scss.validate": false,
// 启用 Stylelint 代码片段的语言
"stylelint.snippet": ["css", "scss", "vue"],
// Stylelint 检查的语言
"stylelint.validate": ["css", "scss", "vue"],
// CSS、SCSS、Vue 手动保存后 Stylelint 自动修复
"[css][scss][vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": "explicit"
}
}
}
},
{
description: "markdownlint is the linter for markdown file.",
field: "markdownlint",
name: "markdownlint",
packages: ["markdownlint-cli"],
path: ".markdownlint.json",
patterns: [".markdowlintrc*", ".markdownlint.*", "markdownlint.config.*"],
scripts: {
"lint:markdownlint": "markdownlint . --fix --ignore-path=.gitignore"
},
template: `{
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json",
"extends": "@modyqyw/fabric/markdownlint.json"
}
`,
value: "markdownlint",
vscodeRecommendations: ["DavidAnson.vscode-markdownlint"],
vscodeSettings: {
// markdown 手动保存后 markdownlint 自动修复
"[markdown]": {
"editor.codeActionsOnSave": {
"source.fixAll.markdownlint": "explicit"
}
}
}
},
{
description: "Biome is an all-in-one high-performance toolchain for web projects, aimed to provide functionalities to maintain them. It is a performant linter and a fast formatter.",
name: "Biome",
packages: ["@biomejs/biome"],
path: "biome.json",
patterns: ["biome.json"],
scripts: {
check: "@biomejs/biome check --write"
},
template: `{
"extends": "@modyqyw/fabric/biome.json"
}
`,
value: "biome",
vscodeRecommendations: ["biomejs.biome"],
vscodeSettings: {
// 手动保存后 Biome 自动修复
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
// 指定默认代码格式化器为 Biome
"editor.defaultFormatter": "biomejs.biome",
// 保存自动格式化
"editor.formatOnSave": true
}
},
{
// TODO: support monorepo
description: "tsc is the official type checker that comes with TypeScript.",
name: "tsc",
packages: [
"typescript",
isPackageExists("vue") ? "vue-tsc" : undefined
].filter(Boolean),
scripts: {
"type-check": packageJsonObject?.scripts?.["type-check"] ?? (isPackageExists("vue") ? "vue-tsc --noEmit" : "tsc --noEmit")
},
value: "tsc"
},
{
description: "commitlint is a widely adopted Git tool that lints commit messages and helps your team adhere to a commit convention.",
field: "commitlint",
name: "commitlint",
packages: ["@commitlint/cli"],
path: "commitlint.config.mjs",
patterns: [".commitlintrc*", "commitlint.config.*"],
template: `import { commitlint } from '@modyqyw/fabric';
export default commitlint();
`,
value: "commitlint"
},
{
description: "lint-staged is a widely adopted Git tool that executes commands against staged git files to prevent erroneous code from entering the repository.",
field: "lint-staged",
name: "lint-staged",
packages: ["lint-staged"],
path: "lint-staged.config.mjs",
patterns: [".lintstagedrc*", "lint-staged.config.*"],
template: `import { lintStaged } from '@modyqyw/fabric';
export default lintStaged();
`,
value: "lintStaged"
},
{
description: "simple-git-hooks is a widely adopted Git tool that helps you manage Git hooks easily.",
field: "simple-git-hooks",
name: "simple-git-hooks",
packages: ["simple-git-hooks", "is-ci", "esbuild-register"],
path: ".simple-git-hooks.cjs",
patterns: [".simple-git-hooks*", "simple-git-hooks.*"],
scripts: {
prepare: "is-ci || simple-git-hooks"
},
template: `require('esbuild-register');
const { simpleGitHooks } = require('@modyqyw/fabric');
module.exports = simpleGitHooks();
`,
value: "simpleGitHooks"
}
];
async function getConflictResolution(task) {
return await task.prompt(ListrInquirerPromptAdapter).run(select, {
choices: [
{
name: "Keep Biome and remove other functions",
value: "keep"
},
{
name: "Remove Biome and keep other functions",
value: "remove"
}
],
message: "Biome may conflict with other functions and is recommended to be used separately. What do you want to do?"
});
}
const tasks = new Listr([
{
rendererOptions: {
persistentOutput: true
},
task: async (ctx, task) => {
ctx.functions = [];
let shouldSkip = false;
if (opts.all) {
const resolution = await getConflictResolution(task);
ctx.functions.push(
...functionOptions.filter((o) => {
if (resolution === "keep") {
return o.name !== "Prettier" && o.name !== "ESLint" && o.name !== "oxlint" && o.name !== "Stylelint";
}
return o.name !== "Biome";
}).map((o) => o.name)
);
shouldSkip = true;
} else {
if (opts.biome && (opts.prettier || opts.eslint || opts.oxlint || opts.stylelint)) {
const resolution = await getConflictResolution(task);
if (resolution === "keep") {
opts.prettier = false;
opts.eslint = false;
opts.oxlint = false;
opts.stylelint = false;
} else {
opts.biome = false;
}
}
for (const o of functionOptions) {
if (opts[o.value]) {
ctx.functions.push(o.name);
shouldSkip = true;
}
}
}
if (shouldSkip) {
task.output = `${ctx.functions.join(", ")}`;
return task.skip();
}
const functions = await task.prompt(ListrInquirerPromptAdapter).run(checkbox, {
choices: functionOptions,
message: "What functions do you want for your project?"
});
if (functions.includes("biome") && (functions.includes("prettier") || functions.includes("eslint") || functions.includes("oxlint") || functions.includes("stylelint"))) {
const resolution = await getConflictResolution(task);
if (resolution === "keep") {
functions.splice(functions.indexOf("prettier"), 1);
functions.splice(functions.indexOf("eslint"), 1);
functions.splice(functions.indexOf("oxlint"), 1);
functions.splice(functions.indexOf("stylelint"), 1);
} else {
functions.splice(functions.indexOf("biome"), 1);
}
}
ctx.functions = functions.map(
(f) => functionOptions.find((o) => o.value === f).name
);
task.output = `${ctx.functions.join(", ")}`;
},
title: "Select functions"
},
{
rendererOptions: {
persistentOutput: true
},
task: async (ctx, task) => {
if (opts.vscode != null) {
ctx.vscode = opts.vscode;
task.output = ctx.vscode ? "Yes" : "No";
return task.skip();
}
ctx.vscode = await task.prompt(ListrInquirerPromptAdapter).run(confirm, {
default: true,
message: "Setup .vscode?"
});
task.output = ctx.vscode ? "Yes" : "No";
},
title: "Setup .vscode?"
},
{
task: async (ctx) => {
const filtered = functionOptions.filter(
(o) => ctx.functions.includes(o.name)
);
await Promise.all(
filtered.filter((f) => !!f.patterns).flatMap(
(f) => globSync(f.patterns, { dot: true }).map(
(f2) => unlink(resolvePath(f2))
)
)
);
if (packageJsonObject) {
for (const o of filtered) {
if (o.field) delete packageJsonObject[o.field];
}
}
},
title: "Clean legacy setup"
},
{
retry: 1,
task: async (ctx) => {
const promises = [];
const filtered = functionOptions.filter(
(o) => ctx.functions.includes(o.name)
);
const packages = [...new Set(filtered.flatMap((f) => f.packages ?? []))];
const notInstalled = packages.filter(
(p) => !(packageJsonObject?.devDependencies?.[p] || packageJsonObject?.dependencies?.[p])
);
if (notInstalled.length > 0) {
promises.push(
installPackage(notInstalled, {
cwd: resolve(cwd, dir),
dev: true,
silent: true
})
);
}
for (const f of filtered) {
if (f.path && f.template) {
promises.push(writeFile(resolvePath(f.path), f.template));
}
if (f.scripts) {
packageJsonObject.scripts = {
...packageJsonObject.scripts,
...f.scripts
};
}
}
if (ctx.vscode) {
try {
accessSync(resolve(".vscode"));
} catch {
mkdirSync(resolve(".vscode"));
}
const vscodeRecommendations = filtered.flatMap(
(f) => f.vscodeRecommendations ?? []
);
if (vscodeRecommendations.length > 0) {
promises.push(
writeFile(
resolvePath(".vscode", "extensions.json"),
JSON.stringify(
{
recommendations: vscodeRecommendations.toSorted(
(a, b) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())
)
},
null,
packageJsonIndent
) + packageJsonEof
)
);
}
const vscodeSettings = defu(
{},
...filtered.map((f) => f.vscodeSettings ?? {})
);
if (Object.keys(vscodeSettings).length > 0) {
promises.push(
writeFile(
resolvePath(".vscode", "settings.json"),
JSON.stringify(
sortObjectKeys(vscodeSettings),
null,
packageJsonIndent
) + packageJsonEof
)
);
}
}
promises.push(
writeFile(
packageJsonPath,
JSON.stringify(
sortPackageJson(packageJsonObject),
null,
packageJsonIndent
) + packageJsonEof
)
);
await Promise.all(promises);
},
title: "Setup"
}
]);
tasks.run().catch((error) => {
consola.error(error);
throw new Error(error);
});