@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 +21 -0
- package/package.json +44 -0
- package/readme.md +95 -0
- package/scripts/fix.js +5 -0
- package/scripts/init.js +39 -0
- package/scripts/lint.js +5 -0
- package/scripts/run-helper.js +128 -0
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
package/scripts/init.js
ADDED
|
@@ -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();
|
package/scripts/lint.js
ADDED
|
@@ -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
|
+
}
|