@dvukovic/style-guide 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,50 +1,61 @@
1
1
  # @dvukovic/style-guide
2
2
 
3
- Personal style guide with ESLint, Prettier, and other code quality tools
3
+ Personal style guide with ESLint, Prettier, Stylelint, and CSpell configurations.
4
4
 
5
- ## Installation
5
+ ## Quick Start
6
6
 
7
7
  ```bash
8
- yarn add -D @dvukovic/style-guide eslint prettier cspell stylelint
8
+ npx @dvukovic/style-guide init
9
9
  ```
10
10
 
11
- ## ESLint Configuration
11
+ This interactive CLI will:
12
12
 
13
- ### Basic Usage
13
+ - Let you select which tools to configure (ESLint, Prettier, Stylelint, CSpell)
14
+ - Ask about your project setup (TypeScript, React/Next.js, testing frameworks)
15
+ - Install required dependencies
16
+ - Generate configuration files
14
17
 
15
- Create an `eslint.config.js` file in your project root:
18
+ ## Manual Installation
19
+
20
+ ```bash
21
+ yarn add -D @dvukovic/style-guide eslint prettier stylelint cspell knip
22
+ ```
23
+
24
+ ## ESLint
25
+
26
+ Create `eslint.config.js`:
16
27
 
17
28
  ```js
18
29
  import { customDefineConfig, core, typescript } from "@dvukovic/style-guide/eslint"
19
30
 
20
- export default customDefineConfig(["dist/**", "build/**"], [core(), typescript()])
31
+ export default customDefineConfig(["dist", "build"], [core(), typescript()])
21
32
  ```
22
33
 
23
34
  ### Available Configs
24
35
 
25
- Each config is a factory function that returns ESLint configuration:
26
-
27
36
  - `core()` - Essential rules for all JavaScript/TypeScript projects
28
- - `node()` - Node.js specific rules
29
- - `react()` - React framework rules
30
37
  - `typescript()` - TypeScript parser and rules
31
38
  - `typescriptStrict()` - Additional strict TypeScript rules
39
+ - `react()` - React framework rules
40
+ - `next()` - Next.js framework
41
+ - `node()` - Node.js specific rules
32
42
  - `jest()` - Jest testing framework
33
43
  - `vitest()` - Vitest testing framework
34
44
  - `playwright()` - Playwright e2e testing
35
45
  - `mobx()` - MobX state management
36
- - `next()` - Next.js framework
37
46
  - `storybook()` - Storybook
47
+ - `packageJson()` - package.json linting
48
+ - `packageJsonWorkspace()` - package.json linting for monorepos
38
49
 
39
50
  ### Customizing Configs
40
51
 
41
- Each factory function accepts a `config` parameter to extend or override settings:
52
+ Each factory function accepts a config parameter to extend or override settings:
42
53
 
43
54
  ```js
44
55
  import { customDefineConfig, core, typescript } from "@dvukovic/style-guide/eslint"
45
56
 
46
57
  export default customDefineConfig(
47
- [],
58
+ ["dist"],
48
59
  [
49
60
  core(),
50
61
  typescript({
@@ -56,105 +67,87 @@ export default customDefineConfig(
56
67
  )
57
68
  ```
58
69
 
59
- ### Scripts
70
+ ## Prettier
60
71
 
61
- Add these scripts to your `package.json`:
72
+ Create `prettier.config.ts`:
62
73
 
63
- ```json
64
- {
65
- "scripts": {
66
- "lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:spell",
67
- "lint:eslint": "eslint . --cache --concurrency=auto",
68
- "lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write && yarn lint:stylelint --fix && yarn lint:spell",
69
- "lint:prettier": "prettier --check --cache .",
70
- "lint:spell": "cspell --no-progress --no-summary --unique '**'",
71
- "lint:stylelint": "stylelint ./**/*.css --cache",
72
- "test": "vitest run"
73
- }
74
+ ```ts
75
+ import type { Config } from "prettier"
76
+
77
+ import { core } from "@dvukovic/style-guide/prettier"
78
+
79
+ const config: Config = {
80
+ ...core,
74
81
  }
82
+
83
+ export default config
75
84
  ```
76
85
 
77
- ### Complete Example
86
+ ## Stylelint
78
87
 
79
- A full-featured project (this is the actual config used by this package):
88
+ Create `stylelint.config.js`:
80
89
 
81
90
  ```js
