@dvukovic/style-guide 0.5.2 → 0.6.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.
@@ -0,0 +1 @@
1
+ export function generateCspellConfig(): string;
@@ -0,0 +1 @@
1
+ export function generateESLintConfig(options: any): string;
@@ -0,0 +1 @@
1
+ export function generatePrettierConfig(): string;
@@ -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,12 @@
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>;
@@ -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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvukovic/style-guide",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "My own style guide",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,11 @@
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"
@@ -26,8 +30,10 @@
26
30
  "default": "./src/stylelint/index.js"
27
31
  }
28
32
  },
33
+ "bin": "./src/cli/index.js",
29
34
  "files": [
30
35
  "dist",
36
+ "src/cli",
31
37
  "src/cspell",
32
38
  "src/eslint",
33
39
  "src/package-json",
@@ -46,6 +52,7 @@
46
52
  "test": "vitest run"
47
53
  },
48
54
  "dependencies": {
55
+ "@clack/prompts": "0.10.0",
49
56
  "@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
50
57
  "@eslint/compat": "2.0.0",
51
58
  "@eslint/config-helpers": "0.4.2",
@@ -89,6 +96,7 @@
89
96
  "typescript-eslint": "8.50.0"
90
97
  },
91
98
  "devDependencies": {
99
+ "@dvukovic/style-guide": "0.5.2",
92
100
  "@release-it/conventional-changelog": "10.0.4",
93
101
  "@types/eslint": "9.6.1",
94
102
  "@types/node": "24.10.4",
@@ -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,12 @@
1
+ export function generatePrettierConfig() {
2
+ return `import type { Config } from "prettier"
3
+
4
+ import prettierConfig from "@dvukovic/style-guide/prettier"
5
+
6
+ const config: Config = {
7
+ ...prettierConfig,
8
+ }
9
+
10
+ export default config
11
+ `
12
+ }
@@ -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,171 @@
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 { generatePrettierConfig } from "./generators/prettier.js"
10
+ import { generateStylelintConfig } from "./generators/stylelint.js"
11
+ import {
12
+ promptESLintOptions,
13
+ promptInstall,
14
+ promptOverwrite,
15
+ promptToolSelection,
16
+ } from "./prompts.js"
17
+
18
+ const TOOL_PACKAGES = {
19
+ cspell: "cspell",
20
+ eslint: "eslint",
21
+ prettier: "prettier",
22
+ stylelint: "stylelint",
23
+ }
24
+
25
+ function detectPackageManager() {
26
+ if (existsSync("pnpm-lock.yaml")) {
27
+ return "pnpm"
28
+ }
29
+
30
+ if (existsSync("yarn.lock")) {
31
+ return "yarn"
32
+ }
33
+
34
+ if (existsSync("bun.lockb")) {
35
+ return "bun"
36
+ }
37
+
38
+ return "npm"
39
+ }
40
+
41
+ function getInstalledPackages() {
42
+ try {
43
+ const packageJson = JSON.parse(readFileSync("package.json", "utf8"))
44
+
45
+ return {
46
+ ...packageJson.dependencies,
47
+ ...packageJson.devDependencies,
48
+ }
49
+ } catch {
50
+ return {}
51
+ }
52
+ }
53
+
54
+ function getMissingPackages(tools) {
55
+ const installed = getInstalledPackages()
56
+ const missing = []
57
+
58
+ if (!("@dvukovic/style-guide" in installed)) {
59
+ missing.push("@dvukovic/style-guide")
60
+ }
61
+
62
+ for (const tool of tools) {
63
+ const packageName = TOOL_PACKAGES[tool]
64
+
65
+ if (packageName && !(packageName in installed)) {
66
+ missing.push(packageName)
67
+ }
68
+ }
69
+
70
+ return missing
71
+ }
72
+
73
+ async function installPackages(tools) {
74
+ const missingPackages = getMissingPackages(tools)
75
+
76
+ if (missingPackages.length === 0) {
77
+ return
78
+ }
79
+
80
+ const packageManager = detectPackageManager()
81
+ const shouldInstall = await promptInstall(packageManager, missingPackages)
82
+
83
+ if (!shouldInstall) {
84
+ return
85
+ }
86
+
87
+ const packages = missingPackages.join(" ")
88
+ const installCmd =
89
+ packageManager === "npm"
90
+ ? `npm install -D ${packages}`
91
+ : `${packageManager} add -D ${packages}`
92
+
93
+ const spinner = clack.spinner()
94
+
95
+ spinner.start("Installing packages")
96
+
97
+ try {
98
+ // eslint-disable-next-line sonarjs/os-command -- User-confirmed install command
99
+ execSync(installCmd, { stdio: "pipe" })
100
+ spinner.stop("Packages installed")
101
+ } catch {
102
+ spinner.stop("Failed to install. Please run manually:")
103
+ clack.log.info(` ${installCmd}`)
104
+ }
105
+ }
106
+
107
+ async function writeConfigFile(filename, content) {
108
+ if (existsSync(filename)) {
109
+ const overwrite = await promptOverwrite(filename)
110
+
111
+ if (!overwrite) {
112
+ clack.log.info(`Skipped ${filename}`)
113
+
114
+ return false
115
+ }
116
+ }
117
+
118
+ await writeFile(filename, content)
119
+ clack.log.success(`Created ${filename}`)
120
+
121
+ return true
122
+ }
123
+
124
+ export async function runInit() {
125
+ const tools = await promptToolSelection()
126
+
127
+ let eslintOptions = null
128
+
129
+ if (tools.includes("eslint")) {
130
+ eslintOptions = await promptESLintOptions()
131
+ }
132
+
133
+ await installPackages(tools)
134
+
135
+ const spinner = clack.spinner()
136
+
137
+ spinner.start("Generating configuration files")
138
+
139
+ const results = []
140
+
141
+ if (tools.includes("eslint") && eslintOptions) {
142
+ const content = generateESLintConfig(eslintOptions)
143
+
144
+ results.push({ content, filename: "eslint.config.js" })
145
+ }
146
+
147
+ if (tools.includes("prettier")) {
148
+ const content = generatePrettierConfig()
149
+
150
+ results.push({ content, filename: "prettier.config.ts" })
151
+ }
152
+
153
+ if (tools.includes("stylelint")) {
154
+ const content = generateStylelintConfig()
155
+
156
+ results.push({ content, filename: "stylelint.config.js" })
157
+ }
158
+
159
+ if (tools.includes("cspell")) {
160
+ const content = generateCspellConfig()
161
+
162
+ results.push({ content, filename: "cspell.config.js" })
163
+ }
164
+
165
+ spinner.stop("Configuration files generated")
166
+
167
+ for (const { content, filename } of results) {
168
+ // eslint-disable-next-line no-await-in-loop -- Sequential writes for user feedback
169
+ await writeConfigFile(filename, content)
170
+ }
171
+ }
@@ -0,0 +1,182 @@
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
+ ],
20
+ required: true,
21
+ })
22
+
23
+ if (clack.isCancel(tools)) {
24
+ clack.cancel("Setup cancelled.")
25
+
26
+ throw new SetupCancelledError()
27
+ }
28
+
29
+ return tools
30
+ }
31
+
32
+ export async function promptESLintOptions() {
33
+ const language = await clack.select({
34
+ message: "Which language are you using?",
35
+ options: [
36
+ { label: "TypeScript", value: "typescript" },
37
+ { label: "JavaScript only", value: "javascript" },
38
+ ],
39
+ })
40
+
41
+ if (clack.isCancel(language)) {
42
+ clack.cancel("Setup cancelled.")
43
+
44
+ throw new SetupCancelledError()
45
+ }
46
+
47
+ let strictMode = false
48
+
49
+ if (language === "typescript") {
50
+ const strict = await clack.select({
51
+ message: "TypeScript strictness level?",
52
+ options: [
53
+ { label: "Standard", value: "standard" },
54
+ { label: "Strict (additional type-checked rules)", value: "strict" },
55
+ ],
56
+ })
57
+
58
+ if (clack.isCancel(strict)) {
59
+ clack.cancel("Setup cancelled.")
60
+
61
+ throw new SetupCancelledError()
62
+ }
63
+
64
+ strictMode = strict === "strict"
65
+ }
66
+
67
+ const framework = await clack.select({
68
+ message: "Which framework are you using?",
69
+ options: [
70
+ { label: "None (Node.js)", value: "none" },
71
+ { label: "React", value: "react" },
72
+ { label: "Next.js", value: "next" },
73
+ ],
74
+ })
75
+
76
+ if (clack.isCancel(framework)) {
77
+ clack.cancel("Setup cancelled.")
78
+
79
+ throw new SetupCancelledError()
80
+ }
81
+
82
+ let includeNode = framework === "none"
83
+
84
+ if (framework !== "none") {
85
+ const nodeBackend = await clack.confirm({
86
+ initialValue: false,
87
+ message: "Do you have a Node.js backend?",
88
+ })
89
+
90
+ if (clack.isCancel(nodeBackend)) {
91
+ clack.cancel("Setup cancelled.")
92
+
93
+ throw new SetupCancelledError()
94
+ }
95
+
96
+ includeNode = nodeBackend
97
+ }
98
+
99
+ const testing = await clack.multiselect({
100
+ message: "Which testing frameworks do you use?",
101
+ options: [
102
+ { label: "Jest", value: "jest" },
103
+ { label: "Vitest", value: "vitest" },
104
+ { label: "Playwright", value: "playwright" },
105
+ ],
106
+ required: false,
107
+ })
108
+
109
+ if (clack.isCancel(testing)) {
110
+ clack.cancel("Setup cancelled.")
111
+
112
+ throw new SetupCancelledError()
113
+ }
114
+
115
+ const extras = await clack.multiselect({
116
+ message: "Any additional configurations?",
117
+ options: [
118
+ { label: "MobX", value: "mobx" },
119
+ { label: "Storybook", value: "storybook" },
120
+ ],
121
+ required: false,
122
+ })
123
+
124
+ if (clack.isCancel(extras)) {
125
+ clack.cancel("Setup cancelled.")
126
+
127
+ throw new SetupCancelledError()
128
+ }
129
+
130
+ const monorepo = await clack.confirm({
131
+ initialValue: false,
132
+ message: "Is this a monorepo?",
133
+ })
134
+
135
+ if (clack.isCancel(monorepo)) {
136
+ clack.cancel("Setup cancelled.")
137
+
138
+ throw new SetupCancelledError()
139
+ }
140
+
141
+ const isMonorepo = monorepo
142
+
143
+ return {
144
+ extras,
145
+ framework,
146
+ includeNode,
147
+ isMonorepo,
148
+ language,
149
+ strictMode,
150
+ testing,
151
+ }
152
+ }
153
+
154
+ export async function promptOverwrite(filename) {
155
+ const overwrite = await clack.confirm({
156
+ initialValue: false,
157
+ message: `${filename} already exists. Overwrite?`,
158
+ })
159
+
160
+ if (clack.isCancel(overwrite)) {
161
+ clack.cancel("Setup cancelled.")
162
+
163
+ throw new SetupCancelledError()
164
+ }
165
+
166
+ return overwrite
167
+ }
168
+
169
+ export async function promptInstall(packageManager, packages) {
170
+ const packageList = packages.join(", ")
171
+
172
+ const install = await clack.confirm({
173
+ initialValue: true,
174
+ message: `Install ${packageList} using ${packageManager}?`,
175
+ })
176
+
177
+ if (clack.isCancel(install)) {
178
+ return false
179
+ }
180
+
181
+ return install
182
+ }
@@ -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"