@kitschpatrol/shared-config 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Eric Mika
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@kitschpatrol/shared-config",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Linting and formatting for web projects.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git@github.com:kitschpatrol/shared-config.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/kitschpatrol/shared-config/issues",
12
+ "email": "eric@ericmika.com"
13
+ },
14
+ "author": {
15
+ "name": "Eric Mika",
16
+ "email": "eric@ericmika.com",
17
+ "url": "https://ericmika.com"
18
+ },
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=18.0.0",
22
+ "pnpm": ">=8.0.0"
23
+ },
24
+ "bin": {
25
+ "shared-config-init": "./scripts/init.js",
26
+ "shared-config-fix": "./scripts/fix.js",
27
+ "shared-config-lint": "./scripts/lint.js"
28
+ },
29
+ "files": [
30
+ "scripts"
31
+ ],
32
+ "dependencies": {
33
+ "@kitschpatrol/eslint-config": "1.0.0",
34
+ "@kitschpatrol/npm-config": "1.0.0",
35
+ "@kitschpatrol/vscode-config": "1.0.0",
36
+ "@kitschpatrol/prettier-config": "1.0.0",
37
+ "@kitschpatrol/stylelint-config": "1.0.0",
38
+ "@kitschpatrol/markdownlint-config": "1.0.0",
39
+ "@kitschpatrol/cspell-config": "1.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
package/readme.md ADDED
@@ -0,0 +1,95 @@
1
+ # shared-config
2
+
3
+ ## Overview
4
+
5
+ A collection of shared configurations for various linters and tools.
6
+
7
+ This package takes a maximalist approach, bundling plugins I need on a regular basis into a single dependency.
8
+
9
+ It takes care of dependencies and configurations for:
10
+
11
+ - CSpell
12
+ - ESLint (including Svelte, Astro, and Typescript support)
13
+ - Stylelint
14
+ - markdownlint
15
+ - NPM (`.npmrc` config)
16
+ - Prettier (including a bunch of extra plugins)
17
+ - VSCode (extension recommendations and extension settings)
18
+
19
+ It's only been tested with `pnpm`.
20
+
21
+ ## Setup
22
+
23
+ 1. Install the requisite `.npmrc`:
24
+
25
+ ```sh
26
+ pnpm dlx @kitschpatrol/npm-config
27
+ ```
28
+
29
+ 2. Install the package:
30
+
31
+ ```sh
32
+ pnpm add @kitschpatrol/shared-config
33
+ ```
34
+
35
+ 3. Add default config files for all the tools to your project root:
36
+
37
+ ```sh
38
+ pnpm shared-config-init
39
+ ```
40
+
41
+ 4. Add helper scripts to your `package.json`:
42
+
43
+ These work a bit like [npm-run-all](https://github.com/mysticatea/npm-run-all) to invoke all of the bundled tools.
44
+
45
+ ```json
46
+ ...
47
+ "scripts": {
48
+ "format": "shared-config-fix",
49
+ "lint": "shared-config-lint",
50
+ }
51
+ ...
52
+ ```
53
+
54
+ Note that Prettier formatting for Ruby requires some extra legwork to configure, see [`@kitschpatrol/prettier-config`](https://github.com/kitschpatrol/prettier-config) for more details.
55
+
56
+ ## Usage
57
+
58
+ Various VSCode plugins should "just work".
59
+
60
+ To lint your entire project:
61
+
62
+ ```sh
63
+ pnpm run lint
64
+ ```
65
+
66
+ To run all of the tools in a _potentially destructive_ "fix" capacity:
67
+
68
+ ```sh
69
+ pnpm run format
70
+ ```
71
+
72
+ ## Todo
73
+
74
+ - [ ] `.tsconfig`?
75
+ - [ ] Interactive override / merge prompt
76
+ - [ ] DRY script invocation / initial config copying?
77
+
78
+ ## Issues
79
+
80
+ - CSpell, markdownlint, ESLint, and Prettier all need to be hoisted via `public-hoist-pattern` to be accessible in `pnpm exec` scripts and to VSCode plugins.
81
+
82
+ - Even basic file-only packages like `vscode-config` and `npm-config` seem to need to be hoisted via for their bin scripts to be accessible via `pnpm exec`
83
+
84
+ ## Dev Notes
85
+
86
+ - Note that `prettier` and `eslint` packages are [hoisted by default](https://pnpm.io/npmrc#public-hoist-pattern) in `pnpm`
87
+
88
+ - For local development via `pnpm`, use `file:` dependency protocol instead of `link:`
89
+
90
+ ## Reference projects
91
+
92
+ - [1stG/configs](https://github.com/1stG/configs)
93
+ - [sheriff](https://www.eslint-config-sheriff.dev)
94
+ - [xo](https://github.com/xojs/xo)
95
+ - [standard](https://standardjs.com)
package/scripts/fix.js ADDED
@@ -0,0 +1,5 @@
1
+ // Uses all tools to fix the project
2
+
3
+ import { runAllCommands } from './run-helper.js';
4
+
5
+ runAllCommands(true);
@@ -0,0 +1,39 @@
1
+ // Copies configuration files for all tools to the root of the project
2
+ // It does not overwrite existing files
3
+
4
+ import { exec as execCallback } from 'node:child_process';
5
+ import console from 'node:console';
6
+ import { promisify } from 'node:util';
7
+
8
+ const exec = promisify(execCallback);
9
+
10
+ async function execCommand(command) {
11
+ try {
12
+ const { stderr, stdout } = await exec(command);
13
+ if (stderr) {
14
+ console.error(`stderr: ${stderr}`);
15
+ }
16
+ console.log(stdout);
17
+ return stdout;
18
+ } catch (error) {
19
+ console.error(`Error: ${error}`);
20
+ throw error; // Rethrow the error for further handling
21
+ }
22
+ }
23
+
24
+ async function runCommands() {
25
+ try {
26
+ await execCommand('pnpm exec eslint-config-init');
27
+ await execCommand('pnpm exec prettier-config-init');
28
+ await execCommand('pnpm exec stylelint-config-init');
29
+ await execCommand('pnpm exec npm-config-init');
30
+ await execCommand('pnpm exec markdownlint-config-init');
31
+ await execCommand('pnpm exec cspell-config-init');
32
+ await execCommand('pnpm exec vscode-config-init');
33
+ console.log('All configurations have been initialized successfully.');
34
+ } catch (error) {
35
+ console.error('An error occurred while initializing configurations:', error);
36
+ }
37
+ }
38
+
39
+ await runCommands();
@@ -0,0 +1,5 @@
1
+ // Uses all tools to check / lint the project
2
+
3
+ import { runAllCommands } from './run-helper.js';
4
+
5
+ runAllCommands(false);
@@ -0,0 +1,128 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ // Helper to run multiple commands
3
+ import { spawn } from 'node:child_process';
4
+ import console from 'node:console';
5
+ import { once } from 'node:events';
6
+ import process from 'node:process';
7
+
8
+ const commands = stylePrefixes([
9
+ {
10
+ command: 'eslint .',
11
+ fixArgument: '--fix',
12
+ lintArgument: '',
13
+ prefix: 'ESLint',
14
+ },
15
+ // Other commands...
16
+ {
17
+ command: 'stylelint --allow-empty-input "**/*.{css,scss,sass,svelte,html,astro}"',
18
+ fixArgument: '--fix',
19
+ lintArgument: '',
20
+ prefix: 'Stylelint',
21
+ },
22
+ {
23
+ command:
24
+ 'prettier --plugin=@prettier/plugin-php --plugin=@prettier/plugin-ruby --plugin=@prettier/plugin-xml --plugin=prettier-plugin-astro --plugin=prettier-plugin-pkg --plugin=prettier-plugin-sh --plugin=prettier-plugin-sql --plugin=prettier-plugin-svelte --plugin=prettier-plugin-tailwindcss --plugin=prettier-plugin-toml .',
25
+ fixArgument: '--write',
26
+ lintArgument: '--check',
27
+ prefix: 'Prettier',
28
+ },
29
+ {
30
+ command: 'markdownlint "**/*.{md,mdx}" --ignore-path .gitignore',
31
+ fixArgument: '--fix',
32
+ lintArgument: '',
33
+ prefix: 'markdownlint',
34
+ },
35
+ {
36
+ command: 'cspell --quiet .',
37
+ fixArgument: '',
38
+ lintArgument: '',
39
+ prefix: 'CSpell',
40
+ },
41
+ ]);
42
+
43
+ function stylePrefixes(commands) {
44
+ // ANSI color codes (excluding red and ANSI 256-colors)
45
+ const colors = [
46
+ '\u001B[32m', // Green
47
+ '\u001B[34m', // Blue
48
+ '\u001B[33m', // Yellow
49
+ '\u001B[35m', // Magenta
50
+ '\u001B[36m', // Cyan
51
+ '\u001B[94m', // Bright Blue
52
+ '\u001B[92m', // Bright Green
53
+ '\u001B[93m', // Bright Yellow
54
+ '\u001B[95m', // Bright Magenta
55
+ ];
56
+ const boldCode = '\u001B[1m';
57
+ const resetCode = '\u001B[0m';
58
+
59
+ // Find the length of the longest prefix
60
+ const longestPrefixLength = commands.reduce((max, cmd) => Math.max(max, cmd.prefix.length), 0);
61
+
62
+ // Pad each prefix with spaces to match the length of the longest prefix
63
+ return commands.map((cmd, index) => {
64
+ const colorCode = boldCode + colors[index % colors.length];
65
+ const paddedLength = longestPrefixLength;
66
+ const paddingLength = paddedLength - cmd.prefix.length;
67
+ const paddedPrefix = colorCode + '[' + cmd.prefix + ' '.repeat(paddingLength) + ']' + resetCode;
68
+ return { ...cmd, prefix: paddedPrefix };
69
+ });
70
+ }
71
+
72
+ async function runCommand({ command, fixArgument, lintArgument, prefix }, fix = false) {
73
+ const fullCommand = `${command} ${fix ? fixArgument : lintArgument}`;
74
+ console.log(`${prefix} Running command: ${fullCommand}`);
75
+
76
+ const subprocess = spawn(fullCommand, {
77
+ env: { ...process.env, FORCE_COLOR: true },
78
+ shell: true,
79
+ });
80
+
81
+ subprocess.stdout.on('data', (data) => {
82
+ const lines = data.toString().split(/\r?\n/);
83
+ const prefixedLines = lines
84
+ .filter((line) => line.trim() !== '') // Filter out empty lines
85
+ .map((line) => `${prefix} ${line}`)
86
+ .join('\n');
87
+ if (prefixedLines) {
88
+ process.stdout.write(prefixedLines + '\n'); // Add a newline at the end if there's output
89
+ }
90
+ });
91
+
92
+ subprocess.stderr.on('data', (data) => {
93
+ const lines = data.toString().split(/\r?\n/);
94
+ const prefixedLines = lines
95
+ .filter((line) => line.trim() !== '') // Filter out empty lines
96
+ .map((line) => `${prefix} ${line}`)
97
+ .join('\n');
98
+ if (prefixedLines) {
99
+ console.error(prefixedLines + '\n'); // Add a newline at the end if there's output
100
+ }
101
+ });
102
+
103
+ // Wait for the 'close' event
104
+ const [code] = await once(subprocess, 'close');
105
+ if (code !== 0) {
106
+ throw new Error(`${prefix} Command failed with exit code ${code}`);
107
+ }
108
+ }
109
+
110
+ export async function runAllCommands(fix = false) {
111
+ let errors = [];
112
+ for (const cmd of commands) {
113
+ try {
114
+ await runCommand(cmd, fix);
115
+ } catch (error) {
116
+ errors.push(error.message);
117
+ }
118
+ }
119
+
120
+ if (errors.length > 0) {
121
+ console.error(`${errors.length} task(s) failed:`);
122
+ for (const error of errors) console.error(error);
123
+ process.exit(1);
124
+ } else {
125
+ console.log('All tasks completed successfully.');
126
+ process.exit(0);
127
+ }
128
+ }