82
- import {
83
- customDefineConfig,
84
- core,
85
- node,
86
- mobx,
87
- react,
88
- next,
89
- typescript,
90
- typescriptStrict,
91
- jest,
92
- vitest,
93
- playwright,
94
- } from "@dvukovic/style-guide/eslint"
91
+ import stylelintConfig from "@dvukovic/style-guide/stylelint"
95
92
 
96
- export default customDefineConfig(
97
- ["node_modules"],
98
- [
99
- core(),
100
- node(),
101
- mobx(),
102
- react(),
103
- next(),
104
- typescript(),
105
- typescriptStrict(),
106
- jest(),
107
- vitest(),
108
- playwright(),
109
- ],
110
- )
93
+ /** @type {import("stylelint").Config} */
94
+ const config = {
95
+ ...stylelintConfig,
96
+ }
97
+
98
+ export default config
111
99
  ```
112
100
 
113
- ## Prettier Configuration
101
+ ## CSpell
114
102
 
115
- ```js prettier.config.ts
116
- import type { Config } from "prettier"
103
+ Create `cspell.config.js`:
117
104
 
118
- import core from "@dvukovic/style-guide/src/prettier/configs/core.js"
105
+ ```js
106
+ import cspellConfig from "@dvukovic/style-guide/cspell"
119
107
 
120
- const config: Config = {
121
- ...core,
108
+ /** @type {import("cspell").FileSettings} */
109
+ const config = {
110
+ ...cspellConfig,
111
+ ignorePaths: [],
112
+ ignoreWords: [],
122
113
  }
123
114
 
124
115
  export default config
125
116
  ```
126
117
 
127
- ## Stylelint Configuration
118
+ ## Knip
128
119
 
129
- ```js stylelint.config.js
130
- /** @type {import("stylelint").Config} */
131
- module.exports = {
132
- extends: "@dvukovic/style-guide/src/stylelint/configs/core",
133
- allowEmptyInput: true,
120
+ Create `knip.config.ts`:
121
+
122
+ ```ts
123
+ import type { KnipConfig } from "knip"
124
+
125
+ import { core } from "@dvukovic/style-guide/knip"
126
+
127
+ const config: KnipConfig = {
128
+ ...core,
129
+ ignore: [],
130
+ ignoreDependencies: [...core.ignoreDependencies],
134
131
  }
132
+
133
+ export default config
135
134
  ```
136
135
 
137
- ## Cspell Configuration
136
+ ## Scripts
138
137
 
139
- ```cspell.config.js
140
- /** @type {import("cspell").FileSettings} */
141
- module.exports = {
142
- cache: {
143
- cacheLocation: "./node_modules/.cache/cspell",
144
- useCache: true,
145
- },
146
- caseSensitive: false,
147
- ignorePaths: [],
148
- dictionaries: ["shared"],
149
- dictionaryDefinitions: [
150
- {
151
- name: "shared",
152
- path: "./node_modules/@dvukovic/style-guide/src/cspell/base.txt",
153
- },
154
- ],
155
- useGitignore: true,
156
- ignoreWords: [
157
- ],
138
+ Add to your `package.json`:
139
+
140
+ ```json
141
+ {
142
+ "scripts": {
143
+ "lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:cspell && yarn lint:knip",
144
+ "lint:eslint": "eslint . --cache --concurrency=auto",
145
+ "lint:prettier": "prettier --check --cache .",
146
+ "lint:stylelint": "stylelint ./**/*.css --cache",
147
+ "lint:cspell": "cspell --no-progress --no-summary --unique '**'",
148
+ "lint:knip": "knip",
149
+ "lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write && yarn lint:stylelint --fix"
150
+ }
158
151
  }
159
152
  ```
160
153
 
@@ -0,0 +1 @@
1
+ export function generateCspellConfig(): string;
@@ -0,0 +1 @@
1
+ export function generateESLintConfig(options: any): string;
@@ -0,0 +1 @@
1
+ export function generateKnipConfig(): string;
@@ -0,0 +1 @@
1
+ export function generatePrettierConfig(): string;
@@ -0,0 +1,9 @@
1
+ export function generateScripts(tools: any, packageManager: any): {
2
+ "lint:eslint": string;
3
+ "lint:prettier": string;
4
+ "lint:stylelint": string;
5
+ "lint:cspell": string;
6
+ "lint:knip": string;
7
+ lint: string;
8
+ "lint:fix": string;
9
+ };
@@ -0,0 +1 @@
1
+ export function generateStylelintConfig(): string;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1 @@
1
+ export function runInit(): Promise<void>;
@@ -0,0 +1,13 @@
1
+ export function promptToolSelection(): Promise<string[]>;
2
+ export function promptESLintOptions(): Promise<{
3
+ extras: ("mobx" | "storybook")[];
4
+ framework: "none" | "react" | "next";
5
+ includeNode: boolean;
6
+ isMonorepo: boolean;
7
+ language: "typescript" | "javascript";
8
+ strictMode: boolean;
9
+ testing: ("jest" | "vitest" | "playwright")[];
10
+ }>;
11
+ export function promptOverwrite(filename: any): Promise<boolean>;
12
+ export function promptInstall(packageManager: any, packages: any): Promise<boolean>;
13
+ export function promptAddScripts(): Promise<boolean>;
@@ -0,0 +1,3 @@
1
+ export default config;
2
+ /** @type {import("cspell").FileSettings} */
3
+ declare const config: import("cspell").FileSettings;
@@ -0,0 +1 @@
1
+ export { default as core, default } from "./configs/core.js";
@@ -1,4 +1,3 @@
1
- export function isExpressionOrIdentifierOrLiteral(node: any): any;
2
1
  export function hasEmptyBody(program: any): boolean;
3
2
  export function hasExpressionBody(program: any): any;
4
3
  export function hasLabeledStatementBody(program: any): boolean;
@@ -0,0 +1,3 @@
1
+ export default config;
2
+ /** @type {import("knip").KnipConfig} */
3
+ declare const config: import("knip").KnipConfig;
@@ -0,0 +1 @@
1
+ export { default as core } from "./configs/core.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvukovic/style-guide",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "My own style guide",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,11 +12,19 @@
12
12
  },
13
13
  "type": "module",
14
14
  "exports": {
15
- "./cspell/dictionary.txt": "./src/cspell/dictionary.txt",
15
+ "./cspell": {
16
+ "types": "./dist/cspell/index.d.ts",
17
+ "default": "./src/cspell/index.js"
18
+ },
19
+ "./cspell/base.txt": "./src/cspell/base.txt",
16
20
  "./eslint": {
17
21
  "types": "./dist/eslint/index.d.ts",
18
22
  "default": "./src/eslint/index.js"
19
23
  },
24
+ "./knip": {
25
+ "types": "./dist/knip/index.d.ts",
26
+ "default": "./src/knip/index.js"
27
+ },
20
28
  "./prettier": {
21
29
  "types": "./dist/prettier/index.d.ts",
22
30
  "default": "./src/prettier/index.js"
@@ -26,31 +34,34 @@
26
34
  "default": "./src/stylelint/index.js"
27
35
  }
28
36
  },
37
+ "bin": "./src/cli/index.js",
29
38
  "files": [
30
39
  "dist",
40
+ "src/cli",
31
41
  "src/cspell",
32
42
  "src/eslint",
43
+ "src/knip",
33
44
  "src/package-json",
34
45
  "src/prettier",
35
46
  "src/stylelint"
36
47
  ],
37
48
  "scripts": {
38
49
  "build": "tsc",
39
- "lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:cspell",
50
+ "lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:cspell && yarn lint:knip",
40
51
  "lint:cspell": "cspell --no-progress --no-summary --unique '**'",
41
52
  "lint:eslint": "eslint . --cache --concurrency=auto",
42
- "lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write && yarn lint:stylelint --fix && yarn lint:cspell",
53
+ "lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write && yarn lint:stylelint --fix && yarn lint:cspell && yarn lint:knip",
54
+ "lint:knip": "knip",
43
55
  "lint:prettier": "prettier --check --cache .",
44
56
  "lint:stylelint": "stylelint ./**/*.css --cache",
45
57
  "release": "release-it",
46
58
  "test": "vitest run"
47
59
  },
48
60
  "dependencies": {
61
+ "@clack/prompts": "0.10.0",
49
62
  "@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
50
- "@eslint/compat": "2.0.0",
51
63
  "@eslint/config-helpers": "0.4.2",
52
64
  "@next/eslint-plugin-next": "16.1.0",
53
- "@prettier/plugin-xml": "3.4.2",
54
65
  "@rimac-technology/eslint-plugin": "1.5.0",
55
66
  "@stylistic/eslint-plugin": "5.6.1",
56
67
  "@typescript-eslint/parser": "8.50.0",
@@ -90,10 +101,10 @@
90
101
  },
91
102
  "devDependencies": {
92
103
  "@release-it/conventional-changelog": "10.0.4",
93
- "@types/eslint": "9.6.1",
94
104
  "@types/node": "24.10.4",
95
105
  "cspell": "9.4.0",
96
106
  "eslint": "9.39.2",
107
+ "knip": "5.82.1",
97
108
  "prettier": "3.7.4",
98
109
  "release-it": "19.1.0",
99
110
  "stylelint": "16.26.1",
@@ -103,6 +114,7 @@
103
114
  "peerDependencies": {
104
115
  "cspell": "9",
105
116
  "eslint": "^9",
117
+ "knip": "5",
106
118
  "prettier": "3",
107
119
  "stylelint": "16"
108
120
  },
@@ -113,6 +125,9 @@
113
125
  "eslint": {
114
126
  "optional": true
115
127
  },
128
+ "knip": {
129
+ "optional": true
130
+ },
116
131
  "prettier": {
117
132
  "optional": true
118
133
  },
@@ -0,0 +1,13 @@
1
+ export function generateCspellConfig() {
2
+ return `import cspellConfig from "@dvukovic/style-guide/cspell"
3
+
4
+ /** @type {import("cspell").FileSettings} */
5
+ const config = {
6
+ ...cspellConfig,
7
+ ignorePaths: [],
8
+ ignoreWords: [],
9
+ }
10
+
11
+ export default config
12
+ `
13
+ }
@@ -0,0 +1,80 @@
1
+ export function generateESLintConfig(options) {
2
+ const { extras, framework, includeNode, isMonorepo, language, strictMode, testing } = options
3
+
4
+ const imports = ["core", "customDefineConfig"]
5
+ const configs = ["core()"]
6
+ const ignores = ["dist", "node_modules"]
7
+
8
+ if (language === "typescript") {
9
+ imports.push("typescript")
10
+ configs.push("typescript()")
11
+
12
+ if (strictMode) {
13
+ imports.push("typescriptStrict")
14
+ configs.push("typescriptStrict()")
15
+ }
16
+ }
17
+
18
+ if (framework === "react") {
19
+ imports.push("react")
20
+ configs.push("react()")
21
+ ignores.push("build")
22
+ }
23
+
24
+ if (framework === "next") {
25
+ imports.push("next")
26
+ configs.push("next()")
27
+ ignores.push(".next", "out")
28
+ }
29
+
30
+ if (includeNode) {
31
+ imports.push("node")
32
+ configs.push("node()")
33
+ }
34
+
35
+ if (testing.includes("jest")) {
36
+ imports.push("jest")
37
+ configs.push("jest()")
38
+ }
39
+
40
+ if (testing.includes("vitest")) {
41
+ imports.push("vitest")
42
+ configs.push("vitest()")
43
+ }
44
+
45
+ if (testing.includes("playwright")) {
46
+ imports.push("playwright")
47
+ configs.push("playwright()")
48
+ }
49
+
50
+ if (extras.includes("mobx")) {
51
+ imports.push("mobx")
52
+ configs.push("mobx()")
53
+ }
54
+
55
+ if (extras.includes("storybook")) {
56
+ imports.push("storybook")
57
+ configs.push("storybook()")
58
+ }
59
+
60
+ if (isMonorepo) {
61
+ imports.push("packageJsonWorkspace")
62
+ configs.push("...packageJsonWorkspace()")
63
+ } else {
64
+ imports.push("packageJson")
65
+ configs.push("packageJson()")
66
+ }
67
+
68
+ const importStatement = `import {\n ${imports.join(",\n ")},\n} from "@dvukovic/style-guide/eslint"`
69
+
70
+ const ignoresArray = `["${ignores.join('", "')}"]`
71
+ const configsArray = `[\n ${configs.join(",\n ")},\n ]`
72
+
73
+ return `${importStatement}
74
+
75
+ export default customDefineConfig(
76
+ ${ignoresArray},
77
+ ${configsArray},
78
+ )
79
+ `
80
+ }
@@ -0,0 +1,14 @@
1
+ export function generateKnipConfig() {
2
+ return `import type { KnipConfig } from "knip"
3
+
4
+ import { core } from "@dvukovic/style-guide/knip"
5
+
6
+ const config: KnipConfig = {
7
+ ...core,
8
+ ignore: [],
9
+ ignoreDependencies: [...core.ignoreDependencies],
10
+ }
11
+
12
+ export default config
13
+ `
14
+ }
@@ -0,0 +1,12 @@
1
+ export function generatePrettierConfig() {
2
+ return `import type { Config } from "prettier"
3
+
4
+ import { core } from "@dvukovic/style-guide/prettier"
5
+
6
+ const config: Config = {
7
+ ...core,
8
+ }
9
+
10
+ export default config
11
+ `
12
+ }
@@ -0,0 +1,45 @@
1
+ export function generateScripts(tools, packageManager) {
2
+ const runner = packageManager === "npm" ? "npm run" : packageManager
3
+
4
+ const scripts = {}
5
+ const lintParts = []
6
+ const fixParts = []
7
+
8
+ if (tools.includes("eslint")) {
9
+ scripts["lint:eslint"] = "eslint . --cache --concurrency=auto"
10
+ lintParts.push(`${runner} lint:eslint`)
11
+ fixParts.push(`${runner} lint:eslint --fix`)
12
+ }
13
+
14
+ if (tools.includes("prettier")) {
15
+ scripts["lint:prettier"] = "prettier --check --cache ."
16
+ lintParts.push(`${runner} lint:prettier`)
17
+ fixParts.push(`${runner} lint:prettier --write`)
18
+ }
19
+
20
+ if (tools.includes("stylelint")) {
21
+ scripts["lint:stylelint"] = "stylelint ./**/*.css --cache"
22
+ lintParts.push(`${runner} lint:stylelint`)
23
+ fixParts.push(`${runner} lint:stylelint --fix`)
24
+ }
25
+
26
+ if (tools.includes("cspell")) {
27
+ scripts["lint:cspell"] = "cspell --no-progress --no-summary --unique '**'"
28
+ lintParts.push(`${runner} lint:cspell`)
29
+ }
30
+
31
+ if (tools.includes("knip")) {
32
+ scripts["lint:knip"] = "knip"
33
+ lintParts.push(`${runner} lint:knip`)
34
+ }
35
+
36
+ if (lintParts.length > 0) {
37
+ scripts.lint = lintParts.join(" && ")
38
+ }
39
+
40
+ if (fixParts.length > 0) {
41
+ scripts["lint:fix"] = fixParts.join(" && ")
42
+ }
43
+
44
+ return scripts
45
+ }
@@ -0,0 +1,11 @@
1
+ export function generateStylelintConfig() {
2
+ return `import stylelintConfig from "@dvukovic/style-guide/stylelint"
3
+
4
+ /** @type {import("stylelint").Config} */
5
+ const config = {
6
+ ...stylelintConfig,
7
+ }
8
+
9
+ export default config
10
+ `
11
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { intro, outro } from "@clack/prompts"
4
+
5
+ import { runInit } from "./init.js"
6
+
7
+ const command = process.argv[2]
8
+
9
+ if (command === "init") {
10
+ intro("@dvukovic/style-guide")
11
+ await runInit()
12
+ outro("Configuration complete!")
13
+ } else {
14
+ process.stderr.write("Usage: style-guide init\n")
15
+ process.stderr.write("\n")
16
+ process.stderr.write("Commands:\n")
17
+ process.stderr.write(" init Initialize configuration files\n")
18
+ // eslint-disable-next-line n/no-process-exit -- CLI entry point
19
+ process.exit(1)
20
+ }
@@ -0,0 +1,221 @@
1
+ import { execSync } from "node:child_process"
2
+ import { existsSync, readFileSync } from "node:fs"
3
+ import { writeFile } from "node:fs/promises"
4
+
5
+ import * as clack from "@clack/prompts"
6
+
7
+ import { generateCspellConfig } from "./generators/cspell.js"
8
+ import { generateESLintConfig } from "./generators/eslint.js"
9
+ import { generateKnipConfig } from "./generators/knip.js"
10
+ import { generatePrettierConfig } from "./generators/prettier.js"
11
+ import { generateScripts } from "./generators/scripts.js"
12
+ import { generateStylelintConfig } from "./generators/stylelint.js"
13
+ import {
14
+ promptAddScripts,
15
+ promptESLintOptions,
16
+ promptInstall,
17
+ promptOverwrite,
18
+ promptToolSelection,
19
+ } from "./prompts.js"
20
+
21
+ const TOOL_PACKAGES = {
22
+ cspell: "cspell",
23
+ eslint: "eslint",
24
+ knip: "knip",
25
+ prettier: "prettier",
26
+ stylelint: "stylelint",
27
+ }
28
+
29
+ function detectPackageManager() {
30
+ if (existsSync("pnpm-lock.yaml")) {
31
+ return "pnpm"
32
+ }
33
+
34
+ if (existsSync("yarn.lock")) {
35
+ return "yarn"
36
+ }
37
+
38
+ if (existsSync("bun.lockb")) {
39
+ return "bun"
40
+ }
41
+
42
+ return "npm"
43
+ }
44
+
45
+ function getInstalledPackages() {
46
+ try {
47
+ const packageJson = JSON.parse(readFileSync("package.json", "utf8"))
48
+
49
+ return {
50
+ ...packageJson.dependencies,
51
+ ...packageJson.devDependencies,
52
+ }
53
+ } catch {
54
+ return {}
55
+ }
56
+ }
57
+
58
+ function getMissingPackages(tools) {
59
+ const installed = getInstalledPackages()
60
+ const missing = []
61
+
62
+ if (!("@dvukovic/style-guide" in installed)) {
63
+ missing.push("@dvukovic/style-guide")
64
+ }
65
+
66
+ for (const tool of tools) {
67
+ const packageName = TOOL_PACKAGES[tool]
68
+
69
+ if (packageName && !(packageName in installed)) {
70
+ missing.push(packageName)
71
+ }
72
+ }
73
+
74
+ return missing
75
+ }
76
+
77
+ async function installPackages(tools, packageManager) {
78
+ const missingPackages = getMissingPackages(tools)
79
+
80
+ if (missingPackages.length === 0) {
81
+ return
82
+ }
83
+
84
+ const shouldInstall = await promptInstall(packageManager, missingPackages)
85
+
86
+ if (!shouldInstall) {
87
+ return
88
+ }
89
+
90
+ const packages = missingPackages.join(" ")
91
+ const installCmd =
92
+ packageManager === "npm"
93
+ ? `npm install -D ${packages}`
94
+ : `${packageManager} add -D ${packages}`
95
+
96
+ const spinner = clack.spinner()
97
+
98
+ spinner.start("Installing packages")
99
+
100
+ try {
101
+ // eslint-disable-next-line sonarjs/os-command -- User-confirmed install command
102
+ execSync(installCmd, { stdio: "pipe" })
103
+ spinner.stop("Packages installed")
104
+ } catch {
105
+ spinner.stop("Failed to install. Please run manually:")
106
+ clack.log.info(` ${installCmd}`)
107
+ }
108
+ }
109
+
110
+ async function writeConfigFile(filename, content) {
111
+ if (existsSync(filename)) {
112
+ const overwrite = await promptOverwrite(filename)
113
+
114
+ if (!overwrite) {
115
+ clack.log.info(`Skipped ${filename}`)
116
+
117
+ return false
118
+ }
119
+ }
120
+
121
+ await writeFile(filename, content)
122
+ clack.log.success(`Created ${filename}`)
123
+
124
+ return true
125
+ }
126
+
127
+ async function updatePackageJsonScripts(tools, packageManager) {
128
+ const scripts = generateScripts(tools, packageManager)
129
+
130
+ if (Object.keys(scripts).length === 0) {
131
+ return
132
+ }
133
+
134
+ try {
135
+ const packageJsonContent = readFileSync("package.json", "utf8")
136
+ const packageJson = JSON.parse(packageJsonContent)
137
+
138
+ packageJson.scripts = {
139
+ ...packageJson.scripts,
140
+ ...scripts,
141
+ }
142
+
143
+ const sortedKeys = Object.keys(packageJson.scripts).sort((left, right) => {
144
+ return left.localeCompare(right)
145
+ })
146
+ const sortedScripts = {}
147
+
148
+ for (const key of sortedKeys) {
149
+ sortedScripts[key] = packageJson.scripts[key]
150
+ }
151
+
152
+ packageJson.scripts = sortedScripts
153
+
154
+ await writeFile("package.json", `${JSON.stringify(packageJson, null, 4)}\n`)
155
+ clack.log.success("Added scripts to package.json")
156
+ } catch {
157
+ clack.log.error("Failed to update package.json scripts")
158
+ }
159
+ }
160
+
161
+ export async function runInit() {
162
+ const tools = await promptToolSelection()
163
+
164
+ let eslintOptions = null
165
+
166
+ if (tools.includes("eslint")) {
167
+ eslintOptions = await promptESLintOptions()
168
+ }
169
+
170
+ const shouldAddScripts = await promptAddScripts()
171
+ const packageManager = detectPackageManager()
172
+
173
+ await installPackages(tools, packageManager)
174
+
175
+ const spinner = clack.spinner()
176
+
177
+ spinner.start("Generating configuration files")
178
+
179
+ const results = []
180
+
181
+ if (tools.includes("eslint") && eslintOptions) {
182
+ const content = generateESLintConfig(eslintOptions)
183
+
184
+ results.push({ content, filename: "eslint.config.js" })
185
+ }
186
+
187
+ if (tools.includes("prettier")) {
188
+ const content = generatePrettierConfig()
189
+
190
+ results.push({ content, filename: "prettier.config.ts" })
191
+ }
192
+
193
+ if (tools.includes("stylelint")) {
194
+ const content = generateStylelintConfig()
195
+
196
+ results.push({ content, filename: "stylelint.config.js" })
197
+ }
198
+
199
+ if (tools.includes("cspell")) {
200
+ const content = generateCspellConfig()
201
+
202
+ results.push({ content, filename: "cspell.config.js" })
203
+ }
204
+
205
+ if (tools.includes("knip")) {
206
+ const content = generateKnipConfig()
207
+
208
+ results.push({ content, filename: "knip.config.ts" })
209
+ }
210
+
211
+ spinner.stop("Configuration files generated")
212
+
213
+ for (const { content, filename } of results) {
214
+ // eslint-disable-next-line no-await-in-loop -- Sequential writes for user feedback
215
+ await writeConfigFile(filename, content)
216
+ }
217
+
218
+ if (shouldAddScripts) {
219
+ await updatePackageJsonScripts(tools, packageManager)
220
+ }
221
+ }
@@ -0,0 +1,196 @@
1
+ import * as clack from "@clack/prompts"
2
+
3
+ class SetupCancelledError extends Error {
4
+ constructor() {
5
+ super("Setup cancelled.")
6
+ this.name = "SetupCancelledError"
7
+ }
8
+ }
9
+
10
+ export async function promptToolSelection() {
11
+ const tools = await clack.multiselect({
12
+ initialValues: ["eslint", "prettier"],
13
+ message: "Which tools would you like to configure?",
14
+ options: [
15
+ { label: "ESLint", value: "eslint" },
16
+ { label: "Prettier", value: "prettier" },
17
+ { label: "Stylelint", value: "stylelint" },
18
+ { label: "CSpell", value: "cspell" },
19
+ { label: "Knip", value: "knip" },
20
+ ],
21
+ required: true,
22
+ })
23
+
24
+ if (clack.isCancel(tools)) {
25
+ clack.cancel("Setup cancelled.")
26
+
27
+ throw new SetupCancelledError()
28
+ }
29
+
30
+ return tools
31
+ }
32
+
33
+ export async function promptESLintOptions() {
34
+ const language = await clack.select({
35
+ message: "Which language are you using?",
36
+ options: [
37
+ { label: "TypeScript", value: "typescript" },
38
+ { label: "JavaScript only", value: "javascript" },
39
+ ],
40
+ })
41
+
42
+ if (clack.isCancel(language)) {
43
+ clack.cancel("Setup cancelled.")
44
+
45
+ throw new SetupCancelledError()
46
+ }
47
+
48
+ let strictMode = false
49
+
50
+ if (language === "typescript") {
51
+ const strict = await clack.select({
52
+ message: "TypeScript strictness level?",
53
+ options: [
54
+ { label: "Standard", value: "standard" },
55
+ { label: "Strict (additional type-checked rules)", value: "strict" },
56
+ ],
57
+ })
58
+
59
+ if (clack.isCancel(strict)) {
60
+ clack.cancel("Setup cancelled.")
61
+
62
+ throw new SetupCancelledError()
63
+ }
64
+
65
+ strictMode = strict === "strict"
66
+ }
67
+
68
+ const framework = await clack.select({
69
+ message: "Which framework are you using?",
70
+ options: [
71
+ { label: "None (Node.js)", value: "none" },
72
+ { label: "React", value: "react" },
73
+ { label: "Next.js", value: "next" },
74
+ ],
75
+ })
76
+
77
+ if (clack.isCancel(framework)) {
78
+ clack.cancel("Setup cancelled.")
79
+
80
+ throw new SetupCancelledError()
81
+ }
82
+
83
+ let includeNode = framework === "none"
84
+
85
+ if (framework !== "none") {
86
+ const nodeBackend = await clack.confirm({
87
+ initialValue: false,
88
+ message: "Do you have a Node.js backend?",
89
+ })
90
+
91
+ if (clack.isCancel(nodeBackend)) {
92
+ clack.cancel("Setup cancelled.")
93
+
94
+ throw new SetupCancelledError()
95
+ }
96
+
97
+ includeNode = nodeBackend
98
+ }
99
+
100
+ const testing = await clack.multiselect({
101
+ message: "Which testing frameworks do you use?",
102
+ options: [
103
+ { label: "Jest", value: "jest" },
104
+ { label: "Vitest", value: "vitest" },
105
+ { label: "Playwright", value: "playwright" },
106
+ ],
107
+ required: false,
108
+ })
109
+
110
+ if (clack.isCancel(testing)) {
111
+ clack.cancel("Setup cancelled.")
112
+
113
+ throw new SetupCancelledError()
114
+ }
115
+
116
+ const extras = await clack.multiselect({
117
+ message: "Any additional configurations?",
118
+ options: [
119
+ { label: "MobX", value: "mobx" },
120
+ { label: "Storybook", value: "storybook" },
121
+ ],
122
+ required: false,
123
+ })
124
+
125
+ if (clack.isCancel(extras)) {
126
+ clack.cancel("Setup cancelled.")
127
+
128
+ throw new SetupCancelledError()
129
+ }
130
+
131
+ const monorepo = await clack.confirm({
132
+ initialValue: false,
133
+ message: "Is this a monorepo?",
134
+ })
135
+
136
+ if (clack.isCancel(monorepo)) {
137
+ clack.cancel("Setup cancelled.")
138
+
139
+ throw new SetupCancelledError()
140
+ }
141
+
142
+ const isMonorepo = monorepo
143
+
144
+ return {
145
+ extras,
146
+ framework,
147
+ includeNode,
148
+ isMonorepo,
149
+ language,
150
+ strictMode,
151
+ testing,
152
+ }
153
+ }
154
+
155
+ export async function promptOverwrite(filename) {
156
+ const overwrite = await clack.confirm({
157
+ initialValue: false,
158
+ message: `${filename} already exists. Overwrite?`,
159
+ })
160
+
161
+ if (clack.isCancel(overwrite)) {
162
+ clack.cancel("Setup cancelled.")
163
+
164
+ throw new SetupCancelledError()
165
+ }
166
+
167
+ return overwrite
168
+ }
169
+
170
+ export async function promptInstall(packageManager, packages) {
171
+ const packageList = packages.join(", ")
172
+
173
+ const install = await clack.confirm({
174
+ initialValue: true,
175
+ message: `Install ${packageList} using ${packageManager}?`,
176
+ })
177
+
178
+ if (clack.isCancel(install)) {
179
+ return false
180
+ }
181
+
182
+ return install
183
+ }
184
+
185
+ export async function promptAddScripts() {
186
+ const addScripts = await clack.confirm({
187
+ initialValue: true,
188
+ message: "Add lint scripts to package.json?",
189
+ })
190
+
191
+ if (clack.isCancel(addScripts)) {
192
+ return false
193
+ }
194
+
195
+ return addScripts
196
+ }
@@ -0,0 +1,18 @@
1
+ /** @type {import("cspell").FileSettings} */
2
+ const config = {
3
+ cache: {
4
+ cacheLocation: "./node_modules/.cache/cspell",
5
+ useCache: true,
6
+ },
7
+ caseSensitive: false,
8
+ dictionaries: ["shared"],
9
+ dictionaryDefinitions: [
10
+ {
11
+ name: "shared",
12
+ path: "./node_modules/@dvukovic/style-guide/cspell/base.txt",
13
+ },
14
+ ],
15
+ useGitignore: true,
16
+ }
17
+
18
+ export default config
@@ -0,0 +1 @@
1
+ export { default as core, default } from "./configs/core.js"
@@ -1,4 +1,4 @@
1
- export function isExpressionOrIdentifierOrLiteral(node) {
1
+ function isExpressionOrIdentifierOrLiteral(node) {
2
2
  if (node.type === "Identifier") {
3
3
  return true
4
4
  }
@@ -0,0 +1,14 @@
1
+ /** @type {import("knip").KnipConfig} */
2
+ const config = {
3
+ ignore: [],
4
+ ignoreDependencies: [
5
+ "cspell",
6
+ "eslint",
7
+ "prettier",
8
+ "stylelint",
9
+ "stylelint-no-unused-selectors",
10
+ "stylelint-order",
11
+ ],
12
+ }
13
+
14
+ export default config
@@ -0,0 +1,3 @@
1
+ import type { KnipConfig } from "knip"
2
+
3
+ export declare const core: KnipConfig
@@ -0,0 +1 @@
1
+ export { default as core } from "./configs/core.js"